@agentcentral/cli 0.1.4 → 0.1.5

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 +29 -3
  2. package/dist/index.js +180 -26
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -76,6 +76,28 @@ agentcentral tools call update_keyword_bids @bids.json \
76
76
  --yes --idempotency-key "$(uuidgen)"
77
77
  ```
78
78
 
79
+ #### Auto-pagination (`--all` / `--paginate`)
80
+
81
+ Some live-API read tools return their results across multiple pages. Pass `--all` (alias `--paginate`)
82
+ to `tools call` and the CLI follows the server's continuation block, fetching every page and emitting
83
+ the concatenated rows:
84
+
85
+ ```bash
86
+ agentcentral tools call get_sponsored_ads_entities_live '{"ad_product":"SP"}' --all
87
+ agentcentral tools call get_audience_segments '{}' --all --format ndjson
88
+ ```
89
+
90
+ Notes:
91
+
92
+ - `--all` is reads only. Combining it with a write tool or with `--yes` is a usage error (exit 2).
93
+ - For `json`, `csv`, `table`, and `markdown`, the rows from every page are accumulated and rendered
94
+ once. For `ndjson`, each page's rows stream to stdout as they arrive (incremental); with `--output`,
95
+ ndjson buffers and writes the file once instead.
96
+ - Auto-pagination stops at a 100-page cap and warns on stderr if the cap is hit, so narrow the query
97
+ to fetch the rest.
98
+ - A tool whose response carries no pagination block is a single call plus a one-line stderr note, not
99
+ an error. This is also how `--all` degrades gracefully against an older server.
100
+
79
101
  ### Convenience read commands
80
102
 
81
103
  Thin wrappers over the same hosted read tools. Each maps friendly flags onto the exact parameters its
@@ -122,9 +144,13 @@ Every command accepts: `--format/-f json|ndjson|table|csv|markdown`, `--output/-
122
144
  `--profile/-p <name>`, `--timeout <ms>`, `--limit <n>`, `--quiet/-q`, `--no-color`, `--verbose`,
123
145
  `--request-id <id>`, `--yes/-y`, `--idempotency-key <key>`, `--api-key-stdin`.
124
146
 
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.
147
+ `tools call` also accepts `--all` / `--paginate` (auto-pagination, reads only; see above).
148
+
149
+ Truncation is reported authoritatively from the server, not guessed. When the result envelope says it
150
+ hit the row limit (`more_rows_possible` / `limit_reached`), the CLI warns on stderr with the row count
151
+ (and the exact `returned N of M matching rows` phrasing when the tool supplies a total). When more
152
+ pages are available, it instead notes that you can pass `--all` to fetch them. These messages go to
153
+ stderr only (honoring `--quiet`) and never touch stdout.
128
154
 
129
155
  ## Exit codes
130
156
 
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ var DEFAULT_RETRY = {
34
34
  honorRetryAfter: true
35
35
  };
36
36
  var API_KEY_PREFIX = "ac_live_";
37
+ var MAX_AUTO_PAGES = 100;
37
38
 
38
39
  // src/config.ts
39
40
  import {
@@ -3290,6 +3291,83 @@ function formatOutput(value, format) {
3290
3291
  }
3291
3292
  }
3292
3293
 
