coder-link 0.0.9 → 0.1.0
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 +31 -13
- package/dist/cli.js +214 -76
- package/dist/cli.js.map +1 -1
- package/dist/lib/claude-code-manager.d.ts +8 -1
- package/dist/lib/claude-code-manager.d.ts.map +1 -1
- package/dist/lib/claude-code-manager.js +84 -118
- package/dist/lib/claude-code-manager.js.map +1 -1
- package/dist/lib/codex-manager.d.ts +34 -0
- package/dist/lib/codex-manager.d.ts.map +1 -0
- package/dist/lib/codex-manager.js +223 -0
- package/dist/lib/codex-manager.js.map +1 -0
- package/dist/lib/config-io.d.ts +5 -0
- package/dist/lib/config-io.d.ts.map +1 -0
- package/dist/lib/config-io.js +50 -0
- package/dist/lib/config-io.js.map +1 -0
- package/dist/lib/crush-manager.d.ts +1 -2
- package/dist/lib/crush-manager.d.ts.map +1 -1
- package/dist/lib/crush-manager.js +20 -58
- package/dist/lib/crush-manager.js.map +1 -1
- package/dist/lib/factory-droid-manager.d.ts +8 -2
- package/dist/lib/factory-droid-manager.d.ts.map +1 -1
- package/dist/lib/factory-droid-manager.js +193 -260
- package/dist/lib/factory-droid-manager.js.map +1 -1
- package/dist/lib/kimi-manager.d.ts.map +1 -1
- package/dist/lib/kimi-manager.js +19 -30
- package/dist/lib/kimi-manager.js.map +1 -1
- package/dist/lib/opencode-manager.d.ts +1 -1
- package/dist/lib/opencode-manager.d.ts.map +1 -1
- package/dist/lib/opencode-manager.js +187 -63
- package/dist/lib/opencode-manager.js.map +1 -1
- package/dist/lib/pi-manager.d.ts +0 -1
- package/dist/lib/pi-manager.d.ts.map +1 -1
- package/dist/lib/pi-manager.js +18 -41
- package/dist/lib/pi-manager.js.map +1 -1
- package/dist/lib/provider-registry.d.ts +200 -0
- package/dist/lib/provider-registry.d.ts.map +1 -0
- package/dist/lib/provider-registry.js +581 -0
- package/dist/lib/provider-registry.js.map +1 -0
- package/dist/lib/tool-manager.d.ts +42 -3
- package/dist/lib/tool-manager.d.ts.map +1 -1
- package/dist/lib/tool-manager.js +155 -34
- package/dist/lib/tool-manager.js.map +1 -1
- package/dist/mcp-services.d.ts.map +1 -1
- package/dist/mcp-services.js +57 -3
- package/dist/mcp-services.js.map +1 -1
- package/dist/{menu.d.ts → menu/main-menu.d.ts} +1 -1
- package/dist/menu/main-menu.d.ts.map +1 -0
- package/dist/menu/main-menu.js +90 -0
- package/dist/menu/main-menu.js.map +1 -0
- package/dist/menu/mcp-menu.d.ts +2 -0
- package/dist/menu/mcp-menu.d.ts.map +1 -0
- package/dist/menu/mcp-menu.js +136 -0
- package/dist/menu/mcp-menu.js.map +1 -0
- package/dist/menu/provider-menu.d.ts +4 -0
- package/dist/menu/provider-menu.d.ts.map +1 -0
- package/dist/menu/provider-menu.js +623 -0
- package/dist/menu/provider-menu.js.map +1 -0
- package/dist/menu/shared.d.ts +17 -0
- package/dist/menu/shared.d.ts.map +1 -0
- package/dist/menu/shared.js +255 -0
- package/dist/menu/shared.js.map +1 -0
- package/dist/menu/system-menu.d.ts +3 -0
- package/dist/menu/system-menu.d.ts.map +1 -0
- package/dist/menu/system-menu.js +111 -0
- package/dist/menu/system-menu.js.map +1 -0
- package/dist/menu/tool-menu.d.ts +3 -0
- package/dist/menu/tool-menu.d.ts.map +1 -0
- package/dist/menu/tool-menu.js +619 -0
- package/dist/menu/tool-menu.js.map +1 -0
- package/dist/utils/api-test.d.ts +6 -0
- package/dist/utils/api-test.d.ts.map +1 -1
- package/dist/utils/api-test.js +66 -0
- package/dist/utils/api-test.js.map +1 -1
- package/dist/utils/brand.d.ts.map +1 -1
- package/dist/utils/brand.js +14 -0
- package/dist/utils/brand.js.map +1 -1
- package/dist/utils/config.d.ts +29 -4
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +194 -68
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/providers.d.ts +32 -0
- package/dist/utils/providers.d.ts.map +1 -0
- package/dist/utils/providers.js +63 -0
- package/dist/utils/providers.js.map +1 -0
- package/dist/wizard.d.ts.map +1 -1
- package/dist/wizard.js +123 -35
- package/dist/wizard.js.map +1 -1
- package/package.json +1 -1
- package/dist/menu.d.ts.map +0 -1
- package/dist/menu.js +0 -1226
- package/dist/menu.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ A CLI that links coding tools to models from multiple providers.
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **Multi-Provider Support**: GLM Coding Plan (Global/China), Kimi 2.5, OpenRouter, and
|
|
7
|
+
- **Multi-Provider Support**: GLM Coding Plan (Global/China), Kimi 2.5, OpenRouter, NVIDIA, and LM Studio (local)
|
|
8
8
|
- **Interactive Wizard**: Friendly onboarding guidance on first launch
|
|
9
9
|
- **Tool Management**: Automatically configures CLI tools with your API credentials
|
|
10
10
|
- **MCP Configuration**: Easily manage Model Context Protocol services
|
|
@@ -12,6 +12,17 @@ A CLI that links coding tools to models from multiple providers.
|
|
|
12
12
|
- **Internationalization**: Chinese and English bilingual interface
|
|
13
13
|
- **Status & Health Check**: View current configuration and diagnose issues quickly
|
|
14
14
|
|
|
15
|
+
## Screenshots
|
|
16
|
+
|
|
17
|
+
### Main Menu
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
### Provider Configuration
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
### Coding Tools
|
|
24
|
+

