@aiwg/cli 2026.8.1 → 2026.8.3

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.
@@ -20,6 +20,7 @@
20
20
  import path from 'path';
21
21
  import { installPackage } from '../../packages/registry.js';
22
22
  import { recordDeployment } from '../../packages/package-registry.js';
23
+ import { marketplaceConfigDir, resolveVerificationPolicy } from '../../marketplace/exchange.js';
23
24
  import { createScriptRunner } from './script-runner.js';
24
25
  import { handlerResultFromError } from '../errors.js';
25
26
  import * as ui from '../ui.js';
@@ -59,6 +60,12 @@ export const installHandler = {
59
60
  ' --provider <name> Target provider (claude, copilot, cursor...)',
60
61
  ' --target <dir> Project directory to deploy into',
61
62
  ' --refresh Force re-pull even if cached',
63
+ ' --ref <tag-or-sha> Resolve this ref and lock its immutable commit',
64
+ ' --package <id> Select one wrapper when a repository contains several',
65
+ ' --verify Require a publisher signature trusted by local policy',
66
+ ' --policy <name|path> Named trust policy or JSON policy file',
67
+ ' --project-local Store registry, lock, receipts, and index under <target>/.aiwg',
68
+ ' --global Store package state in the user AIWG directory (default)',
62
69
  ].join('\n'),
63
70
  };
64
71
  }
@@ -66,6 +73,15 @@ export const installHandler = {
66
73
  const refresh = hasFlag(ctx.args, '--refresh');
67
74
  const provider = parseFlag(ctx.args, '--provider') ?? 'claude';
68
75
  const target = parseFlag(ctx.args, '--target') ?? ctx.cwd;
76
+ const projectLocal = hasFlag(ctx.args, '--project-local');
77
+ const global = hasFlag(ctx.args, '--global');
78
+ if (projectLocal && global) {
79
+ return { exitCode: 1, message: 'Error: Choose either --project-local or --global, not both' };
80
+ }
81
+ const verify = hasFlag(ctx.args, '--verify');
82
+ const policyName = parseFlag(ctx.args, '--policy');
83
+ const scope = { projectLocal, projectDir: target };
84
+ const configDir = marketplaceConfigDir(scope);
69
85
  ui.blank();
70
86
  console.log(` ${ui.brandMark()} ${ui.bold('aiwg install')} ${ui.dimText(rawRef)}`);
71
87
  ui.rule();
@@ -75,8 +91,23 @@ export const installHandler = {
75
91
  let key;
76
92
  let type;
77
93
  let namespace;
94
+ let lockId;
95
+ let verificationStatus;
78
96
  try {
79
- ({ cachePath, key, type, namespace } = await installPackage(rawRef, { refresh }));
97
+ const resolvedPolicy = await resolveVerificationPolicy(policyName, scope);
98
+ const installed = await installPackage(rawRef, {
99
+ refresh,
100
+ ref: parseFlag(ctx.args, '--ref'),
101
+ packageSelector: parseFlag(ctx.args, '--package'),
102
+ verify,
103
+ verificationPolicy: resolvedPolicy.policy,
104
+ trustStore: resolvedPolicy.trustStore,
105
+ configDir,
106
+ actor: 'local-user',
107
+ });
108
+ ({ cachePath, key, type, namespace } = installed);
109
+ lockId = installed.lock.lockId;
110
+ verificationStatus = installed.verification.status;
80
111
  }
81
112
  catch (error) {
82
113
  // Preserve AiwgError.exitCode while keeping the "Error: " prefix users
@@ -85,9 +116,12 @@ export const installHandler = {
85
116
  return { ...result, message: `Error: ${result.message}` };
86
117
  }
87
118
  ui.success(`Installed: ${key} (${type})`);
88
- ui.dimText(` Cache: ${cachePath}`);
119
+ ui.dim(` Cache: ${cachePath}`);
120
+ ui.dim(` Lock: ${lockId}`);
121
+ ui.dim(` Verification: ${verificationStatus}`);
122
+ ui.dim(` Scope: ${projectLocal ? 'project-local' : 'global'}`);
89
123
  if (namespace !== 'aiwg') {
90
- ui.dimText(` Namespace: ${namespace}`);
124
+ ui.dim(` Namespace: ${namespace}`);
91
125
  }
92
126
  // Optionally deploy
93
127
  if (deploy) {
@@ -98,6 +132,10 @@ export const installHandler = {
98
132
  '--deploy-commands',
99
133
  '--deploy-skills',
100
134
  '--deploy-rules',
135
+ // External packages do not participate in AIWG's global artifact
136
+ // index. Copy their complete skill payload into the target so a
137
+ // successful install cannot silently deploy agents/rules only.
138
+ '--copy-all',
101
139
  '--provider', provider,
102
140
  '--target', target,
103
141
  '--namespace', namespace,
@@ -116,7 +154,7 @@ export const installHandler = {
116
154
  projectPath: target,
117
155
  provider,
118
156
  deployedAt: new Date().toISOString(),
119
- });
157
+ }, configDir);
120
158
  }
121
159
  }
