abelworkflow 0.9.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +54 -182
  2. package/bin/abelworkflow.mjs +5 -3
  3. package/extensions/pi-gpt-responses-compat/index.ts +1 -1
  4. package/lib/cli/args.mjs +76 -0
  5. package/lib/cli/main.mjs +385 -0
  6. package/lib/cli/prompts.mjs +106 -0
  7. package/lib/config/dotenv.mjs +102 -0
  8. package/lib/config/jsonc.mjs +80 -0
  9. package/lib/config/store.mjs +310 -0
  10. package/lib/config/toml.mjs +638 -0
  11. package/lib/installer/assets.mjs +324 -0
  12. package/lib/installer/install.mjs +153 -0
  13. package/lib/installer/links.mjs +427 -0
  14. package/lib/installer/state.mjs +163 -0
  15. package/lib/paths.mjs +99 -0
  16. package/lib/providers/claude.mjs +197 -0
  17. package/lib/providers/codex.mjs +433 -0
  18. package/lib/providers/pi.mjs +577 -0
  19. package/lib/providers/skills.mjs +105 -0
  20. package/lib/templates/codex/agents/default.toml +7 -0
  21. package/lib/templates/codex/agents/explorer.toml +7 -0
  22. package/lib/templates/codex/agents/planner.toml +7 -0
  23. package/lib/templates/codex/agents/reviewer.toml +7 -0
  24. package/lib/templates/codex/agents/worker.toml +7 -0
  25. package/lib/templates/codex/config-base.toml +11 -3
  26. package/lib/templates/workflow/AGENTS.md +50 -0
  27. package/lib/templates/workflow/commands/abel-design.md +175 -0
  28. package/{commands → lib/templates/workflow/commands}/abel-diagnose.md +5 -5
  29. package/lib/templates/workflow/commands/abel-implement.md +170 -0
  30. package/{commands → lib/templates/workflow/commands}/abel-init.md +3 -5
  31. package/{.gitignore → lib/templates/workflow/gitignore.template} +0 -2
  32. package/lib/tools/cli-installer.mjs +277 -0
  33. package/package.json +37 -10
  34. package/skills/context7-auto-research/SKILL.md +5 -5
  35. package/skills/context7-auto-research/{context7-api.js → context7-api.cjs} +5 -8
  36. package/skills/dev-browser/SKILL.md +53 -33
  37. package/skills/dev-browser/dist/scripts/start.d.ts +2 -0
  38. package/skills/dev-browser/dist/scripts/start.d.ts.map +1 -0
  39. package/skills/dev-browser/dist/scripts/start.js +118 -0
  40. package/skills/dev-browser/dist/scripts/start.js.map +1 -0
  41. package/skills/dev-browser/dist/src/client.d.ts +93 -0
  42. package/skills/dev-browser/dist/src/client.d.ts.map +1 -0
  43. package/skills/dev-browser/dist/src/client.js +310 -0
  44. package/skills/dev-browser/dist/src/client.js.map +1 -0
  45. package/skills/dev-browser/dist/src/entrypoint.d.ts +29 -0
  46. package/skills/dev-browser/dist/src/entrypoint.d.ts.map +1 -0
  47. package/skills/dev-browser/dist/src/entrypoint.js +106 -0
  48. package/skills/dev-browser/dist/src/entrypoint.js.map +1 -0
  49. package/skills/dev-browser/dist/src/index.d.ts +4 -0
  50. package/skills/dev-browser/dist/src/index.d.ts.map +1 -0
  51. package/skills/dev-browser/dist/src/index.js +2 -0
  52. package/skills/dev-browser/dist/src/index.js.map +1 -0
  53. package/skills/dev-browser/dist/src/page-api.d.ts +25 -0
  54. package/skills/dev-browser/dist/src/page-api.d.ts.map +1 -0
  55. package/skills/dev-browser/dist/src/page-api.js +104 -0
  56. package/skills/dev-browser/dist/src/page-api.js.map +1 -0
  57. package/skills/dev-browser/dist/src/relay.d.ts +27 -0
  58. package/skills/dev-browser/dist/src/relay.d.ts.map +1 -0
  59. package/skills/dev-browser/dist/src/relay.js +521 -0
  60. package/skills/dev-browser/dist/src/relay.js.map +1 -0
  61. package/skills/dev-browser/dist/src/runtime.d.ts +45 -0
  62. package/skills/dev-browser/dist/src/runtime.d.ts.map +1 -0
  63. package/skills/dev-browser/dist/src/runtime.js +54 -0
  64. package/skills/dev-browser/dist/src/runtime.js.map +1 -0
  65. package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +23 -0
  66. package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts.map +1 -0
  67. package/skills/dev-browser/{src/snapshot/browser-script.ts → dist/src/snapshot/browser-script.js} +21 -30
  68. package/skills/dev-browser/dist/src/snapshot/browser-script.js.map +1 -0
  69. package/skills/dev-browser/dist/src/snapshot/index.d.ts +14 -0
  70. package/skills/dev-browser/dist/src/snapshot/index.d.ts.map +1 -0
  71. package/skills/dev-browser/{src/snapshot/index.ts → dist/src/snapshot/index.js} +2 -2
  72. package/skills/dev-browser/dist/src/snapshot/index.js.map +1 -0
  73. package/skills/dev-browser/dist/src/snapshot/inject.d.ts +13 -0
  74. package/skills/dev-browser/dist/src/snapshot/inject.d.ts.map +1 -0
  75. package/skills/dev-browser/{src/snapshot/inject.ts → dist/src/snapshot/inject.js} +2 -2
  76. package/skills/dev-browser/dist/src/snapshot/inject.js.map +1 -0
  77. package/skills/dev-browser/dist/src/standalone.d.ts +32 -0
  78. package/skills/dev-browser/dist/src/standalone.d.ts.map +1 -0
  79. package/skills/dev-browser/dist/src/standalone.js +174 -0
  80. package/skills/dev-browser/dist/src/standalone.js.map +1 -0
  81. package/skills/dev-browser/dist/src/startup.d.ts +55 -0
  82. package/skills/dev-browser/dist/src/startup.d.ts.map +1 -0
  83. package/skills/dev-browser/dist/src/startup.js +81 -0
  84. package/skills/dev-browser/dist/src/startup.js.map +1 -0
  85. package/skills/dev-browser/dist/src/target-registry.d.ts +29 -0
  86. package/skills/dev-browser/dist/src/target-registry.d.ts.map +1 -0
  87. package/skills/dev-browser/dist/src/target-registry.js +135 -0
  88. package/skills/dev-browser/dist/src/target-registry.js.map +1 -0
  89. package/skills/dev-browser/dist/src/types.d.ts +27 -0
  90. package/skills/dev-browser/dist/src/types.d.ts.map +1 -0
  91. package/skills/dev-browser/dist/src/types.js +2 -0
  92. package/skills/dev-browser/dist/src/types.js.map +1 -0
  93. package/skills/dev-browser/package-lock.json +67 -1510
  94. package/skills/dev-browser/package.json +18 -14
  95. package/skills/dev-browser/references/scraping.md +94 -105
  96. package/skills/grok-search/.env.example +3 -3
  97. package/skills/grok-search/SKILL.md +1 -1
  98. package/skills/grok-search/defaults.json +3 -0
  99. package/skills/grok-search/gitignore.template +4 -0
  100. package/skills/grok-search/scripts/_dotenv.py +5 -1
  101. package/skills/grok-search/scripts/groksearch_cli.py +15 -33
  102. package/skills/prompt-enhancer/SKILL.md +2 -27
  103. package/skills/prompt-enhancer/TEMPLATE.md +0 -4
  104. package/.skill-lock.json +0 -29
  105. package/AGENTS.md +0 -45
  106. package/commands/abel-implement.md +0 -157
  107. package/commands/abel-plan.md +0 -88
  108. package/commands/abel-research.md +0 -126
  109. package/lib/cli/logic.mjs +0 -213
  110. package/lib/cli.mjs +0 -3016
  111. package/skills/confidence-check/SKILL.md +0 -34
  112. package/skills/confidence-check/confidence.ts +0 -276
  113. package/skills/dev-browser/bun.lock +0 -460
  114. package/skills/dev-browser/scripts/start.ts +0 -280
  115. package/skills/dev-browser/src/client.ts +0 -474
  116. package/skills/dev-browser/src/entrypoint.ts +0 -157
  117. package/skills/dev-browser/src/index.ts +0 -289
  118. package/skills/dev-browser/src/relay.ts +0 -731
  119. package/skills/dev-browser/src/runtime.test.ts +0 -60
  120. package/skills/dev-browser/src/runtime.ts +0 -171
  121. package/skills/dev-browser/src/startup.test.ts +0 -95
  122. package/skills/dev-browser/src/startup.ts +0 -153
  123. package/skills/dev-browser/src/types.ts +0 -35
  124. package/skills/dev-browser/tsconfig.json +0 -36
  125. package/skills/dev-browser/vitest.config.ts +0 -12
  126. package/skills/prompt-enhancer/.env.example +0 -12
  127. package/skills/prompt-enhancer/ADVANCED.md +0 -103
  128. package/skills/prompt-enhancer/requirements.txt +0 -2
  129. package/skills/prompt-enhancer/scripts/_dotenv.py +0 -28
  130. package/skills/prompt-enhancer/scripts/enhance.py +0 -191
  131. package/skills/prompt-enhancer/scripts/prompt_enhancer_entry.py +0 -253
  132. package/skills/sequential-think/SKILL.md +0 -198
  133. package/skills/sequential-think/scripts/.env.example +0 -5
  134. package/skills/sequential-think/scripts/sequential_think_cli.py +0 -253
