@agentcentral/cli 0.1.3 → 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 +186 -29
  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
@@ -17,7 +17,10 @@ var CliError = class extends Error {
17
17
  };
18
18
 
19
19
  // src/constants.ts
20
- var CLI_VERSION = "0.1.2";
20
+ import { createRequire } from "module";
21
+ var nodeRequire = createRequire(import.meta.url);
22
+ var pkg = nodeRequire("../package.json");
23
+ var CLI_VERSION = pkg.version;
21
24
  var DEFAULT_MCP_ENDPOINT = "https://mcp.agentcentral.to/mcp";
22
25
  var ENV_API_KEY = "AGENT_CENTRAL_API_KEY";
23
26
  var ENV_API_KEY_LEGACY = "AGENTCENTRAL_API_KEY";
@@ -31,6 +34,7 @@ var DEFAULT_RETRY = {
31
34
  honorRetryAfter: true
32
35
  };
33
36
  var API_KEY_PREFIX = "ac_live_";
37
+ var MAX_AUTO_PAGES = 100;
34
38
 
35
39
  // src/config.ts
36
40
  import {
@@ -3287,6 +3291,83 @@ function formatOutput(value, format) {
3287
3291
  }
3288
3292
  }
3289
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
+
3290
3371
  // src/commands/tools.ts
3291
3372
  function parseJsonArgs(text, origin) {
3292
3373
  try {
@@ -3446,14 +3527,6 @@ function selectOutputValue(result) {
3446
3527
  return text;
3447
3528
  }
3448
3529
  }
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
- }
3457
3530
  function echoWriteSummary(ctx, result) {
3458
3531
  const env = writeEnvelopeFrom(result);
3459
3532
  if (!env) {
@@ -3470,11 +3543,28 @@ function echoWriteSummary(ctx, result) {
3470
3543
  ctx.err("note: a prior submission with this idempotency key stands; no new write was made.");
3471
3544
  }
3472
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
+ }
3473
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
+ }
3474
3561
  const input = await resolveInput(inputSpec, readStdinOnce);
3475
3562
  const client = await ctx.getClient();
3476
3563
  const tools = await client.listTools({ timeoutMs: ctx.flags.timeoutMs });
3477
3564
  const def = requireTool(tools, name);
3565
+ if (ctx.flags.all) {
3566
+ return toolsCallAll(ctx, client, def, name, input);
3567
+ }
3478
3568
  const args = buildCallArgs(ctx, def, input);
3479
3569
  const response = await client.rpc(
3480
3570
  "tools/call",
@@ -3490,17 +3580,89 @@ async function toolsCall(ctx, name, inputSpec) {
3490
3580
  return classified.code;
3491
3581
  }
3492
3582
  echoWriteSummary(ctx, result);
3583
+ const meta = readResultMeta(result);
3584
+ warnFromResultMeta(ctx, meta, false);
3493
3585
  const value = selectOutputValue(result);
3494
- warnIfLimitTruncated(ctx, value);
3495
3586
  const rendered = formatOutput(value, ctx.flags.format);
3496
- if (ctx.flags.output) {
3497
- await writeFile(ctx.flags.output, rendered, { encoding: "utf8", mode: 384 });
3498
- ctx.err(`wrote result to ${ctx.flags.output}`);
3499
- } else {
3500
- ctx.out(rendered);
3501
- }
3587
+ await emitRendered(ctx, rendered);
3502
3588
  return classified.code;
3503
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
+ }
3504
3666
  async function readStdinOnce() {
3505
3667
  const chunks = [];
3506
3668
  for await (const chunk of process.stdin) {
@@ -4067,7 +4229,7 @@ async function profilesUse(ctx, name) {
4067
4229
  }
4068
4230
 
4069
4231
  // src/keychain.ts
4070
- import { createRequire } from "module";
4232
+ import { createRequire as createRequire2 } from "module";
4071
4233
  var SERVICE = "agentcentral-cli";
4072
4234
  var DEFAULT_ACCOUNT = "default";
4073
4235
  var DISABLE_ENV = "AGENT_CENTRAL_DISABLE_KEYCHAIN";
@@ -4076,7 +4238,7 @@ var loaderOverride = void 0;
4076
4238
  function disabled() {
4077
4239
  return process.env[DISABLE_ENV] === "1";
4078
4240
  }
4079
- var requireNative = createRequire(import.meta.url);
4241
+ var requireNative = createRequire2(import.meta.url);
4080
4242
  function loadEntryClass() {
4081
4243
  if (entryClassCache !== void 0) {
4082
4244
  return entryClassCache;
@@ -4272,14 +4434,6 @@ function selectOutputValue2(result) {
4272
4434
  return text;
4273
4435
  }
4274
4436
  }
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
- }
4283
4437
  async function emit(ctx, name, args) {
4284
4438
  const client = await ctx.getClient();
4285
4439
  const response = await client.rpc(
@@ -4295,8 +4449,9 @@ async function emit(ctx, name, args) {
4295
4449
  }
4296
4450
  return classified.code;
4297
4451
  }
4452
+ const meta = readResultMeta(result);
4453
+ warnFromResultMeta(ctx, meta, ctx.flags.all === true);
4298
4454
  const value = selectOutputValue2(result);
4299
- warnIfLimitTruncated2(ctx, value);
4300
4455
  const rendered = formatOutput(value, ctx.flags.format);
4301
4456
  if (ctx.flags.output) {
4302
4457
  await writeFile2(ctx.flags.output, rendered, { encoding: "utf8", mode: 384 });
@@ -4439,7 +4594,9 @@ function parseGlobalFlags(options) {
4439
4594
  verbose: options.verbose === true,
4440
4595
  requestId: asString(options.requestId),
4441
4596
  yes: options.yes === true,
4442
- 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
4443
4600
  };
4444
4601
  }
4445
4602
  async function readKeyFromStdin() {
@@ -4534,7 +4691,7 @@ async function run(argv) {
4534
4691
  withGlobalOptions(cli.command("tools schema <tool>", "print a tool's JSON Schema")).action(
4535
4692
  (tool, options) => dispatch((ctx) => toolsSchema(ctx, tool), options)
4536
4693
  );
4537
- 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(
4538
4695
  (tool, input, options) => dispatch((ctx) => toolsCall(ctx, tool, asString(options.input) ?? input), options)
4539
4696
  );
4540
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.3",
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": [