@zeroclickai/zam-cli 0.3.15

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.
package/dist/index.js ADDED
@@ -0,0 +1,3456 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { log as log22 } from "@clack/prompts";
5
+ import { Command as Command20 } from "commander";
6
+
7
+ // package.json
8
+ var package_default = {
9
+ name: "@zeroclickai/zam-cli",
10
+ version: "0.3.15",
11
+ type: "module",
12
+ bin: {
13
+ zam: "bin/zam.js",
14
+ "zam-cli": "bin/zam.js"
15
+ },
16
+ files: [
17
+ "bin",
18
+ "dist",
19
+ "skills",
20
+ "hooks"
21
+ ],
22
+ publishConfig: {
23
+ access: "public"
24
+ },
25
+ scripts: {
26
+ build: "tsup src/index.ts --format esm --out-dir dist --clean",
27
+ prepublishOnly: "pnpm run build",
28
+ dev: "tsx src/index.ts",
29
+ cli: "ZAM_API_URL=http://localhost:1111 tsx src/index.ts",
30
+ "test:integration": "vitest run --project integration",
31
+ "test:unit": "vitest run --project unit",
32
+ test: "pnpm run test:integration",
33
+ typecheck: "tsc"
34
+ },
35
+ dependencies: {
36
+ "@clack/prompts": "^0.11.0",
37
+ commander: "^13.0.0",
38
+ open: "^11.0.0",
39
+ "posthog-node": "^5.28.5",
40
+ ws: "^8.20.0",
41
+ zod: "^4.3.5"
42
+ },
43
+ devDependencies: {
44
+ "@types/node": "^25.0.7",
45
+ "@types/ws": "^8.18.1",
46
+ msw: "^2.12.14",
47
+ tsup: "^8.5.1",
48
+ tsx: "^4.21.0",
49
+ typescript: "^5.9.3",
50
+ vitest: "^4.0.17"
51
+ }
52
+ };
53
+
54
+ // src/api-client.ts
55
+ var ApiError = class extends Error {
56
+ constructor(status, message, body) {
57
+ super(message);
58
+ this.status = status;
59
+ this.body = body;
60
+ this.name = "ApiError";
61
+ }
62
+ status;
63
+ body;
64
+ };
65
+ var handleResponse = async (response) => {
66
+ if (!response.ok) {
67
+ const body = await response.json().catch(() => ({ error: "Unknown error" }));
68
+ throw new ApiError(
69
+ response.status,
70
+ body.error ?? `HTTP ${response.status}`,
71
+ body
72
+ );
73
+ }
74
+ if (response.status === 204) {
75
+ return void 0;
76
+ }
77
+ return response.json();
78
+ };
79
+ var createApiClient = (config) => {
80
+ const headers = (hasBody) => {
81
+ const h = {};
82
+ if (hasBody) {
83
+ h["content-type"] = "application/json";
84
+ }
85
+ if (config.apiKey) {
86
+ h["x-zam-api-key"] = config.apiKey;
87
+ }
88
+ return h;
89
+ };
90
+ const request = async (method, path, body) => {
91
+ const url = `${config.baseUrl}${path}`;
92
+ const hasBody = body !== void 0;
93
+ const opts = {
94
+ method,
95
+ headers: headers(hasBody),
96
+ body: hasBody ? JSON.stringify(body) : void 0
97
+ };
98
+ return handleResponse(await fetch(url, opts));
99
+ };
100
+ const get = (path) => request("GET", path);
101
+ const post = (path, body) => request("POST", path, body);
102
+ const patch = (path, body) => request("PATCH", path, body);
103
+ const del = (path) => request("DELETE", path);
104
+ return {
105
+ searchZams: async (params) => {
106
+ const sp = new URLSearchParams();
107
+ if (params?.query) sp.set("query", params.query);
108
+ if (params?.category) sp.set("category", params.category);
109
+ const qs = sp.toString();
110
+ const response = await get(`/v1/marketplace/zams${qs ? `?${qs}` : ""}`);
111
+ return Array.isArray(response) ? response : response.results;
112
+ },
113
+ getMarketplaceZam: (id) => get(`/v1/marketplace/zams/${id}`),
114
+ resolveZid: (zid) => get(
115
+ `/v1/marketplace/resolve/${zid}`
116
+ ),
117
+ commitPreflight: (preflightToken) => post("/v1/orders/", { preflightToken }),
118
+ createPreflight: (listingId, requestBody, providerId) => post("/v1/orders/preflight", {
119
+ listingId,
120
+ requestBody,
121
+ providerId
122
+ }),
123
+ listOrders: () => get("/v1/orders/"),
124
+ getOrder: (id) => get(`/v1/orders/${id}`),
125
+ listApiKeys: () => get("/v1/api-keys/"),
126
+ createApiKey: (name, scopes) => post("/v1/api-keys/", {
127
+ name,
128
+ scopes
129
+ }),
130
+ updateApiKey: (id, data) => patch(`/v1/api-keys/${id}`, data),
131
+ deleteApiKey: (id) => del(`/v1/api-keys/${id}`),
132
+ getOpenApiSpec: () => get("/openapi.json"),
133
+ getSigningSecret: (id) => get(`/v1/zams/${id}/signing-secret`),
134
+ rotateSigningSecret: (id) => post(`/v1/zams/${id}/signing-secret/rotate`),
135
+ getWalletInfo: () => get("/v1/wallets/me"),
136
+ updateWalletProfile: (data) => patch("/v1/wallets/me", data),
137
+ approveDeviceCode: (code) => post(`/v1/device-codes/${code}/approve`),
138
+ createFundingSession: (amount) => post(
139
+ "/v1/wallets/fund",
140
+ amount ? { amount } : {}
141
+ ),
142
+ checkFundingStatus: (sessionId) => get(`/v1/wallets/fund/status/${sessionId}`),
143
+ listProviders: () => get("/v1/zams/"),
144
+ getProvider: (id) => get(`/v1/zams/${id}`),
145
+ createProvider: (data) => post("/v1/zams/", data),
146
+ updateProvider: (id, data) => patch(`/v1/zams/${id}`, data),
147
+ deleteProvider: (id) => del(`/v1/zams/${id}`),
148
+ activate: (zid, input, fromCapability) => {
149
+ const url = fromCapability ? `/v1/activate/${zid}?fromCapability=${fromCapability}` : `/v1/activate/${zid}`;
150
+ return post(url, { input });
151
+ },
152
+ searchCapabilities: async (query) => {
153
+ const sp = new URLSearchParams();
154
+ sp.set("query", query);
155
+ return get(
156
+ `/v1/marketplace/capabilities?${sp.toString()}`
157
+ );
158
+ },
159
+ getCapability: (zid) => get(`/v1/marketplace/capabilities/${zid}`),
160
+ unifiedSearch: async (params) => {
161
+ const sp = new URLSearchParams();
162
+ if (params.query) sp.set("query", params.query);
163
+ if (params.limit) sp.set("limit", String(params.limit));
164
+ const qs = sp.toString();
165
+ return get(
166
+ `/v1/marketplace/search${qs ? `?${qs}` : ""}`
167
+ );
168
+ },
169
+ getZamCapabilities: (id) => get(`/v1/zams/${id}/capabilities`)
170
+ };
171
+ };
172
+
173
+ // src/commands/activate.ts
174
+ import { log as log3 } from "@clack/prompts";
175
+ import { Command } from "commander";
176
+
177
+ // src/auth.ts
178
+ import { log } from "@clack/prompts";
179
+ var requireAuth = (apiKey) => {
180
+ if (!apiKey) {
181
+ log.error(
182
+ "Authentication required.\n\nCreate a wallet with:\n zam wallet create\n\nOr set your API key with:\n zam config set-key <your-api-key>\n\nOr set the ZAM_API_KEY environment variable."
183
+ );
184
+ process.exitCode = 1;
185
+ throw new Error("Authentication required");
186
+ }
187
+ };
188
+
189
+ // src/commands/activate-flow.ts
190
+ import { log as log2, spinner } from "@clack/prompts";
191
+
192
+ // src/format.ts
193
+ var formatMicrodollars = (microdollars) => {
194
+ const dollars = Number(microdollars) / 1e6;
195
+ return `$${dollars.toFixed(4)}`;
196
+ };
197
+ var formatPrice = (price) => {
198
+ if (!price) return "Free";
199
+ const raw = price.amountMicrodollars;
200
+ if (raw === void 0 || raw === null) return "Free";
201
+ const micros = Number(raw);
202
+ if (!Number.isFinite(micros) || micros <= 0) return "Free";
203
+ const unit = price.unit ?? "call";
204
+ return `$${(micros / 1e6).toFixed(2)}/${unit}`;
205
+ };
206
+ var formatDate = (iso) => new Date(iso).toLocaleDateString("en-US", {
207
+ year: "numeric",
208
+ month: "short",
209
+ day: "numeric"
210
+ });
211
+ var formatProviderSummary = (p) => `${p.title} [${p.providerState}]
212
+ ID: ${p.id}
213
+ ZID: ${p.zid}
214
+ Slug: ${p.slug}
215
+ Category: ${p.category ?? "\u2014"} | Price: ${formatPrice(p.price)}
216
+ ` + (p.tags?.length ? ` Tags: ${p.tags.join(", ")}
217
+ ` : "") + (p.description ? ` ${p.description}` : "");
218
+ var formatProviderDetail = (p) => {
219
+ let s = `${p.title}
220
+ ID: ${p.id}
221
+ ZID: ${p.zid}
222
+ Slug: ${p.slug}
223
+ State: ${p.providerState}
224
+ Category: ${p.category ?? "\u2014"}
225
+ Category ID: ${p.categoryId ?? "\u2014"}
226
+ Price: ${formatPrice(p.price)}
227
+ `;
228
+ if (p.tags?.length) s += ` Tags: ${p.tags.join(", ")}
229
+ `;
230
+ if (p.description) s += ` Description: ${p.description}
231
+ `;
232
+ s += `${formatRunContract(p.runContract ?? null)}
233
+ `;
234
+ if (p.reviewRejectionReason) {
235
+ s += ` Review Rejection: ${p.reviewRejectionReason}
236
+ `;
237
+ }
238
+ s += ` Created: ${formatDate(p.createdAt)} | Updated: ${formatDate(p.updatedAt)}`;
239
+ return s;
240
+ };
241
+ var formatOrderSummary = (o) => `Order ${o.id} [${o.orderState}]
242
+ ZAM: ${o.zamId ?? o.providerId ?? o.listingId ?? "\u2014"}
243
+ ` + (o.errorMessage ? ` Error: ${o.errorMessage}
244
+ ` : "") + ` Created: ${formatDate(o.createdAt)}`;
245
+ var formatOrderDetail = (o) => {
246
+ let s = `Order ${o.id}
247
+ State: ${o.orderState}
248
+ ZAM: ${o.zamId ?? o.providerId ?? o.listingId ?? "\u2014"}
249
+ `;
250
+ if (o.requestBody) s += ` Request: ${JSON.stringify(o.requestBody)}
251
+ `;
252
+ if (o.result) s += ` Result: ${JSON.stringify(o.result)}
253
+ `;
254
+ if (o.errorMessage) s += ` Error: ${o.errorMessage}
255
+ `;
256
+ if (o.startedAt) s += ` Started: ${formatDate(o.startedAt)}
257
+ `;
258
+ if (o.completedAt) s += ` Completed: ${formatDate(o.completedAt)}
259
+ `;
260
+ s += ` Created: ${formatDate(o.createdAt)} | Updated: ${formatDate(o.updatedAt)}`;
261
+ return s;
262
+ };
263
+ var formatRunContract = (rc) => {
264
+ if (!rc) return " Run Contract: none";
265
+ const endpoint = "endpointPath" in rc ? rc.endpointPath : void 0;
266
+ let s = endpoint ? ` Run Contract: ${rc.method} ${endpoint}
267
+ ` : ` Run Contract: ${rc.method}
268
+ `;
269
+ if (rc.inputSchema)
270
+ s += ` Input Schema:
271
+ ${JSON.stringify(rc.inputSchema, null, 2).split("\n").map((line) => ` ${line}`).join("\n")}
272
+ `;
273
+ if (rc.outputSchema)
274
+ s += ` Output Schema:
275
+ ${JSON.stringify(rc.outputSchema, null, 2).split("\n").map((line) => ` ${line}`).join("\n")}
276
+ `;
277
+ if (rc.requestExampleJson)
278
+ s += ` Request Example: ${rc.requestExampleJson}
279
+ `;
280
+ if (rc.responseExampleJson)
281
+ s += ` Response Example: ${rc.responseExampleJson}
282
+ `;
283
+ return s.trimEnd();
284
+ };
285
+ var formatApiKeySummary = (k) => `${k.name} (${k.keyPrefix}...)
286
+ ID: ${k.id}
287
+ Scopes: ${k.scopes.join(", ")}
288
+ ` + (k.lastUsedAt ? ` Last used: ${formatDate(k.lastUsedAt)}
289
+ ` : "") + (k.expiresAt ? ` Expires: ${formatDate(k.expiresAt)}` : " No expiry");
290
+
291
+ // src/commands/activate-flow.ts
292
+ var POLL_INTERVAL_MS = 1e3;
293
+ var DEFAULT_TIMEOUT_S = 10;
294
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
295
+ var activateAndPoll = async (client2, identifier, requestBody, timeoutS = DEFAULT_TIMEOUT_S, opts) => {
296
+ const quiet = opts?.json === true;
297
+ const s = spinner();
298
+ let order;
299
+ let activateResponse;
300
+ if (opts?.preflight) {
301
+ if (!quiet) s.start("Running preflight check...");
302
+ try {
303
+ const result = await client2.createPreflight(identifier, requestBody);
304
+ if (!result.accepted) {
305
+ if (!quiet) s.stop("Preflight rejected");
306
+ if (!quiet) log2.error(`Seller rejected: ${result.reason}`);
307
+ return {
308
+ order: void 0,
309
+ activateResponse: void 0,
310
+ success: false,
311
+ errorMessage: `Seller rejected: ${result.reason}`
312
+ };
313
+ }
314
+ if (!quiet) {
315
+ s.stop("Preflight accepted");
316
+ log2.info(
317
+ [
318
+ `Order: ${result.orderId}`,
319
+ `Price: ${formatMicrodollars(result.price.amountMicrodollars)}`,
320
+ `Expires: ${result.expiresAt}`
321
+ ].join("\n")
322
+ );
323
+ }
324
+ if (!quiet) s.start("Committing order...");
325
+ order = await client2.commitPreflight(result.preflightToken);
326
+ if (!quiet) s.stop(`Order committed: ${order.id} [${order.orderState}]`);
327
+ } catch (err) {
328
+ if (!quiet) s.stop("Preflight failed");
329
+ const msg = err instanceof Error ? err.message : "Unknown error";
330
+ if (!quiet) log2.error(msg);
331
+ return {
332
+ order: void 0,
333
+ activateResponse: void 0,
334
+ success: false,
335
+ errorMessage: msg
336
+ };
337
+ }
338
+ } else {
339
+ if (!quiet) s.start("Activating...");
340
+ try {
341
+ activateResponse = await client2.activate(identifier, requestBody ?? {});
342
+ order = activateResponse.order;
343
+ if (!quiet) s.stop(`Order created: ${order.id} [${order.orderState}]`);
344
+ } catch (err) {
345
+ if (err instanceof ApiError && err.status === 422 && err.body) {
346
+ const body = err.body;
347
+ if (body.preflightRejected) {
348
+ if (!quiet) {
349
+ s.stop("Preflight rejected");
350
+ log2.error(err.message);
351
+ if (body.suggestions?.length) {
352
+ log2.warn(
353
+ `${body.suggestions.length} alternative ZAM(s) available:`
354
+ );
355
+ for (const suggestion of body.suggestions) {
356
+ const label = suggestion.zamTitle ?? suggestion.zamZid ?? suggestion.zamId;
357
+ const zid = suggestion.zamZid ?? suggestion.zamId;
358
+ log2.info(` \u2192 ${label} (${zid})`);
359
+ }
360
+ const first = body.suggestions[0];
361
+ if (first) {
362
+ const firstZid = first.zamZid ?? first.zamId;
363
+ log2.info(
364
+ `
365
+ Try this next:
366
+ zam activate ${firstZid} --request-body '...'`
367
+ );
368
+ }
369
+ } else {
370
+ log2.warn("No other ZAMs available in this capability.");
371
+ }
372
+ }
373
+ return {
374
+ order: void 0,
375
+ activateResponse: {
376
+ suggestions: body.suggestions
377
+ },
378
+ success: false,
379
+ errorMessage: err.message
380
+ };
381
+ }
382
+ }
383
+ if (!quiet) s.stop("Failed to create order");
384
+ const msg = err instanceof Error ? err.message : "Unknown error";
385
+ if (!quiet) log2.error(msg);
386
+ return {
387
+ order: void 0,
388
+ activateResponse: void 0,
389
+ success: false,
390
+ errorMessage: msg
391
+ };
392
+ }
393
+ }
394
+ if (order.orderState === "pending" || order.orderState === "running") {
395
+ if (!quiet) s.start("Waiting for result...");
396
+ const deadline = Date.now() + timeoutS * 1e3;
397
+ while (Date.now() < deadline) {
398
+ await sleep(POLL_INTERVAL_MS);
399
+ try {
400
+ order = await client2.getOrder(order.id);
401
+ } catch (err) {
402
+ if (!quiet) s.stop("Failed to poll order");
403
+ if (!quiet)
404
+ log2.error(err instanceof Error ? err.message : "Unknown error");
405
+ return { order, activateResponse, success: false };
406
+ }
407
+ if (order.orderState === "completed" || order.orderState === "failed") {
408
+ break;
409
+ }
410
+ }
411
+ if (!quiet) {
412
+ if (order.orderState === "completed") {
413
+ s.stop("Order completed!");
414
+ } else if (order.orderState === "failed") {
415
+ s.stop("Order failed");
416
+ } else {
417
+ s.stop(
418
+ `Order still ${order.orderState} after ${timeoutS}s. Check later with: zam orders get ${order.id}`
419
+ );
420
+ }
421
+ }
422
+ }
423
+ if (!quiet) log2.info(formatOrderDetail(order));
424
+ return { order, activateResponse, success: order.orderState !== "failed" };
425
+ };
426
+
427
+ // src/commands/activate.ts
428
+ var activateCommand = (client2, apiKey) => new Command("activate").description(
429
+ "Activate a ZAM or Capability (create an order and poll for result)"
430
+ ).argument(
431
+ "<identifier>",
432
+ "ZAM ZID (ZP-*), Capability ZID (ZG-*), UUID, or slug"
433
+ ).option("--request-body <json>", "Request body as JSON string").option(
434
+ "--timeout <seconds>",
435
+ "Polling timeout in seconds (default: 10)",
436
+ String(DEFAULT_TIMEOUT_S)
437
+ ).option(
438
+ "--preflight",
439
+ "Run a preflight check before executing (validates input, gets dynamic price)"
440
+ ).option("--json", "Output as JSON").action(
441
+ async (identifier, options) => {
442
+ requireAuth(apiKey);
443
+ let requestBody;
444
+ if (options.requestBody) {
445
+ try {
446
+ requestBody = JSON.parse(options.requestBody);
447
+ } catch {
448
+ log3.error("Invalid JSON for --request-body");
449
+ process.exitCode = 1;
450
+ return;
451
+ }
452
+ }
453
+ const timeoutS = Number.parseInt(
454
+ options.timeout ?? String(DEFAULT_TIMEOUT_S),
455
+ 10
456
+ );
457
+ const { order, activateResponse, success, errorMessage } = await activateAndPoll(client2, identifier, requestBody, timeoutS, {
458
+ preflight: options.preflight,
459
+ json: options.json
460
+ });
461
+ if (options.json) {
462
+ const output = {};
463
+ if (order) output.order = order;
464
+ if (activateResponse?.fulfilledBy) {
465
+ output.fulfilledBy = activateResponse.fulfilledBy;
466
+ }
467
+ if (activateResponse?.suggestions) {
468
+ output.suggestions = activateResponse.suggestions;
469
+ }
470
+ if (!order && !success) {
471
+ output.error = errorMessage ?? "Activation failed";
472
+ }
473
+ process.stdout.write(`${JSON.stringify(output, null, 2)}
474
+ `);
475
+ }
476
+ if (!success) {
477
+ process.exitCode = 1;
478
+ }
479
+ }
480
+ );
481
+
482
+ // src/commands/agents.ts
483
+ import { createHash } from "crypto";
484
+ import {
485
+ chmodSync,
486
+ cpSync,
487
+ existsSync,
488
+ mkdirSync,
489
+ readdirSync,
490
+ readFileSync,
491
+ writeFileSync
492
+ } from "fs";
493
+ import { homedir } from "os";
494
+ import { dirname, join, relative } from "path";
495
+ import { fileURLToPath } from "url";
496
+ import { confirm, isCancel, log as log4, spinner as spinner2 } from "@clack/prompts";
497
+ import { Command as Command2 } from "commander";
498
+ var TOOLS = [
499
+ { name: "Claude Code", configDir: ".claude" },
500
+ { name: "Codex", configDir: ".codex" },
501
+ { name: "OpenCode", configDir: ".config/opencode" },
502
+ { name: "Cursor", configDir: ".cursor" }
503
+ ];
504
+ var getPackageRoot = () => {
505
+ let dir = dirname(fileURLToPath(import.meta.url));
506
+ while (!existsSync(join(dir, "package.json"))) {
507
+ const parent = dirname(dir);
508
+ if (parent === dir) break;
509
+ dir = parent;
510
+ }
511
+ return dir;
512
+ };
513
+ var getSkillsSourceDir = () => join(getPackageRoot(), "skills");
514
+ var getSkillDirs = (skillsSourceDir) => readdirSync(skillsSourceDir, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("zam-")).map((d) => d.name);
515
+ var getHooksSourceDir = () => join(getPackageRoot(), "hooks");
516
+ var sha256File = (filePath) => createHash("sha256").update(readFileSync(filePath)).digest("hex");
517
+ var verifyFileCopy = (src, dest) => {
518
+ if (!existsSync(dest)) return false;
519
+ return sha256File(src) === sha256File(dest);
520
+ };
521
+ var collectAllFiles = (dir) => {
522
+ const files = [];
523
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
524
+ const fullPath = join(dir, entry.name);
525
+ if (entry.isDirectory()) {
526
+ files.push(...collectAllFiles(fullPath));
527
+ } else {
528
+ files.push(fullPath);
529
+ }
530
+ }
531
+ return files;
532
+ };
533
+ var installHooks = (homeDir) => {
534
+ const home = homeDir ?? homedir();
535
+ const claudeDir = join(home, ".claude");
536
+ if (!existsSync(claudeDir)) {
537
+ return false;
538
+ }
539
+ const zamHooksDir = join(home, ".zam", "hooks");
540
+ mkdirSync(zamHooksDir, { recursive: true });
541
+ const hookSource = join(getHooksSourceDir(), "auto-approve-zam.sh");
542
+ const hookDest = join(zamHooksDir, "auto-approve-zam.sh");
543
+ cpSync(hookSource, hookDest);
544
+ chmodSync(hookDest, 493);
545
+ if (!verifyFileCopy(hookSource, hookDest)) {
546
+ throw new Error(
547
+ `Integrity check failed: ${hookDest} does not match source`
548
+ );
549
+ }
550
+ const settingsPath = join(claudeDir, "settings.json");
551
+ let settings = {};
552
+ if (existsSync(settingsPath)) {
553
+ try {
554
+ settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
555
+ } catch {
556
+ }
557
+ }
558
+ if (!settings.hooks || typeof settings.hooks !== "object") {
559
+ settings.hooks = {};
560
+ }
561
+ const hooks = settings.hooks;
562
+ if (!Array.isArray(hooks.PreToolUse)) {
563
+ hooks.PreToolUse = [];
564
+ }
565
+ const preToolUse = hooks.PreToolUse;
566
+ const zamHookEntry = {
567
+ matcher: "Bash",
568
+ hooks: [
569
+ {
570
+ type: "command",
571
+ command: hookDest
572
+ }
573
+ ]
574
+ };
575
+ const existingIdx = preToolUse.findIndex((entry) => {
576
+ const entryHooks = entry.hooks;
577
+ if (!Array.isArray(entryHooks)) return false;
578
+ return entryHooks.some(
579
+ (h) => typeof h.command === "string" && h.command.includes("auto-approve-zam")
580
+ );
581
+ });
582
+ if (existingIdx >= 0) {
583
+ preToolUse[existingIdx] = zamHookEntry;
584
+ } else {
585
+ preToolUse.push(zamHookEntry);
586
+ }
587
+ writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
588
+ `);
589
+ return true;
590
+ };
591
+ var installSkills = (homeDir) => {
592
+ const home = homeDir ?? homedir();
593
+ const skillsSourceDir = getSkillsSourceDir();
594
+ const skillDirs = getSkillDirs(skillsSourceDir);
595
+ const toolsDetected = [];
596
+ const skillsCopied = [];
597
+ for (const tool of TOOLS) {
598
+ const toolConfigPath = join(home, tool.configDir);
599
+ if (!existsSync(toolConfigPath)) {
600
+ continue;
601
+ }
602
+ const toolSkillsPath = join(toolConfigPath, "skills");
603
+ mkdirSync(toolSkillsPath, { recursive: true });
604
+ toolsDetected.push({ name: tool.name, skillsPath: toolSkillsPath });
605
+ for (const skillDir of skillDirs) {
606
+ const src = join(skillsSourceDir, skillDir);
607
+ const dest = join(toolSkillsPath, skillDir);
608
+ cpSync(src, dest, { recursive: true });
609
+ for (const srcFile of collectAllFiles(src)) {
610
+ const relPath = relative(src, srcFile);
611
+ const destFile = join(dest, relPath);
612
+ if (!verifyFileCopy(srcFile, destFile)) {
613
+ throw new Error(
614
+ `Integrity check failed: ${destFile} does not match source`
615
+ );
616
+ }
617
+ }
618
+ skillsCopied.push(`${tool.name}: ${dest}`);
619
+ }
620
+ }
621
+ return { toolsDetected, skillsCopied, hooksInstalled: false };
622
+ };
623
+ var describeInstallPlan = (homeDir) => {
624
+ const home = homeDir ?? homedir();
625
+ const skillsSourceDir = getSkillsSourceDir();
626
+ const skillDirs = getSkillDirs(skillsSourceDir);
627
+ const lines = [];
628
+ for (const tool of TOOLS) {
629
+ const toolConfigPath = join(home, tool.configDir);
630
+ if (!existsSync(toolConfigPath)) continue;
631
+ const toolSkillsPath = join(toolConfigPath, "skills");
632
+ for (const skillDir of skillDirs) {
633
+ const dest = join(toolSkillsPath, skillDir);
634
+ const exists = existsSync(dest);
635
+ lines.push(
636
+ ` ${tool.name}: ${dest}${exists ? " (overwrite)" : " (new)"}`
637
+ );
638
+ }
639
+ }
640
+ const claudeDir = join(home, ".claude");
641
+ if (existsSync(claudeDir)) {
642
+ const hookDest = join(home, ".zam", "hooks", "auto-approve-zam.sh");
643
+ const exists = existsSync(hookDest);
644
+ lines.push(` Hook: ${hookDest}${exists ? " (overwrite)" : " (new)"}`);
645
+ }
646
+ return lines;
647
+ };
648
+ var agentsCommand = (homeDir) => new Command2("agents").description("Manage agent skills").addCommand(
649
+ new Command2("install").description("Install ZAM skills for AI coding agents").option("-y, --yes", "Skip confirmation prompt").action(async (opts) => {
650
+ const s = spinner2();
651
+ s.start("Detecting AI coding tools...");
652
+ const home = homeDir ?? homedir();
653
+ const hasTools = TOOLS.some((t) => existsSync(join(home, t.configDir)));
654
+ if (!hasTools) {
655
+ s.stop("No supported AI coding tools detected");
656
+ log4.warn(
657
+ "No AI coding tools found. Supported tools:\n - Claude Code (~/.claude/)\n - Codex (~/.codex/)\n - OpenCode (~/.config/opencode/)\n - Cursor (~/.cursor/)"
658
+ );
659
+ return;
660
+ }
661
+ s.stop("Tools detected");
662
+ const planLines = describeInstallPlan(homeDir);
663
+ log4.info(`Files to install:
664
+ ${planLines.join("\n")}`);
665
+ if (!opts.yes) {
666
+ const confirmed = await confirm({
667
+ message: "Proceed with installation?"
668
+ });
669
+ if (isCancel(confirmed) || !confirmed) {
670
+ log4.info("Installation cancelled.");
671
+ return;
672
+ }
673
+ }
674
+ const s2 = spinner2();
675
+ s2.start("Installing skills...");
676
+ const result = installSkills(homeDir);
677
+ s2.stop(
678
+ `Found ${result.toolsDetected.length} tool(s): ${result.toolsDetected.map((t) => t.name).join(", ")}`
679
+ );
680
+ for (const entry of result.skillsCopied) {
681
+ log4.info(` Installed: ${entry}`);
682
+ }
683
+ const hooksInstalled = installHooks(homeDir);
684
+ if (hooksInstalled) {
685
+ log4.info(" Installed auto-approval hook for Claude Code");
686
+ log4.info(
687
+ " Auto-approved: zam search, zam orders list/get, zam openapi, zam config show"
688
+ );
689
+ }
690
+ log4.success(
691
+ `Installed ZAM skills for ${result.toolsDetected.length} tool(s)`
692
+ );
693
+ })
694
+ );
695
+
696
+ // src/commands/api-keys.ts
697
+ import {
698
+ cancel,
699
+ confirm as confirm2,
700
+ isCancel as isCancel2,
701
+ log as log5,
702
+ multiselect,
703
+ spinner as spinner3,
704
+ text
705
+ } from "@clack/prompts";
706
+ import { Command as Command3 } from "commander";
707
+ var ALL_SCOPES = [
708
+ "api_key:create",
709
+ "api_key:read",
710
+ "api_key:update",
711
+ "api_key:delete",
712
+ "zam:create",
713
+ "zam:read",
714
+ "zam:update",
715
+ "zam:delete",
716
+ "order:create",
717
+ "order:read"
718
+ ];
719
+ var apiKeysCommand = (client2, apiKey) => new Command3("api-keys").description("Manage API keys").addCommand(
720
+ new Command3("list").description("List your API keys").option("--json", "Output as JSON").action(async (opts) => {
721
+ requireAuth(apiKey);
722
+ const s = spinner3();
723
+ if (!opts.json) s.start("Fetching API keys...");
724
+ try {
725
+ const keys = await client2.listApiKeys();
726
+ if (opts.json) {
727
+ process.stdout.write(`${JSON.stringify(keys, null, 2)}
728
+ `);
729
+ return;
730
+ }
731
+ s.stop(`Found ${keys.length} API key(s)`);
732
+ if (keys.length === 0) {
733
+ log5.info("No API keys found.");
734
+ return;
735
+ }
736
+ for (const k of keys) {
737
+ log5.info(formatApiKeySummary(k));
738
+ }
739
+ } catch (err) {
740
+ if (!opts.json) s.stop("Failed to fetch API keys");
741
+ log5.error(err instanceof Error ? err.message : "Unknown error");
742
+ process.exitCode = 1;
743
+ }
744
+ })
745
+ ).addCommand(
746
+ new Command3("create").description("Create a new API key").option("--json", "Output as JSON").option("--name <name>", "Key name").option(
747
+ "--scopes <scopes>",
748
+ "Comma-separated scopes (e.g. zam:read,order:create)"
749
+ ).action(
750
+ async (opts) => {
751
+ requireAuth(apiKey);
752
+ const name = opts.name ?? await (async () => {
753
+ const v = await text({
754
+ message: "Key name:",
755
+ validate: (val) => val.length > 0 ? void 0 : "Name is required"
756
+ });
757
+ if (isCancel2(v)) {
758
+ cancel("Cancelled.");
759
+ process.exit(0);
760
+ }
761
+ return v;
762
+ })();
763
+ const scopes = opts.scopes ? opts.scopes.split(",").map((s2) => s2.trim()).filter(Boolean) : await (async () => {
764
+ const selected = await multiselect({
765
+ message: "Select scopes:",
766
+ options: ALL_SCOPES.map((s2) => ({
767
+ value: s2,
768
+ label: s2
769
+ })),
770
+ required: true
771
+ });
772
+ if (isCancel2(selected)) {
773
+ cancel("Cancelled.");
774
+ process.exit(0);
775
+ }
776
+ return selected;
777
+ })();
778
+ const s = spinner3();
779
+ if (!opts.json) s.start("Creating API key...");
780
+ try {
781
+ const result = await client2.createApiKey(name, scopes);
782
+ if (opts.json) {
783
+ process.stdout.write(
784
+ `${JSON.stringify(
785
+ {
786
+ apiKey: result.apiKey,
787
+ plaintextKey: result.plaintextKey
788
+ },
789
+ null,
790
+ 2
791
+ )}
792
+ `
793
+ );
794
+ return;
795
+ }
796
+ s.stop("API key created");
797
+ log5.warning(
798
+ `Save this key \u2014 it will not be shown again:
799
+
800
+ ${result.plaintextKey}
801
+ `
802
+ );
803
+ log5.info(formatApiKeySummary(result.apiKey));
804
+ } catch (err) {
805
+ if (!opts.json) s.stop("Failed to create API key");
806
+ log5.error(err instanceof Error ? err.message : "Unknown error");
807
+ process.exitCode = 1;
808
+ }
809
+ }
810
+ )
811
+ ).addCommand(
812
+ new Command3("update").description("Update an API key").argument("<id>", "API key ID").option("--json", "Output as JSON").option("--name <name>", "New name").option(
813
+ "--scopes <scopes>",
814
+ "Comma-separated new scopes (e.g. zam:read,order:create)"
815
+ ).action(
816
+ async (id, opts) => {
817
+ requireAuth(apiKey);
818
+ const hasFlags = opts.name !== void 0 || opts.scopes !== void 0;
819
+ let nameVal;
820
+ let scopesVal;
821
+ if (hasFlags) {
822
+ nameVal = opts.name;
823
+ scopesVal = opts.scopes ? opts.scopes.split(",").map((s2) => s2.trim()).filter(Boolean) : void 0;
824
+ } else {
825
+ const nameInput = await text({
826
+ message: "New name (leave empty to skip):",
827
+ defaultValue: ""
828
+ });
829
+ if (isCancel2(nameInput)) {
830
+ cancel("Cancelled.");
831
+ process.exit(0);
832
+ }
833
+ nameVal = nameInput || void 0;
834
+ const changeScopes = await confirm2({
835
+ message: "Update scopes?"
836
+ });
837
+ if (isCancel2(changeScopes)) {
838
+ cancel("Cancelled.");
839
+ process.exit(0);
840
+ }
841
+ if (changeScopes) {
842
+ const selected = await multiselect({
843
+ message: "Select new scopes:",
844
+ options: ALL_SCOPES.map((s2) => ({
845
+ value: s2,
846
+ label: s2
847
+ })),
848
+ required: true
849
+ });
850
+ if (isCancel2(selected)) {
851
+ cancel("Cancelled.");
852
+ process.exit(0);
853
+ }
854
+ scopesVal = selected;
855
+ }
856
+ }
857
+ const s = spinner3();
858
+ if (!opts.json) s.start("Updating API key...");
859
+ try {
860
+ const updated = await client2.updateApiKey(id, {
861
+ name: nameVal,
862
+ scopes: scopesVal
863
+ });
864
+ if (opts.json) {
865
+ process.stdout.write(`${JSON.stringify(updated, null, 2)}
866
+ `);
867
+ return;
868
+ }
869
+ s.stop("API key updated");
870
+ log5.success(formatApiKeySummary(updated));
871
+ } catch (err) {
872
+ if (!opts.json) s.stop("Failed to update API key");
873
+ log5.error(err instanceof Error ? err.message : "Unknown error");
874
+ process.exitCode = 1;
875
+ }
876
+ }
877
+ )
878
+ ).addCommand(
879
+ new Command3("delete").description("Delete an API key").argument("<id>", "API key ID").option("--json", "Output as JSON").option("-y, --yes", "Skip confirmation prompt").action(async (id, opts) => {
880
+ requireAuth(apiKey);
881
+ if (!opts.json && !opts.yes) {
882
+ const confirmed = await confirm2({
883
+ message: `Delete API key ${id}?`
884
+ });
885
+ if (isCancel2(confirmed) || !confirmed) {
886
+ cancel("Cancelled.");
887
+ return;
888
+ }
889
+ }
890
+ const s = spinner3();
891
+ if (!opts.json) s.start("Deleting API key...");
892
+ try {
893
+ await client2.deleteApiKey(id);
894
+ if (opts.json) {
895
+ process.stdout.write(
896
+ `${JSON.stringify({ deleted: true, id }, null, 2)}
897
+ `
898
+ );
899
+ return;
900
+ }
901
+ s.stop("API key deleted");
902
+ log5.success(`API key ${id} deleted.`);
903
+ } catch (err) {
904
+ if (!opts.json) s.stop("Failed to delete API key");
905
+ log5.error(err instanceof Error ? err.message : "Unknown error");
906
+ process.exitCode = 1;
907
+ }
908
+ })
909
+ );
910
+
911
+ // src/commands/auth-approve.ts
912
+ import { log as log6, spinner as spinner4 } from "@clack/prompts";
913
+ import { Command as Command4 } from "commander";
914
+ var authApproveCommand = (client2, apiKey) => new Command4("approve").description("Approve a device code to sign in on the dashboard").argument("<code>", "The device code shown on the dashboard").action(async (code) => {
915
+ if (!apiKey) {
916
+ log6.error(
917
+ "No API key configured. Run `zam wallet create` or `zam config set-key` first."
918
+ );
919
+ process.exitCode = 1;
920
+ return;
921
+ }
922
+ const s = spinner4();
923
+ s.start("Approving device code...");
924
+ try {
925
+ await client2.approveDeviceCode(code);
926
+ s.stop("Device code approved!");
927
+ log6.success(
928
+ "Dashboard sign-in approved. You can close your browser prompt."
929
+ );
930
+ } catch (err) {
931
+ s.stop("Failed to approve");
932
+ log6.error(
933
+ err instanceof Error ? err.message : "Failed to approve device code"
934
+ );
935
+ process.exitCode = 1;
936
+ }
937
+ });
938
+
939
+ // src/commands/capabilities.ts
940
+ import { log as log7, spinner as spinner5 } from "@clack/prompts";
941
+ import { Command as Command5 } from "commander";
942
+ var formatCapabilitySummary = (c) => {
943
+ let s = `${c.name} ${c.zid}
944
+ `;
945
+ if (c.description) s += ` ${c.description}
946
+ `;
947
+ return s.trimEnd();
948
+ };
949
+ var capabilitiesCommand = (client2) => new Command5("capabilities").description("Browse and inspect capabilities").addCommand(
950
+ new Command5("list").description("List published capabilities").option("--json", "Output as JSON").action(async (opts) => {
951
+ const s = spinner5();
952
+ if (!opts.json) s.start("Fetching capabilities...");
953
+ try {
954
+ const capabilities = await client2.searchCapabilities("");
955
+ if (opts.json) {
956
+ process.stdout.write(
957
+ `${JSON.stringify(capabilities, null, 2)}
958
+ `
959
+ );
960
+ return;
961
+ }
962
+ s.stop(`Found ${capabilities.length} capability(ies)`);
963
+ if (capabilities.length === 0) {
964
+ log7.info("No capabilities found.");
965
+ return;
966
+ }
967
+ for (const c of capabilities) {
968
+ log7.info(formatCapabilitySummary(c));
969
+ }
970
+ } catch (err) {
971
+ if (!opts.json) s.stop("Failed to fetch capabilities");
972
+ log7.error(err instanceof Error ? err.message : "Unknown error");
973
+ process.exitCode = 1;
974
+ }
975
+ })
976
+ ).addCommand(
977
+ new Command5("inspect").description("Inspect a capability by ZID").argument("<zid>", "Capability ZID (e.g. ZG-sms-texting)").option("--json", "Output as JSON").action(async (zid, opts) => {
978
+ const s = spinner5();
979
+ if (!opts.json) s.start("Fetching capability...");
980
+ try {
981
+ const detail = await client2.getCapability(zid);
982
+ if (opts.json) {
983
+ process.stdout.write(`${JSON.stringify(detail, null, 2)}
984
+ `);
985
+ return;
986
+ }
987
+ s.stop("Capability found");
988
+ let output = `${detail.name}
989
+ `;
990
+ output += ` ZID: ${zid}
991
+ `;
992
+ if (detail.description) {
993
+ output += ` Description: ${detail.description}
994
+ `;
995
+ }
996
+ output += ` Sensitivity: ${detail.sensitivityLevel}
997
+ `;
998
+ output += ` Schema Version: ${detail.schemaVersion}
999
+ `;
1000
+ if (detail.tags?.length) {
1001
+ output += ` Tags: ${detail.tags.join(", ")}
1002
+ `;
1003
+ }
1004
+ if (detail.defaultInputSchema) {
1005
+ output += ` Input Schema:
1006
+ ${JSON.stringify(
1007
+ detail.defaultInputSchema,
1008
+ null,
1009
+ 2
1010
+ ).split("\n").map((line) => ` ${line}`).join("\n")}
1011
+ `;
1012
+ }
1013
+ if (detail.defaultOutputSchema) {
1014
+ output += ` Output Schema:
1015
+ ${JSON.stringify(
1016
+ detail.defaultOutputSchema,
1017
+ null,
1018
+ 2
1019
+ ).split("\n").map((line) => ` ${line}`).join("\n")}
1020
+ `;
1021
+ }
1022
+ log7.info(output);
1023
+ if (detail.members.length > 0) {
1024
+ log7.info(`Members (${detail.members.length}):`);
1025
+ for (const m of detail.members) {
1026
+ const price = formatPrice(m.price);
1027
+ let memberLine = ` ${m.title} ${m.zid} ${price}`;
1028
+ if (m.description) {
1029
+ memberLine += `
1030
+ ${m.description}`;
1031
+ }
1032
+ log7.info(memberLine);
1033
+ }
1034
+ } else {
1035
+ log7.info("No members.");
1036
+ }
1037
+ log7.info(`
1038
+ Activate: zam activate ${zid} --request-body '{...}'`);
1039
+ } catch (err) {
1040
+ if (!opts.json) s.stop("Failed to fetch capability");
1041
+ log7.error(err instanceof Error ? err.message : "Unknown error");
1042
+ process.exitCode = 1;
1043
+ }
1044
+ })
1045
+ ).addCommand(
1046
+ new Command5("search").description("Search capabilities by query").argument("<query>", "Search query").option("--json", "Output as JSON").action(async (query, opts) => {
1047
+ const s = spinner5();
1048
+ if (!opts.json) s.start("Searching capabilities...");
1049
+ try {
1050
+ const capabilities = await client2.searchCapabilities(query);
1051
+ if (opts.json) {
1052
+ process.stdout.write(
1053
+ `${JSON.stringify(capabilities, null, 2)}
1054
+ `
1055
+ );
1056
+ return;
1057
+ }
1058
+ s.stop(`Found ${capabilities.length} capability(ies)`);
1059
+ if (capabilities.length === 0) {
1060
+ log7.info("No capabilities matched your query.");
1061
+ return;
1062
+ }
1063
+ for (const c of capabilities) {
1064
+ log7.info(formatCapabilitySummary(c));
1065
+ }
1066
+ } catch (err) {
1067
+ if (!opts.json) s.stop("Search failed");
1068
+ log7.error(err instanceof Error ? err.message : "Unknown error");
1069
+ process.exitCode = 1;
1070
+ }
1071
+ })
1072
+ );
1073
+
1074
+ // src/commands/config.ts
1075
+ import { cancel as cancel2, isCancel as isCancel3, log as log8, text as text2 } from "@clack/prompts";
1076
+ import { Command as Command6 } from "commander";
1077
+
1078
+ // src/config.ts
1079
+ import {
1080
+ chmodSync as chmodSync2,
1081
+ mkdirSync as mkdirSync2,
1082
+ readFileSync as readFileSync2,
1083
+ writeFileSync as writeFileSync2
1084
+ } from "fs";
1085
+ import { homedir as homedir2 } from "os";
1086
+ import { dirname as dirname2, join as join2 } from "path";
1087
+ import { z } from "zod";
1088
+ var CONFIG_DIR = join2(homedir2(), ".zam");
1089
+ var CONFIG_FILE = join2(CONFIG_DIR, "config.v2.json");
1090
+ var DEFAULT_API_URL = "https://api.zeroclick.am";
1091
+ var configSchema = z.object({
1092
+ apiKey: z.string().optional(),
1093
+ apiUrl: z.string().optional(),
1094
+ telemetry: z.boolean().optional(),
1095
+ telemetryNoticeShown: z.boolean().optional()
1096
+ });
1097
+ var loadConfig = (configPath = CONFIG_FILE) => {
1098
+ try {
1099
+ const raw = readFileSync2(configPath, "utf-8");
1100
+ return configSchema.parse(JSON.parse(raw));
1101
+ } catch {
1102
+ return {};
1103
+ }
1104
+ };
1105
+ var saveConfig = (updates, configPath = CONFIG_FILE) => {
1106
+ const configDir = dirname2(configPath);
1107
+ mkdirSync2(configDir, { recursive: true, mode: 448 });
1108
+ chmodSync2(configDir, 448);
1109
+ const existing = loadConfig(configPath);
1110
+ const merged = { ...existing, ...updates };
1111
+ writeFileSync2(configPath, `${JSON.stringify(merged, null, 2)}
1112
+ `, {
1113
+ mode: 384
1114
+ });
1115
+ chmodSync2(configPath, 384);
1116
+ };
1117
+ var getEffectiveConfig = (configPath = CONFIG_FILE) => {
1118
+ const file = loadConfig(configPath);
1119
+ return {
1120
+ apiUrl: process.env.ZAM_API_URL ?? file.apiUrl ?? DEFAULT_API_URL,
1121
+ apiKey: process.env.ZAM_API_KEY ?? file.apiKey
1122
+ };
1123
+ };
1124
+
1125
+ // src/telemetry.ts
1126
+ import { randomUUID } from "crypto";
1127
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
1128
+ import { join as join3 } from "path";
1129
+ import { PostHog } from "posthog-node";
1130
+ var POSTHOG_KEY = "phc_yzL0ANbc4pgoBNQRCtYIuXXctrzYUSMP4R9zpDDKE72";
1131
+ var POSTHOG_HOST = "https://us.i.posthog.com";
1132
+ var MAX_ERROR_LENGTH = 200;
1133
+ var SHUTDOWN_TIMEOUT_MS = 2e3;
1134
+ var SESSION_FILE = join3(
1135
+ process.env.HOME ?? process.env.USERPROFILE ?? ".",
1136
+ ".zam",
1137
+ "telemetry-id"
1138
+ );
1139
+ var client = null;
1140
+ var isTelemetryEnabled = () => {
1141
+ const envVal = process.env.ZAM_TELEMETRY?.toLowerCase();
1142
+ if (envVal === "off" || envVal === "false" || envVal === "0") return false;
1143
+ const config = loadConfig();
1144
+ if (config.telemetry === false) return false;
1145
+ return true;
1146
+ };
1147
+ var getAnonymousId = () => {
1148
+ try {
1149
+ if (existsSync3(SESSION_FILE)) {
1150
+ return readFileSync3(SESSION_FILE, "utf-8").trim();
1151
+ }
1152
+ } catch {
1153
+ }
1154
+ const id = randomUUID().replace(/-/g, "").slice(0, 16);
1155
+ try {
1156
+ writeFileSync3(SESSION_FILE, id);
1157
+ } catch {
1158
+ }
1159
+ return id;
1160
+ };
1161
+ var getClient = () => {
1162
+ if (!isTelemetryEnabled()) return null;
1163
+ if (!client) {
1164
+ client = new PostHog(POSTHOG_KEY, {
1165
+ host: POSTHOG_HOST,
1166
+ flushAt: 1,
1167
+ flushInterval: 0
1168
+ });
1169
+ }
1170
+ return client;
1171
+ };
1172
+ var trackCommand = (command, properties) => {
1173
+ const ph = getClient();
1174
+ if (!ph) return;
1175
+ ph.capture({
1176
+ distinctId: getAnonymousId(),
1177
+ event: "cli_command",
1178
+ properties: {
1179
+ command,
1180
+ cliVersion: package_default.version,
1181
+ os: process.platform,
1182
+ nodeVersion: process.version,
1183
+ ...properties
1184
+ }
1185
+ });
1186
+ };
1187
+ var trackError = (command, error) => {
1188
+ const ph = getClient();
1189
+ if (!ph) return;
1190
+ ph.capture({
1191
+ distinctId: getAnonymousId(),
1192
+ event: "cli_error",
1193
+ properties: {
1194
+ command,
1195
+ error: error.slice(0, MAX_ERROR_LENGTH),
1196
+ cliVersion: package_default.version,
1197
+ os: process.platform
1198
+ }
1199
+ });
1200
+ };
1201
+ var shutdownTelemetry = async () => {
1202
+ if (client) {
1203
+ const c = client;
1204
+ client = null;
1205
+ await c.shutdown(SHUTDOWN_TIMEOUT_MS);
1206
+ }
1207
+ };
1208
+ var printTelemetryNotice = () => {
1209
+ if (!isTelemetryEnabled()) return;
1210
+ const config = loadConfig();
1211
+ if (config.telemetryNoticeShown) return;
1212
+ console.error(
1213
+ "\n ZAM collects anonymous usage data to improve the CLI.\n Run 'zam config set-telemetry off' or set ZAM_TELEMETRY=off to disable.\n"
1214
+ );
1215
+ saveConfig({ telemetryNoticeShown: true });
1216
+ };
1217
+
1218
+ // src/commands/config.ts
1219
+ var configCommand = (opts) => new Command6("config").description("Manage CLI configuration").addCommand(
1220
+ new Command6("set-key").description("Set the API key").argument("[key]", "API key (zam_... format)").action(async (key) => {
1221
+ const apiKey = key ?? await (async () => {
1222
+ const v = await text2({
1223
+ message: "Enter your ZAM API key:",
1224
+ placeholder: "zam_...",
1225
+ validate: (val) => val.startsWith("zam_") ? void 0 : "API key must start with zam_"
1226
+ });
1227
+ if (isCancel3(v)) {
1228
+ cancel2("Cancelled.");
1229
+ process.exit(0);
1230
+ }
1231
+ return v;
1232
+ })();
1233
+ saveConfig({ apiKey }, opts?.configPath);
1234
+ log8.success("API key saved.");
1235
+ })
1236
+ ).addCommand(
1237
+ new Command6("set-url").description("Set the API base URL").argument("[url]", "API base URL").action(async (url) => {
1238
+ const apiUrl = url ?? await (async () => {
1239
+ const v = await text2({
1240
+ message: "Enter the API base URL:",
1241
+ placeholder: "http://localhost:3000"
1242
+ });
1243
+ if (isCancel3(v)) {
1244
+ cancel2("Cancelled.");
1245
+ process.exit(0);
1246
+ }
1247
+ return v;
1248
+ })();
1249
+ saveConfig({ apiUrl }, opts?.configPath);
1250
+ log8.success("API URL saved.");
1251
+ })
1252
+ ).addCommand(
1253
+ new Command6("set-telemetry").description("Enable or disable anonymous telemetry (on/off)").argument("<value>", "'on' or 'off'").action((value) => {
1254
+ const enabled = value.toLowerCase() === "on";
1255
+ saveConfig({ telemetry: enabled }, opts?.configPath);
1256
+ log8.success(`Telemetry ${enabled ? "enabled" : "disabled"}.`);
1257
+ })
1258
+ ).addCommand(
1259
+ new Command6("show").description("Show current configuration").action(() => {
1260
+ const config = getEffectiveConfig(opts?.configPath);
1261
+ log8.info(`API URL: ${config.apiUrl}`);
1262
+ log8.info(`API Key: ${config.apiKey ?? "(not set)"}`);
1263
+ log8.info(`Telemetry: ${isTelemetryEnabled() ? "on" : "off"}`);
1264
+ })
1265
+ );
1266
+
1267
+ // src/commands/inspect.ts
1268
+ import { log as log9, spinner as spinner6 } from "@clack/prompts";
1269
+ import { Command as Command7 } from "commander";
1270
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1271
+ var fetchZam = async (client2, identifier) => {
1272
+ if (UUID_RE.test(identifier)) {
1273
+ return client2.getMarketplaceZam(identifier);
1274
+ }
1275
+ try {
1276
+ const resolved = await client2.resolveZid(identifier);
1277
+ if (resolved.type === "capability") {
1278
+ return resolved.data;
1279
+ }
1280
+ return client2.getMarketplaceZam(
1281
+ resolved.data.id
1282
+ );
1283
+ } catch {
1284
+ const results = await client2.searchZams({ query: identifier });
1285
+ const match = results.find(
1286
+ (z3) => z3.slug === identifier || z3.title.toLowerCase() === identifier.toLowerCase()
1287
+ );
1288
+ if (match) {
1289
+ return client2.getMarketplaceZam(match.id);
1290
+ }
1291
+ throw new Error(`ZAM "${identifier}" not found`);
1292
+ }
1293
+ };
1294
+ var inspectCommand = (client2) => new Command7("inspect").description(
1295
+ "Inspect a ZAM or Capability \u2014 view schema, pricing, and details before activating"
1296
+ ).argument(
1297
+ "<identifier>",
1298
+ "ZAM or capability ZID, UUID, or slug (e.g. ZPXXXX, ZG-sms-texting, send-sms)"
1299
+ ).option("--json", "Output as JSON (machine-readable)").action(async (identifier, options) => {
1300
+ const s = spinner6();
1301
+ if (!options.json) s.start("Fetching details...");
1302
+ try {
1303
+ const resolved = await fetchZam(client2, identifier);
1304
+ const isCapability = !!(resolved.defaultInputSchema || resolved.name && !resolved.title);
1305
+ const title = resolved.title ?? resolved.name;
1306
+ if (!options.json) s.stop(title);
1307
+ const inputSchema = isCapability ? resolved.defaultInputSchema ?? null : resolved.runContract?.inputSchema ?? null;
1308
+ const outputSchema = isCapability ? resolved.defaultOutputSchema ?? null : resolved.runContract?.outputSchema ?? null;
1309
+ if (options.json) {
1310
+ const output = {
1311
+ type: isCapability ? "capability" : "zam",
1312
+ zid: resolved.zid ?? null,
1313
+ title,
1314
+ description: resolved.description ?? null,
1315
+ inputSchema,
1316
+ outputSchema
1317
+ };
1318
+ if (!isCapability) {
1319
+ output.id = resolved.id;
1320
+ output.category = resolved.category ?? null;
1321
+ output.price = resolved.price ?? null;
1322
+ }
1323
+ process.stdout.write(`${JSON.stringify(output, null, 2)}
1324
+ `);
1325
+ return;
1326
+ }
1327
+ const lines = [];
1328
+ if (isCapability) {
1329
+ lines.push(` Type: Capability`);
1330
+ }
1331
+ if (resolved.zid) lines.push(` ZID: ${resolved.zid}`);
1332
+ if (resolved.id) lines.push(` ID: ${resolved.id}`);
1333
+ if (resolved.category) lines.push(` Category: ${resolved.category}`);
1334
+ if (!isCapability) {
1335
+ lines.push(
1336
+ ` Price: ${formatPrice(resolved.price)}`
1337
+ );
1338
+ }
1339
+ if (resolved.providerState)
1340
+ lines.push(` State: ${resolved.providerState}`);
1341
+ if (Array.isArray(resolved.tags) && resolved.tags.length)
1342
+ lines.push(` Tags: ${resolved.tags.join(", ")}`);
1343
+ if (resolved.description)
1344
+ lines.push(` Description: ${resolved.description}`);
1345
+ if (inputSchema) {
1346
+ lines.push(" Input Schema:");
1347
+ lines.push(
1348
+ JSON.stringify(inputSchema, null, 2).split("\n").map((l) => ` ${l}`).join("\n")
1349
+ );
1350
+ }
1351
+ if (outputSchema) {
1352
+ lines.push(" Output Schema:");
1353
+ lines.push(
1354
+ JSON.stringify(outputSchema, null, 2).split("\n").map((l) => ` ${l}`).join("\n")
1355
+ );
1356
+ }
1357
+ log9.info(lines.join("\n"));
1358
+ const zid = resolved.zid ?? resolved.id;
1359
+ if (inputSchema) {
1360
+ const props = inputSchema.properties;
1361
+ if (props) {
1362
+ const fields = Object.keys(props).map((k) => `"${k}": ...`).join(", ");
1363
+ log9.info(
1364
+ `
1365
+ To activate:
1366
+ zam activate ${zid} --request-body '{${fields}}'`
1367
+ );
1368
+ }
1369
+ } else {
1370
+ log9.info(`
1371
+ To activate:
1372
+ zam activate ${zid}`);
1373
+ }
1374
+ if (isCapability) {
1375
+ log9.info(
1376
+ `
1377
+ This is a capability \u2014 the system will pick the best ZAM and map your input automatically.`
1378
+ );
1379
+ }
1380
+ } catch (err) {
1381
+ if (!options.json) s.stop("Failed");
1382
+ log9.error(err instanceof Error ? err.message : "Unknown error");
1383
+ process.exitCode = 1;
1384
+ }
1385
+ });
1386
+
1387
+ // src/commands/node.ts
1388
+ import { log as log10, spinner as spinner7 } from "@clack/prompts";
1389
+ import { Command as Command8 } from "commander";
1390
+
1391
+ // src/node/bundle-manager.ts
1392
+ import { createHash as createHash2 } from "crypto";
1393
+ import { mkdir, readFile, stat, writeFile } from "fs/promises";
1394
+ import { homedir as homedir3 } from "os";
1395
+ import { dirname as dirname3, join as join4 } from "path";
1396
+ var BUNDLE_DIR = join4(homedir3(), ".zam-node", "bundles");
1397
+ var BundleManager = class {
1398
+ constructor(bundleDir = BUNDLE_DIR) {
1399
+ this.bundleDir = bundleDir;
1400
+ }
1401
+ bundleDir;
1402
+ /** Check if a bundle version is already cached locally. */
1403
+ async hasCached(zamId, version2) {
1404
+ const path = this.bundlePath(zamId, version2);
1405
+ const s = await stat(path).catch(() => null);
1406
+ return s !== null;
1407
+ }
1408
+ /** Download a bundle from the platform, verify SHA-256, and cache it. */
1409
+ async download(bundleUrl, expectedSha256, zamId, version2, apiKey, metadata) {
1410
+ const response = await fetch(bundleUrl, {
1411
+ headers: { "x-zam-api-key": apiKey }
1412
+ });
1413
+ if (!response.ok) {
1414
+ throw new Error(
1415
+ `Bundle download failed: ${response.status} ${response.statusText}`
1416
+ );
1417
+ }
1418
+ const content = Buffer.from(await response.arrayBuffer());
1419
+ const actualSha256 = createHash2("sha256").update(content).digest("hex");
1420
+ if (actualSha256 !== expectedSha256) {
1421
+ throw new Error(
1422
+ `Bundle integrity check failed: expected ${expectedSha256}, got ${actualSha256}`
1423
+ );
1424
+ }
1425
+ const path = this.bundlePath(zamId, version2);
1426
+ await mkdir(dirname3(path), { recursive: true });
1427
+ await writeFile(path, content);
1428
+ await writeFile(
1429
+ this.metaPath(zamId, version2),
1430
+ JSON.stringify({
1431
+ zamId,
1432
+ version: version2,
1433
+ sha256: actualSha256,
1434
+ signature: metadata?.signature,
1435
+ capabilityLevel: metadata?.capabilityLevel,
1436
+ resourceLimits: metadata?.resourceLimits
1437
+ })
1438
+ );
1439
+ return path;
1440
+ }
1441
+ /** Read a cached bundle from disk. */
1442
+ async readBundle(zamId, version2) {
1443
+ const path = this.bundlePath(zamId, version2);
1444
+ const content = await readFile(path, "utf-8");
1445
+ return content;
1446
+ }
1447
+ /** Get the filesystem path for a bundle. */
1448
+ bundlePath(zamId, version2) {
1449
+ return join4(this.bundleDir, zamId, `${version2}.js`);
1450
+ }
1451
+ metaPath(zamId, version2) {
1452
+ return join4(this.bundleDir, zamId, `${version2}.meta.json`);
1453
+ }
1454
+ };
1455
+
1456
+ // src/node/tunnel-client.ts
1457
+ import WebSocket from "ws";
1458
+ var RECONNECT_DELAY_MS = 3e3;
1459
+ var MAX_RECONNECT_DELAY_MS = 3e4;
1460
+ var TunnelClient = class {
1461
+ constructor(opts) {
1462
+ this.opts = opts;
1463
+ }
1464
+ opts;
1465
+ ws = null;
1466
+ reconnectDelay = RECONNECT_DELAY_MS;
1467
+ reconnectTimer = null;
1468
+ stopped = false;
1469
+ /** Connect to the tunnel gateway. Auto-reconnects on disconnect. */
1470
+ connect() {
1471
+ this.stopped = false;
1472
+ this.doConnect();
1473
+ }
1474
+ /** Gracefully disconnect. */
1475
+ disconnect() {
1476
+ this.stopped = true;
1477
+ if (this.reconnectTimer) {
1478
+ clearTimeout(this.reconnectTimer);
1479
+ this.reconnectTimer = null;
1480
+ }
1481
+ if (this.ws) {
1482
+ this.ws.close(1e3, "Client shutdown");
1483
+ this.ws = null;
1484
+ }
1485
+ }
1486
+ /** Send a capabilities message to advertise loaded ZAMs. */
1487
+ sendCapabilities(runtime, loadedZamIds, runtimeVersion) {
1488
+ this.send({
1489
+ type: "capabilities",
1490
+ runtime,
1491
+ runtimeVersion,
1492
+ loadedZamIds
1493
+ });
1494
+ }
1495
+ doConnect() {
1496
+ const tunnelUrl = this.opts.apiUrl.replace("http://", "ws://").replace("https://", "wss://");
1497
+ const url = `${tunnelUrl}/tunnel/${this.opts.nodeId}`;
1498
+ this.opts.log(`Connecting to ${url}...`);
1499
+ const ws = new WebSocket(url);
1500
+ this.ws = ws;
1501
+ ws.on("open", () => {
1502
+ this.opts.log("WebSocket open, authenticating...");
1503
+ this.send({
1504
+ type: "auth",
1505
+ apiKey: this.opts.apiKey,
1506
+ nodeId: this.opts.nodeId,
1507
+ version: this.opts.version ?? "1.0.0"
1508
+ });
1509
+ });
1510
+ ws.on("message", (data) => {
1511
+ try {
1512
+ const msg = JSON.parse(data.toString());
1513
+ this.handleMessage(msg);
1514
+ } catch (err) {
1515
+ this.opts.log(`Invalid message: ${err}`);
1516
+ }
1517
+ });
1518
+ ws.on("close", (code, reason) => {
1519
+ const reasonStr = reason?.toString() ?? "unknown";
1520
+ this.opts.log(`Disconnected: ${code} ${reasonStr}`);
1521
+ this.opts.onDisconnected(code, reasonStr);
1522
+ this.ws = null;
1523
+ this.scheduleReconnect();
1524
+ });
1525
+ ws.on("error", (err) => {
1526
+ this.opts.log(`WebSocket error: ${err.message}`);
1527
+ });
1528
+ }
1529
+ handleMessage(msg) {
1530
+ switch (msg.type) {
1531
+ case "auth_ok":
1532
+ this.opts.log(`Authenticated. Heartbeat: ${msg.heartbeatIntervalMs}ms`);
1533
+ this.reconnectDelay = RECONNECT_DELAY_MS;
1534
+ this.opts.onConnected();
1535
+ break;
1536
+ case "auth_error":
1537
+ this.opts.log(`Auth failed: ${msg.message}`);
1538
+ this.stopped = true;
1539
+ this.opts.onAuthError(msg.message);
1540
+ break;
1541
+ case "ping":
1542
+ this.send({ type: "pong", ts: msg.ts });
1543
+ break;
1544
+ case "request":
1545
+ this.handleRequest(msg);
1546
+ break;
1547
+ case "deploy":
1548
+ this.handleDeploy(msg);
1549
+ break;
1550
+ case "undeploy":
1551
+ this.opts.onUndeploy(msg.zamId).catch(
1552
+ (err) => this.opts.log(`Undeploy error for ${msg.zamId}: ${err}`)
1553
+ );
1554
+ break;
1555
+ }
1556
+ }
1557
+ handleRequest(msg) {
1558
+ this.opts.onRequest(msg).then((response) => {
1559
+ this.send({
1560
+ type: "response",
1561
+ id: msg.id,
1562
+ ...response
1563
+ });
1564
+ }).catch((err) => {
1565
+ this.send({
1566
+ type: "response",
1567
+ id: msg.id,
1568
+ status: 500,
1569
+ headers: { "content-type": "application/json" },
1570
+ body: JSON.stringify({
1571
+ error: err instanceof Error ? err.message : "Internal error"
1572
+ })
1573
+ });
1574
+ });
1575
+ }
1576
+ handleDeploy(msg) {
1577
+ this.opts.onDeploy(msg).then((result) => {
1578
+ this.send({
1579
+ type: "deploy_ack",
1580
+ zamId: msg.zamId,
1581
+ success: result.success,
1582
+ error: result.error
1583
+ });
1584
+ }).catch((err) => {
1585
+ this.send({
1586
+ type: "deploy_ack",
1587
+ zamId: msg.zamId,
1588
+ success: false,
1589
+ error: err instanceof Error ? err.message : "Deploy failed"
1590
+ });
1591
+ });
1592
+ }
1593
+ send(msg) {
1594
+ if (this.ws?.readyState === WebSocket.OPEN) {
1595
+ this.ws.send(JSON.stringify(msg));
1596
+ }
1597
+ }
1598
+ scheduleReconnect() {
1599
+ if (this.stopped) return;
1600
+ this.opts.log(`Reconnecting in ${this.reconnectDelay}ms...`);
1601
+ this.reconnectTimer = setTimeout(() => {
1602
+ this.reconnectTimer = null;
1603
+ this.doConnect();
1604
+ }, this.reconnectDelay);
1605
+ this.reconnectDelay = Math.min(
1606
+ this.reconnectDelay * 2,
1607
+ MAX_RECONNECT_DELAY_MS
1608
+ );
1609
+ }
1610
+ };
1611
+
1612
+ // src/node/workerd-manager.ts
1613
+ import { spawn } from "child_process";
1614
+ import { copyFile, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
1615
+ import { homedir as homedir4 } from "os";
1616
+ import { basename, join as join5 } from "path";
1617
+ var WORKERD_DIR = join5(homedir4(), ".zam-node", "workerd");
1618
+ var DEFAULT_PORT = 8787;
1619
+ var WorkerdManager = class {
1620
+ process = null;
1621
+ workers = /* @__PURE__ */ new Map();
1622
+ port;
1623
+ workerdBinary;
1624
+ configDir;
1625
+ constructor(opts) {
1626
+ this.port = opts?.port ?? DEFAULT_PORT;
1627
+ this.workerdBinary = opts?.workerdBinary ?? "workerd";
1628
+ this.configDir = opts?.configDir ?? WORKERD_DIR;
1629
+ }
1630
+ /** Start the workerd process with current config. */
1631
+ async start() {
1632
+ await this.generateConfig();
1633
+ await this.spawnWorkerd();
1634
+ }
1635
+ /** Stop the workerd process. */
1636
+ async stop() {
1637
+ if (!this.process) return;
1638
+ return new Promise((resolve) => {
1639
+ const proc = this.process;
1640
+ if (!proc) {
1641
+ resolve();
1642
+ return;
1643
+ }
1644
+ const killTimer = setTimeout(() => {
1645
+ proc.kill("SIGKILL");
1646
+ }, 5e3);
1647
+ proc.once("exit", () => {
1648
+ clearTimeout(killTimer);
1649
+ this.process = null;
1650
+ resolve();
1651
+ });
1652
+ proc.kill("SIGTERM");
1653
+ });
1654
+ }
1655
+ /** Deploy a ZAM worker: add to config and restart workerd. */
1656
+ async deployWorker(zamId, bundlePath, capabilityLevel) {
1657
+ this.workers.set(zamId, { zamId, bundlePath, capabilityLevel });
1658
+ await this.restart();
1659
+ }
1660
+ /** Remove a ZAM worker and restart workerd. */
1661
+ async removeWorker(zamId) {
1662
+ this.workers.delete(zamId);
1663
+ await this.restart();
1664
+ }
1665
+ /** Get list of currently loaded ZAM IDs. */
1666
+ getLoadedZamIds() {
1667
+ return [...this.workers.keys()];
1668
+ }
1669
+ /** Check if workerd process is running. */
1670
+ isRunning() {
1671
+ return this.process !== null && this.process.exitCode === null;
1672
+ }
1673
+ /** Execute a request against a loaded worker via HTTP. */
1674
+ async executeRequest(zamId, method, headers, body) {
1675
+ if (!this.workers.has(zamId)) {
1676
+ return {
1677
+ status: 404,
1678
+ headers: { "content-type": "application/json" },
1679
+ body: JSON.stringify({ error: `ZAM ${zamId} not loaded` })
1680
+ };
1681
+ }
1682
+ try {
1683
+ const response = await fetch(`http://127.0.0.1:${this.port}/`, {
1684
+ method,
1685
+ headers: {
1686
+ ...headers,
1687
+ "x-zam-worker": zamId
1688
+ },
1689
+ body: method !== "GET" ? body : void 0,
1690
+ signal: AbortSignal.timeout(5e3)
1691
+ });
1692
+ const responseBody = await response.text();
1693
+ const responseHeaders = {};
1694
+ response.headers.forEach((v, k) => {
1695
+ responseHeaders[k] = v;
1696
+ });
1697
+ return {
1698
+ status: response.status,
1699
+ headers: responseHeaders,
1700
+ body: responseBody
1701
+ };
1702
+ } catch (err) {
1703
+ return {
1704
+ status: 502,
1705
+ headers: { "content-type": "application/json" },
1706
+ body: JSON.stringify({
1707
+ error: err instanceof Error ? err.message : "workerd execution failed"
1708
+ })
1709
+ };
1710
+ }
1711
+ }
1712
+ // --- Private helpers ---
1713
+ async restart() {
1714
+ await this.stop();
1715
+ await this.generateConfig();
1716
+ if (this.workers.size > 0) {
1717
+ await this.spawnWorkerd();
1718
+ }
1719
+ }
1720
+ async generateConfig() {
1721
+ await mkdir2(this.configDir, { recursive: true });
1722
+ await mkdir2(join5(this.configDir, "workers"), { recursive: true });
1723
+ const workerEntries = [];
1724
+ const serviceEntries = [];
1725
+ const routerBindings = [];
1726
+ for (const [zamId, worker] of this.workers) {
1727
+ const safeName = `z${zamId.replace(/-/g, "")}`;
1728
+ const localBundleName = `${zamId.replace(/-/g, "_")}.js`;
1729
+ await copyFile(
1730
+ worker.bundlePath,
1731
+ join5(this.configDir, "workers", localBundleName)
1732
+ );
1733
+ serviceEntries.push(
1734
+ ` (name = "${safeName}", worker = .${safeName}Worker),`
1735
+ );
1736
+ const networkConfig = worker.capabilityLevel === "L0" ? ' globalOutbound = "denied",' : "";
1737
+ workerEntries.push(`
1738
+ const ${safeName}Worker :Workerd.Worker = (
1739
+ compatibilityDate = "2024-01-01",
1740
+ modules = [
1741
+ (name = "index.js", esModule = embed "workers/${localBundleName}"),
1742
+ ],
1743
+ ${networkConfig}
1744
+ );`);
1745
+ routerBindings.push(
1746
+ ` (name = "${safeName}", service = "${safeName}"),`
1747
+ );
1748
+ }
1749
+ const routerCode = `export default {
1750
+ async fetch(request, env) {
1751
+ const zamId = request.headers.get("x-zam-worker");
1752
+ if (!zamId) {
1753
+ return new Response(JSON.stringify({ error: "Missing x-zam-worker header" }), {
1754
+ status: 400,
1755
+ headers: { "content-type": "application/json" }
1756
+ });
1757
+ }
1758
+ const safeName = "z" + zamId.replace(/-/g, "");
1759
+ const service = env[safeName];
1760
+ if (!service) {
1761
+ return new Response(JSON.stringify({ error: "ZAM not loaded: " + zamId }), {
1762
+ status: 404,
1763
+ headers: { "content-type": "application/json" }
1764
+ });
1765
+ }
1766
+ return service.fetch(request);
1767
+ }
1768
+ };`;
1769
+ await writeFile2(join5(this.configDir, "router.js"), routerCode);
1770
+ const hasL0Workers = [...this.workers.values()].some(
1771
+ (w) => w.capabilityLevel === "L0"
1772
+ );
1773
+ const denyServiceEntry = hasL0Workers ? ' (name = "denied", network = .denyAllNetwork),' : "";
1774
+ const denyNetworkConst = hasL0Workers ? `
1775
+ const denyAllNetwork :Workerd.Network = (
1776
+ allow = [],
1777
+ deny = ["public", "private", "local", "network"],
1778
+ );` : "";
1779
+ const config = `using Workerd = import "/workerd/workerd.capnp";
1780
+
1781
+ const config :Workerd.Config = (
1782
+ services = [
1783
+ (name = "router", worker = .routerWorker),
1784
+ ${denyServiceEntry}
1785
+ ${serviceEntries.join("\n")}
1786
+ ],
1787
+ sockets = [
1788
+ (name = "http", address = "127.0.0.1:${this.port}", http = (), service = "router"),
1789
+ ],
1790
+ );
1791
+
1792
+ const routerWorker :Workerd.Worker = (
1793
+ compatibilityDate = "2024-01-01",
1794
+ modules = [
1795
+ (name = "router.js", esModule = embed "router.js"),
1796
+ ],
1797
+ bindings = [
1798
+ ${routerBindings.join("\n")}
1799
+ ],
1800
+ );
1801
+ ${workerEntries.join("\n")}
1802
+ ${denyNetworkConst}
1803
+ `;
1804
+ await writeFile2(join5(this.configDir, "config.capnp"), config);
1805
+ }
1806
+ async spawnWorkerd() {
1807
+ const configPath = join5(this.configDir, "config.capnp");
1808
+ return new Promise((resolve, reject) => {
1809
+ const proc = spawn(
1810
+ this.workerdBinary,
1811
+ ["serve", basename(configPath), "--verbose"],
1812
+ {
1813
+ cwd: this.configDir,
1814
+ stdio: ["ignore", "pipe", "pipe"]
1815
+ }
1816
+ );
1817
+ this.process = proc;
1818
+ let started = false;
1819
+ const startTimeout = setTimeout(() => {
1820
+ if (!started) {
1821
+ started = true;
1822
+ if (proc.exitCode === null) {
1823
+ resolve();
1824
+ } else {
1825
+ reject(
1826
+ new Error(
1827
+ `workerd did not start within 3s (exit: ${proc.exitCode})`
1828
+ )
1829
+ );
1830
+ }
1831
+ }
1832
+ }, 3e3);
1833
+ proc.stderr?.on("data", (data) => {
1834
+ const line = data.toString();
1835
+ if (!started && (line.includes("listening") || line.includes("ready"))) {
1836
+ started = true;
1837
+ clearTimeout(startTimeout);
1838
+ resolve();
1839
+ }
1840
+ });
1841
+ proc.on("error", (err) => {
1842
+ clearTimeout(startTimeout);
1843
+ this.process = null;
1844
+ if (!started) {
1845
+ started = true;
1846
+ reject(new Error(`Failed to start workerd: ${err.message}`));
1847
+ }
1848
+ });
1849
+ proc.on("exit", (code) => {
1850
+ this.process = null;
1851
+ if (!started) {
1852
+ started = true;
1853
+ clearTimeout(startTimeout);
1854
+ reject(new Error(`workerd exited with code ${code}`));
1855
+ }
1856
+ });
1857
+ });
1858
+ }
1859
+ };
1860
+
1861
+ // src/commands/node.ts
1862
+ var nodeCommand = (apiKey, apiUrl) => {
1863
+ const cmd = new Command8("node").description(
1864
+ "Run a ZAM node to serve activations from your machine"
1865
+ );
1866
+ cmd.command("start").description(
1867
+ "Start a ZAM node: connects to the platform via WebSocket tunnel and serves activations"
1868
+ ).option("-n, --name <name>", "Node name", "my-node").option("--node-id <id>", "Existing node ID (skip registration)").option("-p, --port <port>", "workerd listen port", "8787").option("--workerd <path>", "Path to workerd binary", "workerd").option(
1869
+ "--runtime <runtime>",
1870
+ "Runtime: workerd (sandboxed) or builtin (direct)",
1871
+ "workerd"
1872
+ ).action(async (opts) => {
1873
+ if (!apiKey) {
1874
+ log10.error(
1875
+ "API key required. Run `zam config` or set ZAM_API_KEY env var."
1876
+ );
1877
+ process.exitCode = 1;
1878
+ return;
1879
+ }
1880
+ const s = spinner7();
1881
+ const runtime = opts.runtime ?? "workerd";
1882
+ const port = opts.port ?? 8787;
1883
+ let nodeId = opts.nodeId;
1884
+ if (!nodeId) {
1885
+ s.start("Registering node...");
1886
+ try {
1887
+ const res = await fetch(`${apiUrl}/v1/nodes`, {
1888
+ method: "POST",
1889
+ headers: {
1890
+ "content-type": "application/json",
1891
+ "x-zam-api-key": apiKey
1892
+ },
1893
+ body: JSON.stringify({ name: opts.name ?? "my-node" })
1894
+ });
1895
+ if (!res.ok) {
1896
+ const body2 = await res.json().catch(() => ({}));
1897
+ throw new Error(
1898
+ body2.error ?? `HTTP ${res.status}`
1899
+ );
1900
+ }
1901
+ const body = await res.json();
1902
+ nodeId = body.id;
1903
+ s.stop(`Node registered: ${nodeId}`);
1904
+ } catch (err) {
1905
+ s.stop("Failed to register node");
1906
+ log10.error(err instanceof Error ? err.message : "Registration failed");
1907
+ process.exitCode = 1;
1908
+ return;
1909
+ }
1910
+ } else {
1911
+ log10.info(`Using existing node: ${nodeId}`);
1912
+ }
1913
+ const bundleManager = new BundleManager();
1914
+ const workerdManager = runtime === "workerd" ? new WorkerdManager({
1915
+ port: Number(port),
1916
+ workerdBinary: opts.workerd ?? "workerd"
1917
+ }) : null;
1918
+ const loadedZams = /* @__PURE__ */ new Set();
1919
+ const tunnel = new TunnelClient({
1920
+ apiUrl,
1921
+ apiKey,
1922
+ nodeId,
1923
+ version: "1.0.0",
1924
+ log: (msg) => log10.info(`[tunnel] ${msg}`),
1925
+ onConnected: () => {
1926
+ tunnel.sendCapabilities(
1927
+ runtime === "workerd" ? "workerd" : "builtin",
1928
+ [...loadedZams]
1929
+ );
1930
+ },
1931
+ onDisconnected: (_code, _reason) => {
1932
+ },
1933
+ onAuthError: (message) => {
1934
+ log10.error(`Authentication failed: ${message}`);
1935
+ log10.error("Check your API key and node ID. Exiting.");
1936
+ process.exit(1);
1937
+ },
1938
+ onRequest: async (req) => {
1939
+ const zamId = req.headers["x-zam-worker-id"];
1940
+ if (!zamId) {
1941
+ return {
1942
+ status: 400,
1943
+ headers: { "content-type": "application/json" },
1944
+ body: JSON.stringify({
1945
+ error: "Missing x-zam-worker-id header"
1946
+ })
1947
+ };
1948
+ }
1949
+ if (workerdManager && runtime === "workerd") {
1950
+ return workerdManager.executeRequest(
1951
+ zamId,
1952
+ req.method,
1953
+ req.headers,
1954
+ req.body
1955
+ );
1956
+ }
1957
+ return {
1958
+ status: 501,
1959
+ headers: { "content-type": "application/json" },
1960
+ body: JSON.stringify({
1961
+ error: "No runtime available for this ZAM"
1962
+ })
1963
+ };
1964
+ },
1965
+ onDeploy: async (msg) => {
1966
+ log10.info(
1967
+ `[deploy] ZAM ${msg.zamId} v${msg.bundleVersion} (${msg.capabilityLevel})`
1968
+ );
1969
+ try {
1970
+ const hasCached = await bundleManager.hasCached(
1971
+ msg.zamId,
1972
+ msg.bundleVersion
1973
+ );
1974
+ let bundlePath;
1975
+ if (hasCached) {
1976
+ bundlePath = bundleManager.bundlePath(
1977
+ msg.zamId,
1978
+ msg.bundleVersion
1979
+ );
1980
+ log10.info(`[deploy] Using cached bundle for ${msg.zamId}`);
1981
+ } else {
1982
+ log10.info(`[deploy] Downloading bundle for ${msg.zamId}...`);
1983
+ bundlePath = await bundleManager.download(
1984
+ msg.bundleUrl,
1985
+ msg.bundleSha256,
1986
+ msg.zamId,
1987
+ msg.bundleVersion,
1988
+ apiKey,
1989
+ {
1990
+ signature: msg.signature,
1991
+ capabilityLevel: msg.capabilityLevel,
1992
+ resourceLimits: msg.resourceLimits
1993
+ }
1994
+ );
1995
+ log10.info(`[deploy] Bundle saved to ${bundlePath}`);
1996
+ }
1997
+ if (workerdManager) {
1998
+ await workerdManager.deployWorker(
1999
+ msg.zamId,
2000
+ bundlePath,
2001
+ msg.capabilityLevel
2002
+ );
2003
+ log10.info(`[deploy] workerd reloaded with ${msg.zamId}`);
2004
+ }
2005
+ loadedZams.add(msg.zamId);
2006
+ tunnel.sendCapabilities(
2007
+ runtime === "workerd" ? "workerd" : "builtin",
2008
+ [...loadedZams]
2009
+ );
2010
+ return { success: true };
2011
+ } catch (err) {
2012
+ const error = err instanceof Error ? err.message : "Deploy failed";
2013
+ log10.error(`[deploy] Failed: ${error}`);
2014
+ return { success: false, error };
2015
+ }
2016
+ },
2017
+ onUndeploy: async (zamId) => {
2018
+ log10.info(`[undeploy] Removing ZAM ${zamId}`);
2019
+ loadedZams.delete(zamId);
2020
+ if (workerdManager) {
2021
+ await workerdManager.removeWorker(zamId);
2022
+ }
2023
+ tunnel.sendCapabilities(
2024
+ runtime === "workerd" ? "workerd" : "builtin",
2025
+ [...loadedZams]
2026
+ );
2027
+ }
2028
+ });
2029
+ log10.info(`Starting ZAM node (runtime: ${runtime})...`);
2030
+ tunnel.connect();
2031
+ const shutdown = async () => {
2032
+ log10.info("Shutting down...");
2033
+ tunnel.disconnect();
2034
+ if (workerdManager) {
2035
+ await workerdManager.stop();
2036
+ }
2037
+ process.exit(0);
2038
+ };
2039
+ process.on("SIGINT", shutdown);
2040
+ process.on("SIGTERM", shutdown);
2041
+ await new Promise(() => {
2042
+ });
2043
+ });
2044
+ return cmd;
2045
+ };
2046
+
2047
+ // src/commands/openapi.ts
2048
+ import { log as log11, spinner as spinner8 } from "@clack/prompts";
2049
+ import { Command as Command9 } from "commander";
2050
+ var openapiCommand = (client2) => new Command9("openapi").description("Display the API's OpenAPI specification").option("--raw", "Output raw JSON").action(async (options) => {
2051
+ const s = spinner8();
2052
+ s.start("Fetching OpenAPI spec...");
2053
+ try {
2054
+ const spec = await client2.getOpenApiSpec();
2055
+ s.stop("OpenAPI spec loaded");
2056
+ if (options.raw) {
2057
+ console.log(JSON.stringify(spec, null, 2));
2058
+ return;
2059
+ }
2060
+ const info = spec.info;
2061
+ if (info) {
2062
+ log11.info(`${info.title ?? "API"} v${info.version ?? "?"}`);
2063
+ }
2064
+ const paths = spec.paths;
2065
+ if (!paths) {
2066
+ log11.info("No paths found in spec.");
2067
+ return;
2068
+ }
2069
+ const methods = ["get", "post", "put", "patch", "delete"];
2070
+ const rows = [];
2071
+ for (const [path, pathItem] of Object.entries(paths)) {
2072
+ for (const method of methods) {
2073
+ const operation = pathItem[method];
2074
+ if (!operation) continue;
2075
+ const summary = operation.summary ?? operation.description ?? "";
2076
+ rows.push(
2077
+ ` ${method.toUpperCase().padEnd(7)} ${path.padEnd(35)} ${summary}`
2078
+ );
2079
+ }
2080
+ }
2081
+ log11.info(`Endpoints (${rows.length}):
2082
+
2083
+ ${rows.join("\n")}`);
2084
+ } catch (err) {
2085
+ s.stop("Failed to fetch OpenAPI spec");
2086
+ log11.error(err instanceof Error ? err.message : "Unknown error");
2087
+ process.exitCode = 1;
2088
+ }
2089
+ });
2090
+
2091
+ // src/commands/orders.ts
2092
+ import { log as log12, spinner as spinner9 } from "@clack/prompts";
2093
+ import { Command as Command10 } from "commander";
2094
+ var ordersCommand = (client2, apiKey) => new Command10("orders").description("View orders").addCommand(
2095
+ new Command10("list").description("List your orders").option("--json", "Output as JSON").action(async (opts) => {
2096
+ requireAuth(apiKey);
2097
+ const s = spinner9();
2098
+ if (!opts.json) s.start("Fetching orders...");
2099
+ try {
2100
+ const orders = await client2.listOrders();
2101
+ if (opts.json) {
2102
+ process.stdout.write(`${JSON.stringify(orders, null, 2)}
2103
+ `);
2104
+ return;
2105
+ }
2106
+ s.stop(`Found ${orders.length} order(s)`);
2107
+ if (orders.length === 0) {
2108
+ log12.info("No orders found.");
2109
+ return;
2110
+ }
2111
+ for (const o of orders) {
2112
+ log12.info(formatOrderSummary(o));
2113
+ }
2114
+ } catch (err) {
2115
+ if (!opts.json) s.stop("Failed to fetch orders");
2116
+ log12.error(err instanceof Error ? err.message : "Unknown error");
2117
+ process.exitCode = 1;
2118
+ }
2119
+ })
2120
+ ).addCommand(
2121
+ new Command10("get").description("Get order details").argument("<id>", "Order ID").option("--json", "Output as JSON").action(async (id, opts) => {
2122
+ requireAuth(apiKey);
2123
+ const s = spinner9();
2124
+ if (!opts.json) s.start("Fetching order...");
2125
+ try {
2126
+ const order = await client2.getOrder(id);
2127
+ if (opts.json) {
2128
+ process.stdout.write(`${JSON.stringify(order, null, 2)}
2129
+ `);
2130
+ return;
2131
+ }
2132
+ s.stop("Order found");
2133
+ log12.info(formatOrderDetail(order));
2134
+ } catch (err) {
2135
+ if (!opts.json) s.stop("Failed to fetch order");
2136
+ log12.error(err instanceof Error ? err.message : "Unknown error");
2137
+ process.exitCode = 1;
2138
+ }
2139
+ })
2140
+ );
2141
+
2142
+ // src/commands/preflight.ts
2143
+ import { log as log13, spinner as spinner10 } from "@clack/prompts";
2144
+ import { Command as Command11 } from "commander";
2145
+ var preflightCommand = (client2, apiKey) => new Command11("preflight").description(
2146
+ "Run a preflight check against a ZAM (validate input, get dynamic price quote)"
2147
+ ).argument("<identifier>", "ZAM ZID, UUID, or slug (e.g. ZPXXXX, send-sms)").option("--request-body <json>", "Request body as JSON string").option(
2148
+ "--commit",
2149
+ "Automatically commit the preflight and execute the order"
2150
+ ).option(
2151
+ "--timeout <seconds>",
2152
+ "Polling timeout for commit (default: 10)",
2153
+ String(DEFAULT_TIMEOUT_S)
2154
+ ).action(
2155
+ async (identifier, options) => {
2156
+ requireAuth(apiKey);
2157
+ let requestBody;
2158
+ if (options.requestBody) {
2159
+ try {
2160
+ requestBody = JSON.parse(options.requestBody);
2161
+ } catch {
2162
+ log13.error("Invalid JSON for --request-body");
2163
+ process.exitCode = 1;
2164
+ return;
2165
+ }
2166
+ }
2167
+ const s = spinner10();
2168
+ s.start("Running preflight check...");
2169
+ try {
2170
+ const result = await client2.createPreflight(identifier, requestBody);
2171
+ if (!result.accepted) {
2172
+ s.stop("Preflight rejected");
2173
+ log13.error(`Reason: ${result.reason}`);
2174
+ process.exitCode = 1;
2175
+ return;
2176
+ }
2177
+ s.stop("Preflight accepted");
2178
+ log13.info(
2179
+ [
2180
+ `Order ID: ${result.orderId}`,
2181
+ `Quoted Price: ${formatMicrodollars(result.price.amountMicrodollars)}`,
2182
+ `Expires At: ${result.expiresAt}`,
2183
+ `Preflight Token: ${result.preflightToken}`
2184
+ ].join("\n")
2185
+ );
2186
+ if (!options.commit) {
2187
+ log13.info(
2188
+ `
2189
+ To execute this order, run this command again with --commit, or use the API:
2190
+ zam preflight ${identifier} --request-body '...' --commit
2191
+ # Or via API: POST /v1/orders with {"preflightToken": "${result.preflightToken}"}`
2192
+ );
2193
+ return;
2194
+ }
2195
+ s.start("Committing order...");
2196
+ const order = await client2.commitPreflight(result.preflightToken);
2197
+ s.stop(`Order committed: ${order.id} [${order.orderState}]`);
2198
+ if (order.orderState === "pending" || order.orderState === "running") {
2199
+ const timeoutS = Number.parseInt(
2200
+ options.timeout ?? String(DEFAULT_TIMEOUT_S),
2201
+ 10
2202
+ );
2203
+ s.start("Waiting for result...");
2204
+ const deadline = Date.now() + timeoutS * 1e3;
2205
+ let current = order;
2206
+ while (Date.now() < deadline) {
2207
+ await new Promise((r) => setTimeout(r, 1e3));
2208
+ current = await client2.getOrder(order.id);
2209
+ if (current.orderState === "completed" || current.orderState === "failed") {
2210
+ break;
2211
+ }
2212
+ }
2213
+ if (current.orderState === "completed") {
2214
+ s.stop("Order completed!");
2215
+ } else if (current.orderState === "failed") {
2216
+ s.stop("Order failed");
2217
+ } else {
2218
+ s.stop(
2219
+ `Order still ${current.orderState}. Check: zam orders get ${order.id}`
2220
+ );
2221
+ }
2222
+ log13.info(formatOrderDetail(current));
2223
+ if (current.orderState === "failed") {
2224
+ process.exitCode = 1;
2225
+ }
2226
+ } else {
2227
+ log13.info(formatOrderDetail(order));
2228
+ if (order.orderState === "failed") {
2229
+ process.exitCode = 1;
2230
+ }
2231
+ }
2232
+ } catch (err) {
2233
+ s.stop("Preflight failed");
2234
+ log13.error(err instanceof Error ? err.message : "Unknown error");
2235
+ process.exitCode = 1;
2236
+ }
2237
+ }
2238
+ );
2239
+
2240
+ // src/commands/run.ts
2241
+ import { cancel as cancel3, isCancel as isCancel4, log as log14, select, spinner as spinner11 } from "@clack/prompts";
2242
+ import { Command as Command12 } from "commander";
2243
+ var zamToResult = (z3) => ({
2244
+ id: z3.id,
2245
+ zid: z3.zid,
2246
+ title: z3.title,
2247
+ description: z3.description,
2248
+ source: "zam"
2249
+ });
2250
+ var capabilityToResult = (c) => ({
2251
+ id: c.zid,
2252
+ zid: c.zid,
2253
+ title: c.name,
2254
+ description: c.description,
2255
+ source: "capability"
2256
+ });
2257
+ var unifiedToResult = (u) => ({
2258
+ id: u.zid,
2259
+ zid: u.zid,
2260
+ title: u.type === "zam" ? u.title ?? u.name : u.name,
2261
+ description: u.description,
2262
+ source: u.type === "capability" ? "capability" : "zam"
2263
+ });
2264
+ var runCommand = (client2, apiKey) => new Command12("run").description("Search for a ZAM or Capability and activate it in one step").argument("<query>", "Natural language description of what you need").option("--request-body <json>", "Request body as JSON string").option(
2265
+ "--timeout <seconds>",
2266
+ "Polling timeout in seconds (default: 10)",
2267
+ String(DEFAULT_TIMEOUT_S)
2268
+ ).option(
2269
+ "--preflight",
2270
+ "Run a preflight check before executing (validates input, gets dynamic price)"
2271
+ ).option("--json", "Output as JSON").action(
2272
+ async (query, options) => {
2273
+ requireAuth(apiKey);
2274
+ let requestBody;
2275
+ if (options.requestBody) {
2276
+ try {
2277
+ requestBody = JSON.parse(options.requestBody);
2278
+ } catch {
2279
+ log14.error("Invalid JSON for --request-body");
2280
+ process.exitCode = 1;
2281
+ return;
2282
+ }
2283
+ }
2284
+ const timeoutS = Number.parseInt(
2285
+ options.timeout ?? String(DEFAULT_TIMEOUT_S),
2286
+ 10
2287
+ );
2288
+ const s = spinner11();
2289
+ if (!options.json) s.start(`Searching for "${query}"...`);
2290
+ let results;
2291
+ try {
2292
+ let usedUnified = false;
2293
+ results = [];
2294
+ try {
2295
+ const unified = await client2.unifiedSearch({ query });
2296
+ results = unified.results.map(unifiedToResult);
2297
+ usedUnified = true;
2298
+ } catch (err) {
2299
+ if (err instanceof ApiError && err.status === 404) {
2300
+ } else {
2301
+ throw err;
2302
+ }
2303
+ }
2304
+ if (!usedUnified) {
2305
+ const [zams, capabilities] = await Promise.all([
2306
+ client2.searchZams({ query }),
2307
+ client2.searchCapabilities(query).catch(() => [])
2308
+ ]);
2309
+ for (const cap of capabilities) {
2310
+ results.push(capabilityToResult(cap));
2311
+ }
2312
+ for (const zam of zams) {
2313
+ results.push(zamToResult(zam));
2314
+ }
2315
+ }
2316
+ } catch (err) {
2317
+ if (!options.json) s.stop("Search failed");
2318
+ log14.error(err instanceof Error ? err.message : "Unknown error");
2319
+ process.exitCode = 1;
2320
+ return;
2321
+ }
2322
+ if (results.length === 0) {
2323
+ if (!options.json) s.stop("No results found");
2324
+ if (!options.json) {
2325
+ log14.warning(
2326
+ `No ZAMs matched "${query}". Try a broader search with: zam search`
2327
+ );
2328
+ }
2329
+ if (options.json) {
2330
+ process.stdout.write(
2331
+ JSON.stringify({ error: "No results found", query }, null, 2) + "\n"
2332
+ );
2333
+ }
2334
+ process.exitCode = 1;
2335
+ return;
2336
+ }
2337
+ if (!options.json) s.stop(`Found ${results.length} result(s)`);
2338
+ let chosen;
2339
+ if (results.length === 1) {
2340
+ chosen = results[0];
2341
+ if (!options.json)
2342
+ log14.info(
2343
+ `Activating: ${chosen.title} (${chosen.zid ?? chosen.id})`
2344
+ );
2345
+ } else if (!options.json && process.stdout.isTTY) {
2346
+ const selection = await select({
2347
+ message: "Multiple results found. Which one?",
2348
+ options: results.map((r) => ({
2349
+ value: r,
2350
+ label: r.source === "capability" ? `${r.title} (Capability)` : r.title,
2351
+ hint: r.zid ?? r.id
2352
+ }))
2353
+ });
2354
+ if (isCancel4(selection)) {
2355
+ cancel3("Cancelled.");
2356
+ process.exit(0);
2357
+ }
2358
+ chosen = selection;
2359
+ } else {
2360
+ chosen = results[0];
2361
+ if (!options.json)
2362
+ log14.info(
2363
+ `Auto-selected: ${chosen.title} (${chosen.zid ?? chosen.id})`
2364
+ );
2365
+ }
2366
+ const identifier = chosen.zid ?? chosen.id;
2367
+ const { order, activateResponse, success, errorMessage } = await activateAndPoll(client2, identifier, requestBody, timeoutS, {
2368
+ preflight: options.preflight,
2369
+ json: options.json
2370
+ });
2371
+ if (options.json) {
2372
+ const output = {};
2373
+ if (order) output.order = order;
2374
+ if (activateResponse?.fulfilledBy) {
2375
+ output.fulfilledBy = activateResponse.fulfilledBy;
2376
+ }
2377
+ if (activateResponse?.suggestions) {
2378
+ output.suggestions = activateResponse.suggestions;
2379
+ }
2380
+ if (!order && !success) {
2381
+ output.error = errorMessage ?? "Activation failed";
2382
+ }
2383
+ process.stdout.write(`${JSON.stringify(output, null, 2)}
2384
+ `);
2385
+ }
2386
+ if (!success) {
2387
+ process.exitCode = 1;
2388
+ }
2389
+ }
2390
+ );
2391
+
2392
+ // src/commands/search.ts
2393
+ import { cancel as cancel4, isCancel as isCancel5, log as log15, spinner as spinner12, text as text3 } from "@clack/prompts";
2394
+ import { Command as Command13 } from "commander";
2395
+ var zamToResult2 = (z3) => ({
2396
+ id: z3.id,
2397
+ zid: z3.zid,
2398
+ title: z3.title,
2399
+ description: z3.description,
2400
+ category: z3.category,
2401
+ price: z3.price,
2402
+ tags: z3.tags,
2403
+ runContract: z3.runContract,
2404
+ source: "zam"
2405
+ });
2406
+ var capabilityToZam = (c) => ({
2407
+ id: c.zid,
2408
+ zid: c.zid,
2409
+ title: c.name,
2410
+ description: c.description,
2411
+ category: null,
2412
+ price: null,
2413
+ tags: null,
2414
+ runContract: c.inputSchema ? {
2415
+ method: "POST",
2416
+ inputSchema: c.inputSchema,
2417
+ outputSchema: c.outputSchema
2418
+ } : null,
2419
+ source: "capability"
2420
+ });
2421
+ var unifiedToZamResult = (u) => ({
2422
+ id: u.zid,
2423
+ zid: u.zid,
2424
+ title: u.type === "zam" ? u.title ?? u.name : u.name,
2425
+ description: u.description,
2426
+ category: null,
2427
+ price: u.price ?? null,
2428
+ tags: null,
2429
+ runContract: u.inputSchema ? { method: "POST", inputSchema: u.inputSchema } : null,
2430
+ source: u.type === "capability" ? "capability" : "zam"
2431
+ });
2432
+ var getRequiredFields = (rc) => {
2433
+ const schema = rc?.inputSchema;
2434
+ if (!schema) return null;
2435
+ const props = schema.properties;
2436
+ if (!props) return null;
2437
+ const required = schema.required ?? [];
2438
+ if (required.length > 0) return required;
2439
+ return Object.keys(props).length > 0 ? Object.keys(props) : null;
2440
+ };
2441
+ var formatZamResult = (z3, showSchema) => {
2442
+ const sourceLabel = z3.source === "capability" ? z3.memberCount ? `${z3.memberCount} ZAM${z3.memberCount !== 1 ? "s" : ""} in Capability` : "Capability" : "ZAM";
2443
+ const price = formatPrice(z3.price);
2444
+ const tags = z3.tags?.join(", ") ?? "";
2445
+ const catPart = z3.category ? `Category: ${z3.category} | ` : "";
2446
+ const fields = getRequiredFields(z3.runContract);
2447
+ const fieldHint = fields ? ` Fields: ${fields.join(", ")}
2448
+ ` : "";
2449
+ let output = `${z3.title} (${sourceLabel}) ${z3.zid ?? z3.id}
2450
+ ${catPart}Price: ${price}
2451
+ ` + fieldHint + (tags ? ` Tags: ${tags}
2452
+ ` : "") + (z3.description ? ` ${z3.description}` : "");
2453
+ if (showSchema && z3.runContract) {
2454
+ output += `
2455
+ ${formatRunContract(z3.runContract)}`;
2456
+ }
2457
+ return output;
2458
+ };
2459
+ var searchCommand = (client2) => new Command13("search").description("Search the ZeroClick Marketplace").argument("[query]", "Search query").option("--category <category>", "Filter by category").option("--show-schema", "Show input/output schemas for each result").option("--json", "Output results as JSON (machine-readable)").action(
2460
+ async (queryArg, options) => {
2461
+ const query = queryArg ?? await (async () => {
2462
+ const v = await text3({
2463
+ message: "Search query (leave empty to list all):",
2464
+ defaultValue: ""
2465
+ });
2466
+ if (isCancel5(v)) {
2467
+ cancel4("Cancelled.");
2468
+ process.exit(0);
2469
+ }
2470
+ return v || void 0;
2471
+ })();
2472
+ const s = spinner12();
2473
+ if (!options.json) s.start("Searching...");
2474
+ try {
2475
+ let results = [];
2476
+ const useUnified = !options.category;
2477
+ let usedUnified = false;
2478
+ if (useUnified) {
2479
+ try {
2480
+ const unified = await client2.unifiedSearch({ query });
2481
+ results = unified.results.map(unifiedToZamResult);
2482
+ usedUnified = true;
2483
+ } catch (err) {
2484
+ if (err instanceof ApiError && err.status === 404) {
2485
+ } else {
2486
+ throw err;
2487
+ }
2488
+ }
2489
+ }
2490
+ if (!usedUnified) {
2491
+ const [zams, capabilities] = await Promise.all([
2492
+ client2.searchZams({
2493
+ query,
2494
+ category: options.category
2495
+ }),
2496
+ query ? client2.searchCapabilities(query).catch(() => []) : Promise.resolve([])
2497
+ ]);
2498
+ for (const cap of capabilities) {
2499
+ results.push(capabilityToZam(cap));
2500
+ }
2501
+ for (const zam of zams) {
2502
+ results.push(zamToResult2(zam));
2503
+ }
2504
+ }
2505
+ if (!options.json) s.stop(`Found ${results.length} result(s)`);
2506
+ if (results.length === 0) {
2507
+ if (!options.json) log15.info("No results found.");
2508
+ if (options.json)
2509
+ process.stdout.write(`${JSON.stringify([], null, 2)}
2510
+ `);
2511
+ return;
2512
+ }
2513
+ if (options.json) {
2514
+ process.stdout.write(`${JSON.stringify(results, null, 2)}
2515
+ `);
2516
+ return;
2517
+ }
2518
+ for (const r of results) {
2519
+ log15.info(formatZamResult(r, options.showSchema));
2520
+ }
2521
+ } catch (err) {
2522
+ if (!options.json) s.stop("Search failed");
2523
+ log15.error(err instanceof Error ? err.message : "Unknown error");
2524
+ process.exitCode = 1;
2525
+ }
2526
+ }
2527
+ );
2528
+
2529
+ // src/commands/wallet-create.ts
2530
+ import { log as log16, spinner as spinner13 } from "@clack/prompts";
2531
+ import { Command as Command14 } from "commander";
2532
+
2533
+ // src/pow-solver.ts
2534
+ import { createHash as createHash3 } from "crypto";
2535
+ var solvePow = (challenge, difficulty) => {
2536
+ let nonce = 0;
2537
+ while (true) {
2538
+ const nonceStr = nonce.toString(16);
2539
+ const hash = createHash3("sha256").update(challenge + nonceStr).digest();
2540
+ if (countLeadingZeroBits(hash) >= difficulty) {
2541
+ return nonceStr;
2542
+ }
2543
+ nonce++;
2544
+ }
2545
+ };
2546
+ var countLeadingZeroBits = (buf) => {
2547
+ let bits = 0;
2548
+ for (const byte of buf) {
2549
+ if (byte === 0) {
2550
+ bits += 8;
2551
+ } else {
2552
+ bits += Math.clz32(byte) - 24;
2553
+ break;
2554
+ }
2555
+ }
2556
+ return bits;
2557
+ };
2558
+
2559
+ // src/commands/wallet-create.ts
2560
+ var walletCreateCommand = (baseUrl, saveConfig2) => new Command14("create").description("Create a new ZAM wallet").option("--name <name>", "Set a display name for the wallet").action(async (opts) => {
2561
+ const s = spinner13();
2562
+ try {
2563
+ s.start("Fetching challenge...");
2564
+ const challengeRes = await fetch(`${baseUrl}/v1/wallets/challenge`);
2565
+ if (!challengeRes.ok) {
2566
+ s.stop("Failed to fetch challenge");
2567
+ const body = await challengeRes.json().catch(() => ({}));
2568
+ log16.error(
2569
+ body.error ?? `HTTP ${challengeRes.status}`
2570
+ );
2571
+ process.exitCode = 1;
2572
+ return;
2573
+ }
2574
+ const { challengeId, challenge, difficulty } = await challengeRes.json();
2575
+ s.stop("Challenge received");
2576
+ s.start("Solving proof of work...");
2577
+ const nonce = solvePow(challenge, difficulty);
2578
+ s.stop("Proof of work solved");
2579
+ s.start("Creating wallet...");
2580
+ const createRes = await fetch(`${baseUrl}/v1/wallets`, {
2581
+ method: "POST",
2582
+ headers: { "content-type": "application/json" },
2583
+ body: JSON.stringify({
2584
+ challengeId,
2585
+ nonce,
2586
+ ...opts.name ? { displayName: opts.name } : {}
2587
+ })
2588
+ });
2589
+ if (!createRes.ok) {
2590
+ s.stop("Failed to create wallet");
2591
+ const body = await createRes.json().catch(() => ({}));
2592
+ log16.error(
2593
+ body.error ?? `HTTP ${createRes.status}`
2594
+ );
2595
+ process.exitCode = 1;
2596
+ return;
2597
+ }
2598
+ const { address, apiKey, recoveryPhrase } = await createRes.json();
2599
+ saveConfig2({ apiKey });
2600
+ s.stop("Wallet created!");
2601
+ log16.success(`Address: ${address}`);
2602
+ log16.warn(
2603
+ `SAVE YOUR RECOVERY PHRASE \u2014 you will not see it again:
2604
+
2605
+ ${recoveryPhrase}
2606
+ `
2607
+ );
2608
+ log16.info("API key saved to ~/.zam/config.v2.json");
2609
+ } catch (err) {
2610
+ s.stop("Error");
2611
+ log16.error(
2612
+ err instanceof Error ? err.message : "Failed to create wallet"
2613
+ );
2614
+ process.exitCode = 1;
2615
+ }
2616
+ });
2617
+
2618
+ // src/commands/wallet-fund.ts
2619
+ import { log as log17, spinner as spinner14 } from "@clack/prompts";
2620
+ import { Command as Command15 } from "commander";
2621
+ var walletFundCommand = (client2) => new Command15("fund").description("Fund your wallet via Coinbase Onramp").option("-a, --amount <amount>", "Amount in USD to pre-fill").action(async (opts) => {
2622
+ try {
2623
+ const amount = opts.amount ? Number.parseFloat(opts.amount) : void 0;
2624
+ if (amount !== void 0 && (Number.isNaN(amount) || amount <= 0)) {
2625
+ log17.error("Amount must be a positive number");
2626
+ process.exitCode = 1;
2627
+ return;
2628
+ }
2629
+ log17.info("Creating funding session...");
2630
+ const { sessionId, onrampUrl } = await client2.createFundingSession(amount);
2631
+ log17.info("Opening Coinbase Onramp in your browser...");
2632
+ log17.info(`URL: ${onrampUrl}`);
2633
+ const { spawn: spawn2 } = await import("child_process");
2634
+ const [cmd, args] = process.platform === "darwin" ? ["open", [onrampUrl]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", onrampUrl]] : ["xdg-open", [onrampUrl]];
2635
+ spawn2(cmd, args, {
2636
+ stdio: "ignore",
2637
+ detached: true
2638
+ }).unref();
2639
+ const s = spinner14();
2640
+ s.start("Waiting for funding to complete...");
2641
+ let done = false;
2642
+ while (!done) {
2643
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
2644
+ try {
2645
+ const status = await client2.checkFundingStatus(sessionId);
2646
+ if (status.status === "completed") {
2647
+ s.stop("Funding completed!");
2648
+ const usd = status.amountMicrodollars ? `$${(Number(BigInt(status.amountMicrodollars)) / 1e6).toFixed(2)} USD` : "";
2649
+ log17.success(`Wallet funded with ${usd}`);
2650
+ const wallet = await client2.getWalletInfo();
2651
+ log17.info(
2652
+ `New balance: $${(Number(BigInt(wallet.balanceMicrodollars)) / 1e6).toFixed(2)} USD`
2653
+ );
2654
+ done = true;
2655
+ } else if (status.status === "failed") {
2656
+ s.stop("Funding failed");
2657
+ log17.error(status.failureReason ?? "Unknown failure reason");
2658
+ process.exitCode = 1;
2659
+ done = true;
2660
+ }
2661
+ } catch {
2662
+ }
2663
+ }
2664
+ } catch (err) {
2665
+ log17.error(
2666
+ err instanceof Error ? err.message : "Failed to create funding session"
2667
+ );
2668
+ process.exitCode = 1;
2669
+ }
2670
+ });
2671
+
2672
+ // src/commands/wallet-info.ts
2673
+ import { log as log18 } from "@clack/prompts";
2674
+ import { Command as Command16 } from "commander";
2675
+ var walletInfoCommand = (client2) => new Command16("info").description("Show wallet info").action(async () => {
2676
+ try {
2677
+ const wallet = await client2.getWalletInfo();
2678
+ const { apiKey } = getEffectiveConfig();
2679
+ if (wallet.displayName) {
2680
+ log18.info(`Name: ${wallet.displayName}`);
2681
+ }
2682
+ log18.info(`Address: ${wallet.address}`);
2683
+ log18.info(`Balance: ${formatBalance(wallet.balanceMicrodollars)}`);
2684
+ log18.info(`API Key: ${apiKey ?? "(not set)"}`);
2685
+ log18.info(`Created: ${wallet.createdAt}`);
2686
+ } catch (err) {
2687
+ log18.error(
2688
+ err instanceof Error ? err.message : "Failed to fetch wallet info"
2689
+ );
2690
+ process.exitCode = 1;
2691
+ }
2692
+ });
2693
+ var formatBalance = (microdollars) => {
2694
+ const usd = Number(BigInt(microdollars)) / 1e6;
2695
+ return `$${usd.toFixed(2)} USD`;
2696
+ };
2697
+
2698
+ // src/commands/wallet-recover.ts
2699
+ import { log as log19, spinner as spinner15, text as text4 } from "@clack/prompts";
2700
+ import { Command as Command17 } from "commander";
2701
+ var walletRecoverCommand = (baseUrl, saveConfig2) => new Command17("recover").description("Recover wallet access with recovery phrase").action(async () => {
2702
+ const phrase = await text4({
2703
+ message: "Enter your 12-word recovery phrase:"
2704
+ });
2705
+ if (typeof phrase !== "string") {
2706
+ return;
2707
+ }
2708
+ const s = spinner15();
2709
+ s.start("Recovering wallet...");
2710
+ try {
2711
+ const res = await fetch(`${baseUrl}/v1/wallets/recover`, {
2712
+ method: "POST",
2713
+ headers: { "content-type": "application/json" },
2714
+ body: JSON.stringify({ recoveryPhrase: phrase.trim() })
2715
+ });
2716
+ if (!res.ok) {
2717
+ s.stop("Recovery failed");
2718
+ const body = await res.json().catch(() => ({}));
2719
+ log19.error(body.error ?? `HTTP ${res.status}`);
2720
+ process.exitCode = 1;
2721
+ return;
2722
+ }
2723
+ const { apiKey } = await res.json();
2724
+ saveConfig2({ apiKey });
2725
+ s.stop("Wallet recovered!");
2726
+ log19.success("New API key saved to ~/.zam/config.v2.json");
2727
+ log19.info("All previous API keys have been revoked.");
2728
+ } catch (err) {
2729
+ s.stop("Error");
2730
+ log19.error(err instanceof Error ? err.message : "Recovery failed");
2731
+ process.exitCode = 1;
2732
+ }
2733
+ });
2734
+
2735
+ // src/commands/wallet-update.ts
2736
+ import { log as log20 } from "@clack/prompts";
2737
+ import { Command as Command18 } from "commander";
2738
+ var walletUpdateCommand = (client2) => new Command18("update").description("Update wallet profile").option("--name <name>", "Set display name").option("--clear-name", "Clear display name").action(async (opts) => {
2739
+ try {
2740
+ if (!opts.name && !opts.clearName) {
2741
+ log20.error("Provide --name <name> or --clear-name");
2742
+ process.exitCode = 1;
2743
+ return;
2744
+ }
2745
+ const displayName = opts.clearName ? null : opts.name ?? null;
2746
+ const wallet = await client2.updateWalletProfile({ displayName });
2747
+ if (wallet.displayName) {
2748
+ log20.success(`Display name set to "${wallet.displayName}"`);
2749
+ } else {
2750
+ log20.success("Display name cleared");
2751
+ }
2752
+ } catch (err) {
2753
+ log20.error(
2754
+ err instanceof Error ? err.message : "Failed to update profile"
2755
+ );
2756
+ process.exitCode = 1;
2757
+ }
2758
+ });
2759
+
2760
+ // src/commands/zams.ts
2761
+ import {
2762
+ cancel as cancel5,
2763
+ confirm as confirm3,
2764
+ isCancel as isCancel6,
2765
+ log as log21,
2766
+ select as select2,
2767
+ spinner as spinner16,
2768
+ text as text5
2769
+ } from "@clack/prompts";
2770
+ import { Command as Command19 } from "commander";
2771
+
2772
+ // src/crawl-contract.ts
2773
+ import { z as z2 } from "zod";
2774
+ var serviceContractDataSchema = z2.object({
2775
+ title: z2.string().min(1),
2776
+ description: z2.string().optional(),
2777
+ category: z2.string().optional(),
2778
+ tags: z2.array(z2.string()).optional(),
2779
+ listingState: z2.enum(["draft", "published"]).optional(),
2780
+ price: z2.object({
2781
+ currency: z2.literal("USD"),
2782
+ amountMicrodollars: z2.number(),
2783
+ unit: z2.string()
2784
+ }).optional(),
2785
+ runContract: z2.object({
2786
+ method: z2.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
2787
+ endpointPath: z2.string(),
2788
+ inputSchema: z2.record(z2.string(), z2.unknown()).optional(),
2789
+ outputSchema: z2.record(z2.string(), z2.unknown()).optional(),
2790
+ requestExampleJson: z2.string().optional(),
2791
+ responseExampleJson: z2.string().optional(),
2792
+ maxExecutionTimeMs: z2.number().optional(),
2793
+ healthEndpoint: z2.string().url().optional()
2794
+ })
2795
+ });
2796
+ var serviceContractSchema = z2.object({
2797
+ provider: serviceContractDataSchema.optional(),
2798
+ listing: serviceContractDataSchema.optional()
2799
+ }).refine((data) => data.provider || data.listing, {
2800
+ message: 'Contract must include a "provider" or "listing" key with service details'
2801
+ }).transform((data) => {
2802
+ const provider = data.provider ?? data.listing;
2803
+ if (!provider) throw new Error("unreachable");
2804
+ return { provider };
2805
+ });
2806
+ var CRAWL_TIMEOUT_MS = 3e4;
2807
+ var normalizeServiceUrl = (serviceUrl) => {
2808
+ const base = serviceUrl.replace(/\/+$/, "").replace(/\/contract$/, "");
2809
+ return `${base}/contract`;
2810
+ };
2811
+ var crawlServiceContract = async (serviceUrl) => {
2812
+ const contractUrl = normalizeServiceUrl(serviceUrl);
2813
+ let response;
2814
+ try {
2815
+ response = await fetch(contractUrl, {
2816
+ method: "GET",
2817
+ headers: { accept: "application/json" },
2818
+ signal: AbortSignal.timeout(CRAWL_TIMEOUT_MS)
2819
+ });
2820
+ } catch (error) {
2821
+ const message = error instanceof Error ? error.message : "Unknown network error";
2822
+ throw new Error(`Failed to reach service: ${message}`);
2823
+ }
2824
+ if (!response.ok) {
2825
+ throw new Error(`Service returned HTTP ${response.status}`);
2826
+ }
2827
+ let body;
2828
+ try {
2829
+ body = await response.json();
2830
+ } catch {
2831
+ throw new Error("Service returned non-JSON response");
2832
+ }
2833
+ const parsed = serviceContractSchema.safeParse(body);
2834
+ if (!parsed.success) {
2835
+ const paths = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
2836
+ throw new Error(`Invalid contract response from ${contractUrl}: ${paths}`);
2837
+ }
2838
+ return parsed.data;
2839
+ };
2840
+
2841
+ // src/commands/zams.ts
2842
+ var zamsCommand = (client2, apiKey) => new Command19("zams").description("Manage your ZAMs").addCommand(
2843
+ new Command19("list").description("List your ZAMs").option("--json", "Output as JSON").action(async (opts) => {
2844
+ requireAuth(apiKey);
2845
+ const s = spinner16();
2846
+ if (!opts.json) s.start("Fetching ZAMs...");
2847
+ try {
2848
+ const providers = await client2.listProviders();
2849
+ if (opts.json) {
2850
+ process.stdout.write(`${JSON.stringify(providers, null, 2)}
2851
+ `);
2852
+ return;
2853
+ }
2854
+ s.stop(`Found ${providers.length} ZAM(s)`);
2855
+ if (providers.length === 0) {
2856
+ log21.info("No ZAMs found.");
2857
+ return;
2858
+ }
2859
+ for (const p of providers) {
2860
+ log21.info(formatProviderSummary(p));
2861
+ }
2862
+ } catch (err) {
2863
+ if (!opts.json) s.stop("Failed to fetch ZAMs");
2864
+ log21.error(err instanceof Error ? err.message : "Unknown error");
2865
+ process.exitCode = 1;
2866
+ }
2867
+ })
2868
+ ).addCommand(
2869
+ new Command19("get").description("Get ZAM details").argument("<id>", "ZAM ID").option("--json", "Output as JSON").action(async (id, opts) => {
2870
+ requireAuth(apiKey);
2871
+ const s = spinner16();
2872
+ if (!opts.json) s.start("Fetching ZAM...");
2873
+ try {
2874
+ const provider = await client2.getProvider(id);
2875
+ const memberships = await client2.getZamCapabilities(id).catch(() => []);
2876
+ if (opts.json) {
2877
+ process.stdout.write(
2878
+ `${JSON.stringify({ ...provider, capabilities: memberships }, null, 2)}
2879
+ `
2880
+ );
2881
+ return;
2882
+ }
2883
+ s.stop("ZAM found");
2884
+ log21.info(formatProviderDetail(provider));
2885
+ if (memberships.length > 0) {
2886
+ log21.info(`
2887
+ Capabilities (${memberships.length}):`);
2888
+ for (const m of memberships) {
2889
+ log21.info(
2890
+ ` ${m.capabilityName} ${m.capabilityZid} [${m.membershipStatus}]` + (m.joinedAt ? ` joined ${new Date(m.joinedAt).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" })}` : "")
2891
+ );
2892
+ }
2893
+ }
2894
+ } catch (err) {
2895
+ if (!opts.json) s.stop("Failed to fetch ZAM");
2896
+ log21.error(err instanceof Error ? err.message : "Unknown error");
2897
+ process.exitCode = 1;
2898
+ }
2899
+ })
2900
+ ).addCommand(
2901
+ new Command19("create").description("Create a new ZAM").option("--json", "Output as JSON").option("--title <title>", "ZAM title").option("--description <desc>", "ZAM description").option("--endpoint <url>", "Endpoint URL").option("--method <method>", "HTTP method (default: POST)").option("--tags <tags>", "Comma-separated tags").option("--category <category>", "Category").option("--capability-intent <zid>", "Capability intent ZID").option("--input-schema <json>", "Input schema as JSON string").option("--output-schema <json>", "Output schema as JSON string").action(
2902
+ async (opts) => {
2903
+ requireAuth(apiKey);
2904
+ const title = opts.title ?? await (async () => {
2905
+ const v = await text5({
2906
+ message: "Title:",
2907
+ validate: (val) => val.length > 0 ? void 0 : "Title is required"
2908
+ });
2909
+ if (isCancel6(v)) {
2910
+ cancel5("Cancelled.");
2911
+ process.exit(0);
2912
+ }
2913
+ return v;
2914
+ })();
2915
+ const description = opts.description ?? await (async () => {
2916
+ const v = await text5({
2917
+ message: "Description (optional):",
2918
+ defaultValue: ""
2919
+ });
2920
+ if (isCancel6(v)) {
2921
+ cancel5("Cancelled.");
2922
+ process.exit(0);
2923
+ }
2924
+ return v;
2925
+ })();
2926
+ const category = opts.category ?? await (async () => {
2927
+ const v = await text5({
2928
+ message: "Category (optional):",
2929
+ defaultValue: ""
2930
+ });
2931
+ if (isCancel6(v)) {
2932
+ cancel5("Cancelled.");
2933
+ process.exit(0);
2934
+ }
2935
+ return v;
2936
+ })();
2937
+ const tagsRaw = opts.tags ?? await (async () => {
2938
+ const v = await text5({
2939
+ message: "Tags (comma-separated, optional):",
2940
+ defaultValue: ""
2941
+ });
2942
+ if (isCancel6(v)) {
2943
+ cancel5("Cancelled.");
2944
+ process.exit(0);
2945
+ }
2946
+ return v;
2947
+ })();
2948
+ const tags = tagsRaw ? tagsRaw.split(",").map((t) => t.trim()).filter(Boolean) : void 0;
2949
+ const endpointUrl = opts.endpoint ?? await (async () => {
2950
+ const v = await text5({
2951
+ message: "Endpoint URL (optional):",
2952
+ defaultValue: "",
2953
+ placeholder: "https://your-api.com/run"
2954
+ });
2955
+ if (isCancel6(v)) {
2956
+ cancel5("Cancelled.");
2957
+ process.exit(0);
2958
+ }
2959
+ return v;
2960
+ })();
2961
+ let runContract;
2962
+ if (endpointUrl) {
2963
+ const methodValue = opts.method ? opts.method.toUpperCase() : await (async () => {
2964
+ const v = await select2({
2965
+ message: "HTTP method:",
2966
+ initialValue: "POST",
2967
+ options: [
2968
+ { value: "GET", label: "GET" },
2969
+ { value: "POST", label: "POST" },
2970
+ { value: "PUT", label: "PUT" },
2971
+ { value: "PATCH", label: "PATCH" },
2972
+ { value: "DELETE", label: "DELETE" }
2973
+ ]
2974
+ });
2975
+ if (isCancel6(v)) {
2976
+ cancel5("Cancelled.");
2977
+ process.exit(0);
2978
+ }
2979
+ return v;
2980
+ })();
2981
+ let inputSchema;
2982
+ if (opts.inputSchema) {
2983
+ try {
2984
+ inputSchema = JSON.parse(opts.inputSchema);
2985
+ } catch {
2986
+ log21.error("Invalid JSON for --input-schema");
2987
+ process.exitCode = 1;
2988
+ return;
2989
+ }
2990
+ }
2991
+ let outputSchema;
2992
+ if (opts.outputSchema) {
2993
+ try {
2994
+ outputSchema = JSON.parse(opts.outputSchema);
2995
+ } catch {
2996
+ log21.error("Invalid JSON for --output-schema");
2997
+ process.exitCode = 1;
2998
+ return;
2999
+ }
3000
+ }
3001
+ runContract = {
3002
+ method: methodValue,
3003
+ endpointPath: endpointUrl,
3004
+ ...inputSchema && { inputSchema },
3005
+ ...outputSchema && { outputSchema }
3006
+ };
3007
+ }
3008
+ const s = spinner16();
3009
+ if (!opts.json) s.start("Creating ZAM...");
3010
+ try {
3011
+ const provider = await client2.createProvider({
3012
+ title,
3013
+ description: description || void 0,
3014
+ category: category || void 0,
3015
+ tags,
3016
+ runContract,
3017
+ capabilityIntent: opts.capabilityIntent
3018
+ });
3019
+ if (opts.json) {
3020
+ process.stdout.write(`${JSON.stringify(provider, null, 2)}
3021
+ `);
3022
+ return;
3023
+ }
3024
+ s.stop("ZAM created");
3025
+ log21.success(formatProviderDetail(provider));
3026
+ if (provider.providerState === "pending_review") {
3027
+ log21.info(
3028
+ "ZAM submitted for review. It will be published after passing safety and classification checks."
3029
+ );
3030
+ }
3031
+ } catch (err) {
3032
+ if (!opts.json) s.stop("Failed to create ZAM");
3033
+ log21.error(err instanceof Error ? err.message : "Unknown error");
3034
+ process.exitCode = 1;
3035
+ }
3036
+ }
3037
+ )
3038
+ ).addCommand(
3039
+ new Command19("create-from-service").description("Create a ZAM by crawling a service URL").argument(
3040
+ "<url>",
3041
+ "Service base URL (e.g. https://my-service.example.com)"
3042
+ ).option("--json", "Output as JSON").option("-y, --yes", "Skip confirmation prompt").action(
3043
+ async (url, opts) => {
3044
+ requireAuth(apiKey);
3045
+ const s = spinner16();
3046
+ if (!opts.json) s.start("Crawling service contract...");
3047
+ try {
3048
+ const contract = await crawlServiceContract(url);
3049
+ if (!opts.json) s.stop("Contract fetched");
3050
+ const { provider: svc } = contract;
3051
+ if (!opts.json) {
3052
+ log21.info(
3053
+ `Title: ${svc.title}
3054
+ ` + (svc.description ? ` Description: ${svc.description.slice(0, 100)}${svc.description.length > 100 ? "..." : ""}
3055
+ ` : "") + (svc.category ? ` Category: ${svc.category}
3056
+ ` : "") + (svc.runContract?.endpointPath ? ` Endpoint: ${svc.runContract.method} ${svc.runContract.endpointPath}
3057
+ ` : "")
3058
+ );
3059
+ }
3060
+ if (!opts.json && !opts.yes) {
3061
+ const confirmed = await confirm3({
3062
+ message: "Create ZAM from this contract?"
3063
+ });
3064
+ if (isCancel6(confirmed) || !confirmed) {
3065
+ cancel5("Cancelled.");
3066
+ return;
3067
+ }
3068
+ }
3069
+ if (!opts.json) s.start("Creating ZAM...");
3070
+ const provider = await client2.createProvider({
3071
+ title: svc.title,
3072
+ description: svc.description,
3073
+ category: svc.category,
3074
+ tags: svc.tags,
3075
+ price: svc.price,
3076
+ runContract: svc.runContract
3077
+ });
3078
+ if (opts.json) {
3079
+ process.stdout.write(`${JSON.stringify(provider, null, 2)}
3080
+ `);
3081
+ return;
3082
+ }
3083
+ s.stop("ZAM created");
3084
+ log21.success(formatProviderDetail(provider));
3085
+ if (provider.providerState === "pending_review") {
3086
+ log21.info(
3087
+ "ZAM submitted for review. It will be published after passing safety and classification checks."
3088
+ );
3089
+ }
3090
+ } catch (err) {
3091
+ if (!opts.json) s.stop("Failed to create ZAM from service");
3092
+ log21.error(err instanceof Error ? err.message : "Unknown error");
3093
+ process.exitCode = 1;
3094
+ }
3095
+ }
3096
+ )
3097
+ ).addCommand(
3098
+ // Update only exposes title/description/category/state — structural
3099
+ // changes (runContract, tags) require creating a new ZAM.
3100
+ new Command19("update").description("Update a ZAM").argument("<id>", "ZAM ID").option("--json", "Output as JSON").option("--title <title>", "New title").option("--description <desc>", "New description").option("--category <category>", "New category").option("--tags <tags>", "Comma-separated tags").option(
3101
+ "--state <state>",
3102
+ "New state (pending_review, published, paused, archived)"
3103
+ ).action(
3104
+ async (id, opts) => {
3105
+ requireAuth(apiKey);
3106
+ const hasFlags = opts.title !== void 0 || opts.description !== void 0 || opts.category !== void 0 || opts.tags !== void 0 || opts.state !== void 0;
3107
+ let titleVal;
3108
+ let descriptionVal;
3109
+ let categoryVal;
3110
+ let stateVal;
3111
+ if (hasFlags) {
3112
+ titleVal = opts.title;
3113
+ descriptionVal = opts.description;
3114
+ categoryVal = opts.category;
3115
+ stateVal = opts.state;
3116
+ } else {
3117
+ const fetchSpinner = spinner16();
3118
+ fetchSpinner.start("Fetching current ZAM...");
3119
+ let current;
3120
+ try {
3121
+ current = await client2.getProvider(id);
3122
+ fetchSpinner.stop("ZAM loaded");
3123
+ } catch (err) {
3124
+ fetchSpinner.stop("Failed to fetch ZAM");
3125
+ log21.error(err instanceof Error ? err.message : "Unknown error");
3126
+ process.exitCode = 1;
3127
+ return;
3128
+ }
3129
+ const titleInput = await text5({
3130
+ message: "Title:",
3131
+ defaultValue: current.title
3132
+ });
3133
+ if (isCancel6(titleInput)) {
3134
+ cancel5("Cancelled.");
3135
+ process.exit(0);
3136
+ }
3137
+ titleVal = titleInput;
3138
+ const descInput = await text5({
3139
+ message: "Description:",
3140
+ defaultValue: current.description ?? ""
3141
+ });
3142
+ if (isCancel6(descInput)) {
3143
+ cancel5("Cancelled.");
3144
+ process.exit(0);
3145
+ }
3146
+ descriptionVal = descInput || void 0;
3147
+ const catInput = await text5({
3148
+ message: "Category:",
3149
+ defaultValue: current.category ?? ""
3150
+ });
3151
+ if (isCancel6(catInput)) {
3152
+ cancel5("Cancelled.");
3153
+ process.exit(0);
3154
+ }
3155
+ categoryVal = catInput || void 0;
3156
+ const stateOptions = [
3157
+ {
3158
+ value: "pending_review",
3159
+ label: "Pending Review"
3160
+ },
3161
+ { value: "published", label: "Published" },
3162
+ { value: "paused", label: "Paused" },
3163
+ { value: "archived", label: "Archived" }
3164
+ ];
3165
+ const stateInput = await select2({
3166
+ message: "State:",
3167
+ initialValue: stateOptions.find(
3168
+ (o) => o.value === current.providerState
3169
+ )?.value,
3170
+ options: stateOptions
3171
+ });
3172
+ if (isCancel6(stateInput)) {
3173
+ cancel5("Cancelled.");
3174
+ process.exit(0);
3175
+ }
3176
+ stateVal = stateInput;
3177
+ }
3178
+ const updatePayload = {};
3179
+ if (titleVal !== void 0) updatePayload.title = titleVal;
3180
+ if (descriptionVal !== void 0)
3181
+ updatePayload.description = descriptionVal;
3182
+ if (categoryVal !== void 0) updatePayload.category = categoryVal;
3183
+ if (opts.tags !== void 0) {
3184
+ updatePayload.tags = opts.tags.split(",").map((t) => t.trim()).filter(Boolean);
3185
+ }
3186
+ if (stateVal !== void 0) updatePayload.providerState = stateVal;
3187
+ const s = spinner16();
3188
+ if (!opts.json) s.start("Updating ZAM...");
3189
+ try {
3190
+ const updated = await client2.updateProvider(
3191
+ id,
3192
+ updatePayload
3193
+ );
3194
+ if (opts.json) {
3195
+ process.stdout.write(`${JSON.stringify(updated, null, 2)}
3196
+ `);
3197
+ return;
3198
+ }
3199
+ s.stop("ZAM updated");
3200
+ log21.success(formatProviderDetail(updated));
3201
+ } catch (err) {
3202
+ if (!opts.json) s.stop("Failed to update ZAM");
3203
+ log21.error(err instanceof Error ? err.message : "Unknown error");
3204
+ process.exitCode = 1;
3205
+ }
3206
+ }
3207
+ )
3208
+ ).addCommand(
3209
+ new Command19("get-secret").description("Check whether a signing secret exists for a ZAM").argument("<id>", "ZAM ID").action(async (id) => {
3210
+ requireAuth(apiKey);
3211
+ const s = spinner16();
3212
+ s.start("Checking signing secret...");
3213
+ try {
3214
+ const result = await client2.getSigningSecret(id);
3215
+ s.stop("Signing secret checked");
3216
+ log21.info(`Secret exists: ${result.hasSecret ? "yes" : "no"}`);
3217
+ } catch (err) {
3218
+ s.stop("Failed to check signing secret");
3219
+ log21.error(err instanceof Error ? err.message : "Unknown error");
3220
+ process.exitCode = 1;
3221
+ }
3222
+ })
3223
+ ).addCommand(
3224
+ new Command19("rotate-secret").description("Rotate the signing secret for a ZAM").argument("<id>", "ZAM ID").action(async (id) => {
3225
+ requireAuth(apiKey);
3226
+ const confirmed = await confirm3({
3227
+ message: "This will invalidate the old signing secret. Continue?"
3228
+ });
3229
+ if (isCancel6(confirmed) || !confirmed) {
3230
+ cancel5("Cancelled.");
3231
+ return;
3232
+ }
3233
+ const s = spinner16();
3234
+ s.start("Rotating signing secret...");
3235
+ try {
3236
+ const result = await client2.rotateSigningSecret(id);
3237
+ s.stop("Signing secret rotated");
3238
+ log21.success(`New Signing Secret: ${result.signingSecret}`);
3239
+ } catch (err) {
3240
+ s.stop("Failed to rotate signing secret");
3241
+ log21.error(err instanceof Error ? err.message : "Unknown error");
3242
+ process.exitCode = 1;
3243
+ }
3244
+ })
3245
+ ).addCommand(
3246
+ new Command19("delete").description("Delete a ZAM").argument("<id>", "ZAM ID").option("--json", "Output as JSON").option("-y, --yes", "Skip confirmation prompt").action(async (id, opts) => {
3247
+ requireAuth(apiKey);
3248
+ if (!opts.json && !opts.yes) {
3249
+ const confirmed = await confirm3({
3250
+ message: `Delete ZAM ${id}?`
3251
+ });
3252
+ if (isCancel6(confirmed) || !confirmed) {
3253
+ cancel5("Cancelled.");
3254
+ return;
3255
+ }
3256
+ }
3257
+ const s = spinner16();
3258
+ if (!opts.json) s.start("Deleting ZAM...");
3259
+ try {
3260
+ await client2.deleteProvider(id);
3261
+ if (opts.json) {
3262
+ process.stdout.write(
3263
+ `${JSON.stringify({ deleted: true, id }, null, 2)}
3264
+ `
3265
+ );
3266
+ return;
3267
+ }
3268
+ s.stop("ZAM deleted");
3269
+ log21.success(`ZAM ${id} deleted.`);
3270
+ } catch (err) {
3271
+ if (!opts.json) s.stop("Failed to delete ZAM");
3272
+ log21.error(err instanceof Error ? err.message : "Unknown error");
3273
+ process.exitCode = 1;
3274
+ }
3275
+ })
3276
+ ).addCommand(
3277
+ new Command19("capabilities").description("List capability memberships for a ZAM").argument("<id>", "ZAM ID").option("--json", "Output as JSON").action(async (id, opts) => {
3278
+ requireAuth(apiKey);
3279
+ const s = spinner16();
3280
+ if (!opts.json) s.start("Fetching capability memberships...");
3281
+ try {
3282
+ const memberships = await client2.getZamCapabilities(id);
3283
+ if (opts.json) {
3284
+ process.stdout.write(`${JSON.stringify(memberships, null, 2)}
3285
+ `);
3286
+ return;
3287
+ }
3288
+ s.stop(`Found ${memberships.length} capability membership(s)`);
3289
+ if (memberships.length === 0) {
3290
+ log21.info("This ZAM is not a member of any capabilities.");
3291
+ return;
3292
+ }
3293
+ for (const m of memberships) {
3294
+ log21.info(
3295
+ `${m.capabilityName} ${m.capabilityZid} [${m.membershipStatus}]` + (m.joinedAt ? ` joined ${new Date(m.joinedAt).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" })}` : "")
3296
+ );
3297
+ }
3298
+ } catch (err) {
3299
+ if (!opts.json) s.stop("Failed to fetch capability memberships");
3300
+ log21.error(err instanceof Error ? err.message : "Unknown error");
3301
+ process.exitCode = 1;
3302
+ }
3303
+ })
3304
+ );
3305
+
3306
+ // src/version-check.ts
3307
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
3308
+ import { dirname as dirname4, join as join6 } from "path";
3309
+ var CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
3310
+ var REGISTRY_URL = "https://registry.npmjs.org/@zeroclickai%2fzam-cli/latest";
3311
+ var FETCH_TIMEOUT_MS = 3e3;
3312
+ var getCacheFile = () => join6(
3313
+ process.env.HOME ?? process.env.USERPROFILE ?? ".",
3314
+ ".zam",
3315
+ "version-check.json"
3316
+ );
3317
+ var readCache = () => {
3318
+ try {
3319
+ const cacheFile = getCacheFile();
3320
+ if (!existsSync4(cacheFile)) return null;
3321
+ const raw = JSON.parse(readFileSync4(cacheFile, "utf-8"));
3322
+ if (typeof raw.latestVersion === "string" && typeof raw.checkedAt === "number") {
3323
+ return raw;
3324
+ }
3325
+ return null;
3326
+ } catch {
3327
+ return null;
3328
+ }
3329
+ };
3330
+ var writeCache = (latestVersion) => {
3331
+ try {
3332
+ const cacheFile = getCacheFile();
3333
+ const cacheDir = dirname4(cacheFile);
3334
+ if (!existsSync4(cacheDir)) {
3335
+ mkdirSync3(cacheDir, { recursive: true });
3336
+ }
3337
+ const data = { latestVersion, checkedAt: Date.now() };
3338
+ writeFileSync4(cacheFile, JSON.stringify(data));
3339
+ } catch {
3340
+ }
3341
+ };
3342
+ var isNewer = (current, latest) => {
3343
+ const parse = (v) => v.replace(/-.*$/, "").split(".").map(Number);
3344
+ const c = parse(current);
3345
+ const l = parse(latest);
3346
+ for (let i = 0; i < 3; i++) {
3347
+ const cv = c[i] ?? 0;
3348
+ const lv = l[i] ?? 0;
3349
+ if (lv !== cv) return lv > cv;
3350
+ }
3351
+ return false;
3352
+ };
3353
+ var checkForUpdate = async (currentVersion) => {
3354
+ try {
3355
+ const envVal = process.env.ZAM_UPDATE_CHECK?.toLowerCase();
3356
+ if (envVal === "off" || envVal === "false" || envVal === "0") return null;
3357
+ const cached = readCache();
3358
+ if (cached && Date.now() - cached.checkedAt < CACHE_TTL_MS) {
3359
+ return isNewer(currentVersion, cached.latestVersion) ? cached.latestVersion : null;
3360
+ }
3361
+ const res = await fetch(REGISTRY_URL, {
3362
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
3363
+ });
3364
+ if (!res.ok) return null;
3365
+ const data = await res.json();
3366
+ const latest = data.version;
3367
+ if (!latest) return null;
3368
+ writeCache(latest);
3369
+ return isNewer(currentVersion, latest) ? latest : null;
3370
+ } catch {
3371
+ return null;
3372
+ }
3373
+ };
3374
+ var printUpdateNotice = (currentVersion, latestVersion) => {
3375
+ const updateCmd = detectUpdateCommand();
3376
+ console.error(
3377
+ `
3378
+ Update available: ${currentVersion} \u2192 ${latestVersion}
3379
+ Run \`${updateCmd}\` to update
3380
+ `
3381
+ );
3382
+ };
3383
+ var detectUpdateCommand = () => {
3384
+ const execPath = process.argv[1] ?? "";
3385
+ if (execPath.includes(".pnpm") || execPath.includes("pnpm"))
3386
+ return "pnpm add -g @zeroclickai/zam-cli@latest";
3387
+ if (execPath.includes("yarn"))
3388
+ return "yarn global add @zeroclickai/zam-cli@latest";
3389
+ return "npm i -g @zeroclickai/zam-cli@latest";
3390
+ };
3391
+
3392
+ // src/index.ts
3393
+ var { version } = package_default;
3394
+ var main = async () => {
3395
+ printTelemetryNotice();
3396
+ const updateCheckPromise = checkForUpdate(version);
3397
+ const program = new Command20().name("zam").description("ZeroClick Marketplace CLI").version(version).exitOverride();
3398
+ const effectiveConfig = getEffectiveConfig();
3399
+ const client2 = createApiClient({
3400
+ baseUrl: effectiveConfig.apiUrl,
3401
+ apiKey: effectiveConfig.apiKey
3402
+ });
3403
+ const walletCmd = new Command20("wallet").description("Wallet management");
3404
+ walletCmd.addCommand(walletCreateCommand(effectiveConfig.apiUrl, saveConfig));
3405
+ walletCmd.addCommand(
3406
+ walletRecoverCommand(effectiveConfig.apiUrl, saveConfig)
3407
+ );
3408
+ walletCmd.addCommand(walletInfoCommand(client2));
3409
+ walletCmd.addCommand(walletUpdateCommand(client2));
3410
+ walletCmd.addCommand(walletFundCommand(client2));
3411
+ program.addCommand(walletCmd);
3412
+ const authCmd = new Command20("auth").description("Authentication");
3413
+ authCmd.addCommand(authApproveCommand(client2, effectiveConfig.apiKey));
3414
+ program.addCommand(authCmd);
3415
+ program.addCommand(zamsCommand(client2, effectiveConfig.apiKey));
3416
+ program.addCommand(capabilitiesCommand(client2));
3417
+ program.addCommand(configCommand());
3418
+ program.addCommand(searchCommand(client2));
3419
+ program.addCommand(ordersCommand(client2, effectiveConfig.apiKey));
3420
+ program.addCommand(activateCommand(client2, effectiveConfig.apiKey));
3421
+ program.addCommand(inspectCommand(client2));
3422
+ program.addCommand(preflightCommand(client2, effectiveConfig.apiKey));
3423
+ program.addCommand(runCommand(client2, effectiveConfig.apiKey));
3424
+ program.addCommand(apiKeysCommand(client2, effectiveConfig.apiKey));
3425
+ program.addCommand(openapiCommand(client2));
3426
+ program.addCommand(agentsCommand());
3427
+ program.addCommand(
3428
+ nodeCommand(effectiveConfig.apiKey, effectiveConfig.apiUrl)
3429
+ );
3430
+ const args = process.argv.slice(2);
3431
+ const commandName = args.filter((a) => !a.startsWith("-")).join(" ") || "help";
3432
+ trackCommand(commandName);
3433
+ await program.parseAsync(process.argv);
3434
+ await shutdownTelemetry();
3435
+ const latestVersion = await updateCheckPromise;
3436
+ if (latestVersion) {
3437
+ printUpdateNotice(version, latestVersion);
3438
+ }
3439
+ };
3440
+ main().catch(async (err) => {
3441
+ if (err?.exitCode === 0) {
3442
+ await shutdownTelemetry();
3443
+ return;
3444
+ }
3445
+ const args = process.argv.slice(2);
3446
+ const commandName = args.filter((a) => !a.startsWith("-")).join(" ") || "unknown";
3447
+ trackError(
3448
+ commandName,
3449
+ err instanceof Error ? err.message : "Unexpected error"
3450
+ );
3451
+ await shutdownTelemetry();
3452
+ if (!process.exitCode) {
3453
+ log22.error(err instanceof Error ? err.message : "Unexpected error");
3454
+ process.exitCode = 1;
3455
+ }
3456
+ });