@enactprotocol/cli 1.2.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +88 -0
  2. package/package.json +34 -38
  3. package/src/commands/auth/index.ts +940 -0
  4. package/src/commands/cache/index.ts +361 -0
  5. package/src/commands/config/README.md +239 -0
  6. package/src/commands/config/index.ts +164 -0
  7. package/src/commands/env/README.md +197 -0
  8. package/src/commands/env/index.ts +392 -0
  9. package/src/commands/exec/README.md +110 -0
  10. package/src/commands/exec/index.ts +195 -0
  11. package/src/commands/get/index.ts +198 -0
  12. package/src/commands/index.ts +30 -0
  13. package/src/commands/inspect/index.ts +264 -0
  14. package/src/commands/install/README.md +146 -0
  15. package/src/commands/install/index.ts +682 -0
  16. package/src/commands/list/README.md +115 -0
  17. package/src/commands/list/index.ts +138 -0
  18. package/src/commands/publish/index.ts +350 -0
  19. package/src/commands/report/index.ts +366 -0
  20. package/src/commands/run/README.md +124 -0
  21. package/src/commands/run/index.ts +686 -0
  22. package/src/commands/search/index.ts +368 -0
  23. package/src/commands/setup/index.ts +274 -0
  24. package/src/commands/sign/index.ts +652 -0
  25. package/src/commands/trust/README.md +214 -0
  26. package/src/commands/trust/index.ts +453 -0
  27. package/src/commands/unyank/index.ts +107 -0
  28. package/src/commands/yank/index.ts +143 -0
  29. package/src/index.ts +96 -0
  30. package/src/types.ts +81 -0
  31. package/src/utils/errors.ts +409 -0
  32. package/src/utils/exit-codes.ts +159 -0
  33. package/src/utils/ignore.ts +147 -0
  34. package/src/utils/index.ts +107 -0
  35. package/src/utils/output.ts +242 -0
  36. package/src/utils/spinner.ts +214 -0
  37. package/tests/commands/auth.test.ts +217 -0
  38. package/tests/commands/cache.test.ts +286 -0
  39. package/tests/commands/config.test.ts +277 -0
  40. package/tests/commands/env.test.ts +293 -0
  41. package/tests/commands/exec.test.ts +112 -0
  42. package/tests/commands/get.test.ts +179 -0
  43. package/tests/commands/inspect.test.ts +201 -0
  44. package/tests/commands/install-integration.test.ts +343 -0
  45. package/tests/commands/install.test.ts +288 -0
  46. package/tests/commands/list.test.ts +160 -0
  47. package/tests/commands/publish.test.ts +186 -0
  48. package/tests/commands/report.test.ts +194 -0
  49. package/tests/commands/run.test.ts +231 -0
  50. package/tests/commands/search.test.ts +131 -0
  51. package/tests/commands/sign.test.ts +164 -0
  52. package/tests/commands/trust.test.ts +236 -0
  53. package/tests/commands/unyank.test.ts +114 -0
  54. package/tests/commands/yank.test.ts +154 -0
  55. package/tests/e2e.test.ts +554 -0
  56. package/tests/fixtures/calculator/enact.yaml +34 -0
  57. package/tests/fixtures/echo-tool/enact.md +31 -0
  58. package/tests/fixtures/env-tool/enact.yaml +19 -0
  59. package/tests/fixtures/greeter/enact.yaml +18 -0
  60. package/tests/fixtures/invalid-tool/enact.yaml +4 -0
  61. package/tests/index.test.ts +8 -0
  62. package/tests/types.test.ts +84 -0
  63. package/tests/utils/errors.test.ts +303 -0
  64. package/tests/utils/exit-codes.test.ts +189 -0
  65. package/tests/utils/ignore.test.ts +461 -0
  66. package/tests/utils/output.test.ts +126 -0
  67. package/tsconfig.json +17 -0
  68. package/tsconfig.tsbuildinfo +1 -0
  69. package/dist/index.js +0 -231410
  70. package/dist/index.js.bak +0 -231409
  71. package/dist/web/static/app.js +0 -663
  72. package/dist/web/static/index.html +0 -117
  73. package/dist/web/static/style.css +0 -291
