@agentcentral/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -0
  3. package/dist/index.js +1801 -0
  4. package/package.json +54 -0
package/dist/index.js ADDED
@@ -0,0 +1,1801 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { cac } from "cac";
5
+
6
+ // src/types.ts
7
+ var CliError = class extends Error {
8
+ code;
9
+ /** Optional machine-stable detail (echoed to stderr). */
10
+ detail;
11
+ constructor(code, message, detail) {
12
+ super(message);
13
+ this.name = "CliError";
14
+ this.code = code;
15
+ this.detail = detail;
16
+ }
17
+ };
18
+
19
+ // src/constants.ts
20
+ var CLI_VERSION = "0.1.0";
21
+ var DEFAULT_MCP_ENDPOINT = "https://mcp.agentcentral.to/mcp";
22
+ var ENV_API_KEY = "AGENT_CENTRAL_API_KEY";
23
+ var ENV_API_KEY_LEGACY = "AGENTCENTRAL_API_KEY";
24
+ var ENV_ENDPOINT = "AGENT_CENTRAL_ENDPOINT";
25
+ var DEFAULT_TIMEOUT_MS = 6e4;
26
+ var USER_AGENT = "agentcentral-cli";
27
+ var DEFAULT_RETRY = {
28
+ maxAttempts: 4,
29
+ baseBackoffMs: 500,
30
+ honorRetryAfter: true
31
+ };
32
+ var API_KEY_PREFIX = "ac_live_";
33
+
34
+ // src/config.ts
35
+ import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
36
+ import { homedir } from "os";
37
+ import { dirname, join } from "path";
38
+ import { z } from "zod";
39
+ var ProfileLabelSchema = z.object({
40
+ tenant: z.string().optional(),
41
+ marketplace: z.string().optional(),
42
+ domains: z.array(z.string()).optional()
43
+ }).strict();
44
+ var ProfileSchema = z.object({
45
+ name: z.string().min(1),
46
+ apiKey: z.string().min(1),
47
+ endpoint: z.string().optional(),
48
+ label: ProfileLabelSchema.optional()
49
+ }).strict();
50
+ var CliConfigSchema = z.object({
51
+ activeProfile: z.string().optional(),
52
+ profiles: z.array(ProfileSchema)
53
+ }).strict();
54
+ function configPath(env = process.env) {
55
+ const base = env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
56
+ return join(base, "agentcentral", "config.json");
57
+ }
58
+ function loadConfig(env = process.env) {
59
+ const path = configPath(env);
60
+ if (!existsSync(path)) {
61
+ return { profiles: [] };
62
+ }
63
+ const stat = statSync(path);
64
+ if (process.platform !== "win32" && (stat.mode & 63) !== 0) {
65
+ throw new CliError(
66
+ 1 /* GeneralFailure */,
67
+ `config ${path} is group/world-accessible; run: chmod 600 ${path}`
68
+ );
69
+ }
70
+ let parsed;
71
+ try {
72
+ parsed = JSON.parse(readFileSync(path, "utf8"));
73
+ } catch (error) {
74
+ throw new CliError(
75
+ 1 /* GeneralFailure */,
76
+ `config ${path} is not valid JSON`,
77
+ error instanceof Error ? error.message : String(error)
78
+ );
79
+ }
80
+ const result = CliConfigSchema.safeParse(parsed);
81
+ if (!result.success) {
82
+ throw new CliError(
83
+ 1 /* GeneralFailure */,
84
+ `config ${path} is invalid`,
85
+ result.error.message
86
+ );
87
+ }
88
+ return result.data;
89
+ }
90
+ function saveConfig(config, env = process.env) {
91
+ const path = configPath(env);
92
+ const dir = dirname(path);
93
+ mkdirSync(dir, { recursive: true, mode: 448 });
94
+ writeFileSync(path, JSON.stringify(config, null, 2), { mode: 384 });
95
+ chmodSync(path, 384);
96
+ }
97
+ function upsertProfile(config, profile) {
98
+ const exists = config.profiles.some((p) => p.name === profile.name);
99
+ const profiles = exists ? config.profiles.map((p) => p.name === profile.name ? profile : p) : [...config.profiles, profile];
100
+ return { ...config, profiles };
101
+ }
102
+ function removeProfile(config, name) {
103
+ const profiles = config.profiles.filter((p) => p.name !== name);
104
+ const activeProfile = config.activeProfile === name ? void 0 : config.activeProfile;
105
+ return { ...config, activeProfile, profiles };
106
+ }
107
+ function getProfile(config, name) {
108
+ return config.profiles.find((p) => p.name === name);
109
+ }
110
+ function setActiveProfile(config, name) {
111
+ if (!config.profiles.some((p) => p.name === name)) {
112
+ throw new CliError(2 /* UsageError */, `unknown profile: ${name}`);
113
+ }
114
+ return { ...config, activeProfile: name };
115
+ }
116
+
117
+ // src/auth.ts
118
+ function redactKey(key) {
119
+ const last4 = key.slice(-4);
120
+ if (key.startsWith(API_KEY_PREFIX)) {
121
+ return `${API_KEY_PREFIX}***${last4}`;
122
+ }
123
+ return `***${last4}`;
124
+ }
125
+ function looksLikeApiKey(key) {
126
+ return key.startsWith(API_KEY_PREFIX);
127
+ }
128
+ async function resolveKey(input) {
129
+ const { flags, env, config, readKeyFromStdin: readKeyFromStdin2 } = input;
130
+ if (flags.apiKeyStdin === true) {
131
+ if (!readKeyFromStdin2) {
132
+ throw new CliError(2 /* UsageError */, "no stdin reader available for --api-key-stdin");
133
+ }
134
+ const apiKey = (await readKeyFromStdin2()).trim();
135
+ return { apiKey, source: "flag" };
136
+ }
137
+ if (flags.profile) {
138
+ const profile = getProfile(config, flags.profile);
139
+ if (!profile) {
140
+ throw new CliError(2 /* UsageError */, `unknown profile: ${flags.profile}`);
141
+ }
142
+ return { apiKey: profile.apiKey, source: "profile", profile };
143
+ }
144
+ const envKey = env[ENV_API_KEY];
145
+ if (envKey) {
146
+ return { apiKey: envKey, source: "env" };
147
+ }
148
+ const legacyKey = env[ENV_API_KEY_LEGACY];
149
+ if (legacyKey) {
150
+ return { apiKey: legacyKey, source: "env_legacy" };
151
+ }
152
+ if (config.activeProfile) {
153
+ const profile = getProfile(config, config.activeProfile);
154
+ if (profile) {
155
+ return { apiKey: profile.apiKey, source: "active_profile", profile };
156
+ }
157
+ }
158
+ if (input.keychainGet) {
159
+ const keychainKey = input.keychainGet();
160
+ if (keychainKey) {
161
+ return { apiKey: keychainKey, source: "keychain" };
162
+ }
163
+ }
164
+ if (config.profiles.length === 1) {
165
+ const profile = config.profiles[0];
166
+ return { apiKey: profile.apiKey, source: "default", profile };
167
+ }
168
+ throw new CliError(
169
+ 3 /* AuthError */,
170
+ "No API key. Set AGENT_CENTRAL_API_KEY, run: agentcentral auth login, or pass --profile <name>."
171
+ );
172
+ }
173
+ async function resolveAuth(input) {
174
+ const { flags, env } = input;
175
+ const warn = input.warn ?? (() => {
176
+ });
177
+ const resolution = await resolveKey(input);
178
+ if (resolution.source === "env_legacy") {
179
+ warn("AGENTCENTRAL_API_KEY is deprecated; use AGENT_CENTRAL_API_KEY");
180
+ }
181
+ if (env[ENV_API_KEY] && flags.profile) {
182
+ warn(
183
+ `Both AGENT_CENTRAL_API_KEY and --profile are set; using profile ${flags.profile}. Keys are single-marketplace bound \u2014 verify the target account.`
184
+ );
185
+ }
186
+ const apiKey = resolution.apiKey.trim();
187
+ if (!looksLikeApiKey(apiKey)) {
188
+ warn("API key does not start with ac_live_; continuing.");
189
+ }
190
+ const endpoint = resolution.profile?.endpoint ?? env[ENV_ENDPOINT] ?? DEFAULT_MCP_ENDPOINT;
191
+ return {
192
+ apiKey,
193
+ endpoint,
194
+ source: resolution.source,
195
+ ...resolution.profile ? { profileName: resolution.profile.name } : {}
196
+ };
197
+ }
198
+
199
+ // src/transport.ts
200
+ function parseSseJsonRpc(text) {
201
+ const dataLines = text.trim().split(/\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
202
+ return JSON.parse(dataLines.length ? dataLines.join("\n") : text);
203
+ }
204
+ var RetryableError = class extends Error {
205
+ status;
206
+ retryAfterMs;
207
+ constructor(message, opts) {
208
+ super(message);
209
+ this.name = "RetryableError";
210
+ this.status = opts?.status;
211
+ this.retryAfterMs = opts?.retryAfterMs;
212
+ }
213
+ };
214
+ function isAbortError(error) {
215
+ return error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
216
+ }
217
+ function parseRetryAfterMs(value, now) {
218
+ if (value == null) return void 0;
219
+ const trimmed = value.trim();
220
+ if (trimmed === "") return void 0;
221
+ if (/^\d+$/.test(trimmed)) {
222
+ return Number(trimmed) * 1e3;
223
+ }
224
+ const dateMs = Date.parse(trimmed);
225
+ if (Number.isNaN(dateMs)) return void 0;
226
+ return Math.max(0, dateMs - now);
227
+ }
228
+ function sleep(ms) {
229
+ return new Promise((resolve) => setTimeout(resolve, ms));
230
+ }
231
+ function buildHeaders(auth, requestId) {
232
+ return {
233
+ "Content-Type": "application/json",
234
+ "Accept": "application/json, text/event-stream",
235
+ "User-Agent": USER_AGENT,
236
+ "Authorization": `Bearer ${auth.apiKey}`,
237
+ ...requestId ? { "X-Request-Id": requestId } : {}
238
+ };
239
+ }
240
+ function createClient(auth, opts = {}) {
241
+ const retry = opts.retry ?? DEFAULT_RETRY;
242
+ const defaultTimeoutMs = opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
243
+ const headers = buildHeaders(auth, opts.requestId);
244
+ let counter = 0;
245
+ const nextId = () => {
246
+ counter += 1;
247
+ return counter;
248
+ };
249
+ async function attempt(method, params, id, timeoutMs) {
250
+ let res;
251
+ try {
252
+ res = await fetch(auth.endpoint, {
253
+ method: "POST",
254
+ headers,
255
+ body: JSON.stringify({ jsonrpc: "2.0", id, method, params }),
256
+ signal: AbortSignal.timeout(timeoutMs)
257
+ });
258
+ } catch (error) {
259
+ if (isAbortError(error)) {
260
+ throw new CliError(
261
+ 8 /* Timeout */,
262
+ `Request timed out after ${timeoutMs}ms`,
263
+ method
264
+ );
265
+ }
266
+ throw new RetryableError(
267
+ error instanceof Error ? error.message : String(error)
268
+ );
269
+ }
270
+ if (res.status === 401) {
271
+ throw new CliError(
272
+ 3 /* AuthError */,
273
+ "Authentication failed (HTTP 401): the API key was rejected by the hosted server.",
274
+ method
275
+ );
276
+ }
277
+ if (res.status === 429 || res.status >= 500) {
278
+ const retryAfterMs = retry.honorRetryAfter ? parseRetryAfterMs(res.headers.get("retry-after"), Date.now()) : void 0;
279
+ throw new RetryableError(`HTTP ${res.status}`, {
280
+ status: res.status,
281
+ retryAfterMs
282
+ });
283
+ }
284
+ const text = await res.text();
285
+ if (!res.ok) {
286
+ throw new CliError(
287
+ 6 /* TransportFailure */,
288
+ `HTTP ${res.status}: ${text.slice(0, 200)}`,
289
+ method
290
+ );
291
+ }
292
+ return parseSseJsonRpc(text);
293
+ }
294
+ async function rpc(method, params, rpcOpts) {
295
+ const timeoutMs = rpcOpts?.timeoutMs ?? defaultTimeoutMs;
296
+ const id = rpcOpts?.id ?? nextId();
297
+ const maxAttempts = Math.max(1, retry.maxAttempts);
298
+ let lastReason = "transport failure";
299
+ for (let n = 1; n <= maxAttempts; n += 1) {
300
+ try {
301
+ return await attempt(method, params ?? {}, id, timeoutMs);
302
+ } catch (error) {
303
+ if (error instanceof CliError) {
304
+ throw error;
305
+ }
306
+ if (!(error instanceof RetryableError)) {
307
+ throw error;
308
+ }
309
+ lastReason = error.message;
310
+ if (n >= maxAttempts) break;
311
+ const backoffMs = retry.baseBackoffMs * 2 ** (n - 1);
312
+ const delayMs = error.retryAfterMs ?? backoffMs;
313
+ opts.onRetry?.({ attempt: n, delayMs, reason: lastReason });
314
+ await sleep(delayMs);
315
+ }
316
+ }
317
+ throw new CliError(
318
+ 6 /* TransportFailure */,
319
+ `Transport failure after ${maxAttempts} attempt(s): ${lastReason}`,
320
+ method
321
+ );
322
+ }
323
+ async function listTools(listOpts) {
324
+ const response = await rpc("tools/list", {}, { timeoutMs: listOpts?.timeoutMs });
325
+ if (response.error) {
326
+ throw new CliError(7 /* ToolExecutionFailed */, response.error.message, "tools/list");
327
+ }
328
+ const result = response.result ?? {};
329
+ return result.tools ?? [];
330
+ }
331
+ async function callTool(name, args, callOpts) {
332
+ const response = await rpc(
333
+ "tools/call",
334
+ { name, arguments: args },
335
+ { timeoutMs: callOpts?.timeoutMs }
336
+ );
337
+ if (response.error) {
338
+ throw new CliError(7 /* ToolExecutionFailed */, response.error.message, name);
339
+ }
340
+ return response.result ?? {};
341
+ }
342
+ return { rpc, listTools, callTool };
343
+ }
344
+
345
+ // src/commands/tools.ts
346
+ import { readFile, writeFile } from "fs/promises";
347
+ import { existsSync as existsSync2 } from "fs";
348
+ import { TOOL_DOMAINS } from "@agent-central/shared";
349
+
350
+ // src/format.ts
351
+ function isTabular(value) {
352
+ if (!Array.isArray(value) || value.length === 0) {
353
+ return false;
354
+ }
355
+ return value.every(isPlainObject);
356
+ }
357
+ function isPlainObject(value) {
358
+ return typeof value === "object" && value !== null && !Array.isArray(value);
359
+ }
360
+ function cellString(value) {
361
+ if (value === null || value === void 0) {
362
+ return "";
363
+ }
364
+ if (typeof value === "object") {
365
+ return JSON.stringify(value);
366
+ }
367
+ return String(value);
368
+ }
369
+ function columns(rows) {
370
+ const seen = /* @__PURE__ */ new Set();
371
+ const keys = [];
372
+ for (const row of rows) {
373
+ for (const key of Object.keys(row)) {
374
+ if (!seen.has(key)) {
375
+ seen.add(key);
376
+ keys.push(key);
377
+ }
378
+ }
379
+ }
380
+ return keys;
381
+ }
382
+ function padCell(cell, width) {
383
+ return cell + " ".repeat(width - cell.length);
384
+ }
385
+ function formatTable(rows) {
386
+ const cols = columns(rows);
387
+ const cells = rows.map((row) => cols.map((col) => cellString(row[col])));
388
+ const widths = cols.map(
389
+ (col, i) => cells.reduce((max, rowCells) => Math.max(max, rowCells[i].length), col.length)
390
+ );
391
+ const header = cols.map((col, i) => padCell(col, widths[i])).join(" ");
392
+ const separator = widths.map((width) => "-".repeat(width)).join(" ");
393
+ const body = cells.map(
394
+ (rowCells) => rowCells.map((cell, i) => padCell(cell, widths[i])).join(" ")
395
+ );
396
+ return [header, separator, ...body].join("\n");
397
+ }
398
+ function csvField(cell) {
399
+ if (/[",\r\n]/.test(cell)) {
400
+ return '"' + cell.replace(/"/g, '""') + '"';
401
+ }
402
+ return cell;
403
+ }
404
+ function formatCsv(rows) {
405
+ const cols = columns(rows);
406
+ const header = cols.map(csvField).join(",");
407
+ const body = rows.map((row) => cols.map((col) => csvField(cellString(row[col]))).join(","));
408
+ return [header, ...body].join("\n");
409
+ }
410
+ function mdCell(cell) {
411
+ return cell.replace(/\|/g, "\\|").replace(/[\r\n]+/g, " ");
412
+ }
413
+ function formatMarkdown(rows) {
414
+ const cols = columns(rows);
415
+ const header = "| " + cols.map(mdCell).join(" | ") + " |";
416
+ const separator = "| " + cols.map(() => "---").join(" | ") + " |";
417
+ const body = rows.map(
418
+ (row) => "| " + cols.map((col) => mdCell(cellString(row[col]))).join(" | ") + " |"
419
+ );
420
+ return [header, separator, ...body].join("\n");
421
+ }
422
+ function formatOutput(value, format) {
423
+ switch (format) {
424
+ case "json":
425
+ return JSON.stringify(value, null, 2);
426
+ case "ndjson":
427
+ return Array.isArray(value) ? value.map((v) => JSON.stringify(v)).join("\n") : JSON.stringify(value);
428
+ case "table":
429
+ if (!isTabular(value)) {
430
+ throw new CliError(2 /* UsageError */, "result is not tabular; use --format json");
431
+ }
432
+ return formatTable(value);
433
+ case "csv":
434
+ if (!isTabular(value)) {
435
+ throw new CliError(2 /* UsageError */, "result is not tabular; use --format json");
436
+ }
437
+ return formatCsv(value);
438
+ case "markdown":
439
+ if (!isTabular(value)) {
440
+ throw new CliError(2 /* UsageError */, "result is not tabular; use --format json");
441
+ }
442
+ return formatMarkdown(value);
443
+ }
444
+ }
445
+
446
+ // src/exit-codes.ts
447
+ var RPC_INVALID_PARAMS = -32602;
448
+ var DETAIL_MAX_LENGTH = 180;
449
+ var PERMISSION_PATTERN = /no_amazon|not connected|expired|suspended|revoked|past_due|trial expired|permission|not authorized|scope/i;
450
+ var VALIDATION_PATTERN = /invalid|validation|must be|required/i;
451
+ function writeEnvelopeFrom(result) {
452
+ const structured = result.structuredContent;
453
+ if (!structured) {
454
+ return void 0;
455
+ }
456
+ const hasWriteShape = "mode" in structured || "status" in structured || "requested_count" in structured;
457
+ if (!hasWriteShape) {
458
+ return void 0;
459
+ }
460
+ return structured;
461
+ }
462
+ function toolClassification(def) {
463
+ const properties = def.inputSchema.properties;
464
+ const props = properties && typeof properties === "object" ? properties : {};
465
+ const isWrite = "confirm" in props && "idempotency_key" in props;
466
+ return {
467
+ isWrite,
468
+ supportsPreview: isWrite
469
+ };
470
+ }
471
+ function classifyToolText(text) {
472
+ if (PERMISSION_PATTERN.test(text)) {
473
+ return 4 /* PermissionDenied */;
474
+ }
475
+ if (VALIDATION_PATTERN.test(text)) {
476
+ return 5 /* ValidationFailed */;
477
+ }
478
+ return void 0;
479
+ }
480
+ function joinContentText(result) {
481
+ return result.content.map((block) => typeof block.text === "string" ? block.text : "").filter((text) => text.length > 0).join(" ").trim();
482
+ }
483
+ function truncateDetail(text) {
484
+ if (text.length <= DETAIL_MAX_LENGTH) {
485
+ return text;
486
+ }
487
+ return `${text.slice(0, DETAIL_MAX_LENGTH)}\u2026`;
488
+ }
489
+ function classifyWriteStatus(status) {
490
+ switch (status) {
491
+ case "validation_error":
492
+ return 5 /* ValidationFailed */;
493
+ case "partial":
494
+ return 9 /* PartialWrite */;
495
+ case "blocked":
496
+ case "failed":
497
+ return 7 /* ToolExecutionFailed */;
498
+ case "duplicate_blocked":
499
+ case "accepted":
500
+ case "preview":
501
+ default:
502
+ return 0 /* Success */;
503
+ }
504
+ }
505
+ function summarizeWriteEnvelope(env) {
506
+ const parts = [];
507
+ if (typeof env.requested_count === "number") {
508
+ parts.push(`requested=${env.requested_count}`);
509
+ }
510
+ if (typeof env.accepted_count === "number") {
511
+ parts.push(`accepted=${env.accepted_count}`);
512
+ }
513
+ if (typeof env.failed_count === "number") {
514
+ parts.push(`failed=${env.failed_count}`);
515
+ }
516
+ if (typeof env.previewed_count === "number") {
517
+ parts.push(`previewed=${env.previewed_count}`);
518
+ }
519
+ const status = env.status ?? "unknown";
520
+ return parts.length > 0 ? `${status} (${parts.join(", ")})` : status;
521
+ }
522
+ function classifyResponse(response) {
523
+ if (response.error) {
524
+ const message = response.error.message;
525
+ const code = response.error.code === RPC_INVALID_PARAMS ? 5 /* ValidationFailed */ : classifyToolText(message) ?? 7 /* ToolExecutionFailed */;
526
+ return {
527
+ code,
528
+ status: "rpc_error",
529
+ detail: message
530
+ };
531
+ }
532
+ const result = response.result;
533
+ const env = writeEnvelopeFrom(result);
534
+ if (env?.status) {
535
+ return {
536
+ code: classifyWriteStatus(env.status),
537
+ status: env.status,
538
+ detail: summarizeWriteEnvelope(env),
539
+ result
540
+ };
541
+ }
542
+ if (result.isError) {
543
+ const text = joinContentText(result);
544
+ const code = classifyToolText(text) ?? 7 /* ToolExecutionFailed */;
545
+ return {
546
+ code,
547
+ status: "tool_error",
548
+ detail: truncateDetail(text),
549
+ result
550
+ };
551
+ }
552
+ return {
553
+ code: 0 /* Success */,
554
+ status: "ok",
555
+ detail: truncateDetail(joinContentText(result)),
556
+ result
557
+ };
558
+ }
559
+
560
+ // src/commands/tools.ts
561
+ function parseJsonArgs(text, origin) {
562
+ try {
563
+ return JSON.parse(text);
564
+ } catch (error) {
565
+ const reason = error instanceof Error ? error.message : String(error);
566
+ throw new CliError(2 /* UsageError */, `invalid JSON in ${origin}: ${reason}`);
567
+ }
568
+ }
569
+ async function resolveInput(spec, readStdin) {
570
+ if (spec === void 0) {
571
+ return {};
572
+ }
573
+ if (spec === "-") {
574
+ return parseJsonArgs(await readStdin(), "stdin");
575
+ }
576
+ if (spec.startsWith("@")) {
577
+ const path = spec.slice(1);
578
+ let contents;
579
+ try {
580
+ contents = await readFile(path, "utf8");
581
+ } catch (error) {
582
+ const reason = error instanceof Error ? error.message : String(error);
583
+ throw new CliError(2 /* UsageError */, `could not read --input file: ${path} (${reason})`);
584
+ }
585
+ return parseJsonArgs(contents, `file ${path}`);
586
+ }
587
+ const trimmed = spec.trim();
588
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
589
+ return parseJsonArgs(spec, "--input");
590
+ }
591
+ if (existsSync2(spec)) {
592
+ let contents;
593
+ try {
594
+ contents = await readFile(spec, "utf8");
595
+ } catch (error) {
596
+ const reason = error instanceof Error ? error.message : String(error);
597
+ throw new CliError(2 /* UsageError */, `could not read --input file: ${spec} (${reason})`);
598
+ }
599
+ return parseJsonArgs(contents, `file ${spec}`);
600
+ }
601
+ throw new CliError(
602
+ 2 /* UsageError */,
603
+ `could not interpret --input: ${spec} (expected JSON, @file, "-", or an existing file path)`
604
+ );
605
+ }
606
+ var TOOL_DOMAIN_BY_NAME = TOOL_DOMAINS.reduce(
607
+ (acc, domain) => {
608
+ const entries = domain.tools.reduce(
609
+ (inner, tool) => ({ ...inner, [tool.name]: domain.serverDomain }),
610
+ {}
611
+ );
612
+ return { ...acc, ...entries };
613
+ },
614
+ {}
615
+ );
616
+ function requireTool(tools, name) {
617
+ const def = tools.find((tool) => tool.name === name);
618
+ if (!def) {
619
+ throw new CliError(2 /* UsageError */, `unknown tool: ${name}`);
620
+ }
621
+ return def;
622
+ }
623
+ function schemaProperties(def) {
624
+ const properties = def.inputSchema.properties;
625
+ return properties && typeof properties === "object" ? properties : {};
626
+ }
627
+ function schemaRequired(def) {
628
+ const required = def.inputSchema.required;
629
+ return Array.isArray(required) ? new Set(required.map(String)) : /* @__PURE__ */ new Set();
630
+ }
631
+ async function toolsList(ctx) {
632
+ const client = await ctx.getClient();
633
+ const tools = await client.listTools({ timeoutMs: ctx.flags.timeoutMs });
634
+ const domainFilter = ctx.flags.domain;
635
+ const filtered = domainFilter ? tools.filter((def) => TOOL_DOMAIN_BY_NAME[def.name] === domainFilter) : tools;
636
+ const rows = filtered.map((def) => ({
637
+ name: def.name,
638
+ write: toolClassification(def).isWrite,
639
+ description: def.description ?? ""
640
+ }));
641
+ const format = ctx.flags.format === "table" ? "table" : ctx.flags.format;
642
+ ctx.out(formatOutput(rows, format));
643
+ return 0 /* Success */;
644
+ }
645
+ async function toolsDescribe(ctx, name) {
646
+ const client = await ctx.getClient();
647
+ const tools = await client.listTools({ timeoutMs: ctx.flags.timeoutMs });
648
+ const def = requireTool(tools, name);
649
+ if (ctx.flags.format !== "table") {
650
+ ctx.out(formatOutput(def, ctx.flags.format));
651
+ return 0 /* Success */;
652
+ }
653
+ const properties = schemaProperties(def);
654
+ const required = schemaRequired(def);
655
+ const rows = Object.keys(properties).map((param) => {
656
+ const schema = properties[param];
657
+ const schemaObj = schema && typeof schema === "object" ? schema : {};
658
+ return {
659
+ parameter: param,
660
+ type: typeof schemaObj.type === "string" ? schemaObj.type : "",
661
+ required: required.has(param),
662
+ description: typeof schemaObj.description === "string" ? schemaObj.description : ""
663
+ };
664
+ });
665
+ if (rows.length === 0) {
666
+ ctx.out(`${def.name}: no parameters`);
667
+ return 0 /* Success */;
668
+ }
669
+ ctx.out(formatOutput(rows, "table"));
670
+ return 0 /* Success */;
671
+ }
672
+ async function toolsSchema(ctx, name) {
673
+ const client = await ctx.getClient();
674
+ const tools = await client.listTools({ timeoutMs: ctx.flags.timeoutMs });
675
+ const def = requireTool(tools, name);
676
+ ctx.out(JSON.stringify(def.inputSchema, null, 2));
677
+ return 0 /* Success */;
678
+ }
679
+ function buildCallArgs(ctx, def, input) {
680
+ const isWrite = toolClassification(def).isWrite;
681
+ const properties = schemaProperties(def);
682
+ const limitOverride = ctx.flags.limit != null && "limit" in properties ? { limit: ctx.flags.limit } : {};
683
+ if (!isWrite) {
684
+ return { ...input, ...limitOverride };
685
+ }
686
+ if (!ctx.flags.yes) {
687
+ return { ...input, ...limitOverride };
688
+ }
689
+ if (!ctx.flags.idempotencyKey) {
690
+ throw new CliError(5 /* ValidationFailed */, "submit requires --idempotency-key");
691
+ }
692
+ return {
693
+ ...input,
694
+ ...limitOverride,
695
+ confirm: true,
696
+ idempotency_key: ctx.flags.idempotencyKey
697
+ };
698
+ }
699
+ function selectOutputValue(result) {
700
+ if (result.structuredContent !== void 0) {
701
+ return result.structuredContent;
702
+ }
703
+ const first = result.content.find((block) => typeof block.text === "string");
704
+ const text = first?.text;
705
+ if (text === void 0) {
706
+ return result.content;
707
+ }
708
+ try {
709
+ return JSON.parse(text);
710
+ } catch {
711
+ return text;
712
+ }
713
+ }
714
+ function echoWriteSummary(ctx, result) {
715
+ const env = writeEnvelopeFrom(result);
716
+ if (!env) {
717
+ return;
718
+ }
719
+ const parts = [];
720
+ if (env.mode) parts.push(`mode=${env.mode}`);
721
+ if (env.status) parts.push(`status=${env.status}`);
722
+ if (typeof env.accepted_count === "number") parts.push(`accepted=${env.accepted_count}`);
723
+ if (typeof env.failed_count === "number") parts.push(`failed=${env.failed_count}`);
724
+ if (env.idempotency_key) parts.push(`idempotency_key=${env.idempotency_key}`);
725
+ ctx.err(parts.join(" "));
726
+ if (env.status === "duplicate_blocked") {
727
+ ctx.err("note: a prior submission with this idempotency key stands; no new write was made.");
728
+ }
729
+ }
730
+ async function toolsCall(ctx, name, inputSpec) {
731
+ const input = await resolveInput(inputSpec, readStdinOnce);
732
+ const client = await ctx.getClient();
733
+ const tools = await client.listTools({ timeoutMs: ctx.flags.timeoutMs });
734
+ const def = requireTool(tools, name);
735
+ const args = buildCallArgs(ctx, def, input);
736
+ const response = await client.rpc(
737
+ "tools/call",
738
+ { name, arguments: args },
739
+ { timeoutMs: ctx.flags.timeoutMs }
740
+ );
741
+ const classified = classifyResponse(response);
742
+ const result = classified.result;
743
+ if (!result) {
744
+ if (classified.detail) {
745
+ ctx.err(classified.detail);
746
+ }
747
+ return classified.code;
748
+ }
749
+ echoWriteSummary(ctx, result);
750
+ const value = selectOutputValue(result);
751
+ const rendered = formatOutput(value, ctx.flags.format);
752
+ if (ctx.flags.output) {
753
+ await writeFile(ctx.flags.output, rendered, "utf8");
754
+ ctx.err(`wrote result to ${ctx.flags.output}`);
755
+ } else {
756
+ ctx.out(rendered);
757
+ }
758
+ return classified.code;
759
+ }
760
+ async function readStdinOnce() {
761
+ const chunks = [];
762
+ for await (const chunk of process.stdin) {
763
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
764
+ }
765
+ return Buffer.concat(chunks).toString("utf8");
766
+ }
767
+
768
+ // src/commands/mcp.ts
769
+ import { TOOL_DOMAINS as TOOL_DOMAINS2 } from "@agent-central/shared";
770
+ var DOMAIN_BY_TOOL = new Map(
771
+ TOOL_DOMAINS2.flatMap(
772
+ (domain) => domain.tools.map((tool) => [tool.name, domain.serverDomain])
773
+ )
774
+ );
775
+ function filterToolsList(tools, opts) {
776
+ const domainSet = opts.domains && opts.domains.length > 0 ? new Set(opts.domains) : void 0;
777
+ return tools.filter((def) => {
778
+ if (opts.readOnly && toolClassification(def).isWrite) {
779
+ return false;
780
+ }
781
+ if (domainSet) {
782
+ const domain = DOMAIN_BY_TOOL.get(def.name);
783
+ if (domain !== void 0 && !domainSet.has(domain)) {
784
+ return false;
785
+ }
786
+ }
787
+ return true;
788
+ });
789
+ }
790
+ var PARSE_ERROR_RESPONSE = JSON.stringify({
791
+ jsonrpc: "2.0",
792
+ id: null,
793
+ error: { code: -32700, message: "Parse error" }
794
+ });
795
+ async function handleMessage(line, client, opts) {
796
+ const trimmed = line.trim();
797
+ if (trimmed === "") {
798
+ return null;
799
+ }
800
+ let message;
801
+ try {
802
+ message = JSON.parse(trimmed);
803
+ } catch {
804
+ return PARSE_ERROR_RESPONSE;
805
+ }
806
+ const method = typeof message.method === "string" ? message.method : "";
807
+ if (message.id === void 0) {
808
+ try {
809
+ await client.rpc(method, message.params);
810
+ } catch {
811
+ }
812
+ return null;
813
+ }
814
+ const id = message.id;
815
+ try {
816
+ const response = await client.rpc(method, message.params, {
817
+ id
818
+ });
819
+ const withId = { ...response, id };
820
+ const shouldFilter = opts.readOnly || (opts.domains?.length ?? 0) > 0;
821
+ const result = withId.result;
822
+ if (method === "tools/list" && shouldFilter && Array.isArray(result?.tools)) {
823
+ const filtered = {
824
+ ...withId,
825
+ result: { ...result, tools: filterToolsList(result.tools, opts) }
826
+ };
827
+ return JSON.stringify(filtered);
828
+ }
829
+ return JSON.stringify(withId);
830
+ } catch (error) {
831
+ const message_ = error instanceof CliError ? error.message : "internal bridge error";
832
+ return JSON.stringify({
833
+ jsonrpc: "2.0",
834
+ id,
835
+ error: { code: -32001, message: message_ }
836
+ });
837
+ }
838
+ }
839
+ function keylessClient() {
840
+ const fail = (method) => {
841
+ throw new CliError(
842
+ 3 /* AuthError */,
843
+ "no API key: set AGENT_CENTRAL_API_KEY before starting the bridge",
844
+ method
845
+ );
846
+ };
847
+ return {
848
+ rpc: async (method) => fail(method),
849
+ listTools: async () => fail("tools/list"),
850
+ callTool: async (name) => fail(name)
851
+ };
852
+ }
853
+ function readServeFlags(ctx) {
854
+ const flags = ctx.flags;
855
+ const readOnly = flags.readOnly === true;
856
+ const rawDomains = flags.domains;
857
+ const domains = Array.isArray(rawDomains) ? rawDomains.filter((entry) => typeof entry === "string") : typeof rawDomains === "string" && rawDomains.length > 0 ? rawDomains.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) : void 0;
858
+ return { readOnly, domains: domains && domains.length > 0 ? domains : void 0 };
859
+ }
860
+ async function mcpServe(ctx) {
861
+ const opts = readServeFlags(ctx);
862
+ let client;
863
+ try {
864
+ client = await ctx.getClient();
865
+ } catch (error) {
866
+ if (error instanceof CliError && error.code === 3 /* AuthError */) {
867
+ ctx.err("warning: no API key resolved; bridge will reject every request (set AGENT_CENTRAL_API_KEY)");
868
+ client = keylessClient();
869
+ } else {
870
+ throw error;
871
+ }
872
+ }
873
+ ctx.err(
874
+ `bridge started${opts.readOnly ? " (read-only)" : ""}${opts.domains ? ` (domains: ${opts.domains.join(", ")})` : ""}`
875
+ );
876
+ return await pump(ctx, client, opts);
877
+ }
878
+ function pump(ctx, client, opts, input = process.stdin) {
879
+ return new Promise((resolve, reject) => {
880
+ input.setEncoding("utf8");
881
+ let buffer = "";
882
+ let chain = Promise.resolve();
883
+ const dispatch = (line) => {
884
+ chain = chain.then(async () => {
885
+ try {
886
+ const out = await handleMessage(line, client, opts);
887
+ if (out !== null) {
888
+ ctx.out(out);
889
+ }
890
+ } catch (error) {
891
+ ctx.err(`bridge error: ${error instanceof Error ? error.message : String(error)}`);
892
+ }
893
+ });
894
+ };
895
+ input.on("data", (data) => {
896
+ buffer += typeof data === "string" ? data : data.toString("utf8");
897
+ let newlineIndex = buffer.indexOf("\n");
898
+ while (newlineIndex !== -1) {
899
+ const line = buffer.slice(0, newlineIndex);
900
+ buffer = buffer.slice(newlineIndex + 1);
901
+ dispatch(line);
902
+ newlineIndex = buffer.indexOf("\n");
903
+ }
904
+ });
905
+ input.on("error", (error) => {
906
+ reject(new CliError(6 /* TransportFailure */, `stdin error: ${error.message}`));
907
+ });
908
+ input.on("end", () => {
909
+ const tail = buffer;
910
+ buffer = "";
911
+ if (tail.trim() !== "") {
912
+ dispatch(tail);
913
+ }
914
+ chain.then(() => {
915
+ ctx.err("bridge stopped (stdin closed)");
916
+ resolve(0 /* Success */);
917
+ }).catch(reject);
918
+ });
919
+ });
920
+ }
921
+ var API_KEY_NOTE = "Set AGENT_CENTRAL_API_KEY in the environment the client launches the CLI in.";
922
+ var STDIO_SERVER = {
923
+ command: "agentcentral",
924
+ args: ["mcp", "serve"]
925
+ };
926
+ async function mcpConfig(ctx, client = "claude") {
927
+ const normalized = client.trim().toLowerCase();
928
+ switch (normalized) {
929
+ case "claude":
930
+ case "cursor": {
931
+ const config = { mcpServers: { agentcentral: { ...STDIO_SERVER } } };
932
+ ctx.out(JSON.stringify(config, null, 2));
933
+ ctx.err(API_KEY_NOTE);
934
+ return 0 /* Success */;
935
+ }
936
+ case "openclaw":
937
+ case "hermes": {
938
+ const config = { agentcentral: { ...STDIO_SERVER } };
939
+ ctx.out(JSON.stringify(config, null, 2));
940
+ ctx.err(API_KEY_NOTE);
941
+ return 0 /* Success */;
942
+ }
943
+ default:
944
+ throw new CliError(
945
+ 2 /* UsageError */,
946
+ `unknown client "${client}" (expected one of: claude, cursor, openclaw, hermes)`
947
+ );
948
+ }
949
+ }
950
+ async function mcpTest(ctx) {
951
+ try {
952
+ const client = await ctx.getClient();
953
+ const tools = await client.listTools({ timeoutMs: 1e4 });
954
+ ctx.err(`bridge ok: ${tools.length} tools reachable`);
955
+ return 0 /* Success */;
956
+ } catch (error) {
957
+ if (error instanceof CliError) {
958
+ ctx.err(`bridge test failed: ${error.message}`);
959
+ return error.code;
960
+ }
961
+ throw error;
962
+ }
963
+ }
964
+
965
+ // src/commands/diagnostics.ts
966
+ import { statSync as statSync2 } from "fs";
967
+ function payloadFromResult(result) {
968
+ if (result.structuredContent) {
969
+ return result.structuredContent;
970
+ }
971
+ const text = result.content.find((block) => typeof block.text === "string")?.text;
972
+ if (typeof text !== "string") {
973
+ throw new CliError(7 /* ToolExecutionFailed */, "get_account_info returned no content");
974
+ }
975
+ try {
976
+ return JSON.parse(text);
977
+ } catch (error) {
978
+ throw new CliError(
979
+ 7 /* ToolExecutionFailed */,
980
+ "get_account_info returned unparseable content",
981
+ error instanceof Error ? error.message : String(error)
982
+ );
983
+ }
984
+ }
985
+ function summarizeAccountInfo(payload) {
986
+ const info = payload;
987
+ const lines = [];
988
+ const mp = info.marketplace;
989
+ if (mp) {
990
+ const where = [mp.country_name ?? mp.country_code, mp.currency_code].filter(Boolean).join(" \xB7 ");
991
+ lines.push(`marketplace ${where || mp.marketplace_id || "unknown"}`);
992
+ } else {
993
+ lines.push("marketplace (none \u2014 no resolved marketplace scope)");
994
+ }
995
+ const sc = info.connections?.seller_central?.status ?? "unknown";
996
+ const ads = info.connections?.amazon_ads?.status ?? "unknown";
997
+ lines.push(`seller central ${sc}`);
998
+ lines.push(`amazon ads ${ads}`);
999
+ const domains = info.enabled_domains ?? [];
1000
+ lines.push(`enabled domains ${domains.length > 0 ? domains.join(", ") : "(none)"}`);
1001
+ const tools = info.tools;
1002
+ if (tools) {
1003
+ lines.push(
1004
+ `read tools ${tools.read_enabled ?? 0} enabled, ${tools.read_disabled ?? 0} disabled`
1005
+ );
1006
+ lines.push(
1007
+ `write tools ${tools.write_enabled ?? 0} enabled, ${tools.write_disabled ?? 0} disabled`
1008
+ );
1009
+ }
1010
+ return lines.join("\n");
1011
+ }
1012
+ async function accountInfo(ctx) {
1013
+ const client = await ctx.getClient();
1014
+ const result = await client.callTool("get_account_info", {});
1015
+ const payload = payloadFromResult(result);
1016
+ if (ctx.flags.format === "table") {
1017
+ ctx.out(summarizeAccountInfo(payload));
1018
+ } else {
1019
+ ctx.out(formatOutput(payload, ctx.flags.format));
1020
+ }
1021
+ return 0 /* Success */;
1022
+ }
1023
+ var STATUS_GLYPH = {
1024
+ ok: "ok ",
1025
+ warn: "warn",
1026
+ fail: "fail",
1027
+ info: "info"
1028
+ };
1029
+ var STATUS_MARK = {
1030
+ ok: "[x]",
1031
+ warn: "[!]",
1032
+ fail: "[ ]",
1033
+ info: "[i]"
1034
+ };
1035
+ function originFromEndpoint(endpoint) {
1036
+ try {
1037
+ return new URL(endpoint).origin;
1038
+ } catch {
1039
+ return void 0;
1040
+ }
1041
+ }
1042
+ function renderTextReport(checks) {
1043
+ const width = checks.reduce((max, c) => Math.max(max, c.name.length), 0);
1044
+ const lines = checks.map((c) => {
1045
+ const padded = c.name + " ".repeat(width - c.name.length);
1046
+ return `${STATUS_GLYPH[c.status]} ${padded} ${c.detail}`;
1047
+ });
1048
+ return ["agentcentral doctor", "", ...lines].join("\n");
1049
+ }
1050
+ function renderMarkdownReport(checks) {
1051
+ const lines = checks.map((c) => `- ${STATUS_MARK[c.status]} **${c.name}** \u2014 ${c.detail}`);
1052
+ return ["# agentcentral doctor", "", ...lines].join("\n");
1053
+ }
1054
+ function checkConfigPerms(env) {
1055
+ const path = configPath(env);
1056
+ try {
1057
+ const stat = statSync2(path);
1058
+ if (process.platform === "win32") {
1059
+ return { name: "config perms", status: "ok", detail: `${path} (POSIX perms not enforced on Windows)` };
1060
+ }
1061
+ const mode = (stat.mode & 511).toString(8).padStart(3, "0");
1062
+ if ((stat.mode & 63) !== 0) {
1063
+ return {
1064
+ name: "config perms",
1065
+ status: "warn",
1066
+ detail: `${path} is mode ${mode}; group/world accessible \u2014 run: chmod 600 ${path}`
1067
+ };
1068
+ }
1069
+ return { name: "config perms", status: "ok", detail: `${path} (mode ${mode})` };
1070
+ } catch {
1071
+ return { name: "config perms", status: "info", detail: `no config file (${path})` };
1072
+ }
1073
+ }
1074
+ async function checkHealth(origin, timeoutMs) {
1075
+ if (!origin) {
1076
+ return {
1077
+ reachable: false,
1078
+ check: { name: "hosted /health", status: "fail", detail: "endpoint URL did not parse" }
1079
+ };
1080
+ }
1081
+ const url = `${origin}/health`;
1082
+ try {
1083
+ const res = await fetch(url, {
1084
+ method: "GET",
1085
+ headers: { Accept: "application/json" },
1086
+ signal: AbortSignal.timeout(timeoutMs)
1087
+ });
1088
+ if (!res.ok) {
1089
+ return {
1090
+ reachable: false,
1091
+ check: { name: "hosted /health", status: "fail", detail: `${url} -> HTTP ${res.status}` }
1092
+ };
1093
+ }
1094
+ let status;
1095
+ try {
1096
+ const body = await res.json();
1097
+ status = body.status;
1098
+ } catch {
1099
+ status = void 0;
1100
+ }
1101
+ if (status === "ok") {
1102
+ return { reachable: true, check: { name: "hosted /health", status: "ok", detail: url } };
1103
+ }
1104
+ return {
1105
+ reachable: false,
1106
+ check: {
1107
+ name: "hosted /health",
1108
+ status: "warn",
1109
+ detail: `${url} reachable but status=${String(status)}`
1110
+ }
1111
+ };
1112
+ } catch (error) {
1113
+ return {
1114
+ reachable: false,
1115
+ check: {
1116
+ name: "hosted /health",
1117
+ status: "fail",
1118
+ detail: `${url} unreachable: ${error instanceof Error ? error.message : String(error)}`
1119
+ }
1120
+ };
1121
+ }
1122
+ }
1123
+ async function checkAuth(ctx) {
1124
+ try {
1125
+ const auth = await ctx.resolveAuth();
1126
+ const profile = auth.profileName ? ` profile=${auth.profileName}` : "";
1127
+ return {
1128
+ auth,
1129
+ check: {
1130
+ name: "auth",
1131
+ status: "ok",
1132
+ detail: `${redactKey(auth.apiKey)} (source=${auth.source}${profile}) endpoint=${auth.endpoint}`
1133
+ }
1134
+ };
1135
+ } catch {
1136
+ return { auth: void 0, check: { name: "auth", status: "fail", detail: "no API key" } };
1137
+ }
1138
+ }
1139
+ function accountCheckFromPayload(payload) {
1140
+ const info = payload;
1141
+ const mp = info.marketplace;
1142
+ const where = mp ? mp.country_name ?? mp.country_code ?? mp.marketplace_id ?? "unknown" : "(none)";
1143
+ const sc = info.connections?.seller_central?.status ?? "unknown";
1144
+ const ads = info.connections?.amazon_ads?.status ?? "unknown";
1145
+ const domains = info.enabled_domains ?? [];
1146
+ const reads = info.tools?.read_enabled ?? 0;
1147
+ const writes = info.tools?.write_enabled ?? 0;
1148
+ return {
1149
+ name: "account",
1150
+ status: "ok",
1151
+ detail: `${where} \xB7 seller_central=${sc}, amazon_ads=${ads} \xB7 domains=${domains.length} \xB7 ${reads} read / ${writes} write tools`
1152
+ };
1153
+ }
1154
+ async function doctor(ctx) {
1155
+ const checks = [];
1156
+ checks.push({ name: "cli version", status: "info", detail: CLI_VERSION });
1157
+ checks.push({ name: "node version", status: "info", detail: process.version });
1158
+ checks.push({ name: "platform", status: "info", detail: process.platform });
1159
+ checks.push({ name: "install path", status: "info", detail: process.argv[1] ?? "(unknown)" });
1160
+ const { auth, check: authCheck } = await checkAuth(ctx);
1161
+ const authed = auth !== void 0;
1162
+ const checksWithAuth = [...checks, authCheck, checkConfigPerms(ctx.env)];
1163
+ const origin = auth ? originFromEndpoint(auth.endpoint) : void 0;
1164
+ const { reachable, check: healthCheck } = await checkHealth(origin, ctx.flags.timeoutMs);
1165
+ const checksWithHealth = auth ? [...checksWithAuth, healthCheck] : checksWithAuth;
1166
+ const finalChecks = await (async () => {
1167
+ if (!authed) {
1168
+ return checksWithHealth;
1169
+ }
1170
+ try {
1171
+ const client = await ctx.getClient();
1172
+ const result = await client.callTool("get_account_info", {});
1173
+ const payload = payloadFromResult(result);
1174
+ return [...checksWithHealth, accountCheckFromPayload(payload)];
1175
+ } catch (error) {
1176
+ const detail = error instanceof Error ? error.message : String(error);
1177
+ return [
1178
+ ...checksWithHealth,
1179
+ { name: "account", status: "fail", detail: `get_account_info failed: ${detail}` }
1180
+ ];
1181
+ }
1182
+ })();
1183
+ void ctx.flags.redact;
1184
+ const markdown = ctx.flags.format === "markdown";
1185
+ const report = markdown ? renderMarkdownReport(finalChecks) : renderTextReport(finalChecks);
1186
+ ctx.out(report);
1187
+ if (!authed) {
1188
+ return 3 /* AuthError */;
1189
+ }
1190
+ if (!reachable) {
1191
+ return 6 /* TransportFailure */;
1192
+ }
1193
+ return 0 /* Success */;
1194
+ }
1195
+
1196
+ // src/commands/profiles.ts
1197
+ import { createInterface } from "readline";
1198
+ async function readSecret(promptLabel, ctx) {
1199
+ if (process.stdin.isTTY) {
1200
+ ctx.err(promptLabel);
1201
+ return readOneLine();
1202
+ }
1203
+ return readAllStdin();
1204
+ }
1205
+ function readOneLine() {
1206
+ return new Promise((resolve, reject) => {
1207
+ const rl = createInterface({ input: process.stdin });
1208
+ rl.once("line", (line) => {
1209
+ rl.close();
1210
+ resolve(line.trim());
1211
+ });
1212
+ rl.once("error", (error) => {
1213
+ rl.close();
1214
+ reject(error);
1215
+ });
1216
+ });
1217
+ }
1218
+ function readAllStdin() {
1219
+ return new Promise((resolve, reject) => {
1220
+ const chunks = [];
1221
+ process.stdin.on("data", (chunk) => chunks.push(chunk));
1222
+ process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8").trim()));
1223
+ process.stdin.on("error", (error) => reject(error));
1224
+ });
1225
+ }
1226
+ async function profilesList(ctx) {
1227
+ const config = loadConfig(ctx.env);
1228
+ const rows = config.profiles.map((profile) => ({
1229
+ name: profile.name,
1230
+ marketplace: profile.label?.marketplace ?? "",
1231
+ active: profile.name === config.activeProfile,
1232
+ key: redactKey(profile.apiKey)
1233
+ }));
1234
+ if (ctx.flags.format === "json") {
1235
+ ctx.out(JSON.stringify(rows, null, 2));
1236
+ return 0 /* Success */;
1237
+ }
1238
+ if (rows.length === 0) {
1239
+ ctx.err("no profiles; run: agentcentral auth login");
1240
+ return 0 /* Success */;
1241
+ }
1242
+ for (const line of formatProfileTable(rows)) {
1243
+ ctx.out(line);
1244
+ }
1245
+ return 0 /* Success */;
1246
+ }
1247
+ function formatProfileTable(rows) {
1248
+ const header = ["", "NAME", "MARKETPLACE", "KEY"];
1249
+ const matrix = rows.map((row) => [
1250
+ row.active ? "*" : "",
1251
+ row.name,
1252
+ row.marketplace,
1253
+ row.key
1254
+ ]);
1255
+ const widths = header.map(
1256
+ (label, i) => matrix.reduce((max, cells) => Math.max(max, cells[i].length), label.length)
1257
+ );
1258
+ const pad = (cell, i) => cell + " ".repeat(widths[i] - cell.length);
1259
+ const render = (cells) => cells.map((cell, i) => pad(cell, i)).join(" ").trimEnd();
1260
+ return [render(header), ...matrix.map(render)];
1261
+ }
1262
+ async function profilesAdd(ctx, name) {
1263
+ if (!name) {
1264
+ throw new CliError(2 /* UsageError */, "profile name is required");
1265
+ }
1266
+ const key = await readSecret(`Paste API key for profile ${name}: `, ctx);
1267
+ if (!key) {
1268
+ throw new CliError(5 /* ValidationFailed */, "no API key provided");
1269
+ }
1270
+ if (!looksLikeApiKey(key)) {
1271
+ ctx.err("API key does not start with ac_live_; continuing.");
1272
+ }
1273
+ const profile = { name, apiKey: key };
1274
+ const withProfile = upsertProfile(loadConfig(ctx.env), profile);
1275
+ saveConfig(withProfile, ctx.env);
1276
+ const marketplace = await tryFetchMarketplace(ctx, name);
1277
+ if (marketplace) {
1278
+ const labeled = { ...profile, label: { marketplace } };
1279
+ saveConfig(upsertProfile(loadConfig(ctx.env), labeled), ctx.env);
1280
+ }
1281
+ ctx.err(`saved profile ${name}`);
1282
+ return 0 /* Success */;
1283
+ }
1284
+ async function tryFetchMarketplace(ctx, name) {
1285
+ try {
1286
+ const probeCtx = { ...ctx, flags: { ...ctx.flags, profile: name } };
1287
+ const client = await probeCtx.getClient();
1288
+ const result = await client.callTool("get_account_info", {});
1289
+ return readMarketplaceFromAccountInfo(result.structuredContent);
1290
+ } catch {
1291
+ return void 0;
1292
+ }
1293
+ }
1294
+ function readMarketplaceFromAccountInfo(structured) {
1295
+ if (typeof structured !== "object" || structured === null) {
1296
+ return void 0;
1297
+ }
1298
+ const marketplace = structured.marketplace;
1299
+ if (typeof marketplace !== "object" || marketplace === null) {
1300
+ return void 0;
1301
+ }
1302
+ const id = marketplace.marketplace_id;
1303
+ return typeof id === "string" ? id : void 0;
1304
+ }
1305
+ async function profilesUse(ctx, name) {
1306
+ if (!name) {
1307
+ throw new CliError(2 /* UsageError */, "profile name is required");
1308
+ }
1309
+ const next = setActiveProfile(loadConfig(ctx.env), name);
1310
+ saveConfig(next, ctx.env);
1311
+ ctx.err(
1312
+ `active profile set to ${name} (note: this is global state \u2014 for cron/concurrent use pass --profile or AGENT_CENTRAL_API_KEY instead).`
1313
+ );
1314
+ return 0 /* Success */;
1315
+ }
1316
+
1317
+ // src/keychain.ts
1318
+ import { Entry } from "@napi-rs/keyring";
1319
+ var SERVICE = "agentcentral-cli";
1320
+ var DEFAULT_ACCOUNT = "default";
1321
+ var DISABLE_ENV = "AGENT_CENTRAL_DISABLE_KEYCHAIN";
1322
+ function disabled() {
1323
+ return process.env[DISABLE_ENV] === "1";
1324
+ }
1325
+ function keychainAvailable() {
1326
+ if (disabled()) {
1327
+ return false;
1328
+ }
1329
+ try {
1330
+ const entry = new Entry(SERVICE, DEFAULT_ACCOUNT);
1331
+ try {
1332
+ entry.getPassword();
1333
+ } catch {
1334
+ }
1335
+ return true;
1336
+ } catch {
1337
+ return false;
1338
+ }
1339
+ }
1340
+ function keychainGet(account) {
1341
+ if (disabled()) {
1342
+ return null;
1343
+ }
1344
+ try {
1345
+ const entry = new Entry(SERVICE, account ?? DEFAULT_ACCOUNT);
1346
+ const secret = entry.getPassword();
1347
+ const trimmed = secret?.trim() ?? "";
1348
+ return trimmed.length > 0 ? trimmed : null;
1349
+ } catch {
1350
+ return null;
1351
+ }
1352
+ }
1353
+ function keychainSet(account, secret) {
1354
+ if (disabled()) {
1355
+ return false;
1356
+ }
1357
+ try {
1358
+ const entry = new Entry(SERVICE, account ?? DEFAULT_ACCOUNT);
1359
+ entry.setPassword(secret);
1360
+ return true;
1361
+ } catch {
1362
+ return false;
1363
+ }
1364
+ }
1365
+ function keychainDelete(account) {
1366
+ if (disabled()) {
1367
+ return false;
1368
+ }
1369
+ try {
1370
+ const entry = new Entry(SERVICE, account ?? DEFAULT_ACCOUNT);
1371
+ return entry.deletePassword();
1372
+ } catch {
1373
+ return false;
1374
+ }
1375
+ }
1376
+
1377
+ // src/commands/auth.ts
1378
+ var DEFAULT_PROFILE = "default";
1379
+ async function authLogin(ctx) {
1380
+ const key = await readSecret("Paste your agentcentral API key: ", ctx);
1381
+ if (!key) {
1382
+ throw new CliError(5 /* ValidationFailed */, "no API key provided");
1383
+ }
1384
+ if (keychainAvailable() && keychainSet(void 0, key)) {
1385
+ ctx.err("logged in (stored in OS keychain)");
1386
+ return 0 /* Success */;
1387
+ }
1388
+ const withProfile = upsertProfile(loadConfig(ctx.env), {
1389
+ name: DEFAULT_PROFILE,
1390
+ apiKey: key
1391
+ });
1392
+ const active = setActiveProfile(withProfile, DEFAULT_PROFILE);
1393
+ saveConfig(active, ctx.env);
1394
+ ctx.err("logged in (OS keychain unavailable; stored in 0600 config file)");
1395
+ return 0 /* Success */;
1396
+ }
1397
+ async function authStatus(ctx) {
1398
+ let auth;
1399
+ try {
1400
+ auth = await ctx.resolveAuth();
1401
+ } catch (error) {
1402
+ if (error instanceof CliError && error.code === 3 /* AuthError */) {
1403
+ ctx.err(error.message);
1404
+ return 3 /* AuthError */;
1405
+ }
1406
+ throw error;
1407
+ }
1408
+ const payload = {
1409
+ source: auth.source,
1410
+ key: redactKey(auth.apiKey),
1411
+ endpoint: auth.endpoint,
1412
+ profile: auth.profileName ?? null
1413
+ };
1414
+ ctx.out(JSON.stringify(payload, null, 2));
1415
+ return 0 /* Success */;
1416
+ }
1417
+ async function authLogout(ctx) {
1418
+ if (keychainAvailable()) {
1419
+ keychainDelete();
1420
+ }
1421
+ const next = removeProfile(loadConfig(ctx.env), DEFAULT_PROFILE);
1422
+ saveConfig(next, ctx.env);
1423
+ ctx.err("logged out");
1424
+ return 0 /* Success */;
1425
+ }
1426
+
1427
+ // src/commands/convenience.ts
1428
+ import { writeFile as writeFile2 } from "fs/promises";
1429
+ var RELATIVE_SINCE_RE = /^(\d+)d$/;
1430
+ var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
1431
+ function toIsoDate(date) {
1432
+ return date.toISOString().slice(0, 10);
1433
+ }
1434
+ function parseSince(since, now = /* @__PURE__ */ new Date()) {
1435
+ if (since === void 0) {
1436
+ return {};
1437
+ }
1438
+ const trimmed = since.trim();
1439
+ const relative = RELATIVE_SINCE_RE.exec(trimmed);
1440
+ if (relative) {
1441
+ const days = Number(relative[1]);
1442
+ const start = new Date(now.getTime() - days * 24 * 60 * 60 * 1e3);
1443
+ return { start_date: toIsoDate(start), end_date: toIsoDate(now) };
1444
+ }
1445
+ if (ISO_DATE_RE.test(trimmed)) {
1446
+ return { start_date: trimmed, end_date: toIsoDate(now) };
1447
+ }
1448
+ throw new CliError(
1449
+ 2 /* UsageError */,
1450
+ `invalid --since: ${since} (expected a lookback like "30d" or a YYYY-MM-DD date)`
1451
+ );
1452
+ }
1453
+ function extraFlags(ctx) {
1454
+ const flags = ctx.flags;
1455
+ return {
1456
+ since: flags.since,
1457
+ groupBy: flags.groupBy,
1458
+ asin: flags.asin
1459
+ };
1460
+ }
1461
+ function selectOutputValue2(result) {
1462
+ if (result.structuredContent !== void 0) {
1463
+ return result.structuredContent;
1464
+ }
1465
+ const first = result.content.find((block) => typeof block.text === "string");
1466
+ const text = first?.text;
1467
+ if (text === void 0) {
1468
+ return result.content;
1469
+ }
1470
+ try {
1471
+ return JSON.parse(text);
1472
+ } catch {
1473
+ return text;
1474
+ }
1475
+ }
1476
+ async function emit(ctx, name, args) {
1477
+ const client = await ctx.getClient();
1478
+ const response = await client.rpc(
1479
+ "tools/call",
1480
+ { name, arguments: args },
1481
+ { timeoutMs: ctx.flags.timeoutMs }
1482
+ );
1483
+ const classified = classifyResponse(response);
1484
+ const result = classified.result;
1485
+ if (!result) {
1486
+ if (classified.detail) {
1487
+ ctx.err(classified.detail);
1488
+ }
1489
+ return classified.code;
1490
+ }
1491
+ const value = selectOutputValue2(result);
1492
+ const rendered = formatOutput(value, ctx.flags.format);
1493
+ if (ctx.flags.output) {
1494
+ await writeFile2(ctx.flags.output, rendered, "utf8");
1495
+ ctx.err(`wrote result to ${ctx.flags.output}`);
1496
+ } else {
1497
+ ctx.out(rendered);
1498
+ }
1499
+ return classified.code;
1500
+ }
1501
+ function limitArg(ctx) {
1502
+ return ctx.flags.limit != null ? { limit: ctx.flags.limit } : {};
1503
+ }
1504
+ async function inventoryHealth(ctx) {
1505
+ return emit(ctx, "get_inventory_health", { ...limitArg(ctx) });
1506
+ }
1507
+ async function adsPerformance(ctx) {
1508
+ const { since } = extraFlags(ctx);
1509
+ const dates = parseSince(since);
1510
+ return emit(ctx, "get_campaign_performance", { ...dates, ...limitArg(ctx) });
1511
+ }
1512
+ async function catalogQuality(ctx) {
1513
+ const { asin } = extraFlags(ctx);
1514
+ const asinArg = asin ? { asin } : {};
1515
+ return emit(ctx, "get_listing_quality", { ...asinArg, ...limitArg(ctx) });
1516
+ }
1517
+ async function actionsHistory(ctx) {
1518
+ const { since } = extraFlags(ctx);
1519
+ const dates = parseSince(since);
1520
+ return emit(ctx, "get_action_history", { ...dates, ...limitArg(ctx) });
1521
+ }
1522
+
1523
+ // src/cli.ts
1524
+ var FORMATS = ["json", "ndjson", "table", "csv", "markdown"];
1525
+ var COMMAND_NAMES = [
1526
+ "tools list",
1527
+ "tools describe",
1528
+ "tools schema",
1529
+ "tools call",
1530
+ "mcp serve",
1531
+ "mcp config",
1532
+ "mcp test",
1533
+ "account info",
1534
+ "profiles list",
1535
+ "profiles add",
1536
+ "profiles use",
1537
+ "auth login",
1538
+ "auth status",
1539
+ "auth logout",
1540
+ "inventory health",
1541
+ "ads performance",
1542
+ "catalog quality",
1543
+ "actions history",
1544
+ "doctor"
1545
+ ];
1546
+ function normalizeArgv(argv) {
1547
+ const head = argv.slice(0, 2);
1548
+ const rest = argv.slice(2);
1549
+ const leading = [];
1550
+ for (const token of rest) {
1551
+ if (token.startsWith("-")) break;
1552
+ leading.push(token);
1553
+ }
1554
+ let matchWords = 0;
1555
+ for (const name of COMMAND_NAMES) {
1556
+ const words = name.split(" ");
1557
+ if (words.length <= leading.length && words.every((w, i) => leading[i] === w)) {
1558
+ if (words.length > matchWords) {
1559
+ matchWords = words.length;
1560
+ }
1561
+ }
1562
+ }
1563
+ if (matchWords <= 1) {
1564
+ return argv;
1565
+ }
1566
+ const joined = leading.slice(0, matchWords).join(" ");
1567
+ const tail = rest.slice(matchWords);
1568
+ return [...head, joined, ...tail];
1569
+ }
1570
+ function asString(value) {
1571
+ if (typeof value === "string") {
1572
+ const trimmed = value.trim();
1573
+ return trimmed === "" ? void 0 : trimmed;
1574
+ }
1575
+ if (typeof value === "number") {
1576
+ return String(value);
1577
+ }
1578
+ return void 0;
1579
+ }
1580
+ function asPositiveInt(value, label) {
1581
+ if (value === void 0 || value === null) {
1582
+ return void 0;
1583
+ }
1584
+ const n = typeof value === "number" ? value : Number(asString(value));
1585
+ if (!Number.isFinite(n) || n <= 0 || !Number.isInteger(n)) {
1586
+ throw new CliError(2 /* UsageError */, `--${label} must be a positive integer`);
1587
+ }
1588
+ return n;
1589
+ }
1590
+ function parseGlobalFlags(options) {
1591
+ const rawFormat = asString(options.format);
1592
+ if (rawFormat !== void 0 && !FORMATS.includes(rawFormat)) {
1593
+ throw new CliError(
1594
+ 2 /* UsageError */,
1595
+ `--format must be one of: ${FORMATS.join(", ")} (got "${rawFormat}")`
1596
+ );
1597
+ }
1598
+ const format = rawFormat ?? "json";
1599
+ const timeoutMs = asPositiveInt(options.timeout, "timeout") ?? DEFAULT_TIMEOUT_MS;
1600
+ const limit = asPositiveInt(options.limit, "limit");
1601
+ return {
1602
+ format,
1603
+ output: asString(options.output),
1604
+ profile: asString(options.profile),
1605
+ apiKeyStdin: options.apiKeyStdin === true,
1606
+ timeoutMs,
1607
+ ...limit !== void 0 ? { limit } : {},
1608
+ quiet: options.quiet === true,
1609
+ // cac maps --no-color to { color: false }; default (color absent) means colour allowed.
1610
+ noColor: options.color === false,
1611
+ verbose: options.verbose === true,
1612
+ requestId: asString(options.requestId),
1613
+ yes: options.yes === true,
1614
+ idempotencyKey: asString(options.idempotencyKey)
1615
+ };
1616
+ }
1617
+ async function readKeyFromStdin() {
1618
+ const chunks = [];
1619
+ for await (const chunk of process.stdin) {
1620
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1621
+ }
1622
+ return Buffer.concat(chunks).toString("utf8").trim();
1623
+ }
1624
+ function buildContext(flags, opts = {}) {
1625
+ const env = process.env;
1626
+ const out = (line) => {
1627
+ process.stdout.write(line + "\n");
1628
+ };
1629
+ const err = (line) => {
1630
+ if (!flags.quiet) {
1631
+ process.stderr.write(line + "\n");
1632
+ }
1633
+ };
1634
+ const mergedFlags = opts.extra ? { ...flags, ...opts.extra } : flags;
1635
+ let authPromise;
1636
+ let echoed = false;
1637
+ const resolveAuth2 = () => {
1638
+ if (!authPromise) {
1639
+ authPromise = (async () => {
1640
+ const config = loadConfig(env);
1641
+ const auth = await resolveAuth({
1642
+ flags: mergedFlags,
1643
+ env,
1644
+ config,
1645
+ readKeyFromStdin,
1646
+ keychainGet,
1647
+ warn: err
1648
+ });
1649
+ if (!echoed && !opts.silentAuthEcho) {
1650
+ echoed = true;
1651
+ const profile = auth.profileName ? ` profile=${auth.profileName}` : "";
1652
+ err(
1653
+ `using ${redactKey(auth.apiKey)} (source=${auth.source}${profile}) endpoint=${auth.endpoint}`
1654
+ );
1655
+ }
1656
+ return auth;
1657
+ })();
1658
+ }
1659
+ return authPromise;
1660
+ };
1661
+ let clientPromise;
1662
+ const getClient = () => {
1663
+ if (!clientPromise) {
1664
+ clientPromise = (async () => {
1665
+ const auth = await resolveAuth2();
1666
+ return createClient(auth, {
1667
+ retry: DEFAULT_RETRY,
1668
+ defaultTimeoutMs: flags.timeoutMs,
1669
+ requestId: flags.requestId,
1670
+ onRetry: (info) => err(`retry ${info.attempt} in ${info.delayMs}ms: ${info.reason}`)
1671
+ });
1672
+ })();
1673
+ }
1674
+ return clientPromise;
1675
+ };
1676
+ return {
1677
+ flags: mergedFlags,
1678
+ env,
1679
+ resolveAuth: resolveAuth2,
1680
+ getClient,
1681
+ out,
1682
+ err
1683
+ };
1684
+ }
1685
+ function withGlobalOptions(cmd) {
1686
+ return cmd.option("-f, --format <format>", "output format: json | ndjson | table | csv | markdown", {
1687
+ default: "json"
1688
+ }).option("-o, --output <path>", "write the result to a file instead of stdout").option("-p, --profile <name>", "use a stored profile").option("--timeout <ms>", "per-request timeout in milliseconds", { default: DEFAULT_TIMEOUT_MS }).option("--limit <n>", "row limit (capped server-side)").option("-q, --quiet", "suppress stderr diagnostics").option("--no-color", "disable coloured output").option("--verbose", "verbose diagnostics").option("--request-id <id>", "client correlation id").option("-y, --yes", "promote a write to submit (requires --idempotency-key)").option("--idempotency-key <key>", "idempotency key for submit-mode writes").option("--api-key-stdin", "read the API key from stdin");
1689
+ }
1690
+ async function run(argv) {
1691
+ const cli = cac("agentcentral");
1692
+ let pending;
1693
+ let lastQuiet = false;
1694
+ const dispatch = (handler, options, ctxOpts = {}) => {
1695
+ const flags = parseGlobalFlags(options);
1696
+ lastQuiet = flags.quiet;
1697
+ const ctx = buildContext(flags, ctxOpts);
1698
+ pending = handler(ctx);
1699
+ };
1700
+ withGlobalOptions(cli.command("tools list", "list key-scoped tools")).option("--domain <domain>", "filter by server domain").action(
1701
+ (options) => dispatch((ctx) => toolsList(ctx), options, { extra: { domain: asString(options.domain) } })
1702
+ );
1703
+ withGlobalOptions(cli.command("tools describe <tool>", "describe one tool")).action(
1704
+ (tool, options) => dispatch((ctx) => toolsDescribe(ctx, tool), options)
1705
+ );
1706
+ withGlobalOptions(cli.command("tools schema <tool>", "print a tool's JSON Schema")).action(
1707
+ (tool, options) => dispatch((ctx) => toolsSchema(ctx, tool), options)
1708
+ );
1709
+ withGlobalOptions(cli.command("tools call <tool> [input]", "invoke a tool")).option("-i, --input <spec>", "tool input as JSON, @file, a file path, or - for stdin").action(
1710
+ (tool, input, options) => dispatch((ctx) => toolsCall(ctx, tool, asString(options.input) ?? input), options)
1711
+ );
1712
+ withGlobalOptions(cli.command("mcp serve", "run the transparent stdio MCP bridge")).option("--domain <domain>", "advisory domain scope (repeatable / comma-separated)").option("--read-only", "advisory: hide write tools from tools/list").action(
1713
+ (options) => dispatch((ctx) => mcpServe(ctx), options, {
1714
+ silentAuthEcho: true,
1715
+ extra: { domains: options.domain, readOnly: options.readOnly === true }
1716
+ })
1717
+ );
1718
+ withGlobalOptions(cli.command("mcp config [client]", "print MCP client config")).option("--client <name>", "target client: claude | cursor | openclaw | hermes").action(
1719
+ (client, options) => dispatch((ctx) => mcpConfig(ctx, asString(options.client) ?? client), options)
1720
+ );
1721
+ withGlobalOptions(cli.command("mcp test", "check hosted-server reachability")).action(
1722
+ (options) => dispatch((ctx) => mcpTest(ctx), options)
1723
+ );
1724
+ withGlobalOptions(cli.command("doctor", "run a health sweep")).option("--redact", "redact account identifiers in the report").action(
1725
+ (options) => dispatch((ctx) => doctor(ctx), options, { extra: { redact: options.redact === true } })
1726
+ );
1727
+ withGlobalOptions(cli.command("account info", "show resolved account info")).action(
1728
+ (options) => dispatch((ctx) => accountInfo(ctx), options)
1729
+ );
1730
+ withGlobalOptions(cli.command("profiles list", "list stored profiles")).action(
1731
+ (options) => dispatch((ctx) => profilesList(ctx), options)
1732
+ );
1733
+ withGlobalOptions(cli.command("profiles add <name>", "add a stored profile")).action(
1734
+ (name, options) => dispatch((ctx) => profilesAdd(ctx, name), options)
1735
+ );
1736
+ withGlobalOptions(cli.command("profiles use <name>", "set the active profile")).action(
1737
+ (name, options) => dispatch((ctx) => profilesUse(ctx, name), options)
1738
+ );
1739
+ withGlobalOptions(cli.command("auth login", "store and activate an API key")).action(
1740
+ (options) => dispatch((ctx) => authLogin(ctx), options)
1741
+ );
1742
+ withGlobalOptions(cli.command("auth status", "show resolved auth")).action(
1743
+ (options) => dispatch((ctx) => authStatus(ctx), options)
1744
+ );
1745
+ withGlobalOptions(cli.command("auth logout", "remove the default profile")).action(
1746
+ (options) => dispatch((ctx) => authLogout(ctx), options)
1747
+ );
1748
+ withGlobalOptions(cli.command("inventory health", "summarize inventory health")).action(
1749
+ (options) => dispatch((ctx) => inventoryHealth(ctx), options)
1750
+ );
1751
+ withGlobalOptions(cli.command("ads performance", "summarize campaign performance")).option("--since <dur>", "lookback window: a relative duration like 30d or a YYYY-MM-DD date").option("--group-by <field>", "advisory grain hint (documented; not wired server-side)").action(
1752
+ (options) => dispatch((ctx) => adsPerformance(ctx), options, {
1753
+ extra: { since: asString(options.since), groupBy: asString(options["group-by"]) }
1754
+ })
1755
+ );
1756
+ withGlobalOptions(cli.command("catalog quality", "summarize listing quality")).option("--asin <asin>", "scope to a single ASIN").action(
1757
+ (options) => dispatch((ctx) => catalogQuality(ctx), options, { extra: { asin: asString(options.asin) } })
1758
+ );
1759
+ withGlobalOptions(cli.command("actions history", "list write-action history")).option("--since <dur>", "lookback window: a relative duration like 30d or a YYYY-MM-DD date").action(
1760
+ (options) => dispatch((ctx) => actionsHistory(ctx), options, { extra: { since: asString(options.since) } })
1761
+ );
1762
+ cli.help();
1763
+ cli.version(CLI_VERSION);
1764
+ try {
1765
+ const normalized = normalizeArgv(argv);
1766
+ cli.parse(normalized, { run: false });
1767
+ const wantsHelp = argv.includes("--help") || argv.includes("-h");
1768
+ const wantsVersion = argv.includes("--version") || argv.includes("-v");
1769
+ if (wantsHelp || wantsVersion && !cli.matchedCommand) {
1770
+ return 0 /* Success */;
1771
+ }
1772
+ cli.runMatchedCommand();
1773
+ if (pending === void 0) {
1774
+ cli.outputHelp();
1775
+ return 2 /* UsageError */;
1776
+ }
1777
+ return await pending;
1778
+ } catch (error) {
1779
+ if (error instanceof CliError) {
1780
+ if (!lastQuiet) {
1781
+ process.stderr.write(error.message + "\n");
1782
+ if (error.detail) {
1783
+ process.stderr.write(error.detail + "\n");
1784
+ }
1785
+ }
1786
+ return error.code;
1787
+ }
1788
+ const message = error instanceof Error ? error.message : String(error);
1789
+ const isUsage = error instanceof Error && error.name === "CACError";
1790
+ if (!lastQuiet) {
1791
+ process.stderr.write(message + "\n");
1792
+ }
1793
+ return isUsage ? 2 /* UsageError */ : 1 /* GeneralFailure */;
1794
+ }
1795
+ }
1796
+
1797
+ // src/index.ts
1798
+ run(process.argv).then((code) => process.exit(code)).catch((e) => {
1799
+ process.stderr.write(String(e?.message ?? e) + "\n");
1800
+ process.exit(1);
1801
+ });