abelworkflow 1.1.1 → 1.1.2
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/lib/cli/main.mjs +39 -48
- package/lib/cli/prompts.mjs +22 -19
- package/lib/config/dotenv.mjs +1 -9
- package/lib/config/store.mjs +0 -29
- package/lib/config/toml.mjs +10 -9
- package/lib/installer/assets.mjs +2 -6
- package/lib/installer/install.mjs +18 -43
- package/lib/installer/links.mjs +7 -18
- package/lib/installer/state.mjs +3 -22
- package/lib/providers/claude.mjs +8 -20
- package/lib/providers/codex.mjs +29 -37
- package/lib/providers/pi.mjs +7 -12
- package/lib/providers/skills.mjs +20 -27
- package/lib/templates/codex/config-base.toml +1 -2
- package/lib/utils.mjs +15 -0
- package/package.json +1 -1
package/lib/cli/main.mjs
CHANGED
|
@@ -126,31 +126,32 @@ ${c.bold("Default behavior:")}
|
|
|
126
126
|
`);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
async function
|
|
129
|
+
async function configureProviderWithMetadata(paths, configure) {
|
|
130
130
|
const metadata = await readInstallMetadata(paths);
|
|
131
131
|
const packageVersion = await packageVersionFor(paths, metadata);
|
|
132
|
-
const
|
|
133
|
-
managedAuthKeys: getPreviousManagedCodexAuthKeys(metadata),
|
|
134
|
-
managedCodexAgentFiles: getPreviousManagedCodexAgentFiles(metadata)
|
|
135
|
-
});
|
|
132
|
+
const overrides = await configure(metadata);
|
|
136
133
|
await writeInstallMetadata(paths, finalizeProviderInstallMetadata({
|
|
137
134
|
previousMetadata: metadata,
|
|
138
135
|
packageVersion,
|
|
139
|
-
overrides
|
|
136
|
+
overrides
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function configureCodexForPaths(paths) {
|
|
141
|
+
await configureProviderWithMetadata(paths, async (metadata) => {
|
|
142
|
+
const result = await configureCodexApi(paths, promptApi, {
|
|
143
|
+
managedAuthKeys: getPreviousManagedCodexAuthKeys(metadata),
|
|
144
|
+
managedCodexAgentFiles: getPreviousManagedCodexAgentFiles(metadata)
|
|
145
|
+
});
|
|
146
|
+
return {
|
|
140
147
|
managedCodexAuthKeys: result.managedAuthKeys,
|
|
141
148
|
managedCodexAgentFiles: result.managedCodexAgentFiles
|
|
142
|
-
}
|
|
143
|
-
})
|
|
149
|
+
};
|
|
150
|
+
});
|
|
144
151
|
}
|
|
145
152
|
|
|
146
153
|
async function configureClaudeForPaths(paths) {
|
|
147
|
-
|
|
148
|
-
const packageVersion = await packageVersionFor(paths, metadata);
|
|
149
|
-
await configureClaudeApi(paths, promptApi);
|
|
150
|
-
await writeInstallMetadata(paths, finalizeProviderInstallMetadata({
|
|
151
|
-
previousMetadata: metadata,
|
|
152
|
-
packageVersion
|
|
153
|
-
}));
|
|
154
|
+
await configureProviderWithMetadata(paths, () => configureClaudeApi(paths, promptApi));
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
async function runFullInit(options) {
|
|
@@ -176,13 +177,13 @@ async function runFullInit(options) {
|
|
|
176
177
|
await installCliTool("pi", promptApi);
|
|
177
178
|
}
|
|
178
179
|
if (await confirmOrCancel({ message: "是否配置 Pi 当前有效 Provider API?", initialValue: commandExists("pi") })) {
|
|
179
|
-
await configurePiApi(options.paths,
|
|
180
|
+
await configurePiApi(options.paths, ensurePiResourcesLinkedWithReport, promptApi);
|
|
180
181
|
}
|
|
181
182
|
if (await confirmOrCancel({ message: "是否填写 grok-search 环境变量?", initialValue: false })) {
|
|
182
|
-
await configureGrokSearchEnv(options.paths, (paths) =>
|
|
183
|
+
await configureGrokSearchEnv(options.paths, (paths) => ensureSkillPresentWithReport(paths, "grok-search"), promptApi);
|
|
183
184
|
}
|
|
184
185
|
if (await confirmOrCancel({ message: "是否填写 context7-auto-research 环境变量?", initialValue: false })) {
|
|
185
|
-
await configureContext7Env(options.paths, (paths) =>
|
|
186
|
+
await configureContext7Env(options.paths, (paths) => ensureSkillPresentWithReport(paths, "context7-auto-research"), promptApi);
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
const conflictCount = uniqueReportPaths(installReport.conflicts).length;
|
|
@@ -205,35 +206,31 @@ async function runInteractiveMenu(options) {
|
|
|
205
206
|
return opt;
|
|
206
207
|
};
|
|
207
208
|
const cliToolMenus = {
|
|
208
|
-
|
|
209
|
-
tool: "pi",
|
|
209
|
+
pi: {
|
|
210
210
|
title: "Pi",
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
"pi-api": async () => configurePiApi(options.paths, ensurePiResourcesLinkedWithReport, promptApi)
|
|
214
|
-
}
|
|
211
|
+
install: () => installCliTool("pi", promptApi),
|
|
212
|
+
configure: () => configurePiApi(options.paths, ensurePiResourcesLinkedWithReport, promptApi)
|
|
215
213
|
},
|
|
216
|
-
|
|
217
|
-
tool: "codex",
|
|
214
|
+
codex: {
|
|
218
215
|
title: "Codex",
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
"codex-api": async () => configureCodexForPaths(options.paths)
|
|
222
|
-
}
|
|
216
|
+
install: () => installCliTool("codex", promptApi),
|
|
217
|
+
configure: () => configureCodexForPaths(options.paths)
|
|
223
218
|
},
|
|
224
|
-
|
|
225
|
-
tool: "claude",
|
|
219
|
+
claude: {
|
|
226
220
|
title: "Claude Code",
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
"claude-api": async () => configureClaudeForPaths(options.paths)
|
|
230
|
-
}
|
|
221
|
+
install: () => installCliTool("claude", promptApi),
|
|
222
|
+
configure: () => configureClaudeForPaths(options.paths)
|
|
231
223
|
}
|
|
232
224
|
};
|
|
233
|
-
const runCliToolMenu = async (
|
|
225
|
+
const runCliToolMenu = async (tool) => {
|
|
226
|
+
const menu = cliToolMenus[tool];
|
|
227
|
+
const actions = {
|
|
228
|
+
[`${tool}-api`]: menu.configure,
|
|
229
|
+
[`${tool}-install`]: menu.install
|
|
230
|
+
};
|
|
234
231
|
while (true) {
|
|
235
232
|
const choice = await p.select({
|
|
236
|
-
message: `请选择 ${title} 操作`,
|
|
233
|
+
message: `请选择 ${menu.title} 操作`,
|
|
237
234
|
options: buildCliToolMenuDescriptors(tool).map(buildOption),
|
|
238
235
|
initialValue: `${tool}-api`
|
|
239
236
|
});
|
|
@@ -259,9 +256,9 @@ async function runInteractiveMenu(options) {
|
|
|
259
256
|
}),
|
|
260
257
|
"grok-search": async () => configureGrokSearchEnv(options.paths, (paths) => ensureSkillPresentWithReport(paths, "grok-search"), promptApi),
|
|
261
258
|
context7: async () => configureContext7Env(options.paths, (paths) => ensureSkillPresentWithReport(paths, "context7-auto-research"), promptApi),
|
|
262
|
-
"pi-cli": async () => runCliToolMenu(
|
|
263
|
-
"codex-cli": async () => runCliToolMenu(
|
|
264
|
-
"claude-cli": async () => runCliToolMenu(
|
|
259
|
+
"pi-cli": async () => runCliToolMenu("pi"),
|
|
260
|
+
"codex-cli": async () => runCliToolMenu("codex"),
|
|
261
|
+
"claude-cli": async () => runCliToolMenu("claude")
|
|
265
262
|
};
|
|
266
263
|
|
|
267
264
|
while (true) {
|
|
@@ -376,10 +373,4 @@ async function main(argv, runtime = {}) {
|
|
|
376
373
|
}
|
|
377
374
|
}
|
|
378
375
|
|
|
379
|
-
export {
|
|
380
|
-
main,
|
|
381
|
-
presentInstallReport,
|
|
382
|
-
printHelp,
|
|
383
|
-
runFullInit,
|
|
384
|
-
runInteractiveMenu
|
|
385
|
-
};
|
|
376
|
+
export { main, presentInstallReport };
|
package/lib/cli/prompts.mjs
CHANGED
|
@@ -11,22 +11,10 @@ const interactiveMenuDescriptors = [
|
|
|
11
11
|
{ value: "exit", label: "退出", group: "exit" }
|
|
12
12
|
];
|
|
13
13
|
const interactiveMenuDefaultValue = "full-init";
|
|
14
|
-
const
|
|
15
|
-
pi:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
{ value: "back", label: "返回上一级" }
|
|
19
|
-
],
|
|
20
|
-
codex: [
|
|
21
|
-
{ value: "codex-api", label: "配置 Codex API" },
|
|
22
|
-
{ value: "codex-install", label: "安装/更新 Codex" },
|
|
23
|
-
{ value: "back", label: "返回上一级" }
|
|
24
|
-
],
|
|
25
|
-
claude: [
|
|
26
|
-
{ value: "claude-api", label: "配置 Claude Code API" },
|
|
27
|
-
{ value: "claude-install", label: "安装/更新 Claude Code" },
|
|
28
|
-
{ value: "back", label: "返回上一级" }
|
|
29
|
-
]
|
|
14
|
+
const cliToolLabels = {
|
|
15
|
+
pi: "Pi",
|
|
16
|
+
codex: "Codex",
|
|
17
|
+
claude: "Claude Code"
|
|
30
18
|
};
|
|
31
19
|
class CancelledError extends Error {
|
|
32
20
|
constructor(message = "用户取消") {
|
|
@@ -36,9 +24,13 @@ class CancelledError extends Error {
|
|
|
36
24
|
}
|
|
37
25
|
|
|
38
26
|
function buildCliToolMenuDescriptors(tool) {
|
|
39
|
-
const
|
|
40
|
-
if (!
|
|
41
|
-
return
|
|
27
|
+
const label = cliToolLabels[tool];
|
|
28
|
+
if (!label) throw new Error(`Unknown CLI tool: ${tool}`);
|
|
29
|
+
return [
|
|
30
|
+
{ value: `${tool}-api`, label: `配置 ${label} API` },
|
|
31
|
+
{ value: `${tool}-install`, label: `安装/更新 ${label}` },
|
|
32
|
+
{ value: "back", label: "返回上一级" }
|
|
33
|
+
];
|
|
42
34
|
}
|
|
43
35
|
|
|
44
36
|
function required(message = "此项不能为空") {
|
|
@@ -79,6 +71,16 @@ function assertNotCancelled(value) {
|
|
|
79
71
|
if (p.isCancel(value)) throw new CancelledError();
|
|
80
72
|
}
|
|
81
73
|
|
|
74
|
+
async function passwordOrExisting({ message, existingValue, requiredMessage = "API Key 不能为空" }) {
|
|
75
|
+
const input = await p.password(passwordPromptOptions(
|
|
76
|
+
message,
|
|
77
|
+
existingValue,
|
|
78
|
+
requiredMessage ? requiredUnlessExisting(existingValue, requiredMessage) : undefined
|
|
79
|
+
));
|
|
80
|
+
assertNotCancelled(input);
|
|
81
|
+
return resolvePasswordValue(input, existingValue);
|
|
82
|
+
}
|
|
83
|
+
|
|
82
84
|
async function confirmOrCancel({ message, initialValue = false }) {
|
|
83
85
|
const value = await p.confirm({ message, initialValue, active: "是", inactive: "否" });
|
|
84
86
|
assertNotCancelled(value);
|
|
@@ -98,6 +100,7 @@ export {
|
|
|
98
100
|
confirmOrCancel,
|
|
99
101
|
interactiveMenuDefaultValue,
|
|
100
102
|
interactiveMenuDescriptors,
|
|
103
|
+
passwordOrExisting,
|
|
101
104
|
passwordPromptOptions,
|
|
102
105
|
required,
|
|
103
106
|
requiredUnlessExisting,
|
package/lib/config/dotenv.mjs
CHANGED
|
@@ -34,14 +34,6 @@ function quoteEnvValue(value) {
|
|
|
34
34
|
throw new Error("Environment value cannot be represented without changing its meaning");
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
function renderDotenv(values) {
|
|
38
|
-
const lines = Object.entries(values)
|
|
39
|
-
.filter(([, value]) => value !== undefined && value !== null && value !== "")
|
|
40
|
-
.sort(([left], [right]) => left.localeCompare(right))
|
|
41
|
-
.map(([key, value]) => `${key}=${quoteEnvValue(String(value))}`);
|
|
42
|
-
return lines.length ? `${lines.join("\n")}\n` : "";
|
|
43
|
-
}
|
|
44
|
-
|
|
45
37
|
function getDotenvLines(content) {
|
|
46
38
|
const lines = [];
|
|
47
39
|
for (let start = 0; start < content.length;) {
|
|
@@ -99,4 +91,4 @@ function updateDotenvContent(content, updates) {
|
|
|
99
91
|
return next;
|
|
100
92
|
}
|
|
101
93
|
|
|
102
|
-
export { parseDotenv, quoteEnvValue,
|
|
94
|
+
export { parseDotenv, quoteEnvValue, updateDotenvContent };
|
package/lib/config/store.mjs
CHANGED
|
@@ -6,7 +6,6 @@ import { stripJsonComments } from "./jsonc.mjs";
|
|
|
6
6
|
|
|
7
7
|
const newBackupMarker = ".abelworkflow.bak.";
|
|
8
8
|
const newBackupSuffixPattern = /^\d+-\d+-\d{10,}$/u;
|
|
9
|
-
const chmod = fs.chmod;
|
|
10
9
|
let uniqueFileIndex = 0;
|
|
11
10
|
|
|
12
11
|
function nextUniqueSuffix() {
|
|
@@ -225,21 +224,6 @@ async function updateLockedJson(path, updater, options = {}) {
|
|
|
225
224
|
}
|
|
226
225
|
}
|
|
227
226
|
|
|
228
|
-
async function backupExistingPath(targetPath, options = {}) {
|
|
229
|
-
return copyBackup(targetPath, options);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
async function backupPrivateFile(targetPath, content, options = {}) {
|
|
233
|
-
if (!(await pathExists(targetPath))) return null;
|
|
234
|
-
const backupLimit = options.backupLimit ?? 3;
|
|
235
|
-
if (backupLimit === 0) return null;
|
|
236
|
-
const backupPath = await createBackupPath(targetPath);
|
|
237
|
-
await fs.writeFile(backupPath, content, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
238
|
-
if (isPosix()) await fs.chmod(backupPath, 0o600);
|
|
239
|
-
await pruneNewBackups(targetPath, backupLimit);
|
|
240
|
-
return backupPath;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
227
|
async function backupIfNeeded(targetPath, options = {}) {
|
|
244
228
|
if (!(await pathExists(targetPath))) return null;
|
|
245
229
|
const backupLimit = options.backupLimit ?? 3;
|
|
@@ -281,19 +265,8 @@ async function updateDotenvFile(path, updates, options = {}) {
|
|
|
281
265
|
return writeText(path, updateDotenvContent(content, updates), options);
|
|
282
266
|
}
|
|
283
267
|
|
|
284
|
-
async function writeJsonFileSafe(path, data, options = {}) {
|
|
285
|
-
return writeJson(path, data, { ...options, backupLimit: options.backupLimit ?? 0 });
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
async function writeJsonFileWithBackup(path, data, options = {}) {
|
|
289
|
-
return writeJson(path, data, options);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
268
|
export {
|
|
293
|
-
backupExistingPath,
|
|
294
269
|
backupIfNeeded,
|
|
295
|
-
backupPrivateFile,
|
|
296
|
-
chmod,
|
|
297
270
|
ensurePrivateJsonFile,
|
|
298
271
|
pathExists,
|
|
299
272
|
pathTargetExists,
|
|
@@ -304,7 +277,5 @@ export {
|
|
|
304
277
|
updateDotenvFile,
|
|
305
278
|
updateLockedJson,
|
|
306
279
|
writeJson,
|
|
307
|
-
writeJsonFileSafe,
|
|
308
|
-
writeJsonFileWithBackup,
|
|
309
280
|
writeText
|
|
310
281
|
};
|
package/lib/config/toml.mjs
CHANGED
|
@@ -46,6 +46,15 @@ function removeTomlSection(content, sectionName) {
|
|
|
46
46
|
return `${content.slice(0, section.start)}${content.slice(section.end)}`;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function removeTomlSectionField(content, sectionName, field) {
|
|
50
|
+
const document = parseTomlDocument(content);
|
|
51
|
+
const section = findTomlSection(document, sectionName);
|
|
52
|
+
const entry = section && findTomlAssignment(document.assignments, field, section);
|
|
53
|
+
return entry
|
|
54
|
+
? `${content.slice(0, entry.start)}${content.slice(entry.lineEnd)}`
|
|
55
|
+
: content;
|
|
56
|
+
}
|
|
57
|
+
|
|
49
58
|
function buildTomlSection(sectionName, values, lineEnding = "\n") {
|
|
50
59
|
const lines = [`[${sectionName}]`];
|
|
51
60
|
for (const [key, value] of Object.entries(values)) {
|
|
@@ -399,14 +408,6 @@ function findTomlAssignment(assignments, field, section) {
|
|
|
399
408
|
));
|
|
400
409
|
}
|
|
401
410
|
|
|
402
|
-
function splitTopLevelTomlContent(content) {
|
|
403
|
-
const { topLevelEnd } = parseTomlDocument(content);
|
|
404
|
-
return {
|
|
405
|
-
topLevel: topLevelEnd === -1 ? content : content.slice(0, topLevelEnd),
|
|
406
|
-
rest: topLevelEnd === -1 ? "" : content.slice(topLevelEnd)
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
|
|
410
411
|
function extractTopLevelTomlEntries(content) {
|
|
411
412
|
const document = parseTomlDocument(content);
|
|
412
413
|
return document.assignments
|
|
@@ -631,8 +632,8 @@ export {
|
|
|
631
632
|
parseTomlSection,
|
|
632
633
|
readTopLevelTomlString,
|
|
633
634
|
removeTomlSection,
|
|
635
|
+
removeTomlSectionField,
|
|
634
636
|
removeTopLevelTomlField,
|
|
635
|
-
splitTopLevelTomlContent,
|
|
636
637
|
updateTomlSectionFields,
|
|
637
638
|
updateTopLevelTomlField
|
|
638
639
|
};
|
package/lib/installer/assets.mjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import * as fs from "node:fs/promises";
|
|
3
2
|
import { join, relative, resolve } from "node:path";
|
|
4
3
|
import {
|
|
@@ -8,6 +7,7 @@ import {
|
|
|
8
7
|
readJsonFileSafe,
|
|
9
8
|
writeText
|
|
10
9
|
} from "../config/store.mjs";
|
|
10
|
+
import { hashBytes } from "../utils.mjs";
|
|
11
11
|
import { createInstallReport, readInstallMetadata } from "./state.mjs";
|
|
12
12
|
|
|
13
13
|
const ignoredSkillPathPatterns = [
|
|
@@ -69,10 +69,6 @@ function mapGitignoreTemplate(relativePath) {
|
|
|
69
69
|
return relativePath.replace(/(^|\/)gitignore\.template$/u, "$1.gitignore");
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
function hashBytes(content) {
|
|
73
|
-
return createHash("sha256").update(content).digest("hex");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
72
|
function shouldCopySkillPath(skillsRoot, sourcePath) {
|
|
77
73
|
const relativePath = normalizeRelativePath(relative(skillsRoot, sourcePath));
|
|
78
74
|
if (!relativePath) return true;
|
|
@@ -316,9 +312,9 @@ async function ensureManagedContainerDirectory(targetPath, sourcePath) {
|
|
|
316
312
|
export {
|
|
317
313
|
collectManagedAssets,
|
|
318
314
|
ensureManagedContainerDirectory,
|
|
319
|
-
hashBytes,
|
|
320
315
|
pathsReferToSameEntry,
|
|
321
316
|
readManagedFiles,
|
|
317
|
+
readPackageVersion,
|
|
322
318
|
shouldCopySkillPath,
|
|
323
319
|
syncManagedFiles
|
|
324
320
|
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { readManagedFiles, syncManagedFiles } from "./assets.mjs";
|
|
1
|
+
import { pathExists } from "../config/store.mjs";
|
|
2
|
+
import { createPaths } from "../paths.mjs";
|
|
3
|
+
import { readManagedFiles, readPackageVersion, syncManagedFiles } from "./assets.mjs";
|
|
5
4
|
import { linkClaude, linkCodex, linkPi } from "./links.mjs";
|
|
6
5
|
import {
|
|
7
6
|
buildInstallMetadata,
|
|
@@ -27,15 +26,6 @@ function validatedPaths(paths) {
|
|
|
27
26
|
});
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
function resolveInstallPaths(options = {}) {
|
|
31
|
-
if (options.paths) return validatedPaths(options.paths);
|
|
32
|
-
return createPaths({
|
|
33
|
-
homeDir: options.homeDir ?? defaultPaths.homeDir,
|
|
34
|
-
packageRoot: options.packageRoot ?? defaultPaths.packageRoot,
|
|
35
|
-
agentsDir: options.agentsDir ?? defaultPaths.agentsDir
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
29
|
function reportFromLinkResults(results) {
|
|
40
30
|
const report = createInstallReport();
|
|
41
31
|
for (const result of results) {
|
|
@@ -58,12 +48,17 @@ async function packageVersionFor(paths, previousMetadata) {
|
|
|
58
48
|
&& previousMetadata.packageVersion) {
|
|
59
49
|
return previousMetadata.packageVersion;
|
|
60
50
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
51
|
+
return readPackageVersion(paths);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function writeInstallState(paths, assetResult, linkedTargets) {
|
|
55
|
+
const metadata = buildInstallMetadata({
|
|
56
|
+
previousMetadata: assetResult.previousMetadata,
|
|
57
|
+
packageVersion: assetResult.packageVersion,
|
|
58
|
+
managedFiles: assetResult.managedFiles,
|
|
59
|
+
linkedTargets
|
|
60
|
+
});
|
|
61
|
+
await writeInstallMetadata(paths, metadata);
|
|
67
62
|
}
|
|
68
63
|
|
|
69
64
|
async function installWorkflow(options) {
|
|
@@ -94,20 +89,12 @@ async function installWorkflow(options) {
|
|
|
94
89
|
const codexResults = await linkCodex(paths, previousLinkedTargets, linkOptions);
|
|
95
90
|
const piResults = await linkPi(paths, previousLinkedTargets, linkOptions);
|
|
96
91
|
const linkResults = [...claudeResults, ...codexResults, ...piResults];
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const metadata = buildInstallMetadata({
|
|
100
|
-
previousMetadata,
|
|
101
|
-
packageVersion: assetResult.packageVersion,
|
|
102
|
-
managedFiles: assetResult.managedFiles,
|
|
103
|
-
linkedTargets
|
|
104
|
-
});
|
|
105
|
-
await writeInstallMetadata(paths, metadata);
|
|
92
|
+
await writeInstallState(paths, assetResult, mergeLinkedTargets(previousLinkedTargets, linkResults));
|
|
106
93
|
return mergeInstallReports(assetResult.report, reportFromLinkResults(linkResults));
|
|
107
94
|
}
|
|
108
95
|
|
|
109
96
|
async function installManagedWorkflow(options = {}) {
|
|
110
|
-
return installWorkflow({ ...options, paths:
|
|
97
|
+
return installWorkflow({ ...options, paths: validatedPaths(options.paths) });
|
|
111
98
|
}
|
|
112
99
|
|
|
113
100
|
async function ensureSkillPresent(inputPaths, skillName) {
|
|
@@ -118,13 +105,7 @@ async function ensureSkillPresent(inputPaths, skillName) {
|
|
|
118
105
|
force: false,
|
|
119
106
|
pathPrefix: `skills/${skillName}/`
|
|
120
107
|
});
|
|
121
|
-
|
|
122
|
-
previousMetadata,
|
|
123
|
-
packageVersion: assetResult.packageVersion,
|
|
124
|
-
managedFiles: assetResult.managedFiles,
|
|
125
|
-
linkedTargets: previousMetadata.linkedTargets ?? {}
|
|
126
|
-
});
|
|
127
|
-
await writeInstallMetadata(paths, metadata);
|
|
108
|
+
await writeInstallState(paths, assetResult, previousMetadata.linkedTargets ?? {});
|
|
128
109
|
return assetResult.report;
|
|
129
110
|
}
|
|
130
111
|
|
|
@@ -134,13 +115,7 @@ async function ensurePiResourcesLinked(inputPaths) {
|
|
|
134
115
|
const assetResult = await syncManagedFiles({ paths, force: false });
|
|
135
116
|
const previousLinkedTargets = previousMetadata.linkedTargets ?? {};
|
|
136
117
|
const piResults = await linkPi(paths, previousLinkedTargets, { force: false });
|
|
137
|
-
|
|
138
|
-
previousMetadata,
|
|
139
|
-
packageVersion: assetResult.packageVersion,
|
|
140
|
-
managedFiles: assetResult.managedFiles,
|
|
141
|
-
linkedTargets: mergeLinkedTargets(previousLinkedTargets, piResults)
|
|
142
|
-
});
|
|
143
|
-
await writeInstallMetadata(paths, metadata);
|
|
118
|
+
await writeInstallState(paths, assetResult, mergeLinkedTargets(previousLinkedTargets, piResults));
|
|
144
119
|
return mergeInstallReports(assetResult.report, reportFromLinkResults(piResults));
|
|
145
120
|
}
|
|
146
121
|
|
package/lib/installer/links.mjs
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
3
|
import { backupIfNeeded, pathExists, pathTargetExists } from "../config/store.mjs";
|
|
4
|
-
import { isWindows } from "../paths.mjs";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
function shouldForceFileSymlinkFailure(kind) {
|
|
8
|
-
return process.env.ABELWORKFLOW_TEST_FORCE_FILE_SYMLINK_EPERM === "1" && isWindows() && kind === "file";
|
|
9
|
-
}
|
|
4
|
+
import { containsPath, isWindows } from "../paths.mjs";
|
|
5
|
+
import { hashBytes } from "../utils.mjs";
|
|
6
|
+
import { ensureManagedContainerDirectory } from "./assets.mjs";
|
|
10
7
|
|
|
11
8
|
async function createManagedTargetState(targetPath, sourcePath, kind, mode, status) {
|
|
12
9
|
let targetHash;
|
|
@@ -16,12 +13,7 @@ async function createManagedTargetState(targetPath, sourcePath, kind, mode, stat
|
|
|
16
13
|
return { targetPath, sourcePath, kind, mode, status, ...(targetHash ? { targetHash } : {}) };
|
|
17
14
|
}
|
|
18
15
|
|
|
19
|
-
async function createSymlink(targetPath, sourcePath, linkType
|
|
20
|
-
if (shouldForceFileSymlinkFailure(kind)) {
|
|
21
|
-
const error = new Error("simulated EPERM");
|
|
22
|
-
error.code = "EPERM";
|
|
23
|
-
throw error;
|
|
24
|
-
}
|
|
16
|
+
async function createSymlink(targetPath, sourcePath, linkType) {
|
|
25
17
|
await fs.symlink(sourcePath, targetPath, linkType);
|
|
26
18
|
}
|
|
27
19
|
|
|
@@ -150,7 +142,7 @@ async function ensureManagedLink(targetPath, sourcePath, kind, previousLinkedTar
|
|
|
150
142
|
|
|
151
143
|
const linkType = isWindows() ? (kind === "dir" ? "junction" : "file") : kind;
|
|
152
144
|
try {
|
|
153
|
-
await createSymlink(targetPath, sourcePath, linkType
|
|
145
|
+
await createSymlink(targetPath, sourcePath, linkType);
|
|
154
146
|
return createManagedTargetState(targetPath, sourcePath, kind, "symlink", "linked");
|
|
155
147
|
} catch (error) {
|
|
156
148
|
if (!shouldFallbackToManagedFile(error, kind)) throw error;
|
|
@@ -258,11 +250,8 @@ async function getPiExtensionNames(extensionsDir) {
|
|
|
258
250
|
}
|
|
259
251
|
|
|
260
252
|
function isWithinManagedRoot(targetPath, managedSourceRoot, pathOps = { isAbsolute, relative, sep }) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
&& !pathOps.isAbsolute(relativePath)
|
|
264
|
-
&& relativePath !== ".."
|
|
265
|
-
&& !relativePath.startsWith(`..${pathOps.sep}`);
|
|
253
|
+
return pathOps.relative(managedSourceRoot, targetPath) !== ""
|
|
254
|
+
&& containsPath(managedSourceRoot, targetPath, pathOps);
|
|
266
255
|
}
|
|
267
256
|
|
|
268
257
|
async function pruneManagedTargets(targetDir, managedSourceRoot, expectedNames, previousLinkedTargets, options = {}) {
|
package/lib/installer/state.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { readJsonFileSafe, writeJson } from "../config/store.mjs";
|
|
3
|
+
import { isManagedCodexAgentFileEntry } from "../utils.mjs";
|
|
3
4
|
|
|
4
5
|
function metadataPathFor(paths) {
|
|
5
6
|
return join(paths.agentsDir, paths.installMetadataName);
|
|
@@ -30,11 +31,7 @@ function getPreviousManagedCodexAuthKeys(previousMetadata = {}) {
|
|
|
30
31
|
function normalizeManagedCodexAgentFiles(value = {}) {
|
|
31
32
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
32
33
|
return sortObject(Object.fromEntries(Object.entries(value).filter(([name, hash]) => (
|
|
33
|
-
name
|
|
34
|
-
&& !name.includes("/")
|
|
35
|
-
&& !name.includes("\\")
|
|
36
|
-
&& typeof hash === "string"
|
|
37
|
-
&& /^[a-f0-9]{64}$/u.test(hash)
|
|
34
|
+
isManagedCodexAgentFileEntry(name, hash)
|
|
38
35
|
))));
|
|
39
36
|
}
|
|
40
37
|
|
|
@@ -63,7 +60,7 @@ function buildInstallMetadata({
|
|
|
63
60
|
managedCodexAgentFiles = getPreviousManagedCodexAgentFiles(previousMetadata),
|
|
64
61
|
linkedTargets = {}
|
|
65
62
|
}) {
|
|
66
|
-
|
|
63
|
+
return {
|
|
67
64
|
schemaVersion: 2,
|
|
68
65
|
packageVersion,
|
|
69
66
|
managedFiles: sortObject(managedFiles),
|
|
@@ -71,10 +68,6 @@ function buildInstallMetadata({
|
|
|
71
68
|
managedCodexAgentFiles: normalizeManagedCodexAgentFiles(managedCodexAgentFiles),
|
|
72
69
|
linkedTargets: sortObject(linkedTargets)
|
|
73
70
|
};
|
|
74
|
-
if (typeof previousMetadata.installedAt === "string") {
|
|
75
|
-
metadata.installedAt = previousMetadata.installedAt;
|
|
76
|
-
}
|
|
77
|
-
return metadata;
|
|
78
71
|
}
|
|
79
72
|
|
|
80
73
|
function finalizeProviderInstallMetadata({
|
|
@@ -100,17 +93,6 @@ function finalizeProviderInstallMetadata({
|
|
|
100
93
|
});
|
|
101
94
|
}
|
|
102
95
|
|
|
103
|
-
function linkedTargetsFromResults(results) {
|
|
104
|
-
return Object.fromEntries(results
|
|
105
|
-
.filter((result) => result.sourcePath)
|
|
106
|
-
.map((result) => [result.targetPath, {
|
|
107
|
-
sourcePath: result.sourcePath,
|
|
108
|
-
kind: result.kind,
|
|
109
|
-
mode: result.mode,
|
|
110
|
-
...(result.targetHash ? { targetHash: result.targetHash } : {})
|
|
111
|
-
}]));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
96
|
function mergeLinkedTargets(previousLinkedTargets = {}, results = []) {
|
|
115
97
|
const nextLinkedTargets = { ...previousLinkedTargets };
|
|
116
98
|
for (const result of results) {
|
|
@@ -154,7 +136,6 @@ export {
|
|
|
154
136
|
finalizeProviderInstallMetadata,
|
|
155
137
|
getPreviousManagedCodexAgentFiles,
|
|
156
138
|
getPreviousManagedCodexAuthKeys,
|
|
157
|
-
linkedTargetsFromResults,
|
|
158
139
|
mergeInstallReports,
|
|
159
140
|
mergeLinkedTargets,
|
|
160
141
|
metadataPathFor,
|
package/lib/providers/claude.mjs
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
|
-
import {
|
|
3
|
-
readJsonFileSafe,
|
|
4
|
-
writeJsonFileWithBackup
|
|
5
|
-
} from "../config/store.mjs";
|
|
2
|
+
import { readJsonFileSafe, writeJson } from "../config/store.mjs";
|
|
6
3
|
import { defaultPaths, maskSecret, pathToLabel } from "../paths.mjs";
|
|
7
4
|
|
|
8
5
|
const claudeModelEnvKeys = [
|
|
@@ -111,18 +108,12 @@ function buildClaudeApiSettings(settings, {
|
|
|
111
108
|
}
|
|
112
109
|
|
|
113
110
|
async function persistClaudeConfiguration(paths, { settings, metaConfig }) {
|
|
114
|
-
await
|
|
115
|
-
await
|
|
111
|
+
await writeJson(paths.claudeSettingsPath, settings, { sensitive: true });
|
|
112
|
+
await writeJson(paths.claudeMetaConfigPath, metaConfig, { sensitive: true });
|
|
116
113
|
}
|
|
117
114
|
|
|
118
115
|
async function configureClaudeApi(paths = defaultPaths, promptApi) {
|
|
119
|
-
const {
|
|
120
|
-
assertNotCancelled,
|
|
121
|
-
passwordPromptOptions,
|
|
122
|
-
required,
|
|
123
|
-
requiredUnlessExisting,
|
|
124
|
-
resolvePasswordValue
|
|
125
|
-
} = promptApi;
|
|
116
|
+
const { assertNotCancelled, passwordOrExisting, required } = promptApi;
|
|
126
117
|
const settings = await readJsonFileSafe(paths.claudeSettingsPath, {}, { sensitive: true });
|
|
127
118
|
const existing = getExistingClaudeApiConfig(settings);
|
|
128
119
|
|
|
@@ -133,13 +124,10 @@ async function configureClaudeApi(paths = defaultPaths, promptApi) {
|
|
|
133
124
|
});
|
|
134
125
|
assertNotCancelled(baseUrl);
|
|
135
126
|
|
|
136
|
-
const
|
|
137
|
-
"Claude Code API Key",
|
|
138
|
-
existing.key
|
|
139
|
-
|
|
140
|
-
));
|
|
141
|
-
assertNotCancelled(key);
|
|
142
|
-
const finalKey = resolvePasswordValue(key, existing.key);
|
|
127
|
+
const finalKey = await passwordOrExisting({
|
|
128
|
+
message: "Claude Code API Key",
|
|
129
|
+
existingValue: existing.key
|
|
130
|
+
});
|
|
143
131
|
|
|
144
132
|
const model = await p.text({
|
|
145
133
|
message: "Claude Code 模型",
|
package/lib/providers/codex.mjs
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import { lstat, mkdir, readFile, readdir, unlink } from "node:fs/promises";
|
|
3
2
|
import { join } from "node:path";
|
|
4
3
|
import * as p from "@clack/prompts";
|
|
5
4
|
import {
|
|
6
5
|
pathExists,
|
|
7
6
|
readJsonFileSafe,
|
|
8
|
-
|
|
7
|
+
writeJson,
|
|
9
8
|
writeText
|
|
10
9
|
} from "../config/store.mjs";
|
|
11
10
|
import {
|
|
@@ -18,11 +17,13 @@ import {
|
|
|
18
17
|
parseTomlSection,
|
|
19
18
|
readTopLevelTomlString,
|
|
20
19
|
removeTomlSection,
|
|
20
|
+
removeTomlSectionField,
|
|
21
21
|
removeTopLevelTomlField,
|
|
22
22
|
updateTomlSectionFields,
|
|
23
23
|
updateTopLevelTomlField
|
|
24
24
|
} from "../config/toml.mjs";
|
|
25
25
|
import { defaultPaths, maskSecret, pathToLabel } from "../paths.mjs";
|
|
26
|
+
import { hashBytes, isManagedCodexAgentFileEntry } from "../utils.mjs";
|
|
26
27
|
import { normalizeOpenAiBaseUrl } from "./pi.mjs";
|
|
27
28
|
|
|
28
29
|
const CODEX_ENV_KEY = "OPENAI_API_KEY";
|
|
@@ -98,7 +99,7 @@ function mergeCodexTemplateDefaults(content, templateContent) {
|
|
|
98
99
|
const currentDeveloperInstructions = readTopLevelTomlString(nextContent, "developer_instructions");
|
|
99
100
|
const templateDeveloperInstructions = readTopLevelTomlString(templateContent, "developer_instructions");
|
|
100
101
|
if (templateDeveloperInstructions && publishedCodexDeveloperInstructionHashes.has(
|
|
101
|
-
|
|
102
|
+
hashBytes(currentDeveloperInstructions)
|
|
102
103
|
)) {
|
|
103
104
|
nextContent = updateTopLevelTomlField(
|
|
104
105
|
nextContent,
|
|
@@ -125,10 +126,6 @@ async function loadBundledCodexConfigTemplate(paths = defaultPaths) {
|
|
|
125
126
|
return readFile(paths.codexTemplateConfigPath, "utf8");
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
function sha256(content) {
|
|
129
|
-
return createHash("sha256").update(content).digest("hex");
|
|
130
|
-
}
|
|
131
|
-
|
|
132
129
|
async function readCodexAgentTarget(path) {
|
|
133
130
|
try {
|
|
134
131
|
const targetStat = await lstat(path);
|
|
@@ -175,14 +172,6 @@ async function ensureCodexAgentContainer(homeDir) {
|
|
|
175
172
|
return targetDir;
|
|
176
173
|
}
|
|
177
174
|
|
|
178
|
-
function isCodexAgentOwnership(name, hash) {
|
|
179
|
-
return name.endsWith(".toml")
|
|
180
|
-
&& !name.includes("/")
|
|
181
|
-
&& !name.includes("\\")
|
|
182
|
-
&& typeof hash === "string"
|
|
183
|
-
&& /^[a-f0-9]{64}$/u.test(hash);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
175
|
function isPublishedCodexAgent(name, hash) {
|
|
187
176
|
return publishedCodexAgentHashes[name]?.has(hash) ?? false;
|
|
188
177
|
}
|
|
@@ -205,9 +194,9 @@ async function deployBundledCodexAgents(paths = defaultPaths, previousManagedFil
|
|
|
205
194
|
const source = join(paths.codexTemplateAgentsPath, name);
|
|
206
195
|
const target = join(targetDir, name);
|
|
207
196
|
const sourceContent = await readFile(source);
|
|
208
|
-
const sourceHash =
|
|
197
|
+
const sourceHash = hashBytes(sourceContent);
|
|
209
198
|
const current = await readCodexAgentTarget(target);
|
|
210
|
-
const currentHash = current.content ?
|
|
199
|
+
const currentHash = current.content ? hashBytes(current.content) : "";
|
|
211
200
|
|
|
212
201
|
if (!current.exists) {
|
|
213
202
|
await writeText(target, sourceContent, { backupLimit: 0 });
|
|
@@ -222,7 +211,7 @@ async function deployBundledCodexAgents(paths = defaultPaths, previousManagedFil
|
|
|
222
211
|
result.updated.push(target);
|
|
223
212
|
} else {
|
|
224
213
|
result.conflicts.push(target);
|
|
225
|
-
if (
|
|
214
|
+
if (isManagedCodexAgentFileEntry(name, previousManagedFiles?.[name])) {
|
|
226
215
|
result.managedFiles[name] = previousManagedFiles[name];
|
|
227
216
|
}
|
|
228
217
|
continue;
|
|
@@ -237,11 +226,11 @@ async function deployBundledCodexAgents(paths = defaultPaths, previousManagedFil
|
|
|
237
226
|
? Object.entries(previousManagedFiles).sort(([left], [right]) => left.localeCompare(right))
|
|
238
227
|
: [];
|
|
239
228
|
for (const [name, previousHash] of previousEntries) {
|
|
240
|
-
if (bundledNames.has(name) || !
|
|
229
|
+
if (bundledNames.has(name) || !isManagedCodexAgentFileEntry(name, previousHash)) continue;
|
|
241
230
|
const target = join(targetDir, name);
|
|
242
231
|
const current = await readCodexAgentTarget(target);
|
|
243
232
|
if (!current.exists) continue;
|
|
244
|
-
if (current.content &&
|
|
233
|
+
if (current.content && hashBytes(current.content) === previousHash) {
|
|
245
234
|
await unlink(target);
|
|
246
235
|
continue;
|
|
247
236
|
}
|
|
@@ -291,17 +280,15 @@ async function getExistingCodexApiConfig(paths = defaultPaths) {
|
|
|
291
280
|
|
|
292
281
|
async function persistCodexConfiguration(paths, { content, auth }) {
|
|
293
282
|
await writeText(paths.codexConfigPath, content);
|
|
294
|
-
await
|
|
283
|
+
await writeJson(paths.codexAuthPath, auth, { sensitive: true });
|
|
295
284
|
}
|
|
296
285
|
|
|
297
286
|
async function configureCodexApi(paths = defaultPaths, promptApi, ownership = {}) {
|
|
298
287
|
const {
|
|
299
288
|
assertNotCancelled,
|
|
300
289
|
confirmOrCancel,
|
|
301
|
-
|
|
302
|
-
required
|
|
303
|
-
requiredUnlessExisting,
|
|
304
|
-
resolvePasswordValue
|
|
290
|
+
passwordOrExisting,
|
|
291
|
+
required
|
|
305
292
|
} = promptApi;
|
|
306
293
|
const existing = await getExistingCodexApiConfig(paths);
|
|
307
294
|
const providerId = existing.providerId || "abelworkflow";
|
|
@@ -314,13 +301,10 @@ async function configureCodexApi(paths = defaultPaths, promptApi, ownership = {}
|
|
|
314
301
|
assertNotCancelled(baseUrlInput);
|
|
315
302
|
const baseUrl = normalizeOpenAiBaseUrl(baseUrlInput);
|
|
316
303
|
|
|
317
|
-
const
|
|
318
|
-
"Codex 第三方 API Key",
|
|
319
|
-
existing.apiKey
|
|
320
|
-
|
|
321
|
-
));
|
|
322
|
-
assertNotCancelled(apiKey);
|
|
323
|
-
const finalApiKey = resolvePasswordValue(apiKey, existing.apiKey);
|
|
304
|
+
const finalApiKey = await passwordOrExisting({
|
|
305
|
+
message: "Codex 第三方 API Key",
|
|
306
|
+
existingValue: existing.apiKey
|
|
307
|
+
});
|
|
324
308
|
|
|
325
309
|
const shouldDeploySubagents = await confirmOrCancel({ message: "是否部署 Codex subagents 配置?", initialValue: true });
|
|
326
310
|
let managedCodexAgentFiles = { ...(ownership.managedCodexAgentFiles ?? {}) };
|
|
@@ -389,13 +373,16 @@ function buildCodexConfigContent(currentContent, {
|
|
|
389
373
|
if (includeSubagentDefaults && readTopLevelTomlString(content, "approvals_reviewer") === "reviewer") {
|
|
390
374
|
content = updateTopLevelTomlField(content, "approvals_reviewer", "guardian_subagent");
|
|
391
375
|
}
|
|
376
|
+
const providerSectionName = getCodexProviderSectionName(providerId);
|
|
392
377
|
content = updateTopLevelTomlField(content, "model_provider", providerId);
|
|
393
|
-
content =
|
|
394
|
-
content =
|
|
378
|
+
content = removeTopLevelTomlField(content, "preferred_auth_method");
|
|
379
|
+
content = removeTopLevelTomlField(content, "temp_env_key");
|
|
380
|
+
content = removeTomlSectionField(content, providerSectionName, "temp_env_key");
|
|
381
|
+
content = removeTomlSectionField(content, providerSectionName, "env_key");
|
|
382
|
+
content = updateTomlSectionFields(content, providerSectionName, {
|
|
395
383
|
name: providerName,
|
|
396
384
|
base_url: baseUrl,
|
|
397
385
|
wire_api: "responses",
|
|
398
|
-
temp_env_key: CODEX_ENV_KEY,
|
|
399
386
|
requires_openai_auth: true,
|
|
400
387
|
supports_websockets: true
|
|
401
388
|
});
|
|
@@ -408,8 +395,13 @@ function mergeCodexAuthData(auth, envKey, apiKey, managedAuthKeys = []) {
|
|
|
408
395
|
for (const managedAuthKey of managedAuthKeys) {
|
|
409
396
|
delete nextAuth[managedAuthKey];
|
|
410
397
|
}
|
|
411
|
-
if (apiKey)
|
|
412
|
-
|
|
398
|
+
if (apiKey) {
|
|
399
|
+
nextAuth.auth_mode = "apikey";
|
|
400
|
+
nextAuth[envKey] = apiKey;
|
|
401
|
+
} else {
|
|
402
|
+
delete nextAuth[envKey];
|
|
403
|
+
if (nextAuth.auth_mode === "apikey") delete nextAuth.auth_mode;
|
|
404
|
+
}
|
|
413
405
|
return nextAuth;
|
|
414
406
|
}
|
|
415
407
|
|
package/lib/providers/pi.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
readJsonFileSafe,
|
|
6
6
|
readJsoncFileSafe,
|
|
7
7
|
updateLockedJson,
|
|
8
|
-
|
|
8
|
+
writeJson,
|
|
9
9
|
writeText
|
|
10
10
|
} from "../config/store.mjs";
|
|
11
11
|
import { defaultPaths, maskSecret, pathToLabel } from "../paths.mjs";
|
|
@@ -412,7 +412,7 @@ async function persistPiConfiguration(paths, configuration, operations = {}) {
|
|
|
412
412
|
`${JSON.stringify(value, null, 2)}\n`,
|
|
413
413
|
{ sensitive: true }
|
|
414
414
|
));
|
|
415
|
-
const writeSettings = operations.writeSettings ?? ((path, value) =>
|
|
415
|
+
const writeSettings = operations.writeSettings ?? ((path, value) => writeJson(path, value));
|
|
416
416
|
await updateAuth(paths.piAuthPath, configuration.providerId, configuration.apiKey);
|
|
417
417
|
await writeModels(paths.piModelsPath, configuration.models);
|
|
418
418
|
await writeSettings(paths.piSettingsPath, configuration.settings);
|
|
@@ -428,10 +428,8 @@ async function configurePiApi(paths = defaultPaths, ensurePiResourcesLinked = as
|
|
|
428
428
|
}
|
|
429
429
|
const {
|
|
430
430
|
assertNotCancelled,
|
|
431
|
-
|
|
431
|
+
passwordOrExisting,
|
|
432
432
|
required,
|
|
433
|
-
requiredUnlessExisting,
|
|
434
|
-
resolvePasswordValue,
|
|
435
433
|
selectOrCancel
|
|
436
434
|
} = promptApi;
|
|
437
435
|
const {
|
|
@@ -477,13 +475,10 @@ async function configurePiApi(paths = defaultPaths, ensurePiResourcesLinked = as
|
|
|
477
475
|
...(initialApi ? { initialValue: initialApi } : {})
|
|
478
476
|
});
|
|
479
477
|
|
|
480
|
-
const
|
|
481
|
-
`${providerLabel} API Key`,
|
|
482
|
-
existing.apiKey
|
|
483
|
-
|
|
484
|
-
));
|
|
485
|
-
assertNotCancelled(apiKey);
|
|
486
|
-
const finalApiKey = resolvePasswordValue(apiKey, existing.apiKey);
|
|
478
|
+
const finalApiKey = await passwordOrExisting({
|
|
479
|
+
message: `${providerLabel} API Key`,
|
|
480
|
+
existingValue: existing.apiKey
|
|
481
|
+
});
|
|
487
482
|
|
|
488
483
|
const modelIdsText = await p.text({
|
|
489
484
|
message: `${providerLabel} 模型 ID(多个用逗号分隔)`,
|
package/lib/providers/skills.mjs
CHANGED
|
@@ -21,10 +21,8 @@ async function configureGrokSearchEnv(paths, ensureSkillPresent = async () => {}
|
|
|
21
21
|
const {
|
|
22
22
|
assertNotCancelled,
|
|
23
23
|
confirmOrCancel,
|
|
24
|
-
|
|
25
|
-
required
|
|
26
|
-
requiredUnlessExisting,
|
|
27
|
-
resolvePasswordValue
|
|
24
|
+
passwordOrExisting,
|
|
25
|
+
required
|
|
28
26
|
} = promptApi;
|
|
29
27
|
await ensureSkillPresent(paths);
|
|
30
28
|
const envPath = join(paths.agentsDir, "skills", "grok-search", ".env");
|
|
@@ -36,13 +34,11 @@ async function configureGrokSearchEnv(paths, ensureSkillPresent = async () => {}
|
|
|
36
34
|
});
|
|
37
35
|
assertNotCancelled(baseUrl);
|
|
38
36
|
|
|
39
|
-
const
|
|
40
|
-
"Grok API Key",
|
|
41
|
-
existing.GROK_API_KEY,
|
|
42
|
-
|
|
43
|
-
)
|
|
44
|
-
assertNotCancelled(apiKey);
|
|
45
|
-
const finalApiKey = resolvePasswordValue(apiKey, existing.GROK_API_KEY);
|
|
37
|
+
const finalApiKey = await passwordOrExisting({
|
|
38
|
+
message: "Grok API Key",
|
|
39
|
+
existingValue: existing.GROK_API_KEY,
|
|
40
|
+
requiredMessage: "Grok API Key 不能为空"
|
|
41
|
+
});
|
|
46
42
|
|
|
47
43
|
const model = await p.text({
|
|
48
44
|
message: "Grok 默认模型",
|
|
@@ -65,15 +61,13 @@ async function configureGrokSearchEnv(paths, ensureSkillPresent = async () => {}
|
|
|
65
61
|
: null;
|
|
66
62
|
if (useTavily) assertNotCancelled(tavilyUrl);
|
|
67
63
|
|
|
68
|
-
const
|
|
69
|
-
? await
|
|
70
|
-
"Tavily API Key",
|
|
71
|
-
existing.TAVILY_API_KEY,
|
|
72
|
-
|
|
73
|
-
)
|
|
74
|
-
:
|
|
75
|
-
if (useTavily) assertNotCancelled(tavilyKey);
|
|
76
|
-
const finalTavilyKey = useTavily ? resolvePasswordValue(tavilyKey, existing.TAVILY_API_KEY) : null;
|
|
64
|
+
const finalTavilyKey = useTavily
|
|
65
|
+
? await passwordOrExisting({
|
|
66
|
+
message: "Tavily API Key",
|
|
67
|
+
existingValue: existing.TAVILY_API_KEY,
|
|
68
|
+
requiredMessage: "Tavily API Key 不能为空"
|
|
69
|
+
})
|
|
70
|
+
: null;
|
|
77
71
|
|
|
78
72
|
await updateSkillEnvFile(envPath, {
|
|
79
73
|
GROK_API_URL: baseUrl,
|
|
@@ -88,16 +82,15 @@ async function configureGrokSearchEnv(paths, ensureSkillPresent = async () => {}
|
|
|
88
82
|
}
|
|
89
83
|
|
|
90
84
|
async function configureContext7Env(paths, ensureSkillPresent = async () => {}, promptApi) {
|
|
91
|
-
const {
|
|
85
|
+
const { passwordOrExisting } = promptApi;
|
|
92
86
|
await ensureSkillPresent(paths);
|
|
93
87
|
const envPath = join(paths.agentsDir, "skills", "context7-auto-research", ".env");
|
|
94
88
|
const existing = await readSkillEnvFile(envPath);
|
|
95
|
-
const
|
|
96
|
-
"Context7 API Key(可选)",
|
|
97
|
-
existing.CONTEXT7_API_KEY
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const finalApiKey = resolvePasswordValue(apiKey, existing.CONTEXT7_API_KEY);
|
|
89
|
+
const finalApiKey = await passwordOrExisting({
|
|
90
|
+
message: "Context7 API Key(可选)",
|
|
91
|
+
existingValue: existing.CONTEXT7_API_KEY,
|
|
92
|
+
requiredMessage: null
|
|
93
|
+
});
|
|
101
94
|
|
|
102
95
|
await updateSkillEnvFile(envPath, {
|
|
103
96
|
CONTEXT7_API_KEY: finalApiKey
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
personality = "pragmatic"
|
|
2
2
|
model_provider = "abelworkflow"
|
|
3
3
|
disable_response_storage = true
|
|
4
|
-
preferred_auth_method = "apikey"
|
|
5
4
|
approvals_reviewer = "guardian_subagent"
|
|
6
5
|
approval_policy = "on-request"
|
|
7
|
-
sandbox_mode = "
|
|
6
|
+
sandbox_mode = "danger-full-access"
|
|
8
7
|
model = "gpt-5.6-sol"
|
|
9
8
|
model_reasoning_effort = "high"
|
|
10
9
|
network_access = true
|
package/lib/utils.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
function hashBytes(content) {
|
|
4
|
+
return createHash("sha256").update(content).digest("hex");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function isManagedCodexAgentFileEntry(name, hash) {
|
|
8
|
+
return name.endsWith(".toml")
|
|
9
|
+
&& !name.includes("/")
|
|
10
|
+
&& !name.includes("\\")
|
|
11
|
+
&& typeof hash === "string"
|
|
12
|
+
&& /^[a-f0-9]{64}$/u.test(hash);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { hashBytes, isManagedCodexAgentFileEntry };
|