Skip to content

Commit 20bb415

Browse files
authored
Merge pull request #8 from link-assistant/issue-3-97c6556ec6f2
Add full Claude Code CLI capabilities support
2 parents b195de4 + 4f9277b commit 20bb415

File tree

9 files changed

+517
-19
lines changed

9 files changed

+517
-19
lines changed

README.md

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,24 @@ bun add agent-commander
5151

5252
## Supported Tools
5353

54-
| Tool | Description | JSON Output | Model Aliases |
55-
|------|-------------|-------------|---------------|
56-
| `claude` | Anthropic Claude Code CLI || `sonnet`, `opus`, `haiku` |
57-
| `codex` | OpenAI Codex CLI || `gpt5`, `o3`, `gpt4o` |
58-
| `opencode` | OpenCode CLI || `grok`, `gemini`, `sonnet` |
59-
| `agent` | @link-assistant/agent || `grok`, `sonnet`, `haiku` |
54+
| Tool | Description | JSON Output | JSON Input | Model Aliases |
55+
|------|-------------|-------------|------------|---------------|
56+
| `claude` | Anthropic Claude Code CLI | ✅ (stream-json) | ✅ (stream-json) | `sonnet`, `opus`, `haiku` |
57+
| `codex` | OpenAI Codex CLI ||| `gpt5`, `o3`, `gpt4o` |
58+
| `opencode` | OpenCode CLI ||| `grok`, `gemini`, `sonnet` |
59+
| `agent` | @link-assistant/agent ||| `grok`, `sonnet`, `haiku` |
60+
61+
### Claude-specific Features
62+
63+
The Claude Code CLI supports additional features:
64+
65+
- **Stream JSON format**: Uses `--output-format stream-json` and `--input-format stream-json` for real-time streaming
66+
- **Permission bypass**: Automatically includes `--dangerously-skip-permissions` for unrestricted operation
67+
- **Fallback model**: Use `--fallback-model` for automatic fallback when the primary model is overloaded
68+
- **Session management**: Full support for `--session-id`, `--fork-session`, and `--resume`
69+
- **System prompt appending**: Use `--append-system-prompt` to add to the default system prompt
70+
- **Verbose mode**: Enable with `--verbose` for detailed output
71+
- **User message replay**: Use `--replay-user-messages` for streaming acknowledgment
6072

6173
## CLI Usage
6274

@@ -74,7 +86,14 @@ start-agent --tool claude --working-directory "/tmp/dir" --prompt "Solve the iss
7486
- `--working-directory <path>` - Working directory for the agent [required]
7587
- `--prompt <text>` - Prompt for the agent
7688
- `--system-prompt <text>` - System prompt for the agent
89+
- `--append-system-prompt <text>` - Append to the default system prompt (Claude only)
7790
- `--model <name>` - Model to use (e.g., 'sonnet', 'opus', 'grok')
91+
- `--fallback-model <name>` - Fallback model when default is overloaded (Claude only)
92+
- `--verbose` - Enable verbose mode (Claude only)
93+
- `--resume <sessionId>` - Resume a previous session by ID
94+
- `--session-id <uuid>` - Use a specific session ID (Claude only, must be valid UUID)
95+
- `--fork-session` - Create new session ID when resuming (Claude only)
96+
- `--replay-user-messages` - Re-emit user messages on stdout (Claude only, streaming mode)
7897
- `--isolation <mode>` - Isolation mode: none, screen, docker (default: none)
7998
- `--screen-name <name>` - Screen session name (required for screen isolation)
8099
- `--container-name <name>` - Container name (required for docker isolation)
@@ -99,6 +118,18 @@ start-agent --tool codex --working-directory "/tmp/dir" --prompt "Fix the bug" -
99118
start-agent --tool agent --working-directory "/tmp/dir" --prompt "Analyze code" --model grok
100119
```
101120

121+
**With model fallback (Claude)**
122+
```bash
123+
start-agent --tool claude --working-directory "/tmp/dir" \
124+
--prompt "Complex task" --model opus --fallback-model sonnet
125+
```
126+
127+
**Resume a session with fork (Claude)**
128+
```bash
129+
start-agent --tool claude --working-directory "/tmp/dir" \
130+
--resume abc123 --fork-session
131+
```
132+
102133
**With screen isolation (detached)**
103134
```bash
104135
start-agent --tool claude --working-directory "/tmp/dir" \

