abelworkflow 0.9.2 → 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +55 -182
  2. package/bin/abelworkflow.mjs +5 -3
  3. package/lib/cli/args.mjs +76 -0
  4. package/lib/cli/main.mjs +387 -0
  5. package/lib/cli/prompts.mjs +119 -0
  6. package/lib/config/dotenv.mjs +102 -0
  7. package/lib/config/jsonc.mjs +80 -0
  8. package/lib/config/store.mjs +310 -0
  9. package/lib/config/toml.mjs +638 -0
  10. package/lib/installer/assets.mjs +326 -0
  11. package/lib/installer/install.mjs +167 -0
  12. package/lib/installer/links.mjs +427 -0
  13. package/lib/installer/render.mjs +46 -0
  14. package/lib/installer/state.mjs +182 -0
  15. package/lib/paths.mjs +99 -0
  16. package/lib/providers/claude.mjs +388 -0
  17. package/lib/providers/codex.mjs +433 -0
  18. package/lib/providers/pi.mjs +362 -0
  19. package/lib/providers/skills.mjs +164 -0
  20. package/lib/templates/codex/agents/default.toml +7 -0
  21. package/lib/templates/codex/agents/explorer.toml +7 -0
  22. package/lib/templates/codex/agents/planner.toml +7 -0
  23. package/lib/templates/codex/agents/reviewer.toml +7 -0
  24. package/lib/templates/codex/agents/worker.toml +7 -0
  25. package/lib/templates/codex/config-base.toml +11 -3
  26. package/{AGENTS.md → lib/templates/workflow/AGENTS.md} +8 -3
  27. package/{commands → lib/templates/workflow/commands}/abel-diagnose.md +5 -5
  28. package/{commands → lib/templates/workflow/commands}/abel-implement.md +3 -8
  29. package/{commands → lib/templates/workflow/commands}/abel-init.md +1 -1
  30. package/{commands → lib/templates/workflow/commands}/abel-plan.md +1 -7
  31. package/{commands → lib/templates/workflow/commands}/abel-research.md +1 -1
  32. package/{.gitignore → lib/templates/workflow/gitignore.template} +0 -2
  33. package/lib/tools/cli-installer.mjs +277 -0
  34. package/package.json +43 -10
  35. package/skills/context7-auto-research/SKILL.md +5 -5
  36. package/skills/context7-auto-research/{context7-api.js → context7-api.cjs} +5 -8
  37. package/skills/dev-browser/SKILL.md +53 -33
  38. package/skills/dev-browser/dist/scripts/start.d.ts +2 -0
  39. package/skills/dev-browser/dist/scripts/start.d.ts.map +1 -0
  40. package/skills/dev-browser/dist/scripts/start.js +118 -0
  41. package/skills/dev-browser/dist/scripts/start.js.map +1 -0
  42. package/skills/dev-browser/dist/src/client.d.ts +93 -0
  43. package/skills/dev-browser/dist/src/client.d.ts.map +1 -0
  44. package/skills/dev-browser/dist/src/client.js +310 -0
  45. package/skills/dev-browser/dist/src/client.js.map +1 -0
  46. package/skills/dev-browser/dist/src/entrypoint.d.ts +29 -0
  47. package/skills/dev-browser/dist/src/entrypoint.d.ts.map +1 -0
  48. package/skills/dev-browser/dist/src/entrypoint.js +106 -0
  49. package/skills/dev-browser/dist/src/entrypoint.js.map +1 -0
  50. package/skills/dev-browser/dist/src/index.d.ts +4 -0
  51. package/skills/dev-browser/dist/src/index.d.ts.map +1 -0
  52. package/skills/dev-browser/dist/src/index.js +2 -0
  53. package/skills/dev-browser/dist/src/index.js.map +1 -0
  54. package/skills/dev-browser/dist/src/page-api.d.ts +25 -0
  55. package/skills/dev-browser/dist/src/page-api.d.ts.map +1 -0
  56. package/skills/dev-browser/dist/src/page-api.js +104 -0
  57. package/skills/dev-browser/dist/src/page-api.js.map +1 -0
  58. package/skills/dev-browser/dist/src/relay.d.ts +27 -0
  59. package/skills/dev-browser/dist/src/relay.d.ts.map +1 -0
  60. package/skills/dev-browser/dist/src/relay.js +521 -0
  61. package/skills/dev-browser/dist/src/relay.js.map +1 -0
  62. package/skills/dev-browser/dist/src/runtime.d.ts +45 -0
  63. package/skills/dev-browser/dist/src/runtime.d.ts.map +1 -0
  64. package/skills/dev-browser/dist/src/runtime.js +54 -0
  65. package/skills/dev-browser/dist/src/runtime.js.map +1 -0
  66. package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +23 -0
  67. package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts.map +1 -0
  68. package/skills/dev-browser/{src/snapshot/browser-script.ts → dist/src/snapshot/browser-script.js} +21 -30
  69. package/skills/dev-browser/dist/src/snapshot/browser-script.js.map +1 -0
  70. package/skills/dev-browser/dist/src/snapshot/index.d.ts +14 -0
  71. package/skills/dev-browser/dist/src/snapshot/index.d.ts.map +1 -0
  72. package/skills/dev-browser/{src/snapshot/index.ts → dist/src/snapshot/index.js} +2 -2
  73. package/skills/dev-browser/dist/src/snapshot/index.js.map +1 -0
  74. package/skills/dev-browser/dist/src/snapshot/inject.d.ts +13 -0
  75. package/skills/dev-browser/dist/src/snapshot/inject.d.ts.map +1 -0
  76. package/skills/dev-browser/{src/snapshot/inject.ts → dist/src/snapshot/inject.js} +2 -2
  77. package/skills/dev-browser/dist/src/snapshot/inject.js.map +1 -0
  78. package/skills/dev-browser/dist/src/standalone.d.ts +32 -0
  79. package/skills/dev-browser/dist/src/standalone.d.ts.map +1 -0
  80. package/skills/dev-browser/dist/src/standalone.js +174 -0
  81. package/skills/dev-browser/dist/src/standalone.js.map +1 -0
  82. package/skills/dev-browser/dist/src/startup.d.ts +55 -0
  83. package/skills/dev-browser/dist/src/startup.d.ts.map +1 -0
  84. package/skills/dev-browser/dist/src/startup.js +81 -0
  85. package/skills/dev-browser/dist/src/startup.js.map +1 -0
  86. package/skills/dev-browser/dist/src/target-registry.d.ts +29 -0
  87. package/skills/dev-browser/dist/src/target-registry.d.ts.map +1 -0
  88. package/skills/dev-browser/dist/src/target-registry.js +135 -0
  89. package/skills/dev-browser/dist/src/target-registry.js.map +1 -0
  90. package/skills/dev-browser/dist/src/types.d.ts +27 -0
  91. package/skills/dev-browser/dist/src/types.d.ts.map +1 -0
  92. package/skills/dev-browser/dist/src/types.js +2 -0
  93. package/skills/dev-browser/dist/src/types.js.map +1 -0
  94. package/skills/dev-browser/package-lock.json +67 -1510
  95. package/skills/dev-browser/package.json +18 -14
  96. package/skills/dev-browser/references/scraping.md +94 -105
  97. package/skills/grok-search/defaults.json +3 -0
  98. package/skills/grok-search/gitignore.template +4 -0
  99. package/skills/grok-search/scripts/_dotenv.py +5 -1
  100. package/skills/grok-search/scripts/groksearch_cli.py +15 -33
  101. package/skills/prompt-enhancer/TEMPLATE.md +0 -4
  102. package/skills/prompt-enhancer/requirements.txt +0 -1
  103. package/skills/prompt-enhancer/scripts/_dotenv.py +5 -1
  104. package/skills/prompt-enhancer/scripts/enhance.py +4 -51
  105. package/skills/prompt-enhancer/scripts/prompt_enhancer_entry.py +1 -16
  106. package/.skill-lock.json +0 -29
  107. package/lib/cli/logic.mjs +0 -213
  108. package/lib/cli.mjs +0 -3016
  109. package/skills/confidence-check/SKILL.md +0 -34
  110. package/skills/confidence-check/confidence.ts +0 -276
  111. package/skills/dev-browser/bun.lock +0 -460
  112. package/skills/dev-browser/scripts/start.ts +0 -280
  113. package/skills/dev-browser/src/client.ts +0 -474
  114. package/skills/dev-browser/src/entrypoint.ts +0 -157
  115. package/skills/dev-browser/src/index.ts +0 -289
  116. package/skills/dev-browser/src/relay.ts +0 -731
  117. package/skills/dev-browser/src/runtime.test.ts +0 -60
  118. package/skills/dev-browser/src/runtime.ts +0 -171
  119. package/skills/dev-browser/src/startup.test.ts +0 -95
  120. package/skills/dev-browser/src/startup.ts +0 -153
  121. package/skills/dev-browser/src/types.ts +0 -35
  122. package/skills/dev-browser/tsconfig.json +0 -36
  123. package/skills/dev-browser/vitest.config.ts +0 -12
  124. package/skills/sequential-think/SKILL.md +0 -198
  125. package/skills/sequential-think/scripts/.env.example +0 -5
  126. package/skills/sequential-think/scripts/sequential_think_cli.py +0 -253
