abelworkflow 1.1.3 → 1.2.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/LICENSE +21 -0
- package/README.md +30 -29
- package/extensions/gpt-responses-compat.ts +213 -0
- package/extensions/k2think-compat.ts +40 -0
- package/lib/cli/args.mjs +5 -19
- package/lib/cli/main.mjs +105 -72
- package/lib/cli/prompts.mjs +9 -2
- package/lib/config/dotenv.mjs +1 -1
- package/lib/config/jsonc.mjs +1 -1
- package/lib/config/store.mjs +197 -45
- package/lib/config/toml.mjs +147 -453
- package/lib/installer/assets.mjs +67 -45
- package/lib/installer/install.mjs +202 -45
- package/lib/installer/links.mjs +124 -101
- package/lib/installer/lock.mjs +82 -0
- package/lib/installer/state.mjs +14 -21
- package/lib/paths.mjs +9 -9
- package/lib/providers/claude.mjs +58 -114
- package/lib/providers/codex.mjs +101 -198
- package/lib/providers/pi.mjs +331 -496
- package/lib/providers/skills.mjs +88 -68
- package/lib/providers/url.mjs +35 -0
- package/lib/templates/workflow/commands/abel-design.md +23 -161
- package/lib/templates/workflow/commands/abel-diagnose.md +30 -56
- package/lib/templates/workflow/commands/abel-implement.md +30 -150
- package/lib/templates/workflow/commands/abel-init.md +1 -1
- package/lib/tools/cli-installer.mjs +29 -190
- package/package.json +7 -9
- package/skills/context7-auto-research/context7-api.cjs +75 -17
- package/skills/dev-browser/SKILL.md +15 -1
- package/skills/dev-browser/dist/scripts/start.d.ts +0 -1
- package/skills/dev-browser/dist/scripts/start.js +8 -36
- package/skills/dev-browser/dist/src/client.d.ts +3 -4
- package/skills/dev-browser/dist/src/client.js +117 -117
- package/skills/dev-browser/dist/src/entrypoint.d.ts +1 -1
- package/skills/dev-browser/dist/src/entrypoint.js +38 -31
- package/skills/dev-browser/dist/src/index.d.ts +0 -1
- package/skills/dev-browser/dist/src/index.js +0 -1
- package/skills/dev-browser/dist/src/page-api.d.ts +0 -1
- package/skills/dev-browser/dist/src/page-api.js +0 -1
- package/skills/dev-browser/dist/src/relay.d.ts +0 -1
- package/skills/dev-browser/dist/src/relay.js +53 -7
- package/skills/dev-browser/dist/src/runtime.d.ts +3 -14
- package/skills/dev-browser/dist/src/runtime.js +5 -15
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +0 -1
- package/skills/dev-browser/dist/src/snapshot/browser-script.js +0 -1
- package/skills/dev-browser/dist/src/snapshot/index.d.ts +0 -1
- package/skills/dev-browser/dist/src/snapshot/index.js +0 -1
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts +0 -1
- package/skills/dev-browser/dist/src/snapshot/inject.js +0 -1
- package/skills/dev-browser/dist/src/standalone.d.ts +0 -1
- package/skills/dev-browser/dist/src/standalone.js +2 -3
- package/skills/dev-browser/dist/src/startup.d.ts +3 -12
- package/skills/dev-browser/dist/src/startup.js +6 -10
- package/skills/dev-browser/dist/src/target-registry.d.ts +0 -1
- package/skills/dev-browser/dist/src/target-registry.js +0 -1
- package/skills/dev-browser/dist/src/types.d.ts +0 -1
- package/skills/dev-browser/dist/src/types.js +0 -1
- package/skills/dev-browser/package-lock.json +1 -1
- package/skills/dev-browser/package.json +2 -2
- package/skills/grok-search/.env.example +1 -1
- package/skills/grok-search/SKILL.md +13 -16
- package/skills/grok-search/scripts/_dotenv.py +1 -1
- package/skills/grok-search/scripts/groksearch_cli.py +271 -195
- package/skills/time/SKILL.md +5 -2
- package/skills/time/scripts/time_cli.py +61 -22
- package/extensions/pi-gpt-responses-compat/index.ts +0 -25
- package/lib/templates/codex/config-base.toml +0 -103
- package/skills/dev-browser/dist/scripts/start.d.ts.map +0 -1
- package/skills/dev-browser/dist/scripts/start.js.map +0 -1
- package/skills/dev-browser/dist/src/client.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/client.js.map +0 -1
- package/skills/dev-browser/dist/src/entrypoint.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/entrypoint.js.map +0 -1
- package/skills/dev-browser/dist/src/index.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/index.js.map +0 -1
- package/skills/dev-browser/dist/src/page-api.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/page-api.js.map +0 -1
- package/skills/dev-browser/dist/src/relay.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/relay.js.map +0 -1
- package/skills/dev-browser/dist/src/runtime.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/runtime.js.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/browser-script.js.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/index.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/index.js.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/inject.js.map +0 -1
- package/skills/dev-browser/dist/src/standalone.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/standalone.js.map +0 -1
- package/skills/dev-browser/dist/src/startup.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/startup.js.map +0 -1
- package/skills/dev-browser/dist/src/target-registry.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/target-registry.js.map +0 -1
- package/skills/dev-browser/dist/src/types.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/types.js.map +0 -1
- package/skills/grok-search/gitignore.template +0 -4
- package/skills/grok-search/scripts/groksearch_entry.py +0 -131
package/lib/providers/claude.mjs
CHANGED
|
@@ -1,161 +1,105 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
|
10
|
-
"CLAUDE_CODE_SUBAGENT_MODEL"
|
|
11
|
-
];
|
|
12
|
-
const defaultClaudeSettings = {
|
|
13
|
-
$schema: "https://json.schemastore.org/claude-code-settings.json",
|
|
14
|
-
env: {
|
|
15
|
-
DISABLE_TELEMETRY: "1",
|
|
16
|
-
DISABLE_ERROR_REPORTING: "1",
|
|
17
|
-
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
|
|
18
|
-
},
|
|
19
|
-
includeCoAuthoredBy: false,
|
|
20
|
-
permissions: {
|
|
21
|
-
allow: [],
|
|
22
|
-
deny: [],
|
|
23
|
-
defaultMode: "bypassPermissions"
|
|
24
|
-
},
|
|
25
|
-
hooks: {},
|
|
26
|
-
alwaysThinkingEnabled: true,
|
|
27
|
-
language: "Chinese"
|
|
28
|
-
};
|
|
29
|
-
function buildDefaultClaudeSettings() {
|
|
30
|
-
return {
|
|
31
|
-
...defaultClaudeSettings,
|
|
32
|
-
env: { ...defaultClaudeSettings.env },
|
|
33
|
-
permissions: {
|
|
34
|
-
...defaultClaudeSettings.permissions,
|
|
35
|
-
allow: [...defaultClaudeSettings.permissions.allow],
|
|
36
|
-
deny: [...defaultClaudeSettings.permissions.deny]
|
|
37
|
-
},
|
|
38
|
-
hooks: { ...defaultClaudeSettings.hooks }
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
function mergeClaudeSettingsWithDefaults(settings) {
|
|
42
|
-
const defaults = buildDefaultClaudeSettings();
|
|
43
|
-
const env = settings?.env && typeof settings.env === "object" ? settings.env : {};
|
|
44
|
-
const permissions = settings?.permissions && typeof settings.permissions === "object" ? settings.permissions : {};
|
|
45
|
-
return {
|
|
46
|
-
...defaults,
|
|
47
|
-
...settings,
|
|
48
|
-
env: {
|
|
49
|
-
...defaults.env,
|
|
50
|
-
...env
|
|
51
|
-
},
|
|
52
|
-
permissions: {
|
|
53
|
-
...defaults.permissions,
|
|
54
|
-
...permissions,
|
|
55
|
-
allow: Array.isArray(permissions.allow) ? permissions.allow : defaults.permissions.allow,
|
|
56
|
-
deny: Array.isArray(permissions.deny) ? permissions.deny : defaults.permissions.deny
|
|
57
|
-
},
|
|
58
|
-
hooks: settings?.hooks && typeof settings.hooks === "object" ? settings.hooks : defaults.hooks
|
|
59
|
-
};
|
|
60
|
-
}
|
|
2
|
+
import {
|
|
3
|
+
assertPlainObject,
|
|
4
|
+
readJsonFileSafe,
|
|
5
|
+
withFileTransaction
|
|
6
|
+
} from "../config/store.mjs";
|
|
7
|
+
import { maskSecret, pathToLabel } from "../paths.mjs";
|
|
8
|
+
import { normalizeHttpBaseUrl } from "./url.mjs";
|
|
61
9
|
|
|
62
10
|
function getExistingClaudeApiConfig(settings) {
|
|
63
|
-
|
|
11
|
+
assertPlainObject(settings, "Claude settings");
|
|
12
|
+
const env = settings.env === undefined ? {} : assertPlainObject(settings.env, "Claude settings env");
|
|
64
13
|
return {
|
|
65
14
|
baseUrl: env.ANTHROPIC_BASE_URL || "https://api.anthropic.com",
|
|
66
15
|
key: env.ANTHROPIC_API_KEY || "",
|
|
67
|
-
model:
|
|
16
|
+
model: typeof env.ANTHROPIC_MODEL === "string" ? env.ANTHROPIC_MODEL : ""
|
|
68
17
|
};
|
|
69
18
|
}
|
|
70
19
|
|
|
71
|
-
function ensureApprovedClaudeApiKey(config, apiKey) {
|
|
72
|
-
if (!apiKey) {
|
|
73
|
-
return config;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const truncated = apiKey.slice(0, 20);
|
|
77
|
-
if (!config.customApiKeyResponses || typeof config.customApiKeyResponses !== "object") {
|
|
78
|
-
config.customApiKeyResponses = { approved: [], rejected: [] };
|
|
79
|
-
}
|
|
80
|
-
if (!Array.isArray(config.customApiKeyResponses.approved)) {
|
|
81
|
-
config.customApiKeyResponses.approved = [];
|
|
82
|
-
}
|
|
83
|
-
if (!Array.isArray(config.customApiKeyResponses.rejected)) {
|
|
84
|
-
config.customApiKeyResponses.rejected = [];
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
config.customApiKeyResponses.rejected = config.customApiKeyResponses.rejected.filter((item) => item !== truncated);
|
|
88
|
-
if (!config.customApiKeyResponses.approved.includes(truncated)) {
|
|
89
|
-
config.customApiKeyResponses.approved.push(truncated);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return config;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
20
|
function buildClaudeApiSettings(settings, {
|
|
96
21
|
baseUrl,
|
|
97
22
|
key,
|
|
98
23
|
model
|
|
99
24
|
}) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
nextSettings
|
|
25
|
+
assertPlainObject(settings, "Claude settings");
|
|
26
|
+
const env = settings.env === undefined ? {} : assertPlainObject(settings.env, "Claude settings env");
|
|
27
|
+
const nextSettings = {
|
|
28
|
+
...settings,
|
|
29
|
+
env: {
|
|
30
|
+
...env,
|
|
31
|
+
ANTHROPIC_BASE_URL: normalizeHttpBaseUrl(baseUrl),
|
|
32
|
+
ANTHROPIC_API_KEY: key,
|
|
33
|
+
ANTHROPIC_MODEL: String(model).trim()
|
|
34
|
+
}
|
|
35
|
+
};
|
|
103
36
|
delete nextSettings.env.ANTHROPIC_AUTH_TOKEN;
|
|
104
|
-
for (const field of claudeModelEnvKeys) {
|
|
105
|
-
nextSettings.env[field] = model;
|
|
106
|
-
}
|
|
107
37
|
return nextSettings;
|
|
108
38
|
}
|
|
109
39
|
|
|
110
|
-
async function persistClaudeConfiguration(paths,
|
|
111
|
-
|
|
112
|
-
|
|
40
|
+
async function persistClaudeConfiguration(paths, intent) {
|
|
41
|
+
return withFileTransaction([{
|
|
42
|
+
path: paths.claudeSettingsPath,
|
|
43
|
+
safeRoot: paths.homeDir,
|
|
44
|
+
sensitive: true
|
|
45
|
+
}], async (transaction) => {
|
|
46
|
+
const settings = transaction.readJson(paths.claudeSettingsPath, {});
|
|
47
|
+
assertPlainObject(settings, "Claude settings");
|
|
48
|
+
await transaction.writeJson(
|
|
49
|
+
paths.claudeSettingsPath,
|
|
50
|
+
buildClaudeApiSettings(settings, intent)
|
|
51
|
+
);
|
|
52
|
+
});
|
|
113
53
|
}
|
|
114
54
|
|
|
115
|
-
async function configureClaudeApi(paths =
|
|
116
|
-
const
|
|
117
|
-
const
|
|
55
|
+
async function configureClaudeApi(paths, promptApi, runtime = {}) {
|
|
56
|
+
const text = runtime.text ?? p.text;
|
|
57
|
+
const log = runtime.log ?? p.log;
|
|
58
|
+
const { assertNotCancelled, passwordOrExisting } = promptApi;
|
|
59
|
+
const settings = await readJsonFileSafe(paths.claudeSettingsPath, {}, {
|
|
60
|
+
safeRoot: paths.homeDir,
|
|
61
|
+
sensitive: true
|
|
62
|
+
});
|
|
63
|
+
assertPlainObject(settings, "Claude settings");
|
|
118
64
|
const existing = getExistingClaudeApiConfig(settings);
|
|
119
65
|
|
|
120
|
-
const
|
|
66
|
+
const baseUrlInput = await text({
|
|
121
67
|
message: "Claude Code Base URL",
|
|
122
68
|
initialValue: existing.baseUrl,
|
|
123
|
-
validate:
|
|
69
|
+
validate: (value) => {
|
|
70
|
+
try {
|
|
71
|
+
normalizeHttpBaseUrl(value);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
return error.message;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
124
76
|
});
|
|
125
|
-
assertNotCancelled(
|
|
77
|
+
assertNotCancelled(baseUrlInput);
|
|
78
|
+
const baseUrl = normalizeHttpBaseUrl(baseUrlInput);
|
|
126
79
|
|
|
127
80
|
const finalKey = await passwordOrExisting({
|
|
128
81
|
message: "Claude Code API Key",
|
|
129
82
|
existingValue: existing.key
|
|
130
83
|
});
|
|
131
84
|
|
|
132
|
-
const model = await
|
|
85
|
+
const model = await text({
|
|
133
86
|
message: "Claude Code 模型",
|
|
134
87
|
initialValue: existing.model || undefined,
|
|
135
|
-
validate:
|
|
88
|
+
validate: (value) => String(value || "").trim() ? undefined : "不能为空"
|
|
136
89
|
});
|
|
137
90
|
assertNotCancelled(model);
|
|
138
91
|
|
|
139
|
-
|
|
92
|
+
await persistClaudeConfiguration(paths, {
|
|
140
93
|
baseUrl,
|
|
141
94
|
key: finalKey,
|
|
142
|
-
model
|
|
143
|
-
});
|
|
144
|
-
const metaConfig = await readJsonFileSafe(paths.claudeMetaConfigPath, {}, { sensitive: true });
|
|
145
|
-
metaConfig.hasCompletedOnboarding = true;
|
|
146
|
-
ensureApprovedClaudeApiKey(metaConfig, finalKey);
|
|
147
|
-
await persistClaudeConfiguration(paths, {
|
|
148
|
-
settings: apiSettings,
|
|
149
|
-
metaConfig
|
|
95
|
+
model: String(model).trim()
|
|
150
96
|
});
|
|
151
97
|
|
|
152
|
-
|
|
98
|
+
log.step(`已更新 ${pathToLabel(paths.claudeSettingsPath, paths.homeDir)} (${maskSecret(finalKey)})`);
|
|
153
99
|
}
|
|
154
100
|
|
|
155
101
|
export {
|
|
156
102
|
buildClaudeApiSettings,
|
|
157
|
-
buildDefaultClaudeSettings,
|
|
158
103
|
configureClaudeApi,
|
|
159
|
-
mergeClaudeSettingsWithDefaults,
|
|
160
104
|
persistClaudeConfiguration
|
|
161
105
|
};
|
package/lib/providers/codex.mjs
CHANGED
|
@@ -2,128 +2,38 @@ import { lstat, mkdir, readFile, readdir, unlink } from "node:fs/promises";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import * as p from "@clack/prompts";
|
|
4
4
|
import {
|
|
5
|
+
assertPlainObject,
|
|
6
|
+
assertSafeFilePath,
|
|
5
7
|
pathExists,
|
|
6
8
|
readJsonFileSafe,
|
|
7
|
-
|
|
9
|
+
withFileTransaction,
|
|
8
10
|
writeText
|
|
9
11
|
} from "../config/store.mjs";
|
|
10
12
|
import {
|
|
11
|
-
buildTomlSection,
|
|
12
|
-
detectLineEnding,
|
|
13
|
-
extractTopLevelTomlEntries,
|
|
14
|
-
getTomlSectionFieldNames,
|
|
15
13
|
formatTomlKeySegment,
|
|
16
|
-
|
|
17
|
-
parseTomlSection,
|
|
18
|
-
readTopLevelTomlString,
|
|
19
|
-
removeTomlSection,
|
|
14
|
+
parseToml,
|
|
20
15
|
removeTomlSectionField,
|
|
21
16
|
removeTopLevelTomlField,
|
|
22
17
|
updateTomlSectionFields,
|
|
23
18
|
updateTopLevelTomlField
|
|
24
19
|
} from "../config/toml.mjs";
|
|
25
|
-
import {
|
|
20
|
+
import { maskSecret, pathToLabel } from "../paths.mjs";
|
|
26
21
|
import { hashBytes, isManagedCodexAgentFileEntry } from "../utils.mjs";
|
|
27
|
-
import { normalizeOpenAiBaseUrl } from "./
|
|
22
|
+
import { normalizeOpenAiBaseUrl } from "./url.mjs";
|
|
28
23
|
|
|
29
24
|
const CODEX_ENV_KEY = "OPENAI_API_KEY";
|
|
25
|
+
const CODEX_PROVIDER_ID = "abelworkflow";
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const publishedCodexAgentHashes = {
|
|
36
|
-
"default.toml": new Set([
|
|
37
|
-
"7add720eb7ed030871088fe9f994a7e34a2b3cae1f898fe7f97c4e302f4a1844",
|
|
38
|
-
"72d3da0275f4add64ff05da94f391443e8e4b5c43b646e0f89f2d17fa4345cb4",
|
|
39
|
-
"7dc002559f5d78534e70c7c4683d51a48ffe94725c199855a35f61124aead876"
|
|
40
|
-
]),
|
|
41
|
-
"explorer.toml": new Set([
|
|
42
|
-
"e4e9c4eed4c028dded095b4cf36cb0ac408abcd86b9ab7c139440e1c58355644",
|
|
43
|
-
"a236d7b586c4e312a62bb8b36115fe9780e28502c0af160ce5cd45b67c5b8479",
|
|
44
|
-
"4740f13e9a478528dcb097188997445efa7607b1b07ae64774b1dd26fcd6f853"
|
|
45
|
-
]),
|
|
46
|
-
"planner.toml": new Set([
|
|
47
|
-
"2b16f821b14224cdfc9dab1fe14c41b9ff2b63642b0d9b340ef84b67a753a3b7",
|
|
48
|
-
"741c9dfce5085d6bdc49f6e5004475974d54c2bde95c0b589eda86a63e96e58f",
|
|
49
|
-
"7fde442a5ba5e996936b4ec853b036ebbb9f06ee9baef6540124d081c7107196"
|
|
50
|
-
]),
|
|
51
|
-
"reviewer.toml": new Set([
|
|
52
|
-
"08dcf8f5fee281f3b6120b63559086b7c675a619be72f761de4add711543b353",
|
|
53
|
-
"9455d0189f324f7c606f7ae94ca338e9bd5c468f072188ac1f18e3749c918cd1",
|
|
54
|
-
"a7292a533c9146bf5a2eb292cbfe889ca47bf4808324cddc1732c3c1721d437c"
|
|
55
|
-
]),
|
|
56
|
-
"worker.toml": new Set([
|
|
57
|
-
"13533715464b1f58a9c311e16dc3a29e92ecb95793fd8aff69a5ca731ae46ac8",
|
|
58
|
-
"ea12b5b6173eb8fcaa57a8b447b1f81b35a6ac59e75025b88198c3f85175922c",
|
|
59
|
-
"bfbc214e7b582774de759b118544c3f1fb61e1284d6ffd6a61ab772d28b6eaa0"
|
|
60
|
-
])
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
function stripCodexSubagentDefaults(content) {
|
|
64
|
-
const lineEnding = detectLineEnding(content);
|
|
65
|
-
let nextContent = removeTopLevelTomlField(content, "approvals_reviewer");
|
|
66
|
-
nextContent = removeTopLevelTomlField(nextContent, "developer_instructions");
|
|
67
|
-
nextContent = removeTomlSection(nextContent, "agents");
|
|
68
|
-
|
|
69
|
-
const featureValues = parseTomlSection(nextContent, "features");
|
|
70
|
-
delete featureValues.multi_agent;
|
|
71
|
-
delete featureValues.guardian_approval;
|
|
72
|
-
nextContent = removeTomlSection(nextContent, "features");
|
|
73
|
-
|
|
74
|
-
if (Object.keys(featureValues).length) {
|
|
75
|
-
const featuresSection = buildTomlSection("features", featureValues, lineEnding).trimEnd();
|
|
76
|
-
nextContent = nextContent.trimEnd()
|
|
77
|
-
? `${nextContent.trimEnd()}${lineEnding}${lineEnding}${featuresSection}${lineEnding}`
|
|
78
|
-
: `${featuresSection}${lineEnding}`;
|
|
27
|
+
function assertTomlTable(value, name) {
|
|
28
|
+
if (value === undefined) return {};
|
|
29
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
30
|
+
throw new Error(`${name} must be a TOML table`);
|
|
79
31
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function mergeCodexTemplateDefaults(content, templateContent) {
|
|
85
|
-
const defaultTopLevelFields = new Set([
|
|
86
|
-
"personality",
|
|
87
|
-
"disable_response_storage",
|
|
88
|
-
"approvals_reviewer",
|
|
89
|
-
"approval_policy",
|
|
90
|
-
"sandbox_mode",
|
|
91
|
-
"service_tier",
|
|
92
|
-
"model",
|
|
93
|
-
"model_reasoning_effort",
|
|
94
|
-
"developer_instructions"
|
|
95
|
-
]);
|
|
96
|
-
const templateEntries = extractTopLevelTomlEntries(templateContent)
|
|
97
|
-
.filter(({ field }) => defaultTopLevelFields.has(field));
|
|
98
|
-
let nextContent = mergeMissingTopLevelTomlEntries(content, templateEntries);
|
|
99
|
-
const currentDeveloperInstructions = readTopLevelTomlString(nextContent, "developer_instructions");
|
|
100
|
-
const templateDeveloperInstructions = readTopLevelTomlString(templateContent, "developer_instructions");
|
|
101
|
-
if (templateDeveloperInstructions && publishedCodexDeveloperInstructionHashes.has(
|
|
102
|
-
hashBytes(currentDeveloperInstructions)
|
|
103
|
-
)) {
|
|
104
|
-
nextContent = updateTopLevelTomlField(
|
|
105
|
-
nextContent,
|
|
106
|
-
"developer_instructions",
|
|
107
|
-
templateDeveloperInstructions
|
|
108
|
-
);
|
|
32
|
+
const prototype = Object.getPrototypeOf(value);
|
|
33
|
+
if (prototype !== null && prototype !== Object.prototype) {
|
|
34
|
+
throw new Error(`${name} must be a TOML table`);
|
|
109
35
|
}
|
|
110
|
-
|
|
111
|
-
for (const sectionName of ["agents", "features"]) {
|
|
112
|
-
const templateValues = parseTomlSection(templateContent, sectionName);
|
|
113
|
-
const currentFields = new Set(getTomlSectionFieldNames(nextContent, sectionName));
|
|
114
|
-
const missingValues = Object.fromEntries(
|
|
115
|
-
Object.entries(templateValues).filter(([field]) => !currentFields.has(field))
|
|
116
|
-
);
|
|
117
|
-
if (Object.keys(missingValues).length) {
|
|
118
|
-
nextContent = updateTomlSectionFields(nextContent, sectionName, missingValues);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return nextContent;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async function loadBundledCodexConfigTemplate(paths = defaultPaths) {
|
|
126
|
-
return readFile(paths.codexTemplateConfigPath, "utf8");
|
|
36
|
+
return value;
|
|
127
37
|
}
|
|
128
38
|
|
|
129
39
|
async function readCodexAgentTarget(path) {
|
|
@@ -172,11 +82,7 @@ async function ensureCodexAgentContainer(homeDir) {
|
|
|
172
82
|
return targetDir;
|
|
173
83
|
}
|
|
174
84
|
|
|
175
|
-
function
|
|
176
|
-
return publishedCodexAgentHashes[name]?.has(hash) ?? false;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
async function deployBundledCodexAgents(paths = defaultPaths, previousManagedFiles = {}) {
|
|
85
|
+
async function deployBundledCodexAgents(paths, previousManagedFiles = {}) {
|
|
180
86
|
const targetDir = await ensureCodexAgentContainer(paths.homeDir);
|
|
181
87
|
const entries = (await readdir(paths.codexTemplateAgentsPath, { withFileTypes: true }))
|
|
182
88
|
.filter((entry) => entry.isFile() && entry.name.endsWith(".toml"))
|
|
@@ -203,10 +109,7 @@ async function deployBundledCodexAgents(paths = defaultPaths, previousManagedFil
|
|
|
203
109
|
result.created.push(target);
|
|
204
110
|
} else if (currentHash === sourceHash) {
|
|
205
111
|
result.unchanged.push(target);
|
|
206
|
-
} else if (current.content &&
|
|
207
|
-
previousManagedFiles?.[name] === currentHash
|
|
208
|
-
|| isPublishedCodexAgent(name, currentHash)
|
|
209
|
-
)) {
|
|
112
|
+
} else if (current.content && previousManagedFiles?.[name] === currentHash) {
|
|
210
113
|
await writeText(target, sourceContent, { backupLimit: 0 });
|
|
211
114
|
result.updated.push(target);
|
|
212
115
|
} else {
|
|
@@ -240,10 +143,6 @@ async function deployBundledCodexAgents(paths = defaultPaths, previousManagedFil
|
|
|
240
143
|
return result;
|
|
241
144
|
}
|
|
242
145
|
|
|
243
|
-
function getCodexProviderSectionName(providerId) {
|
|
244
|
-
return `model_providers.${formatTomlKeySegment(providerId)}`;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
146
|
function assertCodexAuthKeyAvailable(auth, envKey) {
|
|
248
147
|
if (
|
|
249
148
|
auth
|
|
@@ -257,46 +156,80 @@ function assertCodexAuthKeyAvailable(auth, envKey) {
|
|
|
257
156
|
}
|
|
258
157
|
|
|
259
158
|
function resolveExistingCodexApiConfig(content, auth = {}) {
|
|
260
|
-
const
|
|
261
|
-
|
|
159
|
+
const config = parseToml(content);
|
|
160
|
+
assertPlainObject(auth, "Codex auth");
|
|
161
|
+
const providers = assertTomlTable(config.model_providers, "model_providers");
|
|
162
|
+
const provider = assertTomlTable(
|
|
163
|
+
providers[CODEX_PROVIDER_ID],
|
|
164
|
+
`model_providers.${CODEX_PROVIDER_ID}`
|
|
165
|
+
);
|
|
262
166
|
const envKey = CODEX_ENV_KEY;
|
|
263
167
|
assertCodexAuthKeyAvailable(auth, envKey);
|
|
264
168
|
const apiKey = typeof auth[envKey] === "string" ? auth[envKey] : "";
|
|
265
169
|
|
|
266
170
|
return {
|
|
267
|
-
providerId,
|
|
268
|
-
providerName: provider.name
|
|
269
|
-
baseUrl: provider.base_url
|
|
171
|
+
providerId: CODEX_PROVIDER_ID,
|
|
172
|
+
providerName: typeof provider.name === "string" && provider.name ? provider.name : CODEX_PROVIDER_ID,
|
|
173
|
+
baseUrl: typeof provider.base_url === "string" && provider.base_url
|
|
174
|
+
? provider.base_url
|
|
175
|
+
: "https://api.openai.com/v1",
|
|
270
176
|
envKey,
|
|
271
177
|
apiKey
|
|
272
178
|
};
|
|
273
179
|
}
|
|
274
180
|
|
|
275
|
-
async function getExistingCodexApiConfig(paths
|
|
181
|
+
async function getExistingCodexApiConfig(paths) {
|
|
182
|
+
await assertSafeFilePath(paths.codexConfigPath, paths.homeDir);
|
|
276
183
|
const content = await pathExists(paths.codexConfigPath) ? await readFile(paths.codexConfigPath, "utf8") : "";
|
|
277
|
-
const auth = await readJsonFileSafe(paths.codexAuthPath, {}, {
|
|
184
|
+
const auth = await readJsonFileSafe(paths.codexAuthPath, {}, {
|
|
185
|
+
safeRoot: paths.homeDir,
|
|
186
|
+
sensitive: true
|
|
187
|
+
});
|
|
278
188
|
return resolveExistingCodexApiConfig(content, auth);
|
|
279
189
|
}
|
|
280
190
|
|
|
281
|
-
async function persistCodexConfiguration(paths,
|
|
282
|
-
|
|
283
|
-
|
|
191
|
+
async function persistCodexConfiguration(paths, intent, operations = {}) {
|
|
192
|
+
return withFileTransaction([
|
|
193
|
+
{ path: paths.codexConfigPath, safeRoot: paths.homeDir },
|
|
194
|
+
{ path: paths.codexAuthPath, safeRoot: paths.homeDir, sensitive: true }
|
|
195
|
+
], async (transaction) => {
|
|
196
|
+
const currentContent = transaction.readText(paths.codexConfigPath);
|
|
197
|
+
const currentAuth = transaction.readJson(paths.codexAuthPath, {});
|
|
198
|
+
assertPlainObject(currentAuth, "Codex auth");
|
|
199
|
+
const current = resolveExistingCodexApiConfig(currentContent, currentAuth);
|
|
200
|
+
const content = buildCodexConfigContent(currentContent, {
|
|
201
|
+
providerName: current.providerName,
|
|
202
|
+
baseUrl: intent.baseUrl
|
|
203
|
+
});
|
|
204
|
+
const auth = mergeCodexAuthData(currentAuth, intent.apiKey);
|
|
205
|
+
await transaction.writeText(paths.codexConfigPath, content, {
|
|
206
|
+
...(operations.writeConfig ? { writer: operations.writeConfig } : {})
|
|
207
|
+
});
|
|
208
|
+
await transaction.writeJson(paths.codexAuthPath, auth, {
|
|
209
|
+
...(operations.writeAuth ? { writer: operations.writeAuth } : {})
|
|
210
|
+
});
|
|
211
|
+
});
|
|
284
212
|
}
|
|
285
213
|
|
|
286
|
-
async function configureCodexApi(paths
|
|
214
|
+
async function configureCodexApi(paths, promptApi, ownership = {}, runtime = {}) {
|
|
215
|
+
const text = runtime.text ?? p.text;
|
|
216
|
+
const log = runtime.log ?? p.log;
|
|
287
217
|
const {
|
|
288
218
|
assertNotCancelled,
|
|
289
219
|
confirmOrCancel,
|
|
290
|
-
passwordOrExisting
|
|
291
|
-
required
|
|
220
|
+
passwordOrExisting
|
|
292
221
|
} = promptApi;
|
|
293
222
|
const existing = await getExistingCodexApiConfig(paths);
|
|
294
|
-
const
|
|
295
|
-
const providerName = existing.providerName || providerId;
|
|
296
|
-
const baseUrlInput = await p.text({
|
|
223
|
+
const baseUrlInput = await text({
|
|
297
224
|
message: "Codex Base URL",
|
|
298
225
|
initialValue: existing.baseUrl,
|
|
299
|
-
validate:
|
|
226
|
+
validate: (value) => {
|
|
227
|
+
try {
|
|
228
|
+
normalizeOpenAiBaseUrl(value);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
return error.message;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
300
233
|
});
|
|
301
234
|
assertNotCancelled(baseUrlInput);
|
|
302
235
|
const baseUrl = normalizeOpenAiBaseUrl(baseUrlInput);
|
|
@@ -309,97 +242,69 @@ async function configureCodexApi(paths = defaultPaths, promptApi, ownership = {}
|
|
|
309
242
|
const shouldDeploySubagents = await confirmOrCancel({ message: "是否部署 Codex subagents 配置?", initialValue: true });
|
|
310
243
|
let managedCodexAgentFiles = { ...(ownership.managedCodexAgentFiles ?? {}) };
|
|
311
244
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const templateContent = await loadBundledCodexConfigTemplate(paths);
|
|
315
|
-
const content = buildCodexConfigContent(currentContent, {
|
|
316
|
-
templateContent,
|
|
317
|
-
mergeMissingTemplateDefaults: true,
|
|
318
|
-
includeSubagentDefaults: shouldDeploySubagents,
|
|
319
|
-
providerId,
|
|
320
|
-
providerName,
|
|
321
|
-
baseUrl
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
const auth = mergeCodexAuthData(
|
|
325
|
-
await readJsonFileSafe(paths.codexAuthPath, {}, { sensitive: true }),
|
|
326
|
-
envKey,
|
|
327
|
-
finalApiKey,
|
|
328
|
-
ownership.managedAuthKeys ?? []
|
|
329
|
-
);
|
|
330
|
-
await persistCodexConfiguration(paths, { content, auth });
|
|
245
|
+
await ownership.guard?.();
|
|
246
|
+
await persistCodexConfiguration(paths, { baseUrl, apiKey: finalApiKey });
|
|
331
247
|
|
|
332
|
-
|
|
333
|
-
|
|
248
|
+
log.step(`已更新 ${pathToLabel(paths.codexConfigPath, paths.homeDir)} (${CODEX_PROVIDER_ID})`);
|
|
249
|
+
log.step(`已更新 ${pathToLabel(paths.codexAuthPath, paths.homeDir)} (${maskSecret(finalApiKey)})`);
|
|
334
250
|
if (String(baseUrl).startsWith("https://") && !process.env.CODEX_CA_CERTIFICATE && !process.env.SSL_CERT_FILE) {
|
|
335
|
-
|
|
251
|
+
log.message("自签名证书需在启动 Codex 前设置 CODEX_CA_CERTIFICATE=/absolute/path/relay-ca.pem。");
|
|
336
252
|
}
|
|
337
253
|
if (shouldDeploySubagents) {
|
|
254
|
+
await ownership.guard?.();
|
|
338
255
|
const deployment = await deployBundledCodexAgents(paths, managedCodexAgentFiles);
|
|
339
256
|
managedCodexAgentFiles = deployment.managedFiles;
|
|
340
|
-
|
|
257
|
+
log.step(`Codex subagents 同步完成:新增 ${deployment.created.length},更新 ${deployment.updated.length},未变 ${deployment.unchanged.length}`);
|
|
341
258
|
if (deployment.conflicts.length) {
|
|
342
|
-
|
|
259
|
+
log.warn([
|
|
343
260
|
`检测到 ${deployment.conflicts.length} 个 Codex subagent 冲突,已保留原文件:`,
|
|
344
261
|
...deployment.conflicts.map((path) => `- ${pathToLabel(path, paths.homeDir)}`)
|
|
345
262
|
].join("\n"));
|
|
346
263
|
}
|
|
347
264
|
} else {
|
|
348
|
-
|
|
265
|
+
log.message("已跳过 Codex subagents 部署。");
|
|
349
266
|
}
|
|
350
267
|
return {
|
|
351
|
-
managedAuthKeys: finalApiKey ? [envKey] : [],
|
|
352
268
|
managedCodexAgentFiles
|
|
353
269
|
};
|
|
354
270
|
}
|
|
355
271
|
|
|
356
272
|
function buildCodexConfigContent(currentContent, {
|
|
357
|
-
templateContent = "",
|
|
358
|
-
mergeMissingTemplateDefaults = false,
|
|
359
|
-
includeSubagentDefaults = true,
|
|
360
|
-
providerId,
|
|
361
273
|
providerName,
|
|
362
274
|
baseUrl
|
|
363
275
|
}) {
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const lineEnding = detectLineEnding(content);
|
|
373
|
-
if (includeSubagentDefaults && readTopLevelTomlString(content, "approvals_reviewer") === "reviewer") {
|
|
374
|
-
content = updateTopLevelTomlField(content, "approvals_reviewer", "guardian_subagent");
|
|
375
|
-
}
|
|
376
|
-
const providerSectionName = getCodexProviderSectionName(providerId);
|
|
377
|
-
content = updateTopLevelTomlField(content, "model_provider", providerId);
|
|
276
|
+
const config = parseToml(currentContent);
|
|
277
|
+
const providers = assertTomlTable(config.model_providers, "model_providers");
|
|
278
|
+
const provider = assertTomlTable(
|
|
279
|
+
providers[CODEX_PROVIDER_ID],
|
|
280
|
+
`model_providers.${CODEX_PROVIDER_ID}`
|
|
281
|
+
);
|
|
282
|
+
const providerSection = `model_providers.${formatTomlKeySegment(CODEX_PROVIDER_ID)}`;
|
|
283
|
+
let content = updateTopLevelTomlField(currentContent, "model_provider", CODEX_PROVIDER_ID);
|
|
378
284
|
content = removeTopLevelTomlField(content, "preferred_auth_method");
|
|
379
285
|
content = removeTopLevelTomlField(content, "temp_env_key");
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
286
|
+
for (const field of ["env_key", "temp_env_key", "supports_websockets"]) {
|
|
287
|
+
content = removeTomlSectionField(content, providerSection, field);
|
|
288
|
+
}
|
|
289
|
+
content = updateTomlSectionFields(content, providerSection, {
|
|
290
|
+
name: providerName || CODEX_PROVIDER_ID,
|
|
291
|
+
base_url: normalizeOpenAiBaseUrl(baseUrl),
|
|
385
292
|
wire_api: "responses",
|
|
386
|
-
requires_openai_auth: true
|
|
387
|
-
supports_websockets: true
|
|
293
|
+
requires_openai_auth: true
|
|
388
294
|
});
|
|
389
|
-
|
|
295
|
+
parseToml(content);
|
|
296
|
+
return content;
|
|
390
297
|
}
|
|
391
298
|
|
|
392
|
-
function mergeCodexAuthData(auth,
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
delete nextAuth[managedAuthKey];
|
|
397
|
-
}
|
|
299
|
+
function mergeCodexAuthData(auth, apiKey) {
|
|
300
|
+
assertPlainObject(auth, "Codex auth");
|
|
301
|
+
const nextAuth = { ...auth };
|
|
302
|
+
assertCodexAuthKeyAvailable(nextAuth, CODEX_ENV_KEY);
|
|
398
303
|
if (apiKey) {
|
|
399
304
|
nextAuth.auth_mode = "apikey";
|
|
400
|
-
nextAuth[
|
|
305
|
+
nextAuth[CODEX_ENV_KEY] = apiKey;
|
|
401
306
|
} else {
|
|
402
|
-
delete nextAuth[
|
|
307
|
+
delete nextAuth[CODEX_ENV_KEY];
|
|
403
308
|
if (nextAuth.auth_mode === "apikey") delete nextAuth.auth_mode;
|
|
404
309
|
}
|
|
405
310
|
return nextAuth;
|
|
@@ -410,8 +315,6 @@ export {
|
|
|
410
315
|
configureCodexApi,
|
|
411
316
|
deployBundledCodexAgents,
|
|
412
317
|
mergeCodexAuthData,
|
|
413
|
-
mergeCodexTemplateDefaults,
|
|
414
318
|
persistCodexConfiguration,
|
|
415
|
-
resolveExistingCodexApiConfig
|
|
416
|
-
stripCodexSubagentDefaults
|
|
319
|
+
resolveExistingCodexApiConfig
|
|
417
320
|
};
|