@aiwg/cli 2026.7.21 → 2026.7.24

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 (32) hide show
  1. package/README.md +14 -3
  2. package/dist/src/api/index.d.ts +1 -0
  3. package/dist/src/api/index.js +1 -0
  4. package/dist/src/artifacts/browser-export.js +2 -0
  5. package/dist/src/artifacts/index-builder.js +44 -8
  6. package/dist/src/artifacts/query-engine.js +1 -1
  7. package/dist/src/artifacts/types.js +1 -0
  8. package/dist/src/cli/handlers/index.js +5 -1
  9. package/dist/src/cli/handlers/sessions.js +339 -40
  10. package/dist/src/cli/handlers/setup-manifest.js +800 -0
  11. package/dist/src/cli/handlers/use.js +127 -17
  12. package/dist/src/config/aiwg-config.js +18 -2
  13. package/dist/src/config/cli.js +16 -3
  14. package/dist/src/extensions/commands/definitions.js +99 -0
  15. package/dist/src/security/threat-assessment-config.js +296 -0
  16. package/dist/src/serve/sandbox-registry.js +34 -0
  17. package/dist/src/sessions/adapters/claude.js +37 -9
  18. package/dist/src/sessions/adapters/codex.js +38 -11
  19. package/dist/src/sessions/adapters/cursor.js +166 -10
  20. package/dist/src/sessions/adapters/factory.js +50 -9
  21. package/dist/src/sessions/batch-contracts.js +121 -0
  22. package/dist/src/sessions/batch-import.js +265 -0
  23. package/dist/src/sessions/contracts.js +32 -5
  24. package/dist/src/sessions/import-lease.js +152 -0
  25. package/dist/src/sessions/importer.js +163 -14
  26. package/dist/src/sessions/index.js +6 -0
  27. package/dist/src/sessions/origin.js +117 -0
  28. package/dist/src/sessions/readers.js +1 -1
  29. package/dist/src/sessions/repository.js +354 -13
  30. package/dist/src/sessions/timeline.js +148 -0
  31. package/dist/src/sessions/workspace-discovery.js +319 -0
  32. package/package.json +1 -1