package/lib/paths.mjs ADDED
@@ -0,0 +1,99 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const defaultPackageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
7
+
8
+ function createPaths({
9
+ homeDir = homedir(),
10
+ packageRoot = defaultPackageRoot,
11
+ agentsDir = join(homeDir, ".agents")
12
+ } = {}) {
13
+ const absoluteHomeDir = resolve(homeDir);
14
+ const absolutePackageRoot = resolve(packageRoot);
15
+ const absoluteAgentsDir = resolve(agentsDir);
16
+ assertSeparateSourceAndDeployment(absolutePackageRoot, absoluteAgentsDir);
17
+
18
+ const codexTemplateRoot = join(absolutePackageRoot, "lib", "templates", "codex");
19
+ const workflowTemplateRoot = join(absolutePackageRoot, "lib", "templates", "workflow");
20
+ const piAgentDir = join(absoluteHomeDir, ".pi", "agent");
21
+ return {
22
+ homeDir: absoluteHomeDir,
23
+ packageRoot: absolutePackageRoot,
24
+ agentsDir: absoluteAgentsDir,
25
+ workflowTemplateRoot,
26
+ installMetadataName: ".abelworkflow-install.json",
27
+ claudeSettingsPath: join(absoluteHomeDir, ".claude", "settings.json"),
28
+ claudeMetaConfigPath: join(absoluteHomeDir, ".claude.json"),
29
+ codexConfigPath: join(absoluteHomeDir, ".codex", "config.toml"),
30
+ codexAuthPath: join(absoluteHomeDir, ".codex", "auth.json"),
31
+ codexTemplateConfigPath: join(codexTemplateRoot, "config-base.toml"),
32
+ codexTemplateAgentsPath: join(codexTemplateRoot, "agents"),
33
+ piAgentDir,
34
+ piAuthPath: join(piAgentDir, "auth.json"),
35
+ piModelsPath: join(piAgentDir, "models.json"),
36
+ piSettingsPath: join(piAgentDir, "settings.json")
37
+ };
38
+ }
39
+
40
+ function assertSeparateSourceAndDeployment(packageRoot, agentsDir) {
41
+ const canonicalPackageRoot = canonicalizeExistingPath(packageRoot);
42
+ const canonicalAgentsDir = canonicalizeExistingPath(agentsDir);
43
+ if (!containsPath(canonicalPackageRoot, canonicalAgentsDir)
44
+ && !containsPath(canonicalAgentsDir, canonicalPackageRoot)) {
45
+ return;
46
+ }
47
+
48
+ throw new Error(
49
+ `源码目录 packageRoot (${packageRoot}) 与部署目录 agentsDir (${agentsDir}) 不能相同或互相嵌套。`
50
+ + "请将源码克隆到独立目录(例如 ~/src/AbelWorkflow),再用 --agents-dir 指向 ~/.agents;"
51
+ + "请手动迁移已有源码克隆,安装器不会自动移动或删除数据,--force 也不能绕过此限制。"
52
+ );
53
+ }
54
+
55
+ function canonicalizeExistingPath(path) {
56
+ const missingParts = [];
57
+ let candidate = path;
58
+
59
+ while (true) {
60
+ try {
61
+ return resolve(realpathSync.native(candidate), ...missingParts);
62
+ } catch (error) {
63
+ const parent = dirname(candidate);
64
+ if (error?.code !== "ENOENT" || parent === candidate) return path;
65
+ missingParts.unshift(basename(candidate));
66
+ candidate = parent;
67
+ }
68
+ }
69
+ }
70
+
71
+ function containsPath(parent, child, pathOps = { isAbsolute, relative, sep }) {
72
+ const childRelativePath = pathOps.relative(parent, child);
73
+ return childRelativePath === ""
74
+ || (childRelativePath !== ".."
75
+ && !childRelativePath.startsWith(`..${pathOps.sep}`)
76
+ && !pathOps.isAbsolute(childRelativePath));
77
+ }
78
+
79
+ const defaultPaths = createPaths();
80
+
81
+ function getPlatform() {
82
+ return process.env.ABELWORKFLOW_TEST_PLATFORM || process.platform;
83
+ }
84
+
85
+ function isWindows() {
86
+ return getPlatform() === "win32";
87
+ }
88
+
89
+ function pathToLabel(path, homeDir = defaultPaths.homeDir) {
90
+ return path.replace(homeDir, "~");
91
+ }
92
+
93
+ function maskSecret(value) {
94
+ if (!value) return "未配置";
95
+ if (value.length <= 4) return "*".repeat(value.length);
96
+ return `${"*".repeat(Math.max(4, value.length - 4))}${value.slice(-4)}`;
97
+ }
98
+
99
+ export { containsPath, createPaths, defaultPaths, getPlatform, isWindows, maskSecret, pathToLabel };
@@ -0,0 +1,388 @@
1
+ import * as p from "@clack/prompts";
2
+ import {
3
+ pathExists,
4
+ readJsonFileSafe,
5
+ writeJsonFileWithBackup
6
+ } from "../config/store.mjs";
7
+ import { defaultPaths, maskSecret, pathToLabel } from "../paths.mjs";
8
+
9
+ const augmentContextEnginePermission = "mcp__augment-context-engine";
10
+ const claudePermissionProfiles = ["standard", "trusted"];
11
+ const trustedClaudePermissions = [
12
+ "Bash",
13
+ "Skill",
14
+ "LS",
15
+ "Read",
16
+ "Agent",
17
+ "Write",
18
+ "Edit",
19
+ "MultiEdit",
20
+ "Glob",
21
+ "Grep",
22
+ "WebFetch",
23
+ "WebSearch",
24
+ "TodoWrite",
25
+ "NotebookRead",
26
+ "NotebookEdit"
27
+ ];
28
+ const claudeModelEnvKeys = [
29
+ "ANTHROPIC_MODEL",
30
+ "ANTHROPIC_DEFAULT_OPUS_MODEL",
31
+ "ANTHROPIC_DEFAULT_SONNET_MODEL",
32
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL",
33
+ "CLAUDE_CODE_SUBAGENT_MODEL"
34
+ ];
35
+ const defaultClaudeSettings = {
36
+ $schema: "https://json.schemastore.org/claude-code-settings.json",
37
+ env: {
38
+ DISABLE_TELEMETRY: "1",
39
+ DISABLE_ERROR_REPORTING: "1",
40
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
41
+ ANTHROPIC_BASE_URL: "",
42
+ ANTHROPIC_API_KEY: "",
43
+ ANTHROPIC_MODEL: "",
44
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "",
45
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "",
46
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: "",
47
+ CLAUDE_CODE_SUBAGENT_MODEL: ""
48
+ },
49
+ includeCoAuthoredBy: false,
50
+ permissions: {
51
+ allow: [],
52
+ deny: []
53
+ },
54
+ hooks: {},
55
+ alwaysThinkingEnabled: true,
56
+ language: "Chinese"
57
+ };
58
+ function buildDefaultClaudeSettings({ augmentContextEngine = false, profile = "standard" } = {}) {
59
+ if (!claudePermissionProfiles.includes(profile)) {
60
+ throw new Error(`Unknown Claude permission profile: ${profile}`);
61
+ }
62
+ const settings = {
63
+ ...defaultClaudeSettings,
64
+ env: { ...defaultClaudeSettings.env },
65
+ permissions: {
66
+ ...defaultClaudeSettings.permissions,
67
+ allow: profile === "trusted" ? [...trustedClaudePermissions] : [],
68
+ deny: [...defaultClaudeSettings.permissions.deny]
69
+ },
70
+ hooks: { ...defaultClaudeSettings.hooks }
71
+ };
72
+
73
+ if (augmentContextEngine && !settings.permissions.allow.includes(augmentContextEnginePermission)) {
74
+ settings.permissions.allow.push(augmentContextEnginePermission);
75
+ }
76
+
77
+ return settings;
78
+ }
79
+ function mergeClaudeSettingsWithDefaults(settings, { augmentContextEngine = false } = {}) {
80
+ const defaults = buildDefaultClaudeSettings({ augmentContextEngine });
81
+ const env = settings?.env && typeof settings.env === "object" ? settings.env : {};
82
+ const permissions = settings?.permissions && typeof settings.permissions === "object" ? settings.permissions : {};
83
+ return {
84
+ ...defaults,
85
+ ...settings,
86
+ env: {
87
+ ...defaults.env,
88
+ ...env
89
+ },
90
+ permissions: {
91
+ ...defaults.permissions,
92
+ ...permissions,
93
+ allow: Array.isArray(permissions.allow) ? permissions.allow : defaults.permissions.allow,
94
+ deny: Array.isArray(permissions.deny) ? permissions.deny : defaults.permissions.deny
95
+ },
96
+ hooks: settings?.hooks && typeof settings.hooks === "object" ? settings.hooks : defaults.hooks
97
+ };
98
+ }
99
+
100
+ function applyClaudeInsecureTlsSetting(env = {}, enabled = false) {
101
+ const nextEnv = { ...env };
102
+ if (enabled) {
103
+ nextEnv.NODE_TLS_REJECT_UNAUTHORIZED = "0";
104
+ } else if (nextEnv.NODE_TLS_REJECT_UNAUTHORIZED === "0") {
105
+ delete nextEnv.NODE_TLS_REJECT_UNAUTHORIZED;
106
+ }
107
+ return nextEnv;
108
+ }
109
+
110
+ function getPreviousManagedClaudePermissions(previousMetadata = {}) {
111
+ return Array.isArray(previousMetadata.managedClaudePermissions)
112
+ ? previousMetadata.managedClaudePermissions.filter((value) => typeof value === "string")
113
+ : [];
114
+ }
115
+
116
+ function applyClaudePermissionProfile(settings, {
117
+ profile = "standard",
118
+ previousManagedPermissions = []
119
+ } = {}) {
120
+ if (!claudePermissionProfiles.includes(profile)) {
121
+ throw new Error(`Unknown Claude permission profile: ${profile}`);
122
+ }
123
+ const hasSettings = settings && typeof settings === "object" && !Array.isArray(settings);
124
+ const nextSettings = hasSettings
125
+ ? {
126
+ ...settings,
127
+ permissions: settings.permissions && typeof settings.permissions === "object"
128
+ ? { ...settings.permissions }
129
+ : {}
130
+ }
131
+ : buildDefaultClaudeSettings();
132
+ const allow = Array.isArray(nextSettings.permissions.allow)
133
+ ? [...nextSettings.permissions.allow]
134
+ : [];
135
+ const previousManaged = new Set(previousManagedPermissions.filter((value) => typeof value === "string"));
136
+ const retainedManaged = [];
137
+
138
+ for (const permission of previousManaged) {
139
+ if (permission === augmentContextEnginePermission) {
140
+ if (allow.includes(permission)) retainedManaged.push(permission);
141
+ continue;
142
+ }
143
+ const index = allow.indexOf(permission);
144
+ if (index !== -1) allow.splice(index, 1);
145
+ }
146
+
147
+ if (profile === "trusted") {
148
+ for (const permission of trustedClaudePermissions) {
149
+ if (allow.includes(permission)) continue;
150
+ allow.push(permission);
151
+ retainedManaged.push(permission);
152
+ }
153
+ }
154
+
155
+ const previousAllow = Array.isArray(nextSettings.permissions.allow)
156
+ ? nextSettings.permissions.allow
157
+ : [];
158
+ nextSettings.permissions.allow = allow;
159
+ return {
160
+ settings: nextSettings,
161
+ changed: !hasSettings || previousAllow.length !== allow.length
162
+ || previousAllow.some((permission, index) => permission !== allow[index]),
163
+ managedPermissions: [...new Set(retainedManaged)]
164
+ };
165
+ }
166
+
167
+ function applyClaudePermissionFeature(settings, {
168
+ augmentContextEngine = false,
169
+ previousManagedPermissions = []
170
+ } = {}) {
171
+ const hasSettings = settings && typeof settings === "object";
172
+ const wasManaged = previousManagedPermissions.includes(augmentContextEnginePermission);
173
+ if (!hasSettings && !augmentContextEngine) {
174
+ return { settings, changed: false, managedPermissions: [] };
175
+ }
176
+
177
+ const nextSettings = hasSettings
178
+ ? {
179
+ ...settings,
180
+ permissions: settings.permissions && typeof settings.permissions === "object"
181
+ ? { ...settings.permissions }
182
+ : {}
183
+ }
184
+ : { permissions: {} };
185
+ const permissions = nextSettings.permissions;
186
+ const allow = Array.isArray(permissions.allow)
187
+ ? [...permissions.allow]
188
+ : [];
189
+ let changed = !hasSettings;
190
+ let isManaged = wasManaged;
191
+
192
+ if (augmentContextEngine) {
193
+ if (!allow.includes(augmentContextEnginePermission)) {
194
+ allow.push(augmentContextEnginePermission);
195
+ changed = true;
196
+ isManaged = true;
197
+ }
198
+ } else if (wasManaged && allow.includes(augmentContextEnginePermission)) {
199
+ allow.splice(allow.indexOf(augmentContextEnginePermission), 1);
200
+ changed = true;
201
+ isManaged = false;
202
+ } else {
203
+ isManaged = false;
204
+ }
205
+
206
+ permissions.allow = allow;
207
+ nextSettings.permissions = permissions;
208
+
209
+ return {
210
+ settings: nextSettings,
211
+ changed,
212
+ managedPermissions: [
213
+ ...previousManagedPermissions.filter((permission) => permission !== augmentContextEnginePermission && allow.includes(permission)),
214
+ ...(isManaged ? [augmentContextEnginePermission] : [])
215
+ ]
216
+ };
217
+ }
218
+
219
+ async function ensureClaudeSettingsForFeature(paths, augmentContextEngine, previousMetadata) {
220
+ const settingsExists = await pathExists(paths.claudeSettingsPath);
221
+ const settings = settingsExists
222
+ ? await readJsonFileSafe(paths.claudeSettingsPath, {}, { sensitive: true })
223
+ : undefined;
224
+ const result = applyClaudePermissionFeature(settings, {
225
+ augmentContextEngine,
226
+ previousManagedPermissions: getPreviousManagedClaudePermissions(previousMetadata)
227
+ });
228
+
229
+ if (result.changed && result.settings) {
230
+ await writeJsonFileWithBackup(paths.claudeSettingsPath, result.settings, { sensitive: true });
231
+ }
232
+
233
+ return result.managedPermissions;
234
+ }
235
+
236
+ function getExistingClaudeApiConfig(settings) {
237
+ const env = mergeClaudeSettingsWithDefaults(settings).env;
238
+ return {
239
+ baseUrl: env.ANTHROPIC_BASE_URL || "https://api.anthropic.com",
240
+ authType: env.ANTHROPIC_AUTH_TOKEN ? "auth_token" : "api_key",
241
+ key: env.ANTHROPIC_AUTH_TOKEN || env.ANTHROPIC_API_KEY || "",
242
+ model: claudeModelEnvKeys.map((field) => env[field]).find(Boolean) || "",
243
+ insecureTls: env.NODE_TLS_REJECT_UNAUTHORIZED === "0"
244
+ };
245
+ }
246
+
247
+ function ensureApprovedClaudeApiKey(config, apiKey) {
248
+ if (!apiKey) {
249
+ return config;
250
+ }
251
+
252
+ const truncated = apiKey.slice(0, 20);
253
+ if (!config.customApiKeyResponses || typeof config.customApiKeyResponses !== "object") {
254
+ config.customApiKeyResponses = { approved: [], rejected: [] };
255
+ }
256
+ if (!Array.isArray(config.customApiKeyResponses.approved)) {
257
+ config.customApiKeyResponses.approved = [];
258
+ }
259
+ if (!Array.isArray(config.customApiKeyResponses.rejected)) {
260
+ config.customApiKeyResponses.rejected = [];
261
+ }
262
+
263
+ config.customApiKeyResponses.rejected = config.customApiKeyResponses.rejected.filter((item) => item !== truncated);
264
+ if (!config.customApiKeyResponses.approved.includes(truncated)) {
265
+ config.customApiKeyResponses.approved.push(truncated);
266
+ }
267
+
268
+ return config;
269
+ }
270
+
271
+ function buildClaudeApiSettings(settings, {
272
+ authType,
273
+ baseUrl,
274
+ key,
275
+ model,
276
+ insecureTls = false
277
+ }) {
278
+ const nextSettings = mergeClaudeSettingsWithDefaults(settings);
279
+ nextSettings.env = applyClaudeInsecureTlsSetting(
280
+ nextSettings.env && typeof nextSettings.env === "object" ? nextSettings.env : {},
281
+ insecureTls
282
+ );
283
+ nextSettings.env.ANTHROPIC_BASE_URL = baseUrl;
284
+ if (authType === "auth_token") {
285
+ nextSettings.env.ANTHROPIC_AUTH_TOKEN = key;
286
+ delete nextSettings.env.ANTHROPIC_API_KEY;
287
+ } else {
288
+ nextSettings.env.ANTHROPIC_API_KEY = key;
289
+ delete nextSettings.env.ANTHROPIC_AUTH_TOKEN;
290
+ }
291
+ for (const field of claudeModelEnvKeys) {
292
+ nextSettings.env[field] = model;
293
+ }
294
+ return nextSettings;
295
+ }
296
+
297
+ async function persistClaudeConfiguration(paths, { settings, metaConfig }) {
298
+ await writeJsonFileWithBackup(paths.claudeSettingsPath, settings, { sensitive: true });
299
+ await writeJsonFileWithBackup(paths.claudeMetaConfigPath, metaConfig, { sensitive: true });
300
+ }
301
+
302
+ async function configureClaudeApi(paths = defaultPaths, promptApi, options = {}) {
303
+ const {
304
+ assertNotCancelled,
305
+ confirmOrCancel,
306
+ required,
307
+ requiredUnlessExisting,
308
+ resolvePasswordValue,
309
+ selectOrCancel
310
+ } = promptApi;
311
+ const settings = await readJsonFileSafe(paths.claudeSettingsPath, {}, { sensitive: true });
312
+ const existing = getExistingClaudeApiConfig(settings);
313
+ const authType = await selectOrCancel({
314
+ message: "Claude Code 第三方 API 认证方式",
315
+ options: [
316
+ { value: "api_key", label: "API Key" },
317
+ { value: "auth_token", label: "Auth Token" }
318
+ ],
319
+ initialValue: existing.authType
320
+ });
321
+
322
+ const baseUrl = await p.text({
323
+ message: "Claude Code Base URL",
324
+ defaultValue: existing.baseUrl,
325
+ validate: required()
326
+ });
327
+ assertNotCancelled(baseUrl);
328
+
329
+ const key = await p.password({
330
+ message: authType === "auth_token" ? "Claude Code Auth Token(输入 - 清除)" : "Claude Code API Key(输入 - 清除)",
331
+ mask: "*",
332
+ defaultValue: existing.key || undefined,
333
+ validate: requiredUnlessExisting(existing.key, "API Key / Auth Token 不能为空")
334
+ });
335
+ assertNotCancelled(key);
336
+ const finalKey = resolvePasswordValue(key, existing.key);
337
+
338
+ const insecureTls = await confirmOrCancel({
339
+ message: "是否跳过 Claude Code TLS 证书校验?仅证书无法修复时启用(会放宽该进程全部 HTTPS 请求)",
340
+ initialValue: existing.insecureTls
341
+ });
342
+
343
+ const model = await p.text({
344
+ message: "Claude Code 模型",
345
+ defaultValue: existing.model || undefined,
346
+ validate: required()
347
+ });
348
+ assertNotCancelled(model);
349
+
350
+ const apiSettings = buildClaudeApiSettings(settings, {
351
+ authType,
352
+ baseUrl,
353
+ key: finalKey,
354
+ model,
355
+ insecureTls
356
+ });
357
+ const profileResult = applyClaudePermissionProfile(apiSettings, {
358
+ profile: options.permissionProfile ?? "standard",
359
+ previousManagedPermissions: options.previousManagedPermissions ?? []
360
+ });
361
+ const metaConfig = await readJsonFileSafe(paths.claudeMetaConfigPath, {}, { sensitive: true });
362
+ metaConfig.hasCompletedOnboarding = true;
363
+ ensureApprovedClaudeApiKey(metaConfig, finalKey);
364
+ await persistClaudeConfiguration(paths, {
365
+ settings: profileResult.settings,
366
+ metaConfig
367
+ });
368
+
369
+ p.log.step(`已更新 ${pathToLabel(paths.claudeSettingsPath, paths.homeDir)} (${authType}, ${baseUrl}, ${maskSecret(finalKey)}, ${options.permissionProfile ?? "standard"})`);
370
+ return {
371
+ managedPermissions: profileResult.managedPermissions,
372
+ permissionProfile: options.permissionProfile ?? "standard"
373
+ };
374
+ }
375
+
376
+
377
+ export {
378
+ applyClaudeInsecureTlsSetting,
379
+ applyClaudePermissionFeature,
380
+ applyClaudePermissionProfile,
381
+ buildClaudeApiSettings,
382
+ buildDefaultClaudeSettings,
383
+ configureClaudeApi,
384
+ ensureClaudeSettingsForFeature,
385
+ getPreviousManagedClaudePermissions,
386
+ mergeClaudeSettingsWithDefaults,
387
+ persistClaudeConfiguration
388
+ };