@typeship-ax/cli 0.6.0 → 0.8.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/api.json +5597 -3010
- package/api.md +433 -54
- package/dist/cli-agent.d.ts +9 -1
- package/dist/cli-agent.d.ts.map +1 -1
- package/dist/cli-agent.js +26 -9
- package/dist/cli.js +83 -232
- package/dist/core/http.d.ts +6 -92
- package/dist/core/http.d.ts.map +1 -1
- package/dist/core/http.js +70 -209
- package/dist/core/pagination.d.ts.map +1 -1
- package/dist/core/pagination.js +6 -34
- package/dist/dates.d.ts +0 -2
- package/dist/dates.d.ts.map +1 -1
- package/dist/dates.js +0 -1
- package/dist/docs.d.ts +11 -0
- package/dist/docs.d.ts.map +1 -0
- package/dist/docs.js +114 -0
- package/dist/errors.d.ts +27 -27
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +7 -7
- package/dist/index.d.ts +19 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -13
- package/dist/ops.d.ts +5 -0
- package/dist/ops.d.ts.map +1 -1
- package/dist/ops.js +31 -17
- package/dist/resources/account.d.ts +2 -2
- package/dist/resources/account.d.ts.map +1 -1
- package/dist/resources/api-keys.d.ts +10 -5
- package/dist/resources/api-keys.d.ts.map +1 -1
- package/dist/resources/api-keys.js +3 -1
- package/dist/resources/definition-revisions.d.ts +58 -0
- package/dist/resources/definition-revisions.d.ts.map +1 -0
- package/dist/resources/definition-revisions.js +110 -0
- package/dist/resources/definitions.d.ts +24 -0
- package/dist/resources/definitions.d.ts.map +1 -0
- package/dist/resources/definitions.js +51 -0
- package/dist/resources/generate.d.ts +5 -5
- package/dist/resources/generate.d.ts.map +1 -1
- package/dist/resources/generate.js +3 -3
- package/dist/resources/generations.d.ts +3 -3
- package/dist/resources/generations.d.ts.map +1 -1
- package/dist/resources/generations.js +1 -1
- package/dist/resources/projects.d.ts +66 -26
- package/dist/resources/projects.d.ts.map +1 -1
- package/dist/resources/projects.js +87 -13
- package/dist/resources/targets.d.ts +86 -0
- package/dist/resources/targets.d.ts.map +1 -0
- package/dist/resources/targets.js +184 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +119 -62
- package/dist/types.d.ts +1761 -222
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -3
- package/package.json +1 -1
- package/src/cli-agent.ts +28 -11
- package/src/cli.ts +89 -233
- package/src/core/http.ts +75 -293
- package/src/core/pagination.ts +6 -30
- package/src/dates.ts +0 -1
- package/src/docs.ts +101 -0
- package/src/errors.ts +30 -30
- package/src/index.ts +23 -13
- package/src/ops.ts +43 -17
- package/src/resources/account.ts +3 -3
- package/src/resources/api-keys.ts +22 -7
- package/src/resources/definition-revisions.ts +198 -0
- package/src/resources/definitions.ts +97 -0
- package/src/resources/generate.ts +6 -6
- package/src/resources/generations.ts +4 -4
- package/src/resources/projects.ts +182 -37
- package/src/resources/targets.ts +346 -0
- package/src/schemas.ts +119 -62
- package/src/types.ts +1947 -281
- package/dist/resources/spec-revisions.d.ts +0 -47
- package/dist/resources/spec-revisions.d.ts.map +0 -1
- package/dist/resources/spec-revisions.js +0 -90
- package/src/resources/spec-revisions.ts +0 -150
package/src/cli.ts
CHANGED
|
@@ -11,31 +11,36 @@ import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, statSync, wr
|
|
|
11
11
|
import { homedir, hostname } from "node:os";
|
|
12
12
|
import { basename, dirname, join } from "node:path";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
|
-
import { TypeshipClient, formatDebugEvent, type DebugEvent } from "./index.js";
|
|
15
|
-
import { GLOBALS, OPS, buildArgs, findOp, missingRequired, type OpSpec, type ParamSpec } from "./ops.js";
|
|
14
|
+
import { TypeshipClient, formatDebugEvent, type ClientOptions, type DebugEvent } from "./index.js";
|
|
15
|
+
import { GLOBALS, OMITTED_OPS, OPS, buildArgs, findOp, missingRequired, type OmittedOpSpec, type OpSpec, type ParamSpec } from "./ops.js";
|
|
16
16
|
import {
|
|
17
17
|
MCP_CLIENTS, agentGuide, agentBlock, agentInstructionsFile, agentMode, bundleProperty, claimProperty, classifyApiError, collectionProperty, detectHarness, envelope,
|
|
18
18
|
exitCodeFor, findMcpClient, installSkills, mcpConfigured, pendingClaims, recordClaim, summarizeDoctor, upsertAgentBlock, writeBundle, writeMcpConfig,
|
|
19
19
|
type AgentContext, type CommandSummary, type DoctorCheck, type EnvelopeInput, type IssueCode, type McpEntry, type McpWriteResult,
|
|
20
20
|
} from "./cli-agent.js";
|
|
21
21
|
import { relativeDate } from "./dates.js";
|
|
22
|
+
import { fetchDocsText, resolveDocsContentUrl } from "./docs.js";
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
const BIN = "typeship";
|
|
25
26
|
const DEFAULT_BASE_URL = "https://typeship.dev/api/v1";
|
|
26
27
|
const AUTH_SCALARS: { option: string; flag: string; env: string }[] = [{"option":"bearerToken","flag":"token","env":"TYPESHIP_TOKEN"}];
|
|
27
28
|
const BASIC: { envUser: string; envPass: string } | null = null;
|
|
29
|
+
/** Operations omitted from the generated package by its plan cap. */
|
|
28
30
|
const EXCLUDED_OPS = 0;
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
+
/** Generated CLI operations that are intentionally unavailable to MCP. */
|
|
32
|
+
const MCP_EXCLUDED_OPS = 0;
|
|
33
|
+
const VERSION = "0.8.0";
|
|
34
|
+
const API_VERSION = "1.0.0";
|
|
31
35
|
const SPEC_FORMAT = "openapi";
|
|
32
36
|
const WHOAMI: { resource: string; method: string } | null = {"resource":"account","method":"retrieve"};
|
|
33
37
|
const ENVIRONMENTS: Record<string, string> = {};
|
|
34
38
|
const HAS_MCP = false;
|
|
35
39
|
const PKG_NAME = "@typeship-ax/cli";
|
|
36
40
|
const UPDATE_NOTICE = false;
|
|
37
|
-
const API_DESCRIPTION: string | null = "
|
|
41
|
+
const API_DESCRIPTION: string | null = "Resolve an OpenAPI or GraphQL Definition, diagnose it, and keep every\nselected SDK, CLI, and MCP Target current.\n\nEvery operation but one requires a bearer credential: an organization\nAPI key from the console, or an OAuth access token carrying the operation's\nread, generate, or write capability and the organization selected during\nconsent. OAuth grants cannot switch organizations after consent. A browser\nsession is not a credential for this API. The exception is POST /generate,\nwhich works anonymously with the free plan's limits.\n";
|
|
38
42
|
const DOCS_URL_DEFAULT: string | null = "https://typeship.dev";
|
|
43
|
+
const DOCS_INDEX_URL_DEFAULT: string | null = null;
|
|
39
44
|
const RELAY: { mintUrl: string; project: string } | null = null;
|
|
40
45
|
const SUPPORT_URL: string | null = null;
|
|
41
46
|
const OAUTH_TOKEN_URL: string | null = null;
|
|
@@ -258,7 +263,7 @@ function humanError(body: ReturnType<typeof envelope>, code: number): string {
|
|
|
258
263
|
lines.push(paintErr("red", BIN + ":") + " " + (issue?.message ?? "failed"));
|
|
259
264
|
for (const extra of body.issues.slice(1)) lines.push(" " + extra.message);
|
|
260
265
|
for (const step of body.next_steps ?? []) lines.push(" " + step);
|
|
261
|
-
const detail = body.detail as { status?: number; body?: unknown; violations?: unknown } | undefined;
|
|
266
|
+
const detail = body.detail as { status?: number; body?: unknown; violations?: unknown; request_id?: string } | undefined;
|
|
262
267
|
if (detail?.violations !== undefined) {
|
|
263
268
|
for (const v of (detail.violations as { path?: string; message?: string }[]).slice(0, 8)) lines.push(" " + paintErr("dim", (v.path ? v.path + ": " : "") + (v.message ?? "")));
|
|
264
269
|
} else if (detail?.body !== undefined) {
|
|
@@ -267,7 +272,7 @@ function humanError(body: ReturnType<typeof envelope>, code: number): string {
|
|
|
267
272
|
const compact = typeof detail.body === "string" ? detail.body : JSON.stringify(detail.body);
|
|
268
273
|
const firstWords = (issue?.message ?? "").slice(0, 40);
|
|
269
274
|
if (compact && compact !== "{}" && !(firstWords && compact.includes(firstWords))) lines.push(" " + paintErr("dim", "API said: " + (compact.length > 300 ? compact.slice(0, 297) + "…" : compact)));
|
|
270
|
-
const requestId = (detail.body as { request_id?: unknown; requestId?: unknown } | null)?.request_id ?? (detail.body as { requestId?: unknown } | null)?.requestId;
|
|
275
|
+
const requestId = detail.request_id ?? (detail.body as { request_id?: unknown; requestId?: unknown } | null)?.request_id ?? (detail.body as { requestId?: unknown } | null)?.requestId;
|
|
271
276
|
if (typeof requestId === "string") lines.push(" " + paintErr("dim", "request id: " + requestId));
|
|
272
277
|
}
|
|
273
278
|
const tags = [issue?.code ?? "ERROR", ...(typeof detail?.status === "number" && detail.status > 0 ? ["HTTP " + detail.status] : []), "exit " + code, "pipe stderr or --mode agent for JSON"];
|
|
@@ -287,7 +292,7 @@ function fail(code: number, message: string, extra?: unknown, nextSteps?: string
|
|
|
287
292
|
: /^(Missing required|Expected \d+ argument)/.test(message) ? "MISSING_ARGUMENT"
|
|
288
293
|
: "INVALID_USAGE";
|
|
289
294
|
return failWith({
|
|
290
|
-
code: code === 2 ? usageCode : "
|
|
295
|
+
code: code === 2 ? usageCode : "CALL_FAILED",
|
|
291
296
|
message,
|
|
292
297
|
...(extra !== undefined ? { detail: extra } : {}),
|
|
293
298
|
nextSteps: nextSteps ?? (code === 2 ? ["Run '" + USAGE_HINT + "' for the flags this command takes."] : []),
|
|
@@ -547,7 +552,7 @@ async function browserApprove(headless: boolean, flags: Map<string, string | boo
|
|
|
547
552
|
});
|
|
548
553
|
start = await response.json() as typeof start;
|
|
549
554
|
if (!response.ok || !start.session || !start.verification_url) {
|
|
550
|
-
failWith({ code: "
|
|
555
|
+
failWith({ code: "CALL_FAILED", message: "Could not start the browser login: " + (start.error ?? "HTTP " + response.status), nextSteps: ["Pass the credential directly: '" + BIN + " login --" + first.flag + " <value>'."] });
|
|
551
556
|
}
|
|
552
557
|
} catch (e) {
|
|
553
558
|
failWith({ code: "NETWORK_ERROR", message: "Could not reach " + authUrl + "/start: " + (e as Error).message, nextSteps: ["Check the network, or pass the credential directly: '" + BIN + " login --" + first.flag + " <value>'."] });
|
|
@@ -582,7 +587,7 @@ async function browserApprove(headless: boolean, flags: Map<string, string | boo
|
|
|
582
587
|
}
|
|
583
588
|
if (poll.status === "denied") failWith({ status: "action_required", code: "AUTH_INVALID", message: "The request was denied in the browser.", nextSteps: ["Run '" + BIN + " login' again if that was a mistake, or pass a credential directly with --" + first.flag + "."] });
|
|
584
589
|
if (poll.status === "expired") break;
|
|
585
|
-
failWith({ code: "
|
|
590
|
+
failWith({ code: "CALL_FAILED", message: "Browser login stopped: " + (poll.status ?? "unknown status"), nextSteps: ["Run '" + BIN + " login' again."] });
|
|
586
591
|
}
|
|
587
592
|
failWith({ status: "action_required", code: "TTY_REQUIRED", message: "The browser approval expired after " + expiresIn + "s without a decision.", nextSteps: ["Run '" + BIN + " login' again and approve the link within ten minutes.", "Or pass the credential directly: '" + BIN + " login --" + first.flag + " <value>'."] });
|
|
588
593
|
}
|
|
@@ -849,16 +854,6 @@ function mcpServerPath(): { path: string; warning: string | null } {
|
|
|
849
854
|
return { path, warning };
|
|
850
855
|
}
|
|
851
856
|
|
|
852
|
-
function claudeDesktopConfigPath(): string {
|
|
853
|
-
if (process.platform === "darwin") {
|
|
854
|
-
return join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
855
|
-
}
|
|
856
|
-
if (process.platform === "win32") {
|
|
857
|
-
return join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
858
|
-
}
|
|
859
|
-
return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "Claude", "claude_desktop_config.json");
|
|
860
|
-
}
|
|
861
|
-
|
|
862
857
|
/** The mcp entry for a client: the hosted endpoint when the API has one
|
|
863
858
|
* (auth env var as a reference, never a literal), else the local stdio
|
|
864
859
|
* server. --url overrides. */
|
|
@@ -902,11 +897,20 @@ async function cmdMcp(parsed: Parsed): Promise<void> {
|
|
|
902
897
|
" " + BIN + " mcp install --claude --read-only register a read-only server (writes are not callable)",
|
|
903
898
|
"",
|
|
904
899
|
(MCP_URL ? "Default entry: the hosted endpoint " + MCP_URL + " with the auth env var as a reference (never a literal key)." : "Default entry: this package's local stdio server, which reads credentials saved by '" + BIN + " login' or the CLI's auth env vars."),
|
|
905
|
-
"
|
|
900
|
+
"--all skips Cursor until it speaks MCP 2026-07-28.",
|
|
906
901
|
];
|
|
907
902
|
process.stdout.write(lines.join("\n") + "\n");
|
|
908
903
|
await flushExit(0);
|
|
909
904
|
}
|
|
905
|
+
const sub = parsed.positionals[1];
|
|
906
|
+
if (sub !== undefined && sub !== "install") {
|
|
907
|
+
fail(2, "Unknown mcp command: " + sub + ". Try '" + BIN + " mcp install --all'.");
|
|
908
|
+
}
|
|
909
|
+
const requestedInstall = parsed.flags.get("all") === true
|
|
910
|
+
|| Object.keys(MCP_CLIENT_FLAGS).some((flag) => parsed.flags.get(flag) === true);
|
|
911
|
+
if (requestedInstall && sub !== "install") {
|
|
912
|
+
fail(2, "Installing an MCP entry requires '" + BIN + " mcp install'.");
|
|
913
|
+
}
|
|
910
914
|
const url = typeof parsed.flags.get("url") === "string" ? parsed.flags.get("url") as string : undefined;
|
|
911
915
|
if (url) {
|
|
912
916
|
try {
|
|
@@ -968,6 +972,9 @@ function agentContext(): AgentContext {
|
|
|
968
972
|
envPrefix: ENV_PREFIX,
|
|
969
973
|
authEnvVars: [...AUTH_SCALARS.map((a) => a.env), ...(BASIC ? [BASIC.envUser, BASIC.envPass] : [])],
|
|
970
974
|
docsUrl: docsSiteUrl(),
|
|
975
|
+
docsIndexUrl: docsIndexUrl(),
|
|
976
|
+
generatedOperationCount: OPS.length,
|
|
977
|
+
omittedOperations: OMITTED_OPS.map((op) => ({ command: op.command.join(" "), tool: op.tool, method: op.httpMethod, path: op.path })),
|
|
971
978
|
mcpUrl: MCP_URL,
|
|
972
979
|
skillsRepo: SKILLS_REPO,
|
|
973
980
|
hasMcp: HAS_MCP,
|
|
@@ -1028,6 +1035,14 @@ function helpJson(): Record<string, unknown> {
|
|
|
1028
1035
|
};
|
|
1029
1036
|
}),
|
|
1030
1037
|
})),
|
|
1038
|
+
...(EXCLUDED_OPS > 0 ? {
|
|
1039
|
+
coverage: {
|
|
1040
|
+
generated_operations: OPS.length,
|
|
1041
|
+
total_operations: OPS.length + EXCLUDED_OPS,
|
|
1042
|
+
omitted_operations: OMITTED_OPS.map((op) => ({ command: op.command.join(" "), tool: op.tool, method: op.httpMethod, path: op.path })),
|
|
1043
|
+
reason: "plan_limit",
|
|
1044
|
+
},
|
|
1045
|
+
} : {}),
|
|
1031
1046
|
discovery: {
|
|
1032
1047
|
search: BIN + " docs search <term> --json",
|
|
1033
1048
|
operation: BIN + " docs <resource> <command> --json",
|
|
@@ -1303,7 +1318,7 @@ async function cmdUpgrade(parsed: Parsed): Promise<void> {
|
|
|
1303
1318
|
if (manager === "npx") fail(1, "This run came through npx, which fetches the latest version each time; there is nothing to upgrade in place.");
|
|
1304
1319
|
if (manager !== "npm") {
|
|
1305
1320
|
const command = manager === "pnpm" ? "pnpm add -g " + PKG_NAME + "@" + latest : manager === "bun" ? "bun add -g " + PKG_NAME + "@" + latest : "yarn global add " + PKG_NAME + "@" + latest;
|
|
1306
|
-
failWith({ status: "action_required", code: "
|
|
1321
|
+
failWith({ status: "action_required", code: "CALL_FAILED", message: PKG_NAME + " was installed with " + manager + ", so npm can't upgrade it in place.", nextSteps: ["Run: " + command] });
|
|
1307
1322
|
}
|
|
1308
1323
|
process.stderr.write("Upgrading " + PKG_NAME + " " + VERSION + " -> " + latest + "\n");
|
|
1309
1324
|
// Windows resolves npm to npm.cmd, which only a shell can start.
|
|
@@ -1319,7 +1334,7 @@ async function cmdUpgrade(parsed: Parsed): Promise<void> {
|
|
|
1319
1334
|
// completion — shell completion scripts built from the op table
|
|
1320
1335
|
// ---------------------------------------------------------------------------
|
|
1321
1336
|
|
|
1322
|
-
const TOP_WORDS = ["login",
|
|
1337
|
+
const TOP_WORDS = ["login","logout","whoami","config","mcp","docs","upgrade","completion","help","version","init","agent-guide","auth","doctor"];
|
|
1323
1338
|
|
|
1324
1339
|
/** Every flag an API command accepts, with the values a flag completes to (enums, on|off|auto, agent|human). */
|
|
1325
1340
|
function completionFlagsFor(op: OpSpec): { flags: string[]; values: Record<string, string[]> } {
|
|
@@ -1339,7 +1354,7 @@ const BUILTIN_WORDS: Record<string, string[]> = {
|
|
|
1339
1354
|
completion: ["bash", "zsh", "fish"],
|
|
1340
1355
|
auth: ["check"],
|
|
1341
1356
|
mcp: ["install"],
|
|
1342
|
-
|
|
1357
|
+
|
|
1343
1358
|
};
|
|
1344
1359
|
|
|
1345
1360
|
/** bash (and zsh via bashcompinit): resources, commands, flags, and the values a flag takes. */
|
|
@@ -1439,16 +1454,19 @@ function docsSiteUrl(): string | null {
|
|
|
1439
1454
|
return readConfig().docsUrl ?? DOCS_URL_DEFAULT;
|
|
1440
1455
|
}
|
|
1441
1456
|
|
|
1457
|
+
function docsIndexUrl(): string | null {
|
|
1458
|
+
const configured = readConfig().docsUrl;
|
|
1459
|
+
return configured
|
|
1460
|
+
? resolveDocsContentUrl(configured, null, "llms.txt")
|
|
1461
|
+
: DOCS_INDEX_URL_DEFAULT ?? resolveDocsContentUrl(DOCS_URL_DEFAULT, null, "llms.txt");
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1442
1464
|
/** Fetch llms.txt / llms-full.txt / a prose page from the docs site,
|
|
1443
1465
|
* with a 1h cache in the config dir. Explicit command = explicit fetch;
|
|
1444
1466
|
* nothing here runs unless the user asked for docs. */
|
|
1445
1467
|
async function fetchDocs(pathOrFile: string): Promise<string | null> {
|
|
1446
1468
|
const base = docsSiteUrl();
|
|
1447
|
-
const url =
|
|
1448
|
-
? pathOrFile
|
|
1449
|
-
: base === null
|
|
1450
|
-
? null
|
|
1451
|
-
: base.replace(/\/+$/, "") + "/" + pathOrFile.replace(/^\/+/, "");
|
|
1469
|
+
const url = resolveDocsContentUrl(base, docsIndexUrl(), pathOrFile);
|
|
1452
1470
|
if (url === null) return null;
|
|
1453
1471
|
const cacheFile = join(configDir(), "docs-cache", url.replace(/[^a-zA-Z0-9.]+/g, "_").slice(-120));
|
|
1454
1472
|
try {
|
|
@@ -1456,12 +1474,8 @@ async function fetchDocs(pathOrFile: string): Promise<string | null> {
|
|
|
1456
1474
|
if (Date.now() - stat.mtimeMs < 60 * 60 * 1000) return readFileSync(cacheFile, "utf8");
|
|
1457
1475
|
} catch { /* not cached */ }
|
|
1458
1476
|
try {
|
|
1459
|
-
const
|
|
1460
|
-
|
|
1461
|
-
signal: AbortSignal.timeout(10_000),
|
|
1462
|
-
});
|
|
1463
|
-
if (!response.ok) return null;
|
|
1464
|
-
const text = await response.text();
|
|
1477
|
+
const text = await fetchDocsText(url);
|
|
1478
|
+
if (text === null) return null;
|
|
1465
1479
|
mkdirSync(join(configDir(), "docs-cache"), { recursive: true, mode: 0o700 });
|
|
1466
1480
|
writeFileSync(cacheFile, text);
|
|
1467
1481
|
return text;
|
|
@@ -1678,6 +1692,8 @@ async function cmdDocs(parsed: Parsed): Promise<void> {
|
|
|
1678
1692
|
if (sub !== undefined) {
|
|
1679
1693
|
const op = findOp(sub!, parsed.positionals[2] ?? "");
|
|
1680
1694
|
if (!op) {
|
|
1695
|
+
const omitted = omittedCommand(sub!, parsed.positionals[2]);
|
|
1696
|
+
if (omitted) failOmitted(omitted);
|
|
1681
1697
|
const list = OPS.filter((o) => o.command[0] === sub);
|
|
1682
1698
|
if (list.length === 0) fail(2, "Unknown docs topic: " + sub + ". Run '" + BIN + " docs' for the overview.");
|
|
1683
1699
|
const lines = [paintOut("bold", "Commands for " + sub + ":"), ""];
|
|
@@ -1730,185 +1746,11 @@ async function cmdDocs(parsed: Parsed): Promise<void> {
|
|
|
1730
1746
|
// webhooks listen — forward relayed events to a local handler
|
|
1731
1747
|
// ---------------------------------------------------------------------------
|
|
1732
1748
|
|
|
1733
|
-
interface RelayedEvent {
|
|
1734
|
-
seq: number;
|
|
1735
|
-
method: string;
|
|
1736
|
-
headers: Record<string, string>;
|
|
1737
|
-
content_type: string | null;
|
|
1738
|
-
body: string;
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
function eventTypeOf(body: string): string | null {
|
|
1742
|
-
try {
|
|
1743
|
-
const parsed = JSON.parse(body) as Record<string, unknown>;
|
|
1744
|
-
for (const key of ["type", "event", "event_type", "eventType", "name"]) {
|
|
1745
|
-
if (typeof parsed[key] === "string") return parsed[key] as string;
|
|
1746
|
-
}
|
|
1747
|
-
} catch { /* not JSON */ }
|
|
1748
|
-
return null;
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
async function cmdWebhooksFake(parsed: Parsed): Promise<void> {
|
|
1752
|
-
void parsed;
|
|
1753
|
-
fail(2, "This API's spec declares no webhooks, so there is nothing to fake.");
|
|
1754
|
-
}
|
|
1755
1749
|
|
|
1756
|
-
async function cmdWebhooks(parsed: Parsed): Promise<void> {
|
|
1757
|
-
const sub = parsed.positionals[1];
|
|
1758
|
-
if (parsed.help || sub === undefined) {
|
|
1759
|
-
const lines = [
|
|
1760
|
-
BIN + " webhooks — work with this API's webhook events",
|
|
1761
|
-
"",
|
|
1762
|
-
" " + BIN + " webhooks listen --forward-to localhost:3000/webhooks",
|
|
1763
|
-
" " + BIN + " webhooks listen --forward-to localhost:3000/hooks --events account.created,account.updated",
|
|
1764
|
-
" " + BIN + " webhooks fake [<event>] [--key whsec_...] [--forward-to <url>]",
|
|
1765
|
-
"",
|
|
1766
|
-
"listen mints a private relay URL to point the API's webhook settings",
|
|
1767
|
-
"at; events replay locally with their original headers, so signature",
|
|
1768
|
-
"verification keeps working. No tunnels, no exposed ports.",
|
|
1769
|
-
"fake sends (or prints) a signed sample event for local handler testing.",
|
|
1770
|
-
];
|
|
1771
|
-
process.stdout.write(lines.join("\n") + "\n");
|
|
1772
|
-
await flushExit(parsed.help ? 0 : 2);
|
|
1773
|
-
}
|
|
1774
|
-
if (sub === "fake") { await cmdWebhooksFake(parsed); }
|
|
1775
|
-
if (sub !== "listen") fail(2, "Unknown webhooks command: " + String(sub) + ". Try '" + BIN + " webhooks listen' or '" + BIN + " webhooks fake'.");
|
|
1776
|
-
if (!RELAY) {
|
|
1777
|
-
fail(2, "The webhook relay isn't enabled for this package. The API provider can enable it in their typeship console; the next regeneration bakes it in.");
|
|
1778
|
-
}
|
|
1779
|
-
const forwardRaw = parsed.flags.get("forward-to");
|
|
1780
|
-
if (typeof forwardRaw !== "string") {
|
|
1781
|
-
fail(2, "webhooks listen requires --forward-to <url>, e.g. --forward-to localhost:3000/webhooks");
|
|
1782
|
-
}
|
|
1783
|
-
const forwardTo = /^https?:\/\//.test(forwardRaw as string) ? forwardRaw as string : "http://" + (forwardRaw as string);
|
|
1784
|
-
const eventsRaw = parsed.flags.get("events");
|
|
1785
|
-
const eventsFilter = typeof eventsRaw === "string"
|
|
1786
|
-
? eventsRaw.split(",").map((s) => s.trim()).filter(Boolean)
|
|
1787
|
-
: null;
|
|
1788
|
-
|
|
1789
|
-
interface MintedSession {
|
|
1790
|
-
session_id?: string;
|
|
1791
|
-
ingest_url?: string;
|
|
1792
|
-
poll_url?: string;
|
|
1793
|
-
errors?: { message?: string }[];
|
|
1794
|
-
}
|
|
1795
|
-
let minted: MintedSession | null = null;
|
|
1796
|
-
try {
|
|
1797
|
-
const response = await fetch(RELAY!.mintUrl, {
|
|
1798
|
-
method: "POST",
|
|
1799
|
-
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
1800
|
-
body: JSON.stringify({ project: RELAY!.project }),
|
|
1801
|
-
signal: AbortSignal.timeout(15_000),
|
|
1802
|
-
});
|
|
1803
|
-
minted = await response.json().catch(() => null) as MintedSession | null;
|
|
1804
|
-
if (!response.ok || typeof minted?.ingest_url !== "string" || typeof minted.poll_url !== "string") {
|
|
1805
|
-
const detail = minted?.errors?.[0]?.message ?? "HTTP " + response.status;
|
|
1806
|
-
fail(1, "Couldn't start a relay session: " + detail);
|
|
1807
|
-
}
|
|
1808
|
-
} catch (e) {
|
|
1809
|
-
if (e instanceof ExitPending) throw e;
|
|
1810
|
-
fail(1, "Couldn't reach the relay: " + (e as Error).message);
|
|
1811
|
-
}
|
|
1812
1750
|
|
|
1813
|
-
process.stderr.write(paintErr("green", "Ready!") + " Forwarding relayed events to " + forwardTo + "\n");
|
|
1814
|
-
process.stderr.write("Point this API's webhook endpoint at: " + paintErr("cyan", minted!.ingest_url!) + " (^C to quit)\n");
|
|
1815
1751
|
|
|
1816
|
-
let cursor = 0;
|
|
1817
|
-
let pollFailures = 0;
|
|
1818
|
-
for (;;) {
|
|
1819
|
-
let payload: { events: RelayedEvent[]; next: number };
|
|
1820
|
-
try {
|
|
1821
|
-
const response = await fetch(minted!.poll_url! + "?after=" + cursor + "&wait=1", {
|
|
1822
|
-
headers: { Accept: "application/json" },
|
|
1823
|
-
signal: AbortSignal.timeout(30_000),
|
|
1824
|
-
});
|
|
1825
|
-
if (response.status === 404) fail(1, "The relay session expired. Run '" + BIN + " webhooks listen' again.");
|
|
1826
|
-
if (!response.ok) throw new Error("HTTP " + response.status);
|
|
1827
|
-
payload = await response.json() as typeof payload;
|
|
1828
|
-
pollFailures = 0;
|
|
1829
|
-
} catch (e) {
|
|
1830
|
-
if (e instanceof ExitPending) throw e;
|
|
1831
|
-
pollFailures++;
|
|
1832
|
-
if (pollFailures > 5) fail(1, "Lost the relay: " + (e as Error).message);
|
|
1833
|
-
await new Promise((resolve) => setTimeout(resolve, 2000 * pollFailures));
|
|
1834
|
-
continue;
|
|
1835
|
-
}
|
|
1836
|
-
for (const event of payload.events) {
|
|
1837
|
-
cursor = event.seq;
|
|
1838
|
-
const type = eventTypeOf(event.body);
|
|
1839
|
-
if (eventsFilter !== null && (type === null || !eventsFilter.includes(type))) continue;
|
|
1840
|
-
const started = Date.now();
|
|
1841
|
-
try {
|
|
1842
|
-
const forwarded = await fetch(forwardTo, {
|
|
1843
|
-
method: event.method,
|
|
1844
|
-
headers: event.headers,
|
|
1845
|
-
body: event.method === "GET" ? undefined : event.body,
|
|
1846
|
-
signal: AbortSignal.timeout(30_000),
|
|
1847
|
-
});
|
|
1848
|
-
process.stderr.write(
|
|
1849
|
-
(forwarded.ok ? paintErr("green", String(forwarded.status)) : paintErr("yellow", String(forwarded.status))) +
|
|
1850
|
-
" " + (type ?? event.method) + " [" + event.seq + "] (" + (Date.now() - started) + "ms)\n",
|
|
1851
|
-
);
|
|
1852
|
-
} catch (e) {
|
|
1853
|
-
process.stderr.write(paintErr("yellow", "unreachable") + " " + (type ?? event.method) + " [" + event.seq + "]: " + (e as Error).message + "\n");
|
|
1854
|
-
}
|
|
1855
|
-
}
|
|
1856
|
-
if (payload.next > cursor) cursor = payload.next;
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
1752
|
|
|
1860
|
-
async function cmdFeedback(parsed: Parsed): Promise<void> {
|
|
1861
|
-
if (parsed.help) {
|
|
1862
|
-
process.stdout.write(BIN + " feedback — open the API provider's issue tracker with environment details prefilled\n");
|
|
1863
|
-
await flushExit(0);
|
|
1864
|
-
}
|
|
1865
|
-
if (!SUPPORT_URL) {
|
|
1866
|
-
fail(2, "No support URL is configured for this CLI. The API provider can set one in their typeship console.");
|
|
1867
|
-
}
|
|
1868
|
-
let target = SUPPORT_URL!;
|
|
1869
|
-
if (/github\.com\/[^/]+\/[^/]+\/issues\/new/.test(target)) {
|
|
1870
|
-
const bodyLines = [
|
|
1871
|
-
"<!-- describe the problem or request above the line -->",
|
|
1872
|
-
"",
|
|
1873
|
-
"---",
|
|
1874
|
-
"- cli: " + BIN + " " + VERSION + " (api " + API_VERSION + ")",
|
|
1875
|
-
"- node: " + process.version,
|
|
1876
|
-
"- platform: " + process.platform + "/" + process.arch,
|
|
1877
|
-
];
|
|
1878
|
-
const sep = target.includes("?") ? "&" : "?";
|
|
1879
|
-
target += sep + "title=" + encodeURIComponent("[" + BIN + "] ") + "&body=" + encodeURIComponent(bodyLines.join("\n"));
|
|
1880
|
-
}
|
|
1881
|
-
if (!nonInteractive(parsed)) process.stderr.write("Opening " + paintErr("cyan", SUPPORT_URL!) + "\n");
|
|
1882
|
-
const opened = openInBrowser(target, parsed);
|
|
1883
|
-
if (opened.opened) out({ ok: true, opened: SUPPORT_URL });
|
|
1884
|
-
await flushExit(0);
|
|
1885
|
-
}
|
|
1886
1753
|
|
|
1887
|
-
/** Opt-in (console setting) once-a-day upgrade hint. Off by default:
|
|
1888
|
-
* generated code phones nobody unless the project owner chose this. The
|
|
1889
|
-
* notice prints the previous run's cached answer; at most once a day it
|
|
1890
|
-
* refreshes the cache first, with a 1.5s cap so a slow registry can't
|
|
1891
|
-
* hold a command hostage. */
|
|
1892
|
-
async function maybeUpdateNotice(commandWord: string | undefined, quiet: boolean): Promise<void> {
|
|
1893
|
-
if (!UPDATE_NOTICE || quiet) return;
|
|
1894
|
-
if (commandWord === undefined || ["upgrade", "version", "help", "login", "logout"].includes(commandWord)) return;
|
|
1895
|
-
const cacheFile = join(configDir(), "update-check.json");
|
|
1896
|
-
let cache: { checkedAt?: number; latest?: string } = {};
|
|
1897
|
-
try {
|
|
1898
|
-
cache = JSON.parse(readFileSync(cacheFile, "utf8")) as typeof cache;
|
|
1899
|
-
} catch { /* no cache yet */ }
|
|
1900
|
-
if ((cache.checkedAt ?? 0) < Date.now() - 24 * 60 * 60 * 1000) {
|
|
1901
|
-
const latest = await latestVersion(1500);
|
|
1902
|
-
if (latest !== null) {
|
|
1903
|
-
cache = { checkedAt: Date.now(), latest };
|
|
1904
|
-
mkdirSync(configDir(), { recursive: true, mode: 0o700 });
|
|
1905
|
-
writeFileSync(cacheFile, JSON.stringify(cache) + "\n");
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1908
|
-
if (cache.latest !== undefined && semverLess(VERSION, cache.latest)) {
|
|
1909
|
-
process.stderr.write(paintErr("yellow", "A newer " + BIN + " is available: " + VERSION + " -> " + cache.latest + ". Run '" + BIN + " upgrade'.") + "\n");
|
|
1910
|
-
}
|
|
1911
|
-
}
|
|
1912
1754
|
|
|
1913
1755
|
/** The command that fetches the next page: same positionals, the next page's query flags. */
|
|
1914
1756
|
function nextCommandFor(op: OpSpec, pathValues: string[], next: Record<string, unknown>): string {
|
|
@@ -2051,7 +1893,7 @@ function printRoot(stream: NodeJS.WriteStream = process.stdout): void {
|
|
|
2051
1893
|
}
|
|
2052
1894
|
const width = termWidth();
|
|
2053
1895
|
const lines: string[] = [];
|
|
2054
|
-
lines.push(paintOut("bold", BIN) + ": " + "typeship" + " (v" + "0.
|
|
1896
|
+
lines.push(paintOut("bold", BIN) + ": " + "typeship API" + " (v" + "1.0.0" + "), package " + "0.8.0");
|
|
2055
1897
|
lines.push("");
|
|
2056
1898
|
lines.push(paintOut("bold", "Usage:") + " " + BIN + " <resource> <command> [args] [--flags]");
|
|
2057
1899
|
lines.push("");
|
|
@@ -2068,6 +1910,12 @@ function printRoot(stream: NodeJS.WriteStream = process.stdout): void {
|
|
|
2068
1910
|
lines.push(" " + padPaint("cyan", resource, col) + wrapped[0]);
|
|
2069
1911
|
for (const more of wrapped.slice(1)) lines.push(" ".repeat(2 + col) + more);
|
|
2070
1912
|
}
|
|
1913
|
+
if (EXCLUDED_OPS > 0) {
|
|
1914
|
+
lines.push("");
|
|
1915
|
+
lines.push(...labeled(paintOut("yellow", "Plan limit:") + " ", "generated " + OPS.length + " of " + (OPS.length + EXCLUDED_OPS) + " operations", width, 14));
|
|
1916
|
+
lines.push(...labeled("Omitted: ", OMITTED_OPS.map((op) => op.command.join(" ") + " (" + op.httpMethod + " " + op.path + ")").join(", "), width, 14));
|
|
1917
|
+
lines.push(...labeled("Upgrade: ", "https://typeship.dev/pricing, then regenerate without the operation cap", width, 14));
|
|
1918
|
+
}
|
|
2071
1919
|
lines.push("");
|
|
2072
1920
|
const flagsText = "-v/--version, -h/--help, --debug, --non-interactive, --color on|off|auto, --base-url <url>, --data '<json>', --fields <a,b.c>, --all (paginated lists), --validate (schema-check bodies)" +
|
|
2073
1921
|
(AUTH_SCALARS.length > 0 ? ", " + AUTH_SCALARS.map((a) => "--" + a.flag + " <value>").join(", ") : "");
|
|
@@ -2083,9 +1931,9 @@ function printRoot(stream: NodeJS.WriteStream = process.stdout): void {
|
|
|
2083
1931
|
lines.push(...labeled("Docs: ", BIN + " docs [<resource> <command> | search <term> | read <page> | --web]", width, 6));
|
|
2084
1932
|
if (RELAY) lines.push("Webhooks: " + BIN + " webhooks listen --forward-to <url> (local event forwarding)");
|
|
2085
1933
|
if (SUPPORT_URL) lines.push("Feedback: " + BIN + " feedback (opens the provider's issue tracker)");
|
|
2086
|
-
if (
|
|
1934
|
+
if (MCP_EXCLUDED_OPS > 0 && HAS_MCP) {
|
|
2087
1935
|
lines.push("");
|
|
2088
|
-
lines.push("Note: " +
|
|
1936
|
+
lines.push("Note: " + MCP_EXCLUDED_OPS + " generated operation(s) with uploads or event streams are CLI/SDK-only, not MCP tools.");
|
|
2089
1937
|
}
|
|
2090
1938
|
lines.push("");
|
|
2091
1939
|
lines.push("Run '" + BIN + " <resource>' for a resource's commands, '" + BIN + " <resource> <command> --help' for flags.");
|
|
@@ -2334,10 +2182,9 @@ function resolveBaseUrl(flags: Map<string, string | boolean>): string | undefine
|
|
|
2334
2182
|
|
|
2335
2183
|
async function makeClient(flags: Map<string, string | boolean>): Promise<TypeshipClient> {
|
|
2336
2184
|
const stored = readCreds();
|
|
2337
|
-
const options: Record<string, unknown> = {};
|
|
2338
2185
|
const baseUrl = resolveBaseUrl(flags);
|
|
2339
2186
|
if (baseUrl === undefined) fail(2, "No base URL. Pass --base-url, set TYPESHIP_BASE_URL, or run '" + BIN + " config set base-url <url>'.");
|
|
2340
|
-
options
|
|
2187
|
+
const options: ClientOptions & Record<string, unknown> = { baseUrl };
|
|
2341
2188
|
for (const a of AUTH_SCALARS) {
|
|
2342
2189
|
const v = (typeof flags.get(a.flag) === "string" ? flags.get(a.flag) as string : undefined)
|
|
2343
2190
|
?? process.env[a.env] ?? stored?.scalars?.[a.option];
|
|
@@ -2377,7 +2224,7 @@ async function makeClient(flags: Map<string, string | boolean>): Promise<Typeshi
|
|
|
2377
2224
|
...(options.defaultHeaders as Record<string, string> | undefined),
|
|
2378
2225
|
"User-Agent": PKG_NAME + "-cli/" + VERSION + " (typeship" + (harness ? "; harness=" + harness : "") + caller + ")",
|
|
2379
2226
|
};
|
|
2380
|
-
return new TypeshipClient(options
|
|
2227
|
+
return new TypeshipClient(options);
|
|
2381
2228
|
}
|
|
2382
2229
|
|
|
2383
2230
|
function editDistance(a: string, b: string): number {
|
|
@@ -2405,7 +2252,22 @@ function didYouMean(input: string, candidates: Iterable<string>): string | undef
|
|
|
2405
2252
|
return best;
|
|
2406
2253
|
}
|
|
2407
2254
|
|
|
2408
|
-
|
|
2255
|
+
function omittedCommand(resource: string, method?: string): OmittedOpSpec | undefined {
|
|
2256
|
+
return OMITTED_OPS.find((op) => op.command[0] === resource &&
|
|
2257
|
+
(method === undefined || op.command[1] === method || op.commandAlias === method));
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
function failOmitted(op: OmittedOpSpec): never {
|
|
2261
|
+
return failWith({
|
|
2262
|
+
status: "action_required",
|
|
2263
|
+
code: "PLAN_LIMIT",
|
|
2264
|
+
message: "The command '" + BIN + " " + op.command.join(" ") + "' exists in the API Definition but was omitted from this generated package by its plan limit.",
|
|
2265
|
+
detail: { operation: op.tool, method: op.httpMethod, path: op.path, generated_operations: OPS.length, total_operations: OPS.length + EXCLUDED_OPS },
|
|
2266
|
+
nextSteps: ["Upgrade at https://typeship.dev/pricing and regenerate the package without the operation cap.", "Do not invent or retry an omitted command against this generated package."],
|
|
2267
|
+
});
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
const BUILTIN_COMMANDS = ["login","logout","whoami","config","mcp","docs","upgrade","completion","help","version","init","agent-guide","auth","doctor"];
|
|
2409
2271
|
|
|
2410
2272
|
async function main(): Promise<void> {
|
|
2411
2273
|
const argv = process.argv.slice(2);
|
|
@@ -2430,17 +2292,17 @@ async function main(): Promise<void> {
|
|
|
2430
2292
|
COLOR_ERR = colorEnabled(process.stderr, parsed);
|
|
2431
2293
|
// Prose errors for a person: stderr is a terminal (or --mode human says
|
|
2432
2294
|
// to behave as if), and nothing asked for JSON.
|
|
2433
|
-
const forcedHuman = parsed.flags.get("mode") === "human" ||
|
|
2295
|
+
const forcedHuman = parsed.flags.get("mode") === "human" || process.env["TYPESHIP_MODE"] === "human";
|
|
2434
2296
|
HUMAN_ERRORS = (process.stderr.isTTY === true || forcedHuman) && !isAgentMode(parsed) && parsed.flags.get("format") !== "json" && parsed.flags.get("json") !== true;
|
|
2435
|
-
|
|
2297
|
+
|
|
2436
2298
|
|
|
2437
2299
|
if (resourceCmd === "init") { await cmdInit(parsed); }
|
|
2438
2300
|
if (resourceCmd === "agent-guide") { await cmdAgentGuide(parsed); }
|
|
2439
2301
|
if (resourceCmd === "auth") { await cmdAuth(parsed); }
|
|
2440
2302
|
if (resourceCmd === "doctor") { await cmdDoctor(parsed); }
|
|
2441
2303
|
if (resourceCmd === "upgrade") { await cmdUpgrade(parsed); }
|
|
2442
|
-
|
|
2443
|
-
|
|
2304
|
+
|
|
2305
|
+
|
|
2444
2306
|
if (resourceCmd === "docs") { await cmdDocs(parsed); }
|
|
2445
2307
|
if (resourceCmd === "completion") { await cmdCompletion(parsed); }
|
|
2446
2308
|
if (resourceCmd === "config") { await cmdConfig(parsed); }
|
|
@@ -2465,6 +2327,8 @@ async function main(): Promise<void> {
|
|
|
2465
2327
|
if (!resourceCmd) { printRoot(parsed.help ? process.stdout : process.stderr); await flushExit(parsed.help ? 0 : 2); }
|
|
2466
2328
|
const resourceExists = OPS.some((o) => o.command[0] === resourceCmd);
|
|
2467
2329
|
if (!resourceExists) {
|
|
2330
|
+
const omitted = omittedCommand(resourceCmd, methodCmd);
|
|
2331
|
+
if (omitted) failOmitted(omitted);
|
|
2468
2332
|
const suggestion = didYouMean(resourceCmd, [...new Set(OPS.map((o) => o.command[0])), ...BUILTIN_COMMANDS]);
|
|
2469
2333
|
fail(2, "Unknown command: " + resourceCmd + "." + (suggestion ? " Did you mean '" + BIN + " " + suggestion + "'?" : ""),
|
|
2470
2334
|
undefined, ["Run '" + BIN + " --help' for the commands."]);
|
|
@@ -2473,6 +2337,8 @@ async function main(): Promise<void> {
|
|
|
2473
2337
|
|
|
2474
2338
|
const op = findOp(resourceCmd, methodCmd);
|
|
2475
2339
|
if (!op) {
|
|
2340
|
+
const omitted = omittedCommand(resourceCmd, methodCmd);
|
|
2341
|
+
if (omitted) failOmitted(omitted);
|
|
2476
2342
|
const suggestion = didYouMean(methodCmd, OPS.filter((o) => o.command[0] === resourceCmd).map((o) => o.command[1]));
|
|
2477
2343
|
fail(2, "Unknown command: " + resourceCmd + " " + methodCmd + "." + (suggestion ? " Did you mean '" + BIN + " " + resourceCmd + " " + suggestion + "'?" : ""),
|
|
2478
2344
|
undefined, ["Run '" + BIN + " " + resourceCmd + "' for its commands."]);
|
|
@@ -2612,7 +2478,7 @@ async function main(): Promise<void> {
|
|
|
2612
2478
|
}
|
|
2613
2479
|
}
|
|
2614
2480
|
|
|
2615
|
-
const result = await (callResult as Promise<{ ok: boolean; data?: unknown; error?: unknown }>);
|
|
2481
|
+
const result = await (callResult as Promise<{ ok: boolean; data?: unknown; error?: unknown; response?: { requestId?: string } }>);
|
|
2616
2482
|
if (result.ok) {
|
|
2617
2483
|
if (op.sse) {
|
|
2618
2484
|
// Server-sent events as NDJSON, one line per event, until the stream ends.
|
|
@@ -2638,12 +2504,13 @@ async function main(): Promise<void> {
|
|
|
2638
2504
|
// One page, plus what fetches the next: the raw arguments (the MCP
|
|
2639
2505
|
// tool's nextPage shape) and the exact command, so a script or an
|
|
2640
2506
|
// agent never has to reconstruct the cursor flag.
|
|
2641
|
-
const page = result.data as { items: unknown[]; hasNextPage(): boolean; nextPageParams(): Record<string, unknown> | null };
|
|
2507
|
+
const page = result.data as { items: unknown[]; hasNextPage(): boolean; nextPageParams(): Record<string, unknown> | null; response: { requestId?: string } };
|
|
2642
2508
|
const next = page.nextPageParams();
|
|
2643
2509
|
out({
|
|
2644
2510
|
items: project(page.items),
|
|
2645
2511
|
hasMore: next !== null,
|
|
2646
2512
|
...(next !== null ? { nextPage: next, nextCommand: nextCommandFor(op, pathValues, next) } : {}),
|
|
2513
|
+
...(page.response.requestId ? { request_id: page.response.requestId } : {}),
|
|
2647
2514
|
});
|
|
2648
2515
|
} else if (FIELDS !== null && collectionField !== null && result.data !== null && typeof result.data === "object" && !Array.isArray(result.data) && Array.isArray((result.data as Record<string, unknown>)[collectionField])) {
|
|
2649
2516
|
// Batch-style collection envelopes ({data: [...]}) use item-relative
|
|
@@ -2674,17 +2541,6 @@ function errorMessage(error: unknown): string {
|
|
|
2674
2541
|
return base;
|
|
2675
2542
|
}
|
|
2676
2543
|
|
|
2677
|
-
function serializeError(error: unknown): unknown {
|
|
2678
|
-
if (error && typeof error === "object" && "violations" in error) {
|
|
2679
|
-
return { violations: (error as { violations: unknown }).violations };
|
|
2680
|
-
}
|
|
2681
|
-
if (error && typeof error === "object" && "status" in error) {
|
|
2682
|
-
const e = error as { status: number; body?: unknown };
|
|
2683
|
-
return { status: e.status, body: e.body };
|
|
2684
|
-
}
|
|
2685
|
-
return undefined;
|
|
2686
|
-
}
|
|
2687
|
-
|
|
2688
2544
|
main().catch((e) => {
|
|
2689
2545
|
if (e instanceof ExitPending) return;
|
|
2690
2546
|
try {
|