abelworkflow 1.2.3 → 1.3.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.
Files changed (63) hide show
  1. package/README.md +26 -25
  2. package/extensions/gpt-responses-compat.ts +166 -27
  3. package/lib/cli/main.mjs +18 -86
  4. package/lib/cli/pi.mjs +43 -0
  5. package/lib/cli/prompts.mjs +3 -3
  6. package/lib/installer/assets.mjs +35 -31
  7. package/lib/installer/install.mjs +29 -9
  8. package/lib/installer/links.mjs +97 -84
  9. package/lib/paths.mjs +0 -2
  10. package/lib/providers/claude.mjs +14 -3
  11. package/lib/providers/codex.mjs +14 -9
  12. package/lib/providers/pi.mjs +61 -68
  13. package/lib/providers/skills.mjs +22 -22
  14. package/lib/providers/url.mjs +32 -1
  15. package/lib/templates/codex/agents/default.toml +16 -54
  16. package/lib/templates/codex/agents/explorer.toml +25 -52
  17. package/lib/templates/codex/agents/planner.toml +23 -77
  18. package/lib/templates/codex/agents/reviewer.toml +16 -62
  19. package/lib/templates/codex/agents/worker.toml +30 -64
  20. package/lib/templates/codex/config-base.toml +0 -6
  21. package/lib/templates/{workflow/gitignore.template → gitignore.template} +0 -3
  22. package/lib/tools/cli-installer.mjs +40 -12
  23. package/package.json +13 -18
  24. package/lib/templates/workflow/AGENTS.md +0 -50
  25. package/lib/templates/workflow/commands/abel-design.md +0 -175
  26. package/lib/templates/workflow/commands/abel-diagnose.md +0 -63
  27. package/lib/templates/workflow/commands/abel-implement.md +0 -170
  28. package/lib/templates/workflow/commands/abel-init.md +0 -25
  29. package/skills/dev-browser/SKILL.md +0 -281
  30. package/skills/dev-browser/dist/scripts/start.d.ts +0 -1
  31. package/skills/dev-browser/dist/scripts/start.js +0 -90
  32. package/skills/dev-browser/dist/src/client.d.ts +0 -92
  33. package/skills/dev-browser/dist/src/client.js +0 -310
  34. package/skills/dev-browser/dist/src/entrypoint.d.ts +0 -29
  35. package/skills/dev-browser/dist/src/entrypoint.js +0 -113
  36. package/skills/dev-browser/dist/src/index.d.ts +0 -3
  37. package/skills/dev-browser/dist/src/index.js +0 -1
  38. package/skills/dev-browser/dist/src/page-api.d.ts +0 -24
  39. package/skills/dev-browser/dist/src/page-api.js +0 -103
  40. package/skills/dev-browser/dist/src/relay.d.ts +0 -26
  41. package/skills/dev-browser/dist/src/relay.js +0 -567
  42. package/skills/dev-browser/dist/src/runtime.d.ts +0 -34
  43. package/skills/dev-browser/dist/src/runtime.js +0 -44
  44. package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +0 -22
  45. package/skills/dev-browser/dist/src/snapshot/browser-script.js +0 -868
  46. package/skills/dev-browser/dist/src/snapshot/index.d.ts +0 -13
  47. package/skills/dev-browser/dist/src/snapshot/index.js +0 -13
  48. package/skills/dev-browser/dist/src/snapshot/inject.d.ts +0 -12
  49. package/skills/dev-browser/dist/src/snapshot/inject.js +0 -12
  50. package/skills/dev-browser/dist/src/standalone.d.ts +0 -31
  51. package/skills/dev-browser/dist/src/standalone.js +0 -173
  52. package/skills/dev-browser/dist/src/startup.d.ts +0 -46
  53. package/skills/dev-browser/dist/src/startup.js +0 -77
  54. package/skills/dev-browser/dist/src/target-registry.d.ts +0 -28
  55. package/skills/dev-browser/dist/src/target-registry.js +0 -134
  56. package/skills/dev-browser/dist/src/types.d.ts +0 -26
  57. package/skills/dev-browser/dist/src/types.js +0 -1
  58. package/skills/dev-browser/package-lock.json +0 -1545
  59. package/skills/dev-browser/package.json +0 -35
  60. package/skills/dev-browser/references/scraping.md +0 -144
  61. package/skills/git-commit/SKILL.md +0 -124
  62. package/skills/time/SKILL.md +0 -119
  63. package/skills/time/scripts/time_cli.py +0 -143