@@ -0,0 +1,800 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { parseDocument, stringify } from 'yaml';
6
+ import { findPackageRoot } from '../find-package-root.js';
7
+ const SETUP_SCHEMA_REL = 'agentic/code/addons/agentic-installer/schemas/v1/setup-manifest.schema.json';
8
+ const GENERATE_HELP = `aiwg setup-generate - generate a starter setup.aiwg.io/v1 SetupManifest
9
+
10
+ Usage:
11
+ aiwg setup-generate [--output PATH] [--name NAME] [--type user|developer|ci]
12
+ [--platform OS] [--force] [--json]
13
+
14
+ Options:
15
+ --output PATH Manifest path to create. Defaults to ./setup.manifest.yaml.
16
+ --name NAME Manifest metadata.name. Defaults to package name or directory name.
17
+ --type TYPE Install type: user, developer, or ci. Defaults to developer.
18
+ --platform OS Platform target: linux, macos, windows, docker. Defaults to current OS.
19
+ --force Overwrite existing generated files.
20
+ --json Emit machine-readable generation result.
21
+ --help, -h Show this help.
22
+ `;
23
+ const VALIDATE_HELP = `aiwg setup-validate - validate a setup.aiwg.io/v1 SetupManifest
24
+
25
+ Usage:
26
+ aiwg setup-validate [manifest-path] [--schema PATH] [--strict] [--fix] [--json]
27
+ aiwg setup-validate --manifest PATH [--json]
28
+
29
+ Options:
30
+ --manifest PATH Manifest path. Defaults to ./setup.manifest.yaml.
31
+ --schema PATH Override canonical schema path.
32
+ --strict Treat warnings as failures.
33
+ --fix Reserved for future safe autofixes; currently validates only.
34
+ --json Emit machine-readable validation results.
35
+ --help, -h Show this help.
36
+ `;
37
+ const RUN_HELP = `aiwg setup-run - execute a setup.aiwg.io/v1 SetupManifest
38
+
39
+ Usage:
40
+ aiwg setup-run [manifest-path] [--manifest PATH] [--dry-run] [--platform OS]
41
+ [--distro NAME] [--params-file PATH] [--param KEY=VALUE]
42
+ [--step STEP_ID] [--skip A,B] [--type user|developer|ci]
43
+ [--yes|--confirm]
44
+
45
+ Options:
46
+ --manifest PATH Manifest path. Defaults to ./setup.manifest.yaml.
47
+ --dry-run Validate and print the execution plan without running steps.
48
+ --platform OS Override platform detection: linux, macos, windows, docker.
49
+ --distro NAME Override Linux distro detection.
50
+ --params-file PATH YAML object containing parameter values.
51
+ --param KEY=VALUE Parameter override. Repeatable; --set is also accepted.
52
+ --step STEP_ID Run only one step, useful after a failed setup.
53
+ --skip A,B Comma-separated step IDs to skip.
54
+ --type TYPE Select default manifest by install type when no path is given.
55
+ --yes, --confirm Explicitly authorize mutating step execution and recovery.
56
+ --help, -h Show this help.
57
+
58
+ Safety:
59
+ setup-run always runs setup-validate before platform detection or execution.
60
+ Mutating execution refuses to run without explicit confirmation.
61
+ `;
62
+ function flagValue(args, name) {
63
+ for (let i = 0; i < args.length; i += 1) {
64
+ if (args[i] === name) {
65
+ const value = args[i + 1];
66
+ return value && !value.startsWith('--') ? value : undefined;
67
+ }
68
+ }
69
+ return undefined;
70
+ }
71
+ function hasFlag(args, ...names) {
72
+ return args.some((arg) => names.includes(arg));
73
+ }
74
+ function positionalManifest(args) {
75
+ const valueFlags = new Set([
76
+ '--manifest',
77
+ '--schema',
78
+ '--platform',
79
+ '--distro',
80
+ '--params-file',
81
+ '--param',
82
+ '--set',
83
+ '--step',
84
+ '--skip',
85
+ '--type',
86
+ '--output',
87
+ '--name',
88
+ ]);
89
+ for (let i = 0; i < args.length; i += 1) {
90
+ const arg = args[i];
91
+ if (valueFlags.has(arg)) {
92
+ i += 1;
93
+ continue;
94
+ }
95
+ if (!arg.startsWith('-'))
96
+ return arg;
97
+ }
98
+ return undefined;
99
+ }
100
+ function resolveSchemaPath(frameworkRoot, cwd, override) {
101
+ if (override)
102
+ return path.resolve(cwd, override);
103
+ const candidates = [
104
+ path.join(frameworkRoot, SETUP_SCHEMA_REL),
105
+ path.join(cwd, SETUP_SCHEMA_REL),
106
+ ];
107
+ const packageRoot = findPackageRoot(path.dirname(new URL(import.meta.url).pathname));
108
+ if (packageRoot)
109
+ candidates.push(path.join(packageRoot, SETUP_SCHEMA_REL));
110
+ const found = candidates.find((candidate) => existsSync(candidate));
111
+ if (!found) {
112
+ throw new Error(`setup schema not found; expected ${SETUP_SCHEMA_REL}`);
113
+ }
114
+ return found;
115
+ }
116
+ function parseYamlFile(filePath) {
117
+ const text = readFileSync(filePath, 'utf8');
118
+ const doc = parseDocument(text, { prettyErrors: false });
119
+ const errors = doc.errors.map((error) => ({
120
+ severity: 'error',
121
+ path: '/',
122
+ rule: 'yaml',
123
+ message: error.message,
124
+ }));
125
+ return { value: errors.length ? null : doc.toJSON(), errors };
126
+ }
127
+ function jsonPointer(parent, segment) {
128
+ const safe = String(segment).replace(/~/g, '~0').replace(/\//g, '~1');
129
+ return parent === '/' ? `/${safe}` : `${parent}/${safe}`;
130
+ }
131
+ function typeName(value) {
132
+ if (Array.isArray(value))
133
+ return 'array';
134
+ if (value === null)
135
+ return 'null';
136
+ return typeof value;
137
+ }
138
+ function schemaTypeMatches(value, expected) {
139
+ if (expected === 'array')
140
+ return Array.isArray(value);
141
+ if (expected === 'object')
142
+ return !!value && typeof value === 'object' && !Array.isArray(value);
143
+ if (expected === 'integer')
144
+ return Number.isInteger(value);
145
+ if (expected === 'boolean')
146
+ return typeof value === 'boolean';
147
+ if (expected === 'string')
148
+ return typeof value === 'string';
149
+ return true;
150
+ }
151
+ function resolveRef(rootSchema, ref) {
152
+ if (!ref.startsWith('#/'))
153
+ throw new Error(`unsupported schema ref: ${ref}`);
154
+ return ref.slice(2).split('/').reduce((current, part) => current?.[part], rootSchema);
155
+ }
156
+ function validateAgainstSchema(value, schema, rootSchema, pointer = '/') {
157
+ const findings = [];
158
+ if (schema.$ref)
159
+ return validateAgainstSchema(value, resolveRef(rootSchema, schema.$ref), rootSchema, pointer);
160
+ if (schema.oneOf) {
161
+ const matches = schema.oneOf.filter((candidate) => validateAgainstSchema(value, candidate, rootSchema, pointer).length === 0);
162
+ if (matches.length !== 1) {
163
+ findings.push({ severity: 'error', path: pointer, rule: 'oneOf', message: `must match exactly one allowed schema shape; matched ${matches.length}` });
164
+ }
165
+ return findings;
166
+ }
167
+ if (schema.type && !schemaTypeMatches(value, schema.type)) {
168
+ findings.push({ severity: 'error', path: pointer, rule: 'type', message: `expected ${schema.type}, got ${typeName(value)}` });
169
+ return findings;
170
+ }
171
+ if (schema.const !== undefined && value !== schema.const) {
172
+ findings.push({ severity: 'error', path: pointer, rule: 'const', message: `expected ${JSON.stringify(schema.const)}` });
173
+ }
174
+ if (schema.enum && !schema.enum.includes(value)) {
175
+ findings.push({ severity: 'error', path: pointer, rule: 'enum', message: `must be one of: ${schema.enum.join(', ')}` });
176
+ }
177
+ if (Array.isArray(value)) {
178
+ if (schema.minItems !== undefined && value.length < schema.minItems) {
179
+ findings.push({ severity: 'error', path: pointer, rule: 'minItems', message: `must contain at least ${schema.minItems} item(s)` });
180
+ }
181
+ if (schema.items) {
182
+ value.forEach((item, index) => findings.push(...validateAgainstSchema(item, schema.items, rootSchema, jsonPointer(pointer, index))));
183
+ }
184
+ }
185
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
186
+ const obj = value;
187
+ for (const required of schema.required ?? []) {
188
+ if (!(required in obj)) {
189
+ findings.push({ severity: 'error', path: pointer, rule: 'required', message: `missing required property '${required}'` });
190
+ }
191
+ }
192
+ if (schema.additionalProperties === false && schema.properties) {
193
+ const allowed = new Set(Object.keys(schema.properties));
194
+ for (const key of Object.keys(obj)) {
195
+ if (!allowed.has(key)) {
196
+ findings.push({ severity: 'error', path: jsonPointer(pointer, key), rule: 'additionalProperties', message: 'unknown property' });
197
+ }
198
+ }
199
+ }
200
+ for (const [key, childSchema] of Object.entries(schema.properties ?? {})) {
201
+ if (key in obj)
202
+ findings.push(...validateAgainstSchema(obj[key], childSchema, rootSchema, jsonPointer(pointer, key)));
203
+ }
204
+ }
205
+ return findings;
206
+ }
207
+ function asManifest(value) {
208
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
209
+ }
210
+ function allSteps(manifest) {
211
+ const out = [];
212
+ const visit = (steps, base) => {
213
+ steps?.forEach((step, index) => {
214
+ const pointer = jsonPointer(base, index);
215
+ out.push({ step, pointer });
216
+ step.routes?.forEach((route, routeIndex) => visit(route.steps, `${pointer}/routes/${routeIndex}/steps`));
217
+ });
218
+ };
219
+ visit(manifest.spec.steps, '/spec/steps');
220
+ manifest.spec.recovery?.forEach((recovery, index) => visit(recovery.steps, `/spec/recovery/${index}/steps`));
221
+ return out;
222
+ }
223
+ function existingRelative(manifestDir, rel) {
224
+ return !!rel && existsSync(path.resolve(manifestDir, rel));
225
+ }
226
+ function installerConsistencyChecks(manifest, manifestDir) {
227
+ if (!manifest)
228
+ return [];
229
+ const findings = [];
230
+ const topStepIds = new Set(manifest.spec.steps.map((step) => step.id));
231
+ const allStepIds = new Set();
232
+ const recoveryIds = new Set((manifest.spec.recovery ?? []).map((recovery) => recovery.id));
233
+ const osConfigIds = new Set((manifest.spec.os_config ?? []).map((entry) => entry.id));
234
+ const installType = manifest.metadata.install_type ?? 'user';
235
+ for (const [index, step] of manifest.spec.steps.entries()) {
236
+ if (allStepIds.has(step.id)) {
237
+ findings.push({ severity: 'error', path: `/spec/steps/${index}/id`, rule: 'uniqueStepId', message: `duplicate step id '${step.id}'` });
238
+ }
239
+ allStepIds.add(step.id);
240
+ for (const dep of step.depends_on ?? []) {
241
+ if (!topStepIds.has(dep)) {
242
+ findings.push({ severity: 'error', path: `/spec/steps/${index}/depends_on`, rule: 'depends_on', message: `unresolved step id '${dep}'` });
243
+ }
244
+ }
245
+ if (step.on_fail && !recoveryIds.has(step.on_fail) && step.on_fail !== 'recover') {
246
+ findings.push({ severity: 'error', path: `/spec/steps/${index}/on_fail`, rule: 'on_fail', message: `unresolved recovery '${step.on_fail}'` });
247
+ }
248
+ }
249
+ for (const { step, pointer } of allSteps(manifest)) {
250
+ if (step.type === 'script' && !existingRelative(manifestDir, step.script)) {
251
+ findings.push({ severity: 'error', path: `${pointer}/script`, rule: 'scriptExists', message: `script not found: ${step.script ?? '(missing)'}` });
252
+ }
253
+ if (step.type === 'chain' && !existingRelative(manifestDir, step.manifest)) {
254
+ findings.push({ severity: 'error', path: `${pointer}/manifest`, rule: 'chainManifestExists', message: `chain manifest not found: ${step.manifest ?? '(missing)'}` });
255
+ }
256
+ if (step.type === 'os-config' && (!step.config_id || !osConfigIds.has(step.config_id))) {
257
+ findings.push({ severity: 'error', path: `${pointer}/config_id`, rule: 'osConfigReference', message: `config_id '${step.config_id ?? '(missing)'}' not found in spec.os_config` });
258
+ }
259
+ if (step.type === 'agentic') {
260
+ if (!step.instruction) {
261
+ findings.push({ severity: 'error', path: `${pointer}/instruction`, rule: 'agenticInstruction', message: 'agentic step requires instruction' });
262
+ }
263
+ else {
264
+ findings.push({ severity: 'warning', path: pointer, rule: 'agenticStep', message: 'agentic steps are exception handling only and require manual intervention during setup-run' });
265
+ }
266
+ }
267
+ if (step.type === 'platform-route') {
268
+ for (const [routeIndex, route] of (step.routes ?? []).entries()) {
269
+ if (!route.steps?.length) {
270
+ findings.push({ severity: 'error', path: `${pointer}/routes/${routeIndex}/steps`, rule: 'platformRouteSteps', message: 'platform-route must contain route steps' });
271
+ }
272
+ }
273
+ }
274
+ }
275
+ for (const [index, param] of (manifest.spec.params ?? []).entries()) {
276
+ if (installType === 'developer' && param.interactive_required && param.default !== undefined) {
277
+ findings.push({ severity: 'error', path: `/spec/params/${index}/default`, rule: 'interactiveDefault', message: 'interactive_required params must not define a default in developer manifests' });
278
+ }
279
+ if (param.interactive_required && param.required) {
280
+ findings.push({ severity: 'warning', path: `/spec/params/${index}/required`, rule: 'redundantRequired', message: 'required is redundant when interactive_required is true' });
281
+ }
282
+ }
283
+ if (installType === 'developer') {
284
+ if (!(manifest.spec.os_config ?? []).length && !/no os configuration/i.test(manifest.metadata.description ?? '')) {
285
+ findings.push({ severity: 'warning', path: '/spec/os_config', rule: 'developerOsConfig', message: 'developer manifest has no os_config block' });
286
+ }
287
+ const applied = new Set(manifest.spec.steps.filter((step) => step.type === 'os-config').map((step) => step.config_id));
288
+ for (const [index, entry] of (manifest.spec.os_config ?? []).entries()) {
289
+ if (entry.requires_relogin && !applied.has(entry.id)) {
290
+ findings.push({ severity: 'warning', path: `/spec/os_config/${index}`, rule: 'unusedReloginOsConfig', message: `os_config '${entry.id}' requires relogin but no step applies it` });
291
+ }
292
+ if (entry.interactive && !entry.description.trim()) {
293
+ findings.push({ severity: 'warning', path: `/spec/os_config/${index}/description`, rule: 'interactiveDescription', message: 'interactive os_config entries need a user-facing description' });
294
+ }
295
+ }
296
+ }
297
+ for (const [index, prereq] of (manifest.spec.prerequisites ?? []).entries()) {
298
+ if (prereq.required !== false && !prereq.install_hint) {
299
+ findings.push({ severity: 'warning', path: `/spec/prerequisites/${index}/install_hint`, rule: 'installHint', message: `required prerequisite '${prereq.name}' has no install_hint` });
300
+ }
301
+ }
302
+ return findings;
303
+ }
304
+ export function validateSetupManifest(options) {
305
+ const manifestPath = path.resolve(options.cwd, options.manifestPath ?? 'setup.manifest.yaml');
306
+ const schemaPath = resolveSchemaPath(options.frameworkRoot, options.cwd, options.schemaPath);
307
+ const findings = [];
308
+ if (!existsSync(manifestPath)) {
309
+ findings.push({ severity: 'error', path: manifestPath, rule: 'manifestExists', message: `manifest not found: ${manifestPath}` });
310
+ return { manifest: null, raw: null, manifestPath, manifestDir: path.dirname(manifestPath), schemaPath, findings };
311
+ }
312
+ const parsed = parseYamlFile(manifestPath);
313
+ findings.push(...parsed.errors);
314
+ let manifest = asManifest(parsed.value);
315
+ if (!parsed.errors.length) {
316
+ const schema = JSON.parse(readFileSync(schemaPath, 'utf8'));
317
+ findings.push(...validateAgainstSchema(parsed.value, schema, schema));
318
+ manifest = findings.some((finding) => finding.severity === 'error') ? null : asManifest(parsed.value);
319
+ findings.push(...installerConsistencyChecks(manifest, path.dirname(manifestPath)));
320
+ }
321
+ if (options.strict) {
322
+ for (const finding of findings) {
323
+ if (finding.severity === 'warning')
324
+ finding.severity = 'error';
325
+ }
326
+ }
327
+ return { manifest, raw: parsed.value, manifestPath, manifestDir: path.dirname(manifestPath), schemaPath, findings };
328
+ }
329
+ function renderValidationText(result) {
330
+ const errors = result.findings.filter((finding) => finding.severity === 'error');
331
+ const warnings = result.findings.filter((finding) => finding.severity === 'warning');
332
+ const manifest = result.manifest;
333
+ const lines = [
334
+ `Validating: ${result.manifestPath}`,
335
+ '',
336
+ ` Schema: ${errors.length ? 'invalid' : 'valid'} (${result.schemaPath})`,
337
+ ];
338
+ if (manifest) {
339
+ lines.push(` Install type: ${manifest.metadata.install_type ?? 'user'}`);
340
+ lines.push(` Metadata: name=${manifest.metadata.name}${manifest.metadata.version ? ` version=${manifest.metadata.version}` : ''}`);
341
+ lines.push(` Platform: ${manifest.spec.platforms.map((p) => p.os).join(', ')}`);
342
+ lines.push(` Params: ${(manifest.spec.params ?? []).length}`);
343
+ lines.push(` Prerequisites: ${(manifest.spec.prerequisites ?? []).length}`);
344
+ lines.push(` OS Config: ${(manifest.spec.os_config ?? []).length}`);
345
+ lines.push(` Steps: ${manifest.spec.steps.length}`);
346
+ lines.push(` Recovery: ${(manifest.spec.recovery ?? []).length}`);
347
+ }
348
+ if (errors.length) {
349
+ lines.push('', ` Errors (${errors.length}):`);
350
+ errors.forEach((finding) => lines.push(` x ${finding.path}: ${finding.rule} - ${finding.message}`));
351
+ }
352
+ if (warnings.length) {
353
+ lines.push('', ` Warnings (${warnings.length}):`);
354
+ warnings.forEach((finding) => lines.push(` ! ${finding.path}: ${finding.rule} - ${finding.message}`));
355
+ }
356
+ lines.push('', `Result: ${errors.length ? 'INVALID' : 'VALID'}${warnings.length ? ` (${warnings.length} warning${warnings.length === 1 ? '' : 's'})` : ''}`);
357
+ return lines.join('\n') + '\n';
358
+ }
359
+ function parseParamValues(args) {
360
+ const values = {};
361
+ for (let i = 0; i < args.length; i += 1) {
362
+ if (args[i] !== '--param' && args[i] !== '--set')
363
+ continue;
364
+ const raw = args[i + 1] ?? '';
365
+ const eq = raw.indexOf('=');
366
+ if (eq <= 0)
367
+ throw new Error(`${args[i]} requires KEY=VALUE`);
368
+ values[raw.slice(0, eq)] = raw.slice(eq + 1);
369
+ i += 1;
370
+ }
371
+ return values;
372
+ }
373
+ function parseRunOptions(ctx) {
374
+ const args = ctx.args;
375
+ const type = flagValue(args, '--type');
376
+ let manifest = flagValue(args, '--manifest') ?? positionalManifest(args);
377
+ if (!manifest && type === 'developer' && existsSync(path.join(ctx.cwd, 'installer/setup.dev.manifest.yaml'))) {
378
+ manifest = 'installer/setup.dev.manifest.yaml';
379
+ }
380
+ if (!manifest && type === 'user' && existsSync(path.join(ctx.cwd, 'installer/setup.user.manifest.yaml'))) {
381
+ manifest = 'installer/setup.user.manifest.yaml';
382
+ }
383
+ return {
384
+ cwd: ctx.cwd,
385
+ frameworkRoot: ctx.frameworkRoot,
386
+ manifestPath: manifest,
387
+ dryRun: hasFlag(args, '--dry-run') || ctx.dryRun,
388
+ platform: flagValue(args, '--platform'),
389
+ distro: flagValue(args, '--distro'),
390
+ paramsFile: flagValue(args, '--params-file'),
391
+ paramValues: parseParamValues(args),
392
+ step: flagValue(args, '--step'),
393
+ skip: new Set((flagValue(args, '--skip') ?? '').split(',').map((s) => s.trim()).filter(Boolean)),
394
+ type,
395
+ yes: hasFlag(args, '--yes', '--confirm'),
396
+ };
397
+ }
398
+ function detectPlatform(options) {
399
+ const mappedOs = process.platform === 'darwin' ? 'macos' : process.platform === 'win32' ? 'windows' : 'linux';
400
+ let distro = options.distro;
401
+ if (!distro && mappedOs === 'linux' && existsSync('/etc/os-release')) {
402
+ const match = readFileSync('/etc/os-release', 'utf8').match(/^ID=(.+)$/m);
403
+ distro = match?.[1]?.replace(/^"|"$/g, '');
404
+ }
405
+ return {
406
+ os: options.platform ?? mappedOs,
407
+ distro,
408
+ arch: os.arch() === 'arm64' ? 'arm64' : os.arch(),
409
+ shell: process.env.SHELL ? path.basename(process.env.SHELL) : (mappedOs === 'windows' ? 'native' : 'sh'),
410
+ };
411
+ }
412
+ function currentSetupOs() {
413
+ if (process.platform === 'darwin')
414
+ return 'macos';
415
+ if (process.platform === 'win32')
416
+ return 'windows';
417
+ return 'linux';
418
+ }
419
+ function defaultManifestName(cwd) {
420
+ const packagePath = path.join(cwd, 'package.json');
421
+ if (existsSync(packagePath)) {
422
+ try {
423
+ const pkg = JSON.parse(readFileSync(packagePath, 'utf8'));
424
+ if (typeof pkg.name === 'string' && pkg.name.trim())
425
+ return pkg.name.trim();
426
+ }
427
+ catch {
428
+ // Fall back to directory name.
429
+ }
430
+ }
431
+ return path.basename(cwd);
432
+ }
433
+ function parseGenerateOptions(ctx) {
434
+ const type = flagValue(ctx.args, '--type');
435
+ if (type && !['user', 'developer', 'ci'].includes(type)) {
436
+ throw new Error(`setup-generate: unsupported --type '${type}'`);
437
+ }
438
+ return {
439
+ cwd: ctx.cwd,
440
+ output: flagValue(ctx.args, '--output') ?? flagValue(ctx.args, '--manifest') ?? positionalManifest(ctx.args),
441
+ name: flagValue(ctx.args, '--name'),
442
+ type: type ?? 'developer',
443
+ platform: flagValue(ctx.args, '--platform') ?? currentSetupOs(),
444
+ force: hasFlag(ctx.args, '--force'),
445
+ json: hasFlag(ctx.args, '--json'),
446
+ };
447
+ }
448
+ export function generateSetupManifest(options) {
449
+ const manifestPath = path.resolve(options.cwd, options.output ?? 'setup.manifest.yaml');
450
+ const manifestDir = path.dirname(manifestPath);
451
+ const scriptRel = 'scripts/setup.sh';
452
+ const scriptPath = path.join(manifestDir, scriptRel);
453
+ const conflicts = [manifestPath, scriptPath].filter((candidate) => existsSync(candidate));
454
+ if (conflicts.length && !options.force) {
455
+ return {
456
+ exitCode: 1,
457
+ message: `setup-generate: refusing to overwrite existing files without --force: ${conflicts.join(', ')}`,
458
+ };
459
+ }
460
+ const manifest = {
461
+ apiVersion: 'setup.aiwg.io/v1',
462
+ kind: 'SetupManifest',
463
+ metadata: {
464
+ name: options.name ?? defaultManifestName(options.cwd),
465
+ description: 'Generated starter manifest; no os configuration required.',
466
+ install_type: options.type ?? 'developer',
467
+ },
468
+ spec: {
469
+ platforms: [
470
+ { os: options.platform ?? currentSetupOs() },
471
+ ],
472
+ steps: [
473
+ {
474
+ id: 'setup',
475
+ type: 'script',
476
+ script: scriptRel,
477
+ },
478
+ ],
479
+ briefing: {
480
+ success: 'Setup completed.',
481
+ next_steps: [
482
+ 'Review generated script bodies before running against a real environment.',
483
+ ],
484
+ },
485
+ },
486
+ };
487
+ mkdirSync(path.dirname(scriptPath), { recursive: true });
488
+ mkdirSync(manifestDir, { recursive: true });
489
+ writeFileSync(scriptPath, [
490
+ '#!/usr/bin/env sh',
491
+ 'set -eu',
492
+ `echo "Setup placeholder for ${manifest.metadata.name}"`,
493
+ '',
494
+ ].join('\n'), 'utf8');
495
+ chmodSync(scriptPath, 0o755);
496
+ writeFileSync(manifestPath, stringify(manifest), 'utf8');
497
+ const payload = { manifestPath, scriptPath, manifest };
498
+ if (options.json)
499
+ process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
500
+ else {
501
+ process.stdout.write([
502
+ `Generated: ${manifestPath}`,
503
+ `Script: ${scriptPath}`,
504
+ 'Next: aiwg setup-validate --manifest ' + manifestPath,
505
+ '',
506
+ ].join('\n'));
507
+ }
508
+ return { exitCode: 0 };
509
+ }
510
+ function platformMatches(target, candidate) {
511
+ if (candidate.os !== target.os)
512
+ return false;
513
+ if (candidate.distros?.length && (!target.distro || !candidate.distros.includes(target.distro)))
514
+ return false;
515
+ if (candidate.arch?.length && !candidate.arch.includes(target.arch))
516
+ return false;
517
+ if (candidate.shell && candidate.shell !== target.shell)
518
+ return false;
519
+ return true;
520
+ }
521
+ function stepPlatformMatches(target, step) {
522
+ if (!step.platform)
523
+ return true;
524
+ return Array.isArray(step.platform) ? step.platform.includes(target) : step.platform === target;
525
+ }
526
+ function loadParamsFile(cwd, paramsFile) {
527
+ if (!paramsFile)
528
+ return {};
529
+ const full = path.resolve(cwd, paramsFile);
530
+ const parsed = parseYamlFile(full);
531
+ if (parsed.errors.length)
532
+ throw new Error(`params file is invalid YAML: ${parsed.errors[0].message}`);
533
+ if (!parsed.value || typeof parsed.value !== 'object' || Array.isArray(parsed.value)) {
534
+ throw new Error('params file must be a YAML object');
535
+ }
536
+ return Object.fromEntries(Object.entries(parsed.value).map(([key, value]) => [key, String(value)]));
537
+ }
538
+ function expandPathValue(value) {
539
+ if (value === '~')
540
+ return os.homedir();
541
+ if (value.startsWith('~/'))
542
+ return path.join(os.homedir(), value.slice(2));
543
+ return value.replace(/\$HOME\b/g, os.homedir());
544
+ }
545
+ function resolveParams(manifest, options) {
546
+ const fileValues = loadParamsFile(options.cwd, options.paramsFile);
547
+ const values = {};
548
+ const missing = [];
549
+ for (const param of manifest.spec.params ?? []) {
550
+ let value = options.paramValues[param.name] ?? fileValues[param.name] ?? process.env[param.name];
551
+ if (value === undefined && !param.interactive_required && param.default !== undefined)
552
+ value = String(param.default);
553
+ if ((param.required || param.interactive_required) && value === undefined) {
554
+ missing.push(param.name);
555
+ continue;
556
+ }
557
+ if (value !== undefined) {
558
+ if (param.type === 'choice' && param.choices?.length && !param.choices.includes(value)) {
559
+ missing.push(`${param.name} (must be one of: ${param.choices.join(', ')})`);
560
+ continue;
561
+ }
562
+ values[param.name] = param.type === 'path' ? expandPathValue(value) : value;
563
+ }
564
+ }
565
+ return { values, missing };
566
+ }
567
+ function selectedSteps(manifest, options) {
568
+ let steps = manifest.spec.steps;
569
+ if (options.step)
570
+ steps = steps.filter((step) => step.id === options.step);
571
+ return steps.filter((step) => !options.skip.has(step.id));
572
+ }
573
+ function planLines(manifest, manifestPath, target, params, steps) {
574
+ const lines = [
575
+ `[setup:dry-run] Would execute: ${manifestPath}`,
576
+ ` Platform: ${target.os}${target.distro ? `/${target.distro}` : ''}/${target.arch}`,
577
+ ` Install type: ${manifest.metadata.install_type ?? 'user'}`,
578
+ '',
579
+ ` Params (${Object.keys(params).length}):`,
580
+ ...Object.keys(params).sort().map((key) => ` ${key}=${params[key]}`),
581
+ '',
582
+ ` Steps (${steps.length}):`,
583
+ ];
584
+ steps.forEach((step, index) => {
585
+ lines.push(` ${index + 1}. ${step.id} (${step.type})${step.script ? ` script=${step.script}` : ''}${step.config_id ? ` config_id=${step.config_id}` : ''}${step.manifest ? ` manifest=${step.manifest}` : ''}`);
586
+ });
587
+ return lines.join('\n') + '\n';
588
+ }
589
+ function commandResult(status) {
590
+ const code = status === null ? 1 : status;
591
+ return { ok: code === 0, status: code };
592
+ }
593
+ function runShell(command, cwd, env) {
594
+ const result = spawnSync(command, { cwd, env, shell: true, stdio: 'inherit' });
595
+ return commandResult(result.status);
596
+ }
597
+ function isDestructiveScript(filePath) {
598
+ const name = path.basename(filePath);
599
+ if (/reset/i.test(name))
600
+ return true;
601
+ if (!existsSync(filePath) || !statSync(filePath).isFile())
602
+ return false;
603
+ const text = readFileSync(filePath, 'utf8');
604
+ return /\brm\s+-rf\b|\b(cp|mv)\s+-f\b|>\s*\S+/.test(text);
605
+ }
606
+ function runStep(step, manifest, manifestDir, params, targetOs, confirmed, recovery = false) {
607
+ if (!stepPlatformMatches(targetOs, step))
608
+ return { ok: true, status: 0 };
609
+ const env = { ...process.env, ...params };
610
+ console.log(`[setup] Step ${step.id} (${step.type})`);
611
+ if (step.type === 'script') {
612
+ const scriptPath = path.resolve(manifestDir, step.script);
613
+ console.log(` script: ${scriptPath}`);
614
+ console.log(` env: ${Object.keys(params).sort().join(', ') || '(none)'}`);
615
+ if (step.verify)
616
+ console.log(` verify: ${Array.isArray(step.verify) ? step.verify.join(' && ') : step.verify}`);
617
+ if ((recovery || isDestructiveScript(scriptPath)) && !confirmed) {
618
+ console.error(`setup-run: refusing destructive step '${step.id}' without --yes/--confirm`);
619
+ return { ok: false, status: 2, failedStep: step.id };
620
+ }
621
+ const result = spawnSync(scriptPath, [], { cwd: manifestDir, env, stdio: 'inherit' });
622
+ const ran = commandResult(result.status);
623
+ if (!ran.ok)
624
+ return { ...ran, failedStep: step.id };
625
+ const verifies = Array.isArray(step.verify) ? step.verify : step.verify ? [step.verify] : [];
626
+ for (const verify of verifies) {
627
+ const verified = runShell(verify, manifestDir, env);
628
+ if (!verified.ok)
629
+ return { ...verified, failedStep: step.id };
630
+ }
631
+ return ran;
632
+ }
633
+ if (step.type === 'verify' || step.type === 'detect') {
634
+ const commands = step.commands ?? (Array.isArray(step.verify) ? step.verify : step.verify ? [step.verify] : []);
635
+ for (const command of commands) {
636
+ console.log(` command: ${command}`);
637
+ const result = runShell(command, manifestDir, env);
638
+ if (!result.ok)
639
+ return { ...result, failedStep: step.id };
640
+ }
641
+ return { ok: true, status: 0 };
642
+ }
643
+ if (step.type === 'os-config') {
644
+ const entry = manifest.spec.os_config?.find((item) => item.id === step.config_id);
645
+ console.log(` os_config: ${entry.id} - ${entry.description}`);
646
+ const check = runShell(entry.check, manifestDir, env);
647
+ if (check.ok)
648
+ return { ok: true, status: 0 };
649
+ if (!confirmed) {
650
+ console.error(`setup-run: refusing OS configuration '${entry.id}' without --yes/--confirm`);
651
+ return { ok: false, status: 2, failedStep: step.id };
652
+ }
653
+ return runShell(entry.apply, manifestDir, env);
654
+ }
655
+ if (step.type === 'agentic') {
656
+ console.error(`setup-run: agentic step '${step.id}' requires manual installer-agent handling; aborting deterministic CLI execution`);
657
+ return { ok: false, status: 2, failedStep: step.id };
658
+ }
659
+ if (step.type === 'chain') {
660
+ console.error(`setup-run: chain step '${step.id}' validated manifest existence but deterministic nested execution is not supported yet`);
661
+ return { ok: false, status: 2, failedStep: step.id };
662
+ }
663
+ if (step.type === 'ask') {
664
+ console.error(`setup-run: ask step '${step.id}' requires interactive input; use params before execution`);
665
+ return { ok: false, status: 2, failedStep: step.id };
666
+ }
667
+ if (step.type === 'platform-route') {
668
+ const route = step.routes?.find((item) => item.platform === targetOs);
669
+ for (const nested of route?.steps ?? []) {
670
+ const result = runStep(nested, manifest, manifestDir, params, targetOs, confirmed);
671
+ if (!result.ok)
672
+ return result;
673
+ }
674
+ return { ok: true, status: 0 };
675
+ }
676
+ return { ok: true, status: 0 };
677
+ }
678
+ export function runSetupManifest(options) {
679
+ const validation = validateSetupManifest({
680
+ cwd: options.cwd,
681
+ frameworkRoot: options.frameworkRoot,
682
+ manifestPath: options.manifestPath,
683
+ });
684
+ const errors = validation.findings.filter((finding) => finding.severity === 'error');
685
+ if (errors.length || !validation.manifest) {
686
+ process.stdout.write(renderValidationText(validation));
687
+ return { exitCode: 1, message: 'setup-run: manifest validation failed before execution' };
688
+ }
689
+ const manifest = validation.manifest;
690
+ const target = detectPlatform(options);
691
+ if (!manifest.spec.platforms.some((candidate) => platformMatches(target, candidate))) {
692
+ return { exitCode: 1, message: `setup-run: platform ${target.os}${target.distro ? `/${target.distro}` : ''}/${target.arch}/${target.shell} is not declared in the manifest` };
693
+ }
694
+ const params = resolveParams(manifest, options);
695
+ if (params.missing.length) {
696
+ return { exitCode: 1, message: `setup-run: required params missing before execution: ${params.missing.join(', ')}` };
697
+ }
698
+ const steps = selectedSteps(manifest, options);
699
+ if (options.step && steps.length === 0)
700
+ return { exitCode: 1, message: `setup-run: step '${options.step}' not found` };
701
+ if (options.dryRun) {
702
+ process.stdout.write(planLines(manifest, validation.manifestPath, target, params.values, steps));
703
+ return { exitCode: 0 };
704
+ }
705
+ if (!options.yes) {
706
+ return { exitCode: 2, message: 'setup-run: mutating execution requires --yes or --confirm after reviewing the plan with --dry-run' };
707
+ }
708
+ for (const step of steps) {
709
+ const result = runStep(step, manifest, validation.manifestDir, params.values, target.os, options.yes);
710
+ if (!result.ok) {
711
+ if (result.failedStep && step.on_fail) {
712
+ const recovery = manifest.spec.recovery?.find((item) => item.id === step.on_fail);
713
+ if (recovery) {
714
+ console.error(`[setup] recovery '${recovery.id}' available for failed step '${result.failedStep}'`);
715
+ for (const recoveryStep of recovery.steps) {
716
+ const recoveryResult = runStep(recoveryStep, manifest, validation.manifestDir, params.values, target.os, options.yes, true);
717
+ if (!recoveryResult.ok)
718
+ return { exitCode: recoveryResult.status || 1, message: `setup-run: recovery '${recovery.id}' failed` };
719
+ }
720
+ }
721
+ }
722
+ return { exitCode: result.status || 1, message: `setup-run: step '${result.failedStep ?? step.id}' failed` };
723
+ }
724
+ }
725
+ console.log('[setup] Installation complete');
726
+ if (manifest.spec.briefing?.success)
727
+ console.log(manifest.spec.briefing.success);
728
+ for (const next of manifest.spec.briefing?.next_steps ?? [])
729
+ console.log(` - ${next}`);
730
+ return { exitCode: 0 };
731
+ }
732
+ export const setupValidateHandler = {
733
+ id: 'setup-validate',
734
+ name: 'Setup Manifest Validate',
735
+ description: 'Validate setup.aiwg.io/v1 manifests against the canonical schema and consistency checks',
736
+ category: 'project',
737
+ aliases: [],
738
+ async execute(ctx) {
739
+ if (hasFlag(ctx.args, '--help', '-h')) {
740
+ process.stdout.write(VALIDATE_HELP);
741
+ return { exitCode: 0 };
742
+ }
743
+ const strict = hasFlag(ctx.args, '--strict');
744
+ const result = validateSetupManifest({
745
+ cwd: ctx.cwd,
746
+ frameworkRoot: ctx.frameworkRoot,
747
+ manifestPath: flagValue(ctx.args, '--manifest') ?? positionalManifest(ctx.args),
748
+ schemaPath: flagValue(ctx.args, '--schema'),
749
+ strict,
750
+ });
751
+ const payload = {
752
+ valid: !result.findings.some((finding) => finding.severity === 'error'),
753
+ manifestPath: result.manifestPath,
754
+ schemaPath: result.schemaPath,
755
+ findings: result.findings,
756
+ };
757
+ if (hasFlag(ctx.args, '--json'))
758
+ process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
759
+ else
760
+ process.stdout.write(renderValidationText(result));
761
+ return { exitCode: payload.valid ? 0 : 1 };
762
+ },
763
+ };
764
+ export const setupGenerateHandler = {
765
+ id: 'setup-generate',
766
+ name: 'Setup Manifest Generate',
767
+ description: 'Generate starter setup.aiwg.io/v1 manifests for agentic installer automation',
768
+ category: 'project',
769
+ aliases: [],
770
+ async execute(ctx) {
771
+ if (hasFlag(ctx.args, '--help', '-h')) {
772
+ process.stdout.write(GENERATE_HELP);
773
+ return { exitCode: 0 };
774
+ }
775
+ try {
776
+ return generateSetupManifest(parseGenerateOptions(ctx));
777
+ }
778
+ catch (error) {
779
+ return {
780
+ exitCode: 1,
781
+ message: error instanceof Error ? error.message : String(error),
782
+ };
783
+ }
784
+ },
785
+ };
786
+ export const setupRunHandler = {
787
+ id: 'setup-run',
788
+ name: 'Setup Manifest Run',
789
+ description: 'Validate and execute setup.aiwg.io/v1 manifests with installer safety gates',
790
+ category: 'project',
791
+ aliases: [],
792
+ async execute(ctx) {
793
+ if (hasFlag(ctx.args, '--help', '-h')) {
794
+ process.stdout.write(RUN_HELP);
795
+ return { exitCode: 0 };
796
+ }
797
+ return runSetupManifest(parseRunOptions(ctx));
798
+ },
799
+ };
800
+ //# sourceMappingURL=setup-manifest.js.map