@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,115 @@
1
+ # enact list
2
+
3
+ List installed tools.
4
+
5
+ ## Synopsis
6
+
7
+ ```bash
8
+ enact list [options]
9
+ ```
10
+
11
+ ## Description
12
+
13
+ The `list` command displays installed tools, showing their name and version. By default, it shows project tools. Use `-g` to show global tools.
14
+
15
+ ## Options
16
+
17
+ | Option | Description |
18
+ |--------|-------------|
19
+ | `-g, --global` | List global tools (`~/.enact/tools/`) |
20
+ | `-v, --verbose` | Show detailed output including file paths |
21
+ | `--json` | Output as JSON |
22
+
23
+ ## Examples
24
+
25
+ ### Basic usage
26
+
27
+ ```bash
28
+ # List project tools (default)
29
+ enact list
30
+
31
+ # List global tools
32
+ enact list -g
33
+
34
+ # List with full paths
35
+ enact list --verbose
36
+ ```
37
+
38
+ ### JSON output
39
+
40
+ ```bash
41
+ # Get JSON for scripting
42
+ enact list --json
43
+
44
+ # Global tools as JSON
45
+ enact list -g --json
46
+ ```
47
+
48
+ ## Output Format
49
+
50
+ ### Table Output (Default)
51
+
52
+ ```
53
+ Project Tools
54
+ ─────────────
55
+
56
+ Name Version
57
+ ────────────────────────────────────────
58
+ alice/utils/greeter 1.0.0
59
+ company/internal-tool 0.5.0
60
+
61
+ Total: 2 tool(s)
62
+ ```
63
+
64
+ ### Global Tools
65
+
66
+ ```
67
+ Global Tools
68
+ ────────────
69
+
70
+ Name Version
71
+ ────────────────────────────────────────
72
+ EnactProtocol/pdf-extract 2.1.0
73
+
74
+ Total: 1 tool(s)
75
+ ```
76
+
77
+ ### Verbose Output
78
+
79
+ ```
80
+ Name Version Location
81
+ ──────────────────────────────────────────────────────────────────────
82
+ alice/utils/greeter 1.0.0 /path/to/project/.enact/tools/alice/utils/greeter
83
+ ```
84
+
85
+ ### JSON Output
86
+
87
+ ```json
88
+ [
89
+ {
90
+ "name": "alice/utils/greeter",
91
+ "version": "1.0.0",
92
+ "location": "/path/to/project/.enact/tools/alice/utils/greeter",
93
+ "scope": "project"
94
+ }
95
+ ]
96
+ ```
97
+
98
+ ## Tool Locations
99
+
100
+ | Scope | Directory | Description |
101
+ |-------|-----------|-------------|
102
+ | `project` | `.enact/tools/` | Tools installed for the current project |
103
+ | `global` | `~/.enact/tools/` | Tools installed globally for the user |
104
+
105
+ ## Exit Codes
106
+
107
+ | Code | Description |
108
+ |------|-------------|
109
+ | `0` | Success |
110
+ | `1` | Error reading directories |
111
+
112
+ ## See Also
113
+
114
+ - [enact install](../install/README.md) - Install tools
115
+ - [enact run](../run/README.md) - Execute tools
@@ -0,0 +1,138 @@
1
+ /**
2
+ * enact list command
3
+ *
4
+ * List installed tools from tools.json registries.
5
+ * - Default: project tools (via .enact/tools.json)
6
+ * - --global/-g: global tools (via ~/.enact/tools.json)
7
+ *
8
+ * All tools are stored in ~/.enact/cache/{tool}/{version}/
9
+ */
10
+
11
+ import { listInstalledTools, tryLoadManifestFromDir } from "@enactprotocol/shared";
12
+ import type { Command } from "commander";
13
+ import type { CommandContext, GlobalOptions } from "../../types";
14
+ import {
15
+ type TableColumn,
16
+ dim,
17
+ error,
18
+ formatError,
19
+ header,
20
+ info,
21
+ json,
22
+ newline,
23
+ table,
24
+ } from "../../utils";
25
+
26
+ interface ListOptions extends GlobalOptions {
27
+ global?: boolean;
28
+ }
29
+
30
+ interface ToolInfo {
31
+ name: string;
32
+ description: string;
33
+ version: string;
34
+ location: string;
35
+ scope: string;
36
+ [key: string]: string; // Index signature for table compatibility
37
+ }
38
+
39
+ /**
40
+ * List tools from tools.json registry
41
+ */
42
+ function listToolsFromRegistry(scope: "global" | "project", cwd?: string): ToolInfo[] {
43
+ const tools: ToolInfo[] = [];
44
+ const installedTools = listInstalledTools(scope, cwd);
45
+
46
+ for (const tool of installedTools) {
47
+ // Load manifest from cache to get description
48
+ const loaded = tryLoadManifestFromDir(tool.cachePath);
49
+ tools.push({
50
+ name: tool.name,
51
+ description: loaded?.manifest.description ?? "-",
52
+ version: tool.version,
53
+ location: tool.cachePath,
54
+ scope,
55
+ });
56
+ }
57
+
58
+ return tools;
59
+ }
60
+
61
+ /**
62
+ * List command handler
63
+ */
64
+ async function listHandler(options: ListOptions, ctx: CommandContext): Promise<void> {
65
+ const allTools: ToolInfo[] = [];
66
+
67
+ if (options.global) {
68
+ // Global tools (via ~/.enact/tools.json)
69
+ const globalTools = listToolsFromRegistry("global");
70
+ allTools.push(...globalTools);
71
+ } else {
72
+ // Project tools (via .enact/tools.json)
73
+ const projectTools = listToolsFromRegistry("project", ctx.cwd);
74
+ allTools.push(...projectTools);
75
+ }
76
+
77
+ // Output
78
+ if (options.json) {
79
+ json(allTools);
80
+ return;
81
+ }
82
+
83
+ if (allTools.length === 0) {
84
+ if (options.global) {
85
+ info("No global tools installed.");
86
+ dim("Install globally with 'enact install <tool> -g'");
87
+ } else {
88
+ info("No project tools installed.");
89
+ dim("Install with 'enact install <tool>' or use '-g' for global");
90
+ }
91
+ return;
92
+ }
93
+
94
+ header(options.global ? "Global Tools" : "Project Tools");
95
+ newline();
96
+
97
+ const columns: TableColumn[] = [
98
+ { key: "name", header: "Name", width: 28 },
99
+ { key: "description", header: "Description", width: 50 },
100
+ ];
101
+
102
+ if (options.verbose) {
103
+ columns.push({ key: "version", header: "Version", width: 10 });
104
+ columns.push({ key: "location", header: "Location", width: 40 });
105
+ }
106
+
107
+ table(allTools, columns);
108
+ newline();
109
+ dim(`Total: ${allTools.length} tool(s)`);
110
+ }
111
+
112
+ /**
113
+ * Configure the list command
114
+ */
115
+ export function configureListCommand(program: Command): void {
116
+ program
117
+ .command("list")
118
+ .alias("ls")
119
+ .description("List installed tools")
120
+ .option("-g, --global", "List global tools (via ~/.enact/tools.json)")
121
+ .option("-v, --verbose", "Show detailed output including paths")
122
+ .option("--json", "Output as JSON")
123
+ .action(async (options: ListOptions) => {
124
+ const ctx: CommandContext = {
125
+ cwd: process.cwd(),
126
+ options,
127
+ isCI: Boolean(process.env.CI),
128
+ isInteractive: process.stdout.isTTY ?? false,
129
+ };
130
+
131
+ try {
132
+ await listHandler(options, ctx);
133
+ } catch (err) {
134
+ error(formatError(err));
135
+ process.exit(1);
136
+ }
137
+ });
138
+ }
@@ -0,0 +1,350 @@
1
+ /**
2
+ * enact publish command
3
+ *
4
+ * Publish a tool to the Enact registry using v2 multipart upload.
5
+ */
6
+
7
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
8
+ import { dirname, join, relative, resolve } from "node:path";
9
+ import { createApiClient, publishTool } from "@enactprotocol/api";
10
+ import { getSecret } from "@enactprotocol/secrets";
11
+ import {
12
+ type LoadedManifest,
13
+ type ToolManifest,
14
+ loadConfig,
15
+ loadManifest,
16
+ loadManifestFromDir,
17
+ validateManifest,
18
+ } from "@enactprotocol/shared";
19
+ import type { Command } from "commander";
20
+ import type { CommandContext, GlobalOptions } from "../../types";
21
+ import {
22
+ dim,
23
+ error,
24
+ formatError,
25
+ header,
26
+ info,
27
+ json,
28
+ keyValue,
29
+ newline,
30
+ success,
31
+ warning,
32
+ withSpinner,
33
+ } from "../../utils";
34
+ import { loadGitignore, shouldIgnore } from "../../utils/ignore";
35
+
36
+ /** Auth namespace for token storage */
37
+ const AUTH_NAMESPACE = "enact:auth";
38
+ const ACCESS_TOKEN_KEY = "access_token";
39
+
40
+ interface PublishOptions extends GlobalOptions {
41
+ dryRun?: boolean;
42
+ tag?: string;
43
+ skipAuth?: boolean;
44
+ }
45
+
46
+ /**
47
+ * Recursively collect all files in a directory
48
+ */
49
+ function collectFiles(
50
+ dir: string,
51
+ baseDir: string,
52
+ files: Array<{ path: string; relativePath: string }> = [],
53
+ ignorePatterns: string[] = []
54
+ ): Array<{ path: string; relativePath: string }> {
55
+ const entries = readdirSync(dir, { withFileTypes: true });
56
+
57
+ for (const entry of entries) {
58
+ const fullPath = join(dir, entry.name);
59
+ const relativePath = relative(baseDir, fullPath);
60
+
61
+ // Check if file should be ignored
62
+ if (shouldIgnore(relativePath, entry.name, ignorePatterns)) {
63
+ continue;
64
+ }
65
+
66
+ if (entry.isDirectory()) {
67
+ collectFiles(fullPath, baseDir, files, ignorePatterns);
68
+ } else if (entry.isFile()) {
69
+ files.push({ path: fullPath, relativePath });
70
+ }
71
+ }
72
+
73
+ return files;
74
+ }
75
+
76
+ /**
77
+ * Create a tar.gz bundle from the tool directory
78
+ */
79
+ async function createBundleFromDir(toolDir: string): Promise<Uint8Array> {
80
+ // Load gitignore patterns (ALWAYS_IGNORE is already checked in shouldIgnore)
81
+ const gitignorePatterns = loadGitignore(toolDir);
82
+
83
+ // Collect all files to include (respecting ignore patterns)
84
+ const files = collectFiles(toolDir, toolDir, [], gitignorePatterns);
85
+
86
+ // Use tar to create the bundle (available on all supported platforms)
87
+ const tempDir = join(process.env.TMPDIR ?? "/tmp", `enact-bundle-${Date.now()}`);
88
+ const tempBundle = join(tempDir, "bundle.tar.gz");
89
+
90
+ const { mkdirSync, rmSync } = await import("node:fs");
91
+ mkdirSync(tempDir, { recursive: true });
92
+
93
+ try {
94
+ // Create tar.gz using tar command
95
+ const fileList = files.map((f) => f.relativePath).join("\n");
96
+ const fileListPath = join(tempDir, "files.txt");
97
+ const { writeFileSync } = await import("node:fs");
98
+ writeFileSync(fileListPath, fileList);
99
+
100
+ // Use COPYFILE_DISABLE=1 to prevent macOS from adding AppleDouble (._) files
101
+ const proc = Bun.spawn(["tar", "-czf", tempBundle, "-C", toolDir, "-T", fileListPath], {
102
+ stdout: "pipe",
103
+ stderr: "pipe",
104
+ env: {
105
+ ...process.env,
106
+ COPYFILE_DISABLE: "1", // Prevents macOS extended attributes/resource forks
107
+ },
108
+ });
109
+
110
+ const exitCode = await proc.exited;
111
+
112
+ if (exitCode !== 0) {
113
+ const stderr = await new Response(proc.stderr).text();
114
+ throw new Error(`Failed to create bundle: ${stderr}`);
115
+ }
116
+
117
+ // Read the bundle
118
+ const bundleData = readFileSync(tempBundle);
119
+ return new Uint8Array(bundleData);
120
+ } finally {
121
+ // Clean up temp files
122
+ try {
123
+ rmSync(tempDir, { recursive: true, force: true });
124
+ } catch {
125
+ // Ignore cleanup errors
126
+ }
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Load README from tool directory
132
+ */
133
+ function loadReadme(toolDir: string): string | undefined {
134
+ const readmeNames = ["README.md", "readme.md", "README", "readme"];
135
+
136
+ for (const name of readmeNames) {
137
+ const readmePath = join(toolDir, name);
138
+ if (existsSync(readmePath)) {
139
+ return readFileSync(readmePath, "utf-8");
140
+ }
141
+ }
142
+
143
+ return undefined;
144
+ }
145
+
146
+ /**
147
+ * Load and validate manifest from file or directory
148
+ */
149
+ async function loadAndValidateManifest(
150
+ pathArg: string,
151
+ ctx: CommandContext
152
+ ): Promise<{ manifest: ToolManifest; toolDir: string }> {
153
+ const fullPath = resolve(ctx.cwd, pathArg);
154
+
155
+ if (!existsSync(fullPath)) {
156
+ throw new Error(`Path not found: ${fullPath}`);
157
+ }
158
+
159
+ // Load manifest - handle both files and directories
160
+ let loaded: LoadedManifest | undefined;
161
+ const stats = statSync(fullPath);
162
+ if (stats.isDirectory()) {
163
+ loaded = loadManifestFromDir(fullPath);
164
+ } else {
165
+ loaded = loadManifest(fullPath);
166
+ }
167
+
168
+ if (!loaded) {
169
+ throw new Error(`Could not load manifest from: ${fullPath}`);
170
+ }
171
+
172
+ // Validate manifest
173
+ const validation = validateManifest(loaded.manifest);
174
+ if (!validation.valid) {
175
+ const errors = validation.errors?.map((e) => ` - ${e}`).join("\n");
176
+ throw new Error(`Invalid manifest:\n${errors}`);
177
+ }
178
+
179
+ return {
180
+ manifest: loaded.manifest,
181
+ toolDir: dirname(loaded.filePath),
182
+ };
183
+ }
184
+
185
+ /**
186
+ * Publish command handler
187
+ */
188
+ async function publishHandler(
189
+ pathArg: string,
190
+ options: PublishOptions,
191
+ ctx: CommandContext
192
+ ): Promise<void> {
193
+ // Load and validate manifest
194
+ const { manifest, toolDir } = await loadAndValidateManifest(pathArg, ctx);
195
+
196
+ const toolName = manifest.name;
197
+ const version = manifest.version ?? "0.0.0";
198
+
199
+ header(`Publishing ${toolName}@${version}`);
200
+ newline();
201
+
202
+ // Show what we're publishing
203
+ keyValue("Name", toolName);
204
+ keyValue("Version", version);
205
+ keyValue("Description", manifest.description);
206
+ if (manifest.tags && manifest.tags.length > 0) {
207
+ keyValue("Tags", manifest.tags.join(", "));
208
+ }
209
+ newline();
210
+
211
+ // Get registry URL from config or environment
212
+ const config = loadConfig();
213
+ const registryUrl =
214
+ process.env.ENACT_REGISTRY_URL ??
215
+ config.registry?.url ??
216
+ "https://siikwkfgsmouioodghho.supabase.co/functions/v1";
217
+
218
+ if (options.verbose) {
219
+ keyValue("Registry", registryUrl);
220
+ }
221
+
222
+ // Check for auth token from keyring (skip in local dev mode)
223
+ let authToken: string | undefined;
224
+ if (options.skipAuth) {
225
+ warning("Skipping authentication (local development mode)");
226
+ // For local dev, use the Supabase anon key from environment or default local key
227
+ authToken =
228
+ process.env.SUPABASE_ANON_KEY ??
229
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0";
230
+ } else {
231
+ const secretToken = await getSecret(AUTH_NAMESPACE, ACCESS_TOKEN_KEY);
232
+ authToken = secretToken ?? undefined;
233
+ if (!authToken) {
234
+ // Check config registry authToken
235
+ authToken = config.registry?.authToken;
236
+ }
237
+ if (!authToken) {
238
+ // Also check environment variable for CI/local dev
239
+ authToken = process.env.ENACT_AUTH_TOKEN;
240
+ }
241
+ if (!authToken) {
242
+ // Fallback to official registry anon key if using official registry
243
+ if (registryUrl.includes("siikwkfgsmouioodghho.supabase.co")) {
244
+ authToken =
245
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNpaWt3a2Znc21vdWlvb2RnaGhvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjQ2MTkzMzksImV4cCI6MjA4MDE5NTMzOX0.kxnx6-IPFhmGx6rzNx36vbyhFMFZKP_jFqaDbKnJ_E0";
246
+ }
247
+ }
248
+ if (!authToken) {
249
+ error("Not authenticated. Please run: enact auth login");
250
+ dim("Or set ENACT_AUTH_TOKEN environment variable");
251
+ dim("Or use --skip-auth for local development");
252
+ process.exit(1);
253
+ }
254
+ }
255
+
256
+ const client = createApiClient({ baseUrl: registryUrl });
257
+ if (authToken) {
258
+ client.setAuthToken(authToken);
259
+ }
260
+
261
+ // Dry run mode
262
+ if (options.dryRun) {
263
+ warning("Dry run mode - not actually publishing");
264
+ newline();
265
+ info("Would publish to registry:");
266
+ keyValue("Tool", toolName);
267
+ keyValue("Version", version);
268
+ keyValue("Source", toolDir);
269
+
270
+ // Show files that would be bundled
271
+ const files = collectFiles(toolDir, toolDir);
272
+ info(`Would bundle ${files.length} files`);
273
+ if (options.verbose) {
274
+ for (const file of files.slice(0, 10)) {
275
+ dim(` ${file.relativePath}`);
276
+ }
277
+ if (files.length > 10) {
278
+ dim(` ... and ${files.length - 10} more`);
279
+ }
280
+ }
281
+ return;
282
+ }
283
+
284
+ // Load README if available
285
+ const readme = loadReadme(toolDir);
286
+ if (readme) {
287
+ info("Found README.md");
288
+ }
289
+
290
+ // Create bundle
291
+ const bundle = await withSpinner("Creating bundle...", async () => {
292
+ return await createBundleFromDir(toolDir);
293
+ });
294
+
295
+ info(`Bundle size: ${(bundle.length / 1024).toFixed(1)} KB`);
296
+
297
+ // Publish to registry using v2 multipart API
298
+ const result = await withSpinner("Publishing to registry...", async () => {
299
+ return await publishTool(client, {
300
+ name: toolName,
301
+ manifest: manifest as unknown as Record<string, unknown>,
302
+ bundle,
303
+ readme,
304
+ });
305
+ });
306
+
307
+ // JSON output
308
+ if (options.json) {
309
+ json(result);
310
+ return;
311
+ }
312
+
313
+ // Success output
314
+ newline();
315
+ success(`Published ${result.name}@${result.version}`);
316
+ keyValue("Bundle Hash", result.bundleHash);
317
+ keyValue("Published At", result.publishedAt.toISOString());
318
+ newline();
319
+ dim(`Install with: enact install ${toolName}`);
320
+ }
321
+
322
+ /**
323
+ * Configure the publish command
324
+ */
325
+ export function configurePublishCommand(program: Command): void {
326
+ program
327
+ .command("publish [path]")
328
+ .description("Publish a tool to the Enact registry")
329
+ .option("-n, --dry-run", "Show what would be published without publishing")
330
+ .option("-t, --tag <tag>", "Add a release tag (e.g., latest, beta)")
331
+ .option("-v, --verbose", "Show detailed output")
332
+ .option("--skip-auth", "Skip authentication (for local development)")
333
+ .option("--json", "Output as JSON")
334
+ .action(async (pathArg: string | undefined, options: PublishOptions) => {
335
+ const resolvedPath = pathArg ?? ".";
336
+ const ctx: CommandContext = {
337
+ cwd: process.cwd(),
338
+ options,
339
+ isCI: Boolean(process.env.CI),
340
+ isInteractive: process.stdout.isTTY ?? false,
341
+ };
342
+
343
+ try {
344
+ await publishHandler(resolvedPath, options, ctx);
345
+ } catch (err) {
346
+ error(formatError(err));
347
+ process.exit(1);
348
+ }
349
+ });
350
+ }