@aiwg/cli 2026.8.0 → 2026.8.2

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 (70) hide show
  1. package/README.md +33 -0
  2. package/agentic/code/providers/capability-matrix.yaml +511 -0
  3. package/agentic/code/providers/model-capabilities.v1.json +120 -0
  4. package/agentic/code/providers/model-catalog.v1.json +96 -0
  5. package/agentic/code/providers/model-policy-evaluations.v1.json +50 -0
  6. package/agentic/code/providers/premium-model-allowlist.v1.json +36 -0
  7. package/bin/aiwg.mjs +14 -10
  8. package/dist/src/api/index.d.ts +1 -0
  9. package/dist/src/api/index.js +1 -0
  10. package/dist/src/artifacts/cli.js +2 -0
  11. package/dist/src/artifacts/types.js +4 -0
  12. package/dist/src/auth/client.js +209 -0
  13. package/dist/src/auth/config.js +38 -0
  14. package/dist/src/auth/credential-store.js +141 -0
  15. package/dist/src/auth/resource-credentials.js +25 -0
  16. package/dist/src/auth/types.js +2 -0
  17. package/dist/src/channel/manager.mjs +5 -5
  18. package/dist/src/cli/handlers/auth.js +125 -0
  19. package/dist/src/cli/handlers/help.js +1 -0
  20. package/dist/src/cli/handlers/index.js +3 -1
  21. package/dist/src/cli/handlers/install.js +42 -4
  22. package/dist/src/cli/handlers/marketplace.js +375 -122
  23. package/dist/src/cli/handlers/resource-versions.js +2 -0
  24. package/dist/src/cli/handlers/sessions.js +23 -5
  25. package/dist/src/cli/handlers/subcommands.js +10 -1
  26. package/dist/src/cli/handlers/use.js +342 -43
  27. package/dist/src/config/gitignore.js +1 -0
  28. package/dist/src/extensions/commands/definitions.js +19 -0
  29. package/dist/src/marketplace/exchange.js +602 -0
  30. package/dist/src/marketplace/provenance-types.js +19 -0
  31. package/dist/src/marketplace/provenance.js +834 -0
  32. package/dist/src/memory/canonical-context.js +342 -0
  33. package/dist/src/memory/context-pack.js +282 -0
  34. package/dist/src/memory/index.js +4 -0
  35. package/dist/src/memory/intake.js +118 -0
  36. package/dist/src/packages/adapters/git.js +79 -29
  37. package/dist/src/packages/package-discovery.js +81 -0
  38. package/dist/src/packages/package-registry.js +2 -0
  39. package/dist/src/packages/registry.js +119 -20
  40. package/dist/src/resources/resolver.js +1 -0
  41. package/dist/src/resources/web-release.d.ts +3 -1
  42. package/dist/src/resources/web-release.js +14 -6
  43. package/dist/src/serve/agentic-sandbox-fleet-client.js +213 -0
  44. package/dist/src/serve/fleet-mission-conductor.js +293 -0
  45. package/dist/src/sessions/index.js +1 -0
  46. package/dist/src/sessions/output-registration.js +338 -0
  47. package/dist/src/sessions/promotion.js +73 -2
  48. package/dist/src/sessions/repository.js +2 -1
  49. package/dist/src/update/notifier.mjs +13 -2
  50. package/package.json +8 -1
  51. package/tools/_resolve-impl.mjs +74 -0
  52. package/tools/agents/deploy-agents.mjs +962 -0
  53. package/tools/agents/providers/base.mjs +2954 -0
  54. package/tools/agents/providers/claude.mjs +711 -0
  55. package/tools/agents/providers/codex.mjs +699 -0
  56. package/tools/agents/providers/copilot.mjs +659 -0
  57. package/tools/agents/providers/cursor.mjs +714 -0
  58. package/tools/agents/providers/factory.mjs +1130 -0
  59. package/tools/agents/providers/hermes.mjs +663 -0
  60. package/tools/agents/providers/hook-capabilities.mjs +85 -0
  61. package/tools/agents/providers/model-role.mjs +56 -0
  62. package/tools/agents/providers/openclaw-translator.mjs +348 -0
  63. package/tools/agents/providers/openclaw.mjs +680 -0
  64. package/tools/agents/providers/opencode.mjs +675 -0
  65. package/tools/agents/providers/openhuman.mjs +292 -0
  66. package/tools/agents/providers/warp.mjs +413 -0
  67. package/tools/agents/providers/windsurf.mjs +748 -0
  68. package/tools/commands/deploy-prompts-codex.mjs +336 -0
  69. package/tools/plugin/package-plugins.mjs +1013 -0
  70. package/tools/skills/deploy-skills-codex.mjs +571 -0
