abelworkflow 0.9.2 → 1.0.0-rc.1

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 (126) hide show
  1. package/README.md +55 -182
  2. package/bin/abelworkflow.mjs +5 -3
  3. package/lib/cli/args.mjs +76 -0
  4. package/lib/cli/main.mjs +387 -0
  5. package/lib/cli/prompts.mjs +119 -0
  6. package/lib/config/dotenv.mjs +102 -0
  7. package/lib/config/jsonc.mjs +80 -0
  8. package/lib/config/store.mjs +310 -0
  9. package/lib/config/toml.mjs +638 -0
  10. package/lib/installer/assets.mjs +326 -0
  11. package/lib/installer/install.mjs +167 -0
  12. package/lib/installer/links.mjs +427 -0
  13. package/lib/installer/render.mjs +46 -0
  14. package/lib/installer/state.mjs +182 -0
  15. package/lib/paths.mjs +99 -0
  16. package/lib/providers/claude.mjs +388 -0
  17. package/lib/providers/codex.mjs +433 -0
  18. package/lib/providers/pi.mjs +362 -0
  19. package/lib/providers/skills.mjs +164 -0
  20. package/lib/templates/codex/agents/default.toml +7 -0
  21. package/lib/templates/codex/agents/explorer.toml +7 -0
  22. package/lib/templates/codex/agents/planner.toml +7 -0
  23. package/lib/templates/codex/agents/reviewer.toml +7 -0
  24. package/lib/templates/codex/agents/worker.toml +7 -0
  25. package/lib/templates/codex/config-base.toml +11 -3
  26. package/{AGENTS.md → lib/templates/workflow/AGENTS.md} +8 -3
  27. package/{commands → lib/templates/workflow/commands}/abel-diagnose.md +5 -5
  28. package/{commands → lib/templates/workflow/commands}/abel-implement.md +3 -8
  29. package/{commands → lib/templates/workflow/commands}/abel-init.md +1 -1
  30. package/{commands → lib/templates/workflow/commands}/abel-plan.md +1 -7
  31. package/{commands → lib/templates/workflow/commands}/abel-research.md +1 -1
  32. package/{.gitignore → lib/templates/workflow/gitignore.template} +0 -2
  33. package/lib/tools/cli-installer.mjs +277 -0
  34. package/package.json +43 -10
  35. package/skills/context7-auto-research/SKILL.md +5 -5
  36. package/skills/context7-auto-research/{context7-api.js → context7-api.cjs} +5 -8
  37. package/skills/dev-browser/SKILL.md +53 -33
  38. package/skills/dev-browser/dist/scripts/start.d.ts +2 -0
  39. package/skills/dev-browser/dist/scripts/start.d.ts.map +1 -0
  40. package/skills/dev-browser/dist/scripts/start.js +118 -0
  41. package/skills/dev-browser/dist/scripts/start.js.map +1 -0
  42. package/skills/dev-browser/dist/src/client.d.ts +93 -0
  43. package/skills/dev-browser/dist/src/client.d.ts.map +1 -0
  44. package/skills/dev-browser/dist/src/client.js +310 -0
  45. package/skills/dev-browser/dist/src/client.js.map +1 -0
  46. package/skills/dev-browser/dist/src/entrypoint.d.ts +29 -0
  47. package/skills/dev-browser/dist/src/entrypoint.d.ts.map +1 -0
  48. package/skills/dev-browser/dist/src/entrypoint.js +106 -0
  49. package/skills/dev-browser/dist/src/entrypoint.js.map +1 -0
  50. package/skills/dev-browser/dist/src/index.d.ts +4 -0
  51. package/skills/dev-browser/dist/src/index.d.ts.map +1 -0
  52. package/skills/dev-browser/dist/src/index.js +2 -0
  53. package/skills/dev-browser/dist/src/index.js.map +1 -0
  54. package/skills/dev-browser/dist/src/page-api.d.ts +25 -0
  55. package/skills/dev-browser/dist/src/page-api.d.ts.map +1 -0
  56. package/skills/dev-browser/dist/src/page-api.js +104 -0
  57. package/skills/dev-browser/dist/src/page-api.js.map +1 -0
  58. package/skills/dev-browser/dist/src/relay.d.ts +27 -0
  59. package/skills/dev-browser/dist/src/relay.d.ts.map +1 -0
  60. package/skills/dev-browser/dist/src/relay.js +521 -0
  61. package/skills/dev-browser/dist/src/relay.js.map +1 -0
  62. package/skills/dev-browser/dist/src/runtime.d.ts +45 -0
  63. package/skills/dev-browser/dist/src/runtime.d.ts.map +1 -0
  64. package/skills/dev-browser/dist/src/runtime.js +54 -0
  65. package/skills/dev-browser/dist/src/runtime.js.map +1 -0
  66. package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +23 -0
  67. package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts.map +1 -0
  68. package/skills/dev-browser/{src/snapshot/browser-script.ts → dist/src/snapshot/browser-script.js} +21 -30
  69. package/skills/dev-browser/dist/src/snapshot/browser-script.js.map +1 -0
  70. package/skills/dev-browser/dist/src/snapshot/index.d.ts +14 -0
  71. package/skills/dev-browser/dist/src/snapshot/index.d.ts.map +1 -0
  72. package/skills/dev-browser/{src/snapshot/index.ts → dist/src/snapshot/index.js} +2 -2
  73. package/skills/dev-browser/dist/src/snapshot/index.js.map +1 -0
  74. package/skills/dev-browser/dist/src/snapshot/inject.d.ts +13 -0
  75. package/skills/dev-browser/dist/src/snapshot/inject.d.ts.map +1 -0
  76. package/skills/dev-browser/{src/snapshot/inject.ts → dist/src/snapshot/inject.js} +2 -2
  77. package/skills/dev-browser/dist/src/snapshot/inject.js.map +1 -0
  78. package/skills/dev-browser/dist/src/standalone.d.ts +32 -0
  79. package/skills/dev-browser/dist/src/standalone.d.ts.map +1 -0
  80. package/skills/dev-browser/dist/src/standalone.js +174 -0
  81. package/skills/dev-browser/dist/src/standalone.js.map +1 -0
  82. package/skills/dev-browser/dist/src/startup.d.ts +55 -0
  83. package/skills/dev-browser/dist/src/startup.d.ts.map +1 -0
  84. package/skills/dev-browser/dist/src/startup.js +81 -0
  85. package/skills/dev-browser/dist/src/startup.js.map +1 -0
  86. package/skills/dev-browser/dist/src/target-registry.d.ts +29 -0
  87. package/skills/dev-browser/dist/src/target-registry.d.ts.map +1 -0
  88. package/skills/dev-browser/dist/src/target-registry.js +135 -0
  89. package/skills/dev-browser/dist/src/target-registry.js.map +1 -0
  90. package/skills/dev-browser/dist/src/types.d.ts +27 -0
  91. package/skills/dev-browser/dist/src/types.d.ts.map +1 -0
  92. package/skills/dev-browser/dist/src/types.js +2 -0
  93. package/skills/dev-browser/dist/src/types.js.map +1 -0
  94. package/skills/dev-browser/package-lock.json +67 -1510
  95. package/skills/dev-browser/package.json +18 -14
  96. package/skills/dev-browser/references/scraping.md +94 -105
  97. package/skills/grok-search/defaults.json +3 -0
  98. package/skills/grok-search/gitignore.template +4 -0
  99. package/skills/grok-search/scripts/_dotenv.py +5 -1
  100. package/skills/grok-search/scripts/groksearch_cli.py +15 -33
  101. package/skills/prompt-enhancer/TEMPLATE.md +0 -4
  102. package/skills/prompt-enhancer/requirements.txt +0 -1
  103. package/skills/prompt-enhancer/scripts/_dotenv.py +5 -1
  104. package/skills/prompt-enhancer/scripts/enhance.py +4 -51
  105. package/skills/prompt-enhancer/scripts/prompt_enhancer_entry.py +1 -16
  106. package/.skill-lock.json +0 -29
  107. package/lib/cli/logic.mjs +0 -213
  108. package/lib/cli.mjs +0 -3016
  109. package/skills/confidence-check/SKILL.md +0 -34
  110. package/skills/confidence-check/confidence.ts +0 -276
  111. package/skills/dev-browser/bun.lock +0 -460
  112. package/skills/dev-browser/scripts/start.ts +0 -280
  113. package/skills/dev-browser/src/client.ts +0 -474
  114. package/skills/dev-browser/src/entrypoint.ts +0 -157
  115. package/skills/dev-browser/src/index.ts +0 -289
  116. package/skills/dev-browser/src/relay.ts +0 -731
  117. package/skills/dev-browser/src/runtime.test.ts +0 -60
  118. package/skills/dev-browser/src/runtime.ts +0 -171
  119. package/skills/dev-browser/src/startup.test.ts +0 -95
  120. package/skills/dev-browser/src/startup.ts +0 -153
  121. package/skills/dev-browser/src/types.ts +0 -35
  122. package/skills/dev-browser/tsconfig.json +0 -36
  123. package/skills/dev-browser/vitest.config.ts +0 -12
  124. package/skills/sequential-think/SKILL.md +0 -198
  125. package/skills/sequential-think/scripts/.env.example +0 -5
  126. package/skills/sequential-think/scripts/sequential_think_cli.py +0 -253
