@djordje-stojanovic/sigmaskills 0.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/LICENSE +21 -0
  3. package/README.md +342 -0
  4. package/bin/sigmaskills.js +7 -0
  5. package/manifest.json +28 -0
  6. package/package.json +42 -0
  7. package/registry/agent-hosts.json +2404 -0
  8. package/registry/schema.json +110 -0
  9. package/registry/skill-baselines.json +4 -0
  10. package/registry/source.json +6 -0
  11. package/sigmabrief/SKILL.md +56 -0
  12. package/sigmabrief/agents/openai.yaml +12 -0
  13. package/sigmabrief/references/brief-method.md +73 -0
  14. package/sigmabrief/references/prompt-contract.md +174 -0
  15. package/sigmaperformance/SKILL.md +118 -0
  16. package/sigmaperformance/agents/openai.yaml +12 -0
  17. package/sigmaperformance/references/audit-method.md +112 -0
  18. package/sigmaperformance/references/calibration.md +45 -0
  19. package/sigmaperformance/references/report-contract.md +103 -0
  20. package/sigmareview/SKILL.md +133 -0
  21. package/sigmareview/agents/openai.yaml +12 -0
  22. package/sigmareview/references/report-contract.md +217 -0
  23. package/sigmareview/references/review-method.md +233 -0
  24. package/sigmawrite/SKILL.md +45 -0
  25. package/sigmawrite/agents/openai.yaml +12 -0
  26. package/src/adoption.js +370 -0
  27. package/src/backup.js +398 -0
  28. package/src/catalog.js +211 -0
  29. package/src/cli.js +657 -0
  30. package/src/customization.js +344 -0
  31. package/src/destinations.js +491 -0
  32. package/src/interactive.js +959 -0
  33. package/src/links.js +157 -0
  34. package/src/plan.js +429 -0
  35. package/src/prepack.js +10 -0
  36. package/src/project-lock.js +169 -0
  37. package/src/purge.js +477 -0
  38. package/src/registry/automation-ci.js +411 -0
  39. package/src/registry/automation.js +554 -0
  40. package/src/registry/diff.js +149 -0
  41. package/src/registry/normalize.js +67 -0
  42. package/src/registry/parse.js +184 -0
  43. package/src/registry/sync.js +230 -0
  44. package/src/registry/validate.js +223 -0
  45. package/src/release-ci.js +12 -0
  46. package/src/release.js +837 -0
  47. package/src/restore.js +518 -0
  48. package/src/revision.js +82 -0
  49. package/src/state.js +480 -0
  50. package/src/status.js +469 -0
  51. package/src/transaction.js +636 -0
  52. package/src/uninstall.js +647 -0
  53. package/src/update.js +815 -0