@@ -0,0 +1,118 @@
1
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, writeFileSync, } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import { basename, dirname, extname, relative, resolve, sep } from 'node:path';
4
+ function sha256(value) {
5
+ return `sha256:${createHash('sha256').update(value).digest('hex')}`;
6
+ }
7
+ function safeProjectFile(projectRoot, requested) {
8
+ const root = realpathSync(projectRoot);
9
+ const candidate = realpathSync(resolve(root, requested));
10
+ if (candidate !== root && !candidate.startsWith(`${root}${sep}`)) {
11
+ throw new Error('intake source must resolve inside the project');
12
+ }
13
+ const segments = relative(root, candidate).split(sep);
14
+ if (segments.some(segment => segment === '.env' || segment === '.ssh'
15
+ || /^(?:credentials?|secrets?|tokens?)(?:\.|$)/i.test(segment))) {
16
+ throw new Error('protected paths cannot be ingested into ordinary project memory');
17
+ }
18
+ return { absolute: candidate, locator: segments.join('/') };
19
+ }
20
+ function kindFor(filePath) {
21
+ const extension = extname(filePath).toLocaleLowerCase();
22
+ if (['.jsonl', '.transcript'].includes(extension))
23
+ return 'session-transcript';
24
+ if (['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'].includes(extension))
25
+ return 'image';
26
+ if (extension === '.pdf')
27
+ return 'pdf';
28
+ if (['.yaml', '.yml', '.json'].includes(extension))
29
+ return 'structured';
30
+ if (['.md', '.txt', '.html', '.htm'].includes(extension))
31
+ return 'document';
32
+ return 'artifact';
33
+ }
34
+ function assertFuturePathInsideProject(projectRoot, target) {
35
+ let ancestor = target;
36
+ while (!existsSync(ancestor))
37
+ ancestor = dirname(ancestor);
38
+ const actual = realpathSync(ancestor);
39
+ if (actual !== projectRoot && !actual.startsWith(`${projectRoot}${sep}`)) {
40
+ throw new Error('intake storage cannot traverse a link outside the project');
41
+ }
42
+ }
43
+ function atomicJson(filePath, value) {
44
+ mkdirSync(dirname(filePath), { recursive: true });
45
+ const temporary = `${filePath}.${process.pid}.${Date.now()}.tmp`;
46
+ writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
47
+ renameSync(temporary, filePath);
48
+ }
49
+ export class MemoryIntakeCoordinator {
50
+ projectRoot;
51
+ rawRoot;
52
+ receiptRoot;
53
+ constructor(projectRoot) {
54
+ this.projectRoot = realpathSync(projectRoot);
55
+ this.rawRoot = resolve(this.projectRoot, '.aiwg/wiki/raw');
56
+ this.receiptRoot = resolve(this.projectRoot, '.aiwg/memory/compound-memory/intake-receipts');
57
+ assertFuturePathInsideProject(this.projectRoot, this.rawRoot);
58
+ assertFuturePathInsideProject(this.projectRoot, this.receiptRoot);
59
+ }
60
+ preview(requested) {
61
+ const source = safeProjectFile(this.projectRoot, requested);
62
+ const stat = statSync(source.absolute);
63
+ if (!stat.isFile())
64
+ throw new Error('intake source must be a regular file');
65
+ const bytes = readFileSync(source.absolute);
66
+ const digest = sha256(bytes);
67
+ const kind = kindFor(source.absolute);
68
+ const safeName = basename(source.absolute).replace(/[^a-zA-Z0-9._-]+/g, '-');
69
+ const rawLocator = `.aiwg/wiki/raw/${digest.slice(7, 23)}-${safeName}`;
70
+ const rawPath = resolve(this.projectRoot, rawLocator);
71
+ const duplicate = existsSync(rawPath) && sha256(readFileSync(rawPath)) === digest;
72
+ const identity = {
73
+ source: { locator: source.locator, digest, byteLength: bytes.length, kind },
74
+ rawLocator,
75
+ route: kind === 'session-transcript' ? 'sessions' : 'llm-wiki',
76
+ };
77
+ return {
78
+ schemaVersion: 'aiwg.compound-memory.intake-preview.v1',
79
+ operationId: sha256(JSON.stringify(identity)),
80
+ ...identity,
81
+ duplicate,
82
+ confirmationRequired: true,
83
+ mutation: { wouldCopyRaw: !duplicate, wouldPromoteKnowledge: false },
84
+ };
85
+ }
86
+ confirm(requested, operationId) {
87
+ const preview = this.preview(requested);
88
+ const receiptPath = resolve(this.receiptRoot, `${operationId.replace(':', '_')}.json`);
89
+ if (existsSync(receiptPath)) {
90
+ return { ...JSON.parse(readFileSync(receiptPath, 'utf8')), duplicate: true };
91
+ }
92
+ if (preview.operationId !== operationId) {
93
+ throw new Error('intake confirmation requires the exact current preview');
94
+ }
95
+ const source = safeProjectFile(this.projectRoot, requested);
96
+ const rawPath = resolve(this.projectRoot, preview.rawLocator);
97
+ mkdirSync(this.rawRoot, { recursive: true, mode: 0o700 });
98
+ if (!preview.duplicate)
99
+ copyFileSync(source.absolute, rawPath, 1);
100
+ if (sha256(readFileSync(rawPath)) !== preview.source.digest) {
101
+ throw new Error('immutable raw copy digest does not match the source preview');
102
+ }
103
+ const receipt = {
104
+ schemaVersion: 'aiwg.compound-memory.intake-receipt.v1',
105
+ receiptId: sha256(`${operationId}\0${preview.rawLocator}`),
106
+ operationId,
107
+ sourceLocator: preview.source.locator,
108
+ sourceDigest: preview.source.digest,
109
+ rawLocator: preview.rawLocator,
110
+ route: preview.route,
111
+ duplicate: preview.duplicate,
112
+ registeredAt: new Date().toISOString(),
113
+ };
114
+ atomicJson(receiptPath, receipt);
115
+ return receipt;
116
+ }
117
+ }
118
+ //# sourceMappingURL=intake.js.map
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import { execFile } from 'child_process';
13
13
  import { promisify } from 'util';
