@runtypelabs/cli 2.30.1 → 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.
- package/dist/index.js +621 -138
- package/package.json +2 -2
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
|
-
|
|
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
|
-
|
|
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
|
|
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";
|
|
@@ -38886,6 +38956,13 @@ var userProfileFeaturesSchema = external_exports.object({
|
|
|
38886
38956
|
// until a rule enables it). Execution and tool listings are gated
|
|
38887
38957
|
// server-side by the same flag.
|
|
38888
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(),
|
|
38889
38966
|
// Gates the workspace-model arms of the dashboard signup / switcher UI
|
|
38890
38967
|
// (docs/features/planning/2026-07-07-workspace-model-migration.md). Driven by
|
|
38891
38968
|
// the `enable-workspace-model` boolean flag (fail-closed in production until a
|
|
@@ -45920,7 +45997,18 @@ var ApiKeyManager = class {
|
|
|
45920
45997
|
try {
|
|
45921
45998
|
data = await client.get("/auth/me");
|
|
45922
45999
|
} catch {
|
|
45923
|
-
|
|
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
|
+
}
|
|
45924
46012
|
}
|
|
45925
46013
|
if (!isAuthMeResponse(data)) {
|
|
45926
46014
|
throw new Error("Invalid user response format");
|
|
@@ -46167,23 +46255,418 @@ function MutationResult({
|
|
|
46167
46255
|
return null;
|
|
46168
46256
|
}
|
|
46169
46257
|
|
|
46170
|
-
// src/commands/auth.ts
|
|
46171
|
-
|
|
46172
|
-
|
|
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;
|
|
46173
46289
|
try {
|
|
46174
|
-
|
|
46175
|
-
|
|
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();
|
|
46176
46310
|
} catch {
|
|
46177
|
-
return null;
|
|
46178
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 };
|
|
46179
46318
|
}
|
|
46180
|
-
|
|
46181
|
-
|
|
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) {
|
|
46182
46665
|
console.error(
|
|
46183
46666
|
JSON.stringify(
|
|
46184
46667
|
{
|
|
46185
|
-
|
|
46186
|
-
error: "Browser login
|
|
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."
|
|
46187
46670
|
},
|
|
46188
46671
|
null,
|
|
46189
46672
|
2
|
|
@@ -46191,6 +46674,22 @@ authCommand.command("signup").description('Create a new Runtype account (alias f
|
|
|
46191
46674
|
);
|
|
46192
46675
|
process.exit(1);
|
|
46193
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
|
+
}
|
|
46194
46693
|
const App = () => {
|
|
46195
46694
|
const [loading, setLoading] = useState2(true);
|
|
46196
46695
|
const [loadingLabel, setLoadingLabel] = useState2("Opening browser for authentication...");
|
|
@@ -46263,7 +46762,11 @@ async function loginAction(options) {
|
|
|
46263
46762
|
});
|
|
46264
46763
|
console.log(
|
|
46265
46764
|
JSON.stringify(
|
|
46266
|
-
{
|
|
46765
|
+
{
|
|
46766
|
+
status: "authenticated",
|
|
46767
|
+
userId: userData.userId,
|
|
46768
|
+
next: "Continue with setup: runtype install-mcp (then confirm with: runtype auth whoami)"
|
|
46769
|
+
},
|
|
46267
46770
|
null,
|
|
46268
46771
|
2
|
|
46269
46772
|
)
|
|
@@ -46321,14 +46824,9 @@ async function loginAction(options) {
|
|
|
46321
46824
|
await waitUntilExit2();
|
|
46322
46825
|
return;
|
|
46323
46826
|
}
|
|
46827
|
+
refuseForcedTtyLogin(options);
|
|
46324
46828
|
if (!isTTY(options)) {
|
|
46325
|
-
console.error(
|
|
46326
|
-
JSON.stringify(
|
|
46327
|
-
{ status: "error", error: "Browser login requires TTY mode. Use --api-key instead." },
|
|
46328
|
-
null,
|
|
46329
|
-
2
|
|
46330
|
-
)
|
|
46331
|
-
);
|
|
46829
|
+
console.error(JSON.stringify(NON_TTY_LOGIN_GUIDANCE, null, 2));
|
|
46332
46830
|
process.exit(1);
|
|
46333
46831
|
}
|
|
46334
46832
|
const App = () => {
|
|
@@ -46393,17 +46891,34 @@ authCommand.command("status").description("Show authentication status").option("
|
|
|
46393
46891
|
try {
|
|
46394
46892
|
const hasCredentials = await store.hasCredentials();
|
|
46395
46893
|
if (!hasCredentials) {
|
|
46396
|
-
console.log(
|
|
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
|
+
);
|
|
46397
46904
|
return;
|
|
46398
46905
|
}
|
|
46399
46906
|
const credentials = await store.getCredentials();
|
|
46907
|
+
const claimState = await store.getClaimState();
|
|
46400
46908
|
console.log(
|
|
46401
46909
|
JSON.stringify(
|
|
46402
46910
|
{
|
|
46403
46911
|
status: "authenticated",
|
|
46912
|
+
accountState: claimState ? "temporary" : "full",
|
|
46404
46913
|
userId: credentials?.userId,
|
|
46405
46914
|
apiUrl: credentials?.apiUrl || "default",
|
|
46406
|
-
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
|
+
} : {}
|
|
46407
46922
|
},
|
|
46408
46923
|
null,
|
|
46409
46924
|
2
|
|
@@ -46486,6 +47001,7 @@ authCommand.command("export-key").description("Print the stored API key to stdou
|
|
|
46486
47001
|
}
|
|
46487
47002
|
process.stdout.write(apiKey);
|
|
46488
47003
|
});
|
|
47004
|
+
registerAgentAuthCommands(authCommand);
|
|
46489
47005
|
authCommand.command("logout").description("Remove stored credentials").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (options) => {
|
|
46490
47006
|
const store = new CredentialStore();
|
|
46491
47007
|
if (!isTTY(options)) {
|
|
@@ -46548,7 +47064,7 @@ authCommand.command("whoami").description("Display current user information").op
|
|
|
46548
47064
|
if (!isTTY(options) || options.json) {
|
|
46549
47065
|
console.log(JSON.stringify({ status: "unauthenticated" }, null, 2));
|
|
46550
47066
|
} else {
|
|
46551
|
-
console.log(
|
|
47067
|
+
console.log(chalk2.yellow("Not logged in"));
|
|
46552
47068
|
console.log("Run: runtype auth login");
|
|
46553
47069
|
}
|
|
46554
47070
|
return;
|
|
@@ -46640,17 +47156,17 @@ import { useState as useState3, useEffect as useEffect4 } from "react";
|
|
|
46640
47156
|
|
|
46641
47157
|
// src/lib/file-utils.ts
|
|
46642
47158
|
import { readFileSync as readFileSync3 } from "fs";
|
|
46643
|
-
import
|
|
47159
|
+
import chalk3 from "chalk";
|
|
46644
47160
|
function parseJsonObject(raw, label) {
|
|
46645
47161
|
let parsed;
|
|
46646
47162
|
try {
|
|
46647
47163
|
parsed = JSON.parse(raw);
|
|
46648
47164
|
} catch {
|
|
46649
|
-
console.error(
|
|
47165
|
+
console.error(chalk3.red(`Invalid JSON in ${label}`));
|
|
46650
47166
|
process.exit(1);
|
|
46651
47167
|
}
|
|
46652
47168
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
46653
|
-
console.error(
|
|
47169
|
+
console.error(chalk3.red(`${label} must be a JSON object`));
|
|
46654
47170
|
process.exit(1);
|
|
46655
47171
|
}
|
|
46656
47172
|
return parsed;
|
|
@@ -46661,7 +47177,7 @@ function readJsonFile(path20, opts = {}) {
|
|
|
46661
47177
|
try {
|
|
46662
47178
|
raw = readFileSync3(path20, "utf-8");
|
|
46663
47179
|
} catch {
|
|
46664
|
-
console.error(
|
|
47180
|
+
console.error(chalk3.red(`Failed to read ${label}: ${path20}`));
|
|
46665
47181
|
process.exit(1);
|
|
46666
47182
|
}
|
|
46667
47183
|
const obj = parseJsonObject(raw, label);
|
|
@@ -46675,11 +47191,11 @@ function readJsonFile(path20, opts = {}) {
|
|
|
46675
47191
|
}
|
|
46676
47192
|
function resolveJsonPayload(inline, fromFile, label) {
|
|
46677
47193
|
if (inline !== void 0 && fromFile !== void 0) {
|
|
46678
|
-
console.error(
|
|
47194
|
+
console.error(chalk3.red(`Error: provide only one of inline JSON or --from-file for ${label}`));
|
|
46679
47195
|
process.exit(1);
|
|
46680
47196
|
}
|
|
46681
47197
|
if (inline === void 0 && fromFile === void 0) {
|
|
46682
|
-
console.error(
|
|
47198
|
+
console.error(chalk3.red(`Error: ${label} is required \u2014 pass inline JSON or --from-file <path>`));
|
|
46683
47199
|
process.exit(1);
|
|
46684
47200
|
}
|
|
46685
47201
|
if (fromFile !== void 0) {
|
|
@@ -46696,7 +47212,7 @@ import {
|
|
|
46696
47212
|
// src/lib/ensure-auth.ts
|
|
46697
47213
|
init_credential_store();
|
|
46698
47214
|
import readline from "readline";
|
|
46699
|
-
import
|
|
47215
|
+
import chalk4 from "chalk";
|
|
46700
47216
|
async function promptConfirm(message, options) {
|
|
46701
47217
|
const defaultYes = options?.default !== false;
|
|
46702
47218
|
const hint = defaultYes ? "Y/n" : "y/N";
|
|
@@ -46706,7 +47222,7 @@ async function promptConfirm(message, options) {
|
|
|
46706
47222
|
terminal: true
|
|
46707
47223
|
});
|
|
46708
47224
|
return new Promise((resolve13) => {
|
|
46709
|
-
rl.question(
|
|
47225
|
+
rl.question(chalk4.cyan(`${message} (${hint}): `), (answer) => {
|
|
46710
47226
|
rl.close();
|
|
46711
47227
|
const trimmed2 = answer.trim().toLowerCase();
|
|
46712
47228
|
if (trimmed2 === "") {
|
|
@@ -46735,9 +47251,9 @@ async function ensureAuth(options) {
|
|
|
46735
47251
|
return apiKey;
|
|
46736
47252
|
}
|
|
46737
47253
|
if (options?.interactive === false) {
|
|
46738
|
-
console.error(
|
|
47254
|
+
console.error(chalk4.yellow("\nAuthentication required."));
|
|
46739
47255
|
console.error(
|
|
46740
|
-
|
|
47256
|
+
chalk4.gray(
|
|
46741
47257
|
'Run "runtype auth login" first or set RUNTYPE_API_KEY before reading command input from stdin.'
|
|
46742
47258
|
)
|
|
46743
47259
|
);
|
|
@@ -46746,10 +47262,10 @@ async function ensureAuth(options) {
|
|
|
46746
47262
|
}
|
|
46747
47263
|
return null;
|
|
46748
47264
|
}
|
|
46749
|
-
console.log(
|
|
47265
|
+
console.log(chalk4.yellow("\nAuthentication required."));
|
|
46750
47266
|
const shouldLogin = await promptConfirm("Would you like to log in now?", { default: true });
|
|
46751
47267
|
if (!shouldLogin) {
|
|
46752
|
-
console.log(
|
|
47268
|
+
console.log(chalk4.gray(`Run "runtype auth login" when you're ready.`));
|
|
46753
47269
|
if (options?.exitOnFailure !== false) {
|
|
46754
47270
|
process.exit(1);
|
|
46755
47271
|
}
|
|
@@ -46758,100 +47274,37 @@ async function ensureAuth(options) {
|
|
|
46758
47274
|
return handleBrowserLogin(store, options?.apiUrl);
|
|
46759
47275
|
}
|
|
46760
47276
|
async function handleBrowserLogin(store, apiUrl) {
|
|
46761
|
-
console.log(
|
|
47277
|
+
console.log(chalk4.gray("Opening browser for authentication..."));
|
|
46762
47278
|
try {
|
|
46763
47279
|
const oauth = new OAuthManager({
|
|
46764
47280
|
clerkPublishableKey: getClerkPublishableKey(),
|
|
46765
47281
|
dashboardUrl: getDashboardUrl()
|
|
46766
47282
|
});
|
|
46767
|
-
console.log(
|
|
47283
|
+
console.log(chalk4.gray("Waiting for browser authentication..."));
|
|
46768
47284
|
const sessionToken = await oauth.authenticate("sign-in");
|
|
46769
|
-
console.log(
|
|
47285
|
+
console.log(chalk4.gray("Getting API key..."));
|
|
46770
47286
|
const apiKeyManager = new ApiKeyManager();
|
|
46771
47287
|
const { key, userId, orgId } = await apiKeyManager.exchangeSessionForApiKey(
|
|
46772
47288
|
sessionToken,
|
|
46773
47289
|
apiUrl || getApiUrl()
|
|
46774
47290
|
);
|
|
46775
|
-
console.log(
|
|
47291
|
+
console.log(chalk4.gray("Storing credentials..."));
|
|
46776
47292
|
await store.saveCredentials({
|
|
46777
47293
|
apiKey: key,
|
|
46778
47294
|
userId,
|
|
46779
47295
|
orgId,
|
|
46780
47296
|
apiUrl: apiUrl || getApiUrl()
|
|
46781
47297
|
});
|
|
46782
|
-
console.log(
|
|
47298
|
+
console.log(chalk4.green("Logged in successfully!"));
|
|
46783
47299
|
return key;
|
|
46784
47300
|
} catch (error51) {
|
|
46785
47301
|
const message = error51 instanceof Error ? error51.message : "Unknown error";
|
|
46786
|
-
console.log(
|
|
46787
|
-
console.error(
|
|
47302
|
+
console.log(chalk4.red("Login failed"));
|
|
47303
|
+
console.error(chalk4.red(message));
|
|
46788
47304
|
return null;
|
|
46789
47305
|
}
|
|
46790
47306
|
}
|
|
46791
47307
|
|
|
46792
|
-
// src/lib/output.ts
|
|
46793
|
-
import chalk4 from "chalk";
|
|
46794
|
-
function printJson(data) {
|
|
46795
|
-
console.log(JSON.stringify(data, null, 2));
|
|
46796
|
-
}
|
|
46797
|
-
function printList(items, options) {
|
|
46798
|
-
console.log(chalk4.cyan(`${options.title}:`));
|
|
46799
|
-
if (items.length === 0) {
|
|
46800
|
-
console.log(chalk4.gray(` ${options.emptyMessage || "None found"}`));
|
|
46801
|
-
return;
|
|
46802
|
-
}
|
|
46803
|
-
for (const item of items) {
|
|
46804
|
-
const parts = [];
|
|
46805
|
-
for (const col of options.columns) {
|
|
46806
|
-
const value = item[col.key];
|
|
46807
|
-
if (value === void 0 || value === null) continue;
|
|
46808
|
-
const str = String(value);
|
|
46809
|
-
const colorFn = col.color ? chalk4[col.color] : (s) => s;
|
|
46810
|
-
if (col.label) {
|
|
46811
|
-
parts.push(`${col.label}: ${colorFn(str)}`);
|
|
46812
|
-
} else {
|
|
46813
|
-
parts.push(colorFn(str));
|
|
46814
|
-
}
|
|
46815
|
-
}
|
|
46816
|
-
console.log(` ${parts.join(" - ")}`);
|
|
46817
|
-
}
|
|
46818
|
-
if (options.total !== void 0) {
|
|
46819
|
-
console.log(chalk4.dim(`
|
|
46820
|
-
Total: ${options.total}`));
|
|
46821
|
-
}
|
|
46822
|
-
}
|
|
46823
|
-
function printDetail(title, fields) {
|
|
46824
|
-
console.log(chalk4.cyan(`${title}:`));
|
|
46825
|
-
for (const field of fields) {
|
|
46826
|
-
if (field.value === void 0 || field.value === null) continue;
|
|
46827
|
-
const str = typeof field.value === "object" ? JSON.stringify(field.value) : String(field.value);
|
|
46828
|
-
console.log(` ${chalk4.gray(field.label + ":")} ${str}`);
|
|
46829
|
-
}
|
|
46830
|
-
}
|
|
46831
|
-
function getTotalCount(pagination) {
|
|
46832
|
-
return pagination?.totalCount ?? pagination?.total ?? void 0;
|
|
46833
|
-
}
|
|
46834
|
-
function buildListParams(options, extra) {
|
|
46835
|
-
const params = {};
|
|
46836
|
-
if (options.limit) params.limit = options.limit;
|
|
46837
|
-
if (options.cursor) params.cursor = options.cursor;
|
|
46838
|
-
if (extra) {
|
|
46839
|
-
for (const [key, value] of Object.entries(extra)) {
|
|
46840
|
-
if (value !== void 0) params[key] = value;
|
|
46841
|
-
}
|
|
46842
|
-
}
|
|
46843
|
-
return params;
|
|
46844
|
-
}
|
|
46845
|
-
function printNextCursorHint(commandPath, pagination) {
|
|
46846
|
-
const hasMore = pagination?.hasMore ?? pagination?.hasNextPage ?? false;
|
|
46847
|
-
const nextCursor = pagination?.nextCursor !== void 0 ? pagination.nextCursor : pagination?.cursor ?? null;
|
|
46848
|
-
if (!hasMore || !nextCursor) return;
|
|
46849
|
-
console.log(
|
|
46850
|
-
chalk4.yellow("\n More results available \u2014 fetch the next page with:") + chalk4.dim(`
|
|
46851
|
-
${commandPath} --cursor ${nextCursor}`)
|
|
46852
|
-
);
|
|
46853
|
-
}
|
|
46854
|
-
|
|
46855
47308
|
// src/commands/flows.ts
|
|
46856
47309
|
function flowsListParams(options) {
|
|
46857
47310
|
return {
|
|
@@ -72823,32 +73276,54 @@ import chalk42 from "chalk";
|
|
|
72823
73276
|
// ../shared/dist/agent-onboarding-content.mjs
|
|
72824
73277
|
var AGENT_ONBOARDING_MARKDOWN = `# Runtype for Coding Agents
|
|
72825
73278
|
|
|
72826
|
-
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
|
|
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.
|
|
72827
73282
|
|
|
72828
73283
|
## Golden path \u2014 do exactly this
|
|
72829
73284
|
|
|
72830
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:
|
|
72831
73286
|
|
|
72832
|
-
### 1. Install and
|
|
73287
|
+
### 1. Install the CLI and check auth
|
|
72833
73288
|
|
|
72834
73289
|
\`\`\`bash
|
|
72835
73290
|
npm install -g @runtypelabs/cli
|
|
72836
|
-
runtype auth
|
|
73291
|
+
runtype auth status
|
|
72837
73292
|
\`\`\`
|
|
72838
73293
|
|
|
72839
|
-
|
|
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.
|
|
73295
|
+
|
|
73296
|
+
### 2. Sign the user up from the terminal
|
|
72840
73297
|
|
|
72841
|
-
|
|
73298
|
+
Ask the user one question: "What email address should I use for your Runtype account?" Then run:
|
|
72842
73299
|
|
|
72843
73300
|
\`\`\`bash
|
|
72844
|
-
runtype
|
|
73301
|
+
runtype auth register --email <their-email>
|
|
73302
|
+
\`\`\`
|
|
73303
|
+
|
|
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>
|
|
72845
73308
|
\`\`\`
|
|
72846
73309
|
|
|
72847
|
-
|
|
73310
|
+
That completes signup. Every command in this flow prints JSON with a \`next\` field \u2014 follow it. Notes:
|
|
73311
|
+
|
|
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>\`.
|
|
72848
73317
|
|
|
72849
|
-
|
|
73318
|
+
### 3. Install skills and the MCP connection
|
|
72850
73319
|
|
|
72851
|
-
|
|
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
|
|
72852
73327
|
|
|
72853
73328
|
Do not stop just because the new MCP connection is not visible yet. Use CLI commands for useful first-session work:
|
|
72854
73329
|
|
|
@@ -72860,19 +73335,19 @@ runtype products list
|
|
|
72860
73335
|
runtype --help
|
|
72861
73336
|
\`\`\`
|
|
72862
73337
|
|
|
72863
|
-
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.
|
|
72864
73339
|
|
|
72865
|
-
###
|
|
73340
|
+
### 5. Tell the user to restart or reload the harness
|
|
72866
73341
|
|
|
72867
|
-
Before finishing this session, read the final status and next steps from \`runtype install-mcp\`.
|
|
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.
|
|
72868
73343
|
|
|
72869
|
-
Only after
|
|
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:
|
|
72870
73345
|
|
|
72871
|
-
> Runtype
|
|
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.
|
|
72872
73347
|
|
|
72873
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.
|
|
72874
73349
|
|
|
72875
|
-
###
|
|
73350
|
+
### 6. Switch to MCP after restart
|
|
72876
73351
|
|
|
72877
73352
|
In the next session, use Runtype MCP first. Do not fall back to the CLI just because it worked previously. Start by calling:
|
|
72878
73353
|
|
|
@@ -72898,13 +73373,21 @@ runtype install-mcp --agent opencode
|
|
|
72898
73373
|
runtype install-mcp --agent vscode
|
|
72899
73374
|
\`\`\`
|
|
72900
73375
|
|
|
72901
|
-
Use only the line for the current harness. Follow the command's
|
|
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:
|
|
72902
73381
|
|
|
72903
|
-
|
|
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
|
|
72904
73387
|
|
|
72905
|
-
|
|
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.
|
|
72906
73389
|
|
|
72907
|
-
|
|
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.
|
|
72908
73391
|
|
|
72909
73392
|
## After MCP is active
|
|
72910
73393
|
|
|
@@ -72923,7 +73406,7 @@ API: [api.runtype.com](https://api.runtype.com)
|
|
|
72923
73406
|
|
|
72924
73407
|
// src/commands/onboard.ts
|
|
72925
73408
|
init_credential_store();
|
|
72926
|
-
var AUTH_LOGIN_INSTRUCTION = "Sign in to Runtype:
|
|
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`.";
|
|
72927
73410
|
var AFTER_RESTART_STEPS = [
|
|
72928
73411
|
"Use the Runtype MCP server first for all Runtype work.",
|
|
72929
73412
|
"Start with the get_build_instructions tool before creating or changing resources.",
|
|
@@ -73208,7 +73691,7 @@ function collectBundleFiles(dir) {
|
|
|
73208
73691
|
walk(dir);
|
|
73209
73692
|
return zippable;
|
|
73210
73693
|
}
|
|
73211
|
-
function
|
|
73694
|
+
function fail2(message, jsonMode, code) {
|
|
73212
73695
|
if (jsonMode) {
|
|
73213
73696
|
printJson({ status: "error", error: message, code });
|
|
73214
73697
|
} else {
|
|
@@ -73247,7 +73730,7 @@ appsCommand.command("list").description("List your apps").option("--json", "Stru
|
|
|
73247
73730
|
console.log(` ${app.url} ${status} ${app.visibility}${serving}`);
|
|
73248
73731
|
}
|
|
73249
73732
|
} catch (error51) {
|
|
73250
|
-
|
|
73733
|
+
fail2(
|
|
73251
73734
|
`Failed to list apps: ${error51 instanceof Error ? error51.message : String(error51)}`,
|
|
73252
73735
|
jsonMode,
|
|
73253
73736
|
"LIST_FAILED"
|
|
@@ -73276,7 +73759,7 @@ appsCommand.command("create").description("Create an app (the hostname is {slug}
|
|
|
73276
73759
|
console.log(` ${app.url}`);
|
|
73277
73760
|
console.log(chalk43.dim(" Deploy a bundle with `runtype apps deploy <dir> --app " + app.id + "`"));
|
|
73278
73761
|
} catch (error51) {
|
|
73279
|
-
|
|
73762
|
+
fail2(
|
|
73280
73763
|
`Failed to create app: ${error51 instanceof Error ? error51.message : String(error51)}`,
|
|
73281
73764
|
jsonMode,
|
|
73282
73765
|
"CREATE_FAILED"
|
|
@@ -73291,27 +73774,27 @@ appsCommand.command("deploy <dir>").description("Zip a bundle directory, upload
|
|
|
73291
73774
|
const apiKey = await ensureAuth();
|
|
73292
73775
|
if (!apiKey) return;
|
|
73293
73776
|
if (!existsSync17(dir) || !statSync7(dir).isDirectory()) {
|
|
73294
|
-
|
|
73777
|
+
fail2(`Not a directory: ${dir}`, jsonMode, "INVALID_DIR");
|
|
73295
73778
|
}
|
|
73296
73779
|
let bundleFiles = {};
|
|
73297
73780
|
try {
|
|
73298
73781
|
bundleFiles = collectBundleFiles(dir);
|
|
73299
73782
|
} catch (error51) {
|
|
73300
|
-
|
|
73783
|
+
fail2(
|
|
73301
73784
|
`Failed to read ${dir}: ${error51 instanceof Error ? error51.message : String(error51)}`,
|
|
73302
73785
|
jsonMode,
|
|
73303
73786
|
"INVALID_DIR"
|
|
73304
73787
|
);
|
|
73305
73788
|
}
|
|
73306
73789
|
if (!bundleFiles["index.html"]) {
|
|
73307
|
-
|
|
73790
|
+
fail2(
|
|
73308
73791
|
`${dir} is missing index.html at its root (symlinked files are not bundled).`,
|
|
73309
73792
|
jsonMode,
|
|
73310
73793
|
"MISSING_INDEX"
|
|
73311
73794
|
);
|
|
73312
73795
|
}
|
|
73313
73796
|
if (!bundleFiles[MANIFEST_FILENAME]) {
|
|
73314
|
-
|
|
73797
|
+
fail2(
|
|
73315
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.`,
|
|
73316
73799
|
jsonMode,
|
|
73317
73800
|
"MISSING_MANIFEST"
|
|
@@ -73359,7 +73842,7 @@ appsCommand.command("deploy <dir>").description("Zip a bundle directory, upload
|
|
|
73359
73842
|
console.log(chalk43.dim(` Uploaded but not activated. Activate with \`runtype apps activate ${options.app} ${version2.id}\``));
|
|
73360
73843
|
}
|
|
73361
73844
|
} catch (error51) {
|
|
73362
|
-
|
|
73845
|
+
fail2(
|
|
73363
73846
|
`Deploy failed: ${error51 instanceof Error ? error51.message : String(error51)}`,
|
|
73364
73847
|
jsonMode,
|
|
73365
73848
|
"DEPLOY_FAILED"
|
|
@@ -73390,7 +73873,7 @@ appsCommand.command("versions <appId>").description("List an app's versions, new
|
|
|
73390
73873
|
);
|
|
73391
73874
|
}
|
|
73392
73875
|
} catch (error51) {
|
|
73393
|
-
|
|
73876
|
+
fail2(
|
|
73394
73877
|
`Failed to list versions: ${error51 instanceof Error ? error51.message : String(error51)}`,
|
|
73395
73878
|
jsonMode,
|
|
73396
73879
|
"VERSIONS_FAILED"
|
|
@@ -73412,7 +73895,7 @@ appsCommand.command("activate <appId> <versionId>").description("Activate an upl
|
|
|
73412
73895
|
console.log(chalk43.green(`Activated version ${version2.versionNumber}`));
|
|
73413
73896
|
console.log(` Live at ${chalk43.bold(app.url)}`);
|
|
73414
73897
|
} catch (error51) {
|
|
73415
|
-
|
|
73898
|
+
fail2(
|
|
73416
73899
|
`Activation failed: ${error51 instanceof Error ? error51.message : String(error51)}`,
|
|
73417
73900
|
jsonMode,
|
|
73418
73901
|
"ACTIVATE_FAILED"
|
|
@@ -73433,7 +73916,7 @@ appsCommand.command("delete <appId>").description("Permanently delete an app, it
|
|
|
73433
73916
|
}
|
|
73434
73917
|
console.log(chalk43.green(`Deleted ${appId}`));
|
|
73435
73918
|
} catch (error51) {
|
|
73436
|
-
|
|
73919
|
+
fail2(
|
|
73437
73920
|
`Delete failed: ${error51 instanceof Error ? error51.message : String(error51)}`,
|
|
73438
73921
|
jsonMode,
|
|
73439
73922
|
"DELETE_FAILED"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtypelabs/cli",
|
|
3
|
-
"version": "2.
|
|
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",
|
|
@@ -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.
|
|
44
|
+
"@runtypelabs/shared": "3.5.0"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
|
47
47
|
"node": ">=22.12.0"
|