@runtypelabs/cli 2.59.0 → 2.59.2

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 (84) hide show
  1. package/dist/chunk-0-T9NWjH.js +3899 -0
  2. package/dist/chunk-0x7UKC1U.js +17 -0
  3. package/dist/chunk-2ySxXtcg.js +418 -0
  4. package/dist/chunk-3An_BRHv.js +501 -0
  5. package/dist/chunk-6uTxgdiY.js +16218 -0
  6. package/dist/chunk-87nRnNRG.js +1363 -0
  7. package/dist/chunk-B3wo8uRx.js +322 -0
  8. package/dist/chunk-BGp3ZdBy.js +560 -0
  9. package/dist/chunk-BJQv9Elp.js +107 -0
  10. package/dist/chunk-BOpO6xE-.js +6053 -0
  11. package/dist/chunk-BQXXCL8B.js +102 -0
  12. package/dist/chunk-BV-8isOd.js +34 -0
  13. package/dist/chunk-BVMhjGVe.js +826 -0
  14. package/dist/chunk-BW2fg46T.js +141 -0
  15. package/dist/chunk-BWJdY5Ag.js +36 -0
  16. package/dist/chunk-BYJOrLhh.js +302 -0
  17. package/dist/chunk-BYab8EVL.js +127 -0
  18. package/dist/chunk-BfEKvp7h.js +82 -0
  19. package/dist/chunk-BhgLSfH6.js +1123 -0
  20. package/dist/chunk-BirUxTtH.js +296 -0
  21. package/dist/chunk-BmB6UVZ9.js +1559 -0
  22. package/dist/chunk-BnWhIqW1.js +109 -0
  23. package/dist/chunk-Bpfnfk0t.js +123 -0
  24. package/dist/chunk-BrhgdnM3.js +108 -0
  25. package/dist/chunk-Bt0XAgX5.js +327 -0
  26. package/dist/chunk-Bu0FJr6S.js +84 -0
  27. package/dist/chunk-BvEoKpN3.js +255 -0
  28. package/dist/chunk-BxNJgYbE.js +28 -0
  29. package/dist/chunk-C1OFhd7F.js +6131 -0
  30. package/dist/chunk-C7e7WwbU.js +331 -0
  31. package/dist/chunk-CDTYP6bN.js +558 -0
  32. package/dist/chunk-CGDQoqDC.js +24 -0
  33. package/dist/chunk-CJraEBVd.js +127 -0
  34. package/dist/chunk-CQrNhOtv.js +40 -0
  35. package/dist/chunk-CW6AzBH6.js +128 -0
  36. package/dist/chunk-CXgL4w-9.js +317 -0
  37. package/dist/chunk-CZrqoPyf.js +233 -0
  38. package/dist/chunk-CcFCyqbB.js +83 -0
  39. package/dist/chunk-CdaIvO-y.js +485 -0
  40. package/dist/chunk-CehSXtgK.js +624 -0
  41. package/dist/chunk-Ciqkop65.js +218 -0
  42. package/dist/chunk-CoAWdSiE.js +43 -0
  43. package/dist/chunk-Crb4y7t4.js +830 -0
  44. package/dist/chunk-CrlqIfmb.js +337 -0
  45. package/dist/chunk-Cs4pwCQ-.js +324 -0
  46. package/dist/chunk-CwM_9y01.js +519 -0
  47. package/dist/chunk-CyRbIJg8.js +440 -0
  48. package/dist/chunk-D0OHsyMy.js +212 -0
  49. package/dist/chunk-D3y2bURs.js +1347 -0
  50. package/dist/chunk-D4_mJk03.js +2338 -0
  51. package/dist/chunk-D6C8rned.js +578 -0
  52. package/dist/chunk-D9ZbUnks.js +11 -0
  53. package/dist/chunk-DEPmLshX.js +834 -0
  54. package/dist/chunk-DFj1UPB2.js +330 -0
  55. package/dist/chunk-DGdFdZVf.js +197 -0
  56. package/dist/chunk-DGv7X9S_.js +63 -0
  57. package/dist/chunk-DJQ47WYZ.js +664 -0
  58. package/dist/chunk-DJw3O61j.js +1974 -0
  59. package/dist/chunk-DK4FbWVK.js +100 -0
  60. package/dist/chunk-DSRYhUus.js +993 -0
  61. package/dist/chunk-DajZ9Eg4.js +299 -0
  62. package/dist/chunk-Df4dunvU.js +195 -0
  63. package/dist/chunk-DjNTPhfO.js +481 -0
  64. package/dist/chunk-Dn8eaJuu.js +1042 -0
  65. package/dist/chunk-DwDWqSxM.js +637 -0
  66. package/dist/chunk-Dz7cEtxl.js +84 -0
  67. package/dist/chunk-E_0bgjrS.js +359 -0
  68. package/dist/chunk-KPYN2vUp.js +776 -0
  69. package/dist/chunk-KcqPQJBi.js +86 -0
  70. package/dist/chunk-N9H0JnoG.js +349 -0
  71. package/dist/chunk-NONFcXkn.js +728 -0
  72. package/dist/chunk-PrFjcpmH.js +256 -0
  73. package/dist/chunk-QSfNYiw9.js +11 -0
  74. package/dist/chunk-R265bzj9.js +226 -0
  75. package/dist/chunk-X9V_r6jM.js +166 -0
  76. package/dist/chunk-ZBtPUtyA.js +55 -0
  77. package/dist/chunk-ZCTfmOs7.js +4939 -0
  78. package/dist/chunk-h6oavj0t.js +97 -0
  79. package/dist/chunk-i7DwkvvD.js +42 -0
  80. package/dist/chunk-mG-4eZ6S.js +203 -0
  81. package/dist/chunk-o0ygUbCf.js +313 -0
  82. package/dist/chunk-ucu5lrDZ.js +5715 -0
  83. package/dist/index.js +11367 -73764
  84. package/package.json +6 -6
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ import { c as loadEffectiveContext, d as useStoredCredential, f as verifyExpectedIdentity, i as assertExpectedApiUrl, n as CliContextError, s as getInvocationOptions } from "./index.js";
3
+ import { n as ApiKeyValidationUnavailableError, t as ApiKeyManager } from "./chunk-BW2fg46T.js";
4
+ import { n as browserLogin, r as orgMismatchWarning } from "./chunk-BnWhIqW1.js";
5
+ import chalk from "chalk";
6
+ import readline from "readline";
7
+ // src/lib/ensure-auth.ts
8
+ const verifiedUsers = /* @__PURE__ */ new WeakMap();
9
+ async function getAuthenticatedUser() {
10
+ const context = await loadEffectiveContext();
11
+ if (!context.apiKey) throw new CliContextError("Authentication required.", "AUTH_REQUIRED");
12
+ const cached = verifiedUsers.get(context);
13
+ if (cached) return cached;
14
+ const apiKey = context.apiKey;
15
+ const user = await verifyExpectedIdentity(context, () => new ApiKeyManager().getCurrentUser(apiKey, context.apiUrl));
16
+ verifiedUsers.set(context, user);
17
+ return user;
18
+ }
19
+ async function assertExpectedOrganization() {
20
+ if (getInvocationOptions().expectOrg === void 0) return;
21
+ await getAuthenticatedUser();
22
+ }
23
+ /**
24
+ * Inline readline-based prompt for confirm (y/n).
25
+ * Used here instead of interactive.ts so we can delete that file.
26
+ */
27
+ async function promptConfirm(message, options) {
28
+ const defaultYes = options?.default !== false;
29
+ const hint = defaultYes ? "Y/n" : "y/N";
30
+ const rl = readline.createInterface({
31
+ input: process.stdin,
32
+ output: process.stdout,
33
+ terminal: true
34
+ });
35
+ return new Promise((resolve) => {
36
+ rl.question(chalk.cyan(`${message} (${hint}): `), (answer) => {
37
+ rl.close();
38
+ const trimmed = answer.trim().toLowerCase();
39
+ if (trimmed === "") resolve(defaultYes);
40
+ else resolve(trimmed === "y" || trimmed === "yes");
41
+ });
42
+ });
43
+ }
44
+ /**
45
+ * Ensure the user is authenticated, offering interactive login if not.
46
+ * Returns the API key on success, or null if the user declines to authenticate.
47
+ * Checks in order: RUNTYPE_API_KEY env var, stored credentials, then interactive browser login.
48
+ * When `exitOnFailure` is true (default) the process exits with code 1 if authentication is declined.
49
+ */
50
+ async function ensureAuth(options) {
51
+ const context = await loadEffectiveContext(options?.apiUrl);
52
+ const invocation = getInvocationOptions();
53
+ const strictIdentity = invocation.expectApiUrl !== void 0 || invocation.expectOrg !== void 0;
54
+ assertExpectedApiUrl(context.apiUrl);
55
+ if (context.credentialSource === "environment" && context.apiKey) {
56
+ if (invocation.expectOrg !== void 0) {
57
+ await assertExpectedOrganization();
58
+ return context.apiKey;
59
+ }
60
+ try {
61
+ if (await new ApiKeyManager().validateApiKey(context.apiKey, context.apiUrl)) return context.apiKey;
62
+ } catch (error) {
63
+ if (strictIdentity) throw new CliContextError("Cannot verify RUNTYPE_API_KEY for the expected target.", "AUTH_REQUIRED");
64
+ if (error instanceof ApiKeyValidationUnavailableError) console.error(chalk.yellow(`\n${error.message}`));
65
+ }
66
+ if (strictIdentity) throw new CliContextError("RUNTYPE_API_KEY was rejected for the expected target.", "AUTH_REQUIRED");
67
+ useStoredCredential(context);
68
+ }
69
+ if (context.apiKey) {
70
+ await assertExpectedOrganization();
71
+ return context.apiKey;
72
+ }
73
+ if (options?.interactive === false || process.stdin.isTTY !== true || invocation.noInput || strictIdentity) {
74
+ if (options?.exitOnFailure === false) return null;
75
+ const message = authRequiredMessage(context);
76
+ console.error(invocation.json ? JSON.stringify({
77
+ success: false,
78
+ error: {
79
+ code: "AUTH_REQUIRED",
80
+ message
81
+ }
82
+ }) : chalk.yellow(message));
83
+ process.exit(1);
84
+ return null;
85
+ }
86
+ console.log(chalk.yellow(`\n${authRequiredMessage(context)}`));
87
+ if (!await promptConfirm("Would you like to log in now?", { default: true })) {
88
+ console.log(chalk.gray("Run \"runtype auth login\" when you're ready."));
89
+ if (options?.exitOnFailure !== false) process.exit(1);
90
+ return null;
91
+ }
92
+ const apiKey = await handleBrowserLogin(context.apiUrl);
93
+ if (apiKey) {
94
+ context.apiKey = apiKey;
95
+ context.credentialSource = "stored";
96
+ }
97
+ return apiKey;
98
+ }
99
+ /** Name the organization the invocation needs, so a linked project never reads as "logged out". */
100
+ function authRequiredMessage(context) {
101
+ if (context.credentialError && context.account) return `The stored login for ${context.account.orgId ?? "your personal account"} could not be read (${context.credentialError}). Log in again with "runtype login" (or set RUNTYPE_CREDENTIAL_STORE=file first if the OS keychain is unavailable here), or set RUNTYPE_API_KEY.`;
102
+ if (context.requestedOrgId) return `${context.accountSource === "project" && context.projectLink ? `This directory is linked to ${context.requestedOrgId} (${context.projectLink.path})` : `This command targets ${context.requestedOrgId}`}, but there is no stored login for it on ${context.apiUrl}. Run "runtype login" and authorize that organization, or set RUNTYPE_API_KEY.`;
103
+ return "Authentication required. Run \"runtype auth login\" first or set RUNTYPE_API_KEY.";
104
+ }
105
+ async function handleBrowserLogin(apiUrl) {
106
+ console.log(chalk.gray("Opening browser for authentication..."));
107
+ try {
108
+ const login = await browserLogin({
109
+ apiUrl,
110
+ onProgress: (label) => console.log(chalk.gray(label))
111
+ });
112
+ const warning = orgMismatchWarning(login);
113
+ if (warning) {
114
+ console.log(chalk.yellow(warning));
115
+ return null;
116
+ }
117
+ console.log(chalk.green("Logged in successfully!"));
118
+ return login.apiKey;
119
+ } catch (error) {
120
+ const message = error instanceof Error ? error.message : "Unknown error";
121
+ console.log(chalk.red("Login failed"));
122
+ console.error(chalk.red(message));
123
+ return null;
124
+ }
125
+ }
126
+ export { ensureAuth as n, getAuthenticatedUser as r, authRequiredMessage as t };
127
+
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ import { a as printJson } from "./chunk-CcFCyqbB.js";
3
+ import { n as addExecutionInputOptions, r as resolveExecutionInput, t as runExecution } from "./chunk-Cs4pwCQ-.js";
4
+ import { t as buildAgentVersionSelector } from "./chunk-BxNJgYbE.js";
5
+ import { Command } from "commander";
6
+ import chalk from "chalk";
7
+ // src/commands/dispatch.ts
8
+ const dispatchCommand = addExecutionInputOptions(new Command("dispatch").description("Execute a flow or agent via the dispatch API").option("-f, --flow <id>", "Flow ID to execute").option("-a, --agent <id>", "Agent ID to execute").option("--alias <name>", "Run whichever version this release alias points at").option("--version-id <id>", "Run this exact version snapshot").option("-r, --record <id>", "Existing record ID").option("--record-json <file>", "JSON file with record data (use - for stdin)").option("-m, --message <text>", "Message to send").option("--stream", "Stream the response (default)", true).option("--no-stream", "Wait for complete response").option("--json", "Output one final JSON result").option("--jsonl", "Output stream events and a terminal execution_summary as JSON lines").option("--debug", "Show step-level details").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode")).action(async (options) => {
9
+ await runExecution(options, () => buildDispatchRequest(options), (client, payload) => options.stream ? client.dispatch.executeStream(payload) : client.dispatch.execute(payload), "execution", renderDispatchResult);
10
+ });
11
+ /** Build the canonical dispatch request, validating every input before authentication. */
12
+ function buildDispatchRequest(options) {
13
+ if (!options.flow && !options.agent) throw new Error("Either --flow or --agent is required");
14
+ if (options.flow && options.agent) throw new Error("Use only one of --flow and --agent");
15
+ const selector = buildAgentVersionSelector(options);
16
+ if (selector && !options.agent) throw new Error("A version selector needs an agent target: pass --agent alongside it.");
17
+ return {
18
+ ...resolveExecutionInput(options),
19
+ ...options.flow ? { flow: { id: options.flow } } : { agent: {
20
+ agentId: options.agent,
21
+ ...selector ?? {}
22
+ } },
23
+ options: { streamResponse: options.stream }
24
+ };
25
+ }
26
+ function renderDispatchResult(value) {
27
+ const result = value;
28
+ const steps = result.steps;
29
+ if (!steps) {
30
+ printJson(result);
31
+ return;
32
+ }
33
+ for (const step of steps) {
34
+ console.log(chalk.cyan(`\n[${step.name}]`));
35
+ if (typeof step.result === "string") console.log(step.result);
36
+ else printJson(step.result);
37
+ }
38
+ }
39
+ export { dispatchCommand };
40
+
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env node
2
+ // src/lib/terminal-image.ts
3
+ const ESC = "\x1B";
4
+ const BEL = "\x07";
5
+ const ST = `${ESC}\\`;
6
+ /**
7
+ * Detect whether stdout is an interactive terminal that supports an inline-image
8
+ * protocol we can drive without decoding the image. `env` / `isTTY` are injectable
9
+ * for testing; production callers use the process defaults.
10
+ */
11
+ function detectImageProtocol(env = process.env, isTTY = process.stdout.isTTY === true) {
12
+ if (!isTTY) return null;
13
+ if (env.TERM === "xterm-kitty" || env.KITTY_WINDOW_ID || env.TERM_PROGRAM === "ghostty") return "kitty";
14
+ if (env.TERM_PROGRAM === "iTerm.app" || env.TERM_PROGRAM === "WezTerm" || env.LC_TERMINAL === "iTerm2") return "iterm2";
15
+ return null;
16
+ }
17
+ /** Extract `{ mimeType, base64 }` from a `data:<mime>;base64,<data>` URI, or null. */
18
+ function parseImageDataUri(value) {
19
+ if (typeof value !== "string" || !value.startsWith("data:")) return null;
20
+ const match = value.match(/^data:([^;,]+)(?:;[^,]*)?;base64,(.+)$/s);
21
+ if (!match || !match[1] || !match[2]) return null;
22
+ return {
23
+ mimeType: match[1],
24
+ base64: match[2]
25
+ };
26
+ }
27
+ /** Decoded byte length of a base64 string (no allocation). */
28
+ function base64DecodedByteLength(base64) {
29
+ const len = base64.length;
30
+ if (len === 0) return 0;
31
+ let padding = 0;
32
+ if (base64.endsWith("==")) padding = 2;
33
+ else if (base64.endsWith("=")) padding = 1;
34
+ return Math.floor(len * 3 / 4) - padding;
35
+ }
36
+ /** Split a Kitty payload into `m=`-chunked APC sequences (≤4096 base64 chars each). */
37
+ function kittyImageSequence(base64) {
38
+ const CHUNK = 4096;
39
+ if (base64.length <= CHUNK) return `${ESC}_Ga=T,f=100,m=0;${base64}${ST}`;
40
+ let out = "";
41
+ for (let i = 0; i < base64.length; i += CHUNK) {
42
+ const piece = base64.slice(i, i + CHUNK);
43
+ const more = i + CHUNK < base64.length ? 1 : 0;
44
+ out += i === 0 ? `${ESC}_Ga=T,f=100,m=${more};${piece}${ST}` : `${ESC}_Gm=${more};${piece}${ST}`;
45
+ }
46
+ return out;
47
+ }
48
+ /**
49
+ * Build the escape sequence that renders an inline image for the given protocol,
50
+ * or null when the protocol can't display this format dependency-free (Kitty +
51
+ * non-PNG) so the caller falls back to a text marker.
52
+ */
53
+ function encodeInlineImage(opts) {
54
+ const { base64, mimeType, protocol } = opts;
55
+ if (!base64) return null;
56
+ if (protocol === "iterm2") {
57
+ const sizeBytes = base64DecodedByteLength(base64);
58
+ return `${ESC}]1337;File=inline=1;size=${sizeBytes};width=auto;height=auto;preserveAspectRatio=1:${base64}${BEL}`;
59
+ }
60
+ if (mimeType !== "image/png") return null;
61
+ return kittyImageSequence(base64);
62
+ }
63
+ /**
64
+ * Render an inline image from a `data:` URI for the detected protocol, or null
65
+ * when it isn't a renderable inline-image data URI / format. A convenience that
66
+ * composes {@link parseImageDataUri} + {@link encodeInlineImage}.
67
+ */
68
+ function renderInlineImage(value, protocol) {
69
+ const parsed = parseImageDataUri(value);
70
+ if (!parsed) return null;
71
+ return encodeInlineImage({
72
+ base64: parsed.base64,
73
+ mimeType: parsed.mimeType,
74
+ protocol
75
+ });
76
+ }
77
+ // src/lib/message-content.ts
78
+ /**
79
+ * Render a message's `content` to a single human-readable line for the CLI.
80
+ * A plain string passes through; an array of parts renders text inline and collapses media parts to compact markers.
81
+ * This is what keeps read commands from printing `[object Object]` for multi-modal or offloaded messages.
82
+ */
83
+ function renderMessageContent(content) {
84
+ if (typeof content === "string") return content;
85
+ if (!Array.isArray(content)) return content == null ? "" : String(content);
86
+ return content.map((part) => {
87
+ if (part == null || typeof part !== "object") return part == null ? "" : String(part);
88
+ const p = part;
89
+ switch (p.type) {
90
+ case "text":
91
+ case "reasoning": return typeof p.text === "string" ? p.text : "";
92
+ case "image": return `[image${"mimeType" in p && p.mimeType ? ` ${p.mimeType}` : ""}]`;
93
+ case "file": return `[file${"filename" in p && p.filename ? ` ${p.filename}` : ""}${"mimeType" in p && p.mimeType ? ` (${p.mimeType})` : ""}]`;
94
+ case "asset_ref": return `[attachment${"filename" in p && p.filename ? ` ${p.filename}` : ""}${"mimeType" in p && p.mimeType ? ` ${p.mimeType}` : ""}]`;
95
+ default: return typeof p.type === "string" ? `[${p.type}]` : "";
96
+ }
97
+ }).filter((s) => s.length > 0).join(" ");
98
+ }
99
+ /**
100
+ * Split a message's `content` into the text line and inline-image escape sequences for renderable parts.
101
+ * Pure; the caller decides how to write (role prefix, ordering).
102
+ * Used only on the interactive image-capable path; unrenderable content degrades to `renderMessageContent`'s marker text.
103
+ */
104
+ function renderMessageContentWithImages(content, protocol) {
105
+ if (!Array.isArray(content)) return {
106
+ text: renderMessageContent(content),
107
+ images: []
108
+ };
109
+ const inline = [];
110
+ const images = [];
111
+ for (const part of content) {
112
+ if (part && typeof part === "object" && part.type === "image") {
113
+ const image = part.image;
114
+ const seq = typeof image === "string" ? renderInlineImage(image, protocol) : null;
115
+ if (seq) {
116
+ images.push(seq);
117
+ continue;
118
+ }
119
+ }
120
+ inline.push(renderMessageContent([part]));
121
+ }
122
+ return {
123
+ text: inline.filter((s) => s.length > 0).join(" "),
124
+ images
125
+ };
126
+ }
127
+ export { renderMessageContentWithImages as n, detectImageProtocol as r, renderMessageContent as t };
128
+
@@ -0,0 +1,317 @@
1
+ #!/usr/bin/env node
2
+ import { t as isTTY } from "./index.js";
3
+ import { DataList, EntityCard, MutationResult, React, render, useEffect, useState } from "./chunk-CoAWdSiE.js";
4
+ import { r as createCliClient } from "./chunk-BW2fg46T.js";
5
+ import { n as ensureAuth } from "./chunk-CJraEBVd.js";
6
+ import { a as printJson, i as printDetail, n as buildListParams, s as printNextCursorHint } from "./chunk-CcFCyqbB.js";
7
+ import { c as buildEntityPullCommand, i as SURFACE_ENSURE_SPEC, s as buildEntityEnsureCommand } from "./chunk-BirUxTtH.js";
8
+ import { Command, Option } from "commander";
9
+ import chalk from "chalk";
10
+ import { readFile } from "node:fs/promises";
11
+ // src/commands/surfaces.ts
12
+ const DELIVERY_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
13
+ async function readStdin() {
14
+ let value = "";
15
+ process.stdin.setEncoding("utf8");
16
+ for await (const chunk of process.stdin) value += chunk;
17
+ return value;
18
+ }
19
+ /** The `--payload` object, read from a file or from stdin when the source is `-`. */
20
+ async function readPayloadObject(source) {
21
+ const label = source === "-" ? "stdin" : source;
22
+ const raw = source === "-" ? await readStdin() : await readFile(source, "utf8");
23
+ let parsed;
24
+ try {
25
+ parsed = JSON.parse(raw);
26
+ } catch {
27
+ throw new Error(`--payload: invalid JSON in ${label}`);
28
+ }
29
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`--payload: ${label} must contain a JSON object`);
30
+ return parsed;
31
+ }
32
+ /** `Attempt 1: HTTP 202, run exec_1 (route: event_route opened)` */
33
+ function formatDeliveryAttempt(attempt) {
34
+ const outcome = attempt.deduplicated ? ", deduplicated" : attempt.executionId ? `, run ${attempt.executionId}` : "";
35
+ const route = attempt.route ? ` (route: ${[attempt.route.matchedBy, attempt.route.pattern].filter(Boolean).join(" ")})` : "";
36
+ return `Attempt ${attempt.attempt}: HTTP ${attempt.status}${outcome}${route}`;
37
+ }
38
+ function parsePositiveInteger(value, option) {
39
+ const parsed = Number(value);
40
+ if (!Number.isInteger(parsed) || parsed < 1) throw new Error(`${option} must be a positive integer`);
41
+ return parsed;
42
+ }
43
+ const surfacesCommand = new Command("surfaces").description("View product surfaces");
44
+ surfacesCommand.addCommand(buildEntityEnsureCommand(SURFACE_ENSURE_SPEC));
45
+ surfacesCommand.addCommand(buildEntityPullCommand(SURFACE_ENSURE_SPEC));
46
+ surfacesCommand.command("list").description("List surfaces across your products").option("--json", "Output as JSON").option("--limit <n>", "Maximum number of surfaces to return").option("--cursor <cursor>", "Pagination cursor for the next page").option("--type <type>", "Filter by surface type").option("--status <status>", "Filter by surface status").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (options) => {
47
+ const apiKey = await ensureAuth();
48
+ if (!apiKey) return;
49
+ const client = createCliClient(apiKey);
50
+ const query = buildListParams(options, {
51
+ type: options.type,
52
+ status: options.status
53
+ });
54
+ if (!isTTY(options) || options.json) {
55
+ try {
56
+ const data = await client.get("/surfaces", query);
57
+ if (options.json) {
58
+ printJson(data);
59
+ return;
60
+ }
61
+ const surfaces = data.data ?? [];
62
+ if (surfaces.length === 0) {
63
+ console.log("No surfaces found");
64
+ return;
65
+ }
66
+ console.log("Your Surfaces:");
67
+ for (const surface of surfaces) {
68
+ console.log(` ${chalk.green(surface.id)} ${surface.name} [${surface.type}]`);
69
+ console.log(` Product: ${surface.productId}`);
70
+ if (surface.status) console.log(` Status: ${surface.status}`);
71
+ if (surface.updatedAt) console.log(` Updated: ${surface.updatedAt}`);
72
+ }
73
+ const total = data.pagination?.totalCount;
74
+ console.log(chalk.dim(`\n Showing ${surfaces.length}${total !== void 0 ? ` of ${total}` : ""} surfaces`));
75
+ printNextCursorHint("runtype surfaces list", data.pagination);
76
+ } catch (error) {
77
+ const message = error instanceof Error ? error.message : "Unknown error";
78
+ console.error(chalk.red(`Failed to fetch surfaces: ${message}`));
79
+ process.exit(1);
80
+ }
81
+ return;
82
+ }
83
+ const App = () => {
84
+ const [items, setItems] = useState(null);
85
+ const [error, setError] = useState(null);
86
+ const [total, setTotal] = useState();
87
+ useEffect(() => {
88
+ client.get("/surfaces", query).then((res) => {
89
+ const surfaces = res.data ?? [];
90
+ setItems(surfaces);
91
+ setTotal(res.pagination?.totalCount ?? surfaces.length);
92
+ }).catch((err) => setError(err instanceof Error ? err : new Error(String(err))));
93
+ }, []);
94
+ return React.createElement(DataList, {
95
+ title: "Your Surfaces",
96
+ items,
97
+ error,
98
+ loading: items === null && error === null,
99
+ total,
100
+ emptyMessage: "No surfaces found",
101
+ renderCard: (item) => {
102
+ const surface = item;
103
+ return React.createElement(EntityCard, { fields: [
104
+ {
105
+ label: "ID",
106
+ value: surface.id,
107
+ color: "green"
108
+ },
109
+ {
110
+ label: "Name",
111
+ value: surface.name
112
+ },
113
+ {
114
+ label: "Type",
115
+ value: surface.type,
116
+ color: "gray"
117
+ },
118
+ {
119
+ label: "Product",
120
+ value: surface.productId,
121
+ color: "gray"
122
+ },
123
+ {
124
+ label: "Status",
125
+ value: surface.status,
126
+ color: "gray"
127
+ },
128
+ {
129
+ label: "Updated",
130
+ value: surface.updatedAt
131
+ }
132
+ ] });
133
+ }
134
+ });
135
+ };
136
+ const { waitUntilExit } = render(React.createElement(App));
137
+ await waitUntilExit();
138
+ });
139
+ surfacesCommand.command("get <id>").description("Get surface details including its items and keys").addOption(new Option("--product <productId>", "Deprecated; surface IDs resolve their product").hideHelp()).option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (id, options) => {
140
+ const apiKey = await ensureAuth();
141
+ if (!apiKey) return;
142
+ const client = createCliClient(apiKey);
143
+ const detailPath = `/surfaces/${encodeURIComponent(id)}`;
144
+ if (!isTTY(options) || options.json) {
145
+ try {
146
+ const data = await client.get(detailPath);
147
+ if (options.json) {
148
+ printJson(data);
149
+ return;
150
+ }
151
+ printDetail("Surface", [
152
+ {
153
+ label: "ID",
154
+ value: data.id
155
+ },
156
+ {
157
+ label: "Name",
158
+ value: data.name
159
+ },
160
+ {
161
+ label: "Type",
162
+ value: data.type
163
+ },
164
+ {
165
+ label: "Product",
166
+ value: data.productId
167
+ },
168
+ {
169
+ label: "Status",
170
+ value: data.status
171
+ },
172
+ {
173
+ label: "Created",
174
+ value: data.createdAt
175
+ },
176
+ {
177
+ label: "Updated",
178
+ value: data.updatedAt
179
+ }
180
+ ]);
181
+ const items = data.items ?? [];
182
+ console.log(chalk.cyan(`\nItems (${items.length}):`));
183
+ if (items.length === 0) console.log(chalk.gray(" No items"));
184
+ else for (const item of items) {
185
+ const label = item.exposedName || item.capabilityName || item.capabilityId;
186
+ const entry = item.isEntryPoint ? " (entry point)" : "";
187
+ const enabled = item.enabled === false ? chalk.gray(" [disabled]") : "";
188
+ console.log(` ${chalk.green(item.id)} ${label}${entry}${enabled}`);
189
+ }
190
+ const keys = data.keys ?? [];
191
+ console.log(chalk.cyan(`\nKeys (${keys.length}):`));
192
+ if (keys.length === 0) console.log(chalk.gray(" No keys"));
193
+ else for (const key of keys) {
194
+ const prefix = key.keyPrefix ? `${key.keyPrefix}` : "";
195
+ const status = key.isActive === false ? chalk.gray(" [inactive]") : "";
196
+ console.log(` ${chalk.green(key.id)} ${key.name ?? "(unnamed)"} ${prefix}${status}`);
197
+ }
198
+ } catch (error) {
199
+ const message = error instanceof Error ? error.message : "Unknown error";
200
+ console.error(chalk.red(`Failed to fetch surface: ${message}`));
201
+ process.exit(1);
202
+ }
203
+ return;
204
+ }
205
+ const App = () => {
206
+ const [loading, setLoading] = useState(true);
207
+ const [success, setSuccess] = useState(null);
208
+ const [error, setError] = useState(null);
209
+ const [resultNode, setResultNode] = useState(void 0);
210
+ useEffect(() => {
211
+ const run = async () => {
212
+ try {
213
+ const data = await client.get(detailPath);
214
+ const items = data.items ?? [];
215
+ const keys = data.keys ?? [];
216
+ const itemSummary = items.length === 0 ? "No items" : items.map((i) => `${i.exposedName || i.capabilityName || i.capabilityId}${i.isEntryPoint ? " (entry point)" : ""}`).join("\n");
217
+ const keySummary = keys.length === 0 ? "No keys" : keys.map((k) => `${k.name ?? "(unnamed)"} ${k.keyPrefix ?? ""}`.trim()).join("\n");
218
+ setResultNode(React.createElement(EntityCard, { fields: [
219
+ {
220
+ label: "ID",
221
+ value: data.id,
222
+ color: "green"
223
+ },
224
+ {
225
+ label: "Name",
226
+ value: data.name
227
+ },
228
+ {
229
+ label: "Type",
230
+ value: data.type,
231
+ color: "gray"
232
+ },
233
+ {
234
+ label: "Product",
235
+ value: data.productId,
236
+ color: "gray"
237
+ },
238
+ {
239
+ label: "Status",
240
+ value: data.status,
241
+ color: "gray"
242
+ },
243
+ {
244
+ label: "Created",
245
+ value: data.createdAt
246
+ },
247
+ {
248
+ label: "Updated",
249
+ value: data.updatedAt
250
+ },
251
+ {
252
+ label: `Items (${items.length})`,
253
+ value: itemSummary
254
+ },
255
+ {
256
+ label: `Keys (${keys.length})`,
257
+ value: keySummary
258
+ }
259
+ ] }));
260
+ setSuccess(true);
261
+ setLoading(false);
262
+ } catch (err) {
263
+ setError(err instanceof Error ? err : new Error(String(err)));
264
+ setSuccess(false);
265
+ setLoading(false);
266
+ }
267
+ };
268
+ run();
269
+ }, []);
270
+ return React.createElement(MutationResult, {
271
+ loading,
272
+ loadingLabel: "Fetching surface...",
273
+ success,
274
+ successMessage: "Surface",
275
+ error,
276
+ result: resultNode
277
+ });
278
+ };
279
+ const { waitUntilExit } = render(React.createElement(App));
280
+ await waitUntilExit();
281
+ });
282
+ surfacesCommand.command("test <id>").description("Send a provider fixture through a surface’s real ingress").addOption(new Option("--product <productId>", "Deprecated; surface IDs resolve their product").hideHelp()).requiredOption("--fixture <fixture>", "Fixture name, such as github.pull_request.opened or github.pull_request.merged_plan").requiredOption("--repo <owner/repository>", "GitHub repository in owner/repository format").requiredOption("--pr <number>", "Pull request number").option("--repeat <number>", "Deliver the same provider event more than once", "1").option("--planning-path <path>", "Planning-document directory for merged-plan fixtures").option("--delivery-id <id>", "Reuse a previous delivery ID (X-GitHub-Delivery), to check the surface handles a repeat once").option("--payload <file>", "Send and sign this JSON object instead of the built fixture; - reads it from stdin").option("--json", "Output as JSON").action(async (id, options) => {
283
+ const apiKey = await ensureAuth();
284
+ if (!apiKey) return;
285
+ try {
286
+ const pullRequest = parsePositiveInteger(options.pr, "--pr");
287
+ const repeat = parsePositiveInteger(options.repeat, "--repeat");
288
+ if (repeat > 3) throw new Error("--repeat must not exceed 3");
289
+ if (options.deliveryId !== void 0 && !DELIVERY_ID_PATTERN.test(options.deliveryId)) throw new Error("--delivery-id must be 1-128 letters, digits, dots, underscores, colons or hyphens");
290
+ const payload = options.payload !== void 0 ? await readPayloadObject(options.payload) : void 0;
291
+ const result = await createCliClient(apiKey).post(`/surfaces/${encodeURIComponent(id)}/test-deliveries`, {
292
+ fixture: options.fixture,
293
+ repository: options.repo,
294
+ pullRequest,
295
+ repeat,
296
+ ...options.planningPath ? { planningPath: options.planningPath } : {},
297
+ ...options.deliveryId ? { deliveryId: options.deliveryId } : {},
298
+ ...payload ? { payload } : {}
299
+ });
300
+ if (options.json) {
301
+ printJson(result);
302
+ return;
303
+ }
304
+ console.log(chalk.green("Test delivery accepted"));
305
+ console.log(` Surface: ${result.surface.id} [${result.surface.type}/${result.surface.provider}]`);
306
+ console.log(` Fixture: ${result.fixture}`);
307
+ console.log(` Source: ${result.repository}#${result.pullRequest}`);
308
+ console.log(` Delivery: ${result.deliveryId}`);
309
+ for (const attempt of result.attempts) console.log(` ${formatDeliveryAttempt(attempt)}`);
310
+ } catch (error) {
311
+ const message = error instanceof Error ? error.message : "Unknown error";
312
+ console.error(chalk.red(`Failed to test surface: ${message}`));
313
+ process.exitCode = 1;
314
+ }
315
+ });
316
+ export { surfacesCommand };
317
+