14
- import { mkdir, readFile } from 'fs/promises';
14
+ import { mkdir, mkdtemp, readFile, rename, rm } from 'fs/promises';
15
15
  import { join } from 'path';
16
16
  import { homedir } from 'os';
17
17
  import { existsSync } from 'fs';
@@ -82,6 +82,41 @@ async function resolveLatestTag(gitUrl) {
82
82
  }
83
83
  return 'latest';
84
84
  }
85
+ async function resolveRemoteCommit(gitUrl, requestedRef) {
86
+ if (/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(requestedRef))
87
+ return requestedRef;
88
+ const patterns = requestedRef === 'HEAD'
89
+ ? ['HEAD']
90
+ : [`refs/tags/${requestedRef}^{}`, `refs/tags/${requestedRef}`, `refs/heads/${requestedRef}`, requestedRef];
91
+ try {
92
+ const output = await git(['ls-remote', gitUrl, ...patterns]);
93
+ const rows = output.split('\n').filter(Boolean).map((line) => {
94
+ const [oid = '', ref = ''] = line.split(/\s+/, 2);
95
+ return { oid, ref };
96
+ });
97
+ const peeled = rows.find((row) => row.ref.endsWith('^{}'));
98
+ const selected = peeled ?? rows[0];
99
+ return selected && /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(selected.oid)
100
+ ? selected.oid
101
+ : undefined;
102
+ }
103
+ catch {
104
+ return undefined;
105
+ }
106
+ }
107
+ function urlCacheIdentity(gitUrl) {
108
+ const urlKey = gitUrl
109
+ .replace(/^https?:\/\//, '')
110
+ .replace(/^ssh:\/\//, '')
111
+ .replace(/^git@/, '')
112
+ .replace(/\.git$/, '')
113
+ .replace(/[:/]/g, '_');
114
+ const parts = urlKey.split('_').filter(Boolean);
115
+ return {
116
+ name: parts[parts.length - 1] ?? 'package',
117
+ owner: parts[parts.length - 2] ?? 'unknown',
118
+ };
119
+ }
85
120
  /**
86
121
  * GitAdapter
87
122
  *
@@ -111,38 +146,53 @@ export class GitAdapter {
111
146
  };
112
147
  }
113
148
  async fetch(source, options = {}) {
114
- // Determine version
115
- let version = source.ref;
116
- if (!version) {
117
- version = await resolveLatestTag(source.gitUrl);
118
- }
119
- // Build cache key from URL
120
- const urlKey = source.gitUrl
121
- .replace(/^https?:\/\//, '')
122
- .replace(/^git@/, '')
123
- .replace(/\.git$/, '')
124
- .replace(/[:/]/g, '_');
125
- const parts = urlKey.split('_');
126
- const name = parts[parts.length - 1] ?? 'package';
127
- const owner = parts[parts.length - 2] ?? 'unknown';
128
- const cachePath = buildCachePath(owner, name, version);
129
- if (!options.refresh && existsSync(cachePath)) {
130
- return cachePath;
149
+ // Resolve discovery inputs before any deployment and cache by immutable
150
+ // commit rather than by a movable tag/branch name (#2009).
151
+ let requestedRef = source.ref;
152
+ if (!requestedRef) {
153
+ const latest = await resolveLatestTag(source.gitUrl);
154
+ requestedRef = latest === 'latest' ? 'HEAD' : latest;
155
+ source.ref = requestedRef;
131
156
  }
132
- await mkdir(cachePath, { recursive: true });
133
- if (existsSync(join(cachePath, '.git'))) {
134
- // Update existing clone
135
- await git(['fetch', '--tags', '--prune'], cachePath);
157
+ const advertisedCommit = await resolveRemoteCommit(source.gitUrl, requestedRef);
158
+ const { owner, name } = urlCacheIdentity(source.gitUrl);
159
+ if (advertisedCommit) {
160
+ const existing = buildCachePath(owner, name, advertisedCommit);
161
+ if (!options.refresh && existsSync(join(existing, '.git'))) {
162
+ const existingCommit = await git(['rev-parse', 'HEAD^{commit}'], existing);
163
+ if (existingCommit !== advertisedCommit)
164
+ throw new Error(`Immutable package cache mismatch at ${existing}`);
165
+ return existing;
166
+ }
136
167
  }
137
- else {
138
- // Fresh clone (no --depth to get tags)
139
- await git(['clone', source.gitUrl, cachePath]);
168
+ const ownerDir = join(getCacheRoot(), owner);
169
+ await mkdir(ownerDir, { recursive: true });
170
+ const stage = await mkdtemp(join(ownerDir, `.${name}.resolve-`));
171
+ try {
172
+ await git(['clone', '--no-checkout', source.gitUrl, stage]);
173
+ await git(['checkout', '--detach', requestedRef], stage);
174
+ const resolvedCommit = await git(['rev-parse', 'HEAD^{commit}'], stage);
175
+ if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(resolvedCommit)) {
176
+ throw new Error(`Git resolved '${requestedRef}' to invalid commit '${resolvedCommit}'`);
177
+ }
178
+ if (advertisedCommit && resolvedCommit !== advertisedCommit) {
179
+ throw new Error(`Git ref '${requestedRef}' moved during resolution (${advertisedCommit} -> ${resolvedCommit})`);
180
+ }
181
+ const cachePath = buildCachePath(owner, name, resolvedCommit);
182
+ if (existsSync(join(cachePath, '.git'))) {
183
+ const cachedCommit = await git(['rev-parse', 'HEAD^{commit}'], cachePath);
184
+ if (cachedCommit !== resolvedCommit)
185
+ throw new Error(`Immutable package cache mismatch at ${cachePath}`);
186
+ await rm(stage, { recursive: true, force: true });
187
+ return cachePath;
188
+ }
189
+ await rename(stage, cachePath);
190
+ return cachePath;
140
191
  }
141
- // Checkout requested ref
142
- if (version && version !== 'latest') {
143
- await git(['checkout', version], cachePath);
192
+ catch (error) {
193
+ await rm(stage, { recursive: true, force: true });
194
+ throw error;
144
195
  }
145
- return cachePath;
146
196
  }
147
197
  /** GitAdapter does not list packages */
148
198
  async list() {
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Resolve a fetched Git checkout to one deployable package without executing it.
3
+ * Supports root bundles and the documented standalone .aiwg/plugins layout.
4
+ *
5
+ * @implements #1997
6
+ * @implements #2009
7
+ */
8
+ import { readFile } from 'node:fs/promises';
9
+ import path from 'node:path';
10
+ import { discoverProjectLocalBundles, loadAndValidateManifest } from '../extensions/project-local-discovery.js';
11
+ const TYPES = new Set(['framework', 'addon', 'extension', 'plugin', 'provider']);
12
+ function formatErrors(errors) {
13
+ return errors.map((error) => `${error.field}: expected ${error.expected}, got ${error.actual}${error.hint ? `; ${error.hint}` : ''}`).join('\n - ');
14
+ }
15
+ async function rootManifest(checkoutPath) {
16
+ try {
17
+ const value = JSON.parse(await readFile(path.join(checkoutPath, 'manifest.json'), 'utf8'));
18
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
19
+ }
20
+ catch {
21
+ return undefined;
22
+ }
23
+ }
24
+ export async function discoverInstallablePackage(checkoutPath, selector) {
25
+ const checkout = path.resolve(checkoutPath);
26
+ const root = await rootManifest(checkout);
27
+ if (root) {
28
+ const declaredType = String(root.type ?? '');
29
+ if (root.manifestVersion === '1' && TYPES.has(declaredType)) {
30
+ const validation = await loadAndValidateManifest(path.join(checkout, 'manifest.json'), declaredType, checkout);
31
+ if (!validation.bundle) {
32
+ throw new Error(`Root package manifest validation failed:\n - ${formatErrors(validation.errors)}`);
33
+ }
34
+ if (selector && selector !== validation.bundle.id) {
35
+ throw new Error(`Package selector '${selector}' does not match root package '${validation.bundle.id}'`);
36
+ }
37
+ return {
38
+ manifest: validation.bundle.manifest,
39
+ type: validation.bundle.type === 'provider' ? 'unknown' : validation.bundle.type,
40
+ wrapperPath: checkout,
41
+ artifactPath: validation.bundle.artifactPath,
42
+ };
43
+ }
44
+ // Legacy root manifests remain installable for compatibility, but unknown
45
+ // roots no longer report a successful zero-artifact deployment.
46
+ if (['framework', 'addon', 'extension'].includes(String(root.type))) {
47
+ const name = String(root.id ?? root.name ?? '');
48
+ if (selector && selector !== name)
49
+ throw new Error(`Package selector '${selector}' does not match root package '${name}'`);
50
+ return {
51
+ manifest: root,
52
+ type: root.type,
53
+ wrapperPath: checkout,
54
+ artifactPath: checkout,
55
+ };
56
+ }
57
+ }
58
+ const discovery = await discoverProjectLocalBundles(checkout);
59
+ if (discovery.errors.length) {
60
+ throw new Error(`Standalone package discovery failed:\n - ${formatErrors(discovery.errors)}`);
61
+ }
62
+ let candidates = discovery.bundles.filter((bundle) => bundle.type === 'plugin');
63
+ if (selector)
64
+ candidates = candidates.filter((bundle) => bundle.id === selector);
65
+ if (candidates.length === 0) {
66
+ throw new Error(selector
67
+ ? `No valid standalone plugin '${selector}' exists in this Git repository`
68
+ : 'Git repository contains no valid root package or standalone .aiwg/plugins wrapper');
69
+ }
70
+ if (candidates.length > 1) {
71
+ throw new Error(`Git repository contains multiple standalone plugins (${candidates.map((bundle) => bundle.id).sort().join(', ')}); select one with --package <id>`);
72
+ }
73
+ const selected = candidates[0];
74
+ return {
75
+ manifest: selected.manifest,
76
+ type: 'plugin',
77
+ wrapperPath: selected.bundlePath,
78
+ artifactPath: selected.artifactPath,
79
+ };
80
+ }
81
+ //# sourceMappingURL=package-discovery.js.map
@@ -137,6 +137,8 @@ export async function listPackages(configDir) {
137
137
  source: entry.source,
138
138
  installedAt: entry.installedAt,
139
139
  deployCount: entry.deployedTo?.length ?? 0,
140
+ ...(entry.provenance?.lockId ? { lockId: entry.provenance.lockId } : {}),
141
+ ...(entry.provenance?.verificationStatus ? { verificationStatus: entry.provenance.verificationStatus } : {}),
140
142
  };
141
143
  });
142
144
  }
