@runtypelabs/cli 2.30.0 → 2.31.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 (2) hide show
  1. package/dist/index.js +648 -174
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -72,12 +72,35 @@ var init_credential_store = __esm({
72
72
  }
73
73
  }
74
74
  }
75
+ /**
76
+ * Replace the entire backing store in one file write. Conf's runtime has a
77
+ * `store` setter (used for exactly this whole-store replacement), but its
78
+ * published typings declare the property read-only, so the assignment needs
79
+ * a cast. Single-write replacement is load-bearing for claim completion:
80
+ * the server has already revoked the previous key, so a multi-write
81
+ * sequence could lose the only copy of the fresh credential.
82
+ */
83
+ replaceStore(data) {
84
+ ;
85
+ this.config.store = data;
86
+ }
75
87
  getMachineKey() {
76
88
  const hostname4 = os.hostname();
77
89
  const username = os.userInfo().username;
78
90
  return crypto3.createHash("sha256").update(`runtype-cli-${hostname4}-${username}`).digest("hex").substring(0, 32);
79
91
  }
80
- async saveCredentials(credentials) {
92
+ /**
93
+ * Store credentials, optionally together with agent-auth claim state in the
94
+ * same write. Passing `claimState` here (rather than a follow-up
95
+ * `saveClaimState` call) keeps the temporary credential and its claim token
96
+ * atomic — a crash between two writes would otherwise orphan a pre-claim
97
+ * key that `auth status` then misreports as a full account.
98
+ *
99
+ * Storing credentials WITHOUT `claimState` invalidates any in-flight claim
100
+ * ceremony: every login path (api-key, browser, claim completion) must not
101
+ * leave stale claim state behind.
102
+ */
103
+ async saveCredentials(credentials, claimState) {
81
104
  const configData = {
82
105
  apiKey: this.encrypt(credentials.apiKey),
83
106
  userId: credentials.userId,
@@ -90,7 +113,21 @@ var init_credential_store = __esm({
90
113
  if (credentials.apiUrl !== void 0) {
91
114
  configData.apiUrl = credentials.apiUrl;
92
115
  }
93
- this.config.set(configData);
116
+ if (claimState) {
117
+ Object.assign(configData, this.claimStateToConfig(claimState));
118
+ }
119
+ this.replaceStore(configData);
120
+ }
121
+ claimStateToConfig(state) {
122
+ const data = {
123
+ claimToken: this.encrypt(state.claimToken),
124
+ registrationId: state.registrationId
125
+ };
126
+ if (state.claimEmail !== void 0) data.claimEmail = state.claimEmail;
127
+ if (state.credentialExpires !== void 0) data.credentialExpires = state.credentialExpires;
128
+ if (state.claimTokenExpires !== void 0) data.claimTokenExpires = state.claimTokenExpires;
129
+ if (state.apiUrl !== void 0) data.claimApiUrl = state.apiUrl;
130
+ return data;
94
131
  }
95
132
  async getApiKey() {
96
133
  const encrypted = this.config.get("apiKey");
@@ -109,6 +146,39 @@ var init_credential_store = __esm({
109
146
  async clearCredentials() {
110
147
  this.config.clear();
111
148
  }
149
+ /**
150
+ * Persist agent-auth claim ceremony state alongside the pre-claim credential
151
+ * so `runtype auth claim` / `runtype auth verify` can run as separate
152
+ * invocations (the agent asks the user for their email and OTP in between).
153
+ * The claim token is encrypted like the API key — it can mint the
154
+ * post-claim credential, so it is a secret.
155
+ */
156
+ async saveClaimState(state) {
157
+ this.config.set(this.claimStateToConfig(state));
158
+ }
159
+ async getClaimState() {
160
+ const claimToken = this.config.get("claimToken");
161
+ const registrationId = this.config.get("registrationId");
162
+ if (!claimToken || !registrationId) return null;
163
+ return {
164
+ claimToken: this.decrypt(claimToken),
165
+ registrationId,
166
+ claimEmail: this.config.get("claimEmail"),
167
+ credentialExpires: this.config.get("credentialExpires"),
168
+ claimTokenExpires: this.config.get("claimTokenExpires"),
169
+ apiUrl: this.config.get("claimApiUrl")
170
+ };
171
+ }
172
+ async clearClaimState() {
173
+ const store = { ...this.config.store };
174
+ delete store.claimToken;
175
+ delete store.registrationId;
176
+ delete store.claimEmail;
177
+ delete store.credentialExpires;
178
+ delete store.claimTokenExpires;
179
+ delete store.claimApiUrl;
180
+ this.replaceStore(store);
181
+ }
112
182
  async hasCredentials() {
113
183
  return !!this.config.get("apiKey");
114
184
  }
@@ -159,7 +229,7 @@ function loadEnv(path20 = resolve(process.cwd(), ".env")) {
159
229
 
160
230
  // src/commands/auth.ts
161
231
  import { Command } from "commander";
162
- import chalk from "chalk";
232
+ import chalk2 from "chalk";
163
233
  import React from "react";
164
234
  import { render } from "ink";
165
235
  import { useState as useState2, useEffect as useEffect3 } from "react";
@@ -38852,6 +38922,9 @@ var coverageInventoryOutputSchema = external_exports.object({
38852
38922
  instructions: external_exports.array(coverageInstructionSchema)
38853
38923
  });
38854
38924
  var userProfileFeaturesSchema = external_exports.object({
38925
+ // Agent Skills is permanently enabled — the API always returns `true`. Kept
38926
+ // on the wire (rather than dropped) for compatibility with deployed SPA
38927
+ // bundles and the published SDK that still read this field.
38855
38928
  enableAgentSkills: external_exports.boolean(),
38856
38929
  // Gates the WebMCP-powered dashboard assistant (copilot panel + chat
38857
38930
  // proxy). Driven by the `enable-dashboard-assistant` boolean flag.
@@ -38883,6 +38956,13 @@ var userProfileFeaturesSchema = external_exports.object({
38883
38956
  // until a rule enables it). Execution and tool listings are gated
38884
38957
  // server-side by the same flag.
38885
38958
  enableMassiveWebRender: external_exports.boolean(),
38959
+ // Gates the agent voice configuration UI in the dashboard (the Voice tab in
38960
+ // the agent editor). Driven by the `enable-voice` boolean flag (fail-closed
38961
+ // in production until a rule enables it; non-prod defaults on, preserving
38962
+ // the staging-first rollout the flag replaced). The voice API routes stay
38963
+ // ungated: they already require per-agent `config.voice.enabled` opt-in,
38964
+ // which production users can only set once this flag exposes the UI.
38965
+ enableVoice: external_exports.boolean(),
38886
38966
  // Gates the workspace-model arms of the dashboard signup / switcher UI
38887
38967
  // (docs/features/planning/2026-07-07-workspace-model-migration.md). Driven by
38888
38968
  // the `enable-workspace-model` boolean flag (fail-closed in production until a
@@ -38938,10 +39018,16 @@ var MODEL_FAMILY_PROVIDER_IDS = {
38938
39018
  "anthropic": "claude-opus-4-7",
38939
39019
  "vercel": "anthropic/claude-opus-4.7"
38940
39020
  },
39021
+ "claude-opus-4-7-fast": {
39022
+ "vercel": "anthropic/claude-opus-4.7-fast"
39023
+ },
38941
39024
  "claude-opus-4-8": {
38942
39025
  "anthropic": "claude-opus-4-8",
38943
39026
  "vercel": "anthropic/claude-opus-4.8"
38944
39027
  },
39028
+ "claude-opus-4-8-fast": {
39029
+ "vercel": "anthropic/claude-opus-4.8-fast"
39030
+ },
38945
39031
  "claude-opus-4.1": {
38946
39032
  "vercel": "anthropic/claude-opus-4.1"
38947
39033
  },
@@ -38954,9 +39040,15 @@ var MODEL_FAMILY_PROVIDER_IDS = {
38954
39040
  "claude-opus-4.7": {
38955
39041
  "vercel": "anthropic/claude-opus-4.7"
38956
39042
  },
39043
+ "claude-opus-4.7-fast": {
39044
+ "vercel": "anthropic/claude-opus-4.7-fast"
39045
+ },
38957
39046
  "claude-opus-4.8": {
38958
39047
  "vercel": "anthropic/claude-opus-4.8"
38959
39048
  },
39049
+ "claude-opus-4.8-fast": {
39050
+ "vercel": "anthropic/claude-opus-4.8-fast"
39051
+ },
38960
39052
  "claude-sonnet-4": {
38961
39053
  "vercel": "anthropic/claude-sonnet-4"
38962
39054
  },
@@ -39006,9 +39098,6 @@ var MODEL_FAMILY_PROVIDER_IDS = {
39006
39098
  "devstral-2": {
39007
39099
  "vercel": "mistral/devstral-2"
39008
39100
  },
39009
- "devstral-small": {
39010
- "vercel": "mistral/devstral-small"
39011
- },
39012
39101
  "devstral-small-2": {
39013
39102
  "vercel": "mistral/devstral-small-2"
39014
39103
  },
@@ -39366,6 +39455,9 @@ var MODEL_FAMILY_PROVIDER_IDS = {
39366
39455
  "gpt-oss-20b": {
39367
39456
  "togetherai": "togetherai/openai/gpt-oss-20b"
39368
39457
  },
39458
+ "gpt-realtime-whisper": {
39459
+ "vercel": "openai/gpt-realtime-whisper"
39460
+ },
39369
39461
  "grok-4-20-0309-non-reasoning": {
39370
39462
  "xai": "grok-4.20-0309-non-reasoning"
39371
39463
  },
@@ -39438,6 +39530,9 @@ var MODEL_FAMILY_PROVIDER_IDS = {
39438
39530
  "grok-voice-think-fast-1.0": {
39439
39531
  "vercel": "xai/grok-voice-think-fast-1.0"
39440
39532
  },
39533
+ "inkling": {
39534
+ "vercel": "thinkingmachines/inkling"
39535
+ },
39441
39536
  "interfaze-beta": {
39442
39537
  "vercel": "interfaze/interfaze-beta"
39443
39538
  },
@@ -39495,6 +39590,9 @@ var MODEL_FAMILY_PROVIDER_IDS = {
39495
39590
  "kimi-k2.7-code-highspeed": {
39496
39591
  "vercel": "moonshotai/kimi-k2.7-code-highspeed"
39497
39592
  },
39593
+ "kimi-k3": {
39594
+ "vercel": "moonshotai/kimi-k3"
39595
+ },
39498
39596
  "lfm2-24b-a2b": {
39499
39597
  "togetherai": "togetherai/LiquidAI/LFM2-24B-A2B"
39500
39598
  },
@@ -39546,12 +39644,6 @@ var MODEL_FAMILY_PROVIDER_IDS = {
39546
39644
  "llama3-3-70b": {
39547
39645
  "vercel": "meta/llama-3.3-70b"
39548
39646
  },
39549
- "longcat-flash-chat": {
39550
- "vercel": "meituan/longcat-flash-chat"
39551
- },
39552
- "longcat-flash-thinking-2601": {
39553
- "vercel": "meituan/longcat-flash-thinking-2601"
39554
- },
39555
39647
  "magistral-medium": {
39556
39648
  "vercel": "mistral/magistral-medium"
39557
39649
  },
@@ -39573,9 +39665,6 @@ var MODEL_FAMILY_PROVIDER_IDS = {
39573
39665
  "mimo-v2-5-pro": {
39574
39666
  "vercel": "xiaomi/mimo-v2.5-pro"
39575
39667
  },
39576
- "mimo-v2-pro": {
39577
- "vercel": "xiaomi/mimo-v2-pro"
39578
- },
39579
39668
  "mimo-v2.5": {
39580
39669
  "vercel": "xiaomi/mimo-v2.5"
39581
39670
  },
@@ -39714,9 +39803,6 @@ var MODEL_FAMILY_PROVIDER_IDS = {
39714
39803
  "pixtral-12b": {
39715
39804
  "vercel": "mistral/pixtral-12b"
39716
39805
  },
39717
- "pixtral-large": {
39718
- "vercel": "mistral/pixtral-large"
39719
- },
39720
39806
  "qwen-3-14b": {
39721
39807
  "vercel": "alibaba/qwen-3-14b"
39722
39808
  },
@@ -39894,18 +39980,6 @@ var MODEL_FAMILY_PROVIDER_IDS = {
39894
39980
  "qwen3.7-plus": {
39895
39981
  "vercel": "alibaba/qwen3.7-plus"
39896
39982
  },
39897
- "rerank-2-5": {
39898
- "vercel": "voyage/rerank-2.5"
39899
- },
39900
- "rerank-2-5-lite": {
39901
- "vercel": "voyage/rerank-2.5-lite"
39902
- },
39903
- "rerank-2.5": {
39904
- "vercel": "voyage/rerank-2.5"
39905
- },
39906
- "rerank-2.5-lite": {
39907
- "vercel": "voyage/rerank-2.5-lite"
39908
- },
39909
39983
  "rerank-v3-5": {
39910
39984
  "vercel": "cohere/rerank-v3.5"
39911
39985
  },
@@ -39966,9 +40040,6 @@ var MODEL_FAMILY_PROVIDER_IDS = {
39966
40040
  "trinity-large": {
39967
40041
  "vercel": "arcee-ai/trinity-large-thinking"
39968
40042
  },
39969
- "trinity-large-preview": {
39970
- "vercel": "arcee-ai/trinity-large-preview"
39971
- },
39972
40043
  "trinity-large-thinking": {
39973
40044
  "vercel": "arcee-ai/trinity-large-thinking"
39974
40045
  },
@@ -43433,12 +43504,10 @@ var platformCatalogSchema = external_exports.object({
43433
43504
  /**
43434
43505
  * Agent Skills — loadable SKILL.md context bundles a deployed agent pulls into
43435
43506
  * its working context on demand. Distinct from A2A AgentSkill (agent-card
43436
- * roles). Flag-gated, so it's described here rather than listed as a fixed
43437
- * catalog (skills are user-authored, not a built-in set).
43507
+ * roles). Described here rather than listed as a fixed catalog (skills are
43508
+ * user-authored, not a built-in set).
43438
43509
  */
43439
43510
  agentSkills: external_exports.object({
43440
- /** Rollout flag controlling availability; off in production until ramped. */
43441
- rolloutFlag: external_exports.string(),
43442
43511
  description: external_exports.string(),
43443
43512
  /** How an agent acquires a bound skill at runtime. */
43444
43513
  loadModel: external_exports.string(),
@@ -43560,7 +43629,6 @@ var PLATFORM_CATALOG = {
43560
43629
  }
43561
43630
  ],
43562
43631
  agentSkills: {
43563
- rolloutFlag: "enable-agent-skills",
43564
43632
  description: "Loadable SKILL.md context bundles (Anthropic frontmatter + a runtype: capability block) that a deployed agent pulls into its working context on demand.",
43565
43633
  loadModel: "A bound skill is surfaced as a `skill:<slug>` virtual tool \u2014 the model loads it on demand (L1 description in the tool list \u2192 L2 markdown body returned on call); bound capabilities activate for later turns.",
43566
43634
  governance: "Two-plane: admin/control plane (REST /v1/skills, scoped by SKILLS:READ/WRITE, no review queue) vs deployed-agent/data plane (propose_skill, review-by-default via /v1/skill-proposals).",
@@ -44016,7 +44084,7 @@ ${renderDefaultModelStrengthsGuide()}`;
44016
44084
  - **Built-in tools**: GPT Image 2 image generation, provider-native web search (OpenAI web search, Anthropic web search/fetch, xAI Live Search for Grok-3 series, xAI X Search / Web Search agentic tools for Grok-4 series), Exa (AI search), Firecrawl (simple web scraping), Browser Run tools (rendered markdown, HTML, screenshots, snapshots, selector scraping, JSON extraction, link harvesting \u2014 stateless), Browser Session tools (persistent interactive browsing: open, navigate, click, type, scroll, screenshot, evaluate, close), Browser Agent (autonomous higher-level alternative \u2014 give a natural-language goal plus a startUrl or sessionId and the agent navigates/clicks/extracts on its own), Store Asset (host any file from a URL or base64 inline content \u2014 returns a permanent public or signed private URL, downloads as attachment), Publish Page (host HTML that renders inline in the browser at a shareable 7-day /preview/ URL under a locked-down CSP), vector search, UCP commerce tools (discovery, catalog, cart, checkout, orders for any UCP-enabled merchant)
44017
44085
  - **Integrations**: ${integrations}
44018
44086
  - **Flow step types**: prompt, ${stepTypes}
44019
- - **Agent Skills**: loadable SKILL.md context bundles an agent pulls in on demand via a \`skill:<slug>\` tool (L1 description \u2192 L2 body), optionally binding flows/subagents/tools. Gated behind the \`enable-agent-skills\` rollout flag. See the Agent Skills guide.
44087
+ - **Agent Skills**: loadable SKILL.md context bundles an agent pulls in on demand via a \`skill:<slug>\` tool (L1 description \u2192 L2 body), optionally binding flows/subagents/tools. See the Agent Skills guide.
44020
44088
  - **Available models**: ${models}
44021
44089
  - **Surface types**: ${PLATFORM_CATALOG.availableSurfaceTypes.join(", ")}`;
44022
44090
  })();
@@ -45929,7 +45997,18 @@ var ApiKeyManager = class {
45929
45997
  try {
45930
45998
  data = await client.get("/auth/me");
45931
45999
  } catch {
45932
- throw new Error("Failed to get user information");
46000
+ try {
46001
+ const profile = await client.get(
46002
+ "/users/profile"
46003
+ );
46004
+ if (typeof profile.id === "string") {
46005
+ data = { userId: profile.id, orgId: profile.orgId ?? null, email: profile.email };
46006
+ }
46007
+ } catch {
46008
+ }
46009
+ if (!data) {
46010
+ throw new Error("Failed to get user information");
46011
+ }
45933
46012
  }
45934
46013
  if (!isAuthMeResponse(data)) {
45935
46014
  throw new Error("Invalid user response format");
@@ -46176,23 +46255,418 @@ function MutationResult({
46176
46255
  return null;
46177
46256
  }
46178
46257
 
46179
- // src/commands/auth.ts
46180
- var authCommand = new Command("auth").description("Manage authentication");
46181
- async function getBillingInfo(apiKey, apiUrl) {
46258
+ // src/commands/auth-agent.ts
46259
+ init_credential_store();
46260
+
46261
+ // src/auth/agent-auth-client.ts
46262
+ var AgentAuthError = class extends Error {
46263
+ constructor(code, status, message, hint) {
46264
+ super(message);
46265
+ this.code = code;
46266
+ this.status = status;
46267
+ this.hint = hint;
46268
+ this.name = "AgentAuthError";
46269
+ }
46270
+ code;
46271
+ status;
46272
+ hint;
46273
+ };
46274
+ var ERROR_HINTS = {
46275
+ email_already_registered: "This email already has a Runtype account, so it cannot claim a new agent account. Ask the user to sign in at the dashboard, create an API key (Settings > API keys), and give it to you. Then run: runtype auth login --api-key <key> --no-tty",
46276
+ invalid_claim_token: "The claim token is invalid or expired. Run: runtype auth register --force",
46277
+ claim_expired: "The claim window expired. Run: runtype auth register --force",
46278
+ credential_expired: "The temporary credential expired. Run: runtype auth register --force",
46279
+ claimed_or_in_flight: "A claim is already in progress. Ask the user for the 6-digit code from their email, then run: runtype auth verify <code>. If the code expired, re-run: runtype auth claim <email>",
46280
+ otp_invalid: "That code is not correct. Ask the user to re-check the 6-digit code from the most recent Runtype email and run: runtype auth verify <code>",
46281
+ otp_expired: "The code expired. Re-send it with: runtype auth claim <email>",
46282
+ previously_claimed: "This registration was already claimed. Run: runtype auth status to inspect stored credentials.",
46283
+ email_send_failed: "The verification email could not be sent. Retry: runtype auth claim <email>",
46284
+ rate_limited: "Too many attempts. Wait a minute, then retry the same command.",
46285
+ service_disabled: "Agent registration is temporarily disabled. Try again later."
46286
+ };
46287
+ async function postJson(url2, body) {
46288
+ let response;
46182
46289
  try {
46183
- const client = createCliClient(apiKey, apiUrl);
46184
- return await client.get("/billing/status");
46290
+ response = await fetch(url2, {
46291
+ method: "POST",
46292
+ headers: {
46293
+ "content-type": "application/json",
46294
+ "user-agent": "runtype-cli"
46295
+ },
46296
+ body: JSON.stringify(body)
46297
+ });
46298
+ } catch (error51) {
46299
+ const message = error51 instanceof Error ? error51.message : String(error51);
46300
+ throw new AgentAuthError(
46301
+ "network_error",
46302
+ 0,
46303
+ `Could not reach ${url2}: ${message}`,
46304
+ "Check network access and the --api-url value, then retry the same command."
46305
+ );
46306
+ }
46307
+ let data = {};
46308
+ try {
46309
+ data = await response.json();
46185
46310
  } catch {
46186
- return null;
46187
46311
  }
46312
+ if (!response.ok) {
46313
+ const code = typeof data.error === "string" ? data.error : `http_${response.status}`;
46314
+ const message = typeof data.message === "string" ? data.message : `Request failed (${response.status})`;
46315
+ throw new AgentAuthError(code, response.status, message, ERROR_HINTS[code]);
46316
+ }
46317
+ return { status: response.status, data };
46188
46318
  }
46189
- authCommand.command("signup").description('Create a new Runtype account (alias for "auth login")').option("--api-url <url>", "Custom API URL").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (options) => {
46190
- if (!isTTY(options)) {
46319
+ function requireString(data, key) {
46320
+ const value = data[key];
46321
+ if (typeof value !== "string" || value.length === 0) {
46322
+ throw new AgentAuthError(
46323
+ "invalid_response",
46324
+ 0,
46325
+ `Registration response is missing "${key}". The API may be an incompatible version.`
46326
+ );
46327
+ }
46328
+ return value;
46329
+ }
46330
+ function stringArray(value) {
46331
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
46332
+ }
46333
+ async function registerAnonymousAgent(apiUrl) {
46334
+ const base = apiUrl || getApiUrl();
46335
+ const { data } = await postJson(`${base}/agent/auth`, {
46336
+ // @snake-case-ok-start: auth.md protocol field names
46337
+ type: "anonymous",
46338
+ requested_credential_type: "api_key"
46339
+ // @snake-case-ok-end
46340
+ });
46341
+ return {
46342
+ // @snake-case-ok-start: auth.md protocol field names
46343
+ registrationId: requireString(data, "registration_id"),
46344
+ credential: requireString(data, "credential"),
46345
+ credentialExpires: requireString(data, "credential_expires"),
46346
+ scopes: stringArray(data.scopes),
46347
+ claimToken: requireString(data, "claim_token"),
46348
+ claimTokenExpires: requireString(data, "claim_token_expires"),
46349
+ postClaimScopes: stringArray(data.post_claim_scopes)
46350
+ // @snake-case-ok-end
46351
+ };
46352
+ }
46353
+ async function initiateClaim(claimToken, email3, apiUrl) {
46354
+ const base = apiUrl || getApiUrl();
46355
+ const { data } = await postJson(`${base}/agent/auth/claim`, {
46356
+ // @snake-case-ok-start: auth.md protocol field names
46357
+ claim_token: claimToken,
46358
+ email: email3
46359
+ // @snake-case-ok-end
46360
+ });
46361
+ return {
46362
+ // @snake-case-ok-start: auth.md protocol field names
46363
+ registrationId: requireString(data, "registration_id"),
46364
+ status: typeof data.status === "string" ? data.status : "initiated",
46365
+ otpExpiresAt: requireString(data, "expires_at")
46366
+ // @snake-case-ok-end
46367
+ };
46368
+ }
46369
+ async function completeClaim(claimToken, otp, apiUrl) {
46370
+ const base = apiUrl || getApiUrl();
46371
+ const { data } = await postJson(`${base}/agent/auth/claim/complete`, {
46372
+ // @snake-case-ok-start: auth.md protocol field names
46373
+ claim_token: claimToken,
46374
+ otp
46375
+ // @snake-case-ok-end
46376
+ });
46377
+ return {
46378
+ // @snake-case-ok-start: auth.md protocol field names
46379
+ registrationId: requireString(data, "registration_id"),
46380
+ credential: requireString(data, "credential"),
46381
+ scopes: stringArray(data.scopes)
46382
+ // @snake-case-ok-end
46383
+ };
46384
+ }
46385
+
46386
+ // src/lib/output.ts
46387
+ import chalk from "chalk";
46388
+ function printJson(data) {
46389
+ console.log(JSON.stringify(data, null, 2));
46390
+ }
46391
+ function printList(items, options) {
46392
+ console.log(chalk.cyan(`${options.title}:`));
46393
+ if (items.length === 0) {
46394
+ console.log(chalk.gray(` ${options.emptyMessage || "None found"}`));
46395
+ return;
46396
+ }
46397
+ for (const item of items) {
46398
+ const parts = [];
46399
+ for (const col of options.columns) {
46400
+ const value = item[col.key];
46401
+ if (value === void 0 || value === null) continue;
46402
+ const str = String(value);
46403
+ const colorFn = col.color ? chalk[col.color] : (s) => s;
46404
+ if (col.label) {
46405
+ parts.push(`${col.label}: ${colorFn(str)}`);
46406
+ } else {
46407
+ parts.push(colorFn(str));
46408
+ }
46409
+ }
46410
+ console.log(` ${parts.join(" - ")}`);
46411
+ }
46412
+ if (options.total !== void 0) {
46413
+ console.log(chalk.dim(`
46414
+ Total: ${options.total}`));
46415
+ }
46416
+ }
46417
+ function printDetail(title, fields) {
46418
+ console.log(chalk.cyan(`${title}:`));
46419
+ for (const field of fields) {
46420
+ if (field.value === void 0 || field.value === null) continue;
46421
+ const str = typeof field.value === "object" ? JSON.stringify(field.value) : String(field.value);
46422
+ console.log(` ${chalk.gray(field.label + ":")} ${str}`);
46423
+ }
46424
+ }
46425
+ function getTotalCount(pagination) {
46426
+ return pagination?.totalCount ?? pagination?.total ?? void 0;
46427
+ }
46428
+ function buildListParams(options, extra) {
46429
+ const params = {};
46430
+ if (options.limit) params.limit = options.limit;
46431
+ if (options.cursor) params.cursor = options.cursor;
46432
+ if (extra) {
46433
+ for (const [key, value] of Object.entries(extra)) {
46434
+ if (value !== void 0) params[key] = value;
46435
+ }
46436
+ }
46437
+ return params;
46438
+ }
46439
+ function printNextCursorHint(commandPath, pagination) {
46440
+ const hasMore = pagination?.hasMore ?? pagination?.hasNextPage ?? false;
46441
+ const nextCursor = pagination?.nextCursor !== void 0 ? pagination.nextCursor : pagination?.cursor ?? null;
46442
+ if (!hasMore || !nextCursor) return;
46443
+ console.log(
46444
+ chalk.yellow("\n More results available \u2014 fetch the next page with:") + chalk.dim(`
46445
+ ${commandPath} --cursor ${nextCursor}`)
46446
+ );
46447
+ }
46448
+
46449
+ // src/commands/auth-agent.ts
46450
+ var VERIFY_NEXT = "Ask the user for the 6-digit code from their email, then run: runtype auth verify <code>";
46451
+ function claimNext(email3) {
46452
+ return email3 ? `Send the verification email with: runtype auth claim ${email3}` : "Ask the user for their email address, then run: runtype auth claim <email>";
46453
+ }
46454
+ function fail(payload) {
46455
+ console.error(JSON.stringify(payload, null, 2));
46456
+ process.exit(1);
46457
+ }
46458
+ function failFromError(error51, fallbackNext) {
46459
+ if (error51 instanceof AgentAuthError) {
46460
+ fail({
46461
+ status: "error",
46462
+ code: error51.code,
46463
+ error: error51.message,
46464
+ ...error51.hint || fallbackNext ? { next: error51.hint || fallbackNext } : {}
46465
+ });
46466
+ }
46467
+ const message = error51 instanceof Error ? error51.message : String(error51);
46468
+ fail({ status: "error", error: message, ...fallbackNext ? { next: fallbackNext } : {} });
46469
+ }
46470
+ async function fetchIdentity(apiKey, apiUrl) {
46471
+ const client = createCliClient(apiKey, apiUrl);
46472
+ const profile = await client.get(
46473
+ "/users/profile"
46474
+ );
46475
+ return {
46476
+ userId: typeof profile.id === "string" ? profile.id : "unknown",
46477
+ orgId: typeof profile.orgId === "string" ? profile.orgId : void 0,
46478
+ email: typeof profile.email === "string" ? profile.email : void 0
46479
+ };
46480
+ }
46481
+ async function registerAction(options) {
46482
+ const store = new CredentialStore();
46483
+ const apiUrl = options.apiUrl || getApiUrl();
46484
+ const existingClaim = await store.getClaimState();
46485
+ const hasCredentials = await store.hasCredentials();
46486
+ if (existingClaim && !options.force) {
46487
+ const resumeDeadline = existingClaim.claimTokenExpires ?? existingClaim.credentialExpires;
46488
+ const expired = resumeDeadline && new Date(resumeDeadline) < /* @__PURE__ */ new Date();
46489
+ if (!expired) {
46490
+ const payload = {
46491
+ status: "registration_pending",
46492
+ registrationId: existingClaim.registrationId,
46493
+ credentialExpires: existingClaim.credentialExpires,
46494
+ next: existingClaim.claimEmail ? VERIFY_NEXT : claimNext(options.email)
46495
+ };
46496
+ if (existingClaim.claimEmail) payload.claimEmail = existingClaim.claimEmail;
46497
+ printJson(payload);
46498
+ return;
46499
+ }
46500
+ }
46501
+ if (hasCredentials && !existingClaim && !options.force) {
46502
+ fail({
46503
+ status: "error",
46504
+ code: "already_authenticated",
46505
+ error: "Credentials are already stored for a claimed account.",
46506
+ next: "Run: runtype auth whoami --no-tty to confirm, or runtype auth logout first if the user wants a different account."
46507
+ });
46508
+ }
46509
+ let registration;
46510
+ try {
46511
+ registration = await registerAnonymousAgent(apiUrl);
46512
+ } catch (error51) {
46513
+ failFromError(error51, "Retry: runtype auth register");
46514
+ }
46515
+ let identity = { userId: "unknown" };
46516
+ try {
46517
+ identity = await fetchIdentity(registration.credential, apiUrl);
46518
+ } catch {
46519
+ }
46520
+ const claimState = {
46521
+ claimToken: registration.claimToken,
46522
+ registrationId: registration.registrationId,
46523
+ credentialExpires: registration.credentialExpires,
46524
+ claimTokenExpires: registration.claimTokenExpires,
46525
+ apiUrl
46526
+ };
46527
+ await store.saveCredentials(
46528
+ {
46529
+ apiKey: registration.credential,
46530
+ userId: identity.userId,
46531
+ orgId: identity.orgId,
46532
+ apiUrl
46533
+ },
46534
+ claimState
46535
+ );
46536
+ const base = {
46537
+ status: "registered",
46538
+ accountState: "temporary",
46539
+ registrationId: registration.registrationId,
46540
+ userId: identity.userId,
46541
+ credentialExpires: registration.credentialExpires,
46542
+ note: "A temporary account is active: CLI commands work now with limited models and quota. Verifying an email upgrades it to a full account and replaces the stored credential automatically."
46543
+ };
46544
+ if (options.email) {
46545
+ try {
46546
+ const claim = await initiateClaim(registration.claimToken, options.email, apiUrl);
46547
+ await store.saveClaimState({ ...claimState, claimEmail: options.email });
46548
+ printJson({
46549
+ ...base,
46550
+ status: "otp_sent",
46551
+ claimEmail: options.email,
46552
+ otpExpiresAt: claim.otpExpiresAt,
46553
+ next: VERIFY_NEXT
46554
+ });
46555
+ return;
46556
+ } catch (error51) {
46557
+ if (error51 instanceof AgentAuthError) {
46558
+ fail({
46559
+ ...base,
46560
+ status: "registered_claim_failed",
46561
+ code: error51.code,
46562
+ error: error51.message,
46563
+ next: error51.hint || claimNext(options.email)
46564
+ });
46565
+ }
46566
+ throw error51;
46567
+ }
46568
+ }
46569
+ printJson({ ...base, next: claimNext() });
46570
+ }
46571
+ async function claimAction(email3, options) {
46572
+ const store = new CredentialStore();
46573
+ const claimState = await store.getClaimState();
46574
+ const apiUrl = options.apiUrl || claimState?.apiUrl || (await store.getCredentials())?.apiUrl || getApiUrl();
46575
+ if (!claimState) {
46576
+ const hasCredentials = await store.hasCredentials();
46577
+ fail({
46578
+ status: "error",
46579
+ code: "no_pending_registration",
46580
+ error: hasCredentials ? "Stored credentials are already claimed; there is nothing to claim." : "No registration found.",
46581
+ next: hasCredentials ? "Run: runtype auth whoami --no-tty" : `Run: runtype auth register --email ${email3}`
46582
+ });
46583
+ }
46584
+ try {
46585
+ const claim = await initiateClaim(claimState.claimToken, email3, apiUrl);
46586
+ await store.saveClaimState({ ...claimState, claimEmail: email3 });
46587
+ printJson({
46588
+ status: "otp_sent",
46589
+ registrationId: claim.registrationId,
46590
+ claimEmail: email3,
46591
+ otpExpiresAt: claim.otpExpiresAt,
46592
+ next: VERIFY_NEXT
46593
+ });
46594
+ } catch (error51) {
46595
+ failFromError(error51, `Retry: runtype auth claim ${email3}`);
46596
+ }
46597
+ }
46598
+ async function verifyAction(code, options) {
46599
+ const store = new CredentialStore();
46600
+ const claimState = await store.getClaimState();
46601
+ const apiUrl = options.apiUrl || claimState?.apiUrl || (await store.getCredentials())?.apiUrl || getApiUrl();
46602
+ if (!claimState) {
46603
+ fail({
46604
+ status: "error",
46605
+ code: "no_pending_registration",
46606
+ error: "No claim in progress.",
46607
+ next: "Run: runtype auth register --email <email> first."
46608
+ });
46609
+ }
46610
+ if (!claimState.claimEmail) {
46611
+ fail({
46612
+ status: "error",
46613
+ code: "claim_not_initiated",
46614
+ error: "No verification email has been sent yet, so there is no code to verify.",
46615
+ next: claimNext()
46616
+ });
46617
+ }
46618
+ let completed;
46619
+ try {
46620
+ completed = await completeClaim(claimState.claimToken, code.trim(), apiUrl);
46621
+ } catch (error51) {
46622
+ failFromError(error51);
46623
+ }
46624
+ let identity = { userId: "unknown" };
46625
+ try {
46626
+ identity = await fetchIdentity(completed.credential, apiUrl);
46627
+ } catch {
46628
+ }
46629
+ await store.saveCredentials({
46630
+ apiKey: completed.credential,
46631
+ userId: identity.userId,
46632
+ orgId: identity.orgId,
46633
+ apiUrl
46634
+ });
46635
+ await store.clearClaimState();
46636
+ printJson({
46637
+ status: "authenticated",
46638
+ accountState: "full",
46639
+ registrationId: completed.registrationId,
46640
+ userId: identity.userId,
46641
+ email: identity.email ?? claimState.claimEmail,
46642
+ note: "The account is fully activated and the stored credential was rotated. All limits from the temporary account are removed.",
46643
+ next: "Continue with setup: runtype install-mcp (then confirm with: runtype auth whoami --no-tty)"
46644
+ });
46645
+ }
46646
+ function registerAgentAuthCommands(authCommand2) {
46647
+ authCommand2.command("register").description("Create a Runtype account without a browser (agent signup, step 1 of 2)").option("--email <email>", "Also send the verification email in the same step").option("--force", "Register a new account even if one is stored or pending").option("--api-url <url>", "Custom API URL").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(registerAction);
46648
+ authCommand2.command("claim <email>").description("Email the user a 6-digit code to attach the account to them").option("--api-url <url>", "Custom API URL").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(claimAction);
46649
+ authCommand2.command("verify <code>").description("Complete signup with the 6-digit code from the verification email (step 2 of 2)").option("--api-url <url>", "Custom API URL").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(verifyAction);
46650
+ }
46651
+
46652
+ // src/commands/auth.ts
46653
+ var authCommand = new Command("auth").description("Manage authentication");
46654
+ var NON_TTY_LOGIN_GUIDANCE = {
46655
+ status: "error",
46656
+ code: "tty_required",
46657
+ error: "Browser login needs an interactive terminal, which this environment does not have.",
46658
+ next: [
46659
+ "New account (no browser needed): ask the user for their email, then run: runtype auth register --email <email> \u2014 a 6-digit code is emailed to them; finish with: runtype auth verify <code>",
46660
+ "Existing account: ask the user for an API key from the dashboard (Settings > API keys), then run: runtype auth login --api-key <key>"
46661
+ ]
46662
+ };
46663
+ function refuseForcedTtyLogin(options) {
46664
+ if (options.tty === true && process.stdout.isTTY !== true) {
46191
46665
  console.error(
46192
46666
  JSON.stringify(
46193
46667
  {
46194
- status: "error",
46195
- error: "Browser login requires TTY mode. Use --api-key with auth login instead."
46668
+ ...NON_TTY_LOGIN_GUIDANCE,
46669
+ error: "Browser login needs a real interactive terminal. Forcing --tty from a non-terminal only changes output formatting; the browser OAuth callback still cannot complete here, so the command would hang."
46196
46670
  },
46197
46671
  null,
46198
46672
  2
@@ -46200,6 +46674,22 @@ authCommand.command("signup").description('Create a new Runtype account (alias f
46200
46674
  );
46201
46675
  process.exit(1);
46202
46676
  }
46677
+ return false;
46678
+ }
46679
+ async function getBillingInfo(apiKey, apiUrl) {
46680
+ try {
46681
+ const client = createCliClient(apiKey, apiUrl);
46682
+ return await client.get("/billing/status");
46683
+ } catch {
46684
+ return null;
46685
+ }
46686
+ }
46687
+ authCommand.command("signup").description('Create a new Runtype account (alias for "auth login")').option("--api-url <url>", "Custom API URL").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (options) => {
46688
+ refuseForcedTtyLogin(options);
46689
+ if (!isTTY(options)) {
46690
+ console.error(JSON.stringify(NON_TTY_LOGIN_GUIDANCE, null, 2));
46691
+ process.exit(1);
46692
+ }
46203
46693
  const App = () => {
46204
46694
  const [loading, setLoading] = useState2(true);
46205
46695
  const [loadingLabel, setLoadingLabel] = useState2("Opening browser for authentication...");
@@ -46272,7 +46762,11 @@ async function loginAction(options) {
46272
46762
  });
46273
46763
  console.log(
46274
46764
  JSON.stringify(
46275
- { status: "authenticated", userId: userData.userId },
46765
+ {
46766
+ status: "authenticated",
46767
+ userId: userData.userId,
46768
+ next: "Continue with setup: runtype install-mcp (then confirm with: runtype auth whoami)"
46769
+ },
46276
46770
  null,
46277
46771
  2
46278
46772
  )
@@ -46330,14 +46824,9 @@ async function loginAction(options) {
46330
46824
  await waitUntilExit2();
46331
46825
  return;
46332
46826
  }
46827
+ refuseForcedTtyLogin(options);
46333
46828
  if (!isTTY(options)) {
46334
- console.error(
46335
- JSON.stringify(
46336
- { status: "error", error: "Browser login requires TTY mode. Use --api-key instead." },
46337
- null,
46338
- 2
46339
- )
46340
- );
46829
+ console.error(JSON.stringify(NON_TTY_LOGIN_GUIDANCE, null, 2));
46341
46830
  process.exit(1);
46342
46831
  }
46343
46832
  const App = () => {
@@ -46402,17 +46891,34 @@ authCommand.command("status").description("Show authentication status").option("
46402
46891
  try {
46403
46892
  const hasCredentials = await store.hasCredentials();
46404
46893
  if (!hasCredentials) {
46405
- console.log(JSON.stringify({ status: "unauthenticated" }, null, 2));
46894
+ console.log(
46895
+ JSON.stringify(
46896
+ {
46897
+ status: "unauthenticated",
46898
+ next: "New account: runtype auth register --email <email>. Existing account: runtype auth login --api-key <key>."
46899
+ },
46900
+ null,
46901
+ 2
46902
+ )
46903
+ );
46406
46904
  return;
46407
46905
  }
46408
46906
  const credentials = await store.getCredentials();
46907
+ const claimState = await store.getClaimState();
46409
46908
  console.log(
46410
46909
  JSON.stringify(
46411
46910
  {
46412
46911
  status: "authenticated",
46912
+ accountState: claimState ? "temporary" : "full",
46413
46913
  userId: credentials?.userId,
46414
46914
  apiUrl: credentials?.apiUrl || "default",
46415
- orgId: credentials?.orgId || null
46915
+ orgId: credentials?.orgId || null,
46916
+ ...claimState ? {
46917
+ registrationId: claimState.registrationId,
46918
+ credentialExpires: claimState.credentialExpires,
46919
+ claimEmail: claimState.claimEmail ?? null,
46920
+ next: claimState.claimEmail ? "Finish signup: ask the user for the 6-digit code from their email, then run: runtype auth verify <code>" : "Finish signup: ask the user for their email, then run: runtype auth claim <email>"
46921
+ } : {}
46416
46922
  },
46417
46923
  null,
46418
46924
  2
@@ -46495,6 +47001,7 @@ authCommand.command("export-key").description("Print the stored API key to stdou
46495
47001
  }
46496
47002
  process.stdout.write(apiKey);
46497
47003
  });
47004
+ registerAgentAuthCommands(authCommand);
46498
47005
  authCommand.command("logout").description("Remove stored credentials").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (options) => {
46499
47006
  const store = new CredentialStore();
46500
47007
  if (!isTTY(options)) {
@@ -46557,7 +47064,7 @@ authCommand.command("whoami").description("Display current user information").op
46557
47064
  if (!isTTY(options) || options.json) {
46558
47065
  console.log(JSON.stringify({ status: "unauthenticated" }, null, 2));
46559
47066
  } else {
46560
- console.log(chalk.yellow("Not logged in"));
47067
+ console.log(chalk2.yellow("Not logged in"));
46561
47068
  console.log("Run: runtype auth login");
46562
47069
  }
46563
47070
  return;
@@ -46649,17 +47156,17 @@ import { useState as useState3, useEffect as useEffect4 } from "react";
46649
47156
 
46650
47157
  // src/lib/file-utils.ts
46651
47158
  import { readFileSync as readFileSync3 } from "fs";
46652
- import chalk2 from "chalk";
47159
+ import chalk3 from "chalk";
46653
47160
  function parseJsonObject(raw, label) {
46654
47161
  let parsed;
46655
47162
  try {
46656
47163
  parsed = JSON.parse(raw);
46657
47164
  } catch {
46658
- console.error(chalk2.red(`Invalid JSON in ${label}`));
47165
+ console.error(chalk3.red(`Invalid JSON in ${label}`));
46659
47166
  process.exit(1);
46660
47167
  }
46661
47168
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
46662
- console.error(chalk2.red(`${label} must be a JSON object`));
47169
+ console.error(chalk3.red(`${label} must be a JSON object`));
46663
47170
  process.exit(1);
46664
47171
  }
46665
47172
  return parsed;
@@ -46670,7 +47177,7 @@ function readJsonFile(path20, opts = {}) {
46670
47177
  try {
46671
47178
  raw = readFileSync3(path20, "utf-8");
46672
47179
  } catch {
46673
- console.error(chalk2.red(`Failed to read ${label}: ${path20}`));
47180
+ console.error(chalk3.red(`Failed to read ${label}: ${path20}`));
46674
47181
  process.exit(1);
46675
47182
  }
46676
47183
  const obj = parseJsonObject(raw, label);
@@ -46684,11 +47191,11 @@ function readJsonFile(path20, opts = {}) {
46684
47191
  }
46685
47192
  function resolveJsonPayload(inline, fromFile, label) {
46686
47193
  if (inline !== void 0 && fromFile !== void 0) {
46687
- console.error(chalk2.red(`Error: provide only one of inline JSON or --from-file for ${label}`));
47194
+ console.error(chalk3.red(`Error: provide only one of inline JSON or --from-file for ${label}`));
46688
47195
  process.exit(1);
46689
47196
  }
46690
47197
  if (inline === void 0 && fromFile === void 0) {
46691
- console.error(chalk2.red(`Error: ${label} is required \u2014 pass inline JSON or --from-file <path>`));
47198
+ console.error(chalk3.red(`Error: ${label} is required \u2014 pass inline JSON or --from-file <path>`));
46692
47199
  process.exit(1);
46693
47200
  }
46694
47201
  if (fromFile !== void 0) {
@@ -46705,7 +47212,7 @@ import {
46705
47212
  // src/lib/ensure-auth.ts
46706
47213
  init_credential_store();
46707
47214
  import readline from "readline";
46708
- import chalk3 from "chalk";
47215
+ import chalk4 from "chalk";
46709
47216
  async function promptConfirm(message, options) {
46710
47217
  const defaultYes = options?.default !== false;
46711
47218
  const hint = defaultYes ? "Y/n" : "y/N";
@@ -46715,7 +47222,7 @@ async function promptConfirm(message, options) {
46715
47222
  terminal: true
46716
47223
  });
46717
47224
  return new Promise((resolve13) => {
46718
- rl.question(chalk3.cyan(`${message} (${hint}): `), (answer) => {
47225
+ rl.question(chalk4.cyan(`${message} (${hint}): `), (answer) => {
46719
47226
  rl.close();
46720
47227
  const trimmed2 = answer.trim().toLowerCase();
46721
47228
  if (trimmed2 === "") {
@@ -46744,9 +47251,9 @@ async function ensureAuth(options) {
46744
47251
  return apiKey;
46745
47252
  }
46746
47253
  if (options?.interactive === false) {
46747
- console.error(chalk3.yellow("\nAuthentication required."));
47254
+ console.error(chalk4.yellow("\nAuthentication required."));
46748
47255
  console.error(
46749
- chalk3.gray(
47256
+ chalk4.gray(
46750
47257
  'Run "runtype auth login" first or set RUNTYPE_API_KEY before reading command input from stdin.'
46751
47258
  )
46752
47259
  );
@@ -46755,10 +47262,10 @@ async function ensureAuth(options) {
46755
47262
  }
46756
47263
  return null;
46757
47264
  }
46758
- console.log(chalk3.yellow("\nAuthentication required."));
47265
+ console.log(chalk4.yellow("\nAuthentication required."));
46759
47266
  const shouldLogin = await promptConfirm("Would you like to log in now?", { default: true });
46760
47267
  if (!shouldLogin) {
46761
- console.log(chalk3.gray(`Run "runtype auth login" when you're ready.`));
47268
+ console.log(chalk4.gray(`Run "runtype auth login" when you're ready.`));
46762
47269
  if (options?.exitOnFailure !== false) {
46763
47270
  process.exit(1);
46764
47271
  }
@@ -46767,100 +47274,37 @@ async function ensureAuth(options) {
46767
47274
  return handleBrowserLogin(store, options?.apiUrl);
46768
47275
  }
46769
47276
  async function handleBrowserLogin(store, apiUrl) {
46770
- console.log(chalk3.gray("Opening browser for authentication..."));
47277
+ console.log(chalk4.gray("Opening browser for authentication..."));
46771
47278
  try {
46772
47279
  const oauth = new OAuthManager({
46773
47280
  clerkPublishableKey: getClerkPublishableKey(),
46774
47281
  dashboardUrl: getDashboardUrl()
46775
47282
  });
46776
- console.log(chalk3.gray("Waiting for browser authentication..."));
47283
+ console.log(chalk4.gray("Waiting for browser authentication..."));
46777
47284
  const sessionToken = await oauth.authenticate("sign-in");
46778
- console.log(chalk3.gray("Getting API key..."));
47285
+ console.log(chalk4.gray("Getting API key..."));
46779
47286
  const apiKeyManager = new ApiKeyManager();
46780
47287
  const { key, userId, orgId } = await apiKeyManager.exchangeSessionForApiKey(
46781
47288
  sessionToken,
46782
47289
  apiUrl || getApiUrl()
46783
47290
  );
46784
- console.log(chalk3.gray("Storing credentials..."));
47291
+ console.log(chalk4.gray("Storing credentials..."));
46785
47292
  await store.saveCredentials({
46786
47293
  apiKey: key,
46787
47294
  userId,
46788
47295
  orgId,
46789
47296
  apiUrl: apiUrl || getApiUrl()
46790
47297
  });
46791
- console.log(chalk3.green("Logged in successfully!"));
47298
+ console.log(chalk4.green("Logged in successfully!"));
46792
47299
  return key;
46793
47300
  } catch (error51) {
46794
47301
  const message = error51 instanceof Error ? error51.message : "Unknown error";
46795
- console.log(chalk3.red("Login failed"));
46796
- console.error(chalk3.red(message));
47302
+ console.log(chalk4.red("Login failed"));
47303
+ console.error(chalk4.red(message));
46797
47304
  return null;
46798
47305
  }
46799
47306
  }
46800
47307
 
46801
- // src/lib/output.ts
46802
- import chalk4 from "chalk";
46803
- function printJson(data) {
46804
- console.log(JSON.stringify(data, null, 2));
46805
- }
46806
- function printList(items, options) {
46807
- console.log(chalk4.cyan(`${options.title}:`));
46808
- if (items.length === 0) {
46809
- console.log(chalk4.gray(` ${options.emptyMessage || "None found"}`));
46810
- return;
46811
- }
46812
- for (const item of items) {
46813
- const parts = [];
46814
- for (const col of options.columns) {
46815
- const value = item[col.key];
46816
- if (value === void 0 || value === null) continue;
46817
- const str = String(value);
46818
- const colorFn = col.color ? chalk4[col.color] : (s) => s;
46819
- if (col.label) {
46820
- parts.push(`${col.label}: ${colorFn(str)}`);
46821
- } else {
46822
- parts.push(colorFn(str));
46823
- }
46824
- }
46825
- console.log(` ${parts.join(" - ")}`);
46826
- }
46827
- if (options.total !== void 0) {
46828
- console.log(chalk4.dim(`
46829
- Total: ${options.total}`));
46830
- }
46831
- }
46832
- function printDetail(title, fields) {
46833
- console.log(chalk4.cyan(`${title}:`));
46834
- for (const field of fields) {
46835
- if (field.value === void 0 || field.value === null) continue;
46836
- const str = typeof field.value === "object" ? JSON.stringify(field.value) : String(field.value);
46837
- console.log(` ${chalk4.gray(field.label + ":")} ${str}`);
46838
- }
46839
- }
46840
- function getTotalCount(pagination) {
46841
- return pagination?.totalCount ?? pagination?.total ?? void 0;
46842
- }
46843
- function buildListParams(options, extra) {
46844
- const params = {};
46845
- if (options.limit) params.limit = options.limit;
46846
- if (options.cursor) params.cursor = options.cursor;
46847
- if (extra) {
46848
- for (const [key, value] of Object.entries(extra)) {
46849
- if (value !== void 0) params[key] = value;
46850
- }
46851
- }
46852
- return params;
46853
- }
46854
- function printNextCursorHint(commandPath, pagination) {
46855
- const hasMore = pagination?.hasMore ?? pagination?.hasNextPage ?? false;
46856
- const nextCursor = pagination?.nextCursor !== void 0 ? pagination.nextCursor : pagination?.cursor ?? null;
46857
- if (!hasMore || !nextCursor) return;
46858
- console.log(
46859
- chalk4.yellow("\n More results available \u2014 fetch the next page with:") + chalk4.dim(`
46860
- ${commandPath} --cursor ${nextCursor}`)
46861
- );
46862
- }
46863
-
46864
47308
  // src/commands/flows.ts
46865
47309
  function flowsListParams(options) {
46866
47310
  return {
@@ -72832,32 +73276,54 @@ import chalk42 from "chalk";
72832
73276
  // ../shared/dist/agent-onboarding-content.mjs
72833
73277
  var AGENT_ONBOARDING_MARKDOWN = `# Runtype for Coding Agents
72834
73278
 
72835
- You are reading the setup script served by \`https://runtype.ai\`. Your durable goal is to leave this coding harness connected to Runtype's hosted MCP server with browser OAuth. Use the Runtype CLI as a bridge during this first session; do not treat it as the final integration.
73279
+ You are reading the setup script served by \`https://runtype.ai\`. Your durable goal is to leave this coding harness connected to Runtype's hosted MCP server. Use the Runtype CLI as a bridge during this first session; do not treat it as the final integration.
73280
+
73281
+ Signup never requires a browser. The only things you may ask the user for are their email address and the 6-digit code Runtype emails them. Do not tell the user to open a dashboard, sign-in page, or OAuth window during signup.
72836
73282
 
72837
73283
  ## Golden path \u2014 do exactly this
72838
73284
 
72839
73285
  If the live Runtype MCP tools are already available in this session, skip setup and go to "After MCP is active" below. Otherwise follow every step in order:
72840
73286
 
72841
- ### 1. Install and authenticate the CLI
73287
+ ### 1. Install the CLI and check auth
72842
73288
 
72843
73289
  \`\`\`bash
72844
73290
  npm install -g @runtypelabs/cli
72845
- runtype auth login
73291
+ runtype auth status
72846
73292
  \`\`\`
72847
73293
 
72848
- Complete the browser sign-in when it opens. The CLI makes this first session productive even though a running coding harness often cannot discover a newly installed MCP connection until it restarts.
73294
+ If status reports \`authenticated\` with \`accountState: "full"\`, skip to step 3. If it reports a pending signup, its \`next\` field tells you the exact command to resume with.
72849
73295
 
72850
- ### 2. Install skills and the OAuth MCP connection
73296
+ ### 2. Sign the user up from the terminal
73297
+
73298
+ Ask the user one question: "What email address should I use for your Runtype account?" Then run:
72851
73299
 
72852
73300
  \`\`\`bash
72853
- runtype install-mcp
73301
+ runtype auth register --email <their-email>
72854
73302
  \`\`\`
72855
73303
 
72856
- This command detects the current coding harness, installs the Runtype agent skills for it, writes the user-level hosted MCP configuration, and starts the harness's own OAuth flow when possible. If it asks which harness to configure, select the harness running this session. If a browser opens, have the user complete OAuth.
73304
+ This creates the account and emails the user a 6-digit code. Ask the user: "Runtype emailed a 6-digit code to <their-email> \u2014 what is it?" Then run:
73305
+
73306
+ \`\`\`bash
73307
+ runtype auth verify <code>
73308
+ \`\`\`
72857
73309
 
72858
- Do not manually create an API key, export the CLI key into MCP, or choose a different setup path merely to avoid a restart. CLI authentication and MCP OAuth are separate on purpose: the CLI is the first-session bridge; client-owned OAuth MCP is the durable connection.
73310
+ That completes signup. Every command in this flow prints JSON with a \`next\` field \u2014 follow it. Notes:
72859
73311
 
72860
- ### 3. Keep working in this session with the CLI
73312
+ - These commands work without a TTY and never open a browser.
73313
+ - \`register\` is safe to re-run; it resumes a pending signup instead of creating a duplicate account.
73314
+ - After \`register\` (before \`verify\`) you already hold a temporary credential: CLI commands work with restricted models and quota, and \`verify\` upgrades the same stored login in place. Do not build anything substantial until \`verify\` succeeds.
73315
+ - If the email already has a Runtype account, the command reports \`email_already_registered\`. In that case ask the user for an API key from their dashboard (Settings > API keys) and run \`runtype auth login --api-key <key>\` instead. This is the only path where the user touches the dashboard, and only because they already have an account.
73316
+ - A wrong code returns \`otp_invalid\` \u2014 ask the user to re-check and run \`runtype auth verify\` again. An expired code returns \`otp_expired\` \u2014 re-send with \`runtype auth claim <email>\`. Codes expire after 10 minutes and lock after 5 wrong attempts, so confirm the code with the user rather than guessing; after a lockout or expiry, re-send with \`runtype auth claim <email>\`.
73317
+
73318
+ ### 3. Install skills and the MCP connection
73319
+
73320
+ \`\`\`bash
73321
+ runtype install-mcp
73322
+ \`\`\`
73323
+
73324
+ This command detects the current coding harness, installs the Runtype agent skills for it, and writes the user-level hosted MCP configuration. If it asks which harness to configure, select the harness running this session. If it reports an OAuth action for the harness's own MCP connection, relay that instruction to the user for later \u2014 it happens on the harness's schedule, not during signup. That harness-side OAuth may open a browser when the user runs it; that is fine and expected \u2014 the no-browser rule above applies to Runtype signup, not to the harness's own connection ceremony.
73325
+
73326
+ ### 4. Keep working in this session with the CLI
72861
73327
 
72862
73328
  Do not stop just because the new MCP connection is not visible yet. Use CLI commands for useful first-session work:
72863
73329
 
@@ -72869,19 +73335,19 @@ runtype products list
72869
73335
  runtype --help
72870
73336
  \`\`\`
72871
73337
 
72872
- The CLI also supports dispatch, records, validation, product setup, and other account operations. Read command help before mutating resources, and report concrete IDs and URLs to the user.
73338
+ The CLI also supports dispatch, records, validation, product setup, and other account operations. Read command help before mutating resources, and report concrete IDs and URLs to the user. If you need Runtype MCP tools before the restart, the CLI bridges them: \`runtype mcp tools\` lists them and \`runtype mcp call <tool>\` invokes one \u2014 same hosted tools, no restart needed.
72873
73339
 
72874
- ### 4. Tell the user to restart or reload the harness
73340
+ ### 5. Tell the user to restart or reload the harness
72875
73341
 
72876
- Before finishing this session, read the final status and next steps from \`runtype install-mcp\`. Complete any OAuth action it requests and retry any failed skill installation. Do not call setup complete while the command reports \`action-required\`, \`partial\`, or an error.
73342
+ Before finishing this session, read the final status and next steps from \`runtype install-mcp\`. Retry any failed skill installation. Do not call setup complete while the command reports \`action-required\`, \`partial\`, or an error.
72877
73343
 
72878
- Only after skills and OAuth both succeed, tell the user exactly what changed and ask them to restart or reload the coding harness. Use direct language such as:
73344
+ Only after signup is verified and skills are installed, tell the user exactly what changed and ask them to restart or reload the coding harness. Use direct language such as:
72879
73345
 
72880
- > Runtype skills and the OAuth MCP connection are installed. Please restart or reload this coding agent (or start a new session) so it can activate the new MCP tools. I used the CLI to keep this first session productive; after restart I will use MCP by default.
73346
+ > Your Runtype account is verified and the skills plus MCP connection are installed. Please restart or reload this coding agent (or start a new session) so it can activate the new MCP tools. I used the CLI to keep this first session productive; after restart I will use MCP by default.
72881
73347
 
72882
73348
  If an action remains, report that exact action instead of using the success wording above. Do not claim the running agent can use MCP until its Runtype tools actually appear.
72883
73349
 
72884
- ### 5. Switch to MCP after restart
73350
+ ### 6. Switch to MCP after restart
72885
73351
 
72886
73352
  In the next session, use Runtype MCP first. Do not fall back to the CLI just because it worked previously. Start by calling:
72887
73353
 
@@ -72907,13 +73373,21 @@ runtype install-mcp --agent opencode
72907
73373
  runtype install-mcp --agent vscode
72908
73374
  \`\`\`
72909
73375
 
72910
- Use only the line for the current harness. Follow the command's OAuth and restart instructions. Use \`--force\` only when the command reports that an existing MCP server named \`runtype\` points to a different URL and the user agrees to replace it.
73376
+ Use only the line for the current harness. Follow the command's restart instructions. Use \`--force\` only when the command reports that an existing MCP server named \`runtype\` points to a different URL and the user agrees to replace it.
73377
+
73378
+ ## Auth reference
73379
+
73380
+ The signup commands wrap the agent auth protocol documented at \`https://runtype.com/auth.md\` (anonymous registration plus an email claim ceremony). You normally never need the raw protocol \u2014 the CLI is the supported client:
72911
73381
 
72912
- ## Headless exception \u2014 not the normal path
73382
+ - \`runtype auth register --email <email>\` \u2014 create the account and send the code (one step)
73383
+ - \`runtype auth claim <email>\` \u2014 re-send or change the email for a pending signup
73384
+ - \`runtype auth verify <code>\` \u2014 finish signup with the 6-digit code
73385
+ - \`runtype auth login --api-key <key>\` \u2014 existing accounts only
73386
+ - \`runtype auth status\` / \`runtype auth whoami\` \u2014 inspect the current login
72913
73387
 
72914
- Only use an API key when browser OAuth is genuinely impossible in the user's environment, not merely inconvenient. Ask the user before switching authentication modes, then follow the canonical headless registration and claim protocol at \`https://runtype.com/auth.md\`. Never print a secret into the transcript or write a bearer token directly into a committed config file.
73388
+ A user-supplied \`RUNTYPE_API_KEY\` environment variable also authenticates every CLI command without a stored login. Never print a credential into the transcript or write one into a committed file.
72915
73389
 
72916
- The hosted MCP endpoint is \`https://api.runtype.com/v1/mcp/protocol\`. A user-supplied \`RUNTYPE_API_KEY\` can authenticate CLI commands in a headless environment, but return to client-owned OAuth MCP when the harness can support it.
73390
+ Browser login (\`runtype auth login\` with no flags) exists for humans running the CLI in their own interactive terminal. Do not use it from a coding harness.
72917
73391
 
72918
73392
  ## After MCP is active
72919
73393
 
@@ -72932,7 +73406,7 @@ API: [api.runtype.com](https://api.runtype.com)
72932
73406
 
72933
73407
  // src/commands/onboard.ts
72934
73408
  init_credential_store();
72935
- var AUTH_LOGIN_INSTRUCTION = "Sign in to Runtype: run `runtype auth login` (or set RUNTYPE_API_KEY), then re-run `runtype onboard`.";
73409
+ var AUTH_LOGIN_INSTRUCTION = "Sign in to Runtype first. New account: `runtype auth register --email <email>` then `runtype auth verify <code>`. Existing account: `runtype auth login --api-key <key>` (or set RUNTYPE_API_KEY). Then re-run `runtype onboard`.";
72936
73410
  var AFTER_RESTART_STEPS = [
72937
73411
  "Use the Runtype MCP server first for all Runtype work.",
72938
73412
  "Start with the get_build_instructions tool before creating or changing resources.",
@@ -73217,7 +73691,7 @@ function collectBundleFiles(dir) {
73217
73691
  walk(dir);
73218
73692
  return zippable;
73219
73693
  }
73220
- function fail(message, jsonMode, code) {
73694
+ function fail2(message, jsonMode, code) {
73221
73695
  if (jsonMode) {
73222
73696
  printJson({ status: "error", error: message, code });
73223
73697
  } else {
@@ -73256,7 +73730,7 @@ appsCommand.command("list").description("List your apps").option("--json", "Stru
73256
73730
  console.log(` ${app.url} ${status} ${app.visibility}${serving}`);
73257
73731
  }
73258
73732
  } catch (error51) {
73259
- fail(
73733
+ fail2(
73260
73734
  `Failed to list apps: ${error51 instanceof Error ? error51.message : String(error51)}`,
73261
73735
  jsonMode,
73262
73736
  "LIST_FAILED"
@@ -73285,7 +73759,7 @@ appsCommand.command("create").description("Create an app (the hostname is {slug}
73285
73759
  console.log(` ${app.url}`);
73286
73760
  console.log(chalk43.dim(" Deploy a bundle with `runtype apps deploy <dir> --app " + app.id + "`"));
73287
73761
  } catch (error51) {
73288
- fail(
73762
+ fail2(
73289
73763
  `Failed to create app: ${error51 instanceof Error ? error51.message : String(error51)}`,
73290
73764
  jsonMode,
73291
73765
  "CREATE_FAILED"
@@ -73300,27 +73774,27 @@ appsCommand.command("deploy <dir>").description("Zip a bundle directory, upload
73300
73774
  const apiKey = await ensureAuth();
73301
73775
  if (!apiKey) return;
73302
73776
  if (!existsSync17(dir) || !statSync7(dir).isDirectory()) {
73303
- fail(`Not a directory: ${dir}`, jsonMode, "INVALID_DIR");
73777
+ fail2(`Not a directory: ${dir}`, jsonMode, "INVALID_DIR");
73304
73778
  }
73305
73779
  let bundleFiles = {};
73306
73780
  try {
73307
73781
  bundleFiles = collectBundleFiles(dir);
73308
73782
  } catch (error51) {
73309
- fail(
73783
+ fail2(
73310
73784
  `Failed to read ${dir}: ${error51 instanceof Error ? error51.message : String(error51)}`,
73311
73785
  jsonMode,
73312
73786
  "INVALID_DIR"
73313
73787
  );
73314
73788
  }
73315
73789
  if (!bundleFiles["index.html"]) {
73316
- fail(
73790
+ fail2(
73317
73791
  `${dir} is missing index.html at its root (symlinked files are not bundled).`,
73318
73792
  jsonMode,
73319
73793
  "MISSING_INDEX"
73320
73794
  );
73321
73795
  }
73322
73796
  if (!bundleFiles[MANIFEST_FILENAME]) {
73323
- fail(
73797
+ fail2(
73324
73798
  `${dir} is missing ${MANIFEST_FILENAME} at its root (symlinked files are not bundled). Every app bundle needs a manifest; see the build-runtype-app skill or the Runtype docs.`,
73325
73799
  jsonMode,
73326
73800
  "MISSING_MANIFEST"
@@ -73368,7 +73842,7 @@ appsCommand.command("deploy <dir>").description("Zip a bundle directory, upload
73368
73842
  console.log(chalk43.dim(` Uploaded but not activated. Activate with \`runtype apps activate ${options.app} ${version2.id}\``));
73369
73843
  }
73370
73844
  } catch (error51) {
73371
- fail(
73845
+ fail2(
73372
73846
  `Deploy failed: ${error51 instanceof Error ? error51.message : String(error51)}`,
73373
73847
  jsonMode,
73374
73848
  "DEPLOY_FAILED"
@@ -73399,7 +73873,7 @@ appsCommand.command("versions <appId>").description("List an app's versions, new
73399
73873
  );
73400
73874
  }
73401
73875
  } catch (error51) {
73402
- fail(
73876
+ fail2(
73403
73877
  `Failed to list versions: ${error51 instanceof Error ? error51.message : String(error51)}`,
73404
73878
  jsonMode,
73405
73879
  "VERSIONS_FAILED"
@@ -73421,7 +73895,7 @@ appsCommand.command("activate <appId> <versionId>").description("Activate an upl
73421
73895
  console.log(chalk43.green(`Activated version ${version2.versionNumber}`));
73422
73896
  console.log(` Live at ${chalk43.bold(app.url)}`);
73423
73897
  } catch (error51) {
73424
- fail(
73898
+ fail2(
73425
73899
  `Activation failed: ${error51 instanceof Error ? error51.message : String(error51)}`,
73426
73900
  jsonMode,
73427
73901
  "ACTIVATE_FAILED"
@@ -73442,7 +73916,7 @@ appsCommand.command("delete <appId>").description("Permanently delete an app, it
73442
73916
  }
73443
73917
  console.log(chalk43.green(`Deleted ${appId}`));
73444
73918
  } catch (error51) {
73445
- fail(
73919
+ fail2(
73446
73920
  `Delete failed: ${error51 instanceof Error ? error51.message : String(error51)}`,
73447
73921
  jsonMode,
73448
73922
  "DELETE_FAILED"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runtypelabs/cli",
3
- "version": "2.30.0",
3
+ "version": "2.31.0",
4
4
  "description": "Command-line interface for Runtype AI platform",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -26,7 +26,7 @@
26
26
  "rosie-skills": "0.8.1",
27
27
  "yaml": "^2.9.0",
28
28
  "@runtypelabs/ink-components": "0.3.5",
29
- "@runtypelabs/sdk": "6.1.3",
29
+ "@runtypelabs/sdk": "6.1.4",
30
30
  "@runtypelabs/terminal-animations": "0.2.2"
31
31
  },
32
32
  "devDependencies": {
@@ -41,7 +41,7 @@
41
41
  "tsx": "^4.7.1",
42
42
  "typescript": "^6.0.3",
43
43
  "vitest": "^4.1.0",
44
- "@runtypelabs/shared": "3.4.3"
44
+ "@runtypelabs/shared": "3.5.0"
45
45
  },
46
46
  "engines": {
47
47
  "node": ">=22.12.0"