abelworkflow 0.9.2 → 1.0.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/README.md +54 -182
- package/bin/abelworkflow.mjs +5 -3
- package/extensions/pi-gpt-responses-compat/index.ts +1 -1
- package/lib/cli/args.mjs +76 -0
- package/lib/cli/main.mjs +385 -0
- package/lib/cli/prompts.mjs +106 -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 +324 -0
- package/lib/installer/install.mjs +153 -0
- package/lib/installer/links.mjs +427 -0
- package/lib/installer/state.mjs +163 -0
- package/lib/paths.mjs +99 -0
- package/lib/providers/claude.mjs +197 -0
- package/lib/providers/codex.mjs +433 -0
- package/lib/providers/pi.mjs +577 -0
- package/lib/providers/skills.mjs +105 -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/lib/templates/workflow/AGENTS.md +50 -0
- package/lib/templates/workflow/commands/abel-design.md +175 -0
- package/{commands → lib/templates/workflow/commands}/abel-diagnose.md +5 -5
- package/lib/templates/workflow/commands/abel-implement.md +170 -0
- package/{commands → lib/templates/workflow/commands}/abel-init.md +3 -5
- package/{.gitignore → lib/templates/workflow/gitignore.template} +0 -2
- package/lib/tools/cli-installer.mjs +277 -0
- package/package.json +37 -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/.env.example +3 -3
- package/skills/grok-search/SKILL.md +1 -1
- 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/SKILL.md +2 -27
- package/skills/prompt-enhancer/TEMPLATE.md +0 -4
- package/.skill-lock.json +0 -29
- package/AGENTS.md +0 -45
- package/commands/abel-implement.md +0 -157
- package/commands/abel-plan.md +0 -88
- package/commands/abel-research.md +0 -126
- 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/prompt-enhancer/.env.example +0 -12
- package/skills/prompt-enhancer/ADVANCED.md +0 -103
- package/skills/prompt-enhancer/requirements.txt +0 -2
- package/skills/prompt-enhancer/scripts/_dotenv.py +0 -28
- package/skills/prompt-enhancer/scripts/enhance.py +0 -191
- package/skills/prompt-enhancer/scripts/prompt_enhancer_entry.py +0 -253
- 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,197 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import {
|
|
3
|
+
readJsonFileSafe,
|
|
4
|
+
writeJsonFileWithBackup
|
|
5
|
+
} from "../config/store.mjs";
|
|
6
|
+
import { defaultPaths, maskSecret, pathToLabel } from "../paths.mjs";
|
|
7
|
+
|
|
8
|
+
const claudeModelEnvKeys = [
|
|
9
|
+
"ANTHROPIC_MODEL",
|
|
10
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
11
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL",
|
|
12
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
|
13
|
+
"CLAUDE_CODE_SUBAGENT_MODEL"
|
|
14
|
+
];
|
|
15
|
+
const defaultClaudeSettings = {
|
|
16
|
+
$schema: "https://json.schemastore.org/claude-code-settings.json",
|
|
17
|
+
env: {
|
|
18
|
+
DISABLE_TELEMETRY: "1",
|
|
19
|
+
DISABLE_ERROR_REPORTING: "1",
|
|
20
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
|
|
21
|
+
},
|
|
22
|
+
includeCoAuthoredBy: false,
|
|
23
|
+
permissions: {
|
|
24
|
+
allow: [],
|
|
25
|
+
deny: [],
|
|
26
|
+
defaultMode: "bypassPermissions"
|
|
27
|
+
},
|
|
28
|
+
hooks: {},
|
|
29
|
+
alwaysThinkingEnabled: true,
|
|
30
|
+
language: "Chinese"
|
|
31
|
+
};
|
|
32
|
+
function buildDefaultClaudeSettings() {
|
|
33
|
+
return {
|
|
34
|
+
...defaultClaudeSettings,
|
|
35
|
+
env: { ...defaultClaudeSettings.env },
|
|
36
|
+
permissions: {
|
|
37
|
+
...defaultClaudeSettings.permissions,
|
|
38
|
+
allow: [...defaultClaudeSettings.permissions.allow],
|
|
39
|
+
deny: [...defaultClaudeSettings.permissions.deny]
|
|
40
|
+
},
|
|
41
|
+
hooks: { ...defaultClaudeSettings.hooks }
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function mergeClaudeSettingsWithDefaults(settings) {
|
|
45
|
+
const defaults = buildDefaultClaudeSettings();
|
|
46
|
+
const env = settings?.env && typeof settings.env === "object" ? settings.env : {};
|
|
47
|
+
const permissions = settings?.permissions && typeof settings.permissions === "object" ? settings.permissions : {};
|
|
48
|
+
return {
|
|
49
|
+
...defaults,
|
|
50
|
+
...settings,
|
|
51
|
+
env: {
|
|
52
|
+
...defaults.env,
|
|
53
|
+
...env
|
|
54
|
+
},
|
|
55
|
+
permissions: {
|
|
56
|
+
...defaults.permissions,
|
|
57
|
+
...permissions,
|
|
58
|
+
allow: Array.isArray(permissions.allow) ? permissions.allow : defaults.permissions.allow,
|
|
59
|
+
deny: Array.isArray(permissions.deny) ? permissions.deny : defaults.permissions.deny
|
|
60
|
+
},
|
|
61
|
+
hooks: settings?.hooks && typeof settings.hooks === "object" ? settings.hooks : defaults.hooks
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function applyClaudeInsecureTlsSetting(env = {}, enabled = false) {
|
|
66
|
+
const nextEnv = { ...env };
|
|
67
|
+
if (enabled) {
|
|
68
|
+
nextEnv.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
69
|
+
} else if (nextEnv.NODE_TLS_REJECT_UNAUTHORIZED === "0") {
|
|
70
|
+
delete nextEnv.NODE_TLS_REJECT_UNAUTHORIZED;
|
|
71
|
+
}
|
|
72
|
+
return nextEnv;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function getExistingClaudeApiConfig(settings) {
|
|
76
|
+
const env = mergeClaudeSettingsWithDefaults(settings).env;
|
|
77
|
+
return {
|
|
78
|
+
baseUrl: env.ANTHROPIC_BASE_URL || "https://api.anthropic.com",
|
|
79
|
+
key: env.ANTHROPIC_API_KEY || "",
|
|
80
|
+
model: claudeModelEnvKeys.map((field) => env[field]).find(Boolean) || "",
|
|
81
|
+
insecureTls: env.NODE_TLS_REJECT_UNAUTHORIZED === "0"
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function ensureApprovedClaudeApiKey(config, apiKey) {
|
|
86
|
+
if (!apiKey) {
|
|
87
|
+
return config;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const truncated = apiKey.slice(0, 20);
|
|
91
|
+
if (!config.customApiKeyResponses || typeof config.customApiKeyResponses !== "object") {
|
|
92
|
+
config.customApiKeyResponses = { approved: [], rejected: [] };
|
|
93
|
+
}
|
|
94
|
+
if (!Array.isArray(config.customApiKeyResponses.approved)) {
|
|
95
|
+
config.customApiKeyResponses.approved = [];
|
|
96
|
+
}
|
|
97
|
+
if (!Array.isArray(config.customApiKeyResponses.rejected)) {
|
|
98
|
+
config.customApiKeyResponses.rejected = [];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
config.customApiKeyResponses.rejected = config.customApiKeyResponses.rejected.filter((item) => item !== truncated);
|
|
102
|
+
if (!config.customApiKeyResponses.approved.includes(truncated)) {
|
|
103
|
+
config.customApiKeyResponses.approved.push(truncated);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return config;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function buildClaudeApiSettings(settings, {
|
|
110
|
+
baseUrl,
|
|
111
|
+
key,
|
|
112
|
+
model,
|
|
113
|
+
insecureTls = false
|
|
114
|
+
}) {
|
|
115
|
+
const nextSettings = mergeClaudeSettingsWithDefaults(settings);
|
|
116
|
+
nextSettings.env = applyClaudeInsecureTlsSetting(
|
|
117
|
+
nextSettings.env && typeof nextSettings.env === "object" ? nextSettings.env : {},
|
|
118
|
+
insecureTls
|
|
119
|
+
);
|
|
120
|
+
nextSettings.env.ANTHROPIC_BASE_URL = baseUrl;
|
|
121
|
+
nextSettings.env.ANTHROPIC_API_KEY = key;
|
|
122
|
+
delete nextSettings.env.ANTHROPIC_AUTH_TOKEN;
|
|
123
|
+
for (const field of claudeModelEnvKeys) {
|
|
124
|
+
nextSettings.env[field] = model;
|
|
125
|
+
}
|
|
126
|
+
return nextSettings;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function persistClaudeConfiguration(paths, { settings, metaConfig }) {
|
|
130
|
+
await writeJsonFileWithBackup(paths.claudeSettingsPath, settings, { sensitive: true });
|
|
131
|
+
await writeJsonFileWithBackup(paths.claudeMetaConfigPath, metaConfig, { sensitive: true });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function configureClaudeApi(paths = defaultPaths, promptApi) {
|
|
135
|
+
const {
|
|
136
|
+
assertNotCancelled,
|
|
137
|
+
confirmOrCancel,
|
|
138
|
+
passwordPromptOptions,
|
|
139
|
+
required,
|
|
140
|
+
requiredUnlessExisting,
|
|
141
|
+
resolvePasswordValue
|
|
142
|
+
} = promptApi;
|
|
143
|
+
const settings = await readJsonFileSafe(paths.claudeSettingsPath, {}, { sensitive: true });
|
|
144
|
+
const existing = getExistingClaudeApiConfig(settings);
|
|
145
|
+
|
|
146
|
+
const baseUrl = await p.text({
|
|
147
|
+
message: "Claude Code Base URL",
|
|
148
|
+
initialValue: existing.baseUrl,
|
|
149
|
+
validate: required()
|
|
150
|
+
});
|
|
151
|
+
assertNotCancelled(baseUrl);
|
|
152
|
+
|
|
153
|
+
const key = await p.password(passwordPromptOptions(
|
|
154
|
+
"Claude Code API Key",
|
|
155
|
+
existing.key,
|
|
156
|
+
requiredUnlessExisting(existing.key, "API Key 不能为空")
|
|
157
|
+
));
|
|
158
|
+
assertNotCancelled(key);
|
|
159
|
+
const finalKey = resolvePasswordValue(key, existing.key);
|
|
160
|
+
|
|
161
|
+
const insecureTls = await confirmOrCancel({
|
|
162
|
+
message: "是否跳过 Claude Code TLS 证书校验?仅证书无法修复时启用(会放宽该进程全部 HTTPS 请求)",
|
|
163
|
+
initialValue: existing.insecureTls
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const model = await p.text({
|
|
167
|
+
message: "Claude Code 模型",
|
|
168
|
+
initialValue: existing.model || undefined,
|
|
169
|
+
validate: required()
|
|
170
|
+
});
|
|
171
|
+
assertNotCancelled(model);
|
|
172
|
+
|
|
173
|
+
const apiSettings = buildClaudeApiSettings(settings, {
|
|
174
|
+
baseUrl,
|
|
175
|
+
key: finalKey,
|
|
176
|
+
model,
|
|
177
|
+
insecureTls
|
|
178
|
+
});
|
|
179
|
+
const metaConfig = await readJsonFileSafe(paths.claudeMetaConfigPath, {}, { sensitive: true });
|
|
180
|
+
metaConfig.hasCompletedOnboarding = true;
|
|
181
|
+
ensureApprovedClaudeApiKey(metaConfig, finalKey);
|
|
182
|
+
await persistClaudeConfiguration(paths, {
|
|
183
|
+
settings: apiSettings,
|
|
184
|
+
metaConfig
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
p.log.step(`已更新 ${pathToLabel(paths.claudeSettingsPath, paths.homeDir)} (${baseUrl}, ${maskSecret(finalKey)})`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export {
|
|
191
|
+
applyClaudeInsecureTlsSetting,
|
|
192
|
+
buildClaudeApiSettings,
|
|
193
|
+
buildDefaultClaudeSettings,
|
|
194
|
+
configureClaudeApi,
|
|
195
|
+
mergeClaudeSettingsWithDefaults,
|
|
196
|
+
persistClaudeConfiguration
|
|
197
|
+
};
|
|
@@ -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
|
+
passwordPromptOptions,
|
|
308
|
+
required,
|
|
309
|
+
requiredUnlessExisting,
|
|
310
|
+
resolvePasswordValue
|
|
311
|
+
} = promptApi;
|
|
312
|
+
const existing = await getExistingCodexApiConfig(paths);
|
|
313
|
+
const providerId = existing.providerId || "abelworkflow";
|
|
314
|
+
const providerName = existing.providerName || providerId;
|
|
315
|
+
const baseUrlInput = await p.text({
|
|
316
|
+
message: "Codex Base URL",
|
|
317
|
+
initialValue: existing.baseUrl,
|
|
318
|
+
validate: required()
|
|
319
|
+
});
|
|
320
|
+
assertNotCancelled(baseUrlInput);
|
|
321
|
+
const baseUrl = normalizeOpenAiBaseUrl(baseUrlInput);
|
|
322
|
+
|
|
323
|
+
const apiKey = await p.password(passwordPromptOptions(
|
|
324
|
+
"Codex 第三方 API Key",
|
|
325
|
+
existing.apiKey,
|
|
326
|
+
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
|
+
};
|