3294
+ // src/pagination.ts
3295
+ function readNumber(source, key) {
3296
+ const value = source[key];
3297
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3298
+ }
3299
+ function readBoolean(source, key) {
3300
+ const value = source[key];
3301
+ return typeof value === "boolean" ? value : void 0;
3302
+ }
3303
+ function readPagination(structured) {
3304
+ const raw = structured.pagination;
3305
+ if (!raw || typeof raw !== "object") {
3306
+ return void 0;
3307
+ }
3308
+ const block = raw;
3309
+ const mode = block.mode;
3310
+ const continueParam = block.continue_param;
3311
+ if (mode !== "token" && mode !== "offset" || typeof continueParam !== "string") {
3312
+ return void 0;
3313
+ }
3314
+ const nextToken = typeof block.next_token === "string" || block.next_token === null ? block.next_token : void 0;
3315
+ const nextOffset = readNumber(block, "next_offset");
3316
+ const derivedHasMore = mode === "token" ? nextToken != null : nextOffset != null;
3317
+ const hasMore = readBoolean(block, "has_more") ?? derivedHasMore;
3318
+ return {
3319
+ mode,
3320
+ ...nextToken !== void 0 ? { next_token: nextToken } : {},
3321
+ ...nextOffset !== void 0 ? { next_offset: nextOffset } : {},
3322
+ continue_param: continueParam,
3323
+ has_more: hasMore
3324
+ };
3325
+ }
3326
+ function readResultMeta(result) {
3327
+ const structured = result.structuredContent;
3328
+ if (!structured || typeof structured !== "object") {
3329
+ return {};
3330
+ }
3331
+ return {
3332
+ rowCount: readNumber(structured, "row_count"),
3333
+ requestedLimit: readNumber(structured, "requested_limit"),
3334
+ limitReached: readBoolean(structured, "limit_reached"),
3335
+ moreRowsPossible: readBoolean(structured, "more_rows_possible"),
3336
+ totalMatching: readNumber(structured, "total_matching"),
3337
+ pagination: readPagination(structured)
3338
+ };
3339
+ }
3340
+ function warnFromResultMeta(ctx, meta, allRequested) {
3341
+ if (meta.pagination?.has_more && !allRequested) {
3342
+ ctx.err("note: more pages available; pass --all to fetch them.");
3343
+ return;
3344
+ }
3345
+ if (meta.moreRowsPossible || meta.limitReached) {
3346
+ const rows = meta.rowCount;
3347
+ if (typeof meta.totalMatching === "number" && typeof rows === "number") {
3348
+ ctx.err(
3349
+ `warning: returned ${rows} of ${meta.totalMatching} matching rows at the limit. Raise --limit or narrow the query (deep paging is not available for this tool).`
3350
+ );
3351
+ return;
3352
+ }
3353
+ const count = typeof rows === "number" ? `${rows} rows` : "rows";
3354
+ ctx.err(
3355
+ `warning: returned ${count} at the limit. Raise --limit or narrow the query (deep paging is not available for this tool).`
3356
+ );
3357
+ }
3358
+ }
3359
+ function readRows(result) {
3360
+ const structured = result.structuredContent;
3361
+ if (!structured || typeof structured !== "object") {
3362
+ return [];
3363
+ }
3364
+ const rows = structured.rows;
3365
+ return Array.isArray(rows) ? rows : [];
3366
+ }
3367
+ function nextContinueValue(pagination) {
3368
+ return pagination.mode === "token" ? pagination.next_token : pagination.next_offset;
3369
+ }
3370
+
3293
3371
  // src/commands/tools.ts
