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.
- package/README.md +200 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +684 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/amp-manager.d.ts +24 -0
- package/dist/lib/amp-manager.d.ts.map +1 -0
- package/dist/lib/amp-manager.js +46 -0
- package/dist/lib/amp-manager.js.map +1 -0
- package/dist/lib/claude-code-manager.d.ts +55 -0
- package/dist/lib/claude-code-manager.d.ts.map +1 -0
- package/dist/lib/claude-code-manager.js +408 -0
- package/dist/lib/claude-code-manager.js.map +1 -0
- package/dist/lib/crush-manager.d.ts +31 -0
- package/dist/lib/crush-manager.d.ts.map +1 -0
- package/dist/lib/crush-manager.js +262 -0
- package/dist/lib/crush-manager.js.map +1 -0
- package/dist/lib/factory-droid-manager.d.ts +36 -0
- package/dist/lib/factory-droid-manager.d.ts.map +1 -0
- package/dist/lib/factory-droid-manager.js +387 -0
- package/dist/lib/factory-droid-manager.js.map +1 -0
- package/dist/lib/kimi-manager.d.ts +34 -0
- package/dist/lib/kimi-manager.d.ts.map +1 -0
- package/dist/lib/kimi-manager.js +316 -0
- package/dist/lib/kimi-manager.js.map +1 -0
- package/dist/lib/opencode-manager.d.ts +31 -0
- package/dist/lib/opencode-manager.d.ts.map +1 -0
- package/dist/lib/opencode-manager.js +315 -0
- package/dist/lib/opencode-manager.js.map +1 -0
- package/dist/lib/pi-manager.d.ts +30 -0
- package/dist/lib/pi-manager.d.ts.map +1 -0
- package/dist/lib/pi-manager.js +196 -0
- package/dist/lib/pi-manager.js.map +1 -0
- package/dist/lib/tool-manager.d.ts +51 -0
- package/dist/lib/tool-manager.d.ts.map +1 -0
- package/dist/lib/tool-manager.js +113 -0
- package/dist/lib/tool-manager.js.map +1 -0
- package/dist/locales/en_US.json +49 -0
- package/dist/locales/zh_CN.json +49 -0
- package/dist/mcp-services.d.ts +3 -0
- package/dist/mcp-services.d.ts.map +1 -0
- package/dist/mcp-services.js +26 -0
- package/dist/mcp-services.js.map +1 -0
- package/dist/menu.d.ts +2 -0
- package/dist/menu.d.ts.map +1 -0
- package/dist/menu.js +1226 -0
- package/dist/menu.js.map +1 -0
- package/dist/utils/api-test.d.ts +32 -0
- package/dist/utils/api-test.d.ts.map +1 -0
- package/dist/utils/api-test.js +163 -0
- package/dist/utils/api-test.js.map +1 -0
- package/dist/utils/brand.d.ts +39 -0
- package/dist/utils/brand.d.ts.map +1 -0
- package/dist/utils/brand.js +195 -0
- package/dist/utils/brand.js.map +1 -0
- package/dist/utils/config.d.ts +100 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +483 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/exec.d.ts +5 -0
- package/dist/utils/exec.d.ts.map +1 -0
- package/dist/utils/exec.js +145 -0
- package/dist/utils/exec.js.map +1 -0
- package/dist/utils/i18n.d.ts +56 -0
- package/dist/utils/i18n.d.ts.map +1 -0
- package/dist/utils/i18n.js +42 -0
- package/dist/utils/i18n.js.map +1 -0
- package/dist/utils/keyboard.d.ts +32 -0
- package/dist/utils/keyboard.d.ts.map +1 -0
- package/dist/utils/keyboard.js +109 -0
- package/dist/utils/keyboard.js.map +1 -0
- package/dist/utils/logger.d.ts +14 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +55 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/output.d.ts +58 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +93 -0
- package/dist/utils/output.js.map +1 -0
- package/dist/wizard.d.ts +2 -0
- package/dist/wizard.d.ts.map +1 -0
- package/dist/wizard.js +114 -0
- package/dist/wizard.js.map +1 -0
- 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
|