abelworkflow 0.9.2 → 1.0.0

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