@typeship-ax/cli 0.23.1 → 0.24.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.
- package/AGENTS.md +2 -2
- package/README.md +2 -2
- package/api.md +2 -2
- package/dist/arguments.d.ts +9 -2
- package/dist/arguments.d.ts.map +1 -1
- package/dist/arguments.js +17 -6
- package/dist/cli-agent.d.ts +42 -1
- package/dist/cli-agent.d.ts.map +1 -1
- package/dist/cli-agent.js +187 -18
- package/dist/cli.js +341 -76
- package/dist/fields.d.ts +8 -1
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +91 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/ops.d.ts +5 -0
- package/dist/ops.d.ts.map +1 -1
- package/dist/ops.js +66 -6
- package/dist/resources/deliveries.d.ts +1 -1
- package/dist/resources/deliveries.d.ts.map +1 -1
- package/dist/resources/deliveries.js +1 -1
- package/dist/resources/drafts.d.ts +1 -1
- package/dist/resources/drafts.d.ts.map +1 -1
- package/dist/resources/drafts.js +1 -1
- package/dist/resources/generations.d.ts +2 -2
- package/dist/resources/generations.d.ts.map +1 -1
- package/dist/resources/generations.js +2 -2
- package/dist/resources/releases.d.ts +1 -1
- package/dist/resources/releases.d.ts.map +1 -1
- package/dist/resources/releases.js +1 -1
- package/dist/resources/spec-revisions.d.ts +1 -1
- package/dist/resources/spec-revisions.d.ts.map +1 -1
- package/dist/resources/spec-revisions.js +1 -1
- package/dist/resources/targets.d.ts +1 -1
- package/dist/resources/targets.d.ts.map +1 -1
- package/dist/resources/targets.js +1 -1
- package/dist/table.d.ts +28 -0
- package/dist/table.d.ts.map +1 -0
- package/dist/table.js +167 -0
- package/dist/type-docs.d.ts +61 -0
- package/dist/type-docs.d.ts.map +1 -0
- package/dist/type-docs.js +174 -0
- package/package.json +1 -1
- package/src/arguments.ts +19 -7
- package/src/cli-agent.ts +196 -17
- package/src/cli.ts +330 -76
- package/src/fields.ts +81 -5
- package/src/index.ts +3 -3
- package/src/ops.ts +69 -6
- package/src/resources/deliveries.ts +2 -2
- package/src/resources/drafts.ts +2 -2
- package/src/resources/generations.ts +4 -4
- package/src/resources/releases.ts +2 -2
- package/src/resources/spec-revisions.ts +2 -2
- package/src/resources/targets.ts +2 -2
- package/src/table.ts +167 -0
- package/src/type-docs.ts +205 -0
package/dist/cli.js
CHANGED
|
@@ -21,13 +21,15 @@ import { fileURLToPath } from "node:url";
|
|
|
21
21
|
import { TypeshipClient, formatDebugEvent } from "./index.js";
|
|
22
22
|
import { asApiResult, mediaTypeForPath, validateAgainstSchema, ValidationError } from "./core/http.js";
|
|
23
23
|
import { SCHEMAS, DEFS } from "./schemas.js";
|
|
24
|
-
import { GLOBALS, OMITTED_OPS, OPS, buildArgs, findOp, missingRequired } from "./ops.js";
|
|
25
|
-
import { MCP_CLIENTS, requiredScopes, agentGuide, agentBlock, agentInstructionsFile, agentMode, bundleProperty, claimProperty, classifyApiError, classifyAuthFailure, collectionProperty, detectHarness, envelope, exitCodeFor, findMcpClient, installSkills, mcpConfigured, pendingClaims, recordClaim, summarizeDoctor, upsertAgentBlock, writeBundle, writeMcpConfig, } from "./cli-agent.js";
|
|
24
|
+
import { GLOBALS, INPUT_TYPES, OMITTED_OPS, OPS, buildArgs, findOp, missingRequired } from "./ops.js";
|
|
25
|
+
import { MCP_CLIENTS, requiredScopes, agentGuide, agentBlock, agentInstructionsFile, agentMode, bundleProperty, claimProperty, classifyApiError, classifyAuthFailure, collectionProperty, detectHarness, envelope, exitCodeFor, findMcpClient, formatRequestPreview, installSkills, mcpConfigured, pendingClaims, recordClaim, requestPreview, summarizeDoctor, upsertAgentBlock, writeBundle, writeMcpConfig, } from "./cli-agent.js";
|
|
26
26
|
import { relativeDate } from "./dates.js";
|
|
27
27
|
import { checkValue } from "./arguments.js";
|
|
28
28
|
import { docsReadCommand, docsReadTarget, fetchDocsText, resolveDocsContentUrl, searchConnectedGuides } from "./docs.js";
|
|
29
29
|
import { projectFields, unmatchedFields, unmatchedFieldsMessage } from "./fields.js";
|
|
30
|
+
import { renderTable } from "./table.js";
|
|
30
31
|
import { SEARCH_PAGE_SIZE, rankOperations } from "./search.js";
|
|
32
|
+
import { argumentPathText, findInputType, inputTypeText, namedTypesIn } from "./type-docs.js";
|
|
31
33
|
const BIN = "typeship";
|
|
32
34
|
/** The MCP server's key in client configs: the API's name, even when this
|
|
33
35
|
* command was renamed away from a vendor's. It reads this command's login. */
|
|
@@ -39,6 +41,8 @@ const AUTH_SCALARS = [{ "option": "bearerToken", "flag": "token", "env": "TYPESH
|
|
|
39
41
|
const HOSTED_MCP_HEADERS = { "Authorization": "Bearer ${TYPESHIP_TOKEN}" };
|
|
40
42
|
const HOSTED_MCP_NOTE = null;
|
|
41
43
|
const BASIC = null;
|
|
44
|
+
/** Header and query names the API's key schemes use: --dry-run redacts them. */
|
|
45
|
+
const CREDENTIAL_NAMES = [];
|
|
42
46
|
/** The spec declares no security, so the token is offered, never required. */
|
|
43
47
|
const AUTH_UNDECLARED = false;
|
|
44
48
|
/** Repeated --header "Name: value" flags for this invocation. */
|
|
@@ -47,7 +51,7 @@ let HEADER_FLAGS = [];
|
|
|
47
51
|
const EXCLUDED_OPS = 0;
|
|
48
52
|
/** Generated CLI operations that are intentionally unavailable to MCP. */
|
|
49
53
|
const MCP_EXCLUDED_OPS = 0;
|
|
50
|
-
const VERSION = "0.
|
|
54
|
+
const VERSION = "0.24.0";
|
|
51
55
|
const API_VERSION = "1.0.0";
|
|
52
56
|
const SPEC_FORMAT = "openapi";
|
|
53
57
|
const IDENTITY_POLICY = {};
|
|
@@ -92,7 +96,7 @@ const API_TITLE = "Typeship";
|
|
|
92
96
|
* API parameter with the same name (`accounts list --cursor <c>`) still
|
|
93
97
|
* takes its value. Boolean API parameters are recognized once the command
|
|
94
98
|
* is known. */
|
|
95
|
-
const CORE_BOOLEAN_FLAGS = new Set(["all", "version", "non-interactive", "debug", "validate", "yes", "force", "json"]);
|
|
99
|
+
const CORE_BOOLEAN_FLAGS = new Set(["all", "version", "non-interactive", "debug", "validate", "yes", "force", "json", "dry-run"]);
|
|
96
100
|
const BUILTIN_BOOLEAN_FLAGS = {
|
|
97
101
|
login: ["with-token", "no-browser", "device"],
|
|
98
102
|
logout: ["local"],
|
|
@@ -235,29 +239,44 @@ function out(value) {
|
|
|
235
239
|
}
|
|
236
240
|
/** --fields a,b.c: the dotted paths to keep in API results (null = everything). Set in main(). */
|
|
237
241
|
let FIELDS = null;
|
|
242
|
+
/** --format table: print API results as text for a person instead of JSON. Opt-in only; set in main(). */
|
|
243
|
+
let TABLE = false;
|
|
244
|
+
/** An API result on stdout: JSON, or the --format table view of the same value. */
|
|
245
|
+
function printResult(value, resource, collectionField = null) {
|
|
246
|
+
if (!TABLE) {
|
|
247
|
+
out(value);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
process.stdout.write(renderTable(value, { width: process.stdout.columns || 120, heading: (text) => paintOut("bold", text), collectionField, resource }));
|
|
251
|
+
}
|
|
238
252
|
/** Whether the command being run writes: an --fields mistake on a write
|
|
239
253
|
* must not tempt anyone into running it again. Set in main(). */
|
|
240
254
|
let FIELDS_AFTER_WRITE = false;
|
|
241
255
|
/** Keep only FIELDS of a result: arrays item by item, objects by dotted path;
|
|
242
256
|
* scalars untouched. A path that matches nothing is an error naming the keys
|
|
243
|
-
* that exist, never a silent {}
|
|
244
|
-
|
|
257
|
+
* that exist, never a silent {}; one the response schema declares (an
|
|
258
|
+
* optional key no item has) is simply absent. */
|
|
259
|
+
function project(value, perItem, schema) {
|
|
245
260
|
if (FIELDS === null)
|
|
246
261
|
return value;
|
|
247
|
-
const unmatched = unmatchedFields(value, FIELDS);
|
|
262
|
+
const unmatched = unmatchedFields(value, FIELDS, schema);
|
|
248
263
|
if (unmatched.length > 0)
|
|
249
264
|
failUnmatchedFields(unmatched, perItem, value);
|
|
250
265
|
return projectFields(value, FIELDS);
|
|
251
266
|
}
|
|
267
|
+
/** The declared schema of the list a result holds under `key`. */
|
|
268
|
+
function listSchemaOf(schema, key) {
|
|
269
|
+
return schema?.properties?.[key];
|
|
270
|
+
}
|
|
252
271
|
/** --fields over a stream (--all, events): paths no item has matched yet.
|
|
253
272
|
* The stream is printed as it arrives, so the check fails at its end. */
|
|
254
|
-
function streamFieldsCheck() {
|
|
273
|
+
function streamFieldsCheck(schema) {
|
|
255
274
|
let pending = null;
|
|
256
275
|
return {
|
|
257
276
|
item(value) {
|
|
258
277
|
if (FIELDS === null)
|
|
259
278
|
return value;
|
|
260
|
-
const unmatched = unmatchedFields(value, FIELDS);
|
|
279
|
+
const unmatched = unmatchedFields(value, FIELDS, schema);
|
|
261
280
|
pending = pending === null ? unmatched : pending.filter((p) => unmatched.some((u) => u.path === p.path));
|
|
262
281
|
return projectFields(value, FIELDS);
|
|
263
282
|
},
|
|
@@ -271,10 +290,13 @@ function failUnmatchedFields(unmatched, perItem, result) {
|
|
|
271
290
|
return failWith({
|
|
272
291
|
code: "FIELDS_UNMATCHED",
|
|
273
292
|
message: unmatchedFieldsMessage(unmatched, perItem),
|
|
274
|
-
nextSteps:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
293
|
+
nextSteps: [
|
|
294
|
+
FIELDS_AFTER_WRITE
|
|
295
|
+
? "This command has already run; do not run it again to change --fields." + (result !== undefined ? " Its full result is in detail.result." : "")
|
|
296
|
+
: result !== undefined ? "The full result is in detail.result; use it rather than running the command again." : "",
|
|
297
|
+
(FIELDS_AFTER_WRITE ? "Next time, use" : "To project a later run, use") + " --fields from the available keys" + (perItem ? " (fields apply to each item)" : "") + ", or omit --fields for the whole result.",
|
|
298
|
+
].filter((step) => step !== ""),
|
|
299
|
+
detail: { unmatched, ...(result !== undefined ? { result } : {}) },
|
|
278
300
|
});
|
|
279
301
|
}
|
|
280
302
|
/** Thrown after scheduling exit so sync callers stop; main() swallows it. */
|
|
@@ -1152,60 +1174,172 @@ function commandSummaries() {
|
|
|
1152
1174
|
}));
|
|
1153
1175
|
}
|
|
1154
1176
|
/**
|
|
1155
|
-
* help --json:
|
|
1156
|
-
*
|
|
1157
|
-
*
|
|
1177
|
+
* help --json: discovery as data, bounded so one call cannot fill an agent's
|
|
1178
|
+
* context window on a large API. The default is an index of resources and
|
|
1179
|
+
* command names; "help <resource> --json" pages through one resource's
|
|
1180
|
+
* commands with their methods, paths and summaries; "help <resource>
|
|
1181
|
+
* <command> --json" is one command with its flags; "help --json --all" is
|
|
1182
|
+
* every command with every flag. Full schemas stay behind the docs command.
|
|
1158
1183
|
*/
|
|
1159
|
-
|
|
1184
|
+
const HELP_INDEX_NAMES = 40;
|
|
1185
|
+
const HELP_INDEX_BYTES = 16_000;
|
|
1186
|
+
const HELP_PAGE_SIZE = 50;
|
|
1187
|
+
function helpHeader(detail) {
|
|
1188
|
+
return { schema_version: "3", detail, name: BIN, version: VERSION, api: API_TITLE, api_version: API_VERSION, spec_format: SPEC_FORMAT };
|
|
1189
|
+
}
|
|
1190
|
+
function helpDiscovery() {
|
|
1191
|
+
return {
|
|
1192
|
+
resource: BIN + " help <resource> --json",
|
|
1193
|
+
command: BIN + " help <resource> <command> --json",
|
|
1194
|
+
search: BIN + " docs search <term> --json",
|
|
1195
|
+
operation: BIN + " docs <resource> <command> --json",
|
|
1196
|
+
all: BIN + " help --json --all",
|
|
1197
|
+
note: "Find a command by resource or search, read its flags with help <resource> <command> --json, and its complete schemas with docs. --all prints every command with every flag at once.",
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
function helpCoverage() {
|
|
1201
|
+
return EXCLUDED_OPS > 0 ? { coverage: { generated_operations: OPS.length, total_operations: OPS.length + EXCLUDED_OPS } } : {};
|
|
1202
|
+
}
|
|
1203
|
+
function opsByResource() {
|
|
1160
1204
|
const byResource = new Map();
|
|
1161
|
-
for (const
|
|
1162
|
-
const list = byResource.get(
|
|
1163
|
-
list.push(
|
|
1164
|
-
byResource.set(
|
|
1205
|
+
for (const op of OPS) {
|
|
1206
|
+
const list = byResource.get(op.command[0]) ?? [];
|
|
1207
|
+
list.push(op);
|
|
1208
|
+
byResource.set(op.command[0], list);
|
|
1165
1209
|
}
|
|
1210
|
+
return byResource;
|
|
1211
|
+
}
|
|
1212
|
+
/** One command with its flags: an entry of help --json --all. */
|
|
1213
|
+
function helpEntry(op, summary) {
|
|
1166
1214
|
return {
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1215
|
+
command: op.command[1],
|
|
1216
|
+
method: summary.method,
|
|
1217
|
+
path: summary.path,
|
|
1218
|
+
...(summary.summary ? { summary: summary.summary } : {}),
|
|
1219
|
+
paginated: summary.paginated,
|
|
1220
|
+
safety: op.safety,
|
|
1221
|
+
destructive: summary.destructive,
|
|
1222
|
+
auth: summary.auth,
|
|
1223
|
+
positional: op.params.filter((p) => p.kind === "path").map((p) => p.name),
|
|
1224
|
+
flags: summary.flags,
|
|
1225
|
+
details_command: BIN + " docs " + op.command[0] + " " + op.command[1] + " --json",
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
function helpAll() {
|
|
1229
|
+
const summaries = commandSummaries();
|
|
1230
|
+
const byResource = opsByResource();
|
|
1231
|
+
return {
|
|
1232
|
+
...helpHeader("all"),
|
|
1173
1233
|
usage: BIN + " <resource> <command> [args] [--flags]",
|
|
1174
|
-
resources: [...byResource.entries()].map(([resource,
|
|
1234
|
+
resources: [...byResource.entries()].map(([resource, ops]) => ({
|
|
1175
1235
|
resource,
|
|
1176
|
-
commands:
|
|
1177
|
-
const op = OPS.find((o) => o.command[0] === resource && o.command[1] === c.command);
|
|
1178
|
-
return {
|
|
1179
|
-
command: c.command,
|
|
1180
|
-
method: c.method,
|
|
1181
|
-
path: c.path,
|
|
1182
|
-
...(c.summary ? { summary: c.summary } : {}),
|
|
1183
|
-
paginated: c.paginated,
|
|
1184
|
-
safety: op.safety,
|
|
1185
|
-
destructive: c.destructive,
|
|
1186
|
-
auth: c.auth,
|
|
1187
|
-
positional: op.params.filter((p) => p.kind === "path").map((p) => p.name),
|
|
1188
|
-
flags: c.flags,
|
|
1189
|
-
details_command: BIN + " docs " + resource + " " + c.command + " --json",
|
|
1190
|
-
};
|
|
1191
|
-
}),
|
|
1236
|
+
commands: ops.map((op) => helpEntry(op, summaries[OPS.indexOf(op)])),
|
|
1192
1237
|
})),
|
|
1193
|
-
...(
|
|
1194
|
-
|
|
1195
|
-
generated_operations: OPS.length,
|
|
1196
|
-
total_operations: OPS.length + EXCLUDED_OPS,
|
|
1197
|
-
},
|
|
1198
|
-
} : {}),
|
|
1199
|
-
discovery: {
|
|
1200
|
-
search: BIN + " docs search <term> --json",
|
|
1201
|
-
operation: BIN + " docs <resource> <command> --json",
|
|
1202
|
-
note: "Choose an operation from this index, then read only that operation's complete schemas and example arguments.",
|
|
1203
|
-
},
|
|
1238
|
+
...helpCoverage(),
|
|
1239
|
+
discovery: helpDiscovery(),
|
|
1204
1240
|
builtins: BUILTIN_COMMANDS,
|
|
1205
|
-
global_flags: ["--help", "--version", "--debug", "--non-interactive", "--mode agent|human", "--yes", "--force", "--color on|off|auto", "--credentials @<JSON-file>|-", "--header 'Name: value'", "--timeout <seconds>", "--base-url <url>", "--profile <name>", "--data '<json>' | @<file> | -", "--fields <a,b.c>", "--all", "--validate", "--out <dir>", ...AUTH_SCALARS.map((a) => "--" + a.flag + " <value>")],
|
|
1241
|
+
global_flags: ["--help", "--version", "--debug", "--non-interactive", "--mode agent|human", "--yes", "--force", "--color on|off|auto", "--credentials @<JSON-file>|-", "--header 'Name: value'", "--timeout <seconds>", "--base-url <url>", "--profile <name>", "--data '<json>' | @<file> | -", "--fields <a,b.c>", "--all", "--validate", "--dry-run", "--out <dir>", ...AUTH_SCALARS.map((a) => "--" + a.flag + " <value>")],
|
|
1206
1242
|
auth_env_vars: agentContext().authEnvVars,
|
|
1207
1243
|
};
|
|
1208
1244
|
}
|
|
1245
|
+
/** Resources and command names. Past HELP_INDEX_BYTES a resource keeps only its count. */
|
|
1246
|
+
function helpIndex() {
|
|
1247
|
+
const all = helpAll();
|
|
1248
|
+
let bytes = 0;
|
|
1249
|
+
let overBudget = false;
|
|
1250
|
+
let truncated = false;
|
|
1251
|
+
const resources = [...opsByResource().entries()].map(([resource, ops]) => {
|
|
1252
|
+
const names = ops.map((op) => op.command[1]);
|
|
1253
|
+
const entry = { resource, command_count: names.length, commands: names.slice(0, HELP_INDEX_NAMES) };
|
|
1254
|
+
const size = JSON.stringify(entry).length;
|
|
1255
|
+
if (overBudget || bytes + size > HELP_INDEX_BYTES) {
|
|
1256
|
+
overBudget = truncated = true;
|
|
1257
|
+
return { resource, command_count: names.length };
|
|
1258
|
+
}
|
|
1259
|
+
bytes += size;
|
|
1260
|
+
if (names.length > HELP_INDEX_NAMES)
|
|
1261
|
+
truncated = true;
|
|
1262
|
+
return entry;
|
|
1263
|
+
});
|
|
1264
|
+
return {
|
|
1265
|
+
...helpHeader("index"),
|
|
1266
|
+
usage: all.usage,
|
|
1267
|
+
command_count: OPS.length,
|
|
1268
|
+
resource_count: resources.length,
|
|
1269
|
+
resources,
|
|
1270
|
+
...(truncated ? { truncated: "Some resources list only their first " + HELP_INDEX_NAMES + " command names or only a count. Run " + BIN + " help <resource> --json for a resource's commands." } : {}),
|
|
1271
|
+
...helpCoverage(),
|
|
1272
|
+
discovery: all.discovery,
|
|
1273
|
+
builtins: all.builtins,
|
|
1274
|
+
global_flags: all.global_flags,
|
|
1275
|
+
auth_env_vars: all.auth_env_vars,
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
function helpTarget(resource, method) {
|
|
1279
|
+
const ops = opsByResource().get(resource);
|
|
1280
|
+
if (!ops) {
|
|
1281
|
+
const omitted = omittedCommand(resource, method);
|
|
1282
|
+
if (omitted)
|
|
1283
|
+
failOmitted(omitted);
|
|
1284
|
+
const suggestion = didYouMean(resource, opsByResource().keys());
|
|
1285
|
+
fail(2, "Unknown command: " + resource + "." + (suggestion ? " Did you mean '" + BIN + " help " + suggestion + " --json'?" : ""), undefined, ["Run '" + BIN + " help --json' for the resources and their commands."]);
|
|
1286
|
+
}
|
|
1287
|
+
if (method === undefined)
|
|
1288
|
+
return ops;
|
|
1289
|
+
const op = findOp(resource, method);
|
|
1290
|
+
if (!op) {
|
|
1291
|
+
const omitted = omittedCommand(resource, method);
|
|
1292
|
+
if (omitted)
|
|
1293
|
+
failOmitted(omitted);
|
|
1294
|
+
const suggestion = didYouMean(method, ops.map((o) => o.command[1]));
|
|
1295
|
+
fail(2, "Unknown command: " + resource + " " + method + "." + (suggestion ? " Did you mean '" + BIN + " help " + resource + " " + suggestion + " --json'?" : ""), undefined, ["Run '" + BIN + " help " + resource + " --json' for its commands."]);
|
|
1296
|
+
}
|
|
1297
|
+
return [op];
|
|
1298
|
+
}
|
|
1299
|
+
async function cmdHelpJson(parsed) {
|
|
1300
|
+
const [, resource, method, extra] = parsed.positionals;
|
|
1301
|
+
if (extra !== undefined)
|
|
1302
|
+
fail(2, "help --json takes at most a resource and a command.", undefined, ["Run '" + BIN + " help --json' for the index."]);
|
|
1303
|
+
const all = parsed.flags.get("all") === true;
|
|
1304
|
+
const pageFlag = parsed.flags.get("page");
|
|
1305
|
+
const page = pageFlag === undefined ? 1 : Number(pageFlag);
|
|
1306
|
+
if (!Number.isInteger(page) || page < 1)
|
|
1307
|
+
fail(2, "--page expects a whole number from 1.");
|
|
1308
|
+
if (resource === undefined) {
|
|
1309
|
+
if (pageFlag !== undefined)
|
|
1310
|
+
fail(2, "--page applies to help <resource> --json.");
|
|
1311
|
+
out(all ? helpAll() : helpIndex());
|
|
1312
|
+
await flushExit(0);
|
|
1313
|
+
}
|
|
1314
|
+
const ops = helpTarget(resource, method);
|
|
1315
|
+
const summaries = commandSummaries();
|
|
1316
|
+
if (method !== undefined) {
|
|
1317
|
+
out({ ...helpHeader("command"), resource, ...helpEntry(ops[0], summaries[OPS.indexOf(ops[0])]) });
|
|
1318
|
+
await flushExit(0);
|
|
1319
|
+
}
|
|
1320
|
+
if (all) {
|
|
1321
|
+
out({ ...helpHeader("resource"), resource, command_count: ops.length, commands: ops.map((op) => helpEntry(op, summaries[OPS.indexOf(op)])) });
|
|
1322
|
+
await flushExit(0);
|
|
1323
|
+
}
|
|
1324
|
+
const pages = Math.max(1, Math.ceil(ops.length / HELP_PAGE_SIZE));
|
|
1325
|
+
if (page > pages)
|
|
1326
|
+
fail(2, "--page " + page + " is past the last page (" + pages + ") of " + resource + ".", undefined, ["Run '" + BIN + " help " + resource + " --json' for the first page."]);
|
|
1327
|
+
const commands = ops.slice((page - 1) * HELP_PAGE_SIZE, page * HELP_PAGE_SIZE).map((op) => {
|
|
1328
|
+
const s = summaries[OPS.indexOf(op)];
|
|
1329
|
+
return { command: op.command[1], method: s.method, path: s.path, ...(s.summary ? { summary: s.summary } : {}), paginated: s.paginated, safety: op.safety, positional: op.params.filter((p) => p.kind === "path").map((p) => p.name) };
|
|
1330
|
+
});
|
|
1331
|
+
out({
|
|
1332
|
+
...helpHeader("resource"),
|
|
1333
|
+
resource,
|
|
1334
|
+
command_count: ops.length,
|
|
1335
|
+
page,
|
|
1336
|
+
pages,
|
|
1337
|
+
commands,
|
|
1338
|
+
...(page < pages ? { next_command: BIN + " help " + resource + " --json --page " + (page + 1) } : {}),
|
|
1339
|
+
discovery: { command: BIN + " help " + resource + " <command> --json", operation: BIN + " docs " + resource + " <command> --json", all: BIN + " help " + resource + " --json --all" },
|
|
1340
|
+
});
|
|
1341
|
+
await flushExit(0);
|
|
1342
|
+
}
|
|
1209
1343
|
async function cmdAgentGuide(parsed) {
|
|
1210
1344
|
if (parsed.help) {
|
|
1211
1345
|
process.stdout.write(BIN + " agent-guide [--format json] — how an agent should drive this CLI: conventions, first command, docs, MCP, skills, next steps. JSON.\n");
|
|
@@ -1581,7 +1715,7 @@ function completionFlagsFor(op) {
|
|
|
1581
1715
|
}
|
|
1582
1716
|
return { flags, values };
|
|
1583
1717
|
}
|
|
1584
|
-
const COMPLETION_GLOBAL_FLAGS = ["--help", "--version", "--non-interactive", "--color", "--credentials", "--header", "--timeout", "--base-url", "--profile", "--data", "--fields", "--all", "--validate", "--debug", "--mode", "--yes", "--force", "--out", ...AUTH_SCALARS.map((a) => "--" + a.flag)];
|
|
1718
|
+
const COMPLETION_GLOBAL_FLAGS = ["--help", "--version", "--non-interactive", "--color", "--credentials", "--header", "--timeout", "--base-url", "--profile", "--data", "--fields", "--all", "--validate", "--dry-run", "--debug", "--mode", "--yes", "--force", "--out", ...AUTH_SCALARS.map((a) => "--" + a.flag)];
|
|
1585
1719
|
const BUILTIN_WORDS = {
|
|
1586
1720
|
config: ["list", "get", "set", "unset", "path"],
|
|
1587
1721
|
completion: ["bash", "zsh", "fish"],
|
|
@@ -1742,7 +1876,9 @@ function referenceFor(op, includeSchemas = false) {
|
|
|
1742
1876
|
lines.push("", paintOut("bold", label + ":"));
|
|
1743
1877
|
for (const p of params) {
|
|
1744
1878
|
const name = p.kind === "path" ? "<" + p.name + ">" : "--" + p.flag;
|
|
1745
|
-
|
|
1879
|
+
// An object flag reads as its named type, which docs read explains.
|
|
1880
|
+
const named = objectTypeName(op, p);
|
|
1881
|
+
lines.push(" " + padPaint("cyan", name, 30) + (named ? INPUT_TYPES.args[op.tool][p.name] : typeLabel(p)) + (p.required ? " " + paintOut("yellow", "(required)") : ""));
|
|
1746
1882
|
const values = p.type === "array" ? p.items?.enum : p.enum;
|
|
1747
1883
|
if (values && values.join("|").length > 24)
|
|
1748
1884
|
lines.push(" one of: " + values.join(", "));
|
|
@@ -1766,10 +1902,31 @@ function referenceFor(op, includeSchemas = false) {
|
|
|
1766
1902
|
lines.push("", paintOut("bold", "Output schema:"), JSON.stringify(op.outputSchema, null, 2));
|
|
1767
1903
|
}
|
|
1768
1904
|
else {
|
|
1905
|
+
const nested = op.params.find((p) => objectTypeName(op, p) || p.type === "object" || (p.type === "array" && p.items?.type === "object"));
|
|
1906
|
+
if (nested) {
|
|
1907
|
+
const named = objectTypeName(op, nested);
|
|
1908
|
+
lines.push("", "Nested fields: " + BIN + " docs " + op.command.join(" ") + " --path " + nested.name + " gives a flag's type and all its fields; extend the path (" + nested.name + ".<field>) to go deeper."
|
|
1909
|
+
+ (named ? " Named types: " + BIN + " docs read " + named + "." : ""));
|
|
1910
|
+
}
|
|
1769
1911
|
lines.push("", "Add --schema for the complete input/output schemas, or --json for the machine contract.");
|
|
1770
1912
|
}
|
|
1771
1913
|
return lines.join("\n");
|
|
1772
1914
|
}
|
|
1915
|
+
/** The named input object a flag takes (IssueFilter), when it has one. */
|
|
1916
|
+
function objectTypeName(op, p) {
|
|
1917
|
+
return namedTypesIn(INPUT_TYPES, INPUT_TYPES.args[op.tool]?.[p.name]).find((name) => INPUT_TYPES.types[name].fields);
|
|
1918
|
+
}
|
|
1919
|
+
function docsTypeHint(name) {
|
|
1920
|
+
return BIN + " docs read " + name;
|
|
1921
|
+
}
|
|
1922
|
+
/** docs --path: one nested argument, or a usage error naming the fields. */
|
|
1923
|
+
async function docsPathExit(root, path) {
|
|
1924
|
+
const found = argumentPathText(INPUT_TYPES, root, path, docsTypeHint);
|
|
1925
|
+
if (!found.ok)
|
|
1926
|
+
fail(2, found.message + (found.available.length > 0 ? " Fields: " + found.available.join(", ") + "." : ""));
|
|
1927
|
+
process.stdout.write(found.text + "\n");
|
|
1928
|
+
return flushExit(0);
|
|
1929
|
+
}
|
|
1773
1930
|
/** Opens a browser for a person; under an agent it prints the URL instead of opening anything. */
|
|
1774
1931
|
function openInBrowser(url, parsed) {
|
|
1775
1932
|
if (parsed && nonInteractive(parsed)) {
|
|
@@ -1788,11 +1945,12 @@ async function cmdDocs(parsed) {
|
|
|
1788
1945
|
"",
|
|
1789
1946
|
" " + BIN + " docs overview",
|
|
1790
1947
|
" " + BIN + " docs <resource> <command> operation contract and example",
|
|
1948
|
+
" --path <a.b> one nested argument's type and fields",
|
|
1791
1949
|
" --schema include input/output JSON Schema",
|
|
1792
1950
|
" --json print the machine contract as JSON",
|
|
1793
1951
|
" " + BIN + " docs search <term> search reference and guides",
|
|
1794
1952
|
" --json / --format json print structured matches and availability",
|
|
1795
|
-
" " + BIN + " docs read <page> print a docs-site page
|
|
1953
|
+
" " + BIN + " docs read <page> print a named input type or a docs-site page",
|
|
1796
1954
|
" " + BIN + " docs --web open the docs site in a browser",
|
|
1797
1955
|
"",
|
|
1798
1956
|
"Guides come from the docs site's llms.txt (set with '" + BIN + " config set docs-url <url>').",
|
|
@@ -1864,6 +2022,14 @@ async function cmdDocs(parsed) {
|
|
|
1864
2022
|
const page = parsed.positionals[2];
|
|
1865
2023
|
if (page === undefined)
|
|
1866
2024
|
fail(2, "docs read expects a page path or URL");
|
|
2025
|
+
const typeName = findInputType(INPUT_TYPES, page);
|
|
2026
|
+
if (typeName) {
|
|
2027
|
+
const path = parsed.flags.get("path");
|
|
2028
|
+
if (typeof path === "string" && path.trim())
|
|
2029
|
+
await docsPathExit({ type: typeName }, path);
|
|
2030
|
+
process.stdout.write(inputTypeText(INPUT_TYPES, typeName, docsTypeHint) + "\n");
|
|
2031
|
+
await flushExit(0);
|
|
2032
|
+
}
|
|
1867
2033
|
let target = page;
|
|
1868
2034
|
if (!/^https?:\/\//.test(target)) {
|
|
1869
2035
|
const index = await fetchDocs("llms.txt");
|
|
@@ -1903,6 +2069,9 @@ async function cmdDocs(parsed) {
|
|
|
1903
2069
|
}, null, 2) + "\n");
|
|
1904
2070
|
await flushExit(0);
|
|
1905
2071
|
}
|
|
2072
|
+
const path = parsed.flags.get("path");
|
|
2073
|
+
if (typeof path === "string" && path.trim())
|
|
2074
|
+
await docsPathExit({ tool: op.tool, inputSchema: op.inputSchema, label: op.command.join(" ") }, path);
|
|
1906
2075
|
process.stdout.write(referenceFor(op, parsed.flags.get("schema") === true) + "\n");
|
|
1907
2076
|
await flushExit(0);
|
|
1908
2077
|
}
|
|
@@ -2088,7 +2257,7 @@ function printRoot(stream = process.stdout) {
|
|
|
2088
2257
|
}
|
|
2089
2258
|
const width = termWidth();
|
|
2090
2259
|
const lines = [];
|
|
2091
|
-
lines.push(paintOut("bold", BIN) + ": " + "Typeship API" + " (v" + "1.0.0" + "), package " + "0.
|
|
2260
|
+
lines.push(paintOut("bold", BIN) + ": " + "Typeship API" + " (v" + "1.0.0" + "), package " + "0.24.0");
|
|
2092
2261
|
lines.push("");
|
|
2093
2262
|
lines.push(paintOut("bold", "Usage:") + " " + BIN + " <resource> <command> [args] [--flags]");
|
|
2094
2263
|
lines.push("");
|
|
@@ -2111,7 +2280,7 @@ function printRoot(stream = process.stdout) {
|
|
|
2111
2280
|
lines.push(...labeled(paintOut("yellow", "Coverage:") + " ", "this build includes " + OPS.length + " of " + (OPS.length + EXCLUDED_OPS) + " operations; api.json lists the rest", width, 14));
|
|
2112
2281
|
}
|
|
2113
2282
|
lines.push("");
|
|
2114
|
-
const flagsText = "-v/--version, -h/--help, --debug, --non-interactive, --color on|off|auto, --base-url <url>, --profile <name>, --credentials @<file>|-, --header \"Name: value\", --timeout <seconds>, --data '<json>', --fields <a,b.c>, --all (paginated lists), --validate (schema-check parameters and JSON bodies)" +
|
|
2283
|
+
const flagsText = "-v/--version, -h/--help, --debug, --non-interactive, --color on|off|auto, --base-url <url>, --profile <name>, --credentials @<file>|-, --header \"Name: value\", --timeout <seconds>, --data '<json>', --fields <a,b.c>, --all (paginated lists), --format table (results as text), --validate (schema-check parameters and JSON bodies), --dry-run (print the request, send nothing)" +
|
|
2115
2284
|
(AUTH_SCALARS.length > 0 ? ", " + AUTH_SCALARS.map((a) => "--" + a.flag + " <value>").join(", ") : "");
|
|
2116
2285
|
lines.push(...labeled(paintOut("bold", "Global flags:") + " ", flagsText, width, 14).map((l, i) => (i === 0 ? l : l)));
|
|
2117
2286
|
lines.push(...labeled("Credential env vars: ", [
|
|
@@ -2179,6 +2348,9 @@ function commandExtras(op) {
|
|
|
2179
2348
|
extras.push(["--fields <a,b.c>", "keep only these fields of the result" + (op.paginated ? " (per item)" : collectionField ? " (per item in " + collectionField + ")" : "")]);
|
|
2180
2349
|
if ((op.fileBundleProperty ?? bundleProperty(op.outputSchema)) !== null)
|
|
2181
2350
|
extras.push(["--out <dir>", "write the response's files ({path, content}) into a directory"]);
|
|
2351
|
+
extras.push(["--dry-run", "print the resolved request (method, URL, headers, body) with credentials redacted; nothing is sent" + (op.safety === "destructive" ? ", so no --force is needed" : "")]);
|
|
2352
|
+
if (!op.rawResponse && !op.sse)
|
|
2353
|
+
extras.push(["--format table", "print the result as a table for reading instead of JSON"]);
|
|
2182
2354
|
if (op.safety === "destructive")
|
|
2183
2355
|
extras.push(["--force, -y", "destructive: required without a terminal, skips the prompt with one"]);
|
|
2184
2356
|
return extras;
|
|
@@ -2442,6 +2614,73 @@ function validateParameters(op, values, flags) {
|
|
|
2442
2614
|
let LAST_CLIENT_HAD_CREDENTIAL = false;
|
|
2443
2615
|
/** The last client's Basic credentials, for path arguments that default to the username. */
|
|
2444
2616
|
let LAST_CLIENT_BASIC = {};
|
|
2617
|
+
/** --dry-run: the client's fetch records the first request and sends
|
|
2618
|
+
* nothing. Set before makeClient; secrets are the credential values it
|
|
2619
|
+
* resolved, redacted wherever they appear in the preview. */
|
|
2620
|
+
let DRY_RUN = null;
|
|
2621
|
+
class DryRunStop extends Error {
|
|
2622
|
+
}
|
|
2623
|
+
function dryRunCapture() {
|
|
2624
|
+
const capture = {
|
|
2625
|
+
secrets: [],
|
|
2626
|
+
fetch: async (input, init) => {
|
|
2627
|
+
if (!capture.request) {
|
|
2628
|
+
const headers = {};
|
|
2629
|
+
new Headers(init?.headers).forEach((value, name) => { headers[name] = value; });
|
|
2630
|
+
capture.request = { method: init?.method ?? "GET", url: typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url, headers, body: init?.body ?? undefined };
|
|
2631
|
+
}
|
|
2632
|
+
throw new DryRunStop("dry run: not sent");
|
|
2633
|
+
},
|
|
2634
|
+
};
|
|
2635
|
+
return capture;
|
|
2636
|
+
}
|
|
2637
|
+
/** Every credential value a client was built with (not a Basic username,
|
|
2638
|
+
* which is an account identifier such as Twilio's AccountSid). */
|
|
2639
|
+
function credentialSecrets(options) {
|
|
2640
|
+
const found = [];
|
|
2641
|
+
const walk = (value, key) => {
|
|
2642
|
+
if (typeof value === "string") {
|
|
2643
|
+
if (key !== "username")
|
|
2644
|
+
found.push(value);
|
|
2645
|
+
return;
|
|
2646
|
+
}
|
|
2647
|
+
if (value && typeof value === "object")
|
|
2648
|
+
for (const [k, v] of Object.entries(value))
|
|
2649
|
+
walk(v, k);
|
|
2650
|
+
};
|
|
2651
|
+
for (const a of AUTH_SCALARS)
|
|
2652
|
+
walk(options[a.option], a.option);
|
|
2653
|
+
walk(options.basicAuth, "basicAuth");
|
|
2654
|
+
walk(options.credentials, "credentials");
|
|
2655
|
+
walk(options.bearerToken, "bearerToken");
|
|
2656
|
+
return found;
|
|
2657
|
+
}
|
|
2658
|
+
/** Drive the command's call until its request reaches the dry-run fetch,
|
|
2659
|
+
* then print that request (JSON in agent mode, text for a person) and exit.
|
|
2660
|
+
* A failure before any request (a --validate violation) is reported as usual. */
|
|
2661
|
+
async function printDryRun(parsed, callResult) {
|
|
2662
|
+
let failure;
|
|
2663
|
+
try {
|
|
2664
|
+
const pending = callResult;
|
|
2665
|
+
if (typeof pending.then === "function")
|
|
2666
|
+
await callResult;
|
|
2667
|
+
else if (typeof pending[Symbol.asyncIterator] === "function")
|
|
2668
|
+
await pending[Symbol.asyncIterator]().next();
|
|
2669
|
+
}
|
|
2670
|
+
catch (e) {
|
|
2671
|
+
failure = e;
|
|
2672
|
+
}
|
|
2673
|
+
const request = DRY_RUN?.request;
|
|
2674
|
+
if (!request) {
|
|
2675
|
+
failApi(failure ?? new Error("--dry-run: the command made no request"), LAST_CLIENT_HAD_CREDENTIAL);
|
|
2676
|
+
}
|
|
2677
|
+
const preview = await requestPreview(request, { sensitiveNames: CREDENTIAL_NAMES, secrets: DRY_RUN.secrets });
|
|
2678
|
+
if (isAgentMode(parsed))
|
|
2679
|
+
out(preview);
|
|
2680
|
+
else
|
|
2681
|
+
process.stdout.write(formatRequestPreview(preview));
|
|
2682
|
+
return await flushExit(0);
|
|
2683
|
+
}
|
|
2445
2684
|
/** The configured Basic-auth username: the named scheme's, else basicAuth's. */
|
|
2446
2685
|
function credentialUsername(scheme) {
|
|
2447
2686
|
const named = LAST_CLIENT_BASIC.credentials?.[scheme];
|
|
@@ -2677,6 +2916,12 @@ async function makeClient(flags, op, candidate, forIdentity = false) {
|
|
|
2677
2916
|
const extraHeaders = extraRequestHeaders();
|
|
2678
2917
|
if (Object.keys(extraHeaders).length)
|
|
2679
2918
|
options.onRequest = (context) => { applyExtraHeaders(context.headers, extraHeaders); };
|
|
2919
|
+
// --dry-run swaps fetch on this client only: an OAuth refresh (and its
|
|
2920
|
+
// identity check) still uses the real transport, the command does not.
|
|
2921
|
+
if (DRY_RUN && !forIdentity) {
|
|
2922
|
+
DRY_RUN.secrets = credentialSecrets(options);
|
|
2923
|
+
return new TypeshipClient({ ...options, fetch: DRY_RUN.fetch, maxRetries: 0 });
|
|
2924
|
+
}
|
|
2680
2925
|
return new TypeshipClient(options);
|
|
2681
2926
|
}
|
|
2682
2927
|
function editDistance(a, b) {
|
|
@@ -2735,16 +2980,20 @@ async function main() {
|
|
|
2735
2980
|
if (!parsed.help && ["login", "init"].includes(parsed.positionals[0] ?? ""))
|
|
2736
2981
|
expectedLoginIdentity(parsed.flags);
|
|
2737
2982
|
if (parsed.positionals[0] === "help") {
|
|
2738
|
-
// help --json:
|
|
2983
|
+
// help [<resource> [<command>]] --json: bounded discovery as data; --all is exhaustive.
|
|
2739
2984
|
if (parsed.flags.get("json") === true || parsed.flags.get("format") === "json") {
|
|
2740
|
-
|
|
2741
|
-
await flushExit(0);
|
|
2985
|
+
await cmdHelpJson(parsed);
|
|
2742
2986
|
}
|
|
2743
2987
|
parsed.positionals.shift();
|
|
2744
2988
|
parsed.help = true;
|
|
2745
2989
|
}
|
|
2746
|
-
|
|
2747
|
-
|
|
2990
|
+
const formatFlag = parsed.flags.get("format");
|
|
2991
|
+
if (formatFlag !== undefined && formatFlag !== "json" && formatFlag !== "table") {
|
|
2992
|
+
fail(2, "--format takes json (the default) or table.");
|
|
2993
|
+
}
|
|
2994
|
+
TABLE = formatFlag === "table";
|
|
2995
|
+
if (TABLE && !parsed.help && (BUILTIN_COMMANDS.includes(parsed.positionals[0] ?? "") || parsed.positionals.length === 0)) {
|
|
2996
|
+
fail(2, "--format table applies to API commands; " + (parsed.positionals[0] ? BIN + " " + parsed.positionals[0] : BIN) + " prints JSON.");
|
|
2748
2997
|
}
|
|
2749
2998
|
if (parsed.flags.has("version") || parsed.positionals[0] === "version") {
|
|
2750
2999
|
// "acme 1.0.0 (acme 1.0.0)" would say the name twice; when the API's
|
|
@@ -2874,7 +3123,7 @@ async function main() {
|
|
|
2874
3123
|
// Mirrors opReservedFlags() in the generator: API parameters never use these
|
|
2875
3124
|
// names (colliding ones are emitted as --<kind>-<name>), so an unknown flag
|
|
2876
3125
|
// check can be exact.
|
|
2877
|
-
const RESERVED_FLAGS = new Set(["data", "credentials", "header", "timeout", "all", "select", "base-url", "profile", "debug", "validate", "non-interactive", "color", "version", "help", "yes", "force", "mode", "format", "json", "out", "fields", ...AUTH_SCALARS.map((a) => a.flag), ...(BASIC ? ["username", "password"] : []), ...GLOBALS.map((g) => g.flag)]);
|
|
3126
|
+
const RESERVED_FLAGS = new Set(["data", "credentials", "header", "timeout", "all", "select", "base-url", "profile", "debug", "validate", "dry-run", "non-interactive", "color", "version", "help", "yes", "force", "mode", "format", "json", "out", "fields", ...AUTH_SCALARS.map((a) => a.flag), ...(BASIC ? ["username", "password"] : []), ...GLOBALS.map((g) => g.flag)]);
|
|
2878
3127
|
for (const spec of op.params) {
|
|
2879
3128
|
if (spec.kind === "path")
|
|
2880
3129
|
continue;
|
|
@@ -2971,6 +3220,20 @@ async function main() {
|
|
|
2971
3220
|
if (!op.paginated && parsed.flags.get("all") === true) {
|
|
2972
3221
|
fail(2, op.command.join(" ") + " does not paginate, so --all has nothing to walk. Run it without --all; it returns the whole response.");
|
|
2973
3222
|
}
|
|
3223
|
+
// --dry-run resolves everything a real call would (arguments, defaults,
|
|
3224
|
+
// credentials, the body encoding) and prints the request instead of
|
|
3225
|
+
// sending it. A destructive command needs no --force: nothing runs.
|
|
3226
|
+
if (parsed.flags.get("dry-run") === true) {
|
|
3227
|
+
if (op.paginated && parsed.flags.get("all") === true)
|
|
3228
|
+
fail(2, "--dry-run previews one request; run it without --all to see the first page's.");
|
|
3229
|
+
DRY_RUN = dryRunCapture();
|
|
3230
|
+
}
|
|
3231
|
+
// A table needs the whole result: streams (--all, events) and raw bodies stay as they are.
|
|
3232
|
+
if (TABLE) {
|
|
3233
|
+
const streams = parsed.flags.get("all") === true || op.sse || (op.streamMethod !== undefined && values[op.streamMethod.flag] === op.streamMethod.value);
|
|
3234
|
+
if (streams || op.rawResponse)
|
|
3235
|
+
fail(2, "--format table needs one complete JSON result; " + op.command.join(" ") + (op.rawResponse ? " returns a raw body." : " streams NDJSON here.") + " Drop --format table" + (parsed.flags.get("all") === true ? " or --all." : "."));
|
|
3236
|
+
}
|
|
2974
3237
|
const client = await makeClient(parsed.flags, op);
|
|
2975
3238
|
for (const spec of pathSpecs) {
|
|
2976
3239
|
if (!spec.credential || values[spec.name] !== undefined)
|
|
@@ -2984,7 +3247,7 @@ async function main() {
|
|
|
2984
3247
|
// Destructive commands need --force. A person gets asked; an agent gets
|
|
2985
3248
|
// an action_required envelope with the exact command to run, so nothing
|
|
2986
3249
|
// is deleted on a guess.
|
|
2987
|
-
if (op.safety === "destructive" && !assumeYes(parsed)) {
|
|
3250
|
+
if (op.safety === "destructive" && !assumeYes(parsed) && !DRY_RUN) {
|
|
2988
3251
|
// Credential flags are replaced by placeholders: the rerun is shown to
|
|
2989
3252
|
// agents and logged, so it must never repeat a key.
|
|
2990
3253
|
const secretFlags = new Set([...AUTH_SCALARS.map((a) => "--" + a.flag), "--header"]);
|
|
@@ -3016,8 +3279,10 @@ async function main() {
|
|
|
3016
3279
|
// as NDJSON as they arrive, instead of failing on the event stream.
|
|
3017
3280
|
const streaming = op.streamMethod !== undefined && values[op.streamMethod.flag] === op.streamMethod.value;
|
|
3018
3281
|
const callResult = target[streaming ? op.streamMethod.method : op.method](...args);
|
|
3282
|
+
if (DRY_RUN)
|
|
3283
|
+
await printDryRun(parsed, callResult);
|
|
3019
3284
|
if (op.paginated && parsed.flags.get("all") === true) {
|
|
3020
|
-
const fieldsCheck = streamFieldsCheck();
|
|
3285
|
+
const fieldsCheck = streamFieldsCheck(listSchemaOf(op.outputSchema, "items")?.items);
|
|
3021
3286
|
try {
|
|
3022
3287
|
for await (const item of callResult) {
|
|
3023
3288
|
process.stdout.write(JSON.stringify(fieldsCheck.item(item)) + "\n");
|
|
@@ -3051,7 +3316,7 @@ async function main() {
|
|
|
3051
3316
|
if (result.ok) {
|
|
3052
3317
|
if (op.sse || streaming) {
|
|
3053
3318
|
// Server-sent events as NDJSON, one line per event, until the stream ends.
|
|
3054
|
-
const fieldsCheck = streamFieldsCheck();
|
|
3319
|
+
const fieldsCheck = streamFieldsCheck(undefined);
|
|
3055
3320
|
try {
|
|
3056
3321
|
for await (const event of result.data) {
|
|
3057
3322
|
process.stdout.write(JSON.stringify(fieldsCheck.item(event)) + "\n");
|
|
@@ -3100,18 +3365,18 @@ async function main() {
|
|
|
3100
3365
|
// agent never has to reconstruct the cursor flag.
|
|
3101
3366
|
const page = result.data;
|
|
3102
3367
|
const next = page.nextPageParams();
|
|
3103
|
-
|
|
3104
|
-
items: project(page.items, true),
|
|
3368
|
+
printResult({
|
|
3369
|
+
items: project(page.items, true, listSchemaOf(op.outputSchema, "items")),
|
|
3105
3370
|
hasMore: next !== null,
|
|
3106
3371
|
...(next !== null ? { nextPage: next, nextCommand: nextCommandFor(op, pathValues, next) } : {}),
|
|
3107
3372
|
...(page.response.requestId ? { request_id: page.response.requestId } : {}),
|
|
3108
|
-
});
|
|
3373
|
+
}, op.command[0]);
|
|
3109
3374
|
}
|
|
3110
3375
|
else if (FIELDS !== null && collectionField !== null && result.data !== null && typeof result.data === "object" && !Array.isArray(result.data) && Array.isArray(result.data[collectionField])) {
|
|
3111
3376
|
// Batch-style collection envelopes ({data: [...]}) use item-relative
|
|
3112
3377
|
// fields, matching paginated results, while retaining envelope metadata.
|
|
3113
3378
|
const data = result.data;
|
|
3114
|
-
|
|
3379
|
+
printResult({ ...data, [collectionField]: project(data[collectionField], true, listSchemaOf(op.outputSchema, collectionField)) }, op.command[0], collectionField);
|
|
3115
3380
|
}
|
|
3116
3381
|
else if (outDir !== undefined && bundleField !== null) {
|
|
3117
3382
|
// --out: a file-shaped response (an array of {path, content}) lands
|
|
@@ -3120,10 +3385,10 @@ async function main() {
|
|
|
3120
3385
|
const files = data[bundleField] ?? [];
|
|
3121
3386
|
const written = writeBundle(outDir, files);
|
|
3122
3387
|
const { [bundleField]: _omitted, ...rest } = data;
|
|
3123
|
-
|
|
3388
|
+
printResult({ ...project(rest, false, op.outputSchema), out: written }, op.command[0]);
|
|
3124
3389
|
}
|
|
3125
3390
|
else {
|
|
3126
|
-
|
|
3391
|
+
printResult(result.data === undefined || result.data === null ? { ok: true } : project(result.data, Array.isArray(result.data), op.outputSchema), op.command[0], collectionField);
|
|
3127
3392
|
}
|
|
3128
3393
|
await flushExit(0);
|
|
3129
3394
|
}
|