@amsterdamdatalabs/enact-extensions 0.1.47 → 0.1.49
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/bundled-extensions.json +1 -4
- package/dist/create/claude.d.ts.map +1 -1
- package/dist/create/claude.js +1 -1
- package/dist/create/claude.js.map +1 -1
- package/dist/create/codex.d.ts.map +1 -1
- package/dist/create/codex.js +2 -3
- package/dist/create/codex.js.map +1 -1
- package/dist/create/cursor.d.ts.map +1 -1
- package/dist/create/cursor.js +3 -7
- package/dist/create/cursor.js.map +1 -1
- package/dist/create/enact.d.ts.map +1 -1
- package/dist/create/enact.js.map +1 -1
- package/dist/create/index.d.ts +3 -3
- package/dist/create/index.d.ts.map +1 -1
- package/dist/create/index.js +4 -4
- package/dist/create/index.js.map +1 -1
- package/dist/create/kimi.d.ts.map +1 -1
- package/dist/create/kimi.js +2 -3
- package/dist/create/kimi.js.map +1 -1
- package/dist/create/opencode.d.ts.map +1 -1
- package/dist/create/opencode.js +5 -12
- package/dist/create/opencode.js.map +1 -1
- package/dist/index.d.ts +12 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -9
- package/dist/index.js.map +1 -1
- package/dist/internal/agents.d.ts.map +1 -1
- package/dist/internal/agents.js +1 -1
- package/dist/internal/agents.js.map +1 -1
- package/dist/internal/extensions-table.d.ts +27 -6
- package/dist/internal/extensions-table.d.ts.map +1 -1
- package/dist/internal/extensions-table.js +85 -12
- package/dist/internal/extensions-table.js.map +1 -1
- package/dist/internal/file-ops.js +1 -1
- package/dist/internal/file-ops.js.map +1 -1
- package/dist/internal/io.d.ts.map +1 -1
- package/dist/internal/io.js.map +1 -1
- package/dist/internal/json-merge.d.ts.map +1 -1
- package/dist/internal/json-merge.js +4 -2
- package/dist/internal/json-merge.js.map +1 -1
- package/dist/internal/lock-ownership.d.ts.map +1 -1
- package/dist/internal/lock-ownership.js.map +1 -1
- package/dist/internal/pick.d.ts.map +1 -1
- package/dist/internal/pick.js.map +1 -1
- package/dist/internal/platform.d.ts.map +1 -1
- package/dist/internal/platform.js +7 -53
- package/dist/internal/platform.js.map +1 -1
- package/dist/internal/schema.d.ts.map +1 -1
- package/dist/internal/schema.js.map +1 -1
- package/dist/internal/toml-block.d.ts.map +1 -1
- package/dist/internal/toml-block.js.map +1 -1
- package/dist/projection.d.ts.map +1 -1
- package/dist/projection.js +2 -2
- package/dist/projection.js.map +1 -1
- package/dist/validate/index.d.ts +2 -2
- package/dist/validate/index.d.ts.map +1 -1
- package/dist/validate/index.js +3 -3
- package/dist/validate/index.js.map +1 -1
- package/dist/validate/run.d.ts.map +1 -1
- package/dist/validate/run.js +2 -5
- package/dist/validate/run.js.map +1 -1
- package/extensions/enact-repo/.agents/plugin.json +2 -2
- package/extensions/enact-repo/skills/interaction-design/references/animation-libraries-gsap.md +0 -1
- package/extensions/enact-repo/skills/interaction-design/references/animation-libraries.md +0 -1
- package/extensions/enact-repo/skills/interaction-design/references/scroll-animations.md +0 -1
- package/package.json +9 -8
- package/scripts/enact-extensions.mjs +331 -303
- package/scripts/lib/build-index.mjs +1 -3
- package/scripts/lib/resolve-bundle.mjs +1 -3
- package/scripts/lib/run-repo-doctor.mjs +50 -17
- package/scripts/lib/run-repo-install.mjs +130 -58
- package/scripts/lib/run-sync.mjs +3 -9
- package/scripts/lib/run-validate.mjs +1 -3
- package/scripts/lib/skill-validator.mjs +319 -95
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { resolveBundlePath } from "./lib/resolve-bundle.mjs";
|
|
3
|
-
import { listBundles } from "./lib/list-bundles.mjs";
|
|
4
|
-
import { buildIndex } from "./lib/build-index.mjs";
|
|
5
|
-
import { runValidate } from "./lib/run-validate.mjs";
|
|
6
|
-
import { runSync } from "./lib/run-sync.mjs";
|
|
7
|
-
import { runRepoInstall, runRepoUninstall } from "./lib/run-repo-install.mjs";
|
|
8
|
-
import { runRepoDoctor } from "./lib/run-repo-doctor.mjs";
|
|
9
|
-
import { dirname, join, resolve } from "node:path";
|
|
10
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
|
-
import {
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
12
4
|
import { cwd as processCwd } from "node:process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
13
6
|
import { Ajv } from "ajv";
|
|
14
7
|
import addFormatsModule from "ajv-formats";
|
|
8
|
+
import { buildIndex } from "./lib/build-index.mjs";
|
|
9
|
+
import { listBundles } from "./lib/list-bundles.mjs";
|
|
10
|
+
import { resolveBundlePath } from "./lib/resolve-bundle.mjs";
|
|
11
|
+
import { runRepoDoctor } from "./lib/run-repo-doctor.mjs";
|
|
12
|
+
import { runRepoInstall, runRepoUninstall } from "./lib/run-repo-install.mjs";
|
|
13
|
+
import { runSync } from "./lib/run-sync.mjs";
|
|
14
|
+
import { runValidate } from "./lib/run-validate.mjs";
|
|
15
15
|
|
|
16
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
17
|
const PACKAGE_ROOT = resolve(__dirname, "..");
|
|
@@ -44,7 +44,7 @@ Usage:
|
|
|
44
44
|
the real run would have. Touches nothing.
|
|
45
45
|
enact-extensions uninstall <name> --repo <path> [--purge] [--force]
|
|
46
46
|
Remove exactly what the repo lock records (--purge also removes
|
|
47
|
-
the [
|
|
47
|
+
the [extensions] block install wrote to enact-config.toml, and the
|
|
48
48
|
file itself if install created it and only version = 1 remains)
|
|
49
49
|
enact-extensions uninstall <name> --repo <path> --dry-run [--purge]
|
|
50
50
|
Print exactly what uninstall would remove/restore, per file
|
|
@@ -56,7 +56,7 @@ Usage:
|
|
|
56
56
|
(missing/drifted managed files), per-host status (claude,
|
|
57
57
|
codex incl. trust_level, cursor, kimi incl. workspace-trust,
|
|
58
58
|
opencode), required global binaries on PATH, the
|
|
59
|
-
enact-config.toml marker ([
|
|
59
|
+
enact-config.toml marker ([extensions] present; a stale
|
|
60
60
|
.agents/enact-repo.toml is reported), an "enact-hook doctor
|
|
61
61
|
--repo" passthrough when available, and the enact-hook
|
|
62
62
|
deny/activity log paths, existence, row counts, and (deny
|
|
@@ -80,7 +80,7 @@ Options:
|
|
|
80
80
|
exit with the same success/failure the real run would have. Writes nothing.
|
|
81
81
|
--force (install/uninstall --repo) Overwrite user-edited managed content and take over
|
|
82
82
|
same-named entries not owned by the bundle. Never overwrites a user skill dir.
|
|
83
|
-
--purge (uninstall --repo) Also remove the [
|
|
83
|
+
--purge (uninstall --repo) Also remove the [extensions] block install wrote to enact-config.toml.
|
|
84
84
|
|
|
85
85
|
Name resolution (bare plugin names):
|
|
86
86
|
When a bare name (no "/" in it) is given, it is resolved in this order:
|
|
@@ -175,349 +175,377 @@ function parseArgs(argv) {
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
try {
|
|
178
|
-
const rawArgv = process.argv.slice(2);
|
|
179
|
-
if (rawArgv.length === 1 && (rawArgv[0] === "-v" || rawArgv[0] === "--version")) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const { command, path, options, unknown } = parseArgs(rawArgv);
|
|
185
|
-
|
|
186
|
-
if (!command || command === "help") {
|
|
187
|
-
console.log(HELP);
|
|
188
|
-
exit(command ? 0 : 1);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (!COMMANDS.has(command)) {
|
|
192
|
-
console.error(`Unknown command: ${command}\n\n${REPO_USAGE_HINT}\nRun enact-extensions --help for usage.`);
|
|
193
|
-
exit(1);
|
|
194
|
-
}
|
|
178
|
+
const rawArgv = process.argv.slice(2);
|
|
179
|
+
if (rawArgv.length === 1 && (rawArgv[0] === "-v" || rawArgv[0] === "--version")) {
|
|
180
|
+
console.log(`enact-extensions ${VERSION}`);
|
|
181
|
+
exit(0);
|
|
182
|
+
}
|
|
195
183
|
|
|
196
|
-
|
|
197
|
-
fail(`Unknown option(s) for ${command}: ${unknown.join(", ")}\n${REPO_USAGE_HINT}`);
|
|
198
|
-
}
|
|
184
|
+
const { command, path, options, unknown } = parseArgs(rawArgv);
|
|
199
185
|
|
|
200
|
-
if (
|
|
201
|
-
|
|
202
|
-
|
|
186
|
+
if (!command || command === "help") {
|
|
187
|
+
console.log(HELP);
|
|
188
|
+
exit(command ? 0 : 1);
|
|
189
|
+
}
|
|
203
190
|
|
|
204
|
-
if (!
|
|
205
|
-
|
|
206
|
-
|
|
191
|
+
if (!COMMANDS.has(command)) {
|
|
192
|
+
console.error(`Unknown command: ${command}\n\n${REPO_USAGE_HINT}\nRun enact-extensions --help for usage.`);
|
|
193
|
+
exit(1);
|
|
194
|
+
}
|
|
207
195
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
console.log(`[doctor enact-repo] repo: ${report.repo}`);
|
|
211
|
-
console.log(` installed: ${report.installed ? "yes" : "NO"}`);
|
|
212
|
-
if (!report.installed) {
|
|
213
|
-
for (const p of report.problems) console.error(` problem: ${p}`);
|
|
214
|
-
return;
|
|
196
|
+
if (unknown.length > 0) {
|
|
197
|
+
fail(`Unknown option(s) for ${command}: ${unknown.join(", ")}\n${REPO_USAGE_HINT}`);
|
|
215
198
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (report.version.contentOutdated.length > 0) {
|
|
220
|
-
console.log(` content drifted from current bundle (${report.version.contentOutdated.length}): ${report.version.contentOutdated.join(", ")}`);
|
|
199
|
+
|
|
200
|
+
if ((command === "install" || command === "uninstall") && !options.repo) {
|
|
201
|
+
fail(`${command} requires --repo <path>.\n${REPO_USAGE_HINT}`);
|
|
221
202
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (report.lock.jsonIssues.length) console.log(` json: ${report.lock.jsonIssues.join(", ")}`);
|
|
229
|
-
if (report.lock.tomlIssues.length) console.log(` toml: ${report.lock.tomlIssues.join(", ")}`);
|
|
203
|
+
|
|
204
|
+
if (
|
|
205
|
+
!["install", "uninstall"].includes(command) &&
|
|
206
|
+
(options.force || options.purge || options.host || options.dryRun)
|
|
207
|
+
) {
|
|
208
|
+
fail("--host/--force/--purge/--dry-run only apply to install|uninstall <name> --repo <path>");
|
|
230
209
|
}
|
|
231
210
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
211
|
+
function printRepoDoctorReport(report) {
|
|
212
|
+
const ok = (b) => (b ? "OK" : "FAIL");
|
|
213
|
+
console.log(`[doctor enact-repo] repo: ${report.repo}`);
|
|
214
|
+
console.log(` installed: ${report.installed ? "yes" : "NO"}`);
|
|
215
|
+
if (!report.installed) {
|
|
216
|
+
for (const p of report.problems) console.error(` problem: ${p}`);
|
|
217
|
+
return;
|
|
237
218
|
}
|
|
238
|
-
|
|
239
|
-
|
|
219
|
+
console.log(
|
|
220
|
+
` version: installed=${report.version.installed} bundle=${report.version.bundle ?? "(unknown)"} outdated=${report.version.outdated ? "YES" : "no"}`,
|
|
221
|
+
);
|
|
222
|
+
if (report.version.contentOutdated.length > 0) {
|
|
240
223
|
console.log(
|
|
241
|
-
`
|
|
224
|
+
` content drifted from current bundle (${report.version.contentOutdated.length}): ${report.version.contentOutdated.join(", ")}`,
|
|
242
225
|
);
|
|
243
226
|
}
|
|
244
|
-
if (
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
227
|
+
if (report.version.note) console.log(` note: ${report.version.note}`);
|
|
228
|
+
|
|
229
|
+
console.log(` lock integrity: ${ok(report.lock.valid)}`);
|
|
230
|
+
if (!report.lock.valid) {
|
|
231
|
+
if (report.lock.missingFiles.length) console.log(` missing: ${report.lock.missingFiles.join(", ")}`);
|
|
232
|
+
if (report.lock.driftedFiles.length) console.log(` drifted: ${report.lock.driftedFiles.join(", ")}`);
|
|
233
|
+
if (report.lock.jsonIssues.length) console.log(` json: ${report.lock.jsonIssues.join(", ")}`);
|
|
234
|
+
if (report.lock.tomlIssues.length) console.log(` toml: ${report.lock.tomlIssues.join(", ")}`);
|
|
248
235
|
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
console.log(" binaries:");
|
|
252
|
-
for (const [bin, info] of Object.entries(report.binaries)) {
|
|
253
|
-
console.log(` ${bin}: ${info.found ? `${info.version ?? "(unknown version)"} @ ${info.path}` : "NOT FOUND"}`);
|
|
254
|
-
}
|
|
255
236
|
|
|
256
|
-
|
|
257
|
-
|
|
237
|
+
for (const [host, info] of Object.entries(report.hosts)) {
|
|
238
|
+
console.log(` host ${host}: targeted=${info.targeted} present=${info.targeted ? ok(info.present) : "n/a"}`);
|
|
239
|
+
if (info.note) console.log(` ${info.note}`);
|
|
240
|
+
if (info.trust) {
|
|
241
|
+
console.log(
|
|
242
|
+
` codex trust_level: ${info.trust.trustLevel ?? "(none)"}${info.trust.note ? ` -- ${info.trust.note}` : ""}`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
if (info.globalHooksNote) console.log(` ${info.globalHooksNote}`);
|
|
246
|
+
if (info.workspaceTrust) {
|
|
247
|
+
console.log(
|
|
248
|
+
` kimi workspace trust: ${info.workspaceTrust.trusted ? "TRUSTED" : "NOT TRUSTED"}${info.workspaceTrust.note ? ` -- ${info.workspaceTrust.note}` : ""}`,
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
if (host === "opencode" && info.targeted) {
|
|
252
|
+
console.log(
|
|
253
|
+
` generated from current bundle: ${info.generatedFromCurrentBundle === false ? "NO (stale)" : info.generatedFromCurrentBundle === true ? "yes" : "n/a"}`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
258
257
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (line) console.log(` ${line}`);
|
|
258
|
+
console.log(" binaries:");
|
|
259
|
+
for (const [bin, info] of Object.entries(report.binaries)) {
|
|
260
|
+
console.log(` ${bin}: ${info.found ? `${info.version ?? "(unknown version)"} @ ${info.path}` : "NOT FOUND"}`);
|
|
263
261
|
}
|
|
264
|
-
}
|
|
265
262
|
|
|
266
|
-
if (report.hookLogs) {
|
|
267
|
-
const { denyLog, activityLog } = report.hookLogs;
|
|
268
263
|
console.log(
|
|
269
|
-
`
|
|
264
|
+
` marker (${report.marker.path}): ${ok(report.marker.valid)}${report.marker.note ? ` -- ${report.marker.note}` : ""}`,
|
|
270
265
|
);
|
|
271
|
-
if (
|
|
272
|
-
console.log(`
|
|
273
|
-
|
|
274
|
-
|
|
266
|
+
if (report.staleMarker.present)
|
|
267
|
+
console.log(` stale marker (${report.staleMarker.path}): ${report.staleMarker.note}`);
|
|
268
|
+
|
|
269
|
+
console.log(
|
|
270
|
+
` enact-hook doctor --repo: ${report.hookDoctor.available ? `exit ${report.hookDoctor.exitCode}` : `not available -- ${report.hookDoctor.note}`}`,
|
|
271
|
+
);
|
|
272
|
+
if (report.hookDoctor.available && report.hookDoctor.stdout) {
|
|
273
|
+
for (const line of report.hookDoctor.stdout.trim().split("\n")) {
|
|
274
|
+
if (line) console.log(` ${line}`);
|
|
275
275
|
}
|
|
276
276
|
}
|
|
277
|
-
}
|
|
278
277
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
278
|
+
if (report.hookLogs) {
|
|
279
|
+
const { denyLog, activityLog } = report.hookLogs;
|
|
280
|
+
console.log(
|
|
281
|
+
` hook logs: ${denyLog.path} ${denyLog.exists ? `(${denyLog.rowCount} row(s))` : "(not present)"}; ${activityLog.path} ${activityLog.exists ? `(${activityLog.rowCount} row(s))` : "(not present)"}`,
|
|
282
|
+
);
|
|
283
|
+
if (denyLog.lastRows.length > 0) {
|
|
284
|
+
console.log(` last ${denyLog.lastRows.length} deny row(s):`);
|
|
285
|
+
for (const row of denyLog.lastRows) {
|
|
286
|
+
console.log(` ${row.timestamp} ${row.surface} ${row.reason_id} ${row.command} ${row.agent_message}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
286
290
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (command === "doctor") {
|
|
294
|
-
if (!path || !options.repo) {
|
|
295
|
-
fail(`doctor requires: doctor <plugin> --repo <path> [--json]\n${REPO_USAGE_HINT}`);
|
|
296
|
-
}
|
|
297
|
-
let pluginRoot;
|
|
298
|
-
try {
|
|
299
|
-
pluginRoot = resolveBundlePath(path);
|
|
300
|
-
} catch (err) {
|
|
301
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
302
|
-
exit(1);
|
|
303
|
-
}
|
|
304
|
-
let report;
|
|
305
|
-
try {
|
|
306
|
-
report = runRepoDoctor(pluginRoot, { repo: options.repo });
|
|
307
|
-
} catch (err) {
|
|
308
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
309
|
-
exit(1);
|
|
310
|
-
}
|
|
311
|
-
if (options.json) {
|
|
312
|
-
console.log(JSON.stringify(report, null, 2));
|
|
313
|
-
} else {
|
|
314
|
-
printRepoDoctorReport(report);
|
|
291
|
+
if (report.problems.length > 0) {
|
|
292
|
+
console.error(`[doctor enact-repo] ${report.problems.length} problem(s):`);
|
|
293
|
+
for (const p of report.problems) console.error(` - ${p}`);
|
|
294
|
+
} else {
|
|
295
|
+
console.log("[doctor enact-repo] all hard requirements OK");
|
|
296
|
+
}
|
|
315
297
|
}
|
|
316
|
-
exit(report.exitCode);
|
|
317
|
-
}
|
|
318
298
|
|
|
319
|
-
// ---------------------------------------------------------------------------
|
|
320
|
-
// `
|
|
321
|
-
//
|
|
322
|
-
|
|
323
|
-
//
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
// `doctor <plugin> --repo <path>` — read-only repo-local health check.
|
|
301
|
+
// Any plugin, bundled or not: bundled-extensions.json is the DEFAULT set, not
|
|
302
|
+
// an allow-list. `<plugin>` is a bundled name or an explicit path, resolved
|
|
303
|
+
// exactly like install/uninstall.
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
if (command === "doctor") {
|
|
306
|
+
if (!path || !options.repo) {
|
|
307
|
+
fail(`doctor requires: doctor <plugin> --repo <path> [--json]\n${REPO_USAGE_HINT}`);
|
|
308
|
+
}
|
|
309
|
+
let pluginRoot;
|
|
310
|
+
try {
|
|
311
|
+
pluginRoot = resolveBundlePath(path);
|
|
312
|
+
} catch (err) {
|
|
313
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
314
|
+
exit(1);
|
|
315
|
+
}
|
|
316
|
+
let report;
|
|
317
|
+
try {
|
|
318
|
+
report = runRepoDoctor(pluginRoot, { repo: options.repo });
|
|
319
|
+
} catch (err) {
|
|
320
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
321
|
+
exit(1);
|
|
337
322
|
}
|
|
323
|
+
if (options.json) {
|
|
324
|
+
console.log(JSON.stringify(report, null, 2));
|
|
325
|
+
} else {
|
|
326
|
+
printRepoDoctorReport(report);
|
|
327
|
+
}
|
|
328
|
+
exit(report.exitCode);
|
|
338
329
|
}
|
|
339
330
|
|
|
340
|
-
|
|
331
|
+
// ---------------------------------------------------------------------------
|
|
332
|
+
// `list` command — enumerate available plugin bundles.
|
|
333
|
+
// ---------------------------------------------------------------------------
|
|
334
|
+
if (command === "list") {
|
|
335
|
+
// The package's own extensions/ dir, resolved relative to this script.
|
|
336
|
+
const PACKAGE_ROOT = resolve(__dirname, "..");
|
|
337
|
+
const packageExtensions = join(PACKAGE_ROOT, "extensions");
|
|
338
|
+
|
|
339
|
+
let roots;
|
|
340
|
+
if (path) {
|
|
341
|
+
// Explicit dir argument → scan only that dir.
|
|
342
|
+
roots = [resolve(processCwd(), path)];
|
|
343
|
+
} else {
|
|
344
|
+
// Default: package-bundled extensions/ + cwd/extensions/ (if it differs).
|
|
345
|
+
roots = [packageExtensions];
|
|
346
|
+
const cwdExtensions = join(processCwd(), "extensions");
|
|
347
|
+
if (resolve(cwdExtensions) !== resolve(packageExtensions) && existsSync(cwdExtensions)) {
|
|
348
|
+
roots.push(cwdExtensions);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
341
351
|
|
|
342
|
-
|
|
343
|
-
console.log(JSON.stringify(entries, null, 2));
|
|
344
|
-
exit(0);
|
|
345
|
-
}
|
|
352
|
+
const entries = listBundles(roots);
|
|
346
353
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
return str.length > len ? str.slice(0, len - 1) + "…" : str;
|
|
352
|
-
}
|
|
354
|
+
if (options.json) {
|
|
355
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
356
|
+
exit(0);
|
|
357
|
+
}
|
|
353
358
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
359
|
+
// Human-readable table.
|
|
360
|
+
const TRUNC = 60;
|
|
361
|
+
function trunc(str, len) {
|
|
362
|
+
if (!str) return "";
|
|
363
|
+
return str.length > len ? str.slice(0, len - 1) + "…" : str;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (entries.length === 0) {
|
|
367
|
+
console.log("No plugin bundles found.");
|
|
368
|
+
exit(0);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Header.
|
|
372
|
+
const COL_NAME = 28;
|
|
373
|
+
const COL_VER = 8;
|
|
374
|
+
const COL_CAT = 18;
|
|
375
|
+
const COL_SURF = 22;
|
|
358
376
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const hr = "─".repeat(COL_NAME + COL_VER + COL_CAT + COL_SURF + 3 + TRUNC);
|
|
368
|
-
console.log(hr);
|
|
369
|
-
console.log(
|
|
370
|
-
padR("Name", COL_NAME) + " " +
|
|
371
|
-
padR("Ver", COL_VER) + " " +
|
|
372
|
-
padR("Category", COL_CAT) + " " +
|
|
373
|
-
padR("Surfaces", COL_SURF) + " " +
|
|
374
|
-
"Description"
|
|
375
|
-
);
|
|
376
|
-
console.log(hr);
|
|
377
|
-
|
|
378
|
-
for (const e of entries) {
|
|
379
|
-
const surfaces = e.targets.join(", ");
|
|
377
|
+
function padR(str, n) {
|
|
378
|
+
return String(str ?? "")
|
|
379
|
+
.padEnd(n)
|
|
380
|
+
.slice(0, n);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const hr = "─".repeat(COL_NAME + COL_VER + COL_CAT + COL_SURF + 3 + TRUNC);
|
|
384
|
+
console.log(hr);
|
|
380
385
|
console.log(
|
|
381
|
-
padR(
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
+
padR("Name", COL_NAME) +
|
|
387
|
+
" " +
|
|
388
|
+
padR("Ver", COL_VER) +
|
|
389
|
+
" " +
|
|
390
|
+
padR("Category", COL_CAT) +
|
|
391
|
+
" " +
|
|
392
|
+
padR("Surfaces", COL_SURF) +
|
|
393
|
+
" " +
|
|
394
|
+
"Description",
|
|
386
395
|
);
|
|
387
|
-
|
|
388
|
-
console.log(hr);
|
|
389
|
-
console.log(`${entries.length} bundle${entries.length === 1 ? "" : "s"} found.`);
|
|
390
|
-
exit(0);
|
|
391
|
-
}
|
|
396
|
+
console.log(hr);
|
|
392
397
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
398
|
+
for (const e of entries) {
|
|
399
|
+
const surfaces = e.targets.join(", ");
|
|
400
|
+
console.log(
|
|
401
|
+
padR(e.name, COL_NAME) +
|
|
402
|
+
" " +
|
|
403
|
+
padR(e.version, COL_VER) +
|
|
404
|
+
" " +
|
|
405
|
+
padR(e.category, COL_CAT) +
|
|
406
|
+
" " +
|
|
407
|
+
padR(surfaces, COL_SURF) +
|
|
408
|
+
" " +
|
|
409
|
+
trunc(e.description, TRUNC),
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
console.log(hr);
|
|
413
|
+
console.log(`${entries.length} bundle${entries.length === 1 ? "" : "s"} found.`);
|
|
414
|
+
exit(0);
|
|
409
415
|
}
|
|
410
416
|
|
|
411
|
-
//
|
|
412
|
-
//
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
// `index` command — generate a discovery index of all plugin bundles.
|
|
419
|
+
//
|
|
420
|
+
// The index is a LOCAL GENERATED ARTIFACT (never committed). The generated/
|
|
421
|
+
// directory is gitignored. The schema at spec/index.json is the committed
|
|
422
|
+
// contract. No network, no marketplace, no catalog/ — purely local.
|
|
423
|
+
// ---------------------------------------------------------------------------
|
|
424
|
+
if (command === "index") {
|
|
425
|
+
const PACKAGE_ROOT = resolve(__dirname, "..");
|
|
426
|
+
const packageExtensions = join(PACKAGE_ROOT, "extensions");
|
|
427
|
+
|
|
428
|
+
// Same default roots as `list`.
|
|
429
|
+
const roots = [packageExtensions];
|
|
430
|
+
const cwdExtensions = join(processCwd(), "extensions");
|
|
431
|
+
if (resolve(cwdExtensions) !== resolve(packageExtensions) && existsSync(cwdExtensions)) {
|
|
432
|
+
roots.push(cwdExtensions);
|
|
433
|
+
}
|
|
420
434
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
435
|
+
// Build the index. The wall-clock timestamp is computed here at the CLI
|
|
436
|
+
// boundary and passed in — buildIndex itself stays pure (never calls Date).
|
|
437
|
+
let index;
|
|
438
|
+
try {
|
|
439
|
+
index = buildIndex(roots, { now: new Date().toISOString(), packageRoot: PACKAGE_ROOT });
|
|
440
|
+
} catch (err) {
|
|
441
|
+
console.error(
|
|
442
|
+
`[enact-extensions index] Failed to build index: ${err instanceof Error ? err.message : String(err)}`,
|
|
443
|
+
);
|
|
444
|
+
exit(1);
|
|
445
|
+
}
|
|
430
446
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
447
|
+
// Validate against spec/index.json before writing.
|
|
448
|
+
const schemaPath = join(PACKAGE_ROOT, "spec", "index.json");
|
|
449
|
+
let schema;
|
|
450
|
+
try {
|
|
451
|
+
schema = JSON.parse(readFileSync(schemaPath, "utf8"));
|
|
452
|
+
} catch (err) {
|
|
453
|
+
console.error(
|
|
454
|
+
`[enact-extensions index] Cannot read schema ${schemaPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
455
|
+
);
|
|
456
|
+
exit(1);
|
|
457
|
+
}
|
|
441
458
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
? resolve(processCwd(), options.out)
|
|
453
|
-
: join(PACKAGE_ROOT, "generated", "index.json");
|
|
454
|
-
|
|
455
|
-
// Ensure parent directory exists.
|
|
456
|
-
const outDir = resolve(outPath, "..");
|
|
457
|
-
mkdirSync(outDir, { recursive: true });
|
|
458
|
-
|
|
459
|
-
writeFileSync(outPath, json + "\n", "utf8");
|
|
460
|
-
process.stderr.write(`[enact-extensions index] Wrote ${index.count} plugin${index.count === 1 ? "" : "s"} to ${outPath}\n`);
|
|
461
|
-
}
|
|
459
|
+
const addFormats = addFormatsModule.default ?? addFormatsModule;
|
|
460
|
+
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
461
|
+
addFormats(ajv);
|
|
462
|
+
const validate = ajv.compile(schema);
|
|
463
|
+
const valid = validate(index);
|
|
464
|
+
if (!valid) {
|
|
465
|
+
const errs = (validate.errors ?? []).map((e) => ` ${e.instancePath || "/"}: ${e.message}`).join("\n");
|
|
466
|
+
console.error(`[enact-extensions index] Generated index failed schema validation:\n${errs}`);
|
|
467
|
+
exit(1);
|
|
468
|
+
}
|
|
462
469
|
|
|
463
|
-
|
|
464
|
-
|
|
470
|
+
const json = JSON.stringify(index, null, 2);
|
|
471
|
+
// --stdout is normalized to "-" during arg parsing, so this single check covers both.
|
|
472
|
+
const toStdout = options.out === "-";
|
|
465
473
|
|
|
466
|
-
|
|
467
|
-
//
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
} catch (err) {
|
|
473
|
-
// uninstall --repo must work even when the source bundle is gone: a bare
|
|
474
|
-
// name that does not resolve is used verbatim as the plugin name.
|
|
475
|
-
const isBareName = typeof path === "string" && path.length > 0 && !path.includes("/") && !path.includes("\\");
|
|
476
|
-
if (!(command === "uninstall" && isBareName)) {
|
|
477
|
-
fail(err instanceof Error ? err.message : String(err));
|
|
478
|
-
}
|
|
479
|
-
}
|
|
474
|
+
if (toStdout) {
|
|
475
|
+
// Write JSON to stdout; summary to stderr (keeps stdout pipe-clean).
|
|
476
|
+
process.stdout.write(json + "\n");
|
|
477
|
+
process.stderr.write(`[enact-extensions index] ${index.count} plugin${index.count === 1 ? "" : "s"} indexed.\n`);
|
|
478
|
+
} else {
|
|
479
|
+
const outPath = options.out ? resolve(processCwd(), options.out) : join(PACKAGE_ROOT, "generated", "index.json");
|
|
480
480
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
}
|
|
481
|
+
// Ensure parent directory exists.
|
|
482
|
+
const outDir = resolve(outPath, "..");
|
|
483
|
+
mkdirSync(outDir, { recursive: true });
|
|
485
484
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
485
|
+
writeFileSync(outPath, json + "\n", "utf8");
|
|
486
|
+
process.stderr.write(
|
|
487
|
+
`[enact-extensions index] Wrote ${index.count} plugin${index.count === 1 ? "" : "s"} to ${outPath}\n`,
|
|
488
|
+
);
|
|
489
|
+
}
|
|
491
490
|
|
|
492
|
-
if (command === "sync") {
|
|
493
|
-
runSync(pluginRoot, { name: options.name });
|
|
494
491
|
exit(0);
|
|
495
492
|
}
|
|
496
493
|
|
|
497
|
-
//
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
494
|
+
// ---------------------------------------------------------------------------
|
|
495
|
+
// Commands that resolve a plugin root path: validate, sync, install/uninstall --repo.
|
|
496
|
+
// ---------------------------------------------------------------------------
|
|
497
|
+
let pluginRoot = null;
|
|
498
|
+
try {
|
|
499
|
+
pluginRoot = path ? resolveBundlePath(path) : resolve(processCwd());
|
|
500
|
+
} catch (err) {
|
|
501
|
+
// uninstall --repo must work even when the source bundle is gone: a bare
|
|
502
|
+
// name that does not resolve is used verbatim as the plugin name.
|
|
503
|
+
const isBareName = typeof path === "string" && path.length > 0 && !path.includes("/") && !path.includes("\\");
|
|
504
|
+
if (!(command === "uninstall" && isBareName)) {
|
|
505
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
506
|
+
}
|
|
502
507
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
508
|
+
|
|
509
|
+
function manifestName(root) {
|
|
510
|
+
const manifestPath = root ? join(root, ".agents", "plugin.json") : null;
|
|
511
|
+
return manifestPath && existsSync(manifestPath) ? JSON.parse(readFileSync(manifestPath, "utf8")).name : null;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
try {
|
|
515
|
+
if (command === "validate") {
|
|
516
|
+
const ok = runValidate(pluginRoot, undefined, { pluginValidate: true, skillValidate: true });
|
|
517
|
+
exit(ok ? 0 : 1);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (command === "sync") {
|
|
521
|
+
runSync(pluginRoot, { name: options.name });
|
|
522
|
+
exit(0);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// install | uninstall --repo
|
|
526
|
+
const dryRun = options.dryRun === true;
|
|
527
|
+
const repoPluginName = manifestName(pluginRoot) ?? path;
|
|
528
|
+
if (!repoPluginName) {
|
|
529
|
+
fail(`${command} --repo requires a plugin name`);
|
|
530
|
+
}
|
|
531
|
+
const result =
|
|
532
|
+
command === "install"
|
|
533
|
+
? runRepoInstall(pluginRoot, { repo: options.repo, host: options.host, force: options.force, dryRun })
|
|
534
|
+
: runRepoUninstall(repoPluginName, { repo: options.repo, purge: options.purge, force: options.force, dryRun });
|
|
535
|
+
if (dryRun) {
|
|
536
|
+
if (result.actions.length === 0) {
|
|
537
|
+
console.log("[dry-run] no filesystem changes — nothing touched");
|
|
538
|
+
} else {
|
|
539
|
+
for (const a of result.actions) {
|
|
540
|
+
console.log(`[dry-run] would ${a.action}: ${a.file} — ${a.reason}`);
|
|
541
|
+
}
|
|
513
542
|
}
|
|
514
543
|
}
|
|
544
|
+
exit(0);
|
|
545
|
+
} catch (err) {
|
|
546
|
+
if (err instanceof ExitSignal) throw err;
|
|
547
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
515
548
|
}
|
|
516
|
-
exit(0);
|
|
517
|
-
} catch (err) {
|
|
518
|
-
if (err instanceof ExitSignal) throw err;
|
|
519
|
-
fail(err instanceof Error ? err.message : String(err));
|
|
520
|
-
}
|
|
521
549
|
} catch (err) {
|
|
522
550
|
// The only expected escape: a command asked to finish with a status.
|
|
523
551
|
// Setting exitCode rather than exiting lets node drain stdout first.
|