abelworkflow 1.1.0 → 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/extensions/pi-gpt-responses-compat/index.ts +0 -17
- 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 +10 -46
- package/lib/providers/codex.mjs +29 -37
- package/lib/providers/pi.mjs +9 -49
- 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/extensions/pi-gpt-responses-compat/tls-fetch.mjs +0 -188
|
@@ -1,23 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { createNodeInsecureDispatcher, installProviderTlsFetch } from "./tls-fetch.mjs";
|
|
3
|
-
|
|
4
|
-
let nodeInsecureDispatcher: unknown;
|
|
5
|
-
|
|
6
|
-
function getNodeInsecureDispatcher() {
|
|
7
|
-
if (nodeInsecureDispatcher) return nodeInsecureDispatcher;
|
|
8
|
-
nodeInsecureDispatcher = createNodeInsecureDispatcher();
|
|
9
|
-
return nodeInsecureDispatcher;
|
|
10
|
-
}
|
|
11
2
|
|
|
12
3
|
export default function (pi: ExtensionAPI) {
|
|
13
|
-
if (typeof globalThis.fetch === "function") {
|
|
14
|
-
const runtime = typeof (globalThis as any).Bun === "undefined" ? "node" : "bun";
|
|
15
|
-
installProviderTlsFetch({
|
|
16
|
-
runtime,
|
|
17
|
-
insecureDispatcher: runtime === "node" ? getNodeInsecureDispatcher : undefined
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
|
|
21
4
|
pi.on("before_provider_request", (event, ctx) => {
|
|
22
5
|
const payload = event.payload as any;
|
|
23
6
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return;
|
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,
|