@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/dist/cli.js
CHANGED
|
@@ -11,24 +11,29 @@ import { homedir, hostname } from "node:os";
|
|
|
11
11
|
import { basename, join } from "node:path";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
import { TypeshipClient, formatDebugEvent } from "./index.js";
|
|
14
|
-
import { GLOBALS, OPS, buildArgs, findOp, missingRequired } from "./ops.js";
|
|
14
|
+
import { GLOBALS, OMITTED_OPS, OPS, buildArgs, findOp, missingRequired } from "./ops.js";
|
|
15
15
|
import { MCP_CLIENTS, agentGuide, agentBlock, agentInstructionsFile, agentMode, bundleProperty, claimProperty, classifyApiError, collectionProperty, detectHarness, envelope, exitCodeFor, findMcpClient, installSkills, mcpConfigured, pendingClaims, recordClaim, summarizeDoctor, upsertAgentBlock, writeBundle, writeMcpConfig, } from "./cli-agent.js";
|
|
16
16
|
import { relativeDate } from "./dates.js";
|
|
17
|
+
import { fetchDocsText, resolveDocsContentUrl } from "./docs.js";
|
|
17
18
|
const BIN = "typeship";
|
|
18
19
|
const DEFAULT_BASE_URL = "https://typeship.dev/api/v1";
|
|
19
20
|
const AUTH_SCALARS = [{ "option": "bearerToken", "flag": "token", "env": "TYPESHIP_TOKEN" }];
|
|
20
21
|
const BASIC = null;
|
|
22
|
+
/** Operations omitted from the generated package by its plan cap. */
|
|
21
23
|
const EXCLUDED_OPS = 0;
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
+
/** Generated CLI operations that are intentionally unavailable to MCP. */
|
|
25
|
+
const MCP_EXCLUDED_OPS = 0;
|
|
26
|
+
const VERSION = "0.8.0";
|
|
27
|
+
const API_VERSION = "1.0.0";
|
|
24
28
|
const SPEC_FORMAT = "openapi";
|
|
25
29
|
const WHOAMI = { "resource": "account", "method": "retrieve" };
|
|
26
30
|
const ENVIRONMENTS = {};
|
|
27
31
|
const HAS_MCP = false;
|
|
28
32
|
const PKG_NAME = "@typeship-ax/cli";
|
|
29
33
|
const UPDATE_NOTICE = false;
|
|
30
|
-
const API_DESCRIPTION = "
|
|
34
|
+
const API_DESCRIPTION = "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";
|
|
31
35
|
const DOCS_URL_DEFAULT = "https://typeship.dev";
|
|
36
|
+
const DOCS_INDEX_URL_DEFAULT = null;
|
|
32
37
|
const RELAY = null;
|
|
33
38
|
const SUPPORT_URL = null;
|
|
34
39
|
const OAUTH_TOKEN_URL = null;
|
|
@@ -271,7 +276,7 @@ function humanError(body, code) {
|
|
|
271
276
|
const firstWords = (issue?.message ?? "").slice(0, 40);
|
|
272
277
|
if (compact && compact !== "{}" && !(firstWords && compact.includes(firstWords)))
|
|
273
278
|
lines.push(" " + paintErr("dim", "API said: " + (compact.length > 300 ? compact.slice(0, 297) + "…" : compact)));
|
|
274
|
-
const requestId = detail.body?.request_id ?? detail.body?.requestId;
|
|
279
|
+
const requestId = detail.request_id ?? detail.body?.request_id ?? detail.body?.requestId;
|
|
275
280
|
if (typeof requestId === "string")
|
|
276
281
|
lines.push(" " + paintErr("dim", "request id: " + requestId));
|
|
277
282
|
}
|
|
@@ -290,7 +295,7 @@ function fail(code, message, extra, nextSteps) {
|
|
|
290
295
|
: /^(Missing required|Expected \d+ argument)/.test(message) ? "MISSING_ARGUMENT"
|
|
291
296
|
: "INVALID_USAGE";
|
|
292
297
|
return failWith({
|
|
293
|
-
code: code === 2 ? usageCode : "
|
|
298
|
+
code: code === 2 ? usageCode : "CALL_FAILED",
|
|
294
299
|
message,
|
|
295
300
|
...(extra !== undefined ? { detail: extra } : {}),
|
|
296
301
|
nextSteps: nextSteps ?? (code === 2 ? ["Run '" + USAGE_HINT + "' for the flags this command takes."] : []),
|
|
@@ -547,7 +552,7 @@ async function browserApprove(headless, flags) {
|
|
|
547
552
|
});
|
|
548
553
|
start = await response.json();
|
|
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
|
}
|
|
553
558
|
catch (e) {
|
|
@@ -588,7 +593,7 @@ async function browserApprove(headless, flags) {
|
|
|
588
593
|
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 + "."] });
|
|
589
594
|
if (poll.status === "expired")
|
|
590
595
|
break;
|
|
591
|
-
failWith({ code: "
|
|
596
|
+
failWith({ code: "CALL_FAILED", message: "Browser login stopped: " + (poll.status ?? "unknown status"), nextSteps: ["Run '" + BIN + " login' again."] });
|
|
592
597
|
}
|
|
593
598
|
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>'."] });
|
|
594
599
|
}
|
|
@@ -848,15 +853,6 @@ function mcpServerPath() {
|
|
|
848
853
|
}
|
|
849
854
|
return { path, warning };
|
|
850
855
|
}
|
|
851
|
-
function claudeDesktopConfigPath() {
|
|
852
|
-
if (process.platform === "darwin") {
|
|
853
|
-
return join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
854
|
-
}
|
|
855
|
-
if (process.platform === "win32") {
|
|
856
|
-
return join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
857
|
-
}
|
|
858
|
-
return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "Claude", "claude_desktop_config.json");
|
|
859
|
-
}
|
|
860
856
|
/** The mcp entry for a client: the hosted endpoint when the API has one
|
|
861
857
|
* (auth env var as a reference, never a literal), else the local stdio
|
|
862
858
|
* server. --url overrides. */
|
|
@@ -900,11 +896,20 @@ async function cmdMcp(parsed) {
|
|
|
900
896
|
" " + BIN + " mcp install --claude --read-only register a read-only server (writes are not callable)",
|
|
901
897
|
"",
|
|
902
898
|
(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."),
|
|
903
|
-
"
|
|
899
|
+
"--all skips Cursor until it speaks MCP 2026-07-28.",
|
|
904
900
|
];
|
|
905
901
|
process.stdout.write(lines.join("\n") + "\n");
|
|
906
902
|
await flushExit(0);
|
|
907
903
|
}
|
|
904
|
+
const sub = parsed.positionals[1];
|
|
905
|
+
if (sub !== undefined && sub !== "install") {
|
|
906
|
+
fail(2, "Unknown mcp command: " + sub + ". Try '" + BIN + " mcp install --all'.");
|
|
907
|
+
}
|
|
908
|
+
const requestedInstall = parsed.flags.get("all") === true
|
|
909
|
+
|| Object.keys(MCP_CLIENT_FLAGS).some((flag) => parsed.flags.get(flag) === true);
|
|
910
|
+
if (requestedInstall && sub !== "install") {
|
|
911
|
+
fail(2, "Installing an MCP entry requires '" + BIN + " mcp install'.");
|
|
912
|
+
}
|
|
908
913
|
const url = typeof parsed.flags.get("url") === "string" ? parsed.flags.get("url") : undefined;
|
|
909
914
|
if (url) {
|
|
910
915
|
try {
|
|
@@ -970,6 +975,9 @@ function agentContext() {
|
|
|
970
975
|
envPrefix: ENV_PREFIX,
|
|
971
976
|
authEnvVars: [...AUTH_SCALARS.map((a) => a.env), ...(BASIC ? [BASIC.envUser, BASIC.envPass] : [])],
|
|
972
977
|
docsUrl: docsSiteUrl(),
|
|
978
|
+
docsIndexUrl: docsIndexUrl(),
|
|
979
|
+
generatedOperationCount: OPS.length,
|
|
980
|
+
omittedOperations: OMITTED_OPS.map((op) => ({ command: op.command.join(" "), tool: op.tool, method: op.httpMethod, path: op.path })),
|
|
973
981
|
mcpUrl: MCP_URL,
|
|
974
982
|
skillsRepo: SKILLS_REPO,
|
|
975
983
|
hasMcp: HAS_MCP,
|
|
@@ -1028,6 +1036,14 @@ function helpJson() {
|
|
|
1028
1036
|
};
|
|
1029
1037
|
}),
|
|
1030
1038
|
})),
|
|
1039
|
+
...(EXCLUDED_OPS > 0 ? {
|
|
1040
|
+
coverage: {
|
|
1041
|
+
generated_operations: OPS.length,
|
|
1042
|
+
total_operations: OPS.length + EXCLUDED_OPS,
|
|
1043
|
+
omitted_operations: OMITTED_OPS.map((op) => ({ command: op.command.join(" "), tool: op.tool, method: op.httpMethod, path: op.path })),
|
|
1044
|
+
reason: "plan_limit",
|
|
1045
|
+
},
|
|
1046
|
+
} : {}),
|
|
1031
1047
|
discovery: {
|
|
1032
1048
|
search: BIN + " docs search <term> --json",
|
|
1033
1049
|
operation: BIN + " docs <resource> <command> --json",
|
|
@@ -1315,7 +1331,7 @@ async function cmdUpgrade(parsed) {
|
|
|
1315
1331
|
fail(1, "This run came through npx, which fetches the latest version each time; there is nothing to upgrade in place.");
|
|
1316
1332
|
if (manager !== "npm") {
|
|
1317
1333
|
const command = manager === "pnpm" ? "pnpm add -g " + PKG_NAME + "@" + latest : manager === "bun" ? "bun add -g " + PKG_NAME + "@" + latest : "yarn global add " + PKG_NAME + "@" + latest;
|
|
1318
|
-
failWith({ status: "action_required", code: "
|
|
1334
|
+
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] });
|
|
1319
1335
|
}
|
|
1320
1336
|
process.stderr.write("Upgrading " + PKG_NAME + " " + VERSION + " -> " + latest + "\n");
|
|
1321
1337
|
// Windows resolves npm to npm.cmd, which only a shell can start.
|
|
@@ -1329,7 +1345,7 @@ async function cmdUpgrade(parsed) {
|
|
|
1329
1345
|
// ---------------------------------------------------------------------------
|
|
1330
1346
|
// completion — shell completion scripts built from the op table
|
|
1331
1347
|
// ---------------------------------------------------------------------------
|
|
1332
|
-
const TOP_WORDS = ["login", "logout", "whoami", "config", "mcp", "
|
|
1348
|
+
const TOP_WORDS = ["login", "logout", "whoami", "config", "mcp", "docs", "upgrade", "completion", "help", "version", "init", "agent-guide", "auth", "doctor"];
|
|
1333
1349
|
/** Every flag an API command accepts, with the values a flag completes to (enums, on|off|auto, agent|human). */
|
|
1334
1350
|
function completionFlagsFor(op) {
|
|
1335
1351
|
const flags = [...op.params.filter((p) => p.kind !== "path").map((p) => "--" + p.flag), ...COMPLETION_GLOBAL_FLAGS];
|
|
@@ -1349,7 +1365,6 @@ const BUILTIN_WORDS = {
|
|
|
1349
1365
|
completion: ["bash", "zsh", "fish"],
|
|
1350
1366
|
auth: ["check"],
|
|
1351
1367
|
mcp: ["install"],
|
|
1352
|
-
webhooks: ["listen", "fake"],
|
|
1353
1368
|
};
|
|
1354
1369
|
/** bash (and zsh via bashcompinit): resources, commands, flags, and the values a flag takes. */
|
|
1355
1370
|
function completionScript() {
|
|
@@ -1446,16 +1461,18 @@ async function cmdCompletion(parsed) {
|
|
|
1446
1461
|
function docsSiteUrl() {
|
|
1447
1462
|
return readConfig().docsUrl ?? DOCS_URL_DEFAULT;
|
|
1448
1463
|
}
|
|
1464
|
+
function docsIndexUrl() {
|
|
1465
|
+
const configured = readConfig().docsUrl;
|
|
1466
|
+
return configured
|
|
1467
|
+
? resolveDocsContentUrl(configured, null, "llms.txt")
|
|
1468
|
+
: DOCS_INDEX_URL_DEFAULT ?? resolveDocsContentUrl(DOCS_URL_DEFAULT, null, "llms.txt");
|
|
1469
|
+
}
|
|
1449
1470
|
/** Fetch llms.txt / llms-full.txt / a prose page from the docs site,
|
|
1450
1471
|
* with a 1h cache in the config dir. Explicit command = explicit fetch;
|
|
1451
1472
|
* nothing here runs unless the user asked for docs. */
|
|
1452
1473
|
async function fetchDocs(pathOrFile) {
|
|
1453
1474
|
const base = docsSiteUrl();
|
|
1454
|
-
const url =
|
|
1455
|
-
? pathOrFile
|
|
1456
|
-
: base === null
|
|
1457
|
-
? null
|
|
1458
|
-
: base.replace(/\/+$/, "") + "/" + pathOrFile.replace(/^\/+/, "");
|
|
1475
|
+
const url = resolveDocsContentUrl(base, docsIndexUrl(), pathOrFile);
|
|
1459
1476
|
if (url === null)
|
|
1460
1477
|
return null;
|
|
1461
1478
|
const cacheFile = join(configDir(), "docs-cache", url.replace(/[^a-zA-Z0-9.]+/g, "_").slice(-120));
|
|
@@ -1466,13 +1483,9 @@ async function fetchDocs(pathOrFile) {
|
|
|
1466
1483
|
}
|
|
1467
1484
|
catch { /* not cached */ }
|
|
1468
1485
|
try {
|
|
1469
|
-
const
|
|
1470
|
-
|
|
1471
|
-
signal: AbortSignal.timeout(10_000),
|
|
1472
|
-
});
|
|
1473
|
-
if (!response.ok)
|
|
1486
|
+
const text = await fetchDocsText(url);
|
|
1487
|
+
if (text === null)
|
|
1474
1488
|
return null;
|
|
1475
|
-
const text = await response.text();
|
|
1476
1489
|
mkdirSync(join(configDir(), "docs-cache"), { recursive: true, mode: 0o700 });
|
|
1477
1490
|
writeFileSync(cacheFile, text);
|
|
1478
1491
|
return text;
|
|
@@ -1708,6 +1721,9 @@ async function cmdDocs(parsed) {
|
|
|
1708
1721
|
if (sub !== undefined) {
|
|
1709
1722
|
const op = findOp(sub, parsed.positionals[2] ?? "");
|
|
1710
1723
|
if (!op) {
|
|
1724
|
+
const omitted = omittedCommand(sub, parsed.positionals[2]);
|
|
1725
|
+
if (omitted)
|
|
1726
|
+
failOmitted(omitted);
|
|
1711
1727
|
const list = OPS.filter((o) => o.command[0] === sub);
|
|
1712
1728
|
if (list.length === 0)
|
|
1713
1729
|
fail(2, "Unknown docs topic: " + sub + ". Run '" + BIN + " docs' for the overview.");
|
|
@@ -1759,182 +1775,9 @@ async function cmdDocs(parsed) {
|
|
|
1759
1775
|
process.stdout.write(lines.join("\n") + "\n");
|
|
1760
1776
|
await flushExit(0);
|
|
1761
1777
|
}
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
for (const key of ["type", "event", "event_type", "eventType", "name"]) {
|
|
1766
|
-
if (typeof parsed[key] === "string")
|
|
1767
|
-
return parsed[key];
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
1770
|
-
catch { /* not JSON */ }
|
|
1771
|
-
return null;
|
|
1772
|
-
}
|
|
1773
|
-
async function cmdWebhooksFake(parsed) {
|
|
1774
|
-
void parsed;
|
|
1775
|
-
fail(2, "This API's spec declares no webhooks, so there is nothing to fake.");
|
|
1776
|
-
}
|
|
1777
|
-
async function cmdWebhooks(parsed) {
|
|
1778
|
-
const sub = parsed.positionals[1];
|
|
1779
|
-
if (parsed.help || sub === undefined) {
|
|
1780
|
-
const lines = [
|
|
1781
|
-
BIN + " webhooks — work with this API's webhook events",
|
|
1782
|
-
"",
|
|
1783
|
-
" " + BIN + " webhooks listen --forward-to localhost:3000/webhooks",
|
|
1784
|
-
" " + BIN + " webhooks listen --forward-to localhost:3000/hooks --events account.created,account.updated",
|
|
1785
|
-
" " + BIN + " webhooks fake [<event>] [--key whsec_...] [--forward-to <url>]",
|
|
1786
|
-
"",
|
|
1787
|
-
"listen mints a private relay URL to point the API's webhook settings",
|
|
1788
|
-
"at; events replay locally with their original headers, so signature",
|
|
1789
|
-
"verification keeps working. No tunnels, no exposed ports.",
|
|
1790
|
-
"fake sends (or prints) a signed sample event for local handler testing.",
|
|
1791
|
-
];
|
|
1792
|
-
process.stdout.write(lines.join("\n") + "\n");
|
|
1793
|
-
await flushExit(parsed.help ? 0 : 2);
|
|
1794
|
-
}
|
|
1795
|
-
if (sub === "fake") {
|
|
1796
|
-
await cmdWebhooksFake(parsed);
|
|
1797
|
-
}
|
|
1798
|
-
if (sub !== "listen")
|
|
1799
|
-
fail(2, "Unknown webhooks command: " + String(sub) + ". Try '" + BIN + " webhooks listen' or '" + BIN + " webhooks fake'.");
|
|
1800
|
-
if (!RELAY) {
|
|
1801
|
-
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.");
|
|
1802
|
-
}
|
|
1803
|
-
const forwardRaw = parsed.flags.get("forward-to");
|
|
1804
|
-
if (typeof forwardRaw !== "string") {
|
|
1805
|
-
fail(2, "webhooks listen requires --forward-to <url>, e.g. --forward-to localhost:3000/webhooks");
|
|
1806
|
-
}
|
|
1807
|
-
const forwardTo = /^https?:\/\//.test(forwardRaw) ? forwardRaw : "http://" + forwardRaw;
|
|
1808
|
-
const eventsRaw = parsed.flags.get("events");
|
|
1809
|
-
const eventsFilter = typeof eventsRaw === "string"
|
|
1810
|
-
? eventsRaw.split(",").map((s) => s.trim()).filter(Boolean)
|
|
1811
|
-
: null;
|
|
1812
|
-
let minted = null;
|
|
1813
|
-
try {
|
|
1814
|
-
const response = await fetch(RELAY.mintUrl, {
|
|
1815
|
-
method: "POST",
|
|
1816
|
-
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
1817
|
-
body: JSON.stringify({ project: RELAY.project }),
|
|
1818
|
-
signal: AbortSignal.timeout(15_000),
|
|
1819
|
-
});
|
|
1820
|
-
minted = await response.json().catch(() => null);
|
|
1821
|
-
if (!response.ok || typeof minted?.ingest_url !== "string" || typeof minted.poll_url !== "string") {
|
|
1822
|
-
const detail = minted?.errors?.[0]?.message ?? "HTTP " + response.status;
|
|
1823
|
-
fail(1, "Couldn't start a relay session: " + detail);
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
catch (e) {
|
|
1827
|
-
if (e instanceof ExitPending)
|
|
1828
|
-
throw e;
|
|
1829
|
-
fail(1, "Couldn't reach the relay: " + e.message);
|
|
1830
|
-
}
|
|
1831
|
-
process.stderr.write(paintErr("green", "Ready!") + " Forwarding relayed events to " + forwardTo + "\n");
|
|
1832
|
-
process.stderr.write("Point this API's webhook endpoint at: " + paintErr("cyan", minted.ingest_url) + " (^C to quit)\n");
|
|
1833
|
-
let cursor = 0;
|
|
1834
|
-
let pollFailures = 0;
|
|
1835
|
-
for (;;) {
|
|
1836
|
-
let payload;
|
|
1837
|
-
try {
|
|
1838
|
-
const response = await fetch(minted.poll_url + "?after=" + cursor + "&wait=1", {
|
|
1839
|
-
headers: { Accept: "application/json" },
|
|
1840
|
-
signal: AbortSignal.timeout(30_000),
|
|
1841
|
-
});
|
|
1842
|
-
if (response.status === 404)
|
|
1843
|
-
fail(1, "The relay session expired. Run '" + BIN + " webhooks listen' again.");
|
|
1844
|
-
if (!response.ok)
|
|
1845
|
-
throw new Error("HTTP " + response.status);
|
|
1846
|
-
payload = await response.json();
|
|
1847
|
-
pollFailures = 0;
|
|
1848
|
-
}
|
|
1849
|
-
catch (e) {
|
|
1850
|
-
if (e instanceof ExitPending)
|
|
1851
|
-
throw e;
|
|
1852
|
-
pollFailures++;
|
|
1853
|
-
if (pollFailures > 5)
|
|
1854
|
-
fail(1, "Lost the relay: " + e.message);
|
|
1855
|
-
await new Promise((resolve) => setTimeout(resolve, 2000 * pollFailures));
|
|
1856
|
-
continue;
|
|
1857
|
-
}
|
|
1858
|
-
for (const event of payload.events) {
|
|
1859
|
-
cursor = event.seq;
|
|
1860
|
-
const type = eventTypeOf(event.body);
|
|
1861
|
-
if (eventsFilter !== null && (type === null || !eventsFilter.includes(type)))
|
|
1862
|
-
continue;
|
|
1863
|
-
const started = Date.now();
|
|
1864
|
-
try {
|
|
1865
|
-
const forwarded = await fetch(forwardTo, {
|
|
1866
|
-
method: event.method,
|
|
1867
|
-
headers: event.headers,
|
|
1868
|
-
body: event.method === "GET" ? undefined : event.body,
|
|
1869
|
-
signal: AbortSignal.timeout(30_000),
|
|
1870
|
-
});
|
|
1871
|
-
process.stderr.write((forwarded.ok ? paintErr("green", String(forwarded.status)) : paintErr("yellow", String(forwarded.status))) +
|
|
1872
|
-
" " + (type ?? event.method) + " [" + event.seq + "] (" + (Date.now() - started) + "ms)\n");
|
|
1873
|
-
}
|
|
1874
|
-
catch (e) {
|
|
1875
|
-
process.stderr.write(paintErr("yellow", "unreachable") + " " + (type ?? event.method) + " [" + event.seq + "]: " + e.message + "\n");
|
|
1876
|
-
}
|
|
1877
|
-
}
|
|
1878
|
-
if (payload.next > cursor)
|
|
1879
|
-
cursor = payload.next;
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
async function cmdFeedback(parsed) {
|
|
1883
|
-
if (parsed.help) {
|
|
1884
|
-
process.stdout.write(BIN + " feedback — open the API provider's issue tracker with environment details prefilled\n");
|
|
1885
|
-
await flushExit(0);
|
|
1886
|
-
}
|
|
1887
|
-
if (!SUPPORT_URL) {
|
|
1888
|
-
fail(2, "No support URL is configured for this CLI. The API provider can set one in their typeship console.");
|
|
1889
|
-
}
|
|
1890
|
-
let target = SUPPORT_URL;
|
|
1891
|
-
if (/github\.com\/[^/]+\/[^/]+\/issues\/new/.test(target)) {
|
|
1892
|
-
const bodyLines = [
|
|
1893
|
-
"<!-- describe the problem or request above the line -->",
|
|
1894
|
-
"",
|
|
1895
|
-
"---",
|
|
1896
|
-
"- cli: " + BIN + " " + VERSION + " (api " + API_VERSION + ")",
|
|
1897
|
-
"- node: " + process.version,
|
|
1898
|
-
"- platform: " + process.platform + "/" + process.arch,
|
|
1899
|
-
];
|
|
1900
|
-
const sep = target.includes("?") ? "&" : "?";
|
|
1901
|
-
target += sep + "title=" + encodeURIComponent("[" + BIN + "] ") + "&body=" + encodeURIComponent(bodyLines.join("\n"));
|
|
1902
|
-
}
|
|
1903
|
-
if (!nonInteractive(parsed))
|
|
1904
|
-
process.stderr.write("Opening " + paintErr("cyan", SUPPORT_URL) + "\n");
|
|
1905
|
-
const opened = openInBrowser(target, parsed);
|
|
1906
|
-
if (opened.opened)
|
|
1907
|
-
out({ ok: true, opened: SUPPORT_URL });
|
|
1908
|
-
await flushExit(0);
|
|
1909
|
-
}
|
|
1910
|
-
/** Opt-in (console setting) once-a-day upgrade hint. Off by default:
|
|
1911
|
-
* generated code phones nobody unless the project owner chose this. The
|
|
1912
|
-
* notice prints the previous run's cached answer; at most once a day it
|
|
1913
|
-
* refreshes the cache first, with a 1.5s cap so a slow registry can't
|
|
1914
|
-
* hold a command hostage. */
|
|
1915
|
-
async function maybeUpdateNotice(commandWord, quiet) {
|
|
1916
|
-
if (!UPDATE_NOTICE || quiet)
|
|
1917
|
-
return;
|
|
1918
|
-
if (commandWord === undefined || ["upgrade", "version", "help", "login", "logout"].includes(commandWord))
|
|
1919
|
-
return;
|
|
1920
|
-
const cacheFile = join(configDir(), "update-check.json");
|
|
1921
|
-
let cache = {};
|
|
1922
|
-
try {
|
|
1923
|
-
cache = JSON.parse(readFileSync(cacheFile, "utf8"));
|
|
1924
|
-
}
|
|
1925
|
-
catch { /* no cache yet */ }
|
|
1926
|
-
if ((cache.checkedAt ?? 0) < Date.now() - 24 * 60 * 60 * 1000) {
|
|
1927
|
-
const latest = await latestVersion(1500);
|
|
1928
|
-
if (latest !== null) {
|
|
1929
|
-
cache = { checkedAt: Date.now(), latest };
|
|
1930
|
-
mkdirSync(configDir(), { recursive: true, mode: 0o700 });
|
|
1931
|
-
writeFileSync(cacheFile, JSON.stringify(cache) + "\n");
|
|
1932
|
-
}
|
|
1933
|
-
}
|
|
1934
|
-
if (cache.latest !== undefined && semverLess(VERSION, cache.latest)) {
|
|
1935
|
-
process.stderr.write(paintErr("yellow", "A newer " + BIN + " is available: " + VERSION + " -> " + cache.latest + ". Run '" + BIN + " upgrade'.") + "\n");
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1778
|
+
// ---------------------------------------------------------------------------
|
|
1779
|
+
// webhooks listen — forward relayed events to a local handler
|
|
1780
|
+
// ---------------------------------------------------------------------------
|
|
1938
1781
|
/** The command that fetches the next page: same positionals, the next page's query flags. */
|
|
1939
1782
|
function nextCommandFor(op, pathValues, next) {
|
|
1940
1783
|
const parts = [BIN, op.command[0], op.command[1], ...pathValues.map(shellQuote)];
|
|
@@ -2083,7 +1926,7 @@ function printRoot(stream = process.stdout) {
|
|
|
2083
1926
|
}
|
|
2084
1927
|
const width = termWidth();
|
|
2085
1928
|
const lines = [];
|
|
2086
|
-
lines.push(paintOut("bold", BIN) + ": " + "typeship" + " (v" + "0.
|
|
1929
|
+
lines.push(paintOut("bold", BIN) + ": " + "typeship API" + " (v" + "1.0.0" + "), package " + "0.8.0");
|
|
2087
1930
|
lines.push("");
|
|
2088
1931
|
lines.push(paintOut("bold", "Usage:") + " " + BIN + " <resource> <command> [args] [--flags]");
|
|
2089
1932
|
lines.push("");
|
|
@@ -2101,6 +1944,12 @@ function printRoot(stream = process.stdout) {
|
|
|
2101
1944
|
for (const more of wrapped.slice(1))
|
|
2102
1945
|
lines.push(" ".repeat(2 + col) + more);
|
|
2103
1946
|
}
|
|
1947
|
+
if (EXCLUDED_OPS > 0) {
|
|
1948
|
+
lines.push("");
|
|
1949
|
+
lines.push(...labeled(paintOut("yellow", "Plan limit:") + " ", "generated " + OPS.length + " of " + (OPS.length + EXCLUDED_OPS) + " operations", width, 14));
|
|
1950
|
+
lines.push(...labeled("Omitted: ", OMITTED_OPS.map((op) => op.command.join(" ") + " (" + op.httpMethod + " " + op.path + ")").join(", "), width, 14));
|
|
1951
|
+
lines.push(...labeled("Upgrade: ", "https://typeship.dev/pricing, then regenerate without the operation cap", width, 14));
|
|
1952
|
+
}
|
|
2104
1953
|
lines.push("");
|
|
2105
1954
|
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)" +
|
|
2106
1955
|
(AUTH_SCALARS.length > 0 ? ", " + AUTH_SCALARS.map((a) => "--" + a.flag + " <value>").join(", ") : "");
|
|
@@ -2118,9 +1967,9 @@ function printRoot(stream = process.stdout) {
|
|
|
2118
1967
|
lines.push("Webhooks: " + BIN + " webhooks listen --forward-to <url> (local event forwarding)");
|
|
2119
1968
|
if (SUPPORT_URL)
|
|
2120
1969
|
lines.push("Feedback: " + BIN + " feedback (opens the provider's issue tracker)");
|
|
2121
|
-
if (
|
|
1970
|
+
if (MCP_EXCLUDED_OPS > 0 && HAS_MCP) {
|
|
2122
1971
|
lines.push("");
|
|
2123
|
-
lines.push("Note: " +
|
|
1972
|
+
lines.push("Note: " + MCP_EXCLUDED_OPS + " generated operation(s) with uploads or event streams are CLI/SDK-only, not MCP tools.");
|
|
2124
1973
|
}
|
|
2125
1974
|
lines.push("");
|
|
2126
1975
|
lines.push("Run '" + BIN + " <resource>' for a resource's commands, '" + BIN + " <resource> <command> --help' for flags.");
|
|
@@ -2406,11 +2255,10 @@ function resolveBaseUrl(flags) {
|
|
|
2406
2255
|
}
|
|
2407
2256
|
async function makeClient(flags) {
|
|
2408
2257
|
const stored = readCreds();
|
|
2409
|
-
const options = {};
|
|
2410
2258
|
const baseUrl = resolveBaseUrl(flags);
|
|
2411
2259
|
if (baseUrl === undefined)
|
|
2412
2260
|
fail(2, "No base URL. Pass --base-url, set TYPESHIP_BASE_URL, or run '" + BIN + " config set base-url <url>'.");
|
|
2413
|
-
options
|
|
2261
|
+
const options = { baseUrl };
|
|
2414
2262
|
for (const a of AUTH_SCALARS) {
|
|
2415
2263
|
const v = (typeof flags.get(a.flag) === "string" ? flags.get(a.flag) : undefined)
|
|
2416
2264
|
?? process.env[a.env] ?? stored?.scalars?.[a.option];
|
|
@@ -2483,7 +2331,20 @@ function didYouMean(input, candidates) {
|
|
|
2483
2331
|
}
|
|
2484
2332
|
return best;
|
|
2485
2333
|
}
|
|
2486
|
-
|
|
2334
|
+
function omittedCommand(resource, method) {
|
|
2335
|
+
return OMITTED_OPS.find((op) => op.command[0] === resource &&
|
|
2336
|
+
(method === undefined || op.command[1] === method || op.commandAlias === method));
|
|
2337
|
+
}
|
|
2338
|
+
function failOmitted(op) {
|
|
2339
|
+
return failWith({
|
|
2340
|
+
status: "action_required",
|
|
2341
|
+
code: "PLAN_LIMIT",
|
|
2342
|
+
message: "The command '" + BIN + " " + op.command.join(" ") + "' exists in the API Definition but was omitted from this generated package by its plan limit.",
|
|
2343
|
+
detail: { operation: op.tool, method: op.httpMethod, path: op.path, generated_operations: OPS.length, total_operations: OPS.length + EXCLUDED_OPS },
|
|
2344
|
+
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."],
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
const BUILTIN_COMMANDS = ["login", "logout", "whoami", "config", "mcp", "docs", "upgrade", "completion", "help", "version", "init", "agent-guide", "auth", "doctor"];
|
|
2487
2348
|
async function main() {
|
|
2488
2349
|
const argv = process.argv.slice(2);
|
|
2489
2350
|
const parsed = parseArgv(argv);
|
|
@@ -2511,9 +2372,8 @@ async function main() {
|
|
|
2511
2372
|
COLOR_ERR = colorEnabled(process.stderr, parsed);
|
|
2512
2373
|
// Prose errors for a person: stderr is a terminal (or --mode human says
|
|
2513
2374
|
// to behave as if), and nothing asked for JSON.
|
|
2514
|
-
const forcedHuman = parsed.flags.get("mode") === "human" ||
|
|
2375
|
+
const forcedHuman = parsed.flags.get("mode") === "human" || process.env["TYPESHIP_MODE"] === "human";
|
|
2515
2376
|
HUMAN_ERRORS = (process.stderr.isTTY === true || forcedHuman) && !isAgentMode(parsed) && parsed.flags.get("format") !== "json" && parsed.flags.get("json") !== true;
|
|
2516
|
-
await maybeUpdateNotice(resourceCmd, explicitNonInteractive(parsed));
|
|
2517
2377
|
if (resourceCmd === "init") {
|
|
2518
2378
|
await cmdInit(parsed);
|
|
2519
2379
|
}
|
|
@@ -2529,12 +2389,6 @@ async function main() {
|
|
|
2529
2389
|
if (resourceCmd === "upgrade") {
|
|
2530
2390
|
await cmdUpgrade(parsed);
|
|
2531
2391
|
}
|
|
2532
|
-
if (resourceCmd === "webhooks") {
|
|
2533
|
-
await cmdWebhooks(parsed);
|
|
2534
|
-
}
|
|
2535
|
-
if (resourceCmd === "feedback") {
|
|
2536
|
-
await cmdFeedback(parsed);
|
|
2537
|
-
}
|
|
2538
2392
|
if (resourceCmd === "docs") {
|
|
2539
2393
|
await cmdDocs(parsed);
|
|
2540
2394
|
}
|
|
@@ -2574,6 +2428,9 @@ async function main() {
|
|
|
2574
2428
|
}
|
|
2575
2429
|
const resourceExists = OPS.some((o) => o.command[0] === resourceCmd);
|
|
2576
2430
|
if (!resourceExists) {
|
|
2431
|
+
const omitted = omittedCommand(resourceCmd, methodCmd);
|
|
2432
|
+
if (omitted)
|
|
2433
|
+
failOmitted(omitted);
|
|
2577
2434
|
const suggestion = didYouMean(resourceCmd, [...new Set(OPS.map((o) => o.command[0])), ...BUILTIN_COMMANDS]);
|
|
2578
2435
|
fail(2, "Unknown command: " + resourceCmd + "." + (suggestion ? " Did you mean '" + BIN + " " + suggestion + "'?" : ""), undefined, ["Run '" + BIN + " --help' for the commands."]);
|
|
2579
2436
|
}
|
|
@@ -2583,6 +2440,9 @@ async function main() {
|
|
|
2583
2440
|
}
|
|
2584
2441
|
const op = findOp(resourceCmd, methodCmd);
|
|
2585
2442
|
if (!op) {
|
|
2443
|
+
const omitted = omittedCommand(resourceCmd, methodCmd);
|
|
2444
|
+
if (omitted)
|
|
2445
|
+
failOmitted(omitted);
|
|
2586
2446
|
const suggestion = didYouMean(methodCmd, OPS.filter((o) => o.command[0] === resourceCmd).map((o) => o.command[1]));
|
|
2587
2447
|
fail(2, "Unknown command: " + resourceCmd + " " + methodCmd + "." + (suggestion ? " Did you mean '" + BIN + " " + resourceCmd + " " + suggestion + "'?" : ""), undefined, ["Run '" + BIN + " " + resourceCmd + "' for its commands."]);
|
|
2588
2448
|
}
|
|
@@ -2758,6 +2618,7 @@ async function main() {
|
|
|
2758
2618
|
items: project(page.items),
|
|
2759
2619
|
hasMore: next !== null,
|
|
2760
2620
|
...(next !== null ? { nextPage: next, nextCommand: nextCommandFor(op, pathValues, next) } : {}),
|
|
2621
|
+
...(page.response.requestId ? { request_id: page.response.requestId } : {}),
|
|
2761
2622
|
});
|
|
2762
2623
|
}
|
|
2763
2624
|
else if (FIELDS !== null && collectionField !== null && result.data !== null && typeof result.data === "object" && !Array.isArray(result.data) && Array.isArray(result.data[collectionField])) {
|
|
@@ -2789,16 +2650,6 @@ function errorMessage(error) {
|
|
|
2789
2650
|
}
|
|
2790
2651
|
return base;
|
|
2791
2652
|
}
|
|
2792
|
-
function serializeError(error) {
|
|
2793
|
-
if (error && typeof error === "object" && "violations" in error) {
|
|
2794
|
-
return { violations: error.violations };
|
|
2795
|
-
}
|
|
2796
|
-
if (error && typeof error === "object" && "status" in error) {
|
|
2797
|
-
const e = error;
|
|
2798
|
-
return { status: e.status, body: e.body };
|
|
2799
|
-
}
|
|
2800
|
-
return undefined;
|
|
2801
|
-
}
|
|
2802
2653
|
main().catch((e) => {
|
|
2803
2654
|
if (e instanceof ExitPending)
|
|
2804
2655
|
return;
|