@@ -0,0 +1,214 @@
1
+ # enact trust
2
+
3
+ Manage trusted publishers and auditors.
4
+
5
+ ## Synopsis
6
+
7
+ ```bash
8
+ enact trust [identity] [options]
9
+ enact trust <subcommand> [options]
10
+ ```
11
+
12
+ ## Description
13
+
14
+ The `trust` command manages your trust configuration for Enact tools. Trust determines which tools can run and under what conditions.
15
+
16
+ Enact supports two types of trusted identities:
17
+
18
+ 1. **Publishers** - Tool authors (e.g., `alice`, `EnactProtocol`)
19
+ 2. **Auditors** - Third-party reviewers (e.g., `github:securityteam`)
20
+
21
+ ## Quick Usage
22
+
23
+ ```bash
24
+ # Add a trusted publisher
25
+ enact trust alice
26
+
27
+ # Remove a trusted publisher
28
+ enact trust -r alice
29
+
30
+ # Add a trusted auditor (provider:identity format)
31
+ enact trust github:securityteam
32
+
33
+ # List all trusted identities
34
+ enact trust
35
+ ```
36
+
37
+ ## Identity Formats
38
+
39
+ | Type | Format | Examples |
40
+ |------|--------|----------|
41
+ | Publisher | `name` | `alice`, `EnactProtocol`, `mycompany` |
42
+ | Auditor | `provider:identity` | `github:user`, `google:user@example.com` |
43
+
44
+ The presence of a colon (`:`) determines whether an identity is treated as a publisher or auditor.
45
+
46
+ ## Options
47
+
48
+ | Option | Description |
49
+ |--------|-------------|
50
+ | `-r, --remove` | Remove identity from trusted list |
51
+ | `--json` | Output as JSON |
52
+
53
+ ## Subcommands
54
+
55
+ ### trust (default)
56
+
57
+ With no subcommand and no identity, lists all trusted publishers and auditors.
58
+
59
+ ```bash
60
+ enact trust
61
+ ```
62
+
63
+ With an identity, adds it to the trust list (or removes with `-r`).
64
+
65
+ ```bash
66
+ enact trust alice # Add publisher
67
+ enact trust -r alice # Remove publisher
68
+ enact trust github:user # Add auditor
69
+ enact trust -r github:user # Remove auditor
70
+ ```
71
+
72
+ ### trust list
73
+
74
+ List all trusted publishers and auditors.
75
+
76
+ ```bash
77
+ enact trust list [options]
78
+ ```
79
+
80
+ **Options:**
81
+ | Option | Description |
82
+ |--------|-------------|
83
+ | `--json` | Output as JSON |
84
+
85
+ **Example:**
86
+
87
+ ```bash
88
+ $ enact trust list
89
+
90
+ Trusted Publishers
91
+ • alice
92
+ • EnactProtocol
93
+
94
+ Trusted Auditors
95
+ • github:securityteam
96
+
97
+ Policy: warn
98
+ ```
99
+
100
+ ### trust check
101
+
102
+ Check the trust status of a specific tool.
103
+
104
+ ```bash
105
+ enact trust check <tool> [options]
106
+ ```
107
+
108
+ **Arguments:**
109
+ | Argument | Description |
110
+ |----------|-------------|
111
+ | `tool` | Tool to check (name@version format) |
112
+
113
+ **Options:**
114
+ | Option | Description |
115
+ |--------|-------------|
116
+ | `--json` | Output as JSON |
117
+
118
+ **Example:**
119
+
120
+ ```bash
121
+ enact trust check alice/api/slack-notifier@1.0.0
122
+ ```
123
+
124
+ > **Note:** Full trust verification requires the Trust Package (Phase 6).
125
+
126
+ ## Trust Policy
127
+
128
+ The trust policy determines behavior when running untrusted tools:
129
+
130
+ | Policy | Behavior |
131
+ |--------|----------|
132
+ | `strict` | Refuse to run untrusted tools |
133
+ | `warn` | Warn but allow (default) |
134
+ | `allow` | Run without warnings |
135
+
136
+ Set the policy using the config command:
137
+
138
+ ```bash
139
+ enact config set trust.policy strict
140
+ ```
141
+
142
+ ## How Trust Works
143
+
144
+ When you run a tool, Enact checks:
145
+
146
+ 1. **Is the publisher trusted?**
147
+ - Tool signed by `alice` → check if `alice` is in `trust.publishers`
148
+
149
+ 2. **Is an auditor trusted?**
150
+ - Tool audited by `github:user` → check if `github:user` is in `trust.auditors`
151
+
152
+ 3. **Apply policy**
153
+ - If trusted: run normally
154
+ - If not trusted: apply policy (strict/warn/allow)
155
+
156
+ ## Examples
157
+
158
+ ### Set Up Trust for a Team
159
+
160
+ ```bash
161
+ # Trust your company's namespace
162
+ enact trust mycompany
163
+
164
+ # Trust the official Enact tools
165
+ enact trust EnactProtocol
166
+
167
+ # Trust your security team as auditors
168
+ enact trust github:myorg/security-team
169
+ ```
170
+
171
+ ### Audit Current Trust
172
+
173
+ ```bash
174
+ # List everything
175
+ enact trust list
176
+
177
+ # Get as JSON for scripting
178
+ enact trust list --json
179
+ ```
180
+
181
+ ### Check a Specific Tool
182
+
183
+ ```bash
184
+ # Before running, check if tool is trusted
185
+ enact trust check alice/api/slack-notifier@1.0.0
186
+ ```
187
+
188
+ ## Configuration File
189
+
190
+ Trust settings are stored in `~/.enact/config.yaml`:
191
+
192
+ ```yaml
193
+ trust:
194
+ publishers:
195
+ - alice
196
+ - EnactProtocol
197
+ auditors:
198
+ - github:securityteam
199
+ policy: warn
200
+ ```
201
+
202
+ You can edit this file directly or use the `enact trust` and `enact config` commands.
203
+
204
+ ## Exit Codes
205
+
206
+ | Code | Description |
207
+ |------|-------------|
208
+ | `0` | Success |
209
+ | `1` | Error |
210
+
211
+ ## See Also
212
+
213
+ - [enact config](../config/README.md) - Manage CLI configuration including trust policy
214
+ - [enact run](../run/README.md) - How trust affects tool execution
@@ -0,0 +1,453 @@
1
+ /**
2
+ * enact trust command
3
+ *
4
+ * Manage trusted identities for attestation verification.
5
+ * Uses a unified model: all trust is based on cryptographic attestations.
6
+ * Publishers who want their tools trusted should self-sign them.
7
+ */
8
+
9
+ import {
10
+ type AttestationListResponse,
11
+ addTrustedAuditor as addTrustedAuditorToRegistry,
12
+ createApiClient,
13
+ getAttestationList,
14
+ getMyTrustedAuditors,
15
+ getToolVersion,
16
+ removeTrustedAuditor as removeTrustedAuditorFromRegistry,
17
+ verifyAllAttestations,
18
+ } from "@enactprotocol/api";
19
+ import { getSecret } from "@enactprotocol/secrets";
20
+ import {
21
+ addTrustedIdentity,
22
+ getMinimumAttestations,
23
+ getTrustPolicy,
24
+ getTrustedIdentities,
25
+ removeTrustedIdentity,
26
+ } from "@enactprotocol/shared";
27
+ import type { Command } from "commander";
28
+ import type { CommandContext, GlobalOptions } from "../../types";
29
+ import {
30
+ dim,
31
+ error,
32
+ formatError,
33
+ header,
34
+ info,
35
+ json,
36
+ keyValue,
37
+ listItem,
38
+ newline,
39
+ success,
40
+ warning,
41
+ } from "../../utils";
42
+
43
+ /** Auth namespace for token storage */
44
+ const AUTH_NAMESPACE = "enact:auth";
45
+ const ACCESS_TOKEN_KEY = "access_token";
46
+
47
+ interface TrustOptions extends GlobalOptions {
48
+ remove?: boolean;
49
+ sync?: boolean;
50
+ }
51
+
52
+ interface TrustCheckOptions extends GlobalOptions {}
53
+
54
+ interface TrustListOptions extends GlobalOptions {
55
+ sync?: boolean;
56
+ }
57
+
58
+ /**
59
+ * Validate identity format
60
+ * Must be provider:identity format (e.g., github:alice, google:user@example.com)
61
+ */
62
+ function validateIdentity(identity: string): { valid: boolean; error?: string } {
63
+ if (!identity.includes(":")) {
64
+ return {
65
+ valid: false,
66
+ error:
67
+ "Invalid identity format. Use provider:identity format (e.g., github:alice, google:user@example.com)",
68
+ };
69
+ }
70
+
71
+ const [provider, ...rest] = identity.split(":");
72
+ const id = rest.join(":"); // Handle cases like google:user@example.com
73
+
74
+ if (!provider || !id) {
75
+ return {
76
+ valid: false,
77
+ error: "Invalid identity format. Use provider:identity format (e.g., github:alice)",
78
+ };
79
+ }
80
+
81
+ const validProviders = ["github", "google", "microsoft", "gitlab"];
82
+ if (!validProviders.includes(provider) && !provider.startsWith("http")) {
83
+ warning(`Unknown provider '${provider}'. Common providers: ${validProviders.join(", ")}`);
84
+ }
85
+
86
+ return { valid: true };
87
+ }
88
+
89
+ /**
90
+ * Add a trusted identity
91
+ */
92
+ async function addTrust(
93
+ identity: string,
94
+ options: TrustOptions,
95
+ _ctx: CommandContext
96
+ ): Promise<void> {
97
+ // Validate identity format
98
+ const validation = validateIdentity(identity);
99
+ if (!validation.valid) {
100
+ error(validation.error!);
101
+ process.exit(1);
102
+ }
103
+
104
+ // Add to local config
105
+ const added = addTrustedIdentity(identity);
106
+ if (added) {
107
+ success(`Added ${identity} to trusted identities`);
108
+ } else {
109
+ info(`${identity} is already trusted`);
110
+ }
111
+
112
+ // Sync to registry if authenticated and --sync flag
113
+ if (options.sync) {
114
+ const authToken = await getSecret(AUTH_NAMESPACE, ACCESS_TOKEN_KEY);
115
+ if (authToken) {
116
+ const client = createApiClient();
117
+ client.setAuthToken(authToken);
118
+ try {
119
+ await addTrustedAuditorToRegistry(client, identity);
120
+ success(`Synced ${identity} to registry`);
121
+ } catch (err) {
122
+ warning(`Failed to sync to registry: ${formatError(err)}`);
123
+ }
124
+ } else {
125
+ dim("Not authenticated - skipping registry sync");
126
+ dim("Run 'enact auth login' to enable registry sync");
127
+ }
128
+ }
129
+
130
+ if (options.json) {
131
+ json({ added: true, identity });
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Remove a trusted identity
137
+ */
138
+ async function removeTrust(
139
+ identity: string,
140
+ options: TrustOptions,
141
+ _ctx: CommandContext
142
+ ): Promise<void> {
143
+ // Remove from local config
144
+ const removed = removeTrustedIdentity(identity);
145
+ if (removed) {
146
+ success(`Removed ${identity} from trusted identities`);
147
+ } else {
148
+ info(`${identity} was not in trusted list`);
149
+ }
150
+
151
+ // Sync to registry if authenticated and --sync flag
152
+ if (options.sync) {
153
+ const authToken = await getSecret(AUTH_NAMESPACE, ACCESS_TOKEN_KEY);
154
+ if (authToken) {
155
+ const client = createApiClient();
156
+ client.setAuthToken(authToken);
157
+ try {
158
+ await removeTrustedAuditorFromRegistry(client, identity);
159
+ success(`Removed ${identity} from registry`);
160
+ } catch (err) {
161
+ warning(`Failed to sync to registry: ${formatError(err)}`);
162
+ }
163
+ } else {
164
+ dim("Not authenticated - skipping registry sync");
165
+ }
166
+ }
167
+
168
+ if (options.json) {
169
+ json({ removed: true, identity });
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Trust command handler (add or remove)
175
+ */
176
+ async function trustHandler(
177
+ identity: string,
178
+ options: TrustOptions,
179
+ ctx: CommandContext
180
+ ): Promise<void> {
181
+ if (options.remove) {
182
+ return removeTrust(identity, options, ctx);
183
+ }
184
+ return addTrust(identity, options, ctx);
185
+ }
186
+
187
+ /**
188
+ * List trusted identities
189
+ */
190
+ async function trustListHandler(options: TrustListOptions, _ctx: CommandContext): Promise<void> {
191
+ const auditors = getTrustedIdentities();
192
+ const policy = getTrustPolicy();
193
+ const minimumAttestations = getMinimumAttestations();
194
+
195
+ // Get remote identities if authenticated and --sync flag
196
+ let remoteIdentities: string[] = [];
197
+ if (options.sync) {
198
+ const authToken = await getSecret(AUTH_NAMESPACE, ACCESS_TOKEN_KEY);
199
+ if (authToken) {
200
+ const client = createApiClient();
201
+ client.setAuthToken(authToken);
202
+ try {
203
+ remoteIdentities = await getMyTrustedAuditors(client);
204
+ } catch (err) {
205
+ warning(`Failed to fetch remote trust config: ${formatError(err)}`);
206
+ }
207
+ } else {
208
+ dim("Not authenticated - showing local config only");
209
+ }
210
+ }
211
+
212
+ if (options.json) {
213
+ json({
214
+ identities: auditors,
215
+ remoteIdentities: options.sync ? remoteIdentities : undefined,
216
+ policy,
217
+ minimum_attestations: minimumAttestations,
218
+ });
219
+ return;
220
+ }
221
+
222
+ header("Trusted Identities (Local)");
223
+ newline();
224
+ if (auditors.length === 0) {
225
+ dim(" No trusted identities configured");
226
+ dim(" Add with: enact trust provider:identity");
227
+ } else {
228
+ for (const identity of auditors) {
229
+ listItem(identity, 2);
230
+ }
231
+ }
232
+
233
+ if (options.sync && remoteIdentities.length > 0) {
234
+ newline();
235
+ header("Trusted Identities (Registry)");
236
+ newline();
237
+ for (const identity of remoteIdentities) {
238
+ listItem(identity, 2);
239
+ }
240
+ }
241
+
242
+ newline();
243
+ dim(`Policy: ${policy}`);
244
+ dim(`Minimum attestations: ${minimumAttestations}`);
245
+ }
246
+
247
+ /**
248
+ * Parse tool@version syntax
249
+ */
250
+ function parseToolSpec(spec: string): { name: string; version: string | undefined } {
251
+ const atIndex = spec.lastIndexOf("@");
252
+ if (atIndex === -1 || atIndex === 0) {
253
+ return { name: spec, version: undefined };
254
+ }
255
+ return {
256
+ name: spec.slice(0, atIndex),
257
+ version: spec.slice(atIndex + 1),
258
+ };
259
+ }
260
+
261
+ /**
262
+ * Check trust status of a tool
263
+ */
264
+ async function trustCheckHandler(
265
+ tool: string,
266
+ options: TrustCheckOptions,
267
+ _ctx: CommandContext
268
+ ): Promise<void> {
269
+ const { name, version } = parseToolSpec(tool);
270
+
271
+ if (!version) {
272
+ error("Please specify a version: tool-name@version");
273
+ process.exit(1);
274
+ }
275
+
276
+ const trustedIdentities = getTrustedIdentities();
277
+ const client = createApiClient();
278
+
279
+ const trustedBy: string[] = [];
280
+ let verifiedAuditors: string[] = [];
281
+ let allAttestors: string[] = [];
282
+ let bundleHash = "";
283
+
284
+ try {
285
+ // Fetch tool version info to get bundle hash
286
+ const toolVersion = await getToolVersion(client, name, version);
287
+ bundleHash = toolVersion.bundle?.hash ?? "";
288
+
289
+ if (!bundleHash) {
290
+ warning("Cannot verify attestations: tool bundle hash not found");
291
+ } else {
292
+ // Fetch attestations from registry
293
+ const attestationsResponse: AttestationListResponse = await getAttestationList(
294
+ client,
295
+ name,
296
+ version
297
+ );
298
+ const attestations = attestationsResponse.attestations;
299
+
300
+ // Collect all attestors
301
+ allAttestors = attestations.map((att: { auditor: string }) => att.auditor);
302
+
303
+ if (attestations.length > 0) {
304
+ // Verify all attestations locally (never trust registry's verification status)
305
+ const verifiedResults = await verifyAllAttestations(client, name, version, bundleHash);
306
+
307
+ // Update verifiedAuditors with the new format
308
+ verifiedAuditors = verifiedResults.map((a) => a.providerIdentity);
309
+
310
+ // Check which verified auditors are trusted
311
+ for (const result of verifiedResults) {
312
+ if (trustedIdentities.includes(result.providerIdentity)) {
313
+ trustedBy.push(result.providerIdentity);
314
+ }
315
+ }
316
+ }
317
+ }
318
+ } catch (err) {
319
+ if (options.json) {
320
+ json({
321
+ tool: name,
322
+ version,
323
+ trusted: false,
324
+ error: formatError(err),
325
+ });
326
+ return;
327
+ }
328
+ warning(`Failed to check attestations: ${formatError(err)}`);
329
+ }
330
+
331
+ const trusted = trustedBy.length > 0;
332
+ const hasAnyAttestation = allAttestors.length > 0;
333
+ const verifiedCount = verifiedAuditors.length;
334
+
335
+ if (options.json) {
336
+ json({
337
+ tool: name,
338
+ version,
339
+ trusted,
340
+ trustedBy,
341
+ verifiedAuditors,
342
+ totalAttestations: allAttestors.length,
343
+ verifiedAttestations: verifiedCount,
344
+ checkedIdentities: trustedIdentities.length,
345
+ });
346
+ return;
347
+ }
348
+
349
+ header(`Trust Status: ${name}@${version}`);
350
+ newline();
351
+
352
+ if (trusted) {
353
+ success("✓ Trusted");
354
+ keyValue("Verified by trusted identity(ies)", trustedBy.join(", "));
355
+ } else if (hasAnyAttestation) {
356
+ warning("⚠ Not trusted by any configured identities");
357
+ if (verifiedCount > 0) {
358
+ keyValue("Verified attestations", verifiedAuditors.join(", "));
359
+ }
360
+ } else {
361
+ warning("⚠ No attestations found");
362
+ }
363
+
364
+ newline();
365
+ dim(`Total attestations: ${allAttestors.length}`);
366
+ dim(`Cryptographically verified: ${verifiedCount}`);
367
+ dim(`Trusted identities configured: ${trustedIdentities.length}`);
368
+
369
+ if (!trusted && verifiedCount > 0) {
370
+ newline();
371
+ info("To trust this tool, add one of the verified identities:");
372
+ for (const identity of verifiedAuditors.slice(0, 3)) {
373
+ dim(` enact trust ${identity}`);
374
+ }
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Configure the trust command
380
+ */
381
+ export function configureTrustCommand(program: Command): void {
382
+ const trust = program
383
+ .command("trust")
384
+ .description("Manage trusted publishers and auditors")
385
+ .argument("[identity]", "Identity to trust (format: provider:identity, e.g., github:alice)")
386
+ .option("-r, --remove", "Remove from trusted list instead of adding")
387
+ .option("-s, --sync", "Sync with registry (requires authentication)")
388
+ .option("--json", "Output as JSON")
389
+ .action(async (identity: string | undefined, options: TrustOptions) => {
390
+ const ctx: CommandContext = {
391
+ cwd: process.cwd(),
392
+ options,
393
+ isCI: Boolean(process.env.CI),
394
+ isInteractive: process.stdout.isTTY ?? false,
395
+ };
396
+
397
+ try {
398
+ if (!identity) {
399
+ // No identity provided, show list
400
+ await trustListHandler(options, ctx);
401
+ } else {
402
+ await trustHandler(identity, options, ctx);
403
+ }
404
+ } catch (err) {
405
+ error(formatError(err));
406
+ process.exit(1);
407
+ }
408
+ });
409
+
410
+ // trust list
411
+ trust
412
+ .command("list")
413
+ .description("List all trusted identities")
414
+ .option("-s, --sync", "Also show registry trust config (requires authentication)")
415
+ .option("--json", "Output as JSON")
416
+ .action(async (options: TrustListOptions) => {
417
+ const ctx: CommandContext = {
418
+ cwd: process.cwd(),
419
+ options,
420
+ isCI: Boolean(process.env.CI),
421
+ isInteractive: process.stdout.isTTY ?? false,
422
+ };
423
+
424
+ try {
425
+ await trustListHandler(options, ctx);
426
+ } catch (err) {
427
+ error(formatError(err));
428
+ process.exit(1);
429
+ }
430
+ });
431
+
432
+ // trust check
433
+ trust
434
+ .command("check")
435
+ .description("Check trust status of a tool")
436
+ .argument("<tool>", "Tool to check (name@version)")
437
+ .option("--json", "Output as JSON")
438
+ .action(async (tool: string, options: TrustCheckOptions) => {
439
+ const ctx: CommandContext = {
440
+ cwd: process.cwd(),
441
+ options,
442
+ isCI: Boolean(process.env.CI),
443
+ isInteractive: process.stdout.isTTY ?? false,
444
+ };
445
+
446
+ try {
447
+ await trustCheckHandler(tool, options, ctx);
448
+ } catch (err) {
449
+ error(formatError(err));
450
+ process.exit(1);
451
+ }
452
+ });
453
+ }