coder-link 0.0.9

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 (88) hide show
  1. package/README.md +200 -0
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +684 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/index.d.ts +12 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +12 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/lib/amp-manager.d.ts +24 -0
  11. package/dist/lib/amp-manager.d.ts.map +1 -0
  12. package/dist/lib/amp-manager.js +46 -0
  13. package/dist/lib/amp-manager.js.map +1 -0
  14. package/dist/lib/claude-code-manager.d.ts +55 -0
  15. package/dist/lib/claude-code-manager.d.ts.map +1 -0
  16. package/dist/lib/claude-code-manager.js +408 -0
  17. package/dist/lib/claude-code-manager.js.map +1 -0
  18. package/dist/lib/crush-manager.d.ts +31 -0
  19. package/dist/lib/crush-manager.d.ts.map +1 -0
  20. package/dist/lib/crush-manager.js +262 -0
  21. package/dist/lib/crush-manager.js.map +1 -0
  22. package/dist/lib/factory-droid-manager.d.ts +36 -0
  23. package/dist/lib/factory-droid-manager.d.ts.map +1 -0
  24. package/dist/lib/factory-droid-manager.js +387 -0
  25. package/dist/lib/factory-droid-manager.js.map +1 -0
  26. package/dist/lib/kimi-manager.d.ts +34 -0
  27. package/dist/lib/kimi-manager.d.ts.map +1 -0
  28. package/dist/lib/kimi-manager.js +316 -0
  29. package/dist/lib/kimi-manager.js.map +1 -0
  30. package/dist/lib/opencode-manager.d.ts +31 -0
  31. package/dist/lib/opencode-manager.d.ts.map +1 -0
  32. package/dist/lib/opencode-manager.js +315 -0
  33. package/dist/lib/opencode-manager.js.map +1 -0
  34. package/dist/lib/pi-manager.d.ts +30 -0
  35. package/dist/lib/pi-manager.d.ts.map +1 -0
  36. package/dist/lib/pi-manager.js +196 -0
  37. package/dist/lib/pi-manager.js.map +1 -0
  38. package/dist/lib/tool-manager.d.ts +51 -0
  39. package/dist/lib/tool-manager.d.ts.map +1 -0
  40. package/dist/lib/tool-manager.js +113 -0
  41. package/dist/lib/tool-manager.js.map +1 -0
  42. package/dist/locales/en_US.json +49 -0
  43. package/dist/locales/zh_CN.json +49 -0
  44. package/dist/mcp-services.d.ts +3 -0
  45. package/dist/mcp-services.d.ts.map +1 -0
  46. package/dist/mcp-services.js +26 -0
  47. package/dist/mcp-services.js.map +1 -0
  48. package/dist/menu.d.ts +2 -0
  49. package/dist/menu.d.ts.map +1 -0
  50. package/dist/menu.js +1226 -0
  51. package/dist/menu.js.map +1 -0
  52. package/dist/utils/api-test.d.ts +32 -0
  53. package/dist/utils/api-test.d.ts.map +1 -0
  54. package/dist/utils/api-test.js +163 -0
  55. package/dist/utils/api-test.js.map +1 -0
  56. package/dist/utils/brand.d.ts +39 -0
  57. package/dist/utils/brand.d.ts.map +1 -0
  58. package/dist/utils/brand.js +195 -0
  59. package/dist/utils/brand.js.map +1 -0
  60. package/dist/utils/config.d.ts +100 -0
  61. package/dist/utils/config.d.ts.map +1 -0
  62. package/dist/utils/config.js +483 -0
  63. package/dist/utils/config.js.map +1 -0
  64. package/dist/utils/exec.d.ts +5 -0
  65. package/dist/utils/exec.d.ts.map +1 -0
  66. package/dist/utils/exec.js +145 -0
  67. package/dist/utils/exec.js.map +1 -0
  68. package/dist/utils/i18n.d.ts +56 -0
  69. package/dist/utils/i18n.d.ts.map +1 -0
  70. package/dist/utils/i18n.js +42 -0
  71. package/dist/utils/i18n.js.map +1 -0
  72. package/dist/utils/keyboard.d.ts +32 -0
  73. package/dist/utils/keyboard.d.ts.map +1 -0
  74. package/dist/utils/keyboard.js +109 -0
  75. package/dist/utils/keyboard.js.map +1 -0
  76. package/dist/utils/logger.d.ts +14 -0
  77. package/dist/utils/logger.d.ts.map +1 -0
  78. package/dist/utils/logger.js +55 -0
  79. package/dist/utils/logger.js.map +1 -0
  80. package/dist/utils/output.d.ts +58 -0
  81. package/dist/utils/output.d.ts.map +1 -0
  82. package/dist/utils/output.js +93 -0
  83. package/dist/utils/output.js.map +1 -0
  84. package/dist/wizard.d.ts +2 -0
  85. package/dist/wizard.d.ts.map +1 -0
  86. package/dist/wizard.js +114 -0
  87. package/dist/wizard.js.map +1 -0
  88. package/package.json +65 -0