@@ -7,13 +7,17 @@
7
7
  * @implements #557
8
8
  */
9
9
  import { readFile } from 'fs/promises';
10
- import { join } from 'path';
11
- import { GitAdapter, detectManifestType } from './adapters/git.js';
10
+ import { existsSync } from 'fs';
11
+ import { join, relative, sep } from 'path';
12
+ import { GitAdapter } from './adapters/git.js';
12
13
  import { GiteaAdapter } from './adapters/gitea.js';
13
14
  import { GitHubAdapter } from './adapters/github.js';
14
15
  import { ClawHubPackageAdapter } from './adapters/clawhub.js';
15
16
  import { LocalCacheAdapter } from './adapters/local-cache.js';
16
- import { setPackageEntry, listPackages as listFromRegistry, removePackageEntry, } from './package-registry.js';
17
+ import { getPackageEntry, setPackageEntry, listPackages as listFromRegistry, removePackageEntry, } from './package-registry.js';
18
+ import { discoverInstallablePackage } from './package-discovery.js';
19
+ import { buildFortemiEnvelopeShard, canonicalJson, createOperationReceipt, createProvenanceEnvelope, sha256, validateProvenanceEnvelope, verifyProvenanceEnvelope, } from '../marketplace/provenance.js';
20
+ import { findIndexedPackage, readMarketplaceIndex, readTrustStore, recordInstalledPackage, } from '../marketplace/exchange.js';
17
21
  /**
18
22
  * All adapters in resolution priority order
19
23
  * (Scheme-prefixed adapters first so explicit prefixes are matched before
@@ -26,6 +30,9 @@ const ALL_ADAPTERS = [
26
30
  new GitAdapter(),
27
31
  ];
28
32
  const CACHE_ADAPTER = new LocalCacheAdapter();
33
+ function pathSeparatorSafe(parent, child) {
34
+ return relative(parent, child).replaceAll(sep, '/') || '.';
35
+ }
29
36
  /**
30
37
  * Parse a raw reference string into a PackageRef
31
38
  *
@@ -176,26 +183,118 @@ export async function installPackage(rawRef, options = {}) {
176
183
  ` git@host:owner/name.git (SSH URL)`);
177
184
  }
178
185
  const { source, adapter } = resolved;
186
+ if (options.ref)
187
+ source.ref = options.ref;
179
188
  const cachePath = await adapter.fetch(source, { refresh: options.refresh });
180
- // Detect type from manifest.json
181
- const type = await detectManifestType(cachePath);
182
- // Build registry key
183
- const key = ref.owner && ref.name
184
- ? `${ref.owner}/${ref.name}`
185
- : source.label.replace(/https?:\/\/[^/]+\//, '').replace(/\.git$/, '');
186
- const version = ref.version ?? source.ref ?? 'latest';
187
- // Resolve namespace for artifact deployment isolation (#804)
188
- const namespace = await readPackageNamespace(cachePath, rawRef);
189
- // Register in packages.yaml
190
- await setPackageEntry(key, {
191
- version,
192
- source: source.gitUrl,
193
- type,
189
+ const discovered = await discoverInstallablePackage(cachePath, options.packageSelector);
190
+ const standardEnvelopePaths = [
191
+ join(cachePath, '.aiwg', 'marketplace', 'envelope.json'),
192
+ join(cachePath, 'aiwg-marketplace-envelope.json'),
193
+ ];
194
+ let envelope = options.expectedEnvelope;
195
+ if (!envelope) {
196
+ for (const filename of standardEnvelopePaths) {
197
+ if (!existsSync(filename))
198
+ continue;
199
+ const value = JSON.parse(await readFile(filename, 'utf8'));
200
+ validateProvenanceEnvelope(value);
201
+ envelope = value;
202
+ break;
203
+ }
204
+ }
205
+ if (!envelope) {
206
+ envelope = await createProvenanceEnvelope({
207
+ checkoutPath: cachePath,
208
+ artifactPath: discovered.artifactPath,
209
+ wrapperPath: pathSeparatorSafe(cachePath, discovered.wrapperPath),
210
+ manifest: discovered.manifest,
211
+ requestedRef: source.ref ?? ref.version ?? 'HEAD',
212
+ remote: source.gitUrl,
213
+ publisher: String(discovered.manifest.author ?? discovered.manifest.namespace ?? ref.owner ?? 'unverified'),
214
+ });
215
+ }
216
+ const key = `${envelope.package.namespace}/${envelope.package.name}`;
217
+ const namespace = envelope.package.namespace;
218
+ const type = discovered.type;
219
+ const existingEntry = await getPackageEntry(key, options.configDir);
220
+ let previousLock;
221
+ if (existingEntry?.provenance?.lockId) {
222
+ previousLock = (await findIndexedPackage(existingEntry.provenance.lockId, { configDir: options.configDir }))?.lock;
223
+ }
224
+ const localIndex = await readMarketplaceIndex({ configDir: options.configDir });
225
+ const installedLocks = Object.fromEntries(Object.values(localIndex.packages).map((entry) => [entry.lock.identity, entry.lock.lockId]));
226
+ const trustStore = options.trustStore ?? await readTrustStore({ configDir: options.configDir });
227
+ const verification = await verifyProvenanceEnvelope({
228
+ envelope,
229
+ contentRoot: discovered.artifactPath,
230
+ checkoutPath: cachePath,
231
+ trustStore,
232
+ policy: options.verify
233
+ ? { requireSignature: true, allowIntegrityOnly: false, ...options.verificationPolicy }
234
+ : options.verificationPolicy,
235
+ installedLocks,
236
+ previousLock,
237
+ });
238
+ if (!verification.ok)
239
+ throw new Error(`Package verification failed: ${verification.errors.join('; ')}`);
240
+ if (options.expectedLockId && verification.lock.lockId !== options.expectedLockId) {
241
+ throw new Error(`Catalog/direct lock mismatch: expected ${options.expectedLockId}, got ${verification.lock.lockId}`);
242
+ }
243
+ if (options.expectedEnvelope && sha256(canonicalJson(options.expectedEnvelope)) !== verification.envelopeSha256) {
244
+ throw new Error('Catalog envelope changed before installation');
245
+ }
246
+ const fortemi = await buildFortemiEnvelopeShard(envelope);
247
+ const receipt = createOperationReceipt({
248
+ operation: 'install',
249
+ lock: verification.lock,
250
+ actor: options.actor ?? 'aiwg',
251
+ verificationStatus: verification.status,
252
+ evidence: {
253
+ source: source.gitUrl,
254
+ requestedRef: envelope.source.requestedRef,
255
+ resolvedCommit: envelope.source.resolvedCommit,
256
+ catalog: options.catalogId ?? null,
257
+ directGit: !options.catalogId,
258
+ },
259
+ conformance: fortemi.conformance,
260
+ });
261
+ await recordInstalledPackage({
262
+ configDir: options.configDir,
263
+ envelope,
264
+ lock: verification.lock,
265
+ receipt,
194
266
  cachePath,
195
- installedAt: new Date().toISOString(),
196
- deployedTo: [],
267
+ artifactPath: discovered.artifactPath,
268
+ verificationStatus: verification.status,
269
+ fortemiShard: fortemi.archive,
270
+ catalogId: options.catalogId,
271
+ });
272
+ await setPackageEntry(key, {
273
+ version: envelope.package.version,
274
+ source: envelope.source.canonicalRemote,
275
+ type: type === 'plugin' ? 'extension' : type,
276
+ cachePath: discovered.artifactPath,
277
+ installedAt: receipt.occurredAt,
278
+ deployedTo: existingEntry?.deployedTo ?? [],
279
+ provenance: {
280
+ lockId: verification.lock.lockId,
281
+ resolvedCommit: verification.lock.resolvedCommit,
282
+ treeSha256: verification.lock.treeSha256,
283
+ artifactSha256: verification.lock.artifactSha256,
284
+ envelopeSha256: verification.lock.envelopeSha256,
285
+ verificationStatus: verification.status,
286
+ },
197
287
  }, options.configDir);
198
- return { cachePath, key, type, namespace };
288
+ return {
289
+ cachePath: discovered.artifactPath,
290
+ checkoutPath: cachePath,
291
+ key,
292
+ type,
293
+ namespace,
294
+ envelope,
295
+ lock: verification.lock,
296
+ verification,
297
+ };
199
298
  }
200
299
  /**
201
300
  * Refresh all registered remote packages (used by `aiwg sync`)
@@ -117,6 +117,7 @@ export async function resolveAiwgResourceBytes(logicalId, options) {
117
117
  const bytes = await fetchVerifiedRawResource(webRelease, parsed.rawPath, {
118
118
  baseUrl: options.webReleaseOptions?.baseUrl,
119
119
  fetcher: options.webReleaseOptions?.fetcher,
120
+ credentialProvider: options.webReleaseOptions?.credentialProvider,
120
121
  allowInsecureLoopbackHttp: options.webReleaseOptions?.allowInsecureLoopbackHttp,
121
122
  offline: options.offline,
122
123
  });
@@ -45,6 +45,8 @@ export interface WebReleaseOptions {
45
45
  cacheRoot?: string;
46
46
  publicKeyPem?: string | Buffer;
47
47
  fetcher?: ResourceFetcher;
48
+ /** Returns a bearer token at request time. Tokens never participate in URLs or cache keys. */
49
+ credentialProvider?: () => Promise<string | null>;
48
50
  /** Test/development escape hatch. HTTP remains restricted to loopback. */
