@enactprotocol/cli 1.2.13 → 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 -231612
  70. package/dist/index.js.bak +0 -231611
  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,361 @@
1
+ /**
2
+ * enact cache command
3
+ *
4
+ * Manage the local tool cache.
5
+ */
6
+
7
+ import { existsSync, readdirSync, rmSync, statSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { getCacheDir, tryLoadManifestFromDir } from "@enactprotocol/shared";
10
+ import type { Command } from "commander";
11
+ import type { CommandContext, GlobalOptions } from "../../types";
12
+ import {
13
+ type TableColumn,
14
+ confirm,
15
+ dim,
16
+ error,
17
+ formatError,
18
+ header,
19
+ info,
20
+ json,
21
+ keyValue,
22
+ newline,
23
+ success,
24
+ table,
25
+ warning,
26
+ } from "../../utils";
27
+
28
+ interface CacheOptions extends GlobalOptions {
29
+ force?: boolean;
30
+ }
31
+
32
+ interface CachedTool {
33
+ name: string;
34
+ version: string;
35
+ size: string;
36
+ path: string;
37
+ [key: string]: string;
38
+ }
39
+
40
+ /**
41
+ * Format bytes to human readable
42
+ */
43
+ function formatSize(bytes: number): string {
44
+ if (bytes < 1024) return `${bytes} B`;
45
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
46
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
47
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
48
+ }
49
+
50
+ /**
51
+ * Get directory size recursively
52
+ */
53
+ function getDirSize(dirPath: string): number {
54
+ let size = 0;
55
+ try {
56
+ const entries = readdirSync(dirPath, { withFileTypes: true });
57
+ for (const entry of entries) {
58
+ const entryPath = join(dirPath, entry.name);
59
+ if (entry.isDirectory()) {
60
+ size += getDirSize(entryPath);
61
+ } else {
62
+ size += statSync(entryPath).size;
63
+ }
64
+ }
65
+ } catch {
66
+ // Ignore errors
67
+ }
68
+ return size;
69
+ }
70
+
71
+ /**
72
+ * List cached tools
73
+ */
74
+ function listCachedTools(cacheDir: string): CachedTool[] {
75
+ const tools: CachedTool[] = [];
76
+
77
+ if (!existsSync(cacheDir)) {
78
+ return tools;
79
+ }
80
+
81
+ function walkDir(dir: string, prefix = ""): void {
82
+ try {
83
+ const entries = readdirSync(dir, { withFileTypes: true });
84
+
85
+ for (const entry of entries) {
86
+ if (!entry.isDirectory()) continue;
87
+
88
+ const entryPath = join(dir, entry.name);
89
+ const toolName = prefix ? `${prefix}/${entry.name}` : entry.name;
90
+
91
+ const loaded = tryLoadManifestFromDir(entryPath);
92
+ if (loaded) {
93
+ const size = getDirSize(entryPath);
94
+ tools.push({
95
+ name: loaded.manifest.name,
96
+ version: loaded.manifest.version ?? "-",
97
+ size: formatSize(size),
98
+ path: entryPath,
99
+ });
100
+ } else {
101
+ walkDir(entryPath, toolName);
102
+ }
103
+ }
104
+ } catch {
105
+ // Ignore errors
106
+ }
107
+ }
108
+
109
+ walkDir(cacheDir);
110
+ return tools;
111
+ }
112
+
113
+ /**
114
+ * Cache list handler
115
+ */
116
+ async function listHandler(options: CacheOptions, _ctx: CommandContext): Promise<void> {
117
+ const cacheDir = getCacheDir();
118
+ const tools = listCachedTools(cacheDir);
119
+
120
+ if (options.json) {
121
+ json({ cacheDir, tools, count: tools.length });
122
+ return;
123
+ }
124
+
125
+ header("Cached Tools");
126
+ newline();
127
+
128
+ if (tools.length === 0) {
129
+ info("No tools cached.");
130
+ dim(`Cache directory: ${cacheDir}`);
131
+ return;
132
+ }
133
+
134
+ const columns: TableColumn[] = [
135
+ { key: "name", header: "Name", width: 35 },
136
+ { key: "version", header: "Version", width: 12 },
137
+ { key: "size", header: "Size", width: 10 },
138
+ ];
139
+
140
+ if (options.verbose) {
141
+ columns.push({ key: "path", header: "Path", width: 50 });
142
+ }
143
+
144
+ table(tools, columns);
145
+ newline();
146
+
147
+ const totalSize = tools.reduce((sum, t) => {
148
+ const match = t.size.match(/^([\d.]+)\s*(\w+)$/);
149
+ if (!match) return sum;
150
+ const [, num, unit] = match;
151
+ const multipliers: Record<string, number> = {
152
+ B: 1,
153
+ KB: 1024,
154
+ MB: 1024 * 1024,
155
+ GB: 1024 * 1024 * 1024,
156
+ };
157
+ return sum + Number.parseFloat(num ?? "0") * (multipliers[unit ?? "B"] ?? 1);
158
+ }, 0);
159
+
160
+ dim(`Total: ${tools.length} tool(s), ${formatSize(totalSize)}`);
161
+ dim(`Cache directory: ${cacheDir}`);
162
+ }
163
+
164
+ /**
165
+ * Cache clean handler - remove old/unused tools
166
+ */
167
+ async function cleanHandler(options: CacheOptions, ctx: CommandContext): Promise<void> {
168
+ const cacheDir = getCacheDir();
169
+ const tools = listCachedTools(cacheDir);
170
+
171
+ if (tools.length === 0) {
172
+ info("Cache is already empty.");
173
+ return;
174
+ }
175
+
176
+ // For now, clean removes everything (future: could be smarter)
177
+ if (!options.force && ctx.isInteractive) {
178
+ const shouldProceed = await confirm(`Remove ${tools.length} cached tool(s)?`);
179
+ if (!shouldProceed) {
180
+ info("Cancelled.");
181
+ return;
182
+ }
183
+ }
184
+
185
+ let removed = 0;
186
+ for (const tool of tools) {
187
+ try {
188
+ rmSync(tool.path, { recursive: true, force: true });
189
+ removed++;
190
+ if (options.verbose) {
191
+ dim(`Removed: ${tool.name}@${tool.version}`);
192
+ }
193
+ } catch {
194
+ warning(`Failed to remove: ${tool.path}`);
195
+ }
196
+ }
197
+
198
+ if (options.json) {
199
+ json({ removed, total: tools.length });
200
+ return;
201
+ }
202
+
203
+ success(`Removed ${removed} cached tool(s)`);
204
+ }
205
+
206
+ /**
207
+ * Cache clear handler - remove entire cache
208
+ */
209
+ async function clearHandler(options: CacheOptions, ctx: CommandContext): Promise<void> {
210
+ const cacheDir = getCacheDir();
211
+
212
+ if (!existsSync(cacheDir)) {
213
+ info("Cache directory does not exist.");
214
+ return;
215
+ }
216
+
217
+ const size = getDirSize(cacheDir);
218
+
219
+ if (!options.force && ctx.isInteractive) {
220
+ const shouldProceed = await confirm(`Clear entire cache (${formatSize(size)})?`);
221
+ if (!shouldProceed) {
222
+ info("Cancelled.");
223
+ return;
224
+ }
225
+ }
226
+
227
+ try {
228
+ rmSync(cacheDir, { recursive: true, force: true });
229
+
230
+ if (options.json) {
231
+ json({ cleared: true, size: formatSize(size) });
232
+ return;
233
+ }
234
+
235
+ success(`Cleared cache (${formatSize(size)})`);
236
+ } catch (err) {
237
+ error(`Failed to clear cache: ${formatError(err)}`);
238
+ process.exit(1);
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Cache info handler
244
+ */
245
+ async function infoHandler(options: CacheOptions, _ctx: CommandContext): Promise<void> {
246
+ const cacheDir = getCacheDir();
247
+ const exists = existsSync(cacheDir);
248
+ const tools = exists ? listCachedTools(cacheDir) : [];
249
+ const size = exists ? getDirSize(cacheDir) : 0;
250
+
251
+ if (options.json) {
252
+ json({
253
+ directory: cacheDir,
254
+ exists,
255
+ toolCount: tools.length,
256
+ totalSize: size,
257
+ totalSizeFormatted: formatSize(size),
258
+ });
259
+ return;
260
+ }
261
+
262
+ header("Cache Information");
263
+ newline();
264
+
265
+ keyValue("Directory", cacheDir);
266
+ keyValue("Exists", exists ? "Yes" : "No");
267
+ keyValue("Tools", String(tools.length));
268
+ keyValue("Total Size", formatSize(size));
269
+ }
270
+
271
+ /**
272
+ * Configure the cache command
273
+ */
274
+ export function configureCacheCommand(program: Command): void {
275
+ const cache = program.command("cache").description("Manage the local tool cache");
276
+
277
+ cache
278
+ .command("list")
279
+ .alias("ls")
280
+ .description("List cached tools")
281
+ .option("-v, --verbose", "Show detailed output including paths")
282
+ .option("--json", "Output as JSON")
283
+ .action(async (options: CacheOptions) => {
284
+ const ctx: CommandContext = {
285
+ cwd: process.cwd(),
286
+ options,
287
+ isCI: Boolean(process.env.CI),
288
+ isInteractive: process.stdout.isTTY ?? false,
289
+ };
290
+
291
+ try {
292
+ await listHandler(options, ctx);
293
+ } catch (err) {
294
+ error(formatError(err));
295
+ process.exit(1);
296
+ }
297
+ });
298
+
299
+ cache
300
+ .command("clean")
301
+ .description("Remove old or unused cached tools")
302
+ .option("-f, --force", "Skip confirmation")
303
+ .option("-v, --verbose", "Show detailed output")
304
+ .option("--json", "Output as JSON")
305
+ .action(async (options: CacheOptions) => {
306
+ const ctx: CommandContext = {
307
+ cwd: process.cwd(),
308
+ options,
309
+ isCI: Boolean(process.env.CI),
310
+ isInteractive: process.stdout.isTTY ?? false,
311
+ };
312
+
313
+ try {
314
+ await cleanHandler(options, ctx);
315
+ } catch (err) {
316
+ error(formatError(err));
317
+ process.exit(1);
318
+ }
319
+ });
320
+
321
+ cache
322
+ .command("clear")
323
+ .description("Clear the entire cache")
324
+ .option("-f, --force", "Skip confirmation")
325
+ .option("--json", "Output as JSON")
326
+ .action(async (options: CacheOptions) => {
327
+ const ctx: CommandContext = {
328
+ cwd: process.cwd(),
329
+ options,
330
+ isCI: Boolean(process.env.CI),
331
+ isInteractive: process.stdout.isTTY ?? false,
332
+ };
333
+
334
+ try {
335
+ await clearHandler(options, ctx);
336
+ } catch (err) {
337
+ error(formatError(err));
338
+ process.exit(1);
339
+ }
340
+ });
341
+
342
+ cache
343
+ .command("info")
344
+ .description("Show cache information")
345
+ .option("--json", "Output as JSON")
346
+ .action(async (options: CacheOptions) => {
347
+ const ctx: CommandContext = {
348
+ cwd: process.cwd(),
349
+ options,
350
+ isCI: Boolean(process.env.CI),
351
+ isInteractive: process.stdout.isTTY ?? false,
352
+ };
353
+
354
+ try {
355
+ await infoHandler(options, ctx);
356
+ } catch (err) {
357
+ error(formatError(err));
358
+ process.exit(1);
359
+ }
360
+ });
361
+ }
@@ -0,0 +1,239 @@
1
+ # enact config
2
+
3
+ Manage CLI configuration.
4
+
5
+ ## Synopsis
6
+
7
+ ```bash
8
+ enact config <subcommand> [options]
9
+ ```
10
+
11
+ ## Description
12
+
13
+ The `config` command manages Enact's global configuration settings. Configuration is stored in `~/.enact/config.yaml` and controls default behaviors, trust settings, and other preferences.
14
+
15
+ ## Subcommands
16
+
17
+ ### config get
18
+
19
+ Get a configuration value.
20
+
21
+ ```bash
22
+ enact config get <key> [options]
23
+ ```
24
+
25
+ **Arguments:**
26
+ | Argument | Description |
27
+ |----------|-------------|
28
+ | `key` | Configuration key using dot notation |
29
+
30
+ **Options:**
31
+ | Option | Description |
32
+ |--------|-------------|
33
+ | `--json` | Output as JSON |
34
+
35
+ **Examples:**
36
+
37
+ ```bash
38
+ # Get trust policy
39
+ enact config get trust.policy
40
+
41
+ # Get as JSON
42
+ enact config get trust.publishers --json
43
+ ```
44
+
45
+ ### config set
46
+
47
+ Set a configuration value.
48
+
49
+ ```bash
50
+ enact config set <key> <value> [options]
51
+ ```
52
+
53
+ **Arguments:**
54
+ | Argument | Description |
55
+ |----------|-------------|
56
+ | `key` | Configuration key using dot notation |
57
+ | `value` | Value to set (use JSON for arrays/objects) |
58
+
59
+ **Options:**
60
+ | Option | Description |
61
+ |--------|-------------|
62
+ | `--json` | Output as JSON |
63
+
64
+ **Examples:**
65
+
66
+ ```bash
67
+ # Set trust policy
68
+ enact config set trust.policy strict
69
+
70
+ # Set an array value (use JSON)
71
+ enact config set trust.publishers '["alice","bob"]'
72
+
73
+ # Set a boolean
74
+ enact config set execution.sandbox true
75
+ ```
76
+
77
+ ### config list
78
+
79
+ List all configuration values.
80
+
81
+ ```bash
82
+ enact config list [options]
83
+ ```
84
+
85
+ **Options:**
86
+ | Option | Description |
87
+ |--------|-------------|
88
+ | `--json` | Output as JSON |
89
+
90
+ **Examples:**
91
+
92
+ ```bash
93
+ $ enact config list
94
+
95
+ Configuration file: /Users/you/.enact/config.yaml
96
+
97
+ trust.publishers: ["alice","EnactProtocol"]
98
+ trust.auditors: ["github:securityteam"]
99
+ trust.policy: warn
100
+ execution.timeout: 300
101
+ execution.sandbox: true
102
+ ```
103
+
104
+ ## Configuration Keys
105
+
106
+ ### Trust Settings
107
+
108
+ | Key | Type | Default | Description |
109
+ |-----|------|---------|-------------|
110
+ | `trust.publishers` | `string[]` | `[]` | Trusted publisher names |
111
+ | `trust.auditors` | `string[]` | `[]` | Trusted auditor identities |
112
+ | `trust.policy` | `string` | `"warn"` | Policy for untrusted tools: `strict`, `warn`, `allow` |
113
+
114
+ ### Execution Settings
115
+
116
+ | Key | Type | Default | Description |
117
+ |-----|------|---------|-------------|
118
+ | `execution.timeout` | `number` | `300` | Default timeout in seconds |
119
+ | `execution.sandbox` | `boolean` | `true` | Enable sandboxing by default |
120
+
121
+ ### Output Settings
122
+
123
+ | Key | Type | Default | Description |
124
+ |-----|------|---------|-------------|
125
+ | `output.json` | `boolean` | `false` | Default to JSON output |
126
+ | `output.verbose` | `boolean` | `false` | Enable verbose output |
127
+
128
+ ## Dot Notation
129
+
130
+ Configuration keys use dot notation to access nested values:
131
+
132
+ ```yaml
133
+ # config.yaml
134
+ trust:
135
+ publishers:
136
+ - alice
137
+ - bob
138
+ policy: warn
139
+ ```
140
+
141
+ Access with:
142
+ - `trust.policy` → `warn`
143
+ - `trust.publishers` → `["alice","bob"]`
144
+
145
+ ## Configuration File
146
+
147
+ The configuration file is located at `~/.enact/config.yaml`. You can edit it directly or use the `config` commands.
148
+
149
+ ### Example Configuration
150
+
151
+ ```yaml
152
+ # ~/.enact/config.yaml
153
+ trust:
154
+ publishers:
155
+ - EnactProtocol
156
+ - mycompany
157
+ auditors:
158
+ - github:security-team
159
+ policy: warn
160
+
161
+ execution:
162
+ timeout: 300
163
+ sandbox: true
164
+
165
+ output:
166
+ json: false
167
+ verbose: false
168
+ ```
169
+
170
+ ## JSON Output
171
+
172
+ All subcommands support `--json` for machine-readable output:
173
+
174
+ ```bash
175
+ $ enact config list --json
176
+ {
177
+ "trust": {
178
+ "publishers": ["alice"],
179
+ "auditors": [],
180
+ "policy": "warn"
181
+ },
182
+ "execution": {
183
+ "timeout": 300
184
+ }
185
+ }
186
+ ```
187
+
188
+ ## Examples
189
+
190
+ ### Initial Setup
191
+
192
+ ```bash
193
+ # Set up default trust
194
+ enact config set trust.policy warn
195
+
196
+ # Trust official tools
197
+ enact config set trust.publishers '["EnactProtocol"]'
198
+
199
+ # Configure execution defaults
200
+ enact config set execution.timeout 600
201
+ enact config set execution.sandbox true
202
+ ```
203
+
204
+ ### View Current Settings
205
+
206
+ ```bash
207
+ # See everything
208
+ enact config list
209
+
210
+ # Check specific setting
211
+ enact config get trust.policy
212
+
213
+ # Export for backup
214
+ enact config list --json > enact-config-backup.json
215
+ ```
216
+
217
+ ### Scripting
218
+
219
+ ```bash
220
+ # Get value for use in script
221
+ policy=$(enact config get trust.policy --json | jq -r '.value')
222
+
223
+ # Conditionally run based on config
224
+ if [[ "$policy" == "strict" ]]; then
225
+ echo "Running in strict mode"
226
+ fi
227
+ ```
228
+
229
+ ## Exit Codes
230
+
231
+ | Code | Description |
232
+ |------|-------------|
233
+ | `0` | Success |
234
+ | `1` | Error |
235
+
236
+ ## See Also
237
+
238
+ - [enact trust](../trust/README.md) - Shorthand commands for trust management
239
+ - [enact env](../env/README.md) - Manage environment variables