@agentcentral/cli 0.1.2 → 0.1.3

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 (3) hide show
  1. package/README.md +33 -3
  2. package/dist/index.js +213 -136
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -53,6 +53,7 @@ The config lives at `${XDG_CONFIG_HOME:-~/.config}/agentcentral/config.json`, wr
53
53
  ```bash
54
54
  agentcentral tools list # key-scoped tools (json)
55
55
  agentcentral tools list --format table # aligned table
56
+ agentcentral tools list --domain inventory # filter by server domain
56
57
  agentcentral tools describe get_orders # full descriptor
57
58
  agentcentral tools schema get_orders # raw JSON Schema
58
59
 
@@ -62,6 +63,12 @@ agentcentral tools call get_orders @args.json
62
63
  echo '{"limit":5}' | agentcentral tools call get_orders -
63
64
  ```
64
65
 
66
+ A new hosted tool is callable (`tools list`, `describe`, `schema`, `call`) with no CLI release. The one
67
+ caveat is domain filtering: `tools list --domain` and `mcp serve --domain` fall back to the bundled
68
+ `TOOL_DOMAINS` registry from `@agent-central/shared` when the server omits `_meta` domain hints, so a
69
+ brand-new tool will not appear under a domain filter until that registry is updated (or the server emits
70
+ the tool's domain via `_meta`).
71
+
65
72
  Write tools default to a non-mutating **preview**. To submit, pass `--yes` and an `--idempotency-key`:
66
73
 
67
74
  ```bash
@@ -69,6 +76,25 @@ agentcentral tools call update_keyword_bids @bids.json \
69
76
  --yes --idempotency-key "$(uuidgen)"
70
77
  ```
71
78
 
79
+ ### Convenience read commands
80
+
81
+ Thin wrappers over the same hosted read tools. Each maps friendly flags onto the exact parameters its
82
+ backing tool declares. They return facts only and add no recommendations or agentcentral-authored next
83
+ actions.
84
+
85
+ ```bash
86
+ agentcentral inventory health # -> get_inventory_health (--limit)
87
+ agentcentral inventory risk --asin B00... # -> get_inventory_risk_triage (--asin, --limit)
88
+ agentcentral ads performance --since 30d # -> get_campaign_performance (--since, --limit)
89
+ agentcentral catalog quality --asin B00... # -> get_listing_quality (--asin, --limit)
90
+ agentcentral actions history --since 7d # -> get_action_history (--since, --limit; needs agent_actions:read scope)
91
+ agentcentral today --fulfillment-channel AFN # -> get_todays_sales (current-day only; --fulfillment-channel AFN|MFN, --limit)
92
+ agentcentral orders --since 30d --asin B00... # -> get_orders (--since, --asin, --fulfillment-channel AFN|MFN, --limit)
93
+ ```
94
+
95
+ `--since` accepts a relative lookback (`30d`) or an absolute `YYYY-MM-DD` date. `today` is current-day
96
+ only and takes no `--since`.
97
+
72
98
  ### Run the stdio MCP bridge
73
99
 
74
100
  `mcp serve` relays newline-delimited JSON-RPC between a local MCP client and the hosted server. stdout
@@ -92,9 +118,13 @@ agentcentral doctor --format markdown
92
118
 
93
119
  ## Global flags
94
120
 
95
- Every command accepts: `--format/-f json|ndjson|table`, `--output/-o <path>`, `--profile/-p <name>`,
96
- `--timeout <ms>`, `--limit <n>`, `--quiet/-q`, `--no-color`, `--verbose`, `--request-id <id>`,
97
- `--yes/-y`, `--idempotency-key <key>`, `--api-key-stdin`.
121
+ Every command accepts: `--format/-f json|ndjson|table|csv|markdown`, `--output/-o <path>`,
122
+ `--profile/-p <name>`, `--timeout <ms>`, `--limit <n>`, `--quiet/-q`, `--no-color`, `--verbose`,
123
+ `--request-id <id>`, `--yes/-y`, `--idempotency-key <key>`, `--api-key-stdin`.
124
+
125
+ When `--limit <n>` is set and the result comes back with exactly `n` rows, the CLI emits a best-effort
126
+ truncation WARNING to stderr. The hosted server exposes no truncation flag or total-row count, so this
127
+ is a heuristic, it only fires when `--limit` is set, and it never touches stdout.
98
128
 
99
129
  ## Exit codes
100
130
 
package/dist/index.js CHANGED
@@ -34,13 +34,14 @@ var API_KEY_PREFIX = "ac_live_";
34
34
 
35
35
  // src/config.ts
36
36
  import {
37
- chmodSync,
38
37
  closeSync,
39
38
  existsSync,
40
39
  fstatSync,
41
40
  mkdirSync,
42
41
  openSync,
43
42
  readFileSync,
43
+ renameSync,
44
+ unlinkSync,
44
45
  writeFileSync
45
46
  } from "fs";
46
47
  import { homedir } from "os";
@@ -107,9 +108,23 @@ function loadConfig(env = process.env) {
107
108
  function saveConfig(config, env = process.env) {
108
109
  const path = configPath(env);
109
110
  const dir = dirname(path);
111
+ const tmp = `${path}.${process.pid}.tmp`;
110
112
  mkdirSync(dir, { recursive: true, mode: 448 });
111
- writeFileSync(path, JSON.stringify(config, null, 2), { mode: 384 });
112
- chmodSync(path, 384);
113
+ const data = JSON.stringify(config, null, 2);
114
+ try {
115
+ writeFileSync(tmp, data, { mode: 384, flag: "wx" });
116
+ renameSync(tmp, path);
117
+ } catch (error) {
118
+ try {
119
+ unlinkSync(tmp);
120
+ } catch {
121
+ }
122
+ throw new CliError(
123
+ 1 /* GeneralFailure */,
124
+ `failed to write config ${path}`,
125
+ error instanceof Error ? error.message : String(error)
126
+ );
127
+ }
113
128
  }
114
129
  function upsertProfile(config, profile) {
115
130
  const exists = config.profiles.some((p) => p.name === profile.name);
@@ -243,7 +258,128 @@ async function resolveAuth(input) {
243
258
  };
244
259
  }
245
260
 
261
+ // src/exit-codes.ts
262
+ var RPC_INVALID_PARAMS = -32602;
263
+ var DETAIL_MAX_LENGTH = 180;
264
+ var PERMISSION_PATTERN = /no_amazon|not connected|expired|suspended|revoked|past_due|trial expired|permission|not authorized|scope/i;
265
+ var VALIDATION_PATTERN = /invalid|validation|must be|required/i;
266
+ function writeEnvelopeFrom(result) {
267
+ const structured = result.structuredContent;
268
+ if (!structured) {
269
+ return void 0;
270
+ }
271
+ const hasWriteShape = "mode" in structured || "status" in structured || "requested_count" in structured;
272
+ if (!hasWriteShape) {
273
+ return void 0;
274
+ }
275
+ return structured;
276
+ }
277
+ function toolClassification(def) {
278
+ const hint = def.annotations;
279
+ const annotationWrite = hint?.destructiveHint === true || hint?.readOnlyHint === false;
280
+ const properties = def.inputSchema.properties;
281
+ const props = properties && typeof properties === "object" ? properties : {};
282
+ const schemaWrite = "confirm" in props && "idempotency_key" in props;
283
+ const isWrite = annotationWrite || schemaWrite;
284
+ return {
285
+ isWrite,
286
+ supportsPreview: isWrite
287
+ };
288
+ }
289
+ function classifyToolText(text) {
290
+ if (PERMISSION_PATTERN.test(text)) {
291
+ return 4 /* PermissionDenied */;
292
+ }
293
+ if (VALIDATION_PATTERN.test(text)) {
294
+ return 5 /* ValidationFailed */;
295
+ }
296
+ return void 0;
297
+ }
298
+ function joinContentText(result) {
299
+ return result.content.map((block) => typeof block.text === "string" ? block.text : "").filter((text) => text.length > 0).join(" ").trim();
300
+ }
301
+ function truncateDetail(text) {
302
+ if (text.length <= DETAIL_MAX_LENGTH) {
303
+ return text;
304
+ }
305
+ return `${text.slice(0, DETAIL_MAX_LENGTH)}\u2026`;
306
+ }
307
+ function classifyWriteStatus(status) {
308
+ switch (status) {
309
+ case "validation_error":
310
+ return 5 /* ValidationFailed */;
311
+ case "partial":
312
+ return 9 /* PartialWrite */;
313
+ case "blocked":
314
+ case "failed":
315
+ return 7 /* ToolExecutionFailed */;
316
+ case "duplicate_blocked":
317
+ case "accepted":
318
+ case "preview":
319
+ default:
320
+ return 0 /* Success */;
321
+ }
322
+ }
323
+ function summarizeWriteEnvelope(env) {
324
+ const parts = [];
325
+ if (typeof env.requested_count === "number") {
326
+ parts.push(`requested=${env.requested_count}`);
327
+ }
328
+ if (typeof env.accepted_count === "number") {
329
+ parts.push(`accepted=${env.accepted_count}`);
330
+ }
331
+ if (typeof env.failed_count === "number") {
332
+ parts.push(`failed=${env.failed_count}`);
333
+ }
334
+ if (typeof env.previewed_count === "number") {
335
+ parts.push(`previewed=${env.previewed_count}`);
336
+ }
337
+ const status = env.status ?? "unknown";
338
+ return parts.length > 0 ? `${status} (${parts.join(", ")})` : status;
339
+ }
340
+ function classifyResponse(response) {
341
+ if (response.error) {
342
+ const message = response.error.message;
343
+ const code = response.error.code === RPC_INVALID_PARAMS ? 5 /* ValidationFailed */ : classifyToolText(message) ?? 7 /* ToolExecutionFailed */;
344
+ return {
345
+ code,
346
+ status: "rpc_error",
347
+ detail: message
348
+ };
349
+ }
350
+ const result = response.result;
351
+ const env = writeEnvelopeFrom(result);
352
+ if (env?.status) {
353
+ return {
354
+ code: classifyWriteStatus(env.status),
355
+ status: env.status,
356
+ detail: summarizeWriteEnvelope(env),
357
+ result
358
+ };
359
+ }
360
+ if (result.isError) {
361
+ const text = joinContentText(result);
362
+ const code = classifyToolText(text) ?? 7 /* ToolExecutionFailed */;
363
+ return {
364
+ code,
365
+ status: "tool_error",
366
+ detail: truncateDetail(text),
367
+ result
368
+ };
369
+ }
370
+ return {
371
+ code: 0 /* Success */,
372
+ status: "ok",
373
+ detail: truncateDetail(joinContentText(result)),
374
+ result
375
+ };
376
+ }
377
+
246
378
  // src/transport.ts
379
+ var RPC_INVALID_PARAMS2 = -32602;
380
+ function classifyRpcError(error) {
381
+ return error.code === RPC_INVALID_PARAMS2 ? 5 /* ValidationFailed */ : classifyToolText(error.message) ?? 7 /* ToolExecutionFailed */;
382
+ }
247
383
  function parseSseJsonRpc(text) {
248
384
  const dataLines = text.trim().split(/\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
249
385
  return JSON.parse(dataLines.length ? dataLines.join("\n") : text);
@@ -293,13 +429,14 @@ function createClient(auth, opts = {}) {
293
429
  counter += 1;
294
430
  return counter;
295
431
  };
296
- async function attempt(method, params, id, timeoutMs) {
432
+ async function attempt(method, params, id, timeoutMs, notification) {
433
+ const requestBody = notification ? { jsonrpc: "2.0", method, params } : { jsonrpc: "2.0", id, method, params };
297
434
  let res;
298
435
  try {
299
436
  res = await fetch(auth.endpoint, {
300
437
  method: "POST",
301
438
  headers,
302
- body: JSON.stringify({ jsonrpc: "2.0", id, method, params }),
439
+ body: JSON.stringify(requestBody),
303
440
  signal: AbortSignal.timeout(timeoutMs)
304
441
  });
305
442
  } catch (error) {
@@ -336,16 +473,20 @@ function createClient(auth, opts = {}) {
336
473
  method
337
474
  );
338
475
  }
476
+ if (notification) {
477
+ return { jsonrpc: "2.0" };
478
+ }
339
479
  return parseSseJsonRpc(text);
340
480
  }
341
481
  async function rpc(method, params, rpcOpts) {
342
482
  const timeoutMs = rpcOpts?.timeoutMs ?? defaultTimeoutMs;
483
+ const notification = rpcOpts?.notification === true;
343
484
  const id = rpcOpts?.id ?? nextId();
344
485
  const maxAttempts = Math.max(1, retry.maxAttempts);
345
486
  let lastReason = "transport failure";
346
487
  for (let n = 1; n <= maxAttempts; n += 1) {
347
488
  try {
348
- return await attempt(method, params ?? {}, id, timeoutMs);
489
+ return await attempt(method, params ?? {}, id, timeoutMs, notification);
349
490
  } catch (error) {
350
491
  if (error instanceof CliError) {
351
492
  throw error;
@@ -370,7 +511,7 @@ function createClient(auth, opts = {}) {
370
511
  async function listTools(listOpts) {
371
512
  const response = await rpc("tools/list", {}, { timeoutMs: listOpts?.timeoutMs });
372
513
  if (response.error) {
373
- throw new CliError(7 /* ToolExecutionFailed */, response.error.message, "tools/list");
514
+ throw new CliError(classifyRpcError(response.error), response.error.message, "tools/list");
374
515
  }
375
516
  const result = response.result ?? {};
376
517
  return result.tools ?? [];
@@ -382,7 +523,7 @@ function createClient(auth, opts = {}) {
382
523
  { timeoutMs: callOpts?.timeoutMs }
383
524
  );
384
525
  if (response.error) {
385
- throw new CliError(7 /* ToolExecutionFailed */, response.error.message, name);
526
+ throw new CliError(classifyRpcError(response.error), response.error.message, name);
386
527
  }
387
528
  return response.result ?? {};
388
529
  }
@@ -1473,7 +1614,7 @@ var TOOL_DOMAINS = [
1473
1614
  { name: "get_buybox_status", description: "Live Buy Box status plus daily win-rate history" },
1474
1615
  { name: "get_competitive_pricing", description: "Live price points plus historical pricing snapshots" },
1475
1616
  { name: "get_offer_listings", description: "Live seller offers for an ASIN" },
1476
- { name: "get_replenishment_offers", description: "Live and historical Subscribe and Save replenishment offer status" },
1617
+ { name: "get_subscribe_and_save", description: "Subscribe & Save offer status, history, and Amazon-provided metrics" },
1477
1618
  { name: "get_fba_eligibility", description: "Check FBA eligibility per ASIN" },
1478
1619
  { name: "get_listing_restrictions", description: "Listing restrictions and approvals" }
1479
1620
  ]
@@ -2032,7 +2173,7 @@ var TOOL_MARKETPLACE_SUPPORT_DECLARED_TOOL_NAMES = [
2032
2173
  "get_buybox_status",
2033
2174
  "get_competitive_pricing",
2034
2175
  "get_offer_listings",
2035
- "get_replenishment_offers",
2176
+ "get_subscribe_and_save",
2036
2177
  "get_fba_eligibility",
2037
2178
  "get_listing_restrictions",
2038
2179
  "get_financial_events",
@@ -3146,123 +3287,6 @@ function formatOutput(value, format) {
3146
3287
  }
3147
3288
  }
3148
3289
 
3149
- // src/exit-codes.ts
3150
- var RPC_INVALID_PARAMS = -32602;
3151
- var DETAIL_MAX_LENGTH = 180;
3152
- var PERMISSION_PATTERN = /no_amazon|not connected|expired|suspended|revoked|past_due|trial expired|permission|not authorized|scope/i;
3153
- var VALIDATION_PATTERN = /invalid|validation|must be|required/i;
3154
- function writeEnvelopeFrom(result) {
3155
- const structured = result.structuredContent;
3156
- if (!structured) {
3157
- return void 0;
3158
- }
3159
- const hasWriteShape = "mode" in structured || "status" in structured || "requested_count" in structured;
3160
- if (!hasWriteShape) {
3161
- return void 0;
3162
- }
3163
- return structured;
3164
- }
3165
- function toolClassification(def) {
3166
- const hint = def.annotations;
3167
- const annotationWrite = hint?.destructiveHint === true || hint?.readOnlyHint === false;
3168
- const properties = def.inputSchema.properties;
3169
- const props = properties && typeof properties === "object" ? properties : {};
3170
- const schemaWrite = "confirm" in props && "idempotency_key" in props;
3171
- const isWrite = annotationWrite || schemaWrite;
3172
- return {
3173
- isWrite,
3174
- supportsPreview: isWrite
3175
- };
3176
- }
3177
- function classifyToolText(text) {
3178
- if (PERMISSION_PATTERN.test(text)) {
3179
- return 4 /* PermissionDenied */;
3180
- }
3181
- if (VALIDATION_PATTERN.test(text)) {
3182
- return 5 /* ValidationFailed */;
3183
- }
3184
- return void 0;
3185
- }
3186
- function joinContentText(result) {
3187
- return result.content.map((block) => typeof block.text === "string" ? block.text : "").filter((text) => text.length > 0).join(" ").trim();
3188
- }
3189
- function truncateDetail(text) {
3190
- if (text.length <= DETAIL_MAX_LENGTH) {
3191
- return text;
3192
- }
3193
- return `${text.slice(0, DETAIL_MAX_LENGTH)}\u2026`;
3194
- }
3195
- function classifyWriteStatus(status) {
3196
- switch (status) {
3197
- case "validation_error":
3198
- return 5 /* ValidationFailed */;
3199
- case "partial":
3200
- return 9 /* PartialWrite */;
3201
- case "blocked":
3202
- case "failed":
3203
- return 7 /* ToolExecutionFailed */;
3204
- case "duplicate_blocked":
3205
- case "accepted":
3206
- case "preview":
3207
- default:
3208
- return 0 /* Success */;
3209
- }
3210
- }
3211
- function summarizeWriteEnvelope(env) {
3212
- const parts = [];
3213
- if (typeof env.requested_count === "number") {
3214
- parts.push(`requested=${env.requested_count}`);
3215
- }
3216
- if (typeof env.accepted_count === "number") {
3217
- parts.push(`accepted=${env.accepted_count}`);
3218
- }
3219
- if (typeof env.failed_count === "number") {
3220
- parts.push(`failed=${env.failed_count}`);
3221
- }
3222
- if (typeof env.previewed_count === "number") {
3223
- parts.push(`previewed=${env.previewed_count}`);
3224
- }
3225
- const status = env.status ?? "unknown";
3226
- return parts.length > 0 ? `${status} (${parts.join(", ")})` : status;
3227
- }
3228
- function classifyResponse(response) {
3229
- if (response.error) {
3230
- const message = response.error.message;
3231
- const code = response.error.code === RPC_INVALID_PARAMS ? 5 /* ValidationFailed */ : classifyToolText(message) ?? 7 /* ToolExecutionFailed */;
3232
- return {
3233
- code,
3234
- status: "rpc_error",
3235
- detail: message
3236
- };
3237
- }
3238
- const result = response.result;
3239
- const env = writeEnvelopeFrom(result);
3240
- if (env?.status) {
3241
- return {
3242
- code: classifyWriteStatus(env.status),
3243
- status: env.status,
3244
- detail: summarizeWriteEnvelope(env),
3245
- result
3246
- };
3247
- }
3248
- if (result.isError) {
3249
- const text = joinContentText(result);
3250
- const code = classifyToolText(text) ?? 7 /* ToolExecutionFailed */;
3251
- return {
3252
- code,
3253
- status: "tool_error",
3254
- detail: truncateDetail(text),
3255
- result
3256
- };
3257
- }
3258
- return {
3259
- code: 0 /* Success */,
3260
- status: "ok",
3261
- detail: truncateDetail(joinContentText(result)),
3262
- result
3263
- };
3264
- }
3265
-
3266
3290
  // src/commands/tools.ts
3267
3291
  function parseJsonArgs(text, origin) {
3268
3292
  try {
@@ -3422,6 +3446,14 @@ function selectOutputValue(result) {
3422
3446
  return text;
3423
3447
  }
3424
3448
  }
3449
+ function warnIfLimitTruncated(ctx, value) {
3450
+ const limit = ctx.flags.limit;
3451
+ if (limit != null && Array.isArray(value) && value.length === limit) {
3452
+ ctx.err(
3453
+ `warning: result returned exactly --limit (${limit}) rows; output may be truncated. Raise --limit or narrow the query.`
3454
+ );
3455
+ }
3456
+ }
3425
3457
  function echoWriteSummary(ctx, result) {
3426
3458
  const env = writeEnvelopeFrom(result);
3427
3459
  if (!env) {
@@ -3459,6 +3491,7 @@ async function toolsCall(ctx, name, inputSpec) {
3459
3491
  }
3460
3492
  echoWriteSummary(ctx, result);
3461
3493
  const value = selectOutputValue(result);
3494
+ warnIfLimitTruncated(ctx, value);
3462
3495
  const rendered = formatOutput(value, ctx.flags.format);
3463
3496
  if (ctx.flags.output) {
3464
3497
  await writeFile(ctx.flags.output, rendered, { encoding: "utf8", mode: 384 });
@@ -3516,7 +3549,7 @@ async function handleMessage(line, client, opts) {
3516
3549
  const method = typeof message.method === "string" ? message.method : "";
3517
3550
  if (message.id === void 0) {
3518
3551
  try {
3519
- await client.rpc(method, message.params);
3552
+ await client.rpc(method, message.params, { notification: true });
3520
3553
  } catch {
3521
3554
  }
3522
3555
  return null;
@@ -3589,9 +3622,9 @@ function pump(ctx, client, opts, input = process.stdin) {
3589
3622
  return new Promise((resolve, reject) => {
3590
3623
  input.setEncoding("utf8");
3591
3624
  let buffer = "";
3592
- let chain = Promise.resolve();
3625
+ const inflight = /* @__PURE__ */ new Set();
3593
3626
  const dispatch = (line) => {
3594
- chain = chain.then(async () => {
3627
+ const task = (async () => {
3595
3628
  try {
3596
3629
  const out = await handleMessage(line, client, opts);
3597
3630
  if (out !== null) {
@@ -3600,6 +3633,10 @@ function pump(ctx, client, opts, input = process.stdin) {
3600
3633
  } catch (error) {
3601
3634
  ctx.err(`bridge error: ${error instanceof Error ? error.message : String(error)}`);
3602
3635
  }
3636
+ })();
3637
+ inflight.add(task);
3638
+ void task.finally(() => {
3639
+ inflight.delete(task);
3603
3640
  });
3604
3641
  };
3605
3642
  input.on("data", (data) => {
@@ -3621,10 +3658,10 @@ function pump(ctx, client, opts, input = process.stdin) {
3621
3658
  if (tail.trim() !== "") {
3622
3659
  dispatch(tail);
3623
3660
  }
3624
- chain.then(() => {
3661
+ Promise.allSettled([...inflight]).then(() => {
3625
3662
  ctx.err("bridge stopped (stdin closed)");
3626
3663
  resolve(0 /* Success */);
3627
- }).catch(reject);
3664
+ }, reject);
3628
3665
  });
3629
3666
  });
3630
3667
  }
@@ -4030,17 +4067,37 @@ async function profilesUse(ctx, name) {
4030
4067
  }
4031
4068
 
4032
4069
  // src/keychain.ts
4033
- import { Entry } from "@napi-rs/keyring";
4070
+ import { createRequire } from "module";
4034
4071
  var SERVICE = "agentcentral-cli";
4035
4072
  var DEFAULT_ACCOUNT = "default";
4036
4073
  var DISABLE_ENV = "AGENT_CENTRAL_DISABLE_KEYCHAIN";
4074
+ var entryClassCache = void 0;
4075
+ var loaderOverride = void 0;
4037
4076
  function disabled() {
4038
4077
  return process.env[DISABLE_ENV] === "1";
4039
4078
  }
4079
+ var requireNative = createRequire(import.meta.url);
4080
+ function loadEntryClass() {
4081
+ if (entryClassCache !== void 0) {
4082
+ return entryClassCache;
4083
+ }
4084
+ try {
4085
+ const loaded = loaderOverride ? loaderOverride() : requireNative("@napi-rs/keyring").Entry;
4086
+ entryClassCache = loaded;
4087
+ return loaded;
4088
+ } catch {
4089
+ entryClassCache = null;
4090
+ return null;
4091
+ }
4092
+ }
4040
4093
  function keychainAvailable() {
4041
4094
  if (disabled()) {
4042
4095
  return false;
4043
4096
  }
4097
+ const Entry = loadEntryClass();
4098
+ if (!Entry) {
4099
+ return false;
4100
+ }
4044
4101
  try {
4045
4102
  const entry = new Entry(SERVICE, DEFAULT_ACCOUNT);
4046
4103
  try {
@@ -4056,6 +4113,10 @@ function keychainGet(account) {
4056
4113
  if (disabled()) {
4057
4114
  return null;
4058
4115
  }
4116
+ const Entry = loadEntryClass();
4117
+ if (!Entry) {
4118
+ return null;
4119
+ }
4059
4120
  try {
4060
4121
  const entry = new Entry(SERVICE, account ?? DEFAULT_ACCOUNT);
4061
4122
  const secret = entry.getPassword();
@@ -4069,6 +4130,10 @@ function keychainSet(account, secret) {
4069
4130
  if (disabled()) {
4070
4131
  return false;
4071
4132
  }
4133
+ const Entry = loadEntryClass();
4134
+ if (!Entry) {
4135
+ return false;
4136
+ }
4072
4137
  try {
4073
4138
  const entry = new Entry(SERVICE, account ?? DEFAULT_ACCOUNT);
4074
4139
  entry.setPassword(secret);
@@ -4081,6 +4146,10 @@ function keychainDelete(account) {
4081
4146
  if (disabled()) {
4082
4147
  return false;
4083
4148
  }
4149
+ const Entry = loadEntryClass();
4150
+ if (!Entry) {
4151
+ return false;
4152
+ }
4084
4153
  try {
4085
4154
  const entry = new Entry(SERVICE, account ?? DEFAULT_ACCOUNT);
4086
4155
  return entry.deletePassword();
@@ -4169,7 +4238,6 @@ function extraFlags(ctx) {
4169
4238
  const flags = ctx.flags;
4170
4239
  return {
4171
4240
  since: flags.since,
4172
- groupBy: flags.groupBy,
4173
4241
  asin: flags.asin,
4174
4242
  fulfillmentChannel: flags.fulfillmentChannel
4175
4243
  };
@@ -4204,6 +4272,14 @@ function selectOutputValue2(result) {
4204
4272
  return text;
4205
4273
  }
4206
4274
  }
4275
+ function warnIfLimitTruncated2(ctx, value) {
4276
+ const limit = ctx.flags.limit;
4277
+ if (limit != null && Array.isArray(value) && value.length === limit) {
4278
+ ctx.err(
4279
+ `warning: result returned exactly --limit (${limit}) rows; output may be truncated. Raise --limit or narrow the query.`
4280
+ );
4281
+ }
4282
+ }
4207
4283
  async function emit(ctx, name, args) {
4208
4284
  const client = await ctx.getClient();
4209
4285
  const response = await client.rpc(
@@ -4220,6 +4296,7 @@ async function emit(ctx, name, args) {
4220
4296
  return classified.code;
4221
4297
  }
4222
4298
  const value = selectOutputValue2(result);
4299
+ warnIfLimitTruncated2(ctx, value);
4223
4300
  const rendered = formatOutput(value, ctx.flags.format);
4224
4301
  if (ctx.flags.output) {
4225
4302
  await writeFile2(ctx.flags.output, rendered, { encoding: "utf8", mode: 384 });
@@ -4499,9 +4576,9 @@ async function run(argv) {
4499
4576
  withGlobalOptions(cli.command("inventory health", "summarize inventory health")).action(
4500
4577
  (options) => dispatch((ctx) => inventoryHealth(ctx), options)
4501
4578
  );
4502
- 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(
4579
+ withGlobalOptions(cli.command("ads performance", "summarize campaign performance")).option("--since <dur>", "lookback window: a relative duration like 30d or a YYYY-MM-DD date").action(
4503
4580
  (options) => dispatch((ctx) => adsPerformance(ctx), options, {
4504
- extra: { since: asString(options.since), groupBy: asString(options.groupBy) }
4581
+ extra: { since: asString(options.since) }
4505
4582
  })
4506
4583
  );
4507
4584
  withGlobalOptions(cli.command("catalog quality", "summarize listing quality")).option("--asin <asin>", "scope to a single ASIN").action(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcentral/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "license": "MIT",
5
5
  "description": "agentcentral CLI — local MCP bridge and generic tool runner for Amazon seller data",
6
6
  "keywords": [