abelworkflow 0.6.5 → 0.8.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 +3 -1
- package/AGENTS.md +3 -3
- package/README.md +6 -6
- package/commands/{oc/diagnose.md → abel-diagnose.md} +6 -6
- package/commands/{oc/implementation.md → abel-implement.md} +5 -5
- package/commands/{oc/init.md → abel-init.md} +8 -8
- package/commands/{oc/plan.md → abel-plan.md} +7 -7
- package/commands/{oc/research.md → abel-research.md} +9 -9
- package/lib/cli/logic.mjs +99 -41
- package/lib/cli.mjs +538 -292
- package/package.json +7 -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);
|
|
@@ -30,6 +35,14 @@ const codexTemplateConfigPath = join(codexTemplateRoot, "config-base.toml");
|
|
|
30
35
|
const codexTemplateAgentsPath = join(codexTemplateRoot, "agents");
|
|
31
36
|
const installBackupStamp = Date.now();
|
|
32
37
|
const createdBackupPaths = new Set();
|
|
38
|
+
const augmentContextEnginePermission = "mcp__augment-context-engine";
|
|
39
|
+
const augmentContextEngineRetrievalTool = "mcp__augment-context-engine__codebase-retrieval";
|
|
40
|
+
const augmentContextEngineFeaturePrompt = {
|
|
41
|
+
message: "是否启用 augment-context-engine MCP 代码检索支持?不确定建议选否,可减少 MCP 安装和配置麻烦。",
|
|
42
|
+
initialValue: false
|
|
43
|
+
};
|
|
44
|
+
const localCodebaseRetrievalPolicy = "Use local codebase retrieval with `rg`, `rg --files`, `git grep`, and direct file reads. Do not require augment-context-engine MCP.";
|
|
45
|
+
const augmentCodebaseRetrievalPolicy = `Use \`${augmentContextEngineRetrievalTool}\` as the primary codebase search tool.`;
|
|
33
46
|
const claudeModelEnvKeys = [
|
|
34
47
|
"ANTHROPIC_MODEL",
|
|
35
48
|
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
@@ -69,8 +82,7 @@ const defaultClaudeSettings = {
|
|
|
69
82
|
"WebSearch",
|
|
70
83
|
"TodoWrite",
|
|
71
84
|
"NotebookRead",
|
|
72
|
-
"NotebookEdit"
|
|
73
|
-
"mcp__augment-context-engine"
|
|
85
|
+
"NotebookEdit"
|
|
74
86
|
],
|
|
75
87
|
deny: []
|
|
76
88
|
},
|
|
@@ -98,20 +110,85 @@ const ignoredSkillPathPatterns = [
|
|
|
98
110
|
/^dev-browser\/tmp(\/|$)/
|
|
99
111
|
];
|
|
100
112
|
|
|
101
|
-
function
|
|
102
|
-
|
|
113
|
+
function buildDefaultClaudeSettings({ augmentContextEngine = false } = {}) {
|
|
114
|
+
const settings = {
|
|
115
|
+
...defaultClaudeSettings,
|
|
116
|
+
env: { ...defaultClaudeSettings.env },
|
|
117
|
+
permissions: {
|
|
118
|
+
...defaultClaudeSettings.permissions,
|
|
119
|
+
allow: [...defaultClaudeSettings.permissions.allow],
|
|
120
|
+
deny: [...defaultClaudeSettings.permissions.deny]
|
|
121
|
+
},
|
|
122
|
+
hooks: { ...defaultClaudeSettings.hooks }
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
if (augmentContextEngine && !settings.permissions.allow.includes(augmentContextEnginePermission)) {
|
|
126
|
+
settings.permissions.allow.push(augmentContextEnginePermission);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return settings;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function getAugmentContextEnginePromptOptions() {
|
|
133
|
+
return { ...augmentContextEngineFeaturePrompt };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function resolveAugmentContextEngineFeature(options = {}, previousMetadata = {}) {
|
|
137
|
+
if (typeof options.augmentContextEngine === "boolean") {
|
|
138
|
+
return options.augmentContextEngine;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (typeof previousMetadata?.features?.augmentContextEngine === "boolean") {
|
|
142
|
+
return previousMetadata.features.augmentContextEngine;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function getWorkflowRenderValues(augmentContextEngine) {
|
|
149
|
+
return {
|
|
150
|
+
CODEBASE_RETRIEVAL_POLICY: augmentContextEngine
|
|
151
|
+
? augmentCodebaseRetrievalPolicy
|
|
152
|
+
: localCodebaseRetrievalPolicy,
|
|
153
|
+
AUGMENT_CONTEXT_ENGINE_VALIDATION: augmentContextEngine
|
|
154
|
+
? `Verify MCP availability:\n - \`${augmentContextEngineRetrievalTool}\``
|
|
155
|
+
: "Skip augment-context-engine MCP validation; use local retrieval tools.",
|
|
156
|
+
CODEBASE_RETRIEVAL_MANDATORY_RULE: augmentContextEngine
|
|
157
|
+
? `Mandatory use of \`${augmentContextEngineRetrievalTool}\``
|
|
158
|
+
: "Mandatory use of configured codebase retrieval policy.",
|
|
159
|
+
CODEBASE_RETRIEVAL_STRUCTURE_REFERENCE: augmentContextEngine
|
|
160
|
+
? `Inspect codebase structure: \`${augmentContextEngineRetrievalTool}\` with \`file list --recursive\`.`
|
|
161
|
+
: "Inspect codebase structure with `rg --files`, `git grep`, and direct file reads.",
|
|
162
|
+
CODEBASE_RETRIEVAL_PATTERN_AUDIT: augmentContextEngine
|
|
163
|
+
? `Use augment-context-engine to validate against existing codebase patterns.\n ${augmentContextEngineRetrievalTool}: "Search for existing implementations similar to change <change_name>. Keywords: [key concepts from proposal]"`
|
|
164
|
+
: "Use `rg`, `rg --files`, `git grep`, and direct file reads to validate against existing codebase patterns."
|
|
165
|
+
};
|
|
166
|
+
}
|
|
103
167
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
168
|
+
function renderManagedWorkflowContent(content, { augmentContextEngine = false } = {}) {
|
|
169
|
+
let nextContent = content;
|
|
170
|
+
for (const [key, value] of Object.entries(getWorkflowRenderValues(augmentContextEngine))) {
|
|
171
|
+
nextContent = nextContent.replaceAll(`{{${key}}}`, value);
|
|
172
|
+
}
|
|
173
|
+
return nextContent;
|
|
174
|
+
}
|
|
111
175
|
|
|
112
|
-
|
|
176
|
+
function printHelp() {
|
|
177
|
+
console.log(`${c.bold("AbelWorkflow")} ${c.cyan("installer")}
|
|
178
|
+
|
|
179
|
+
${c.bold("Usage:")}
|
|
180
|
+
${c.cyan("npx abelworkflow")}
|
|
181
|
+
${c.cyan("npx abelworkflow init")}
|
|
182
|
+
${c.cyan("npx abelworkflow install")}
|
|
183
|
+
${c.cyan("npx abelworkflow install --force")}
|
|
184
|
+
${c.cyan("npx abelworkflow install --link-only")}
|
|
185
|
+
${c.cyan("npx abelworkflow install --agents-dir /custom/path")}
|
|
186
|
+
${c.cyan("npx abelworkflow --non-interactive")}
|
|
187
|
+
|
|
188
|
+
${c.bold("Default behavior:")}
|
|
113
189
|
- npx abelworkflow: open the interactive setup menu.
|
|
114
190
|
- npx abelworkflow install: sync managed files and links explicitly.
|
|
191
|
+
- --non-interactive: auto-execute install (skip interactive menu); auto-enabled in CI.
|
|
115
192
|
`);
|
|
116
193
|
}
|
|
117
194
|
|
|
@@ -178,18 +255,18 @@ async function backupExistingPath(targetPath) {
|
|
|
178
255
|
const backupPath = await createBackupPath(targetPath);
|
|
179
256
|
await cp(targetPath, backupPath, { recursive: true, force: false });
|
|
180
257
|
createdBackupPaths.add(targetPath);
|
|
181
|
-
|
|
258
|
+
p.log.message(`已备份已有配置: ${pathToLabel(targetPath)} -> ${pathToLabel(backupPath)}`);
|
|
182
259
|
return backupPath;
|
|
183
260
|
}
|
|
184
261
|
|
|
185
|
-
async function backupIfNeeded(targetPath
|
|
262
|
+
async function backupIfNeeded(targetPath) {
|
|
186
263
|
if (!(await pathExists(targetPath))) {
|
|
187
264
|
return null;
|
|
188
265
|
}
|
|
189
266
|
|
|
190
267
|
const backupPath = await createBackupPath(targetPath);
|
|
191
268
|
await rename(targetPath, backupPath);
|
|
192
|
-
|
|
269
|
+
p.log.message(`已备份已有配置: ${pathToLabel(targetPath)} -> ${pathToLabel(backupPath)}`);
|
|
193
270
|
return backupPath;
|
|
194
271
|
}
|
|
195
272
|
|
|
@@ -313,6 +390,28 @@ async function writeInstallMetadata(agentsDir, metadata) {
|
|
|
313
390
|
await writeFile(join(agentsDir, installMetadataName), `${JSON.stringify(metadata, null, 2)}\n`, "utf8");
|
|
314
391
|
}
|
|
315
392
|
|
|
393
|
+
async function renderManagedWorkflowFile(path, augmentContextEngine) {
|
|
394
|
+
if (!(await pathIsFile(path))) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const content = await readFile(path, "utf8");
|
|
399
|
+
const rendered = renderManagedWorkflowContent(content, { augmentContextEngine });
|
|
400
|
+
if (rendered !== content) {
|
|
401
|
+
await writeFile(path, rendered, "utf8");
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async function renderManagedWorkflowFiles(agentsDir, augmentContextEngine) {
|
|
406
|
+
await renderManagedWorkflowFile(join(agentsDir, "AGENTS.md"), augmentContextEngine);
|
|
407
|
+
|
|
408
|
+
const commandsDir = join(agentsDir, "commands");
|
|
409
|
+
const commandFiles = await getCommandNames(commandsDir);
|
|
410
|
+
for (const fileName of commandFiles) {
|
|
411
|
+
await renderManagedWorkflowFile(join(commandsDir, fileName), augmentContextEngine);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
316
415
|
async function syncPreservedManagedEntry(sourceRoot, targetRoot, entry, previousManagedChildren) {
|
|
317
416
|
await removeIfNotDirectory(targetRoot);
|
|
318
417
|
await mkdir(targetRoot, { recursive: true });
|
|
@@ -449,7 +548,7 @@ async function createSymlink(targetPath, sourcePath, linkType, kind) {
|
|
|
449
548
|
await symlink(sourcePath, targetPath, linkType);
|
|
450
549
|
}
|
|
451
550
|
|
|
452
|
-
async function ensureManagedLink(targetPath, sourcePath, kind,
|
|
551
|
+
async function ensureManagedLink(targetPath, sourcePath, kind, previousLinkedTargets) {
|
|
453
552
|
await mkdir(dirname(targetPath), { recursive: true });
|
|
454
553
|
const sourceResolved = resolve(sourcePath);
|
|
455
554
|
const sourceExists = await pathTargetExists(sourcePath);
|
|
@@ -487,7 +586,7 @@ async function ensureManagedLink(targetPath, sourcePath, kind, force, previousLi
|
|
|
487
586
|
if (wasPreviouslyManaged) {
|
|
488
587
|
await rm(targetPath, { recursive: true, force: true });
|
|
489
588
|
} else {
|
|
490
|
-
await backupIfNeeded(targetPath
|
|
589
|
+
await backupIfNeeded(targetPath);
|
|
491
590
|
}
|
|
492
591
|
} else if (!sourceExists) {
|
|
493
592
|
return { targetPath, status: "skipped" };
|
|
@@ -525,7 +624,7 @@ function shouldCopyManagedFile(error) {
|
|
|
525
624
|
return ["EPERM", "EACCES", "EXDEV", "EINVAL", "UNKNOWN"].includes(error?.code);
|
|
526
625
|
}
|
|
527
626
|
|
|
528
|
-
async function linkSkillDirectories(baseDir, agentsDir,
|
|
627
|
+
async function linkSkillDirectories(baseDir, agentsDir, previousLinkedTargets) {
|
|
529
628
|
const results = [];
|
|
530
629
|
const skillsRoot = join(agentsDir, "skills");
|
|
531
630
|
const skillNames = (await getDirectoryNames(skillsRoot)).filter((skillName) => skillName !== ".system");
|
|
@@ -536,7 +635,6 @@ async function linkSkillDirectories(baseDir, agentsDir, force, previousLinkedTar
|
|
|
536
635
|
join(baseDir, "skills", skillName),
|
|
537
636
|
join(skillsRoot, skillName),
|
|
538
637
|
"dir",
|
|
539
|
-
force,
|
|
540
638
|
previousLinkedTargets
|
|
541
639
|
)
|
|
542
640
|
);
|
|
@@ -664,19 +762,7 @@ function isWithinManagedRoot(targetPath, managedSourceRoot) {
|
|
|
664
762
|
return relativePath !== ".." && !relativePath.startsWith(`..${isWindows() ? "\\" : "/"}`);
|
|
665
763
|
}
|
|
666
764
|
|
|
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) {
|
|
765
|
+
async function linkClaude(agentsDir, previousLinkedTargets) {
|
|
680
766
|
const claudeDir = join(home, ".claude");
|
|
681
767
|
await mkdir(claudeDir, { recursive: true });
|
|
682
768
|
await removeIfNotDirectory(join(claudeDir, "commands"));
|
|
@@ -689,21 +775,34 @@ async function linkClaude(agentsDir, force, previousLinkedTargets) {
|
|
|
689
775
|
join(claudeDir, "CLAUDE.md"),
|
|
690
776
|
join(agentsDir, "AGENTS.md"),
|
|
691
777
|
"file",
|
|
692
|
-
force,
|
|
693
778
|
previousLinkedTargets
|
|
694
779
|
),
|
|
695
|
-
await
|
|
696
|
-
join(
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
780
|
+
...(await (async () => {
|
|
781
|
+
const commandFiles = await getCommandNames(join(agentsDir, "commands"));
|
|
782
|
+
const r = [];
|
|
783
|
+
r.push(...(await pruneManagedTargets(
|
|
784
|
+
join(claudeDir, "commands"),
|
|
785
|
+
join(agentsDir, "commands"),
|
|
786
|
+
commandFiles,
|
|
787
|
+
previousLinkedTargets
|
|
788
|
+
)));
|
|
789
|
+
for (const fileName of commandFiles) {
|
|
790
|
+
r.push(
|
|
791
|
+
await ensureManagedLink(
|
|
792
|
+
join(claudeDir, "commands", fileName),
|
|
793
|
+
join(agentsDir, "commands", fileName),
|
|
794
|
+
"file",
|
|
795
|
+
previousLinkedTargets
|
|
796
|
+
)
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
return r;
|
|
800
|
+
})()),
|
|
801
|
+
...(await linkSkillDirectories(claudeDir, agentsDir, previousLinkedTargets))
|
|
703
802
|
];
|
|
704
803
|
}
|
|
705
804
|
|
|
706
|
-
async function linkCodex(agentsDir,
|
|
805
|
+
async function linkCodex(agentsDir, previousLinkedTargets) {
|
|
707
806
|
const results = [];
|
|
708
807
|
const codexDir = join(home, ".codex");
|
|
709
808
|
await mkdir(codexDir, { recursive: true });
|
|
@@ -717,17 +816,16 @@ async function linkCodex(agentsDir, force, previousLinkedTargets) {
|
|
|
717
816
|
join(codexDir, "AGENTS.md"),
|
|
718
817
|
join(agentsDir, "AGENTS.md"),
|
|
719
818
|
"file",
|
|
720
|
-
force,
|
|
721
819
|
previousLinkedTargets
|
|
722
820
|
)
|
|
723
821
|
);
|
|
724
|
-
results.push(...(await linkSkillDirectories(codexDir, agentsDir,
|
|
822
|
+
results.push(...(await linkSkillDirectories(codexDir, agentsDir, previousLinkedTargets)));
|
|
725
823
|
|
|
726
|
-
const commandFiles = await getCommandNames(join(agentsDir, "commands"
|
|
824
|
+
const commandFiles = await getCommandNames(join(agentsDir, "commands"));
|
|
727
825
|
results.push(
|
|
728
826
|
...(await pruneManagedTargets(
|
|
729
827
|
join(codexDir, "prompts"),
|
|
730
|
-
join(agentsDir, "commands"
|
|
828
|
+
join(agentsDir, "commands"),
|
|
731
829
|
commandFiles,
|
|
732
830
|
previousLinkedTargets
|
|
733
831
|
))
|
|
@@ -736,9 +834,8 @@ async function linkCodex(agentsDir, force, previousLinkedTargets) {
|
|
|
736
834
|
results.push(
|
|
737
835
|
await ensureManagedLink(
|
|
738
836
|
join(codexDir, "prompts", fileName),
|
|
739
|
-
join(agentsDir, "commands",
|
|
837
|
+
join(agentsDir, "commands", fileName),
|
|
740
838
|
"file",
|
|
741
|
-
force,
|
|
742
839
|
previousLinkedTargets
|
|
743
840
|
)
|
|
744
841
|
);
|
|
@@ -750,19 +847,41 @@ async function linkCodex(agentsDir, force, previousLinkedTargets) {
|
|
|
750
847
|
async function installManagedWorkflow(options) {
|
|
751
848
|
let previousMetadata = {};
|
|
752
849
|
let managedChildren = {};
|
|
850
|
+
let augmentContextEngine = false;
|
|
851
|
+
const s = p.spinner();
|
|
753
852
|
|
|
754
853
|
if (!options.relinkOnly) {
|
|
755
|
-
(
|
|
854
|
+
s.start("正在同步工作流文件...");
|
|
855
|
+
try {
|
|
856
|
+
({ previousMetadata, managedChildren } = await syncManagedFiles(options.agentsDir));
|
|
857
|
+
augmentContextEngine = resolveAugmentContextEngineFeature(options, previousMetadata);
|
|
858
|
+
await renderManagedWorkflowFiles(options.agentsDir, augmentContextEngine);
|
|
859
|
+
} catch (e) {
|
|
860
|
+
s.cancel(c.red(`同步失败: ${e.message}`));
|
|
861
|
+
throw e;
|
|
862
|
+
}
|
|
863
|
+
s.stop("工作流文件已同步");
|
|
756
864
|
} else if (!(await pathExists(options.agentsDir))) {
|
|
757
865
|
throw new Error(`${options.agentsDir} does not exist; remove --link-only or install first`);
|
|
758
866
|
} else {
|
|
759
867
|
previousMetadata = await readInstallMetadata(options.agentsDir);
|
|
760
868
|
managedChildren = previousMetadata.managedChildren ?? {};
|
|
869
|
+
augmentContextEngine = resolveAugmentContextEngineFeature(options, previousMetadata);
|
|
761
870
|
}
|
|
762
871
|
|
|
763
872
|
const previousLinkedTargets = previousMetadata.linkedTargets ?? {};
|
|
764
|
-
|
|
765
|
-
|
|
873
|
+
s.start("正在链接 Claude / Codex...");
|
|
874
|
+
let claudeResults;
|
|
875
|
+
let codexResults;
|
|
876
|
+
try {
|
|
877
|
+
claudeResults = await linkClaude(options.agentsDir, previousLinkedTargets);
|
|
878
|
+
codexResults = await linkCodex(options.agentsDir, previousLinkedTargets);
|
|
879
|
+
} catch (e) {
|
|
880
|
+
s.cancel(c.red(`链接失败: ${e.message}`));
|
|
881
|
+
throw e;
|
|
882
|
+
}
|
|
883
|
+
s.stop("链接完成");
|
|
884
|
+
|
|
766
885
|
const linkedTargets = Object.fromEntries(
|
|
767
886
|
[...claudeResults, ...codexResults]
|
|
768
887
|
.filter((result) => result.sourcePath)
|
|
@@ -776,21 +895,30 @@ async function installManagedWorkflow(options) {
|
|
|
776
895
|
])
|
|
777
896
|
);
|
|
778
897
|
|
|
898
|
+
const managedClaudePermissions = await ensureClaudeSettingsForFeature(augmentContextEngine, previousMetadata);
|
|
899
|
+
|
|
779
900
|
await writeInstallMetadata(options.agentsDir, {
|
|
780
901
|
package: "abelworkflow",
|
|
781
902
|
installedAt: new Date().toISOString(),
|
|
903
|
+
features: {
|
|
904
|
+
...(previousMetadata.features && typeof previousMetadata.features === "object" ? previousMetadata.features : {}),
|
|
905
|
+
augmentContextEngine
|
|
906
|
+
},
|
|
782
907
|
managedChildren,
|
|
908
|
+
managedClaudePermissions,
|
|
783
909
|
linkedTargets
|
|
784
910
|
});
|
|
785
911
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
|
|
912
|
+
const resultLines = [...claudeResults, ...codexResults].map((result) => {
|
|
913
|
+
const icon = result.status === "unchanged" ? c.gray("=")
|
|
914
|
+
: result.status === "removed" ? c.yellow("−")
|
|
915
|
+
: c.green("+");
|
|
916
|
+
return `${icon} ${pathToLabel(result.targetPath)}`;
|
|
917
|
+
}).join("\n");
|
|
918
|
+
|
|
919
|
+
p.note(resultLines, "链接结果");
|
|
920
|
+
p.log.step(`工作流目录: ${c.cyan(pathToLabel(options.agentsDir))}`);
|
|
921
|
+
p.log.message(`完成后可运行 ${c.cyan("npx abelworkflow@latest")} 更新托管文件`);
|
|
794
922
|
}
|
|
795
923
|
|
|
796
924
|
async function readJsonFileSafe(path, fallback = {}) {
|
|
@@ -882,121 +1010,6 @@ async function updateDotenvFile(path, updates) {
|
|
|
882
1010
|
await writeFile(path, renderDotenv(current), "utf8");
|
|
883
1011
|
}
|
|
884
1012
|
|
|
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
1013
|
function commandExists(command) {
|
|
1001
1014
|
const checker = isWindows() ? "where" : "which";
|
|
1002
1015
|
const result = spawnSync(checker, [command], { stdio: "ignore" });
|
|
@@ -1017,22 +1030,12 @@ async function runCommand(command, args) {
|
|
|
1017
1030
|
});
|
|
1018
1031
|
}
|
|
1019
1032
|
|
|
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
1033
|
async function ensureWorkflowPresent(agentsDir) {
|
|
1031
1034
|
if (await pathExists(join(agentsDir, "AGENTS.md"))) {
|
|
1032
1035
|
return;
|
|
1033
1036
|
}
|
|
1034
1037
|
|
|
1035
|
-
|
|
1038
|
+
p.log.message("未检测到已安装的 AbelWorkflow,先执行一次工作流同步。");
|
|
1036
1039
|
await installManagedWorkflow({
|
|
1037
1040
|
agentsDir,
|
|
1038
1041
|
force: false,
|
|
@@ -1044,123 +1047,239 @@ async function configureGrokSearchEnv(agentsDir) {
|
|
|
1044
1047
|
await ensureWorkflowPresent(agentsDir);
|
|
1045
1048
|
const envPath = join(agentsDir, "skills", "grok-search", ".env");
|
|
1046
1049
|
const existing = await readDotenvFile(envPath);
|
|
1047
|
-
const baseUrl = await
|
|
1048
|
-
|
|
1050
|
+
const baseUrl = await p.text({
|
|
1051
|
+
message: "Grok API URL",
|
|
1052
|
+
defaultValue: existing.GROK_API_URL || "https://api.x.ai/v1",
|
|
1053
|
+
validate: required()
|
|
1049
1054
|
});
|
|
1050
|
-
|
|
1051
|
-
|
|
1055
|
+
assertNotCancelled(baseUrl);
|
|
1056
|
+
|
|
1057
|
+
const apiKey = await p.password({
|
|
1058
|
+
message: "Grok API Key(输入 - 清除)",
|
|
1059
|
+
mask: "*",
|
|
1060
|
+
defaultValue: existing.GROK_API_KEY || undefined,
|
|
1061
|
+
validate: requiredUnlessExisting(existing.GROK_API_KEY, "Grok API Key 不能为空")
|
|
1052
1062
|
});
|
|
1053
|
-
|
|
1054
|
-
|
|
1063
|
+
assertNotCancelled(apiKey);
|
|
1064
|
+
const finalApiKey = resolvePasswordValue(apiKey, existing.GROK_API_KEY);
|
|
1065
|
+
|
|
1066
|
+
const model = await p.text({
|
|
1067
|
+
message: "Grok 默认模型",
|
|
1068
|
+
defaultValue: existing.GROK_MODEL || "grok-4-fast",
|
|
1069
|
+
validate: required()
|
|
1070
|
+
});
|
|
1071
|
+
assertNotCancelled(model);
|
|
1072
|
+
|
|
1073
|
+
const useTavily = await confirmOrCancel({
|
|
1074
|
+
message: "是否同时配置 Tavily 作为额外搜索源?",
|
|
1075
|
+
initialValue: Boolean(existing.TAVILY_API_KEY)
|
|
1055
1076
|
});
|
|
1056
|
-
|
|
1077
|
+
|
|
1057
1078
|
const tavilyKey = useTavily
|
|
1058
|
-
? await
|
|
1079
|
+
? await p.password({
|
|
1080
|
+
message: "Tavily API Key(输入 - 清除)",
|
|
1081
|
+
mask: "*",
|
|
1082
|
+
defaultValue: existing.TAVILY_API_KEY || undefined,
|
|
1083
|
+
validate: requiredUnlessExisting(existing.TAVILY_API_KEY, "Tavily API Key 不能为空")
|
|
1084
|
+
})
|
|
1059
1085
|
: "";
|
|
1086
|
+
if (useTavily) assertNotCancelled(tavilyKey);
|
|
1087
|
+
const finalTavilyKey = useTavily ? resolvePasswordValue(tavilyKey, existing.TAVILY_API_KEY) : null;
|
|
1060
1088
|
|
|
1061
1089
|
await updateDotenvFile(envPath, {
|
|
1062
1090
|
GROK_API_URL: baseUrl,
|
|
1063
|
-
GROK_API_KEY:
|
|
1091
|
+
GROK_API_KEY: finalApiKey,
|
|
1064
1092
|
GROK_MODEL: model,
|
|
1065
|
-
TAVILY_API_KEY:
|
|
1093
|
+
TAVILY_API_KEY: finalTavilyKey,
|
|
1066
1094
|
TAVILY_ENABLED: useTavily ? "true" : null
|
|
1067
1095
|
});
|
|
1068
1096
|
|
|
1069
|
-
|
|
1097
|
+
p.log.step(`已写入 ${pathToLabel(envPath)}`);
|
|
1070
1098
|
}
|
|
1071
1099
|
|
|
1072
1100
|
async function configureContext7Env(agentsDir) {
|
|
1073
1101
|
await ensureWorkflowPresent(agentsDir);
|
|
1074
1102
|
const envPath = join(agentsDir, "skills", "context7-auto-research", ".env");
|
|
1075
1103
|
const existing = await readDotenvFile(envPath);
|
|
1076
|
-
const apiKey = await
|
|
1077
|
-
|
|
1078
|
-
|
|
1104
|
+
const apiKey = await p.password({
|
|
1105
|
+
message: "Context7 API Key (可选,输入 - 清除)",
|
|
1106
|
+
mask: "*",
|
|
1107
|
+
defaultValue: existing.CONTEXT7_API_KEY || undefined
|
|
1079
1108
|
});
|
|
1109
|
+
assertNotCancelled(apiKey);
|
|
1110
|
+
const finalApiKey = resolvePasswordValue(apiKey, existing.CONTEXT7_API_KEY);
|
|
1080
1111
|
|
|
1081
1112
|
await updateDotenvFile(envPath, {
|
|
1082
|
-
CONTEXT7_API_KEY:
|
|
1113
|
+
CONTEXT7_API_KEY: finalApiKey
|
|
1083
1114
|
});
|
|
1084
1115
|
|
|
1085
|
-
|
|
1116
|
+
p.log.step(`已写入 ${pathToLabel(envPath)}`);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
function hasPromptEnhancerApiConfig(config) {
|
|
1120
|
+
return [config.PE_API_URL, config.PE_API_KEY, config.PE_MODEL]
|
|
1121
|
+
.every((value) => typeof value === "string" && value.trim() !== "");
|
|
1086
1122
|
}
|
|
1087
1123
|
|
|
1088
1124
|
function resolvePromptEnhancerMode(existing) {
|
|
1089
|
-
|
|
1090
|
-
return "anthropic";
|
|
1091
|
-
}
|
|
1092
|
-
if (existing.OPENAI_API_KEY) {
|
|
1093
|
-
return "openai";
|
|
1094
|
-
}
|
|
1095
|
-
return "local";
|
|
1125
|
+
return hasPromptEnhancerApiConfig(existing) ? "openai-compatible" : "agent";
|
|
1096
1126
|
}
|
|
1097
1127
|
|
|
1098
1128
|
async function configurePromptEnhancerEnv(agentsDir) {
|
|
1099
1129
|
await ensureWorkflowPresent(agentsDir);
|
|
1100
1130
|
const envPath = join(agentsDir, "skills", "prompt-enhancer", ".env");
|
|
1101
1131
|
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
|
-
});
|
|
1132
|
+
const mode = await selectOrCancel({
|
|
1133
|
+
message: "请选择 prompt-enhancer 的运行方式",
|
|
1134
|
+
options: [
|
|
1135
|
+
{ value: "openai-compatible", label: "第三方 OpenAI 兼容接口" },
|
|
1136
|
+
{ value: "agent", label: "直接使用当前 Agent" }
|
|
1137
|
+
],
|
|
1138
|
+
initialValue: resolvePromptEnhancerMode(existing)
|
|
1139
|
+
});
|
|
1115
1140
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1141
|
+
if (mode === "openai-compatible") {
|
|
1142
|
+
const apiUrl = await p.text({
|
|
1143
|
+
message: "PE_API_URL",
|
|
1144
|
+
defaultValue: existing.PE_API_URL || undefined,
|
|
1145
|
+
validate: required()
|
|
1120
1146
|
});
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1147
|
+
assertNotCancelled(apiUrl);
|
|
1148
|
+
|
|
1149
|
+
const apiKey = await p.password({
|
|
1150
|
+
message: "PE_API_KEY(输入 - 清除)",
|
|
1151
|
+
mask: "*",
|
|
1152
|
+
defaultValue: existing.PE_API_KEY || undefined,
|
|
1153
|
+
validate: requiredUnlessExisting(existing.PE_API_KEY, "PE_API_KEY 不能为空")
|
|
1124
1154
|
});
|
|
1125
|
-
|
|
1126
|
-
|
|
1155
|
+
assertNotCancelled(apiKey);
|
|
1156
|
+
const finalApiKey = resolvePasswordValue(apiKey, existing.PE_API_KEY);
|
|
1157
|
+
|
|
1158
|
+
const model = await p.text({
|
|
1159
|
+
message: "PE_MODEL",
|
|
1160
|
+
defaultValue: existing.PE_MODEL || undefined,
|
|
1161
|
+
validate: required()
|
|
1127
1162
|
});
|
|
1163
|
+
assertNotCancelled(model);
|
|
1128
1164
|
|
|
1129
1165
|
await updateDotenvFile(envPath, {
|
|
1130
|
-
|
|
1166
|
+
PE_API_URL: apiUrl,
|
|
1167
|
+
PE_API_KEY: finalApiKey,
|
|
1168
|
+
PE_MODEL: model,
|
|
1131
1169
|
ANTHROPIC_API_KEY: null,
|
|
1132
|
-
|
|
1170
|
+
OPENAI_API_KEY: null
|
|
1133
1171
|
});
|
|
1134
1172
|
} else {
|
|
1135
1173
|
await updateDotenvFile(envPath, {
|
|
1174
|
+
PE_API_URL: null,
|
|
1175
|
+
PE_API_KEY: null,
|
|
1176
|
+
PE_MODEL: null,
|
|
1136
1177
|
ANTHROPIC_API_KEY: null,
|
|
1137
1178
|
OPENAI_API_KEY: null
|
|
1138
1179
|
});
|
|
1139
1180
|
}
|
|
1140
1181
|
|
|
1141
|
-
|
|
1182
|
+
p.log.step(`已写入 ${pathToLabel(envPath)}`);
|
|
1142
1183
|
}
|
|
1143
1184
|
|
|
1144
|
-
function mergeClaudeSettingsWithDefaults(settings) {
|
|
1185
|
+
function mergeClaudeSettingsWithDefaults(settings, { augmentContextEngine = false } = {}) {
|
|
1186
|
+
const defaults = buildDefaultClaudeSettings({ augmentContextEngine });
|
|
1145
1187
|
const env = settings?.env && typeof settings.env === "object" ? settings.env : {};
|
|
1146
1188
|
const permissions = settings?.permissions && typeof settings.permissions === "object" ? settings.permissions : {};
|
|
1147
1189
|
return {
|
|
1148
|
-
...
|
|
1190
|
+
...defaults,
|
|
1149
1191
|
...settings,
|
|
1150
1192
|
env: {
|
|
1151
|
-
...
|
|
1193
|
+
...defaults.env,
|
|
1152
1194
|
...env
|
|
1153
1195
|
},
|
|
1154
1196
|
permissions: {
|
|
1155
|
-
...
|
|
1197
|
+
...defaults.permissions,
|
|
1156
1198
|
...permissions,
|
|
1157
|
-
allow: Array.isArray(permissions.allow) ? permissions.allow :
|
|
1158
|
-
deny: Array.isArray(permissions.deny) ? permissions.deny :
|
|
1199
|
+
allow: Array.isArray(permissions.allow) ? permissions.allow : defaults.permissions.allow,
|
|
1200
|
+
deny: Array.isArray(permissions.deny) ? permissions.deny : defaults.permissions.deny
|
|
1159
1201
|
},
|
|
1160
|
-
hooks: settings?.hooks && typeof settings.hooks === "object" ? settings.hooks :
|
|
1202
|
+
hooks: settings?.hooks && typeof settings.hooks === "object" ? settings.hooks : defaults.hooks
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function getPreviousManagedClaudePermissions(previousMetadata = {}) {
|
|
1207
|
+
return Array.isArray(previousMetadata.managedClaudePermissions)
|
|
1208
|
+
? previousMetadata.managedClaudePermissions.filter((value) => typeof value === "string")
|
|
1209
|
+
: [];
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
function applyClaudePermissionFeature(settings, {
|
|
1213
|
+
augmentContextEngine = false,
|
|
1214
|
+
previousManagedPermissions = []
|
|
1215
|
+
} = {}) {
|
|
1216
|
+
const hasSettings = settings && typeof settings === "object";
|
|
1217
|
+
const wasManaged = previousManagedPermissions.includes(augmentContextEnginePermission);
|
|
1218
|
+
if (!hasSettings && !augmentContextEngine) {
|
|
1219
|
+
return { settings, changed: false, managedPermissions: [] };
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
const defaults = buildDefaultClaudeSettings({ augmentContextEngine: false });
|
|
1223
|
+
const nextSettings = hasSettings
|
|
1224
|
+
? {
|
|
1225
|
+
...settings,
|
|
1226
|
+
permissions: settings.permissions && typeof settings.permissions === "object"
|
|
1227
|
+
? { ...settings.permissions }
|
|
1228
|
+
: {}
|
|
1229
|
+
}
|
|
1230
|
+
: defaults;
|
|
1231
|
+
const permissions = nextSettings.permissions;
|
|
1232
|
+
const allow = Array.isArray(permissions.allow)
|
|
1233
|
+
? [...permissions.allow]
|
|
1234
|
+
: [...defaults.permissions.allow];
|
|
1235
|
+
let changed = !hasSettings;
|
|
1236
|
+
let isManaged = wasManaged;
|
|
1237
|
+
|
|
1238
|
+
if (augmentContextEngine) {
|
|
1239
|
+
if (!allow.includes(augmentContextEnginePermission)) {
|
|
1240
|
+
allow.push(augmentContextEnginePermission);
|
|
1241
|
+
changed = true;
|
|
1242
|
+
isManaged = true;
|
|
1243
|
+
}
|
|
1244
|
+
} else if (wasManaged && allow.includes(augmentContextEnginePermission)) {
|
|
1245
|
+
allow.splice(allow.indexOf(augmentContextEnginePermission), 1);
|
|
1246
|
+
changed = true;
|
|
1247
|
+
isManaged = false;
|
|
1248
|
+
} else {
|
|
1249
|
+
isManaged = false;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
permissions.allow = allow;
|
|
1253
|
+
if (!Array.isArray(permissions.deny)) {
|
|
1254
|
+
permissions.deny = [...defaults.permissions.deny];
|
|
1255
|
+
}
|
|
1256
|
+
nextSettings.permissions = permissions;
|
|
1257
|
+
|
|
1258
|
+
return {
|
|
1259
|
+
settings: nextSettings,
|
|
1260
|
+
changed,
|
|
1261
|
+
managedPermissions: isManaged ? [augmentContextEnginePermission] : []
|
|
1161
1262
|
};
|
|
1162
1263
|
}
|
|
1163
1264
|
|
|
1265
|
+
async function ensureClaudeSettingsForFeature(augmentContextEngine, previousMetadata) {
|
|
1266
|
+
const settingsExists = await pathExists(claudeSettingsPath);
|
|
1267
|
+
const settings = settingsExists ? await readJsonFileSafe(claudeSettingsPath, {}) : undefined;
|
|
1268
|
+
const result = applyClaudePermissionFeature(settings, {
|
|
1269
|
+
augmentContextEngine,
|
|
1270
|
+
previousManagedPermissions: getPreviousManagedClaudePermissions(previousMetadata)
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
if (result.changed && result.settings) {
|
|
1274
|
+
if (settingsExists) {
|
|
1275
|
+
await backupExistingPath(claudeSettingsPath);
|
|
1276
|
+
}
|
|
1277
|
+
await writeJsonFileSafe(claudeSettingsPath, result.settings);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
return result.managedPermissions;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1164
1283
|
function getExistingClaudeApiConfig(settings) {
|
|
1165
1284
|
const env = mergeClaudeSettingsWithDefaults(settings).env;
|
|
1166
1285
|
return {
|
|
@@ -1198,28 +1317,46 @@ function ensureApprovedClaudeApiKey(config, apiKey) {
|
|
|
1198
1317
|
async function configureClaudeApi() {
|
|
1199
1318
|
const settings = await readJsonFileSafe(claudeSettingsPath, {});
|
|
1200
1319
|
const existing = getExistingClaudeApiConfig(settings);
|
|
1201
|
-
const authType = await
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1320
|
+
const authType = await selectOrCancel({
|
|
1321
|
+
message: "Claude Code 第三方 API 认证方式",
|
|
1322
|
+
options: [
|
|
1323
|
+
{ value: "api_key", label: "API Key" },
|
|
1324
|
+
{ value: "auth_token", label: "Auth Token" }
|
|
1325
|
+
],
|
|
1326
|
+
initialValue: existing.authType
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1329
|
+
const baseUrl = await p.text({
|
|
1330
|
+
message: "Claude Code Base URL",
|
|
1331
|
+
defaultValue: existing.baseUrl,
|
|
1332
|
+
validate: required()
|
|
1207
1333
|
});
|
|
1208
|
-
|
|
1209
|
-
|
|
1334
|
+
assertNotCancelled(baseUrl);
|
|
1335
|
+
|
|
1336
|
+
const key = await p.password({
|
|
1337
|
+
message: authType === "auth_token" ? "Claude Code Auth Token(输入 - 清除)" : "Claude Code API Key(输入 - 清除)",
|
|
1338
|
+
mask: "*",
|
|
1339
|
+
defaultValue: existing.key || undefined,
|
|
1340
|
+
validate: requiredUnlessExisting(existing.key, "API Key / Auth Token 不能为空")
|
|
1210
1341
|
});
|
|
1211
|
-
|
|
1212
|
-
|
|
1342
|
+
assertNotCancelled(key);
|
|
1343
|
+
const finalKey = resolvePasswordValue(key, existing.key);
|
|
1344
|
+
|
|
1345
|
+
const model = await p.text({
|
|
1346
|
+
message: "Claude Code 模型",
|
|
1347
|
+
defaultValue: existing.model || undefined,
|
|
1348
|
+
validate: required()
|
|
1213
1349
|
});
|
|
1350
|
+
assertNotCancelled(model);
|
|
1214
1351
|
|
|
1215
1352
|
const nextSettings = mergeClaudeSettingsWithDefaults(settings);
|
|
1216
1353
|
nextSettings.env.ANTHROPIC_BASE_URL = baseUrl;
|
|
1217
1354
|
|
|
1218
1355
|
if (authType === "auth_token") {
|
|
1219
|
-
nextSettings.env.ANTHROPIC_AUTH_TOKEN =
|
|
1356
|
+
nextSettings.env.ANTHROPIC_AUTH_TOKEN = finalKey;
|
|
1220
1357
|
delete nextSettings.env.ANTHROPIC_API_KEY;
|
|
1221
1358
|
} else {
|
|
1222
|
-
nextSettings.env.ANTHROPIC_API_KEY =
|
|
1359
|
+
nextSettings.env.ANTHROPIC_API_KEY = finalKey;
|
|
1223
1360
|
delete nextSettings.env.ANTHROPIC_AUTH_TOKEN;
|
|
1224
1361
|
}
|
|
1225
1362
|
for (const field of claudeModelEnvKeys) {
|
|
@@ -1230,10 +1367,10 @@ async function configureClaudeApi() {
|
|
|
1230
1367
|
|
|
1231
1368
|
const metaConfig = await readJsonFileSafe(claudeMetaConfigPath, {});
|
|
1232
1369
|
metaConfig.hasCompletedOnboarding = true;
|
|
1233
|
-
ensureApprovedClaudeApiKey(metaConfig,
|
|
1370
|
+
ensureApprovedClaudeApiKey(metaConfig, finalKey);
|
|
1234
1371
|
await writeJsonFileWithBackup(claudeMetaConfigPath, metaConfig);
|
|
1235
1372
|
|
|
1236
|
-
|
|
1373
|
+
p.log.step(`已更新 ${pathToLabel(claudeSettingsPath)} (${authType}, ${baseUrl}, ${maskSecret(finalKey)})`);
|
|
1237
1374
|
}
|
|
1238
1375
|
|
|
1239
1376
|
function updateTopLevelTomlField(content, field, value) {
|
|
@@ -1723,13 +1860,24 @@ async function configureCodexApi() {
|
|
|
1723
1860
|
const existing = await getExistingCodexApiConfig();
|
|
1724
1861
|
const providerId = existing.providerId || "abelworkflow";
|
|
1725
1862
|
const providerName = existing.providerName || providerId;
|
|
1726
|
-
const baseUrl = await
|
|
1727
|
-
|
|
1863
|
+
const baseUrl = await p.text({
|
|
1864
|
+
message: "Codex Base URL",
|
|
1865
|
+
defaultValue: existing.baseUrl,
|
|
1866
|
+
validate: required()
|
|
1728
1867
|
});
|
|
1729
|
-
|
|
1730
|
-
|
|
1868
|
+
assertNotCancelled(baseUrl);
|
|
1869
|
+
|
|
1870
|
+
const apiKey = await p.password({
|
|
1871
|
+
message: "Codex 第三方 API Key(输入 - 清除)",
|
|
1872
|
+
mask: "*",
|
|
1873
|
+
defaultValue: existing.apiKey || undefined,
|
|
1874
|
+
validate: requiredUnlessExisting(existing.apiKey, "API Key 不能为空")
|
|
1731
1875
|
});
|
|
1732
|
-
|
|
1876
|
+
assertNotCancelled(apiKey);
|
|
1877
|
+
const finalApiKey = resolvePasswordValue(apiKey, existing.apiKey);
|
|
1878
|
+
|
|
1879
|
+
const shouldDeploySubagents = await confirmOrCancel({ message: "是否部署 Codex subagents 配置?", initialValue: true });
|
|
1880
|
+
|
|
1733
1881
|
const envKey = existing.envKey || "OPENAI_API_KEY";
|
|
1734
1882
|
const currentContent = await pathExists(codexConfigPath) ? await readFile(codexConfigPath, "utf8") : "";
|
|
1735
1883
|
const templateContent = await loadBundledCodexConfigTemplate();
|
|
@@ -1747,16 +1895,16 @@ async function configureCodexApi() {
|
|
|
1747
1895
|
await mkdir(dirname(codexConfigPath), { recursive: true });
|
|
1748
1896
|
await writeFile(codexConfigPath, content, "utf8");
|
|
1749
1897
|
|
|
1750
|
-
const auth = mergeCodexAuthData(await readJsonFileSafe(codexAuthPath, {}), envKey,
|
|
1898
|
+
const auth = mergeCodexAuthData(await readJsonFileSafe(codexAuthPath, {}), envKey, finalApiKey, existing.legacyEnvKeys || []);
|
|
1751
1899
|
await writeJsonFileWithBackup(codexAuthPath, auth);
|
|
1752
1900
|
|
|
1753
|
-
|
|
1754
|
-
|
|
1901
|
+
p.log.step(`已更新 ${pathToLabel(codexConfigPath)} (${providerId}, ${baseUrl})`);
|
|
1902
|
+
p.log.step(`已更新 ${pathToLabel(codexAuthPath)} (${maskSecret(finalApiKey)})`);
|
|
1755
1903
|
if (shouldDeploySubagents) {
|
|
1756
1904
|
const deployed = await deployBundledCodexAgents();
|
|
1757
|
-
|
|
1905
|
+
p.log.step(`已部署 ${deployed.length} 个 Codex subagents 到 ${pathToLabel(join(home, ".codex", "agents"))}`);
|
|
1758
1906
|
} else {
|
|
1759
|
-
|
|
1907
|
+
p.log.message("已跳过 Codex subagents 部署。");
|
|
1760
1908
|
}
|
|
1761
1909
|
}
|
|
1762
1910
|
|
|
@@ -1827,55 +1975,68 @@ async function installCliTool(tool) {
|
|
|
1827
1975
|
|
|
1828
1976
|
const installed = commandExists(toolConfig.command);
|
|
1829
1977
|
if (installed) {
|
|
1830
|
-
const shouldUpdate = await
|
|
1978
|
+
const shouldUpdate = await confirmOrCancel({
|
|
1979
|
+
message: `${toolConfig.label} 已检测到,是否继续执行 npm 强制安装/更新?`,
|
|
1980
|
+
initialValue: false
|
|
1981
|
+
});
|
|
1831
1982
|
if (!shouldUpdate) {
|
|
1832
|
-
|
|
1983
|
+
p.log.message(`跳过 ${toolConfig.label} 安装。`);
|
|
1833
1984
|
return;
|
|
1834
1985
|
}
|
|
1835
1986
|
}
|
|
1836
1987
|
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1988
|
+
const s = p.spinner();
|
|
1989
|
+
s.start(`正在安装 ${toolConfig.label}...`);
|
|
1990
|
+
try {
|
|
1991
|
+
await runCommand("npm", ["install", "-g", toolConfig.packageName, "--force"]);
|
|
1992
|
+
s.stop(`${toolConfig.label} 安装完成`);
|
|
1993
|
+
} catch (e) {
|
|
1994
|
+
s.cancel(c.red(`${toolConfig.label} 安装失败: ${e.message}`));
|
|
1995
|
+
throw e;
|
|
1996
|
+
}
|
|
1840
1997
|
}
|
|
1841
1998
|
|
|
1842
1999
|
async function runFullInit(options) {
|
|
2000
|
+
const augmentContextEngine = options.nonInteractive
|
|
2001
|
+
? false
|
|
2002
|
+
: await confirmOrCancel(getAugmentContextEnginePromptOptions());
|
|
2003
|
+
|
|
1843
2004
|
await installManagedWorkflow({
|
|
1844
2005
|
agentsDir: options.agentsDir,
|
|
1845
2006
|
force: options.force,
|
|
1846
|
-
relinkOnly: false
|
|
2007
|
+
relinkOnly: false,
|
|
2008
|
+
augmentContextEngine
|
|
1847
2009
|
});
|
|
1848
2010
|
|
|
1849
|
-
if (await
|
|
2011
|
+
if (await confirmOrCancel({ message: "是否安装或更新 Claude Code CLI?", initialValue: false })) {
|
|
1850
2012
|
await installCliTool("claude");
|
|
1851
2013
|
}
|
|
1852
|
-
if (await
|
|
2014
|
+
if (await confirmOrCancel({ message: "是否配置 Claude Code 第三方 API?", initialValue: commandExists("claude") })) {
|
|
1853
2015
|
await configureClaudeApi();
|
|
1854
2016
|
}
|
|
1855
|
-
if (await
|
|
2017
|
+
if (await confirmOrCancel({ message: "是否安装或更新 Codex CLI?", initialValue: false })) {
|
|
1856
2018
|
await installCliTool("codex");
|
|
1857
2019
|
}
|
|
1858
|
-
if (await
|
|
2020
|
+
if (await confirmOrCancel({ message: "是否配置 Codex 第三方 API?", initialValue: commandExists("codex") })) {
|
|
1859
2021
|
await configureCodexApi();
|
|
1860
2022
|
}
|
|
1861
|
-
if (await
|
|
2023
|
+
if (await confirmOrCancel({ message: "是否填写 grok-search 环境变量?", initialValue: false })) {
|
|
1862
2024
|
await configureGrokSearchEnv(options.agentsDir);
|
|
1863
2025
|
}
|
|
1864
|
-
if (await
|
|
2026
|
+
if (await confirmOrCancel({ message: "是否填写 context7-auto-research 环境变量?", initialValue: false })) {
|
|
1865
2027
|
await configureContext7Env(options.agentsDir);
|
|
1866
2028
|
}
|
|
1867
|
-
if (await
|
|
2029
|
+
if (await confirmOrCancel({ message: "是否填写 prompt-enhancer 环境变量?", initialValue: false })) {
|
|
1868
2030
|
await configurePromptEnhancerEnv(options.agentsDir);
|
|
1869
2031
|
}
|
|
1870
2032
|
|
|
1871
|
-
|
|
2033
|
+
p.log.success(c.green("AbelWorkflow 完整初始化完成"));
|
|
1872
2034
|
}
|
|
1873
2035
|
|
|
1874
2036
|
async function runInteractiveMenu(options) {
|
|
1875
|
-
|
|
1876
|
-
|
|
2037
|
+
p.intro(c.bold(c.bgCyan(c.black(" AbelWorkflow Setup "))));
|
|
2038
|
+
p.log.message(`工作流目录: ${c.cyan(pathToLabel(options.agentsDir))}`);
|
|
1877
2039
|
|
|
1878
|
-
const menuChoices = interactiveMenuDescriptors.map(({ value, label }) => ({ value, label }));
|
|
1879
2040
|
const menuActions = {
|
|
1880
2041
|
"full-init": async () => runFullInit(options),
|
|
1881
2042
|
install: async () => installManagedWorkflow({
|
|
@@ -1892,14 +2053,63 @@ async function runInteractiveMenu(options) {
|
|
|
1892
2053
|
"codex-api": async () => configureCodexApi()
|
|
1893
2054
|
};
|
|
1894
2055
|
|
|
2056
|
+
const buildOption = (d) => {
|
|
2057
|
+
const opt = { value: d.value, label: d.label };
|
|
2058
|
+
if (d.hint) {
|
|
2059
|
+
opt.hint = d.hint;
|
|
2060
|
+
}
|
|
2061
|
+
return opt;
|
|
2062
|
+
};
|
|
2063
|
+
|
|
1895
2064
|
while (true) {
|
|
1896
|
-
const
|
|
1897
|
-
|
|
1898
|
-
|
|
2065
|
+
const selectOptions = [
|
|
2066
|
+
...interactiveMenuDescriptors
|
|
2067
|
+
.filter((d) => d.group === "main")
|
|
2068
|
+
.map(buildOption),
|
|
2069
|
+
{ value: "__sep_skills__", label: "─── 技能配置 ───", disabled: true },
|
|
2070
|
+
...interactiveMenuDescriptors
|
|
2071
|
+
.filter((d) => d.group === "skill")
|
|
2072
|
+
.map(buildOption),
|
|
2073
|
+
{ value: "__sep_cli__", label: "─── CLI 工具 ───", disabled: true },
|
|
2074
|
+
...interactiveMenuDescriptors
|
|
2075
|
+
.filter((d) => d.group === "cli")
|
|
2076
|
+
.map(buildOption),
|
|
2077
|
+
{ value: "__sep_exit__", label: "────────────────", disabled: true },
|
|
2078
|
+
...interactiveMenuDescriptors
|
|
2079
|
+
.filter((d) => d.group === "exit")
|
|
2080
|
+
.map(buildOption)
|
|
2081
|
+
];
|
|
2082
|
+
|
|
2083
|
+
const choice = await p.select({
|
|
2084
|
+
message: "请选择操作",
|
|
2085
|
+
options: selectOptions,
|
|
2086
|
+
initialValue: interactiveMenuDefaultValue
|
|
2087
|
+
});
|
|
2088
|
+
|
|
2089
|
+
if (p.isCancel(choice)) {
|
|
2090
|
+
p.outro(c.gray("已退出"));
|
|
2091
|
+
return;
|
|
2092
|
+
}
|
|
2093
|
+
if (choice === "exit") {
|
|
2094
|
+
p.outro(c.gray("已退出"));
|
|
1899
2095
|
return;
|
|
1900
2096
|
}
|
|
1901
2097
|
|
|
1902
|
-
|
|
2098
|
+
const action = menuActions[choice];
|
|
2099
|
+
if (!action) {
|
|
2100
|
+
p.log.warn(`未知菜单选项: ${choice}`);
|
|
2101
|
+
continue;
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
try {
|
|
2105
|
+
await action();
|
|
2106
|
+
} catch (error) {
|
|
2107
|
+
if (error instanceof CancelledError) {
|
|
2108
|
+
p.log.warn("操作已取消,返回菜单");
|
|
2109
|
+
continue;
|
|
2110
|
+
}
|
|
2111
|
+
throw error;
|
|
2112
|
+
}
|
|
1903
2113
|
}
|
|
1904
2114
|
}
|
|
1905
2115
|
|
|
@@ -1914,26 +2124,62 @@ async function main() {
|
|
|
1914
2124
|
return;
|
|
1915
2125
|
}
|
|
1916
2126
|
|
|
2127
|
+
// 非交互模式下菜单命令自动回退为 install
|
|
2128
|
+
if (options.command === "menu" && options.nonInteractive) {
|
|
2129
|
+
console.log("检测到非交互模式,自动执行工作流安装...");
|
|
2130
|
+
options.command = "install";
|
|
2131
|
+
}
|
|
2132
|
+
|
|
1917
2133
|
if (options.command === "install") {
|
|
1918
|
-
|
|
2134
|
+
try {
|
|
2135
|
+
await installManagedWorkflow(options);
|
|
2136
|
+
} catch (error) {
|
|
2137
|
+
const message = `操作失败: ${error.message || String(error)}`;
|
|
2138
|
+
// 非交互模式输出纯文本便于日志捕获/管道处理;交互模式使用 clack 格式化输出
|
|
2139
|
+
if (options.nonInteractive) {
|
|
2140
|
+
console.error(message);
|
|
2141
|
+
} else {
|
|
2142
|
+
p.outro(c.red(message));
|
|
2143
|
+
}
|
|
2144
|
+
process.exit(1);
|
|
2145
|
+
}
|
|
1919
2146
|
return;
|
|
1920
2147
|
}
|
|
1921
2148
|
|
|
1922
2149
|
assertInteractiveMenuSupported({
|
|
1923
2150
|
command: options.command,
|
|
1924
2151
|
inputIsTTY: input.isTTY,
|
|
1925
|
-
outputIsTTY: output.isTTY
|
|
2152
|
+
outputIsTTY: output.isTTY,
|
|
2153
|
+
nonInteractive: options.nonInteractive
|
|
1926
2154
|
});
|
|
1927
2155
|
|
|
1928
|
-
|
|
2156
|
+
try {
|
|
2157
|
+
await runInteractiveMenu(options);
|
|
2158
|
+
} catch (error) {
|
|
2159
|
+
const message = `操作失败: ${error.message || String(error)}`;
|
|
2160
|
+
// 非交互模式输出纯文本便于日志捕获/管道处理;交互模式使用 clack 格式化输出
|
|
2161
|
+
if (options.nonInteractive) {
|
|
2162
|
+
console.error(message);
|
|
2163
|
+
} else {
|
|
2164
|
+
p.outro(c.red(message));
|
|
2165
|
+
}
|
|
2166
|
+
process.exit(1);
|
|
2167
|
+
}
|
|
1929
2168
|
}
|
|
1930
2169
|
|
|
1931
2170
|
export {
|
|
2171
|
+
applyClaudePermissionFeature,
|
|
2172
|
+
buildDefaultClaudeSettings,
|
|
1932
2173
|
buildCodexConfigContent,
|
|
2174
|
+
getAugmentContextEnginePromptOptions,
|
|
1933
2175
|
getRunCommandSpawnOptions,
|
|
2176
|
+
hasPromptEnhancerApiConfig,
|
|
1934
2177
|
main,
|
|
1935
2178
|
mergeCodexAuthData,
|
|
1936
2179
|
mergeClaudeSettingsWithDefaults,
|
|
2180
|
+
renderManagedWorkflowContent,
|
|
2181
|
+
resolveAugmentContextEngineFeature,
|
|
2182
|
+
resolvePromptEnhancerMode,
|
|
1937
2183
|
resolveExistingCodexApiConfig,
|
|
1938
2184
|
updateTomlSectionFields
|
|
1939
2185
|
};
|