abelworkflow 0.9.2 → 1.0.0-rc.1
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/README.md +55 -182
- package/bin/abelworkflow.mjs +5 -3
- package/lib/cli/args.mjs +76 -0
- package/lib/cli/main.mjs +387 -0
- package/lib/cli/prompts.mjs +119 -0
- package/lib/config/dotenv.mjs +102 -0
- package/lib/config/jsonc.mjs +80 -0
- package/lib/config/store.mjs +310 -0
- package/lib/config/toml.mjs +638 -0
- package/lib/installer/assets.mjs +326 -0
- package/lib/installer/install.mjs +167 -0
- package/lib/installer/links.mjs +427 -0
- package/lib/installer/render.mjs +46 -0
- package/lib/installer/state.mjs +182 -0
- package/lib/paths.mjs +99 -0
- package/lib/providers/claude.mjs +388 -0
- package/lib/providers/codex.mjs +433 -0
- package/lib/providers/pi.mjs +362 -0
- package/lib/providers/skills.mjs +164 -0
- package/lib/templates/codex/agents/default.toml +7 -0
- package/lib/templates/codex/agents/explorer.toml +7 -0
- package/lib/templates/codex/agents/planner.toml +7 -0
- package/lib/templates/codex/agents/reviewer.toml +7 -0
- package/lib/templates/codex/agents/worker.toml +7 -0
- package/lib/templates/codex/config-base.toml +11 -3
- package/{AGENTS.md → lib/templates/workflow/AGENTS.md} +8 -3
- package/{commands → lib/templates/workflow/commands}/abel-diagnose.md +5 -5
- package/{commands → lib/templates/workflow/commands}/abel-implement.md +3 -8
- package/{commands → lib/templates/workflow/commands}/abel-init.md +1 -1
- package/{commands → lib/templates/workflow/commands}/abel-plan.md +1 -7
- package/{commands → lib/templates/workflow/commands}/abel-research.md +1 -1
- package/{.gitignore → lib/templates/workflow/gitignore.template} +0 -2
- package/lib/tools/cli-installer.mjs +277 -0
- package/package.json +43 -10
- package/skills/context7-auto-research/SKILL.md +5 -5
- package/skills/context7-auto-research/{context7-api.js → context7-api.cjs} +5 -8
- package/skills/dev-browser/SKILL.md +53 -33
- package/skills/dev-browser/dist/scripts/start.d.ts +2 -0
- package/skills/dev-browser/dist/scripts/start.d.ts.map +1 -0
- package/skills/dev-browser/dist/scripts/start.js +118 -0
- package/skills/dev-browser/dist/scripts/start.js.map +1 -0
- package/skills/dev-browser/dist/src/client.d.ts +93 -0
- package/skills/dev-browser/dist/src/client.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/client.js +310 -0
- package/skills/dev-browser/dist/src/client.js.map +1 -0
- package/skills/dev-browser/dist/src/entrypoint.d.ts +29 -0
- package/skills/dev-browser/dist/src/entrypoint.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/entrypoint.js +106 -0
- package/skills/dev-browser/dist/src/entrypoint.js.map +1 -0
- package/skills/dev-browser/dist/src/index.d.ts +4 -0
- package/skills/dev-browser/dist/src/index.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/index.js +2 -0
- package/skills/dev-browser/dist/src/index.js.map +1 -0
- package/skills/dev-browser/dist/src/page-api.d.ts +25 -0
- package/skills/dev-browser/dist/src/page-api.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/page-api.js +104 -0
- package/skills/dev-browser/dist/src/page-api.js.map +1 -0
- package/skills/dev-browser/dist/src/relay.d.ts +27 -0
- package/skills/dev-browser/dist/src/relay.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/relay.js +521 -0
- package/skills/dev-browser/dist/src/relay.js.map +1 -0
- package/skills/dev-browser/dist/src/runtime.d.ts +45 -0
- package/skills/dev-browser/dist/src/runtime.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/runtime.js +54 -0
- package/skills/dev-browser/dist/src/runtime.js.map +1 -0
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +23 -0
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts.map +1 -0
- package/skills/dev-browser/{src/snapshot/browser-script.ts → dist/src/snapshot/browser-script.js} +21 -30
- package/skills/dev-browser/dist/src/snapshot/browser-script.js.map +1 -0
- package/skills/dev-browser/dist/src/snapshot/index.d.ts +14 -0
- package/skills/dev-browser/dist/src/snapshot/index.d.ts.map +1 -0
- package/skills/dev-browser/{src/snapshot/index.ts → dist/src/snapshot/index.js} +2 -2
- package/skills/dev-browser/dist/src/snapshot/index.js.map +1 -0
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts +13 -0
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts.map +1 -0
- package/skills/dev-browser/{src/snapshot/inject.ts → dist/src/snapshot/inject.js} +2 -2
- package/skills/dev-browser/dist/src/snapshot/inject.js.map +1 -0
- package/skills/dev-browser/dist/src/standalone.d.ts +32 -0
- package/skills/dev-browser/dist/src/standalone.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/standalone.js +174 -0
- package/skills/dev-browser/dist/src/standalone.js.map +1 -0
- package/skills/dev-browser/dist/src/startup.d.ts +55 -0
- package/skills/dev-browser/dist/src/startup.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/startup.js +81 -0
- package/skills/dev-browser/dist/src/startup.js.map +1 -0
- package/skills/dev-browser/dist/src/target-registry.d.ts +29 -0
- package/skills/dev-browser/dist/src/target-registry.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/target-registry.js +135 -0
- package/skills/dev-browser/dist/src/target-registry.js.map +1 -0
- package/skills/dev-browser/dist/src/types.d.ts +27 -0
- package/skills/dev-browser/dist/src/types.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/types.js +2 -0
- package/skills/dev-browser/dist/src/types.js.map +1 -0
- package/skills/dev-browser/package-lock.json +67 -1510
- package/skills/dev-browser/package.json +18 -14
- package/skills/dev-browser/references/scraping.md +94 -105
- package/skills/grok-search/defaults.json +3 -0
- package/skills/grok-search/gitignore.template +4 -0
- package/skills/grok-search/scripts/_dotenv.py +5 -1
- package/skills/grok-search/scripts/groksearch_cli.py +15 -33
- package/skills/prompt-enhancer/TEMPLATE.md +0 -4
- package/skills/prompt-enhancer/requirements.txt +0 -1
- package/skills/prompt-enhancer/scripts/_dotenv.py +5 -1
- package/skills/prompt-enhancer/scripts/enhance.py +4 -51
- package/skills/prompt-enhancer/scripts/prompt_enhancer_entry.py +1 -16
- package/.skill-lock.json +0 -29
- package/lib/cli/logic.mjs +0 -213
- package/lib/cli.mjs +0 -3016
- package/skills/confidence-check/SKILL.md +0 -34
- package/skills/confidence-check/confidence.ts +0 -276
- package/skills/dev-browser/bun.lock +0 -460
- package/skills/dev-browser/scripts/start.ts +0 -280
- package/skills/dev-browser/src/client.ts +0 -474
- package/skills/dev-browser/src/entrypoint.ts +0 -157
- package/skills/dev-browser/src/index.ts +0 -289
- package/skills/dev-browser/src/relay.ts +0 -731
- package/skills/dev-browser/src/runtime.test.ts +0 -60
- package/skills/dev-browser/src/runtime.ts +0 -171
- package/skills/dev-browser/src/startup.test.ts +0 -95
- package/skills/dev-browser/src/startup.ts +0 -153
- package/skills/dev-browser/src/types.ts +0 -35
- package/skills/dev-browser/tsconfig.json +0 -36
- package/skills/dev-browser/vitest.config.ts +0 -12
- package/skills/sequential-think/SKILL.md +0 -198
- package/skills/sequential-think/scripts/.env.example +0 -5
- package/skills/sequential-think/scripts/sequential_think_cli.py +0 -253
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { lstat, mkdir, readFile, readdir, unlink } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import * as p from "@clack/prompts";
|
|
5
|
+
import {
|
|
6
|
+
pathExists,
|
|
7
|
+
readJsonFileSafe,
|
|
8
|
+
writeJsonFileWithBackup,
|
|
9
|
+
writeText
|
|
10
|
+
} from "../config/store.mjs";
|
|
11
|
+
import {
|
|
12
|
+
buildTomlSection,
|
|
13
|
+
detectLineEnding,
|
|
14
|
+
extractTopLevelTomlEntries,
|
|
15
|
+
getTomlSectionFieldNames,
|
|
16
|
+
formatTomlKeySegment,
|
|
17
|
+
mergeMissingTopLevelTomlEntries,
|
|
18
|
+
parseTomlSection,
|
|
19
|
+
readTopLevelTomlString,
|
|
20
|
+
removeTomlSection,
|
|
21
|
+
removeTopLevelTomlField,
|
|
22
|
+
updateTomlSectionFields,
|
|
23
|
+
updateTopLevelTomlField
|
|
24
|
+
} from "../config/toml.mjs";
|
|
25
|
+
import { defaultPaths, maskSecret, pathToLabel } from "../paths.mjs";
|
|
26
|
+
import { normalizeOpenAiBaseUrl } from "./pi.mjs";
|
|
27
|
+
|
|
28
|
+
const publishedCodexDeveloperInstructionHashes = new Set([
|
|
29
|
+
"957adb227dd3fc298985acca38db4c22b8e018d387dab91f1ea8287db35385db"
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const publishedCodexAgentHashes = {
|
|
33
|
+
"default.toml": new Set([
|
|
34
|
+
"7add720eb7ed030871088fe9f994a7e34a2b3cae1f898fe7f97c4e302f4a1844",
|
|
35
|
+
"72d3da0275f4add64ff05da94f391443e8e4b5c43b646e0f89f2d17fa4345cb4",
|
|
36
|
+
"7dc002559f5d78534e70c7c4683d51a48ffe94725c199855a35f61124aead876"
|
|
37
|
+
]),
|
|
38
|
+
"explorer.toml": new Set([
|
|
39
|
+
"e4e9c4eed4c028dded095b4cf36cb0ac408abcd86b9ab7c139440e1c58355644",
|
|
40
|
+
"a236d7b586c4e312a62bb8b36115fe9780e28502c0af160ce5cd45b67c5b8479",
|
|
41
|
+
"4740f13e9a478528dcb097188997445efa7607b1b07ae64774b1dd26fcd6f853"
|
|
42
|
+
]),
|
|
43
|
+
"planner.toml": new Set([
|
|
44
|
+
"2b16f821b14224cdfc9dab1fe14c41b9ff2b63642b0d9b340ef84b67a753a3b7",
|
|
45
|
+
"741c9dfce5085d6bdc49f6e5004475974d54c2bde95c0b589eda86a63e96e58f",
|
|
46
|
+
"7fde442a5ba5e996936b4ec853b036ebbb9f06ee9baef6540124d081c7107196"
|
|
47
|
+
]),
|
|
48
|
+
"reviewer.toml": new Set([
|
|
49
|
+
"08dcf8f5fee281f3b6120b63559086b7c675a619be72f761de4add711543b353",
|
|
50
|
+
"9455d0189f324f7c606f7ae94ca338e9bd5c468f072188ac1f18e3749c918cd1",
|
|
51
|
+
"a7292a533c9146bf5a2eb292cbfe889ca47bf4808324cddc1732c3c1721d437c"
|
|
52
|
+
]),
|
|
53
|
+
"worker.toml": new Set([
|
|
54
|
+
"13533715464b1f58a9c311e16dc3a29e92ecb95793fd8aff69a5ca731ae46ac8",
|
|
55
|
+
"ea12b5b6173eb8fcaa57a8b447b1f81b35a6ac59e75025b88198c3f85175922c",
|
|
56
|
+
"bfbc214e7b582774de759b118544c3f1fb61e1284d6ffd6a61ab772d28b6eaa0"
|
|
57
|
+
])
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function stripCodexSubagentDefaults(content) {
|
|
61
|
+
const lineEnding = detectLineEnding(content);
|
|
62
|
+
let nextContent = removeTopLevelTomlField(content, "approvals_reviewer");
|
|
63
|
+
nextContent = removeTopLevelTomlField(nextContent, "developer_instructions");
|
|
64
|
+
nextContent = removeTomlSection(nextContent, "agents");
|
|
65
|
+
|
|
66
|
+
const featureValues = parseTomlSection(nextContent, "features");
|
|
67
|
+
delete featureValues.multi_agent;
|
|
68
|
+
delete featureValues.guardian_approval;
|
|
69
|
+
nextContent = removeTomlSection(nextContent, "features");
|
|
70
|
+
|
|
71
|
+
if (Object.keys(featureValues).length) {
|
|
72
|
+
const featuresSection = buildTomlSection("features", featureValues, lineEnding).trimEnd();
|
|
73
|
+
nextContent = nextContent.trimEnd()
|
|
74
|
+
? `${nextContent.trimEnd()}${lineEnding}${lineEnding}${featuresSection}${lineEnding}`
|
|
75
|
+
: `${featuresSection}${lineEnding}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return nextContent;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function mergeCodexTemplateDefaults(content, templateContent) {
|
|
82
|
+
const defaultTopLevelFields = new Set([
|
|
83
|
+
"personality",
|
|
84
|
+
"disable_response_storage",
|
|
85
|
+
"approvals_reviewer",
|
|
86
|
+
"approval_policy",
|
|
87
|
+
"sandbox_mode",
|
|
88
|
+
"service_tier",
|
|
89
|
+
"model",
|
|
90
|
+
"model_reasoning_effort",
|
|
91
|
+
"developer_instructions"
|
|
92
|
+
]);
|
|
93
|
+
const templateEntries = extractTopLevelTomlEntries(templateContent)
|
|
94
|
+
.filter(({ field }) => defaultTopLevelFields.has(field));
|
|
95
|
+
let nextContent = mergeMissingTopLevelTomlEntries(content, templateEntries);
|
|
96
|
+
const currentDeveloperInstructions = readTopLevelTomlString(nextContent, "developer_instructions");
|
|
97
|
+
const templateDeveloperInstructions = readTopLevelTomlString(templateContent, "developer_instructions");
|
|
98
|
+
if (templateDeveloperInstructions && publishedCodexDeveloperInstructionHashes.has(
|
|
99
|
+
sha256(currentDeveloperInstructions)
|
|
100
|
+
)) {
|
|
101
|
+
nextContent = updateTopLevelTomlField(
|
|
102
|
+
nextContent,
|
|
103
|
+
"developer_instructions",
|
|
104
|
+
templateDeveloperInstructions
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
for (const sectionName of ["agents", "features"]) {
|
|
109
|
+
const templateValues = parseTomlSection(templateContent, sectionName);
|
|
110
|
+
const currentFields = new Set(getTomlSectionFieldNames(nextContent, sectionName));
|
|
111
|
+
const missingValues = Object.fromEntries(
|
|
112
|
+
Object.entries(templateValues).filter(([field]) => !currentFields.has(field))
|
|
113
|
+
);
|
|
114
|
+
if (Object.keys(missingValues).length) {
|
|
115
|
+
nextContent = updateTomlSectionFields(nextContent, sectionName, missingValues);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return nextContent;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function loadBundledCodexConfigTemplate(paths = defaultPaths) {
|
|
123
|
+
return readFile(paths.codexTemplateConfigPath, "utf8");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function sha256(content) {
|
|
127
|
+
return createHash("sha256").update(content).digest("hex");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function readCodexAgentTarget(path) {
|
|
131
|
+
try {
|
|
132
|
+
const targetStat = await lstat(path);
|
|
133
|
+
return targetStat.isFile()
|
|
134
|
+
? { exists: true, content: await readFile(path) }
|
|
135
|
+
: { exists: true, content: null };
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (error?.code === "ENOENT") return { exists: false, content: null };
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function assertCodexAgentDirectory(targetDir, targetStat, kind) {
|
|
143
|
+
if (targetStat.isSymbolicLink() || !targetStat.isDirectory()) {
|
|
144
|
+
throw new Error(`Codex agent ${kind} conflict at ${targetDir}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function ensureCodexAgentDirectory(targetDir, kind) {
|
|
149
|
+
try {
|
|
150
|
+
assertCodexAgentDirectory(targetDir, await lstat(targetDir), kind);
|
|
151
|
+
return;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (error?.code !== "ENOENT") throw error;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
await mkdir(targetDir);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
if (error?.code === "EEXIST" || error?.code === "ENOTDIR") {
|
|
160
|
+
throw new Error(`Codex agent ${kind} conflict at ${targetDir}`, { cause: error });
|
|
161
|
+
}
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
assertCodexAgentDirectory(targetDir, await lstat(targetDir), kind);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function ensureCodexAgentContainer(homeDir) {
|
|
168
|
+
await mkdir(homeDir, { recursive: true });
|
|
169
|
+
const codexDir = join(homeDir, ".codex");
|
|
170
|
+
await ensureCodexAgentDirectory(codexDir, "ancestor");
|
|
171
|
+
const targetDir = join(codexDir, "agents");
|
|
172
|
+
await ensureCodexAgentDirectory(targetDir, "container");
|
|
173
|
+
return targetDir;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function isCodexAgentOwnership(name, hash) {
|
|
177
|
+
return name.endsWith(".toml")
|
|
178
|
+
&& !name.includes("/")
|
|
179
|
+
&& !name.includes("\\")
|
|
180
|
+
&& typeof hash === "string"
|
|
181
|
+
&& /^[a-f0-9]{64}$/u.test(hash);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function isPublishedCodexAgent(name, hash) {
|
|
185
|
+
return publishedCodexAgentHashes[name]?.has(hash) ?? false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function deployBundledCodexAgents(paths = defaultPaths, previousManagedFiles = {}) {
|
|
189
|
+
const targetDir = await ensureCodexAgentContainer(paths.homeDir);
|
|
190
|
+
const entries = (await readdir(paths.codexTemplateAgentsPath, { withFileTypes: true }))
|
|
191
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".toml"))
|
|
192
|
+
.map((entry) => entry.name)
|
|
193
|
+
.sort((left, right) => left.localeCompare(right));
|
|
194
|
+
|
|
195
|
+
const result = {
|
|
196
|
+
managedFiles: {},
|
|
197
|
+
created: [],
|
|
198
|
+
updated: [],
|
|
199
|
+
unchanged: [],
|
|
200
|
+
conflicts: []
|
|
201
|
+
};
|
|
202
|
+
for (const name of entries) {
|
|
203
|
+
const source = join(paths.codexTemplateAgentsPath, name);
|
|
204
|
+
const target = join(targetDir, name);
|
|
205
|
+
const sourceContent = await readFile(source);
|
|
206
|
+
const sourceHash = sha256(sourceContent);
|
|
207
|
+
const current = await readCodexAgentTarget(target);
|
|
208
|
+
const currentHash = current.content ? sha256(current.content) : "";
|
|
209
|
+
|
|
210
|
+
if (!current.exists) {
|
|
211
|
+
await writeText(target, sourceContent, { backupLimit: 0 });
|
|
212
|
+
result.created.push(target);
|
|
213
|
+
} else if (currentHash === sourceHash) {
|
|
214
|
+
result.unchanged.push(target);
|
|
215
|
+
} else if (current.content && (
|
|
216
|
+
previousManagedFiles?.[name] === currentHash
|
|
217
|
+
|| isPublishedCodexAgent(name, currentHash)
|
|
218
|
+
)) {
|
|
219
|
+
await writeText(target, sourceContent, { backupLimit: 0 });
|
|
220
|
+
result.updated.push(target);
|
|
221
|
+
} else {
|
|
222
|
+
result.conflicts.push(target);
|
|
223
|
+
if (isCodexAgentOwnership(name, previousManagedFiles?.[name])) {
|
|
224
|
+
result.managedFiles[name] = previousManagedFiles[name];
|
|
225
|
+
}
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
result.managedFiles[name] = sourceHash;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const bundledNames = new Set(entries);
|
|
232
|
+
const previousEntries = previousManagedFiles
|
|
233
|
+
&& typeof previousManagedFiles === "object"
|
|
234
|
+
&& !Array.isArray(previousManagedFiles)
|
|
235
|
+
? Object.entries(previousManagedFiles).sort(([left], [right]) => left.localeCompare(right))
|
|
236
|
+
: [];
|
|
237
|
+
for (const [name, previousHash] of previousEntries) {
|
|
238
|
+
if (bundledNames.has(name) || !isCodexAgentOwnership(name, previousHash)) continue;
|
|
239
|
+
const target = join(targetDir, name);
|
|
240
|
+
const current = await readCodexAgentTarget(target);
|
|
241
|
+
if (!current.exists) continue;
|
|
242
|
+
if (current.content && sha256(current.content) === previousHash) {
|
|
243
|
+
await unlink(target);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
result.managedFiles[name] = previousHash;
|
|
247
|
+
result.conflicts.push(target);
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function getDefaultCodexEnvKey(providerId) {
|
|
253
|
+
if (providerId === "openai") return "OPENAI_API_KEY";
|
|
254
|
+
if (providerId === "abelworkflow") return "ABELWORKFLOW_API_KEY";
|
|
255
|
+
const digest = createHash("sha256").update(providerId).digest("hex").toUpperCase();
|
|
256
|
+
return `ABELWORKFLOW_PROVIDER_${digest}_API_KEY`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function getCodexProviderSectionName(providerId) {
|
|
260
|
+
return `model_providers.${formatTomlKeySegment(providerId)}`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function assertCodexAuthKeyAvailable(auth, envKey) {
|
|
264
|
+
if (
|
|
265
|
+
auth
|
|
266
|
+
&& typeof auth === "object"
|
|
267
|
+
&& !Array.isArray(auth)
|
|
268
|
+
&& Object.hasOwn(auth, envKey)
|
|
269
|
+
&& typeof auth[envKey] !== "string"
|
|
270
|
+
) {
|
|
271
|
+
throw new Error(`Environment key ${envKey} conflicts with structured Codex auth state`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function resolveExistingCodexApiConfig(content, auth = {}) {
|
|
276
|
+
const providerId = readTopLevelTomlString(content, "model_provider") || "abelworkflow";
|
|
277
|
+
const provider = parseTomlSection(content, getCodexProviderSectionName(providerId));
|
|
278
|
+
const configuredEnvKey = provider.temp_env_key || "";
|
|
279
|
+
const envKey = configuredEnvKey || getDefaultCodexEnvKey(providerId);
|
|
280
|
+
assertCodexAuthKeyAvailable(auth, envKey);
|
|
281
|
+
const apiKey = typeof auth[envKey] === "string" ? auth[envKey] : "";
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
providerId,
|
|
285
|
+
providerName: provider.name || providerId,
|
|
286
|
+
baseUrl: provider.base_url || "https://api.openai.com/v1",
|
|
287
|
+
envKey,
|
|
288
|
+
apiKey
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function getExistingCodexApiConfig(paths = defaultPaths) {
|
|
293
|
+
const content = await pathExists(paths.codexConfigPath) ? await readFile(paths.codexConfigPath, "utf8") : "";
|
|
294
|
+
const auth = await readJsonFileSafe(paths.codexAuthPath, {}, { sensitive: true });
|
|
295
|
+
return resolveExistingCodexApiConfig(content, auth);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function persistCodexConfiguration(paths, { content, auth }) {
|
|
299
|
+
await writeText(paths.codexConfigPath, content);
|
|
300
|
+
await writeJsonFileWithBackup(paths.codexAuthPath, auth, { sensitive: true });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function configureCodexApi(paths = defaultPaths, promptApi, ownership = {}) {
|
|
304
|
+
const {
|
|
305
|
+
assertNotCancelled,
|
|
306
|
+
confirmOrCancel,
|
|
307
|
+
required,
|
|
308
|
+
requiredUnlessExisting,
|
|
309
|
+
resolvePasswordValue
|
|
310
|
+
} = promptApi;
|
|
311
|
+
const existing = await getExistingCodexApiConfig(paths);
|
|
312
|
+
const providerId = existing.providerId || "abelworkflow";
|
|
313
|
+
const providerName = existing.providerName || providerId;
|
|
314
|
+
const baseUrlInput = await p.text({
|
|
315
|
+
message: "Codex Base URL",
|
|
316
|
+
defaultValue: existing.baseUrl,
|
|
317
|
+
validate: required()
|
|
318
|
+
});
|
|
319
|
+
assertNotCancelled(baseUrlInput);
|
|
320
|
+
const baseUrl = normalizeOpenAiBaseUrl(baseUrlInput);
|
|
321
|
+
|
|
322
|
+
const apiKey = await p.password({
|
|
323
|
+
message: "Codex 第三方 API Key(输入 - 清除)",
|
|
324
|
+
mask: "*",
|
|
325
|
+
defaultValue: existing.apiKey || undefined,
|
|
326
|
+
validate: requiredUnlessExisting(existing.apiKey, "API Key 不能为空")
|
|
327
|
+
});
|
|
328
|
+
assertNotCancelled(apiKey);
|
|
329
|
+
const finalApiKey = resolvePasswordValue(apiKey, existing.apiKey);
|
|
330
|
+
|
|
331
|
+
const shouldDeploySubagents = await confirmOrCancel({ message: "是否部署 Codex subagents 配置?", initialValue: true });
|
|
332
|
+
let managedCodexAgentFiles = { ...(ownership.managedCodexAgentFiles ?? {}) };
|
|
333
|
+
|
|
334
|
+
const envKey = existing.envKey || "OPENAI_API_KEY";
|
|
335
|
+
const currentContent = await pathExists(paths.codexConfigPath) ? await readFile(paths.codexConfigPath, "utf8") : "";
|
|
336
|
+
const templateContent = await loadBundledCodexConfigTemplate(paths);
|
|
337
|
+
const content = buildCodexConfigContent(currentContent, {
|
|
338
|
+
templateContent,
|
|
339
|
+
mergeMissingTemplateDefaults: true,
|
|
340
|
+
includeSubagentDefaults: shouldDeploySubagents,
|
|
341
|
+
providerId,
|
|
342
|
+
providerName,
|
|
343
|
+
baseUrl,
|
|
344
|
+
envKey
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const auth = mergeCodexAuthData(
|
|
348
|
+
await readJsonFileSafe(paths.codexAuthPath, {}, { sensitive: true }),
|
|
349
|
+
envKey,
|
|
350
|
+
finalApiKey,
|
|
351
|
+
ownership.managedAuthKeys ?? []
|
|
352
|
+
);
|
|
353
|
+
await persistCodexConfiguration(paths, { content, auth });
|
|
354
|
+
|
|
355
|
+
p.log.step(`已更新 ${pathToLabel(paths.codexConfigPath, paths.homeDir)} (${providerId}, ${baseUrl})`);
|
|
356
|
+
p.log.step(`已更新 ${pathToLabel(paths.codexAuthPath, paths.homeDir)} (${maskSecret(finalApiKey)})`);
|
|
357
|
+
if (String(baseUrl).startsWith("https://") && !process.env.CODEX_CA_CERTIFICATE && !process.env.SSL_CERT_FILE) {
|
|
358
|
+
p.log.message("自签名证书需在启动 Codex 前设置 CODEX_CA_CERTIFICATE=/absolute/path/relay-ca.pem。");
|
|
359
|
+
}
|
|
360
|
+
if (shouldDeploySubagents) {
|
|
361
|
+
const deployment = await deployBundledCodexAgents(paths, managedCodexAgentFiles);
|
|
362
|
+
managedCodexAgentFiles = deployment.managedFiles;
|
|
363
|
+
p.log.step(`Codex subagents 同步完成:新增 ${deployment.created.length},更新 ${deployment.updated.length},未变 ${deployment.unchanged.length}`);
|
|
364
|
+
if (deployment.conflicts.length) {
|
|
365
|
+
p.log.warn([
|
|
366
|
+
`检测到 ${deployment.conflicts.length} 个 Codex subagent 冲突,已保留原文件:`,
|
|
367
|
+
...deployment.conflicts.map((path) => `- ${pathToLabel(path, paths.homeDir)}`)
|
|
368
|
+
].join("\n"));
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
p.log.message("已跳过 Codex subagents 部署。");
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
managedAuthKeys: finalApiKey ? [envKey] : [],
|
|
375
|
+
managedCodexAgentFiles
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function buildCodexConfigContent(currentContent, {
|
|
380
|
+
templateContent = "",
|
|
381
|
+
mergeMissingTemplateDefaults = false,
|
|
382
|
+
includeSubagentDefaults = true,
|
|
383
|
+
providerId,
|
|
384
|
+
providerName,
|
|
385
|
+
baseUrl,
|
|
386
|
+
envKey
|
|
387
|
+
}) {
|
|
388
|
+
const effectiveTemplateContent = includeSubagentDefaults
|
|
389
|
+
? templateContent
|
|
390
|
+
: stripCodexSubagentDefaults(templateContent);
|
|
391
|
+
const hasCurrentContent = Boolean(currentContent.trim());
|
|
392
|
+
let content = hasCurrentContent ? currentContent : effectiveTemplateContent;
|
|
393
|
+
if (hasCurrentContent && mergeMissingTemplateDefaults && effectiveTemplateContent.trim()) {
|
|
394
|
+
content = mergeCodexTemplateDefaults(content, effectiveTemplateContent);
|
|
395
|
+
}
|
|
396
|
+
const lineEnding = detectLineEnding(content);
|
|
397
|
+
if (includeSubagentDefaults && readTopLevelTomlString(content, "approvals_reviewer") === "reviewer") {
|
|
398
|
+
content = updateTopLevelTomlField(content, "approvals_reviewer", "guardian_subagent");
|
|
399
|
+
}
|
|
400
|
+
content = updateTopLevelTomlField(content, "model_provider", providerId);
|
|
401
|
+
content = updateTopLevelTomlField(content, "preferred_auth_method", "apikey");
|
|
402
|
+
content = updateTomlSectionFields(content, getCodexProviderSectionName(providerId), {
|
|
403
|
+
name: providerName,
|
|
404
|
+
base_url: baseUrl,
|
|
405
|
+
wire_api: "responses",
|
|
406
|
+
temp_env_key: envKey,
|
|
407
|
+
requires_openai_auth: true,
|
|
408
|
+
supports_websockets: true
|
|
409
|
+
});
|
|
410
|
+
return `${content.trim()}${lineEnding}`;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function mergeCodexAuthData(auth, envKey, apiKey, managedAuthKeys = []) {
|
|
414
|
+
const nextAuth = auth && typeof auth === "object" && !Array.isArray(auth) ? { ...auth } : {};
|
|
415
|
+
assertCodexAuthKeyAvailable(nextAuth, envKey);
|
|
416
|
+
for (const managedAuthKey of managedAuthKeys) {
|
|
417
|
+
delete nextAuth[managedAuthKey];
|
|
418
|
+
}
|
|
419
|
+
if (apiKey) nextAuth[envKey] = apiKey;
|
|
420
|
+
else delete nextAuth[envKey];
|
|
421
|
+
return nextAuth;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export {
|
|
425
|
+
buildCodexConfigContent,
|
|
426
|
+
configureCodexApi,
|
|
427
|
+
deployBundledCodexAgents,
|
|
428
|
+
mergeCodexAuthData,
|
|
429
|
+
mergeCodexTemplateDefaults,
|
|
430
|
+
persistCodexConfiguration,
|
|
431
|
+
resolveExistingCodexApiConfig,
|
|
432
|
+
stripCodexSubagentDefaults
|
|
433
|
+
};
|