abelworkflow 0.6.4 → 0.7.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/lib/cli.mjs CHANGED
@@ -3,8 +3,23 @@ import { cp, link, lstat, mkdir, readFile, readdir, readlink, realpath, rename,
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, join, relative, resolve } from "node:path";
5
5
  import { stdin as input, stdout as output } from "node:process";
6
- import { createInterface } from "node:readline/promises";
7
6
  import { fileURLToPath } from "node:url";
7
+ import * as p from "@clack/prompts";
8
+ import c from "picocolors";
9
+ import {
10
+ assertInteractiveMenuSupported,
11
+ assertNotCancelled,
12
+ CancelledError,
13
+ confirmOrCancel,
14
+ getRunCommandSpawnOptions,
15
+ interactiveMenuDefaultValue,
16
+ interactiveMenuDescriptors,
17
+ parseArgs,
18
+ required,
19
+ requiredUnlessExisting,
20
+ resolvePasswordValue,
21
+ selectOrCancel
22
+ } from "./cli/logic.mjs";
8
23
 
9
24
  const __filename = fileURLToPath(import.meta.url);
10
25
  const packageRoot = dirname(dirname(__filename));
@@ -87,98 +102,23 @@ const ignoredSkillPathPatterns = [
87
102
  /^dev-browser\/profiles(\/|$)/,
88
103
  /^dev-browser\/tmp(\/|$)/
89
104
  ];
90
- const menuChoices = [
91
- { value: "full-init", label: "完整初始化:同步工作流 + 可选安装/配置 Claude Code、Codex、技能环境" },
92
- { value: "install", label: "仅同步/更新工作流到 ~/.agents 并重新链接 Claude/Codex" },
93
- { value: "grok-search", label: "配置 grok-search 环境变量" },
94
- { value: "context7", label: "配置 context7-auto-research 环境变量" },
95
- { value: "prompt-enhancer", label: "配置 prompt-enhancer 环境变量" },
96
- { value: "claude-install", label: "安装或更新 Claude Code CLI" },
97
- { value: "claude-api", label: "配置 Claude Code 第三方 API" },
98
- { value: "codex-install", label: "安装或更新 Codex CLI" },
99
- { value: "codex-api", label: "配置 Codex 第三方 API" },
100
- { value: "exit", label: "退出" }
101
- ];
102
-
103
- function parseArgs(argv) {
104
- const options = {
105
- agentsDir: defaultAgentsDir,
106
- force: false,
107
- relinkOnly: false,
108
- command: "menu"
109
- };
110
- const positional = [];
111
- let helpRequested = false;
112
-
113
- for (let i = 0; i < argv.length; i += 1) {
114
- const arg = argv[i];
115
- if (arg === "--force" || arg === "-f") {
116
- options.force = true;
117
- continue;
118
- }
119
- if (arg === "--link-only") {
120
- options.relinkOnly = true;
121
- continue;
122
- }
123
- if (arg === "--agents-dir") {
124
- const value = argv[i + 1];
125
- if (!value) {
126
- throw new Error("--agents-dir requires a path");
127
- }
128
- options.agentsDir = resolve(value);
129
- i += 1;
130
- continue;
131
- }
132
- if (arg === "--help" || arg === "-h" || arg === "help") {
133
- helpRequested = true;
134
- options.command = "help";
135
- continue;
136
- }
137
- if (arg.startsWith("-")) {
138
- throw new Error(`Unknown argument: ${arg}`);
139
- }
140
- positional.push(arg);
141
- }
142
-
143
- if (positional.length > 1) {
144
- throw new Error(`Unknown argument: ${positional.slice(1).join(" ")}`);
145
- }
146
-
147
- if (positional[0]) {
148
- if (["menu", "init"].includes(positional[0])) {
149
- if (!helpRequested) {
150
- options.command = "menu";
151
- }
152
- } else if (["install", "sync"].includes(positional[0])) {
153
- if (!helpRequested) {
154
- options.command = "install";
155
- }
156
- } else {
157
- throw new Error(`Unknown command: ${positional[0]}`);
158
- }
159
- }
160
-
161
- if (options.command === "menu" && (options.force || options.relinkOnly || options.agentsDir !== defaultAgentsDir)) {
162
- throw new Error("`--force`、`--link-only`、`--agents-dir` 仅能与 `install` 命令一起使用");
163
- }
164
-
165
- return options;
166
- }
167
105
 
