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.
- package/README.md +54 -182
- package/bin/abelworkflow.mjs +5 -3
- package/extensions/pi-gpt-responses-compat/index.ts +1 -1
- package/lib/cli/args.mjs +76 -0
- package/lib/cli/main.mjs +385 -0
- package/lib/cli/prompts.mjs +106 -0
- package/lib/config/dotenv.mjs +102 -0
- package/lib/config/jsonc.mjs +80 -0
- package/lib/config/store.mjs +310 -0
- package/lib/config/toml.mjs +638 -0
- package/lib/installer/assets.mjs +324 -0
- package/lib/installer/install.mjs +153 -0
- package/lib/installer/links.mjs +427 -0
- package/lib/installer/state.mjs +163 -0
- package/lib/paths.mjs +99 -0
- package/lib/providers/claude.mjs +197 -0
- package/lib/providers/codex.mjs +433 -0
- package/lib/providers/pi.mjs +577 -0
- package/lib/providers/skills.mjs +105 -0
- package/lib/templates/codex/agents/default.toml +7 -0
- package/lib/templates/codex/agents/explorer.toml +7 -0
- package/lib/templates/codex/agents/planner.toml +7 -0
- package/lib/templates/codex/agents/reviewer.toml +7 -0
- package/lib/templates/codex/agents/worker.toml +7 -0
- package/lib/templates/codex/config-base.toml +11 -3
- package/lib/templates/workflow/AGENTS.md +50 -0
- package/lib/templates/workflow/commands/abel-design.md +175 -0
- package/{commands → lib/templates/workflow/commands}/abel-diagnose.md +5 -5
- package/lib/templates/workflow/commands/abel-implement.md +170 -0
- package/{commands → lib/templates/workflow/commands}/abel-init.md +3 -5
- package/{.gitignore → lib/templates/workflow/gitignore.template} +0 -2
- package/lib/tools/cli-installer.mjs +277 -0
- package/package.json +37 -10
- package/skills/context7-auto-research/SKILL.md +5 -5
- package/skills/context7-auto-research/{context7-api.js → context7-api.cjs} +5 -8
- package/skills/dev-browser/SKILL.md +53 -33
- package/skills/dev-browser/dist/scripts/start.d.ts +2 -0
- package/skills/dev-browser/dist/scripts/start.d.ts.map +1 -0
- package/skills/dev-browser/dist/scripts/start.js +118 -0
- package/skills/dev-browser/dist/scripts/start.js.map +1 -0
- package/skills/dev-browser/dist/src/client.d.ts +93 -0
- package/skills/dev-browser/dist/src/client.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/client.js +310 -0
- package/skills/dev-browser/dist/src/client.js.map +1 -0
- package/skills/dev-browser/dist/src/entrypoint.d.ts +29 -0
- package/skills/dev-browser/dist/src/entrypoint.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/entrypoint.js +106 -0
- package/skills/dev-browser/dist/src/entrypoint.js.map +1 -0
- package/skills/dev-browser/dist/src/index.d.ts +4 -0
- package/skills/dev-browser/dist/src/index.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/index.js +2 -0
- package/skills/dev-browser/dist/src/index.js.map +1 -0
- package/skills/dev-browser/dist/src/page-api.d.ts +25 -0
- package/skills/dev-browser/dist/src/page-api.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/page-api.js +104 -0
- package/skills/dev-browser/dist/src/page-api.js.map +1 -0
- package/skills/dev-browser/dist/src/relay.d.ts +27 -0
- package/skills/dev-browser/dist/src/relay.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/relay.js +521 -0
- package/skills/dev-browser/dist/src/relay.js.map +1 -0
- package/skills/dev-browser/dist/src/runtime.d.ts +45 -0
- package/skills/dev-browser/dist/src/runtime.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/runtime.js +54 -0
- package/skills/dev-browser/dist/src/runtime.js.map +1 -0
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +23 -0
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts.map +1 -0
- package/skills/dev-browser/{src/snapshot/browser-script.ts → dist/src/snapshot/browser-script.js} +21 -30
- package/skills/dev-browser/dist/src/snapshot/browser-script.js.map +1 -0
- package/skills/dev-browser/dist/src/snapshot/index.d.ts +14 -0
- package/skills/dev-browser/dist/src/snapshot/index.d.ts.map +1 -0
- package/skills/dev-browser/{src/snapshot/index.ts → dist/src/snapshot/index.js} +2 -2
- package/skills/dev-browser/dist/src/snapshot/index.js.map +1 -0
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts +13 -0
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts.map +1 -0
- package/skills/dev-browser/{src/snapshot/inject.ts → dist/src/snapshot/inject.js} +2 -2
- package/skills/dev-browser/dist/src/snapshot/inject.js.map +1 -0
- package/skills/dev-browser/dist/src/standalone.d.ts +32 -0
- package/skills/dev-browser/dist/src/standalone.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/standalone.js +174 -0
- package/skills/dev-browser/dist/src/standalone.js.map +1 -0
- package/skills/dev-browser/dist/src/startup.d.ts +55 -0
- package/skills/dev-browser/dist/src/startup.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/startup.js +81 -0
- package/skills/dev-browser/dist/src/startup.js.map +1 -0
- package/skills/dev-browser/dist/src/target-registry.d.ts +29 -0
- package/skills/dev-browser/dist/src/target-registry.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/target-registry.js +135 -0
- package/skills/dev-browser/dist/src/target-registry.js.map +1 -0
- package/skills/dev-browser/dist/src/types.d.ts +27 -0
- package/skills/dev-browser/dist/src/types.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/types.js +2 -0
- package/skills/dev-browser/dist/src/types.js.map +1 -0
- package/skills/dev-browser/package-lock.json +67 -1510
- package/skills/dev-browser/package.json +18 -14
- package/skills/dev-browser/references/scraping.md +94 -105
- package/skills/grok-search/.env.example +3 -3
- package/skills/grok-search/SKILL.md +1 -1
- package/skills/grok-search/defaults.json +3 -0
- package/skills/grok-search/gitignore.template +4 -0
- package/skills/grok-search/scripts/_dotenv.py +5 -1
- package/skills/grok-search/scripts/groksearch_cli.py +15 -33
- package/skills/prompt-enhancer/SKILL.md +2 -27
- package/skills/prompt-enhancer/TEMPLATE.md +0 -4
- package/.skill-lock.json +0 -29
- package/AGENTS.md +0 -45
- package/commands/abel-implement.md +0 -157
- package/commands/abel-plan.md +0 -88
- package/commands/abel-research.md +0 -126
- package/lib/cli/logic.mjs +0 -213
- package/lib/cli.mjs +0 -3016
- package/skills/confidence-check/SKILL.md +0 -34
- package/skills/confidence-check/confidence.ts +0 -276
- package/skills/dev-browser/bun.lock +0 -460
- package/skills/dev-browser/scripts/start.ts +0 -280
- package/skills/dev-browser/src/client.ts +0 -474
- package/skills/dev-browser/src/entrypoint.ts +0 -157
- package/skills/dev-browser/src/index.ts +0 -289
- package/skills/dev-browser/src/relay.ts +0 -731
- package/skills/dev-browser/src/runtime.test.ts +0 -60
- package/skills/dev-browser/src/runtime.ts +0 -171
- package/skills/dev-browser/src/startup.test.ts +0 -95
- package/skills/dev-browser/src/startup.ts +0 -153
- package/skills/dev-browser/src/types.ts +0 -35
- package/skills/dev-browser/tsconfig.json +0 -36
- package/skills/dev-browser/vitest.config.ts +0 -12
- package/skills/prompt-enhancer/.env.example +0 -12
- package/skills/prompt-enhancer/ADVANCED.md +0 -103
- package/skills/prompt-enhancer/requirements.txt +0 -2
- package/skills/prompt-enhancer/scripts/_dotenv.py +0 -28
- package/skills/prompt-enhancer/scripts/enhance.py +0 -191
- package/skills/prompt-enhancer/scripts/prompt_enhancer_entry.py +0 -253
- package/skills/sequential-think/SKILL.md +0 -198
- package/skills/sequential-think/scripts/.env.example +0 -5
- 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
|
+
};
|