@aiwg/cli 2026.8.11 → 2026.8.13

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 (42) hide show
  1. package/bin/aiwg.mjs +2 -0
  2. package/dist/src/api/index.d.ts +6 -0
  3. package/dist/src/api/index.js +6 -0
  4. package/dist/src/cli/handlers/artifact-verify.js +171 -0
  5. package/dist/src/cli/handlers/index.js +3 -1
  6. package/dist/src/cli/handlers/setup-manifest.js +52 -3
  7. package/dist/src/cli/handlers/setup.js +15 -2
  8. package/dist/src/cli/handlers/use.js +71 -6
  9. package/dist/src/cli/scope-resolver.js +6 -1
  10. package/dist/src/cli/services/deployment-verification.js +65 -7
  11. package/dist/src/config/aiwg-config.js +4 -3
  12. package/dist/src/config/cli.js +3 -1
  13. package/dist/src/config/gitignore.js +67 -21
  14. package/dist/src/config/workspace.js +8 -1
  15. package/dist/src/extensions/commands/definitions.js +19 -0
  16. package/dist/src/extensions/project-quickref.js +9 -0
  17. package/dist/src/marketplace/artifact-attestation.js +195 -0
  18. package/dist/src/marketplace/exchange.js +437 -79
  19. package/dist/src/marketplace/provenance-types.js +1 -0
  20. package/dist/src/marketplace/provenance.js +7 -1
  21. package/dist/src/providers/hermes-home.js +20 -0
  22. package/dist/src/providers/provider-definitions.js +5 -4
  23. package/dist/src/providers/transformation-receipt-integration.js +448 -0
  24. package/dist/src/providers/transformation-receipt.js +215 -0
  25. package/dist/src/resources/web-release.d.ts +11 -0
  26. package/dist/src/resources/web-release.js +61 -6
  27. package/dist/src/security/artifact-attestation.js +117 -0
  28. package/dist/src/security/artifact-trust.js +557 -0
  29. package/dist/src/security/artifact-verifier.js +478 -0
  30. package/dist/src/skills/deployer.js +5 -1
  31. package/dist/src/tracker/capability-protocol.js +7 -2
  32. package/package.json +5 -1
  33. package/schemas/security/aiwg-artifact-attestation.v1.schema.json +106 -0
  34. package/schemas/security/aiwg-artifact-provenance.v1.schema.json +204 -0
  35. package/schemas/security/aiwg-artifact-trust-root.v1.schema.json +59 -0
  36. package/schemas/security/aiwg-artifact-trust-state.v1.schema.json +32 -0
  37. package/schemas/security/aiwg-artifact-verification-result.v1.schema.json +46 -0
  38. package/schemas/security/threat-assessment-input.v1.schema.json +57 -0
  39. package/schemas/security/threat-assessment-report.v1.schema.json +104 -0
  40. package/tools/agents/deploy-agents.mjs +8 -2
  41. package/tools/agents/providers/base.mjs +29 -2
  42. package/tools/agents/providers/hermes.mjs +163 -19
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { homedir } from 'os';
3
3
  import { join } from 'path';
4
+ import { resolveHermesHomePath } from './hermes-home.js';
4
5
  import { getProviderCapabilities, } from './capability-matrix.js';
