abelworkflow 0.6.5 → 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/.gitignore +1 -1
- package/README.md +1 -1
- package/lib/cli/logic.mjs +99 -41
- package/lib/cli.mjs +314 -274
- package/package.json +6 -2
- package/skills/confidence-check/SKILL.md +20 -110
- package/skills/confidence-check/confidence.ts +1 -60
- package/skills/grok-search/.env.example +3 -3
- package/skills/grok-search/SKILL.md +1 -1
- package/skills/grok-search/scripts/groksearch_cli.py +1 -1
- package/skills/prompt-enhancer/.env.example +6 -8
- package/skills/prompt-enhancer/ADVANCED.md +14 -12
- package/skills/prompt-enhancer/SKILL.md +5 -1
- package/skills/prompt-enhancer/scripts/enhance.py +115 -76
- package/skills/prompt-enhancer/scripts/prompt_enhancer_entry.py +47 -6
package/lib/cli.mjs
CHANGED
|
@@ -3,17 +3,22 @@ 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";
|
|
8
9
|
import {
|
|
9
10
|
assertInteractiveMenuSupported,
|
|
11
|
+
assertNotCancelled,
|
|
12
|
+
CancelledError,
|
|
13
|
+
confirmOrCancel,
|
|
10
14
|
getRunCommandSpawnOptions,
|
|
11
15
|
interactiveMenuDefaultValue,
|
|
12
16
|
interactiveMenuDescriptors,
|
|
13
17
|
parseArgs,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
required,
|
|
19
|
+
requiredUnlessExisting,
|
|
20
|
+
resolvePasswordValue,
|
|
21
|
+
selectOrCancel
|
|
17
22
|
} from "./cli/logic.mjs";
|
|
18
23
|
|
|
19
24
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -99,19 +104,21 @@ const ignoredSkillPathPatterns = [
|
|
|
99
104
|
];
|
|
100
105
|
|
|
101
106
|
function printHelp() {
|
|
102
|
-
console.log(
|
|
103
|
-
|
|
104
|
-
Usage:
|
|
105
|
-
npx abelworkflow
|
|
106
|
-
npx abelworkflow init
|
|
107
|
-
npx abelworkflow install
|
|
108
|
-
npx abelworkflow install --force
|
|
109
|
-
npx abelworkflow install --link-only
|
|
110
|
-
npx abelworkflow install --agents-dir /custom/path
|
|
111
|
-
|
|
112
|
-
|
|
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:")}
|
|
113
119
|
- npx abelworkflow: open the interactive setup menu.
|
|
114
120
|
- npx abelworkflow install: sync managed files and links explicitly.
|
|
121
|
+
- --non-interactive: auto-execute install (skip interactive menu); auto-enabled in CI.
|
|
115
122
|
`);
|
|
116
123
|
}
|
|
117
124
|
|
|
@@ -178,18 +185,18 @@ async function backupExistingPath(targetPath) {
|
|
|
178
185
|
const backupPath = await createBackupPath(targetPath);
|
|
179
186
|
await cp(targetPath, backupPath, { recursive: true, force: false });
|
|
180
187
|
createdBackupPaths.add(targetPath);
|
|
181
|
-
|
|
188
|
+
p.log.message(`已备份已有配置: ${pathToLabel(targetPath)} -> ${pathToLabel(backupPath)}`);
|
|
182
189
|
return backupPath;
|
|
183
190
|
}
|
|
184
191
|
|
|
185
|
-
async function backupIfNeeded(targetPath
|
|
192
|
+
async function backupIfNeeded(targetPath) {
|
|
186
193
|
if (!(await pathExists(targetPath))) {
|
|
187
194
|
return null;
|
|
188
195
|
}
|
|
189
196
|
|
|
190
197
|
const backupPath = await createBackupPath(targetPath);
|
|
191
198
|
await rename(targetPath, backupPath);
|
|
192
|
-
|
|
199
|
+
p.log.message(`已备份已有配置: ${pathToLabel(targetPath)} -> ${pathToLabel(backupPath)}`);
|
|
193
200
|
return backupPath;
|
|
194
201
|
}
|
|
195
202
|
|
|
@@ -449,7 +456,7 @@ async function createSymlink(targetPath, sourcePath, linkType, kind) {
|
|
|
449
456
|
await symlink(sourcePath, targetPath, linkType);
|
|
450
457
|
}
|
|
451
458
|
|
|
452
|
-
async function ensureManagedLink(targetPath, sourcePath, kind,
|
|
459
|
+
async function ensureManagedLink(targetPath, sourcePath, kind, previousLinkedTargets) {
|
|
453
460
|
await mkdir(dirname(targetPath), { recursive: true });
|
|
454
461
|
const sourceResolved = resolve(sourcePath);
|
|
455
462
|
const sourceExists = await pathTargetExists(sourcePath);
|
|
@@ -487,7 +494,7 @@ async function ensureManagedLink(targetPath, sourcePath, kind, force, previousLi
|
|
|
487
494
|
if (wasPreviouslyManaged) {
|
|
488
495
|
await rm(targetPath, { recursive: true, force: true });
|
|
489
496
|
} else {
|
|
490
|
-
await backupIfNeeded(targetPath
|
|
497
|
+
await backupIfNeeded(targetPath);
|
|
491
498
|
}
|
|
492
499
|
} else if (!sourceExists) {
|
|
493
500
|
return { targetPath, status: "skipped" };
|
|
@@ -525,7 +532,7 @@ function shouldCopyManagedFile(error) {
|
|
|
525
532
|
return ["EPERM", "EACCES", "EXDEV", "EINVAL", "UNKNOWN"].includes(error?.code);
|
|
526
533
|
}
|
|
527
534
|
|
|
528
|
-
async function linkSkillDirectories(baseDir, agentsDir,
|
|
535
|
+
async function linkSkillDirectories(baseDir, agentsDir, previousLinkedTargets) {
|
|
529
536
|
const results = [];
|
|
530
537
|
const skillsRoot = join(agentsDir, "skills");
|
|
531
538
|
const skillNames = (await getDirectoryNames(skillsRoot)).filter((skillName) => skillName !== ".system");
|
|
@@ -536,7 +543,6 @@ async function linkSkillDirectories(baseDir, agentsDir, force, previousLinkedTar
|
|
|
536
543
|
join(baseDir, "skills", skillName),
|
|
537
544
|
join(skillsRoot, skillName),
|
|
538
545
|
"dir",
|
|
539
|
-
force,
|
|
540
546
|
previousLinkedTargets
|
|
541
547
|
)
|
|
542
548
|
);
|
|
@@ -664,19 +670,7 @@ function isWithinManagedRoot(targetPath, managedSourceRoot) {
|
|
|
664
670
|
return relativePath !== ".." && !relativePath.startsWith(`..${isWindows() ? "\\" : "/"}`);
|
|
665
671
|
}
|
|
666
672
|
|
|
667
|
-
function
|
|
668
|
-
if (status === "unchanged") {
|
|
669
|
-
return "=";
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
if (status === "removed") {
|
|
673
|
-
return "-";
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
return "+";
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
async function linkClaude(agentsDir, force, previousLinkedTargets) {
|
|
673
|
+
async function linkClaude(agentsDir, previousLinkedTargets) {
|
|
680
674
|
const claudeDir = join(home, ".claude");
|
|
681
675
|
await mkdir(claudeDir, { recursive: true });
|
|
682
676
|
await removeIfNotDirectory(join(claudeDir, "commands"));
|
|
@@ -689,21 +683,19 @@ async function linkClaude(agentsDir, force, previousLinkedTargets) {
|
|
|
689
683
|
join(claudeDir, "CLAUDE.md"),
|
|
690
684
|
join(agentsDir, "AGENTS.md"),
|
|
691
685
|
"file",
|
|
692
|
-
force,
|
|
693
686
|
previousLinkedTargets
|
|
694
687
|
),
|
|
695
688
|
await ensureManagedLink(
|
|
696
689
|
join(claudeDir, "commands", "oc"),
|
|
697
690
|
join(agentsDir, "commands", "oc"),
|
|
698
691
|
"dir",
|
|
699
|
-
force,
|
|
700
692
|
previousLinkedTargets
|
|
701
693
|
),
|
|
702
|
-
...(await linkSkillDirectories(claudeDir, agentsDir,
|
|
694
|
+
...(await linkSkillDirectories(claudeDir, agentsDir, previousLinkedTargets))
|
|
703
695
|
];
|
|
704
696
|
}
|
|
705
697
|
|
|
706
|
-
async function linkCodex(agentsDir,
|
|
698
|
+
async function linkCodex(agentsDir, previousLinkedTargets) {
|
|
707
699
|
const results = [];
|
|
708
700
|
const codexDir = join(home, ".codex");
|
|
709
701
|
await mkdir(codexDir, { recursive: true });
|
|
@@ -717,11 +709,10 @@ async function linkCodex(agentsDir, force, previousLinkedTargets) {
|
|
|
717
709
|
join(codexDir, "AGENTS.md"),
|
|
718
710
|
join(agentsDir, "AGENTS.md"),
|
|
719
711
|
"file",
|
|
720
|
-
force,
|
|
721
712
|
previousLinkedTargets
|
|
722
713
|
)
|
|
723
714
|
);
|
|
724
|
-
results.push(...(await linkSkillDirectories(codexDir, agentsDir,
|
|
715
|
+
results.push(...(await linkSkillDirectories(codexDir, agentsDir, previousLinkedTargets)));
|
|
725
716
|
|
|
726
717
|
const commandFiles = await getCommandNames(join(agentsDir, "commands", "oc"));
|
|
727
718
|
results.push(
|
|
@@ -738,7 +729,6 @@ async function linkCodex(agentsDir, force, previousLinkedTargets) {
|
|
|
738
729
|
join(codexDir, "prompts", fileName),
|
|
739
730
|
join(agentsDir, "commands", "oc", fileName),
|
|
740
731
|
"file",
|
|
741
|
-
force,
|
|
742
732
|
previousLinkedTargets
|
|
743
733
|
)
|
|
744
734
|
);
|
|
@@ -750,9 +740,17 @@ async function linkCodex(agentsDir, force, previousLinkedTargets) {
|
|
|
750
740
|
async function installManagedWorkflow(options) {
|
|
751
741
|
let previousMetadata = {};
|
|
752
742
|
let managedChildren = {};
|
|
743
|
+
const s = p.spinner();
|
|
753
744
|
|
|
754
745
|
if (!options.relinkOnly) {
|
|
755
|
-
(
|
|
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("工作流文件已同步");
|
|
756
754
|
} else if (!(await pathExists(options.agentsDir))) {
|
|
757
755
|
throw new Error(`${options.agentsDir} does not exist; remove --link-only or install first`);
|
|
758
756
|
} else {
|
|
@@ -761,8 +759,18 @@ async function installManagedWorkflow(options) {
|
|
|
761
759
|
}
|
|
762
760
|
|
|
763
761
|
const previousLinkedTargets = previousMetadata.linkedTargets ?? {};
|
|
764
|
-
|
|
765
|
-
|
|
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
|
+
|
|
766
774
|
const linkedTargets = Object.fromEntries(
|
|
767
775
|
[...claudeResults, ...codexResults]
|
|
768
776
|
.filter((result) => result.sourcePath)
|
|
@@ -783,14 +791,16 @@ async function installManagedWorkflow(options) {
|
|
|
783
791
|
linkedTargets
|
|
784
792
|
});
|
|
785
793
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
|
|
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")} 更新托管文件`);
|
|
794
804
|
}
|
|
795
805
|
|
|
796
806
|
async function readJsonFileSafe(path, fallback = {}) {
|
|
@@ -882,121 +892,6 @@ async function updateDotenvFile(path, updates) {
|
|
|
882
892
|
await writeFile(path, renderDotenv(current), "utf8");
|
|
883
893
|
}
|
|
884
894
|
|
|
885
|
-
function currentChoiceIndex(choices, defaultValue) {
|
|
886
|
-
if (defaultValue === undefined) {
|
|
887
|
-
return -1;
|
|
888
|
-
}
|
|
889
|
-
return choices.findIndex((choice) => choice.value === defaultValue);
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
async function setTerminalEcho(enabled) {
|
|
893
|
-
if (!input.isTTY || isWindows()) {
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
const result = spawnSync("stty", [enabled ? "echo" : "-echo"], { stdio: ["inherit", "ignore", "ignore"] });
|
|
898
|
-
if (result.error) {
|
|
899
|
-
throw result.error;
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
async function promptText(message, options = {}) {
|
|
904
|
-
const { defaultValue, allowEmpty = false } = options;
|
|
905
|
-
|
|
906
|
-
while (true) {
|
|
907
|
-
const suffix = defaultValue !== undefined && defaultValue !== ""
|
|
908
|
-
? ` [${defaultValue}]`
|
|
909
|
-
: "";
|
|
910
|
-
const rl = createInterface({ input, output });
|
|
911
|
-
let answer;
|
|
912
|
-
try {
|
|
913
|
-
answer = await rl.question(`${message}${suffix}: `);
|
|
914
|
-
} finally {
|
|
915
|
-
rl.close();
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
const resolved = resolvePromptValue(answer, { defaultValue, allowEmpty });
|
|
919
|
-
if (resolved.ok) {
|
|
920
|
-
return resolved.value;
|
|
921
|
-
}
|
|
922
|
-
console.log(resolved.error);
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
async function promptSecret(message, options = {}) {
|
|
927
|
-
const { defaultValue, allowEmpty = false } = options;
|
|
928
|
-
|
|
929
|
-
if (shouldUseVisibleSecretFallback({ inputIsTTY: input.isTTY, platform: getPlatform() })) {
|
|
930
|
-
while (true) {
|
|
931
|
-
const suffix = defaultValue !== undefined && defaultValue !== ""
|
|
932
|
-
? " [直接回车保留现有值]"
|
|
933
|
-
: "";
|
|
934
|
-
const rl = createInterface({ input, output });
|
|
935
|
-
let answer;
|
|
936
|
-
try {
|
|
937
|
-
answer = await rl.question(`${message}${suffix}: `);
|
|
938
|
-
} finally {
|
|
939
|
-
rl.close();
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
const resolved = resolvePromptValue(answer, { defaultValue, allowEmpty });
|
|
943
|
-
if (resolved.ok) {
|
|
944
|
-
return resolved.value;
|
|
945
|
-
}
|
|
946
|
-
console.log(resolved.error);
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
while (true) {
|
|
951
|
-
const suffix = defaultValue !== undefined && defaultValue !== ""
|
|
952
|
-
? " [直接回车保留现有值]"
|
|
953
|
-
: "";
|
|
954
|
-
const rl = createInterface({ input, output, terminal: true });
|
|
955
|
-
let answer;
|
|
956
|
-
try {
|
|
957
|
-
await setTerminalEcho(false);
|
|
958
|
-
answer = await rl.question(`${message}${suffix}: `);
|
|
959
|
-
output.write("\n");
|
|
960
|
-
} finally {
|
|
961
|
-
await setTerminalEcho(true);
|
|
962
|
-
rl.close();
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
const resolved = resolvePromptValue(answer, { defaultValue, allowEmpty });
|
|
966
|
-
if (resolved.ok) {
|
|
967
|
-
return resolved.value;
|
|
968
|
-
}
|
|
969
|
-
console.log(resolved.error);
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
async function promptSelect(message, choices, options = {}) {
|
|
974
|
-
const defaultIndex = currentChoiceIndex(choices, options.defaultValue);
|
|
975
|
-
console.log(`\n${message}`);
|
|
976
|
-
choices.forEach((choice, index) => {
|
|
977
|
-
const defaultMarker = index === defaultIndex ? " [默认]" : "";
|
|
978
|
-
console.log(` ${index + 1}. ${choice.label}${defaultMarker}`);
|
|
979
|
-
});
|
|
980
|
-
|
|
981
|
-
while (true) {
|
|
982
|
-
const fallbackValue = defaultIndex >= 0 ? String(defaultIndex + 1) : undefined;
|
|
983
|
-
const answer = await promptText("请输入序号", { defaultValue: fallbackValue, allowEmpty: defaultIndex >= 0 });
|
|
984
|
-
const resolved = resolveSelectValue(answer, choices);
|
|
985
|
-
if (resolved.ok) {
|
|
986
|
-
return resolved.value;
|
|
987
|
-
}
|
|
988
|
-
console.log(resolved.error);
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
async function promptConfirm(message, defaultValue = true) {
|
|
993
|
-
const value = await promptSelect(message, [
|
|
994
|
-
{ value: true, label: "是" },
|
|
995
|
-
{ value: false, label: "否" }
|
|
996
|
-
], { defaultValue });
|
|
997
|
-
return value;
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
895
|
function commandExists(command) {
|
|
1001
896
|
const checker = isWindows() ? "where" : "which";
|
|
1002
897
|
const result = spawnSync(checker, [command], { stdio: "ignore" });
|
|
@@ -1017,22 +912,12 @@ async function runCommand(command, args) {
|
|
|
1017
912
|
});
|
|
1018
913
|
}
|
|
1019
914
|
|
|
1020
|
-
function sanitizeProviderId(name) {
|
|
1021
|
-
return name
|
|
1022
|
-
.trim()
|
|
1023
|
-
.toLowerCase()
|
|
1024
|
-
.replace(/[\s.]+/gu, "-")
|
|
1025
|
-
.replace(/[^a-z0-9_-]/gu, "")
|
|
1026
|
-
.replace(/-+/gu, "-")
|
|
1027
|
-
.replace(/^-|-$/gu, "") || "abelworkflow";
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
915
|
async function ensureWorkflowPresent(agentsDir) {
|
|
1031
916
|
if (await pathExists(join(agentsDir, "AGENTS.md"))) {
|
|
1032
917
|
return;
|
|
1033
918
|
}
|
|
1034
919
|
|
|
1035
|
-
|
|
920
|
+
p.log.message("未检测到已安装的 AbelWorkflow,先执行一次工作流同步。");
|
|
1036
921
|
await installManagedWorkflow({
|
|
1037
922
|
agentsDir,
|
|
1038
923
|
force: false,
|
|
@@ -1044,101 +929,139 @@ async function configureGrokSearchEnv(agentsDir) {
|
|
|
1044
929
|
await ensureWorkflowPresent(agentsDir);
|
|
1045
930
|
const envPath = join(agentsDir, "skills", "grok-search", ".env");
|
|
1046
931
|
const existing = await readDotenvFile(envPath);
|
|
1047
|
-
const baseUrl = await
|
|
1048
|
-
|
|
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()
|
|
1049
936
|
});
|
|
1050
|
-
|
|
1051
|
-
|
|
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 不能为空")
|
|
1052
944
|
});
|
|
1053
|
-
|
|
1054
|
-
|
|
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()
|
|
952
|
+
});
|
|
953
|
+
assertNotCancelled(model);
|
|
954
|
+
|
|
955
|
+
const useTavily = await confirmOrCancel({
|
|
956
|
+
message: "是否同时配置 Tavily 作为额外搜索源?",
|
|
957
|
+
initialValue: Boolean(existing.TAVILY_API_KEY)
|
|
1055
958
|
});
|
|
1056
|
-
|
|
959
|
+
|
|
1057
960
|
const tavilyKey = useTavily
|
|
1058
|
-
? await
|
|
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
|
+
})
|
|
1059
967
|
: "";
|
|
968
|
+
if (useTavily) assertNotCancelled(tavilyKey);
|
|
969
|
+
const finalTavilyKey = useTavily ? resolvePasswordValue(tavilyKey, existing.TAVILY_API_KEY) : null;
|
|
1060
970
|
|
|
1061
971
|
await updateDotenvFile(envPath, {
|
|
1062
972
|
GROK_API_URL: baseUrl,
|
|
1063
|
-
GROK_API_KEY:
|
|
973
|
+
GROK_API_KEY: finalApiKey,
|
|
1064
974
|
GROK_MODEL: model,
|
|
1065
|
-
TAVILY_API_KEY:
|
|
975
|
+
TAVILY_API_KEY: finalTavilyKey,
|
|
1066
976
|
TAVILY_ENABLED: useTavily ? "true" : null
|
|
1067
977
|
});
|
|
1068
978
|
|
|
1069
|
-
|
|
979
|
+
p.log.step(`已写入 ${pathToLabel(envPath)}`);
|
|
1070
980
|
}
|
|
1071
981
|
|
|
1072
982
|
async function configureContext7Env(agentsDir) {
|
|
1073
983
|
await ensureWorkflowPresent(agentsDir);
|
|
1074
984
|
const envPath = join(agentsDir, "skills", "context7-auto-research", ".env");
|
|
1075
985
|
const existing = await readDotenvFile(envPath);
|
|
1076
|
-
const apiKey = await
|
|
1077
|
-
|
|
1078
|
-
|
|
986
|
+
const apiKey = await p.password({
|
|
987
|
+
message: "Context7 API Key (可选,输入 - 清除)",
|
|
988
|
+
mask: "*",
|
|
989
|
+
defaultValue: existing.CONTEXT7_API_KEY || undefined
|
|
1079
990
|
});
|
|
991
|
+
assertNotCancelled(apiKey);
|
|
992
|
+
const finalApiKey = resolvePasswordValue(apiKey, existing.CONTEXT7_API_KEY);
|
|
1080
993
|
|
|
1081
994
|
await updateDotenvFile(envPath, {
|
|
1082
|
-
CONTEXT7_API_KEY:
|
|
995
|
+
CONTEXT7_API_KEY: finalApiKey
|
|
1083
996
|
});
|
|
1084
997
|
|
|
1085
|
-
|
|
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() !== "");
|
|
1086
1004
|
}
|
|
1087
1005
|
|
|
1088
1006
|
function resolvePromptEnhancerMode(existing) {
|
|
1089
|
-
|
|
1090
|
-
return "anthropic";
|
|
1091
|
-
}
|
|
1092
|
-
if (existing.OPENAI_API_KEY) {
|
|
1093
|
-
return "openai";
|
|
1094
|
-
}
|
|
1095
|
-
return "local";
|
|
1007
|
+
return hasPromptEnhancerApiConfig(existing) ? "openai-compatible" : "agent";
|
|
1096
1008
|
}
|
|
1097
1009
|
|
|
1098
1010
|
async function configurePromptEnhancerEnv(agentsDir) {
|
|
1099
1011
|
await ensureWorkflowPresent(agentsDir);
|
|
1100
1012
|
const envPath = join(agentsDir, "skills", "prompt-enhancer", ".env");
|
|
1101
1013
|
const existing = await readDotenvFile(envPath);
|
|
1102
|
-
const mode = await
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
defaultValue: existing.ANTHROPIC_API_KEY || undefined
|
|
1111
|
-
});
|
|
1112
|
-
const model = await promptText("PE_MODEL", {
|
|
1113
|
-
defaultValue: existing.PE_MODEL || "claude-sonnet-4-20250514"
|
|
1114
|
-
});
|
|
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
|
+
});
|
|
1115
1022
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
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()
|
|
1120
1028
|
});
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
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 不能为空")
|
|
1124
1036
|
});
|
|
1125
|
-
|
|
1126
|
-
|
|
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()
|
|
1127
1044
|
});
|
|
1045
|
+
assertNotCancelled(model);
|
|
1128
1046
|
|
|
1129
1047
|
await updateDotenvFile(envPath, {
|
|
1130
|
-
|
|
1048
|
+
PE_API_URL: apiUrl,
|
|
1049
|
+
PE_API_KEY: finalApiKey,
|
|
1050
|
+
PE_MODEL: model,
|
|
1131
1051
|
ANTHROPIC_API_KEY: null,
|
|
1132
|
-
|
|
1052
|
+
OPENAI_API_KEY: null
|
|
1133
1053
|
});
|
|
1134
1054
|
} else {
|
|
1135
1055
|
await updateDotenvFile(envPath, {
|
|
1056
|
+
PE_API_URL: null,
|
|
1057
|
+
PE_API_KEY: null,
|
|
1058
|
+
PE_MODEL: null,
|
|
1136
1059
|
ANTHROPIC_API_KEY: null,
|
|
1137
1060
|
OPENAI_API_KEY: null
|
|
1138
1061
|
});
|
|
1139
1062
|
}
|
|
1140
1063
|
|
|
1141
|
-
|
|
1064
|
+
p.log.step(`已写入 ${pathToLabel(envPath)}`);
|
|
1142
1065
|
}
|
|
1143
1066
|
|
|
1144
1067
|
function mergeClaudeSettingsWithDefaults(settings) {
|
|
@@ -1198,28 +1121,46 @@ function ensureApprovedClaudeApiKey(config, apiKey) {
|
|
|
1198
1121
|
async function configureClaudeApi() {
|
|
1199
1122
|
const settings = await readJsonFileSafe(claudeSettingsPath, {});
|
|
1200
1123
|
const existing = getExistingClaudeApiConfig(settings);
|
|
1201
|
-
const authType = await
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
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
|
|
1207
1131
|
});
|
|
1208
|
-
|
|
1209
|
-
|
|
1132
|
+
|
|
1133
|
+
const baseUrl = await p.text({
|
|
1134
|
+
message: "Claude Code Base URL",
|
|
1135
|
+
defaultValue: existing.baseUrl,
|
|
1136
|
+
validate: required()
|
|
1137
|
+
});
|
|
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 不能为空")
|
|
1210
1145
|
});
|
|
1211
|
-
|
|
1212
|
-
|
|
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()
|
|
1213
1153
|
});
|
|
1154
|
+
assertNotCancelled(model);
|
|
1214
1155
|
|
|
1215
1156
|
const nextSettings = mergeClaudeSettingsWithDefaults(settings);
|
|
1216
1157
|
nextSettings.env.ANTHROPIC_BASE_URL = baseUrl;
|
|
1217
1158
|
|
|
1218
1159
|
if (authType === "auth_token") {
|
|
1219
|
-
nextSettings.env.ANTHROPIC_AUTH_TOKEN =
|
|
1160
|
+
nextSettings.env.ANTHROPIC_AUTH_TOKEN = finalKey;
|
|
1220
1161
|
delete nextSettings.env.ANTHROPIC_API_KEY;
|
|
1221
1162
|
} else {
|
|
1222
|
-
nextSettings.env.ANTHROPIC_API_KEY =
|
|
1163
|
+
nextSettings.env.ANTHROPIC_API_KEY = finalKey;
|
|
1223
1164
|
delete nextSettings.env.ANTHROPIC_AUTH_TOKEN;
|
|
1224
1165
|
}
|
|
1225
1166
|
for (const field of claudeModelEnvKeys) {
|
|
@@ -1230,10 +1171,10 @@ async function configureClaudeApi() {
|
|
|
1230
1171
|
|
|
1231
1172
|
const metaConfig = await readJsonFileSafe(claudeMetaConfigPath, {});
|
|
1232
1173
|
metaConfig.hasCompletedOnboarding = true;
|
|
1233
|
-
ensureApprovedClaudeApiKey(metaConfig,
|
|
1174
|
+
ensureApprovedClaudeApiKey(metaConfig, finalKey);
|
|
1234
1175
|
await writeJsonFileWithBackup(claudeMetaConfigPath, metaConfig);
|
|
1235
1176
|
|
|
1236
|
-
|
|
1177
|
+
p.log.step(`已更新 ${pathToLabel(claudeSettingsPath)} (${authType}, ${baseUrl}, ${maskSecret(finalKey)})`);
|
|
1237
1178
|
}
|
|
1238
1179
|
|
|
1239
1180
|
function updateTopLevelTomlField(content, field, value) {
|
|
@@ -1723,13 +1664,24 @@ async function configureCodexApi() {
|
|
|
1723
1664
|
const existing = await getExistingCodexApiConfig();
|
|
1724
1665
|
const providerId = existing.providerId || "abelworkflow";
|
|
1725
1666
|
const providerName = existing.providerName || providerId;
|
|
1726
|
-
const baseUrl = await
|
|
1727
|
-
|
|
1667
|
+
const baseUrl = await p.text({
|
|
1668
|
+
message: "Codex Base URL",
|
|
1669
|
+
defaultValue: existing.baseUrl,
|
|
1670
|
+
validate: required()
|
|
1728
1671
|
});
|
|
1729
|
-
|
|
1730
|
-
|
|
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 不能为空")
|
|
1731
1679
|
});
|
|
1732
|
-
|
|
1680
|
+
assertNotCancelled(apiKey);
|
|
1681
|
+
const finalApiKey = resolvePasswordValue(apiKey, existing.apiKey);
|
|
1682
|
+
|
|
1683
|
+
const shouldDeploySubagents = await confirmOrCancel({ message: "是否部署 Codex subagents 配置?", initialValue: true });
|
|
1684
|
+
|
|
1733
1685
|
const envKey = existing.envKey || "OPENAI_API_KEY";
|
|
1734
1686
|
const currentContent = await pathExists(codexConfigPath) ? await readFile(codexConfigPath, "utf8") : "";
|
|
1735
1687
|
const templateContent = await loadBundledCodexConfigTemplate();
|
|
@@ -1747,16 +1699,16 @@ async function configureCodexApi() {
|
|
|
1747
1699
|
await mkdir(dirname(codexConfigPath), { recursive: true });
|
|
1748
1700
|
await writeFile(codexConfigPath, content, "utf8");
|
|
1749
1701
|
|
|
1750
|
-
const auth = mergeCodexAuthData(await readJsonFileSafe(codexAuthPath, {}), envKey,
|
|
1702
|
+
const auth = mergeCodexAuthData(await readJsonFileSafe(codexAuthPath, {}), envKey, finalApiKey, existing.legacyEnvKeys || []);
|
|
1751
1703
|
await writeJsonFileWithBackup(codexAuthPath, auth);
|
|
1752
1704
|
|
|
1753
|
-
|
|
1754
|
-
|
|
1705
|
+
p.log.step(`已更新 ${pathToLabel(codexConfigPath)} (${providerId}, ${baseUrl})`);
|
|
1706
|
+
p.log.step(`已更新 ${pathToLabel(codexAuthPath)} (${maskSecret(finalApiKey)})`);
|
|
1755
1707
|
if (shouldDeploySubagents) {
|
|
1756
1708
|
const deployed = await deployBundledCodexAgents();
|
|
1757
|
-
|
|
1709
|
+
p.log.step(`已部署 ${deployed.length} 个 Codex subagents 到 ${pathToLabel(join(home, ".codex", "agents"))}`);
|
|
1758
1710
|
} else {
|
|
1759
|
-
|
|
1711
|
+
p.log.message("已跳过 Codex subagents 部署。");
|
|
1760
1712
|
}
|
|
1761
1713
|
}
|
|
1762
1714
|
|
|
@@ -1827,16 +1779,25 @@ async function installCliTool(tool) {
|
|
|
1827
1779
|
|
|
1828
1780
|
const installed = commandExists(toolConfig.command);
|
|
1829
1781
|
if (installed) {
|
|
1830
|
-
const shouldUpdate = await
|
|
1782
|
+
const shouldUpdate = await confirmOrCancel({
|
|
1783
|
+
message: `${toolConfig.label} 已检测到,是否继续执行 npm 强制安装/更新?`,
|
|
1784
|
+
initialValue: false
|
|
1785
|
+
});
|
|
1831
1786
|
if (!shouldUpdate) {
|
|
1832
|
-
|
|
1787
|
+
p.log.message(`跳过 ${toolConfig.label} 安装。`);
|
|
1833
1788
|
return;
|
|
1834
1789
|
}
|
|
1835
1790
|
}
|
|
1836
1791
|
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
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
|
+
}
|
|
1840
1801
|
}
|
|
1841
1802
|
|
|
1842
1803
|
async function runFullInit(options) {
|
|
@@ -1846,36 +1807,35 @@ async function runFullInit(options) {
|
|
|
1846
1807
|
relinkOnly: false
|
|
1847
1808
|
});
|
|
1848
1809
|
|
|
1849
|
-
if (await
|
|
1810
|
+
if (await confirmOrCancel({ message: "是否安装或更新 Claude Code CLI?", initialValue: false })) {
|
|
1850
1811
|
await installCliTool("claude");
|
|
1851
1812
|
}
|
|
1852
|
-
if (await
|
|
1813
|
+
if (await confirmOrCancel({ message: "是否配置 Claude Code 第三方 API?", initialValue: commandExists("claude") })) {
|
|
1853
1814
|
await configureClaudeApi();
|
|
1854
1815
|
}
|
|
1855
|
-
if (await
|
|
1816
|
+
if (await confirmOrCancel({ message: "是否安装或更新 Codex CLI?", initialValue: false })) {
|
|
1856
1817
|
await installCliTool("codex");
|
|
1857
1818
|
}
|
|
1858
|
-
if (await
|
|
1819
|
+
if (await confirmOrCancel({ message: "是否配置 Codex 第三方 API?", initialValue: commandExists("codex") })) {
|
|
1859
1820
|
await configureCodexApi();
|
|
1860
1821
|
}
|
|
1861
|
-
if (await
|
|
1822
|
+
if (await confirmOrCancel({ message: "是否填写 grok-search 环境变量?", initialValue: false })) {
|
|
1862
1823
|
await configureGrokSearchEnv(options.agentsDir);
|
|
1863
1824
|
}
|
|
1864
|
-
if (await
|
|
1825
|
+
if (await confirmOrCancel({ message: "是否填写 context7-auto-research 环境变量?", initialValue: false })) {
|
|
1865
1826
|
await configureContext7Env(options.agentsDir);
|
|
1866
1827
|
}
|
|
1867
|
-
if (await
|
|
1828
|
+
if (await confirmOrCancel({ message: "是否填写 prompt-enhancer 环境变量?", initialValue: false })) {
|
|
1868
1829
|
await configurePromptEnhancerEnv(options.agentsDir);
|
|
1869
1830
|
}
|
|
1870
1831
|
|
|
1871
|
-
|
|
1832
|
+
p.log.success(c.green("AbelWorkflow 完整初始化完成"));
|
|
1872
1833
|
}
|
|
1873
1834
|
|
|
1874
1835
|
async function runInteractiveMenu(options) {
|
|
1875
|
-
|
|
1876
|
-
|
|
1836
|
+
p.intro(c.bold(c.bgCyan(c.black(" AbelWorkflow Setup "))));
|
|
1837
|
+
p.log.message(`工作流目录: ${c.cyan(pathToLabel(options.agentsDir))}`);
|
|
1877
1838
|
|
|
1878
|
-
const menuChoices = interactiveMenuDescriptors.map(({ value, label }) => ({ value, label }));
|
|
1879
1839
|
const menuActions = {
|
|
1880
1840
|
"full-init": async () => runFullInit(options),
|
|
1881
1841
|
install: async () => installManagedWorkflow({
|
|
@@ -1892,14 +1852,63 @@ async function runInteractiveMenu(options) {
|
|
|
1892
1852
|
"codex-api": async () => configureCodexApi()
|
|
1893
1853
|
};
|
|
1894
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
|
+
};
|
|
1862
|
+
|
|
1895
1863
|
while (true) {
|
|
1896
|
-
const
|
|
1897
|
-
|
|
1898
|
-
|
|
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
|
+
});
|
|
1887
|
+
|
|
1888
|
+
if (p.isCancel(choice)) {
|
|
1889
|
+
p.outro(c.gray("已退出"));
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1892
|
+
if (choice === "exit") {
|
|
1893
|
+
p.outro(c.gray("已退出"));
|
|
1899
1894
|
return;
|
|
1900
1895
|
}
|
|
1901
1896
|
|
|
1902
|
-
|
|
1897
|
+
const action = menuActions[choice];
|
|
1898
|
+
if (!action) {
|
|
1899
|
+
p.log.warn(`未知菜单选项: ${choice}`);
|
|
1900
|
+
continue;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
try {
|
|
1904
|
+
await action();
|
|
1905
|
+
} catch (error) {
|
|
1906
|
+
if (error instanceof CancelledError) {
|
|
1907
|
+
p.log.warn("操作已取消,返回菜单");
|
|
1908
|
+
continue;
|
|
1909
|
+
}
|
|
1910
|
+
throw error;
|
|
1911
|
+
}
|
|
1903
1912
|
}
|
|
1904
1913
|
}
|
|
1905
1914
|
|
|
@@ -1914,26 +1923,57 @@ async function main() {
|
|
|
1914
1923
|
return;
|
|
1915
1924
|
}
|
|
1916
1925
|
|
|
1926
|
+
// 非交互模式下菜单命令自动回退为 install
|
|
1927
|
+
if (options.command === "menu" && options.nonInteractive) {
|
|
1928
|
+
console.log("检测到非交互模式,自动执行工作流安装...");
|
|
1929
|
+
options.command = "install";
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1917
1932
|
if (options.command === "install") {
|
|
1918
|
-
|
|
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
|
+
}
|
|
1919
1945
|
return;
|
|
1920
1946
|
}
|
|
1921
1947
|
|
|
1922
1948
|
assertInteractiveMenuSupported({
|
|
1923
1949
|
command: options.command,
|
|
1924
1950
|
inputIsTTY: input.isTTY,
|
|
1925
|
-
outputIsTTY: output.isTTY
|
|
1951
|
+
outputIsTTY: output.isTTY,
|
|
1952
|
+
nonInteractive: options.nonInteractive
|
|
1926
1953
|
});
|
|
1927
1954
|
|
|
1928
|
-
|
|
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
|
+
}
|
|
1929
1967
|
}
|
|
1930
1968
|
|
|
1931
1969
|
export {
|
|
1932
1970
|
buildCodexConfigContent,
|
|
1933
1971
|
getRunCommandSpawnOptions,
|
|
1972
|
+
hasPromptEnhancerApiConfig,
|
|
1934
1973
|
main,
|
|
1935
1974
|
mergeCodexAuthData,
|
|
1936
1975
|
mergeClaudeSettingsWithDefaults,
|
|
1976
|
+
resolvePromptEnhancerMode,
|
|
1937
1977
|
resolveExistingCodexApiConfig,
|
|
1938
1978
|
updateTomlSectionFields
|
|
1939
1979
|
};
|