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/.gitignore +1 -1
- package/README.md +1 -1
- package/lib/cli/logic.mjs +188 -0
- package/lib/cli.mjs +341 -416
- package/package.json +7 -3
- 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,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(
|
|
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
|
-
|
|
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
|
-
|
|
188
|
+
p.log.message(`已备份已有配置: ${pathToLabel(targetPath)} -> ${pathToLabel(backupPath)}`);
|
|
249
189
|
return backupPath;
|
|
250
190
|
}
|
|
251
191
|
|
|
252
|
-
async function backupIfNeeded(targetPath
|
|
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
|
-
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
694
|
+
...(await linkSkillDirectories(claudeDir, agentsDir, previousLinkedTargets))
|
|
770
695
|
];
|
|
771
696
|
}
|
|
772
697
|
|
|
773
|
-
async function linkCodex(agentsDir,
|
|
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,
|
|
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
|
-
(
|
|
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
|
-
|
|
832
|
-
|
|
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
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
|
|
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
|
-
|
|
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
|
|
1138
|
-
|
|
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
|
-
|
|
1141
|
-
|
|
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
|
-
|
|
1144
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
973
|
+
GROK_API_KEY: finalApiKey,
|
|
1154
974
|
GROK_MODEL: model,
|
|
1155
|
-
TAVILY_API_KEY:
|
|
975
|
+
TAVILY_API_KEY: finalTavilyKey,
|
|
1156
976
|
TAVILY_ENABLED: useTavily ? "true" : null
|
|
1157
977
|
});
|
|
1158
978
|
|
|
1159
|
-
|
|
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
|
|
1167
|
-
|
|
1168
|
-
|
|
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:
|
|
995
|
+
CONTEXT7_API_KEY: finalApiKey
|
|
1173
996
|
});
|
|
1174
997
|
|
|
1175
|
-
|
|
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
|
-
|
|
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
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
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
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
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
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
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
|
-
|
|
1216
|
-
|
|
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
|
-
|
|
1048
|
+
PE_API_URL: apiUrl,
|
|
1049
|
+
PE_API_KEY: finalApiKey,
|
|
1050
|
+
PE_MODEL: model,
|
|
1221
1051
|
ANTHROPIC_API_KEY: null,
|
|
1222
|
-
|
|
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
|
-
|
|
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
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
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
|
-
|
|
1299
|
-
|
|
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
|
-
|
|
1302
|
-
|
|
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 =
|
|
1160
|
+
nextSettings.env.ANTHROPIC_AUTH_TOKEN = finalKey;
|
|
1310
1161
|
delete nextSettings.env.ANTHROPIC_API_KEY;
|
|
1311
1162
|
} else {
|
|
1312
|
-
nextSettings.env.ANTHROPIC_API_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,
|
|
1174
|
+
ensureApprovedClaudeApiKey(metaConfig, finalKey);
|
|
1324
1175
|
await writeJsonFileWithBackup(claudeMetaConfigPath, metaConfig);
|
|
1325
1176
|
|
|
1326
|
-
|
|
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
|
|
1817
|
-
|
|
1667
|
+
const baseUrl = await p.text({
|
|
1668
|
+
message: "Codex Base URL",
|
|
1669
|
+
defaultValue: existing.baseUrl,
|
|
1670
|
+
validate: required()
|
|
1818
1671
|
});
|
|
1819
|
-
|
|
1820
|
-
|
|
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
|
-
|
|
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,
|
|
1702
|
+
const auth = mergeCodexAuthData(await readJsonFileSafe(codexAuthPath, {}), envKey, finalApiKey, existing.legacyEnvKeys || []);
|
|
1841
1703
|
await writeJsonFileWithBackup(codexAuthPath, auth);
|
|
1842
1704
|
|
|
1843
|
-
|
|
1844
|
-
|
|
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
|
-
|
|
1709
|
+
p.log.step(`已部署 ${deployed.length} 个 Codex subagents 到 ${pathToLabel(join(home, ".codex", "agents"))}`);
|
|
1848
1710
|
} else {
|
|
1849
|
-
|
|
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
|
|
1782
|
+
const shouldUpdate = await confirmOrCancel({
|
|
1783
|
+
message: `${toolConfig.label} 已检测到,是否继续执行 npm 强制安装/更新?`,
|
|
1784
|
+
initialValue: false
|
|
1785
|
+
});
|
|
1921
1786
|
if (!shouldUpdate) {
|
|
1922
|
-
|
|
1787
|
+
p.log.message(`跳过 ${toolConfig.label} 安装。`);
|
|
1923
1788
|
return;
|
|
1924
1789
|
}
|
|
1925
1790
|
}
|
|
1926
1791
|
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
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
|
|
1810
|
+
if (await confirmOrCancel({ message: "是否安装或更新 Claude Code CLI?", initialValue: false })) {
|
|
1940
1811
|
await installCliTool("claude");
|
|
1941
1812
|
}
|
|
1942
|
-
if (await
|
|
1813
|
+
if (await confirmOrCancel({ message: "是否配置 Claude Code 第三方 API?", initialValue: commandExists("claude") })) {
|
|
1943
1814
|
await configureClaudeApi();
|
|
1944
1815
|
}
|
|
1945
|
-
if (await
|
|
1816
|
+
if (await confirmOrCancel({ message: "是否安装或更新 Codex CLI?", initialValue: false })) {
|
|
1946
1817
|
await installCliTool("codex");
|
|
1947
1818
|
}
|
|
1948
|
-
if (await
|
|
1819
|
+
if (await confirmOrCancel({ message: "是否配置 Codex 第三方 API?", initialValue: commandExists("codex") })) {
|
|
1949
1820
|
await configureCodexApi();
|
|
1950
1821
|
}
|
|
1951
|
-
if (await
|
|
1822
|
+
if (await confirmOrCancel({ message: "是否填写 grok-search 环境变量?", initialValue: false })) {
|
|
1952
1823
|
await configureGrokSearchEnv(options.agentsDir);
|
|
1953
1824
|
}
|
|
1954
|
-
if (await
|
|
1825
|
+
if (await confirmOrCancel({ message: "是否填写 context7-auto-research 环境变量?", initialValue: false })) {
|
|
1955
1826
|
await configureContext7Env(options.agentsDir);
|
|
1956
1827
|
}
|
|
1957
|
-
if (await
|
|
1828
|
+
if (await confirmOrCancel({ message: "是否填写 prompt-enhancer 环境变量?", initialValue: false })) {
|
|
1958
1829
|
await configurePromptEnhancerEnv(options.agentsDir);
|
|
1959
1830
|
}
|
|
1960
1831
|
|
|
1961
|
-
|
|
1832
|
+
p.log.success(c.green("AbelWorkflow 完整初始化完成"));
|
|
1962
1833
|
}
|
|
1963
1834
|
|
|
1964
1835
|
async function runInteractiveMenu(options) {
|
|
1965
|
-
|
|
1966
|
-
|
|
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
|
|
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
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
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
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
1948
|
+
assertInteractiveMenuSupported({
|
|
1949
|
+
command: options.command,
|
|
1950
|
+
inputIsTTY: input.isTTY,
|
|
1951
|
+
outputIsTTY: output.isTTY,
|
|
1952
|
+
nonInteractive: options.nonInteractive
|
|
1953
|
+
});
|
|
2033
1954
|
|
|
2034
|
-
|
|
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
|
-
}
|