A JavaScript library to control agents enclosed in CLI commands like Anthropic Claude Code CLI, OpenAI Codex, OpenCode, Qwen Code, Gemini CLI, and @link-assistant/agent.
Built on the success of hive-mind, agent-commander provides a flexible JavaScript interface and CLI tools for managing agent processes with various isolation levels.
- Universal Runtime Support: Works with Node.js, Bun, and Deno
- Multiple CLI Agents:
claude- Anthropic Claude Code CLIcodex- OpenAI Codex CLIopencode- OpenCode CLIqwen- Qwen Code CLI (Alibaba's AI coding agent)gemini- Gemini CLI (Google's AI coding agent)agent- @link-assistant/agent (unrestricted OpenCode fork)
- Multiple Isolation Modes:
- No isolation (direct execution)
- Screen sessions (detached terminal sessions)
- Docker containers (full containerization)
- JSON Streaming Support: NDJSON input/output for real-time message processing
- Model Mapping: Automatic mapping of model aliases to full model IDs
- CLI & JavaScript Interface: Use as a library or command-line tool
- Graceful Shutdown: CTRL+C handling with proper cleanup
- Dry Run Mode: Preview commands before execution
- Attached/Detached Modes: Monitor output in real-time or run in background
- Read-only Planning Mode: Enforce native no-shell/no-write restrictions for supported tools
npm install -g agent-commandernpm install agent-commanderimport { agent } from 'https://raw.githubusercontent.com/link-assistant/agent-commander/main/src/index.mjs';bun add agent-commander| Tool | Description | JSON Output | JSON Input | Read-only Mode | Model Aliases |
|---|---|---|---|---|---|
claude |
Anthropic Claude Code CLI | ✅ (stream-json) | ✅ (stream-json) | ✅ --permission-mode plan |
sonnet, opus, haiku |
codex |
OpenAI Codex CLI | ✅ | ❌ | ✅ --sandbox read-only |
gpt-5.5 (default), gpt-5.4, gpt5, o3, gpt4o |
opencode |
OpenCode CLI | ✅ | ❌ | ✅ OPENCODE_PERMISSION deny rules |
grok, gemini, sonnet |
qwen |
Qwen Code CLI | ✅ (stream-json) | ✅ (stream-json) | ✅ --approval-mode plan |
qwen3-coder, coder, gpt-4o |
gemini |
Gemini CLI | ✅ (stream-json) | ❌ | ✅ --approval-mode plan |
flash, pro, lite |
agent |
@link-assistant/agent | ✅ | ✅ | ❌ not enforceable | nemotron-3-super-free (default), grok, sonnet, haiku |
The Claude Code CLI supports additional features:
- Stream JSON format: Uses
--output-format stream-jsonand--input-format stream-jsonfor real-time streaming - Permission bypass: Automatically includes
--dangerously-skip-permissionsfor unrestricted operation - Fallback model: Use
--fallback-modelfor automatic fallback when the primary model is overloaded - Session management: Full support for
--session-id,--fork-session, and--resume - System prompt appending: Use
--append-system-promptto add to the default system prompt - Verbose mode: Enable with
--verbosefor detailed output - User message replay: Use
--replay-user-messagesfor streaming acknowledgment
The Qwen Code CLI supports additional features:
- Stream JSON format: Uses
--output-format stream-jsonfor real-time NDJSON streaming - Auto-approval mode: Use
--yoloflag for automatic action approval (enabled by default) - Session management: Support for
--resume <sessionId>and--continuefor most recent session - Context options: Use
--all-filesto include all files,--include-directoriesfor specific dirs - Partial messages: Use
--include-partial-messageswith stream-json for real-time UI updates - Model flexibility: Supports Qwen3-Coder models plus OpenAI-compatible models via API
The Gemini CLI supports additional features:
- Stream JSON format: Uses
--output-format stream-jsonfor real-time NDJSON streaming - Auto-approval mode: Use
--yoloflag for automatic action approval (enabled by default) - Sandbox mode: Use
--sandboxflag for secure tool execution in isolated environment - Checkpointing: Use
--checkpointingto save project snapshots before file modifications - Debug output: Use
-dflag for detailed debug output - Model selection: Use
-mflag to select model (e.g.,gemini-2.5-flash,gemini-2.5-pro) - Interactive mode: Use
-iflag with prompt to start interactive session
The @link-assistant/agent supports additional features:
- JSON Input/Output: Accepts JSON via stdin, outputs JSON event streams (OpenCode-compatible)
- Unrestricted access: No sandbox, no permissions system - full autonomous execution
- 13 built-in tools: Including websearch, codesearch, batch - all enabled by default
- MCP support: Model Context Protocol for extending functionality with MCP servers
- OpenCode compatibility: 100% compatible with OpenCode's JSON event streaming format
Use --read-only or --plan-only when the selected agent should inspect and plan without shell execution or file mutation. The command builder maps the request to the safest native restriction available for each supported tool:
claude:--permission-mode plancodex:--ask-for-approval never exec --sandbox read-onlyopencode:OPENCODE_PERMISSION='{"edit":"deny","bash":"deny","task":"deny"}'qwen:--approval-mode plangemini:--approval-mode plan
If a tool cannot enforce the requested restrictions, start-agent fails before starting the agent. For example, --tool agent --read-only is rejected because @link-assistant/agent has no native permission system.
Start an agent with specified configuration:
start-agent --tool claude --working-directory "/tmp/dir" --prompt "Solve the issue"--tool <name>- CLI tool to use (e.g., 'claude', 'codex', 'opencode', 'qwen', 'gemini', 'agent') [required]--working-directory <path>- Working directory for the agent [required]--prompt <text>- Prompt for the agent--system-prompt <text>- System prompt for the agent--append-system-prompt <text>- Append to the default system prompt (Claude only)--model <name>- Model to use (e.g., 'sonnet', 'opus', 'grok')--fallback-model <name>- Fallback model when default is overloaded (Claude only)--verbose- Enable verbose mode (Claude only)--read-only- Enforce native read-only/planning mode for supported tools--plan-only- Alias for--read-only--resume <sessionId>- Resume a previous session by ID--session-id <uuid>- Use a specific session ID (Claude only, must be valid UUID)--fork-session- Create new session ID when resuming (Claude only)--replay-user-messages- Re-emit user messages on stdout (Claude only, streaming mode)--isolation <mode>- Isolation mode: none, screen, docker (default: none)--screen-name <name>- Screen session name (required for screen isolation)--container-name <name>- Container name (required for docker isolation)--detached- Run in detached mode--dry-run- Show command without executing--help, -h- Show help message
Basic usage with Claude
start-agent --tool claude --working-directory "/tmp/dir" --prompt "Hello" --model sonnetUsing Codex
start-agent --tool codex --working-directory "/tmp/dir" --prompt "Fix the bug" --model gpt5Using @link-assistant/agent with Grok
start-agent --tool agent --working-directory "/tmp/dir" --prompt "Analyze code" --model grokUsing Qwen Code
start-agent --tool qwen --working-directory "/tmp/dir" --prompt "Review this code" --model qwen3-coderUsing Gemini
start-agent --tool gemini --working-directory "/tmp/dir" --prompt "Explain this code" --model flashWith model fallback (Claude)
start-agent --tool claude --working-directory "/tmp/dir" \
--prompt "Complex task" --model opus --fallback-model sonnetResume a session with fork (Claude)
start-agent --tool claude --working-directory "/tmp/dir" \
--resume abc123 --fork-sessionRead-only planning mode
start-agent --tool claude --working-directory "/tmp/dir" \
--prompt "Return a JSON implementation plan" --read-onlyRead-only planning mode with screen isolation
start-agent --tool codex --working-directory "/tmp/dir" \
--prompt "Inspect and plan only" --read-only \
--isolation screen --screen-name planning-agent --detachedWith screen isolation (detached)
start-agent --tool claude --working-directory "/tmp/dir" \
--isolation screen --screen-name my-agent --detachedWith docker isolation (attached)
start-agent --tool claude --working-directory "/tmp/dir" \
--isolation docker --container-name my-agentDry run
start-agent --tool claude --working-directory "/tmp/dir" --dry-runStop a detached agent:
stop-agent --isolation screen --screen-name my-agent--isolation <mode>- Isolation mode: screen, docker [required]--screen-name <name>- Screen session name (required for screen isolation)--container-name <name>- Container name (required for docker isolation)--dry-run- Show command without executing--help, -h- Show help message
Stop screen session
stop-agent --isolation screen --screen-name my-agentStop docker container
stop-agent --isolation docker --container-name my-agentimport { agent } from 'agent-commander';
// Create an agent controller
const myAgent = agent({
tool: 'claude',
workingDirectory: '/tmp/project',
prompt: 'Analyze this code',
systemPrompt: 'You are a helpful assistant',
model: 'sonnet', // Optional: use model alias
});
// Start the agent (non-blocking, returns immediately)
await myAgent.start();
// Do other work while agent runs...
// Stop the agent and collect output
const result = await myAgent.stop();
console.log('Exit code:', result.exitCode);
console.log('Plain output:', result.output.plain);
console.log('Parsed output:', result.output.parsed); // JSON messages if supported
console.log('Session ID:', result.sessionId); // For resuming later
console.log('Usage:', result.usage); // Token usage statisticsimport { agent } from 'agent-commander';
// Using Codex
const codexAgent = agent({
tool: 'codex',
workingDirectory: '/tmp/project',
prompt: 'Fix this bug',
model: 'gpt5',
});
// Using OpenCode
const opencodeAgent = agent({
tool: 'opencode',
workingDirectory: '/tmp/project',
prompt: 'Refactor this code',
model: 'grok',
});
// Using @link-assistant/agent
const linkAgent = agent({
tool: 'agent',
workingDirectory: '/tmp/project',
prompt: 'Implement feature',
model: 'grok',
});
// Using Qwen Code
const qwenAgent = agent({
tool: 'qwen',
workingDirectory: '/tmp/project',
prompt: 'Review this code',
model: 'qwen3-coder',
});
// Using Gemini CLI
const geminiAgent = agent({
tool: 'gemini',
workingDirectory: '/tmp/project',
prompt: 'Explain this code',
model: 'flash',
});import { agent } from 'agent-commander';
const planner = agent({
tool: 'claude',
workingDirectory: '/tmp/project',
prompt: 'Inspect the codebase and return a task split plan',
readOnly: true,
});
await planner.start({ dryRun: true });import { agent } from 'agent-commander';
const myAgent = agent({
tool: 'claude',
workingDirectory: '/tmp/project',
prompt: 'Process this',
json: true, // Enable JSON output mode
});
// Stream messages as they arrive
await myAgent.start({
onMessage: (message) => {
console.log('Received:', message);
},
onOutput: (chunk) => {
// Raw output chunks
console.log(chunk.type, chunk.data);
},
});
const result = await myAgent.stop();
// result.output.parsed contains all JSON messagesimport { createJsonInputStream, createJsonOutputStream } from 'agent-commander';
// Create input stream for sending messages
const input = createJsonInputStream();
input.addSystemMessage({ content: 'You are helpful' });
input.addPrompt({ content: 'Analyze this code' });
console.log(input.toString()); // NDJSON format
// Parse streaming output
const output = createJsonOutputStream({
onMessage: ({ message }) => console.log('Received:', message),
});
// Process chunks as they arrive
output.process({ chunk: '{"type":"hello"}\n' });
output.process({ chunk: '{"type":"done"}\n' });
// Get all messages
const messages = output.getMessages();import { agent } from 'agent-commander';
const myAgent = agent({
tool: 'claude',
workingDirectory: '/tmp/project',
prompt: 'Run tests',
isolation: 'screen',
screenName: 'my-agent-session',
});
// Start in detached mode
await myAgent.start({ detached: true });
// Later, stop the agent
const result = await myAgent.stop();
console.log('Exit code:', result.exitCode);import { agent } from 'agent-commander';
const myAgent = agent({
tool: 'claude',
workingDirectory: '/tmp/project',
prompt: 'Build the project',
isolation: 'docker',
containerName: 'my-agent-container',
});
// Start attached (stream output to console)
await myAgent.start({ attached: true });
// Stop the container and get results
const result = await myAgent.stop();
console.log('Exit code:', result.exitCode);const myAgent = agent({
tool: 'claude',
workingDirectory: '/tmp/project',
prompt: 'Test command',
});
// Preview the command without executing (prints to console)
await myAgent.start({ dryRun: true });import { getTool, listTools, isToolSupported } from 'agent-commander';
// List all available tools
console.log(listTools()); // ['claude', 'codex', 'opencode', 'agent', 'gemini', 'qwen']
// Check if a tool is supported
console.log(isToolSupported({ toolName: 'claude' })); // true
// Get tool configuration
const claudeTool = getTool({ toolName: 'claude' });
console.log(claudeTool.modelMap); // { sonnet: 'claude-sonnet-4-6', opus: 'claude-opus-4-7', ... }
// Map model alias to full ID
const fullId = claudeTool.mapModelToId({ model: 'opus' });
console.log(fullId); // 'claude-opus-4-7'Creates an agent controller.
Parameters:
options.tool(string, required) - CLI tool to use ('claude', 'codex', 'opencode', 'qwen', 'gemini', 'agent')options.workingDirectory(string, required) - Working directoryoptions.prompt(string, optional) - Prompt for the agentoptions.systemPrompt(string, optional) - System promptoptions.model(string, optional) - Model alias or full IDoptions.json(boolean, optional) - Enable JSON output modeoptions.resume(string, optional) - Resume session ID (tool-specific)options.readOnly(boolean, optional) - Enforce native read-only/planning modeoptions.isolation(string, optional) - 'none', 'screen', or 'docker' (default: 'none')options.screenName(string, optional) - Screen session name (required for screen isolation)options.containerName(string, optional) - Container name (required for docker isolation)options.toolOptions(object, optional) - Additional tool-specific options
Returns: Agent controller object with start(), stop(), getSessionId(), getMessages(), and getToolConfig() methods
Starts the agent (non-blocking - returns immediately after starting the process).
Parameters:
startOptions.dryRun(boolean, optional) - Preview command without executingstartOptions.detached(boolean, optional) - Run in detached modestartOptions.attached(boolean, optional) - Stream output (default: true)startOptions.onMessage(function, optional) - Callback for JSON messagesstartOptions.onOutput(function, optional) - Callback for raw output chunks
Returns: Promise resolving to void (or prints command in dry-run mode)
Stops the agent and collects output.
For isolation: 'none': Waits for process to exit and collects all output.
For isolation: 'screen' or 'docker': Sends stop command to the isolated environment.
Parameters:
stopOptions.dryRun(boolean, optional) - Preview command without executing
Returns: Promise resolving to:
{
exitCode: number,
output: {
plain: string, // Raw text output (stdout + stderr)
parsed: Array|null // JSON-parsed messages (if tool supports it)
},
sessionId: string|null, // Session ID for resuming
usage: Object|null // Token usage statistics
}Creates a JSON input stream for building NDJSON input.
Parameters:
options.compact(boolean, optional) - Use compact JSON (default: true)
Returns: JsonInputStream with add(), addPrompt(), addSystemMessage(), toString(), toBuffer() methods
Creates a JSON output stream for parsing NDJSON output.
Parameters:
options.onMessage(function, optional) - Callback for each parsed messageoptions.onError(function, optional) - Callback for parse errors
Returns: JsonOutputStream with process(), flush(), getMessages(), filterByType() methods
Direct execution without isolation. Agent runs as a child process.
Use case: Simple, quick execution with full system access
CTRL+C: Stops the agent gracefully
Runs agent in a GNU Screen session.
Use case: Detached long-running tasks that can be reattached
Requirements: screen must be installed
Management:
# List sessions
screen -ls
# Reattach
screen -r my-agent-session
# Detach
Ctrl+A, then DRuns agent in a Docker container with working directory mounted.
Use case: Isolated, reproducible environments
Requirements: Docker must be installed and running
Management:
# List containers
docker ps -a
# View logs
docker logs my-agent-container
# Stop
stop-agent --isolation docker --container-name my-agent-container# Node.js
npm test
# Bun
bun test
# Deno
deno test --allow-read --allow-run --allow-env --allow-net test/**/*.test.mjsnpm run examplenpm run lintThe library is built using patterns from hive-mind and uses:
- use-m: Dynamic module loading from CDN
- command-stream: Asynchronous command execution with streaming output
agent-commander/
├── src/
│ ├── index.mjs # Main library interface
│ ├── command-builder.mjs # Command string construction
│ ├── executor.mjs # Command execution logic
│ ├── cli-parser.mjs # CLI argument parsing
│ ├── tools/ # Tool configurations
│ │ ├── index.mjs # Tool registry
│ │ ├── claude.mjs # Claude Code CLI config
│ │ ├── codex.mjs # Codex CLI config
│ │ ├── opencode.mjs # OpenCode CLI config
│ │ ├── qwen.mjs # Qwen Code CLI config
│ │ ├── gemini.mjs # Gemini CLI config
│ │ └── agent.mjs # @link-assistant/agent config
│ ├── streaming/ # JSON streaming utilities
│ │ ├── index.mjs # Stream exports
│ │ ├── ndjson.mjs # NDJSON parsing/stringify
│ │ ├── input-stream.mjs # Input stream builder
│ │ └── output-stream.mjs # Output stream parser
│ └── utils/
│ └── loader.mjs # use-m integration
├── bin/
│ ├── start-agent.mjs # CLI: start-agent
│ └── stop-agent.mjs # CLI: stop-agent
├── test/ # Test files
├── examples/ # Usage examples
└── .github/workflows/ # CI/CD pipelines
Contributions are welcome! Please ensure:
- All tests pass:
npm test - Code is linted:
npm run lint - Tests work on Node.js, Bun, and Deno
This is free and unencumbered software released into the public domain. See LICENSE for details.
- Inspired by hive-mind - Distributed AI orchestration platform
- Testing infrastructure based on test-anywhere
- Based on best experience from @link-assistant/agent
- hive-mind - Multi-agent GitHub issue solver
- @link-assistant/agent - Unrestricted OpenCode fork for autonomous agents
- test-anywhere - Universal JavaScript testing