122
160
  ui.blank();
@@ -1,41 +1,39 @@
1
1
  /**
2
- * Marketplace Command Handler
2
+ * Git-native Fortemi marketplace command handler.
3
3
  *
4
- * Implements `aiwg marketplace <subcommand>` for discovering and listing
5
- * packages across all configured marketplace sources (local, ClawHub,
6
- * OpenClaw, etc.).
7
- *
8
- * Subcommands:
9
- * search <query> Fan-out search to all adapters, display with source attribution
10
- * list List installed marketplace packages
11
- *
12
- * Flags:
13
- * --source <id> Limit to a single adapter (e.g. clawhub, openclaw, local)
14
- * --json Emit structured JSON for programmatic use
4
+ * Catalog results are observations, never endorsements. Direct Git and catalog
5
+ * installs share the same immutable lock and local receipt pipeline.
15
6
  *
16
7
  * @implements #805
8
+ * @implements #2009
17
9
  */
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
18
12
  import { searchSkills } from '../../skills/registry.js';
19
- import { listInstalledPackages } from '../../packages/registry.js';
13
+ import { installPackage, listInstalledPackages } from '../../packages/registry.js';
14
+ import { GitAdapter } from '../../packages/adapters/git.js';
15
+ import { inspectGitCheckout } from '../../marketplace/provenance.js';
16
+ import { exportPortablePackage, findIndexedPackage, importPortablePackage, marketplaceConfigDir, publishLocalPackage, readCatalogEnvelope, readTrustStore, registerCatalog, removeCatalog, resolveCatalogEntry, resolveVerificationPolicy, searchCatalogs, verifyIndexedPackage, } from '../../marketplace/exchange.js';
20
17
  import * as ui from '../ui.js';
21
- // ── Usage string ───────────────────────────────────────────────────────────
22
18
  const USAGE = [
23
19
  'Usage:',
24
- ' aiwg marketplace search <query> Search across all marketplace sources',
20
+ ' aiwg marketplace add <catalog-git-url> [--ref <tag-or-sha>]',
21
+ ' aiwg marketplace search <query> Search packages and signed catalogs',
22
+ ' aiwg marketplace info <package> Show provenance and catalog observations',
23
+ ' aiwg marketplace install <url|package> Install and lock direct Git or catalog package',
24
+ ' aiwg marketplace verify <package|lock> Verify cached content and receipts offline',
25
+ ' aiwg marketplace export <package> --output <archive.json>',
26
+ ' aiwg marketplace import <archive.json> [--project-local]',
27
+ ' aiwg marketplace publish <source> --key <pem> --publisher <id>',
28
+ ' aiwg marketplace remove <catalog-id>',
25
29
  ' aiwg marketplace list List installed marketplace packages',
26
30
  '',
27
- 'Flags:',
28
- ' --source <id> Limit search to a specific source (clawhub, openclaw, local)',
29
- ' --json Output structured JSON',
30
- '',
31
- 'Examples:',
32
- ' aiwg marketplace search parallel-dispatch',
33
- ' aiwg marketplace search auth --source clawhub',
34
- ' aiwg marketplace search auth --json',
35
- ' aiwg marketplace list',
36
- ' aiwg marketplace list --json',
31
+ 'Common flags:',
32
+ ' --json Output structured JSON',
33
+ ' --project-local Store/read state below the target project .aiwg directory',
34
+ ' --global Store/read state in the user AIWG directory (default)',
35
+ ' --policy <name> Named trust policy or JSON policy path',
37
36
  ].join('\n');
