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.
Files changed (91) hide show
  1. package/README.md +31 -13
  2. package/dist/cli.js +214 -76
  3. package/dist/cli.js.map +1 -1
  4. package/dist/lib/claude-code-manager.d.ts +8 -1
  5. package/dist/lib/claude-code-manager.d.ts.map +1 -1
  6. package/dist/lib/claude-code-manager.js +84 -118
  7. package/dist/lib/claude-code-manager.js.map +1 -1
  8. package/dist/lib/codex-manager.d.ts +34 -0
  9. package/dist/lib/codex-manager.d.ts.map +1 -0
  10. package/dist/lib/codex-manager.js +223 -0
  11. package/dist/lib/codex-manager.js.map +1 -0
  12. package/dist/lib/config-io.d.ts +5 -0
  13. package/dist/lib/config-io.d.ts.map +1 -0
  14. package/dist/lib/config-io.js +50 -0
  15. package/dist/lib/config-io.js.map +1 -0
  16. package/dist/lib/crush-manager.d.ts +1 -2
  17. package/dist/lib/crush-manager.d.ts.map +1 -1
  18. package/dist/lib/crush-manager.js +20 -58
  19. package/dist/lib/crush-manager.js.map +1 -1
  20. package/dist/lib/factory-droid-manager.d.ts +8 -2
  21. package/dist/lib/factory-droid-manager.d.ts.map +1 -1
  22. package/dist/lib/factory-droid-manager.js +193 -260
  23. package/dist/lib/factory-droid-manager.js.map +1 -1
  24. package/dist/lib/kimi-manager.d.ts.map +1 -1
  25. package/dist/lib/kimi-manager.js +19 -30
  26. package/dist/lib/kimi-manager.js.map +1 -1
  27. package/dist/lib/opencode-manager.d.ts +1 -1
  28. package/dist/lib/opencode-manager.d.ts.map +1 -1
  29. package/dist/lib/opencode-manager.js +187 -63
  30. package/dist/lib/opencode-manager.js.map +1 -1
  31. package/dist/lib/pi-manager.d.ts +0 -1
  32. package/dist/lib/pi-manager.d.ts.map +1 -1
  33. package/dist/lib/pi-manager.js +18 -41
  34. package/dist/lib/pi-manager.js.map +1 -1
  35. package/dist/lib/provider-registry.d.ts +200 -0
  36. package/dist/lib/provider-registry.d.ts.map +1 -0
  37. package/dist/lib/provider-registry.js +581 -0
  38. package/dist/lib/provider-registry.js.map +1 -0
  39. package/dist/lib/tool-manager.d.ts +42 -3
  40. package/dist/lib/tool-manager.d.ts.map +1 -1
  41. package/dist/lib/tool-manager.js +155 -34
  42. package/dist/lib/tool-manager.js.map +1 -1
  43. package/dist/mcp-services.d.ts.map +1 -1
  44. package/dist/mcp-services.js +57 -3
  45. package/dist/mcp-services.js.map +1 -1
  46. package/dist/{menu.d.ts → menu/main-menu.d.ts} +1 -1
  47. package/dist/menu/main-menu.d.ts.map +1 -0
  48. package/dist/menu/main-menu.js +90 -0
  49. package/dist/menu/main-menu.js.map +1 -0
  50. package/dist/menu/mcp-menu.d.ts +2 -0
  51. package/dist/menu/mcp-menu.d.ts.map +1 -0
  52. package/dist/menu/mcp-menu.js +136 -0
  53. package/dist/menu/mcp-menu.js.map +1 -0
  54. package/dist/menu/provider-menu.d.ts +4 -0
  55. package/dist/menu/provider-menu.d.ts.map +1 -0
  56. package/dist/menu/provider-menu.js +623 -0
  57. package/dist/menu/provider-menu.js.map +1 -0
  58. package/dist/menu/shared.d.ts +17 -0
  59. package/dist/menu/shared.d.ts.map +1 -0
  60. package/dist/menu/shared.js +255 -0
  61. package/dist/menu/shared.js.map +1 -0
  62. package/dist/menu/system-menu.d.ts +3 -0
  63. package/dist/menu/system-menu.d.ts.map +1 -0
  64. package/dist/menu/system-menu.js +111 -0
  65. package/dist/menu/system-menu.js.map +1 -0
  66. package/dist/menu/tool-menu.d.ts +3 -0
  67. package/dist/menu/tool-menu.d.ts.map +1 -0
  68. package/dist/menu/tool-menu.js +619 -0
  69. package/dist/menu/tool-menu.js.map +1 -0
  70. package/dist/utils/api-test.d.ts +6 -0
  71. package/dist/utils/api-test.d.ts.map +1 -1
  72. package/dist/utils/api-test.js +66 -0
  73. package/dist/utils/api-test.js.map +1 -1
  74. package/dist/utils/brand.d.ts.map +1 -1
  75. package/dist/utils/brand.js +14 -0
  76. package/dist/utils/brand.js.map +1 -1
  77. package/dist/utils/config.d.ts +29 -4
  78. package/dist/utils/config.d.ts.map +1 -1
  79. package/dist/utils/config.js +194 -68
  80. package/dist/utils/config.js.map +1 -1
  81. package/dist/utils/providers.d.ts +32 -0
  82. package/dist/utils/providers.d.ts.map +1 -0
  83. package/dist/utils/providers.js +63 -0
  84. package/dist/utils/providers.js.map +1 -0
  85. package/dist/wizard.d.ts.map +1 -1
  86. package/dist/wizard.js +123 -35
  87. package/dist/wizard.js.map +1 -1
  88. package/package.json +1 -1
  89. package/dist/menu.d.ts.map +0 -1
  90. package/dist/menu.js +0 -1226
  91. 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 NVIDIA
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
+ ![Main Menu](screenshots/main.png)
19
+
20
+ ### Provider Configuration
21
+ ![Provider Configuration](screenshots/provider-config.png)
22
+
23
+ ### Coding Tools
24
+ ![Coding Tools](screenshots/coding-tools.png)
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
- ncoder-link auth glm_coding_plan_global <token> # Choose Global plan and set key
88
- ncoder-link auth glm_coding_plan_china <token> # Choose China plan and set key
89
- ncoder-link auth kimi <token> # Set Kimi API key
90
- ncoder-link auth openrouter <token> # Set OpenRouter API key
91
- ncoder-link auth nvidia <token> # Set NVIDIA API key
92
- ncoder-link auth revoke # Delete the saved key
93
- ncoder-link auth reload <tool> # Reload config into a tool
94
- ncoder-link auth --help # Show help for auth commands
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
- Apache License 2.0
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.8')
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="glm_coding_plan_global glm_coding_plan_china kimi openrouter nvidia"
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 "glm_coding_plan_global glm_coding_plan_china kimi openrouter nvidia revoke reload" -- "$cur"))
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=(glm_coding_plan_global glm_coding_plan_china kimi openrouter nvidia)
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 "glm_coding_plan_global"
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 = @('glm_coding_plan_global', 'glm_coding_plan_china', 'kimi', 'openrouter', 'nvidia')
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 = toolManager.getSupportedTools();
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
- { id: 'filesystem', name: 'Filesystem', description: 'File system operations' },
445
- { id: 'github', name: 'GitHub', description: 'GitHub integration' }
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
- .action(async () => {
460
- const { kimiManager } = await import('./lib/kimi-manager.js');
461
- const installed = kimiManager.getInstalledMCPs();
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
- .action(async (serviceId) => {
577
+ .option('-t, --tool <tool>', 'Target tool (defaults to last used MCP-capable tool)')
578
+ .action(async (serviceId, options) => {
475
579
  try {
476
- const { kimiManager } = await import('./lib/kimi-manager.js');
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
- if (!auth.plan || !auth.apiKey) {
481
- printError(i18n.t('auth.not_set'), 'Run "coder-link auth <provider> <token>" first');
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
- const service = {
485
- id: serviceId,
486
- name: serviceId,
487
- description: '',
488
- protocol: 'stdio',
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 that Node.js and npm are installed correctly');
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
- .action(async (serviceId) => {
604
+ .option('-t, --tool <tool>', 'Target tool (defaults to last used MCP-capable tool)')
605
+ .action(async (serviceId, options) => {
505
606
  try {
506
- const { kimiManager } = await import('./lib/kimi-manager.js');
507
- await kimiManager.uninstallMCP(serviceId);
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 = toolManager.getSupportedTools();
625
+ const tools = getVisibleTools();
525
626
  const toolStatuses = [];
526
627
  for (const tool of tools) {
527
- const status = await toolManager.isConfigured(tool);
528
- toolStatuses.push({ tool, configured: status });
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: mcpInstalled
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
- console.log(` ${statusIndicator(t.configured)} ${toolLabel(t.tool)}`);
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
- if (mcpInstalled.length === 0) {
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
- for (const id of mcpInstalled) {
563
- console.log(` ${id}`);
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 = toolManager.getSupportedTools();
700
+ const tools = getVisibleTools();
585
701
  let configured = 0;
586
- let total = tools.length;
587
- let toolDetails = [];
702
+ let total = 0;
703
+ const toolDetails = [];
588
704
  for (const tool of tools) {
589
- const isConfigured = await toolManager.isConfigured(tool);
590
- if (isConfigured)
591
- configured++;
592
- toolDetails.push({ tool, label: toolLabel(tool), configured: isConfigured });
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 { kimiManager } = await import('./lib/kimi-manager.js');
596
- const mcps = kimiManager.getInstalledMCPs();
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: mcps.length,
612
- installed: mcps
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.configured ? chalk.green('●') : chalk.gray('○');
632
- console.log(` ${status} ${t.label}`);
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
  }