3294
3372
  function parseJsonArgs(text, origin) {
3295
3373
  try {
@@ -3449,14 +3527,6 @@ function selectOutputValue(result) {
3449
3527
  return text;
3450
3528
  }
3451
3529
  }
3452
- function warnIfLimitTruncated(ctx, value) {
3453
- const limit = ctx.flags.limit;
3454
- if (limit != null && Array.isArray(value) && value.length === limit) {
3455
- ctx.err(
3456
- `warning: result returned exactly --limit (${limit}) rows; output may be truncated. Raise --limit or narrow the query.`
3457
- );
3458
- }
3459
- }
3460
3530
  function echoWriteSummary(ctx, result) {
3461
3531
  const env = writeEnvelopeFrom(result);
3462
3532
  if (!env) {
@@ -3473,11 +3543,28 @@ function echoWriteSummary(ctx, result) {
3473
3543
  ctx.err("note: a prior submission with this idempotency key stands; no new write was made.");
3474
3544
  }
3475
3545
  }
3546
+ async function emitRendered(ctx, rendered) {
3547
+ if (ctx.flags.output) {
3548
+ await writeFile(ctx.flags.output, rendered, { encoding: "utf8", mode: 384 });
3549
+ ctx.err(`wrote result to ${ctx.flags.output}`);
3550
+ } else {
3551
+ ctx.out(rendered);
3552
+ }
3553
+ }
3476
3554
  async function toolsCall(ctx, name, inputSpec) {
3555
+ if (ctx.flags.all && ctx.flags.yes) {
3556
+ throw new CliError(
3557
+ 2 /* UsageError */,
3558
+ "--all is for reads only; it cannot be combined with --yes."
3559
+ );
3560
+ }
3477
3561
  const input = await resolveInput(inputSpec, readStdinOnce);
3478
3562
  const client = await ctx.getClient();
3479
3563
  const tools = await client.listTools({ timeoutMs: ctx.flags.timeoutMs });
3480
3564
  const def = requireTool(tools, name);
3565
+ if (ctx.flags.all) {
3566
+ return toolsCallAll(ctx, client, def, name, input);
3567
+ }
3481
3568
  const args = buildCallArgs(ctx, def, input);
3482
3569
  const response = await client.rpc(
3483
3570
  "tools/call",
@@ -3493,17 +3580,89 @@ async function toolsCall(ctx, name, inputSpec) {
3493
3580
  return classified.code;
3494
3581
  }
3495
3582
  echoWriteSummary(ctx, result);
3583
+ const meta = readResultMeta(result);
3584
+ warnFromResultMeta(ctx, meta, false);
3496
3585
  const value = selectOutputValue(result);
3497
- warnIfLimitTruncated(ctx, value);
3498
3586
  const rendered = formatOutput(value, ctx.flags.format);
3499
- if (ctx.flags.output) {
3500
- await writeFile(ctx.flags.output, rendered, { encoding: "utf8", mode: 384 });
3501
- ctx.err(`wrote result to ${ctx.flags.output}`);
3502
- } else {
3503
- ctx.out(rendered);
3504
- }
3587
+ await emitRendered(ctx, rendered);
3505
3588
  return classified.code;
3506
3589
  }
3590
+ async function toolsCallAll(ctx, client, def, name, input) {
3591
+ if (toolClassification(def).isWrite || ctx.flags.yes) {
3592
+ throw new CliError(
3593
+ 2 /* UsageError */,
3594
+ "--all is for reads only; it cannot be combined with a write tool or --yes."
3595
+ );
3596
+ }
3597
+ const streamNdjson = ctx.flags.format === "ndjson" && !ctx.flags.output;
3598
+ const accumulated = [];
3599
+ let args = buildCallArgs(ctx, def, input);
3600
+ let pages = 0;
3601
+ let exitCode = 0 /* Success */;
3602
+ const collect = (rows) => {
3603
+ if (streamNdjson) {
3604
+ if (rows.length > 0) {
3605
+ ctx.out(formatOutput(rows, "ndjson"));
3606
+ }
3607
+ } else {
3608
+ accumulated.push(...rows);
3609
+ }
3610
+ };
3611
+ for (; ; ) {
3612
+ if (pages >= MAX_AUTO_PAGES) {
3613
+ ctx.err(
3614
+ `warning: pagination stopped at the ${MAX_AUTO_PAGES}-page cap; more pages may remain. Narrow the query to fetch the rest.`
3615
+ );
3616
+ break;
3617
+ }
3618
+ const response = await client.rpc(
3619
+ "tools/call",
3620
+ { name, arguments: args },
3621
+ { timeoutMs: ctx.flags.timeoutMs }
3622
+ );
3623
+ pages += 1;
3624
+ const classified = classifyResponse(response);
3625
+ const result = classified.result;
3626
+ if (!result) {
3627
+ if (classified.detail) {
3628
+ ctx.err(classified.detail);
3629
+ }
3630
+ exitCode = classified.code;
3631
+ break;
3632
+ }
3633
+ if (classified.code !== 0 /* Success */) {
3634
+ if (classified.detail) {
3635
+ ctx.err(classified.detail);
3636
+ }
3637
+ collect(readRows(result));
3638
+ ctx.err(`warning: pagination stopped after ${pages} page(s) on a page error.`);
3639
+ exitCode = classified.code;
3640
+ break;
3641
+ }
3642
+ collect(readRows(result));
3643
+ const meta = readResultMeta(result);
3644
+ if (!meta.pagination) {
3645
+ if (pages === 1) {
3646
+ ctx.err("note: this tool returned no pagination block; fetched a single page.");
3647
+ }
3648
+ warnFromResultMeta(ctx, meta, true);
3649
+ break;
3650
+ }
3651
+ if (!meta.pagination.has_more) {
3652
+ break;
3653
+ }
3654
+ const next = nextContinueValue(meta.pagination);
3655
+ if (next == null) {
3656
+ break;
3657
+ }
3658
+ args = { ...args, [meta.pagination.continue_param]: next };
3659
+ }
3660
+ if (!streamNdjson) {
3661
+ const rendered = formatOutput(accumulated, ctx.flags.format);
3662
+ await emitRendered(ctx, rendered);
3663
+ }
3664
+ return exitCode;
3665
+ }
3507
3666
  async function readStdinOnce() {
3508
3667
  const chunks = [];
3509
3668
  for await (const chunk of process.stdin) {
@@ -4275,14 +4434,6 @@ function selectOutputValue2(result) {
4275
4434
  return text;
4276
4435
  }
4277
4436
  }
4278
- function warnIfLimitTruncated2(ctx, value) {
4279
- const limit = ctx.flags.limit;
4280
- if (limit != null && Array.isArray(value) && value.length === limit) {
4281
- ctx.err(
4282
- `warning: result returned exactly --limit (${limit}) rows; output may be truncated. Raise --limit or narrow the query.`
4283
- );
4284
- }
4285
- }
4286
4437
  async function emit(ctx, name, args) {
4287
4438
  const client = await ctx.getClient();
4288
4439
  const response = await client.rpc(
@@ -4298,8 +4449,9 @@ async function emit(ctx, name, args) {
4298
4449
  }
4299
4450
  return classified.code;
4300
4451
  }
4452
+ const meta = readResultMeta(result);
4453
+ warnFromResultMeta(ctx, meta, ctx.flags.all === true);
4301
4454
  const value = selectOutputValue2(result);
4302
- warnIfLimitTruncated2(ctx, value);
4303
4455
  const rendered = formatOutput(value, ctx.flags.format);
4304
4456
  if (ctx.flags.output) {
4305
4457
  await writeFile2(ctx.flags.output, rendered, { encoding: "utf8", mode: 384 });
@@ -4442,7 +4594,9 @@ function parseGlobalFlags(options) {
4442
4594
  verbose: options.verbose === true,
4443
4595
  requestId: asString(options.requestId),
4444
4596
  yes: options.yes === true,
4445
- idempotencyKey: asString(options.idempotencyKey)
4597
+ idempotencyKey: asString(options.idempotencyKey),
4598
+ // --all and its --paginate alias both land here.
4599
+ all: options.all === true || options.paginate === true
4446
4600
  };
4447
4601
  }
4448
4602
  async function readKeyFromStdin() {
@@ -4537,7 +4691,7 @@ async function run(argv) {
4537
4691
  withGlobalOptions(cli.command("tools schema <tool>", "print a tool's JSON Schema")).action(
4538
4692
  (tool, options) => dispatch((ctx) => toolsSchema(ctx, tool), options)
4539
4693
  );
4540
- 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(
4694
+ 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").option("--all, --paginate", "follow the server's pagination block and fetch every page (reads only)").action(
4541
4695
  (tool, input, options) => dispatch((ctx) => toolsCall(ctx, tool, asString(options.input) ?? input), options)
4542
4696
  );
4543
4697
  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(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcentral/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "license": "MIT",
5
5
  "description": "agentcentral CLI — local MCP bridge and generic tool runner for Amazon seller data",
6
6
  "keywords": [