38
- // ── Flag helpers ───────────────────────────────────────────────────────────
39
37
  function parseFlag(args, flag) {
40
38
  const idx = args.indexOf(flag);
41
39
  if (idx === -1 || idx + 1 >= args.length)
@@ -45,124 +43,379 @@ function parseFlag(args, flag) {
45
43
  function hasFlag(args, flag) {
46
44
  return args.includes(flag);
47
45
  }
48
- // ── Handler ────────────────────────────────────────────────────────────────
46
+ function scopeFromContext(ctx) {
47
+ const projectLocal = hasFlag(ctx.args, '--project-local');
48
+ if (projectLocal && hasFlag(ctx.args, '--global'))
49
+ throw new Error('Choose either --project-local or --global, not both');
50
+ return { projectLocal, projectDir: parseFlag(ctx.args, '--target') ?? ctx.cwd };
51
+ }
52
+ function jsonOrMessage(jsonMode, value, message) {
53
+ if (jsonMode) {
54
+ console.log(JSON.stringify(value, null, 2));
55
+ return { exitCode: 0 };
56
+ }
57
+ return { exitCode: 0, message };
58
+ }
59
+ function failure(error) {
60
+ return { exitCode: 1, message: `Error: ${error instanceof Error ? error.message : String(error)}` };
61
+ }
62
+ function requiredArg(ctx, usage) {
63
+ const value = ctx.args[1];
64
+ return !value || value.startsWith('--') ? { exitCode: 1, message: `Error: ${usage}` } : value;
65
+ }
49
66
  export const marketplaceHandler = {
50
67
  id: 'marketplace',
51
68
  name: 'Marketplace',
52
- description: 'Search and manage marketplace packages (search, list)',
69
+ description: 'Federated Git marketplace with Fortemi provenance and offline verification',
53
70
  category: 'framework',
54
71
  aliases: ['market'],
55
72
  async execute(ctx) {
56
73
  const subcommand = ctx.args[0];
57
- if (!subcommand) {
74
+ if (!subcommand)
58
75
  return { exitCode: 0, message: USAGE };
59
- }
60
- if (subcommand === 'search') {
76
+ if (subcommand === 'add')
77
+ return handleAdd(ctx);
78
+ if (subcommand === 'search')
61
79
  return handleSearch(ctx);
62
- }
63
- if (subcommand === 'list') {
80
+ if (subcommand === 'info')
81
+ return handleInfo(ctx);
82
+ if (subcommand === 'install')
83
+ return handleInstall(ctx);
84
+ if (subcommand === 'verify')
85
+ return handleVerify(ctx);
86
+ if (subcommand === 'export')
87
+ return handleExport(ctx);
88
+ if (subcommand === 'import')
89
+ return handleImport(ctx);
90
+ if (subcommand === 'publish')
91
+ return handlePublish(ctx);
92
+ if (subcommand === 'remove')
93
+ return handleRemove(ctx);
94
+ if (subcommand === 'list')
64
95
  return handleList(ctx);
65
- }
66
- return {
67
- exitCode: 1,
68
- message: `Error: Unknown subcommand '${subcommand}'\n\n${USAGE}`,
69
- };
96
+ return { exitCode: 1, message: `Error: Unknown subcommand '${subcommand}'\n\n${USAGE}` };
70
97
  },
71
98
  };
72
- // ── Search ─────────────────────────────────────────────────────────────────
73
- async function handleSearch(ctx) {
74
- // First positional arg after 'search' that isn't a flag value
75
- const query = ctx.args[1];
76
- if (!query || query.startsWith('--')) {
77
- return {
78
- exitCode: 1,
79
- message: 'Error: Query required\n\nUsage: aiwg marketplace search <query>',
80
- };
81
- }
82
- const source = parseFlag(ctx.args, '--source');
83
- const jsonMode = hasFlag(ctx.args, '--json');
84
- const results = await searchSkills(query, source);
85
- if (jsonMode) {
86
- console.log(JSON.stringify(results, null, 2));
87
- return { exitCode: 0 };
99
+ async function handleAdd(ctx) {
100
+ const argument = requiredArg(ctx, 'Catalog Git URL required');
101
+ if (typeof argument !== 'string')
102
+ return argument;
103
+ try {
104
+ const scope = scopeFromContext(ctx);
105
+ const requestedRef = parseFlag(ctx.args, '--ref') ?? 'HEAD';
106
+ const adapter = new GitAdapter();
107
+ const source = { gitUrl: argument, ref: requestedRef, label: argument };
108
+ const cachePath = await adapter.fetch(source, { refresh: hasFlag(ctx.args, '--refresh') });
109
+ const identity = await inspectGitCheckout(cachePath);
110
+ const candidates = [
111
+ path.join(cachePath, 'aiwg-marketplace-catalog.json'),
112
+ path.join(cachePath, '.aiwg', 'marketplace', 'catalog.json'),
113
+ ];
114
+ const catalogPath = candidates.find((candidate) => fs.existsSync(candidate));
115
+ if (!catalogPath)
116
+ throw new Error('Catalog repository has no aiwg-marketplace-catalog.json or .aiwg/marketplace/catalog.json');
117
+ const trustStore = await readTrustStore({ ...scope, path: parseFlag(ctx.args, '--trust-store') });
118
+ const record = await registerCatalog({
119
+ ...scope,
120
+ catalogPath,
121
+ source: identity.canonicalRemote,
122
+ requestedRef: source.ref ?? requestedRef,
123
+ resolvedCommit: identity.resolvedCommit,
124
+ cachePath,
125
+ trustStore,
126
+ });
127
+ return jsonOrMessage(hasFlag(ctx.args, '--json'), record, `Added verified catalog ${record.catalogId} at ${record.resolvedCommit}`);
88
128
  }
89
- ui.blank();
90
- console.log(` ${ui.brandMark()} ${ui.bold(`Marketplace Search: "${query}"`)}`);
91
- if (source) {
92
- ui.dim(` Source: ${source}`);
129
+ catch (error) {
130
+ return failure(error);
93
131
  }
94
- ui.rule();
95
- if (results.length === 0) {
96
- ui.dim(' No results found.');
132
+ }
133
+ async function handleSearch(ctx) {
134
+ const argument = requiredArg(ctx, 'Query required\n\nUsage: aiwg marketplace search <query>');
135
+ if (typeof argument !== 'string')
136
+ return argument;
137
+ try {
138
+ const source = parseFlag(ctx.args, '--source');
139
+ const scope = scopeFromContext(ctx);
140
+ const [skillResults, catalogResults] = await Promise.all([
141
+ !source || !source.startsWith('catalog:') ? searchSkills(argument, source) : Promise.resolve([]),
142
+ searchCatalogs(argument, scope),
143
+ ]);
144
+ const filteredCatalogs = source?.startsWith('catalog:')
145
+ ? catalogResults.filter((result) => result.catalogId === source.slice('catalog:'.length))
146
+ : source ? [] : catalogResults;
147
+ const results = [
148
+ ...skillResults,
149
+ ...filteredCatalogs.map((entry) => ({
150
+ name: `${entry.identity}@${entry.version}`,
151
+ description: entry.description,
152
+ source: `catalog:${entry.catalogId}`,
153
+ package: entry.identity,
154
+ provenanceCompleteness: entry.provenanceCompleteness,
155
+ verificationStatus: entry.verificationStatus,
156
+ observation: entry.observation,
157
+ })),
158
+ ];
159
+ if (hasFlag(ctx.args, '--json')) {
160
+ console.log(JSON.stringify(results, null, 2));
161
+ return { exitCode: 0 };
162
+ }
97
163
  ui.blank();
98
- ui.dim(' Try a broader query or a different source.');
164
+ console.log(` ${ui.brandMark()} ${ui.bold(`Marketplace Search: "${argument}"`)}`);
165
+ if (source)
166
+ ui.dim(` Source: ${source}`);
167
+ ui.rule();
168
+ if (results.length === 0) {
169
+ ui.dim(' No results found.');
170
+ ui.blank();
171
+ return { exitCode: 0 };
172
+ }
173
+ const nameWidth = Math.max(12, ...results.map((result) => result.name.length));
174
+ const sourceWidth = Math.max(8, ...results.map((result) => result.source.length));
175
+ const pkgWidth = Math.max(10, ...results.map((result) => (result.package ?? '').length));
176
+ const header = ['Name'.padEnd(nameWidth), 'Source'.padEnd(sourceWidth), 'Package'.padEnd(pkgWidth), 'Description'].join(' ');
177
+ ui.dim(` ${header}`);
178
+ ui.dim(` ${'─'.repeat(header.length)}`);
179
+ for (const result of results) {
180
+ console.log(` ${[
181
+ result.name.padEnd(nameWidth),
182
+ result.source.padEnd(sourceWidth),
183
+ (result.package ?? '').padEnd(pkgWidth),
184
+ result.description.length > 60 ? `${result.description.slice(0, 57)}...` : result.description,
185
+ ].join(' ')}`);
186
+ }
99
187
  ui.blank();
188
+ ui.dim(` ${results.length} result${results.length === 1 ? '' : 's'}; catalog inclusion is not an endorsement`);
100
189
  return { exitCode: 0 };
101
190
  }
102
- // Column widths
103
- const nameWidth = Math.max(12, ...results.map((r) => r.name.length));
104
- const sourceWidth = Math.max(8, ...results.map((r) => r.source.length));
105
- const pkgWidth = Math.max(10, ...results.map((r) => (r.package ?? '').length));
106
- const header = [
107
- 'Name'.padEnd(nameWidth),
108
- 'Source'.padEnd(sourceWidth),
109
- 'Package'.padEnd(pkgWidth),
110
- 'Description',
111
- ].join(' ');
112
- ui.dim(` ${header}`);
113
- ui.dim(` ${'─'.repeat(header.length)}`);
114
- for (const r of results) {
115
- const row = [
116
- r.name.padEnd(nameWidth),
117
- r.source.padEnd(sourceWidth),
118
- (r.package ?? '').padEnd(pkgWidth),
119
- r.description.length > 60 ? r.description.slice(0, 57) + '...' : r.description,
120
- ].join(' ');
121
- console.log(` ${row}`);
122
- }
123
- ui.blank();
124
- ui.dim(` ${results.length} result${results.length !== 1 ? 's' : ''}`);
125
- ui.blank();
126
- return { exitCode: 0 };
127
- }
128
- // ── List ───────────────────────────────────────────────────────────────────
129
- async function handleList(ctx) {
130
- const jsonMode = hasFlag(ctx.args, '--json');
131
- const packages = await listInstalledPackages();
132
- if (jsonMode) {
133
- console.log(JSON.stringify(packages, null, 2));
134
- return { exitCode: 0 };
191
+ catch (error) {
192
+ return failure(error);
193
+ }
194
+ }
195
+ async function handleInfo(ctx) {
196
+ const argument = requiredArg(ctx, 'Package query required');
197
+ if (typeof argument !== 'string')
198
+ return argument;
199
+ try {
200
+ const scope = scopeFromContext(ctx);
201
+ const installed = await findIndexedPackage(argument, scope);
202
+ const catalogs = (await searchCatalogs(argument, scope)).filter((entry) => entry.identity === argument || `${entry.identity}@${entry.version}` === argument || entry.lockId === argument);
203
+ if (!installed && catalogs.length === 0)
204
+ return { exitCode: 1, message: `Error: Marketplace package '${argument}' was not found` };
205
+ const result = {
206
+ query: argument,
207
+ installed: installed ? {
208
+ lock: installed.lock,
209
+ verificationStatus: installed.verificationStatus,
210
+ installedAt: installed.installedAt,
211
+ catalogs: installed.catalogs,
212
+ } : null,
213
+ catalogObservations: catalogs,
214
+ endorsement: false,
215
+ };
216
+ return jsonOrMessage(hasFlag(ctx.args, '--json'), result, [
217
+ `Package: ${argument}`,
218
+ `Installed: ${installed ? 'yes' : 'no'}`,
219
+ ...(installed ? [`Lock: ${installed.lock.lockId}`, `Verification: ${installed.verificationStatus}`] : []),
220
+ `Catalog observations: ${catalogs.length} (not endorsements)`,
221
+ ...catalogs.map((entry) => ` ${entry.catalogId}: ${entry.provenanceCompleteness}% provenance, ${entry.verificationStatus}`),
222
+ ].join('\n'));
135
223
  }
136
- ui.blank();
137
- console.log(` ${ui.brandMark()} ${ui.bold('Installed Marketplace Packages')}`);
138
- ui.rule();
139
- if (packages.length === 0) {
140
- ui.dim(' No packages installed.');
224
+ catch (error) {
225
+ return failure(error);
226
+ }
227
+ }
228
+ function looksLikeGitUrl(value) {
229
+ return /^(?:https?:\/\/|ssh:\/\/|git@)/.test(value);
230
+ }
231
+ async function handleInstall(ctx) {
232
+ const argument = requiredArg(ctx, 'Git URL or catalog package required');
233
+ if (typeof argument !== 'string')
234
+ return argument;
235
+ try {
236
+ const scope = scopeFromContext(ctx);
237
+ const configDir = marketplaceConfigDir(scope);
238
+ const resolvedPolicy = await resolveVerificationPolicy(parseFlag(ctx.args, '--policy'), scope);
239
+ let rawRef = argument;
240
+ let expectedEnvelope;
241
+ let expectedLockId;
242
+ let catalogId;
243
+ let ref = parseFlag(ctx.args, '--ref');
244
+ if (!looksLikeGitUrl(argument)) {
245
+ const catalog = await resolveCatalogEntry(argument, scope);
246
+ expectedEnvelope = await readCatalogEnvelope(catalog, scope);
247
+ rawRef = catalog.canonicalRemote;
248
+ expectedLockId = catalog.lockId;
249
+ catalogId = catalog.catalogId;
250
+ ref = catalog.resolvedCommit;
251
+ }
252
+ const installed = await installPackage(rawRef, {
253
+ configDir,
254
+ ref,
255
+ packageSelector: parseFlag(ctx.args, '--package'),
256
+ refresh: hasFlag(ctx.args, '--refresh'),
257
+ verify: hasFlag(ctx.args, '--verify'),
258
+ verificationPolicy: resolvedPolicy.policy,
259
+ trustStore: resolvedPolicy.trustStore,
260
+ expectedEnvelope,
261
+ expectedLockId,
262
+ catalogId,
263
+ actor: 'local-user',
264
+ });
265
+ const result = {
266
+ package: installed.key,
267
+ version: installed.envelope.package.version,
268
+ lockId: installed.lock.lockId,
269
+ resolvedCommit: installed.lock.resolvedCommit,
270
+ verificationStatus: installed.verification.status,
271
+ scope: scope.projectLocal ? 'project-local' : 'global',
272
+ catalog: catalogId ?? null,
273
+ };
274
+ return jsonOrMessage(hasFlag(ctx.args, '--json'), result, `Installed ${result.package}@${result.version}\nLock: ${result.lockId}\nVerification: ${result.verificationStatus}`);
275
+ }
276
+ catch (error) {
277
+ return failure(error);
278
+ }
279
+ }
280
+ async function handleVerify(ctx) {
281
+ const argument = requiredArg(ctx, 'Package or lock query required');
282
+ if (typeof argument !== 'string')
283
+ return argument;
284
+ try {
285
+ const scope = scopeFromContext(ctx);
286
+ const resolved = await resolveVerificationPolicy(parseFlag(ctx.args, '--policy'), scope);
287
+ const result = await verifyIndexedPackage({
288
+ ...scope,
289
+ query: argument,
290
+ policy: resolved.policy,
291
+ trustStore: resolved.trustStore,
292
+ requireSignature: hasFlag(ctx.args, '--require-signature'),
293
+ actor: 'local-user',
294
+ });
295
+ if (!result.verification.ok)
296
+ return { exitCode: 1, message: `Verification failed: ${result.verification.errors.join('; ')}` };
297
+ return jsonOrMessage(hasFlag(ctx.args, '--json'), result.verification, `Verified ${result.entry.lock.identity}@${result.entry.lock.version} offline\nStatus: ${result.verification.status}\nLock: ${result.entry.lock.lockId}`);
298
+ }
299
+ catch (error) {
300
+ return failure(error);
301
+ }
302
+ }
303
+ async function handleExport(ctx) {
304
+ const argument = requiredArg(ctx, 'Package query required');
305
+ if (typeof argument !== 'string')
306
+ return argument;
307
+ const output = parseFlag(ctx.args, '--output');
308
+ if (!output)
309
+ return { exitCode: 1, message: 'Error: Usage: aiwg marketplace export <package> --output <archive.json>' };
310
+ try {
311
+ const result = await exportPortablePackage({ ...scopeFromContext(ctx), query: argument, output, actor: 'local-user' });
312
+ return jsonOrMessage(hasFlag(ctx.args, '--json'), { output: result.output, lockId: result.bundle.lock.lockId, receiptId: result.receipt.receiptId }, `Exported ${result.bundle.lock.identity}@${result.bundle.lock.version} to ${result.output}`);
313
+ }
314
+ catch (error) {
315
+ return failure(error);
316
+ }
317
+ }
318
+ async function handleImport(ctx) {
319
+ const argument = requiredArg(ctx, 'Portable archive path required');
320
+ if (typeof argument !== 'string')
321
+ return argument;
322
+ try {
323
+ const scope = scopeFromContext(ctx);
324
+ const resolved = await resolveVerificationPolicy(parseFlag(ctx.args, '--policy'), scope);
325
+ const result = await importPortablePackage({
326
+ ...scope,
327
+ input: argument,
328
+ verify: hasFlag(ctx.args, '--verify'),
329
+ policy: resolved.policy,
330
+ trustStore: resolved.trustStore,
331
+ actor: 'local-user',
332
+ });
333
+ return jsonOrMessage(hasFlag(ctx.args, '--json'), { lock: result.entry.lock, verification: result.verification, receipt: result.receipt }, `Imported ${result.entry.lock.identity}@${result.entry.lock.version}\nLock: ${result.entry.lock.lockId}\nVerification: ${result.verification.status}`);
334
+ }
335
+ catch (error) {
336
+ return failure(error);
337
+ }
338
+ }
339
+ async function handlePublish(ctx) {
340
+ const argument = requiredArg(ctx, 'Package source directory required');
341
+ if (typeof argument !== 'string')
342
+ return argument;
343
+ const privateKeyPath = parseFlag(ctx.args, '--key');
344
+ const publisher = parseFlag(ctx.args, '--publisher');
345
+ if (!privateKeyPath || !publisher) {
346
+ return { exitCode: 1, message: 'Error: Usage: aiwg marketplace publish <source> --key <ed25519.pem> --publisher <id> [--output <dir>]' };
347
+ }
348
+ try {
349
+ const outputDir = parseFlag(ctx.args, '--output') ?? path.join(path.resolve(argument), '.aiwg', 'marketplace');
350
+ const sequenceText = parseFlag(ctx.args, '--sequence');
351
+ const sequence = sequenceText === undefined ? undefined : Number(sequenceText);
352
+ if (sequence !== undefined && (!Number.isSafeInteger(sequence) || sequence < 1))
353
+ throw new Error('--sequence must be a positive integer');
354
+ const published = await publishLocalPackage({
355
+ sourceDir: argument,
356
+ outputDir,
357
+ privateKeyPath,
358
+ publicKeyPath: parseFlag(ctx.args, '--public-key'),
359
+ keyId: parseFlag(ctx.args, '--key-id'),
360
+ publisher,
361
+ requestedRef: parseFlag(ctx.args, '--ref'),
362
+ packageSelector: parseFlag(ctx.args, '--package'),
363
+ sequence,
364
+ actor: publisher,
365
+ });
366
+ return jsonOrMessage(hasFlag(ctx.args, '--json'), published, `Published signed envelope ${published.lock.lockId}\nEnvelope: ${published.envelopePath}\nFortemi: ${published.shardPath}`);
367
+ }
368
+ catch (error) {
369
+ return failure(error);
370
+ }
371
+ }
372
+ async function handleRemove(ctx) {
373
+ const argument = requiredArg(ctx, 'Catalog ID required');
374
+ if (typeof argument !== 'string')
375
+ return argument;
376
+ try {
377
+ const removed = await removeCatalog(argument, scopeFromContext(ctx));
378
+ return {
379
+ exitCode: removed ? 0 : 1,
380
+ message: removed
381
+ ? `Removed catalog ${argument}; existing immutable package locks remain valid`
382
+ : `Catalog '${argument}' was not registered`,
383
+ };
384
+ }
385
+ catch (error) {
386
+ return failure(error);
387
+ }
388
+ }
389
+ async function handleList(ctx) {
390
+ try {
391
+ const scope = scopeFromContext(ctx);
392
+ const packages = await listInstalledPackages(marketplaceConfigDir(scope));
393
+ if (hasFlag(ctx.args, '--json')) {
394
+ console.log(JSON.stringify(packages, null, 2));
395
+ return { exitCode: 0 };
396
+ }
141
397
  ui.blank();
142
- ui.dim(' Install a package: aiwg install owner/name');
398
+ console.log(` ${ui.brandMark()} ${ui.bold('Installed Marketplace Packages')}`);
399
+ ui.rule();
400
+ if (packages.length === 0) {
401
+ ui.dim(' No packages installed.');
402
+ ui.blank();
403
+ return { exitCode: 0 };
404
+ }
405
+ const keyWidth = Math.max(12, ...packages.map((pkg) => pkg.key.length));
406
+ const versionWidth = Math.max(7, ...packages.map((pkg) => pkg.version.length));
407
+ const header = ['Package'.padEnd(keyWidth), 'Version'.padEnd(versionWidth), 'Type', 'Verification'].join(' ');
408
+ ui.dim(` ${header}`);
409
+ ui.dim(` ${'─'.repeat(header.length)}`);
410
+ for (const pkg of packages) {
411
+ console.log(` ${[pkg.key.padEnd(keyWidth), pkg.version.padEnd(versionWidth), pkg.type, pkg.verificationStatus ?? 'legacy'].join(' ')}`);
412
+ }
143
413
  ui.blank();
414
+ ui.dim(` ${packages.length} package${packages.length === 1 ? '' : 's'} installed`);
144
415
  return { exitCode: 0 };
145
416
  }
146
- const keyWidth = Math.max(12, ...packages.map((p) => p.key.length));
147
- const versionWidth = Math.max(7, ...packages.map((p) => p.version.length));
148
- const header = [
149
- 'Package'.padEnd(keyWidth),
150
- 'Version'.padEnd(versionWidth),
151
- 'Type',
152
- ].join(' ');
153
- ui.dim(` ${header}`);
154
- ui.dim(` ${'─'.repeat(header.length)}`);
155
- for (const pkg of packages) {
156
- const row = [
157
- pkg.key.padEnd(keyWidth),
158
- pkg.version.padEnd(versionWidth),
159
- pkg.type,
160
- ].join(' ');
161
- console.log(` ${row}`);
162
- }
163
- ui.blank();
164
- ui.dim(` ${packages.length} package${packages.length !== 1 ? 's' : ''} installed`);
165
- ui.blank();
166
- return { exitCode: 0 };
417
+ catch (error) {
418
+ return failure(error);
419
+ }
167
420
  }
168
421
  //# sourceMappingURL=marketplace.js.map
@@ -1062,6 +1062,7 @@ export const installPluginHandler = {
1062
1062
  const runner = createScriptRunner(frameworkRoot);
1063
1063
  return runner.run("tools/plugin/plugin-installer-cli.mjs", ctx.args, {
1064
1064
  cwd: ctx.cwd,
1065
+ env: { AIWG_ROOT: frameworkRoot },
1065
1066
  });
1066
1067
  },
1067
1068
  };