package/src/cli.js ADDED
@@ -0,0 +1,657 @@
1
+ import { getCatalog, findPackageRoot } from './catalog.js';
2
+ import { formatPlanHuman, formatPlanJson } from './plan.js';
3
+ import { runProjectInstaller } from './interactive.js';
4
+ import { collectStatus, formatStatusHuman, formatStatusJson } from './status.js';
5
+ import { executeUpdate, formatUpdateHuman, formatUpdateJson } from './update.js';
6
+ import { executeRestore, formatRestoreHuman, formatRestoreJson } from './restore.js';
7
+ import { executeUninstall, formatUninstallHuman, formatUninstallJson } from './uninstall.js';
8
+ import {
9
+ PURGE_CONFIRMATION_PHRASE,
10
+ executePurge,
11
+ formatPurgeHuman,
12
+ formatPurgeJson,
13
+ } from './purge.js';
14
+ import {
15
+ executeRelease,
16
+ formatReleaseHuman,
17
+ formatReleaseJson,
18
+ } from './release.js';
19
+ import { executeProjectInstall } from './transaction.js';
20
+ import { resolveHomeDir } from './destinations.js';
21
+
22
+ /**
23
+ * Format and print help message.
24
+ *
25
+ * @param {object} catalog
26
+ * @returns {string}
27
+ */
28
+ export function buildHelpText(catalog) {
29
+ const version = catalog.manifest.version;
30
+ const skillsList = catalog.skills
31
+ .map((s) => ` - ${s.id.padEnd(18)} ${s.title.padEnd(20)} (${s.revision.slice(0, 8)}…)`)
32
+ .join('\n');
33
+
34
+ return `sigmaskills v${version}
35
+ Portable Agent Skills monorepo and first-party Sigma Installer
36
+
37
+ Usage:
38
+ sigmaskills [options] [command]
39
+
40
+ Commands:
41
+ install <skill> Install a skill into the project (.agents/skills/<skill>)
42
+ add <skill> Alias for install
43
+ update Update selected whole skills to the running CLI Release
44
+ restore Restore the latest retained backup for a skill
45
+ uninstall Uninstall selected skills or every recorded skill in one scope after Uninstall Review
46
+ purge Remove all Sigma-owned content in one scope after the typed confirmation phrase
47
+ release Prepare or dispatch an owner-triggered GitHub and npm Release
48
+ status Report managed Project or Global Installation state and drift
49
+ list List all shipped skills and their Skill Revisions
50
+ verify Validate manifest, skill resources, and compute revisions
51
+
52
+ Options:
53
+ -v, --version Show version number
54
+ -h, --help Show help
55
+ --skill <name> Skill identifier to install, update, restore, or uninstall (repeatable)
56
+ --all Uninstall every recorded Sigma skill in the chosen Project or Global scope
57
+ --dry-run Preview install, update, restore, uninstall, purge, or release without writing files
58
+ --confirm-purge <phrase>
59
+ Exact typed confirmation for purge; --yes, CI, non-TTY, and JSON are not enough
60
+ --write-identities
61
+ Write package, manifest, and changelog version identities for the calculated Release
62
+ --expected-commit <sha>
63
+ Approved source commit for release dispatch
64
+ --expected-version <version>
65
+ Approved Release version for release dispatch
66
+ --expected-digest <sha256>
67
+ Approved tarball SHA-256 digest for release dispatch
68
+ --json Output in versioned JSON format
69
+ --project <path> Target project root directory (defaults to current directory)
70
+ --global User-level Global Installation (requires --yes to write)
71
+ --state-dir <dir> Custom state directory for private machine state
72
+ --destination <dir> Project destination root (repeatable; default .agents/skills)
73
+ --link Recommended links: Windows junctions, macOS/Linux symbolic links
74
+ --copy Independent managed copy at every selected destination
75
+ --adopt-changed <replace|skip|export>
76
+ Resolve changed owned or drifted trees
77
+ --adopt-legacy <replace|skip|export>
78
+ Resolve trees that match a bundled historical baseline
79
+ --adopt-unverified <replace|skip|export>
80
+ Resolve Sigma-looking trees without a baseline
81
+ --adopt-malformed <replace|skip|export>
82
+ Resolve trees with malformed customization markers
83
+ --outside-edit <replace|skip|export>
84
+ Resolve outside-customization edits during update
85
+ --malformed-markers <skip|repair|replace>
86
+ Resolve malformed customization markers during update
87
+ --clean <remove|keep>
88
+ Uninstall Review choice for a clean skill
89
+ --changed <backup|keep|export|delete>
90
+ Uninstall Review choice for a changed, customized, or malformed skill
91
+ --export-dir <dir>
92
+ Collision-safe destination root for export resolutions
93
+ --no-color Disable color and reveal animation
94
+ --static Disable animation and screen repainting
95
+ --narrow Use the narrow-terminal layout
96
+ -y, --yes Skip interactive confirmations
97
+
98
+ Run without a command to start the interactive Project Installation.
99
+
100
+ Available Skills:
101
+ ${skillsList}
102
+ `;
103
+ }
104
+
105
+ /**
106
+ * Parse CLI arguments into structured options.
107
+ *
108
+ * @param {string[]} args
109
+ * @returns {object}
110
+ */
111
+ export function parseCliArgs(args) {
112
+ const parsed = {
113
+ command: null,
114
+ skillId: null,
115
+ skillIds: [],
116
+ projectRoot: null,
117
+ stateDir: null,
118
+ dryRun: false,
119
+ json: false,
120
+ help: false,
121
+ version: false,
122
+ yes: false,
123
+ global: false,
124
+ noColor: false,
125
+ static: false,
126
+ narrow: false,
127
+ method: null,
128
+ destinations: [],
129
+ adoptChanged: null,
130
+ adoptLegacy: null,
131
+ adoptUnverified: null,
132
+ adoptMalformed: null,
133
+ outsideEdit: null,
134
+ malformedMarkers: null,
135
+ clean: null,
136
+ changed: null,
137
+ exportDir: null,
138
+ all: false,
139
+ confirmPurge: undefined,
140
+ writeIdentities: false,
141
+ expectedCommit: null,
142
+ expectedVersion: null,
143
+ expectedDigest: null,
144
+ unknown: [],
145
+ };
146
+
147
+ for (let i = 0; i < args.length; i++) {
148
+ const arg = args[i];
149
+ if (arg === '-h' || arg === '--help') {
150
+ parsed.help = true;
151
+ } else if (arg === '-v' || arg === '--version') {
152
+ parsed.version = true;
153
+ } else if (arg === '--all') {
154
+ parsed.all = true;
155
+ } else if (arg === '--dry-run') {
156
+ parsed.dryRun = true;
157
+ } else if (arg === '--json') {
158
+ parsed.json = true;
159
+ } else if (arg === '-y' || arg === '--yes') {
160
+ parsed.yes = true;
161
+ } else if (arg === '-g' || arg === '--global') {
162
+ parsed.global = true;
163
+ } else if (arg === '--no-color') {
164
+ parsed.noColor = true;
165
+ } else if (arg === '--static') {
166
+ parsed.static = true;
167
+ } else if (arg === '--narrow') {
168
+ parsed.narrow = true;
169
+ } else if (arg === '--copy') {
170
+ parsed.method = parsed.method && parsed.method !== 'copy' ? 'conflict' : 'copy';
171
+ } else if (arg === '--link') {
172
+ parsed.method = parsed.method && parsed.method !== 'link' ? 'conflict' : 'link';
173
+ } else if (arg === '--adopt-changed') {
174
+ parsed.adoptChanged = args[++i];
175
+ } else if (arg.startsWith('--adopt-changed=')) {
176
+ parsed.adoptChanged = arg.slice('--adopt-changed='.length);
177
+ } else if (arg === '--adopt-legacy') {
178
+ parsed.adoptLegacy = args[++i];
179
+ } else if (arg.startsWith('--adopt-legacy=')) {
180
+ parsed.adoptLegacy = arg.slice('--adopt-legacy='.length);
181
+ } else if (arg === '--adopt-unverified') {
182
+ parsed.adoptUnverified = args[++i];
183
+ } else if (arg.startsWith('--adopt-unverified=')) {
184
+ parsed.adoptUnverified = arg.slice('--adopt-unverified='.length);
185
+ } else if (arg === '--adopt-malformed') {
186
+ parsed.adoptMalformed = args[++i];
187
+ } else if (arg.startsWith('--adopt-malformed=')) {
188
+ parsed.adoptMalformed = arg.slice('--adopt-malformed='.length);
189
+ } else if (arg === '--outside-edit') {
190
+ parsed.outsideEdit = args[++i];
191
+ } else if (arg.startsWith('--outside-edit=')) {
192
+ parsed.outsideEdit = arg.slice('--outside-edit='.length);
193
+ } else if (arg === '--malformed-markers') {
194
+ parsed.malformedMarkers = args[++i];
195
+ } else if (arg.startsWith('--malformed-markers=')) {
196
+ parsed.malformedMarkers = arg.slice('--malformed-markers='.length);
197
+ } else if (arg === '--clean') {
198
+ parsed.clean = args[++i];
199
+ } else if (arg.startsWith('--clean=')) {
200
+ parsed.clean = arg.slice('--clean='.length);
201
+ } else if (arg === '--changed') {
202
+ parsed.changed = args[++i];
203
+ } else if (arg.startsWith('--changed=')) {
204
+ parsed.changed = arg.slice('--changed='.length);
205
+ } else if (arg === '--export-dir') {
206
+ parsed.exportDir = args[++i];
207
+ } else if (arg.startsWith('--export-dir=')) {
208
+ parsed.exportDir = arg.slice('--export-dir='.length);
209
+ } else if (arg === '--write-identities') {
210
+ parsed.writeIdentities = true;
211
+ } else if (arg === '--expected-commit') {
212
+ parsed.expectedCommit = args[++i];
213
+ } else if (arg.startsWith('--expected-commit=')) {
214
+ parsed.expectedCommit = arg.slice('--expected-commit='.length);
215
+ } else if (arg === '--expected-version') {
216
+ parsed.expectedVersion = args[++i];
217
+ } else if (arg.startsWith('--expected-version=')) {
218
+ parsed.expectedVersion = arg.slice('--expected-version='.length);
219
+ } else if (arg === '--expected-digest') {
220
+ parsed.expectedDigest = args[++i];
221
+ } else if (arg.startsWith('--expected-digest=')) {
222
+ parsed.expectedDigest = arg.slice('--expected-digest='.length);
223
+ } else if (arg === '--confirm-purge') {
224
+ parsed.confirmPurge = args[++i] ?? '';
225
+ } else if (arg.startsWith('--confirm-purge=')) {
226
+ parsed.confirmPurge = arg.slice('--confirm-purge='.length);
227
+ } else if (arg === '--destination') {
228
+ parsed.destinations.push(args[++i]);
229
+ } else if (arg.startsWith('--destination=')) {
230
+ parsed.destinations.push(arg.slice('--destination='.length));
231
+ } else if (arg === '--skill') {
232
+ parsed.skillId = args[++i];
233
+ if (parsed.skillId) parsed.skillIds.push(parsed.skillId);
234
+ } else if (arg.startsWith('--skill=')) {
235
+ parsed.skillId = arg.slice('--skill='.length);
236
+ if (parsed.skillId) parsed.skillIds.push(parsed.skillId);
237
+ } else if (arg === '--project' || arg === '--cwd') {
238
+ parsed.projectRoot = args[++i];
239
+ } else if (arg.startsWith('--project=')) {
240
+ parsed.projectRoot = arg.slice('--project='.length);
241
+ } else if (arg.startsWith('--cwd=')) {
242
+ parsed.projectRoot = arg.slice('--cwd='.length);
243
+ } else if (arg === '--state-dir') {
244
+ parsed.stateDir = args[++i];
245
+ } else if (arg.startsWith('--state-dir=')) {
246
+ parsed.stateDir = arg.slice('--state-dir='.length);
247
+ } else if (
248
+ !parsed.command &&
249
+ (arg === 'list' || arg === 'verify' || arg === 'check' || arg === 'install' || arg === 'add' || arg === 'status' || arg === 'update' || arg === 'restore' || arg === 'uninstall' || arg === 'purge' || arg === 'release')
250
+ ) {
251
+ parsed.command = arg;
252
+ } else if (!parsed.skillId && (parsed.command === 'install' || parsed.command === 'add')) {
253
+ parsed.skillId = arg;
254
+ } else if ((parsed.command === 'update' || parsed.command === 'restore' || parsed.command === 'uninstall') && !arg.startsWith('-')) {
255
+ parsed.skillIds.push(arg);
256
+ } else if (arg === '--list') {
257
+ parsed.command = 'list';
258
+ } else {
259
+ parsed.unknown.push(arg);
260
+ }
261
+ }
262
+
263
+ return parsed;
264
+ }
265
+
266
+ function readStdinLine(stdin) {
267
+ return new Promise((resolve) => {
268
+ if (!stdin) {
269
+ resolve('');
270
+ return;
271
+ }
272
+ let settled = false;
273
+ let buffer = '';
274
+ const finish = () => {
275
+ if (settled) return;
276
+ settled = true;
277
+ stdin.off('data', onData);
278
+ stdin.off('end', finish);
279
+ if (typeof stdin.pause === 'function') stdin.pause();
280
+ resolve(buffer.split(/\r?\n/)[0] ?? '');
281
+ };
282
+ const onData = (chunk) => {
283
+ buffer += String(chunk);
284
+ if (buffer.includes('\n') || buffer.includes('\r')) finish();
285
+ };
286
+ stdin.on('data', onData);
287
+ stdin.once('end', finish);
288
+ if (typeof stdin.resume === 'function') stdin.resume();
289
+ });
290
+ }
291
+
292
+ /**
293
+ * Main CLI entrypoint.
294
+ *
295
+ * @param {string[]} args Command-line arguments
296
+ * @param {object} [io] Optional custom stdin/stdout/stderr stream handles
297
+ * @returns {Promise<number>} Exit code (0 for success, 1 for failure)
298
+ */
299
+ export async function runCli(args = process.argv.slice(2), io = { stdout: process.stdout, stderr: process.stderr }) {
300
+ const writeOut = (str) => io.stdout.write(str.endsWith('\n') ? str : `${str}\n`);
301
+ const writeErr = (str) => io.stderr.write(str.endsWith('\n') ? str : `${str}\n`);
302
+
303
+ try {
304
+ const opts = parseCliArgs(args);
305
+ const rootDir = findPackageRoot();
306
+ const catalog = getCatalog(rootDir);
307
+
308
+ if (opts.help) {
309
+ writeOut(buildHelpText(catalog));
310
+ return 0;
311
+ }
312
+
313
+ if (opts.version) {
314
+ writeOut(catalog.manifest.version);
315
+ return 0;
316
+ }
317
+
318
+ if (opts.unknown.length > 0) {
319
+ writeErr(`sigmaskills error: unknown option or command: ${opts.unknown[0]}`);
320
+ writeErr(`Run 'sigmaskills --help' for usage information.`);
321
+ return 1;
322
+ }
323
+
324
+ if (opts.method === 'conflict') {
325
+ writeErr('sigmaskills error: use either --link or --copy, not both');
326
+ return 1;
327
+ }
328
+
329
+ const adoptFlags = [
330
+ ['--adopt-changed', opts.adoptChanged],
331
+ ['--adopt-legacy', opts.adoptLegacy],
332
+ ['--adopt-unverified', opts.adoptUnverified],
333
+ ['--adopt-malformed', opts.adoptMalformed],
334
+ ];
335
+ for (const [flag, value] of adoptFlags) {
336
+ if (value && value !== 'replace' && value !== 'skip' && value !== 'export') {
337
+ writeErr(`sigmaskills error: ${flag} must be replace, skip, or export`);
338
+ return 1;
339
+ }
340
+ }
341
+
342
+ if (opts.outsideEdit && opts.outsideEdit !== 'replace' && opts.outsideEdit !== 'skip' && opts.outsideEdit !== 'export') {
343
+ writeErr('sigmaskills error: --outside-edit must be replace, skip, or export');
344
+ return 1;
345
+ }
346
+
347
+ if (opts.malformedMarkers && opts.malformedMarkers !== 'replace' && opts.malformedMarkers !== 'skip' && opts.malformedMarkers !== 'repair') {
348
+ writeErr('sigmaskills error: --malformed-markers must be skip, repair, or replace');
349
+ return 1;
350
+ }
351
+
352
+ if (opts.clean && opts.clean !== 'remove' && opts.clean !== 'keep') {
353
+ writeErr('sigmaskills error: --clean must be remove or keep');
354
+ return 1;
355
+ }
356
+
357
+ if (opts.changed && opts.changed !== 'backup' && opts.changed !== 'keep' && opts.changed !== 'export' && opts.changed !== 'delete') {
358
+ writeErr('sigmaskills error: --changed must be backup, keep, export, or delete');
359
+ return 1;
360
+ }
361
+
362
+ if (opts.command === 'release') {
363
+ const adapters = io.release || {};
364
+ const result = executeRelease({
365
+ rootDir: adapters.rootDir || rootDir,
366
+ catalog: adapters.catalog || catalog,
367
+ dryRun: opts.dryRun,
368
+ yes: opts.yes,
369
+ writeIdentities: opts.writeIdentities,
370
+ expectedCommit: opts.expectedCommit,
371
+ expectedVersion: opts.expectedVersion,
372
+ expectedDigest: opts.expectedDigest,
373
+ git: adapters.git,
374
+ pack: adapters.pack,
375
+ probes: adapters.probes,
376
+ dispatch: adapters.dispatch,
377
+ workflow: adapters.workflow,
378
+ now: adapters.now,
379
+ });
380
+ writeOut(opts.json ? formatReleaseJson(result) : formatReleaseHuman(result));
381
+ return 0;
382
+ }
383
+
384
+ if (opts.command === 'purge') {
385
+ const env = io.env || process.env;
386
+ const stdin = io.stdin || process.stdin;
387
+ let confirmPurge = opts.confirmPurge;
388
+ if (!opts.dryRun && confirmPurge === undefined) {
389
+ const ci = env.CI;
390
+ const nonInteractive = Boolean(opts.json)
391
+ || (ci !== undefined && ci !== '' && ci !== '0' && String(ci).toLowerCase() !== 'false')
392
+ || !stdin.isTTY;
393
+ if (nonInteractive) {
394
+ writeErr('sigmaskills error: purge requires --confirm-purge with the typed confirmation phrase; --yes, CI, non-TTY, and JSON are not authority');
395
+ return 1;
396
+ }
397
+ writeOut(formatPurgeHuman(executePurge({
398
+ catalog,
399
+ projectRoot: opts.projectRoot || process.cwd(),
400
+ homeDir: resolveHomeDir(env),
401
+ scope: opts.global ? 'global' : 'project',
402
+ customStateDir: opts.stateDir,
403
+ packageRoot: rootDir,
404
+ dryRun: true,
405
+ env,
406
+ })));
407
+ writeOut(`Type ${PURGE_CONFIRMATION_PHRASE} to purge, or nothing to cancel.`);
408
+ confirmPurge = await readStdinLine(stdin);
409
+ }
410
+ const result = executePurge({
411
+ catalog,
412
+ projectRoot: opts.projectRoot || process.cwd(),
413
+ homeDir: resolveHomeDir(env),
414
+ scope: opts.global ? 'global' : 'project',
415
+ customStateDir: opts.stateDir,
416
+ packageRoot: rootDir,
417
+ dryRun: opts.dryRun,
418
+ env,
419
+ confirmPurge,
420
+ });
421
+ writeOut(opts.json ? formatPurgeJson(result) : formatPurgeHuman(result));
422
+ return 0;
423
+ }
424
+
425
+ if (opts.command === 'update') {
426
+ const env = io.env || process.env;
427
+ if (opts.global && !opts.dryRun && !opts.yes) {
428
+ writeErr('sigmaskills error: Global Installation requires both --global and --yes; CI, TTY, JSON, and Agent Host detection never imply that authority');
429
+ return 1;
430
+ }
431
+ if (!opts.dryRun && !opts.yes && opts.skillIds.length === 0) {
432
+ writeErr('sigmaskills error: update requires --yes to apply all changed skills, or --skill <id> to select complete skills; use --dry-run to preview');
433
+ return 1;
434
+ }
435
+ const result = executeUpdate({
436
+ catalog,
437
+ projectRoot: opts.projectRoot || process.cwd(),
438
+ homeDir: resolveHomeDir(env),
439
+ scope: opts.global ? 'global' : 'project',
440
+ customStateDir: opts.stateDir,
441
+ packageRoot: rootDir,
442
+ dryRun: opts.dryRun,
443
+ env,
444
+ skillIds: opts.skillIds,
445
+ outsideEdit: opts.outsideEdit || undefined,
446
+ malformedMarkers: opts.malformedMarkers || undefined,
447
+ exportDir: opts.exportDir || undefined,
448
+ });
449
+ writeOut(opts.json ? formatUpdateJson(result) : formatUpdateHuman(result));
450
+ return 0;
451
+ }
452
+
453
+ if (opts.command === 'uninstall') {
454
+ const env = io.env || process.env;
455
+ if (opts.global && !opts.dryRun && !opts.yes) {
456
+ writeErr('sigmaskills error: Global Installation requires both --global and --yes; CI, TTY, JSON, and Agent Host detection never imply that authority');
457
+ return 1;
458
+ }
459
+ if (opts.all && opts.skillIds.length > 0) {
460
+ writeErr('sigmaskills error: uninstall --all cannot be combined with --skill');
461
+ return 1;
462
+ }
463
+ if (opts.skillIds.length === 0 && !opts.all) {
464
+ writeErr('sigmaskills error: uninstall requires --skill <id> or --all');
465
+ return 1;
466
+ }
467
+ if (!opts.dryRun && !opts.yes) {
468
+ writeErr('sigmaskills error: uninstall requires --yes to apply, or --dry-run to preview');
469
+ return 1;
470
+ }
471
+ const result = executeUninstall({
472
+ catalog,
473
+ projectRoot: opts.projectRoot || process.cwd(),
474
+ homeDir: resolveHomeDir(env),
475
+ scope: opts.global ? 'global' : 'project',
476
+ customStateDir: opts.stateDir,
477
+ packageRoot: rootDir,
478
+ dryRun: opts.dryRun,
479
+ env,
480
+ skillIds: opts.skillIds,
481
+ all: opts.all,
482
+ yes: opts.yes,
483
+ clean: opts.clean || (opts.yes || opts.all ? 'remove' : undefined),
484
+ changed: opts.changed || (opts.all ? 'backup' : undefined),
485
+ exportDir: opts.exportDir || undefined,
486
+ });
487
+ writeOut(opts.json ? formatUninstallJson(result) : formatUninstallHuman(result));
488
+ if ((result.summary?.failed || []).length > 0) return 1;
489
+ return 0;
490
+ }
491
+
492
+ if (opts.command === 'restore') {
493
+ const env = io.env || process.env;
494
+ if (opts.global && !opts.dryRun && !opts.yes) {
495
+ writeErr('sigmaskills error: Global Installation requires both --global and --yes; CI, TTY, JSON, and Agent Host detection never imply that authority');
496
+ return 1;
497
+ }
498
+ if (opts.skillIds.length === 0) {
499
+ writeErr('sigmaskills error: restore requires --skill <id>');
500
+ return 1;
501
+ }
502
+ if (!opts.dryRun && !opts.yes) {
503
+ writeErr('sigmaskills error: restore requires --yes to apply, or --dry-run to preview');
504
+ return 1;
505
+ }
506
+ const result = executeRestore({
507
+ catalog,
508
+ projectRoot: opts.projectRoot || process.cwd(),
509
+ homeDir: resolveHomeDir(env),
510
+ scope: opts.global ? 'global' : 'project',
511
+ customStateDir: opts.stateDir,
512
+ packageRoot: rootDir,
513
+ dryRun: opts.dryRun,
514
+ env,
515
+ skillIds: opts.skillIds,
516
+ yes: opts.yes,
517
+ });
518
+ writeOut(opts.json ? formatRestoreJson(result) : formatRestoreHuman(result));
519
+ return 0;
520
+ }
521
+
522
+ if (opts.command === 'status') {
523
+ const env = io.env || process.env;
524
+ const report = collectStatus({
525
+ catalog,
526
+ projectRoot: opts.projectRoot || process.cwd(),
527
+ homeDir: resolveHomeDir(env),
528
+ scope: opts.global ? 'global' : 'project',
529
+ customStateDir: opts.stateDir,
530
+ packageRoot: rootDir,
531
+ env,
532
+ });
533
+ writeOut(opts.json ? formatStatusJson(report) : formatStatusHuman(report));
534
+ return 0;
535
+ }
536
+
537
+ if (opts.command === 'list' || (!opts.command && opts.json && !opts.skillId)) {
538
+ if (opts.json) {
539
+ const payload = {
540
+ name: catalog.manifest.name,
541
+ version: catalog.manifest.version,
542
+ schemaVersion: catalog.manifest.schemaVersion,
543
+ skills: catalog.skills.map((s) => ({
544
+ id: s.id,
545
+ title: s.title,
546
+ description: s.description,
547
+ revision: s.revision,
548
+ files: s.files,
549
+ })),
550
+ };
551
+ writeOut(JSON.stringify(payload, null, 2));
552
+ } else {
553
+ writeOut(`Skill Pack: ${catalog.manifest.name} v${catalog.manifest.version}\n`);
554
+ for (const skill of catalog.skills) {
555
+ writeOut(`• ${skill.title} (${skill.id})`);
556
+ writeOut(` Revision: ${skill.revision}`);
557
+ writeOut(` Description: ${skill.description}\n`);
558
+ }
559
+ }
560
+ return 0;
561
+ }
562
+
563
+ if (opts.command === 'verify' || opts.command === 'check') {
564
+ writeOut(`✔ Manifest verified (${catalog.manifest.name} v${catalog.manifest.version})`);
565
+ for (const skill of catalog.skills) {
566
+ const fileCount = Object.keys(skill.files).length;
567
+ writeOut(`✔ Skill '${skill.id}': ${fileCount} files verified (revision ${skill.revision.slice(0, 12)}…)`);
568
+ }
569
+ writeOut(`\nAll ${catalog.skills.length} skills in Skill Pack validated successfully.`);
570
+ return 0;
571
+ }
572
+
573
+ if (opts.command === 'install' || opts.command === 'add' || opts.skillId) {
574
+ if (!opts.skillId) {
575
+ writeErr("sigmaskills error: missing required skill name for install command (e.g. 'sigmaskills install sigmawrite')");
576
+ return 1;
577
+ }
578
+
579
+ if (opts.global && !opts.dryRun && !opts.yes) {
580
+ writeErr('sigmaskills error: Global Installation requires both --global and --yes; CI, TTY, JSON, and Agent Host detection never imply that authority');
581
+ return 1;
582
+ }
583
+
584
+ const env = io.env || process.env;
585
+ const result = executeProjectInstall({
586
+ catalog,
587
+ skillId: opts.skillId,
588
+ projectRoot: opts.projectRoot,
589
+ homeDir: resolveHomeDir(env),
590
+ scope: opts.global ? 'global' : 'project',
591
+ customStateDir: opts.stateDir,
592
+ packageRoot: rootDir,
593
+ dryRun: opts.dryRun,
594
+ env,
595
+ selectedRoots: opts.destinations.length > 0 ? opts.destinations : undefined,
596
+ method: opts.method || undefined,
597
+ adoptChanged: opts.adoptChanged || undefined,
598
+ adoptLegacy: opts.adoptLegacy || undefined,
599
+ adoptUnverified: opts.adoptUnverified || undefined,
600
+ adoptMalformed: opts.adoptMalformed || undefined,
601
+ exportDir: opts.exportDir || undefined,
602
+ });
603
+
604
+ if (opts.json) {
605
+ writeOut(formatPlanJson(result.plan));
606
+ } else {
607
+ writeOut(formatPlanHuman(result.plan));
608
+ if (!opts.dryRun) {
609
+ writeOut('');
610
+ for (const dest of result.plan.destinations) {
611
+ const method = dest.method ? ` [${dest.method}]` : '';
612
+ writeOut(`✔ Installed ${result.plan.title} (${result.plan.skill}) to ${dest.relativeDestination}${method}`);
613
+ }
614
+ writeOut(` Revision: ${result.plan.sourceRevision}`);
615
+ if (result.plan.scope !== 'global') {
616
+ writeOut(` Project lock: skills-lock.json updated`);
617
+ }
618
+ }
619
+ }
620
+ return 0;
621
+ }
622
+
623
+ if (!opts.command && !opts.skillId && !opts.dryRun && !opts.yes) {
624
+ return await runProjectInstaller({
625
+ catalog,
626
+ packageRoot: rootDir,
627
+ projectRoot: opts.projectRoot || process.cwd(),
628
+ homeDir: resolveHomeDir(io.env || process.env),
629
+ customStateDir: opts.stateDir,
630
+ initialScope: opts.global ? 'global' : 'project',
631
+ io: {
632
+ stdin: io.stdin || process.stdin,
633
+ stdout: io.stdout,
634
+ stderr: io.stderr,
635
+ env: io.env || process.env,
636
+ },
637
+ options: {
638
+ noColor: opts.noColor,
639
+ static: opts.static,
640
+ narrow: opts.narrow,
641
+ json: opts.json,
642
+ },
643
+ });
644
+ }
645
+
646
+ writeErr(`sigmaskills error: unknown option or command`);
647
+ writeErr(`Run 'sigmaskills --help' for usage information.`);
648
+ return 1;
649
+ } catch (err) {
650
+ writeErr(`sigmaskills error: ${err.message}`);
651
+ if (err.linkFailure) {
652
+ writeErr(`Link failed for '${err.linkFailure.relativeDestination || err.linkFailure.destination}'.`);
653
+ writeErr('The installer did not change method. Re-run with --copy to install a complete managed copy at this destination.');
654
+ }
655
+ return 1;
656
+ }
657
+ }