168
106
  function printHelp() {
169
- console.log(`AbelWorkflow installer
170
-
171
- Usage:
172
- npx abelworkflow
173
- npx abelworkflow init
174
- npx abelworkflow install
175
- npx abelworkflow install --force
176
- npx abelworkflow install --link-only
177
- npx abelworkflow install --agents-dir /custom/path
178
-
179
- Default behavior:
107
+ console.log(`${c.bold("AbelWorkflow")} ${c.cyan("installer")}
108
+
109
+ ${c.bold("Usage:")}
110
+ ${c.cyan("npx abelworkflow")}
111
+ ${c.cyan("npx abelworkflow init")}
112
+ ${c.cyan("npx abelworkflow install")}
113
+ ${c.cyan("npx abelworkflow install --force")}
114
+ ${c.cyan("npx abelworkflow install --link-only")}
115
+ ${c.cyan("npx abelworkflow install --agents-dir /custom/path")}
116
+ ${c.cyan("npx abelworkflow --non-interactive")}
117
+
118
+ ${c.bold("Default behavior:")}
180
119
  - npx abelworkflow: open the interactive setup menu.
181
120
  - npx abelworkflow install: sync managed files and links explicitly.
121
+ - --non-interactive: auto-execute install (skip interactive menu); auto-enabled in CI.
182
122
  `);
183
123
  }
184
124
 
@@ -245,18 +185,18 @@ async function backupExistingPath(targetPath) {
245
185
  const backupPath = await createBackupPath(targetPath);
246
186
  await cp(targetPath, backupPath, { recursive: true, force: false });
247
187
  createdBackupPaths.add(targetPath);
248
- console.log(`已备份已有配置: ${pathToLabel(targetPath)} -> ${pathToLabel(backupPath)}`);
188
+ p.log.message(`已备份已有配置: ${pathToLabel(targetPath)} -> ${pathToLabel(backupPath)}`);
249
189
  return backupPath;
250
190
  }
251
191
 
252
- async function backupIfNeeded(targetPath, force) {
192
+ async function backupIfNeeded(targetPath) {
253
193
  if (!(await pathExists(targetPath))) {
254
194
  return null;
255
195
  }
256
196
 
257
197
  const backupPath = await createBackupPath(targetPath);
258
198
  await rename(targetPath, backupPath);
259
- console.log(`已备份已有配置: ${pathToLabel(targetPath)} -> ${pathToLabel(backupPath)}`);
199
+ p.log.message(`已备份已有配置: ${pathToLabel(targetPath)} -> ${pathToLabel(backupPath)}`);
260
200
  return backupPath;
261
201
  }
262
202
 
@@ -516,7 +456,7 @@ async function createSymlink(targetPath, sourcePath, linkType, kind) {
516
456
  await symlink(sourcePath, targetPath, linkType);
517
457
  }
518
458
 
519
- async function ensureManagedLink(targetPath, sourcePath, kind, force, previousLinkedTargets) {
459
+ async function ensureManagedLink(targetPath, sourcePath, kind, previousLinkedTargets) {
520
460
  await mkdir(dirname(targetPath), { recursive: true });
521
461
  const sourceResolved = resolve(sourcePath);
522
462
  const sourceExists = await pathTargetExists(sourcePath);
@@ -554,7 +494,7 @@ async function ensureManagedLink(targetPath, sourcePath, kind, force, previousLi
554
494
  if (wasPreviouslyManaged) {
555
495
  await rm(targetPath, { recursive: true, force: true });
556
496
  } else {
557
- await backupIfNeeded(targetPath, force);
497
+ await backupIfNeeded(targetPath);
558
498
  }
559
499
  } else if (!sourceExists) {
560
500
  return { targetPath, status: "skipped" };
@@ -592,7 +532,7 @@ function shouldCopyManagedFile(error) {
592
532
  return ["EPERM", "EACCES", "EXDEV", "EINVAL", "UNKNOWN"].includes(error?.code);
593
533
  }
594
534
 
595
- async function linkSkillDirectories(baseDir, agentsDir, force, previousLinkedTargets) {
535
+ async function linkSkillDirectories(baseDir, agentsDir, previousLinkedTargets) {
596
536
  const results = [];
597
537
  const skillsRoot = join(agentsDir, "skills");
598
538
  const skillNames = (await getDirectoryNames(skillsRoot)).filter((skillName) => skillName !== ".system");
@@ -603,7 +543,6 @@ async function linkSkillDirectories(baseDir, agentsDir, force, previousLinkedTar
603
543
  join(baseDir, "skills", skillName),
604
544
  join(skillsRoot, skillName),
605
545
  "dir",
606
- force,
607
546
  previousLinkedTargets
608
547
  )
609
548
  );
@@ -731,19 +670,7 @@ function isWithinManagedRoot(targetPath, managedSourceRoot) {
731
670
  return relativePath !== ".." && !relativePath.startsWith(`..${isWindows() ? "\\" : "/"}`);
732
671
  }
733
672
 
734
- function getResultMarker(status) {
735
- if (status === "unchanged") {
736
- return "=";
737
- }
738
-
739
- if (status === "removed") {
740
- return "-";
741
- }
742
-
743
- return "+";
744
- }
745
-
746
- async function linkClaude(agentsDir, force, previousLinkedTargets) {
673
+ async function linkClaude(agentsDir, previousLinkedTargets) {
747
674
  const claudeDir = join(home, ".claude");
748
675
  await mkdir(claudeDir, { recursive: true });
749
676
  await removeIfNotDirectory(join(claudeDir, "commands"));
@@ -756,21 +683,19 @@ async function linkClaude(agentsDir, force, previousLinkedTargets) {
756
683
  join(claudeDir, "CLAUDE.md"),
757
684
  join(agentsDir, "AGENTS.md"),
758
685
  "file",
759
- force,
760
686
  previousLinkedTargets
761
687
  ),
762
688
  await ensureManagedLink(
763
689
  join(claudeDir, "commands", "oc"),
764
690
  join(agentsDir, "commands", "oc"),
765
691
  "dir",
766
- force,
767
692
  previousLinkedTargets
768
693
  ),
769
- ...(await linkSkillDirectories(claudeDir, agentsDir, force, previousLinkedTargets))
694
+ ...(await linkSkillDirectories(claudeDir, agentsDir, previousLinkedTargets))
770
695
  ];
771
696
  }
772
697
 
773
- async function linkCodex(agentsDir, force, previousLinkedTargets) {
698
+ async function linkCodex(agentsDir, previousLinkedTargets) {
774
699
  const results = [];
775
700
  const codexDir = join(home, ".codex");
776
701
  await mkdir(codexDir, { recursive: true });
@@ -784,11 +709,10 @@ async function linkCodex(agentsDir, force, previousLinkedTargets) {
784
709
  join(codexDir, "AGENTS.md"),
785
710
  join(agentsDir, "AGENTS.md"),
786
711
  "file",
787
- force,
788
712
  previousLinkedTargets
789
713
  )
790
714
  );
791
- results.push(...(await linkSkillDirectories(codexDir, agentsDir, force, previousLinkedTargets)));
715
+ results.push(...(await linkSkillDirectories(codexDir, agentsDir, previousLinkedTargets)));
792
716
 
793
717
  const commandFiles = await getCommandNames(join(agentsDir, "commands", "oc"));
794
718
  results.push(
@@ -805,7 +729,6 @@ async function linkCodex(agentsDir, force, previousLinkedTargets) {
805
729
  join(codexDir, "prompts", fileName),
806
730
  join(agentsDir, "commands", "oc", fileName),
807
731
  "file",
808
- force,
809
732
  previousLinkedTargets
810
733
  )
811
734
  );
@@ -817,9 +740,17 @@ async function linkCodex(agentsDir, force, previousLinkedTargets) {
817
740
  async function installManagedWorkflow(options) {
818
741
  let previousMetadata = {};
819
742
  let managedChildren = {};
743
+ const s = p.spinner();
820
744
 
821
745
  if (!options.relinkOnly) {
822
- ({ previousMetadata, managedChildren } = await syncManagedFiles(options.agentsDir));
746
+ s.start("正在同步工作流文件...");
747
+ try {
748
+ ({ previousMetadata, managedChildren } = await syncManagedFiles(options.agentsDir));
749
+ } catch (e) {
750
+ s.cancel(c.red(`同步失败: ${e.message}`));
751
+ throw e;
752
+ }
753
+ s.stop("工作流文件已同步");
823
754
  } else if (!(await pathExists(options.agentsDir))) {
824
755
  throw new Error(`${options.agentsDir} does not exist; remove --link-only or install first`);
825
756
  } else {
@@ -828,8 +759,18 @@ async function installManagedWorkflow(options) {
828
759
  }
829
760
 
830
761
  const previousLinkedTargets = previousMetadata.linkedTargets ?? {};
831
- const claudeResults = await linkClaude(options.agentsDir, options.force, previousLinkedTargets);
832
- const codexResults = await linkCodex(options.agentsDir, options.force, previousLinkedTargets);
762
+ s.start("正在链接 Claude / Codex...");
763
+ let claudeResults;
764
+ let codexResults;
765
+ try {
766
+ claudeResults = await linkClaude(options.agentsDir, previousLinkedTargets);
767
+ codexResults = await linkCodex(options.agentsDir, previousLinkedTargets);
768
+ } catch (e) {
769
+ s.cancel(c.red(`链接失败: ${e.message}`));
770
+ throw e;
771
+ }
772
+ s.stop("链接完成");
773
+
833
774
  const linkedTargets = Object.fromEntries(
834
775
  [...claudeResults, ...codexResults]
835
776
  .filter((result) => result.sourcePath)
@@ -850,14 +791,16 @@ async function installManagedWorkflow(options) {
850
791
  linkedTargets
851
792
  });
852
793
 
853
- console.log(`Installed AbelWorkflow into ${options.agentsDir}`);
854
- console.log("");
855
- console.log("Linked targets:");
856
- for (const result of [...claudeResults, ...codexResults]) {
857
- console.log(`- ${getResultMarker(result.status)} ${result.targetPath}`);
858
- }
859
- console.log("");
860
- console.log("Done. Re-run `npx abelworkflow@latest` to update the managed files.");
794
+ const resultLines = [...claudeResults, ...codexResults].map((result) => {
795
+ const icon = result.status === "unchanged" ? c.gray("=")
796
+ : result.status === "removed" ? c.yellow("−")
797
+ : c.green("+");
798
+ return `${icon} ${pathToLabel(result.targetPath)}`;
799
+ }).join("\n");
800
+
801
+ p.note(resultLines, "链接结果");
802
+ p.log.step(`工作流目录: ${c.cyan(pathToLabel(options.agentsDir))}`);
803
+ p.log.message(`完成后可运行 ${c.cyan("npx abelworkflow@latest")} 更新托管文件`);
861
804
  }
862
805
 
863
806
  async function readJsonFileSafe(path, fallback = {}) {
@@ -949,153 +892,15 @@ async function updateDotenvFile(path, updates) {
949
892
  await writeFile(path, renderDotenv(current), "utf8");
950
893
  }
951
894
 
952
- function currentChoiceIndex(choices, defaultValue) {
953
- if (defaultValue === undefined) {
954
- return -1;
955
- }
956
- return choices.findIndex((choice) => choice.value === defaultValue);
957
- }
958
-
959
- async function setTerminalEcho(enabled) {
960
- if (!input.isTTY || isWindows()) {
961
- return;
962
- }
963
-
964
- const result = spawnSync("stty", [enabled ? "echo" : "-echo"], { stdio: ["inherit", "ignore", "ignore"] });
965
- if (result.error) {
966
- throw result.error;
967
- }
968
- }
969
-
970
- async function promptText(message, options = {}) {
971
- const { defaultValue, allowEmpty = false } = options;
972
-
973
- while (true) {
974
- const suffix = defaultValue !== undefined && defaultValue !== ""
975
- ? ` [${defaultValue}]`
976
- : "";
977
- const rl = createInterface({ input, output });
978
- let answer;
979
- try {
980
- answer = await rl.question(`${message}${suffix}: `);
981
- } finally {
982
- rl.close();
983
- }
984
-
985
- const value = answer.trim();
986
- if (!value && defaultValue !== undefined) {
987
- return defaultValue;
988
- }
989
- if (!value && !allowEmpty) {
990
- console.log("此项不能为空。");
991
- continue;
992
- }
993
- return value;
994
- }
995
- }
996
-
997
- async function promptSecret(message, options = {}) {
998
- const { defaultValue, allowEmpty = false } = options;
999
-
1000
- if (!input.isTTY || isWindows()) {
1001
- while (true) {
1002
- const suffix = defaultValue !== undefined && defaultValue !== ""
1003
- ? " [直接回车保留现有值]"
1004
- : "";
1005
- const rl = createInterface({ input, output });
1006
- let answer;
1007
- try {
1008
- answer = await rl.question(`${message}${suffix}: `);
1009
- } finally {
1010
- rl.close();
1011
- }
1012
-
1013
- const value = answer.trim();
1014
- if (!value && defaultValue !== undefined) {
1015
- return defaultValue;
1016
- }
1017
- if (!value && !allowEmpty) {
1018
- console.log("此项不能为空。");
1019
- continue;
1020
- }
1021
- return value;
1022
- }
1023
- }
1024
-
1025
- while (true) {
1026
- const suffix = defaultValue !== undefined && defaultValue !== ""
1027
- ? " [直接回车保留现有值]"
1028
- : "";
1029
- const rl = createInterface({ input, output, terminal: true });
1030
- let answer;
1031
- try {
1032
- await setTerminalEcho(false);
1033
- answer = await rl.question(`${message}${suffix}: `);
1034
- output.write("\n");
1035
- } finally {
1036
- await setTerminalEcho(true);
1037
- rl.close();
1038
- }
1039
-
1040
- const value = answer.trim();
1041
- if (!value && defaultValue !== undefined) {
1042
- return defaultValue;
1043
- }
1044
- if (!value && !allowEmpty) {
1045
- console.log("此项不能为空。");
1046
- continue;
1047
- }
1048
- return value;
1049
- }
1050
- }
1051
-
1052
- async function promptSelect(message, choices, options = {}) {
1053
- const defaultIndex = currentChoiceIndex(choices, options.defaultValue);
1054
- console.log(`\n${message}`);
1055
- choices.forEach((choice, index) => {
1056
- const defaultMarker = index === defaultIndex ? " [默认]" : "";
1057
- console.log(` ${index + 1}. ${choice.label}${defaultMarker}`);
1058
- });
1059
-
1060
- while (true) {
1061
- const fallbackValue = defaultIndex >= 0 ? String(defaultIndex + 1) : undefined;
1062
- const answer = await promptText("请输入序号", { defaultValue: fallbackValue, allowEmpty: defaultIndex >= 0 });
1063
- const index = Number(answer) - 1;
1064
- if (Number.isInteger(index) && index >= 0 && index < choices.length) {
1065
- return choices[index].value;
1066
- }
1067
- const direct = choices.find((choice) => choice.value === answer);
1068
- if (direct) {
1069
- return direct.value;
1070
- }
1071
- console.log("无效选择,请重新输入。");
1072
- }
1073
- }
1074
-
1075
- async function promptConfirm(message, defaultValue = true) {
1076
- const value = await promptSelect(message, [
1077
- { value: true, label: "是" },
1078
- { value: false, label: "否" }
1079
- ], { defaultValue });
1080
- return value;
1081
- }
1082
-
1083
895
  function commandExists(command) {
1084
896
  const checker = isWindows() ? "where" : "which";
1085
897
  const result = spawnSync(checker, [command], { stdio: "ignore" });
1086
898
  return result.status === 0;
1087
899
  }
1088
900
 
1089
- function getRunCommandSpawnOptions(platform = getPlatform()) {
1090
- return {
1091
- stdio: "inherit",
1092
- shell: platform === "win32"
1093
- };
1094
- }
1095
-
1096
901
  async function runCommand(command, args) {
1097
902
  await new Promise((resolvePromise, rejectPromise) => {
1098
- const child = spawn(command, args, getRunCommandSpawnOptions());
903
+ const child = spawn(command, args, getRunCommandSpawnOptions(getPlatform()));
1099
904
  child.on("error", rejectPromise);
1100
905
  child.on("close", (code) => {
1101
906
  if (code === 0) {
@@ -1107,22 +912,12 @@ async function runCommand(command, args) {
1107
912
  });
1108
913
  }
1109
914
 
1110
- function sanitizeProviderId(name) {
1111
- return name
1112
- .trim()
1113
- .toLowerCase()
1114
- .replace(/[\s.]+/gu, "-")
1115
- .replace(/[^a-z0-9_-]/gu, "")
1116
- .replace(/-+/gu, "-")
1117
- .replace(/^-|-$/gu, "") || "abelworkflow";
1118
- }
1119
-
1120
915
  async function ensureWorkflowPresent(agentsDir) {
1121
916
  if (await pathExists(join(agentsDir, "AGENTS.md"))) {
1122
917
  return;
1123
918
  }
1124
919
 
1125
- console.log("未检测到已安装的 AbelWorkflow,先执行一次工作流同步。");
920
+ p.log.message("未检测到已安装的 AbelWorkflow,先执行一次工作流同步。");
1126
921
  await installManagedWorkflow({
1127
922
  agentsDir,
1128
923
  force: false,
@@ -1134,101 +929,139 @@ async function configureGrokSearchEnv(agentsDir) {
1134
929
  await ensureWorkflowPresent(agentsDir);
1135
930
  const envPath = join(agentsDir, "skills", "grok-search", ".env");
1136
931
  const existing = await readDotenvFile(envPath);
1137
- const baseUrl = await promptText("Grok API URL", {
1138
- defaultValue: existing.GROK_API_URL || "https://api.x.ai/v1"
932
+ const baseUrl = await p.text({
933
+ message: "Grok API URL",
934
+ defaultValue: existing.GROK_API_URL || "https://api.x.ai/v1",
935
+ validate: required()
1139
936
  });
1140
- const apiKey = await promptSecret("Grok API Key", {
1141
- defaultValue: existing.GROK_API_KEY || undefined
937
+ assertNotCancelled(baseUrl);
938
+
939
+ const apiKey = await p.password({
940
+ message: "Grok API Key(输入 - 清除)",
941
+ mask: "*",
942
+ defaultValue: existing.GROK_API_KEY || undefined,
943
+ validate: requiredUnlessExisting(existing.GROK_API_KEY, "Grok API Key 不能为空")
1142
944
  });
1143
- const model = await promptText("Grok 默认模型", {
1144
- defaultValue: existing.GROK_MODEL || "grok-4-fast"
945
+ assertNotCancelled(apiKey);
946
+ const finalApiKey = resolvePasswordValue(apiKey, existing.GROK_API_KEY);
947
+
948
+ const model = await p.text({
949
+ message: "Grok 默认模型",
950
+ defaultValue: existing.GROK_MODEL || "grok-4-fast",
951
+ validate: required()
1145
952
  });
1146
- const useTavily = await promptConfirm("是否同时配置 Tavily 作为额外搜索源?", Boolean(existing.TAVILY_API_KEY));
953
+ assertNotCancelled(model);
954
+
955
+ const useTavily = await confirmOrCancel({
956
+ message: "是否同时配置 Tavily 作为额外搜索源?",
957
+ initialValue: Boolean(existing.TAVILY_API_KEY)
958
+ });
959
+
1147
960
  const tavilyKey = useTavily
1148
- ? await promptSecret("Tavily API Key", { defaultValue: existing.TAVILY_API_KEY || undefined })
961
+ ? await p.password({
962
+ message: "Tavily API Key(输入 - 清除)",
963
+ mask: "*",
964
+ defaultValue: existing.TAVILY_API_KEY || undefined,
965
+ validate: requiredUnlessExisting(existing.TAVILY_API_KEY, "Tavily API Key 不能为空")
966
+ })
1149
967
  : "";
968
+ if (useTavily) assertNotCancelled(tavilyKey);
969
+ const finalTavilyKey = useTavily ? resolvePasswordValue(tavilyKey, existing.TAVILY_API_KEY) : null;
1150
970
 
1151
971
  await updateDotenvFile(envPath, {
1152
972
  GROK_API_URL: baseUrl,
1153
- GROK_API_KEY: apiKey,
973
+ GROK_API_KEY: finalApiKey,
1154
974
  GROK_MODEL: model,
1155
- TAVILY_API_KEY: useTavily ? tavilyKey : null,
975
+ TAVILY_API_KEY: finalTavilyKey,
1156
976
  TAVILY_ENABLED: useTavily ? "true" : null
1157
977
  });
1158
978
 
1159
- console.log(`已写入 ${pathToLabel(envPath)}`);
979
+ p.log.step(`已写入 ${pathToLabel(envPath)}`);
1160
980
  }
1161
981
 
1162
982
  async function configureContext7Env(agentsDir) {
1163
983
  await ensureWorkflowPresent(agentsDir);
1164
984
  const envPath = join(agentsDir, "skills", "context7-auto-research", ".env");
1165
985
  const existing = await readDotenvFile(envPath);
1166
- const apiKey = await promptSecret("Context7 API Key", {
1167
- defaultValue: existing.CONTEXT7_API_KEY || undefined,
1168
- allowEmpty: true
986
+ const apiKey = await p.password({
987
+ message: "Context7 API Key (可选,输入 - 清除)",
988
+ mask: "*",
989
+ defaultValue: existing.CONTEXT7_API_KEY || undefined
1169
990
  });
991
+ assertNotCancelled(apiKey);
992
+ const finalApiKey = resolvePasswordValue(apiKey, existing.CONTEXT7_API_KEY);
1170
993
 
1171
994
  await updateDotenvFile(envPath, {
1172
- CONTEXT7_API_KEY: apiKey || null
995
+ CONTEXT7_API_KEY: finalApiKey
1173
996
  });
1174
997
 
1175
- console.log(`已写入 ${pathToLabel(envPath)}`);
998
+ p.log.step(`已写入 ${pathToLabel(envPath)}`);
999
+ }
1000
+
1001
+ function hasPromptEnhancerApiConfig(config) {
1002
+ return [config.PE_API_URL, config.PE_API_KEY, config.PE_MODEL]
1003
+ .every((value) => typeof value === "string" && value.trim() !== "");
1176
1004
  }
1177
1005
 
1178
1006
  function resolvePromptEnhancerMode(existing) {
1179
- if (existing.ANTHROPIC_API_KEY) {
1180
- return "anthropic";
1181
- }
1182
- if (existing.OPENAI_API_KEY) {
1183
- return "openai";
1184
- }
1185
- return "local";
1007
+ return hasPromptEnhancerApiConfig(existing) ? "openai-compatible" : "agent";
1186
1008
  }
1187
1009
 
1188
1010
  async function configurePromptEnhancerEnv(agentsDir) {
1189
1011
  await ensureWorkflowPresent(agentsDir);
1190
1012
  const envPath = join(agentsDir, "skills", "prompt-enhancer", ".env");
1191
1013
  const existing = await readDotenvFile(envPath);
1192
- const mode = await promptSelect("请选择 prompt-enhancer 使用的提供方", [
1193
- { value: "anthropic", label: "Anthropic 兼容 Key" },
1194
- { value: "openai", label: "OpenAI 兼容 Key" },
1195
- { value: "local", label: "仅保留本地模板兜底,不写 API Key" }
1196
- ], { defaultValue: resolvePromptEnhancerMode(existing) });
1197
-
1198
- if (mode === "anthropic") {
1199
- const apiKey = await promptSecret("ANTHROPIC_API_KEY", {
1200
- defaultValue: existing.ANTHROPIC_API_KEY || undefined
1201
- });
1202
- const model = await promptText("PE_MODEL", {
1203
- defaultValue: existing.PE_MODEL || "claude-sonnet-4-20250514"
1204
- });
1014
+ const mode = await selectOrCancel({
1015
+ message: "请选择 prompt-enhancer 的运行方式",
1016
+ options: [
1017
+ { value: "openai-compatible", label: "第三方 OpenAI 兼容接口" },
1018
+ { value: "agent", label: "直接使用当前 Agent" }
1019
+ ],
1020
+ initialValue: resolvePromptEnhancerMode(existing)
1021
+ });
1205
1022
 
1206
- await updateDotenvFile(envPath, {
1207
- ANTHROPIC_API_KEY: apiKey,
1208
- OPENAI_API_KEY: null,
1209
- PE_MODEL: model
1023
+ if (mode === "openai-compatible") {
1024
+ const apiUrl = await p.text({
1025
+ message: "PE_API_URL",
1026
+ defaultValue: existing.PE_API_URL || undefined,
1027
+ validate: required()
1210
1028
  });
1211
- } else if (mode === "openai") {
1212
- const apiKey = await promptSecret("OPENAI_API_KEY", {
1213
- defaultValue: existing.OPENAI_API_KEY || undefined
1029
+ assertNotCancelled(apiUrl);
1030
+
1031
+ const apiKey = await p.password({
1032
+ message: "PE_API_KEY(输入 - 清除)",
1033
+ mask: "*",
1034
+ defaultValue: existing.PE_API_KEY || undefined,
1035
+ validate: requiredUnlessExisting(existing.PE_API_KEY, "PE_API_KEY 不能为空")
1214
1036
  });
1215
- const model = await promptText("PE_MODEL", {
1216
- defaultValue: existing.PE_MODEL || "gpt-4o"
1037
+ assertNotCancelled(apiKey);
1038
+ const finalApiKey = resolvePasswordValue(apiKey, existing.PE_API_KEY);
1039
+
1040
+ const model = await p.text({
1041
+ message: "PE_MODEL",
1042
+ defaultValue: existing.PE_MODEL || undefined,
1043
+ validate: required()
1217
1044
  });
1045
+ assertNotCancelled(model);
1218
1046
 
1219
1047
  await updateDotenvFile(envPath, {
1220
- OPENAI_API_KEY: apiKey,
1048
+ PE_API_URL: apiUrl,
1049
+ PE_API_KEY: finalApiKey,
1050
+ PE_MODEL: model,
1221
1051
  ANTHROPIC_API_KEY: null,
1222
- PE_MODEL: model
1052
+ OPENAI_API_KEY: null
1223
1053
  });
1224
1054
  } else {
1225
1055
  await updateDotenvFile(envPath, {
1056
+ PE_API_URL: null,
1057
+ PE_API_KEY: null,
1058
+ PE_MODEL: null,
1226
1059
  ANTHROPIC_API_KEY: null,
1227
1060
  OPENAI_API_KEY: null
1228
1061
  });
1229
1062
  }
1230
1063
 
1231
- console.log(`已写入 ${pathToLabel(envPath)}`);
1064
+ p.log.step(`已写入 ${pathToLabel(envPath)}`);
1232
1065
  }
1233
1066
 
1234
1067
  function mergeClaudeSettingsWithDefaults(settings) {
@@ -1288,28 +1121,46 @@ function ensureApprovedClaudeApiKey(config, apiKey) {
1288
1121
  async function configureClaudeApi() {
1289
1122
  const settings = await readJsonFileSafe(claudeSettingsPath, {});
1290
1123
  const existing = getExistingClaudeApiConfig(settings);
1291
- const authType = await promptSelect("Claude Code 第三方 API 认证方式", [
1292
- { value: "api_key", label: "API Key" },
1293
- { value: "auth_token", label: "Auth Token" }
1294
- ], { defaultValue: existing.authType });
1295
- const baseUrl = await promptText("Claude Code Base URL", {
1296
- defaultValue: existing.baseUrl
1124
+ const authType = await selectOrCancel({
1125
+ message: "Claude Code 第三方 API 认证方式",
1126
+ options: [
1127
+ { value: "api_key", label: "API Key" },
1128
+ { value: "auth_token", label: "Auth Token" }
1129
+ ],
1130
+ initialValue: existing.authType
1131
+ });
1132
+
1133
+ const baseUrl = await p.text({
1134
+ message: "Claude Code Base URL",
1135
+ defaultValue: existing.baseUrl,
1136
+ validate: required()
1297
1137
  });
1298
- const key = await promptSecret(authType === "auth_token" ? "Claude Code Auth Token" : "Claude Code API Key", {
1299
- defaultValue: existing.key || undefined
1138
+ assertNotCancelled(baseUrl);
1139
+
1140
+ const key = await p.password({
1141
+ message: authType === "auth_token" ? "Claude Code Auth Token(输入 - 清除)" : "Claude Code API Key(输入 - 清除)",
1142
+ mask: "*",
1143
+ defaultValue: existing.key || undefined,
1144
+ validate: requiredUnlessExisting(existing.key, "API Key / Auth Token 不能为空")
1300
1145
  });
1301
- const model = await promptText("Claude Code 模型", {
1302
- defaultValue: existing.model || undefined
1146
+ assertNotCancelled(key);
1147
+ const finalKey = resolvePasswordValue(key, existing.key);
1148
+
1149
+ const model = await p.text({
1150
+ message: "Claude Code 模型",
1151
+ defaultValue: existing.model || undefined,
1152
+ validate: required()
1303
1153
  });
1154
+ assertNotCancelled(model);
1304
1155
 
1305
1156
  const nextSettings = mergeClaudeSettingsWithDefaults(settings);
1306
1157
  nextSettings.env.ANTHROPIC_BASE_URL = baseUrl;
1307
1158
 
1308
1159
  if (authType === "auth_token") {
1309
- nextSettings.env.ANTHROPIC_AUTH_TOKEN = key;
1160
+ nextSettings.env.ANTHROPIC_AUTH_TOKEN = finalKey;
1310
1161
  delete nextSettings.env.ANTHROPIC_API_KEY;
1311
1162
  } else {
1312
- nextSettings.env.ANTHROPIC_API_KEY = key;
1163
+ nextSettings.env.ANTHROPIC_API_KEY = finalKey;
1313
1164
  delete nextSettings.env.ANTHROPIC_AUTH_TOKEN;
1314
1165
  }
1315
1166
  for (const field of claudeModelEnvKeys) {
@@ -1320,10 +1171,10 @@ async function configureClaudeApi() {
1320
1171
 
1321
1172
  const metaConfig = await readJsonFileSafe(claudeMetaConfigPath, {});
1322
1173
  metaConfig.hasCompletedOnboarding = true;
1323
- ensureApprovedClaudeApiKey(metaConfig, key);
1174
+ ensureApprovedClaudeApiKey(metaConfig, finalKey);
1324
1175
  await writeJsonFileWithBackup(claudeMetaConfigPath, metaConfig);
1325
1176
 
1326
- console.log(`已更新 ${pathToLabel(claudeSettingsPath)} (${authType}, ${baseUrl}, ${maskSecret(key)})`);
1177
+ p.log.step(`已更新 ${pathToLabel(claudeSettingsPath)} (${authType}, ${baseUrl}, ${maskSecret(finalKey)})`);
1327
1178
  }
1328
1179
 
1329
1180
  function updateTopLevelTomlField(content, field, value) {
@@ -1813,13 +1664,24 @@ async function configureCodexApi() {
1813
1664
  const existing = await getExistingCodexApiConfig();
1814
1665
  const providerId = existing.providerId || "abelworkflow";
1815
1666
  const providerName = existing.providerName || providerId;
1816
- const baseUrl = await promptText("Codex Base URL", {
1817
- defaultValue: existing.baseUrl
1667
+ const baseUrl = await p.text({
1668
+ message: "Codex Base URL",
1669
+ defaultValue: existing.baseUrl,
1670
+ validate: required()
1818
1671
  });
1819
- const apiKey = await promptSecret("Codex 第三方 API Key", {
1820
- defaultValue: existing.apiKey || undefined
1672
+ assertNotCancelled(baseUrl);
1673
+
1674
+ const apiKey = await p.password({
1675
+ message: "Codex 第三方 API Key(输入 - 清除)",
1676
+ mask: "*",
1677
+ defaultValue: existing.apiKey || undefined,
1678
+ validate: requiredUnlessExisting(existing.apiKey, "API Key 不能为空")
1821
1679
  });
1822
- const shouldDeploySubagents = await promptConfirm("是否部署 Codex subagents 配置?", true);
1680
+ assertNotCancelled(apiKey);
1681
+ const finalApiKey = resolvePasswordValue(apiKey, existing.apiKey);
1682
+
1683
+ const shouldDeploySubagents = await confirmOrCancel({ message: "是否部署 Codex subagents 配置?", initialValue: true });
1684
+
1823
1685
  const envKey = existing.envKey || "OPENAI_API_KEY";
1824
1686
  const currentContent = await pathExists(codexConfigPath) ? await readFile(codexConfigPath, "utf8") : "";
1825
1687
  const templateContent = await loadBundledCodexConfigTemplate();
@@ -1837,16 +1699,16 @@ async function configureCodexApi() {
1837
1699
  await mkdir(dirname(codexConfigPath), { recursive: true });
1838
1700
  await writeFile(codexConfigPath, content, "utf8");
1839
1701
 
1840
- const auth = mergeCodexAuthData(await readJsonFileSafe(codexAuthPath, {}), envKey, apiKey, existing.legacyEnvKeys || []);
1702
+ const auth = mergeCodexAuthData(await readJsonFileSafe(codexAuthPath, {}), envKey, finalApiKey, existing.legacyEnvKeys || []);
1841
1703
  await writeJsonFileWithBackup(codexAuthPath, auth);
1842
1704
 
1843
- console.log(`已更新 ${pathToLabel(codexConfigPath)} (${providerId}, ${baseUrl})`);
1844
- console.log(`已更新 ${pathToLabel(codexAuthPath)} (${maskSecret(apiKey)})`);
1705
+ p.log.step(`已更新 ${pathToLabel(codexConfigPath)} (${providerId}, ${baseUrl})`);
1706
+ p.log.step(`已更新 ${pathToLabel(codexAuthPath)} (${maskSecret(finalApiKey)})`);
1845
1707
  if (shouldDeploySubagents) {
1846
1708
  const deployed = await deployBundledCodexAgents();
1847
- console.log(`已部署 ${deployed.length} 个 Codex subagents 到 ${pathToLabel(join(home, ".codex", "agents"))}`);
1709
+ p.log.step(`已部署 ${deployed.length} 个 Codex subagents 到 ${pathToLabel(join(home, ".codex", "agents"))}`);
1848
1710
  } else {
1849
- console.log("已跳过 Codex subagents 部署。");
1711
+ p.log.message("已跳过 Codex subagents 部署。");
1850
1712
  }
1851
1713
  }
1852
1714
 
@@ -1917,16 +1779,25 @@ async function installCliTool(tool) {
1917
1779
 
1918
1780
  const installed = commandExists(toolConfig.command);
1919
1781
  if (installed) {
1920
- const shouldUpdate = await promptConfirm(`${toolConfig.label} 已检测到,是否继续执行 npm 强制安装/更新?`, false);
1782
+ const shouldUpdate = await confirmOrCancel({
1783
+ message: `${toolConfig.label} 已检测到,是否继续执行 npm 强制安装/更新?`,
1784
+ initialValue: false
1785
+ });
1921
1786
  if (!shouldUpdate) {
1922
- console.log(`跳过 ${toolConfig.label} 安装。`);
1787
+ p.log.message(`跳过 ${toolConfig.label} 安装。`);
1923
1788
  return;
1924
1789
  }
1925
1790
  }
1926
1791
 
1927
- console.log(`开始安装 ${toolConfig.label}...`);
1928
- await runCommand("npm", ["install", "-g", toolConfig.packageName, "--force"]);
1929
- console.log(`${toolConfig.label} 安装完成。`);
1792
+ const s = p.spinner();
1793
+ s.start(`正在安装 ${toolConfig.label}...`);
1794
+ try {
1795
+ await runCommand("npm", ["install", "-g", toolConfig.packageName, "--force"]);
1796
+ s.stop(`${toolConfig.label} 安装完成`);
1797
+ } catch (e) {
1798
+ s.cancel(c.red(`${toolConfig.label} 安装失败: ${e.message}`));
1799
+ throw e;
1800
+ }
1930
1801
  }
1931
1802
 
1932
1803
  async function runFullInit(options) {
@@ -1936,119 +1807,173 @@ async function runFullInit(options) {
1936
1807
  relinkOnly: false
1937
1808
  });
1938
1809
 
1939
- if (await promptConfirm("是否安装或更新 Claude Code CLI?", false)) {
1810
+ if (await confirmOrCancel({ message: "是否安装或更新 Claude Code CLI?", initialValue: false })) {
1940
1811
  await installCliTool("claude");
1941
1812
  }
1942
- if (await promptConfirm("是否配置 Claude Code 第三方 API?", commandExists("claude"))) {
1813
+ if (await confirmOrCancel({ message: "是否配置 Claude Code 第三方 API?", initialValue: commandExists("claude") })) {
1943
1814
  await configureClaudeApi();
1944
1815
  }
1945
- if (await promptConfirm("是否安装或更新 Codex CLI?", false)) {
1816
+ if (await confirmOrCancel({ message: "是否安装或更新 Codex CLI?", initialValue: false })) {
1946
1817
  await installCliTool("codex");
1947
1818
  }
1948
- if (await promptConfirm("是否配置 Codex 第三方 API?", commandExists("codex"))) {
1819
+ if (await confirmOrCancel({ message: "是否配置 Codex 第三方 API?", initialValue: commandExists("codex") })) {
1949
1820
  await configureCodexApi();
1950
1821
  }
1951
- if (await promptConfirm("是否填写 grok-search 环境变量?", true)) {
1822
+ if (await confirmOrCancel({ message: "是否填写 grok-search 环境变量?", initialValue: false })) {
1952
1823
  await configureGrokSearchEnv(options.agentsDir);
1953
1824
  }
1954
- if (await promptConfirm("是否填写 context7-auto-research 环境变量?", true)) {
1825
+ if (await confirmOrCancel({ message: "是否填写 context7-auto-research 环境变量?", initialValue: false })) {
1955
1826
  await configureContext7Env(options.agentsDir);
1956
1827
  }
1957
- if (await promptConfirm("是否填写 prompt-enhancer 环境变量?", true)) {
1828
+ if (await confirmOrCancel({ message: "是否填写 prompt-enhancer 环境变量?", initialValue: false })) {
1958
1829
  await configurePromptEnhancerEnv(options.agentsDir);
1959
1830
  }
1960
1831
 
1961
- console.log("\nAbelWorkflow 完整初始化完成。");
1832
+ p.log.success(c.green("AbelWorkflow 完整初始化完成"));
1962
1833
  }
1963
1834
 
1964
1835
  async function runInteractiveMenu(options) {
1965
- console.log("AbelWorkflow Setup");
1966
- console.log(`工作流目录: ${pathToLabel(options.agentsDir)}`);
1836
+ p.intro(c.bold(c.bgCyan(c.black(" AbelWorkflow Setup "))));
1837
+ p.log.message(`工作流目录: ${c.cyan(pathToLabel(options.agentsDir))}`);
1838
+
1839
+ const menuActions = {
1840
+ "full-init": async () => runFullInit(options),
1841
+ install: async () => installManagedWorkflow({
1842
+ agentsDir: options.agentsDir,
1843
+ force: options.force,
1844
+ relinkOnly: options.relinkOnly
1845
+ }),
1846
+ "grok-search": async () => configureGrokSearchEnv(options.agentsDir),
1847
+ context7: async () => configureContext7Env(options.agentsDir),
1848
+ "prompt-enhancer": async () => configurePromptEnhancerEnv(options.agentsDir),
1849
+ "claude-install": async () => installCliTool("claude"),
1850
+ "claude-api": async () => configureClaudeApi(),
1851
+ "codex-install": async () => installCliTool("codex"),
1852
+ "codex-api": async () => configureCodexApi()
1853
+ };
1854
+
1855
+ const buildOption = (d) => {
1856
+ const opt = { value: d.value, label: d.label };
1857
+ if (d.hint) {
1858
+ opt.hint = d.hint;
1859
+ }
1860
+ return opt;
1861
+ };
1967
1862
 
1968
1863
  while (true) {
1969
- const choice = await promptSelect("请选择操作", menuChoices, { defaultValue: "full-init" });
1864
+ const selectOptions = [
1865
+ ...interactiveMenuDescriptors
1866
+ .filter((d) => d.group === "main")
1867
+ .map(buildOption),
1868
+ { value: "__sep_skills__", label: "─── 技能配置 ───", disabled: true },
1869
+ ...interactiveMenuDescriptors
1870
+ .filter((d) => d.group === "skill")
1871
+ .map(buildOption),
1872
+ { value: "__sep_cli__", label: "─── CLI 工具 ───", disabled: true },
1873
+ ...interactiveMenuDescriptors
1874
+ .filter((d) => d.group === "cli")
1875
+ .map(buildOption),
1876
+ { value: "__sep_exit__", label: "────────────────", disabled: true },
1877
+ ...interactiveMenuDescriptors
1878
+ .filter((d) => d.group === "exit")
1879
+ .map(buildOption)
1880
+ ];
1881
+
1882
+ const choice = await p.select({
1883
+ message: "请选择操作",
1884
+ options: selectOptions,
1885
+ initialValue: interactiveMenuDefaultValue
1886
+ });
1970
1887
 
1888
+ if (p.isCancel(choice)) {
1889
+ p.outro(c.gray("已退出"));
1890
+ return;
1891
+ }
1971
1892
  if (choice === "exit") {
1893
+ p.outro(c.gray("已退出"));
1972
1894
  return;
1973
1895
  }
1974
1896
 
1975
- if (choice === "full-init") {
1976
- await runFullInit(options);
1977
- continue;
1978
- }
1979
- if (choice === "install") {
1980
- await installManagedWorkflow({
1981
- agentsDir: options.agentsDir,
1982
- force: options.force,
1983
- relinkOnly: options.relinkOnly
1984
- });
1985
- continue;
1986
- }
1987
- if (choice === "grok-search") {
1988
- await configureGrokSearchEnv(options.agentsDir);
1989
- continue;
1990
- }
1991
- if (choice === "context7") {
1992
- await configureContext7Env(options.agentsDir);
1897
+ const action = menuActions[choice];
1898
+ if (!action) {
1899
+ p.log.warn(`未知菜单选项: ${choice}`);
1993
1900
  continue;
1994
1901
  }
1995
- if (choice === "prompt-enhancer") {
1996
- await configurePromptEnhancerEnv(options.agentsDir);
1997
- continue;
1998
- }
1999
- if (choice === "claude-install") {
2000
- await installCliTool("claude");
2001
- continue;
2002
- }
2003
- if (choice === "claude-api") {
2004
- await configureClaudeApi();
2005
- continue;
2006
- }
2007
- if (choice === "codex-install") {
2008
- await installCliTool("codex");
2009
- continue;
2010
- }
2011
- if (choice === "codex-api") {
2012
- await configureCodexApi();
1902
+
1903
+ try {
1904
+ await action();
1905
+ } catch (error) {
1906
+ if (error instanceof CancelledError) {
1907
+ p.log.warn("操作已取消,返回菜单");
1908
+ continue;
1909
+ }
1910
+ throw error;
2013
1911
  }
2014
1912
  }
2015
1913
  }
2016
1914
 
2017
1915
  async function main() {
2018
- const options = parseArgs(process.argv.slice(2));
1916
+ const options = parseArgs(process.argv.slice(2), {
1917
+ defaultAgentsDir,
1918
+ resolvePath: resolve
1919
+ });
2019
1920
 
2020
1921
  if (options.command === "help") {
2021
1922
  printHelp();
2022
1923
  return;
2023
1924
  }
2024
1925
 
1926
+ // 非交互模式下菜单命令自动回退为 install
1927
+ if (options.command === "menu" && options.nonInteractive) {
1928
+ console.log("检测到非交互模式,自动执行工作流安装...");
1929
+ options.command = "install";
1930
+ }
1931
+
2025
1932
  if (options.command === "install") {
2026
- await installManagedWorkflow(options);
1933
+ try {
1934
+ await installManagedWorkflow(options);
1935
+ } catch (error) {
1936
+ const message = `操作失败: ${error.message || String(error)}`;
1937
+ // 非交互模式输出纯文本便于日志捕获/管道处理;交互模式使用 clack 格式化输出
1938
+ if (options.nonInteractive) {
1939
+ console.error(message);
1940
+ } else {
1941
+ p.outro(c.red(message));
1942
+ }
1943
+ process.exit(1);
1944
+ }
2027
1945
  return;
2028
1946
  }
2029
1947
 
2030
- if (!input.isTTY || !output.isTTY) {
2031
- throw new Error("交互式菜单需要 TTY 终端;非交互场景请显式使用 `npx abelworkflow install`");
2032
- }
1948
+ assertInteractiveMenuSupported({
1949
+ command: options.command,
1950
+ inputIsTTY: input.isTTY,
1951
+ outputIsTTY: output.isTTY,
1952
+ nonInteractive: options.nonInteractive
1953
+ });
2033
1954
 
2034
- await runInteractiveMenu(options);
1955
+ try {
1956
+ await runInteractiveMenu(options);
1957
+ } catch (error) {
1958
+ const message = `操作失败: ${error.message || String(error)}`;
1959
+ // 非交互模式输出纯文本便于日志捕获/管道处理;交互模式使用 clack 格式化输出
1960
+ if (options.nonInteractive) {
1961
+ console.error(message);
1962
+ } else {
1963
+ p.outro(c.red(message));
1964
+ }
1965
+ process.exit(1);
1966
+ }
2035
1967
  }
2036
1968
 
2037
1969
  export {
2038
1970
  buildCodexConfigContent,
2039
1971
  getRunCommandSpawnOptions,
1972
+ hasPromptEnhancerApiConfig,
2040
1973
  main,
2041
1974
  mergeCodexAuthData,
2042
1975
  mergeClaudeSettingsWithDefaults,
1976
+ resolvePromptEnhancerMode,
2043
1977
  resolveExistingCodexApiConfig,
2044
1978
  updateTomlSectionFields
2045
1979
  };
2046
-
2047
- const isDirectExecution = process.argv[1] ? resolve(process.argv[1]) === __filename : false;
2048
-
2049
- if (isDirectExecution) {
2050
- main().catch((error) => {
2051
- console.error(error instanceof Error ? error.message : String(error));
2052
- process.exit(1);
2053
- });
2054
- }