A JavaScript library to control agents enclosed in CLI commands like Anthropic Claude Code CLI, OpenAI Codex, OpenCode, 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 CLIagent- @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
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 | Model Aliases |
|---|---|---|---|---|
claude |
Anthropic Claude Code CLI | ✅ (stream-json) | ✅ (stream-json) | sonnet, opus, haiku |
codex |
OpenAI Codex CLI | ✅ | ❌ | gpt5, o3, gpt4o |
opencode |
OpenCode CLI | ✅ | ❌ | grok, gemini, sonnet |
agent |
@link-assistant/agent | ✅ | ❌ | 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
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', '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)--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 grokWith 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-sessionWith 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',
});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']
// 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-5-...', ... }
// Map model alias to full ID
const fullId = claudeTool.mapModelToId({ model: 'opus' });
console.log(fullId); // 'claude-opus-4-5-20251101'Creates an agent controller.
Parameters:
options.tool(string, required) - CLI tool to use ('claude', 'codex', 'opencode', '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.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
│ │ └── 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