@@ -0,0 +1,387 @@
1
+ import { resolve } from "node:path";
2
+ import { stdin as input, stdout as output } from "node:process";
3
+ import * as p from "@clack/prompts";
4
+ import c from "picocolors";
5
+ import { assertInteractiveMenuSupported, parseArgs } from "./args.mjs";
6
+ import * as promptApi from "./prompts.mjs";
7
+ import {
8
+ ensurePiResourcesLinked,
9
+ ensureWorkflowPresent,
10
+ installManagedWorkflow,
11
+ packageVersionFor
12
+ } from "../installer/install.mjs";
13
+ import {
14
+ finalizeProviderInstallMetadata,
15
+ getPreviousClaudePermissionProfile,
16
+ getPreviousManagedCodexAgentFiles,
17
+ getPreviousManagedCodexAuthKeys,
18
+ readInstallMetadata,
19
+ writeInstallMetadata
20
+ } from "../installer/state.mjs";
21
+ import {
22
+ configureClaudeApi,
23
+ getPreviousManagedClaudePermissions
24
+ } from "../providers/claude.mjs";
25
+ import { configureCodexApi } from "../providers/codex.mjs";
26
+ import { configurePiApi } from "../providers/pi.mjs";
27
+ import {
28
+ configureContext7Env,
29
+ configureGrokSearchEnv,
30
+ configurePromptEnhancerEnv
31
+ } from "../providers/skills.mjs";
32
+ import { commandExists, installCliTool } from "../tools/cli-installer.mjs";
33
+ import { createPaths, defaultPaths, pathToLabel } from "../paths.mjs";
34
+
35
+ const {
36
+ buildCliToolMenuDescriptors,
37
+ CancelledError,
38
+ confirmOrCancel,
39
+ getClaudePermissionProfilePromptOptions,
40
+ getAugmentContextEnginePromptOptions,
41
+ interactiveMenuDefaultValue,
42
+ interactiveMenuDescriptors,
43
+ selectOrCancel
44
+ } = promptApi;
45
+
46
+ const installReportSummaryFields = [
47
+ ["新增", "created"],
48
+ ["更新", "updated"],
49
+ ["未变", "unchanged"],
50
+ ["删除", "removed"],
51
+ ["链接", "linked"],
52
+ ["保留", "preserved"],
53
+ ["冲突", "conflicts"]
54
+ ];
55
+
56
+ function uniqueReportPaths(paths = []) {
57
+ return [...new Set(paths)];
58
+ }
59
+
60
+ function presentInstallReport(report, reportOutput) {
61
+ const pathsByField = Object.fromEntries(installReportSummaryFields.map(([, field]) => [
62
+ field,
63
+ uniqueReportPaths(report[field])
64
+ ]));
65
+ reportOutput.message(`安装摘要:${installReportSummaryFields
66
+ .map(([label, field]) => `${label} ${pathsByField[field].length}`)
67
+ .join(",")}`);
68
+
69
+ const conflictPaths = pathsByField.conflicts;
70
+ const conflictSet = new Set(conflictPaths);
71
+ const preservedPaths = pathsByField.preserved.filter((path) => !conflictSet.has(path));
72
+ if (conflictPaths.length > 0) {
73
+ reportOutput.warn([
74
+ `检测到 ${conflictPaths.length} 个冲突,相关路径已保留:`,
75
+ ...conflictPaths.map((path) => `- ${path}`),
76
+ "确认可覆盖后,请在原安装命令中加入 `--force` 后重试。"
77
+ ].join("\n"));
78
+ }
79
+ if (preservedPaths.length > 0) {
80
+ reportOutput.message([
81
+ `另有 ${preservedPaths.length} 个非冲突路径已保留:`,
82
+ ...preservedPaths.map((path) => `- ${path}`)
83
+ ].join("\n"));
84
+ }
85
+ }
86
+
87
+ async function runManagedInstall(options) {
88
+ const report = await installManagedWorkflow(options);
89
+ presentInstallReport(report, p.log);
90
+ return report;
91
+ }
92
+
93
+ function printHelp() {
94
+ console.log(`${c.bold("AbelWorkflow")} ${c.cyan("installer")}
95
+
96
+ ${c.bold("Usage:")}
97
+ ${c.cyan("npx abelworkflow")}
98
+ ${c.cyan("npx abelworkflow init")}
99
+ ${c.cyan("npx abelworkflow install")}
100
+ ${c.cyan("npx abelworkflow install --force")}
101
+ ${c.cyan("npx abelworkflow install --link-only")}
102
+ ${c.cyan("npx abelworkflow install --agents-dir /custom/path")}
103
+ ${c.cyan("npx abelworkflow --non-interactive")}
104
+
105
+ ${c.bold("Default behavior:")}
106
+ - npx abelworkflow: open the interactive setup menu.
107
+ - npx abelworkflow install: sync managed files and links explicitly.
108
+ - --non-interactive: auto-execute install (skip interactive menu); auto-enabled in CI.
109
+ `);
110
+ }
111
+
112
+ async function configureCodexForPaths(paths) {
113
+ const metadata = await readInstallMetadata(paths);
114
+ const packageVersion = await packageVersionFor(paths, metadata);
115
+ const result = await configureCodexApi(paths, promptApi, {
116
+ managedAuthKeys: getPreviousManagedCodexAuthKeys(metadata),
117
+ managedCodexAgentFiles: getPreviousManagedCodexAgentFiles(metadata)
118
+ });
119
+ await writeInstallMetadata(paths, finalizeProviderInstallMetadata({
120
+ previousMetadata: metadata,
121
+ packageVersion,
122
+ overrides: {
123
+ managedCodexAuthKeys: result.managedAuthKeys,
124
+ managedCodexAgentFiles: result.managedCodexAgentFiles
125
+ }
126
+ }));
127
+ }
128
+
129
+ async function configureClaudeForPaths(paths) {
130
+ const metadata = await readInstallMetadata(paths);
131
+ const packageVersion = await packageVersionFor(paths, metadata);
132
+ const permissionProfile = await selectOrCancel(getClaudePermissionProfilePromptOptions(
133
+ getPreviousClaudePermissionProfile(metadata)
134
+ ));
135
+ const result = await configureClaudeApi(paths, promptApi, {
136
+ permissionProfile,
137
+ previousManagedPermissions: getPreviousManagedClaudePermissions(metadata)
138
+ });
139
+ await writeInstallMetadata(paths, finalizeProviderInstallMetadata({
140
+ previousMetadata: metadata,
141
+ packageVersion,
142
+ overrides: {
143
+ claudePermissionProfile: result.permissionProfile,
144
+ managedClaudePermissions: result.managedPermissions
145
+ }
146
+ }));
147
+ }
148
+
149
+ async function runFullInit(options) {
150
+ const augmentContextEngine = options.nonInteractive
151
+ ? false
152
+ : await confirmOrCancel(getAugmentContextEnginePromptOptions());
153
+
154
+ const installReport = await runManagedInstall({
155
+ paths: options.paths,
156
+ force: options.force,
157
+ relinkOnly: false,
158
+ featureOverrides: { augmentContextEngine }
159
+ });
160
+
161
+ if (await confirmOrCancel({ message: "是否安装或更新 Claude Code CLI?", initialValue: false })) {
162
+ await installCliTool("claude", promptApi);
163
+ }
164
+ if (await confirmOrCancel({ message: "是否配置 Claude Code 第三方 API?", initialValue: commandExists("claude") })) {
165
+ await configureClaudeForPaths(options.paths);
166
+ }
167
+ if (await confirmOrCancel({ message: "是否安装或更新 Codex CLI?", initialValue: false })) {
168
+ await installCliTool("codex", promptApi);
169
+ }
170
+ if (await confirmOrCancel({ message: "是否配置 Codex 第三方 API?", initialValue: commandExists("codex") })) {
171
+ await configureCodexForPaths(options.paths);
172
+ }
173
+ if (await confirmOrCancel({ message: "是否安装或更新 Pi CLI?", initialValue: false })) {
174
+ await installCliTool("pi", promptApi);
175
+ }
176
+ if (await confirmOrCancel({ message: "是否配置 Pi gpt 自定义 API?", initialValue: commandExists("pi") })) {
177
+ await configurePiApi(options.paths, ensurePiResourcesLinked, promptApi);
178
+ }
179
+ if (await confirmOrCancel({ message: "是否填写 grok-search 环境变量?", initialValue: false })) {
180
+ await configureGrokSearchEnv(options.paths, ensureWorkflowPresent, promptApi);
181
+ }
182
+ if (await confirmOrCancel({ message: "是否填写 context7-auto-research 环境变量?", initialValue: false })) {
183
+ await configureContext7Env(options.paths, ensureWorkflowPresent, promptApi);
184
+ }
185
+ if (await confirmOrCancel({ message: "是否填写 prompt-enhancer 环境变量?", initialValue: false })) {
186
+ await configurePromptEnhancerEnv(options.paths, ensureWorkflowPresent, promptApi);
187
+ }
188
+
189
+ const conflictCount = uniqueReportPaths(installReport.conflicts).length;
190
+ if (conflictCount > 0) {
191
+ p.log.warn(c.yellow(`AbelWorkflow 初始化流程结束;已保留 ${conflictCount} 个工作流冲突`));
192
+ } else {
193
+ p.log.success(c.green("AbelWorkflow 完整初始化完成"));
194
+ }
195
+ }
196
+
197
+ async function runInteractiveMenu(options) {
198
+ p.intro(c.bold(c.bgCyan(c.black(" AbelWorkflow Setup "))));
199
+ p.log.message(`工作流目录: ${c.cyan(pathToLabel(options.agentsDir))}`);
200
+
201
+ const buildOption = (d) => {
202
+ const opt = { value: d.value, label: d.label };
203
+ if (d.hint) {
204
+ opt.hint = d.hint;
205
+ }
206
+ return opt;
207
+ };
208
+ const cliToolMenus = {
209
+ "pi-cli": {
210
+ tool: "pi",
211
+ title: "Pi",
212
+ actions: {
213
+ "pi-install": async () => installCliTool("pi", promptApi),
214
+ "pi-api": async () => configurePiApi(options.paths, ensurePiResourcesLinked, promptApi)
215
+ }
216
+ },
217
+ "codex-cli": {
218
+ tool: "codex",
219
+ title: "Codex",
220
+ actions: {
221
+ "codex-install": async () => installCliTool("codex", promptApi),
222
+ "codex-api": async () => configureCodexForPaths(options.paths)
223
+ }
224
+ },
225
+ "claude-cli": {
226
+ tool: "claude",
227
+ title: "Claude Code",
228
+ actions: {
229
+ "claude-install": async () => installCliTool("claude", promptApi),
230
+ "claude-api": async () => configureClaudeForPaths(options.paths)
231
+ }
232
+ }
233
+ };
234
+ const runCliToolMenu = async ({ tool, title, actions }) => {
235
+ while (true) {
236
+ const choice = await p.select({
237
+ message: `请选择 ${title} 操作`,
238
+ options: buildCliToolMenuDescriptors(tool).map(buildOption),
239
+ initialValue: `${tool}-install`
240
+ });
241
+
242
+ if (p.isCancel(choice) || choice === "back") {
243
+ return;
244
+ }
245
+
246
+ const action = actions[choice];
247
+ if (!action) {
248
+ p.log.warn(`未知 CLI 工具菜单选项: ${choice}`);
249
+ continue;
250
+ }
251
+ await action();
252
+ }
253
+ };
254
+ const menuActions = {
255
+ "full-init": async () => runFullInit(options),
256
+ install: async () => runManagedInstall({
257
+ paths: options.paths,
258
+ force: options.force,
259
+ relinkOnly: options.relinkOnly
260
+ }),
261
+ "grok-search": async () => configureGrokSearchEnv(options.paths, ensureWorkflowPresent, promptApi),
262
+ context7: async () => configureContext7Env(options.paths, ensureWorkflowPresent, promptApi),
263
+ "prompt-enhancer": async () => configurePromptEnhancerEnv(options.paths, ensureWorkflowPresent, promptApi),
264
+ "pi-cli": async () => runCliToolMenu(cliToolMenus["pi-cli"]),
265
+ "codex-cli": async () => runCliToolMenu(cliToolMenus["codex-cli"]),
266
+ "claude-cli": async () => runCliToolMenu(cliToolMenus["claude-cli"])
267
+ };
268
+
269
+ while (true) {
270
+ const selectOptions = [
271
+ ...interactiveMenuDescriptors
272
+ .filter((d) => d.group === "main")
273
+ .map(buildOption),
274
+ { value: "__sep_skills__", label: "─── 技能配置 ───", disabled: true },
275
+ ...interactiveMenuDescriptors
276
+ .filter((d) => d.group === "skill")
277
+ .map(buildOption),
278
+ { value: "__sep_cli__", label: "─── CLI 工具 ───", disabled: true },
279
+ ...interactiveMenuDescriptors
280
+ .filter((d) => d.group === "cli")
281
+ .map(buildOption),
282
+ { value: "__sep_exit__", label: "────────────────", disabled: true },
283
+ ...interactiveMenuDescriptors
284
+ .filter((d) => d.group === "exit")
285
+ .map(buildOption)
286
+ ];
287
+
288
+ const choice = await p.select({
289
+ message: "请选择操作",
290
+ options: selectOptions,
291
+ initialValue: interactiveMenuDefaultValue
292
+ });
293
+
294
+ if (p.isCancel(choice)) {
295
+ p.outro(c.gray("已退出"));
296
+ return;
297
+ }
298
+ if (choice === "exit") {
299
+ p.outro(c.gray("已退出"));
300
+ return;
301
+ }
302
+
303
+ const action = menuActions[choice];
304
+ if (!action) {
305
+ p.log.warn(`未知菜单选项: ${choice}`);
306
+ continue;
307
+ }
308
+
309
+ try {
310
+ await action();
311
+ } catch (error) {
312
+ if (error instanceof CancelledError) {
313
+ p.log.warn("操作已取消,返回菜单");
314
+ continue;
315
+ }
316
+ throw error;
317
+ }
318
+ }
319
+ }
320
+
321
+ async function main(argv, runtime = {}) {
322
+ const options = parseArgs(argv, {
323
+ defaultAgentsDir: runtime.defaultAgentsDir || defaultPaths.agentsDir,
324
+ resolvePath: runtime.resolvePath || resolve
325
+ });
326
+
327
+ if (options.command === "help") {
328
+ printHelp();
329
+ return 0;
330
+ }
331
+
332
+ if (options.command === "menu" && options.nonInteractive) {
333
+ console.log("检测到非交互模式,自动执行工作流安装...");
334
+ options.command = "install";
335
+ }
336
+
337
+ if (options.command === "install") {
338
+ try {
339
+ await runManagedInstall({
340
+ ...options,
341
+ paths: createPaths({
342
+ homeDir: runtime.homeDir ?? defaultPaths.homeDir,
343
+ packageRoot: runtime.packageRoot ?? defaultPaths.packageRoot,
344
+ agentsDir: options.agentsDir
345
+ })
346
+ });
347
+ } catch (error) {
348
+ const message = `操作失败: ${error.message || String(error)}`;
349
+ if (options.nonInteractive) console.error(message);
350
+ else p.outro(c.red(message));
351
+ return 1;
352
+ }
353
+ return 0;
354
+ }
355
+
356
+ assertInteractiveMenuSupported({
357
+ command: options.command,
358
+ inputIsTTY: runtime.inputIsTTY ?? input.isTTY,
359
+ outputIsTTY: runtime.outputIsTTY ?? output.isTTY,
360
+ nonInteractive: options.nonInteractive
361
+ });
362
+
363
+ try {
364
+ await runInteractiveMenu({
365
+ ...options,
366
+ paths: createPaths({
367
+ homeDir: runtime.homeDir ?? defaultPaths.homeDir,
368
+ packageRoot: runtime.packageRoot ?? defaultPaths.packageRoot,
369
+ agentsDir: options.agentsDir
370
+ })
371
+ });
372
+ return 0;
373
+ } catch (error) {
374
+ const message = `操作失败: ${error.message || String(error)}`;
375
+ if (options.nonInteractive) console.error(message);
376
+ else p.outro(c.red(message));
377
+ return 1;
378
+ }
379
+ }
380
+
381
+ export {
382
+ main,
383
+ presentInstallReport,
384
+ printHelp,
385
+ runFullInit,
386
+ runInteractiveMenu
387
+ };
@@ -0,0 +1,119 @@
1
+ import * as p from "@clack/prompts";
2
+
3
+ const interactiveMenuDescriptors = [
4
+ { value: "full-init", label: "完整初始化", hint: "同步 + 安装 + 配置", group: "main" },
5
+ { value: "install", label: "仅同步工作流", group: "main" },
6
+ { value: "grok-search", label: "配置 grok-search", hint: "技能", group: "skill" },
7
+ { value: "context7", label: "配置 context7-auto-research", hint: "技能", group: "skill" },
8
+ { value: "prompt-enhancer", label: "配置 prompt-enhancer", hint: "技能", group: "skill" },
9
+ { value: "pi-cli", label: "安装/配置 Pi", hint: "CLI", group: "cli" },
10
+ { value: "codex-cli", label: "安装/配置 Codex", hint: "CLI", group: "cli" },
11
+ { value: "claude-cli", label: "安装/配置 Claude Code", hint: "CLI", group: "cli" },
12
+ { value: "exit", label: "退出", group: "exit" }
13
+ ];
14
+ const interactiveMenuDefaultValue = "full-init";
15
+ const cliToolMenuDescriptorMap = {
16
+ pi: [
17
+ { value: "pi-install", label: "安装/更新 Pi" },
18
+ { value: "pi-api", label: "配置 Pi API" },
19
+ { value: "back", label: "返回上一级" }
20
+ ],
21
+ codex: [
22
+ { value: "codex-install", label: "安装/更新 Codex" },
23
+ { value: "codex-api", label: "配置 Codex API" },
24
+ { value: "back", label: "返回上一级" }
25
+ ],
26
+ claude: [
27
+ { value: "claude-install", label: "安装/更新 Claude Code" },
28
+ { value: "claude-api", label: "配置 Claude Code API" },
29
+ { value: "back", label: "返回上一级" }
30
+ ]
31
+ };
32
+ const augmentContextEngineFeaturePrompt = {
33
+ message: "是否启用 augment-context-engine MCP 代码检索支持?不确定建议选否,可减少 MCP 安装和配置麻烦。",
34
+ initialValue: false
35
+ };
36
+
37
+ class CancelledError extends Error {
38
+ constructor(message = "用户取消") {
39
+ super(message);
40
+ this.name = "CancelledError";
41
+ }
42
+ }
43
+
44
+ function buildCliToolMenuDescriptors(tool) {
45
+ const descriptors = cliToolMenuDescriptorMap[tool];
46
+ if (!descriptors) throw new Error(`Unknown CLI tool: ${tool}`);
47
+ return descriptors.map((descriptor) => ({ ...descriptor }));
48
+ }
49
+
50
+ function required(message = "此项不能为空") {
51
+ return (value) => {
52
+ if (value === undefined || value === null || (typeof value === "string" && value.trim() === "")) return message;
53
+ };
54
+ }
55
+
56
+ function requiredUnlessExisting(existingValue, message = "此项不能为空") {
57
+ return (value) => {
58
+ if ((value === undefined || value === null || (typeof value === "string" && value.trim() === "")) && !existingValue) return message;
59
+ };
60
+ }
61
+
62
+ function resolvePasswordValue(userInput, existingValue) {
63
+ if (typeof userInput === "string") {
64
+ const trimmed = userInput.trim();
65
+ if (trimmed === "") {
66
+ if (typeof existingValue === "string" && existingValue.trim() === "") return undefined;
67
+ return existingValue || undefined;
68
+ }
69
+ return trimmed === "-" ? undefined : trimmed;
70
+ }
71
+ if (typeof existingValue === "string" && existingValue.trim() === "") return undefined;
72
+ return existingValue || undefined;
73
+ }
74
+
75
+ function assertNotCancelled(value) {
76
+ if (p.isCancel(value)) throw new CancelledError();
77
+ }
78
+
79
+ async function confirmOrCancel({ message, initialValue = false }) {
80
+ const value = await p.confirm({ message, initialValue, active: "是", inactive: "否" });
81
+ assertNotCancelled(value);
82
+ return value;
83
+ }
84
+
85
+ async function selectOrCancel(options) {
86
+ const value = await p.select(options);
87
+ assertNotCancelled(value);
88
+ return value;
89
+ }
90
+
91
+ function getAugmentContextEnginePromptOptions() {
92
+ return { ...augmentContextEngineFeaturePrompt };
93
+ }
94
+
95
+ function getClaudePermissionProfilePromptOptions(initialValue = "standard") {
96
+ return {
97
+ message: "请选择 Claude Code 权限配置档",
98
+ options: [
99
+ { value: "standard", label: "standard(默认,不预授权 Bash/Write/Edit)" },
100
+ { value: "trusted", label: "trusted(显式授予完整工具权限)" }
101
+ ],
102
+ initialValue: initialValue === "trusted" ? "trusted" : "standard"
103
+ };
104
+ }
105
+
106
+ export {
107
+ assertNotCancelled,
108
+ buildCliToolMenuDescriptors,
109
+ CancelledError,
110
+ confirmOrCancel,
111
+ getClaudePermissionProfilePromptOptions,
112
+ getAugmentContextEnginePromptOptions,
113
+ interactiveMenuDefaultValue,
114
+ interactiveMenuDescriptors,
115
+ required,
116
+ requiredUnlessExisting,
117
+ resolvePasswordValue,
118
+ selectOrCancel
119
+ };
@@ -0,0 +1,102 @@
1
+ function parseDotenvAssignment(line) {
2
+ const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/u);
3
+ if (!match) return null;
4
+ let value = line.slice(match[0].length).trim();
5
+ if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) {
6
+ value = value.slice(1, -1);
7
+ }
8
+ return { key: match[1], value };
9
+ }
10
+
11
+ function parseDotenv(content) {
12
+ const values = {};
13
+ for (const rawLine of content.split(/\r?\n/u)) {
14
+ const line = rawLine.trim();
15
+ if (!line || line.startsWith("#")) continue;
16
+ const assignment = parseDotenvAssignment(line);
17
+ if (assignment && !Object.hasOwn(values, assignment.key)) {
18
+ values[assignment.key] = assignment.value;
19
+ }
20
+ }
21
+ return values;
22
+ }
23
+
24
+ function quoteEnvValue(value) {
25
+ const text = String(value);
26
+ if (/[\r\n]/u.test(text)) throw new Error("Environment values cannot contain newlines");
27
+ if (/^[A-Za-z0-9_./:@-]+$/u.test(text)) return text;
28
+ if (!text.includes("'")) return `'${text}'`;
29
+ if (!text.includes("\"")) return `"${text}"`;
30
+ if (text.trim() === text && !(
31
+ (text.startsWith("'") && text.endsWith("'"))
32
+ || (text.startsWith("\"") && text.endsWith("\""))
33
+ )) return text;
34
+ throw new Error("Environment value cannot be represented without changing its meaning");
35
+ }
36
+
37
+ function renderDotenv(values) {
38
+ const lines = Object.entries(values)
39
+ .filter(([, value]) => value !== undefined && value !== null && value !== "")
40
+ .sort(([left], [right]) => left.localeCompare(right))
41
+ .map(([key, value]) => `${key}=${quoteEnvValue(String(value))}`);
42
+ return lines.length ? `${lines.join("\n")}\n` : "";
43
+ }
44
+
45
+ function getDotenvLines(content) {
46
+ const lines = [];
47
+ for (let start = 0; start < content.length;) {
48
+ const newline = content.indexOf("\n", start);
49
+ const end = newline === -1 ? content.length : newline + 1;
50
+ const raw = content.slice(start, end);
51
+ const lineEnding = raw.endsWith("\r\n") ? "\r\n" : raw.endsWith("\n") ? "\n" : "";
52
+ lines.push({
53
+ assignment: parseDotenvAssignment(lineEnding ? raw.slice(0, -lineEnding.length) : raw),
54
+ lineEnding,
55
+ raw
56
+ });
57
+ start = end;
58
+ }
59
+ return lines;
60
+ }
61
+
62
+ function updateDotenvContent(content, updates) {
63
+ const updateMap = new Map(Object.entries(updates));
64
+ const lines = getDotenvLines(content);
65
+ const counts = new Map();
66
+ for (const { assignment } of lines) {
67
+ if (assignment && updateMap.has(assignment.key)) {
68
+ counts.set(assignment.key, (counts.get(assignment.key) ?? 0) + 1);
69
+ }
70
+ }
71
+
72
+ const seen = new Set();
73
+ let next = "";
74
+ for (const line of lines) {
75
+ const key = line.assignment?.key;
76
+ if (!key || !updateMap.has(key)) {
77
+ next += line.raw;
78
+ continue;
79
+ }
80
+ if (seen.has(key)) continue;
81
+ seen.add(key);
82
+ const value = updateMap.get(key);
83
+ if (value === null || value === undefined || value === "") continue;
84
+ const desired = String(value);
85
+ if (counts.get(key) === 1 && line.assignment.value === desired) {
86
+ next += line.raw;
87
+ } else {
88
+ next += `${key}=${quoteEnvValue(desired)}${line.lineEnding}`;
89
+ }
90
+ }
91
+
92
+ const missing = [...updateMap].filter(([key, value]) => (
93
+ !seen.has(key) && value !== null && value !== undefined && value !== ""
94
+ ));
95
+ if (!missing.length) return next;
96
+ const lineEnding = content.includes("\r\n") ? "\r\n" : "\n";
97
+ if (next && !next.endsWith("\n")) next += lineEnding;
98
+ next += `${missing.map(([key, value]) => `${key}=${quoteEnvValue(String(value))}`).join(lineEnding)}${lineEnding}`;
99
+ return next;
100
+ }
101
+
102
+ export { parseDotenv, quoteEnvValue, renderDotenv, updateDotenvContent };
@@ -0,0 +1,80 @@
1
+ function stripJsonComments(content) {
2
+ let output = "";
3
+ let inString = false;
4
+ let quote = "";
5
+ let escaped = false;
6
+ for (let i = 0; i < content.length; i += 1) {
7
+ const char = content[i];
8
+ const next = content[i + 1];
9
+ if (inString) {
10
+ output += char;
11
+ if (escaped) escaped = false;
12
+ else if (char === "\\") escaped = true;
13
+ else if (char === quote) {
14
+ inString = false;
15
+ quote = "";
16
+ }
17
+ continue;
18
+ }
19
+ if (char === "\"" || char === "'") {
20
+ inString = true;
21
+ quote = char;
22
+ output += char;
23
+ continue;
24
+ }
25
+ if (char === "/" && next === "/") {
26
+ while (i < content.length && content[i] !== "\n") i += 1;
27
+ if (i < content.length) output += content[i];
28
+ continue;
29
+ }
30
+ if (char === "/" && next === "*") {
31
+ i += 2;
32
+ while (i < content.length && !(content[i] === "*" && content[i + 1] === "/")) {
33
+ output += content[i] === "\n" ? "\n" : "";
34
+ i += 1;
35
+ }
36
+ if (i >= content.length) {
37
+ throw new SyntaxError("Unterminated block comment");
38
+ }
39
+ i += 1;
40
+ continue;
41
+ }
42
+ output += char;
43
+ }
44
+ return stripJsonTrailingCommas(output);
45
+ }
46
+
47
+ function stripJsonTrailingCommas(content) {
48
+ let output = "";
49
+ let inString = false;
50
+ let quote = "";
51
+ let escaped = false;
52
+ for (let i = 0; i < content.length; i += 1) {
53
+ const char = content[i];
54
+ if (inString) {
55
+ output += char;
56
+ if (escaped) escaped = false;
57
+ else if (char === "\\") escaped = true;
58
+ else if (char === quote) {
59
+ inString = false;
60
+ quote = "";
61
+ }
62
+ continue;
63
+ }
64
+ if (char === "\"" || char === "'") {
65
+ inString = true;
66
+ quote = char;
67
+ output += char;
68
+ continue;
69
+ }
70
+ if (char === ",") {
71
+ let nextIndex = i + 1;
72
+ while (nextIndex < content.length && /\s/u.test(content[nextIndex])) nextIndex += 1;
73
+ if (content[nextIndex] === "}" || content[nextIndex] === "]") continue;
74
+ }
75
+ output += char;
76
+ }
77
+ return output;
78
+ }
79
+
80
+ export { stripJsonComments, stripJsonTrailingCommas };