@@ -0,0 +1,324 @@
1
+ import { createHash } from "node:crypto";
2
+ import * as fs from "node:fs/promises";
3
+ import { join, relative, resolve } from "node:path";
4
+ import {
5
+ backupIfNeeded,
6
+ pathExists,
7
+ pathTargetExists,
8
+ readJsonFileSafe,
9
+ writeText
10
+ } from "../config/store.mjs";
11
+ import { createInstallReport, readInstallMetadata } from "./state.mjs";
12
+
13
+ const ignoredSkillPathPatterns = [
14
+ /(^|\/)\.env$/u,
15
+ /(^|\/)\.skill-lock\.json$/u,
16
+ /(^|\/)\.venv(\/|$)/u,
17
+ /(^|\/)__pycache__(\/|$)/u,
18
+ /(^|\/)node_modules(\/|$)/u,
19
+ /(^|\/)tmp(\/|$)/u,
20
+ /(^|\/)build(\/|$)/u,
21
+ /^dev-browser\/profiles(\/|$)/u,
22
+ /^dev-browser\/tmp(\/|$)/u
23
+ ];
24
+
25
+ function normalizeRelativePath(path) {
26
+ return path.replaceAll("\\", "/");
27
+ }
28
+
29
+ function validateManagedFilePath(candidate) {
30
+ if (typeof candidate !== "string") {
31
+ throw new Error(`Invalid managed file path: ${String(candidate)}`);
32
+ }
33
+ const normalized = normalizeRelativePath(candidate);
34
+ const segments = normalized.split("/");
35
+ if (!normalized
36
+ || candidate !== normalized
37
+ || normalized.startsWith("/")
38
+ || /^[A-Za-z]:/u.test(normalized)
39
+ || normalized.includes("\0")
40
+ || segments.some((segment) => !segment || segment === "." || segment === "..")) {
41
+ throw new Error(`Invalid managed file path: ${candidate}`);
42
+ }
43
+ return normalized;
44
+ }
45
+
46
+ async function resolveManagedFilePath(paths, candidate) {
47
+ const normalized = validateManagedFilePath(candidate);
48
+ const rootPath = resolve(paths.agentsDir);
49
+ const targetPath = resolve(rootPath, normalized);
50
+ if (normalizeRelativePath(relative(rootPath, targetPath)) !== normalized) {
51
+ throw new Error(`Invalid managed file path: ${candidate}`);
52
+ }
53
+ let ancestorPath = rootPath;
54
+ for (const segment of normalized.split("/").slice(0, -1)) {
55
+ ancestorPath = join(ancestorPath, segment);
56
+ try {
57
+ if ((await fs.lstat(ancestorPath)).isSymbolicLink()) {
58
+ throw new Error(`Invalid managed file path: ${candidate}`);
59
+ }
60
+ } catch (error) {
61
+ if (error?.code === "ENOENT") break;
62
+ throw error;
63
+ }
64
+ }
65
+ return targetPath;
66
+ }
67
+
68
+ function mapGitignoreTemplate(relativePath) {
69
+ return relativePath.replace(/(^|\/)gitignore\.template$/u, "$1.gitignore");
70
+ }
71
+
72
+ function hashBytes(content) {
73
+ return createHash("sha256").update(content).digest("hex");
74
+ }
75
+
76
+ function shouldCopySkillPath(skillsRoot, sourcePath) {
77
+ const relativePath = normalizeRelativePath(relative(skillsRoot, sourcePath));
78
+ if (!relativePath) return true;
79
+ if (/(^|\/)dist(\/|$)/u.test(relativePath) && !/^dev-browser\/dist(\/|$)/u.test(relativePath)) return false;
80
+ return !ignoredSkillPathPatterns.some((pattern) => pattern.test(relativePath));
81
+ }
82
+
83
+ async function collectFiles(root, outputPrefix, { filter, mapTargetPath } = {}) {
84
+ const assets = new Map();
85
+ async function visit(directory) {
86
+ const entries = await fs.readdir(directory, { withFileTypes: true });
87
+ for (const entry of entries) {
88
+ const sourcePath = join(directory, entry.name);
89
+ if (filter && !filter(root, sourcePath)) continue;
90
+ if (entry.isDirectory()) {
91
+ const nested = await visit(sourcePath);
92
+ for (const [key, value] of nested) assets.set(key, value);
93
+ continue;
94
+ }
95
+ if (!entry.isFile() && !entry.isSymbolicLink()) continue;
96
+ const sourceRelativePath = normalizeRelativePath(relative(root, sourcePath));
97
+ if (sourceRelativePath.endsWith(".skill-lock.json")) continue;
98
+ const sourceContent = await fs.readFile(sourcePath);
99
+ const sourceStat = await fs.stat(sourcePath);
100
+ const targetRelativePath = normalizeRelativePath(join(
101
+ outputPrefix,
102
+ mapTargetPath ? mapTargetPath(sourceRelativePath) : sourceRelativePath
103
+ ));
104
+ assets.set(targetRelativePath, {
105
+ content: sourceContent,
106
+ hash: hashBytes(sourceContent),
107
+ sourceHash: hashBytes(sourceContent),
108
+ mode: sourceStat.mode & 0o777,
109
+ sourcePath
110
+ });
111
+ }
112
+ return assets;
113
+ }
114
+ return visit(root);
115
+ }
116
+
117
+ async function addAssetTree(manifest, root, outputPrefix, options = {}) {
118
+ if (!(await pathTargetExists(root))) {
119
+ if (options.required) throw new Error(`Missing managed asset root: ${root}`);
120
+ return;
121
+ }
122
+ for (const [key, value] of await collectFiles(root, outputPrefix, options)) manifest.set(key, value);
123
+ }
124
+
125
+ async function collectManagedAssets(paths) {
126
+ const manifest = new Map();
127
+ await addAssetTree(manifest, paths.workflowTemplateRoot, "", {
128
+ required: true,
129
+ mapTargetPath: mapGitignoreTemplate
130
+ });
131
+
132
+ const readmePath = join(paths.packageRoot, "README.md");
133
+ if (!(await pathTargetExists(readmePath))) throw new Error(`Missing managed asset: ${readmePath}`);
134
+ const readmeContent = await fs.readFile(readmePath);
135
+ manifest.set("README.md", {
136
+ content: readmeContent,
137
+ hash: hashBytes(readmeContent),
138
+ sourceHash: hashBytes(readmeContent),
139
+ mode: (await fs.stat(readmePath)).mode & 0o777,
140
+ sourcePath: readmePath
141
+ });
142
+
143
+ const skillsRoot = join(paths.packageRoot, "skills");
144
+ await addAssetTree(manifest, skillsRoot, "skills", {
145
+ filter: shouldCopySkillPath,
146
+ mapTargetPath: mapGitignoreTemplate
147
+ });
148
+ await addAssetTree(manifest, join(paths.packageRoot, "extensions"), "extensions");
149
+ return manifest;
150
+ }
151
+
152
+ async function readManagedFiles(paths, metadata) {
153
+ if (metadata.schemaVersion !== 2) return {};
154
+ const managedFiles = { ...(metadata.managedFiles ?? {}) };
155
+ for (const relativePath of Object.keys(managedFiles)) {
156
+ await resolveManagedFilePath(paths, relativePath);
157
+ }
158
+ return managedFiles;
159
+ }
160
+
161
+ function recordConflict(report, relativePath) {
162
+ report.conflicts.push(relativePath);
163
+ report.preserved.push(relativePath);
164
+ }
165
+
166
+ async function readRegularTarget(targetPath) {
167
+ try {
168
+ const targetStat = await fs.lstat(targetPath);
169
+ if (targetStat.isSymbolicLink()) {
170
+ return {
171
+ exists: true,
172
+ regular: false,
173
+ symbolicLink: true
174
+ };
175
+ }
176
+ if (!targetStat.isFile()) return { exists: true, regular: false };
177
+ const content = await fs.readFile(targetPath);
178
+ return { exists: true, regular: true, hash: hashBytes(content) };
179
+ } catch (error) {
180
+ if (error?.code === "ENOENT") return { exists: false, regular: false };
181
+ throw error;
182
+ }
183
+ }
184
+
185
+ async function applyManifestEntry(paths, relativePath, asset, previousHash, force, report) {
186
+ const targetPath = await resolveManagedFilePath(paths, relativePath);
187
+ const current = await readRegularTarget(targetPath);
188
+ if (!current.exists) {
189
+ await writeText(targetPath, asset.content, { backupLimit: 3, mode: asset.mode });
190
+ report.created.push(relativePath);
191
+ return asset.hash;
192
+ }
193
+ if (!current.regular) {
194
+ if (force && current.symbolicLink) {
195
+ await backupIfNeeded(targetPath);
196
+ await writeText(targetPath, asset.content, { backupLimit: 3, mode: asset.mode });
197
+ report.updated.push(relativePath);
198
+ return asset.hash;
199
+ }
200
+ recordConflict(report, relativePath);
201
+ return previousHash;
202
+ }
203
+ if (current.hash === asset.hash) {
204
+ const result = await writeText(targetPath, asset.content, { backupLimit: 3, mode: asset.mode });
205
+ report[result.status === "permission-repaired" ? "updated" : "unchanged"].push(relativePath);
206
+ return asset.hash;
207
+ }
208
+ if (!previousHash && current.hash === asset.sourceHash) {
209
+ await writeText(targetPath, asset.content, { backupLimit: 3, mode: asset.mode });
210
+ report.updated.push(relativePath);
211
+ return asset.hash;
212
+ }
213
+ if (previousHash && current.hash === previousHash || force) {
214
+ await writeText(targetPath, asset.content, { backupLimit: 3, mode: asset.mode });
215
+ report.updated.push(relativePath);
216
+ return asset.hash;
217
+ }
218
+ recordConflict(report, relativePath);
219
+ return previousHash;
220
+ }
221
+
222
+ async function removeStaleManagedFile(paths, relativePath, previousHash, force, report) {
223
+ if (relativePath.endsWith(".skill-lock.json")) return;
224
+ const targetPath = await resolveManagedFilePath(paths, relativePath);
225
+ const current = await readRegularTarget(targetPath);
226
+ if (!current.exists) return undefined;
227
+ if (!current.regular) {
228
+ recordConflict(report, relativePath);
229
+ return undefined;
230
+ }
231
+ if (current.regular && current.hash === previousHash) {
232
+ await fs.rm(targetPath, { force: true });
233
+ report.removed.push(relativePath);
234
+ return undefined;
235
+ }
236
+ if (force) {
237
+ await backupIfNeeded(targetPath);
238
+ report.removed.push(relativePath);
239
+ return undefined;
240
+ }
241
+ recordConflict(report, relativePath);
242
+ return previousHash;
243
+ }
244
+
245
+ async function readPackageVersion(paths) {
246
+ const packagePath = join(paths.packageRoot, "package.json");
247
+ const packageData = await readJsonFileSafe(packagePath);
248
+ if (typeof packageData.version !== "string" || !packageData.version) {
249
+ throw new Error(`Missing package version in ${packagePath}`);
250
+ }
251
+ return packageData.version;
252
+ }
253
+
254
+ async function syncManagedFiles({
255
+ paths,
256
+ force = false,
257
+ pathPrefix = null
258
+ }) {
259
+ const previousMetadata = await readInstallMetadata(paths);
260
+ const previousManagedFiles = await readManagedFiles(paths, previousMetadata);
261
+ const manifest = await collectManagedAssets(paths);
262
+ const report = createInstallReport();
263
+ const managedFiles = pathPrefix ? { ...previousManagedFiles } : {};
264
+
265
+ for (const [relativePath, asset] of manifest) {
266
+ if (pathPrefix && !relativePath.startsWith(pathPrefix)) continue;
267
+ const managedHash = await applyManifestEntry(
268
+ paths,
269
+ relativePath,
270
+ asset,
271
+ previousManagedFiles[relativePath],
272
+ force,
273
+ report
274
+ );
275
+ if (managedHash) managedFiles[relativePath] = managedHash;
276
+ else delete managedFiles[relativePath];
277
+ }
278
+ for (const [relativePath, previousHash] of Object.entries(previousManagedFiles)) {
279
+ if (pathPrefix && !relativePath.startsWith(pathPrefix)) continue;
280
+ if (!manifest.has(relativePath)) {
281
+ const managedHash = await removeStaleManagedFile(paths, relativePath, previousHash, force, report);
282
+ if (managedHash) managedFiles[relativePath] = managedHash;
283
+ else delete managedFiles[relativePath];
284
+ }
285
+ }
286
+
287
+ return {
288
+ previousMetadata,
289
+ packageVersion: await readPackageVersion(paths),
290
+ managedFiles,
291
+ report
292
+ };
293
+ }
294
+
295
+ async function pathsReferToSameEntry(sourcePath, targetPath) {
296
+ if (resolve(sourcePath) === resolve(targetPath)) return true;
297
+ try {
298
+ return await fs.realpath(sourcePath) === await fs.realpath(targetPath);
299
+ } catch {
300
+ return false;
301
+ }
302
+ }
303
+
304
+ async function ensureManagedContainerDirectory(targetPath, sourcePath) {
305
+ if (await pathExists(targetPath)) {
306
+ const targetStat = await fs.lstat(targetPath);
307
+ if (targetStat.isSymbolicLink() && await pathsReferToSameEntry(targetPath, sourcePath)) {
308
+ await fs.unlink(targetPath);
309
+ } else if (!targetStat.isDirectory()) {
310
+ throw new Error(`Managed container conflict at ${targetPath}`);
311
+ }
312
+ }
313
+ await fs.mkdir(targetPath, { recursive: true });
314
+ }
315
+
316
+ export {
317
+ collectManagedAssets,
318
+ ensureManagedContainerDirectory,
319
+ hashBytes,
320
+ pathsReferToSameEntry,
321
+ readManagedFiles,
322
+ shouldCopySkillPath,
323
+ syncManagedFiles
324
+ };
@@ -0,0 +1,153 @@
1
+ import { join } from "node:path";
2
+ import { pathExists, readJsonFileSafe } from "../config/store.mjs";
3
+ import { createPaths, defaultPaths } from "../paths.mjs";
4
+ import { readManagedFiles, syncManagedFiles } from "./assets.mjs";
5
+ import { linkClaude, linkCodex, linkPi } from "./links.mjs";
6
+ import {
7
+ buildInstallMetadata,
8
+ createInstallReport,
9
+ mergeInstallReports,
10
+ mergeLinkedTargets,
11
+ readInstallMetadata,
12
+ writeInstallMetadata
13
+ } from "./state.mjs";
14
+
15
+ function validatedPaths(paths) {
16
+ if (!paths || typeof paths !== "object" || [
17
+ paths.homeDir,
18
+ paths.packageRoot,
19
+ paths.agentsDir
20
+ ].some((path) => typeof path !== "string")) {
21
+ throw new TypeError("Expected a canonical Paths object");
22
+ }
23
+ return createPaths({
24
+ homeDir: paths.homeDir,
25
+ packageRoot: paths.packageRoot,
26
+ agentsDir: paths.agentsDir
27
+ });
28
+ }
29
+
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
+ function reportFromLinkResults(results) {
40
+ const report = createInstallReport();
41
+ for (const result of results) {
42
+ if (result.status === "linked" || result.status === "copied") report.linked.push(result.targetPath);
43
+ else if (result.status === "unchanged") report.unchanged.push(result.targetPath);
44
+ else if (result.status === "removed") report.removed.push(result.targetPath);
45
+ else if (result.status === "conflict") {
46
+ report.conflicts.push(result.targetPath);
47
+ report.preserved.push(result.targetPath);
48
+ } else if (result.status === "skipped") {
49
+ report.preserved.push(result.targetPath);
50
+ }
51
+ }
52
+ return report;
53
+ }
54
+
55
+ async function packageVersionFor(paths, previousMetadata) {
56
+ if (previousMetadata.schemaVersion === 2
57
+ && typeof previousMetadata.packageVersion === "string"
58
+ && previousMetadata.packageVersion) {
59
+ return previousMetadata.packageVersion;
60
+ }
61
+ const packagePath = join(paths.packageRoot, "package.json");
62
+ const packageData = await readJsonFileSafe(packagePath);
63
+ if (typeof packageData.version !== "string" || !packageData.version) {
64
+ throw new Error(`Missing package version in ${packagePath}`);
65
+ }
66
+ return packageData.version;
67
+ }
68
+
69
+ async function installWorkflow(options) {
70
+ const paths = validatedPaths(options.paths);
71
+ const previousMetadata = await readInstallMetadata(paths);
72
+ let assetResult;
73
+
74
+ if (options.relinkOnly) {
75
+ if (!(await pathExists(paths.agentsDir))) {
76
+ throw new Error(`${paths.agentsDir} does not exist; remove --link-only or install first`);
77
+ }
78
+ assetResult = {
79
+ previousMetadata,
80
+ packageVersion: await packageVersionFor(paths, previousMetadata),
81
+ managedFiles: await readManagedFiles(paths, previousMetadata),
82
+ report: createInstallReport()
83
+ };
84
+ } else {
85
+ assetResult = await syncManagedFiles({
86
+ paths,
87
+ force: options.force ?? false
88
+ });
89
+ }
90
+
91
+ const previousLinkedTargets = previousMetadata.linkedTargets ?? {};
92
+ const linkOptions = { force: options.force ?? false };
93
+ const claudeResults = await linkClaude(paths, previousLinkedTargets, linkOptions);
94
+ const codexResults = await linkCodex(paths, previousLinkedTargets, linkOptions);
95
+ const piResults = await linkPi(paths, previousLinkedTargets, linkOptions);
96
+ const linkResults = [...claudeResults, ...codexResults, ...piResults];
97
+ const linkedTargets = mergeLinkedTargets(previousLinkedTargets, linkResults);
98
+
99
+ const metadata = buildInstallMetadata({
100
+ previousMetadata,
101
+ packageVersion: assetResult.packageVersion,
102
+ managedFiles: assetResult.managedFiles,
103
+ linkedTargets
104
+ });
105
+ await writeInstallMetadata(paths, metadata);
106
+ return mergeInstallReports(assetResult.report, reportFromLinkResults(linkResults));
107
+ }
108
+
109
+ async function installManagedWorkflow(options = {}) {
110
+ return installWorkflow({ ...options, paths: resolveInstallPaths(options) });
111
+ }
112
+
113
+ async function ensureSkillPresent(inputPaths, skillName) {
114
+ const paths = validatedPaths(inputPaths);
115
+ const previousMetadata = await readInstallMetadata(paths);
116
+ const assetResult = await syncManagedFiles({
117
+ paths,
118
+ force: false,
119
+ pathPrefix: `skills/${skillName}/`
120
+ });
121
+ const metadata = buildInstallMetadata({
122
+ previousMetadata,
123
+ packageVersion: assetResult.packageVersion,
124
+ managedFiles: assetResult.managedFiles,
125
+ linkedTargets: previousMetadata.linkedTargets ?? {}
126
+ });
127
+ await writeInstallMetadata(paths, metadata);
128
+ return assetResult.report;
129
+ }
130
+
131
+ async function ensurePiResourcesLinked(inputPaths) {
132
+ const paths = validatedPaths(inputPaths);
133
+ const previousMetadata = await readInstallMetadata(paths);
134
+ const assetResult = await syncManagedFiles({ paths, force: false });
135
+ const previousLinkedTargets = previousMetadata.linkedTargets ?? {};
136
+ const piResults = await linkPi(paths, previousLinkedTargets, { force: false });
137
+ const metadata = buildInstallMetadata({
138
+ previousMetadata,
139
+ packageVersion: assetResult.packageVersion,
140
+ managedFiles: assetResult.managedFiles,
141
+ linkedTargets: mergeLinkedTargets(previousLinkedTargets, piResults)
142
+ });
143
+ await writeInstallMetadata(paths, metadata);
144
+ return mergeInstallReports(assetResult.report, reportFromLinkResults(piResults));
145
+ }
146
+
147
+ export {
148
+ ensurePiResourcesLinked,
149
+ ensureSkillPresent,
150
+ installManagedWorkflow,
151
+ installWorkflow,
152
+ packageVersionFor
153
+ };