49
51
  allowInsecureLoopbackHttp?: boolean;
50
52
  }
@@ -72,7 +74,7 @@ export interface VerifiedWebRelease {
72
74
  channelSequence?: number;
73
75
  descriptors: ReadonlyMap<string, VerifiedReleaseDescriptor>;
74
76
  }
75
- export interface VerifiedRawResourceOptions extends Pick<WebReleaseOptions, "baseUrl" | "fetcher" | "allowInsecureLoopbackHttp"> {
77
+ export interface VerifiedRawResourceOptions extends Pick<WebReleaseOptions, "baseUrl" | "fetcher" | "credentialProvider" | "allowInsecureLoopbackHttp"> {
76
78
  offline?: boolean;
77
79
  }
78
80
  export declare function verifySignedResourceBytes(bytes: Uint8Array, signatureBytes: Uint8Array, publicKeyPem?: string | Buffer, label?: string): string;
@@ -297,7 +297,7 @@ function resourceUrl(base, relativePath) {
297
297
  url.pathname = `${prefix}/${relativePath}`;
298
298
  return url.toString();
299
299
  }
300
- async function fetchBytes(fetcher, url, label, maxBytes) {
300
+ async function fetchBytes(fetcher, url, label, maxBytes, bearerToken) {
301
301
  if (!Number.isSafeInteger(maxBytes) || maxBytes < 0)
302
302
  throw new Error(`${label} has an invalid network size limit`);
303
303
  const controller = new AbortController();
@@ -315,7 +315,10 @@ async function fetchBytes(fetcher, url, label, maxBytes) {
315
315
  const response = await Promise.race([
316
316
  fetcher(url, {
317
317
  redirect: "error",
318
- headers: { "accept-encoding": "identity" },
318
+ headers: {
319
+ "accept-encoding": "identity",
320
+ ...(bearerToken ? { authorization: `Bearer ${bearerToken}` } : {}),
321
+ },
319
322
  signal: controller.signal,
320
323
  }),
321
324
  timeoutFailure,
@@ -904,16 +907,21 @@ export async function resolveWebRelease(options = {}) {
904
907
  const base = normalizeBaseUrl(options.baseUrl ?? DEFAULT_RESOURCE_BASE_URL, options.allowInsecureLoopbackHttp === true);
905
908
  // Validate injected trust material before reading cache or making requests.
906
909
  loadTrustRoot(publicKeyPem);
910
+ const bearerToken = options.offline ? null : await options.credentialProvider?.() ?? null;
911
+ const authorize = async (input, init = {}) => {
912
+ const headers = { ...(init.headers || {}), ...(bearerToken ? { authorization: `Bearer ${bearerToken}` } : {}) };
913
+ return (options.fetcher ?? globalThis.fetch)(input, { ...init, headers });
914
+ };
907
915
  if (selector.kind === "exact") {
908
916
  if (options.offline)
909
917
  return resolveOfflineExact(cacheRoot, selector, selector.value, publicKeyPem, base);
910
- const fetcher = options.fetcher ?? globalThis.fetch;
918
+ const fetcher = authorize;
911
919
  if (!fetcher)
912
920
  throw new Error("No fetch implementation is available for AIWG web resources");
913
921
  return fetchAndCacheRelease(base, fetcher, cacheRoot, selector, selector.value, publicKeyPem);
914
922
  }
915
923
  if (selector.kind === "range" || selector.kind === "digest") {
916
- const fetcher = options.fetcher ?? globalThis.fetch;
924
+ const fetcher = authorize;
917
925
  const index = await resolveVersionIndex(base, fetcher, cacheRoot, publicKeyPem, options.offline);
918
926
  const selected = selectVersionFromIndex(index, selector);
919
927
  if (options.offline) {
@@ -929,7 +937,7 @@ export async function resolveWebRelease(options = {}) {
929
937
  throw new Error(`AIWG resource channel ${selector.value} is not cached; offline mode cannot fetch it`);
930
938
  return resolveOfflineExact(cacheRoot, selector, cached.manifest.version, publicKeyPem, base, cached.manifest.releaseManifestSha256, cached.manifest.sequence);
931
939
  }
932
- const fetcher = options.fetcher ?? globalThis.fetch;
940
+ const fetcher = authorize;
933
941
  if (!fetcher)
934
942
  throw new Error("No fetch implementation is available for AIWG web resources");
935
943
  const channelPrefix = `resources/channels/${selector.value}`;
@@ -989,7 +997,7 @@ export async function fetchVerifiedRawResource(release, resourcePath, options =
989
997
  const fetcher = options.fetcher ?? globalThis.fetch;
990
998
  if (!fetcher)
991
999
  throw new Error("No fetch implementation is available for AIWG web resources");
992
- const bytes = await fetchBytes(fetcher, resourceUrl(base, `resources/${release.version}/${resourcePath}`), `raw resource ${resourcePath}`, Math.min(descriptor.size, MAX_RAW_RESOURCE_BYTES));
1000
+ const bytes = await fetchBytes(fetcher, resourceUrl(base, `resources/${release.version}/${resourcePath}`), `raw resource ${resourcePath}`, Math.min(descriptor.size, MAX_RAW_RESOURCE_BYTES), await options.credentialProvider?.() ?? null);
993
1001
  verifyDescriptor(bytes, descriptor);
994
1002
  const stagingRoot = path.join(release.cacheDir, ".raw-staging");
995
1003
  fs.mkdirSync(stagingRoot, { recursive: true });