@@ -19,12 +19,6 @@ const ignoredSkillPathPatterns = [
19
19
  /(^|\/)build(\/|$)/u
20
20
  ];
21
21
 
22
- const devBrowserRuntimePathPatterns = [
23
- /^dev-browser\/SKILL\.md$/u,
24
- /^dev-browser\/package[^/]*\.json$/u,
25
- /^dev-browser\/(?:dist|references)(?:\/|$)/u
26
- ];
27
-
28
22
  function normalizeRelativePath(path) {
29
23
  return path.replaceAll("\\", "/");
30
24
  }
@@ -72,13 +66,26 @@ function mapGitignoreTemplate(relativePath) {
72
66
  return relativePath.replace(/(^|\/)gitignore\.template$/u, "$1.gitignore");
73
67
  }
74
68
 
75
- function shouldCopySkillPath(skillsRoot, sourcePath) {
69
+ function selectedSkillNames(previousManagedFiles = {}, pathPrefix = null) {
70
+ const names = new Set();
71
+ if (typeof pathPrefix === "string" && pathPrefix.startsWith("skills/")) {
72
+ const selected = pathPrefix.slice("skills/".length).split("/")[0];
73
+ if (selected) names.add(selected);
74
+ }
75
+ for (const relativePath of Object.keys(previousManagedFiles)) {
76
+ if (!relativePath.startsWith("skills/")) continue;
77
+ const skillName = relativePath.slice("skills/".length).split("/")[0];
78
+ if (skillName) names.add(skillName);
79
+ }
80
+ return names;
81
+ }
82
+
83
+ function shouldCopySkillPath(skillsRoot, sourcePath, selectedSkills) {
76
84
  const relativePath = normalizeRelativePath(relative(skillsRoot, sourcePath));
77
85
  if (!relativePath) return true;
78
- if (relativePath === "dev-browser") return true;
79
- if (relativePath.startsWith("dev-browser/")) {
80
- return devBrowserRuntimePathPatterns.some((pattern) => pattern.test(relativePath));
81
- }
86
+ const skillName = relativePath.split("/")[0];
87
+ if (selectedSkills && !selectedSkills.has(skillName)) return false;
88
+ if (relativePath.startsWith("abel-")) return false;
82
89
  if (/(^|\/)dist(\/|$)/u.test(relativePath)) return false;
83
90
  return !ignoredSkillPathPatterns.some((pattern) => pattern.test(relativePath));
84
91
  }
@@ -115,32 +122,29 @@ async function collectFiles(root, outputPrefix, { filter, mapTargetPath } = {})
115
122
  }
116
123
 
117
124
  async function addAssetTree(manifest, root, outputPrefix, options = {}) {
118
- if (!(await pathTargetExists(root))) {
119
- if (options.required) throw new Error(`Missing managed asset root: ${root}`);
120
- return;
121
- }
125
+ if (!(await pathTargetExists(root))) return;
122
126
  for (const [key, value] of await collectFiles(root, outputPrefix, options)) manifest.set(key, value);
123
127
  }
124
128
 
