@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.
- package/README.md +29 -3
- package/dist/index.js +180 -26
- 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
|
-
|
|
126
|
-
|
|
127
|
-
is
|
|
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
|
-
|
|
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(
|