@typeship-ax/cli 0.10.0 → 0.21.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 +10 -5
- package/README.md +5 -5
- package/api.json +9773 -4501
- package/api.md +501 -271
- package/dist/api-identity.d.ts.map +1 -1
- package/dist/api-identity.js +6 -1
- package/dist/cli-agent.d.ts +5 -5
- package/dist/cli-agent.d.ts.map +1 -1
- package/dist/cli-agent.js +14 -10
- package/dist/cli.js +158 -82
- package/dist/core/http.d.ts +29 -16
- package/dist/core/http.d.ts.map +1 -1
- package/dist/core/http.js +108 -25
- package/dist/core/pagination.d.ts +8 -8
- package/dist/core/pagination.d.ts.map +1 -1
- package/dist/core/pagination.js +7 -16
- package/dist/errors.d.ts +20 -13
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +29 -20
- package/dist/index.d.ts +37 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +42 -18
- package/dist/ops.d.ts +5 -1
- package/dist/ops.d.ts.map +1 -1
- package/dist/ops.js +42 -35
- package/dist/polling-login.d.ts.map +1 -1
- package/dist/polling-login.js +11 -1
- package/dist/resources/api-keys.d.ts +44 -18
- package/dist/resources/api-keys.d.ts.map +1 -1
- package/dist/resources/api-keys.js +46 -14
- package/dist/resources/deliveries.d.ts +46 -0
- package/dist/resources/deliveries.d.ts.map +1 -0
- package/dist/resources/deliveries.js +70 -0
- package/dist/resources/drafts.d.ts +155 -0
- package/dist/resources/drafts.d.ts.map +1 -0
- package/dist/resources/drafts.js +230 -0
- package/dist/resources/files.d.ts +23 -0
- package/dist/resources/files.d.ts.map +1 -0
- package/dist/resources/files.js +38 -0
- package/dist/resources/generate.d.ts +49 -20
- package/dist/resources/generate.d.ts.map +1 -1
- package/dist/resources/generate.js +56 -14
- package/dist/resources/generations.d.ts +70 -18
- package/dist/resources/generations.d.ts.map +1 -1
- package/dist/resources/generations.js +84 -16
- package/dist/resources/organization.d.ts +18 -0
- package/dist/resources/organization.d.ts.map +1 -0
- package/dist/resources/organization.js +32 -0
- package/dist/resources/projects.d.ts +87 -124
- package/dist/resources/projects.d.ts.map +1 -1
- package/dist/resources/projects.js +70 -173
- package/dist/resources/publications.d.ts +46 -0
- package/dist/resources/publications.d.ts.map +1 -0
- package/dist/resources/publications.js +70 -0
- package/dist/resources/releases.d.ts +66 -0
- package/dist/resources/releases.d.ts.map +1 -0
- package/dist/resources/releases.js +101 -0
- package/dist/resources/spec-revisions.d.ts +85 -0
- package/dist/resources/spec-revisions.d.ts.map +1 -0
- package/dist/resources/spec-revisions.js +116 -0
- package/dist/resources/specs.d.ts +72 -0
- package/dist/resources/specs.d.ts.map +1 -0
- package/dist/resources/specs.js +107 -0
- package/dist/resources/targets.d.ts +85 -105
- package/dist/resources/targets.d.ts.map +1 -1
- package/dist/resources/targets.js +70 -163
- package/dist/schemas.d.ts +1 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +194 -139
- package/dist/types.d.ts +2626 -1141
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +90 -11
- package/package.json +3 -3
- package/src/api-identity.ts +6 -2
- package/src/cli-agent.ts +17 -13
- package/src/cli.ts +145 -77
- package/src/core/http.ts +106 -30
- package/src/core/pagination.ts +13 -23
- package/src/errors.ts +30 -20
- package/src/index.ts +46 -24
- package/src/ops.ts +47 -36
- package/src/polling-login.ts +10 -1
- package/src/resources/api-keys.ts +87 -19
- package/src/resources/deliveries.ts +139 -0
- package/src/resources/drafts.ts +422 -0
- package/src/resources/files.ts +68 -0
- package/src/resources/generate.ts +81 -19
- package/src/resources/generations.ts +167 -29
- package/src/resources/organization.ts +53 -0
- package/src/resources/projects.ts +129 -322
- package/src/resources/publications.ts +139 -0
- package/src/resources/releases.ts +199 -0
- package/src/resources/spec-revisions.ts +237 -0
- package/src/resources/specs.ts +200 -0
- package/src/resources/targets.ts +135 -304
- package/src/schemas.ts +195 -140
- package/src/types.ts +2793 -1177
- package/dist/resources/account.d.ts +0 -18
- package/dist/resources/account.d.ts.map +0 -1
- package/dist/resources/account.js +0 -27
- package/dist/resources/definition-revisions.d.ts +0 -58
- package/dist/resources/definition-revisions.d.ts.map +0 -1
- package/dist/resources/definition-revisions.js +0 -114
- package/dist/resources/definitions.d.ts +0 -35
- package/dist/resources/definitions.d.ts.map +0 -1
- package/dist/resources/definitions.js +0 -60
- package/src/resources/account.ts +0 -46
- package/src/resources/definition-revisions.ts +0 -207
- package/src/resources/definitions.ts +0 -122
package/src/cli.ts
CHANGED
|
@@ -21,6 +21,8 @@ import { homedir, hostname } from "node:os";
|
|
|
21
21
|
import { basename, dirname, join } from "node:path";
|
|
22
22
|
import { fileURLToPath } from "node:url";
|
|
23
23
|
import { TypeshipClient, formatDebugEvent, type ClientOptions, type DebugEvent } from "./index.js";
|
|
24
|
+
import { asApiResult, validateAgainstSchema, ValidationError, type Violation } from "./core/http.js";
|
|
25
|
+
import { SCHEMAS, DEFS } from "./schemas.js";
|
|
24
26
|
import { GLOBALS, OMITTED_OPS, OPS, buildArgs, findOp, missingRequired, type OmittedOpSpec, type OpSpec, type ParamSpec } from "./ops.js";
|
|
25
27
|
import {
|
|
26
28
|
MCP_CLIENTS, agentGuide, agentBlock, agentInstructionsFile, agentMode, bundleProperty, claimProperty, classifyApiError, collectionProperty, detectHarness, envelope,
|
|
@@ -40,17 +42,17 @@ const BASIC: { envUser: string; envPass: string } | null = null;
|
|
|
40
42
|
const EXCLUDED_OPS = 0;
|
|
41
43
|
/** Generated CLI operations that are intentionally unavailable to MCP. */
|
|
42
44
|
const MCP_EXCLUDED_OPS = 0;
|
|
43
|
-
const VERSION = "0.
|
|
45
|
+
const VERSION = "0.21.0";
|
|
44
46
|
const API_VERSION = "1.0.0";
|
|
45
47
|
const SPEC_FORMAT = "openapi";
|
|
46
48
|
const IDENTITY_POLICY: IdentityPolicy = {};
|
|
47
49
|
let LOGIN_IDENTITY: VerifiedIdentity | undefined;
|
|
48
|
-
const WHOAMI: { resource: string; method: string } | null =
|
|
50
|
+
const WHOAMI: { resource: string; method: string } | null = null;
|
|
49
51
|
const ENVIRONMENTS: Record<string, string> = {};
|
|
50
52
|
const HAS_MCP = false;
|
|
51
53
|
const PKG_NAME = "@typeship-ax/cli";
|
|
52
54
|
const UPDATE_NOTICE = false;
|
|
53
|
-
const API_DESCRIPTION: string | null = "Resolve an OpenAPI or GraphQL
|
|
55
|
+
const API_DESCRIPTION: string | null = "Resolve an OpenAPI or GraphQL Spec, diagnose it, and keep every\nselected CLI, MCP, and SDK 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\nExamples use Parcel, a fictional delivery service. Replace its domains,\nrepository names, and resource identifiers with your own. The hosted\npetstore Spec is a runnable sample.\n";
|
|
54
56
|
const DOCS_URL_DEFAULT: string | null = "https://typeship.dev";
|
|
55
57
|
const DOCS_INDEX_URL_DEFAULT: string | null = null;
|
|
56
58
|
const RELAY: { mintUrl: string; project: string } | null = null;
|
|
@@ -138,7 +140,8 @@ function parseArgv(argv: string[]): Parsed {
|
|
|
138
140
|
setFlag(arg.slice(2, eq), arg.slice(eq + 1));
|
|
139
141
|
} else if (isBooleanFlag(arg.slice(2), positionals)) {
|
|
140
142
|
const next = argv[i + 1];
|
|
141
|
-
|
|
143
|
+
const nullable = positionals.length >= 2 && findOp(positionals[0]!, positionals[1]!)?.params.some((p) => p.flag === arg.slice(2) && p.nullable);
|
|
144
|
+
if (next === "true" || next === "false" || (next === "null" && nullable)) { setFlag(arg.slice(2), next); i++; }
|
|
142
145
|
else flags.set(arg.slice(2), true);
|
|
143
146
|
} else {
|
|
144
147
|
const next = argv[i + 1];
|
|
@@ -220,25 +223,22 @@ function out(value: unknown): void {
|
|
|
220
223
|
let FIELDS: string[][] | null = null;
|
|
221
224
|
|
|
222
225
|
/** Keep only FIELDS of a result: arrays item by item, objects by dotted path; scalars untouched. */
|
|
223
|
-
function project(value: unknown): unknown {
|
|
224
|
-
if (
|
|
225
|
-
if (Array.isArray(value)) return value.map(project);
|
|
226
|
+
function project(value: unknown, paths: string[][] | null = FIELDS): unknown {
|
|
227
|
+
if (paths === null) return value;
|
|
228
|
+
if (Array.isArray(value)) return value.map((item) => project(item, paths));
|
|
226
229
|
if (value === null || typeof value !== "object") return value;
|
|
230
|
+
const groups = new Map<string, string[][]>();
|
|
231
|
+
for (const [key, ...rest] of paths) {
|
|
232
|
+
if (key === undefined) continue;
|
|
233
|
+
const group = groups.get(key);
|
|
234
|
+
if (group) group.push(rest); else groups.set(key, [rest]);
|
|
235
|
+
}
|
|
227
236
|
const out: Record<string, unknown> = {};
|
|
228
|
-
for (const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
if (cursor === undefined) continue;
|
|
235
|
-
let target = out;
|
|
236
|
-
for (const key of path.slice(0, -1)) {
|
|
237
|
-
const next = target[key];
|
|
238
|
-
if (next === undefined || next === null || typeof next !== "object" || Array.isArray(next)) target[key] = {};
|
|
239
|
-
target = target[key] as Record<string, unknown>;
|
|
240
|
-
}
|
|
241
|
-
target[path[path.length - 1]!] = cursor;
|
|
237
|
+
for (const [key, rests] of groups) {
|
|
238
|
+
const child = (value as Record<string, unknown>)[key];
|
|
239
|
+
if (child === undefined) continue;
|
|
240
|
+
if (rests.some((rest) => rest.length === 0)) out[key] = child;
|
|
241
|
+
else if (child !== null && typeof child === "object") out[key] = project(child, rests);
|
|
242
242
|
}
|
|
243
243
|
return out;
|
|
244
244
|
}
|
|
@@ -289,11 +289,23 @@ function humanError(body: ReturnType<typeof envelope>, code: number): string {
|
|
|
289
289
|
if (detail?.violations !== undefined) {
|
|
290
290
|
for (const v of (detail.violations as { path?: string; message?: string }[]).slice(0, 8)) lines.push(" " + paintErr("dim", (v.path ? v.path + ": " : "") + (v.message ?? "")));
|
|
291
291
|
} else if (detail?.body !== undefined) {
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
292
|
+
// Keep every API error and its field visible, even when the headline
|
|
293
|
+
// already used the first message. Unstructured bodies retain a summary.
|
|
294
|
+
const apiErrors = typeof detail.body === "object" && detail.body !== null
|
|
295
|
+
? (detail.body as { errors?: unknown }).errors : undefined;
|
|
296
|
+
const entries = Array.isArray(apiErrors) ? apiErrors.filter((entry): entry is { message: string; field?: string; in?: string } =>
|
|
297
|
+
entry !== null && typeof entry === "object" && typeof entry.message === "string") : [];
|
|
298
|
+
if (entries.length > 0) {
|
|
299
|
+
for (const entry of entries) {
|
|
300
|
+
const field = typeof entry.field === "string" ? entry.field : "";
|
|
301
|
+
const location = typeof entry.in === "string" ? entry.in + " " : "";
|
|
302
|
+
lines.push(" " + paintErr("dim", (field ? location + field + ": " : "") + entry.message));
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
const compact = typeof detail.body === "string" ? detail.body : JSON.stringify(detail.body);
|
|
306
|
+
const firstWords = (issue?.message ?? "").slice(0, 40);
|
|
307
|
+
if (compact && compact !== "{}" && !(firstWords && compact.includes(firstWords))) lines.push(" " + paintErr("dim", "API said: " + (compact.length > 300 ? compact.slice(0, 297) + "…" : compact)));
|
|
308
|
+
}
|
|
297
309
|
const requestId = detail.request_id ?? (detail.body as { request_id?: unknown; requestId?: unknown } | null)?.request_id ?? (detail.body as { requestId?: unknown } | null)?.requestId;
|
|
298
310
|
if (typeof requestId === "string") lines.push(" " + paintErr("dim", "request id: " + requestId));
|
|
299
311
|
}
|
|
@@ -590,7 +602,7 @@ async function startOAuthBrowserSession(parsed: Parsed, clientId: string, timeou
|
|
|
590
602
|
audience: OAUTH_TOKEN_PARAMS.audience, resource: OAUTH_TOKEN_PARAMS.resource,
|
|
591
603
|
organization: requestedLoginOrganization(parsed.flags),
|
|
592
604
|
}, { signal: controller.signal, timeoutMs, authorize(url) {
|
|
593
|
-
process.stderr.write("Sign in
|
|
605
|
+
process.stderr.write("Sign in with your existing credentials: " + url + "\n");
|
|
594
606
|
if (isAgentMode(parsed) || parsed.flags.get("no-browser") === true) process.stderr.write(JSON.stringify({ event: "oauth_browser", authorization_url: url, note: "Open this URL in a browser on the same computer as the CLI." }) + "\n");
|
|
595
607
|
else openInBrowser(url);
|
|
596
608
|
} });
|
|
@@ -643,7 +655,6 @@ async function cmdLogin(parsed: Parsed): Promise<void> {
|
|
|
643
655
|
...AUTH_SCALARS.map((a) => " " + BIN + " login --" + a.flag + " <value>"),
|
|
644
656
|
...(CLI_AUTH_URL ? [" " + BIN + " login approve in the browser: a key is minted for you (add --no-browser to print the link instead of opening it)"] : []),
|
|
645
657
|
" " + BIN + " login --with-token read the credential from stdin (CI)",
|
|
646
|
-
" " + BIN + " login --console-check <file> test browser login for your Typeship Console without replacing saved logins",
|
|
647
658
|
...(HAS_OAUTH_LOGIN ? [" " + BIN + " login --client-id <id> OAuth " + OAUTH_LOGIN_METHOD + " login" + (OAUTH_CLIENT_ID ? " (a default id is built in)" : "")] : []),
|
|
648
659
|
...(HAS_OAUTH_LOGIN ? [" " + BIN + " login --no-browser print the approval URL instead of opening a browser", " " + BIN + " login --device use device authorization when your provider supports it"] : []),
|
|
649
660
|
...(BASIC ? [" " + BIN + " login --username <u> --password <p>"] : []),
|
|
@@ -769,7 +780,7 @@ async function cmdWhoami(parsed: Parsed): Promise<void> {
|
|
|
769
780
|
if (op) {
|
|
770
781
|
const client = await makeClient(parsed.flags, op);
|
|
771
782
|
const target = (client as unknown as Record<string, Record<string, () => Promise<{ ok: boolean; data?: unknown; error?: unknown }>>>)[op.resource]!;
|
|
772
|
-
const result = await target[op.method]!();
|
|
783
|
+
const result = await asApiResult(target[op.method]!());
|
|
773
784
|
if (result.ok) { out(result.data ?? { ok: true }); await flushExit(0); }
|
|
774
785
|
failApi(result.error, LAST_CLIENT_HAD_CREDENTIAL);
|
|
775
786
|
}
|
|
@@ -940,12 +951,11 @@ async function cmdMcp(parsed: Parsed): Promise<void> {
|
|
|
940
951
|
" " + BIN + " mcp install --codex Codex CLI (~/.codex/config.toml)",
|
|
941
952
|
" " + BIN + " mcp install --vscode VS Code (./.vscode/mcp.json)",
|
|
942
953
|
" " + BIN + " mcp install --windsurf | --gemini | --opencode | --zed | --claude-desktop",
|
|
943
|
-
" " + BIN + " mcp install --cursor Cursor (./.cursor/mcp.json
|
|
954
|
+
" " + BIN + " mcp install --cursor Cursor (./.cursor/mcp.json)",
|
|
944
955
|
" " + BIN + " mcp --url <https://...> use a remote MCP endpoint instead of the local server",
|
|
945
956
|
" " + BIN + " mcp install --claude --read-only register a read-only server (writes are not callable)",
|
|
946
957
|
"",
|
|
947
958
|
(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."),
|
|
948
|
-
"--all skips Cursor until it speaks MCP 2026-07-28.",
|
|
949
959
|
];
|
|
950
960
|
process.stdout.write(lines.join("\n") + "\n");
|
|
951
961
|
await flushExit(0);
|
|
@@ -1040,7 +1050,7 @@ function commandSummaries(): CommandSummary[] {
|
|
|
1040
1050
|
paginated: op.paginated,
|
|
1041
1051
|
destructive: op.safety === "destructive",
|
|
1042
1052
|
auth: op.auth,
|
|
1043
|
-
flags: op.params.filter((p) => p.kind !== "path").map((p) => ({ flag: p.flag, type: p.type, ...(p.items ? { items: p.items } : {}), ...(p.enum ? { enum: p.enum } : {}), required: p.required, ...(p.description ? { description: p.description.split("\n")[0] } : {}) })),
|
|
1053
|
+
flags: op.params.filter((p) => p.kind !== "path").map((p) => ({ flag: p.flag, type: p.type, ...(p.nullable ? { nullable: true } : {}), ...(p.items ? { items: p.items } : {}), ...(p.enum ? { enum: p.enum } : {}), required: p.required, ...(p.description ? { description: p.description.split("\n")[0] } : {}) })),
|
|
1044
1054
|
}));
|
|
1045
1055
|
}
|
|
1046
1056
|
|
|
@@ -1164,7 +1174,7 @@ async function cmdAuth(parsed: Parsed): Promise<void> {
|
|
|
1164
1174
|
if (op) {
|
|
1165
1175
|
const client = await makeClient(parsed.flags, op);
|
|
1166
1176
|
const target = (client as unknown as Record<string, Record<string, () => Promise<{ ok: boolean; data?: unknown; error?: unknown }>>>)[op.resource]!;
|
|
1167
|
-
const result = await target[op.method]!();
|
|
1177
|
+
const result = await asApiResult(target[op.method]!());
|
|
1168
1178
|
if (result.ok) {
|
|
1169
1179
|
if (identityConfiguration() && savedIdentity?.identity) assertApiIdentity(savedIdentity.identity.values, readApiIdentity(result.data, IDENTITY_POLICY));
|
|
1170
1180
|
report.identity = result.data;
|
|
@@ -1188,7 +1198,7 @@ async function cmdDoctor(parsed: Parsed): Promise<void> {
|
|
|
1188
1198
|
}
|
|
1189
1199
|
const checks: DoctorCheck[] = [];
|
|
1190
1200
|
const nodeMajor = Number(process.versions.node.split(".")[0]);
|
|
1191
|
-
checks.push({ name: "node", ok: nodeMajor >=
|
|
1201
|
+
checks.push({ name: "node", ok: nodeMajor >= 20, detail: process.version, ...(nodeMajor >= 20 ? {} : { fix: "Install Node 20 or newer." }) });
|
|
1192
1202
|
checks.push({ name: "cli", ok: true, detail: BIN + " " + VERSION + " (" + PKG_NAME + ")" });
|
|
1193
1203
|
let source: string | null = null;
|
|
1194
1204
|
let storageProblem: string | undefined;
|
|
@@ -1212,7 +1222,7 @@ async function cmdDoctor(parsed: Parsed): Promise<void> {
|
|
|
1212
1222
|
try {
|
|
1213
1223
|
const client = await makeClient(parsed.flags, op);
|
|
1214
1224
|
const target = (client as unknown as Record<string, Record<string, () => Promise<{ ok: boolean; error?: unknown }>>>)[op.resource]!;
|
|
1215
|
-
const result = await target[op.method]!();
|
|
1225
|
+
const result = await asApiResult(target[op.method]!());
|
|
1216
1226
|
checks.push(result.ok ? { name: "identity", ok: true, detail: op.command.join(" ") + " ok" } : { name: "identity", ok: false, detail: classifyApiError(result.error, { bin: BIN, hadCredential: true, docsUrl: DOCS_URL_DEFAULT }).message, fix: "The credential was rejected; run '" + BIN + " login' with a current one." });
|
|
1217
1227
|
} catch (e) {
|
|
1218
1228
|
checks.push({ name: "identity", ok: false, detail: (e as Error).message });
|
|
@@ -1241,7 +1251,7 @@ async function cmdDoctor(parsed: Parsed): Promise<void> {
|
|
|
1241
1251
|
* init: the one command that connects a machine (or a repo) to this API.
|
|
1242
1252
|
* Stores a credential when given one, installs the skills repository, writes
|
|
1243
1253
|
* MCP config for every agent client found, and upserts a marked block into
|
|
1244
|
-
* AGENTS.md (CLAUDE.md
|
|
1254
|
+
* AGENTS.md (or an existing CLAUDE.md when AGENTS.md is absent). Idempotent; --all takes the
|
|
1245
1255
|
* defaults without asking; every part reports and none of them fails init.
|
|
1246
1256
|
*/
|
|
1247
1257
|
async function cmdInit(parsed: Parsed): Promise<void> {
|
|
@@ -1253,7 +1263,7 @@ async function cmdInit(parsed: Parsed): Promise<void> {
|
|
|
1253
1263
|
" --all do everything without prompting (implied under an agent)",
|
|
1254
1264
|
" --no-skills | --no-mcp | --no-agents-md skip a part",
|
|
1255
1265
|
"",
|
|
1256
|
-
"Writes: " + credsPath() + " (credential), the skills directory of each agent on this machine" + (SKILLS_REPO ? " (npx skills add " + SKILLS_REPO + ")" : " (no skills repository configured)") + ", MCP config for detected clients, and a '" + BIN + "' block in ./AGENTS.md.",
|
|
1266
|
+
"Writes: " + credsPath() + " (credential), the skills directory of each agent on this machine" + (SKILLS_REPO ? " (npx skills add " + SKILLS_REPO + ")" : " (no skills repository configured)") + ", MCP config for detected clients, and a '" + BIN + "' block in ./AGENTS.md (or an existing ./CLAUDE.md when AGENTS.md is absent).",
|
|
1257
1267
|
].join("\n") + "\n");
|
|
1258
1268
|
await flushExit(0);
|
|
1259
1269
|
}
|
|
@@ -1316,7 +1326,7 @@ async function cmdInit(parsed: Parsed): Promise<void> {
|
|
|
1316
1326
|
if (parsed.flags.get("no-agents-md") === true) {
|
|
1317
1327
|
report.agents_md = { status: "skipped" };
|
|
1318
1328
|
} else {
|
|
1319
|
-
const file = agentInstructionsFile(cwd
|
|
1329
|
+
const file = agentInstructionsFile(cwd);
|
|
1320
1330
|
const result = upsertAgentBlock(file, BIN + " agent-contract", agentBlock(agentContext(), commandSummaries()));
|
|
1321
1331
|
report.agents_md = { status: result.updated ? "updated" : "written", file: result.file };
|
|
1322
1332
|
}
|
|
@@ -1803,6 +1813,8 @@ async function cmdDocs(parsed: Parsed): Promise<void> {
|
|
|
1803
1813
|
|
|
1804
1814
|
|
|
1805
1815
|
|
|
1816
|
+
|
|
1817
|
+
|
|
1806
1818
|
/** The command that fetches the next page: same positionals, the next page's query flags. */
|
|
1807
1819
|
function nextCommandFor(op: OpSpec, pathValues: string[], next: Record<string, unknown>): string {
|
|
1808
1820
|
const parts = [BIN, op.command[0], op.command[1], ...pathValues.map(shellQuote)];
|
|
@@ -1869,6 +1881,7 @@ function helpSentence(description: string | undefined): string {
|
|
|
1869
1881
|
|
|
1870
1882
|
/** Type column text for a param: string, number, string[], a|b|c, enum, object, json, path. */
|
|
1871
1883
|
function typeLabel(p: ParamSpec): string {
|
|
1884
|
+
if (p.nullable) return typeLabel({ ...p, nullable: false }) + "|null";
|
|
1872
1885
|
if (p.type === "file") return "path (uploaded)";
|
|
1873
1886
|
if (p.format && p.type === "string") return p.format;
|
|
1874
1887
|
const inlineEnum = (values: string[] | undefined) => values && values.join("|").length <= 24 ? values.join("|") : undefined;
|
|
@@ -1944,7 +1957,7 @@ function printRoot(stream: NodeJS.WriteStream = process.stdout): void {
|
|
|
1944
1957
|
}
|
|
1945
1958
|
const width = termWidth();
|
|
1946
1959
|
const lines: string[] = [];
|
|
1947
|
-
lines.push(paintOut("bold", BIN) + ": " + "typeship API" + " (v" + "1.0.0" + "), package " + "0.
|
|
1960
|
+
lines.push(paintOut("bold", BIN) + ": " + "typeship API" + " (v" + "1.0.0" + "), package " + "0.21.0");
|
|
1948
1961
|
lines.push("");
|
|
1949
1962
|
lines.push(paintOut("bold", "Usage:") + " " + BIN + " <resource> <command> [args] [--flags]");
|
|
1950
1963
|
lines.push("");
|
|
@@ -1968,7 +1981,7 @@ function printRoot(stream: NodeJS.WriteStream = process.stdout): void {
|
|
|
1968
1981
|
lines.push(...labeled("Upgrade: ", "https://typeship.dev/pricing, then regenerate without the operation cap", width, 14));
|
|
1969
1982
|
}
|
|
1970
1983
|
lines.push("");
|
|
1971
|
-
const flagsText = "-v/--version, -h/--help, --debug, --non-interactive, --color on|off|auto, --base-url <url>, --profile <name>, --credentials @<file>|-, --data '<json>', --fields <a,b.c>, --all (paginated lists), --validate (schema-check bodies)" +
|
|
1984
|
+
const flagsText = "-v/--version, -h/--help, --debug, --non-interactive, --color on|off|auto, --base-url <url>, --profile <name>, --credentials @<file>|-, --data '<json>', --fields <a,b.c>, --all (paginated lists), --validate (schema-check parameters and JSON bodies)" +
|
|
1972
1985
|
(AUTH_SCALARS.length > 0 ? ", " + AUTH_SCALARS.map((a) => "--" + a.flag + " <value>").join(", ") : "");
|
|
1973
1986
|
lines.push(...labeled(paintOut("bold", "Global flags:") + " ", flagsText, width, 14).map((l, i) => (i === 0 ? l : l)));
|
|
1974
1987
|
lines.push(...labeled("Credential env vars: ", [
|
|
@@ -1976,11 +1989,12 @@ function printRoot(stream: NodeJS.WriteStream = process.stdout): void {
|
|
|
1976
1989
|
...(BASIC ? [BASIC.envUser, BASIC.envPass] : []),
|
|
1977
1990
|
].join(", ") || "none", width, 21));
|
|
1978
1991
|
lines.push(...labeled("Endpoint env var: ", "TYPESHIP_BASE_URL", width, 18));
|
|
1979
|
-
lines.push(...labeled("
|
|
1992
|
+
lines.push(...labeled("Sign-in: ", BIN + " login | logout | whoami | auth check (stored at " + credsPath() + ")", width, 9));
|
|
1980
1993
|
lines.push(...labeled("Setup: ", BIN + " init (connect this machine)" + " | " + BIN + " config (defaults)" + (HAS_MCP || MCP_URL ? " | " + BIN + " mcp install --all (agent clients)" : "") + " | " + BIN + " doctor | " + BIN + " upgrade | " + BIN + " completion <shell>", width, 7));
|
|
1981
1994
|
lines.push(...labeled("Agents: ", BIN + " agent-guide | " + BIN + " help --json | --mode agent | -y/--yes/--force | --out <dir> (JSON errors: {status, issues[{code}], next_steps})", width, 8));
|
|
1982
1995
|
lines.push(...labeled("Docs: ", BIN + " docs [<resource> <command> | search <term> | read <page> | --web]", width, 6));
|
|
1983
1996
|
if (RELAY) lines.push("Webhooks: " + BIN + " webhooks listen --forward-to <url> (local event forwarding)");
|
|
1997
|
+
|
|
1984
1998
|
if (SUPPORT_URL) lines.push("Feedback: " + BIN + " feedback (opens the provider's issue tracker)");
|
|
1985
1999
|
if (MCP_EXCLUDED_OPS > 0 && HAS_MCP) {
|
|
1986
2000
|
lines.push("");
|
|
@@ -2021,16 +2035,17 @@ function commandExtras(op: OpSpec): [string, string][] {
|
|
|
2021
2035
|
if (op.paginated) extras.push(["--all", "stream every item from every page (NDJSON)"]);
|
|
2022
2036
|
const collectionField = collectionProperty(op.outputSchema);
|
|
2023
2037
|
extras.push(["--fields <a,b.c>", "keep only these fields of the result" + (op.paginated ? " (per item)" : collectionField ? " (per item in " + collectionField + ")" : "")]);
|
|
2024
|
-
if (bundleProperty(op.outputSchema) !== null) extras.push(["--out <dir>", "write the response's files ({path, content}) into a directory"]);
|
|
2038
|
+
if ((op.fileBundleProperty ?? bundleProperty(op.outputSchema)) !== null) extras.push(["--out <dir>", "write the response's files ({path, content}) into a directory"]);
|
|
2025
2039
|
if (op.safety === "destructive") extras.push(["--force, -y", "destructive: required without a terminal, skips the prompt with one"]);
|
|
2026
2040
|
return extras;
|
|
2027
2041
|
}
|
|
2028
2042
|
|
|
2029
|
-
/** One runnable example
|
|
2043
|
+
/** One runnable example from required inputs and authored optional values. */
|
|
2030
2044
|
function exampleLine(op: OpSpec): string {
|
|
2031
2045
|
const parts = [BIN, op.command[0], op.command[1]];
|
|
2032
2046
|
for (const p of op.params) {
|
|
2033
|
-
|
|
2047
|
+
const hasExample = p.type !== "file" && Object.hasOwn(op.exampleArguments, p.name);
|
|
2048
|
+
if (!p.required && !hasExample) continue;
|
|
2034
2049
|
const generated = p.type === "file" ? undefined : op.exampleArguments[p.name];
|
|
2035
2050
|
const values = p.type === "array" ? p.items?.enum : p.enum;
|
|
2036
2051
|
const fallback: unknown = p.type === "file" ? "./file"
|
|
@@ -2040,17 +2055,18 @@ function exampleLine(op: OpSpec): string {
|
|
|
2040
2055
|
: p.type === "object" ? {}
|
|
2041
2056
|
: p.type === "array" ? [p.items?.type === "number" ? 1 : "value"]
|
|
2042
2057
|
: "value";
|
|
2043
|
-
const value = generated
|
|
2058
|
+
const value = hasExample ? generated : fallback;
|
|
2044
2059
|
const sample = typeof value === "string" ? shellQuote(value)
|
|
2045
2060
|
: typeof value === "object" ? shellQuote(JSON.stringify(value))
|
|
2046
2061
|
: String(value);
|
|
2047
2062
|
if (p.kind === "path") parts.push(sample);
|
|
2048
2063
|
else parts.push("--" + p.flag, sample);
|
|
2049
2064
|
}
|
|
2050
|
-
const
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2065
|
+
const showBody = op.bodyStyle === "data" && (Object.hasOwn(op.exampleArguments, "body")
|
|
2066
|
+
|| ((op.inputSchema.required as string[] | undefined) ?? []).includes("body"));
|
|
2067
|
+
if (showBody) {
|
|
2068
|
+
const body = Object.hasOwn(op.exampleArguments, "body") ? op.exampleArguments.body : {};
|
|
2069
|
+
parts.push(op.bodyKind === "binary" ? "--file ./file" : "--data " + shellQuote(JSON.stringify(body)));
|
|
2054
2070
|
}
|
|
2055
2071
|
if (op.safety === "destructive") parts.push("--force");
|
|
2056
2072
|
return parts.join(" ");
|
|
@@ -2079,6 +2095,9 @@ function printOp(op: OpSpec): void {
|
|
|
2079
2095
|
lines.push(" " + exampleLine(op));
|
|
2080
2096
|
if (op.paginated) lines.push(" " + usageLine(op) + " --all | jq -r '.id'");
|
|
2081
2097
|
const arrayFlag = rows.find((p) => p.type === "array");
|
|
2098
|
+
if (rows.some((p) => p.nullable)) {
|
|
2099
|
+
lines.push("", "Nullable body flags accept null for JSON null. Use --data to send the literal string null.");
|
|
2100
|
+
}
|
|
2082
2101
|
if (arrayFlag) {
|
|
2083
2102
|
const sample = arrayFlag.items?.enum ? arrayFlag.items.enum.slice(0, 2).join(",") : arrayFlag.items?.type === "number" ? "1,2" : "a,b";
|
|
2084
2103
|
lines.push("", "Array flags take a comma list (--" + arrayFlag.flag + " " + sample + "), the flag repeated, or a JSON array.");
|
|
@@ -2160,6 +2179,7 @@ function coerceScalar(flag: string, type: "string" | "number" | "boolean" | "obj
|
|
|
2160
2179
|
* Objects must be JSON. Enum values are checked locally.
|
|
2161
2180
|
*/
|
|
2162
2181
|
function coerce(spec: ParamSpec, raw: string | boolean, repeated?: string[]): unknown {
|
|
2182
|
+
if (spec.nullable && raw === "null" && repeated === undefined) return null;
|
|
2163
2183
|
if (spec.type === "file") {
|
|
2164
2184
|
if (raw === true) fail(2, "--" + spec.flag + " expects a file path");
|
|
2165
2185
|
return fileFromPath(spec.flag, String(raw));
|
|
@@ -2207,9 +2227,60 @@ function coerce(spec: ParamSpec, raw: string | boolean, repeated?: string[]): un
|
|
|
2207
2227
|
return coerceScalar(spec.flag, spec.type === "json" ? "json" : spec.type === "number" ? "number" : "string", spec.enum, text);
|
|
2208
2228
|
}
|
|
2209
2229
|
|
|
2230
|
+
/** Use validation schemas, not the shortened agent-facing schemas: every union
|
|
2231
|
+
* branch and referenced definition must remain valid input. */
|
|
2232
|
+
function validateParameters(op: OpSpec, values: Record<string, unknown>, flags: Map<string, string | boolean>): void {
|
|
2233
|
+
if (flags.get("validate") !== true) return;
|
|
2234
|
+
const schemas = SCHEMAS[op.resource + "." + op.method]?.params;
|
|
2235
|
+
if (!schemas) return;
|
|
2236
|
+
const violations: Violation[] = [];
|
|
2237
|
+
for (const parameter of op.params) {
|
|
2238
|
+
if (parameter.kind === "body") continue;
|
|
2239
|
+
let value = values[parameter.name];
|
|
2240
|
+
if (value === undefined && parameter.global) {
|
|
2241
|
+
const global = GLOBALS.find((entry) => entry.name === parameter.name);
|
|
2242
|
+
const raw = global ? process.env["TYPESHIP_" + global.envSuffix] : undefined;
|
|
2243
|
+
if (raw !== undefined) value = coerce(parameter, raw);
|
|
2244
|
+
}
|
|
2245
|
+
if (value === undefined) continue;
|
|
2246
|
+
if (parameter.kind === "path") value = coerce(parameter, String(value));
|
|
2247
|
+
const key = parameter.kind + "." + parameter.name;
|
|
2248
|
+
validateAgainstSchema(value, schemas[key], key, violations, DEFS);
|
|
2249
|
+
}
|
|
2250
|
+
if (violations.length) failApi(new ValidationError("request", violations, "parameters"), false);
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2210
2253
|
/** Whether the last client built carried any credential; failApi tells NO_AUTH from AUTH_INVALID with it. */
|
|
2211
2254
|
let LAST_CLIENT_HAD_CREDENTIAL = false;
|
|
2212
2255
|
|
|
2256
|
+
/** Check the same complete alternatives the request runtime can select, after
|
|
2257
|
+
* per-scheme flag, environment, profile, and OAuth resolution. */
|
|
2258
|
+
function requireOperationCredentials(op: OpSpec, options: ClientOptions & Record<string, unknown>): void {
|
|
2259
|
+
if (op.auth !== "required") return;
|
|
2260
|
+
const supplied = (value: unknown): boolean => typeof value === "function" || (typeof value === "string" && value.length > 0)
|
|
2261
|
+
|| (value !== null && typeof value === "object" && "username" in value && "password" in value && typeof value.username === "string" && value.username.length > 0 && typeof value.password === "string" && value.password.length > 0);
|
|
2262
|
+
const named = Object.fromEntries(Object.entries(options.credentials ?? {}).filter(([, value]) => supplied(value))) as NamedCredentials;
|
|
2263
|
+
const available = namedCredentialAvailability(NAMED_SCHEMES, new Set(Object.keys(options).filter((key) => supplied(options[key]))), named);
|
|
2264
|
+
if (op.credentialOptions?.some((alternative) => alternative.length > 0 && alternative.every((option) => available.has(option)))) return;
|
|
2265
|
+
const alternatives = (op.credentialOptions ?? []).filter((alternative) => alternative.length > 0 && alternative.every((option) => option.startsWith("credentials.")));
|
|
2266
|
+
const missing = alternatives.map((alternative) => alternative.filter((option) => !available.has(option)).map((option) => option.slice("credentials.".length)));
|
|
2267
|
+
const names = new Set(missing.flat());
|
|
2268
|
+
const relevant = AUTH_SCALARS.filter((scalar) => [...names].some((name) => NAMED_SCHEMES[name]?.options.includes(scalar.option)));
|
|
2269
|
+
const needsBasic = [...names].some((name) => NAMED_SCHEMES[name]?.options.includes("basicAuth"));
|
|
2270
|
+
failWith({
|
|
2271
|
+
status: "action_required",
|
|
2272
|
+
code: "NO_AUTH",
|
|
2273
|
+
message: op.command.join(" ") + " needs one complete credential alternative (" + wireOf(op) + "). " + (missing.length
|
|
2274
|
+
? "Missing security schemes: " + missing.map((names) => names.join(" + ")).join(" OR ") + "."
|
|
2275
|
+
: "The declared security requirements have no supported, compatible alternative in this CLI."),
|
|
2276
|
+
nextSteps: alternatives.length ? [
|
|
2277
|
+
...relevant.map((a) => "Set " + a.env + " in the environment, pass --" + a.flag + " <value>, or run '" + BIN + " login'."),
|
|
2278
|
+
...(needsBasic && BASIC ? ["Set " + BASIC.envUser + " and " + BASIC.envPass + ", or pass --username and --password."] : []),
|
|
2279
|
+
"Supply all schemes in one alternative through " + "TYPESHIP_CREDENTIALS" + " or --credentials @<JSON-file>: " + alternatives.map((alternative) => alternative.map((option) => option.slice("credentials.".length)).join(" + ")).join(" OR ") + ".",
|
|
2280
|
+
] : ["Check the operation's security schemes in the API Spec and regenerate with a supported, compatible alternative."],
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2213
2284
|
function validateCredentialsInput(flags: Map<string, string | boolean>): void {
|
|
2214
2285
|
const input = flags.get("credentials");
|
|
2215
2286
|
if (BASIC && flags.has("username") !== flags.has("password")) fail(2, "Supply --username and --password together, or use a complete named Basic credential.");
|
|
@@ -2317,18 +2388,17 @@ async function makeClient(flags: Map<string, string | boolean>, op: OpSpec, cand
|
|
|
2317
2388
|
if (value !== undefined) options[g.option] = value;
|
|
2318
2389
|
}
|
|
2319
2390
|
LAST_CLIENT_HAD_CREDENTIAL = Object.keys(options.credentials ?? {}).length > 0 || AUTH_SCALARS.some((a) => options[a.option] !== undefined) || options.basicAuth !== undefined || options.bearerToken !== undefined;
|
|
2320
|
-
|
|
2321
|
-
//
|
|
2322
|
-
//
|
|
2323
|
-
// "non-interactive", so usage by surface does not count CI as agents.
|
|
2324
|
-
// The API can read it back; it carries no secrets.
|
|
2391
|
+
requireOperationCredentials(op, options);
|
|
2392
|
+
// Identify the package and version. Optional harness and caller details
|
|
2393
|
+
// let the API distinguish agent traffic from other non-interactive use.
|
|
2325
2394
|
const harness = detectHarness();
|
|
2326
2395
|
const explicitAgent = flags.get("mode") === "agent" || process.env["TYPESHIP_MODE"] === "agent";
|
|
2327
2396
|
const behaving = agentMode({ flagMode: flags.get("mode"), envMode: process.env["TYPESHIP_MODE"], stdoutIsTTY: process.stdout.isTTY === true, stdinIsTTY: process.stdin.isTTY === true });
|
|
2328
|
-
const caller = harness || explicitAgent ? "
|
|
2397
|
+
const caller = harness || explicitAgent ? "agent" : behaving ? "non-interactive" : null;
|
|
2398
|
+
const details = [harness ? "harness=" + harness : null, caller].filter(Boolean).join("; ");
|
|
2329
2399
|
options.defaultHeaders = {
|
|
2330
2400
|
...(options.defaultHeaders as Record<string, string> | undefined),
|
|
2331
|
-
"User-Agent": PKG_NAME + "-cli/" + VERSION +
|
|
2401
|
+
"User-Agent": PKG_NAME + "-cli/" + VERSION + (details ? " (" + details + ")" : ""),
|
|
2332
2402
|
};
|
|
2333
2403
|
if (forIdentity) { options.fetch = identityFetch(baseUrl); options.maxRetries = 0; options.timeoutMs = 10_000; }
|
|
2334
2404
|
return new TypeshipClient(options);
|
|
@@ -2368,7 +2438,7 @@ function failOmitted(op: OmittedOpSpec): never {
|
|
|
2368
2438
|
return failWith({
|
|
2369
2439
|
status: "action_required",
|
|
2370
2440
|
code: "PLAN_LIMIT",
|
|
2371
|
-
message: "The command '" + BIN + " " + op.command.join(" ") + "' exists in the API
|
|
2441
|
+
message: "The command '" + BIN + " " + op.command.join(" ") + "' exists in the API Spec but was omitted from this generated package by its plan limit.",
|
|
2372
2442
|
detail: { operation: op.tool, method: op.httpMethod, path: op.path, generated_operations: OPS.length, total_operations: OPS.length + EXCLUDED_OPS },
|
|
2373
2443
|
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."],
|
|
2374
2444
|
});
|
|
@@ -2393,8 +2463,8 @@ async function main(): Promise<void> {
|
|
|
2393
2463
|
fail(2, "--format json is the only format; output is always JSON.");
|
|
2394
2464
|
}
|
|
2395
2465
|
if (parsed.flags.has("version") || parsed.positionals[0] === "version") {
|
|
2396
|
-
// "
|
|
2397
|
-
//
|
|
2466
|
+
// "acme 1.0.0 (acme 1.0.0)" would say the name twice; when the API's
|
|
2467
|
+
// title is the bin, name the API version as such.
|
|
2398
2468
|
const apiLabel = API_TITLE.toLowerCase().replace(/[^a-z0-9]/g, "") === BIN.toLowerCase().replace(/[^a-z0-9]/g, "") ? "API " + API_VERSION : API_TITLE + " " + API_VERSION;
|
|
2399
2469
|
process.stdout.write(BIN + " " + VERSION + " (" + apiLabel + ", generated by typeship)\n");
|
|
2400
2470
|
await flushExit(0);
|
|
@@ -2415,6 +2485,7 @@ async function main(): Promise<void> {
|
|
|
2415
2485
|
if (resourceCmd === "upgrade") { await cmdUpgrade(parsed); }
|
|
2416
2486
|
|
|
2417
2487
|
|
|
2488
|
+
|
|
2418
2489
|
if (resourceCmd === "docs") { await cmdDocs(parsed); }
|
|
2419
2490
|
if (resourceCmd === "completion") { await cmdCompletion(parsed); }
|
|
2420
2491
|
if (resourceCmd === "config") { await cmdConfig(parsed); }
|
|
@@ -2532,28 +2603,15 @@ async function main(): Promise<void> {
|
|
|
2532
2603
|
}
|
|
2533
2604
|
|
|
2534
2605
|
// --out <dir> materializes a file-shaped response (see cli-agent.ts bundleProperty).
|
|
2535
|
-
const bundleField = bundleProperty(op.outputSchema);
|
|
2606
|
+
const bundleField = op.fileBundleProperty ?? bundleProperty(op.outputSchema);
|
|
2536
2607
|
const collectionField = collectionProperty(op.outputSchema);
|
|
2537
2608
|
const outDir = typeof parsed.flags.get("out") === "string" ? (parsed.flags.get("out") as string) : undefined;
|
|
2538
2609
|
if (outDir !== undefined && bundleField === null) {
|
|
2539
2610
|
fail(2, "--out applies to commands whose response carries files ({path, content}); " + op.command.join(" ") + " does not.");
|
|
2540
2611
|
}
|
|
2541
2612
|
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
if (op.auth === "required" && (AUTH_SCALARS.length > 0 || BASIC || HAS_OAUTH_LOGIN) && credentialSource(parsed.flags) === null) {
|
|
2545
|
-
failWith({
|
|
2546
|
-
status: "action_required",
|
|
2547
|
-
code: "NO_AUTH",
|
|
2548
|
-
message: op.command.join(" ") + " needs a credential (" + wireOf(op) + " is authenticated) and none was found.",
|
|
2549
|
-
nextSteps: [
|
|
2550
|
-
...AUTH_SCALARS.map((a) => "Set " + a.env + " in the environment, pass --" + a.flag + " <value>, or run '" + BIN + " login'."),
|
|
2551
|
-
...(BASIC ? ["Set " + BASIC.envUser + " and " + BASIC.envPass + ", or pass --username and --password."] : []),
|
|
2552
|
-
...(AUTH_SCALARS.length === 0 && !BASIC ? ["Run '" + BIN + " login'."] : []),
|
|
2553
|
-
"'" + BIN + " auth check' shows what the CLI would send.",
|
|
2554
|
-
],
|
|
2555
|
-
});
|
|
2556
|
-
}
|
|
2613
|
+
validateParameters(op, values, parsed.flags);
|
|
2614
|
+
const client = await makeClient(parsed.flags, op);
|
|
2557
2615
|
|
|
2558
2616
|
// Destructive commands need --force. A person gets asked; an agent gets
|
|
2559
2617
|
// an action_required envelope with the exact command to run, so nothing
|
|
@@ -2573,7 +2631,6 @@ async function main(): Promise<void> {
|
|
|
2573
2631
|
if (answer !== "y" && answer !== "yes") failWith({ status: "action_required", code: "CONFIRMATION_REQUIRED", message: "Cancelled.", nextSteps: ["Run again with --force to skip the prompt: " + rerun] });
|
|
2574
2632
|
}
|
|
2575
2633
|
|
|
2576
|
-
const client = await makeClient(parsed.flags, op);
|
|
2577
2634
|
const selectValue = typeof parsed.flags.get("select") === "string" ? (parsed.flags.get("select") as string) : undefined;
|
|
2578
2635
|
const args = buildArgs(op, values, dataBody, selectValue);
|
|
2579
2636
|
const target = (client as unknown as Record<string, Record<string, (...a: unknown[]) => unknown>>)[op.resource]!;
|
|
@@ -2590,7 +2647,18 @@ async function main(): Promise<void> {
|
|
|
2590
2647
|
}
|
|
2591
2648
|
}
|
|
2592
2649
|
|
|
2593
|
-
|
|
2650
|
+
let result = await asApiResult(callResult as Promise<unknown>);
|
|
2651
|
+
if (result.ok && op.httpMethod === "POST" && op.path === "/projects/{project_id}/generate") {
|
|
2652
|
+
const batch = result.data as { data: Array<{ id: string }> };
|
|
2653
|
+
const generations = (client as unknown as { generations: { wait(id: string): Promise<unknown> } }).generations;
|
|
2654
|
+
const completed: unknown[] = [];
|
|
2655
|
+
for (const generation of batch.data) {
|
|
2656
|
+
const waited = await asApiResult(generations.wait(generation.id));
|
|
2657
|
+
if (!waited.ok) failApi(waited.error, LAST_CLIENT_HAD_CREDENTIAL);
|
|
2658
|
+
completed.push(waited.data);
|
|
2659
|
+
}
|
|
2660
|
+
result = { ...result, data: { ...batch, data: completed } };
|
|
2661
|
+
}
|
|
2594
2662
|
if (result.ok) {
|
|
2595
2663
|
if (op.sse) {
|
|
2596
2664
|
// Server-sent events as NDJSON, one line per event, until the stream ends.
|