125
- async function collectManagedAssets(paths) {
129
+ async function collectManagedAssets(paths, { previousManagedFiles = {}, pathPrefix = null } = {}) {
126
130
  const manifest = new Map();
127
- await addAssetTree(manifest, paths.workflowTemplateRoot, "", {
128
- required: true,
129
- mapTargetPath: mapGitignoreTemplate
130
- });
131
-
132
- const readmePath = join(paths.packageRoot, "README.md");
133
- if (!(await pathTargetExists(readmePath))) throw new Error(`Missing managed asset: ${readmePath}`);
134
- const readmeContent = await fs.readFile(readmePath);
135
- manifest.set("README.md", {
136
- content: readmeContent,
137
- hash: hashBytes(readmeContent),
138
- mode: (await fs.stat(readmePath)).mode & 0o777
139
- });
131
+ for (const [sourcePath, targetPath] of [
132
+ [join(paths.packageRoot, "README.md"), "README.md"],
133
+ [join(paths.packageRoot, "lib/templates/gitignore.template"), ".gitignore"]
134
+ ]) {
135
+ if (!(await pathTargetExists(sourcePath))) throw new Error(`Missing managed asset: ${sourcePath}`);
136
+ const content = await fs.readFile(sourcePath);
137
+ manifest.set(targetPath, {
138
+ content,
139
+ hash: hashBytes(content),
140
+ mode: (await fs.stat(sourcePath)).mode & 0o777
141
+ });
142
+ }
140
143
 
144
+ const selectedSkills = selectedSkillNames(previousManagedFiles, pathPrefix);
141
145
  const skillsRoot = join(paths.packageRoot, "skills");
142
146
  await addAssetTree(manifest, skillsRoot, "skills", {
143
- filter: shouldCopySkillPath,
147
+ filter: (root, sourcePath) => shouldCopySkillPath(root, sourcePath, selectedSkills),
144
148
  mapTargetPath: mapGitignoreTemplate
145
149
  });
146
150
  await addAssetTree(manifest, join(paths.packageRoot, "extensions"), "extensions");
@@ -264,7 +268,7 @@ async function syncManagedFiles({
264
268
  const packageVersion = await readPackageVersion(paths);
265
269
  const previousMetadata = await readInstallMetadata(paths);
266
270
  const previousManagedFiles = await readManagedFiles(paths, previousMetadata);
267
- const manifest = await collectManagedAssets(paths);
271
+ const manifest = await collectManagedAssets(paths, { previousManagedFiles, pathPrefix });
268
272
  const report = createInstallReport();
269
273
  const managedFiles = pathPrefix ? { ...previousManagedFiles } : {};
270
274
 
@@ -3,10 +3,9 @@ import { join } from "node:path";
3
3
  import { createPaths } from "../paths.mjs";
4
4
  import { readPackageVersion, syncManagedFiles } from "./assets.mjs";
5
5
  import {
6
- linkClaude,
7
- linkCodex,
8
6
  linkPi,
9
- linkPiExtensions
7
+ linkPiExtensions,
8
+ linkPiSkill
10
9
  } from "./links.mjs";
11
10
  import { withDeploymentLock } from "./lock.mjs";
12
11
  import {
@@ -139,9 +138,10 @@ async function installWorkflowLocked(paths, options, guard) {
139
138
  onResult: (result) => linkResults.push(result)
140
139
  };
141
140
  try {
142
- await linkClaude(paths, previousLinkedTargets, linkOptions);
143
- await linkCodex(paths, previousLinkedTargets, linkOptions);
144
- await linkPi(paths, previousLinkedTargets, linkOptions);
141
+ await linkPi(paths, previousLinkedTargets, {
142
+ ...linkOptions,
143
+ managedFiles: assetResult.managedFiles
144
+ });
145
145
  } catch (error) {
146
146
  await writeInstallState(
147
147
  paths,
@@ -171,6 +171,7 @@ async function installWorkflow(options) {
171
171
  async function ensureSkillPresentLocked(paths, validatedName, guard) {
172
172
  await guard();
173
173
  const previousMetadata = await readInstallMetadata(paths);
174
+ const previousLinkedTargets = previousMetadata.linkedTargets ?? {};
174
175
  const markIncomplete = onceAsync(() => writeIncompleteInstallState(paths, previousMetadata, guard));
175
176
  const beforeMutation = async () => {
176
177
  await guard();
@@ -188,16 +189,35 @@ async function ensureSkillPresentLocked(paths, validatedName, guard) {
188
189
  await markIncomplete();
189
190
  throw error;
190
191
  }
192
+ const linkResults = [];
193
+ try {
194
+ await linkPiSkill(paths, validatedName, previousLinkedTargets, {
195
+ force: false,
196
+ beforeMutation,
197
+ onResult: (result) => linkResults.push(result),
198
+ managedFiles: assetResult.managedFiles
199
+ });
200
+ } catch (error) {
201
+ await writeInstallState(
202
+ paths,
203
+ assetResult,
204
+ mergeLinkedTargets(previousLinkedTargets, linkResults),
205
+ false,
206
+ guard
207
+ );
208
+ throw error;
209
+ }
210
+ const report = mergeInstallReports(assetResult.report, reportFromLinkResults(linkResults));
191
211
  await writeInstallState(
192
212
  paths,
193
213
  assetResult,
194
- previousMetadata.linkedTargets ?? {},
214
+ mergeLinkedTargets(previousLinkedTargets, linkResults),
195
215
  previousMetadata.workflowInstalled === true
196
216
  && previousMetadata.packageVersion === assetResult.packageVersion
197
- && assetResult.report.conflicts.length === 0,
217
+ && report.conflicts.length === 0,
198
218
  guard
199
219
  );
200
- return assetResult.report;
220
+ return report;
201
221
  }
202
222
 
203
223
  async function ensureSkillPresent(inputPaths, skillName, continuation) {
@@ -228,29 +228,6 @@ async function ensureProviderRootDirectory(homeDir, segments, options = {}) {
228
228
  return directory;
229
229
  }
230
230
 
231
- async function getDirectoryNames(root) {
232
- if (!(await pathIsDirectory(root))) return [];
233
- const entries = await fs.readdir(root, { withFileTypes: true });
234
- const names = await Promise.all(entries.map(async (entry) => {
235
- if (entry.isDirectory()) return entry.name;
236
- if (entry.isSymbolicLink() && await pathIsDirectory(join(root, entry.name))) return entry.name;
237
- return null;
238
- }));
239
- return names.filter(Boolean);
240
- }
241
-
242
- async function getCommandNames(commandsDir) {
243
- if (!(await pathIsDirectory(commandsDir))) return [];
244
- const entries = await fs.readdir(commandsDir, { withFileTypes: true });
245
- const names = await Promise.all(entries.map(async (entry) => {
246
- if (!entry.name.endsWith(".md")) return null;
247
- if (entry.isFile()) return entry.name;
248
- if (entry.isSymbolicLink() && await pathIsFile(join(commandsDir, entry.name))) return entry.name;
249
- return null;
250
- }));
251
- return names.filter(Boolean);
252
- }
253
-
254
231
  async function getPiExtensionNames(extensionsDir) {
255
232
  if (!(await pathIsDirectory(extensionsDir))) return [];
256
233
  const entries = await fs.readdir(extensionsDir, { withFileTypes: true });
@@ -289,10 +266,48 @@ async function pruneManagedTargets(targetDir, managedSourceRoot, expectedNames,
289
266
  return results;
290
267
  }
291
268
 
292
- async function linkSkillDirectories(baseDir, paths, previousLinkedTargets, options) {
269
+ const cadenceOwnedSkills = new Set(["grok-search", "context7-auto-research"]);
270
+
271
+ function shouldLinkSkillToPi(skillName) {
272
+ return skillName !== ".system"
273
+ && !skillName.startsWith("abel-")
274
+ && !cadenceOwnedSkills.has(skillName);
275
+ }
276
+
277
+ function managedSkillNames(managedFiles = {}) {
278
+ const names = new Set();
279
+ for (const relativePath of Object.keys(managedFiles)) {
280
+ if (!relativePath.startsWith("skills/")) continue;
281
+ const skillName = relativePath.slice("skills/".length).split("/")[0];
282
+ if (shouldLinkSkillToPi(skillName)) names.add(skillName);
283
+ }
284
+ return names;
285
+ }
286
+
287
+ function selectedLinkedSkillNames({
288
+ previousLinkedTargets = {},
289
+ skillsRoot,
290
+ managedFiles,
291
+ extraName = null
292
+ } = {}) {
293
+ const names = managedFiles ? managedSkillNames(managedFiles) : new Set();
294
+ if (!managedFiles && skillsRoot) {
295
+ const resolvedSkillsRoot = resolve(skillsRoot);
296
+ for (const previousState of Object.values(previousLinkedTargets)) {
297
+ if (!previousState || previousState.kind !== "dir") continue;
298
+ const sourcePath = resolve(previousState.sourcePath);
299
+ if (dirname(sourcePath) !== resolvedSkillsRoot) continue;
300
+ const skillName = basename(sourcePath);
301
+ if (shouldLinkSkillToPi(skillName)) names.add(skillName);
302
+ }
303
+ }
304
+ if (extraName && shouldLinkSkillToPi(extraName)) names.add(extraName);
305
+ return [...names];
306
+ }
307
+
308
+ async function linkSkillDirectories(baseDir, paths, previousLinkedTargets, options, skillNames) {
293
309
  const results = [];
294
310
  const skillsRoot = join(paths.agentsDir, "skills");
295
- const skillNames = (await getDirectoryNames(skillsRoot)).filter((skillName) => skillName !== ".system");
296
311
  results.push(...await pruneManagedTargets(
297
312
  join(baseDir, "skills"),
298
313
  skillsRoot,
@@ -312,57 +327,54 @@ async function linkSkillDirectories(baseDir, paths, previousLinkedTargets, optio
312
327
  return results;
313
328
  }
314
329
 
315
- async function linkCommands(targetDir, paths, previousLinkedTargets, options) {
316
- const commandsRoot = join(paths.agentsDir, "commands");
317
- const commandFiles = await getCommandNames(commandsRoot);
318
- const results = await pruneManagedTargets(targetDir, commandsRoot, commandFiles, previousLinkedTargets, options);
319
- for (const fileName of commandFiles) {
320
- recordLinkResult(results, options, await ensureManagedLink(
321
- join(targetDir, fileName),
322
- join(commandsRoot, fileName),
323
- "file",
330
+ async function linkPiSkill(paths, skillName, previousLinkedTargets = {}, options = {}) {
331
+ if (!shouldLinkSkillToPi(skillName)) return [];
332
+ const piAgentDir = await ensureProviderRootDirectory(paths.homeDir, [".pi", "agent"], options);
333
+ await ensureManagedContainerDirectories([join(piAgentDir, "skills")], options);
334
+ const skillsRoot = join(paths.agentsDir, "skills");
335
+ return linkSkillDirectories(
336
+ piAgentDir,
337
+ paths,
338
+ previousLinkedTargets,
339
+ options,
340
+ selectedLinkedSkillNames({
324
341
  previousLinkedTargets,
325
- options
326
- ));
327
- }
328
- return results;
342
+ skillsRoot,
343
+ managedFiles: options.managedFiles,
344
+ extraName: skillName
345
+ })
346
+ );
329
347
  }
330
348
 
331
- async function linkClaude(paths, previousLinkedTargets = {}, options = {}) {
332
- const claudeDir = await ensureProviderRootDirectory(paths.homeDir, [".claude"], options);
333
- await ensureManagedContainerDirectories([
334
- join(claudeDir, "commands"),
335
- join(claudeDir, "skills")
336
- ], options);
337
- const results = [];
338
- recordLinkResult(results, options, await ensureManagedLink(
339
- join(claudeDir, "CLAUDE.md"),
340
- join(paths.agentsDir, "AGENTS.md"),
341
- "file",
342
- previousLinkedTargets,
343
- options
344
- ));
345
- results.push(...await linkCommands(join(claudeDir, "commands"), paths, previousLinkedTargets, options));
346
- results.push(...await linkSkillDirectories(claudeDir, paths, previousLinkedTargets, options));
347
- return results;
349
+ async function pruneProviderInstructions(baseDir, commandsName, paths, previousLinkedTargets, options) {
350
+ const cleanupOptions = {
351
+ ...options,
352
+ validateTargetDir: (directory) => ensureManagedContainerDirectories([directory], options)
353
+ };
354
+ return [
355
+ ...await pruneManagedTargets(baseDir, paths.agentsDir, [], previousLinkedTargets, cleanupOptions),
356
+ ...await pruneManagedTargets(join(baseDir, commandsName), join(paths.agentsDir, "commands"), [], previousLinkedTargets, cleanupOptions)
357
+ ];
348
358
  }
349
359
 
350
- async function linkCodex(paths, previousLinkedTargets = {}, options = {}) {
351
- const codexDir = await ensureProviderRootDirectory(paths.homeDir, [".codex"], options);
352
- await ensureManagedContainerDirectories([
353
- join(codexDir, "skills"),
354
- join(codexDir, "prompts")
355
- ], options);
360
+ async function pruneRetiredProviderLinks(paths, previousLinkedTargets = {}, options = {}) {
356
361
  const results = [];
357
- recordLinkResult(results, options, await ensureManagedLink(
358
- join(codexDir, "AGENTS.md"),
359
- join(paths.agentsDir, "AGENTS.md"),
360
- "file",
361
- previousLinkedTargets,
362
- options
363
- ));
364
- results.push(...await linkSkillDirectories(codexDir, paths, previousLinkedTargets, options));
365
- results.push(...await linkCommands(join(codexDir, "prompts"), paths, previousLinkedTargets, options));
362
+ for (const targetDir of [
363
+ join(paths.homeDir, ".claude"),
364
+ join(paths.homeDir, ".claude", "commands"),
365
+ join(paths.homeDir, ".claude", "skills"),
366
+ join(paths.homeDir, ".codex"),
367
+ join(paths.homeDir, ".codex", "prompts"),
368
+ join(paths.homeDir, ".codex", "skills")
369
+ ]) {
370
+ results.push(...await pruneManagedTargets(
371
+ targetDir,
372
+ paths.agentsDir,
373
+ [],
374
+ previousLinkedTargets,
375
+ options
376
+ ));
377
+ }
366
378
  return results;
367
379
  }
368
380
 
@@ -404,19 +416,21 @@ async function linkPi(paths, previousLinkedTargets = {}, options = {}) {
404
416
  const piAgentDir = await ensureProviderRootDirectory(paths.homeDir, [".pi", "agent"], options);
405
417
  await ensureManagedContainerDirectories([
406
418
  join(piAgentDir, "extensions"),
407
- join(piAgentDir, "skills"),
408
- join(piAgentDir, "prompts")
419
+ join(piAgentDir, "skills")
409
420
  ], options);
410
- const results = [];
411
- recordLinkResult(results, options, await ensureManagedLink(
412
- join(piAgentDir, "AGENTS.md"),
413
- join(paths.agentsDir, "AGENTS.md"),
414
- "file",
421
+ const results = await pruneRetiredProviderLinks(paths, previousLinkedTargets, options);
422
+ results.push(...await pruneProviderInstructions(piAgentDir, "prompts", paths, previousLinkedTargets, options));
423
+ results.push(...await linkSkillDirectories(
424
+ piAgentDir,
425
+ paths,
415
426
  previousLinkedTargets,
416
- options
427
+ options,
428
+ selectedLinkedSkillNames({
429
+ previousLinkedTargets,
430
+ skillsRoot: join(paths.agentsDir, "skills"),
431
+ managedFiles: options.managedFiles
432
+ })
417
433
  ));
418
- results.push(...await linkSkillDirectories(piAgentDir, paths, previousLinkedTargets, options));
419
- results.push(...await linkCommands(join(piAgentDir, "prompts"), paths, previousLinkedTargets, options));
420
434
  results.push(...await linkPiExtensionsInDirectory(
421
435
  join(piAgentDir, "extensions"),
422
436
  join(paths.agentsDir, "extensions"),
@@ -429,11 +443,10 @@ async function linkPi(paths, previousLinkedTargets = {}, options = {}) {
429
443
  export {
430
444
  ensureManagedLink,
431
445
  ensureProviderRootDirectory,
432
- getCommandNames,
433
446
  isWithinManagedRoot,
434
- linkClaude,
435
- linkCodex,
436
447
  linkPi,
437
448
  linkPiExtensions,
438
- pruneManagedTargets
449
+ linkPiSkill,
450
+ pruneManagedTargets,
451
+ shouldLinkSkillToPi
439
452
  };
package/lib/paths.mjs CHANGED
@@ -16,13 +16,11 @@ function createPaths({
16
16
  assertSeparateSourceAndDeployment(absolutePackageRoot, absoluteAgentsDir);
17
17
 
18
18
  const codexTemplateRoot = join(absolutePackageRoot, "lib", "templates", "codex");
19
- const workflowTemplateRoot = join(absolutePackageRoot, "lib", "templates", "workflow");
20
19
  const piAgentDir = join(absoluteHomeDir, ".pi", "agent");
21
20
  return {
22
21
  homeDir: absoluteHomeDir,
23
22
  packageRoot: absolutePackageRoot,
24
23
  agentsDir: absoluteAgentsDir,
25
- workflowTemplateRoot,
26
24
  installMetadataName: ".abelworkflow-install.json",
27
25
  claudeSettingsPath: join(absoluteHomeDir, ".claude", "settings.json"),
28
26
  codexConfigPath: join(absoluteHomeDir, ".codex", "config.toml"),
@@ -50,8 +50,15 @@ const defaultClaudeSettings = {
50
50
  language: "Chinese"
51
51
  };
52
52
 
53
+ function assertClaudeApiKey(value, label) {
54
+ if (value !== undefined && typeof value !== "string") {
55
+ throw new TypeError(`${label} must be a string or undefined`);
56
+ }
57
+ }
58
+
53
59
  function getExistingClaudeApiConfig(settings) {
54
60
  const env = settings.env === undefined ? {} : assertPlainObject(settings.env, "Claude settings env");
61
+ assertClaudeApiKey(env.ANTHROPIC_API_KEY, "Claude settings env ANTHROPIC_API_KEY");
55
62
  return {
56
63
  baseUrl: env.ANTHROPIC_BASE_URL || "https://api.anthropic.com",
57
64
  key: env.ANTHROPIC_API_KEY || "",
@@ -66,6 +73,8 @@ function buildClaudeApiSettings(settings, {
66
73
  }) {
67
74
  assertPlainObject(settings, "Claude settings");
68
75
  const env = settings.env === undefined ? {} : assertPlainObject(settings.env, "Claude settings env");
76
+ assertClaudeApiKey(env.ANTHROPIC_API_KEY, "Claude settings env ANTHROPIC_API_KEY");
77
+ assertClaudeApiKey(key, "Claude API key");
69
78
  const permissions = settings.permissions === undefined
70
79
  ? {}
71
80
  : assertPlainObject(settings.permissions, "Claude settings permissions");
@@ -82,8 +91,7 @@ function buildClaudeApiSettings(settings, {
82
91
  env: {
83
92
  ...defaultClaudeSettings.env,
84
93
  ...env,
85
- ANTHROPIC_BASE_URL: normalizeHttpBaseUrl(baseUrl),
86
- ANTHROPIC_API_KEY: key
94
+ ANTHROPIC_BASE_URL: normalizeHttpBaseUrl(baseUrl)
87
95
  },
88
96
  permissions: {
89
97
  ...defaultClaudeSettings.permissions,
@@ -92,6 +100,8 @@ function buildClaudeApiSettings(settings, {
92
100
  deny: Array.isArray(permissions.deny) ? permissions.deny : [...defaultClaudeSettings.permissions.deny]
93
101
  }
94
102
  };
103
+ if (key === undefined) delete nextSettings.env.ANTHROPIC_API_KEY;
104
+ else nextSettings.env.ANTHROPIC_API_KEY = key;
95
105
  delete nextSettings.includeCoAuthoredBy;
96
106
  delete nextSettings.env.ANTHROPIC_AUTH_TOKEN;
97
107
  delete nextSettings.env.DISABLE_TELEMETRY;
@@ -144,7 +154,8 @@ async function configureClaudeApi(paths, promptApi, runtime = {}) {
144
154
 
145
155
  const finalKey = await passwordOrExisting({
146
156
  message: "Claude Code API Key",
147
- existingValue: existing.key
157
+ existingValue: existing.key,
158
+ requiredMessage: existing.key ? null : "API Key 不能为空"
148
159
  });
149
160
 
150
161
  const model = await text({
@@ -62,7 +62,10 @@ function mergeCodexTemplateDefaults(content, templateContent) {
62
62
  ? current.developer_instructions
63
63
  : "";
64
64
 
65
- if (publishedCodexDeveloperInstructionHashes.has(hashBytes(currentInstructions))) {
65
+ const instructionHash = hashBytes(currentInstructions);
66
+ const usesLegacyDefaults = publishedCodexDeveloperInstructionHashes.has(instructionHash);
67
+ if (usesLegacyDefaults
68
+ || instructionHash === "c84017d17fa425175f6dd7df9766b5de74e289420f63fea87054e2f41822a8bc") {
66
69
  const legacyDefaults = {
67
70
  approvals_reviewer: "guardian_subagent",
68
71
  approval_policy: "on-request",
@@ -71,7 +74,7 @@ function mergeCodexTemplateDefaults(content, templateContent) {
71
74
  };
72
75
  const replacementFields = new Set(["developer_instructions"]);
73
76
  for (const [field, value] of Object.entries(legacyDefaults)) {
74
- if (current[field] === value) replacementFields.add(field);
77
+ if (usesLegacyDefaults && current[field] === value) replacementFields.add(field);
75
78
  }
76
79
  for (const field of replacementFields) {
77
80
  nextContent = removeTopLevelTomlField(nextContent, field);
@@ -375,15 +378,17 @@ function buildCodexConfigContent(currentContent, {
375
378
 
376
379
  function mergeCodexAuthData(auth, apiKey) {
377
380
  assertPlainObject(auth, "Codex auth");
378
- const nextAuth = { ...auth };
379
- assertCodexAuthKeyAvailable(nextAuth, CODEX_ENV_KEY);
381
+ assertCodexAuthKeyAvailable(auth, CODEX_ENV_KEY);
380
382
  if (apiKey) {
381
- nextAuth.auth_mode = "apikey";
382
- nextAuth[CODEX_ENV_KEY] = apiKey;
383
- } else {
384
- delete nextAuth[CODEX_ENV_KEY];
385
- if (nextAuth.auth_mode === "apikey") delete nextAuth.auth_mode;
383
+ return {
384
+ auth_mode: "apikey",
385
+ [CODEX_ENV_KEY]: apiKey
386
+ };
386
387
  }
388
+
389
+ const nextAuth = { ...auth };
390
+ delete nextAuth[CODEX_ENV_KEY];
391
+ if (nextAuth.auth_mode === "apikey") delete nextAuth.auth_mode;
387
392
  return nextAuth;
388
393
  }
389
394