5
6
  const ArtifactPathsSchema = z.object({
6
7
  agents: z.string().nullable(),
@@ -464,7 +465,7 @@ const BUILT_IN_SEEDS = [
464
465
  id: 'hermes',
465
466
  aliases: [],
466
467
  builtIn: true,
467
- surfaces: { primary: 'hermes', compatibility: [], precedence: ['AGENTS.md', '.hermes.md', '~/.hermes/skills/'], related: [] },
468
+ surfaces: { primary: 'hermes', compatibility: [], precedence: ['.hermes.md', 'AGENTS.md', resolveHermesHomePath('skills')], related: [] },
468
469
  detection: {
469
470
  env: [],
470
471
  process: ['hermes'],
@@ -474,18 +475,18 @@ const BUILT_IN_SEEDS = [
474
475
  artifacts: {
475
476
  agents: null,
476
477
  commands: null,
477
- skills: '~/.hermes/.aiwg/skills',
478
+ skills: resolveHermesHomePath('skills', '.aiwg'),
478
479
  rules: null,
479
480
  behaviors: null,
480
481
  },
481
- kernelSkills: '~/.hermes/skills',
482
+ kernelSkills: resolveHermesHomePath('skills'),
482
483
  configFile: 'AGENTS.md',
483
484
  contextFiles: { aiwgMd: true, agentsMd: true, claudeMdHook: false, hookFile: '.hermes.md', contextFile: 'AGENTS.md' },
484
485
  },
485
486
  smithPaths: {
486
487
  agents: null,
487
488
  commands: null,
488
- skills: '~/.hermes/skills',
489
+ skills: resolveHermesHomePath('skills'),
489
490
  rules: null,
490
491
  fileExtension: '.md',
491
492
  configFile: 'AGENTS.md',
@@ -0,0 +1,448 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { access, lstat, readFile, readdir } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import path from 'node:path';
5
+ import { readAiwgConfig } from '../config/aiwg-config.js';
6
+ import { readUserRegistry } from '../config/user-registry.js';
7
+ import { USER_SCOPE_PATHS } from '../cli/scope-resolver.js';
8
+ import { getProviderDefinition, getProviderKernelSkillPath, normalizeProviderDefinitionId, resolveProviderPathValue, } from './provider-definitions.js';
9
+ import { createProviderTransformationReceipt, diagnoseProviderTransformationReceipt, providerTransformationReceiptPath, writeProviderTransformationReceipt, } from './transformation-receipt.js';
10
+ const TRANSFORMER_ID = 'aiwg-provider-transformer';
11
+ const TRANSFORMER_VERSION = '1';
12
+ const MANAGED_SIDECAR = '.aiwg-manifest.json';
13
+ const MANAGED_DIRECTORY_MARKER = '.aiwg-managed';
14
+ const MANAGED_FILE_MARKER = /^(?:<!-- aiwg:managed |# aiwg:managed )/m;
15
+ const FRAMEWORK_BUNDLE_DIRS = {
16
+ all: 'sdlc-complete',
17
+ sdlc: 'sdlc-complete',
18
+ marketing: 'media-marketing-kit',
19
+ 'media-curator': 'media-curator',
20
+ research: 'research-complete',
21
+ forensics: 'forensics-complete',
22
+ dfir: 'forensics-complete',
23
+ 'security-engineering': 'security-engineering',
24
+ ops: 'ops-complete',
25
+ validation: 'validation-complete',
26
+ 'knowledge-base': 'knowledge-base',
27
+ };
28
+ function sha256(value) {
29
+ return createHash('sha256').update(value).digest('hex');
30
+ }
31
+ function stable(value) {
32
+ if (Array.isArray(value))
33
+ return `[${value.map(stable).join(',')}]`;
34
+ if (value && typeof value === 'object') {
35
+ const record = value;
36
+ return `{${Object.keys(record).sort().map(key => `${JSON.stringify(key)}:${stable(record[key])}`).join(',')}}`;
37
+ }
38
+ return JSON.stringify(value) ?? 'null';
39
+ }
40
+ function portablePath(root, absolute) {
41
+ const relative = path.relative(root, absolute);
42
+ if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))
43
+ return null;
44
+ return relative.split(path.sep).join('/');
45
+ }
46
+ async function regularFiles(root) {
47
+ const files = [];
48
+ let entries;
49
+ try {
50
+ entries = await readdir(root, { withFileTypes: true });
51
+ }
52
+ catch {
53
+ return files;
54
+ }
55
+ for (const entry of entries) {
56
+ const candidate = path.join(root, entry.name);
57
+ if (entry.isDirectory())
58
+ files.push(...await regularFiles(candidate));
59
+ else if (entry.isFile())
60
+ files.push(candidate);
61
+ }
62
+ return files;
63
+ }
64
+ async function addEntry(files, candidate) {
65
+ try {
66
+ const metadata = await lstat(candidate);
67
+ if (metadata.isFile())
68
+ files.add(candidate);
69
+ else if (metadata.isDirectory()) {
70
+ for (const file of await regularFiles(candidate))
71
+ files.add(file);
72
+ }
73
+ }
74
+ catch {
75
+ // A registry or sidecar entry can describe a stale partial deployment.
76
+ // Receipt finalization binds only successful on-disk outputs; an existing
77
+ // receipt remains responsible for diagnosing later deletion.
78
+ }
79
+ }
80
+ async function collectOwnedDirectory(directory, exactEntries, files) {
81
+ for (const entry of exactEntries)
82
+ await addEntry(files, path.join(directory, entry));
83
+ const sidecarPath = path.join(directory, MANAGED_SIDECAR);
84
+ try {
85
+ const sidecar = JSON.parse(await readFile(sidecarPath, 'utf8'));
86
+ const managed = sidecar.managed && typeof sidecar.managed === 'object'
87
+ ? Object.keys(sidecar.managed)
88
+ : [];
89
+ if (managed.length > 0)
90
+ files.add(sidecarPath);
91
+ for (const name of managed) {
92
+ if (name === path.basename(name) && name !== MANAGED_SIDECAR) {
93
+ await addEntry(files, path.join(directory, name));
94
+ }
95
+ }
96
+ }
97
+ catch {
98
+ // Missing or invalid sidecars are not ownership evidence.
99
+ }
100
+ let entries;
101
+ try {
102
+ entries = await readdir(directory, { withFileTypes: true });
103
+ }
104
+ catch {
105
+ return;
106
+ }
107
+ for (const entry of entries) {
108
+ const candidate = path.join(directory, entry.name);
109
+ if (entry.isDirectory()) {
110
+ try {
111
+ const marker = await lstat(path.join(candidate, MANAGED_DIRECTORY_MARKER));
112
+ if (!marker.isFile())
113
+ continue;
114
+ await addEntry(files, candidate);
115
+ }
116
+ catch {
117
+ // Unmarked directories remain operator-owned.
118
+ }
119
+ continue;
120
+ }
121
+ if (!entry.isFile() || entry.name === MANAGED_SIDECAR)
122
+ continue;
123
+ try {
124
+ const content = await readFile(candidate, 'utf8');
125
+ if (MANAGED_FILE_MARKER.test(content))
126
+ files.add(candidate);
127
+ }
128
+ catch {
129
+ // Unreadable files cannot establish an ownership signal.
130
+ }
131
+ }
132
+ }
133
+ function artifactPaths(provider, scope) {
134
+ const definition = getProviderDefinition(provider);
135
+ if (!definition)
136
+ return null;
137
+ if (scope === 'user')
138
+ return USER_SCOPE_PATHS[provider] ?? null;
139
+ return {
140
+ agents: definition.paths.artifacts.agents ?? '',
141
+ commands: definition.paths.artifacts.commands ?? '',
142
+ skills: definition.paths.artifacts.skills ?? '',
143
+ rules: definition.paths.artifacts.rules ?? '',
144
+ behaviors: definition.paths.artifacts.behaviors ?? '',
145
+ };
146
+ }
147
+ async function installedEntries(options) {
148
+ if (options.scope === 'user' && options.provider !== 'openhuman') {
149
+ return (await readUserRegistry()).installed;
150
+ }
151
+ return (await readAiwgConfig(options.projectRoot))?.installed ?? {};
152
+ }
153
+ function exactUserEntries(installed, bundles, provider, type) {
154
+ const entries = new Set();
155
+ for (const bundle of bundles) {
156
+ const deployment = installed[bundle]?.deployedTo[provider];
157
+ for (const name of deployment?.entries?.[type] ?? []) {
158
+ if (name === path.basename(name) && name !== '.' && name !== '..')
159
+ entries.add(name);
160
+ }
161
+ }
162
+ return [...entries].sort();
163
+ }
164
+ async function sourceBundleFiles(root) {
165
+ const files = [];
166
+ const walk = async (directory) => {
167
+ const entries = await readdir(directory, { withFileTypes: true });
168
+ for (const entry of entries) {
169
+ const candidate = path.join(directory, entry.name);
170
+ if (entry.isSymbolicLink())
171
+ throw new Error(`canonical source bundle contains a symbolic link: ${candidate}`);
172
+ if (entry.isDirectory())
173
+ await walk(candidate);
174
+ else if (entry.isFile())
175
+ files.push(candidate);
176
+ else
177
+ throw new Error(`canonical source bundle contains a non-regular entry: ${candidate}`);
178
+ }
179
+ };
180
+ await walk(root);
181
+ const inventory = await Promise.all(files.map(async (absolutePath) => {
182
+ const content = await readFile(absolutePath);
183
+ return {
184
+ path: path.relative(root, absolutePath).split(path.sep).join('/'),
185
+ sha256: sha256(content),
186
+ bytes: content.byteLength,
187
+ absolutePath,
188
+ };
189
+ }));
190
+ return inventory.sort((left, right) => left.path.localeCompare(right.path));
191
+ }
192
+ async function resolveSourceBundle(options, bundle, entry) {
193
+ const candidates = [];
194
+ if (entry?.source === 'project-local' && entry.localPath) {
195
+ candidates.push(path.resolve(options.projectRoot, entry.localPath, 'manifest.json'));
196
+ }
197
+ const frameworkDir = FRAMEWORK_BUNDLE_DIRS[bundle] ?? bundle;
198
+ candidates.push(path.join(options.frameworkRoot, 'agentic', 'code', 'frameworks', frameworkDir, 'manifest.json'), path.join(options.frameworkRoot, 'agentic', 'code', 'addons', bundle, 'manifest.json'), path.join(options.frameworkRoot, 'agentic', 'code', 'extensions', bundle, 'manifest.json'));
199
+ for (const candidate of candidates) {
200
+ try {
201
+ await access(candidate);
202
+ const root = path.dirname(candidate);
203
+ const files = await sourceBundleFiles(root);
204
+ if (files.length === 0)
205
+ continue;
206
+ return {
207
+ digest: sha256(stable(files.map(({ path: file, sha256: digest, bytes }) => ({ path: file, sha256: digest, bytes })))),
208
+ root,
209
+ files,
210
+ canonicalBytesAvailable: true,
211
+ };
212
+ }
213
+ catch {
214
+ // Continue through the canonical source locations.
215
+ }
216
+ }
217
+ const recorded = entry?.manifestHash?.replace(/^sha256:/, '');
218
+ return recorded && /^[a-f0-9]{64}$/.test(recorded)
219
+ ? { digest: recorded, root: '', files: [], canonicalBytesAvailable: false }
220
+ : null;
221
+ }
222
+ function receiptBundles(installed, options) {
223
+ const deployed = Object.keys(installed)
224
+ .filter(bundle => Boolean(installed[bundle]?.deployedTo[options.provider]))
225
+ .sort();
226
+ return deployed.length > 0 ? deployed : [...new Set(options.requestedBundles)].sort();
227
+ }
228
+ /**
229
+ * Convert an already signature-verified web release into the stable verifier
230
+ * result contract for the complete canonical bundle consumed by deployment.
231
+ * Every local file must match its signed descriptor; a registry's self-recorded
232
+ * manifestHash is never an authentication boundary.
233
+ */
234
+ export async function sourceVerificationsFromSignedWebRelease(options, release) {
235
+ const installed = await installedEntries(options);
236
+ const results = {};
237
+ for (const bundle of receiptBundles(installed, options)) {
238
+ const entry = installed[bundle];
239
+ if (entry?.source === 'project-local')
240
+ continue;
241
+ const source = await resolveSourceBundle(options, bundle, entry);
242
+ if (!source?.canonicalBytesAvailable)
243
+ continue;
244
+ let matched = true;
245
+ for (const file of source.files) {
246
+ const relative = path.relative(options.frameworkRoot, file.absolutePath).split(path.sep).join('/');
247
+ if (!relative || relative === '..' || relative.startsWith('../')) {
248
+ matched = false;
249
+ break;
250
+ }
251
+ const descriptor = release.descriptors.get(`raw/${relative}`);
252
+ if (!descriptor || descriptor.sha256 !== file.sha256 || descriptor.size !== file.bytes) {
253
+ matched = false;
254
+ break;
255
+ }
256
+ }
257
+ if (!matched)
258
+ continue;
259
+ const artifactName = `aiwg:bundle-inventory:${bundle}`;
260
+ results[bundle] = {
261
+ schemaVersion: 'aiwg.verify.result.v1',
262
+ status: 'verified',
263
+ exitCode: 0,
264
+ artifact: { name: artifactName, sha256: source.digest },
265
+ policy: 'aiwg-signed-web-release',
266
+ identities: ['aiwg-release-publisher'],
267
+ ...(release.channelSequence === undefined ? {} : {
268
+ freshness: {
269
+ namespace: 'aiwg',
270
+ channel: release.selector,
271
+ sequence: release.channelSequence,
272
+ version: release.version,
273
+ },
274
+ }),
275
+ diagnostics: [{
276
+ code: 'SIGNED_RELEASE_DESCRIPTOR',
277
+ message: `Exact canonical source bytes match signed release manifest ${release.manifestDigest}`,
278
+ }],
279
+ };
280
+ }
281
+ return results;
282
+ }
283
+ async function sourceEvidence(options, installed) {
284
+ const bundles = [...new Set(options.requestedBundles)].sort();
285
+ const materials = [];
286
+ let authenticated = Boolean(options.sourceVerifications);
287
+ for (const bundle of bundles) {
288
+ const entry = installed[bundle];
289
+ const source = await resolveSourceBundle(options, bundle, entry);
290
+ if (!source) {
291
+ return {
292
+ subject: `aiwg:deployment:${sha256(bundles.join('\0')).slice(0, 24)}`,
293
+ sha256: sha256(stable(materials)),
294
+ verification: 'failed',
295
+ };
296
+ }
297
+ const verification = options.sourceVerifications?.[bundle];
298
+ if (!source.canonicalBytesAvailable
299
+ || verification?.status !== 'verified'
300
+ || verification.artifact.sha256 !== source.digest) {
301
+ authenticated = false;
302
+ }
303
+ materials.push({
304
+ bundle,
305
+ digest: source.digest,
306
+ source: entry?.source ?? 'unknown',
307
+ version: entry?.version ?? 'unknown',
308
+ });
309
+ }
310
+ const digest = sha256(stable(materials));
311
+ return {
312
+ subject: `aiwg:deployment:${digest.slice(0, 24)}`,
313
+ sha256: digest,
314
+ verification: options.sourceVerifications
315
+ ? authenticated ? 'verified' : 'failed'
316
+ : 'policy-exempt',
317
+ };
318
+ }
319
+ function transformerEvidence(provider, scope) {
320
+ const definition = getProviderDefinition(provider);
321
+ const adapterContract = definition
322
+ ? {
323
+ adapters: definition.adapters,
324
+ artifacts: definition.paths.artifacts,
325
+ kernelSkills: definition.paths.kernelSkills,
326
+ scope,
327
+ userArtifacts: scope === 'user' ? USER_SCOPE_PATHS[provider] ?? null : null,
328
+ }
329
+ : { provider, scope };
330
+ return {
331
+ id: TRANSFORMER_ID,
332
+ version: TRANSFORMER_VERSION,
333
+ providerAdapter: provider,
334
+ providerAdapterVersion: sha256(stable(adapterContract)),
335
+ };
336
+ }
337
+ export async function resolveProviderReceiptRuntimeEvidence(rawOptions) {
338
+ const provider = normalizeProviderDefinitionId(rawOptions.provider) ?? rawOptions.provider;
339
+ const options = { ...rawOptions, provider };
340
+ const installed = await installedEntries(options);
341
+ const deployedBundles = receiptBundles(installed, options);
342
+ const evidenceOptions = deployedBundles.length > 0
343
+ ? { ...options, requestedBundles: deployedBundles }
344
+ : options;
345
+ const paths = artifactPaths(provider, options.scope);
346
+ const outputRoot = path.resolve(options.outputRoot ?? (options.scope === 'user' ? homedir() : options.projectRoot));
347
+ const ownedFiles = new Set();
348
+ if (paths) {
349
+ for (const type of ['agents', 'commands', 'skills', 'rules', 'behaviors']) {
350
+ const configured = paths[type];
351
+ if (!configured)
352
+ continue;
353
+ const directory = resolveProviderPathValue(configured, outputRoot);
354
+ const entries = options.scope === 'user'
355
+ ? exactUserEntries(installed, evidenceOptions.requestedBundles, provider, type)
356
+ : [];
357
+ await collectOwnedDirectory(directory, entries, ownedFiles);
358
+ }
359
+ if (options.scope === 'project') {
360
+ const kernel = resolveProviderPathValue(getProviderKernelSkillPath(provider), outputRoot);
361
+ if (kernel)
362
+ await collectOwnedDirectory(kernel, [], ownedFiles);
363
+ }
364
+ }
365
+ const outputPaths = [...ownedFiles]
366
+ .map(file => portablePath(outputRoot, file))
367
+ .filter((file) => Boolean(file))
368
+ .sort();
369
+ return {
370
+ provider,
371
+ outputRoot,
372
+ outputPaths,
373
+ source: await sourceEvidence(evidenceOptions, installed),
374
+ transformer: transformerEvidence(provider, options.scope),
375
+ };
376
+ }
377
+ export async function finalizeProviderTransformationReceipt(options) {
378
+ if (!options.sourceVerifications) {
379
+ return {
380
+ status: 'skipped',
381
+ receiptPath: null,
382
+ outputCount: 0,
383
+ reason: 'authenticated canonical source evidence was not supplied',
384
+ };
385
+ }
386
+ const evidence = await resolveProviderReceiptRuntimeEvidence(options);
387
+ if (evidence.source.verification !== 'verified') {
388
+ return {
389
+ status: 'skipped',
390
+ receiptPath: null,
391
+ outputCount: 0,
392
+ reason: evidence.source.verification === 'failed'
393
+ ? 'canonical source verification failed'
394
+ : 'authenticated canonical source evidence was not supplied',
395
+ };
396
+ }
397
+ if (evidence.outputPaths.length === 0) {
398
+ return { status: 'skipped', receiptPath: null, outputCount: 0, reason: 'no managed provider outputs were found' };
399
+ }
400
+ const verifiedSource = {
401
+ subject: evidence.source.subject,
402
+ sha256: evidence.source.sha256,
403
+ verification: evidence.source.verification,
404
+ };
405
+ const receipt = await createProviderTransformationReceipt({
406
+ projectRoot: options.projectRoot,
407
+ outputRoot: evidence.outputRoot,
408
+ provider: evidence.provider,
409
+ scope: options.scope,
410
+ generatedAt: options.generatedAt,
411
+ source: verifiedSource,
412
+ transformer: evidence.transformer,
413
+ outputPaths: evidence.outputPaths,
414
+ });
415
+ return {
416
+ status: 'written',
417
+ receiptPath: await writeProviderTransformationReceipt(options.projectRoot, receipt),
418
+ outputCount: receipt.outputs.length,
419
+ };
420
+ }
421
+ export async function diagnoseIntegratedProviderTransformationReceipt(options) {
422
+ const provider = normalizeProviderDefinitionId(options.provider) ?? options.provider;
423
+ const receiptPath = providerTransformationReceiptPath(options.projectRoot, provider, options.scope);
424
+ try {
425
+ await access(receiptPath);
426
+ }
427
+ catch {
428
+ return {
429
+ status: 'missing-receipt',
430
+ receiptPath,
431
+ checkedOutputs: 0,
432
+ findings: [{
433
+ kind: 'missing-receipt',
434
+ message: 'No transformation receipt exists for this provider deployment.',
435
+ }],
436
+ };
437
+ }
438
+ const evidence = await resolveProviderReceiptRuntimeEvidence(options);
439
+ return diagnoseProviderTransformationReceipt({
440
+ projectRoot: options.projectRoot,
441
+ outputRoot: evidence.outputRoot,
442
+ provider: evidence.provider,
443
+ scope: options.scope,
444
+ source: evidence.source,
445
+ transformer: evidence.transformer,
446
+ });
447
+ }
448
+ //# sourceMappingURL=transformation-receipt-integration.js.map