package/dist/cli.js ADDED
@@ -0,0 +1,684 @@
1
+ #!/usr/bin/env node
2
+ import { Command, Option } from 'commander';
3
+ import inquirer from 'inquirer';
4
+ import chalk from 'chalk';
5
+ import { logger } from './utils/logger.js';
6
+ import { i18n } from './utils/i18n.js';
7
+ import { configManager } from './utils/config.js';
8
+ import { toolManager } from './lib/tool-manager.js';
9
+ import { runMenu } from './menu.js';
10
+ import { runWizard } from './wizard.js';
11
+ import { statusIndicator, planLabel, planLabelColored, toolLabel } from './utils/brand.js';
12
+ import { setOutputFormat, getOutputFormat, printData, printError } from './utils/output.js';
13
+ const program = new Command();
14
+ // Use persisted UI language for all commands
15
+ i18n.setLang(configManager.getLang());
16
+ // Global options
17
+ const jsonOption = new Option('-j, --json', 'Output as JSON for programmatic use');
18
+ const formatOption = new Option('-f, --format <format>', 'Output format').choices(['pretty', 'json']).default('pretty');
19
+ program
20
+ .name('coder-link')
21
+ .description('Coder Link — Connect coding tools to any model/provider')
22
+ .version('0.0.8')
23
+ .addOption(formatOption)
24
+ .hook('preAction', (thisCommand) => {
25
+ const opts = thisCommand.opts();
26
+ if (opts.format === 'json' || opts.json) {
27
+ setOutputFormat('json');
28
+ }
29
+ });
30
+ // Shell completion command
31
+ program
32
+ .command('completion')
33
+ .description('Generate shell completion script')
34
+ .option('-s, --shell <shell>', 'Shell type (bash, zsh, fish, pwsh)', 'bash')
35
+ .action(async (options) => {
36
+ const shell = options.shell;
37
+ let script = '';
38
+ switch (shell) {
39
+ case 'bash':
40
+ script = `#!/bin/bash
41
+ _coder_link_completion() {
42
+ 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"
46
+ local services="filesystem github"
47
+
48
+ if [[ \${COMP_CWORD} -eq 1 ]]; then
49
+ COMPREPLY=($(compgen -W "$commands" -- "$cur"))
50
+ elif [[ \${COMP_CWORD} -ge 2 ]]; then
51
+ local subcommand="\${COMP_WORDS[1]}"
52
+ case "$subcommand" in
53
+ auth)
54
+ COMPREPLY=($(compgen -W "glm_coding_plan_global glm_coding_plan_china kimi openrouter nvidia revoke reload" -- "$cur"))
55
+ ;;
56
+ tools)
57
+ if [[ "\${COMP_WORDS[2]}" == "install" || "\${COMP_WORDS[2]}" == "uninstall" ]]; then
58
+ COMPREPLY=($(compgen -W "$tools" -- "$cur"))
59
+ else
60
+ COMPREPLY=($(compgen -W "list install uninstall" -- "$cur"))
61
+ fi
62
+ ;;
63
+ mcp)
64
+ if [[ "\${COMP_WORDS[2]}" == "install" || "\${COMP_WORDS[2]}" == "uninstall" ]]; then
65
+ COMPREPLY=($(compgen -W "$services" -- "$cur"))
66
+ else
67
+ COMPREPLY=($(compgen -W "list installed install uninstall" -- "$cur"))
68
+ fi
69
+ ;;
70
+ lang)
71
+ COMPREPLY=($(compgen -W "show set" -- "$cur"))
72
+ ;;
73
+ *)
74
+ COMPREPLY=()
75
+ ;;
76
+ esac
77
+ fi
78
+ }
79
+ complete -F _coder_link_completion coder-link
80
+ `;
81
+ break;
82
+ case 'zsh':
83
+ script = `#compdef coder-link
84
+
85
+ _coder_link() {
86
+ local curcontext="$curcontext" state line
87
+ typeset -A opt_args
88
+
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)
92
+ local services=(filesystem github)
93
+
94
+ _arguments -C \
95
+ '1: :->command' \
96
+ '*: :->args'
97
+
98
+ case "$state" in
99
+ command)
100
+ _describe -t commands "coder-link command" commands
101
+ ;;
102
+ args)
103
+ case "$line[1]" in
104
+ auth)
105
+ _describe -t providers "provider" providers && _values "actions" revoke reload
106
+ ;;
107
+ tools)
108
+ _values "subcommand" list install uninstall
109
+ ;;
110
+ mcp)
111
+ _values "subcommand" list installed install uninstall
112
+ ;;
113
+ lang)
114
+ _values "action" show set
115
+ ;;
116
+ esac
117
+ ;;
118
+ esac
119
+ }
120
+
121
+ _coder_link "$@"
122
+ `;
123
+ break;
124
+ case 'fish':
125
+ script = `complete -c coder-link -f
126
+
127
+ # Commands
128
+ complete -c coder-link -n "__fish_use_subcommand" -a "init" -d "Open interactive menu"
129
+ complete -c coder-link -n "__fish_use_subcommand" -a "auth" -d "API key management"
130
+ complete -c coder-link -n "__fish_use_subcommand" -a "lang" -d "Language management"
131
+ complete -c coder-link -n "__fish_use_subcommand" -a "tools" -d "Manage coding tools"
132
+ complete -c coder-link -n "__fish_use_subcommand" -a "mcp" -d "Manage MCP services"
133
+ complete -c coder-link -n "__fish_use_subcommand" -a "doctor" -d "Inspect system configuration"
134
+ complete -c coder-link -n "__fish_use_subcommand" -a "status" -d "Show current status"
135
+ complete -c coder-link -n "__fish_use_subcommand" -a "completion" -d "Generate shell completion"
136
+
137
+ # 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"
143
+ complete -c coder-link -n "__fish_seen_subcommand_from auth" -a "revoke"
144
+ `;
145
+ break;
146
+ case 'pwsh':
147
+ case 'powershell':
148
+ script = `Register-ArgumentCompleter -Native -CommandName coder-link -ScriptBlock {
149
+ param($wordToComplete, $commandAst, $cursorPosition)
150
+
151
+ $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')
154
+ $services = @('filesystem', 'github')
155
+
156
+ $commandElements = $commandAst.CommandElements | Select-Object -Skip 1
157
+ $command = $commandElements[0].Value
158
+
159
+ if ($commandElements.Count -eq 1) {
160
+ $commands | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
161
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
162
+ }
163
+ } else {
164
+ switch ($command) {
165
+ 'auth' {
166
+ $providers + @('revoke', 'reload') | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
167
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
168
+ }
169
+ }
170
+ 'tools' {
171
+ @('list', 'install', 'uninstall') | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
172
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
173
+ }
174
+ }
175
+ 'mcp' {
176
+ @('list', 'installed', 'install', 'uninstall') | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
177
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
183
+ `;
184
+ break;
185
+ default:
186
+ console.error(`Unknown shell: ${shell}. Supported: bash, zsh, fish, pwsh`);
187
+ process.exit(1);
188
+ }
189
+ console.log(script.trim());
190
+ });
191
+ // Language commands
192
+ program
193
+ .command('lang')
194
+ .description('Language management')
195
+ .addCommand(new Command('show')
196
+ .description('Show current language')
197
+ .action(async () => {
198
+ const lang = configManager.getLang();
199
+ if (getOutputFormat().isJson) {
200
+ printData({ currentLanguage: lang, displayName: lang === 'zh_CN' ? '简体中文' : 'English' });
201
+ }
202
+ else {
203
+ console.log(i18n.t('lang.current', { lang }));
204
+ }
205
+ }))
206
+ .addCommand(new Command('set <lang>')
207
+ .description('Set language (zh_CN or en_US)')
208
+ .action(async (lang) => {
209
+ try {
210
+ if (lang !== 'zh_CN' && lang !== 'en_US') {
211
+ throw new Error(`Unsupported language: ${lang}`);
212
+ }
213
+ configManager.setLang(lang);
214
+ i18n.setLang(lang);
215
+ console.log(i18n.t('lang.changed'));
216
+ }
217
+ catch (error) {
218
+ logger.logError('lang.set', error);
219
+ printError(error instanceof Error ? error.message : String(error), 'Supported languages: zh_CN, en_US');
220
+ process.exit(1);
221
+ }
222
+ }));
223
+ // Auth commands program
224
+ program
225
+ .command('auth')
226
+ .description('API key management')
227
+ .action(async () => {
228
+ // Interactive mode if no subcommand provided
229
+ await runWizard();
230
+ })
231
+ .addCommand(new Command('glm_coding_plan_global [token]')
232
+ .description('Set GLM Coding Plan Global API key')
233
+ .action(async (token) => {
234
+ try {
235
+ if (!token) {
236
+ const { apiKey } = await inquirer.prompt([{
237
+ type: 'password',
238
+ name: 'apiKey',
239
+ message: i18n.t('wizard.enter_api_key'),
240
+ validate: (input) => input.trim().length > 0 || 'API key cannot be empty'
241
+ }]);
242
+ token = apiKey;
243
+ }
244
+ configManager.setAuth('glm_coding_plan_global', token.trim());
245
+ console.log(i18n.t('auth.set_success'));
246
+ }
247
+ catch (error) {
248
+ logger.logError('auth.set', error);
249
+ printError(error instanceof Error ? error.message : String(error), 'Run "coder-link auth" for interactive setup');
250
+ process.exit(1);
251
+ }
252
+ }))
253
+ .addCommand(new Command('glm_coding_plan_china [token]')
254
+ .description('Set GLM Coding Plan China API key')
255
+ .action(async (token) => {
256
+ try {
257
+ if (!token) {
258
+ const { apiKey } = await inquirer.prompt([{
259
+ type: 'password',
260
+ name: 'apiKey',
261
+ message: i18n.t('wizard.enter_api_key'),
262
+ validate: (input) => input.trim().length > 0 || 'API key cannot be empty'
263
+ }]);
264
+ token = apiKey;
265
+ }
266
+ configManager.setAuth('glm_coding_plan_china', token.trim());
267
+ console.log(i18n.t('auth.set_success'));
268
+ }
269
+ catch (error) {
270
+ logger.logError('auth.set', error);
271
+ printError(error instanceof Error ? error.message : String(error), 'Run "coder-link auth" for interactive setup');
272
+ process.exit(1);
273
+ }
274
+ }))
275
+ .addCommand(new Command('kimi [token]')
276
+ .description('Set Kimi API key')
277
+ .action(async (token) => {
278
+ try {
279
+ if (!token) {
280
+ const { apiKey } = await inquirer.prompt([{
281
+ type: 'password',
282
+ name: 'apiKey',
283
+ message: i18n.t('wizard.enter_api_key'),
284
+ validate: (input) => input.trim().length > 0 || 'API key cannot be empty'
285
+ }]);
286
+ token = apiKey;
287
+ }
288
+ configManager.setAuth('kimi', token.trim());
289
+ console.log(i18n.t('auth.set_success'));
290
+ }
291
+ catch (error) {
292
+ logger.logError('auth.set', error);
293
+ printError(error instanceof Error ? error.message : String(error), 'Run "coder-link auth" for interactive setup');
294
+ process.exit(1);
295
+ }
296
+ }))
297
+ .addCommand(new Command('openrouter [token]')
298
+ .description('Set OpenRouter API key')
299
+ .action(async (token) => {
300
+ try {
301
+ if (!token) {
302
+ const { apiKey } = await inquirer.prompt([{
303
+ type: 'password',
304
+ name: 'apiKey',
305
+ message: i18n.t('wizard.enter_api_key'),
306
+ validate: (input) => input.trim().length > 0 || 'API key cannot be empty'
307
+ }]);
308
+ token = apiKey;
309
+ }
310
+ configManager.setAuth('openrouter', token.trim());
311
+ console.log(i18n.t('auth.set_success'));
312
+ }
313
+ catch (error) {
314
+ logger.logError('auth.set', error);
315
+ printError(error instanceof Error ? error.message : String(error), 'Run "coder-link auth" for interactive setup');
316
+ process.exit(1);
317
+ }
318
+ }))
319
+ .addCommand(new Command('nvidia [token]')
320
+ .description('Set NVIDIA API key')
321
+ .action(async (token) => {
322
+ try {
323
+ if (!token) {
324
+ const { apiKey } = await inquirer.prompt([{
325
+ type: 'password',
326
+ name: 'apiKey',
327
+ message: i18n.t('wizard.enter_api_key'),
328
+ validate: (input) => input.trim().length > 0 || 'API key cannot be empty'
329
+ }]);
330
+ token = apiKey;
331
+ }
332
+ configManager.setAuth('nvidia', token.trim());
333
+ console.log(i18n.t('auth.set_success'));
334
+ }
335
+ catch (error) {
336
+ logger.logError('auth.set', error);
337
+ printError(error instanceof Error ? error.message : String(error), 'Run "coder-link auth" for interactive setup');
338
+ process.exit(1);
339
+ }
340
+ }))
341
+ .addCommand(new Command('revoke')
342
+ .description('Delete saved API key')
343
+ .action(async () => {
344
+ try {
345
+ const { confirm } = await inquirer.prompt([{
346
+ type: 'confirm',
347
+ name: 'confirm',
348
+ message: 'Revoke all saved API keys?',
349
+ default: false,
350
+ }]);
351
+ if (!confirm) {
352
+ console.log('Cancelled.');
353
+ return;
354
+ }
355
+ configManager.revokeAuth();
356
+ console.log(i18n.t('auth.revoke_success'));
357
+ }
358
+ catch (error) {
359
+ logger.logError('auth.revoke', error);
360
+ printError(error instanceof Error ? error.message : String(error), 'Manually delete ~/.coder-link/config.yaml if persistent issues');
361
+ process.exit(1);
362
+ }
363
+ }))
364
+ .addCommand(new Command('reload <tool>')
365
+ .description('Reload configuration into a tool')
366
+ .action(async (tool) => {
367
+ try {
368
+ const { plan, apiKey } = configManager.getAuth();
369
+ if (!plan || !apiKey) {
370
+ printError(i18n.t('auth.not_set'), 'Run "coder-link auth <provider> <token>" to set your API key');
371
+ process.exit(1);
372
+ }
373
+ await toolManager.loadConfig(tool, plan, apiKey);
374
+ console.log(i18n.t('auth.reload_success', { tool }));
375
+ }
376
+ catch (error) {
377
+ logger.logError('auth.reload', error);
378
+ printError(error instanceof Error ? error.message : String(error), `Check if "${tool}" is supported and installed`);
379
+ process.exit(1);
380
+ }
381
+ }));
382
+ // Tool management commands
383
+ program
384
+ .command('tools')
385
+ .description('Manage coding tools')
386
+ .addCommand(new Command('list')
387
+ .description('List all supported tools')
388
+ .action(async () => {
389
+ const tools = toolManager.getSupportedTools();
390
+ const toolData = [];
391
+ for (const tool of tools) {
392
+ const status = await toolManager.isConfigured(tool);
393
+ toolData.push({
394
+ name: tool,
395
+ label: toolLabel(tool),
396
+ configured: status,
397
+ status: status ? 'configured' : 'not_configured'
398
+ });
399
+ }
400
+ if (getOutputFormat().isJson) {
401
+ printData({ tools: toolData });
402
+ }
403
+ else {
404
+ console.log(i18n.t('tools.list_header'));
405
+ for (const t of toolData) {
406
+ console.log(` ${statusIndicator(t.configured)} ${t.label}`);
407
+ }
408
+ }
409
+ }))
410
+ .addCommand(new Command('install <tool>')
411
+ .description('Install a coding tool')
412
+ .action(async (tool) => {
413
+ try {
414
+ await toolManager.installTool(tool);
415
+ console.log(i18n.t('tools.install_success', { tool }));
416
+ }
417
+ catch (error) {
418
+ logger.logError('tools.install', error);
419
+ printError(error instanceof Error ? error.message : String(error), `Refer to the "${toolLabel(tool)}" documentation for install instructions`);
420
+ process.exit(1);
421
+ }
422
+ }))
423
+ .addCommand(new Command('uninstall <tool>')
424
+ .description('Uninstall a coding tool')
425
+ .action(async (tool) => {
426
+ try {
427
+ await toolManager.uninstallTool(tool);
428
+ console.log(i18n.t('tools.uninstall_success', { tool }));
429
+ }
430
+ catch (error) {
431
+ logger.logError('tools.uninstall', error);
432
+ printError(error instanceof Error ? error.message : String(error), 'Tool may already be uninstalled or not managed by coder-link');
433
+ process.exit(1);
434
+ }
435
+ }));
436
+ // MCP commands
437
+ program
438
+ .command('mcp')
439
+ .description('Manage MCP services')
440
+ .addCommand(new Command('list')
441
+ .description('List available MCP services')
442
+ .action(async () => {
443
+ const services = [
444
+ { id: 'filesystem', name: 'Filesystem', description: 'File system operations' },
445
+ { id: 'github', name: 'GitHub', description: 'GitHub integration' }
446
+ ];
447
+ if (getOutputFormat().isJson) {
448
+ printData({ services });
449
+ }
450
+ else {
451
+ console.log(i18n.t('mcp.list_header'));
452
+ for (const service of services) {
453
+ console.log(` ${service.id}: ${service.name} - ${service.description}`);
454
+ }
455
+ }
456
+ }))
457
+ .addCommand(new Command('installed')
458
+ .description('List installed MCP services')
459
+ .action(async () => {
460
+ const { kimiManager } = await import('./lib/kimi-manager.js');
461
+ const installed = kimiManager.getInstalledMCPs();
462
+ if (getOutputFormat().isJson) {
463
+ printData({ installed });
464
+ }
465
+ else {
466
+ console.log(i18n.t('mcp.installed_header'));
467
+ for (const id of installed) {
468
+ console.log(` ${id}`);
469
+ }
470
+ }
471
+ }))
472
+ .addCommand(new Command('install <service>')
473
+ .description('Install an MCP service')
474
+ .action(async (serviceId) => {
475
+ 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');
479
+ const auth = configManager.getAuth();
480
+ if (!auth.plan || !auth.apiKey) {
481
+ printError(i18n.t('auth.not_set'), 'Run "coder-link auth <provider> <token>" first');
482
+ process.exit(1);
483
+ }
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 }));
495
+ }
496
+ catch (error) {
497
+ logger.logError('mcp.install', error);
498
+ printError(error instanceof Error ? error.message : String(error), 'Check that Node.js and npm are installed correctly');
499
+ process.exit(1);
500
+ }
501
+ }))
502
+ .addCommand(new Command('uninstall <service>')
503
+ .description('Uninstall an MCP service')
504
+ .action(async (serviceId) => {
505
+ 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 }));
509
+ }
510
+ catch (error) {
511
+ logger.logError('mcp.uninstall', error);
512
+ printError(error instanceof Error ? error.message : String(error), 'Service may not be installed');
513
+ process.exit(1);
514
+ }
515
+ }));
516
+ // Health check
517
+ program
518
+ .command('doctor')
519
+ .description('Inspect system configuration and tool status')
520
+ .addOption(jsonOption)
521
+ .action(async (options) => {
522
+ try {
523
+ const { plan, apiKey } = configManager.getAuth();
524
+ const tools = toolManager.getSupportedTools();
525
+ const toolStatuses = [];
526
+ for (const tool of tools) {
527
+ const status = await toolManager.isConfigured(tool);
528
+ toolStatuses.push({ tool, configured: status });
529
+ }
530
+ const { kimiManager } = await import('./lib/kimi-manager.js');
531
+ const mcpInstalled = kimiManager.getInstalledMCPs();
532
+ if (options.json) {
533
+ setOutputFormat('json');
534
+ printData({
535
+ configPath: configManager.configPath,
536
+ currentProvider: plan || null,
537
+ hasApiKey: !!apiKey,
538
+ tools: toolStatuses,
539
+ mcps: mcpInstalled
540
+ });
541
+ }
542
+ else {
543
+ console.log(i18n.t('doctor.header'));
544
+ console.log(i18n.t('doctor.config_path', { path: configManager.configPath }));
545
+ console.log(i18n.t('doctor.current_auth'));
546
+ if (plan && apiKey) {
547
+ console.log(` ${i18n.t('doctor.plan')}: ${planLabel(plan)}`);
548
+ console.log(` ${i18n.t('doctor.api_key')}: ${apiKey.substring(0, 8)}...`);
549
+ }
550
+ else {
551
+ console.log(` ${i18n.t('doctor.not_set')}`);
552
+ }
553
+ console.log('\n' + i18n.t('doctor.tools_header'));
554
+ for (const t of toolStatuses) {
555
+ console.log(` ${statusIndicator(t.configured)} ${toolLabel(t.tool)}`);
556
+ }
557
+ console.log('\n' + i18n.t('doctor.mcp_header'));
558
+ if (mcpInstalled.length === 0) {
559
+ console.log(` ${i18n.t('doctor.none')}`);
560
+ }
561
+ else {
562
+ for (const id of mcpInstalled) {
563
+ console.log(` ${id}`);
564
+ }
565
+ }
566
+ }
567
+ }
568
+ catch (error) {
569
+ logger.logError('doctor', error);
570
+ printError(error instanceof Error ? error.message : String(error), 'Your config file may be corrupted. Try deleting ~/.coder-link/config.yaml');
571
+ process.exit(1);
572
+ }
573
+ });
574
+ // Quick status command
575
+ program
576
+ .command('status')
577
+ .description('Show current configuration status')
578
+ .addOption(jsonOption)
579
+ .action(async (options) => {
580
+ try {
581
+ const { plan, apiKey } = configManager.getAuth();
582
+ const hasProvider = !!(plan && apiKey);
583
+ // Tool status summary
584
+ const tools = toolManager.getSupportedTools();
585
+ let configured = 0;
586
+ let total = tools.length;
587
+ let toolDetails = [];
588
+ 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 });
593
+ }
594
+ // MCP status
595
+ const { kimiManager } = await import('./lib/kimi-manager.js');
596
+ const mcps = kimiManager.getInstalledMCPs();
597
+ if (options.json) {
598
+ setOutputFormat('json');
599
+ printData({
600
+ provider: {
601
+ configured: hasProvider,
602
+ plan: plan || null,
603
+ apiKeyMasked: apiKey ? `${apiKey.substring(0, 4)}****` : null
604
+ },
605
+ tools: {
606
+ configured,
607
+ total,
608
+ details: toolDetails
609
+ },
610
+ mcps: {
611
+ count: mcps.length,
612
+ installed: mcps
613
+ },
614
+ configPath: configManager.configPath
615
+ });
616
+ }
617
+ else {
618
+ // Unified status format matching menu display
619
+ const providerStatus = plan ? chalk.green('●') : chalk.gray('○');
620
+ const providerLabel = plan ? planLabelColored(plan) : chalk.yellow('Not configured');
621
+ const keyDisplay = apiKey ? `${chalk.green(`${apiKey.substring(0, 4)}****`)}` : chalk.yellow('Not set');
622
+ console.log(`Provider: ${providerStatus} ${providerLabel} (API Key: ${keyDisplay})`);
623
+ const toolStatus = configured === total
624
+ ? chalk.green('●')
625
+ : configured > 0
626
+ ? chalk.yellow('◐')
627
+ : chalk.gray('○');
628
+ console.log(`Tools: ${toolStatus} ${configured}/${total} configured`);
629
+ // Show tool breakdown
630
+ for (const t of toolDetails) {
631
+ const status = t.configured ? chalk.green('●') : chalk.gray('○');
632
+ console.log(` ${status} ${t.label}`);
633
+ }
634
+ const mcpStatus = mcps.length > 0 ? chalk.green('●') : chalk.gray('○');
635
+ console.log(`MCPs: ${mcpStatus} ${mcps.length} installed`);
636
+ console.log(`\nConfig: ${chalk.gray(configManager.configPath)}`);
637
+ }
638
+ }
639
+ catch (error) {
640
+ logger.logError('status', error);
641
+ printError(error instanceof Error ? error.message : String(error), 'Run "coder-link doctor" for detailed diagnostics');
642
+ process.exit(1);
643
+ }
644
+ });
645
+ // Init wizard
646
+ program
647
+ .command('init')
648
+ .description('Open interactive menu')
649
+ .action(async () => {
650
+ try {
651
+ await runMenu();
652
+ }
653
+ catch (error) {
654
+ logger.logError('init', error);
655
+ printError(error instanceof Error ? error.message : String(error), 'Try running with "coder-link --help" to see available commands');
656
+ process.exit(1);
657
+ }
658
+ });
659
+ // Wizard command (backward compatibility)
660
+ program
661
+ .command('wizard')
662
+ .description('Run setup wizard (deprecated, use "init")')
663
+ .action(async () => {
664
+ console.log(chalk.yellow('Note: "wizard" is deprecated. Use "coder-link init" instead.'));
665
+ await runWizard();
666
+ });
667
+ // Default action: run wizard (interactive), otherwise show help.
668
+ if (process.argv.length <= 2) {
669
+ const isInteractive = !!process.stdin.isTTY && !!process.stdout.isTTY;
670
+ if (isInteractive) {
671
+ runMenu().catch((error) => {
672
+ logger.logError('menu', error);
673
+ printError(error instanceof Error ? error.message : String(error), 'Run "coder-link --help" for usage information');
674
+ process.exit(1);
675
+ });
676
+ }
677
+ else {
678
+ program.help();
679
+ }
680
+ }
681
+ else {
682
+ program.parse();
683
+ }
684
+ //# sourceMappingURL=cli.js.map