|
|
25
|
+
|
|
15
26
|
## Supported Coding Tools
|
|
16
27
|
|
|
17
28
|
- Claude Code
|
|
@@ -34,13 +45,20 @@ A CLI that links coding tools to models from multiple providers.
|
|
|
34
45
|
npx coder-link
|
|
35
46
|
```
|
|
36
47
|
|
|
37
|
-
#### Option 2: Install globally
|
|
48
|
+
#### Option 2: Install globally with npm
|
|
38
49
|
|
|
39
50
|
```bash
|
|
40
51
|
npm install -g coder-link
|
|
41
52
|
coder-link
|
|
42
53
|
```
|
|
43
54
|
|
|
55
|
+
#### Option 3: Install globally with bun
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
bun add -g coder-link
|
|
59
|
+
coder-link
|
|
60
|
+
```
|
|
61
|
+
|
|
44
62
|
### Complete the Wizard
|
|
45
63
|
|
|
46
64
|
Once you enter the wizard UI, use Up/Down arrow keys to navigate and press Enter to confirm each action, following the guided initialization flow.
|
|
@@ -84,14 +102,14 @@ coder-link lang --help # Show help for language commands
|
|
|
84
102
|
### API key management
|
|
85
103
|
```bash
|
|
86
104
|
coder-link auth # Interactively set key
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
105
|
+
coder-link auth glm_coding_plan_global <token> # Choose Global plan and set key
|
|
106
|
+
coder-link auth glm_coding_plan_china <token> # Choose China plan and set key
|
|
107
|
+
coder-link auth kimi <token> # Set Kimi API key
|
|
108
|
+
coder-link auth openrouter <token> # Set OpenRouter API key
|
|
109
|
+
coder-link auth nvidia <token> # Set NVIDIA API key
|
|
110
|
+
coder-link auth revoke # Delete the saved key
|
|
111
|
+
coder-link auth reload <tool> # Reload config into a tool
|
|
112
|
+
coder-link auth --help # Show help for auth commands
|
|
95
113
|
```
|
|
96
114
|
|
|
97
115
|
### Tool management
|
|
@@ -128,12 +146,12 @@ api_key: your-api-key-here # API key
|
|
|
128
146
|
|
|
129
147
|
### GLM Coding Plan (Global)
|
|
130
148
|
- **Base URL**: `https://api.z.ai/api/anthropic` (Claude Code) or `https://api.z.ai/api/coding/paas/v4` (others)
|
|
131
|
-
- **Models**: GLM-4.7, GLM-4.6, GLM-4.5-air
|
|
149
|
+
- **Models**: GLM-5, GLM-4.7, GLM-4.6, GLM-4.5-air
|
|
132
150
|
- Get your API key from [Z.AI Open Platform](https://z.ai/model-api)
|
|
133
151
|
|
|
134
152
|
### GLM Coding Plan (China)
|
|
135
153
|
- **Base URL**: `https://open.bigmodel.cn/api/anthropic` (Claude Code) or `https://open.bigmodel.cn/api/coding/paas/v4` (others)
|
|
136
|
-
- **Models**: GLM-4.7, GLM-4.6, GLM-4.5-air
|
|
154
|
+
- **Models**: GLM-5, GLM-4.7, GLM-4.6, GLM-4.5-air
|
|
137
155
|
- Get your API key from [Z.AI Open Platform](https://z.ai/model-api)
|
|
138
156
|
|
|
139
157
|
### OpenRouter
|
|
@@ -197,4 +215,4 @@ We welcome contributions! Please feel free to submit issues or pull requests.
|
|
|
197
215
|
|
|
198
216
|
## License
|
|
199
217
|
|
|
200
|
-
|
|
218
|
+
MIT
|
package/dist/cli.js
CHANGED
|
@@ -6,20 +6,50 @@ import { logger } from './utils/logger.js';
|
|
|
6
6
|
import { i18n } from './utils/i18n.js';
|
|
7
7
|
import { configManager } from './utils/config.js';
|
|
8
8
|
import { toolManager } from './lib/tool-manager.js';
|
|
9
|
-
import { runMenu } from './menu.js';
|
|
9
|
+
import { runMenu } from './menu/main-menu.js';
|
|
10
10
|
import { runWizard } from './wizard.js';
|
|
11
11
|
import { statusIndicator, planLabel, planLabelColored, toolLabel } from './utils/brand.js';
|
|
12
12
|
import { setOutputFormat, getOutputFormat, printData, printError } from './utils/output.js';
|
|
13
|
+
import { PROVIDER_PLAN_VALUES } from './utils/providers.js';
|
|
14
|
+
import { BUILTIN_MCP_SERVICES } from './mcp-services.js';
|
|
13
15
|
const program = new Command();
|
|
14
16
|
// Use persisted UI language for all commands
|
|
15
17
|
i18n.setLang(configManager.getLang());
|
|
16
18
|
// Global options
|
|
17
19
|
const jsonOption = new Option('-j, --json', 'Output as JSON for programmatic use');
|
|
18
20
|
const formatOption = new Option('-f, --format <format>', 'Output format').choices(['pretty', 'json']).default('pretty');
|
|
21
|
+
function getMcpCapableTools() {
|
|
22
|
+
return getVisibleTools().filter((tool) => toolManager.getCapabilities(tool).supportsMcp);
|
|
23
|
+
}
|
|
24
|
+
function getVisibleTools() {
|
|
25
|
+
const enabled = new Set(configManager.getEnabledTools());
|
|
26
|
+
const visible = toolManager.getSupportedTools().filter((tool) => enabled.has(tool));
|
|
27
|
+
return visible.length ? visible : toolManager.getSupportedTools();
|
|
28
|
+
}
|
|
29
|
+
function resolveMcpTool(explicitTool) {
|
|
30
|
+
const mcpTools = getMcpCapableTools();
|
|
31
|
+
if (mcpTools.length === 0) {
|
|
32
|
+
throw new Error('No tools support MCP management');
|
|
33
|
+
}
|
|
34
|
+
if (explicitTool) {
|
|
35
|
+
if (!toolManager.isSupportedTool(explicitTool)) {
|
|
36
|
+
throw new Error(`Unsupported tool: ${explicitTool}`);
|
|
37
|
+
}
|
|
38
|
+
if (!toolManager.getCapabilities(explicitTool).supportsMcp) {
|
|
39
|
+
throw new Error(`${toolLabel(explicitTool)} does not support MCP management`);
|
|
40
|
+
}
|
|
41
|
+
return explicitTool;
|
|
42
|
+
}
|
|
43
|
+
const last = configManager.getLastUsedTool();
|
|
44
|
+
if (last && toolManager.isSupportedTool(last) && toolManager.getCapabilities(last).supportsMcp) {
|
|
45
|
+
return last;
|
|
46
|
+
}
|
|
47
|
+
return mcpTools[0];
|
|
48
|
+
}
|
|
19
49
|
program
|
|
20
50
|
.name('coder-link')
|
|
21
51
|
.description('Coder Link — Connect coding tools to any model/provider')
|
|
22
|
-
.version('0.0.
|
|
52
|
+
.version('0.0.9')
|
|
23
53
|
.addOption(formatOption)
|
|
24
54
|
.hook('preAction', (thisCommand) => {
|
|
25
55
|
const opts = thisCommand.opts();
|
|
@@ -34,15 +64,17 @@ program
|
|
|
34
64
|
.option('-s, --shell <shell>', 'Shell type (bash, zsh, fish, pwsh)', 'bash')
|
|
35
65
|
.action(async (options) => {
|
|
36
66
|
const shell = options.shell;
|
|
67
|
+
const providerValues = PROVIDER_PLAN_VALUES.join(' ');
|
|
68
|
+
const providerValuesQuoted = PROVIDER_PLAN_VALUES.map((p) => `'${p}'`).join(', ');
|
|
37
69
|
let script = '';
|
|
38
70
|
switch (shell) {
|
|
39
71
|
case 'bash':
|
|
40
72
|
script = `#!/bin/bash
|
|
41
73
|
_coder_link_completion() {
|
|
42
74
|
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
43
|
-
local commands="init auth lang tools doctor status completion"
|
|
44
|
-
local tools="claude-code opencode crush factory-droid kimi amp pi"
|
|
45
|
-
local providers="
|
|
75
|
+
local commands="init auth lang tools mcp doctor status completion"
|
|
76
|
+
local tools="claude-code opencode crush factory-droid kimi amp pi codex"
|
|
77
|
+
local providers="${providerValues}"
|
|
46
78
|
local services="filesystem github"
|
|
47
79
|
|
|
48
80
|
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
@@ -51,7 +83,7 @@ _coder_link_completion() {
|
|
|
51
83
|
local subcommand="\${COMP_WORDS[1]}"
|
|
52
84
|
case "$subcommand" in
|
|
53
85
|
auth)
|
|
54
|
-
COMPREPLY=($(compgen -W "
|
|
86
|
+
COMPREPLY=($(compgen -W "${providerValues} revoke reload" -- "$cur"))
|
|
55
87
|
;;
|
|
56
88
|
tools)
|
|
57
89
|
if [[ "\${COMP_WORDS[2]}" == "install" || "\${COMP_WORDS[2]}" == "uninstall" ]]; then
|
|
@@ -86,9 +118,9 @@ _coder_link() {
|
|
|
86
118
|
local curcontext="$curcontext" state line
|
|
87
119
|
typeset -A opt_args
|
|
88
120
|
|
|
89
|
-
local commands=(init auth lang tools doctor status completion)
|
|
90
|
-
local tools=(claude-code opencode crush factory-droid kimi amp pi)
|
|
91
|
-
local providers=(
|
|
121
|
+
local commands=(init auth lang tools mcp doctor status completion)
|
|
122
|
+
local tools=(claude-code opencode crush factory-droid kimi amp pi codex)
|
|
123
|
+
local providers=(${PROVIDER_PLAN_VALUES.join(' ')})
|
|
92
124
|
local services=(filesystem github)
|
|
93
125
|
|
|
94
126
|
_arguments -C \
|
|
@@ -135,11 +167,7 @@ complete -c coder-link -n "__fish_use_subcommand" -a "status" -d "Show current s
|
|
|
135
167
|
complete -c coder-link -n "__fish_use_subcommand" -a "completion" -d "Generate shell completion"
|
|
136
168
|
|
|
137
169
|
# Auth subcommands
|
|
138
|
-
complete -c coder-link -n "__fish_seen_subcommand_from auth" -a "
|
|
139
|
-
complete -c coder-link -n "__fish_seen_subcommand_from auth" -a "glm_coding_plan_china"
|
|
140
|
-
complete -c coder-link -n "__fish_seen_subcommand_from auth" -a "kimi"
|
|
141
|
-
complete -c coder-link -n "__fish_seen_subcommand_from auth" -a "openrouter"
|
|
142
|
-
complete -c coder-link -n "__fish_seen_subcommand_from auth" -a "nvidia"
|
|
170
|
+
${PROVIDER_PLAN_VALUES.map((provider) => `complete -c coder-link -n "__fish_seen_subcommand_from auth" -a "${provider}"`).join('\n')}
|
|
143
171
|
complete -c coder-link -n "__fish_seen_subcommand_from auth" -a "revoke"
|
|
144
172
|
`;
|
|
145
173
|
break;
|
|
@@ -149,8 +177,8 @@ complete -c coder-link -n "__fish_seen_subcommand_from auth" -a "revoke"
|
|
|
149
177
|
param($wordToComplete, $commandAst, $cursorPosition)
|
|
150
178
|
|
|
151
179
|
$commands = @('init', 'auth', 'lang', 'tools', 'mcp', 'doctor', 'status', 'completion')
|
|
152
|
-
$tools = @('claude-code', 'opencode', 'crush', 'factory-droid', 'kimi', 'amp', 'pi')
|
|
153
|
-
$providers = @(
|
|
180
|
+
$tools = @('claude-code', 'opencode', 'crush', 'factory-droid', 'kimi', 'amp', 'pi', 'codex')
|
|
181
|
+
$providers = @(${providerValuesQuoted})
|
|
154
182
|
$services = @('filesystem', 'github')
|
|
155
183
|
|
|
156
184
|
$commandElements = $commandAst.CommandElements | Select-Object -Skip 1
|
|
@@ -337,6 +365,73 @@ program
|
|
|
337
365
|
printError(error instanceof Error ? error.message : String(error), 'Run "coder-link auth" for interactive setup');
|
|
338
366
|
process.exit(1);
|
|
339
367
|
}
|
|
368
|
+
}))
|
|
369
|
+
.addCommand(new Command('alibaba [token]')
|
|
370
|
+
.description('Set Alibaba Coding Plan API key (monthly plan)')
|
|
371
|
+
.action(async (token) => {
|
|
372
|
+
try {
|
|
373
|
+
if (!token) {
|
|
374
|
+
const { apiKey } = await inquirer.prompt([{
|
|
375
|
+
type: 'password',
|
|
376
|
+
name: 'apiKey',
|
|
377
|
+
message: i18n.t('wizard.enter_api_key'),
|
|
378
|
+
validate: (input) => input.trim().length > 0 || 'API key cannot be empty'
|
|
379
|
+
}]);
|
|
380
|
+
token = apiKey;
|
|
381
|
+
}
|
|
382
|
+
configManager.setAuth('alibaba', token.trim());
|
|
383
|
+
console.log(i18n.t('auth.set_success'));
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
logger.logError('auth.set', error);
|
|
387
|
+
printError(error instanceof Error ? error.message : String(error), 'Run "coder-link auth" for interactive setup');
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
}))
|
|
391
|
+
.addCommand(new Command('alibaba_api [token]')
|
|
392
|
+
.description('Set Alibaba Model Studio API key (Singapore endpoint)')
|
|
393
|
+
.alias('alibaba-api')
|
|
394
|
+
.action(async (token) => {
|
|
395
|
+
try {
|
|
396
|
+
if (!token) {
|
|
397
|
+
const { apiKey } = await inquirer.prompt([{
|
|
398
|
+
type: 'password',
|
|
399
|
+
name: 'apiKey',
|
|
400
|
+
message: i18n.t('wizard.enter_api_key'),
|
|
401
|
+
validate: (input) => input.trim().length > 0 || 'API key cannot be empty'
|
|
402
|
+
}]);
|
|
403
|
+
token = apiKey;
|
|
404
|
+
}
|
|
405
|
+
configManager.setAuth('alibaba_api', token.trim());
|
|
406
|
+
console.log(i18n.t('auth.set_success'));
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
logger.logError('auth.set', error);
|
|
410
|
+
printError(error instanceof Error ? error.message : String(error), 'Run "coder-link auth" for interactive setup');
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
|
413
|
+
}))
|
|
414
|
+
.addCommand(new Command('lmstudio [token]')
|
|
415
|
+
.description('Set LM Studio API key (optional for local use)')
|
|
416
|
+
.action(async (token) => {
|
|
417
|
+
try {
|
|
418
|
+
if (!token) {
|
|
419
|
+
const { apiKey } = await inquirer.prompt([{
|
|
420
|
+
type: 'password',
|
|
421
|
+
name: 'apiKey',
|
|
422
|
+
message: `${i18n.t('wizard.enter_api_key')} (leave empty for local LM Studio)`,
|
|
423
|
+
}]);
|
|
424
|
+
token = apiKey;
|
|
425
|
+
}
|
|
426
|
+
const normalized = token.trim() || 'lmstudio';
|
|
427
|
+
configManager.setAuth('lmstudio', normalized);
|
|
428
|
+
console.log(i18n.t('auth.set_success'));
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
logger.logError('auth.set', error);
|
|
432
|
+
printError(error instanceof Error ? error.message : String(error), 'Run "coder-link auth" for interactive setup');
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
340
435
|
}))
|
|
341
436
|
.addCommand(new Command('revoke')
|
|
342
437
|
.description('Delete saved API key')
|
|
@@ -386,7 +481,7 @@ program
|
|
|
386
481
|
.addCommand(new Command('list')
|
|
387
482
|
.description('List all supported tools')
|
|
388
483
|
.action(async () => {
|
|
389
|
-
const tools =
|
|
484
|
+
const tools = getVisibleTools();
|
|
390
485
|
const toolData = [];
|
|
391
486
|
for (const tool of tools) {
|
|
392
487
|
const status = await toolManager.isConfigured(tool);
|
|
@@ -440,76 +535,82 @@ program
|
|
|
440
535
|
.addCommand(new Command('list')
|
|
441
536
|
.description('List available MCP services')
|
|
442
537
|
.action(async () => {
|
|
443
|
-
const services =
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
538
|
+
const services = BUILTIN_MCP_SERVICES.map((service) => ({
|
|
539
|
+
id: service.id,
|
|
540
|
+
name: service.name,
|
|
541
|
+
description: service.description || '',
|
|
542
|
+
protocol: service.protocol,
|
|
543
|
+
}));
|
|
544
|
+
const mcpTools = getMcpCapableTools();
|
|
447
545
|
if (getOutputFormat().isJson) {
|
|
448
|
-
printData({ services });
|
|
546
|
+
printData({ services, tools: mcpTools });
|
|
449
547
|
}
|
|
450
548
|
else {
|
|
451
549
|
console.log(i18n.t('mcp.list_header'));
|
|
452
550
|
for (const service of services) {
|
|
453
|
-
console.log(` ${service.id}: ${service.name} - ${service.description}`);
|
|
551
|
+
console.log(` ${service.id}: ${service.name} - ${service.description} [${service.protocol}]`);
|
|
454
552
|
}
|
|
553
|
+
console.log(`\nMCP-capable tools: ${mcpTools.map((t) => toolLabel(t)).join(', ')}`);
|
|
455
554
|
}
|
|
456
555
|
}))
|
|
457
556
|
.addCommand(new Command('installed')
|
|
458
557
|
.description('List installed MCP services')
|
|
459
|
-
.
|
|
460
|
-
|
|
461
|
-
const
|
|
558
|
+
.option('-t, --tool <tool>', 'Target tool (defaults to last used MCP-capable tool)')
|
|
559
|
+
.action(async (options) => {
|
|
560
|
+
const targetTool = resolveMcpTool(options.tool);
|
|
561
|
+
const installed = await toolManager.getInstalledMCPs(targetTool);
|
|
462
562
|
if (getOutputFormat().isJson) {
|
|
463
|
-
printData({ installed });
|
|
563
|
+
printData({ tool: targetTool, installed });
|
|
464
564
|
}
|
|
465
565
|
else {
|
|
466
|
-
console.log(i18n.t('mcp.installed_header'));
|
|
566
|
+
console.log(`${i18n.t('mcp.installed_header')} (${toolLabel(targetTool)})`);
|
|
467
567
|
for (const id of installed) {
|
|
468
568
|
console.log(` ${id}`);
|
|
469
569
|
}
|
|
570
|
+
if (installed.length === 0) {
|
|
571
|
+
console.log(' (none)');
|
|
572
|
+
}
|
|
470
573
|
}
|
|
471
574
|
}))
|
|
472
575
|
.addCommand(new Command('install <service>')
|
|
473
576
|
.description('Install an MCP service')
|
|
474
|
-
.
|
|
577
|
+
.option('-t, --tool <tool>', 'Target tool (defaults to last used MCP-capable tool)')
|
|
578
|
+
.action(async (serviceId, options) => {
|
|
475
579
|
try {
|
|
476
|
-
const
|
|
477
|
-
const { toolManager } = await import('./lib/tool-manager.js');
|
|
478
|
-
const { configManager } = await import('./utils/config.js');
|
|
580
|
+
const targetTool = resolveMcpTool(options.tool);
|
|
479
581
|
const auth = configManager.getAuth();
|
|
480
|
-
|
|
481
|
-
|
|
582
|
+
const service = BUILTIN_MCP_SERVICES.find((s) => s.id === serviceId);
|
|
583
|
+
if (!service) {
|
|
584
|
+
throw new Error(`Unknown MCP service: ${serviceId}`);
|
|
585
|
+
}
|
|
586
|
+
if (!auth.plan) {
|
|
587
|
+
printError(i18n.t('auth.not_set'), 'Run "coder-link auth <provider> <token>" first to select a provider');
|
|
482
588
|
process.exit(1);
|
|
483
589
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
command: 'npx',
|
|
490
|
-
args: [`-y`, `@modelcontextprotocol/server-${serviceId}`, '/'],
|
|
491
|
-
requiresAuth: false
|
|
492
|
-
};
|
|
493
|
-
await toolManager.installMCP('kimi', service, auth.apiKey, auth.plan);
|
|
494
|
-
console.log(i18n.t('mcp.install_success', { service: serviceId }));
|
|
590
|
+
if (service.requiresAuth && !auth.apiKey) {
|
|
591
|
+
throw new Error(`Provider API key is required to install "${serviceId}"`);
|
|
592
|
+
}
|
|
593
|
+
await toolManager.installMCP(targetTool, service, auth.apiKey || '', auth.plan);
|
|
594
|
+
console.log(`${i18n.t('mcp.install_success', { service: serviceId })} (${toolLabel(targetTool)})`);
|
|
495
595
|
}
|
|
496
596
|
catch (error) {
|
|
497
597
|
logger.logError('mcp.install', error);
|
|
498
|
-
printError(error instanceof Error ? error.message : String(error), 'Check
|
|
598
|
+
printError(error instanceof Error ? error.message : String(error), 'Check provider auth and tool compatibility');
|
|
499
599
|
process.exit(1);
|
|
500
600
|
}
|
|
501
601
|
}))
|
|
502
602
|
.addCommand(new Command('uninstall <service>')
|
|
503
603
|
.description('Uninstall an MCP service')
|
|
504
|
-
.
|
|
604
|
+
.option('-t, --tool <tool>', 'Target tool (defaults to last used MCP-capable tool)')
|
|
605
|
+
.action(async (serviceId, options) => {
|
|
505
606
|
try {
|
|
506
|
-
const
|
|
507
|
-
await
|
|
508
|
-
console.log(i18n.t('mcp.uninstall_success', { service: serviceId }));
|
|
607
|
+
const targetTool = resolveMcpTool(options.tool);
|
|
608
|
+
await toolManager.uninstallMCP(targetTool, serviceId);
|
|
609
|
+
console.log(`${i18n.t('mcp.uninstall_success', { service: serviceId })} (${toolLabel(targetTool)})`);
|
|
509
610
|
}
|
|
510
611
|
catch (error) {
|
|
511
612
|
logger.logError('mcp.uninstall', error);
|
|
512
|
-
printError(error instanceof Error ? error.message : String(error), 'Service may not be installed');
|
|
613
|
+
printError(error instanceof Error ? error.message : String(error), 'Service may not be installed for the selected tool');
|
|
513
614
|
process.exit(1);
|
|
514
615
|
}
|
|
515
616
|
}));
|
|
@@ -521,14 +622,19 @@ program
|
|
|
521
622
|
.action(async (options) => {
|
|
522
623
|
try {
|
|
523
624
|
const { plan, apiKey } = configManager.getAuth();
|
|
524
|
-
const tools =
|
|
625
|
+
const tools = getVisibleTools();
|
|
525
626
|
const toolStatuses = [];
|
|
526
627
|
for (const tool of tools) {
|
|
527
|
-
const
|
|
528
|
-
|
|
628
|
+
const caps = toolManager.getCapabilities(tool);
|
|
629
|
+
const status = caps.supportsProviderConfig ? await toolManager.isConfigured(tool) : false;
|
|
630
|
+
toolStatuses.push({ tool, configured: status, capabilities: caps });
|
|
631
|
+
}
|
|
632
|
+
const mcpByTool = {};
|
|
633
|
+
for (const tool of tools) {
|
|
634
|
+
if (!toolManager.getCapabilities(tool).supportsMcp)
|
|
635
|
+
continue;
|
|
636
|
+
mcpByTool[tool] = await toolManager.getInstalledMCPs(tool);
|
|
529
637
|
}
|
|
530
|
-
const { kimiManager } = await import('./lib/kimi-manager.js');
|
|
531
|
-
const mcpInstalled = kimiManager.getInstalledMCPs();
|
|
532
638
|
if (options.json) {
|
|
533
639
|
setOutputFormat('json');
|
|
534
640
|
printData({
|
|
@@ -536,7 +642,7 @@ program
|
|
|
536
642
|
currentProvider: plan || null,
|
|
537
643
|
hasApiKey: !!apiKey,
|
|
538
644
|
tools: toolStatuses,
|
|
539
|
-
mcps:
|
|
645
|
+
mcps: mcpByTool
|
|
540
646
|
});
|
|
541
647
|
}
|
|
542
648
|
else {
|
|
@@ -552,15 +658,25 @@ program
|
|
|
552
658
|
}
|
|
553
659
|
console.log('\n' + i18n.t('doctor.tools_header'));
|
|
554
660
|
for (const t of toolStatuses) {
|
|
555
|
-
|
|
661
|
+
const status = t.capabilities.supportsProviderConfig ? statusIndicator(t.configured) : '○';
|
|
662
|
+
const suffix = t.capabilities.supportsProviderConfig ? '' : ' (launch only)';
|
|
663
|
+
console.log(` ${status} ${toolLabel(t.tool)}${suffix}`);
|
|
556
664
|
}
|
|
557
665
|
console.log('\n' + i18n.t('doctor.mcp_header'));
|
|
558
|
-
|
|
666
|
+
const entries = Object.entries(mcpByTool);
|
|
667
|
+
if (entries.length === 0) {
|
|
559
668
|
console.log(` ${i18n.t('doctor.none')}`);
|
|
560
669
|
}
|
|
561
670
|
else {
|
|
562
|
-
|
|
563
|
-
|
|
671
|
+
let hasAny = false;
|
|
672
|
+
for (const [tool, services] of entries) {
|
|
673
|
+
if (services.length === 0)
|
|
674
|
+
continue;
|
|
675
|
+
hasAny = true;
|
|
676
|
+
console.log(` ${toolLabel(tool)}: ${services.join(', ')}`);
|
|
677
|
+
}
|
|
678
|
+
if (!hasAny) {
|
|
679
|
+
console.log(` ${i18n.t('doctor.none')}`);
|
|
564
680
|
}
|
|
565
681
|
}
|
|
566
682
|
}
|
|
@@ -581,19 +697,33 @@ program
|
|
|
581
697
|
const { plan, apiKey } = configManager.getAuth();
|
|
582
698
|
const hasProvider = !!(plan && apiKey);
|
|
583
699
|
// Tool status summary
|
|
584
|
-
const tools =
|
|
700
|
+
const tools = getVisibleTools();
|
|
585
701
|
let configured = 0;
|
|
586
|
-
let total =
|
|
587
|
-
|
|
702
|
+
let total = 0;
|
|
703
|
+
const toolDetails = [];
|
|
588
704
|
for (const tool of tools) {
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
705
|
+
const caps = toolManager.getCapabilities(tool);
|
|
706
|
+
const isConfigured = caps.supportsProviderConfig ? await toolManager.isConfigured(tool) : false;
|
|
707
|
+
if (caps.supportsProviderConfig) {
|
|
708
|
+
total++;
|
|
709
|
+
if (isConfigured)
|
|
710
|
+
configured++;
|
|
711
|
+
}
|
|
712
|
+
toolDetails.push({
|
|
713
|
+
tool,
|
|
714
|
+
label: toolLabel(tool),
|
|
715
|
+
configured: isConfigured,
|
|
716
|
+
supportsProviderConfig: caps.supportsProviderConfig,
|
|
717
|
+
});
|
|
593
718
|
}
|
|
594
719
|
// MCP status
|
|
595
|
-
const
|
|
596
|
-
const
|
|
720
|
+
const mcpByTool = {};
|
|
721
|
+
for (const tool of tools) {
|
|
722
|
+
if (!toolManager.getCapabilities(tool).supportsMcp)
|
|
723
|
+
continue;
|
|
724
|
+
mcpByTool[tool] = await toolManager.getInstalledMCPs(tool);
|
|
725
|
+
}
|
|
726
|
+
const mcpTotal = Object.values(mcpByTool).reduce((sum, ids) => sum + ids.length, 0);
|
|
597
727
|
if (options.json) {
|
|
598
728
|
setOutputFormat('json');
|
|
599
729
|
printData({
|
|
@@ -608,8 +738,8 @@ program
|
|
|
608
738
|
details: toolDetails
|
|
609
739
|
},
|
|
610
740
|
mcps: {
|
|
611
|
-
count:
|
|
612
|
-
|
|
741
|
+
count: mcpTotal,
|
|
742
|
+
byTool: mcpByTool
|
|
613
743
|
},
|
|
614
744
|
configPath: configManager.configPath
|
|
615
745
|
});
|
|
@@ -628,11 +758,19 @@ program
|
|
|
628
758
|
console.log(`Tools: ${toolStatus} ${configured}/${total} configured`);
|
|
629
759
|
// Show tool breakdown
|
|
630
760
|
for (const t of toolDetails) {
|
|
631
|
-
const status = t.
|
|
632
|
-
|
|
761
|
+
const status = t.supportsProviderConfig
|
|
762
|
+
? (t.configured ? chalk.green('●') : chalk.gray('○'))
|
|
763
|
+
: chalk.gray('○');
|
|
764
|
+
const suffix = t.supportsProviderConfig ? '' : chalk.gray(' (launch only)');
|
|
765
|
+
console.log(` ${status} ${t.label}${suffix}`);
|
|
766
|
+
}
|
|
767
|
+
const mcpStatus = mcpTotal > 0 ? chalk.green('●') : chalk.gray('○');
|
|
768
|
+
console.log(`MCPs: ${mcpStatus} ${mcpTotal} installed`);
|
|
769
|
+
for (const [tool, services] of Object.entries(mcpByTool)) {
|
|
770
|
+
if (services.length === 0)
|
|
771
|
+
continue;
|
|
772
|
+
console.log(` ${toolLabel(tool)}: ${services.join(', ')}`);
|
|
633
773
|
}
|
|
634
|
-
const mcpStatus = mcps.length > 0 ? chalk.green('●') : chalk.gray('○');
|
|
635
|
-
console.log(`MCPs: ${mcpStatus} ${mcps.length} installed`);
|
|
636
774
|
console.log(`\nConfig: ${chalk.gray(configManager.configPath)}`);
|
|
637
775
|
}
|
|
638
776
|
}
|