@swarmclawai/swarmclaw 1.7.1 → 1.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/package.json +3 -3
- package/scripts/run-next-build.mjs +1 -1
- package/src/app/api/setup/check-provider/route.ts +5 -62
- package/src/app/api/setup/doctor/route.ts +19 -9
- package/src/components/auth/setup-wizard/step-providers.tsx +81 -42
- package/src/components/layout/update-banner.tsx +43 -9
- package/src/lib/provider-sets.test.ts +19 -0
- package/src/lib/provider-sets.ts +8 -3
- package/src/lib/providers/cli-provider-metadata.test.ts +38 -0
- package/src/lib/providers/cli-provider-metadata.ts +208 -0
- package/src/lib/providers/cli-utils.ts +4 -43
- package/src/lib/providers/generic-cli.ts +2 -31
- package/src/lib/providers/index.ts +14 -44
- package/src/lib/server/cli-provider-readiness.test.ts +45 -0
- package/src/lib/server/cli-provider-readiness.ts +84 -0
- package/src/lib/server/provider-health.test.ts +6 -0
- package/src/lib/server/provider-health.ts +2 -2
- package/src/lib/setup-defaults.test.ts +8 -0
- package/src/lib/setup-defaults.ts +38 -178
- package/tsconfig.json +1 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import type { ProviderType } from '../../types/provider.ts'
|
|
2
|
+
|
|
3
|
+
export type CliAuthBackend =
|
|
4
|
+
| 'claude'
|
|
5
|
+
| 'codex'
|
|
6
|
+
| 'opencode'
|
|
7
|
+
| 'gemini'
|
|
8
|
+
| 'copilot'
|
|
9
|
+
| 'droid'
|
|
10
|
+
| 'cursor'
|
|
11
|
+
| 'qwen'
|
|
12
|
+
| 'goose'
|
|
13
|
+
|
|
14
|
+
export interface CliProviderMetadata {
|
|
15
|
+
id: ProviderType
|
|
16
|
+
displayName: string
|
|
17
|
+
binaryName: string
|
|
18
|
+
capability: string
|
|
19
|
+
description: string
|
|
20
|
+
defaultModel: string
|
|
21
|
+
icon: string
|
|
22
|
+
setupBadge: string
|
|
23
|
+
generic: boolean
|
|
24
|
+
optionalApiKey?: boolean
|
|
25
|
+
authBackend?: CliAuthBackend
|
|
26
|
+
keyUrl?: string
|
|
27
|
+
keyLabel?: string
|
|
28
|
+
keyPlaceholder?: string
|
|
29
|
+
modelLibraryUrl?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const BESPOKE_CLI_PROVIDER_METADATA = [
|
|
33
|
+
{
|
|
34
|
+
id: 'claude-cli',
|
|
35
|
+
displayName: 'Claude Code CLI',
|
|
36
|
+
binaryName: 'claude',
|
|
37
|
+
capability: 'multi-file code editing, refactoring, debugging, code review',
|
|
38
|
+
description: "Anthropic's coding agent with native tools, strong edits, and first-class CLI workflows.",
|
|
39
|
+
defaultModel: 'claude-sonnet-4-6',
|
|
40
|
+
icon: 'C',
|
|
41
|
+
setupBadge: 'CLI',
|
|
42
|
+
generic: false,
|
|
43
|
+
authBackend: 'claude',
|
|
44
|
+
modelLibraryUrl: 'https://docs.anthropic.com/en/docs/about-claude/models',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'codex-cli',
|
|
48
|
+
displayName: 'OpenAI Codex CLI',
|
|
49
|
+
binaryName: 'codex',
|
|
50
|
+
capability: 'code generation, file creation, automated coding tasks',
|
|
51
|
+
description: "OpenAI's terminal coding agent with resume support and structured headless output.",
|
|
52
|
+
defaultModel: 'gpt-5.4-codex',
|
|
53
|
+
icon: 'O',
|
|
54
|
+
setupBadge: 'CLI',
|
|
55
|
+
generic: false,
|
|
56
|
+
authBackend: 'codex',
|
|
57
|
+
modelLibraryUrl: 'https://platform.openai.com/docs/models',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'opencode-cli',
|
|
61
|
+
displayName: 'OpenCode CLI',
|
|
62
|
+
binaryName: 'opencode',
|
|
63
|
+
capability: 'code analysis, generation across multiple LLM backends',
|
|
64
|
+
description: 'A flexible coding CLI that can route across multiple model backends.',
|
|
65
|
+
defaultModel: 'claude-sonnet-4-6',
|
|
66
|
+
icon: 'O',
|
|
67
|
+
setupBadge: 'CLI',
|
|
68
|
+
generic: false,
|
|
69
|
+
authBackend: 'opencode',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'gemini-cli',
|
|
73
|
+
displayName: 'Gemini CLI',
|
|
74
|
+
binaryName: 'gemini',
|
|
75
|
+
capability: 'code generation, analysis with Gemini models',
|
|
76
|
+
description: "Google's terminal coding agent with project-aware headless mode and resume support.",
|
|
77
|
+
defaultModel: 'gemini-3.1-pro',
|
|
78
|
+
icon: 'G',
|
|
79
|
+
setupBadge: 'CLI',
|
|
80
|
+
generic: false,
|
|
81
|
+
authBackend: 'gemini',
|
|
82
|
+
modelLibraryUrl: 'https://ai.google.dev/gemini-api/docs/models',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'copilot-cli',
|
|
86
|
+
displayName: 'GitHub Copilot CLI',
|
|
87
|
+
binaryName: 'copilot',
|
|
88
|
+
capability: 'code generation, analysis, multi-model support via GitHub Copilot',
|
|
89
|
+
description: "GitHub's multi-model terminal agent for coding and automation.",
|
|
90
|
+
defaultModel: 'claude-sonnet-4-6',
|
|
91
|
+
icon: 'P',
|
|
92
|
+
setupBadge: 'CLI',
|
|
93
|
+
generic: false,
|
|
94
|
+
authBackend: 'copilot',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'droid-cli',
|
|
98
|
+
displayName: 'Factory Droid CLI',
|
|
99
|
+
binaryName: 'droid',
|
|
100
|
+
capability: 'code generation, refactoring, and automation via Factory Droid with configurable autonomy',
|
|
101
|
+
description: "Factory.ai's terminal coding agent with headless exec mode, session resume, and autonomy controls.",
|
|
102
|
+
defaultModel: 'default',
|
|
103
|
+
icon: 'F',
|
|
104
|
+
setupBadge: 'CLI',
|
|
105
|
+
generic: false,
|
|
106
|
+
optionalApiKey: true,
|
|
107
|
+
authBackend: 'droid',
|
|
108
|
+
keyUrl: 'https://app.factory.ai/settings/api-keys',
|
|
109
|
+
keyLabel: 'app.factory.ai',
|
|
110
|
+
keyPlaceholder: 'FACTORY_API_KEY (optional if signed in via `droid`)',
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: 'cursor-cli',
|
|
114
|
+
displayName: 'Cursor Agent CLI',
|
|
115
|
+
binaryName: 'cursor-agent',
|
|
116
|
+
capability: 'full-agent coding workflows, multi-file edits, project-aware code changes',
|
|
117
|
+
description: "Cursor's terminal agent with resume support, JSON output, and Cursor-native coding workflows.",
|
|
118
|
+
defaultModel: 'auto',
|
|
119
|
+
icon: 'U',
|
|
120
|
+
setupBadge: 'CLI',
|
|
121
|
+
generic: false,
|
|
122
|
+
authBackend: 'cursor',
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: 'qwen-code-cli',
|
|
126
|
+
displayName: 'Qwen Code CLI',
|
|
127
|
+
binaryName: 'qwen',
|
|
128
|
+
capability: 'terminal-native coding workflows, code generation, review, and automation',
|
|
129
|
+
description: "Qwen's terminal coding agent with structured headless mode and multi-provider model config.",
|
|
130
|
+
defaultModel: 'default',
|
|
131
|
+
icon: 'Q',
|
|
132
|
+
setupBadge: 'CLI',
|
|
133
|
+
generic: false,
|
|
134
|
+
authBackend: 'qwen',
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: 'goose',
|
|
138
|
+
displayName: 'Goose',
|
|
139
|
+
binaryName: 'goose',
|
|
140
|
+
capability: 'agentic coding workflows with extensions, tools, and runtime-managed execution',
|
|
141
|
+
description: 'A runtime-managed terminal agent with extensions, session history, and ACP support.',
|
|
142
|
+
defaultModel: 'default',
|
|
143
|
+
icon: 'G',
|
|
144
|
+
setupBadge: 'Runtime',
|
|
145
|
+
generic: false,
|
|
146
|
+
optionalApiKey: true,
|
|
147
|
+
authBackend: 'goose',
|
|
148
|
+
},
|
|
149
|
+
] as const satisfies readonly CliProviderMetadata[]
|
|
150
|
+
|
|
151
|
+
export const GENERIC_CLI_PROVIDER_METADATA = [
|
|
152
|
+
['aider-cli', 'Aider CLI', 'aider', 'paired-programming-style multi-file edits and git-aware code changes'],
|
|
153
|
+
['amp-cli', 'Amp CLI', 'amp', 'agentic coding via Sourcegraph Amp'],
|
|
154
|
+
['augment-cli', 'Augment CLI', 'augment', 'codebase-aware agentic edits via Augment'],
|
|
155
|
+
['adal-cli', 'AdaL CLI', 'adal', 'AdaL coding agent for terminal-driven workflows'],
|
|
156
|
+
['bob-cli', 'IBM Bob CLI', 'bob', 'IBM watsonx Code Assistant terminal coding workflows'],
|
|
157
|
+
['cline-cli', 'Cline CLI', 'cline', 'autonomous file-level edits and terminal automation via Cline'],
|
|
158
|
+
['codebuddy-cli', 'CodeBuddy CLI', 'codebuddy', 'CodeBuddy agentic coding workflows'],
|
|
159
|
+
['command-code-cli', 'Command Code CLI', 'commandcode', 'Command Code terminal-native coding agent'],
|
|
160
|
+
['continue-cli', 'Continue CLI', 'continue', 'agentic coding via the Continue CLI'],
|
|
161
|
+
['cortex-cli', 'Cortex Code CLI', 'cortex', 'Snowflake Cortex Code agentic workflows'],
|
|
162
|
+
['crush-cli', 'Crush CLI', 'crush', 'Crush terminal coding agent'],
|
|
163
|
+
['deepagents-cli', 'Deep Agents CLI', 'deepagents', 'long-horizon planning and multi-step coding via Deep Agents'],
|
|
164
|
+
['firebender-cli', 'Firebender CLI', 'firebender', 'Firebender JetBrains-aligned coding agent'],
|
|
165
|
+
['iflow-cli', 'iFlow CLI', 'iflow', 'iFlow CLI agentic coding workflows'],
|
|
166
|
+
['junie-cli', 'Junie CLI', 'junie', 'JetBrains Junie coding agent for terminal use'],
|
|
167
|
+
['kilo-code-cli', 'Kilo Code CLI', 'kilocode', 'Kilo Code agentic coding workflows'],
|
|
168
|
+
['kimi-cli', 'Kimi CLI', 'kimi', 'Kimi Code CLI coding agent'],
|
|
169
|
+
['kode-cli', 'Kode CLI', 'kode', 'Kode terminal coding agent'],
|
|
170
|
+
['mcpjam-cli', 'MCPJam CLI', 'mcpjam', 'MCPJam-tooled agentic coding workflows'],
|
|
171
|
+
['mistral-vibe-cli', 'Mistral Vibe CLI', 'vibe', 'Mistral Vibe coding agent'],
|
|
172
|
+
['mux-cli', 'Mux CLI', 'mux', 'Mux multi-tool coding agent'],
|
|
173
|
+
['neovate-cli', 'Neovate CLI', 'neovate', 'Neovate coding agent for terminal workflows'],
|
|
174
|
+
['openhands-cli', 'OpenHands CLI', 'openhands', 'OpenHands agentic coding via terminal'],
|
|
175
|
+
['pochi-cli', 'Pochi CLI', 'pochi', 'Pochi coding agent'],
|
|
176
|
+
['qoder-cli', 'Qoder CLI', 'qoder', 'Qoder agentic coding workflows'],
|
|
177
|
+
['replit-cli', 'Replit Agent CLI', 'replit', 'Replit Agent terminal coding workflows'],
|
|
178
|
+
['roo-code-cli', 'Roo Code CLI', 'roo', 'Roo Code agentic coding workflows'],
|
|
179
|
+
['trae-cn-cli', 'TRAE CN CLI', 'trae-cn', 'TRAE CN coding agent'],
|
|
180
|
+
['warp-cli', 'Warp Agent CLI', 'warp', 'Warp Agent terminal-native coding workflows'],
|
|
181
|
+
['windsurf-cli', 'Windsurf CLI', 'windsurf', 'Windsurf agentic coding workflows'],
|
|
182
|
+
['zencoder-cli', 'Zencoder CLI', 'zencoder', 'Zencoder agentic coding workflows'],
|
|
183
|
+
].map(([id, displayName, binaryName, capability]) => ({
|
|
184
|
+
id: id as ProviderType,
|
|
185
|
+
displayName,
|
|
186
|
+
binaryName,
|
|
187
|
+
capability,
|
|
188
|
+
description: `${displayName}: ${capability}.`,
|
|
189
|
+
defaultModel: 'default',
|
|
190
|
+
icon: displayName.charAt(0),
|
|
191
|
+
setupBadge: 'CLI',
|
|
192
|
+
generic: true,
|
|
193
|
+
optionalApiKey: true,
|
|
194
|
+
})) satisfies readonly CliProviderMetadata[]
|
|
195
|
+
|
|
196
|
+
export const CLI_PROVIDER_METADATA = [
|
|
197
|
+
...BESPOKE_CLI_PROVIDER_METADATA,
|
|
198
|
+
...GENERIC_CLI_PROVIDER_METADATA,
|
|
199
|
+
] as const satisfies readonly CliProviderMetadata[]
|
|
200
|
+
|
|
201
|
+
export type CliProviderId = (typeof CLI_PROVIDER_METADATA)[number]['id']
|
|
202
|
+
|
|
203
|
+
export const CLI_PROVIDER_METADATA_BY_ID: Record<string, CliProviderMetadata> =
|
|
204
|
+
Object.fromEntries(CLI_PROVIDER_METADATA.map((provider) => [provider.id, provider]))
|
|
205
|
+
|
|
206
|
+
export function isCliProviderId(providerId: string): providerId is CliProviderId {
|
|
207
|
+
return providerId in CLI_PROVIDER_METADATA_BY_ID
|
|
208
|
+
}
|
|
@@ -12,6 +12,7 @@ import path from 'path'
|
|
|
12
12
|
import { spawnSync, type ChildProcess } from 'child_process'
|
|
13
13
|
import { realpathSync } from 'fs'
|
|
14
14
|
import { log } from '../server/logger'
|
|
15
|
+
import { CLI_PROVIDER_METADATA, CLI_PROVIDER_METADATA_BY_ID } from './cli-provider-metadata'
|
|
15
16
|
|
|
16
17
|
// ---------------------------------------------------------------------------
|
|
17
18
|
// Binary Discovery
|
|
@@ -468,50 +469,10 @@ export function symlinkConfigFiles(
|
|
|
468
469
|
// ---------------------------------------------------------------------------
|
|
469
470
|
|
|
470
471
|
/** Human-readable descriptions of what each CLI provider excels at. */
|
|
471
|
-
export const CLI_PROVIDER_CAPABILITIES: Record<string, string> =
|
|
472
|
-
|
|
473
|
-
'codex-cli': 'code generation, file creation, automated coding tasks',
|
|
474
|
-
'opencode-cli': 'code analysis, generation across multiple LLM backends',
|
|
475
|
-
'gemini-cli': 'code generation, analysis with Gemini models',
|
|
476
|
-
'copilot-cli': 'code generation, analysis, multi-model support via GitHub Copilot',
|
|
477
|
-
'droid-cli': 'code generation, refactoring, and automation via Factory Droid with configurable autonomy',
|
|
478
|
-
'cursor-cli': 'full-agent coding workflows, multi-file edits, project-aware code changes',
|
|
479
|
-
'qwen-code-cli': 'terminal-native coding workflows, code generation, review, and automation',
|
|
480
|
-
goose: 'agentic coding workflows with extensions, tools, and runtime-managed execution',
|
|
481
|
-
'aider-cli': 'paired-programming-style multi-file edits and git-aware code changes',
|
|
482
|
-
'amp-cli': 'agentic coding via Sourcegraph Amp',
|
|
483
|
-
'augment-cli': 'codebase-aware agentic edits via Augment',
|
|
484
|
-
'adal-cli': 'AdaL coding agent for terminal-driven workflows',
|
|
485
|
-
'bob-cli': 'IBM watsonx Code Assistant (Bob) terminal coding workflows',
|
|
486
|
-
'cline-cli': 'autonomous file-level edits and terminal automation via Cline',
|
|
487
|
-
'codebuddy-cli': 'CodeBuddy agentic coding workflows',
|
|
488
|
-
'command-code-cli': 'Command Code terminal-native coding agent',
|
|
489
|
-
'continue-cli': 'agentic coding via the Continue CLI',
|
|
490
|
-
'cortex-cli': 'Snowflake Cortex Code agentic workflows',
|
|
491
|
-
'crush-cli': 'Crush terminal coding agent',
|
|
492
|
-
'deepagents-cli': 'long-horizon planning and multi-step coding via Deep Agents',
|
|
493
|
-
'firebender-cli': 'Firebender JetBrains-aligned coding agent',
|
|
494
|
-
'iflow-cli': 'iFlow CLI agentic coding workflows',
|
|
495
|
-
'junie-cli': 'JetBrains Junie coding agent for terminal use',
|
|
496
|
-
'kilo-code-cli': 'Kilo Code agentic coding workflows',
|
|
497
|
-
'kimi-cli': 'Kimi Code CLI coding agent',
|
|
498
|
-
'kode-cli': 'Kode terminal coding agent',
|
|
499
|
-
'mcpjam-cli': 'MCPJam-tooled agentic coding workflows',
|
|
500
|
-
'mistral-vibe-cli': 'Mistral Vibe coding agent',
|
|
501
|
-
'mux-cli': 'Mux multi-tool coding agent',
|
|
502
|
-
'neovate-cli': 'Neovate coding agent for terminal workflows',
|
|
503
|
-
'openhands-cli': 'OpenHands agentic coding via terminal',
|
|
504
|
-
'pochi-cli': 'Pochi coding agent',
|
|
505
|
-
'qoder-cli': 'Qoder agentic coding workflows',
|
|
506
|
-
'replit-cli': 'Replit Agent terminal coding workflows',
|
|
507
|
-
'roo-code-cli': 'Roo Code agentic coding workflows',
|
|
508
|
-
'trae-cn-cli': 'TRAE CN coding agent',
|
|
509
|
-
'warp-cli': 'Warp Agent terminal-native coding workflows',
|
|
510
|
-
'windsurf-cli': 'Windsurf agentic coding workflows',
|
|
511
|
-
'zencoder-cli': 'Zencoder agentic coding workflows',
|
|
512
|
-
}
|
|
472
|
+
export const CLI_PROVIDER_CAPABILITIES: Record<string, string> =
|
|
473
|
+
Object.fromEntries(CLI_PROVIDER_METADATA.map((provider) => [provider.id, provider.capability]))
|
|
513
474
|
|
|
514
475
|
/** Check if a provider ID is a CLI-based provider. */
|
|
515
476
|
export function isCliProvider(providerId: string): boolean {
|
|
516
|
-
return providerId in
|
|
477
|
+
return providerId in CLI_PROVIDER_METADATA_BY_ID
|
|
517
478
|
}
|
|
@@ -2,6 +2,7 @@ import { spawn } from 'child_process'
|
|
|
2
2
|
import type { StreamChatOptions } from './index'
|
|
3
3
|
import { log } from '../server/logger'
|
|
4
4
|
import { loadRuntimeSettings } from '@/lib/server/runtime/runtime-settings'
|
|
5
|
+
import { GENERIC_CLI_PROVIDER_METADATA } from './cli-provider-metadata'
|
|
5
6
|
import { resolveCliBinary, buildCliEnv, attachAbortHandler, isStderrNoise } from './cli-utils'
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -9,37 +10,7 @@ import { resolveCliBinary, buildCliEnv, attachAbortHandler, isStderrNoise } from
|
|
|
9
10
|
* Used by the generic CLI streamer for tools without a bespoke handler.
|
|
10
11
|
*/
|
|
11
12
|
export const GENERIC_CLI_BINARIES: Record<string, string> = {
|
|
12
|
-
|
|
13
|
-
'amp-cli': 'amp',
|
|
14
|
-
'augment-cli': 'augment',
|
|
15
|
-
'adal-cli': 'adal',
|
|
16
|
-
'bob-cli': 'bob',
|
|
17
|
-
'cline-cli': 'cline',
|
|
18
|
-
'codebuddy-cli': 'codebuddy',
|
|
19
|
-
'command-code-cli': 'commandcode',
|
|
20
|
-
'continue-cli': 'continue',
|
|
21
|
-
'cortex-cli': 'cortex',
|
|
22
|
-
'crush-cli': 'crush',
|
|
23
|
-
'deepagents-cli': 'deepagents',
|
|
24
|
-
'firebender-cli': 'firebender',
|
|
25
|
-
'iflow-cli': 'iflow',
|
|
26
|
-
'junie-cli': 'junie',
|
|
27
|
-
'kilo-code-cli': 'kilocode',
|
|
28
|
-
'kimi-cli': 'kimi',
|
|
29
|
-
'kode-cli': 'kode',
|
|
30
|
-
'mcpjam-cli': 'mcpjam',
|
|
31
|
-
'mistral-vibe-cli': 'vibe',
|
|
32
|
-
'mux-cli': 'mux',
|
|
33
|
-
'neovate-cli': 'neovate',
|
|
34
|
-
'openhands-cli': 'openhands',
|
|
35
|
-
'pochi-cli': 'pochi',
|
|
36
|
-
'qoder-cli': 'qoder',
|
|
37
|
-
'replit-cli': 'replit',
|
|
38
|
-
'roo-code-cli': 'roo',
|
|
39
|
-
'trae-cn-cli': 'trae-cn',
|
|
40
|
-
'warp-cli': 'warp',
|
|
41
|
-
'windsurf-cli': 'windsurf',
|
|
42
|
-
'zencoder-cli': 'zencoder',
|
|
13
|
+
...Object.fromEntries(GENERIC_CLI_PROVIDER_METADATA.map((provider) => [provider.id, provider.binaryName])),
|
|
43
14
|
}
|
|
44
15
|
|
|
45
16
|
interface GenericCliOptions extends StreamChatOptions {
|
|
@@ -8,7 +8,8 @@ import { streamDroidCliChat } from './droid-cli'
|
|
|
8
8
|
import { streamCursorCliChat } from './cursor-cli'
|
|
9
9
|
import { streamQwenCodeCliChat } from './qwen-code-cli'
|
|
10
10
|
import { streamGooseChat } from './goose'
|
|
11
|
-
import { streamGenericCliChat
|
|
11
|
+
import { streamGenericCliChat } from './generic-cli'
|
|
12
|
+
import { GENERIC_CLI_PROVIDER_METADATA, isCliProviderId } from './cli-provider-metadata'
|
|
12
13
|
import { streamOpenAiChat } from './openai'
|
|
13
14
|
import { streamOllamaChat } from './ollama'
|
|
14
15
|
import { streamAnthropicChat } from './anthropic'
|
|
@@ -52,53 +53,22 @@ interface BuiltinProviderConfig extends ProviderInfo {
|
|
|
52
53
|
handler: ProviderHandler
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
const GENERIC_CLI_DISPLAY_NAMES: Record<string, string> = {
|
|
56
|
-
'aider-cli': 'Aider CLI',
|
|
57
|
-
'amp-cli': 'Amp CLI',
|
|
58
|
-
'augment-cli': 'Augment CLI',
|
|
59
|
-
'adal-cli': 'AdaL CLI',
|
|
60
|
-
'bob-cli': 'IBM Bob CLI',
|
|
61
|
-
'cline-cli': 'Cline CLI',
|
|
62
|
-
'codebuddy-cli': 'CodeBuddy CLI',
|
|
63
|
-
'command-code-cli': 'Command Code CLI',
|
|
64
|
-
'continue-cli': 'Continue CLI',
|
|
65
|
-
'cortex-cli': 'Cortex Code CLI',
|
|
66
|
-
'crush-cli': 'Crush CLI',
|
|
67
|
-
'deepagents-cli': 'Deep Agents CLI',
|
|
68
|
-
'firebender-cli': 'Firebender CLI',
|
|
69
|
-
'iflow-cli': 'iFlow CLI',
|
|
70
|
-
'junie-cli': 'Junie CLI',
|
|
71
|
-
'kilo-code-cli': 'Kilo Code CLI',
|
|
72
|
-
'kimi-cli': 'Kimi Code CLI',
|
|
73
|
-
'kode-cli': 'Kode CLI',
|
|
74
|
-
'mcpjam-cli': 'MCPJam CLI',
|
|
75
|
-
'mistral-vibe-cli': 'Mistral Vibe CLI',
|
|
76
|
-
'mux-cli': 'Mux CLI',
|
|
77
|
-
'neovate-cli': 'Neovate CLI',
|
|
78
|
-
'openhands-cli': 'OpenHands CLI',
|
|
79
|
-
'pochi-cli': 'Pochi CLI',
|
|
80
|
-
'qoder-cli': 'Qoder CLI',
|
|
81
|
-
'replit-cli': 'Replit Agent CLI',
|
|
82
|
-
'roo-code-cli': 'Roo Code CLI',
|
|
83
|
-
'trae-cn-cli': 'TRAE CN CLI',
|
|
84
|
-
'warp-cli': 'Warp Agent CLI',
|
|
85
|
-
'windsurf-cli': 'Windsurf CLI',
|
|
86
|
-
'zencoder-cli': 'Zencoder CLI',
|
|
87
|
-
}
|
|
88
|
-
|
|
89
56
|
function buildGenericCliEntries(): Record<string, BuiltinProviderConfig> {
|
|
90
57
|
const entries: Record<string, BuiltinProviderConfig> = {}
|
|
91
|
-
for (const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
models: ['default'],
|
|
58
|
+
for (const provider of GENERIC_CLI_PROVIDER_METADATA) {
|
|
59
|
+
entries[provider.id] = {
|
|
60
|
+
id: provider.id,
|
|
61
|
+
name: provider.displayName,
|
|
62
|
+
models: [provider.defaultModel],
|
|
97
63
|
requiresApiKey: false,
|
|
98
|
-
optionalApiKey:
|
|
64
|
+
optionalApiKey: provider.optionalApiKey,
|
|
99
65
|
requiresEndpoint: false,
|
|
100
66
|
handler: {
|
|
101
|
-
streamChat: (opts) => streamGenericCliChat({
|
|
67
|
+
streamChat: (opts) => streamGenericCliChat({
|
|
68
|
+
...opts,
|
|
69
|
+
binaryName: provider.binaryName,
|
|
70
|
+
displayName: provider.displayName,
|
|
71
|
+
}),
|
|
102
72
|
},
|
|
103
73
|
}
|
|
104
74
|
}
|
|
@@ -530,7 +500,7 @@ export function getProviderList(): ProviderInfo[] {
|
|
|
530
500
|
...info,
|
|
531
501
|
models: overrides[info.id] || info.models,
|
|
532
502
|
defaultModels: info.models,
|
|
533
|
-
supportsModelDiscovery: !
|
|
503
|
+
supportsModelDiscovery: !isCliProviderId(info.id) && info.id !== 'fireworks',
|
|
534
504
|
}
|
|
535
505
|
})
|
|
536
506
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, it } from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { checkCliProviderReady } from './cli-provider-readiness'
|
|
5
|
+
|
|
6
|
+
describe('checkCliProviderReady', () => {
|
|
7
|
+
it('accepts a generic CLI provider when its binary is present', () => {
|
|
8
|
+
const result = checkCliProviderReady('aider-cli', {
|
|
9
|
+
resolveBinary: (name) => name === 'aider' ? '/usr/local/bin/aider' : null,
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
assert.equal(result.ok, true)
|
|
13
|
+
assert.equal(result.displayName, 'Aider CLI')
|
|
14
|
+
assert.equal(result.binaryName, 'aider')
|
|
15
|
+
assert.equal(result.generic, true)
|
|
16
|
+
assert.match(result.message, /binary is available/)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('reports a missing generic CLI binary with install guidance', () => {
|
|
20
|
+
const result = checkCliProviderReady('windsurf-cli', {
|
|
21
|
+
resolveBinary: () => null,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
assert.equal(result.ok, false)
|
|
25
|
+
assert.equal(result.displayName, 'Windsurf CLI')
|
|
26
|
+
assert.equal(result.binaryName, 'windsurf')
|
|
27
|
+
assert.match(result.message, /Install `windsurf`/)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('keeps auth-aware checks for bespoke CLI providers', () => {
|
|
31
|
+
const result = checkCliProviderReady('claude-cli', {
|
|
32
|
+
resolveBinary: () => '/usr/local/bin/claude',
|
|
33
|
+
env: { ...process.env },
|
|
34
|
+
probeAuth: () => ({
|
|
35
|
+
authenticated: false,
|
|
36
|
+
errorMessage: 'Claude CLI is not authenticated.',
|
|
37
|
+
}),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
assert.equal(result.ok, false)
|
|
41
|
+
assert.equal(result.displayName, 'Claude Code CLI')
|
|
42
|
+
assert.equal(result.generic, false)
|
|
43
|
+
assert.equal(result.message, 'Claude CLI is not authenticated.')
|
|
44
|
+
})
|
|
45
|
+
})
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CLI_PROVIDER_METADATA_BY_ID,
|
|
3
|
+
isCliProviderId,
|
|
4
|
+
type CliAuthBackend,
|
|
5
|
+
type CliProviderMetadata,
|
|
6
|
+
} from '@/lib/providers/cli-provider-metadata'
|
|
7
|
+
import { buildCliEnv, probeCliAuth, resolveCliBinary, type AuthProbeResult } from '@/lib/providers/cli-utils'
|
|
8
|
+
|
|
9
|
+
export interface CliProviderReadyResult {
|
|
10
|
+
ok: boolean
|
|
11
|
+
message: string
|
|
12
|
+
providerId?: string
|
|
13
|
+
displayName?: string
|
|
14
|
+
binaryName?: string
|
|
15
|
+
binaryPath?: string
|
|
16
|
+
generic?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface CheckCliProviderReadyOptions {
|
|
20
|
+
cwd?: string
|
|
21
|
+
env?: NodeJS.ProcessEnv
|
|
22
|
+
resolveBinary?: (name: string) => string | null
|
|
23
|
+
probeAuth?: (
|
|
24
|
+
binary: string,
|
|
25
|
+
backend: CliAuthBackend,
|
|
26
|
+
env: NodeJS.ProcessEnv,
|
|
27
|
+
cwd?: string,
|
|
28
|
+
) => AuthProbeResult
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function missingBinaryMessage(meta: CliProviderMetadata): string {
|
|
32
|
+
return `${meta.displayName} is not installed. Install \`${meta.binaryName}\` and ensure it is on your PATH.`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function checkCliProviderReady(
|
|
36
|
+
providerId: string,
|
|
37
|
+
options: CheckCliProviderReadyOptions = {},
|
|
38
|
+
): CliProviderReadyResult {
|
|
39
|
+
if (!isCliProviderId(providerId)) {
|
|
40
|
+
return { ok: false, message: 'Unknown CLI provider.', providerId }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const meta = CLI_PROVIDER_METADATA_BY_ID[providerId]
|
|
44
|
+
const resolveBinary = options.resolveBinary || resolveCliBinary
|
|
45
|
+
const binaryPath = resolveBinary(meta.binaryName)
|
|
46
|
+
if (!binaryPath) {
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
message: missingBinaryMessage(meta),
|
|
50
|
+
providerId,
|
|
51
|
+
displayName: meta.displayName,
|
|
52
|
+
binaryName: meta.binaryName,
|
|
53
|
+
generic: meta.generic,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (meta.authBackend) {
|
|
58
|
+
const env = options.env || buildCliEnv()
|
|
59
|
+
const auth = (options.probeAuth || probeCliAuth)(binaryPath, meta.authBackend, env, options.cwd || process.cwd())
|
|
60
|
+
if (!auth.authenticated) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
message: auth.errorMessage || `${meta.displayName} is not configured.`,
|
|
64
|
+
providerId,
|
|
65
|
+
displayName: meta.displayName,
|
|
66
|
+
binaryName: meta.binaryName,
|
|
67
|
+
binaryPath,
|
|
68
|
+
generic: meta.generic,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
ok: true,
|
|
75
|
+
message: meta.authBackend
|
|
76
|
+
? `${meta.displayName} is installed and ready.`
|
|
77
|
+
: `${meta.displayName} binary is available. If it requires account setup, complete that in \`${meta.binaryName}\` before running agent turns.`,
|
|
78
|
+
providerId,
|
|
79
|
+
displayName: meta.displayName,
|
|
80
|
+
binaryName: meta.binaryName,
|
|
81
|
+
binaryPath,
|
|
82
|
+
generic: meta.generic,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -179,4 +179,10 @@ describe('provider-health', () => {
|
|
|
179
179
|
globalThis.fetch = originalFetch
|
|
180
180
|
}
|
|
181
181
|
})
|
|
182
|
+
|
|
183
|
+
it('skips extended CLI providers instead of treating them as HTTP providers', async () => {
|
|
184
|
+
const result = await providerHealth.pingProvider('aider-cli', undefined, undefined)
|
|
185
|
+
assert.equal(result.ok, true)
|
|
186
|
+
assert.equal(result.message, 'CLI provider - skipped.')
|
|
187
|
+
})
|
|
182
188
|
})
|
|
@@ -2,6 +2,7 @@ import { spawnSync } from 'child_process'
|
|
|
2
2
|
import { errorMessage, hmrSingleton, jitteredBackoff } from '@/lib/shared-utils'
|
|
3
3
|
import { upsertStoredItem, loadCollection } from './storage'
|
|
4
4
|
import { log } from './logger'
|
|
5
|
+
import { isCliProviderId } from '@/lib/providers/cli-provider-metadata'
|
|
5
6
|
|
|
6
7
|
const TAG = 'provider-health'
|
|
7
8
|
|
|
@@ -352,9 +353,8 @@ export async function pingProvider(
|
|
|
352
353
|
apiKey: string | undefined,
|
|
353
354
|
endpoint: string | undefined,
|
|
354
355
|
): Promise<{ ok: boolean; message: string }> {
|
|
355
|
-
const CLI_PROVIDERS = ['claude-cli', 'codex-cli', 'opencode-cli', 'gemini-cli', 'copilot-cli', 'droid-cli', 'cursor-cli', 'qwen-code-cli', 'goose']
|
|
356
356
|
const OPTIONAL_OPENAI_COMPATIBLE_KEY_PROVIDERS = new Set(['hermes'])
|
|
357
|
-
if (
|
|
357
|
+
if (isCliProviderId(provider)) return { ok: true, message: 'CLI provider - skipped.' }
|
|
358
358
|
|
|
359
359
|
try {
|
|
360
360
|
if (provider === 'anthropic') {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert/strict'
|
|
2
2
|
import { test } from 'node:test'
|
|
3
|
+
import { CLI_PROVIDER_METADATA } from './providers/cli-provider-metadata'
|
|
3
4
|
import { DEFAULT_AGENTS, getDefaultModelForProvider } from './setup-defaults'
|
|
4
5
|
|
|
5
6
|
// ---------------------------------------------------------------------------
|
|
@@ -48,6 +49,13 @@ test('getDefaultModelForProvider returns expected defaults for cursor, qwen, and
|
|
|
48
49
|
assert.equal(getDefaultModelForProvider('goose'), 'default')
|
|
49
50
|
})
|
|
50
51
|
|
|
52
|
+
test('every CLI provider has setup default agent coverage', () => {
|
|
53
|
+
for (const provider of CLI_PROVIDER_METADATA) {
|
|
54
|
+
assert.equal(getDefaultModelForProvider(provider.id), provider.defaultModel)
|
|
55
|
+
assert.ok(DEFAULT_AGENTS[provider.id].description.includes(provider.displayName))
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
51
59
|
test('getDefaultModelForProvider returns non-empty for ollama', () => {
|
|
52
60
|
const model = getDefaultModelForProvider('ollama')
|
|
53
61
|
assert.ok(model, 'ollama model should be truthy')
|