js/package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/src/cli-parser.mjs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ export function parseStartAgentArgs(args) {
4848
workingDirectory: parsed['working-directory'],
4949
prompt: parsed.prompt,
5050
systemPrompt: parsed['system-prompt'],
51+
appendSystemPrompt: parsed['append-system-prompt'],
52+
model: parsed.model,
53+
fallbackModel: parsed['fallback-model'],
54+
verbose: parsed.verbose || false,
55+
replayUserMessages: parsed['replay-user-messages'] || false,
56+
resume: parsed.resume,
57+
sessionId: parsed['session-id'],
58+
forkSession: parsed['fork-session'] || false,
5159
isolation: parsed.isolation || 'none',
5260
screenName: parsed['screen-name'],
5361
containerName: parsed['container-name'],
@@ -87,6 +95,14 @@ Options:
8795
--working-directory <path> Working directory for the agent [required]
8896
--prompt <text> Prompt for the agent
8997
--system-prompt <text> System prompt for the agent
98+
--append-system-prompt <text> Append to the default system prompt
99+
--model <model> Model to use (e.g., 'sonnet', 'opus', 'haiku')
100+
--fallback-model <model> Fallback model when default is overloaded
101+
--verbose Enable verbose mode
102+
--resume <sessionId> Resume a previous session by ID
103+
--session-id <uuid> Use a specific session ID (must be valid UUID)
104+
--fork-session Create new session ID when resuming
105+
--replay-user-messages Re-emit user messages on stdout (streaming mode)
90106
--isolation <mode> Isolation mode: none, screen, docker (default: none)
91107
--screen-name <name> Screen session name (required for screen isolation)
92108
--container-name <name> Container name (required for docker isolation)
@@ -98,6 +114,14 @@ Examples:
98114
# Basic usage (no isolation)
99115
start-agent --tool claude --working-directory "/tmp/dir" --prompt "Hello"
100116
117+
# With model selection
118+
start-agent --tool claude --working-directory "/tmp/dir" \\
119+
--prompt "Hello" --model opus --fallback-model sonnet
120+
121+
# Resume a session with fork
122+
start-agent --tool claude --working-directory "/tmp/dir" \\
123+
--resume abc123 --fork-session
124+
101125
# With screen isolation (detached)
102126
start-agent --tool claude --working-directory "/tmp/dir" \\
103127
--isolation screen --screen-name my-agent --detached

js/src/tools/claude.mjs

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,29 +31,51 @@ export function mapModelToId(options) {
3131
* @param {Object} options - Options
3232
* @param {string} [options.prompt] - User prompt
3333
* @param {string} [options.systemPrompt] - System prompt
34+
* @param {string} [options.appendSystemPrompt] - System prompt to append to default
3435
* @param {string} [options.model] - Model to use
36+
* @param {string} [options.fallbackModel] - Fallback model when default is overloaded
3537
* @param {boolean} [options.print] - Print mode (non-interactive)
36-
* @param {boolean} [options.json] - JSON output mode
38+
* @param {boolean} [options.verbose] - Verbose mode
39+
* @param {boolean} [options.json] - JSON output mode (stream-json format)
40+
* @param {boolean} [options.jsonInput] - JSON input mode (stream-json format)
41+
* @param {boolean} [options.replayUserMessages] - Re-emit user messages on stdout
3742
* @param {string} [options.resume] - Resume session ID
43+
* @param {string} [options.sessionId] - Use specific session ID (must be valid UUID)
44+
* @param {boolean} [options.forkSession] - Create new session ID when resuming
3845
* @returns {string[]} Array of CLI arguments
3946
*/
4047
export function buildArgs(options) {
4148
const {
4249
prompt,
4350
systemPrompt,
51+
appendSystemPrompt,
4452
model,
53+
fallbackModel,
4554
print = false,
55+
verbose = false,
4656
json = false,
57+
jsonInput = false,
58+
replayUserMessages = false,
4759
resume,
60+
sessionId,
61+
forkSession = false,
4862
} = options;
4963

5064
const args = [];
5165

66+
// Permission bypass - always enabled, not configurable (per issue #3)
67+
args.push('--dangerously-skip-permissions');
68+
5269
if (model) {
5370
const mappedModel = mapModelToId({ model });
5471
args.push('--model', mappedModel);
5572
}
5673

74+
if (fallbackModel) {
75+
const mappedFallback = mapModelToId({ model: fallbackModel });
76+
args.push('--fallback-model', mappedFallback);
77+
}
78+
5779
if (prompt) {
5880
args.push('--prompt', prompt);
5981
}
@@ -62,18 +84,46 @@ export function buildArgs(options) {
6284
args.push('--system-prompt', systemPrompt);
6385
}
6486

87+
if (appendSystemPrompt) {
88+
args.push('--append-system-prompt', appendSystemPrompt);
89+
}
90+
91+
if (verbose) {
92+
args.push('--verbose');
93+
}
94+
6595
if (print) {
6696
args.push('-p'); // Print mode
6797
}
6898

99+
// JSON output mode - use stream-json format per issue #3
69100
if (json) {
70-
args.push('--output-format', 'json');
101+
args.push('--output-format', 'stream-json');
102+
}
103+
104+
// JSON input mode - use stream-json format per issue #3
105+
if (jsonInput) {
106+
args.push('--input-format', 'stream-json');
107+
}
108+
109+
// Replay user messages (only with stream-json input/output)
110+
if (replayUserMessages) {
111+
args.push('--replay-user-messages');
112+
}
113+
114+
// Session management
115+
if (sessionId) {
116+
args.push('--session-id', sessionId);
71117
}
72118

73119
if (resume) {
74120
args.push('--resume', resume);
75121
}
76122

123+
if (forkSession) {
124+
args.push('--fork-session');
125+
}
126+
77127
return args;
78128
}
79129

@@ -83,10 +133,17 @@ export function buildArgs(options) {
83133
* @param {string} options.workingDirectory - Working directory
84134
* @param {string} [options.prompt] - User prompt
85135
* @param {string} [options.systemPrompt] - System prompt
136+
* @param {string} [options.appendSystemPrompt] - System prompt to append to default
86137
* @param {string} [options.model] - Model to use
138+
* @param {string} [options.fallbackModel] - Fallback model when default is overloaded
87139
* @param {boolean} [options.print] - Print mode (non-interactive)
88-
* @param {boolean} [options.json] - JSON output mode
140+
* @param {boolean} [options.verbose] - Verbose mode
141+
* @param {boolean} [options.json] - JSON output mode (stream-json format)
142+
* @param {boolean} [options.jsonInput] - JSON input mode (stream-json format)
143+
* @param {boolean} [options.replayUserMessages] - Re-emit user messages on stdout
89144
* @param {string} [options.resume] - Resume session ID
145+
* @param {string} [options.sessionId] - Use specific session ID (must be valid UUID)
146+
* @param {boolean} [options.forkSession] - Create new session ID when resuming
90147
* @returns {string} Complete command string
91148
*/
92149
export function buildCommand(options) {
@@ -203,9 +260,15 @@ export const claudeTool = {
203260
displayName: 'Claude Code CLI',
204261
executable: 'claude',
205262
supportsJsonOutput: true,
206-
supportsJsonInput: false, // Claude doesn't support JSON streaming input yet
263+
supportsJsonInput: true, // Claude supports stream-json input format
207264
supportsSystemPrompt: true,
265+
supportsAppendSystemPrompt: true, // Supports --append-system-prompt
208266
supportsResume: true,
267+
supportsForkSession: true, // Supports --fork-session
268+
supportsSessionId: true, // Supports --session-id
269+
supportsFallbackModel: true, // Supports --fallback-model
270+
supportsVerbose: true, // Supports --verbose
271+
supportsReplayUserMessages: true, // Supports --replay-user-messages
209272
defaultModel: 'sonnet',
210273
modelMap,
211274
mapModelToId,

js/test/cli-parser.test.mjs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,86 @@ test('validateStopAgentOptions - missing isolation', () => {
135135
assert.strictEqual(result.valid, false);
136136
assert.ok(result.errors.some((e) => e.includes('isolation')));
137137
});
138+
139+
// New Claude CLI options tests (issue #3)
140+
test('parseStartAgentArgs - with model and fallback-model', () => {
141+
const args = [
142+
'--tool',
143+
'claude',
144+
'--working-directory',
145+
'/tmp/test',
146+
'--model',
147+
'opus',
148+
'--fallback-model',
149+
'sonnet',
150+
];
151+
const result = parseStartAgentArgs(args);
152+
153+
assert.strictEqual(result.model, 'opus');
154+
assert.strictEqual(result.fallbackModel, 'sonnet');
155+
});
156+
157+
test('parseStartAgentArgs - with session management options', () => {
158+
const args = [
159+
'--tool',
160+
'claude',
161+
'--working-directory',
162+
'/tmp/test',
163+
'--resume',
164+
'abc123',
165+
'--session-id',
166+
'123e4567-e89b-12d3-a456-426614174000',
167+
'--fork-session',
168+
];
169+
const result = parseStartAgentArgs(args);
170+
171+
assert.strictEqual(result.resume, 'abc123');
172+
assert.strictEqual(result.sessionId, '123e4567-e89b-12d3-a456-426614174000');
173+
assert.strictEqual(result.forkSession, true);
174+
});
175+
176+
test('parseStartAgentArgs - with append-system-prompt', () => {
177+
const args = [
178+
'--tool',
179+
'claude',
180+
'--working-directory',
181+
'/tmp/test',
182+
'--system-prompt',
183+
'You are a helpful assistant',
184+
'--append-system-prompt',
185+
'Extra instructions here',
186+
];
187+
const result = parseStartAgentArgs(args);
188+
189+
assert.strictEqual(result.systemPrompt, 'You are a helpful assistant');
190+
assert.strictEqual(result.appendSystemPrompt, 'Extra instructions here');
191+
});
192+
193+
test('parseStartAgentArgs - with verbose and replay-user-messages', () => {
194+
const args = [
195+
'--tool',
196+
'claude',
197+
'--working-directory',
198+
'/tmp/test',
199+
'--verbose',
200+
'--replay-user-messages',
201+
];
202+
const result = parseStartAgentArgs(args);
203+
204+
assert.strictEqual(result.verbose, true);
205+
assert.strictEqual(result.replayUserMessages, true);
206+
});
207+
208+
test('parseStartAgentArgs - defaults for new options', () => {
209+
const args = ['--tool', 'claude', '--working-directory', '/tmp/test'];
210+
const result = parseStartAgentArgs(args);
211+
212+
assert.strictEqual(result.verbose, false);
213+
assert.strictEqual(result.replayUserMessages, false);
214+
assert.strictEqual(result.forkSession, false);
215+
assert.strictEqual(result.model, undefined);
216+
assert.strictEqual(result.fallbackModel, undefined);
217+
assert.strictEqual(result.resume, undefined);
218+
assert.strictEqual(result.sessionId, undefined);
219+
assert.strictEqual(result.appendSystemPrompt, undefined);
220+
});

0 commit comments

Comments
 (0)