invokora 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/dist/cli/app.d.ts +55 -0
- package/dist/cli/app.js +1087 -0
- package/dist/cli/config.d.ts +12 -0
- package/dist/cli/config.js +73 -0
- package/dist/cli/constants.d.ts +24 -0
- package/dist/cli/constants.js +52 -0
- package/dist/cli/http.d.ts +2 -0
- package/dist/cli/http.js +23 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.js +11 -0
- package/dist/cli/mcp/app.d.ts +12 -0
- package/dist/cli/mcp/app.js +85 -0
- package/dist/cli/mcp/backend_client.d.ts +10 -0
- package/dist/cli/mcp/backend_client.js +91 -0
- package/dist/cli/mcp/errors.d.ts +28 -0
- package/dist/cli/mcp/errors.js +139 -0
- package/dist/cli/mcp/progress.d.ts +12 -0
- package/dist/cli/mcp/progress.js +49 -0
- package/dist/cli/mcp/responses_session.d.ts +21 -0
- package/dist/cli/mcp/responses_session.js +233 -0
- package/dist/cli/mcp/schemas.d.ts +99 -0
- package/dist/cli/mcp/schemas.js +66 -0
- package/dist/cli/mcp/server.d.ts +4 -0
- package/dist/cli/mcp/server.js +3 -0
- package/dist/cli/mcp/session_store.d.ts +32 -0
- package/dist/cli/mcp/session_store.js +58 -0
- package/dist/cli/mcp/tool_handlers.d.ts +3 -0
- package/dist/cli/mcp/tool_handlers.js +26 -0
- package/dist/cli/mcp_setup.d.ts +33 -0
- package/dist/cli/mcp_setup.js +225 -0
- package/dist/cli/oauth.d.ts +45 -0
- package/dist/cli/oauth.js +594 -0
- package/dist/cli/prompts.d.ts +23 -0
- package/dist/cli/prompts.js +175 -0
- package/dist/cli/release.d.ts +3 -0
- package/dist/cli/release.js +3 -0
- package/dist/cli/skills.d.ts +43 -0
- package/dist/cli/skills.js +443 -0
- package/dist/cli/types.d.ts +183 -0
- package/dist/cli/types.js +1 -0
- package/package.json +29 -0
package/dist/cli/app.js
ADDED
|
@@ -0,0 +1,1087 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
4
|
+
import { RELEASE_VERSION } from './release.js';
|
|
5
|
+
import { CONFIG_PATH, DEFAULT_BACKEND_URL, LANGUAGE_PATH, SYNC_SCOPE_ALIASES, SYNC_TARGET_ALIASES } from './constants.js';
|
|
6
|
+
import { ConfigStore } from './config.js';
|
|
7
|
+
import { McpConfigService } from './mcp_setup.js';
|
|
8
|
+
import { OAuthBrowserLoginFlow, parseLoginCommandOptions } from './oauth.js';
|
|
9
|
+
import { CliPrompts } from './prompts.js';
|
|
10
|
+
import { InvokoraApiClient, SkillShellManager } from './skills.js';
|
|
11
|
+
import { getErrorMessage } from './mcp/errors.js';
|
|
12
|
+
import { startStdioServer } from './mcp/app.js';
|
|
13
|
+
const USAGE_TEXT_EN = `
|
|
14
|
+
invokora CLI
|
|
15
|
+
|
|
16
|
+
Commands:
|
|
17
|
+
-h, --help Print CLI help
|
|
18
|
+
-v, --version Print CLI version
|
|
19
|
+
login [--timeout <ms>] [--no-open]
|
|
20
|
+
Authenticate via browser OAuth loopback callback
|
|
21
|
+
language [en|zh] Show or set CLI language
|
|
22
|
+
sync [--prune] Sync local skills interactively
|
|
23
|
+
--scope <scope> project | user
|
|
24
|
+
--target <client> Repeatable: codex | claude | claudecode | cursor
|
|
25
|
+
--yes Non-interactive mode
|
|
26
|
+
skill add Add or refresh local skills
|
|
27
|
+
--scope <scope> project | user
|
|
28
|
+
--target <client> Repeatable: codex | claude | claudecode | cursor
|
|
29
|
+
--yes Add all syncable skills without prompt
|
|
30
|
+
skill prune Sync and remove stale Invokora-managed local skills
|
|
31
|
+
--scope <scope> project | user
|
|
32
|
+
--target <client> Repeatable: codex | claude | claudecode | cursor
|
|
33
|
+
--yes Non-interactive mode
|
|
34
|
+
skill remove Remove Invokora-managed local skills
|
|
35
|
+
--scope <scope> project | user
|
|
36
|
+
--target <client> Repeatable: codex | claude | claudecode | cursor
|
|
37
|
+
--yes Remove all managed skills without prompt
|
|
38
|
+
workflow add Add or refresh local skills from selected workflows
|
|
39
|
+
--scope <scope> project | user
|
|
40
|
+
--target <client> Repeatable: codex | claude | claudecode | cursor
|
|
41
|
+
--yes Add all syncable workflows without prompt
|
|
42
|
+
mcp serve Start the stdio MCP server
|
|
43
|
+
mcp setup Setup MCP config in Claude/Cursor/Codex
|
|
44
|
+
--target <client> Repeatable: claude | cursor | codex
|
|
45
|
+
--path <file> Optional explicit config path (repeat with target order)
|
|
46
|
+
--yes Non-interactive mode
|
|
47
|
+
mcp defaults Show or set MCP default model/reason
|
|
48
|
+
--model <model> Default model for new MCP chat sessions
|
|
49
|
+
--reason <effort> Default reasoning effort: low | medium | high | xhigh
|
|
50
|
+
--clear-model Clear default model
|
|
51
|
+
--clear-reason Clear default reasoning effort
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
invokora login # Open browser login and save local token
|
|
55
|
+
invokora --version # Print CLI version
|
|
56
|
+
invokora language zh # Show CLI help in Chinese
|
|
57
|
+
invokora login --no-open # Print login URL without opening browser
|
|
58
|
+
invokora login --timeout 180000 # Wait up to 180 seconds for login callback
|
|
59
|
+
invokora sync # Choose scope and clients interactively
|
|
60
|
+
invokora sync --scope project --target codex --yes
|
|
61
|
+
# Sync into project Codex skills without prompts
|
|
62
|
+
invokora skill add # Choose local skills to add or refresh
|
|
63
|
+
invokora skill prune --scope project --target cursor --yes
|
|
64
|
+
# Sync Cursor skills and prune stale managed skills
|
|
65
|
+
invokora skill remove --scope project --target codex --yes
|
|
66
|
+
# Remove project Codex Invokora-managed skills
|
|
67
|
+
invokora workflow add # Choose workflows to add or refresh
|
|
68
|
+
invokora mcp serve # Start stdio MCP server
|
|
69
|
+
invokora mcp setup # Setup MCP config interactively
|
|
70
|
+
invokora mcp setup --target claude --yes # Setup Claude MCP config without prompts
|
|
71
|
+
invokora mcp defaults --model gpt-5.4 --reason high
|
|
72
|
+
# Set MCP default model and reasoning effort
|
|
73
|
+
`.trim();
|
|
74
|
+
const USAGE_TEXT_ZH = `
|
|
75
|
+
invokora CLI
|
|
76
|
+
|
|
77
|
+
命令:
|
|
78
|
+
-h, --help 输出 CLI help
|
|
79
|
+
-v, --version 输出 CLI 版本
|
|
80
|
+
login [--timeout <ms>] [--no-open]
|
|
81
|
+
通过浏览器 OAuth 回调登录
|
|
82
|
+
language [en|zh] 查看或设置 CLI 语言
|
|
83
|
+
sync [--prune] 交互式同步可本地使用的 skills
|
|
84
|
+
--scope <scope> project | user
|
|
85
|
+
--target <client> 可重复:codex | claude | claudecode | cursor
|
|
86
|
+
--yes 非交互模式
|
|
87
|
+
skill add 新增或刷新本地 skills
|
|
88
|
+
--scope <scope> project | user
|
|
89
|
+
--target <client> 可重复:codex | claude | claudecode | cursor
|
|
90
|
+
--yes 免选择新增全部可同步 skills
|
|
91
|
+
skill prune 同步并清理失效的 Invokora 托管本地 skills
|
|
92
|
+
--scope <scope> project | user
|
|
93
|
+
--target <client> 可重复:codex | claude | claudecode | cursor
|
|
94
|
+
--yes 非交互模式
|
|
95
|
+
skill remove 删除 Invokora 托管本地 skills
|
|
96
|
+
--scope <scope> project | user
|
|
97
|
+
--target <client> 可重复:codex | claude | claudecode | cursor
|
|
98
|
+
--yes 免选择删除全部托管 skills
|
|
99
|
+
workflow add 新增或刷新所选 workflow 的本地 skills
|
|
100
|
+
--scope <scope> project | user
|
|
101
|
+
--target <client> 可重复:codex | claude | claudecode | cursor
|
|
102
|
+
--yes 免选择新增全部可同步 workflows
|
|
103
|
+
mcp serve 启动 stdio MCP server
|
|
104
|
+
mcp setup 写入 Claude/Cursor/Codex MCP 配置
|
|
105
|
+
--target <client> 可重复:claude | cursor | codex
|
|
106
|
+
--path <file> 可选配置路径,顺序对应 target
|
|
107
|
+
--yes 非交互模式
|
|
108
|
+
mcp defaults 查看或设置 MCP 默认模型 / 思考深度
|
|
109
|
+
--model <model> 新 MCP 会话默认模型
|
|
110
|
+
--reason <effort> 默认思考深度:low | medium | high | xhigh
|
|
111
|
+
--clear-model 清除默认模型
|
|
112
|
+
--clear-reason 清除默认思考深度
|
|
113
|
+
|
|
114
|
+
示例:
|
|
115
|
+
invokora login # 打开浏览器登录并保存本地 token
|
|
116
|
+
invokora --version # 输出 CLI 版本
|
|
117
|
+
invokora language en # 使用英文显示 CLI help
|
|
118
|
+
invokora login --no-open # 只打印登录 URL,不自动打开浏览器
|
|
119
|
+
invokora login --timeout 180000 # 等待登录回调最多 180 秒
|
|
120
|
+
invokora sync # 交互式选择同步范围和客户端
|
|
121
|
+
invokora sync --scope project --target codex --yes
|
|
122
|
+
# 免交互同步到项目级 Codex skills
|
|
123
|
+
invokora skill add # 选择要新增或刷新的本地 skills
|
|
124
|
+
invokora skill prune --scope project --target cursor --yes
|
|
125
|
+
# 同步 Cursor skills 并清理失效托管目录
|
|
126
|
+
invokora skill remove --scope project --target codex --yes
|
|
127
|
+
# 删除项目级 Codex 的 Invokora 托管 skills
|
|
128
|
+
invokora workflow add # 选择要新增或刷新的 workflows
|
|
129
|
+
invokora mcp serve # 启动 stdio MCP server
|
|
130
|
+
invokora mcp setup # 交互式写入 MCP 配置
|
|
131
|
+
invokora mcp setup --target claude --yes # 免确认写入 Claude MCP 配置
|
|
132
|
+
invokora mcp defaults --model gpt-5.4 --reason high
|
|
133
|
+
# 设置 MCP 默认模型和思考深度
|
|
134
|
+
`.trim();
|
|
135
|
+
const HELP_TEXT_EN = {
|
|
136
|
+
root: USAGE_TEXT_EN,
|
|
137
|
+
login: `
|
|
138
|
+
Usage: invokora login [--timeout <ms>] [--no-open]
|
|
139
|
+
|
|
140
|
+
Authenticate via browser OAuth loopback callback.
|
|
141
|
+
|
|
142
|
+
Options:
|
|
143
|
+
-h, --help Print login help
|
|
144
|
+
--timeout <ms> Wait for login callback; default 120000
|
|
145
|
+
--no-open Print login URL without opening browser
|
|
146
|
+
`.trim(),
|
|
147
|
+
language: `
|
|
148
|
+
Usage: invokora language [en|zh]
|
|
149
|
+
|
|
150
|
+
Show or set CLI language.
|
|
151
|
+
|
|
152
|
+
Options:
|
|
153
|
+
-h, --help Print language help
|
|
154
|
+
`.trim(),
|
|
155
|
+
sync: `
|
|
156
|
+
Usage: invokora sync [--prune] [--scope <scope>] [--target <client>] [--yes]
|
|
157
|
+
|
|
158
|
+
Sync local skills interactively.
|
|
159
|
+
|
|
160
|
+
Options:
|
|
161
|
+
-h, --help Print sync help
|
|
162
|
+
--prune Remove stale Invokora-managed local skills
|
|
163
|
+
--scope <scope> project | user
|
|
164
|
+
--target <client> Repeatable: codex | claude | claudecode | cursor
|
|
165
|
+
--yes Non-interactive mode
|
|
166
|
+
`.trim(),
|
|
167
|
+
skill: `
|
|
168
|
+
Skill Commands:
|
|
169
|
+
invokora skill add Add or refresh local skills
|
|
170
|
+
invokora skill prune Sync and remove stale managed skills
|
|
171
|
+
invokora skill remove Remove managed local skills
|
|
172
|
+
|
|
173
|
+
Options:
|
|
174
|
+
-h, --help Print skill help
|
|
175
|
+
`.trim(),
|
|
176
|
+
'skill-add': `
|
|
177
|
+
Usage: invokora skill add [--scope <scope>] [--target <client>] [--yes]
|
|
178
|
+
|
|
179
|
+
Add or refresh local skills.
|
|
180
|
+
|
|
181
|
+
Options:
|
|
182
|
+
-h, --help Print skill add help
|
|
183
|
+
--scope <scope> project | user
|
|
184
|
+
--target <client> Repeatable: codex | claude | claudecode | cursor
|
|
185
|
+
--yes Add all syncable skills without prompt
|
|
186
|
+
`.trim(),
|
|
187
|
+
'skill-prune': `
|
|
188
|
+
Usage: invokora skill prune [--scope <scope>] [--target <client>] [--yes]
|
|
189
|
+
|
|
190
|
+
Sync and remove stale Invokora-managed local skills.
|
|
191
|
+
|
|
192
|
+
Options:
|
|
193
|
+
-h, --help Print skill prune help
|
|
194
|
+
--scope <scope> project | user
|
|
195
|
+
--target <client> Repeatable: codex | claude | claudecode | cursor
|
|
196
|
+
--yes Non-interactive mode
|
|
197
|
+
`.trim(),
|
|
198
|
+
'skill-remove': `
|
|
199
|
+
Usage: invokora skill remove [--scope <scope>] [--target <client>] [--yes]
|
|
200
|
+
|
|
201
|
+
Remove Invokora-managed local skills.
|
|
202
|
+
|
|
203
|
+
Options:
|
|
204
|
+
-h, --help Print skill remove help
|
|
205
|
+
--scope <scope> project | user
|
|
206
|
+
--target <client> Repeatable: codex | claude | claudecode | cursor
|
|
207
|
+
--yes Remove all managed skills without prompt
|
|
208
|
+
`.trim(),
|
|
209
|
+
workflow: `
|
|
210
|
+
Workflow Commands:
|
|
211
|
+
invokora workflow add Add or refresh local skills from selected workflows
|
|
212
|
+
|
|
213
|
+
Options:
|
|
214
|
+
-h, --help Print workflow help
|
|
215
|
+
`.trim(),
|
|
216
|
+
'workflow-add': `
|
|
217
|
+
Usage: invokora workflow add [--scope <scope>] [--target <client>] [--yes]
|
|
218
|
+
|
|
219
|
+
Add or refresh local skills from selected workflows.
|
|
220
|
+
|
|
221
|
+
Options:
|
|
222
|
+
-h, --help Print workflow add help
|
|
223
|
+
--scope <scope> project | user
|
|
224
|
+
--target <client> Repeatable: codex | claude | claudecode | cursor
|
|
225
|
+
--yes Add all syncable workflows without prompt
|
|
226
|
+
`.trim(),
|
|
227
|
+
mcp: `
|
|
228
|
+
MCP Commands:
|
|
229
|
+
invokora mcp serve Start the stdio MCP server
|
|
230
|
+
invokora mcp setup Setup MCP config in Claude/Cursor/Codex
|
|
231
|
+
invokora mcp defaults Show or set MCP default model/reason
|
|
232
|
+
|
|
233
|
+
Options:
|
|
234
|
+
-h, --help Print MCP help
|
|
235
|
+
`.trim(),
|
|
236
|
+
'mcp-serve': `
|
|
237
|
+
Usage: invokora mcp serve
|
|
238
|
+
|
|
239
|
+
Start the stdio MCP server.
|
|
240
|
+
|
|
241
|
+
Options:
|
|
242
|
+
-h, --help Print MCP serve help
|
|
243
|
+
`.trim(),
|
|
244
|
+
'mcp-setup': `
|
|
245
|
+
Usage: invokora mcp setup [--target <client>] [--path <file>] [--yes]
|
|
246
|
+
|
|
247
|
+
Setup MCP config in Claude/Cursor/Codex.
|
|
248
|
+
|
|
249
|
+
Options:
|
|
250
|
+
-h, --help Print MCP setup help
|
|
251
|
+
--target <client> Repeatable: claude | cursor | codex
|
|
252
|
+
--path <file> Optional explicit config path; repeat with target order
|
|
253
|
+
--yes Non-interactive mode
|
|
254
|
+
`.trim(),
|
|
255
|
+
'mcp-defaults': `
|
|
256
|
+
Usage: invokora mcp defaults [--model <model>] [--reason <effort>] [--clear-model] [--clear-reason]
|
|
257
|
+
|
|
258
|
+
Show or set MCP default model/reason.
|
|
259
|
+
|
|
260
|
+
Options:
|
|
261
|
+
-h, --help Print MCP defaults help
|
|
262
|
+
--model <model> Default model for new MCP chat sessions
|
|
263
|
+
--reason <effort> Default reasoning effort: low | medium | high | xhigh
|
|
264
|
+
--clear-model Clear default model
|
|
265
|
+
--clear-reason Clear default reasoning effort
|
|
266
|
+
`.trim(),
|
|
267
|
+
};
|
|
268
|
+
const HELP_TEXT_ZH = {
|
|
269
|
+
root: USAGE_TEXT_ZH,
|
|
270
|
+
login: `
|
|
271
|
+
用法:invokora login [--timeout <ms>] [--no-open]
|
|
272
|
+
|
|
273
|
+
通过浏览器 OAuth 回调登录。
|
|
274
|
+
|
|
275
|
+
选项:
|
|
276
|
+
-h, --help 输出 login help
|
|
277
|
+
--timeout <ms> 等待登录回调,默认 120000
|
|
278
|
+
--no-open 只打印登录 URL,不自动打开浏览器
|
|
279
|
+
`.trim(),
|
|
280
|
+
language: `
|
|
281
|
+
用法:invokora language [en|zh]
|
|
282
|
+
|
|
283
|
+
查看或设置 CLI 语言。
|
|
284
|
+
|
|
285
|
+
选项:
|
|
286
|
+
-h, --help 输出 language help
|
|
287
|
+
`.trim(),
|
|
288
|
+
sync: `
|
|
289
|
+
用法:invokora sync [--prune] [--scope <scope>] [--target <client>] [--yes]
|
|
290
|
+
|
|
291
|
+
交互式同步可本地使用的 skills。
|
|
292
|
+
|
|
293
|
+
选项:
|
|
294
|
+
-h, --help 输出 sync help
|
|
295
|
+
--prune 清理失效的 Invokora 托管本地 skills
|
|
296
|
+
--scope <scope> project | user
|
|
297
|
+
--target <client> 可重复:codex | claude | claudecode | cursor
|
|
298
|
+
--yes 非交互模式
|
|
299
|
+
`.trim(),
|
|
300
|
+
skill: `
|
|
301
|
+
Skill 命令:
|
|
302
|
+
invokora skill add 新增或刷新本地 skills
|
|
303
|
+
invokora skill prune 同步并清理失效托管 skills
|
|
304
|
+
invokora skill remove 删除托管本地 skills
|
|
305
|
+
|
|
306
|
+
选项:
|
|
307
|
+
-h, --help 输出 skill help
|
|
308
|
+
`.trim(),
|
|
309
|
+
'skill-add': `
|
|
310
|
+
用法:invokora skill add [--scope <scope>] [--target <client>] [--yes]
|
|
311
|
+
|
|
312
|
+
新增或刷新本地 skills。
|
|
313
|
+
|
|
314
|
+
选项:
|
|
315
|
+
-h, --help 输出 skill add help
|
|
316
|
+
--scope <scope> project | user
|
|
317
|
+
--target <client> 可重复:codex | claude | claudecode | cursor
|
|
318
|
+
--yes 免选择新增全部可同步 skills
|
|
319
|
+
`.trim(),
|
|
320
|
+
'skill-prune': `
|
|
321
|
+
用法:invokora skill prune [--scope <scope>] [--target <client>] [--yes]
|
|
322
|
+
|
|
323
|
+
同步并清理失效的 Invokora 托管本地 skills。
|
|
324
|
+
|
|
325
|
+
选项:
|
|
326
|
+
-h, --help 输出 skill prune help
|
|
327
|
+
--scope <scope> project | user
|
|
328
|
+
--target <client> 可重复:codex | claude | claudecode | cursor
|
|
329
|
+
--yes 非交互模式
|
|
330
|
+
`.trim(),
|
|
331
|
+
'skill-remove': `
|
|
332
|
+
用法:invokora skill remove [--scope <scope>] [--target <client>] [--yes]
|
|
333
|
+
|
|
334
|
+
删除 Invokora 托管本地 skills。
|
|
335
|
+
|
|
336
|
+
选项:
|
|
337
|
+
-h, --help 输出 skill remove help
|
|
338
|
+
--scope <scope> project | user
|
|
339
|
+
--target <client> 可重复:codex | claude | claudecode | cursor
|
|
340
|
+
--yes 免选择删除全部托管 skills
|
|
341
|
+
`.trim(),
|
|
342
|
+
workflow: `
|
|
343
|
+
Workflow 命令:
|
|
344
|
+
invokora workflow add 新增或刷新所选 workflow 的本地 skills
|
|
345
|
+
|
|
346
|
+
选项:
|
|
347
|
+
-h, --help 输出 workflow help
|
|
348
|
+
`.trim(),
|
|
349
|
+
'workflow-add': `
|
|
350
|
+
用法:invokora workflow add [--scope <scope>] [--target <client>] [--yes]
|
|
351
|
+
|
|
352
|
+
新增或刷新所选 workflow 的本地 skills。
|
|
353
|
+
|
|
354
|
+
选项:
|
|
355
|
+
-h, --help 输出 workflow add help
|
|
356
|
+
--scope <scope> project | user
|
|
357
|
+
--target <client> 可重复:codex | claude | claudecode | cursor
|
|
358
|
+
--yes 免选择新增全部可同步 workflows
|
|
359
|
+
`.trim(),
|
|
360
|
+
mcp: `
|
|
361
|
+
MCP 命令:
|
|
362
|
+
invokora mcp serve 启动 stdio MCP server
|
|
363
|
+
invokora mcp setup 写入 Claude/Cursor/Codex MCP 配置
|
|
364
|
+
invokora mcp defaults 查看或设置 MCP 默认模型 / 思考深度
|
|
365
|
+
|
|
366
|
+
选项:
|
|
367
|
+
-h, --help 输出 MCP help
|
|
368
|
+
`.trim(),
|
|
369
|
+
'mcp-serve': `
|
|
370
|
+
用法:invokora mcp serve
|
|
371
|
+
|
|
372
|
+
启动 stdio MCP server。
|
|
373
|
+
|
|
374
|
+
选项:
|
|
375
|
+
-h, --help 输出 MCP serve help
|
|
376
|
+
`.trim(),
|
|
377
|
+
'mcp-setup': `
|
|
378
|
+
用法:invokora mcp setup [--target <client>] [--path <file>] [--yes]
|
|
379
|
+
|
|
380
|
+
写入 Claude/Cursor/Codex MCP 配置。
|
|
381
|
+
|
|
382
|
+
选项:
|
|
383
|
+
-h, --help 输出 MCP setup help
|
|
384
|
+
--target <client> 可重复:claude | cursor | codex
|
|
385
|
+
--path <file> 可选配置路径,顺序对应 target
|
|
386
|
+
--yes 非交互模式
|
|
387
|
+
`.trim(),
|
|
388
|
+
'mcp-defaults': `
|
|
389
|
+
用法:invokora mcp defaults [--model <model>] [--reason <effort>] [--clear-model] [--clear-reason]
|
|
390
|
+
|
|
391
|
+
查看或设置 MCP 默认模型 / 思考深度。
|
|
392
|
+
|
|
393
|
+
选项:
|
|
394
|
+
-h, --help 输出 MCP defaults help
|
|
395
|
+
--model <model> 新 MCP 会话默认模型
|
|
396
|
+
--reason <effort> 默认思考深度:low | medium | high | xhigh
|
|
397
|
+
--clear-model 清除默认模型
|
|
398
|
+
--clear-reason 清除默认思考深度
|
|
399
|
+
`.trim(),
|
|
400
|
+
};
|
|
401
|
+
export function parseCliLanguage(raw) {
|
|
402
|
+
const value = raw?.trim().toLowerCase().replace(/_/g, '-');
|
|
403
|
+
if (!value)
|
|
404
|
+
return null;
|
|
405
|
+
if (value === 'en' || value === 'en-us' || value === 'english')
|
|
406
|
+
return 'en';
|
|
407
|
+
if (value === 'zh' || value === 'zh-cn' || value === 'zh-hans' || value === 'chinese' || value === '中文')
|
|
408
|
+
return 'zh';
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
export function loadCliLanguage(languagePath = LANGUAGE_PATH) {
|
|
412
|
+
if (!existsSync(languagePath))
|
|
413
|
+
return 'en';
|
|
414
|
+
try {
|
|
415
|
+
return parseCliLanguage(readFileSync(languagePath, 'utf-8')) ?? 'en';
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
return 'en';
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
export function saveCliLanguage(language, languagePath = LANGUAGE_PATH) {
|
|
422
|
+
mkdirSync(dirname(languagePath), { recursive: true });
|
|
423
|
+
writeFileSync(languagePath, `${language}\n`, 'utf-8');
|
|
424
|
+
}
|
|
425
|
+
export function getUsageText(language = loadCliLanguage()) {
|
|
426
|
+
return language === 'zh' ? USAGE_TEXT_ZH : USAGE_TEXT_EN;
|
|
427
|
+
}
|
|
428
|
+
export function getCommandHelpText(args, language = loadCliLanguage()) {
|
|
429
|
+
const topic = resolveHelpTopic(args);
|
|
430
|
+
return language === 'zh' ? HELP_TEXT_ZH[topic] : HELP_TEXT_EN[topic];
|
|
431
|
+
}
|
|
432
|
+
export class InvokoraCliApp {
|
|
433
|
+
prompts;
|
|
434
|
+
configStore;
|
|
435
|
+
shellManager;
|
|
436
|
+
mcpService;
|
|
437
|
+
constructor(params) {
|
|
438
|
+
this.prompts = params?.prompts ?? new CliPrompts();
|
|
439
|
+
this.configStore = params?.configStore ?? new ConfigStore();
|
|
440
|
+
this.shellManager = params?.shellManager ?? new SkillShellManager();
|
|
441
|
+
this.mcpService = new McpConfigService(this.prompts);
|
|
442
|
+
}
|
|
443
|
+
async run(argv = process.argv) {
|
|
444
|
+
const [, , cmd, ...args] = argv;
|
|
445
|
+
if (isHelpRequest(cmd, args)) {
|
|
446
|
+
this.printUsage([cmd, ...args]);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
switch (cmd) {
|
|
450
|
+
case '-v':
|
|
451
|
+
case '--version':
|
|
452
|
+
return this.cmdVersion();
|
|
453
|
+
case 'login':
|
|
454
|
+
return this.cmdLogin(args);
|
|
455
|
+
case 'language':
|
|
456
|
+
return this.cmdLanguage(args);
|
|
457
|
+
case 'sync': {
|
|
458
|
+
let parsed;
|
|
459
|
+
try {
|
|
460
|
+
parsed = this.parseSyncArgs(args);
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|
|
466
|
+
let destinations;
|
|
467
|
+
try {
|
|
468
|
+
destinations = await this.resolveSyncDestinations(parsed);
|
|
469
|
+
}
|
|
470
|
+
catch (error) {
|
|
471
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
await this.syncSkills({ prune: parsed.prune }, undefined, destinations);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
case 'mcp':
|
|
478
|
+
if (args[0] === 'setup') {
|
|
479
|
+
return this.cmdMcpSetup(args.slice(1));
|
|
480
|
+
}
|
|
481
|
+
if (args[0] === 'serve') {
|
|
482
|
+
if (args.length > 1) {
|
|
483
|
+
console.error(`Unknown mcp serve options: ${args.slice(1).join(' ')}`);
|
|
484
|
+
process.exit(1);
|
|
485
|
+
}
|
|
486
|
+
return this.cmdMcpServe();
|
|
487
|
+
}
|
|
488
|
+
if (args[0] === 'defaults') {
|
|
489
|
+
return this.cmdMcpDefaults(args.slice(1));
|
|
490
|
+
}
|
|
491
|
+
console.error(`Unknown mcp subcommand/options: ${args.join(' ')}`);
|
|
492
|
+
process.exit(1);
|
|
493
|
+
case 'skill':
|
|
494
|
+
return this.cmdSkill(args);
|
|
495
|
+
case 'workflow':
|
|
496
|
+
return this.cmdWorkflow(args);
|
|
497
|
+
default:
|
|
498
|
+
this.printUsage();
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
async cmdLogin(rawArgs) {
|
|
502
|
+
let options;
|
|
503
|
+
try {
|
|
504
|
+
options = parseLoginCommandOptions(rawArgs);
|
|
505
|
+
}
|
|
506
|
+
catch (error) {
|
|
507
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
508
|
+
process.exit(1);
|
|
509
|
+
}
|
|
510
|
+
try {
|
|
511
|
+
const backendUrl = process.env.SKILZ_BACKEND_URL || DEFAULT_BACKEND_URL;
|
|
512
|
+
const response = await new OAuthBrowserLoginFlow({
|
|
513
|
+
backendUrl,
|
|
514
|
+
timeoutMs: options.timeoutMs,
|
|
515
|
+
noOpen: options.noOpen,
|
|
516
|
+
}).run();
|
|
517
|
+
this.configStore.save({
|
|
518
|
+
backend_url: backendUrl,
|
|
519
|
+
token: response.access_token,
|
|
520
|
+
refresh_token: response.refresh_token,
|
|
521
|
+
});
|
|
522
|
+
const email = response.user?.email;
|
|
523
|
+
console.log(`✓ Logged in${email ? ` as ${email}` : ''}`);
|
|
524
|
+
console.log(` Config saved to ${CONFIG_PATH}`);
|
|
525
|
+
}
|
|
526
|
+
catch (error) {
|
|
527
|
+
console.error('Login failed:', error instanceof Error ? error.message : String(error));
|
|
528
|
+
process.exit(1);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
cmdVersion() {
|
|
532
|
+
console.log(`invokora ${RELEASE_VERSION}`);
|
|
533
|
+
}
|
|
534
|
+
cmdLanguage(rawArgs) {
|
|
535
|
+
if (rawArgs.length === 0) {
|
|
536
|
+
console.log(`Language: ${loadCliLanguage()} (${LANGUAGE_PATH})`);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (rawArgs.length > 1) {
|
|
540
|
+
console.error('Usage: invokora language [en|zh]');
|
|
541
|
+
process.exit(1);
|
|
542
|
+
}
|
|
543
|
+
const language = parseCliLanguage(rawArgs[0]);
|
|
544
|
+
if (!language) {
|
|
545
|
+
console.error('Unsupported language. Use: en or zh');
|
|
546
|
+
process.exit(1);
|
|
547
|
+
}
|
|
548
|
+
saveCliLanguage(language);
|
|
549
|
+
console.log(`Language set to ${language}`);
|
|
550
|
+
}
|
|
551
|
+
async cmdMcpServe() {
|
|
552
|
+
try {
|
|
553
|
+
await startStdioServer();
|
|
554
|
+
}
|
|
555
|
+
catch (error) {
|
|
556
|
+
console.error(getErrorMessage(error));
|
|
557
|
+
const config = this.configStore.load();
|
|
558
|
+
if (config) {
|
|
559
|
+
console.error(`Support: ${this.supportUrl(config, { category: 'cannot_invoke' })}`);
|
|
560
|
+
}
|
|
561
|
+
process.exit(1);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
cmdMcpDefaults(rawArgs) {
|
|
565
|
+
let parsed;
|
|
566
|
+
try {
|
|
567
|
+
parsed = parseMcpDefaultsArgs(rawArgs);
|
|
568
|
+
}
|
|
569
|
+
catch (error) {
|
|
570
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
571
|
+
process.exit(1);
|
|
572
|
+
}
|
|
573
|
+
const config = this.requireConfig();
|
|
574
|
+
if (!parsed.changed) {
|
|
575
|
+
this.printMcpDefaults(config);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const next = { ...config };
|
|
579
|
+
if (parsed.clearModel) {
|
|
580
|
+
delete next.default_model;
|
|
581
|
+
}
|
|
582
|
+
if (parsed.model !== undefined) {
|
|
583
|
+
next.default_model = parsed.model;
|
|
584
|
+
}
|
|
585
|
+
if (parsed.clearReason) {
|
|
586
|
+
delete next.default_reason;
|
|
587
|
+
}
|
|
588
|
+
if (parsed.reason !== undefined) {
|
|
589
|
+
next.default_reason = parsed.reason;
|
|
590
|
+
}
|
|
591
|
+
this.configStore.save(next);
|
|
592
|
+
console.log(`✓ MCP defaults saved to ${CONFIG_PATH}`);
|
|
593
|
+
this.printMcpDefaults(next);
|
|
594
|
+
}
|
|
595
|
+
async cmdSkill(args) {
|
|
596
|
+
const [subcommand, ...rawArgs] = args;
|
|
597
|
+
switch (subcommand) {
|
|
598
|
+
case 'add':
|
|
599
|
+
return this.cmdSkillAdd(rawArgs);
|
|
600
|
+
case 'prune':
|
|
601
|
+
return this.cmdSkillPrune(rawArgs);
|
|
602
|
+
case 'remove':
|
|
603
|
+
return this.cmdSkillRemove(rawArgs);
|
|
604
|
+
default:
|
|
605
|
+
console.error(`Unknown skill subcommand/options: ${args.join(' ')}`);
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
async cmdSkillAdd(rawArgs) {
|
|
610
|
+
const parsed = this.parseLocalSkillArgs(rawArgs);
|
|
611
|
+
const destinations = await this.resolveSyncDestinations(parsed);
|
|
612
|
+
const config = this.requireConfig();
|
|
613
|
+
const skills = await this.loadActiveSyncableSkills(config);
|
|
614
|
+
const selectedSkills = parsed.yes ? skills : await this.pickSkillsForAdd(skills);
|
|
615
|
+
if (selectedSkills.length === 0) {
|
|
616
|
+
console.log('No skill selected. Nothing changed.');
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
await this.syncSkills({ prune: false }, config, destinations, selectedSkills);
|
|
620
|
+
}
|
|
621
|
+
async cmdSkillPrune(rawArgs) {
|
|
622
|
+
const parsed = this.parseLocalSkillArgs(rawArgs, { allowPruneFlag: true });
|
|
623
|
+
const destinations = await this.resolveSyncDestinations(parsed);
|
|
624
|
+
await this.syncSkills({ prune: true }, undefined, destinations);
|
|
625
|
+
}
|
|
626
|
+
async cmdSkillRemove(rawArgs) {
|
|
627
|
+
const parsed = this.parseLocalSkillArgs(rawArgs);
|
|
628
|
+
const destinations = await this.resolveSyncDestinations(parsed);
|
|
629
|
+
const selectedByDestination = parsed.yes
|
|
630
|
+
? new Map(destinations.map((destination) => [destination.label, undefined]))
|
|
631
|
+
: await this.pickLocalManagedSkills(destinations);
|
|
632
|
+
if (selectedByDestination.size === 0) {
|
|
633
|
+
console.log('No skill selected. Nothing changed.');
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
if (!parsed.yes) {
|
|
637
|
+
const selectedCount = [...selectedByDestination.values()].reduce((sum, slugs) => sum + (slugs?.length ?? 0), 0);
|
|
638
|
+
const confirmed = await this.prompts.yesNo(`Remove ${selectedCount} selected Invokora-managed local skill(s)?`, false);
|
|
639
|
+
if (!confirmed) {
|
|
640
|
+
console.log('Skipped.');
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
let removed = 0;
|
|
645
|
+
for (const destination of destinations) {
|
|
646
|
+
if (!selectedByDestination.has(destination.label))
|
|
647
|
+
continue;
|
|
648
|
+
const result = this.shellManager.removeManagedSkills(destination.rootDir, selectedByDestination.get(destination.label));
|
|
649
|
+
removed += result.removed;
|
|
650
|
+
console.log(`✓ ${destination.label}: removed ${result.removed} Invokora-managed skill(s) from ${destination.rootDir}`);
|
|
651
|
+
}
|
|
652
|
+
console.log(`Remove complete for ${destinations.length} destination(s), ${removed} skill(s) removed.`);
|
|
653
|
+
}
|
|
654
|
+
async cmdWorkflow(args) {
|
|
655
|
+
const [subcommand, ...rawArgs] = args;
|
|
656
|
+
switch (subcommand) {
|
|
657
|
+
case 'add':
|
|
658
|
+
return this.cmdWorkflowAdd(rawArgs);
|
|
659
|
+
default:
|
|
660
|
+
console.error(`Unknown workflow subcommand/options: ${args.join(' ')}`);
|
|
661
|
+
process.exit(1);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
async cmdWorkflowAdd(rawArgs) {
|
|
665
|
+
const parsed = this.parseLocalSkillArgs(rawArgs);
|
|
666
|
+
const destinations = await this.resolveSyncDestinations(parsed);
|
|
667
|
+
const config = this.requireConfig();
|
|
668
|
+
const client = this.apiClient(config);
|
|
669
|
+
const skills = await this.loadActiveSyncableSkills(config, client);
|
|
670
|
+
const workflows = this.filterWorkflowsWithSyncableSkills(await this.loadSyncableWorkflows(client), skills);
|
|
671
|
+
if (workflows.length === 0) {
|
|
672
|
+
console.log('No workflow available to add. Nothing changed.');
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const selectedWorkflows = parsed.yes ? workflows : await this.pickSyncableWorkflows(workflows);
|
|
676
|
+
if (selectedWorkflows.length === 0) {
|
|
677
|
+
console.log('No workflow selected. Nothing changed.');
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
const selectedSkillIDs = new Set(selectedWorkflows.flatMap((workflow) => workflow.skill_ids));
|
|
681
|
+
const selectedSkills = skills.filter((skill) => selectedSkillIDs.has(skill.id));
|
|
682
|
+
if (selectedSkills.length === 0) {
|
|
683
|
+
console.log('No syncable skill found for selected workflow(s). Nothing changed.');
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
await this.syncSkills({ prune: false }, config, destinations, selectedSkills);
|
|
687
|
+
}
|
|
688
|
+
async cmdMcpSetup(rawArgs) {
|
|
689
|
+
let parsed;
|
|
690
|
+
try {
|
|
691
|
+
parsed = this.mcpService.parseSetupArgs(rawArgs);
|
|
692
|
+
}
|
|
693
|
+
catch (error) {
|
|
694
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
695
|
+
process.exit(1);
|
|
696
|
+
}
|
|
697
|
+
let targets;
|
|
698
|
+
try {
|
|
699
|
+
targets = await this.mcpService.resolveTargets(parsed);
|
|
700
|
+
}
|
|
701
|
+
catch (error) {
|
|
702
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
703
|
+
process.exit(1);
|
|
704
|
+
}
|
|
705
|
+
if (targets.length === 0) {
|
|
706
|
+
console.log('No valid target selected. Nothing changed.');
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
let explicitPaths;
|
|
710
|
+
try {
|
|
711
|
+
explicitPaths = this.mcpService.mapPathsToTargets(targets, parsed.paths);
|
|
712
|
+
}
|
|
713
|
+
catch (error) {
|
|
714
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
715
|
+
process.exit(1);
|
|
716
|
+
}
|
|
717
|
+
const config = this.requireConfig();
|
|
718
|
+
const serverConfig = this.mcpService.buildServerConfig(config);
|
|
719
|
+
let updatedCount = 0;
|
|
720
|
+
for (const target of targets) {
|
|
721
|
+
const explicitPath = explicitPaths.get(target);
|
|
722
|
+
try {
|
|
723
|
+
const result = await this.mcpService.setupTargetConfig({
|
|
724
|
+
target,
|
|
725
|
+
explicitPath,
|
|
726
|
+
serverConfig,
|
|
727
|
+
yes: parsed.yes,
|
|
728
|
+
});
|
|
729
|
+
if (result.status === 'updated') {
|
|
730
|
+
updatedCount++;
|
|
731
|
+
console.log(`✓ ${target}: ${result.path}`);
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
console.log(`- ${target}: skipped`);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
catch (error) {
|
|
738
|
+
console.error(`✗ ${target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
if (updatedCount === 0) {
|
|
742
|
+
console.error('No MCP target was updated.');
|
|
743
|
+
process.exit(1);
|
|
744
|
+
}
|
|
745
|
+
console.log('\nMCP setup complete.');
|
|
746
|
+
}
|
|
747
|
+
parseSyncArgs(args) {
|
|
748
|
+
let prune = false;
|
|
749
|
+
let yes = false;
|
|
750
|
+
let scope;
|
|
751
|
+
const targets = [];
|
|
752
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
753
|
+
const arg = args[i];
|
|
754
|
+
if (arg === '--prune') {
|
|
755
|
+
prune = true;
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
if (arg === '--yes' || arg === '-y') {
|
|
759
|
+
yes = true;
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
if (arg === '--scope') {
|
|
763
|
+
const value = args[i + 1];
|
|
764
|
+
if (!value)
|
|
765
|
+
throw new Error('Missing value for --scope');
|
|
766
|
+
const parsedScope = SYNC_SCOPE_ALIASES[value.trim().toLowerCase()];
|
|
767
|
+
if (!parsedScope)
|
|
768
|
+
throw new Error(`Unsupported sync scope: ${value}`);
|
|
769
|
+
scope = parsedScope;
|
|
770
|
+
i += 1;
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
if (arg === '--target') {
|
|
774
|
+
const value = args[i + 1];
|
|
775
|
+
if (!value)
|
|
776
|
+
throw new Error('Missing value for --target');
|
|
777
|
+
const parsedTarget = SYNC_TARGET_ALIASES[value.trim().toLowerCase()];
|
|
778
|
+
if (!parsedTarget)
|
|
779
|
+
throw new Error(`Unsupported sync target: ${value}`);
|
|
780
|
+
targets.push(parsedTarget);
|
|
781
|
+
i += 1;
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
throw new Error(`Unknown sync option: ${arg}`);
|
|
785
|
+
}
|
|
786
|
+
return { prune, yes, scope, targets: [...new Set(targets)] };
|
|
787
|
+
}
|
|
788
|
+
parseLocalSkillArgs(args, options = {}) {
|
|
789
|
+
try {
|
|
790
|
+
const parsed = this.parseSyncArgs(args);
|
|
791
|
+
if (parsed.prune && !options.allowPruneFlag) {
|
|
792
|
+
throw new Error('--prune is only supported by invokora sync or invokora skill prune');
|
|
793
|
+
}
|
|
794
|
+
return parsed;
|
|
795
|
+
}
|
|
796
|
+
catch (error) {
|
|
797
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
798
|
+
process.exit(1);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
async resolveSyncDestinations(parsed) {
|
|
802
|
+
const scope = parsed.scope ?? (parsed.yes ? 'project' : await this.prompts.pickSyncScope());
|
|
803
|
+
const targets = parsed.targets.length > 0
|
|
804
|
+
? parsed.targets
|
|
805
|
+
: parsed.yes
|
|
806
|
+
? ['codex']
|
|
807
|
+
: await this.prompts.pickSyncTargets(scope);
|
|
808
|
+
return targets.map((target) => this.shellManager.resolveSyncDestination(target, scope));
|
|
809
|
+
}
|
|
810
|
+
async syncSkills(options, config = this.requireConfig(), destinations = [this.shellManager.resolveSyncDestination('codex', 'project')], selectedSkills) {
|
|
811
|
+
const client = this.apiClient(config);
|
|
812
|
+
const skills = selectedSkills ?? await this.loadActiveSyncableSkills(config, client);
|
|
813
|
+
const syncedContentBySkill = await this.downloadManagedSkillContent(client, skills);
|
|
814
|
+
const aggregate = { written: 0, pruned: 0, activeSlugs: new Set() };
|
|
815
|
+
for (const destination of destinations) {
|
|
816
|
+
const result = this.shellManager.sync(skills, {
|
|
817
|
+
...options,
|
|
818
|
+
rootDir: destination.rootDir,
|
|
819
|
+
destinationLabel: destination.label,
|
|
820
|
+
}, syncedContentBySkill);
|
|
821
|
+
result.activeSlugs.forEach((slug) => aggregate.activeSlugs.add(slug));
|
|
822
|
+
aggregate.written += result.written;
|
|
823
|
+
aggregate.pruned += result.pruned;
|
|
824
|
+
if (!options.quiet) {
|
|
825
|
+
console.log(`✓ ${destination.label}: synced ${result.written} skill(s) to ${destination.rootDir}`);
|
|
826
|
+
if (options.prune) {
|
|
827
|
+
console.log(` pruned ${result.pruned} stale skill(s)`);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
if (!options.quiet) {
|
|
832
|
+
console.log(`Sync complete for ${destinations.length} destination(s).`);
|
|
833
|
+
}
|
|
834
|
+
return aggregate;
|
|
835
|
+
}
|
|
836
|
+
async loadActiveSyncableSkills(config, client = this.apiClient(config)) {
|
|
837
|
+
return (await client.listSyncableDetails()).filter((skill) => skill.status === 'active');
|
|
838
|
+
}
|
|
839
|
+
async pickSyncableSkills(skills) {
|
|
840
|
+
const selectedIDs = new Set(await this.prompts.pickSyncableSkills(skills.map((skill) => ({
|
|
841
|
+
value: skill.id,
|
|
842
|
+
label: skill.name || skill.id,
|
|
843
|
+
hint: skill.description || skill.access_source,
|
|
844
|
+
}))));
|
|
845
|
+
return skills.filter((skill) => selectedIDs.has(skill.id));
|
|
846
|
+
}
|
|
847
|
+
async pickSkillsForAdd(skills) {
|
|
848
|
+
if (skills.length === 0) {
|
|
849
|
+
return [];
|
|
850
|
+
}
|
|
851
|
+
const mode = await this.prompts.pickSyncableSkillAddMode();
|
|
852
|
+
if (mode === 'all') {
|
|
853
|
+
return skills;
|
|
854
|
+
}
|
|
855
|
+
if (mode === 'none') {
|
|
856
|
+
return [];
|
|
857
|
+
}
|
|
858
|
+
return this.pickSyncableSkills(skills);
|
|
859
|
+
}
|
|
860
|
+
async pickSyncableWorkflows(workflows) {
|
|
861
|
+
const selectedIDs = new Set(await this.prompts.pickSyncableWorkflows(workflows.map((workflow) => ({
|
|
862
|
+
value: workflow.id,
|
|
863
|
+
label: workflow.name || workflow.id,
|
|
864
|
+
hint: `${workflow.source} · ${workflow.skill_ids.length} skill(s)${workflow.description ? ` · ${workflow.description}` : ''}`,
|
|
865
|
+
}))));
|
|
866
|
+
return workflows.filter((workflow) => selectedIDs.has(workflow.id));
|
|
867
|
+
}
|
|
868
|
+
async loadSyncableWorkflows(client) {
|
|
869
|
+
const workflows = [];
|
|
870
|
+
const entitlements = await client.listEntitlements();
|
|
871
|
+
const marketplaceWorkflowIDs = [...new Set(entitlements.map((entitlement) => entitlement.workflow_id).filter((id) => Boolean(id)))];
|
|
872
|
+
for (const workflowID of marketplaceWorkflowIDs) {
|
|
873
|
+
const detail = await client.getWorkflowDetail(workflowID);
|
|
874
|
+
workflows.push({
|
|
875
|
+
id: `marketplace:${workflowID}`,
|
|
876
|
+
name: detail.workflow.name || workflowID,
|
|
877
|
+
description: detail.workflow.description || '',
|
|
878
|
+
source: 'marketplace',
|
|
879
|
+
skill_ids: this.workflowSkillIDs(detail.components),
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
const personalWorkflows = await client.listPersonalWorkflows();
|
|
883
|
+
for (const workflow of personalWorkflows.filter((item) => item.status === undefined || item.status === 'active')) {
|
|
884
|
+
const detail = await client.getPersonalWorkflowDetail(workflow.id);
|
|
885
|
+
workflows.push({
|
|
886
|
+
id: `personal:${workflow.id}`,
|
|
887
|
+
name: detail.workflow.name || workflow.name || workflow.id,
|
|
888
|
+
description: detail.workflow.description || workflow.description || '',
|
|
889
|
+
source: 'personal',
|
|
890
|
+
skill_ids: this.workflowSkillIDs(detail.components),
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
return workflows;
|
|
894
|
+
}
|
|
895
|
+
filterWorkflowsWithSyncableSkills(workflows, skills) {
|
|
896
|
+
const syncableSkillIDs = new Set(skills.map((skill) => skill.id));
|
|
897
|
+
return workflows
|
|
898
|
+
.map((workflow) => ({
|
|
899
|
+
...workflow,
|
|
900
|
+
skill_ids: workflow.skill_ids.filter((skillID) => syncableSkillIDs.has(skillID)),
|
|
901
|
+
}))
|
|
902
|
+
.filter((workflow) => workflow.skill_ids.length > 0);
|
|
903
|
+
}
|
|
904
|
+
workflowSkillIDs(components) {
|
|
905
|
+
const ids = components.map((component) => {
|
|
906
|
+
if ('skill' in component)
|
|
907
|
+
return component.skill?.id;
|
|
908
|
+
return component.id;
|
|
909
|
+
});
|
|
910
|
+
return [...new Set(ids.filter((id) => Boolean(id)))];
|
|
911
|
+
}
|
|
912
|
+
async pickLocalManagedSkills(destinations) {
|
|
913
|
+
const choices = destinations.flatMap((destination) => this.shellManager.listManagedSkills(destination.rootDir).map((skill) => ({
|
|
914
|
+
value: `${destination.label}\u0000${skill.slug}`,
|
|
915
|
+
label: skill.slug,
|
|
916
|
+
hint: `${destination.label}${skill.skill_id ? ` · ${skill.skill_id}` : ''}`,
|
|
917
|
+
})));
|
|
918
|
+
const selectedValues = await this.prompts.pickLocalManagedSkills(choices);
|
|
919
|
+
const selectedByDestination = new Map();
|
|
920
|
+
for (const selectedValue of selectedValues) {
|
|
921
|
+
const [label, slug] = selectedValue.split('\u0000');
|
|
922
|
+
if (!label || !slug)
|
|
923
|
+
continue;
|
|
924
|
+
const slugs = selectedByDestination.get(label) ?? [];
|
|
925
|
+
slugs.push(slug);
|
|
926
|
+
selectedByDestination.set(label, slugs);
|
|
927
|
+
}
|
|
928
|
+
return selectedByDestination;
|
|
929
|
+
}
|
|
930
|
+
async downloadManagedSkillContent(client, skills) {
|
|
931
|
+
const syncedContentBySkill = new Map();
|
|
932
|
+
for (const skill of skills) {
|
|
933
|
+
if (!this.shouldWriteManagedContent(skill))
|
|
934
|
+
continue;
|
|
935
|
+
syncedContentBySkill.set(skill.id, await client.getSyncableSkillContent(skill.id));
|
|
936
|
+
}
|
|
937
|
+
return syncedContentBySkill;
|
|
938
|
+
}
|
|
939
|
+
shouldWriteManagedContent(skill) {
|
|
940
|
+
if (skill.access_source === 'personal') {
|
|
941
|
+
return true;
|
|
942
|
+
}
|
|
943
|
+
if (skill.access_source !== 'entitlement') {
|
|
944
|
+
return false;
|
|
945
|
+
}
|
|
946
|
+
return skill.billing_mode === 'public_version_lock' || skill.billing_mode === 'public_buyout_updates';
|
|
947
|
+
}
|
|
948
|
+
apiClient(config = this.requireConfig()) {
|
|
949
|
+
return new InvokoraApiClient(config);
|
|
950
|
+
}
|
|
951
|
+
supportUrl(config, params) {
|
|
952
|
+
const q = new URLSearchParams();
|
|
953
|
+
for (const [key, value] of Object.entries(params)) {
|
|
954
|
+
if (value)
|
|
955
|
+
q.set(key, value);
|
|
956
|
+
}
|
|
957
|
+
const query = q.toString();
|
|
958
|
+
return new URL(query ? `/workspace/support?${query}` : '/workspace/support', config.backend_url).toString();
|
|
959
|
+
}
|
|
960
|
+
requireConfig() {
|
|
961
|
+
try {
|
|
962
|
+
return this.configStore.require();
|
|
963
|
+
}
|
|
964
|
+
catch (error) {
|
|
965
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
966
|
+
process.exit(1);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
printUsage(args = []) {
|
|
970
|
+
console.log(getCommandHelpText(args.filter((arg) => typeof arg === 'string')));
|
|
971
|
+
}
|
|
972
|
+
printMcpDefaults(config) {
|
|
973
|
+
console.log(`Default model: ${config.default_model || '(backend default)'}`);
|
|
974
|
+
console.log(`Default reason: ${config.default_reason || '(backend default)'}`);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
function parseMcpDefaultsArgs(args) {
|
|
978
|
+
const parsed = {
|
|
979
|
+
changed: false,
|
|
980
|
+
clearModel: false,
|
|
981
|
+
clearReason: false,
|
|
982
|
+
};
|
|
983
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
984
|
+
const arg = args[i];
|
|
985
|
+
if (arg === '--model') {
|
|
986
|
+
const value = args[i + 1]?.trim();
|
|
987
|
+
if (!value)
|
|
988
|
+
throw new Error('Missing value for --model');
|
|
989
|
+
parsed.model = value;
|
|
990
|
+
parsed.changed = true;
|
|
991
|
+
i += 1;
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
if (arg === '--reason') {
|
|
995
|
+
const value = args[i + 1];
|
|
996
|
+
if (!value)
|
|
997
|
+
throw new Error('Missing value for --reason');
|
|
998
|
+
parsed.reason = parseReasoningEffort(value);
|
|
999
|
+
parsed.changed = true;
|
|
1000
|
+
i += 1;
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
if (arg === '--clear-model') {
|
|
1004
|
+
parsed.clearModel = true;
|
|
1005
|
+
parsed.changed = true;
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
if (arg === '--clear-reason') {
|
|
1009
|
+
parsed.clearReason = true;
|
|
1010
|
+
parsed.changed = true;
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
throw new Error(`Unknown mcp defaults option: ${arg}`);
|
|
1014
|
+
}
|
|
1015
|
+
if (parsed.model !== undefined && parsed.clearModel) {
|
|
1016
|
+
throw new Error('--model and --clear-model cannot be used together');
|
|
1017
|
+
}
|
|
1018
|
+
if (parsed.reason !== undefined && parsed.clearReason) {
|
|
1019
|
+
throw new Error('--reason and --clear-reason cannot be used together');
|
|
1020
|
+
}
|
|
1021
|
+
return parsed;
|
|
1022
|
+
}
|
|
1023
|
+
function parseReasoningEffort(value) {
|
|
1024
|
+
const normalized = value.trim().toLowerCase();
|
|
1025
|
+
if (normalized === 'low' || normalized === 'medium' || normalized === 'high' || normalized === 'xhigh') {
|
|
1026
|
+
return normalized;
|
|
1027
|
+
}
|
|
1028
|
+
throw new Error('--reason must be one of: low, medium, high, xhigh');
|
|
1029
|
+
}
|
|
1030
|
+
function resolveHelpTopic(args) {
|
|
1031
|
+
const tokens = args.filter((arg) => !isHelpFlag(arg));
|
|
1032
|
+
const [cmd, subcommand] = tokens;
|
|
1033
|
+
if (cmd === 'login')
|
|
1034
|
+
return 'login';
|
|
1035
|
+
if (cmd === 'language')
|
|
1036
|
+
return 'language';
|
|
1037
|
+
if (cmd === 'sync')
|
|
1038
|
+
return 'sync';
|
|
1039
|
+
if (cmd === 'skill') {
|
|
1040
|
+
if (subcommand === 'add')
|
|
1041
|
+
return 'skill-add';
|
|
1042
|
+
if (subcommand === 'prune')
|
|
1043
|
+
return 'skill-prune';
|
|
1044
|
+
if (subcommand === 'remove')
|
|
1045
|
+
return 'skill-remove';
|
|
1046
|
+
return 'skill';
|
|
1047
|
+
}
|
|
1048
|
+
if (cmd === 'workflow') {
|
|
1049
|
+
if (subcommand === 'add')
|
|
1050
|
+
return 'workflow-add';
|
|
1051
|
+
return 'workflow';
|
|
1052
|
+
}
|
|
1053
|
+
if (cmd === 'mcp') {
|
|
1054
|
+
if (subcommand === 'serve')
|
|
1055
|
+
return 'mcp-serve';
|
|
1056
|
+
if (subcommand === 'setup')
|
|
1057
|
+
return 'mcp-setup';
|
|
1058
|
+
if (subcommand === 'defaults')
|
|
1059
|
+
return 'mcp-defaults';
|
|
1060
|
+
return 'mcp';
|
|
1061
|
+
}
|
|
1062
|
+
return 'root';
|
|
1063
|
+
}
|
|
1064
|
+
function isHelpRequest(cmd, args) {
|
|
1065
|
+
return isHelpFlag(cmd) || args.some(isHelpFlag);
|
|
1066
|
+
}
|
|
1067
|
+
function isHelpFlag(arg) {
|
|
1068
|
+
return arg === '-h' || arg === '--help';
|
|
1069
|
+
}
|
|
1070
|
+
export async function cmdLogin(rawArgs) {
|
|
1071
|
+
await new InvokoraCliApp().cmdLogin(rawArgs);
|
|
1072
|
+
}
|
|
1073
|
+
export function isMainModule(importMetaUrl) {
|
|
1074
|
+
const entryPoint = process.argv[1];
|
|
1075
|
+
if (typeof entryPoint !== 'string')
|
|
1076
|
+
return false;
|
|
1077
|
+
try {
|
|
1078
|
+
const currentFile = fileURLToPath(importMetaUrl);
|
|
1079
|
+
return realpathSync(entryPoint) === realpathSync(currentFile);
|
|
1080
|
+
}
|
|
1081
|
+
catch {
|
|
1082
|
+
return pathToFileURL(entryPoint).href === importMetaUrl;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
export async function main(argv = process.argv) {
|
|
1086
|
+
await new InvokoraCliApp().run(argv);
|
|
1087
|
+
}
|