abelworkflow 1.1.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +30 -29
- package/extensions/gpt-responses-compat.ts +213 -0
- package/extensions/k2think-compat.ts +40 -0
- package/lib/cli/args.mjs +5 -19
- package/lib/cli/main.mjs +105 -72
- package/lib/cli/prompts.mjs +9 -2
- package/lib/config/dotenv.mjs +1 -1
- package/lib/config/jsonc.mjs +1 -1
- package/lib/config/store.mjs +197 -45
- package/lib/config/toml.mjs +147 -453
- package/lib/installer/assets.mjs +67 -45
- package/lib/installer/install.mjs +202 -45
- package/lib/installer/links.mjs +124 -101
- package/lib/installer/lock.mjs +82 -0
- package/lib/installer/state.mjs +14 -21
- package/lib/paths.mjs +9 -9
- package/lib/providers/claude.mjs +58 -114
- package/lib/providers/codex.mjs +101 -198
- package/lib/providers/pi.mjs +331 -497
- package/lib/providers/skills.mjs +88 -68
- package/lib/providers/url.mjs +35 -0
- package/lib/templates/workflow/commands/abel-design.md +23 -161
- package/lib/templates/workflow/commands/abel-diagnose.md +30 -56
- package/lib/templates/workflow/commands/abel-implement.md +30 -150
- package/lib/templates/workflow/commands/abel-init.md +1 -1
- package/lib/tools/cli-installer.mjs +29 -190
- package/package.json +7 -9
- package/skills/context7-auto-research/context7-api.cjs +75 -17
- package/skills/dev-browser/SKILL.md +15 -1
- package/skills/dev-browser/dist/scripts/start.d.ts +0 -1
- package/skills/dev-browser/dist/scripts/start.js +8 -36
- package/skills/dev-browser/dist/src/client.d.ts +3 -4
- package/skills/dev-browser/dist/src/client.js +117 -117
- package/skills/dev-browser/dist/src/entrypoint.d.ts +1 -1
- package/skills/dev-browser/dist/src/entrypoint.js +38 -31
- package/skills/dev-browser/dist/src/index.d.ts +0 -1
- package/skills/dev-browser/dist/src/index.js +0 -1
- package/skills/dev-browser/dist/src/page-api.d.ts +0 -1
- package/skills/dev-browser/dist/src/page-api.js +0 -1
- package/skills/dev-browser/dist/src/relay.d.ts +0 -1
- package/skills/dev-browser/dist/src/relay.js +53 -7
- package/skills/dev-browser/dist/src/runtime.d.ts +3 -14
- package/skills/dev-browser/dist/src/runtime.js +5 -15
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +0 -1
- package/skills/dev-browser/dist/src/snapshot/browser-script.js +0 -1
- package/skills/dev-browser/dist/src/snapshot/index.d.ts +0 -1
- package/skills/dev-browser/dist/src/snapshot/index.js +0 -1
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts +0 -1
- package/skills/dev-browser/dist/src/snapshot/inject.js +0 -1
- package/skills/dev-browser/dist/src/standalone.d.ts +0 -1
- package/skills/dev-browser/dist/src/standalone.js +2 -3
- package/skills/dev-browser/dist/src/startup.d.ts +3 -12
- package/skills/dev-browser/dist/src/startup.js +6 -10
- package/skills/dev-browser/dist/src/target-registry.d.ts +0 -1
- package/skills/dev-browser/dist/src/target-registry.js +0 -1
- package/skills/dev-browser/dist/src/types.d.ts +0 -1
- package/skills/dev-browser/dist/src/types.js +0 -1
- package/skills/dev-browser/package-lock.json +1 -1
- package/skills/dev-browser/package.json +2 -2
- package/skills/grok-search/.env.example +1 -1
- package/skills/grok-search/SKILL.md +13 -16
- package/skills/grok-search/scripts/_dotenv.py +1 -1
- package/skills/grok-search/scripts/groksearch_cli.py +271 -195
- package/skills/time/SKILL.md +5 -2
- package/skills/time/scripts/time_cli.py +61 -22
- package/extensions/pi-gpt-responses-compat/index.ts +0 -25
- package/lib/templates/codex/config-base.toml +0 -103
- package/skills/dev-browser/dist/scripts/start.d.ts.map +0 -1
- package/skills/dev-browser/dist/scripts/start.js.map +0 -1
- package/skills/dev-browser/dist/src/client.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/client.js.map +0 -1
- package/skills/dev-browser/dist/src/entrypoint.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/entrypoint.js.map +0 -1
- package/skills/dev-browser/dist/src/index.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/index.js.map +0 -1
- package/skills/dev-browser/dist/src/page-api.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/page-api.js.map +0 -1
- package/skills/dev-browser/dist/src/relay.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/relay.js.map +0 -1
- package/skills/dev-browser/dist/src/runtime.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/runtime.js.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/browser-script.js.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/index.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/index.js.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/inject.js.map +0 -1
- package/skills/dev-browser/dist/src/standalone.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/standalone.js.map +0 -1
- package/skills/dev-browser/dist/src/startup.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/startup.js.map +0 -1
- package/skills/dev-browser/dist/src/target-registry.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/target-registry.js.map +0 -1
- package/skills/dev-browser/dist/src/types.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/types.js.map +0 -1
- package/skills/grok-search/gitignore.template +0 -4
- package/skills/grok-search/scripts/groksearch_entry.py +0 -131
package/lib/installer/links.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "nod
|
|
|
3
3
|
import { backupIfNeeded, pathExists, pathTargetExists } from "../config/store.mjs";
|
|
4
4
|
import { containsPath, isWindows } from "../paths.mjs";
|
|
5
5
|
import { hashBytes } from "../utils.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { ensureManagedContainerDirectories } from "./assets.mjs";
|
|
7
7
|
|
|
8
8
|
async function createManagedTargetState(targetPath, sourcePath, kind, mode, status) {
|
|
9
9
|
let targetHash;
|
|
@@ -17,6 +17,11 @@ async function createSymlink(targetPath, sourcePath, linkType) {
|
|
|
17
17
|
await fs.symlink(sourcePath, targetPath, linkType);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
function recordLinkResult(results, options, result) {
|
|
21
|
+
results.push(result);
|
|
22
|
+
options.onResult?.(result);
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
async function symlinkPointsTo(targetPath, sourcePath) {
|
|
21
26
|
try {
|
|
22
27
|
if (!(await fs.lstat(targetPath)).isSymbolicLink()) return false;
|
|
@@ -34,14 +39,6 @@ function previousStateOwnsSource(previousState, sourcePath, kind) {
|
|
|
34
39
|
&& previousState.kind === kind;
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
function isUnverifiedWindowsCopy(previousState, kind) {
|
|
38
|
-
return isWindows()
|
|
39
|
-
&& kind === "file"
|
|
40
|
-
&& previousState?.kind === "file"
|
|
41
|
-
&& previousState.mode === "copy"
|
|
42
|
-
&& !previousState.targetHash;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
42
|
async function targetMatchesPreviousState(targetPath, previousState) {
|
|
46
43
|
if (!previousState || !(await pathExists(targetPath))) return false;
|
|
47
44
|
const targetStat = await fs.lstat(targetPath);
|
|
@@ -84,24 +81,40 @@ async function ordinaryFileMatchesSource(targetPath, sourcePath, kind) {
|
|
|
84
81
|
}
|
|
85
82
|
}
|
|
86
83
|
|
|
87
|
-
async function moveReplaceableTarget(targetPath) {
|
|
84
|
+
async function moveReplaceableTarget(targetPath, options) {
|
|
88
85
|
const targetStat = await fs.lstat(targetPath);
|
|
89
86
|
if (targetStat.isDirectory() && !targetStat.isSymbolicLink()) return false;
|
|
87
|
+
await options.beforeMutation?.();
|
|
90
88
|
await backupIfNeeded(targetPath);
|
|
91
89
|
return true;
|
|
92
90
|
}
|
|
93
91
|
|
|
92
|
+
async function removeRecordedTarget(targetPath, previousState, options = {}) {
|
|
93
|
+
if (!(await pathExists(targetPath))) return { targetPath, status: "removed" };
|
|
94
|
+
const matchesPrevious = await targetMatchesPreviousState(targetPath, previousState);
|
|
95
|
+
if (matchesPrevious) {
|
|
96
|
+
await options.beforeMutation?.();
|
|
97
|
+
await fs.rm(targetPath, { recursive: true, force: true });
|
|
98
|
+
return { targetPath, status: "removed" };
|
|
99
|
+
}
|
|
100
|
+
if (options.force && await moveReplaceableTarget(targetPath, options)) {
|
|
101
|
+
return { targetPath, status: "removed" };
|
|
102
|
+
}
|
|
103
|
+
return { targetPath, status: "conflict" };
|
|
104
|
+
}
|
|
105
|
+
|
|
94
106
|
async function ensureManagedLink(targetPath, sourcePath, kind, previousLinkedTargets = {}, options = {}) {
|
|
95
107
|
const force = options.force ?? false;
|
|
108
|
+
if (!(await pathExists(dirname(targetPath)))) await options.beforeMutation?.();
|
|
96
109
|
await fs.mkdir(dirname(targetPath), { recursive: true });
|
|
97
110
|
const sourceExists = await pathTargetExists(sourcePath);
|
|
98
111
|
const targetExists = await pathExists(targetPath);
|
|
99
112
|
const previousState = previousLinkedTargets[targetPath];
|
|
100
113
|
const wasPreviouslyManaged = previousStateOwnsSource(previousState, sourcePath, kind);
|
|
101
|
-
const isUnverifiedCopy = wasPreviouslyManaged && isUnverifiedWindowsCopy(previousState, kind);
|
|
102
114
|
|
|
103
115
|
if (targetExists && await symlinkPointsTo(targetPath, sourcePath)) {
|
|
104
116
|
if (!sourceExists) {
|
|
117
|
+
await options.beforeMutation?.();
|
|
105
118
|
await fs.rm(targetPath, { recursive: true, force: true });
|
|
106
119
|
return { targetPath, status: "removed" };
|
|
107
120
|
}
|
|
@@ -111,12 +124,12 @@ async function ensureManagedLink(targetPath, sourcePath, kind, previousLinkedTar
|
|
|
111
124
|
if (!sourceExists) {
|
|
112
125
|
if (!targetExists) return { targetPath, status: "removed" };
|
|
113
126
|
if (!wasPreviouslyManaged) return { targetPath, status: "skipped" };
|
|
114
|
-
if (isUnverifiedCopy) return { targetPath, status: "conflict" };
|
|
115
127
|
if (await targetMatchesPreviousState(targetPath, previousState)) {
|
|
128
|
+
await options.beforeMutation?.();
|
|
116
129
|
await fs.rm(targetPath, { recursive: true, force: true });
|
|
117
130
|
return { targetPath, status: "removed" };
|
|
118
131
|
}
|
|
119
|
-
if (force && await moveReplaceableTarget(targetPath)) return { targetPath, status: "removed" };
|
|
132
|
+
if (force && await moveReplaceableTarget(targetPath, options)) return { targetPath, status: "removed" };
|
|
120
133
|
return { targetPath, status: "conflict" };
|
|
121
134
|
}
|
|
122
135
|
|
|
@@ -125,23 +138,18 @@ async function ensureManagedLink(targetPath, sourcePath, kind, previousLinkedTar
|
|
|
125
138
|
}
|
|
126
139
|
|
|
127
140
|
if (targetExists) {
|
|
128
|
-
if (isUnverifiedCopy) {
|
|
129
|
-
if (await ordinaryFileMatchesSource(targetPath, sourcePath, kind)) {
|
|
130
|
-
return createManagedTargetState(targetPath, sourcePath, kind, "copy", "unchanged");
|
|
131
|
-
}
|
|
132
|
-
return { targetPath, status: "conflict" };
|
|
133
|
-
}
|
|
134
141
|
const matchesPrevious = wasPreviouslyManaged && await targetMatchesPreviousState(targetPath, previousState);
|
|
135
142
|
if (matchesPrevious && await ordinaryFileMatchesSource(targetPath, sourcePath, kind)) {
|
|
136
143
|
return createManagedTargetState(targetPath, sourcePath, kind, previousState.mode, "unchanged");
|
|
137
144
|
}
|
|
138
|
-
if ((!matchesPrevious && !force) || !(await moveReplaceableTarget(targetPath))) {
|
|
145
|
+
if ((!matchesPrevious && !force) || !(await moveReplaceableTarget(targetPath, options))) {
|
|
139
146
|
return { targetPath, status: "conflict" };
|
|
140
147
|
}
|
|
141
148
|
}
|
|
142
149
|
|
|
143
150
|
const linkType = isWindows() ? (kind === "dir" ? "junction" : "file") : kind;
|
|
144
151
|
try {
|
|
152
|
+
await options.beforeMutation?.();
|
|
145
153
|
await createSymlink(targetPath, sourcePath, linkType);
|
|
146
154
|
return createManagedTargetState(targetPath, sourcePath, kind, "symlink", "linked");
|
|
147
155
|
} catch (error) {
|
|
@@ -185,7 +193,8 @@ async function pathIsFile(path) {
|
|
|
185
193
|
}
|
|
186
194
|
}
|
|
187
195
|
|
|
188
|
-
async function ensureProviderRootDirectory(homeDir,
|
|
196
|
+
async function ensureProviderRootDirectory(homeDir, segments, options = {}) {
|
|
197
|
+
if (!(await pathExists(homeDir))) await options.beforeMutation?.();
|
|
189
198
|
await fs.mkdir(homeDir, { recursive: true });
|
|
190
199
|
let directory = homeDir;
|
|
191
200
|
for (const segment of segments) {
|
|
@@ -196,13 +205,20 @@ async function ensureProviderRootDirectory(homeDir, ...segments) {
|
|
|
196
205
|
} catch (error) {
|
|
197
206
|
if (error?.code !== "ENOENT") throw error;
|
|
198
207
|
try {
|
|
208
|
+
await options.beforeMutation?.();
|
|
199
209
|
await fs.mkdir(directory);
|
|
200
210
|
} catch (mkdirError) {
|
|
201
211
|
if (mkdirError?.code === "EEXIST") {
|
|
212
|
+
const concurrentTarget = await fs.lstat(directory);
|
|
213
|
+
if (!concurrentTarget.isSymbolicLink() && concurrentTarget.isDirectory()) continue;
|
|
202
214
|
throw new Error(`Provider root conflict at ${directory}`, { cause: mkdirError });
|
|
203
215
|
}
|
|
204
216
|
throw mkdirError;
|
|
205
217
|
}
|
|
218
|
+
const createdTarget = await fs.lstat(directory);
|
|
219
|
+
if (createdTarget.isSymbolicLink() || !createdTarget.isDirectory()) {
|
|
220
|
+
throw new Error(`Provider root conflict at ${directory}`);
|
|
221
|
+
}
|
|
206
222
|
continue;
|
|
207
223
|
}
|
|
208
224
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
@@ -257,37 +273,18 @@ function isWithinManagedRoot(targetPath, managedSourceRoot, pathOps = { isAbsolu
|
|
|
257
273
|
async function pruneManagedTargets(targetDir, managedSourceRoot, expectedNames, previousLinkedTargets, options = {}) {
|
|
258
274
|
const resolvedTargetDir = resolve(targetDir);
|
|
259
275
|
const expectedNameSet = new Set(expectedNames);
|
|
260
|
-
const
|
|
276
|
+
const candidates = [];
|
|
261
277
|
for (const [targetPath, previousState] of Object.entries(previousLinkedTargets)) {
|
|
262
278
|
const resolvedTargetPath = resolve(targetPath);
|
|
263
279
|
if (targetPath !== resolvedTargetPath || dirname(resolvedTargetPath) !== resolvedTargetDir) continue;
|
|
264
280
|
if (!isWithinManagedRoot(resolve(previousState.sourcePath), resolve(managedSourceRoot))) continue;
|
|
265
281
|
if (expectedNameSet.has(basename(resolvedTargetPath)) && await pathTargetExists(previousState.sourcePath)) continue;
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|| (isUnverifiedCopy && await ordinaryFileMatchesSource(
|
|
273
|
-
resolvedTargetPath,
|
|
274
|
-
previousState.sourcePath,
|
|
275
|
-
previousState.kind
|
|
276
|
-
));
|
|
277
|
-
if (matchesPrevious) {
|
|
278
|
-
await fs.rm(resolvedTargetPath, { recursive: true, force: true });
|
|
279
|
-
results.push({ targetPath, status: "removed" });
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
if (isUnverifiedCopy) {
|
|
283
|
-
results.push({ targetPath, status: "conflict" });
|
|
284
|
-
continue;
|
|
285
|
-
}
|
|
286
|
-
if (options.force && await moveReplaceableTarget(resolvedTargetPath)) {
|
|
287
|
-
results.push({ targetPath, status: "removed" });
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
results.push({ targetPath, status: "conflict" });
|
|
282
|
+
candidates.push([resolvedTargetPath, previousState]);
|
|
283
|
+
}
|
|
284
|
+
if (candidates.length > 0) await options.validateTargetDir?.(targetDir);
|
|
285
|
+
const results = [];
|
|
286
|
+
for (const [targetPath, previousState] of candidates) {
|
|
287
|
+
recordLinkResult(results, options, await removeRecordedTarget(targetPath, previousState, options));
|
|
291
288
|
}
|
|
292
289
|
return results;
|
|
293
290
|
}
|
|
@@ -304,7 +301,7 @@ async function linkSkillDirectories(baseDir, paths, previousLinkedTargets, optio
|
|
|
304
301
|
options
|
|
305
302
|
));
|
|
306
303
|
for (const skillName of skillNames) {
|
|
307
|
-
results
|
|
304
|
+
recordLinkResult(results, options, await ensureManagedLink(
|
|
308
305
|
join(baseDir, "skills", skillName),
|
|
309
306
|
join(skillsRoot, skillName),
|
|
310
307
|
"dir",
|
|
@@ -320,7 +317,7 @@ async function linkCommands(targetDir, paths, previousLinkedTargets, options) {
|
|
|
320
317
|
const commandFiles = await getCommandNames(commandsRoot);
|
|
321
318
|
const results = await pruneManagedTargets(targetDir, commandsRoot, commandFiles, previousLinkedTargets, options);
|
|
322
319
|
for (const fileName of commandFiles) {
|
|
323
|
-
results
|
|
320
|
+
recordLinkResult(results, options, await ensureManagedLink(
|
|
324
321
|
join(targetDir, fileName),
|
|
325
322
|
join(commandsRoot, fileName),
|
|
326
323
|
"file",
|
|
@@ -332,68 +329,56 @@ async function linkCommands(targetDir, paths, previousLinkedTargets, options) {
|
|
|
332
329
|
}
|
|
333
330
|
|
|
334
331
|
async function linkClaude(paths, previousLinkedTargets = {}, options = {}) {
|
|
335
|
-
const claudeDir = await ensureProviderRootDirectory(paths.homeDir, ".claude");
|
|
336
|
-
await
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
332
|
+
const claudeDir = await ensureProviderRootDirectory(paths.homeDir, [".claude"], options);
|
|
333
|
+
await ensureManagedContainerDirectories([
|
|
334
|
+
join(claudeDir, "commands"),
|
|
335
|
+
join(claudeDir, "skills")
|
|
336
|
+
], options);
|
|
337
|
+
const results = [];
|
|
338
|
+
recordLinkResult(results, options, await ensureManagedLink(
|
|
339
|
+
join(claudeDir, "CLAUDE.md"),
|
|
340
|
+
join(paths.agentsDir, "AGENTS.md"),
|
|
341
|
+
"file",
|
|
342
|
+
previousLinkedTargets,
|
|
343
|
+
options
|
|
344
|
+
));
|
|
345
|
+
results.push(...await linkCommands(join(claudeDir, "commands"), paths, previousLinkedTargets, options));
|
|
346
|
+
results.push(...await linkSkillDirectories(claudeDir, paths, previousLinkedTargets, options));
|
|
347
|
+
return results;
|
|
349
348
|
}
|
|
350
349
|
|
|
351
350
|
async function linkCodex(paths, previousLinkedTargets = {}, options = {}) {
|
|
352
|
-
const codexDir = await ensureProviderRootDirectory(paths.homeDir, ".codex");
|
|
353
|
-
await
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
351
|
+
const codexDir = await ensureProviderRootDirectory(paths.homeDir, [".codex"], options);
|
|
352
|
+
await ensureManagedContainerDirectories([
|
|
353
|
+
join(codexDir, "skills"),
|
|
354
|
+
join(codexDir, "prompts")
|
|
355
|
+
], options);
|
|
356
|
+
const results = [];
|
|
357
|
+
recordLinkResult(results, options, await ensureManagedLink(
|
|
358
|
+
join(codexDir, "AGENTS.md"),
|
|
359
|
+
join(paths.agentsDir, "AGENTS.md"),
|
|
360
|
+
"file",
|
|
361
|
+
previousLinkedTargets,
|
|
362
|
+
options
|
|
363
|
+
));
|
|
364
|
+
results.push(...await linkSkillDirectories(codexDir, paths, previousLinkedTargets, options));
|
|
365
|
+
results.push(...await linkCommands(join(codexDir, "prompts"), paths, previousLinkedTargets, options));
|
|
366
|
+
return results;
|
|
366
367
|
}
|
|
367
368
|
|
|
368
|
-
async function
|
|
369
|
-
const piAgentDir = await ensureProviderRootDirectory(paths.homeDir, ".pi", "agent");
|
|
370
|
-
await ensureManagedContainerDirectory(join(piAgentDir, "skills"), join(paths.agentsDir, "skills"));
|
|
371
|
-
await ensureManagedContainerDirectory(join(piAgentDir, "prompts"), join(paths.agentsDir, "commands"));
|
|
372
|
-
await ensureManagedContainerDirectory(join(piAgentDir, "extensions"), join(paths.agentsDir, "extensions"));
|
|
373
|
-
const results = [
|
|
374
|
-
await ensureManagedLink(
|
|
375
|
-
join(piAgentDir, "AGENTS.md"),
|
|
376
|
-
join(paths.agentsDir, "AGENTS.md"),
|
|
377
|
-
"file",
|
|
378
|
-
previousLinkedTargets,
|
|
379
|
-
options
|
|
380
|
-
),
|
|
381
|
-
...await linkSkillDirectories(piAgentDir, paths, previousLinkedTargets, options),
|
|
382
|
-
...await linkCommands(join(piAgentDir, "prompts"), paths, previousLinkedTargets, options)
|
|
383
|
-
];
|
|
384
|
-
const extensionsRoot = join(paths.agentsDir, "extensions");
|
|
369
|
+
async function linkPiExtensionsInDirectory(targetDir, extensionsRoot, previousLinkedTargets, options) {
|
|
385
370
|
const extensionNames = await getPiExtensionNames(extensionsRoot);
|
|
386
|
-
results
|
|
387
|
-
|
|
371
|
+
const results = await pruneManagedTargets(
|
|
372
|
+
targetDir,
|
|
388
373
|
extensionsRoot,
|
|
389
374
|
extensionNames,
|
|
390
375
|
previousLinkedTargets,
|
|
391
376
|
options
|
|
392
|
-
)
|
|
377
|
+
);
|
|
393
378
|
for (const entryName of extensionNames) {
|
|
394
379
|
const sourcePath = join(extensionsRoot, entryName);
|
|
395
|
-
results
|
|
396
|
-
join(
|
|
380
|
+
recordLinkResult(results, options, await ensureManagedLink(
|
|
381
|
+
join(targetDir, entryName),
|
|
397
382
|
sourcePath,
|
|
398
383
|
await pathIsDirectory(sourcePath) ? "dir" : "file",
|
|
399
384
|
previousLinkedTargets,
|
|
@@ -403,14 +388,52 @@ async function linkPi(paths, previousLinkedTargets = {}, options = {}) {
|
|
|
403
388
|
return results;
|
|
404
389
|
}
|
|
405
390
|
|
|
391
|
+
async function linkPiExtensions(paths, previousLinkedTargets = {}, options = {}) {
|
|
392
|
+
const piAgentDir = await ensureProviderRootDirectory(paths.homeDir, [".pi", "agent"], options);
|
|
393
|
+
const targetDir = join(piAgentDir, "extensions");
|
|
394
|
+
await ensureManagedContainerDirectories([targetDir], options);
|
|
395
|
+
return linkPiExtensionsInDirectory(
|
|
396
|
+
targetDir,
|
|
397
|
+
join(paths.agentsDir, "extensions"),
|
|
398
|
+
previousLinkedTargets,
|
|
399
|
+
options
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async function linkPi(paths, previousLinkedTargets = {}, options = {}) {
|
|
404
|
+
const piAgentDir = await ensureProviderRootDirectory(paths.homeDir, [".pi", "agent"], options);
|
|
405
|
+
await ensureManagedContainerDirectories([
|
|
406
|
+
join(piAgentDir, "extensions"),
|
|
407
|
+
join(piAgentDir, "skills"),
|
|
408
|
+
join(piAgentDir, "prompts")
|
|
409
|
+
], options);
|
|
410
|
+
const results = [];
|
|
411
|
+
recordLinkResult(results, options, await ensureManagedLink(
|
|
412
|
+
join(piAgentDir, "AGENTS.md"),
|
|
413
|
+
join(paths.agentsDir, "AGENTS.md"),
|
|
414
|
+
"file",
|
|
415
|
+
previousLinkedTargets,
|
|
416
|
+
options
|
|
417
|
+
));
|
|
418
|
+
results.push(...await linkSkillDirectories(piAgentDir, paths, previousLinkedTargets, options));
|
|
419
|
+
results.push(...await linkCommands(join(piAgentDir, "prompts"), paths, previousLinkedTargets, options));
|
|
420
|
+
results.push(...await linkPiExtensionsInDirectory(
|
|
421
|
+
join(piAgentDir, "extensions"),
|
|
422
|
+
join(paths.agentsDir, "extensions"),
|
|
423
|
+
previousLinkedTargets,
|
|
424
|
+
options
|
|
425
|
+
));
|
|
426
|
+
return results;
|
|
427
|
+
}
|
|
428
|
+
|
|
406
429
|
export {
|
|
407
430
|
ensureManagedLink,
|
|
431
|
+
ensureProviderRootDirectory,
|
|
408
432
|
getCommandNames,
|
|
409
433
|
isWithinManagedRoot,
|
|
410
434
|
linkClaude,
|
|
411
435
|
linkCodex,
|
|
412
436
|
linkPi,
|
|
413
|
-
|
|
414
|
-
pathIsFile,
|
|
437
|
+
linkPiExtensions,
|
|
415
438
|
pruneManagedTargets
|
|
416
439
|
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import lockfile from "proper-lockfile";
|
|
4
|
+
|
|
5
|
+
async function readDeploymentRootIdentity(agentsDir) {
|
|
6
|
+
try {
|
|
7
|
+
const target = await fs.lstat(agentsDir);
|
|
8
|
+
if (target.isSymbolicLink() || !target.isDirectory()) {
|
|
9
|
+
throw new Error(`Deployment root conflict at ${agentsDir}`);
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
canonicalPath: await fs.realpath(agentsDir),
|
|
13
|
+
dev: target.dev,
|
|
14
|
+
ino: target.ino
|
|
15
|
+
};
|
|
16
|
+
} catch (error) {
|
|
17
|
+
if (error?.code === "ENOENT") return null;
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function sameIdentity(left, right) {
|
|
23
|
+
return Boolean(left && right)
|
|
24
|
+
&& left.canonicalPath === right.canonicalPath
|
|
25
|
+
&& left.dev === right.dev
|
|
26
|
+
&& left.ino === right.ino;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function assertDeploymentRootIdentity(agentsDir, expected) {
|
|
30
|
+
let current;
|
|
31
|
+
try {
|
|
32
|
+
current = await readDeploymentRootIdentity(agentsDir);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new Error(`Deployment root changed at ${agentsDir}`, { cause: error });
|
|
35
|
+
}
|
|
36
|
+
if (!sameIdentity(current, expected)) {
|
|
37
|
+
throw new Error(`Deployment root changed at ${agentsDir}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function withDeploymentLock(paths, operation, options = {}) {
|
|
42
|
+
await fs.mkdir(dirname(paths.agentsDir), { recursive: true });
|
|
43
|
+
const beforeWait = await readDeploymentRootIdentity(paths.agentsDir);
|
|
44
|
+
const release = await lockfile.lock(paths.agentsDir, {
|
|
45
|
+
realpath: false,
|
|
46
|
+
lockfilePath: `${paths.agentsDir}.abelworkflow-operation.lock`,
|
|
47
|
+
retries: options.retries ?? {
|
|
48
|
+
retries: 100,
|
|
49
|
+
factor: 1,
|
|
50
|
+
minTimeout: 50,
|
|
51
|
+
maxTimeout: 50,
|
|
52
|
+
randomize: false
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
try {
|
|
56
|
+
let identity;
|
|
57
|
+
try {
|
|
58
|
+
identity = await readDeploymentRootIdentity(paths.agentsDir);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
throw new Error(`Deployment root changed at ${paths.agentsDir}`, { cause: error });
|
|
61
|
+
}
|
|
62
|
+
if (beforeWait && !sameIdentity(identity, beforeWait)) {
|
|
63
|
+
throw new Error(`Deployment root changed at ${paths.agentsDir}`);
|
|
64
|
+
}
|
|
65
|
+
if (!identity) {
|
|
66
|
+
try {
|
|
67
|
+
await fs.mkdir(paths.agentsDir);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (error?.code !== "EEXIST") throw error;
|
|
70
|
+
}
|
|
71
|
+
identity = await readDeploymentRootIdentity(paths.agentsDir);
|
|
72
|
+
}
|
|
73
|
+
if (!identity) throw new Error(`Deployment root changed at ${paths.agentsDir}`);
|
|
74
|
+
const guard = () => assertDeploymentRootIdentity(paths.agentsDir, identity);
|
|
75
|
+
await guard();
|
|
76
|
+
return await operation(guard);
|
|
77
|
+
} finally {
|
|
78
|
+
await release();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { withDeploymentLock };
|
package/lib/installer/state.mjs
CHANGED
|
@@ -18,16 +18,6 @@ function objectField(value) {
|
|
|
18
18
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function stringArray(value) {
|
|
22
|
-
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function getPreviousManagedCodexAuthKeys(previousMetadata = {}) {
|
|
26
|
-
return isMetadataV2(previousMetadata)
|
|
27
|
-
? stringArray(previousMetadata.managedCodexAuthKeys)
|
|
28
|
-
: [];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
21
|
function normalizeManagedCodexAgentFiles(value = {}) {
|
|
32
22
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
33
23
|
return sortObject(Object.fromEntries(Object.entries(value).filter(([name, hash]) => (
|
|
@@ -41,30 +31,38 @@ function getPreviousManagedCodexAgentFiles(previousMetadata = {}) {
|
|
|
41
31
|
}
|
|
42
32
|
|
|
43
33
|
async function readInstallMetadata(paths) {
|
|
44
|
-
const metadata = await readJsonFileSafe(metadataPathFor(paths), {});
|
|
34
|
+
const metadata = await readJsonFileSafe(metadataPathFor(paths), {}, { safeRoot: paths.agentsDir });
|
|
45
35
|
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
46
36
|
throw new TypeError(`Install metadata in ${metadataPathFor(paths)} must contain a JSON object`);
|
|
47
37
|
}
|
|
38
|
+
if (Object.keys(metadata).length > 0 && !isMetadataV2(metadata)) {
|
|
39
|
+
throw new Error(`Unsupported install metadata schema in ${metadataPathFor(paths)}`);
|
|
40
|
+
}
|
|
48
41
|
return metadata;
|
|
49
42
|
}
|
|
50
43
|
|
|
51
|
-
async function writeInstallMetadata(paths, metadata) {
|
|
52
|
-
|
|
44
|
+
async function writeInstallMetadata(paths, metadata, options = {}) {
|
|
45
|
+
await options.guard?.();
|
|
46
|
+
return writeJson(metadataPathFor(paths), metadata, {
|
|
47
|
+
backupLimit: 0,
|
|
48
|
+
safeRoot: paths.agentsDir
|
|
49
|
+
});
|
|
53
50
|
}
|
|
54
51
|
|
|
55
52
|
function buildInstallMetadata({
|
|
56
53
|
previousMetadata = {},
|
|
57
54
|
packageVersion,
|
|
55
|
+
workflowInstalled = isMetadataV2(previousMetadata)
|
|
56
|
+
&& previousMetadata.workflowInstalled === true,
|
|
58
57
|
managedFiles = {},
|
|
59
|
-
managedCodexAuthKeys = getPreviousManagedCodexAuthKeys(previousMetadata),
|
|
60
58
|
managedCodexAgentFiles = getPreviousManagedCodexAgentFiles(previousMetadata),
|
|
61
59
|
linkedTargets = {}
|
|
62
60
|
}) {
|
|
63
61
|
return {
|
|
64
62
|
schemaVersion: 2,
|
|
65
63
|
packageVersion,
|
|
64
|
+
...(workflowInstalled ? { workflowInstalled: true } : {}),
|
|
66
65
|
managedFiles: sortObject(managedFiles),
|
|
67
|
-
managedCodexAuthKeys: [...new Set(managedCodexAuthKeys)].sort(),
|
|
68
66
|
managedCodexAgentFiles: normalizeManagedCodexAgentFiles(managedCodexAgentFiles),
|
|
69
67
|
linkedTargets: sortObject(linkedTargets)
|
|
70
68
|
};
|
|
@@ -84,12 +82,9 @@ function finalizeProviderInstallMetadata({
|
|
|
84
82
|
? previousMetadata.packageVersion
|
|
85
83
|
: packageVersion,
|
|
86
84
|
managedFiles: isV2 ? objectField(previousMetadata.managedFiles) : {},
|
|
87
|
-
managedCodexAuthKeys: stringArray(
|
|
88
|
-
overrides.managedCodexAuthKeys ?? getPreviousManagedCodexAuthKeys(previousMetadata)
|
|
89
|
-
),
|
|
90
85
|
managedCodexAgentFiles: overrides.managedCodexAgentFiles
|
|
91
86
|
?? getPreviousManagedCodexAgentFiles(previousMetadata),
|
|
92
|
-
linkedTargets: objectField(previousMetadata.linkedTargets)
|
|
87
|
+
linkedTargets: isV2 ? objectField(previousMetadata.linkedTargets) : {}
|
|
93
88
|
});
|
|
94
89
|
}
|
|
95
90
|
|
|
@@ -135,10 +130,8 @@ export {
|
|
|
135
130
|
createInstallReport,
|
|
136
131
|
finalizeProviderInstallMetadata,
|
|
137
132
|
getPreviousManagedCodexAgentFiles,
|
|
138
|
-
getPreviousManagedCodexAuthKeys,
|
|
139
133
|
mergeInstallReports,
|
|
140
134
|
mergeLinkedTargets,
|
|
141
|
-
metadataPathFor,
|
|
142
135
|
readInstallMetadata,
|
|
143
136
|
writeInstallMetadata
|
|
144
137
|
};
|
package/lib/paths.mjs
CHANGED
|
@@ -25,10 +25,8 @@ function createPaths({
|
|
|
25
25
|
workflowTemplateRoot,
|
|
26
26
|
installMetadataName: ".abelworkflow-install.json",
|
|
27
27
|
claudeSettingsPath: join(absoluteHomeDir, ".claude", "settings.json"),
|
|
28
|
-
claudeMetaConfigPath: join(absoluteHomeDir, ".claude.json"),
|
|
29
28
|
codexConfigPath: join(absoluteHomeDir, ".codex", "config.toml"),
|
|
30
29
|
codexAuthPath: join(absoluteHomeDir, ".codex", "auth.json"),
|
|
31
|
-
codexTemplateConfigPath: join(codexTemplateRoot, "config-base.toml"),
|
|
32
30
|
codexTemplateAgentsPath: join(codexTemplateRoot, "agents"),
|
|
33
31
|
piAgentDir,
|
|
34
32
|
piAuthPath: join(piAgentDir, "auth.json"),
|
|
@@ -48,7 +46,7 @@ function assertSeparateSourceAndDeployment(packageRoot, agentsDir) {
|
|
|
48
46
|
throw new Error(
|
|
49
47
|
`源码目录 packageRoot (${packageRoot}) 与部署目录 agentsDir (${agentsDir}) 不能相同或互相嵌套。`
|
|
50
48
|
+ "请将源码克隆到独立目录(例如 ~/src/AbelWorkflow),再用 --agents-dir 指向 ~/.agents;"
|
|
51
|
-
+ "
|
|
49
|
+
+ "请手动移动已有源码目录,安装器不会自动移动或删除数据,--force 也不能绕过此限制。"
|
|
52
50
|
);
|
|
53
51
|
}
|
|
54
52
|
|
|
@@ -78,16 +76,18 @@ function containsPath(parent, child, pathOps = { isAbsolute, relative, sep }) {
|
|
|
78
76
|
|
|
79
77
|
const defaultPaths = createPaths();
|
|
80
78
|
|
|
81
|
-
function getPlatform() {
|
|
82
|
-
return
|
|
79
|
+
function getPlatform(platform = process.platform) {
|
|
80
|
+
return platform;
|
|
83
81
|
}
|
|
84
82
|
|
|
85
|
-
function isWindows() {
|
|
86
|
-
return
|
|
83
|
+
function isWindows(platform = getPlatform()) {
|
|
84
|
+
return platform === "win32";
|
|
87
85
|
}
|
|
88
86
|
|
|
89
|
-
function pathToLabel(path, homeDir = defaultPaths.homeDir) {
|
|
90
|
-
|
|
87
|
+
function pathToLabel(path, homeDir = defaultPaths.homeDir, pathOps = { isAbsolute, relative, sep }) {
|
|
88
|
+
if (!containsPath(homeDir, path, pathOps)) return path;
|
|
89
|
+
const homeRelativePath = pathOps.relative(homeDir, path);
|
|
90
|
+
return homeRelativePath ? `~${pathOps.sep}${homeRelativePath}` : "~";
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
function maskSecret(value) {
|