@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.
Files changed (78) hide show
  1. package/api.json +5597 -3010
  2. package/api.md +433 -54
  3. package/dist/cli-agent.d.ts +9 -1
  4. package/dist/cli-agent.d.ts.map +1 -1
  5. package/dist/cli-agent.js +26 -9
  6. package/dist/cli.js +83 -232
  7. package/dist/core/http.d.ts +6 -92
  8. package/dist/core/http.d.ts.map +1 -1
  9. package/dist/core/http.js +70 -209
  10. package/dist/core/pagination.d.ts.map +1 -1
  11. package/dist/core/pagination.js +6 -34
  12. package/dist/dates.d.ts +0 -2
  13. package/dist/dates.d.ts.map +1 -1
  14. package/dist/dates.js +0 -1
  15. package/dist/docs.d.ts +11 -0
  16. package/dist/docs.d.ts.map +1 -0
  17. package/dist/docs.js +114 -0
  18. package/dist/errors.d.ts +27 -27
  19. package/dist/errors.d.ts.map +1 -1
  20. package/dist/errors.js +7 -7
  21. package/dist/index.d.ts +19 -11
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +23 -13
  24. package/dist/ops.d.ts +5 -0
  25. package/dist/ops.d.ts.map +1 -1
  26. package/dist/ops.js +31 -17
  27. package/dist/resources/account.d.ts +2 -2
  28. package/dist/resources/account.d.ts.map +1 -1
  29. package/dist/resources/api-keys.d.ts +10 -5
  30. package/dist/resources/api-keys.d.ts.map +1 -1
  31. package/dist/resources/api-keys.js +3 -1
  32. package/dist/resources/definition-revisions.d.ts +58 -0
  33. package/dist/resources/definition-revisions.d.ts.map +1 -0
  34. package/dist/resources/definition-revisions.js +110 -0
  35. package/dist/resources/definitions.d.ts +24 -0
  36. package/dist/resources/definitions.d.ts.map +1 -0
  37. package/dist/resources/definitions.js +51 -0
  38. package/dist/resources/generate.d.ts +5 -5
  39. package/dist/resources/generate.d.ts.map +1 -1
  40. package/dist/resources/generate.js +3 -3
  41. package/dist/resources/generations.d.ts +3 -3
  42. package/dist/resources/generations.d.ts.map +1 -1
  43. package/dist/resources/generations.js +1 -1
  44. package/dist/resources/projects.d.ts +66 -26
  45. package/dist/resources/projects.d.ts.map +1 -1
  46. package/dist/resources/projects.js +87 -13
  47. package/dist/resources/targets.d.ts +86 -0
  48. package/dist/resources/targets.d.ts.map +1 -0
  49. package/dist/resources/targets.js +184 -0
  50. package/dist/schemas.d.ts.map +1 -1
  51. package/dist/schemas.js +119 -62
  52. package/dist/types.d.ts +1761 -222
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/types.js +9 -3
  55. package/package.json +1 -1
  56. package/src/cli-agent.ts +28 -11
  57. package/src/cli.ts +89 -233
  58. package/src/core/http.ts +75 -293
  59. package/src/core/pagination.ts +6 -30
  60. package/src/dates.ts +0 -1
  61. package/src/docs.ts +101 -0
  62. package/src/errors.ts +30 -30
  63. package/src/index.ts +23 -13
  64. package/src/ops.ts +43 -17
  65. package/src/resources/account.ts +3 -3
  66. package/src/resources/api-keys.ts +22 -7
  67. package/src/resources/definition-revisions.ts +198 -0
  68. package/src/resources/definitions.ts +97 -0
  69. package/src/resources/generate.ts +6 -6
  70. package/src/resources/generations.ts +4 -4
  71. package/src/resources/projects.ts +182 -37
  72. package/src/resources/targets.ts +346 -0
  73. package/src/schemas.ts +119 -62
  74. package/src/types.ts +1947 -281
  75. package/dist/resources/spec-revisions.d.ts +0 -47
  76. package/dist/resources/spec-revisions.d.ts.map +0 -1
  77. package/dist/resources/spec-revisions.js +0 -90
  78. 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
- const VERSION = "0.6.0";
23
- const API_VERSION = "0.6.0";
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 = "Generate production SDKs, CLIs, and MCP servers from an OpenAPI or\nGraphQL spec, and keep every selected output current.\n\nEvery operation but one requires an API key, created in the console and\nsent as `Authorization: Bearer ak_...`. A browser session is not a\ncredential for this API. The exception is POST /generate, which works\nanonymously with the free plan's limits.\n";
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 : "COMMAND_FAILED",
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: "COMMAND_FAILED", message: "Could not start the browser login: " + (start.error ?? "HTTP " + response.status), nextSteps: ["Pass the credential directly: '" + BIN + " login --" + first.flag + " <value>'."] });
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: "COMMAND_FAILED", message: "Browser login stopped: " + (poll.status ?? "unknown status"), nextSteps: ["Run '" + BIN + " login' again."] });
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
- "The old spelling '" + BIN + " mcp --claude' still works. --all skips Cursor until it speaks MCP 2026-07-28.",
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: "COMMAND_FAILED", message: PKG_NAME + " was installed with " + manager + ", so npm can't upgrade it in place.", nextSteps: ["Run: " + command] });
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", "upgrade", "docs", "webhooks", "feedback", "completion", "help", "version", "init", "agent-guide", "auth", "doctor"];
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 = /^https?:\/\//.test(pathOrFile)
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 response = await fetch(url, {
1470
- headers: { Accept: "text/markdown, text/plain, */*" },
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
- function eventTypeOf(body) {
1763
- try {
1764
- const parsed = JSON.parse(body);
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.6.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 (EXCLUDED_OPS > 0 && HAS_MCP) {
1970
+ if (MCP_EXCLUDED_OPS > 0 && HAS_MCP) {
2122
1971
  lines.push("");
2123
- lines.push("Note: " + EXCLUDED_OPS + " operation(s) with uploads or event streams are CLI/SDK-only, not MCP tools.");
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.baseUrl = baseUrl;
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
- const BUILTIN_COMMANDS = ["login", "logout", "whoami", "config", "mcp", "docs", "upgrade", "feedback", "completion", "webhooks", "help", "version", "init", "agent-guide", "auth", "doctor"];
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" || parsed.flags.get("mode") === "interactive" || process.env["TYPESHIP_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;