@runtypelabs/cli 2.47.8 → 2.48.1
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/README.md +20 -7
- package/dist/index.js +1944 -1090
- package/package.json +9 -8
package/dist/index.js
CHANGED
|
@@ -2,39 +2,40 @@
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { Command, InvalidArgumentError, Option } from "commander";
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
-
import * as fs$
|
|
5
|
+
import * as fs$1 from "node:fs";
|
|
6
6
|
import fs, { closeSync, constants, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
7
7
|
import * as path$2 from "node:path";
|
|
8
8
|
import path, { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
9
9
|
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
10
10
|
import { Box, Text, render, useApp, useInput, useStdout } from "ink";
|
|
11
|
-
import open from "open";
|
|
12
|
-
import crypto$1 from "crypto";
|
|
13
|
-
import express from "express";
|
|
14
11
|
import { ClientTokensNamespace, DEFAULT_STALL_STOP_AFTER, ENSURE_RELEASE_DEPLOY_CONFLICT_MESSAGE, FlowsNamespace, LEDGER_ARTIFACT_LINE_PREFIX, ProductsNamespace, RuntypeApiError, RuntypeClient, SkillsNamespace, SurfacesNamespace, ToolsNamespace, activateAgentPromotion, buildLedgerOffloadReference, compileWorkflowConfig, createClient, defaultWorkflow, defineFlow, defineFpo, defineProduct, defineSkill, defineSurface, defineTool, deployWorkflow, ensureDefaultWorkflowHooks, evaluateGeneratedRuntimeToolProposal, gameWorkflow, getDefaultPlanPath, isBuiltinToolExecuteResponse, isWorkflowHookRef, parseFinalBuffer, parseLedgerArtifactRelativePath, parseOffloadedOutputId, parseSSEChunk, prepareAgentPromotion, processStream, registerWorkflowHook, validateAgentPromotion } from "@runtypelabs/sdk";
|
|
15
12
|
import Conf from "conf";
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
13
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
14
|
+
import * as crypto$3 from "node:crypto";
|
|
15
|
+
import crypto$1, { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
16
|
+
import * as os$1 from "node:os";
|
|
17
|
+
import os, { constants as constants$1 } from "node:os";
|
|
19
18
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
19
|
+
import open from "open";
|
|
20
|
+
import crypto$2 from "crypto";
|
|
21
|
+
import express from "express";
|
|
20
22
|
import readline from "readline";
|
|
21
23
|
import { ErrorDisplay, Spinner, StatusBar, StreamOutput, renderMarkdownToTerminal, theme } from "@runtypelabs/ink-components";
|
|
22
24
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
23
|
-
import TextInput from "ink-text-input";
|
|
24
25
|
import SelectInput from "ink-select-input";
|
|
26
|
+
import { copyFileSync, existsSync as existsSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, unlinkSync, writeFileSync as writeFileSync$1 } from "fs";
|
|
27
|
+
import path$1, { delimiter, dirname as dirname$1, join as join$1, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "path";
|
|
28
|
+
import { access, chmod, copyFile, mkdir, open as open$1, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
29
|
+
import TextInput from "ink-text-input";
|
|
25
30
|
import { execSync, spawn, spawnSync } from "node:child_process";
|
|
26
|
-
import * as crypto$2 from "node:crypto";
|
|
27
|
-
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
28
|
-
import * as os$2 from "node:os";
|
|
29
|
-
import os$1, { constants as constants$1 } from "node:os";
|
|
30
31
|
import { AnimatedVariant, LoadingAnimation, StartupGridIconCompactLightInverted } from "@runtypelabs/terminal-animations";
|
|
31
32
|
import { createJiti } from "jiti";
|
|
32
33
|
import micromatch from "micromatch";
|
|
33
34
|
import { parse } from "yaml";
|
|
34
|
-
import { access, chmod, copyFile, mkdir, open as open$1, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
35
35
|
import { execFileSync, spawn as spawn$1 } from "child_process";
|
|
36
36
|
import { createServer } from "node:http";
|
|
37
37
|
import { createServer as createServer$1 } from "node:net";
|
|
38
|
+
import { tmpdir } from "os";
|
|
38
39
|
import { zipSync } from "fflate";
|
|
39
40
|
import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
|
|
40
41
|
import WebSocket from "ws";
|
|
@@ -66,378 +67,6 @@ function loadEnv(path = resolve(process.cwd(), ".env")) {
|
|
|
66
67
|
} catch {}
|
|
67
68
|
}
|
|
68
69
|
//#endregion
|
|
69
|
-
//#region src/auth/callback-server.ts
|
|
70
|
-
var CallbackServer = class {
|
|
71
|
-
server = null;
|
|
72
|
-
app;
|
|
73
|
-
codeResolve;
|
|
74
|
-
codeReject;
|
|
75
|
-
codePromise;
|
|
76
|
-
expectedState;
|
|
77
|
-
constructor() {
|
|
78
|
-
this.app = express();
|
|
79
|
-
this.codePromise = new Promise((resolve, reject) => {
|
|
80
|
-
this.codeResolve = resolve;
|
|
81
|
-
this.codeReject = reject;
|
|
82
|
-
});
|
|
83
|
-
this.app.get("/callback", (req, res) => {
|
|
84
|
-
const { token, code, state, error } = req.query;
|
|
85
|
-
if (typeof state !== "string" || state !== this.expectedState) {
|
|
86
|
-
res.status(400).send(this.errorHTML("Invalid authentication state. Continue the login you started in the CLI."));
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
const values = [
|
|
90
|
-
token,
|
|
91
|
-
code,
|
|
92
|
-
error
|
|
93
|
-
].filter((value) => value !== void 0);
|
|
94
|
-
if (values.length !== 1 || typeof values[0] !== "string" || values[0].length === 0) {
|
|
95
|
-
res.status(400).send(this.errorHTML("Invalid authentication callback"));
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
if (typeof error === "string") {
|
|
99
|
-
res.send(this.errorHTML(error));
|
|
100
|
-
this.codeReject(new Error(error));
|
|
101
|
-
setTimeout(() => this.stop(), 1e3);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
res.send(this.successHTML());
|
|
105
|
-
this.codeResolve(values[0]);
|
|
106
|
-
setTimeout(() => this.stop(), 1e3);
|
|
107
|
-
});
|
|
108
|
-
this.app.get("/health", (_req, res) => {
|
|
109
|
-
res.json({ status: "ok" });
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
/** Set expected state parameter for OAuth2 CSRF validation */
|
|
113
|
-
setExpectedState(state) {
|
|
114
|
-
if (!state) throw new Error("Authentication requires a nonempty expected state");
|
|
115
|
-
if (this.server) throw new Error("Expected state must be registered before listening");
|
|
116
|
-
this.expectedState = state;
|
|
117
|
-
}
|
|
118
|
-
async start(port = 8765) {
|
|
119
|
-
if (!this.expectedState) throw new Error("Authentication requires an expected state before listening");
|
|
120
|
-
return new Promise((resolveStart, rejectStart) => {
|
|
121
|
-
this.server = this.app.listen(port, "127.0.0.1", () => {
|
|
122
|
-
const address = this.server?.address();
|
|
123
|
-
const actualPort = address && typeof address === "object" && "port" in address ? address.port : port;
|
|
124
|
-
console.log(`Callback server listening on port ${actualPort}`);
|
|
125
|
-
resolveStart({
|
|
126
|
-
actualPort,
|
|
127
|
-
codePromise: this.codePromise
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
this.server.on("error", (error) => {
|
|
131
|
-
if (error.code === "EADDRINUSE") {
|
|
132
|
-
console.log(`Port ${port} is in use, trying ${port + 1}...`);
|
|
133
|
-
this.server = null;
|
|
134
|
-
this.start(port + 1).then(resolveStart).catch(rejectStart);
|
|
135
|
-
} else rejectStart(error);
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
stop() {
|
|
140
|
-
if (this.server) {
|
|
141
|
-
const s = this.server;
|
|
142
|
-
this.server = null;
|
|
143
|
-
s.unref();
|
|
144
|
-
if (typeof s.closeAllConnections === "function") s.closeAllConnections();
|
|
145
|
-
s.close();
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
successHTML() {
|
|
149
|
-
return `<!DOCTYPE html>
|
|
150
|
-
<html lang="en">
|
|
151
|
-
<head>
|
|
152
|
-
<meta charset="utf-8">
|
|
153
|
-
<title>Authentication Successful</title>
|
|
154
|
-
<style>
|
|
155
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
156
|
-
body {
|
|
157
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
158
|
-
height: 100vh;
|
|
159
|
-
overflow: hidden;
|
|
160
|
-
background: #0a0a0a;
|
|
161
|
-
}
|
|
162
|
-
.grid-bg {
|
|
163
|
-
position: absolute;
|
|
164
|
-
inset: 0;
|
|
165
|
-
z-index: 0;
|
|
166
|
-
overflow: hidden;
|
|
167
|
-
}
|
|
168
|
-
.grid-bg .gradient {
|
|
169
|
-
position: absolute;
|
|
170
|
-
inset: 0;
|
|
171
|
-
background: linear-gradient(to bottom, #0a0a0a 0%, #171717 50%, #262626 100%);
|
|
172
|
-
}
|
|
173
|
-
@keyframes gridMove {
|
|
174
|
-
0% { background-position-y: 0; }
|
|
175
|
-
100% { background-position-y: 30px; }
|
|
176
|
-
}
|
|
177
|
-
.grid-bg .grid-vertical-wrap {
|
|
178
|
-
position: absolute;
|
|
179
|
-
left: 0;
|
|
180
|
-
right: 0;
|
|
181
|
-
bottom: 0;
|
|
182
|
-
top: 40%;
|
|
183
|
-
transform-origin: top center;
|
|
184
|
-
transform: perspective(500px) rotateX(60deg);
|
|
185
|
-
}
|
|
186
|
-
.grid-bg .grid-vertical-wrap .fade {
|
|
187
|
-
position: absolute;
|
|
188
|
-
inset: 0;
|
|
189
|
-
background: linear-gradient(to bottom, transparent 0%, rgba(255,255,255,0.02) 100%);
|
|
190
|
-
}
|
|
191
|
-
.grid-bg .grid-vertical-wrap .lines {
|
|
192
|
-
position: absolute;
|
|
193
|
-
inset: 0;
|
|
194
|
-
background-image: repeating-linear-gradient(
|
|
195
|
-
90deg,
|
|
196
|
-
transparent 0,
|
|
197
|
-
transparent 49px,
|
|
198
|
-
rgba(255,255,255,0.08) 49px,
|
|
199
|
-
rgba(255,255,255,0.08) 50px
|
|
200
|
-
);
|
|
201
|
-
background-size: 50px 100%;
|
|
202
|
-
}
|
|
203
|
-
.grid-bg .grid-horizontal {
|
|
204
|
-
position: absolute;
|
|
205
|
-
left: 0;
|
|
206
|
-
right: 0;
|
|
207
|
-
bottom: 0;
|
|
208
|
-
top: 40%;
|
|
209
|
-
transform-origin: top center;
|
|
210
|
-
transform: perspective(500px) rotateX(60deg);
|
|
211
|
-
background-image: repeating-linear-gradient(
|
|
212
|
-
0deg,
|
|
213
|
-
transparent 0,
|
|
214
|
-
transparent 29px,
|
|
215
|
-
rgba(255,255,255,0.06) 29px,
|
|
216
|
-
rgba(255,255,255,0.06) 30px
|
|
217
|
-
);
|
|
218
|
-
background-size: 100% 30px;
|
|
219
|
-
animation: gridMove 2s linear infinite;
|
|
220
|
-
}
|
|
221
|
-
.card-wrap {
|
|
222
|
-
position: absolute;
|
|
223
|
-
left: 50%;
|
|
224
|
-
top: 50%;
|
|
225
|
-
transform: translate(-50%, -50%);
|
|
226
|
-
z-index: 10;
|
|
227
|
-
width: 100%;
|
|
228
|
-
max-width: 400px;
|
|
229
|
-
perspective: 1400px;
|
|
230
|
-
animation: cardIn 0.4s ease-out;
|
|
231
|
-
}
|
|
232
|
-
@keyframes cardIn {
|
|
233
|
-
from { opacity: 0; transform: translate(-50%, -50%) translateY(16px); }
|
|
234
|
-
to { opacity: 1; transform: translate(-50%, -50%) translateY(0); }
|
|
235
|
-
}
|
|
236
|
-
.card-inner {
|
|
237
|
-
position: relative;
|
|
238
|
-
}
|
|
239
|
-
.card-layer-back,
|
|
240
|
-
.card-layer-mid {
|
|
241
|
-
position: absolute;
|
|
242
|
-
inset: 0;
|
|
243
|
-
border-radius: 12px;
|
|
244
|
-
border: 1px solid #e5e7eb;
|
|
245
|
-
pointer-events: none;
|
|
246
|
-
}
|
|
247
|
-
.card-layer-back {
|
|
248
|
-
background: rgba(0, 0, 0, 0.08);
|
|
249
|
-
transform: translate3d(0, -18px, -36px) rotateX(6deg);
|
|
250
|
-
}
|
|
251
|
-
.card-layer-mid {
|
|
252
|
-
background: rgba(0, 0, 0, 0.05);
|
|
253
|
-
transform: translate3d(0, -10px, -20px) rotateX(3deg);
|
|
254
|
-
}
|
|
255
|
-
.card-front {
|
|
256
|
-
position: relative;
|
|
257
|
-
border-radius: 12px;
|
|
258
|
-
border: 1px solid rgba(255, 255, 255, 1);
|
|
259
|
-
background: rgba(0, 0, 0, .5);
|
|
260
|
-
backdrop-filter: blur(10px);
|
|
261
|
-
-webkit-backdrop-filter: blur(10px);
|
|
262
|
-
padding: 2rem 2.5rem;
|
|
263
|
-
text-align: center;
|
|
264
|
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
265
|
-
transform: rotateX(-10deg) translateZ(0);
|
|
266
|
-
transform-style: preserve-3d;
|
|
267
|
-
}
|
|
268
|
-
.card-front .section-title {
|
|
269
|
-
color: #ffffff;
|
|
270
|
-
font-size: 0.8125rem;
|
|
271
|
-
font-weight: 600;
|
|
272
|
-
letter-spacing: 0.05em;
|
|
273
|
-
text-transform: uppercase;
|
|
274
|
-
margin-bottom: 1rem;
|
|
275
|
-
}
|
|
276
|
-
.card-front .flow-arrow {
|
|
277
|
-
color: rgba(255, 255, 255, 0.7);
|
|
278
|
-
font-size: 1rem;
|
|
279
|
-
margin-bottom: 1rem;
|
|
280
|
-
line-height: 1;
|
|
281
|
-
}
|
|
282
|
-
.card-front .message {
|
|
283
|
-
color: #ffffff;
|
|
284
|
-
font-size: 0.75rem;
|
|
285
|
-
font-weight: 100;
|
|
286
|
-
margin-bottom: 0.5rem;
|
|
287
|
-
font-family: monospace;
|
|
288
|
-
}
|
|
289
|
-
.card-front .progress {
|
|
290
|
-
color: rgba(255, 255, 255, 0.75);
|
|
291
|
-
font-size: 0.8125rem;
|
|
292
|
-
}
|
|
293
|
-
</style>
|
|
294
|
-
</head>
|
|
295
|
-
<body>
|
|
296
|
-
<div class="grid-bg">
|
|
297
|
-
<div class="gradient"></div>
|
|
298
|
-
<div class="grid-vertical-wrap">
|
|
299
|
-
<div class="fade"></div>
|
|
300
|
-
<div class="lines"></div>
|
|
301
|
-
</div>
|
|
302
|
-
<div class="grid-horizontal"></div>
|
|
303
|
-
</div>
|
|
304
|
-
<div id="card-wrap" class="card-wrap">
|
|
305
|
-
<div class="card-inner">
|
|
306
|
-
<div class="card-layer-back" aria-hidden="true"></div>
|
|
307
|
-
<div class="card-layer-mid" aria-hidden="true"></div>
|
|
308
|
-
<div id="card" class="card-front">
|
|
309
|
-
<div class="section-title">Authentication Successful</div>
|
|
310
|
-
<div class="flow-arrow" aria-hidden="true">↓</div>
|
|
311
|
-
<p class="message">You can close this window and return to your terminal.</p>
|
|
312
|
-
<p class="progress" id="progress">Closing automatically in 3 seconds…</p>
|
|
313
|
-
</div>
|
|
314
|
-
</div>
|
|
315
|
-
</div>
|
|
316
|
-
<script>
|
|
317
|
-
setTimeout(function() {
|
|
318
|
-
window.close();
|
|
319
|
-
var prog = document.getElementById('progress');
|
|
320
|
-
if (prog) prog.textContent = 'You can now close this window.';
|
|
321
|
-
var card = document.getElementById('card');
|
|
322
|
-
if (card) card.innerHTML = '<div class="section-title">Runtype is Ready</div><p class="message">You can now close this window</p>';
|
|
323
|
-
}, 3000);
|
|
324
|
-
<\/script>
|
|
325
|
-
</body>
|
|
326
|
-
</html>`;
|
|
327
|
-
}
|
|
328
|
-
escapeHtml(str) {
|
|
329
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
330
|
-
}
|
|
331
|
-
errorHTML(error) {
|
|
332
|
-
return `
|
|
333
|
-
<!DOCTYPE html>
|
|
334
|
-
<html>
|
|
335
|
-
<head>
|
|
336
|
-
<title>Authentication Failed</title>
|
|
337
|
-
<style>
|
|
338
|
-
* {
|
|
339
|
-
margin: 0;
|
|
340
|
-
padding: 0;
|
|
341
|
-
box-sizing: border-box;
|
|
342
|
-
}
|
|
343
|
-
body {
|
|
344
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
345
|
-
display: flex;
|
|
346
|
-
align-items: center;
|
|
347
|
-
justify-content: center;
|
|
348
|
-
height: 100vh;
|
|
349
|
-
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
|
350
|
-
}
|
|
351
|
-
.container {
|
|
352
|
-
background: white;
|
|
353
|
-
padding: 3rem;
|
|
354
|
-
border-radius: 12px;
|
|
355
|
-
text-align: center;
|
|
356
|
-
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
|
357
|
-
max-width: 400px;
|
|
358
|
-
}
|
|
359
|
-
.error-icon {
|
|
360
|
-
color: #ef4444;
|
|
361
|
-
font-size: 64px;
|
|
362
|
-
margin-bottom: 1rem;
|
|
363
|
-
}
|
|
364
|
-
h1 {
|
|
365
|
-
color: #1f2937;
|
|
366
|
-
margin-bottom: 0.5rem;
|
|
367
|
-
font-size: 1.5rem;
|
|
368
|
-
}
|
|
369
|
-
p {
|
|
370
|
-
color: #6b7280;
|
|
371
|
-
font-size: 1rem;
|
|
372
|
-
margin-bottom: 1rem;
|
|
373
|
-
}
|
|
374
|
-
.error-details {
|
|
375
|
-
background: #fef2f2;
|
|
376
|
-
color: #991b1b;
|
|
377
|
-
padding: 0.75rem;
|
|
378
|
-
border-radius: 6px;
|
|
379
|
-
font-size: 0.875rem;
|
|
380
|
-
font-family: monospace;
|
|
381
|
-
}
|
|
382
|
-
</style>
|
|
383
|
-
</head>
|
|
384
|
-
<body>
|
|
385
|
-
<div class="container">
|
|
386
|
-
<div class="error-icon">✕</div>
|
|
387
|
-
<h1>Authentication Failed</h1>
|
|
388
|
-
<p>There was an error during authentication.</p>
|
|
389
|
-
<div class="error-details">${this.escapeHtml(error)}</div>
|
|
390
|
-
</div>
|
|
391
|
-
</body>
|
|
392
|
-
</html>
|
|
393
|
-
`;
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
//#endregion
|
|
397
|
-
//#region src/auth/oauth-manager.ts
|
|
398
|
-
var OAuthManager = class {
|
|
399
|
-
clerkPublishableKey;
|
|
400
|
-
dashboardUrl;
|
|
401
|
-
constructor(config) {
|
|
402
|
-
this.clerkPublishableKey = config.clerkPublishableKey;
|
|
403
|
-
this.dashboardUrl = config.dashboardUrl;
|
|
404
|
-
}
|
|
405
|
-
async authenticate(mode) {
|
|
406
|
-
const state = crypto$1.randomBytes(32).toString("base64url");
|
|
407
|
-
const codeVerifier = crypto$1.randomBytes(32).toString("base64url");
|
|
408
|
-
const codeChallenge = crypto$1.createHash("sha256").update(codeVerifier).digest("base64url");
|
|
409
|
-
const callbackServer = new CallbackServer();
|
|
410
|
-
callbackServer.setExpectedState(state);
|
|
411
|
-
const requestedPort = await this.findAvailablePort();
|
|
412
|
-
const { actualPort, codePromise } = await callbackServer.start(requestedPort);
|
|
413
|
-
const redirectUri = `http://127.0.0.1:${actualPort}/callback`;
|
|
414
|
-
const authUrl = new URL(`${this.dashboardUrl.replace(/\/$/, "")}/${mode}`);
|
|
415
|
-
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
416
|
-
authUrl.searchParams.set("cli", "true");
|
|
417
|
-
authUrl.searchParams.set("state", state);
|
|
418
|
-
authUrl.searchParams.set("code_challenge", codeChallenge);
|
|
419
|
-
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
420
|
-
authUrl.searchParams.set("publishable_key", this.clerkPublishableKey);
|
|
421
|
-
console.log("🌐 Opening browser for authentication...");
|
|
422
|
-
console.log(`If the browser doesn't open, visit: ${authUrl}`);
|
|
423
|
-
try {
|
|
424
|
-
console.log("⏳ Waiting for authentication...");
|
|
425
|
-
const [code] = await Promise.all([codePromise, open(authUrl.toString()).catch(() => {
|
|
426
|
-
console.log("Failed to open browser automatically. Please visit the URL above.");
|
|
427
|
-
})]);
|
|
428
|
-
return await this.exchangeCodeForSession(code, codeVerifier, redirectUri);
|
|
429
|
-
} finally {
|
|
430
|
-
callbackServer.stop();
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
async findAvailablePort() {
|
|
434
|
-
return 8765;
|
|
435
|
-
}
|
|
436
|
-
async exchangeCodeForSession(code, codeVerifier, redirectUri) {
|
|
437
|
-
return code;
|
|
438
|
-
}
|
|
439
|
-
};
|
|
440
|
-
//#endregion
|
|
441
70
|
//#region ../shared/dist/chunk-1Tgh2C1u.mjs
|
|
442
71
|
/**
|
|
443
72
|
* Get nested property value using dot notation and bracket notation.
|
|
@@ -11528,7 +11157,7 @@ function isDataConnectionToolId(toolId) {
|
|
|
11528
11157
|
return toolId.startsWith("dc:");
|
|
11529
11158
|
}
|
|
11530
11159
|
//#endregion
|
|
11531
|
-
//#region ../shared/dist/chunk-
|
|
11160
|
+
//#region ../shared/dist/chunk-InaIcoUA.mjs
|
|
11532
11161
|
/**
|
|
11533
11162
|
* Maps model families to provider-specific model IDs.
|
|
11534
11163
|
* Use for routing: family + provider → exact model ID
|
|
@@ -11551,6 +11180,11 @@ const MODEL_FAMILY_PROVIDER_IDS = {
|
|
|
11551
11180
|
},
|
|
11552
11181
|
"claude-haiku-4-5-20251001": { "anthropic": "claude-haiku-4-5-20251001" },
|
|
11553
11182
|
"claude-haiku-4.5": { "vercel": "anthropic/claude-haiku-4.5" },
|
|
11183
|
+
"claude-haiku-5-5": {
|
|
11184
|
+
"anthropic": "claude-haiku-5-5",
|
|
11185
|
+
"vercel": "anthropic/claude-haiku-5.5"
|
|
11186
|
+
},
|
|
11187
|
+
"claude-haiku-5.5": { "vercel": "anthropic/claude-haiku-5.5" },
|
|
11554
11188
|
"claude-opus-4": { "vercel": "anthropic/claude-opus-4" },
|
|
11555
11189
|
"claude-opus-4-5": {
|
|
11556
11190
|
"anthropic": "claude-opus-4-5-20251101",
|
|
@@ -11613,12 +11247,14 @@ const MODEL_FAMILY_PROVIDER_IDS = {
|
|
|
11613
11247
|
"deep-research-max-preview-04-2026": { "google": "deep-research-max-preview-04-2026" },
|
|
11614
11248
|
"deep-research-preview-04-2026": { "google": "deep-research-preview-04-2026" },
|
|
11615
11249
|
"deepseek-v4-1-flash": { "vercel": "deepseek/deepseek-v4.1-flash" },
|
|
11250
|
+
"deepseek-v4-1-flash-fast": { "vercel": "deepseek/deepseek-v4.1-flash-fast" },
|
|
11616
11251
|
"deepseek-v4-flash": { "vercel": "deepseek/deepseek-v4-flash" },
|
|
11617
11252
|
"deepseek-v4-flash-0731": { "vercel": "deepseek/deepseek-v4-flash-0731" },
|
|
11618
11253
|
"deepseek-v4-flash-vision-exp": { "vercel": "deepseek/deepseek-v4-flash-vision-exp" },
|
|
11619
11254
|
"deepseek-v4-pro": { "vercel": "deepseek/deepseek-v4-pro" },
|
|
11620
11255
|
"deepseek-v4-pro-0813": { "vercel": "deepseek/deepseek-v4-pro-0813" },
|
|
11621
11256
|
"deepseek-v4.1-flash": { "vercel": "deepseek/deepseek-v4.1-flash" },
|
|
11257
|
+
"deepseek-v4.1-flash-fast": { "vercel": "deepseek/deepseek-v4.1-flash-fast" },
|
|
11622
11258
|
"ember-1": { "vercel": "fireworks/ember-1" },
|
|
11623
11259
|
"fugu-max": { "vercel": "sakana/fugu-max" },
|
|
11624
11260
|
"fugu-ultra": { "vercel": "sakana/fugu-ultra-v2" },
|
|
@@ -11746,6 +11382,8 @@ const MODEL_FAMILY_PROVIDER_IDS = {
|
|
|
11746
11382
|
"gemini-3.8-live-extended-thinking": { "vercel": "google/gemini-3.8-live-extended-thinking" },
|
|
11747
11383
|
"gemini-embedding-001": { "google": "gemini-embedding-001" },
|
|
11748
11384
|
"gemini-embedding-2": { "google": "gemini-embedding-2" },
|
|
11385
|
+
"gemini-nano-banana-2-1": { "vercel": "google/gemini-nano-banana-2.1" },
|
|
11386
|
+
"gemini-nano-banana-2.1": { "vercel": "google/gemini-nano-banana-2.1" },
|
|
11749
11387
|
"gemini-omni-flash": { "vercel": "google/gemini-omni-flash-preview" },
|
|
11750
11388
|
"gemini-omni-flash-preview": { "vercel": "google/gemini-omni-flash-preview" },
|
|
11751
11389
|
"gemma-4-26b-a4b-it": {
|
|
@@ -12056,11 +11694,10 @@ const MODEL_FAMILY_PROVIDER_IDS = {
|
|
|
12056
11694
|
"inkling": { "vercel": "thinkingmachines/inkling" },
|
|
12057
11695
|
"inkling-small": { "vercel": "thinkingmachines/inkling-small" },
|
|
12058
11696
|
"interfaze-beta": { "vercel": "interfaze/interfaze-beta" },
|
|
12059
|
-
"kimi-k2": { "vercel": "moonshotai/kimi-k2
|
|
11697
|
+
"kimi-k2": { "vercel": "moonshotai/kimi-k2" },
|
|
12060
11698
|
"kimi-k2-6": { "vercel": "moonshotai/kimi-k2.6" },
|
|
12061
11699
|
"kimi-k2-7-code": { "vercel": "moonshotai/kimi-k2.7-code" },
|
|
12062
11700
|
"kimi-k2-7-code-highspeed": { "vercel": "moonshotai/kimi-k2.7-code-highspeed" },
|
|
12063
|
-
"kimi-k2-thinking": { "vercel": "moonshotai/kimi-k2-thinking" },
|
|
12064
11701
|
"kimi-k2.6": { "vercel": "moonshotai/kimi-k2.6" },
|
|
12065
11702
|
"kimi-k2.7-code": { "vercel": "moonshotai/kimi-k2.7-code" },
|
|
12066
11703
|
"kimi-k2.7-code-highspeed": { "vercel": "moonshotai/kimi-k2.7-code-highspeed" },
|
|
@@ -12072,13 +11709,11 @@ const MODEL_FAMILY_PROVIDER_IDS = {
|
|
|
12072
11709
|
"laguna-s-2.1-free": { "vercel": "poolside/laguna-s-2.1-free" },
|
|
12073
11710
|
"ling-3-0-flash": { "vercel": "inclusionai/ling-3.0-flash" },
|
|
12074
11711
|
"ling-3-0-flash-sante": { "vercel": "inclusionai/ling-3.0-flash-sante" },
|
|
12075
|
-
"ling-3-0-flash-sante-free": { "vercel": "inclusionai/ling-3.0-flash-sante-free" },
|
|
12076
11712
|
"ling-3-0-flash-vl": { "vercel": "inclusionai/ling-3.0-flash-vl" },
|
|
12077
11713
|
"ling-3-1-flash": { "vercel": "inclusionai/ling-3.1-flash" },
|
|
12078
11714
|
"ling-3-1-flash-free": { "vercel": "inclusionai/ling-3.1-flash-free" },
|
|
12079
11715
|
"ling-3.0-flash": { "vercel": "inclusionai/ling-3.0-flash" },
|
|
12080
11716
|
"ling-3.0-flash-sante": { "vercel": "inclusionai/ling-3.0-flash-sante" },
|
|
12081
|
-
"ling-3.0-flash-sante-free": { "vercel": "inclusionai/ling-3.0-flash-sante-free" },
|
|
12082
11717
|
"ling-3.0-flash-vl": { "vercel": "inclusionai/ling-3.0-flash-vl" },
|
|
12083
11718
|
"ling-3.1-flash": { "vercel": "inclusionai/ling-3.1-flash" },
|
|
12084
11719
|
"ling-3.1-flash-free": { "vercel": "inclusionai/ling-3.1-flash-free" },
|
|
@@ -12117,6 +11752,7 @@ const MODEL_FAMILY_PROVIDER_IDS = {
|
|
|
12117
11752
|
"ministral-3b": { "vercel": "mistral/ministral-3b" },
|
|
12118
11753
|
"ministral-8b": { "vercel": "mistral/ministral-8b" },
|
|
12119
11754
|
"mistral-large-3": { "vercel": "mistral/mistral-large-3" },
|
|
11755
|
+
"mistral-large-4": { "vercel": "mistral/mistral-large-4" },
|
|
12120
11756
|
"mistral-medium-3-5": { "vercel": "mistral/mistral-medium-3.5" },
|
|
12121
11757
|
"mistral-medium-3.5": { "vercel": "mistral/mistral-medium-3.5" },
|
|
12122
11758
|
"mistral-small": { "vercel": "mistral/mistral-small" },
|
|
@@ -12228,9 +11864,7 @@ const MODEL_FAMILY_PROVIDER_IDS = {
|
|
|
12228
11864
|
"seed-2-1-turbo": { "vercel": "bytedance/seed-2.1-turbo" },
|
|
12229
11865
|
"seed-2.1-turbo": { "vercel": "bytedance/seed-2.1-turbo" },
|
|
12230
11866
|
"sonar": { "vercel": "perplexity/sonar" },
|
|
12231
|
-
"step-3-5-flash": { "vercel": "stepfun/step-3.5-flash" },
|
|
12232
11867
|
"step-3-7-flash": { "vercel": "stepfun/step-3.7-flash" },
|
|
12233
|
-
"step-3.5-flash": { "vercel": "stepfun/step-3.5-flash" },
|
|
12234
11868
|
"step-3.7-flash": { "vercel": "stepfun/step-3.7-flash" },
|
|
12235
11869
|
"step-5": { "vercel": "stepfun/step-5-preview" },
|
|
12236
11870
|
"step-5-preview": { "vercel": "stepfun/step-5-preview" },
|
|
@@ -12322,7 +11956,10 @@ const QWEN_3_8_27B_IDS = [
|
|
|
12322
11956
|
"claude-sonnet-5",
|
|
12323
11957
|
"anthropic/claude-sonnet-5",
|
|
12324
11958
|
"claude-haiku-4.5",
|
|
12325
|
-
"anthropic/claude-haiku-4.5"
|
|
11959
|
+
"anthropic/claude-haiku-4.5",
|
|
11960
|
+
"claude-haiku-5-5",
|
|
11961
|
+
"claude-haiku-5.5",
|
|
11962
|
+
"anthropic/claude-haiku-5.5"
|
|
12326
11963
|
]) }), {
|
|
12327
11964
|
...workersAiPrimary(DEEPSEEK_V4_FLASH_IDS),
|
|
12328
11965
|
...vercelPrimary(QWEN_3_8_27B_IDS, ["mixlayer"]),
|
|
@@ -12385,6 +12022,14 @@ const MANUAL_PROVIDER_MAP_OVERRIDES = {
|
|
|
12385
12022
|
"vercel": "anthropic/claude-haiku-4.5",
|
|
12386
12023
|
"anthropic": "claude-haiku-4-5-20251001"
|
|
12387
12024
|
},
|
|
12025
|
+
"claude-haiku-5.5": {
|
|
12026
|
+
"vercel": "anthropic/claude-haiku-5.5",
|
|
12027
|
+
"anthropic": "claude-haiku-5-5"
|
|
12028
|
+
},
|
|
12029
|
+
"anthropic/claude-haiku-5.5": {
|
|
12030
|
+
"vercel": "anthropic/claude-haiku-5.5",
|
|
12031
|
+
"anthropic": "claude-haiku-5-5"
|
|
12032
|
+
},
|
|
12388
12033
|
...workersAiAliases("@cf/zai-org/glm-5.3-flash", GLM_5_3_FLASH_IDS),
|
|
12389
12034
|
"qwen/qwen3.8-27b": {
|
|
12390
12035
|
"mixlayer": "qwen/qwen3.8-27b",
|
|
@@ -12495,7 +12140,7 @@ const CREATOR_TO_PROVIDER = {
|
|
|
12495
12140
|
"unknown": "openai"
|
|
12496
12141
|
};
|
|
12497
12142
|
//#endregion
|
|
12498
|
-
//#region ../shared/dist/chunk-
|
|
12143
|
+
//#region ../shared/dist/chunk-ykhTBHKy.mjs
|
|
12499
12144
|
/**
|
|
12500
12145
|
* The reserved model-facing name of the agent state write tool
|
|
12501
12146
|
* (`packages/runtime/src/engine/state-write-tool.ts`). The model is told to call
|
|
@@ -12899,7 +12544,7 @@ function durableCrawlBudget(plan) {
|
|
|
12899
12544
|
};
|
|
12900
12545
|
}
|
|
12901
12546
|
//#endregion
|
|
12902
|
-
//#region ../shared/dist/chunk-
|
|
12547
|
+
//#region ../shared/dist/chunk-BcuKxfZa.mjs
|
|
12903
12548
|
/**
|
|
12904
12549
|
* Unwraps a single {{variableName}} template to its bare variable name, preserving array/object identity instead of stringifying via the template engine.
|
|
12905
12550
|
* Returns source unchanged when it is not a lone {{...}} wrapper.
|
|
@@ -13005,6 +12650,14 @@ strictObject({
|
|
|
13005
12650
|
required: array(fieldNameSchema).optional(),
|
|
13006
12651
|
additionalProperties: boolean().optional()
|
|
13007
12652
|
}).superRefine((schema, ctx) => addSemanticIssues(schema, ctx));
|
|
12653
|
+
//#endregion
|
|
12654
|
+
//#region ../shared/dist/chunk-DoQn4_pS.mjs
|
|
12655
|
+
/** The Tenancy Assurance Levels, in the order `tenancyAssuranceLevelSchema` is built from. */
|
|
12656
|
+
const TENANCY_ASSURANCE_LEVELS = [
|
|
12657
|
+
"anonymous",
|
|
12658
|
+
"asserted",
|
|
12659
|
+
"verified"
|
|
12660
|
+
];
|
|
13008
12661
|
const FLOW_STEP_TYPES = ["prompt", ...[
|
|
13009
12662
|
"crawl",
|
|
13010
12663
|
"fetch-url",
|
|
@@ -13737,19 +13390,68 @@ object({
|
|
|
13737
13390
|
model: string().min(1).optional(),
|
|
13738
13391
|
calibration: judgmentCalibrationSchema.optional()
|
|
13739
13392
|
});
|
|
13740
|
-
Object.freeze({
|
|
13741
|
-
|
|
13742
|
-
|
|
13743
|
-
|
|
13744
|
-
|
|
13745
|
-
|
|
13746
|
-
|
|
13747
|
-
|
|
13748
|
-
|
|
13749
|
-
|
|
13750
|
-
|
|
13393
|
+
Object.freeze({
|
|
13394
|
+
"convaiinnovations/laya": Object.freeze({
|
|
13395
|
+
id: "convaiinnovations/laya",
|
|
13396
|
+
name: "Laya",
|
|
13397
|
+
description: "Laya is Convai Innovations’ System One evaluation model for fast, structured decisions. It evaluates shared state against choice, score, and yes/no questions and returns typed answers with probabilities in a single forward pass. It supports classification, routing, guardrail checks, and rubric-based assessment without generating free-form text.",
|
|
13398
|
+
contextWindow: 8192,
|
|
13399
|
+
supportedSpecifications: Object.freeze(["v4"]),
|
|
13400
|
+
pricing: Object.freeze({
|
|
13401
|
+
inputPerToken: "0",
|
|
13402
|
+
outputPerToken: "0",
|
|
13403
|
+
inputPer1MTokens: 0,
|
|
13404
|
+
outputPer1MTokens: 0
|
|
13405
|
+
})
|
|
13406
|
+
}),
|
|
13407
|
+
"convaiinnovations/laya-free": Object.freeze({
|
|
13408
|
+
id: "convaiinnovations/laya-free",
|
|
13409
|
+
name: "Laya (Free)",
|
|
13410
|
+
description: "Laya is Convai Innovations’ System One evaluation model for fast, structured decisions. It evaluates shared state against choice, score, and yes/no questions and returns typed answers with probabilities in a single forward pass. It supports classification, routing, guardrail checks, and rubric-based assessment without generating free-form text.",
|
|
13411
|
+
contextWindow: 8192,
|
|
13412
|
+
supportedSpecifications: Object.freeze(["v4"]),
|
|
13413
|
+
pricing: Object.freeze({
|
|
13414
|
+
inputPerToken: "0",
|
|
13415
|
+
outputPerToken: "0",
|
|
13416
|
+
inputPer1MTokens: 0,
|
|
13417
|
+
outputPer1MTokens: 0
|
|
13418
|
+
})
|
|
13419
|
+
}),
|
|
13420
|
+
"liquid/d1": Object.freeze({
|
|
13421
|
+
id: "liquid/d1",
|
|
13422
|
+
name: "Liquid d1",
|
|
13423
|
+
description: "Liquid d1 is a decision model for classification, routing, and scoring. It evaluates shared state against typed Choice, Score, and yes/no questions and returns structured answers with calibrated probabilities through the TypeSafe-compatible System One API.",
|
|
13424
|
+
contextWindow: 65536,
|
|
13425
|
+
supportedSpecifications: Object.freeze(["v4"]),
|
|
13426
|
+
pricing: Object.freeze({
|
|
13427
|
+
inputPerToken: "0.00000004",
|
|
13428
|
+
outputPerToken: "0",
|
|
13429
|
+
inputPer1MTokens: .04,
|
|
13430
|
+
outputPer1MTokens: 0
|
|
13431
|
+
})
|
|
13432
|
+
}),
|
|
13433
|
+
"openai/gpt-6-luna-decisions": Object.freeze({
|
|
13434
|
+
id: "openai/gpt-6-luna-decisions",
|
|
13435
|
+
name: "GPT-6 Luna Decisions",
|
|
13436
|
+
description: "GPT-6 Luna Decisions evaluates classification and scoring questions against shared text and image input using OpenAI's Decisions API. It supports predicate, choice, and score questions with probabilities and confidence values.",
|
|
13437
|
+
contextWindow: 105e4,
|
|
13438
|
+
supportedSpecifications: Object.freeze(["v4"]),
|
|
13439
|
+
pricing: null
|
|
13440
|
+
}),
|
|
13441
|
+
"typesafe-ai/jev": Object.freeze({
|
|
13442
|
+
id: "typesafe-ai/jev",
|
|
13443
|
+
name: "Jev",
|
|
13444
|
+
description: "TypeSafe AI System One decision model. Accepts shared state and typed questions, returning choices, scores, and boolean probabilities. Limits: 64,000 tokens total per request; 32,000 tokens for the state plus the longest question. No streaming or output-token limit.",
|
|
13445
|
+
contextWindow: 32e3,
|
|
13446
|
+
supportedSpecifications: Object.freeze(["v4"]),
|
|
13447
|
+
pricing: Object.freeze({
|
|
13448
|
+
inputPerToken: "0.000000042",
|
|
13449
|
+
outputPerToken: "0",
|
|
13450
|
+
inputPer1MTokens: .042,
|
|
13451
|
+
outputPer1MTokens: 0
|
|
13452
|
+
})
|
|
13751
13453
|
})
|
|
13752
|
-
})
|
|
13454
|
+
});
|
|
13753
13455
|
//#endregion
|
|
13754
13456
|
//#region ../shared/dist/chunk-CIi5jW2t.mjs
|
|
13755
13457
|
/**
|
|
@@ -15266,11 +14968,7 @@ const agentStateChannelSchema = object({
|
|
|
15266
14968
|
* Tenant vs End User). The vocabulary the trust boundary grades into and a
|
|
15267
14969
|
* strategy's assurance floor is expressed in. `CONTEXT.md` → "Assurance level".
|
|
15268
14970
|
*/
|
|
15269
|
-
const tenancyAssuranceLevelSchema = _enum(
|
|
15270
|
-
"anonymous",
|
|
15271
|
-
"asserted",
|
|
15272
|
-
"verified"
|
|
15273
|
-
]);
|
|
14971
|
+
const tenancyAssuranceLevelSchema = _enum(TENANCY_ASSURANCE_LEVELS);
|
|
15274
14972
|
/**
|
|
15275
14973
|
* Named archetype preset for a Tenancy Strategy (ADR 0012): the elicitable, auditable unit.
|
|
15276
14974
|
* internal: Builder-Org internal tooling, product-scoped data, no tenant/end-user required.
|
|
@@ -23079,6 +22777,7 @@ const agentExternalConfigSchema = object({
|
|
|
23079
22777
|
auth: record(string(), any()).optional(),
|
|
23080
22778
|
timeout: number$1().positive().optional(),
|
|
23081
22779
|
retryCount: number$1().int().nonnegative().optional(),
|
|
22780
|
+
identityAssertion: boolean().optional(),
|
|
23082
22781
|
skillOrchestration: union([_enum(["delegate", "managed"]), boolean()]).optional()
|
|
23083
22782
|
});
|
|
23084
22783
|
/**
|
|
@@ -25106,7 +24805,7 @@ const AgentInputSchema = object({
|
|
|
25106
24805
|
durability: agentDurabilityConfigSchema.optional(),
|
|
25107
24806
|
tenancyStrategy: agentRuntimeConfigSchema.shape.tenancyStrategy
|
|
25108
24807
|
}).superRefine((val, ctx) => {
|
|
25109
|
-
const unsupported = val.tenancyStrategy !== void 0 ? [["tenancyStrategy"], "tenancyStrategy is server-authored; save it on an agent"] : val.voice !== void 0 ? [["voice"], "voice is unsupported by agent dispatch"] :
|
|
24808
|
+
const unsupported = val.tenancyStrategy !== void 0 ? [["tenancyStrategy"], "tenancyStrategy is server-authored; save it on an agent"] : val.voice !== void 0 ? [["voice"], "voice is unsupported by agent dispatch"] : void 0;
|
|
25110
24809
|
if (unsupported) {
|
|
25111
24810
|
const [path, message] = unsupported;
|
|
25112
24811
|
ctx.addIssue({
|
|
@@ -25973,7 +25672,10 @@ object({
|
|
|
25973
25672
|
metering: _enum(["active", "legacy-untracked"]),
|
|
25974
25673
|
sleepPolicy: sandboxDeploySleepPolicySchema.optional(),
|
|
25975
25674
|
autoStopInterval: number$1().optional(),
|
|
25976
|
-
autoDeleteInterval: number$1().optional()
|
|
25675
|
+
autoDeleteInterval: number$1().optional(),
|
|
25676
|
+
containerEpoch: number$1().int().optional().describe("Runtype Sandbox: the container epoch the preview URL is bound to; a new epoch mints a new URL."),
|
|
25677
|
+
freshContainer: boolean().optional().describe("Runtype Sandbox: true when this deploy started a new container rather than reusing a running one."),
|
|
25678
|
+
previewUrlChanged: boolean().optional().describe("Runtype Sandbox: true when the preview URL differs from the one the previous successful deploy to this sandbox returned. Re-register any saved copy of the old URL. False for a first deploy.")
|
|
25977
25679
|
});
|
|
25978
25680
|
Object.freeze({
|
|
25979
25681
|
/** Automatic attempts per incident, counted in OPERATIONS rather than requests. */
|
|
@@ -26508,97 +26210,550 @@ object({
|
|
|
26508
26210
|
diagnosticId: string().max(64)
|
|
26509
26211
|
});
|
|
26510
26212
|
//#endregion
|
|
26511
|
-
//#region src/
|
|
26512
|
-
|
|
26513
|
-
|
|
26514
|
-
|
|
26515
|
-
function createConf(encryptionKey) {
|
|
26516
|
-
return new Conf({
|
|
26517
|
-
projectName: "runtype-cli",
|
|
26518
|
-
projectSuffix: "",
|
|
26519
|
-
configName: "credentials",
|
|
26520
|
-
encryptionKey,
|
|
26521
|
-
cwd: CREDENTIALS_DIR
|
|
26522
|
-
});
|
|
26213
|
+
//#region src/config/paths.ts
|
|
26214
|
+
/** Root state directory: RUNTYPE_STATE_DIR or ~/.runtype */
|
|
26215
|
+
function getRuntypeHomeDir() {
|
|
26216
|
+
return process.env.RUNTYPE_STATE_DIR || path$2.join(os$1.homedir(), ".runtype");
|
|
26523
26217
|
}
|
|
26524
|
-
|
|
26525
|
-
|
|
26526
|
-
const
|
|
26527
|
-
|
|
26218
|
+
/** Per-project state dir: ~/.runtype/projects/{hash}/ */
|
|
26219
|
+
function getProjectStateDir(projectDir) {
|
|
26220
|
+
const dir = projectDir || process.cwd();
|
|
26221
|
+
const hash = crypto$3.createHash("sha256").update(dir).digest("hex").slice(0, 12);
|
|
26222
|
+
const stateDir = path$2.join(getRuntypeHomeDir(), "projects", hash);
|
|
26223
|
+
const breadcrumb = path$2.join(stateDir, "project-path.txt");
|
|
26224
|
+
if (!fs$1.existsSync(breadcrumb)) {
|
|
26225
|
+
fs$1.mkdirSync(stateDir, { recursive: true });
|
|
26226
|
+
fs$1.writeFileSync(breadcrumb, dir);
|
|
26227
|
+
}
|
|
26228
|
+
return stateDir;
|
|
26528
26229
|
}
|
|
26529
|
-
|
|
26530
|
-
|
|
26531
|
-
|
|
26532
|
-
|
|
26533
|
-
|
|
26230
|
+
/** Marathon state dir: ~/.runtype/projects/{hash}/marathons/ */
|
|
26231
|
+
function getMarathonStateDir(projectDir) {
|
|
26232
|
+
return path$2.join(getProjectStateDir(projectDir), "marathons");
|
|
26233
|
+
}
|
|
26234
|
+
//#endregion
|
|
26235
|
+
//#region src/auth/secret-backend.ts
|
|
26236
|
+
const KEYCHAIN_SERVICE = "runtype-cli";
|
|
26237
|
+
let keyringModule;
|
|
26238
|
+
/** Load the native keyring binding lazily; a missing prebuilt binary means "no keychain", not a crash. */
|
|
26239
|
+
function loadKeyring() {
|
|
26240
|
+
keyringModule ??= import("@napi-rs/keyring").then((mod) => mod.AsyncEntry).catch(() => null);
|
|
26241
|
+
return keyringModule;
|
|
26242
|
+
}
|
|
26243
|
+
var KeychainSecretBackend = class {
|
|
26244
|
+
kind = "keychain";
|
|
26245
|
+
async entry(name) {
|
|
26246
|
+
const AsyncEntry = await loadKeyring();
|
|
26247
|
+
if (!AsyncEntry) throw new Error("OS keychain is not available on this platform.");
|
|
26248
|
+
return new AsyncEntry(KEYCHAIN_SERVICE, name);
|
|
26249
|
+
}
|
|
26250
|
+
async get(name) {
|
|
26251
|
+
return await (await this.entry(name)).getPassword() ?? null;
|
|
26252
|
+
}
|
|
26253
|
+
async set(name, value) {
|
|
26254
|
+
await (await this.entry(name)).setPassword(value);
|
|
26255
|
+
}
|
|
26256
|
+
async delete(name) {
|
|
26257
|
+
await (await this.entry(name)).deleteCredential();
|
|
26258
|
+
}
|
|
26259
|
+
};
|
|
26260
|
+
/**
|
|
26261
|
+
* Fallback for hosts with no usable OS keychain (headless Linux, containers, CI).
|
|
26262
|
+
* Values are AES-256-CBC encrypted with a host/user-derived key: obfuscation at rest, not secrecy
|
|
26263
|
+
* from someone who can read the file. The file is written 0600 inside a 0700 directory.
|
|
26264
|
+
*/
|
|
26265
|
+
var FileSecretBackend = class {
|
|
26266
|
+
filePath;
|
|
26267
|
+
kind = "file";
|
|
26268
|
+
constructor(filePath = path.join(getRuntypeHomeDir(), "secrets.json")) {
|
|
26269
|
+
this.filePath = filePath;
|
|
26270
|
+
}
|
|
26271
|
+
async get(name) {
|
|
26272
|
+
const encrypted = this.read()[name];
|
|
26273
|
+
return encrypted ? decryptWithMachineKey(encrypted) : null;
|
|
26274
|
+
}
|
|
26275
|
+
async set(name, value) {
|
|
26276
|
+
this.write({
|
|
26277
|
+
...this.read(),
|
|
26278
|
+
[name]: encryptWithMachineKey(value)
|
|
26279
|
+
});
|
|
26280
|
+
}
|
|
26281
|
+
async delete(name) {
|
|
26282
|
+
const secrets = this.read();
|
|
26283
|
+
if (!(name in secrets)) return;
|
|
26284
|
+
delete secrets[name];
|
|
26285
|
+
this.write(secrets);
|
|
26286
|
+
}
|
|
26287
|
+
read() {
|
|
26534
26288
|
try {
|
|
26535
|
-
|
|
26536
|
-
|
|
26537
|
-
|
|
26538
|
-
|
|
26539
|
-
if (fs$1.existsSync(CREDENTIALS_PATH)) fs$1.unlinkSync(CREDENTIALS_PATH);
|
|
26540
|
-
} catch {}
|
|
26541
|
-
this.config = createConf(this.encryptionKey);
|
|
26542
|
-
} else throw error;
|
|
26289
|
+
const parsed = JSON.parse(fs.readFileSync(this.filePath, "utf8"));
|
|
26290
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
26291
|
+
} catch {
|
|
26292
|
+
return {};
|
|
26543
26293
|
}
|
|
26544
26294
|
}
|
|
26545
|
-
|
|
26546
|
-
|
|
26547
|
-
|
|
26548
|
-
|
|
26549
|
-
|
|
26550
|
-
|
|
26551
|
-
|
|
26295
|
+
write(secrets) {
|
|
26296
|
+
writePrivateJsonFile(this.filePath, secrets);
|
|
26297
|
+
}
|
|
26298
|
+
};
|
|
26299
|
+
/** Write JSON atomically (temp file + rename) with owner-only permissions. */
|
|
26300
|
+
function writePrivateJsonFile(filePath, data) {
|
|
26301
|
+
const directory = path.dirname(filePath);
|
|
26302
|
+
fs.mkdirSync(directory, {
|
|
26303
|
+
recursive: true,
|
|
26304
|
+
mode: 448
|
|
26305
|
+
});
|
|
26306
|
+
const temporaryPath = path.join(directory, `.${path.basename(filePath)}.${crypto$1.randomUUID()}.tmp`);
|
|
26307
|
+
try {
|
|
26308
|
+
fs.writeFileSync(temporaryPath, `${JSON.stringify(data, null, 2)}\n`, { mode: 384 });
|
|
26309
|
+
fs.renameSync(temporaryPath, filePath);
|
|
26310
|
+
} finally {
|
|
26311
|
+
fs.rmSync(temporaryPath, { force: true });
|
|
26312
|
+
}
|
|
26313
|
+
}
|
|
26314
|
+
function getMachineKey() {
|
|
26315
|
+
return crypto$1.createHash("sha256").update(`runtype-cli-${os.hostname()}-${os.userInfo().username}`).digest("hex").substring(0, 32);
|
|
26316
|
+
}
|
|
26317
|
+
function encryptWithMachineKey(text) {
|
|
26318
|
+
const iv = crypto$1.randomBytes(16);
|
|
26319
|
+
const cipher = crypto$1.createCipheriv("aes-256-cbc", Buffer.from(getMachineKey()), iv);
|
|
26320
|
+
return `${iv.toString("hex")}:${cipher.update(text, "utf8", "hex")}${cipher.final("hex")}`;
|
|
26321
|
+
}
|
|
26322
|
+
function decryptWithMachineKey(text) {
|
|
26323
|
+
const [ivHex, encrypted] = text.split(":");
|
|
26324
|
+
const decipher = crypto$1.createDecipheriv("aes-256-cbc", Buffer.from(getMachineKey()), Buffer.from(ivHex, "hex"));
|
|
26325
|
+
return `${decipher.update(encrypted, "hex", "utf8")}${decipher.final("utf8")}`;
|
|
26326
|
+
}
|
|
26327
|
+
/** `RUNTYPE_CREDENTIAL_STORE=file|keychain` pins the backend; anything else is `auto`. */
|
|
26328
|
+
function getSecretStorePreference() {
|
|
26329
|
+
const value = process.env.RUNTYPE_CREDENTIAL_STORE?.trim().toLowerCase();
|
|
26330
|
+
return value === "file" || value === "keychain" ? value : "auto";
|
|
26331
|
+
}
|
|
26332
|
+
//#endregion
|
|
26333
|
+
//#region src/auth/account-store.ts
|
|
26334
|
+
const LAST_USED_RESOLUTION_MS = 6e4;
|
|
26335
|
+
/** Canonical API base used for account identity; mirrors normalizeApiUrl for valid URLs. */
|
|
26336
|
+
function canonicalApiUrl(value) {
|
|
26337
|
+
try {
|
|
26338
|
+
return new URL(value).toString().replace(/\/+$/, "");
|
|
26339
|
+
} catch {
|
|
26340
|
+
return value.replace(/\/+$/, "");
|
|
26341
|
+
}
|
|
26342
|
+
}
|
|
26343
|
+
function accountIdFor(apiUrl, orgId, userId) {
|
|
26344
|
+
return `${canonicalApiUrl(apiUrl)}#${orgId ?? `user:${userId}`}`;
|
|
26345
|
+
}
|
|
26346
|
+
function recency(account) {
|
|
26347
|
+
return Date.parse(account.lastUsedAt ?? account.createdAt) || 0;
|
|
26348
|
+
}
|
|
26349
|
+
function mostRecent(accounts) {
|
|
26350
|
+
return [...accounts].sort((a, b) => recency(b) - recency(a))[0] ?? null;
|
|
26351
|
+
}
|
|
26352
|
+
/**
|
|
26353
|
+
* Pick the stored login for an invocation. A requested organization only ever matches that
|
|
26354
|
+
* organization: a project linked to org A must never silently run with org B's credential.
|
|
26355
|
+
* Without one, the default login wins when it targets the explicit API URL (if any).
|
|
26356
|
+
*/
|
|
26357
|
+
function selectAccount(accounts, options) {
|
|
26358
|
+
const apiUrl = options.apiUrl ? canonicalApiUrl(options.apiUrl) : void 0;
|
|
26359
|
+
const onTarget = apiUrl ? accounts.filter((account) => account.apiUrl === apiUrl) : accounts;
|
|
26360
|
+
if (options.orgId) return mostRecent(onTarget.filter((account) => account.orgId === options.orgId));
|
|
26361
|
+
return onTarget.find((account) => account.id === options.defaultAccountId) ?? mostRecent(onTarget);
|
|
26362
|
+
}
|
|
26363
|
+
const LOCK_STALE_MS = 3e4;
|
|
26364
|
+
const LOCK_TIMEOUT_MS = 1e4;
|
|
26365
|
+
const heldLocks = new AsyncLocalStorage();
|
|
26366
|
+
/** Multi-account login store: one org-scoped credential per (API URL, organization). */
|
|
26367
|
+
var AccountStore = class {
|
|
26368
|
+
filePath;
|
|
26369
|
+
lockPath;
|
|
26370
|
+
keychain;
|
|
26371
|
+
fileBackend;
|
|
26372
|
+
legacyCredentialsDir;
|
|
26373
|
+
secretPrefix;
|
|
26374
|
+
constructor(options = {}) {
|
|
26375
|
+
const homeDir = path.resolve(options.homeDir ?? getRuntypeHomeDir());
|
|
26376
|
+
this.filePath = path.join(homeDir, "accounts.json");
|
|
26377
|
+
this.lockPath = `${this.filePath}.lock`;
|
|
26378
|
+
this.keychain = options.keychain ?? new KeychainSecretBackend();
|
|
26379
|
+
this.fileBackend = options.file ?? new FileSecretBackend(path.join(homeDir, "secrets.json"));
|
|
26380
|
+
const defaultHome = path.join(os.homedir(), ".runtype");
|
|
26381
|
+
const isDefaultHome = homeDir === defaultHome;
|
|
26382
|
+
this.legacyCredentialsDir = options.legacyCredentialsDir ?? (isDefaultHome ? defaultHome : null);
|
|
26383
|
+
this.secretPrefix = isDefaultHome ? "" : `${crypto$1.createHash("sha256").update(homeDir).digest("hex").slice(0, 12)}/`;
|
|
26384
|
+
}
|
|
26385
|
+
async list() {
|
|
26386
|
+
return (await this.load()).accounts;
|
|
26387
|
+
}
|
|
26388
|
+
async get(id) {
|
|
26389
|
+
return (await this.list()).find((account) => account.id === id) ?? null;
|
|
26390
|
+
}
|
|
26391
|
+
async getDefault() {
|
|
26392
|
+
const data = await this.load();
|
|
26393
|
+
return data.accounts.find((account) => account.id === data.defaultAccountId) ?? null;
|
|
26394
|
+
}
|
|
26395
|
+
async setDefault(id) {
|
|
26396
|
+
await this.withLock(async () => {
|
|
26397
|
+
const data = await this.load();
|
|
26398
|
+
if (id !== null && !data.accounts.some((account) => account.id === id)) throw new Error(`No stored login with id ${id}.`);
|
|
26399
|
+
this.persist({
|
|
26400
|
+
...data,
|
|
26401
|
+
defaultAccountId: id
|
|
26402
|
+
});
|
|
26403
|
+
});
|
|
26404
|
+
}
|
|
26405
|
+
/** Store (or replace) the login for this credential's org, secret first so metadata never points at nothing. */
|
|
26406
|
+
async save(input, options = {}) {
|
|
26407
|
+
return this.withLock(async () => {
|
|
26408
|
+
const apiUrl = canonicalApiUrl(input.apiUrl);
|
|
26409
|
+
const orgId = input.orgId ?? null;
|
|
26410
|
+
const id = accountIdFor(apiUrl, orgId, input.userId);
|
|
26411
|
+
const backend = await this.writeSecret(this.apiKeySecretName(id), input.apiKey);
|
|
26412
|
+
const { claim: claimInput } = options;
|
|
26413
|
+
let claim;
|
|
26414
|
+
if (claimInput) {
|
|
26415
|
+
const { claimToken, ...claimMetadata } = claimInput;
|
|
26416
|
+
await backend.set(this.claimTokenSecretName(id), claimToken);
|
|
26417
|
+
claim = claimMetadata;
|
|
26418
|
+
} else await this.deleteSecret(backend, this.claimTokenSecretName(id));
|
|
26419
|
+
const data = await this.load();
|
|
26420
|
+
const previous = data.accounts.find((account) => account.id === id);
|
|
26421
|
+
if (previous && previous.secretStore !== backend.kind) {
|
|
26422
|
+
await this.deleteSecret(this.backendFor(previous), this.apiKeySecretName(id));
|
|
26423
|
+
await this.deleteSecret(this.backendFor(previous), this.claimTokenSecretName(id));
|
|
26424
|
+
}
|
|
26425
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
26426
|
+
const account = {
|
|
26427
|
+
id,
|
|
26428
|
+
apiUrl,
|
|
26429
|
+
orgId,
|
|
26430
|
+
userId: input.userId,
|
|
26431
|
+
secretStore: backend.kind,
|
|
26432
|
+
createdAt: now,
|
|
26433
|
+
lastUsedAt: now,
|
|
26434
|
+
...claim ? { claim } : {}
|
|
26435
|
+
};
|
|
26436
|
+
const accounts = [...data.accounts.filter((existing) => existing.id !== id), account];
|
|
26437
|
+
const makeDefault = options.makeDefault ?? !data.defaultAccountId;
|
|
26438
|
+
this.persist({
|
|
26439
|
+
version: 1,
|
|
26440
|
+
accounts,
|
|
26441
|
+
defaultAccountId: makeDefault ? id : data.defaultAccountId
|
|
26442
|
+
});
|
|
26443
|
+
return account;
|
|
26444
|
+
});
|
|
26445
|
+
}
|
|
26446
|
+
async getApiKey(account) {
|
|
26447
|
+
return this.backendFor(account).get(this.apiKeySecretName(account.id));
|
|
26448
|
+
}
|
|
26449
|
+
async getClaimToken(account) {
|
|
26450
|
+
if (!account.claim) return null;
|
|
26451
|
+
return this.backendFor(account).get(this.claimTokenSecretName(account.id));
|
|
26452
|
+
}
|
|
26453
|
+
async setClaim(id, claim) {
|
|
26454
|
+
await this.withLock(async () => {
|
|
26455
|
+
const data = await this.load();
|
|
26456
|
+
const account = data.accounts.find((existing) => existing.id === id);
|
|
26457
|
+
if (!account) throw new Error(`No stored login with id ${id}.`);
|
|
26458
|
+
const backend = this.backendFor(account);
|
|
26459
|
+
let nextClaim;
|
|
26460
|
+
if (claim) {
|
|
26461
|
+
const { claimToken, ...metadata } = claim;
|
|
26462
|
+
if (claimToken) await backend.set(this.claimTokenSecretName(id), claimToken);
|
|
26463
|
+
nextClaim = metadata;
|
|
26464
|
+
} else await this.deleteSecret(backend, this.claimTokenSecretName(id));
|
|
26465
|
+
const { claim: _previousClaim, ...rest } = account;
|
|
26466
|
+
const updated = nextClaim ? {
|
|
26467
|
+
...rest,
|
|
26468
|
+
claim: nextClaim
|
|
26469
|
+
} : rest;
|
|
26470
|
+
this.persist({
|
|
26471
|
+
...data,
|
|
26472
|
+
accounts: data.accounts.map((existing) => existing.id === id ? updated : existing)
|
|
26473
|
+
});
|
|
26474
|
+
});
|
|
26475
|
+
}
|
|
26476
|
+
async remove(id) {
|
|
26477
|
+
return this.withLock(async () => {
|
|
26478
|
+
const data = await this.load();
|
|
26479
|
+
const account = data.accounts.find((existing) => existing.id === id);
|
|
26480
|
+
if (!account) return false;
|
|
26481
|
+
const backend = this.backendFor(account);
|
|
26482
|
+
await this.deleteSecret(backend, this.apiKeySecretName(id));
|
|
26483
|
+
await this.deleteSecret(backend, this.claimTokenSecretName(id));
|
|
26484
|
+
this.persist({
|
|
26485
|
+
version: 1,
|
|
26486
|
+
accounts: data.accounts.filter((existing) => existing.id !== id),
|
|
26487
|
+
defaultAccountId: data.defaultAccountId === id ? null : data.defaultAccountId
|
|
26488
|
+
});
|
|
26489
|
+
return true;
|
|
26490
|
+
});
|
|
26552
26491
|
}
|
|
26553
|
-
|
|
26554
|
-
const
|
|
26555
|
-
|
|
26556
|
-
|
|
26492
|
+
async touch(id) {
|
|
26493
|
+
const current = (await this.load()).accounts.find((account) => account.id === id);
|
|
26494
|
+
if (!current) return;
|
|
26495
|
+
const last = current.lastUsedAt ? Date.parse(current.lastUsedAt) : 0;
|
|
26496
|
+
if (Date.now() - last < LAST_USED_RESOLUTION_MS) return;
|
|
26497
|
+
await this.withLock(async () => {
|
|
26498
|
+
const data = await this.load();
|
|
26499
|
+
const account = data.accounts.find((existing) => existing.id === id);
|
|
26500
|
+
if (!account) return;
|
|
26501
|
+
account.lastUsedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
26502
|
+
this.persist(data);
|
|
26503
|
+
});
|
|
26504
|
+
}
|
|
26505
|
+
apiKeySecretName(accountId) {
|
|
26506
|
+
return `${this.secretPrefix}${accountId}:api-key`;
|
|
26507
|
+
}
|
|
26508
|
+
claimTokenSecretName(accountId) {
|
|
26509
|
+
return `${this.secretPrefix}${accountId}:claim-token`;
|
|
26557
26510
|
}
|
|
26558
26511
|
/**
|
|
26559
|
-
*
|
|
26560
|
-
*
|
|
26561
|
-
* Storing credentials WITHOUT claimState invalidates any in-flight claim ceremony.
|
|
26512
|
+
* Serialize read-modify-write of accounts.json (and secrets.json) across CLI processes with an
|
|
26513
|
+
* exclusive lock file. Re-entrant along one async call chain so a locked operation may call another.
|
|
26562
26514
|
*/
|
|
26563
|
-
async
|
|
26564
|
-
const
|
|
26565
|
-
|
|
26566
|
-
|
|
26567
|
-
|
|
26568
|
-
|
|
26515
|
+
async withLock(operation) {
|
|
26516
|
+
const held = heldLocks.getStore() ?? /* @__PURE__ */ new Set();
|
|
26517
|
+
if (held.has(this.lockPath)) return operation();
|
|
26518
|
+
fs.mkdirSync(path.dirname(this.lockPath), {
|
|
26519
|
+
recursive: true,
|
|
26520
|
+
mode: 448
|
|
26521
|
+
});
|
|
26522
|
+
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
26523
|
+
for (;;) try {
|
|
26524
|
+
fs.closeSync(fs.openSync(this.lockPath, "wx", 384));
|
|
26525
|
+
break;
|
|
26526
|
+
} catch (error) {
|
|
26527
|
+
if (error.code !== "EEXIST") throw error;
|
|
26528
|
+
if (this.lockIsStale()) {
|
|
26529
|
+
fs.rmSync(this.lockPath, { force: true });
|
|
26530
|
+
continue;
|
|
26531
|
+
}
|
|
26532
|
+
if (Date.now() > deadline) throw new Error(`Timed out waiting for another runtype process to finish updating logins (${this.lockPath}).`, { cause: error });
|
|
26533
|
+
await new Promise((resolve) => setTimeout(resolve, 25 + Math.random() * 50));
|
|
26534
|
+
}
|
|
26535
|
+
try {
|
|
26536
|
+
return await heldLocks.run(/* @__PURE__ */ new Set([...held, this.lockPath]), operation);
|
|
26537
|
+
} finally {
|
|
26538
|
+
fs.rmSync(this.lockPath, { force: true });
|
|
26539
|
+
}
|
|
26540
|
+
}
|
|
26541
|
+
lockIsStale() {
|
|
26542
|
+
try {
|
|
26543
|
+
return Date.now() - fs.statSync(this.lockPath).mtimeMs > LOCK_STALE_MS;
|
|
26544
|
+
} catch {
|
|
26545
|
+
return false;
|
|
26546
|
+
}
|
|
26547
|
+
}
|
|
26548
|
+
backendFor(account) {
|
|
26549
|
+
return account.secretStore === "keychain" ? this.keychain : this.fileBackend;
|
|
26550
|
+
}
|
|
26551
|
+
async writeSecret(name, value) {
|
|
26552
|
+
const preference = getSecretStorePreference();
|
|
26553
|
+
if (preference === "file") {
|
|
26554
|
+
await this.fileBackend.set(name, value);
|
|
26555
|
+
return this.fileBackend;
|
|
26556
|
+
}
|
|
26557
|
+
try {
|
|
26558
|
+
await this.keychain.set(name, value);
|
|
26559
|
+
return this.keychain;
|
|
26560
|
+
} catch (error) {
|
|
26561
|
+
if (preference === "keychain") throw error;
|
|
26562
|
+
await this.fileBackend.set(name, value);
|
|
26563
|
+
return this.fileBackend;
|
|
26564
|
+
}
|
|
26565
|
+
}
|
|
26566
|
+
async deleteSecret(backend, name) {
|
|
26567
|
+
try {
|
|
26568
|
+
await backend.delete(name);
|
|
26569
|
+
} catch {}
|
|
26570
|
+
}
|
|
26571
|
+
readFile() {
|
|
26572
|
+
let raw;
|
|
26573
|
+
try {
|
|
26574
|
+
raw = fs.readFileSync(this.filePath, "utf8");
|
|
26575
|
+
} catch {
|
|
26576
|
+
return null;
|
|
26577
|
+
}
|
|
26578
|
+
try {
|
|
26579
|
+
const parsed = JSON.parse(raw);
|
|
26580
|
+
if (parsed?.version === 1 && Array.isArray(parsed.accounts)) return {
|
|
26581
|
+
version: 1,
|
|
26582
|
+
defaultAccountId: parsed.defaultAccountId ?? null,
|
|
26583
|
+
accounts: parsed.accounts
|
|
26584
|
+
};
|
|
26585
|
+
} catch {}
|
|
26586
|
+
return {
|
|
26587
|
+
version: 1,
|
|
26588
|
+
defaultAccountId: null,
|
|
26589
|
+
accounts: []
|
|
26569
26590
|
};
|
|
26570
|
-
|
|
26571
|
-
|
|
26572
|
-
|
|
26573
|
-
|
|
26574
|
-
|
|
26575
|
-
|
|
26576
|
-
|
|
26577
|
-
|
|
26578
|
-
|
|
26591
|
+
}
|
|
26592
|
+
async load() {
|
|
26593
|
+
const existing = this.readFile();
|
|
26594
|
+
if (existing) return existing;
|
|
26595
|
+
const empty = {
|
|
26596
|
+
version: 1,
|
|
26597
|
+
defaultAccountId: null,
|
|
26598
|
+
accounts: []
|
|
26599
|
+
};
|
|
26600
|
+
const legacy = this.readLegacyCredentials();
|
|
26601
|
+
if (!legacy) return empty;
|
|
26602
|
+
return this.withLock(async () => {
|
|
26603
|
+
const raced = this.readFile();
|
|
26604
|
+
if (raced) return raced;
|
|
26605
|
+
this.persist(empty);
|
|
26606
|
+
try {
|
|
26607
|
+
await this.save(legacy.account, {
|
|
26608
|
+
claim: legacy.claim,
|
|
26609
|
+
makeDefault: true
|
|
26610
|
+
});
|
|
26611
|
+
} catch {
|
|
26612
|
+
fs.rmSync(this.filePath, { force: true });
|
|
26613
|
+
return empty;
|
|
26614
|
+
}
|
|
26615
|
+
return this.readFile() ?? empty;
|
|
26616
|
+
});
|
|
26617
|
+
}
|
|
26618
|
+
readLegacyCredentials() {
|
|
26619
|
+
if (!this.legacyCredentialsDir) return null;
|
|
26620
|
+
if (!fs.existsSync(path.join(this.legacyCredentialsDir, "credentials.json"))) return null;
|
|
26621
|
+
try {
|
|
26622
|
+
const store = new Conf({
|
|
26623
|
+
projectName: "runtype-cli",
|
|
26624
|
+
projectSuffix: "",
|
|
26625
|
+
configName: "credentials",
|
|
26626
|
+
encryptionKey: getMachineKey(),
|
|
26627
|
+
cwd: this.legacyCredentialsDir
|
|
26628
|
+
}).store;
|
|
26629
|
+
if (!store.apiKey || !store.userId) return null;
|
|
26630
|
+
const apiKey = decryptWithMachineKey(store.apiKey);
|
|
26631
|
+
const claim = store.claimToken && store.registrationId ? {
|
|
26632
|
+
claimToken: decryptWithMachineKey(store.claimToken),
|
|
26633
|
+
registrationId: store.registrationId,
|
|
26634
|
+
claimEmail: store.claimEmail,
|
|
26635
|
+
credentialExpires: store.credentialExpires,
|
|
26636
|
+
claimTokenExpires: store.claimTokenExpires,
|
|
26637
|
+
apiUrl: store.claimApiUrl
|
|
26638
|
+
} : void 0;
|
|
26639
|
+
return {
|
|
26640
|
+
account: {
|
|
26641
|
+
apiKey,
|
|
26642
|
+
userId: store.userId,
|
|
26643
|
+
orgId: store.orgId ?? null,
|
|
26644
|
+
apiUrl: store.apiUrl || "https://api.runtype.com"
|
|
26645
|
+
},
|
|
26646
|
+
claim
|
|
26647
|
+
};
|
|
26648
|
+
} catch {
|
|
26649
|
+
return null;
|
|
26650
|
+
}
|
|
26651
|
+
}
|
|
26652
|
+
persist(data) {
|
|
26653
|
+
writePrivateJsonFile(this.filePath, data);
|
|
26654
|
+
}
|
|
26655
|
+
};
|
|
26656
|
+
//#endregion
|
|
26657
|
+
//#region src/auth/credential-store.ts
|
|
26658
|
+
var credential_store_exports = /* @__PURE__ */ __exportAll({ CredentialStore: () => CredentialStore });
|
|
26659
|
+
/**
|
|
26660
|
+
* Credential access for CLI commands. "Active" methods act on the login this invocation
|
|
26661
|
+
* resolved to (`--org` > `RUNTYPE_ORG` > `.runtype/project.json` > default login); the
|
|
26662
|
+
* account methods manage every stored login. Secrets live in the OS keychain when available.
|
|
26663
|
+
*/
|
|
26664
|
+
var CredentialStore = class {
|
|
26665
|
+
accounts;
|
|
26666
|
+
constructor(accounts = new AccountStore()) {
|
|
26667
|
+
this.accounts = accounts;
|
|
26668
|
+
}
|
|
26669
|
+
async resolveCredentials(selection) {
|
|
26670
|
+
const account = selectAccount(await this.accounts.list(), {
|
|
26671
|
+
...selection,
|
|
26672
|
+
defaultAccountId: (await this.accounts.getDefault())?.id ?? null
|
|
26673
|
+
});
|
|
26674
|
+
if (!account) return null;
|
|
26675
|
+
let apiKey;
|
|
26676
|
+
try {
|
|
26677
|
+
apiKey = await this.accounts.getApiKey(account);
|
|
26678
|
+
} catch (error) {
|
|
26679
|
+
return {
|
|
26680
|
+
account,
|
|
26681
|
+
credentials: null,
|
|
26682
|
+
readError: error instanceof Error ? error.message : String(error)
|
|
26683
|
+
};
|
|
26684
|
+
}
|
|
26685
|
+
if (!apiKey) return {
|
|
26686
|
+
account,
|
|
26687
|
+
credentials: null
|
|
26688
|
+
};
|
|
26689
|
+
await this.accounts.touch(account.id).catch(() => void 0);
|
|
26690
|
+
return {
|
|
26691
|
+
account,
|
|
26692
|
+
credentials: {
|
|
26693
|
+
accountId: account.id,
|
|
26694
|
+
apiKey,
|
|
26695
|
+
userId: account.userId,
|
|
26696
|
+
...account.orgId ? { orgId: account.orgId } : {},
|
|
26697
|
+
apiUrl: account.apiUrl,
|
|
26698
|
+
createdAt: account.createdAt,
|
|
26699
|
+
lastUsed: account.lastUsedAt
|
|
26700
|
+
}
|
|
26579
26701
|
};
|
|
26580
|
-
if (state.claimEmail !== void 0) data.claimEmail = state.claimEmail;
|
|
26581
|
-
if (state.credentialExpires !== void 0) data.credentialExpires = state.credentialExpires;
|
|
26582
|
-
if (state.claimTokenExpires !== void 0) data.claimTokenExpires = state.claimTokenExpires;
|
|
26583
|
-
if (state.apiUrl !== void 0) data.claimApiUrl = state.apiUrl;
|
|
26584
|
-
return data;
|
|
26585
26702
|
}
|
|
26586
26703
|
async getApiKey() {
|
|
26587
|
-
|
|
26588
|
-
if (!encrypted) return null;
|
|
26589
|
-
this.config.set("lastUsed", (/* @__PURE__ */ new Date()).toISOString());
|
|
26590
|
-
return this.decrypt(encrypted);
|
|
26704
|
+
return (await this.getCredentials())?.apiKey ?? null;
|
|
26591
26705
|
}
|
|
26592
26706
|
async getCredentials() {
|
|
26593
|
-
|
|
26594
|
-
|
|
26595
|
-
|
|
26596
|
-
|
|
26597
|
-
|
|
26707
|
+
return (await loadEffectiveContext()).credentials;
|
|
26708
|
+
}
|
|
26709
|
+
async hasCredentials() {
|
|
26710
|
+
return await this.getCredentials() !== null;
|
|
26711
|
+
}
|
|
26712
|
+
/**
|
|
26713
|
+
* Store a login for the credential's organization, replacing any earlier login for the same
|
|
26714
|
+
* (API URL, org). Passing claimState stores the pre-claim credential and its claim token
|
|
26715
|
+
* together; storing without it ends any in-flight claim ceremony for that login.
|
|
26716
|
+
*/
|
|
26717
|
+
async saveCredentials(credentials, claimState, options = {}) {
|
|
26718
|
+
const context = await loadEffectiveContext();
|
|
26719
|
+
const input = {
|
|
26720
|
+
apiKey: credentials.apiKey,
|
|
26721
|
+
userId: credentials.userId,
|
|
26722
|
+
orgId: credentials.orgId ?? null,
|
|
26723
|
+
apiUrl: credentials.apiUrl ?? context.apiUrl
|
|
26598
26724
|
};
|
|
26725
|
+
const hasDefault = await this.accounts.getDefault() !== null;
|
|
26726
|
+
const makeDefault = options.makeDefault ?? (!context.projectLink || !hasDefault);
|
|
26727
|
+
const claim = claimState ? {
|
|
26728
|
+
...claimState,
|
|
26729
|
+
accountId: void 0
|
|
26730
|
+
} : void 0;
|
|
26731
|
+
const account = await this.accounts.save(input, {
|
|
26732
|
+
claim,
|
|
26733
|
+
makeDefault
|
|
26734
|
+
});
|
|
26735
|
+
resetEffectiveContext();
|
|
26736
|
+
return account;
|
|
26599
26737
|
}
|
|
26738
|
+
/** Remove the active login (logout); other stored logins are untouched. */
|
|
26600
26739
|
async clearCredentials() {
|
|
26601
|
-
|
|
26740
|
+
const active = (await loadEffectiveContext()).account;
|
|
26741
|
+
if (!active) return null;
|
|
26742
|
+
await this.accounts.remove(active.id);
|
|
26743
|
+
resetEffectiveContext();
|
|
26744
|
+
return active;
|
|
26745
|
+
}
|
|
26746
|
+
/** Pending claim on the active login, else the most recent login with one. */
|
|
26747
|
+
async getClaimState() {
|
|
26748
|
+
const account = await this.pendingClaimAccount();
|
|
26749
|
+
if (!account?.claim) return null;
|
|
26750
|
+
const claimToken = await this.accounts.getClaimToken(account);
|
|
26751
|
+
if (!claimToken) return null;
|
|
26752
|
+
return {
|
|
26753
|
+
...account.claim,
|
|
26754
|
+
claimToken,
|
|
26755
|
+
accountId: account.id
|
|
26756
|
+
};
|
|
26602
26757
|
}
|
|
26603
26758
|
/**
|
|
26604
26759
|
* Persist agent-auth claim ceremony state alongside the pre-claim credential.
|
|
@@ -26606,54 +26761,129 @@ var CredentialStore = class {
|
|
|
26606
26761
|
* The claim token is encrypted like the API key since it can mint the post-claim credential.
|
|
26607
26762
|
*/
|
|
26608
26763
|
async saveClaimState(state) {
|
|
26609
|
-
|
|
26610
|
-
|
|
26611
|
-
|
|
26612
|
-
|
|
26613
|
-
|
|
26614
|
-
if (!claimToken || !registrationId) return null;
|
|
26615
|
-
return {
|
|
26616
|
-
claimToken: this.decrypt(claimToken),
|
|
26617
|
-
registrationId,
|
|
26618
|
-
claimEmail: this.config.get("claimEmail"),
|
|
26619
|
-
credentialExpires: this.config.get("credentialExpires"),
|
|
26620
|
-
claimTokenExpires: this.config.get("claimTokenExpires"),
|
|
26621
|
-
apiUrl: this.config.get("claimApiUrl")
|
|
26622
|
-
};
|
|
26764
|
+
const { accountId, ...claim } = state;
|
|
26765
|
+
const targetId = accountId ?? (await this.pendingClaimAccount())?.id ?? (await loadEffectiveContext()).account?.id;
|
|
26766
|
+
if (!targetId) throw new Error("No stored login to attach the claim to.");
|
|
26767
|
+
await this.accounts.setClaim(targetId, claim);
|
|
26768
|
+
resetEffectiveContext();
|
|
26623
26769
|
}
|
|
26624
26770
|
async clearClaimState() {
|
|
26625
|
-
const
|
|
26626
|
-
|
|
26627
|
-
|
|
26628
|
-
|
|
26629
|
-
delete store.credentialExpires;
|
|
26630
|
-
delete store.claimTokenExpires;
|
|
26631
|
-
delete store.claimApiUrl;
|
|
26632
|
-
this.replaceStore(store);
|
|
26771
|
+
const account = await this.pendingClaimAccount();
|
|
26772
|
+
if (!account) return;
|
|
26773
|
+
await this.accounts.setClaim(account.id, null);
|
|
26774
|
+
resetEffectiveContext();
|
|
26633
26775
|
}
|
|
26634
|
-
async
|
|
26635
|
-
return
|
|
26636
|
-
}
|
|
26637
|
-
|
|
26638
|
-
|
|
26639
|
-
|
|
26640
|
-
|
|
26641
|
-
|
|
26642
|
-
|
|
26643
|
-
|
|
26644
|
-
|
|
26645
|
-
|
|
26646
|
-
|
|
26647
|
-
|
|
26648
|
-
|
|
26649
|
-
|
|
26650
|
-
const
|
|
26651
|
-
|
|
26652
|
-
|
|
26653
|
-
|
|
26654
|
-
|
|
26776
|
+
async listAccounts() {
|
|
26777
|
+
return this.accounts.list();
|
|
26778
|
+
}
|
|
26779
|
+
async getDefaultAccount() {
|
|
26780
|
+
return this.accounts.getDefault();
|
|
26781
|
+
}
|
|
26782
|
+
async setDefaultAccount(id) {
|
|
26783
|
+
await this.accounts.setDefault(id);
|
|
26784
|
+
resetEffectiveContext();
|
|
26785
|
+
}
|
|
26786
|
+
async removeAccount(id) {
|
|
26787
|
+
const removed = await this.accounts.remove(id);
|
|
26788
|
+
resetEffectiveContext();
|
|
26789
|
+
return removed;
|
|
26790
|
+
}
|
|
26791
|
+
async pendingClaimAccount() {
|
|
26792
|
+
const active = (await loadEffectiveContext()).account;
|
|
26793
|
+
if (active?.claim) return active;
|
|
26794
|
+
return (await this.accounts.list()).filter((account) => account.claim).sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0] ?? null;
|
|
26795
|
+
}
|
|
26796
|
+
};
|
|
26797
|
+
//#endregion
|
|
26798
|
+
//#region src/config/project-link.ts
|
|
26799
|
+
const PROJECT_LINK_DIR = ".runtype";
|
|
26800
|
+
const PROJECT_LINK_FILENAME = "project.json";
|
|
26801
|
+
var ProjectLinkError = class extends Error {
|
|
26802
|
+
path;
|
|
26803
|
+
constructor(message, path) {
|
|
26804
|
+
super(message);
|
|
26805
|
+
this.path = path;
|
|
26806
|
+
this.name = "ProjectLinkError";
|
|
26655
26807
|
}
|
|
26656
26808
|
};
|
|
26809
|
+
const FIRST_PARTY_API_HOST_SUFFIXES = [
|
|
26810
|
+
".runtype.com",
|
|
26811
|
+
".runtype-staging.com",
|
|
26812
|
+
".runtype-preview.com",
|
|
26813
|
+
".runtype.workers.dev"
|
|
26814
|
+
];
|
|
26815
|
+
/** A committed link may only pin HTTPS Runtype-operated hosts; local or other APIs need --api-url. */
|
|
26816
|
+
function isFirstPartyApiUrl(value) {
|
|
26817
|
+
let url;
|
|
26818
|
+
try {
|
|
26819
|
+
url = new URL(value);
|
|
26820
|
+
} catch {
|
|
26821
|
+
return false;
|
|
26822
|
+
}
|
|
26823
|
+
const hostname = url.hostname.toLowerCase();
|
|
26824
|
+
return url.protocol === "https:" && (hostname === "runtype.com" || FIRST_PARTY_API_HOST_SUFFIXES.some((suffix) => hostname.endsWith(suffix)));
|
|
26825
|
+
}
|
|
26826
|
+
function projectLinkPath(directory) {
|
|
26827
|
+
return path.join(directory, PROJECT_LINK_DIR, PROJECT_LINK_FILENAME);
|
|
26828
|
+
}
|
|
26829
|
+
function parseProjectLink(raw, filePath) {
|
|
26830
|
+
let parsed;
|
|
26831
|
+
try {
|
|
26832
|
+
parsed = JSON.parse(raw);
|
|
26833
|
+
} catch {
|
|
26834
|
+
throw new ProjectLinkError(`${filePath} is not valid JSON.`, filePath);
|
|
26835
|
+
}
|
|
26836
|
+
const record = parsed && typeof parsed === "object" ? parsed : {};
|
|
26837
|
+
if (typeof record.orgId !== "string" || record.orgId.trim() === "") throw new ProjectLinkError(`${filePath} must contain a non-empty "orgId" string.`, filePath);
|
|
26838
|
+
if (record.apiUrl !== void 0 && typeof record.apiUrl !== "string") throw new ProjectLinkError(`${filePath} has a non-string "apiUrl".`, filePath);
|
|
26839
|
+
return {
|
|
26840
|
+
orgId: record.orgId.trim(),
|
|
26841
|
+
...typeof record.apiUrl === "string" && record.apiUrl ? { apiUrl: record.apiUrl } : {}
|
|
26842
|
+
};
|
|
26843
|
+
}
|
|
26844
|
+
/** Walk up from `startDir` to the filesystem root and return the nearest link, if any. */
|
|
26845
|
+
function findProjectLink(startDir = process.cwd()) {
|
|
26846
|
+
if (process.env.RUNTYPE_IGNORE_PROJECT_LINK === "1") return null;
|
|
26847
|
+
let directory = path.resolve(startDir);
|
|
26848
|
+
for (;;) {
|
|
26849
|
+
const filePath = projectLinkPath(directory);
|
|
26850
|
+
let raw = null;
|
|
26851
|
+
try {
|
|
26852
|
+
raw = fs.readFileSync(filePath, "utf8");
|
|
26853
|
+
} catch {
|
|
26854
|
+
raw = null;
|
|
26855
|
+
}
|
|
26856
|
+
if (raw !== null) return {
|
|
26857
|
+
...parseProjectLink(raw, filePath),
|
|
26858
|
+
path: filePath,
|
|
26859
|
+
root: directory
|
|
26860
|
+
};
|
|
26861
|
+
const parent = path.dirname(directory);
|
|
26862
|
+
if (parent === directory) return null;
|
|
26863
|
+
directory = parent;
|
|
26864
|
+
}
|
|
26865
|
+
}
|
|
26866
|
+
function writeProjectLink(directory, link) {
|
|
26867
|
+
const filePath = projectLinkPath(directory);
|
|
26868
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
26869
|
+
const content = {
|
|
26870
|
+
orgId: link.orgId,
|
|
26871
|
+
...link.apiUrl ? { apiUrl: link.apiUrl } : {}
|
|
26872
|
+
};
|
|
26873
|
+
fs.writeFileSync(filePath, `${JSON.stringify(content, null, 2)}\n`);
|
|
26874
|
+
return filePath;
|
|
26875
|
+
}
|
|
26876
|
+
/** Remove `project.json`, and the `.runtype/` folder only when nothing else lives in it. */
|
|
26877
|
+
function removeProjectLink(directory) {
|
|
26878
|
+
const filePath = projectLinkPath(directory);
|
|
26879
|
+
if (!fs.existsSync(filePath)) return false;
|
|
26880
|
+
fs.rmSync(filePath);
|
|
26881
|
+
const folder = path.dirname(filePath);
|
|
26882
|
+
try {
|
|
26883
|
+
if (fs.readdirSync(folder).length === 0) fs.rmdirSync(folder);
|
|
26884
|
+
} catch {}
|
|
26885
|
+
return true;
|
|
26886
|
+
}
|
|
26657
26887
|
//#endregion
|
|
26658
26888
|
//#region src/config/effective-context.ts
|
|
26659
26889
|
var CliContextError = class extends Error {
|
|
@@ -26674,11 +26904,15 @@ function getCliConfig() {
|
|
|
26674
26904
|
projectName: "runtype-cli",
|
|
26675
26905
|
projectSuffix: "",
|
|
26676
26906
|
configName: "config",
|
|
26677
|
-
cwd:
|
|
26907
|
+
cwd: getRuntypeHomeDir()
|
|
26678
26908
|
});
|
|
26679
26909
|
}
|
|
26680
26910
|
function setInvocationOptions(options) {
|
|
26681
26911
|
invocationOptions = options;
|
|
26912
|
+
resetEffectiveContext();
|
|
26913
|
+
}
|
|
26914
|
+
/** Drop the memoized context so the next read sees logins saved or removed in this process. */
|
|
26915
|
+
function resetEffectiveContext() {
|
|
26682
26916
|
effectiveContext = void 0;
|
|
26683
26917
|
identityCheck = void 0;
|
|
26684
26918
|
verifiedIdentity = void 0;
|
|
@@ -26697,11 +26931,12 @@ function normalizeApiUrl(value) {
|
|
|
26697
26931
|
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw new CliContextError("API URL must use HTTP(S) without credentials, query, or fragment.");
|
|
26698
26932
|
return url.toString().replace(/\/+$/, "");
|
|
26699
26933
|
}
|
|
26700
|
-
/** API target precedence:
|
|
26934
|
+
/** API target precedence: flag > environment > project link > saved config > credential endpoint > production. */
|
|
26701
26935
|
function resolveApiTarget(values) {
|
|
26702
26936
|
const [apiUrlSource, value] = [
|
|
26703
26937
|
["flag", values.flag],
|
|
26704
26938
|
["environment", values.environment],
|
|
26939
|
+
["project", values.project],
|
|
26705
26940
|
["config", values.config],
|
|
26706
26941
|
["credential", values.credential],
|
|
26707
26942
|
["default", "https://api.runtype.com"]
|
|
@@ -26711,36 +26946,83 @@ function resolveApiTarget(values) {
|
|
|
26711
26946
|
apiUrlSource
|
|
26712
26947
|
};
|
|
26713
26948
|
}
|
|
26714
|
-
function
|
|
26715
|
-
|
|
26716
|
-
|
|
26717
|
-
|
|
26718
|
-
|
|
26719
|
-
|
|
26720
|
-
|
|
26721
|
-
|
|
26949
|
+
function readProjectLink() {
|
|
26950
|
+
try {
|
|
26951
|
+
return findProjectLink();
|
|
26952
|
+
} catch (error) {
|
|
26953
|
+
if (error instanceof ProjectLinkError) throw new CliContextError(error.message);
|
|
26954
|
+
throw error;
|
|
26955
|
+
}
|
|
26956
|
+
}
|
|
26957
|
+
function requestedOrganization(link) {
|
|
26958
|
+
if (invocationOptions.org) return {
|
|
26959
|
+
orgId: invocationOptions.org,
|
|
26960
|
+
source: "flag"
|
|
26961
|
+
};
|
|
26962
|
+
const environmentOrg = process.env.RUNTYPE_ORG?.trim();
|
|
26963
|
+
if (environmentOrg) return {
|
|
26964
|
+
orgId: environmentOrg,
|
|
26965
|
+
source: "environment"
|
|
26966
|
+
};
|
|
26967
|
+
if (link) return {
|
|
26968
|
+
orgId: link.orgId,
|
|
26969
|
+
source: "project"
|
|
26970
|
+
};
|
|
26971
|
+
return { source: "default" };
|
|
26972
|
+
}
|
|
26973
|
+
/**
|
|
26974
|
+
* Every invocation-scoped API target source, in precedence order. A project link's apiUrl only
|
|
26975
|
+
* counts when no flag or environment value outranks it, and must name a Runtype-operated host:
|
|
26976
|
+
* the file is committed, so an arbitrary host would receive credentials from anyone running here.
|
|
26977
|
+
*/
|
|
26978
|
+
function explicitApiTarget(flag) {
|
|
26979
|
+
const projectLink = readProjectLink();
|
|
26980
|
+
const environment = process.env.RUNTYPE_API_URL;
|
|
26981
|
+
const project = projectLink?.apiUrl;
|
|
26982
|
+
if (projectLink && project && !flag && !environment && !isFirstPartyApiUrl(project)) throw new CliContextError(`${projectLink.path} pins apiUrl ${project}, which is not a Runtype API host. Pass --api-url or set RUNTYPE_API_URL to use it deliberately.`);
|
|
26983
|
+
return {
|
|
26984
|
+
projectLink,
|
|
26985
|
+
target: {
|
|
26986
|
+
flag,
|
|
26987
|
+
environment,
|
|
26988
|
+
project,
|
|
26989
|
+
config: getCliConfig().get("apiUrl")
|
|
26990
|
+
}
|
|
26991
|
+
};
|
|
26992
|
+
}
|
|
26993
|
+
async function buildContext(apiUrlOverride) {
|
|
26994
|
+
if (apiUrlOverride && invocationOptions.apiUrl && normalizeApiUrl(apiUrlOverride) !== normalizeApiUrl(invocationOptions.apiUrl)) throw new CliContextError("Conflicting explicit --api-url values; choose one API target.");
|
|
26995
|
+
const { projectLink, target: explicitTarget } = explicitApiTarget(apiUrlOverride || invocationOptions.apiUrl);
|
|
26996
|
+
const explicitApiUrl = Object.values(explicitTarget).some(Boolean) ? resolveApiTarget(explicitTarget).apiUrl : void 0;
|
|
26997
|
+
const { orgId: requestedOrgId, source: accountSource } = requestedOrganization(projectLink);
|
|
26998
|
+
const envKey = process.env.RUNTYPE_API_KEY;
|
|
26999
|
+
const login = await new CredentialStore().resolveCredentials({
|
|
27000
|
+
orgId: requestedOrgId,
|
|
27001
|
+
apiUrl: explicitApiUrl
|
|
27002
|
+
});
|
|
27003
|
+
const credentials = login?.credentials ?? null;
|
|
27004
|
+
return {
|
|
27005
|
+
...resolveApiTarget({
|
|
27006
|
+
...explicitTarget,
|
|
27007
|
+
credential: login?.account.apiUrl
|
|
27008
|
+
}),
|
|
27009
|
+
credentials,
|
|
27010
|
+
account: login?.account ?? null,
|
|
27011
|
+
...login?.readError ? { credentialError: login.readError } : {},
|
|
27012
|
+
apiKey: envKey || credentials?.apiKey,
|
|
27013
|
+
credentialSource: envKey ? "environment" : credentials ? "stored" : "none",
|
|
27014
|
+
...requestedOrgId ? { requestedOrgId } : {},
|
|
27015
|
+
accountSource,
|
|
27016
|
+
projectLink
|
|
27017
|
+
};
|
|
26722
27018
|
}
|
|
26723
27019
|
function getEffectiveApiUrl() {
|
|
26724
|
-
return effectiveContext?.apiUrl ??
|
|
27020
|
+
return effectiveContext?.apiUrl ?? resolveApiTarget(explicitApiTarget(invocationOptions.apiUrl).target).apiUrl;
|
|
26725
27021
|
}
|
|
26726
27022
|
async function loadEffectiveContext(apiUrl) {
|
|
26727
|
-
if (!effectiveContext)
|
|
26728
|
-
|
|
26729
|
-
|
|
26730
|
-
effectiveContext = {
|
|
26731
|
-
...resolveTarget(apiUrl, credentials),
|
|
26732
|
-
credentials,
|
|
26733
|
-
apiKey: envKey || credentials?.apiKey,
|
|
26734
|
-
credentialSource: envKey ? "environment" : credentials?.apiKey ? "stored" : "none"
|
|
26735
|
-
};
|
|
26736
|
-
} else if (apiUrl) {
|
|
26737
|
-
const target = resolveTarget(apiUrl, effectiveContext.credentials);
|
|
26738
|
-
if (target.apiUrl !== effectiveContext.apiUrl) effectiveContext = {
|
|
26739
|
-
...effectiveContext,
|
|
26740
|
-
...target
|
|
26741
|
-
};
|
|
26742
|
-
else effectiveContext.apiUrlSource = target.apiUrlSource;
|
|
26743
|
-
}
|
|
27023
|
+
if (!effectiveContext) effectiveContext = await buildContext(apiUrl);
|
|
27024
|
+
else if (apiUrl && normalizeApiUrl(apiUrl) !== effectiveContext.apiUrl) effectiveContext = await buildContext(apiUrl);
|
|
27025
|
+
else if (apiUrl) effectiveContext.apiUrlSource = "flag";
|
|
26744
27026
|
return effectiveContext;
|
|
26745
27027
|
}
|
|
26746
27028
|
function useStoredCredential(context) {
|
|
@@ -26781,7 +27063,11 @@ function redactEffectiveContext(context) {
|
|
|
26781
27063
|
apiUrlSource: context.apiUrlSource,
|
|
26782
27064
|
credentialSource: context.credentialSource,
|
|
26783
27065
|
orgId: storedOrgApplies ? context.credentials?.orgId ?? null : null,
|
|
26784
|
-
orgVerified: false
|
|
27066
|
+
orgVerified: false,
|
|
27067
|
+
accountSource: context.accountSource,
|
|
27068
|
+
requestedOrgId: context.requestedOrgId ?? null,
|
|
27069
|
+
credentialStore: context.credentialSource === "stored" ? context.account?.secretStore ?? null : null,
|
|
27070
|
+
projectLink: context.projectLink?.path ?? null
|
|
26785
27071
|
};
|
|
26786
27072
|
}
|
|
26787
27073
|
//#endregion
|
|
@@ -26943,6 +27229,437 @@ var ApiKeyManager = class {
|
|
|
26943
27229
|
}
|
|
26944
27230
|
};
|
|
26945
27231
|
//#endregion
|
|
27232
|
+
//#region src/auth/callback-server.ts
|
|
27233
|
+
var CallbackServer = class {
|
|
27234
|
+
server = null;
|
|
27235
|
+
app;
|
|
27236
|
+
codeResolve;
|
|
27237
|
+
codeReject;
|
|
27238
|
+
codePromise;
|
|
27239
|
+
expectedState;
|
|
27240
|
+
constructor() {
|
|
27241
|
+
this.app = express();
|
|
27242
|
+
this.codePromise = new Promise((resolve, reject) => {
|
|
27243
|
+
this.codeResolve = resolve;
|
|
27244
|
+
this.codeReject = reject;
|
|
27245
|
+
});
|
|
27246
|
+
this.app.get("/callback", (req, res) => {
|
|
27247
|
+
const { token, code, state, error } = req.query;
|
|
27248
|
+
if (typeof state !== "string" || state !== this.expectedState) {
|
|
27249
|
+
res.status(400).send(this.errorHTML("Invalid authentication state. Continue the login you started in the CLI."));
|
|
27250
|
+
return;
|
|
27251
|
+
}
|
|
27252
|
+
const values = [
|
|
27253
|
+
token,
|
|
27254
|
+
code,
|
|
27255
|
+
error
|
|
27256
|
+
].filter((value) => value !== void 0);
|
|
27257
|
+
if (values.length !== 1 || typeof values[0] !== "string" || values[0].length === 0) {
|
|
27258
|
+
res.status(400).send(this.errorHTML("Invalid authentication callback"));
|
|
27259
|
+
return;
|
|
27260
|
+
}
|
|
27261
|
+
if (typeof error === "string") {
|
|
27262
|
+
res.send(this.errorHTML(error));
|
|
27263
|
+
this.codeReject(new Error(error));
|
|
27264
|
+
setTimeout(() => this.stop(), 1e3);
|
|
27265
|
+
return;
|
|
27266
|
+
}
|
|
27267
|
+
res.send(this.successHTML());
|
|
27268
|
+
this.codeResolve(values[0]);
|
|
27269
|
+
setTimeout(() => this.stop(), 1e3);
|
|
27270
|
+
});
|
|
27271
|
+
this.app.get("/health", (_req, res) => {
|
|
27272
|
+
res.json({ status: "ok" });
|
|
27273
|
+
});
|
|
27274
|
+
}
|
|
27275
|
+
/** Set expected state parameter for OAuth2 CSRF validation */
|
|
27276
|
+
setExpectedState(state) {
|
|
27277
|
+
if (!state) throw new Error("Authentication requires a nonempty expected state");
|
|
27278
|
+
if (this.server) throw new Error("Expected state must be registered before listening");
|
|
27279
|
+
this.expectedState = state;
|
|
27280
|
+
}
|
|
27281
|
+
async start(port = 8765) {
|
|
27282
|
+
if (!this.expectedState) throw new Error("Authentication requires an expected state before listening");
|
|
27283
|
+
return new Promise((resolveStart, rejectStart) => {
|
|
27284
|
+
this.server = this.app.listen(port, "127.0.0.1", () => {
|
|
27285
|
+
const address = this.server?.address();
|
|
27286
|
+
const actualPort = address && typeof address === "object" && "port" in address ? address.port : port;
|
|
27287
|
+
console.log(`Callback server listening on port ${actualPort}`);
|
|
27288
|
+
resolveStart({
|
|
27289
|
+
actualPort,
|
|
27290
|
+
codePromise: this.codePromise
|
|
27291
|
+
});
|
|
27292
|
+
});
|
|
27293
|
+
this.server.on("error", (error) => {
|
|
27294
|
+
if (error.code === "EADDRINUSE") {
|
|
27295
|
+
console.log(`Port ${port} is in use, trying ${port + 1}...`);
|
|
27296
|
+
this.server = null;
|
|
27297
|
+
this.start(port + 1).then(resolveStart).catch(rejectStart);
|
|
27298
|
+
} else rejectStart(error);
|
|
27299
|
+
});
|
|
27300
|
+
});
|
|
27301
|
+
}
|
|
27302
|
+
stop() {
|
|
27303
|
+
if (this.server) {
|
|
27304
|
+
const s = this.server;
|
|
27305
|
+
this.server = null;
|
|
27306
|
+
s.unref();
|
|
27307
|
+
if (typeof s.closeAllConnections === "function") s.closeAllConnections();
|
|
27308
|
+
s.close();
|
|
27309
|
+
}
|
|
27310
|
+
}
|
|
27311
|
+
successHTML() {
|
|
27312
|
+
return `<!DOCTYPE html>
|
|
27313
|
+
<html lang="en">
|
|
27314
|
+
<head>
|
|
27315
|
+
<meta charset="utf-8">
|
|
27316
|
+
<title>Authentication Successful</title>
|
|
27317
|
+
<style>
|
|
27318
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
27319
|
+
body {
|
|
27320
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
27321
|
+
height: 100vh;
|
|
27322
|
+
overflow: hidden;
|
|
27323
|
+
background: #0a0a0a;
|
|
27324
|
+
}
|
|
27325
|
+
.grid-bg {
|
|
27326
|
+
position: absolute;
|
|
27327
|
+
inset: 0;
|
|
27328
|
+
z-index: 0;
|
|
27329
|
+
overflow: hidden;
|
|
27330
|
+
}
|
|
27331
|
+
.grid-bg .gradient {
|
|
27332
|
+
position: absolute;
|
|
27333
|
+
inset: 0;
|
|
27334
|
+
background: linear-gradient(to bottom, #0a0a0a 0%, #171717 50%, #262626 100%);
|
|
27335
|
+
}
|
|
27336
|
+
@keyframes gridMove {
|
|
27337
|
+
0% { background-position-y: 0; }
|
|
27338
|
+
100% { background-position-y: 30px; }
|
|
27339
|
+
}
|
|
27340
|
+
.grid-bg .grid-vertical-wrap {
|
|
27341
|
+
position: absolute;
|
|
27342
|
+
left: 0;
|
|
27343
|
+
right: 0;
|
|
27344
|
+
bottom: 0;
|
|
27345
|
+
top: 40%;
|
|
27346
|
+
transform-origin: top center;
|
|
27347
|
+
transform: perspective(500px) rotateX(60deg);
|
|
27348
|
+
}
|
|
27349
|
+
.grid-bg .grid-vertical-wrap .fade {
|
|
27350
|
+
position: absolute;
|
|
27351
|
+
inset: 0;
|
|
27352
|
+
background: linear-gradient(to bottom, transparent 0%, rgba(255,255,255,0.02) 100%);
|
|
27353
|
+
}
|
|
27354
|
+
.grid-bg .grid-vertical-wrap .lines {
|
|
27355
|
+
position: absolute;
|
|
27356
|
+
inset: 0;
|
|
27357
|
+
background-image: repeating-linear-gradient(
|
|
27358
|
+
90deg,
|
|
27359
|
+
transparent 0,
|
|
27360
|
+
transparent 49px,
|
|
27361
|
+
rgba(255,255,255,0.08) 49px,
|
|
27362
|
+
rgba(255,255,255,0.08) 50px
|
|
27363
|
+
);
|
|
27364
|
+
background-size: 50px 100%;
|
|
27365
|
+
}
|
|
27366
|
+
.grid-bg .grid-horizontal {
|
|
27367
|
+
position: absolute;
|
|
27368
|
+
left: 0;
|
|
27369
|
+
right: 0;
|
|
27370
|
+
bottom: 0;
|
|
27371
|
+
top: 40%;
|
|
27372
|
+
transform-origin: top center;
|
|
27373
|
+
transform: perspective(500px) rotateX(60deg);
|
|
27374
|
+
background-image: repeating-linear-gradient(
|
|
27375
|
+
0deg,
|
|
27376
|
+
transparent 0,
|
|
27377
|
+
transparent 29px,
|
|
27378
|
+
rgba(255,255,255,0.06) 29px,
|
|
27379
|
+
rgba(255,255,255,0.06) 30px
|
|
27380
|
+
);
|
|
27381
|
+
background-size: 100% 30px;
|
|
27382
|
+
animation: gridMove 2s linear infinite;
|
|
27383
|
+
}
|
|
27384
|
+
.card-wrap {
|
|
27385
|
+
position: absolute;
|
|
27386
|
+
left: 50%;
|
|
27387
|
+
top: 50%;
|
|
27388
|
+
transform: translate(-50%, -50%);
|
|
27389
|
+
z-index: 10;
|
|
27390
|
+
width: 100%;
|
|
27391
|
+
max-width: 400px;
|
|
27392
|
+
perspective: 1400px;
|
|
27393
|
+
animation: cardIn 0.4s ease-out;
|
|
27394
|
+
}
|
|
27395
|
+
@keyframes cardIn {
|
|
27396
|
+
from { opacity: 0; transform: translate(-50%, -50%) translateY(16px); }
|
|
27397
|
+
to { opacity: 1; transform: translate(-50%, -50%) translateY(0); }
|
|
27398
|
+
}
|
|
27399
|
+
.card-inner {
|
|
27400
|
+
position: relative;
|
|
27401
|
+
}
|
|
27402
|
+
.card-layer-back,
|
|
27403
|
+
.card-layer-mid {
|
|
27404
|
+
position: absolute;
|
|
27405
|
+
inset: 0;
|
|
27406
|
+
border-radius: 12px;
|
|
27407
|
+
border: 1px solid #e5e7eb;
|
|
27408
|
+
pointer-events: none;
|
|
27409
|
+
}
|
|
27410
|
+
.card-layer-back {
|
|
27411
|
+
background: rgba(0, 0, 0, 0.08);
|
|
27412
|
+
transform: translate3d(0, -18px, -36px) rotateX(6deg);
|
|
27413
|
+
}
|
|
27414
|
+
.card-layer-mid {
|
|
27415
|
+
background: rgba(0, 0, 0, 0.05);
|
|
27416
|
+
transform: translate3d(0, -10px, -20px) rotateX(3deg);
|
|
27417
|
+
}
|
|
27418
|
+
.card-front {
|
|
27419
|
+
position: relative;
|
|
27420
|
+
border-radius: 12px;
|
|
27421
|
+
border: 1px solid rgba(255, 255, 255, 1);
|
|
27422
|
+
background: rgba(0, 0, 0, .5);
|
|
27423
|
+
backdrop-filter: blur(10px);
|
|
27424
|
+
-webkit-backdrop-filter: blur(10px);
|
|
27425
|
+
padding: 2rem 2.5rem;
|
|
27426
|
+
text-align: center;
|
|
27427
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
27428
|
+
transform: rotateX(-10deg) translateZ(0);
|
|
27429
|
+
transform-style: preserve-3d;
|
|
27430
|
+
}
|
|
27431
|
+
.card-front .section-title {
|
|
27432
|
+
color: #ffffff;
|
|
27433
|
+
font-size: 0.8125rem;
|
|
27434
|
+
font-weight: 600;
|
|
27435
|
+
letter-spacing: 0.05em;
|
|
27436
|
+
text-transform: uppercase;
|
|
27437
|
+
margin-bottom: 1rem;
|
|
27438
|
+
}
|
|
27439
|
+
.card-front .flow-arrow {
|
|
27440
|
+
color: rgba(255, 255, 255, 0.7);
|
|
27441
|
+
font-size: 1rem;
|
|
27442
|
+
margin-bottom: 1rem;
|
|
27443
|
+
line-height: 1;
|
|
27444
|
+
}
|
|
27445
|
+
.card-front .message {
|
|
27446
|
+
color: #ffffff;
|
|
27447
|
+
font-size: 0.75rem;
|
|
27448
|
+
font-weight: 100;
|
|
27449
|
+
margin-bottom: 0.5rem;
|
|
27450
|
+
font-family: monospace;
|
|
27451
|
+
}
|
|
27452
|
+
.card-front .progress {
|
|
27453
|
+
color: rgba(255, 255, 255, 0.75);
|
|
27454
|
+
font-size: 0.8125rem;
|
|
27455
|
+
}
|
|
27456
|
+
</style>
|
|
27457
|
+
</head>
|
|
27458
|
+
<body>
|
|
27459
|
+
<div class="grid-bg">
|
|
27460
|
+
<div class="gradient"></div>
|
|
27461
|
+
<div class="grid-vertical-wrap">
|
|
27462
|
+
<div class="fade"></div>
|
|
27463
|
+
<div class="lines"></div>
|
|
27464
|
+
</div>
|
|
27465
|
+
<div class="grid-horizontal"></div>
|
|
27466
|
+
</div>
|
|
27467
|
+
<div id="card-wrap" class="card-wrap">
|
|
27468
|
+
<div class="card-inner">
|
|
27469
|
+
<div class="card-layer-back" aria-hidden="true"></div>
|
|
27470
|
+
<div class="card-layer-mid" aria-hidden="true"></div>
|
|
27471
|
+
<div id="card" class="card-front">
|
|
27472
|
+
<div class="section-title">Authentication Successful</div>
|
|
27473
|
+
<div class="flow-arrow" aria-hidden="true">↓</div>
|
|
27474
|
+
<p class="message">You can close this window and return to your terminal.</p>
|
|
27475
|
+
<p class="progress" id="progress">Closing automatically in 3 seconds…</p>
|
|
27476
|
+
</div>
|
|
27477
|
+
</div>
|
|
27478
|
+
</div>
|
|
27479
|
+
<script>
|
|
27480
|
+
setTimeout(function() {
|
|
27481
|
+
window.close();
|
|
27482
|
+
var prog = document.getElementById('progress');
|
|
27483
|
+
if (prog) prog.textContent = 'You can now close this window.';
|
|
27484
|
+
var card = document.getElementById('card');
|
|
27485
|
+
if (card) card.innerHTML = '<div class="section-title">Runtype is Ready</div><p class="message">You can now close this window</p>';
|
|
27486
|
+
}, 3000);
|
|
27487
|
+
<\/script>
|
|
27488
|
+
</body>
|
|
27489
|
+
</html>`;
|
|
27490
|
+
}
|
|
27491
|
+
escapeHtml(str) {
|
|
27492
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
27493
|
+
}
|
|
27494
|
+
errorHTML(error) {
|
|
27495
|
+
return `
|
|
27496
|
+
<!DOCTYPE html>
|
|
27497
|
+
<html>
|
|
27498
|
+
<head>
|
|
27499
|
+
<title>Authentication Failed</title>
|
|
27500
|
+
<style>
|
|
27501
|
+
* {
|
|
27502
|
+
margin: 0;
|
|
27503
|
+
padding: 0;
|
|
27504
|
+
box-sizing: border-box;
|
|
27505
|
+
}
|
|
27506
|
+
body {
|
|
27507
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
27508
|
+
display: flex;
|
|
27509
|
+
align-items: center;
|
|
27510
|
+
justify-content: center;
|
|
27511
|
+
height: 100vh;
|
|
27512
|
+
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
|
27513
|
+
}
|
|
27514
|
+
.container {
|
|
27515
|
+
background: white;
|
|
27516
|
+
padding: 3rem;
|
|
27517
|
+
border-radius: 12px;
|
|
27518
|
+
text-align: center;
|
|
27519
|
+
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
|
27520
|
+
max-width: 400px;
|
|
27521
|
+
}
|
|
27522
|
+
.error-icon {
|
|
27523
|
+
color: #ef4444;
|
|
27524
|
+
font-size: 64px;
|
|
27525
|
+
margin-bottom: 1rem;
|
|
27526
|
+
}
|
|
27527
|
+
h1 {
|
|
27528
|
+
color: #1f2937;
|
|
27529
|
+
margin-bottom: 0.5rem;
|
|
27530
|
+
font-size: 1.5rem;
|
|
27531
|
+
}
|
|
27532
|
+
p {
|
|
27533
|
+
color: #6b7280;
|
|
27534
|
+
font-size: 1rem;
|
|
27535
|
+
margin-bottom: 1rem;
|
|
27536
|
+
}
|
|
27537
|
+
.error-details {
|
|
27538
|
+
background: #fef2f2;
|
|
27539
|
+
color: #991b1b;
|
|
27540
|
+
padding: 0.75rem;
|
|
27541
|
+
border-radius: 6px;
|
|
27542
|
+
font-size: 0.875rem;
|
|
27543
|
+
font-family: monospace;
|
|
27544
|
+
}
|
|
27545
|
+
</style>
|
|
27546
|
+
</head>
|
|
27547
|
+
<body>
|
|
27548
|
+
<div class="container">
|
|
27549
|
+
<div class="error-icon">✕</div>
|
|
27550
|
+
<h1>Authentication Failed</h1>
|
|
27551
|
+
<p>There was an error during authentication.</p>
|
|
27552
|
+
<div class="error-details">${this.escapeHtml(error)}</div>
|
|
27553
|
+
</div>
|
|
27554
|
+
</body>
|
|
27555
|
+
</html>
|
|
27556
|
+
`;
|
|
27557
|
+
}
|
|
27558
|
+
};
|
|
27559
|
+
//#endregion
|
|
27560
|
+
//#region src/auth/oauth-manager.ts
|
|
27561
|
+
var OAuthManager = class {
|
|
27562
|
+
clerkPublishableKey;
|
|
27563
|
+
dashboardUrl;
|
|
27564
|
+
constructor(config) {
|
|
27565
|
+
this.clerkPublishableKey = config.clerkPublishableKey;
|
|
27566
|
+
this.dashboardUrl = config.dashboardUrl;
|
|
27567
|
+
}
|
|
27568
|
+
/** `orgId` preselects that organization on the dashboard's account picker; the user can still choose another. */
|
|
27569
|
+
async authenticate(mode, options = {}) {
|
|
27570
|
+
const state = crypto$2.randomBytes(32).toString("base64url");
|
|
27571
|
+
const codeVerifier = crypto$2.randomBytes(32).toString("base64url");
|
|
27572
|
+
const codeChallenge = crypto$2.createHash("sha256").update(codeVerifier).digest("base64url");
|
|
27573
|
+
const callbackServer = new CallbackServer();
|
|
27574
|
+
callbackServer.setExpectedState(state);
|
|
27575
|
+
const requestedPort = await this.findAvailablePort();
|
|
27576
|
+
const { actualPort, codePromise } = await callbackServer.start(requestedPort);
|
|
27577
|
+
const redirectUri = `http://127.0.0.1:${actualPort}/callback`;
|
|
27578
|
+
const authUrl = new URL(`${this.dashboardUrl.replace(/\/$/, "")}/${mode}`);
|
|
27579
|
+
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
27580
|
+
authUrl.searchParams.set("cli", "true");
|
|
27581
|
+
authUrl.searchParams.set("state", state);
|
|
27582
|
+
authUrl.searchParams.set("code_challenge", codeChallenge);
|
|
27583
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
27584
|
+
authUrl.searchParams.set("publishable_key", this.clerkPublishableKey);
|
|
27585
|
+
if (options.orgId) authUrl.searchParams.set("org", options.orgId);
|
|
27586
|
+
console.log("🌐 Opening browser for authentication...");
|
|
27587
|
+
console.log(`If the browser doesn't open, visit: ${authUrl}`);
|
|
27588
|
+
try {
|
|
27589
|
+
console.log("⏳ Waiting for authentication...");
|
|
27590
|
+
const [code] = await Promise.all([codePromise, open(authUrl.toString()).catch(() => {
|
|
27591
|
+
console.log("Failed to open browser automatically. Please visit the URL above.");
|
|
27592
|
+
})]);
|
|
27593
|
+
return await this.exchangeCodeForSession(code, codeVerifier, redirectUri);
|
|
27594
|
+
} finally {
|
|
27595
|
+
callbackServer.stop();
|
|
27596
|
+
}
|
|
27597
|
+
}
|
|
27598
|
+
async findAvailablePort() {
|
|
27599
|
+
return 8765;
|
|
27600
|
+
}
|
|
27601
|
+
async exchangeCodeForSession(code, codeVerifier, redirectUri) {
|
|
27602
|
+
return code;
|
|
27603
|
+
}
|
|
27604
|
+
};
|
|
27605
|
+
//#endregion
|
|
27606
|
+
//#region src/auth/login-flow.ts
|
|
27607
|
+
var InvalidApiKeyError = class extends Error {
|
|
27608
|
+
constructor() {
|
|
27609
|
+
super("Invalid API key");
|
|
27610
|
+
this.name = "InvalidApiKeyError";
|
|
27611
|
+
}
|
|
27612
|
+
};
|
|
27613
|
+
async function storeLogin(credentials) {
|
|
27614
|
+
const context = await loadEffectiveContext(credentials.apiUrl);
|
|
27615
|
+
const store = new CredentialStore();
|
|
27616
|
+
const account = await store.saveCredentials(credentials);
|
|
27617
|
+
const isDefault = (await store.getDefaultAccount())?.id === account.id;
|
|
27618
|
+
return {
|
|
27619
|
+
...credentials,
|
|
27620
|
+
account,
|
|
27621
|
+
isDefault,
|
|
27622
|
+
...context.requestedOrgId ? { requestedOrgId: context.requestedOrgId } : {}
|
|
27623
|
+
};
|
|
27624
|
+
}
|
|
27625
|
+
/** Browser (Clerk) login: mint an org-owned CLI key for the organization the user authorizes. */
|
|
27626
|
+
async function browserLogin(options = {}) {
|
|
27627
|
+
const context = await loadEffectiveContext(options.apiUrl);
|
|
27628
|
+
const oauth = new OAuthManager({
|
|
27629
|
+
clerkPublishableKey: getClerkPublishableKey(),
|
|
27630
|
+
dashboardUrl: options.dashboardUrl || getDashboardUrl()
|
|
27631
|
+
});
|
|
27632
|
+
options.onProgress?.("Waiting for browser authentication...");
|
|
27633
|
+
const sessionToken = await oauth.authenticate("sign-in", { orgId: context.requestedOrgId });
|
|
27634
|
+
options.onProgress?.("Creating API key...");
|
|
27635
|
+
const { key, userId, orgId } = await new ApiKeyManager().exchangeSessionForApiKey(sessionToken, context.apiUrl);
|
|
27636
|
+
options.onProgress?.("Storing credentials securely...");
|
|
27637
|
+
return storeLogin({
|
|
27638
|
+
apiKey: key,
|
|
27639
|
+
userId,
|
|
27640
|
+
orgId,
|
|
27641
|
+
apiUrl: context.apiUrl
|
|
27642
|
+
});
|
|
27643
|
+
}
|
|
27644
|
+
/** Store an existing API key as a login for whichever organization owns it. */
|
|
27645
|
+
async function apiKeyLogin(apiKey, options = {}) {
|
|
27646
|
+
const context = await loadEffectiveContext(options.apiUrl);
|
|
27647
|
+
const manager = new ApiKeyManager();
|
|
27648
|
+
if (!await manager.validateApiKey(apiKey, context.apiUrl)) throw new InvalidApiKeyError();
|
|
27649
|
+
const user = await manager.getCurrentUser(apiKey, context.apiUrl);
|
|
27650
|
+
return storeLogin({
|
|
27651
|
+
apiKey,
|
|
27652
|
+
userId: user.userId,
|
|
27653
|
+
orgId: user.orgId ?? void 0,
|
|
27654
|
+
apiUrl: context.apiUrl
|
|
27655
|
+
});
|
|
27656
|
+
}
|
|
27657
|
+
/** Non-null when the user authorized a different organization than the one this directory targets. */
|
|
27658
|
+
function orgMismatchWarning(login) {
|
|
27659
|
+
if (!login.requestedOrgId || login.requestedOrgId === login.orgId) return null;
|
|
27660
|
+
return `Logged in to ${login.orgId ?? "a personal account"}, but this invocation targets ${login.requestedOrgId}. Commands here will keep asking for a login to ${login.requestedOrgId}; run "runtype login" again and pick that organization, or update the link with "runtype link ${login.orgId ?? ""}".`;
|
|
27661
|
+
}
|
|
27662
|
+
//#endregion
|
|
26946
27663
|
//#region src/lib/ensure-auth.ts
|
|
26947
27664
|
const verifiedUsers = /* @__PURE__ */ new WeakMap();
|
|
26948
27665
|
async function getAuthenticatedUser() {
|
|
@@ -27011,7 +27728,7 @@ async function ensureAuth(options) {
|
|
|
27011
27728
|
}
|
|
27012
27729
|
if (options?.interactive === false || process.stdin.isTTY !== true || invocation.noInput || strictIdentity) {
|
|
27013
27730
|
if (options?.exitOnFailure === false) return null;
|
|
27014
|
-
const message =
|
|
27731
|
+
const message = authRequiredMessage(context);
|
|
27015
27732
|
console.error(invocation.json ? JSON.stringify({
|
|
27016
27733
|
success: false,
|
|
27017
27734
|
error: {
|
|
@@ -27022,39 +27739,39 @@ async function ensureAuth(options) {
|
|
|
27022
27739
|
process.exit(1);
|
|
27023
27740
|
return null;
|
|
27024
27741
|
}
|
|
27025
|
-
console.log(chalk.yellow(
|
|
27742
|
+
console.log(chalk.yellow(`\n${authRequiredMessage(context)}`));
|
|
27026
27743
|
if (!await promptConfirm$2("Would you like to log in now?", { default: true })) {
|
|
27027
27744
|
console.log(chalk.gray("Run \"runtype auth login\" when you're ready."));
|
|
27028
27745
|
if (options?.exitOnFailure !== false) process.exit(1);
|
|
27029
27746
|
return null;
|
|
27030
27747
|
}
|
|
27031
|
-
const apiKey = await handleBrowserLogin(
|
|
27748
|
+
const apiKey = await handleBrowserLogin(context.apiUrl);
|
|
27032
27749
|
if (apiKey) {
|
|
27033
27750
|
context.apiKey = apiKey;
|
|
27034
27751
|
context.credentialSource = "stored";
|
|
27035
27752
|
}
|
|
27036
27753
|
return apiKey;
|
|
27037
27754
|
}
|
|
27038
|
-
|
|
27755
|
+
/** Name the organization the invocation needs, so a linked project never reads as "logged out". */
|
|
27756
|
+
function authRequiredMessage(context) {
|
|
27757
|
+
if (context.credentialError && context.account) return `The stored login for ${context.account.orgId ?? "your personal account"} could not be read (${context.credentialError}). Log in again with "runtype login" (or set RUNTYPE_CREDENTIAL_STORE=file first if the OS keychain is unavailable here), or set RUNTYPE_API_KEY.`;
|
|
27758
|
+
if (context.requestedOrgId) return `${context.accountSource === "project" && context.projectLink ? `This directory is linked to ${context.requestedOrgId} (${context.projectLink.path})` : `This command targets ${context.requestedOrgId}`}, but there is no stored login for it on ${context.apiUrl}. Run "runtype login" and authorize that organization, or set RUNTYPE_API_KEY.`;
|
|
27759
|
+
return "Authentication required. Run \"runtype auth login\" first or set RUNTYPE_API_KEY.";
|
|
27760
|
+
}
|
|
27761
|
+
async function handleBrowserLogin(apiUrl) {
|
|
27039
27762
|
console.log(chalk.gray("Opening browser for authentication..."));
|
|
27040
27763
|
try {
|
|
27041
|
-
const
|
|
27042
|
-
|
|
27043
|
-
|
|
27044
|
-
});
|
|
27045
|
-
console.log(chalk.gray("Waiting for browser authentication..."));
|
|
27046
|
-
const sessionToken = await oauth.authenticate("sign-in");
|
|
27047
|
-
console.log(chalk.gray("Getting API key..."));
|
|
27048
|
-
const { key, userId, orgId } = await new ApiKeyManager().exchangeSessionForApiKey(sessionToken, apiUrl || getApiUrl());
|
|
27049
|
-
console.log(chalk.gray("Storing credentials..."));
|
|
27050
|
-
await store.saveCredentials({
|
|
27051
|
-
apiKey: key,
|
|
27052
|
-
userId,
|
|
27053
|
-
orgId,
|
|
27054
|
-
apiUrl: apiUrl || getApiUrl()
|
|
27764
|
+
const login = await browserLogin({
|
|
27765
|
+
apiUrl,
|
|
27766
|
+
onProgress: (label) => console.log(chalk.gray(label))
|
|
27055
27767
|
});
|
|
27768
|
+
const warning = orgMismatchWarning(login);
|
|
27769
|
+
if (warning) {
|
|
27770
|
+
console.log(chalk.yellow(warning));
|
|
27771
|
+
return null;
|
|
27772
|
+
}
|
|
27056
27773
|
console.log(chalk.green("Logged in successfully!"));
|
|
27057
|
-
return
|
|
27774
|
+
return login.apiKey;
|
|
27058
27775
|
} catch (error) {
|
|
27059
27776
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
27060
27777
|
console.log(chalk.red("Login failed"));
|
|
@@ -27285,6 +28002,25 @@ function MutationResult({ loading, loadingLabel = "Processing...", success, succ
|
|
|
27285
28002
|
return null;
|
|
27286
28003
|
}
|
|
27287
28004
|
//#endregion
|
|
28005
|
+
//#region src/ink/shared/account-picker.ts
|
|
28006
|
+
/** Interactive picker over stored logins; resolves undefined if the user exits without choosing. */
|
|
28007
|
+
async function promptForAccount(accounts) {
|
|
28008
|
+
let chosen;
|
|
28009
|
+
const { waitUntilExit, unmount } = render(React.createElement(SelectInput, {
|
|
28010
|
+
items: accounts.map((account) => ({
|
|
28011
|
+
key: account.id,
|
|
28012
|
+
label: `${account.orgId ?? "Personal"} ${account.apiUrl}`,
|
|
28013
|
+
value: account.id
|
|
28014
|
+
})),
|
|
28015
|
+
onSelect: (item) => {
|
|
28016
|
+
chosen = accounts.find((account) => account.id === item.value);
|
|
28017
|
+
unmount();
|
|
28018
|
+
}
|
|
28019
|
+
}));
|
|
28020
|
+
await waitUntilExit();
|
|
28021
|
+
return chosen;
|
|
28022
|
+
}
|
|
28023
|
+
//#endregion
|
|
27288
28024
|
//#region src/auth/agent-auth-client.ts
|
|
27289
28025
|
/**
|
|
27290
28026
|
* Protocol error with the spec error code and an agent-actionable hint.
|
|
@@ -27539,7 +28275,7 @@ async function registerAction(options) {
|
|
|
27539
28275
|
claimTokenExpires: registration.claimTokenExpires,
|
|
27540
28276
|
apiUrl
|
|
27541
28277
|
};
|
|
27542
|
-
await store.saveCredentials({
|
|
28278
|
+
const registered = await store.saveCredentials({
|
|
27543
28279
|
apiKey: registration.credential,
|
|
27544
28280
|
userId: identity.userId,
|
|
27545
28281
|
orgId: identity.orgId,
|
|
@@ -27557,7 +28293,8 @@ async function registerAction(options) {
|
|
|
27557
28293
|
const claim = await initiateClaim(registration.claimToken, options.email, apiUrl);
|
|
27558
28294
|
await store.saveClaimState({
|
|
27559
28295
|
...claimState,
|
|
27560
|
-
claimEmail: options.email
|
|
28296
|
+
claimEmail: options.email,
|
|
28297
|
+
accountId: registered.id
|
|
27561
28298
|
});
|
|
27562
28299
|
printJson({
|
|
27563
28300
|
...base,
|
|
@@ -27638,13 +28375,15 @@ async function verifyAction(code, options) {
|
|
|
27638
28375
|
try {
|
|
27639
28376
|
identity = await fetchIdentity(completed.credential, apiUrl);
|
|
27640
28377
|
} catch {}
|
|
27641
|
-
|
|
28378
|
+
const preClaimAccountId = claimState.accountId;
|
|
28379
|
+
const wasDefault = (await store.getDefaultAccount())?.id === preClaimAccountId;
|
|
28380
|
+
const saved = await store.saveCredentials({
|
|
27642
28381
|
apiKey: completed.credential,
|
|
27643
28382
|
userId: identity.userId,
|
|
27644
28383
|
orgId: identity.orgId,
|
|
27645
28384
|
apiUrl
|
|
27646
|
-
});
|
|
27647
|
-
await store.
|
|
28385
|
+
}, void 0, { makeDefault: wasDefault });
|
|
28386
|
+
if (preClaimAccountId && preClaimAccountId !== saved.id) await store.removeAccount(preClaimAccountId);
|
|
27648
28387
|
printJson({
|
|
27649
28388
|
status: "authenticated",
|
|
27650
28389
|
accountState: "full",
|
|
@@ -27693,149 +28432,122 @@ async function getBillingInfo(apiKey, apiUrl) {
|
|
|
27693
28432
|
return null;
|
|
27694
28433
|
}
|
|
27695
28434
|
}
|
|
27696
|
-
|
|
27697
|
-
|
|
27698
|
-
refuseForcedTtyLogin(options);
|
|
27699
|
-
if (!isTTY(options)) {
|
|
27700
|
-
console.error(JSON.stringify(NON_TTY_LOGIN_GUIDANCE, null, 2));
|
|
27701
|
-
process.exit(1);
|
|
27702
|
-
}
|
|
28435
|
+
/** Run one async step under the shared spinner/result chrome. */
|
|
28436
|
+
async function renderTask(loadingLabel, task) {
|
|
27703
28437
|
const App = () => {
|
|
27704
28438
|
const [loading, setLoading] = useState(true);
|
|
27705
|
-
const [
|
|
28439
|
+
const [label, setLabel] = useState(loadingLabel);
|
|
27706
28440
|
const [success, setSuccess] = useState(null);
|
|
27707
28441
|
const [error, setError] = useState(null);
|
|
28442
|
+
const [outcome, setOutcome] = useState(null);
|
|
27708
28443
|
useEffect(() => {
|
|
27709
|
-
|
|
27710
|
-
|
|
27711
|
-
|
|
27712
|
-
|
|
27713
|
-
|
|
27714
|
-
|
|
27715
|
-
|
|
27716
|
-
const sessionToken = await oauth.authenticate("sign-in");
|
|
27717
|
-
setLoadingLabel("Creating API key...");
|
|
27718
|
-
const { key, userId, orgId } = await new ApiKeyManager().exchangeSessionForApiKey(sessionToken, options.apiUrl || getApiUrl());
|
|
27719
|
-
setLoadingLabel("Storing credentials securely...");
|
|
27720
|
-
await new CredentialStore().saveCredentials({
|
|
27721
|
-
apiKey: key,
|
|
27722
|
-
userId,
|
|
27723
|
-
orgId,
|
|
27724
|
-
apiUrl: options.apiUrl || getApiUrl()
|
|
27725
|
-
});
|
|
27726
|
-
setSuccess(true);
|
|
27727
|
-
setLoading(false);
|
|
27728
|
-
} catch (err) {
|
|
27729
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
27730
|
-
setSuccess(false);
|
|
27731
|
-
setLoading(false);
|
|
27732
|
-
}
|
|
27733
|
-
};
|
|
27734
|
-
run();
|
|
28444
|
+
task(setLabel).then((value) => {
|
|
28445
|
+
setOutcome(value);
|
|
28446
|
+
setSuccess(true);
|
|
28447
|
+
}).catch((err) => {
|
|
28448
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
28449
|
+
setSuccess(false);
|
|
28450
|
+
}).finally(() => setLoading(false));
|
|
27735
28451
|
}, []);
|
|
27736
28452
|
return React.createElement(MutationResult, {
|
|
27737
28453
|
loading,
|
|
27738
|
-
loadingLabel,
|
|
28454
|
+
loadingLabel: label,
|
|
27739
28455
|
success,
|
|
27740
|
-
successMessage:
|
|
28456
|
+
successMessage: outcome?.message ?? "",
|
|
27741
28457
|
error,
|
|
27742
|
-
result:
|
|
27743
|
-
label: "Next",
|
|
27744
|
-
value: "runtype flows list"
|
|
27745
|
-
}] })
|
|
28458
|
+
result: outcome?.result
|
|
27746
28459
|
});
|
|
27747
28460
|
};
|
|
27748
28461
|
const { waitUntilExit } = render(React.createElement(App));
|
|
27749
28462
|
await waitUntilExit();
|
|
28463
|
+
}
|
|
28464
|
+
function loginFields(login) {
|
|
28465
|
+
return [
|
|
28466
|
+
{
|
|
28467
|
+
label: "Organization",
|
|
28468
|
+
value: login.orgId ?? "Personal"
|
|
28469
|
+
},
|
|
28470
|
+
{
|
|
28471
|
+
label: "User ID",
|
|
28472
|
+
value: login.userId
|
|
28473
|
+
},
|
|
28474
|
+
{
|
|
28475
|
+
label: "API URL",
|
|
28476
|
+
value: login.apiUrl
|
|
28477
|
+
},
|
|
28478
|
+
{
|
|
28479
|
+
label: "Default login",
|
|
28480
|
+
value: login.isDefault ? "yes" : "no (run: runtype auth switch)"
|
|
28481
|
+
}
|
|
28482
|
+
];
|
|
28483
|
+
}
|
|
28484
|
+
function loginJson(login) {
|
|
28485
|
+
const warning = orgMismatchWarning(login);
|
|
28486
|
+
return {
|
|
28487
|
+
status: "authenticated",
|
|
28488
|
+
userId: login.userId,
|
|
28489
|
+
orgId: login.orgId ?? null,
|
|
28490
|
+
apiUrl: login.apiUrl,
|
|
28491
|
+
accountId: login.account.id,
|
|
28492
|
+
isDefault: login.isDefault,
|
|
28493
|
+
secretStore: login.account.secretStore,
|
|
28494
|
+
...warning ? { warning } : {}
|
|
28495
|
+
};
|
|
28496
|
+
}
|
|
28497
|
+
function loginOutcome(login) {
|
|
28498
|
+
return {
|
|
28499
|
+
message: orgMismatchWarning(login) ?? `Logged in to ${login.orgId ?? "your personal account"}.`,
|
|
28500
|
+
result: React.createElement(EntityCard, { fields: loginFields(login) })
|
|
28501
|
+
};
|
|
28502
|
+
}
|
|
28503
|
+
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) => {
|
|
28504
|
+
await loadEffectiveContext(options.apiUrl);
|
|
28505
|
+
refuseForcedTtyLogin(options);
|
|
28506
|
+
if (!isTTY(options)) {
|
|
28507
|
+
console.error(JSON.stringify(NON_TTY_LOGIN_GUIDANCE, null, 2));
|
|
28508
|
+
process.exit(1);
|
|
28509
|
+
}
|
|
28510
|
+
await renderTask("Opening browser for authentication...", async (setLabel) => loginOutcome(await browserLogin({
|
|
28511
|
+
...options,
|
|
28512
|
+
onProgress: setLabel
|
|
28513
|
+
})));
|
|
27750
28514
|
});
|
|
28515
|
+
function failApiKeyLogin(error) {
|
|
28516
|
+
if (error instanceof ApiKeyValidationUnavailableError) {
|
|
28517
|
+
console.error(JSON.stringify({
|
|
28518
|
+
status: "error",
|
|
28519
|
+
code: "api_unreachable",
|
|
28520
|
+
error: error.message,
|
|
28521
|
+
next: API_UNREACHABLE_NEXT
|
|
28522
|
+
}, null, 2));
|
|
28523
|
+
process.exit(1);
|
|
28524
|
+
}
|
|
28525
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
28526
|
+
console.error(JSON.stringify({
|
|
28527
|
+
status: "error",
|
|
28528
|
+
error: message
|
|
28529
|
+
}, null, 2));
|
|
28530
|
+
process.exit(1);
|
|
28531
|
+
}
|
|
27751
28532
|
async function loginAction(options) {
|
|
27752
28533
|
await loadEffectiveContext(options.apiUrl);
|
|
27753
|
-
const store = new CredentialStore();
|
|
27754
28534
|
if (options.apiKey) {
|
|
28535
|
+
const apiKey = options.apiKey;
|
|
27755
28536
|
if (!isTTY(options)) {
|
|
28537
|
+
let login;
|
|
27756
28538
|
try {
|
|
27757
|
-
|
|
27758
|
-
if (!await apiKeyManager.validateApiKey(options.apiKey, options.apiUrl)) {
|
|
27759
|
-
console.error(JSON.stringify({
|
|
27760
|
-
status: "error",
|
|
27761
|
-
error: "Invalid API key"
|
|
27762
|
-
}, null, 2));
|
|
27763
|
-
process.exit(1);
|
|
27764
|
-
}
|
|
27765
|
-
const userData = await apiKeyManager.getCurrentUser(options.apiKey, options.apiUrl);
|
|
27766
|
-
await store.saveCredentials({
|
|
27767
|
-
apiKey: options.apiKey,
|
|
27768
|
-
userId: userData.userId,
|
|
27769
|
-
orgId: userData.orgId ?? void 0,
|
|
27770
|
-
apiUrl: options.apiUrl || getApiUrl()
|
|
27771
|
-
});
|
|
27772
|
-
console.log(JSON.stringify({
|
|
27773
|
-
status: "authenticated",
|
|
27774
|
-
userId: userData.userId,
|
|
27775
|
-
next: "Continue with setup: runtype install-mcp (then confirm with: runtype auth whoami)"
|
|
27776
|
-
}, null, 2));
|
|
28539
|
+
login = await apiKeyLogin(apiKey, { apiUrl: options.apiUrl });
|
|
27777
28540
|
} catch (error) {
|
|
27778
|
-
|
|
27779
|
-
|
|
27780
|
-
status: "error",
|
|
27781
|
-
code: "api_unreachable",
|
|
27782
|
-
error: error.message,
|
|
27783
|
-
next: API_UNREACHABLE_NEXT
|
|
27784
|
-
}, null, 2));
|
|
27785
|
-
process.exit(1);
|
|
27786
|
-
}
|
|
27787
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
27788
|
-
console.error(JSON.stringify({
|
|
27789
|
-
status: "error",
|
|
27790
|
-
error: message
|
|
27791
|
-
}, null, 2));
|
|
27792
|
-
process.exit(1);
|
|
28541
|
+
failApiKeyLogin(error);
|
|
28542
|
+
return;
|
|
27793
28543
|
}
|
|
28544
|
+
console.log(JSON.stringify({
|
|
28545
|
+
...loginJson(login),
|
|
28546
|
+
next: "Continue with setup: runtype install-mcp (then confirm with: runtype auth whoami)"
|
|
28547
|
+
}, null, 2));
|
|
27794
28548
|
return;
|
|
27795
28549
|
}
|
|
27796
|
-
|
|
27797
|
-
const [loading, setLoading] = useState(true);
|
|
27798
|
-
const [success, setSuccess] = useState(null);
|
|
27799
|
-
const [error, setError] = useState(null);
|
|
27800
|
-
const [userId, setUserId] = useState(null);
|
|
27801
|
-
useEffect(() => {
|
|
27802
|
-
const run = async () => {
|
|
27803
|
-
try {
|
|
27804
|
-
const apiKeyManager = new ApiKeyManager();
|
|
27805
|
-
if (!await apiKeyManager.validateApiKey(options.apiKey, options.apiUrl)) {
|
|
27806
|
-
setError(/* @__PURE__ */ new Error("Invalid API key"));
|
|
27807
|
-
setSuccess(false);
|
|
27808
|
-
setLoading(false);
|
|
27809
|
-
return;
|
|
27810
|
-
}
|
|
27811
|
-
const userData = await apiKeyManager.getCurrentUser(options.apiKey, options.apiUrl);
|
|
27812
|
-
await store.saveCredentials({
|
|
27813
|
-
apiKey: options.apiKey,
|
|
27814
|
-
userId: userData.userId,
|
|
27815
|
-
orgId: userData.orgId ?? void 0,
|
|
27816
|
-
apiUrl: options.apiUrl || getApiUrl()
|
|
27817
|
-
});
|
|
27818
|
-
setUserId(userData.userId);
|
|
27819
|
-
setSuccess(true);
|
|
27820
|
-
setLoading(false);
|
|
27821
|
-
} catch (err) {
|
|
27822
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
27823
|
-
setSuccess(false);
|
|
27824
|
-
setLoading(false);
|
|
27825
|
-
}
|
|
27826
|
-
};
|
|
27827
|
-
run();
|
|
27828
|
-
}, []);
|
|
27829
|
-
return React.createElement(MutationResult, {
|
|
27830
|
-
loading,
|
|
27831
|
-
loadingLabel: "Validating API key...",
|
|
27832
|
-
success,
|
|
27833
|
-
successMessage: `Logged in successfully!${userId ? ` User ID: ${userId}` : ""}`,
|
|
27834
|
-
error
|
|
27835
|
-
});
|
|
27836
|
-
};
|
|
27837
|
-
const { waitUntilExit } = render(React.createElement(App));
|
|
27838
|
-
await waitUntilExit();
|
|
28550
|
+
await renderTask("Validating API key...", async () => loginOutcome(await apiKeyLogin(apiKey, { apiUrl: options.apiUrl })));
|
|
27839
28551
|
return;
|
|
27840
28552
|
}
|
|
27841
28553
|
refuseForcedTtyLogin(options);
|
|
@@ -27843,73 +28555,54 @@ async function loginAction(options) {
|
|
|
27843
28555
|
console.error(JSON.stringify(NON_TTY_LOGIN_GUIDANCE, null, 2));
|
|
27844
28556
|
process.exit(1);
|
|
27845
28557
|
}
|
|
27846
|
-
|
|
27847
|
-
|
|
27848
|
-
|
|
27849
|
-
|
|
27850
|
-
|
|
27851
|
-
|
|
27852
|
-
|
|
27853
|
-
|
|
27854
|
-
|
|
27855
|
-
|
|
27856
|
-
|
|
27857
|
-
|
|
27858
|
-
|
|
27859
|
-
|
|
27860
|
-
|
|
27861
|
-
setLoadingLabel("Getting API key...");
|
|
27862
|
-
const { key, userId: uid, orgId } = await new ApiKeyManager().exchangeSessionForApiKey(sessionToken, options.apiUrl || getApiUrl());
|
|
27863
|
-
setLoadingLabel("Storing credentials...");
|
|
27864
|
-
await store.saveCredentials({
|
|
27865
|
-
apiKey: key,
|
|
27866
|
-
userId: uid,
|
|
27867
|
-
orgId,
|
|
27868
|
-
apiUrl: options.apiUrl || getApiUrl()
|
|
27869
|
-
});
|
|
27870
|
-
setUserId(uid);
|
|
27871
|
-
setSuccess(true);
|
|
27872
|
-
setLoading(false);
|
|
27873
|
-
} catch (err) {
|
|
27874
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
27875
|
-
setSuccess(false);
|
|
27876
|
-
setLoading(false);
|
|
27877
|
-
}
|
|
27878
|
-
};
|
|
27879
|
-
run();
|
|
27880
|
-
}, []);
|
|
27881
|
-
return React.createElement(MutationResult, {
|
|
27882
|
-
loading,
|
|
27883
|
-
loadingLabel,
|
|
27884
|
-
success,
|
|
27885
|
-
successMessage: `Logged in successfully!${userId ? ` User ID: ${userId}` : ""}`,
|
|
27886
|
-
error
|
|
27887
|
-
});
|
|
27888
|
-
};
|
|
27889
|
-
const { waitUntilExit } = render(React.createElement(App));
|
|
27890
|
-
await waitUntilExit();
|
|
28558
|
+
await renderTask("Initializing authentication...", async (setLabel) => loginOutcome(await browserLogin({
|
|
28559
|
+
...options,
|
|
28560
|
+
onProgress: setLabel
|
|
28561
|
+
})));
|
|
28562
|
+
}
|
|
28563
|
+
const LOGIN_DESCRIPTION = "Log in to an organization (adds a login; the target org comes from --org, RUNTYPE_ORG, or .runtype/project.json)";
|
|
28564
|
+
authCommand.command("login").description(LOGIN_DESCRIPTION).option("--api-url <url>", "Custom API URL").option("--api-key <key>", "Use API key directly").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(loginAction);
|
|
28565
|
+
const loginCommand = new Command("login").description(LOGIN_DESCRIPTION).option("--api-url <url>", "Custom API URL").option("--api-key <key>", "Use API key directly").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(loginAction);
|
|
28566
|
+
function describeAccountSource(context) {
|
|
28567
|
+
switch (context.accountSource) {
|
|
28568
|
+
case "flag": return "--org flag";
|
|
28569
|
+
case "environment": return "RUNTYPE_ORG";
|
|
28570
|
+
case "project": return context.projectLink ? `project link (${context.projectLink.path})` : "project link";
|
|
28571
|
+
default: return "default login";
|
|
28572
|
+
}
|
|
27891
28573
|
}
|
|
27892
|
-
authCommand.command("
|
|
27893
|
-
const loginCommand = new Command("login").description("Log in to Runtype").option("--api-url <url>", "Custom API URL").option("--api-key <key>", "Use API key directly").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(loginAction);
|
|
27894
|
-
authCommand.command("status").description("Show authentication status").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (options) => {
|
|
28574
|
+
authCommand.command("status").description("Show which login this directory uses").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (options) => {
|
|
27895
28575
|
const store = new CredentialStore();
|
|
27896
28576
|
if (!isTTY(options)) {
|
|
27897
28577
|
try {
|
|
27898
|
-
|
|
28578
|
+
const context = await loadEffectiveContext();
|
|
28579
|
+
const storedLogins = (await store.listAccounts()).length;
|
|
28580
|
+
const credentials = context.credentials;
|
|
28581
|
+
if (!credentials) {
|
|
27899
28582
|
console.log(JSON.stringify({
|
|
27900
28583
|
status: "unauthenticated",
|
|
27901
|
-
|
|
28584
|
+
requestedOrgId: context.requestedOrgId ?? null,
|
|
28585
|
+
accountSource: context.accountSource,
|
|
28586
|
+
projectLink: context.projectLink?.path ?? null,
|
|
28587
|
+
storedLogins,
|
|
28588
|
+
next: context.requestedOrgId ? authRequiredMessage(context) : "New account: runtype auth register --email <email>. Existing account: runtype auth login --api-key <key>."
|
|
27902
28589
|
}, null, 2));
|
|
27903
28590
|
return;
|
|
27904
28591
|
}
|
|
27905
|
-
const
|
|
27906
|
-
const claimState =
|
|
28592
|
+
const pendingClaim = await store.getClaimState();
|
|
28593
|
+
const claimState = pendingClaim?.accountId === credentials.accountId ? pendingClaim : null;
|
|
27907
28594
|
console.log(JSON.stringify({
|
|
27908
28595
|
status: "authenticated",
|
|
27909
28596
|
accountState: claimState ? "temporary" : "full",
|
|
27910
|
-
userId: credentials
|
|
27911
|
-
apiUrl: credentials
|
|
27912
|
-
orgId: credentials
|
|
28597
|
+
userId: credentials.userId,
|
|
28598
|
+
apiUrl: credentials.apiUrl,
|
|
28599
|
+
orgId: credentials.orgId || null,
|
|
28600
|
+
accountId: credentials.accountId,
|
|
28601
|
+
accountSource: context.accountSource,
|
|
28602
|
+
projectLink: context.projectLink?.path ?? null,
|
|
28603
|
+
secretStore: context.account?.secretStore ?? null,
|
|
28604
|
+
storedLogins,
|
|
28605
|
+
...context.credentialSource === "environment" ? { note: "RUNTYPE_API_KEY is set and takes precedence over the stored login." } : {},
|
|
27913
28606
|
...claimState ? {
|
|
27914
28607
|
registrationId: claimState.registrationId,
|
|
27915
28608
|
credentialExpires: claimState.credentialExpires,
|
|
@@ -27926,74 +28619,158 @@ authCommand.command("status").description("Show authentication status").option("
|
|
|
27926
28619
|
}
|
|
27927
28620
|
return;
|
|
27928
28621
|
}
|
|
27929
|
-
|
|
27930
|
-
const
|
|
27931
|
-
const
|
|
27932
|
-
const
|
|
27933
|
-
|
|
27934
|
-
|
|
27935
|
-
|
|
27936
|
-
|
|
27937
|
-
|
|
27938
|
-
|
|
27939
|
-
|
|
27940
|
-
|
|
27941
|
-
|
|
27942
|
-
|
|
27943
|
-
|
|
27944
|
-
|
|
27945
|
-
|
|
27946
|
-
|
|
27947
|
-
|
|
27948
|
-
|
|
27949
|
-
|
|
27950
|
-
|
|
27951
|
-
|
|
27952
|
-
{
|
|
27953
|
-
label: "Status",
|
|
27954
|
-
value: "Authenticated",
|
|
27955
|
-
color: "green"
|
|
27956
|
-
},
|
|
27957
|
-
{
|
|
27958
|
-
label: "User ID",
|
|
27959
|
-
value: credentials.userId
|
|
27960
|
-
},
|
|
27961
|
-
{
|
|
27962
|
-
label: "API URL",
|
|
27963
|
-
value: credentials.apiUrl || "default"
|
|
27964
|
-
},
|
|
27965
|
-
{
|
|
27966
|
-
label: "API Key",
|
|
27967
|
-
value: credentials.apiKey ? credentials.apiKey.substring(0, 12) + "..." : "missing"
|
|
27968
|
-
},
|
|
27969
|
-
{
|
|
27970
|
-
label: "Org ID",
|
|
27971
|
-
value: credentials.orgId || "none"
|
|
27972
|
-
}
|
|
27973
|
-
] }));
|
|
27974
|
-
setSuccess(true);
|
|
27975
|
-
setLoading(false);
|
|
27976
|
-
} catch (err) {
|
|
27977
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
27978
|
-
setSuccess(false);
|
|
27979
|
-
setLoading(false);
|
|
28622
|
+
await renderTask("Checking authentication...", async () => {
|
|
28623
|
+
const context = await loadEffectiveContext();
|
|
28624
|
+
const storedLogins = (await store.listAccounts()).length;
|
|
28625
|
+
const credentials = context.credentials;
|
|
28626
|
+
if (!credentials) return {
|
|
28627
|
+
message: "Auth Status",
|
|
28628
|
+
result: React.createElement(EntityCard, { fields: [
|
|
28629
|
+
{
|
|
28630
|
+
label: "Status",
|
|
28631
|
+
value: "Not authenticated",
|
|
28632
|
+
color: "yellow"
|
|
28633
|
+
},
|
|
28634
|
+
{
|
|
28635
|
+
label: "Selected by",
|
|
28636
|
+
value: describeAccountSource(context)
|
|
28637
|
+
},
|
|
28638
|
+
{
|
|
28639
|
+
label: "Stored logins",
|
|
28640
|
+
value: String(storedLogins)
|
|
28641
|
+
},
|
|
28642
|
+
{
|
|
28643
|
+
label: "Hint",
|
|
28644
|
+
value: context.requestedOrgId ? authRequiredMessage(context) : "Run runtype auth login"
|
|
27980
28645
|
}
|
|
27981
|
-
}
|
|
27982
|
-
|
|
27983
|
-
|
|
27984
|
-
|
|
27985
|
-
|
|
27986
|
-
|
|
27987
|
-
|
|
27988
|
-
|
|
27989
|
-
|
|
27990
|
-
|
|
27991
|
-
|
|
27992
|
-
|
|
27993
|
-
|
|
27994
|
-
|
|
28646
|
+
] })
|
|
28647
|
+
};
|
|
28648
|
+
return {
|
|
28649
|
+
message: "Auth Status",
|
|
28650
|
+
result: React.createElement(EntityCard, { fields: [
|
|
28651
|
+
{
|
|
28652
|
+
label: "Status",
|
|
28653
|
+
value: "Authenticated",
|
|
28654
|
+
color: "green"
|
|
28655
|
+
},
|
|
28656
|
+
{
|
|
28657
|
+
label: "Organization",
|
|
28658
|
+
value: credentials.orgId || "Personal"
|
|
28659
|
+
},
|
|
28660
|
+
{
|
|
28661
|
+
label: "User ID",
|
|
28662
|
+
value: credentials.userId
|
|
28663
|
+
},
|
|
28664
|
+
{
|
|
28665
|
+
label: "API URL",
|
|
28666
|
+
value: credentials.apiUrl
|
|
28667
|
+
},
|
|
28668
|
+
{
|
|
28669
|
+
label: "API Key",
|
|
28670
|
+
value: credentials.apiKey.substring(0, 12) + "..."
|
|
28671
|
+
},
|
|
28672
|
+
{
|
|
28673
|
+
label: "Selected by",
|
|
28674
|
+
value: describeAccountSource(context)
|
|
28675
|
+
},
|
|
28676
|
+
{
|
|
28677
|
+
label: "Stored in",
|
|
28678
|
+
value: context.account?.secretStore === "file" ? "credential file" : "OS keychain"
|
|
28679
|
+
},
|
|
28680
|
+
{
|
|
28681
|
+
label: "Stored logins",
|
|
28682
|
+
value: String(storedLogins)
|
|
28683
|
+
},
|
|
28684
|
+
...context.credentialSource === "environment" ? [{
|
|
28685
|
+
label: "Note",
|
|
28686
|
+
value: "RUNTYPE_API_KEY overrides the stored login",
|
|
28687
|
+
color: "yellow"
|
|
28688
|
+
}] : []
|
|
28689
|
+
] })
|
|
28690
|
+
};
|
|
28691
|
+
});
|
|
28692
|
+
});
|
|
28693
|
+
authCommand.command("accounts").alias("list").description("List stored logins (one per organization and API URL)").option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (options) => {
|
|
28694
|
+
const store = new CredentialStore();
|
|
28695
|
+
const [accounts, defaultAccount, context] = await Promise.all([
|
|
28696
|
+
store.listAccounts(),
|
|
28697
|
+
store.getDefaultAccount(),
|
|
28698
|
+
loadEffectiveContext()
|
|
28699
|
+
]);
|
|
28700
|
+
const rows = accounts.map((account) => ({
|
|
28701
|
+
accountId: account.id,
|
|
28702
|
+
orgId: account.orgId,
|
|
28703
|
+
userId: account.userId,
|
|
28704
|
+
apiUrl: account.apiUrl,
|
|
28705
|
+
isDefault: account.id === defaultAccount?.id,
|
|
28706
|
+
isActive: account.id === context.account?.id,
|
|
28707
|
+
secretStore: account.secretStore,
|
|
28708
|
+
pendingClaim: Boolean(account.claim),
|
|
28709
|
+
lastUsedAt: account.lastUsedAt ?? null
|
|
28710
|
+
}));
|
|
28711
|
+
if (!isTTY(options) || options.json) {
|
|
28712
|
+
console.log(JSON.stringify({ accounts: rows }, null, 2));
|
|
28713
|
+
return;
|
|
28714
|
+
}
|
|
28715
|
+
if (rows.length === 0) {
|
|
28716
|
+
console.log(chalk.yellow("No stored logins. Run: runtype login"));
|
|
28717
|
+
return;
|
|
28718
|
+
}
|
|
28719
|
+
for (const row of rows) {
|
|
28720
|
+
const marker = row.isActive ? chalk.green("*") : " ";
|
|
28721
|
+
const tags = [row.isDefault ? "default" : null, row.pendingClaim ? "unclaimed" : null].filter(Boolean).join(", ");
|
|
28722
|
+
console.log(`${marker} ${chalk.bold(row.orgId ?? "Personal")} ${chalk.gray(row.apiUrl)}${tags ? ` ${chalk.cyan(`(${tags})`)}` : ""}`);
|
|
28723
|
+
}
|
|
28724
|
+
console.log(chalk.gray("\n* = login used in this directory"));
|
|
28725
|
+
});
|
|
28726
|
+
/** Resolve an `auth switch`/`logout` argument (org id or account id) to exactly one stored login. */
|
|
28727
|
+
async function findAccountArgument(store, value, apiUrl) {
|
|
28728
|
+
const accounts = await store.listAccounts();
|
|
28729
|
+
const byId = accounts.find((account) => account.id === value);
|
|
28730
|
+
if (byId) return byId;
|
|
28731
|
+
const target = apiUrl ? normalizeApiUrl(apiUrl) : void 0;
|
|
28732
|
+
const matches = accounts.filter((account) => account.orgId === value && (!target || account.apiUrl === target));
|
|
28733
|
+
if (matches.length === 1) return matches[0];
|
|
28734
|
+
if (matches.length > 1) throw new CliContextError(`${value} has logins on several API URLs (${matches.map((m) => m.apiUrl).join(", ")}); pass --api-url to pick one.`);
|
|
28735
|
+
throw new CliContextError(`No stored login for ${value}. Run: runtype login --org ${value}`);
|
|
28736
|
+
}
|
|
28737
|
+
authCommand.command("switch [org]").description("Set the default login used outside linked projects").option("--api-url <url>", "Disambiguate when the organization has logins on several API URLs").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (org, options) => {
|
|
28738
|
+
const store = new CredentialStore();
|
|
28739
|
+
const accounts = await store.listAccounts();
|
|
28740
|
+
if (accounts.length === 0) {
|
|
28741
|
+
console.error(chalk.yellow("No stored logins. Run: runtype login"));
|
|
28742
|
+
process.exit(1);
|
|
28743
|
+
return;
|
|
28744
|
+
}
|
|
28745
|
+
let account;
|
|
28746
|
+
if (org) account = await findAccountArgument(store, org, options.apiUrl ?? getInvocationOptions().apiUrl);
|
|
28747
|
+
else if (!isTTY(options)) {
|
|
28748
|
+
console.error(JSON.stringify({
|
|
28749
|
+
status: "error",
|
|
28750
|
+
error: "Pass the organization to switch to.",
|
|
28751
|
+
next: "runtype auth accounts --json, then: runtype auth switch <orgId>"
|
|
28752
|
+
}));
|
|
28753
|
+
process.exit(1);
|
|
28754
|
+
return;
|
|
28755
|
+
} else account = await promptForAccount(accounts);
|
|
28756
|
+
if (!account) return;
|
|
28757
|
+
await store.setDefaultAccount(account.id);
|
|
28758
|
+
const context = await loadEffectiveContext();
|
|
28759
|
+
const shadowed = context.accountSource !== "default" && context.account?.id !== account.id ? `This directory still uses ${context.requestedOrgId} (${describeAccountSource(context)}).` : null;
|
|
28760
|
+
if (!isTTY(options)) {
|
|
28761
|
+
console.log(JSON.stringify({
|
|
28762
|
+
status: "switched",
|
|
28763
|
+
accountId: account.id,
|
|
28764
|
+
orgId: account.orgId,
|
|
28765
|
+
apiUrl: account.apiUrl,
|
|
28766
|
+
...shadowed ? { note: shadowed } : {}
|
|
28767
|
+
}, null, 2));
|
|
28768
|
+
return;
|
|
28769
|
+
}
|
|
28770
|
+
console.log(chalk.green(`Default login is now ${account.orgId ?? "Personal"} (${account.apiUrl}).`));
|
|
28771
|
+
if (shadowed) console.log(chalk.yellow(shadowed));
|
|
27995
28772
|
});
|
|
27996
|
-
authCommand.command("export-key").description("Print the
|
|
28773
|
+
authCommand.command("export-key").description("Print the active login's API key to stdout (for use in scripts and .env files)").action(async () => {
|
|
27997
28774
|
const apiKey = await new CredentialStore().getApiKey();
|
|
27998
28775
|
if (!apiKey) {
|
|
27999
28776
|
console.error("Not authenticated. Run `runtype auth login` first.");
|
|
@@ -28003,52 +28780,43 @@ authCommand.command("export-key").description("Print the stored API key to stdou
|
|
|
28003
28780
|
process.stdout.write(apiKey);
|
|
28004
28781
|
});
|
|
28005
28782
|
registerAgentAuthCommands(authCommand);
|
|
28006
|
-
authCommand.command("logout").description("Remove stored
|
|
28783
|
+
authCommand.command("logout [org]").description("Remove the login this directory uses (or the named org's login, or --all)").option("--all", "Remove every stored login").option("--api-url <url>", "Disambiguate when the organization has logins on several API URLs").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (org, options) => {
|
|
28007
28784
|
const store = new CredentialStore();
|
|
28785
|
+
const logout = async () => {
|
|
28786
|
+
if (options.all) {
|
|
28787
|
+
const accounts = await store.listAccounts();
|
|
28788
|
+
for (const account of accounts) await store.removeAccount(account.id);
|
|
28789
|
+
return { removed: accounts };
|
|
28790
|
+
}
|
|
28791
|
+
if (org) {
|
|
28792
|
+
const account = await findAccountArgument(store, org, options.apiUrl ?? getInvocationOptions().apiUrl);
|
|
28793
|
+
await store.removeAccount(account.id);
|
|
28794
|
+
return { removed: [account] };
|
|
28795
|
+
}
|
|
28796
|
+
const removed = await store.clearCredentials();
|
|
28797
|
+
return { removed: removed ? [removed] : [] };
|
|
28798
|
+
};
|
|
28008
28799
|
if (!isTTY(options)) {
|
|
28009
|
-
|
|
28800
|
+
const { removed } = await logout();
|
|
28801
|
+
if (removed.length === 0) {
|
|
28010
28802
|
console.log(JSON.stringify({ status: "no_credentials" }, null, 2));
|
|
28011
28803
|
return;
|
|
28012
28804
|
}
|
|
28013
|
-
|
|
28014
|
-
|
|
28805
|
+
console.log(JSON.stringify({
|
|
28806
|
+
status: "logged_out",
|
|
28807
|
+
removed: removed.map((account) => ({
|
|
28808
|
+
orgId: account.orgId,
|
|
28809
|
+
apiUrl: account.apiUrl
|
|
28810
|
+
})),
|
|
28811
|
+
remainingLogins: (await store.listAccounts()).length
|
|
28812
|
+
}, null, 2));
|
|
28015
28813
|
return;
|
|
28016
28814
|
}
|
|
28017
|
-
|
|
28018
|
-
const
|
|
28019
|
-
|
|
28020
|
-
|
|
28021
|
-
|
|
28022
|
-
useEffect(() => {
|
|
28023
|
-
const run = async () => {
|
|
28024
|
-
try {
|
|
28025
|
-
if (!await store.hasCredentials()) {
|
|
28026
|
-
setMsg("No stored credentials found");
|
|
28027
|
-
setSuccess(true);
|
|
28028
|
-
setLoading(false);
|
|
28029
|
-
return;
|
|
28030
|
-
}
|
|
28031
|
-
await store.clearCredentials();
|
|
28032
|
-
setSuccess(true);
|
|
28033
|
-
setLoading(false);
|
|
28034
|
-
} catch (err) {
|
|
28035
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
28036
|
-
setSuccess(false);
|
|
28037
|
-
setLoading(false);
|
|
28038
|
-
}
|
|
28039
|
-
};
|
|
28040
|
-
run();
|
|
28041
|
-
}, []);
|
|
28042
|
-
return React.createElement(MutationResult, {
|
|
28043
|
-
loading,
|
|
28044
|
-
loadingLabel: "Logging out...",
|
|
28045
|
-
success,
|
|
28046
|
-
successMessage: msg,
|
|
28047
|
-
error
|
|
28048
|
-
});
|
|
28049
|
-
};
|
|
28050
|
-
const { waitUntilExit } = render(React.createElement(App));
|
|
28051
|
-
await waitUntilExit();
|
|
28815
|
+
await renderTask("Logging out...", async () => {
|
|
28816
|
+
const { removed } = await logout();
|
|
28817
|
+
if (removed.length === 0) return { message: "No stored credentials found" };
|
|
28818
|
+
return { message: `Logged out of ${removed.map((account) => account.orgId ?? "Personal").join(", ")}` };
|
|
28819
|
+
});
|
|
28052
28820
|
});
|
|
28053
28821
|
authCommand.command("whoami").description("Display current user information").option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (options) => {
|
|
28054
28822
|
const apiKey = await ensureAuth({
|
|
@@ -28062,10 +28830,16 @@ authCommand.command("whoami").description("Display current user information").op
|
|
|
28062
28830
|
apiUrl: context.apiUrl
|
|
28063
28831
|
};
|
|
28064
28832
|
if (!credentials || !credentials.apiKey) {
|
|
28065
|
-
if (!isTTY(options) || options.json) console.log(JSON.stringify({
|
|
28833
|
+
if (!isTTY(options) || options.json) console.log(JSON.stringify({
|
|
28834
|
+
status: "unauthenticated",
|
|
28835
|
+
...context.requestedOrgId ? {
|
|
28836
|
+
requestedOrgId: context.requestedOrgId,
|
|
28837
|
+
next: authRequiredMessage(context)
|
|
28838
|
+
} : {}
|
|
28839
|
+
}, null, 2));
|
|
28066
28840
|
else {
|
|
28067
|
-
console.log(chalk.yellow("Not logged in"));
|
|
28068
|
-
console.log("Run: runtype auth login");
|
|
28841
|
+
console.log(chalk.yellow(context.requestedOrgId ? authRequiredMessage(context) : "Not logged in"));
|
|
28842
|
+
if (!context.requestedOrgId) console.log("Run: runtype auth login");
|
|
28069
28843
|
}
|
|
28070
28844
|
return;
|
|
28071
28845
|
}
|
|
@@ -28077,6 +28851,9 @@ authCommand.command("whoami").description("Display current user information").op
|
|
|
28077
28851
|
userId: user.userId,
|
|
28078
28852
|
orgId: user.orgId,
|
|
28079
28853
|
apiUrl: credentials.apiUrl || getApiUrl(),
|
|
28854
|
+
credentialSource: context.credentialSource,
|
|
28855
|
+
accountSource: context.accountSource,
|
|
28856
|
+
projectLink: context.projectLink?.path ?? null,
|
|
28080
28857
|
billing: billingInfo
|
|
28081
28858
|
}, null, 2));
|
|
28082
28859
|
} catch (error) {
|
|
@@ -28089,81 +28866,142 @@ authCommand.command("whoami").description("Display current user information").op
|
|
|
28089
28866
|
}
|
|
28090
28867
|
return;
|
|
28091
28868
|
}
|
|
28092
|
-
|
|
28093
|
-
const
|
|
28094
|
-
const
|
|
28095
|
-
const
|
|
28096
|
-
|
|
28097
|
-
|
|
28098
|
-
|
|
28099
|
-
|
|
28100
|
-
|
|
28101
|
-
|
|
28102
|
-
|
|
28103
|
-
|
|
28104
|
-
|
|
28105
|
-
|
|
28106
|
-
|
|
28107
|
-
|
|
28108
|
-
|
|
28109
|
-
|
|
28110
|
-
|
|
28111
|
-
|
|
28112
|
-
|
|
28113
|
-
|
|
28114
|
-
|
|
28115
|
-
|
|
28116
|
-
|
|
28117
|
-
|
|
28118
|
-
|
|
28119
|
-
|
|
28120
|
-
|
|
28121
|
-
|
|
28122
|
-
|
|
28123
|
-
|
|
28124
|
-
|
|
28125
|
-
|
|
28126
|
-
|
|
28127
|
-
|
|
28128
|
-
|
|
28129
|
-
|
|
28130
|
-
|
|
28131
|
-
|
|
28132
|
-
|
|
28133
|
-
|
|
28134
|
-
|
|
28135
|
-
|
|
28136
|
-
|
|
28137
|
-
|
|
28138
|
-
|
|
28139
|
-
|
|
28140
|
-
|
|
28141
|
-
|
|
28142
|
-
|
|
28143
|
-
|
|
28144
|
-
|
|
28145
|
-
|
|
28146
|
-
|
|
28147
|
-
|
|
28148
|
-
|
|
28149
|
-
|
|
28150
|
-
|
|
28151
|
-
|
|
28152
|
-
|
|
28153
|
-
|
|
28154
|
-
|
|
28155
|
-
|
|
28156
|
-
|
|
28157
|
-
|
|
28158
|
-
|
|
28159
|
-
|
|
28160
|
-
|
|
28161
|
-
|
|
28162
|
-
|
|
28163
|
-
|
|
28869
|
+
await renderTask("Fetching user information...", async () => {
|
|
28870
|
+
const user = await getAuthenticatedUser();
|
|
28871
|
+
const billingInfo = await getBillingInfo(credentials.apiKey, credentials.apiUrl);
|
|
28872
|
+
const fields = [
|
|
28873
|
+
{
|
|
28874
|
+
label: "User ID",
|
|
28875
|
+
value: user.userId
|
|
28876
|
+
},
|
|
28877
|
+
{
|
|
28878
|
+
label: "Organization",
|
|
28879
|
+
value: user.orgId || "Personal"
|
|
28880
|
+
},
|
|
28881
|
+
{
|
|
28882
|
+
label: "API Key",
|
|
28883
|
+
value: credentials.apiKey.substring(0, 10) + "..."
|
|
28884
|
+
},
|
|
28885
|
+
{
|
|
28886
|
+
label: "API URL",
|
|
28887
|
+
value: credentials.apiUrl || getApiUrl()
|
|
28888
|
+
},
|
|
28889
|
+
{
|
|
28890
|
+
label: "Selected by",
|
|
28891
|
+
value: context.credentialSource === "environment" ? "RUNTYPE_API_KEY" : describeAccountSource(context)
|
|
28892
|
+
},
|
|
28893
|
+
{
|
|
28894
|
+
label: "Created",
|
|
28895
|
+
value: credentials.createdAt
|
|
28896
|
+
},
|
|
28897
|
+
{
|
|
28898
|
+
label: "Last Used",
|
|
28899
|
+
value: credentials.lastUsed
|
|
28900
|
+
}
|
|
28901
|
+
];
|
|
28902
|
+
if (billingInfo) {
|
|
28903
|
+
const plan = billingInfo.planName || billingInfo.plan;
|
|
28904
|
+
if (plan) fields.push({
|
|
28905
|
+
label: "Plan",
|
|
28906
|
+
value: plan,
|
|
28907
|
+
color: "green"
|
|
28908
|
+
});
|
|
28909
|
+
const usage = billingInfo.usage;
|
|
28910
|
+
if (usage) {
|
|
28911
|
+
const used = usage.executionsToday ?? usage.executionsThisMonth;
|
|
28912
|
+
const value = usage.executionsThisMonthAvailable === false || typeof used !== "number" ? "unavailable" : used.toLocaleString();
|
|
28913
|
+
fields.push({
|
|
28914
|
+
label: "Executions today",
|
|
28915
|
+
value
|
|
28916
|
+
});
|
|
28917
|
+
}
|
|
28918
|
+
}
|
|
28919
|
+
return {
|
|
28920
|
+
message: "Current User",
|
|
28921
|
+
result: React.createElement(EntityCard, { fields })
|
|
28922
|
+
};
|
|
28923
|
+
});
|
|
28924
|
+
});
|
|
28925
|
+
//#endregion
|
|
28926
|
+
//#region src/commands/link.ts
|
|
28927
|
+
const PRODUCTION_API_URL = "https://api.runtype.com";
|
|
28928
|
+
async function chooseOrganization(orgArgument, options) {
|
|
28929
|
+
if (orgArgument) return { orgId: orgArgument };
|
|
28930
|
+
const accounts = (await new CredentialStore().listAccounts()).filter((account) => account.orgId);
|
|
28931
|
+
if (accounts.length > 1 && isTTY(options) && !options.json) {
|
|
28932
|
+
console.log(chalk.cyan("Which organization should this directory use?"));
|
|
28933
|
+
const chosen = await promptForAccount(accounts);
|
|
28934
|
+
if (!chosen?.orgId) throw new CliContextError("No organization selected.");
|
|
28935
|
+
return {
|
|
28936
|
+
orgId: chosen.orgId,
|
|
28937
|
+
apiUrl: chosen.apiUrl
|
|
28938
|
+
};
|
|
28939
|
+
}
|
|
28940
|
+
const context = await loadEffectiveContext();
|
|
28941
|
+
const orgId = context.credentials?.orgId ?? (accounts.length === 1 ? accounts[0].orgId : null);
|
|
28942
|
+
if (!orgId) throw new CliContextError("Pass the organization to link (runtype link <orgId>), or log in first with: runtype login");
|
|
28943
|
+
return {
|
|
28944
|
+
orgId,
|
|
28945
|
+
apiUrl: context.credentials?.orgId ? context.credentials.apiUrl : accounts[0]?.apiUrl
|
|
28164
28946
|
};
|
|
28165
|
-
|
|
28166
|
-
|
|
28947
|
+
}
|
|
28948
|
+
const linkCommand = new Command("link").description("Link this directory to an organization: writes .runtype/project.json (no secrets; commit it)").argument("[orgId]", "Organization ID (defaults to a stored login)").option("--api-url <url>", "Also pin the API URL for this directory").option("--dir <path>", "Directory to link (default: current directory)").option("--force", "Replace an existing link in that directory").option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (orgArgument, options) => {
|
|
28949
|
+
const directory = path.resolve(options.dir ?? process.cwd());
|
|
28950
|
+
const existingPath = projectLinkPath(directory);
|
|
28951
|
+
let existingApiUrl;
|
|
28952
|
+
if (fs.existsSync(existingPath) && !options.force) {
|
|
28953
|
+
let existingOrg = null;
|
|
28954
|
+
try {
|
|
28955
|
+
const existing = parseProjectLink(fs.readFileSync(existingPath, "utf8"), existingPath);
|
|
28956
|
+
existingOrg = existing.orgId;
|
|
28957
|
+
existingApiUrl = existing.apiUrl;
|
|
28958
|
+
} catch {
|
|
28959
|
+
existingOrg = null;
|
|
28960
|
+
}
|
|
28961
|
+
if (existingOrg !== orgArgument || !orgArgument) throw new CliContextError(`${existingPath} already links ${existingOrg ?? "an unreadable organization"}. Pass --force to replace it.`);
|
|
28962
|
+
}
|
|
28963
|
+
const chosen = await chooseOrganization(orgArgument, options);
|
|
28964
|
+
const candidateApiUrl = options.apiUrl ?? getInvocationOptions().apiUrl ?? existingApiUrl ?? chosen.apiUrl;
|
|
28965
|
+
const apiUrl = candidateApiUrl && normalizeApiUrl(candidateApiUrl) !== PRODUCTION_API_URL ? normalizeApiUrl(candidateApiUrl) : void 0;
|
|
28966
|
+
if (apiUrl && !isFirstPartyApiUrl(apiUrl)) throw new CliContextError(`${apiUrl} is not a Runtype API host, so it cannot be pinned in a committed link. Use --api-url or RUNTYPE_API_URL for it instead.`);
|
|
28967
|
+
const filePath = writeProjectLink(directory, {
|
|
28968
|
+
orgId: chosen.orgId,
|
|
28969
|
+
apiUrl
|
|
28970
|
+
});
|
|
28971
|
+
const hasLogin = (await new CredentialStore().listAccounts()).some((account) => account.orgId === chosen.orgId && account.apiUrl === (apiUrl ?? account.apiUrl));
|
|
28972
|
+
const next = hasLogin ? null : `No stored login for ${chosen.orgId} yet. Run: runtype login`;
|
|
28973
|
+
if (options.json || !isTTY(options)) {
|
|
28974
|
+
console.log(JSON.stringify({
|
|
28975
|
+
status: "linked",
|
|
28976
|
+
path: filePath,
|
|
28977
|
+
orgId: chosen.orgId,
|
|
28978
|
+
apiUrl: apiUrl ?? null,
|
|
28979
|
+
hasLogin,
|
|
28980
|
+
...next ? { next } : {}
|
|
28981
|
+
}, null, 2));
|
|
28982
|
+
return;
|
|
28983
|
+
}
|
|
28984
|
+
console.log(chalk.green(`Linked ${directory} to ${chosen.orgId}${apiUrl ? ` (${apiUrl})` : ""}.`));
|
|
28985
|
+
console.log(chalk.gray(`Wrote ${filePath}. It holds no secrets; commit it so teammates use the same org.`));
|
|
28986
|
+
if (next) console.log(chalk.yellow(next));
|
|
28987
|
+
});
|
|
28988
|
+
const unlinkCommand = new Command("unlink").description("Remove the nearest .runtype/project.json so commands use the default login again").option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (options) => {
|
|
28989
|
+
let root = null;
|
|
28990
|
+
try {
|
|
28991
|
+
root = findProjectLink()?.root ?? null;
|
|
28992
|
+
} catch (error) {
|
|
28993
|
+
if (!(error instanceof ProjectLinkError)) throw error;
|
|
28994
|
+
root = path.dirname(path.dirname(error.path));
|
|
28995
|
+
}
|
|
28996
|
+
const removedPath = root && removeProjectLink(root) ? projectLinkPath(root) : null;
|
|
28997
|
+
if (options.json || !isTTY(options)) {
|
|
28998
|
+
console.log(JSON.stringify(removedPath ? {
|
|
28999
|
+
status: "unlinked",
|
|
29000
|
+
path: removedPath
|
|
29001
|
+
} : { status: "not_linked" }, null, 2));
|
|
29002
|
+
return;
|
|
29003
|
+
}
|
|
29004
|
+
console.log(removedPath ? chalk.green(`Removed ${removedPath}. Commands here now use the default login.`) : chalk.yellow("No .runtype/project.json found in this directory or its parents."));
|
|
28167
29005
|
});
|
|
28168
29006
|
//#endregion
|
|
28169
29007
|
//#region src/lib/file-utils.ts
|
|
@@ -31429,6 +32267,9 @@ configCommand.command("context").description("Show the effective API target and
|
|
|
31429
32267
|
console.log(`API URL: ${context.apiUrl} (${context.apiUrlSource})`);
|
|
31430
32268
|
console.log(`Credential source: ${context.credentialSource}`);
|
|
31431
32269
|
console.log(`Organization: ${context.orgId ?? "unknown"} (not verified; use auth whoami)`);
|
|
32270
|
+
console.log(`Login selected by: ${context.accountSource}${context.requestedOrgId ? ` (${context.requestedOrgId})` : ""}`);
|
|
32271
|
+
if (context.projectLink) console.log(`Project link: ${context.projectLink}`);
|
|
32272
|
+
if (context.credentialStore) console.log(`Credential stored in: ${context.credentialStore}`);
|
|
31432
32273
|
});
|
|
31433
32274
|
configCommand.command("get [key]").description("Get configuration value").action((key) => {
|
|
31434
32275
|
const config = getCliConfig();
|
|
@@ -32832,6 +33673,32 @@ integrationsCommand.command("get <id>").description("Get integration details, in
|
|
|
32832
33673
|
});
|
|
32833
33674
|
//#endregion
|
|
32834
33675
|
//#region src/commands/surfaces.ts
|
|
33676
|
+
const DELIVERY_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
33677
|
+
async function readStdin$1() {
|
|
33678
|
+
let value = "";
|
|
33679
|
+
process.stdin.setEncoding("utf8");
|
|
33680
|
+
for await (const chunk of process.stdin) value += chunk;
|
|
33681
|
+
return value;
|
|
33682
|
+
}
|
|
33683
|
+
/** The `--payload` object, read from a file or from stdin when the source is `-`. */
|
|
33684
|
+
async function readPayloadObject(source) {
|
|
33685
|
+
const label = source === "-" ? "stdin" : source;
|
|
33686
|
+
const raw = source === "-" ? await readStdin$1() : await readFile(source, "utf8");
|
|
33687
|
+
let parsed;
|
|
33688
|
+
try {
|
|
33689
|
+
parsed = JSON.parse(raw);
|
|
33690
|
+
} catch {
|
|
33691
|
+
throw new Error(`--payload: invalid JSON in ${label}`);
|
|
33692
|
+
}
|
|
33693
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`--payload: ${label} must contain a JSON object`);
|
|
33694
|
+
return parsed;
|
|
33695
|
+
}
|
|
33696
|
+
/** `Attempt 1: HTTP 202, run exec_1 (route: event_route opened)` */
|
|
33697
|
+
function formatDeliveryAttempt(attempt) {
|
|
33698
|
+
const outcome = attempt.deduplicated ? ", deduplicated" : attempt.executionId ? `, run ${attempt.executionId}` : "";
|
|
33699
|
+
const route = attempt.route ? ` (route: ${[attempt.route.matchedBy, attempt.route.pattern].filter(Boolean).join(" ")})` : "";
|
|
33700
|
+
return `Attempt ${attempt.attempt}: HTTP ${attempt.status}${outcome}${route}`;
|
|
33701
|
+
}
|
|
32835
33702
|
function parsePositiveInteger$1(value, option) {
|
|
32836
33703
|
const parsed = Number(value);
|
|
32837
33704
|
if (!Number.isInteger(parsed) || parsed < 1) throw new Error(`${option} must be a positive integer`);
|
|
@@ -33076,19 +33943,23 @@ surfacesCommand.command("get <id>").description("Get surface details including i
|
|
|
33076
33943
|
const { waitUntilExit } = render(React.createElement(App));
|
|
33077
33944
|
await waitUntilExit();
|
|
33078
33945
|
});
|
|
33079
|
-
surfacesCommand.command("test <id>").description("Send a provider fixture through a surface’s real ingress").addOption(new Option("--product <productId>", "Deprecated; surface IDs resolve their product").hideHelp()).requiredOption("--fixture <fixture>", "Fixture name, such as github.pull_request.opened or github.pull_request.merged_plan").requiredOption("--repo <owner/repository>", "GitHub repository in owner/repository format").requiredOption("--pr <number>", "Pull request number").option("--repeat <number>", "Deliver the same provider event more than once", "1").option("--planning-path <path>", "Planning-document directory for merged-plan fixtures").option("--json", "Output as JSON").action(async (id, options) => {
|
|
33946
|
+
surfacesCommand.command("test <id>").description("Send a provider fixture through a surface’s real ingress").addOption(new Option("--product <productId>", "Deprecated; surface IDs resolve their product").hideHelp()).requiredOption("--fixture <fixture>", "Fixture name, such as github.pull_request.opened or github.pull_request.merged_plan").requiredOption("--repo <owner/repository>", "GitHub repository in owner/repository format").requiredOption("--pr <number>", "Pull request number").option("--repeat <number>", "Deliver the same provider event more than once", "1").option("--planning-path <path>", "Planning-document directory for merged-plan fixtures").option("--delivery-id <id>", "Reuse a previous delivery ID (X-GitHub-Delivery), to check the surface handles a repeat once").option("--payload <file>", "Send and sign this JSON object instead of the built fixture; - reads it from stdin").option("--json", "Output as JSON").action(async (id, options) => {
|
|
33080
33947
|
const apiKey = await ensureAuth();
|
|
33081
33948
|
if (!apiKey) return;
|
|
33082
33949
|
try {
|
|
33083
33950
|
const pullRequest = parsePositiveInteger$1(options.pr, "--pr");
|
|
33084
33951
|
const repeat = parsePositiveInteger$1(options.repeat, "--repeat");
|
|
33085
33952
|
if (repeat > 3) throw new Error("--repeat must not exceed 3");
|
|
33953
|
+
if (options.deliveryId !== void 0 && !DELIVERY_ID_PATTERN.test(options.deliveryId)) throw new Error("--delivery-id must be 1-128 letters, digits, dots, underscores, colons or hyphens");
|
|
33954
|
+
const payload = options.payload !== void 0 ? await readPayloadObject(options.payload) : void 0;
|
|
33086
33955
|
const result = await createCliClient(apiKey).post(`/surfaces/${encodeURIComponent(id)}/test-deliveries`, {
|
|
33087
33956
|
fixture: options.fixture,
|
|
33088
33957
|
repository: options.repo,
|
|
33089
33958
|
pullRequest,
|
|
33090
33959
|
repeat,
|
|
33091
|
-
...options.planningPath ? { planningPath: options.planningPath } : {}
|
|
33960
|
+
...options.planningPath ? { planningPath: options.planningPath } : {},
|
|
33961
|
+
...options.deliveryId ? { deliveryId: options.deliveryId } : {},
|
|
33962
|
+
...payload ? { payload } : {}
|
|
33092
33963
|
});
|
|
33093
33964
|
if (options.json) {
|
|
33094
33965
|
printJson(result);
|
|
@@ -33099,7 +33970,7 @@ surfacesCommand.command("test <id>").description("Send a provider fixture throug
|
|
|
33099
33970
|
console.log(` Fixture: ${result.fixture}`);
|
|
33100
33971
|
console.log(` Source: ${result.repository}#${result.pullRequest}`);
|
|
33101
33972
|
console.log(` Delivery: ${result.deliveryId}`);
|
|
33102
|
-
for (const attempt of result.attempts) console.log(`
|
|
33973
|
+
for (const attempt of result.attempts) console.log(` ${formatDeliveryAttempt(attempt)}`);
|
|
33103
33974
|
} catch (error) {
|
|
33104
33975
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
33105
33976
|
console.error(chalk.red(`Failed to test surface: ${message}`));
|
|
@@ -34100,21 +34971,16 @@ function InitApp({ apiUrl, onComplete }) {
|
|
|
34100
34971
|
loadingLabel: storedKeyNotice ? `${storedKeyNotice} Opening browser to sign in instead...` : "Opening browser to sign in..."
|
|
34101
34972
|
}));
|
|
34102
34973
|
try {
|
|
34103
|
-
const
|
|
34104
|
-
|
|
34105
|
-
|
|
34106
|
-
|
|
34107
|
-
|
|
34108
|
-
|
|
34109
|
-
loadingLabel: "Exchanging credentials..."
|
|
34110
|
-
}));
|
|
34111
|
-
const { key, userId, orgId } = await new ApiKeyManager().exchangeSessionForApiKey(sessionToken, apiUrl || getApiUrl());
|
|
34112
|
-
await store.saveCredentials({
|
|
34113
|
-
apiKey: key,
|
|
34114
|
-
userId,
|
|
34115
|
-
orgId,
|
|
34116
|
-
apiUrl: apiUrl || getApiUrl()
|
|
34974
|
+
const login = await browserLogin({
|
|
34975
|
+
apiUrl,
|
|
34976
|
+
onProgress: (label) => setState((s) => ({
|
|
34977
|
+
...s,
|
|
34978
|
+
loadingLabel: label
|
|
34979
|
+
}))
|
|
34117
34980
|
});
|
|
34981
|
+
const mismatch = orgMismatchWarning(login);
|
|
34982
|
+
if (mismatch) throw new Error(mismatch);
|
|
34983
|
+
const { apiKey: key, userId, orgId } = login;
|
|
34118
34984
|
setState((s) => ({
|
|
34119
34985
|
...s,
|
|
34120
34986
|
apiKey: key,
|
|
@@ -37659,7 +38525,7 @@ function applyMaxLines(raw, maxLines) {
|
|
|
37659
38525
|
function readFileContent(filePath, snapshots, liveTools, maxLines = 5e3) {
|
|
37660
38526
|
try {
|
|
37661
38527
|
return {
|
|
37662
|
-
...applyMaxLines(fs$
|
|
38528
|
+
...applyMaxLines(fs$1.readFileSync(filePath, "utf-8"), maxLines),
|
|
37663
38529
|
source: "disk"
|
|
37664
38530
|
};
|
|
37665
38531
|
} catch {}
|
|
@@ -37964,7 +38830,7 @@ const deployLifecycleParameters = {
|
|
|
37964
38830
|
description: "Runtype destruction backstop. Omitted: Daytona 10m, Runtype Sandbox unlimited. Provider sleep/loss can end availability earlier."
|
|
37965
38831
|
},
|
|
37966
38832
|
sleepPolicy: {
|
|
37967
|
-
description: "Idle compute policy, independent of retention.
|
|
38833
|
+
description: "Idle compute policy, independent of retention.",
|
|
37968
38834
|
oneOf: [{
|
|
37969
38835
|
type: "object",
|
|
37970
38836
|
properties: { mode: { const: "never" } },
|
|
@@ -38087,7 +38953,7 @@ function createDeploySandboxLocalTool(client, debugMode) {
|
|
|
38087
38953
|
let activeCfSandboxId;
|
|
38088
38954
|
function createDeployCfSandboxLocalTool(client, debugMode) {
|
|
38089
38955
|
return {
|
|
38090
|
-
description: "Deploy a live Runtype Sandbox server preview. Omitted retention preserves the unlimited legacy backstop; explicit lifecycle policy requires rollout enablement. Provider sleep/loss can end availability earlier; the URL is not durable hosting.",
|
|
38956
|
+
description: "Deploy a live Runtype Sandbox server preview. Omitted retention preserves the unlimited legacy backstop; explicit lifecycle policy requires rollout enablement. Provider sleep/loss can end availability earlier; the URL is not durable hosting. A redeploy stops the previous server; when effectivePolicy.previewUrlChanged is true, re-register any saved copy of the URL.",
|
|
38091
38957
|
parametersSchema: {
|
|
38092
38958
|
type: "object",
|
|
38093
38959
|
properties: {
|
|
@@ -38096,6 +38962,10 @@ function createDeployCfSandboxLocalTool(client, debugMode) {
|
|
|
38096
38962
|
description: "Code to deploy (main entry point file)"
|
|
38097
38963
|
},
|
|
38098
38964
|
...deployLifecycleParameters,
|
|
38965
|
+
sleepPolicy: {
|
|
38966
|
+
...deployLifecycleParameters.sleepPolicy,
|
|
38967
|
+
description: "Idle sleep, or { mode: \"never\" } to keep the container running until the retention deadline. \"never\" requires retention 10m, 1h or 24h."
|
|
38968
|
+
},
|
|
38099
38969
|
packageJson: {
|
|
38100
38970
|
type: "object",
|
|
38101
38971
|
description: "package.json content as a JSON object, e.g. { \"dependencies\": { \"hono\": \"^4.0.0\" } }"
|
|
@@ -38189,14 +39059,14 @@ function createEventLogWriter(stateFilePath, initialSessionIndex) {
|
|
|
38189
39059
|
let stream = null;
|
|
38190
39060
|
function ensureDir() {
|
|
38191
39061
|
if (!dirCreated) {
|
|
38192
|
-
fs$
|
|
39062
|
+
fs$1.mkdirSync(eventsDir, { recursive: true });
|
|
38193
39063
|
dirCreated = true;
|
|
38194
39064
|
}
|
|
38195
39065
|
}
|
|
38196
39066
|
function ensureStream() {
|
|
38197
39067
|
if (!stream || stream.destroyed) {
|
|
38198
39068
|
ensureDir();
|
|
38199
|
-
stream = fs$
|
|
39069
|
+
stream = fs$1.createWriteStream(currentPath, { flags: "a" });
|
|
38200
39070
|
stream.on("error", () => {});
|
|
38201
39071
|
}
|
|
38202
39072
|
}
|
|
@@ -38225,8 +39095,8 @@ function createEventLogWriter(stateFilePath, initialSessionIndex) {
|
|
|
38225
39095
|
* Returns an empty array if the file doesn't exist. */
|
|
38226
39096
|
function readSessionEventLog(stateFilePath, sessionIndex) {
|
|
38227
39097
|
const logPath = sessionEventLogPath(stateFilePath, sessionIndex);
|
|
38228
|
-
if (!fs$
|
|
38229
|
-
const content = fs$
|
|
39098
|
+
if (!fs$1.existsSync(logPath)) return [];
|
|
39099
|
+
const content = fs$1.readFileSync(logPath, "utf-8");
|
|
38230
39100
|
const events = [];
|
|
38231
39101
|
for (const line of content.split("\n")) {
|
|
38232
39102
|
if (!line.trim()) continue;
|
|
@@ -38261,7 +39131,7 @@ function isTreeLogEntry(value) {
|
|
|
38261
39131
|
function loadTreeLog(filePath) {
|
|
38262
39132
|
let raw;
|
|
38263
39133
|
try {
|
|
38264
|
-
raw = fs$
|
|
39134
|
+
raw = fs$1.readFileSync(filePath, "utf-8");
|
|
38265
39135
|
} catch {
|
|
38266
39136
|
return null;
|
|
38267
39137
|
}
|
|
@@ -38298,8 +39168,8 @@ function loadTreeLog(filePath) {
|
|
|
38298
39168
|
};
|
|
38299
39169
|
}
|
|
38300
39170
|
function appendLine(log, entry) {
|
|
38301
|
-
fs$
|
|
38302
|
-
fs$
|
|
39171
|
+
fs$1.mkdirSync(path$2.dirname(log.filePath), { recursive: true });
|
|
39172
|
+
fs$1.appendFileSync(log.filePath, JSON.stringify(entry) + "\n");
|
|
38303
39173
|
log.entries.push(entry);
|
|
38304
39174
|
if (entry.type !== "head") {
|
|
38305
39175
|
log.byId.set(entry.id, entry);
|
|
@@ -38364,7 +39234,7 @@ function appendArtifact(log, payload) {
|
|
|
38364
39234
|
type: "artifact",
|
|
38365
39235
|
payload: {
|
|
38366
39236
|
...payload,
|
|
38367
|
-
host: payload.host || os$
|
|
39237
|
+
host: payload.host || os$1.hostname()
|
|
38368
39238
|
}
|
|
38369
39239
|
};
|
|
38370
39240
|
appendLine(log, entry);
|
|
@@ -41440,7 +42310,7 @@ function MarathonApp({ taskName, agentId, model, sandbox, goal, initialSessionCo
|
|
|
41440
42310
|
setShowSessionMenu(false);
|
|
41441
42311
|
try {
|
|
41442
42312
|
let json;
|
|
41443
|
-
if (stateFilePath && fs$
|
|
42313
|
+
if (stateFilePath && fs$1.existsSync(stateFilePath)) json = fs$1.readFileSync(stateFilePath, "utf-8");
|
|
41444
42314
|
else json = JSON.stringify({
|
|
41445
42315
|
sessionSnapshots,
|
|
41446
42316
|
content: state.content,
|
|
@@ -41463,8 +42333,8 @@ function MarathonApp({ taskName, agentId, model, sandbox, goal, initialSessionCo
|
|
|
41463
42333
|
setShowSessionMenu(false);
|
|
41464
42334
|
try {
|
|
41465
42335
|
let json;
|
|
41466
|
-
if (stateFilePath && fs$
|
|
41467
|
-
const raw = fs$
|
|
42336
|
+
if (stateFilePath && fs$1.existsSync(stateFilePath)) {
|
|
42337
|
+
const raw = fs$1.readFileSync(stateFilePath, "utf-8");
|
|
41468
42338
|
const parsed = JSON.parse(raw);
|
|
41469
42339
|
json = JSON.stringify(compactMarathonStateJsonForContextExport(parsed), null, 2);
|
|
41470
42340
|
} else {
|
|
@@ -41490,7 +42360,7 @@ function MarathonApp({ taskName, agentId, model, sandbox, goal, initialSessionCo
|
|
|
41490
42360
|
]);
|
|
41491
42361
|
const handleOpenStateFile = useCallback(() => {
|
|
41492
42362
|
setShowSessionMenu(false);
|
|
41493
|
-
if (stateFilePath && fs$
|
|
42363
|
+
if (stateFilePath && fs$1.existsSync(stateFilePath)) {
|
|
41494
42364
|
open(stateFilePath);
|
|
41495
42365
|
showFlash("Opening state file...");
|
|
41496
42366
|
} else showFlash("No state file yet");
|
|
@@ -41885,7 +42755,7 @@ function MarathonApp({ taskName, agentId, model, sandbox, goal, initialSessionCo
|
|
|
41885
42755
|
const file = detailFile ?? trackedFiles[fileCursor];
|
|
41886
42756
|
if (file) {
|
|
41887
42757
|
const filePath = path$2.resolve(file.path);
|
|
41888
|
-
if (fs$
|
|
42758
|
+
if (fs$1.existsSync(filePath)) {
|
|
41889
42759
|
open(filePath);
|
|
41890
42760
|
showFlash(`Opening ${file.path}`);
|
|
41891
42761
|
} else showFlash("File not found on disk");
|
|
@@ -42400,7 +43270,7 @@ function MarathonApp({ taskName, agentId, model, sandbox, goal, initialSessionCo
|
|
|
42400
43270
|
onOpenDashboard: handleOpenDashboard,
|
|
42401
43271
|
onClose: () => setShowSessionMenu(false),
|
|
42402
43272
|
hasDashboard: !!agentPageUrl,
|
|
42403
|
-
hasStateFile: !!stateFilePath && fs$
|
|
43273
|
+
hasStateFile: !!stateFilePath && fs$1.existsSync(stateFilePath)
|
|
42404
43274
|
})
|
|
42405
43275
|
}),
|
|
42406
43276
|
showUpgradeModal && upgradePrompt && /* @__PURE__ */ jsx(Box, {
|
|
@@ -42941,28 +43811,6 @@ function setMarathonTitle() {
|
|
|
42941
43811
|
setTerminalTitle(`Runtype: Marathon (${getFolderName()})`);
|
|
42942
43812
|
}
|
|
42943
43813
|
//#endregion
|
|
42944
|
-
//#region src/config/paths.ts
|
|
42945
|
-
/** Root state directory: RUNTYPE_STATE_DIR or ~/.runtype */
|
|
42946
|
-
function getRuntypeHomeDir() {
|
|
42947
|
-
return process.env.RUNTYPE_STATE_DIR || path$2.join(os$2.homedir(), ".runtype");
|
|
42948
|
-
}
|
|
42949
|
-
/** Per-project state dir: ~/.runtype/projects/{hash}/ */
|
|
42950
|
-
function getProjectStateDir(projectDir) {
|
|
42951
|
-
const dir = projectDir || process.cwd();
|
|
42952
|
-
const hash = crypto$2.createHash("sha256").update(dir).digest("hex").slice(0, 12);
|
|
42953
|
-
const stateDir = path$2.join(getRuntypeHomeDir(), "projects", hash);
|
|
42954
|
-
const breadcrumb = path$2.join(stateDir, "project-path.txt");
|
|
42955
|
-
if (!fs$2.existsSync(breadcrumb)) {
|
|
42956
|
-
fs$2.mkdirSync(stateDir, { recursive: true });
|
|
42957
|
-
fs$2.writeFileSync(breadcrumb, dir);
|
|
42958
|
-
}
|
|
42959
|
-
return stateDir;
|
|
42960
|
-
}
|
|
42961
|
-
/** Marathon state dir: ~/.runtype/projects/{hash}/marathons/ */
|
|
42962
|
-
function getMarathonStateDir(projectDir) {
|
|
42963
|
-
return path$2.join(getProjectStateDir(projectDir), "marathons");
|
|
42964
|
-
}
|
|
42965
|
-
//#endregion
|
|
42966
43814
|
//#region src/marathon/checkpoint.ts
|
|
42967
43815
|
function defaultStateDir$2() {
|
|
42968
43816
|
return getMarathonStateDir();
|
|
@@ -42983,14 +43831,14 @@ function ensureMarathonFileCheckpoint(taskName, targetPath, stateDir, stateFileP
|
|
|
42983
43831
|
const normalizedTargetPath = path$2.resolve(targetPath);
|
|
42984
43832
|
const relativeTargetPath = path$2.relative(process.cwd(), normalizedTargetPath);
|
|
42985
43833
|
if (!relativeTargetPath || relativeTargetPath.startsWith("..")) return void 0;
|
|
42986
|
-
if (!fs$
|
|
42987
|
-
if (!fs$
|
|
43834
|
+
if (!fs$1.existsSync(normalizedTargetPath)) return void 0;
|
|
43835
|
+
if (!fs$1.statSync(normalizedTargetPath).isFile()) return void 0;
|
|
42988
43836
|
if (relativeTargetPath.replace(/\\/g, "/").startsWith(".runtype/")) return void 0;
|
|
42989
43837
|
if (normalizedTargetPath.startsWith(getRuntypeHomeDir() + path$2.sep)) return void 0;
|
|
42990
43838
|
const checkpointPath = resolveMarathonCheckpointPath(taskName, normalizedTargetPath, stateDir, stateFilePath);
|
|
42991
|
-
if (fs$
|
|
42992
|
-
fs$
|
|
42993
|
-
fs$
|
|
43839
|
+
if (fs$1.existsSync(checkpointPath)) return checkpointPath;
|
|
43840
|
+
fs$1.mkdirSync(path$2.dirname(checkpointPath), { recursive: true });
|
|
43841
|
+
fs$1.copyFileSync(normalizedTargetPath, checkpointPath);
|
|
42994
43842
|
return checkpointPath;
|
|
42995
43843
|
}
|
|
42996
43844
|
function restoreMarathonFileCheckpoint(taskName, targetPath, stateDir, stateFilePath) {
|
|
@@ -43001,13 +43849,13 @@ function restoreMarathonFileCheckpoint(taskName, targetPath, stateDir, stateFile
|
|
|
43001
43849
|
error: `Cannot restore checkpoint outside the current repository: ${normalizedTargetPath}`
|
|
43002
43850
|
};
|
|
43003
43851
|
const checkpointPath = resolveMarathonCheckpointPath(taskName, normalizedTargetPath, stateDir, stateFilePath);
|
|
43004
|
-
if (!fs$
|
|
43852
|
+
if (!fs$1.existsSync(checkpointPath)) return {
|
|
43005
43853
|
restored: false,
|
|
43006
43854
|
checkpointPath,
|
|
43007
43855
|
error: `No checkpoint found for ${normalizedTargetPath}`
|
|
43008
43856
|
};
|
|
43009
|
-
fs$
|
|
43010
|
-
fs$
|
|
43857
|
+
fs$1.mkdirSync(path$2.dirname(normalizedTargetPath), { recursive: true });
|
|
43858
|
+
fs$1.copyFileSync(checkpointPath, normalizedTargetPath);
|
|
43011
43859
|
return {
|
|
43012
43860
|
restored: true,
|
|
43013
43861
|
checkpointPath
|
|
@@ -43537,7 +44385,7 @@ function normalizeMarathonStatePath(candidatePath) {
|
|
|
43537
44385
|
}
|
|
43538
44386
|
function marathonStatePathExists(candidatePath) {
|
|
43539
44387
|
if (!candidatePath) return false;
|
|
43540
|
-
return fs$
|
|
44388
|
+
return fs$1.existsSync(path$2.resolve(candidatePath));
|
|
43541
44389
|
}
|
|
43542
44390
|
function isMarathonArtifactStatePath(candidatePath) {
|
|
43543
44391
|
if (!candidatePath) return false;
|
|
@@ -43698,7 +44546,7 @@ function sanitizeLoadedMarathonState(state) {
|
|
|
43698
44546
|
}
|
|
43699
44547
|
function loadState(filePath) {
|
|
43700
44548
|
try {
|
|
43701
|
-
const raw = fs$
|
|
44549
|
+
const raw = fs$1.readFileSync(filePath, "utf-8");
|
|
43702
44550
|
return sanitizeLoadedMarathonState(JSON.parse(raw));
|
|
43703
44551
|
} catch {
|
|
43704
44552
|
return null;
|
|
@@ -43706,7 +44554,7 @@ function loadState(filePath) {
|
|
|
43706
44554
|
}
|
|
43707
44555
|
function saveState(filePath, state, options) {
|
|
43708
44556
|
const dir = path$2.dirname(filePath);
|
|
43709
|
-
fs$
|
|
44557
|
+
fs$1.mkdirSync(dir, { recursive: true });
|
|
43710
44558
|
const stateToWrite = options?.stripSnapshotEvents && state.sessionSnapshots?.length ? {
|
|
43711
44559
|
...state,
|
|
43712
44560
|
sessionSnapshots: state.sessionSnapshots.map((snapshot) => ({
|
|
@@ -43714,7 +44562,7 @@ function saveState(filePath, state, options) {
|
|
|
43714
44562
|
rawEvents: []
|
|
43715
44563
|
}))
|
|
43716
44564
|
} : state;
|
|
43717
|
-
fs$
|
|
44565
|
+
fs$1.writeFileSync(filePath, JSON.stringify(stateToWrite, null, 2));
|
|
43718
44566
|
syncTreeLogWithState(filePath, state);
|
|
43719
44567
|
}
|
|
43720
44568
|
function extractRunTaskResumeState(state) {
|
|
@@ -43760,11 +44608,11 @@ function offsetNewContextCompactionSummaries(summaries, existingSummaryIds, sess
|
|
|
43760
44608
|
function findStateFile(name, stateDir) {
|
|
43761
44609
|
if (stateDir) return stateFilePath(name, stateDir);
|
|
43762
44610
|
const homePath = stateFilePath(name, getMarathonStateDir());
|
|
43763
|
-
if (fs$
|
|
44611
|
+
if (fs$1.existsSync(homePath)) return homePath;
|
|
43764
44612
|
const oldMarathonPath = stateFilePath(name, path$2.join(process.cwd(), ".runtype", "marathons"));
|
|
43765
|
-
if (fs$
|
|
44613
|
+
if (fs$1.existsSync(oldMarathonPath)) return oldMarathonPath;
|
|
43766
44614
|
const oldTaskPath = stateFilePath(name, path$2.join(process.cwd(), ".runtype", "tasks"));
|
|
43767
|
-
if (fs$
|
|
44615
|
+
if (fs$1.existsSync(oldTaskPath)) return oldTaskPath;
|
|
43768
44616
|
return homePath;
|
|
43769
44617
|
}
|
|
43770
44618
|
function findLatestStateFile(stateDir) {
|
|
@@ -43775,11 +44623,11 @@ function findLatestStateFile(stateDir) {
|
|
|
43775
44623
|
];
|
|
43776
44624
|
let latest = null;
|
|
43777
44625
|
for (const dir of dirs) {
|
|
43778
|
-
if (!fs$
|
|
43779
|
-
const files = fs$
|
|
44626
|
+
if (!fs$1.existsSync(dir)) continue;
|
|
44627
|
+
const files = fs$1.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
43780
44628
|
for (const file of files) {
|
|
43781
44629
|
const fullPath = path$2.join(dir, file);
|
|
43782
|
-
const stat = fs$
|
|
44630
|
+
const stat = fs$1.statSync(fullPath);
|
|
43783
44631
|
if (!latest || stat.mtimeMs > latest.mtime) latest = {
|
|
43784
44632
|
name: file.replace(".json", ""),
|
|
43785
44633
|
filePath: fullPath,
|
|
@@ -43863,7 +44711,7 @@ async function resolveMarathonStateResolution(options, taskName, filePath, promp
|
|
|
43863
44711
|
};
|
|
43864
44712
|
}
|
|
43865
44713
|
if (options.fresh) return { action: "fresh" };
|
|
43866
|
-
if (!fs$
|
|
44714
|
+
if (!fs$1.existsSync(filePath)) return { action: "fresh" };
|
|
43867
44715
|
if (!allowPrompt) throw new Error(`Local marathon state already exists at ${filePath}. Use --resume to continue it or --fresh to start over.`);
|
|
43868
44716
|
const choice = await promptForChoice({
|
|
43869
44717
|
reason: "existing-state",
|
|
@@ -44143,9 +44991,9 @@ function shouldIgnoreRepoEntry(entryPath) {
|
|
|
44143
44991
|
function safeReadTextFile(filePath) {
|
|
44144
44992
|
try {
|
|
44145
44993
|
if (isSensitivePath(normalizeToolPath(filePath).replace(/\\/g, "/"))) return null;
|
|
44146
|
-
const stat = fs$
|
|
44994
|
+
const stat = fs$1.statSync(filePath);
|
|
44147
44995
|
if (!stat.isFile() || stat.size > 1048576) return null;
|
|
44148
|
-
const buffer = fs$
|
|
44996
|
+
const buffer = fs$1.readFileSync(filePath);
|
|
44149
44997
|
if (buffer.includes(0)) return null;
|
|
44150
44998
|
return buffer.toString("utf-8");
|
|
44151
44999
|
} catch {
|
|
@@ -44153,14 +45001,14 @@ function safeReadTextFile(filePath) {
|
|
|
44153
45001
|
}
|
|
44154
45002
|
}
|
|
44155
45003
|
function walkRepo(startPath, visitor) {
|
|
44156
|
-
if (!fs$
|
|
45004
|
+
if (!fs$1.existsSync(startPath) || !fs$1.statSync(startPath).isDirectory()) return;
|
|
44157
45005
|
const stack = [startPath];
|
|
44158
45006
|
while (stack.length > 0) {
|
|
44159
45007
|
const currentDir = stack.pop();
|
|
44160
45008
|
if (!currentDir) continue;
|
|
44161
45009
|
let entries;
|
|
44162
45010
|
try {
|
|
44163
|
-
entries = fs$
|
|
45011
|
+
entries = fs$1.readdirSync(currentDir, { withFileTypes: true });
|
|
44164
45012
|
} catch {
|
|
44165
45013
|
continue;
|
|
44166
45014
|
}
|
|
@@ -44200,7 +45048,7 @@ function buildTree(dirPath, maxDepth, depth = 0) {
|
|
|
44200
45048
|
if (depth > maxDepth || shouldIgnoreRepoEntry(dirPath)) return [];
|
|
44201
45049
|
let entries;
|
|
44202
45050
|
try {
|
|
44203
|
-
entries = fs$
|
|
45051
|
+
entries = fs$1.readdirSync(dirPath, { withFileTypes: true });
|
|
44204
45052
|
} catch (error) {
|
|
44205
45053
|
const message = error instanceof Error ? error.message : String(error);
|
|
44206
45054
|
return [`${" ".repeat(depth)}[error reading ${normalizeToolPath(dirPath)}: ${message}]`];
|
|
@@ -44237,8 +45085,8 @@ function resolveCanonicalToolPath(toolPath, allowMissing) {
|
|
|
44237
45085
|
let remainingSegments = absolutePath.slice(canonicalPath.length).split(path$2.sep);
|
|
44238
45086
|
let symlinksFollowed = 0;
|
|
44239
45087
|
try {
|
|
44240
|
-
if (fs$
|
|
44241
|
-
canonicalPath: fs$
|
|
45088
|
+
if (fs$1.existsSync(absolutePath)) return {
|
|
45089
|
+
canonicalPath: fs$1.realpathSync.native(absolutePath),
|
|
44242
45090
|
exists: true
|
|
44243
45091
|
};
|
|
44244
45092
|
if (!allowMissing) return null;
|
|
@@ -44250,25 +45098,25 @@ function resolveCanonicalToolPath(toolPath, allowMissing) {
|
|
|
44250
45098
|
continue;
|
|
44251
45099
|
}
|
|
44252
45100
|
const candidatePath = path$2.join(canonicalPath, segment);
|
|
44253
|
-
const entry = fs$
|
|
45101
|
+
const entry = fs$1.lstatSync(candidatePath, { throwIfNoEntry: false });
|
|
44254
45102
|
if (!entry) {
|
|
44255
45103
|
canonicalPath = candidatePath;
|
|
44256
45104
|
continue;
|
|
44257
45105
|
}
|
|
44258
45106
|
if (entry.isSymbolicLink()) {
|
|
44259
45107
|
if (++symlinksFollowed > MAX_TOOL_PATH_SYMLINKS) return null;
|
|
44260
|
-
const target = fs$
|
|
45108
|
+
const target = fs$1.readlinkSync(candidatePath);
|
|
44261
45109
|
const targetRoot = path$2.isAbsolute(target) ? path$2.parse(target).root : "";
|
|
44262
45110
|
if (targetRoot) canonicalPath = targetRoot;
|
|
44263
45111
|
remainingSegments = [...target.slice(targetRoot.length).split(path$2.sep), ...remainingSegments];
|
|
44264
45112
|
continue;
|
|
44265
45113
|
}
|
|
44266
45114
|
if (remainingSegments.length > 0 && !entry.isDirectory()) return null;
|
|
44267
|
-
canonicalPath = fs$
|
|
45115
|
+
canonicalPath = fs$1.realpathSync.native(candidatePath);
|
|
44268
45116
|
}
|
|
44269
45117
|
return {
|
|
44270
45118
|
canonicalPath,
|
|
44271
|
-
exists: fs$
|
|
45119
|
+
exists: fs$1.existsSync(canonicalPath)
|
|
44272
45120
|
};
|
|
44273
45121
|
} catch {
|
|
44274
45122
|
return null;
|
|
@@ -44294,7 +45142,7 @@ function resolveToolPath(toolPath, options = {}) {
|
|
|
44294
45142
|
ok: false,
|
|
44295
45143
|
error: `Path does not exist: ${requestedPath}`
|
|
44296
45144
|
};
|
|
44297
|
-
const workspaceRoot = fs$
|
|
45145
|
+
const workspaceRoot = fs$1.realpathSync.native(process.cwd());
|
|
44298
45146
|
const extraRoots = (options.allowedRoots || []).map((rootPath) => canonicalizeAllowedRoot(rootPath));
|
|
44299
45147
|
const matchedRoot = [...extraRoots, workspaceRoot].find((rootPath) => isPathWithinRoot(resolved.canonicalPath, rootPath));
|
|
44300
45148
|
if (!matchedRoot) return {
|
|
@@ -44315,7 +45163,7 @@ function resolveToolPath(toolPath, options = {}) {
|
|
|
44315
45163
|
}
|
|
44316
45164
|
}
|
|
44317
45165
|
if (resolved.exists) {
|
|
44318
|
-
const stat = fs$
|
|
45166
|
+
const stat = fs$1.statSync(resolved.canonicalPath);
|
|
44319
45167
|
if (options.requireFile && !stat.isFile()) return {
|
|
44320
45168
|
ok: false,
|
|
44321
45169
|
error: `Path is not a file: ${requestedPath}`
|
|
@@ -44363,7 +45211,7 @@ function createDefaultLocalTools(context) {
|
|
|
44363
45211
|
allowedRoots: allowedReadRoots
|
|
44364
45212
|
});
|
|
44365
45213
|
if (!resolvedPath.ok) return `Error: ${resolvedPath.error}`;
|
|
44366
|
-
return fs$
|
|
45214
|
+
return fs$1.readFileSync(resolvedPath.resolvedPath, "utf-8");
|
|
44367
45215
|
}
|
|
44368
45216
|
},
|
|
44369
45217
|
write_file: {
|
|
@@ -44386,10 +45234,10 @@ function createDefaultLocalTools(context) {
|
|
|
44386
45234
|
const resolvedPath = resolveToolPath(String(args.path || ""), { allowMissing: true });
|
|
44387
45235
|
if (!resolvedPath.ok) return `Error: ${resolvedPath.error}`;
|
|
44388
45236
|
const content = String(args.content || "");
|
|
44389
|
-
if (fs$
|
|
45237
|
+
if (fs$1.existsSync(resolvedPath.resolvedPath) && fs$1.statSync(resolvedPath.resolvedPath).isDirectory()) return `Error: Path is a directory: ${String(args.path || "")}`;
|
|
44390
45238
|
const dir = path$2.dirname(resolvedPath.resolvedPath);
|
|
44391
|
-
fs$
|
|
44392
|
-
fs$
|
|
45239
|
+
fs$1.mkdirSync(dir, { recursive: true });
|
|
45240
|
+
fs$1.writeFileSync(resolvedPath.resolvedPath, content);
|
|
44393
45241
|
return "ok";
|
|
44394
45242
|
}
|
|
44395
45243
|
},
|
|
@@ -44405,7 +45253,7 @@ function createDefaultLocalTools(context) {
|
|
|
44405
45253
|
execute: async (args) => {
|
|
44406
45254
|
const resolvedPath = resolveToolPath(String(args.path || "."), { requireDirectory: true });
|
|
44407
45255
|
if (!resolvedPath.ok) return `Error: ${resolvedPath.error}`;
|
|
44408
|
-
return fs$
|
|
45256
|
+
return fs$1.readdirSync(resolvedPath.resolvedPath, { withFileTypes: true }).filter((entry) => !shouldIgnoreRepoEntry(path$2.join(resolvedPath.resolvedPath, entry.name))).map((entry) => entry.name).join("\n");
|
|
44409
45257
|
}
|
|
44410
45258
|
},
|
|
44411
45259
|
search_repo: {
|
|
@@ -44569,8 +45417,8 @@ function createCheckpointedWriteFileTool(taskName, stateDir, planPath, stateFile
|
|
|
44569
45417
|
const content = String(args.content || "");
|
|
44570
45418
|
ensureMarathonFileCheckpoint(taskName, resolvedPath.resolvedPath, stateDir, stateFilePath);
|
|
44571
45419
|
const dir = path$2.dirname(resolvedPath.resolvedPath);
|
|
44572
|
-
fs$
|
|
44573
|
-
fs$
|
|
45420
|
+
fs$1.mkdirSync(dir, { recursive: true });
|
|
45421
|
+
fs$1.writeFileSync(resolvedPath.resolvedPath, content);
|
|
44574
45422
|
const ext = path$2.extname(resolvedPath.resolvedPath).toLowerCase();
|
|
44575
45423
|
if (ext === ".js" || ext === ".jsx") {
|
|
44576
45424
|
const syntaxError = quickJsSyntaxCheck(content);
|
|
@@ -44646,7 +45494,7 @@ function createEditFileTool(taskName, stateDir, planPath, stateFilePath) {
|
|
|
44646
45494
|
if (oldString === newString) return "Error: old_string and new_string are identical";
|
|
44647
45495
|
let content;
|
|
44648
45496
|
try {
|
|
44649
|
-
content = fs$
|
|
45497
|
+
content = fs$1.readFileSync(resolvedPath.resolvedPath, "utf-8");
|
|
44650
45498
|
} catch {
|
|
44651
45499
|
return `Error: Could not read file: ${String(args.path || "")}`;
|
|
44652
45500
|
}
|
|
@@ -44657,7 +45505,7 @@ function createEditFileTool(taskName, stateDir, planPath, stateFilePath) {
|
|
|
44657
45505
|
}
|
|
44658
45506
|
ensureMarathonFileCheckpoint(taskName, resolvedPath.resolvedPath, stateDir, stateFilePath);
|
|
44659
45507
|
const updated = replaceAll ? content.split(oldString).join(newString) : content.replace(oldString, newString);
|
|
44660
|
-
fs$
|
|
45508
|
+
fs$1.writeFileSync(resolvedPath.resolvedPath, updated);
|
|
44661
45509
|
const ext = path$2.extname(resolvedPath.resolvedPath).toLowerCase();
|
|
44662
45510
|
if (ext === ".js" || ext === ".jsx") {
|
|
44663
45511
|
const syntaxError = quickJsSyntaxCheck(updated);
|
|
@@ -44709,13 +45557,13 @@ function createReadOffloadedOutputTool(taskName, stateDir, stateFilePath) {
|
|
|
44709
45557
|
};
|
|
44710
45558
|
const idEmbeddedSha256 = /^art_([0-9a-f]{64})$/.exec(outputId)?.[1];
|
|
44711
45559
|
const outputPath = path$2.join(getOffloadedOutputDir(taskName, stateDir, stateFilePath), `${outputId}.txt`);
|
|
44712
|
-
if (!fs$
|
|
45560
|
+
if (!fs$1.existsSync(outputPath) || !fs$1.statSync(outputPath).isFile()) {
|
|
44713
45561
|
const resolvedStateFilePath = getStateFilePathForTask(taskName, stateDir, stateFilePath);
|
|
44714
45562
|
const log = loadTreeLog(treeLogPathForStateFile(resolvedStateFilePath));
|
|
44715
45563
|
const artifact = log ? findArtifactPayload(log, outputId) : void 0;
|
|
44716
45564
|
if (artifact) {
|
|
44717
45565
|
const ledgerPath = path$2.resolve(path$2.dirname(treeLogPathForStateFile(resolvedStateFilePath)), artifact.relativePath);
|
|
44718
|
-
if (fs$
|
|
45566
|
+
if (fs$1.existsSync(ledgerPath) && fs$1.statSync(ledgerPath).isFile()) return verifyContent(fs$1.readFileSync(ledgerPath, "utf-8"), artifact.sha256);
|
|
44719
45567
|
return [
|
|
44720
45568
|
`Error: offloaded output recorded in the context ledger but missing locally: ${outputId}`,
|
|
44721
45569
|
artifact.host ? `Stored on host: ${artifact.host}` : void 0,
|
|
@@ -44724,7 +45572,7 @@ function createReadOffloadedOutputTool(taskName, stateDir, stateFilePath) {
|
|
|
44724
45572
|
}
|
|
44725
45573
|
return `Error: offloaded output not found: ${outputId}`;
|
|
44726
45574
|
}
|
|
44727
|
-
return verifyContent(fs$
|
|
45575
|
+
return verifyContent(fs$1.readFileSync(outputPath, "utf-8"), idEmbeddedSha256);
|
|
44728
45576
|
}
|
|
44729
45577
|
};
|
|
44730
45578
|
}
|
|
@@ -44908,7 +45756,7 @@ function resolveLedgerToolResult(tool, stateFilePath, log) {
|
|
|
44908
45756
|
if (!artifact) return result;
|
|
44909
45757
|
const artifactPath = path$2.resolve(path$2.dirname(treeLogPathForStateFile(stateFilePath)), artifact.relativePath);
|
|
44910
45758
|
try {
|
|
44911
|
-
if (fs$
|
|
45759
|
+
if (fs$1.existsSync(artifactPath) && fs$1.statSync(artifactPath).isFile()) return fs$1.readFileSync(artifactPath, "utf-8");
|
|
44912
45760
|
} catch {}
|
|
44913
45761
|
return result;
|
|
44914
45762
|
}
|
|
@@ -45122,8 +45970,8 @@ function offloadToolOutput(taskName, toolName, output, options = {}, stateDir) {
|
|
|
45122
45970
|
const dir = artifactStoreDirForStateFile(stateFilePath);
|
|
45123
45971
|
const fileName = `${outputId}.txt`;
|
|
45124
45972
|
const filePath = path$2.join(dir, fileName);
|
|
45125
|
-
fs$
|
|
45126
|
-
if (!fs$
|
|
45973
|
+
fs$1.mkdirSync(dir, { recursive: true });
|
|
45974
|
+
if (!fs$1.existsSync(filePath)) fs$1.writeFileSync(filePath, output, "utf-8");
|
|
45127
45975
|
recordOffloadedArtifact({
|
|
45128
45976
|
taskName,
|
|
45129
45977
|
toolName,
|
|
@@ -45273,14 +46121,14 @@ const RULES_DIR = ".marathon/rules";
|
|
|
45273
46121
|
function loadRules(cwd) {
|
|
45274
46122
|
const baseCwd = cwd || process.cwd();
|
|
45275
46123
|
const rulesDir = path$2.resolve(baseCwd, RULES_DIR);
|
|
45276
|
-
if (!fs$
|
|
46124
|
+
if (!fs$1.existsSync(rulesDir)) return [];
|
|
45277
46125
|
const rules = [];
|
|
45278
46126
|
try {
|
|
45279
|
-
const entries = fs$
|
|
46127
|
+
const entries = fs$1.readdirSync(rulesDir).filter((f) => f.endsWith(".md"));
|
|
45280
46128
|
for (const entry of entries) {
|
|
45281
46129
|
const filePath = path$2.join(rulesDir, entry);
|
|
45282
46130
|
try {
|
|
45283
|
-
const parsed = parseRuleFile(fs$
|
|
46131
|
+
const parsed = parseRuleFile(fs$1.readFileSync(filePath, "utf-8"));
|
|
45284
46132
|
rules.push({
|
|
45285
46133
|
...parsed,
|
|
45286
46134
|
filePath
|
|
@@ -45416,7 +46264,7 @@ function resolveErrorHandlingForPhase(phase, cliFallbackModel, milestoneFallback
|
|
|
45416
46264
|
//#region src/marathon/playbook-loader.ts
|
|
45417
46265
|
const PLAYBOOKS_DIR = ".runtype/marathons/playbooks";
|
|
45418
46266
|
function getCandidatePaths(nameOrPath, cwd) {
|
|
45419
|
-
const home = os$
|
|
46267
|
+
const home = os$1.homedir();
|
|
45420
46268
|
return [
|
|
45421
46269
|
path$2.resolve(cwd, nameOrPath),
|
|
45422
46270
|
path$2.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.yaml`),
|
|
@@ -45456,7 +46304,7 @@ async function loadModulePlaybook(filePath) {
|
|
|
45456
46304
|
return config;
|
|
45457
46305
|
}
|
|
45458
46306
|
function parsePlaybookFile(filePath) {
|
|
45459
|
-
const raw = fs$
|
|
46307
|
+
const raw = fs$1.readFileSync(filePath, "utf-8");
|
|
45460
46308
|
if (path$2.extname(filePath).toLowerCase() === ".json") return JSON.parse(raw);
|
|
45461
46309
|
return parse(raw);
|
|
45462
46310
|
}
|
|
@@ -45518,7 +46366,7 @@ async function loadPlaybookPlugins(plugins, playbookFilePath, playbookName) {
|
|
|
45518
46366
|
const resolved = path$2.resolve(baseDir, plugin);
|
|
45519
46367
|
if (resolved !== baseDir && !resolved.startsWith(baseDir + path$2.sep)) throw new Error(`Playbook '${playbookName}': plugin "${plugin}" resolves outside the playbook's directory. Keep plugin modules next to the playbook.`);
|
|
45520
46368
|
if (!PLUGIN_EXTENSIONS.has(path$2.extname(resolved).toLowerCase())) throw new Error(`Playbook '${playbookName}': plugin "${plugin}" must be a .js, .mjs, or .cjs module.`);
|
|
45521
|
-
if (!fs$
|
|
46369
|
+
if (!fs$1.existsSync(resolved)) throw new Error(`Playbook '${playbookName}': plugin "${plugin}" not found at ${resolved}.`);
|
|
45522
46370
|
const mod = await import(pathToFileURL(resolved).href);
|
|
45523
46371
|
if (typeof mod.default !== "function") throw new Error(`Playbook '${playbookName}': plugin "${plugin}" must export a default function: export default ({ registerWorkflowHook }) => { ... }`);
|
|
45524
46372
|
await mod.default({ registerWorkflowHook });
|
|
@@ -45551,7 +46399,7 @@ function toWorkflowConfig(config) {
|
|
|
45551
46399
|
async function loadPlaybook(nameOrPath, cwd) {
|
|
45552
46400
|
const candidates = getCandidatePaths(nameOrPath, cwd || process.cwd());
|
|
45553
46401
|
for (const candidate of candidates) {
|
|
45554
|
-
if (!fs$
|
|
46402
|
+
if (!fs$1.existsSync(candidate) || fs$1.statSync(candidate).isDirectory()) continue;
|
|
45555
46403
|
const ext = path$2.extname(candidate).toLowerCase();
|
|
45556
46404
|
const config = MODULE_PLAYBOOK_EXTENSIONS.has(ext) ? await loadModulePlaybook(candidate) : parsePlaybookFile(candidate);
|
|
45557
46405
|
validatePlaybook(config, candidate);
|
|
@@ -45943,7 +46791,7 @@ async function taskAction(agent, options) {
|
|
|
45943
46791
|
forcedResumeFilePath = stateResolution.filePath;
|
|
45944
46792
|
} else {
|
|
45945
46793
|
resumeRequested = false;
|
|
45946
|
-
if (options.fresh && fs$
|
|
46794
|
+
if (options.fresh && fs$1.existsSync(filePath)) {
|
|
45947
46795
|
if (useStartupShell) setStartupStatus("starting fresh");
|
|
45948
46796
|
else console.log(chalk.gray(`Starting fresh and ignoring saved local state at ${filePath}`));
|
|
45949
46797
|
}
|
|
@@ -48777,17 +49625,17 @@ const deployCommand = new Command("deploy").description("Export an agent and sca
|
|
|
48777
49625
|
}
|
|
48778
49626
|
slugSet.add(slug);
|
|
48779
49627
|
}
|
|
48780
|
-
fs$
|
|
48781
|
-
fs$
|
|
49628
|
+
fs$1.mkdirSync(path$2.join(outDir, "agents"), { recursive: true });
|
|
49629
|
+
fs$1.mkdirSync(path$2.join(outDir, "packages"), { recursive: true });
|
|
48782
49630
|
for (const { name, def } of agentDefs) {
|
|
48783
49631
|
const filename = `${slugify(name)}.json`;
|
|
48784
|
-
fs$
|
|
49632
|
+
fs$1.writeFileSync(path$2.join(outDir, "agents", filename), JSON.stringify(def, null, 2));
|
|
48785
49633
|
console.log(` Wrote agents/${filename}`);
|
|
48786
49634
|
}
|
|
48787
49635
|
const agentNames = agentDefs.map((a) => a.name);
|
|
48788
49636
|
let monorepoRoot = process.cwd();
|
|
48789
49637
|
for (let i = 0; i < 8; i++) {
|
|
48790
|
-
if (fs$
|
|
49638
|
+
if (fs$1.existsSync(path$2.join(monorepoRoot, "pnpm-workspace.yaml"))) break;
|
|
48791
49639
|
const parent = path$2.dirname(monorepoRoot);
|
|
48792
49640
|
if (parent === monorepoRoot) break;
|
|
48793
49641
|
monorepoRoot = parent;
|
|
@@ -48803,35 +49651,35 @@ const deployCommand = new Command("deploy").description("Export an agent and sca
|
|
|
48803
49651
|
const secretNames = Array.from(allSecrets);
|
|
48804
49652
|
const optionalSettings = Array.from(allOptionalSettings.values());
|
|
48805
49653
|
if (target === "cloudflare") {
|
|
48806
|
-
fs$
|
|
48807
|
-
fs$
|
|
48808
|
-
fs$
|
|
48809
|
-
fs$
|
|
48810
|
-
fs$
|
|
49654
|
+
fs$1.mkdirSync(path$2.join(outDir, "src"), { recursive: true });
|
|
49655
|
+
fs$1.writeFileSync(path$2.join(outDir, "src", "index.ts"), workerIndexTs(agentSlugs));
|
|
49656
|
+
fs$1.writeFileSync(path$2.join(outDir, "wrangler.toml"), wranglerToml(projectName));
|
|
49657
|
+
fs$1.writeFileSync(path$2.join(outDir, "package.json"), workerPackageJson(projectName, "workspace:*"));
|
|
49658
|
+
fs$1.writeFileSync(path$2.join(outDir, "tsconfig.json"), workerTsconfigJson());
|
|
48811
49659
|
const setupPath = path$2.join(outDir, "setup.sh");
|
|
48812
|
-
fs$
|
|
48813
|
-
fs$
|
|
48814
|
-
fs$
|
|
49660
|
+
fs$1.writeFileSync(setupPath, workerSetupSh(monorepoRoot));
|
|
49661
|
+
fs$1.chmodSync(setupPath, 493);
|
|
49662
|
+
fs$1.writeFileSync(path$2.join(outDir, "README.md"), workerReadme(agentNames, secretNames, optionalSettings));
|
|
48815
49663
|
} else if (target === "vercel") {
|
|
48816
|
-
fs$
|
|
48817
|
-
fs$
|
|
48818
|
-
fs$
|
|
48819
|
-
fs$
|
|
48820
|
-
fs$
|
|
49664
|
+
fs$1.mkdirSync(path$2.join(outDir, "api"), { recursive: true });
|
|
49665
|
+
fs$1.writeFileSync(path$2.join(outDir, "api", "[[...route]].ts"), vercelRouteTs(agentSlugs));
|
|
49666
|
+
fs$1.writeFileSync(path$2.join(outDir, "vercel.json"), vercelJson());
|
|
49667
|
+
fs$1.writeFileSync(path$2.join(outDir, "package.json"), vercelPackageJson(projectName, "workspace:*"));
|
|
49668
|
+
fs$1.writeFileSync(path$2.join(outDir, "tsconfig.json"), vercelTsconfigJson());
|
|
48821
49669
|
const setupPath = path$2.join(outDir, "setup.sh");
|
|
48822
|
-
fs$
|
|
48823
|
-
fs$
|
|
48824
|
-
fs$
|
|
49670
|
+
fs$1.writeFileSync(setupPath, vercelSetupSh(monorepoRoot));
|
|
49671
|
+
fs$1.chmodSync(setupPath, 493);
|
|
49672
|
+
fs$1.writeFileSync(path$2.join(outDir, "README.md"), vercelReadme(agentNames, secretNames, optionalSettings));
|
|
48825
49673
|
} else {
|
|
48826
|
-
fs$
|
|
48827
|
-
fs$
|
|
48828
|
-
fs$
|
|
48829
|
-
fs$
|
|
48830
|
-
fs$
|
|
49674
|
+
fs$1.writeFileSync(path$2.join(outDir, "server.ts"), serverTs());
|
|
49675
|
+
fs$1.writeFileSync(path$2.join(outDir, "Dockerfile"), dockerfile());
|
|
49676
|
+
fs$1.writeFileSync(path$2.join(outDir, ".dockerignore"), dockerignore());
|
|
49677
|
+
fs$1.writeFileSync(path$2.join(outDir, "tsconfig.json"), tsconfigJson());
|
|
49678
|
+
fs$1.writeFileSync(path$2.join(outDir, "package.json"), packageJson(projectName, "workspace:*"));
|
|
48831
49679
|
const setupPath = path$2.join(outDir, "setup.sh");
|
|
48832
|
-
fs$
|
|
48833
|
-
fs$
|
|
48834
|
-
fs$
|
|
49680
|
+
fs$1.writeFileSync(setupPath, setupSh(monorepoRoot));
|
|
49681
|
+
fs$1.chmodSync(setupPath, 493);
|
|
49682
|
+
fs$1.writeFileSync(path$2.join(outDir, "README.md"), readme(agentNames, secretNames, optionalSettings));
|
|
48835
49683
|
}
|
|
48836
49684
|
console.log("");
|
|
48837
49685
|
console.log(chalk.green(`✓ Scaffold written to ${outDir}`));
|
|
@@ -48900,13 +49748,13 @@ const agentExportRuntimeCommand = new Command("export-runtime").description("Exp
|
|
|
48900
49748
|
if (options.format === "json") {
|
|
48901
49749
|
const exported = await client.agents.exportRuntime(id, selector.params);
|
|
48902
49750
|
const body = `${JSON.stringify(exported, null, 2)}\n`;
|
|
48903
|
-
if (options.output) fs$
|
|
49751
|
+
if (options.output) fs$1.writeFileSync(path$2.resolve(options.output), body);
|
|
48904
49752
|
else process.stdout.write(body);
|
|
48905
49753
|
return;
|
|
48906
49754
|
}
|
|
48907
49755
|
const bundle = await client.agents.exportRuntimeBundle(id, selector.params);
|
|
48908
49756
|
const outPath = path$2.resolve(options.output ?? bundle.fileName);
|
|
48909
|
-
fs$
|
|
49757
|
+
fs$1.writeFileSync(outPath, bundle.bytes);
|
|
48910
49758
|
console.log(JSON.stringify({
|
|
48911
49759
|
path: outPath,
|
|
48912
49760
|
bytes: bundle.bytes.byteLength,
|
|
@@ -56436,7 +57284,7 @@ npm install -g @runtypelabs/cli
|
|
|
56436
57284
|
runtype auth status
|
|
56437
57285
|
\`\`\`
|
|
56438
57286
|
|
|
56439
|
-
Reuse an installed CLI rather than reinstalling it. If status reports \`authenticated\` with \`accountState: "full"\`, skip to step 3. If it reports a pending signup, follow its \`next\` command instead of registering again. If \`RUNTYPE_API_KEY\` is configured, verify it with \`runtype auth whoami --no-tty\` and reuse that account before following stored signup state: \`auth status\` only inspects stored credentials. Never print the key.
|
|
57287
|
+
Reuse an installed CLI rather than reinstalling it. If status reports \`authenticated\` with \`accountState: "full"\`, skip to step 3. If it reports a pending signup, follow its \`next\` command instead of registering again. If it reports \`unauthenticated\` with a non-null \`requestedOrgId\`, this directory is linked to an organization (\`.runtype/project.json\`, \`--org\`, or \`RUNTYPE_ORG\`) that has no stored login: do not register a new account. Have the user privately run \`runtype auth login --api-key <key>\` with a key from that organization (or \`runtype login\` in their own terminal), then verify with \`runtype auth whoami --no-tty\`. If the link looks wrong, confirm with the user before \`runtype unlink\`. If \`RUNTYPE_API_KEY\` is configured, verify it with \`runtype auth whoami --no-tty\` and reuse that account before following stored signup state: \`auth status\` only inspects stored credentials. Never print the key.
|
|
56440
57288
|
|
|
56441
57289
|
### 2. Sign the user up from the terminal
|
|
56442
57290
|
|
|
@@ -56525,7 +57373,9 @@ The signup commands wrap the agent auth protocol documented at \`https://runtype
|
|
|
56525
57373
|
- \`runtype auth claim <email>\` — re-send or change the email for a pending signup
|
|
56526
57374
|
- \`runtype auth verify <code>\` — finish signup with the 6-digit code
|
|
56527
57375
|
- \`runtype auth login --api-key <key>\` — privately store an existing credential; not an account-creation command
|
|
56528
|
-
- \`runtype auth status\` — inspect
|
|
57376
|
+
- \`runtype auth status\` — inspect the login this directory uses, linked-org state, and pending signup state
|
|
57377
|
+
- \`runtype auth accounts\` — list stored logins (one per organization)
|
|
57378
|
+
- \`runtype link <orgId>\` — pin the current project to an organization (writes \`.runtype/project.json\`, no secrets)
|
|
56529
57379
|
- \`runtype auth whoami --no-tty\` — verify the effective credential (environment key first, then stored login)
|
|
56530
57380
|
|
|
56531
57381
|
A user-supplied \`RUNTYPE_API_KEY\` environment variable also authenticates API-backed CLI commands without a stored login; \`auth status\` still describes only the stored state. Never print a credential into the transcript or write one into a committed file.
|
|
@@ -58293,7 +59143,8 @@ const FORWARDED_REQUEST_HEADERS = /* @__PURE__ */ new Set([
|
|
|
58293
59143
|
"authorization",
|
|
58294
59144
|
"a2a-version",
|
|
58295
59145
|
"content-type",
|
|
58296
|
-
"x-api-key"
|
|
59146
|
+
"x-api-key",
|
|
59147
|
+
"x-runtype-identity"
|
|
58297
59148
|
]);
|
|
58298
59149
|
const BLOCKED_AUTH_HEADERS = /* @__PURE__ */ new Set([
|
|
58299
59150
|
"host",
|
|
@@ -58396,7 +59247,7 @@ function readIdentity(value) {
|
|
|
58396
59247
|
function resolveDeviceListenName(requested) {
|
|
58397
59248
|
const explicit = requested?.trim();
|
|
58398
59249
|
if (explicit) return explicit.slice(0, 120);
|
|
58399
|
-
const hostname = os
|
|
59250
|
+
const hostname = os.hostname().trim();
|
|
58400
59251
|
if (!hostname) return "This computer";
|
|
58401
59252
|
return hostname.slice(0, 120);
|
|
58402
59253
|
}
|
|
@@ -58416,8 +59267,8 @@ async function signDeviceListenChallenge(identity, input) {
|
|
|
58416
59267
|
});
|
|
58417
59268
|
}
|
|
58418
59269
|
function deviceListenEncryptionKey() {
|
|
58419
|
-
const hostname = os
|
|
58420
|
-
const username = os
|
|
59270
|
+
const hostname = os.hostname();
|
|
59271
|
+
const username = os.userInfo().username;
|
|
58421
59272
|
return createHash("sha256").update(`runtype-device-listen-${hostname}-${username}`).digest("hex");
|
|
58422
59273
|
}
|
|
58423
59274
|
function isCorruptStoreError(error) {
|
|
@@ -58971,8 +59822,8 @@ var MacOSDeviceProvider = class {
|
|
|
58971
59822
|
constructor(options = {}) {
|
|
58972
59823
|
this.platform = options.platform ?? process.platform;
|
|
58973
59824
|
this.architecture = options.architecture ?? process.arch;
|
|
58974
|
-
this.operatingSystemRelease = options.operatingSystemRelease ?? os
|
|
58975
|
-
this.homeDirectory = options.homeDirectory ?? os
|
|
59825
|
+
this.operatingSystemRelease = options.operatingSystemRelease ?? os.release();
|
|
59826
|
+
this.homeDirectory = options.homeDirectory ?? os.homedir();
|
|
58976
59827
|
this.workingDirectory = options.workingDirectory ?? process.cwd();
|
|
58977
59828
|
this.environment = options.environment ?? process.env;
|
|
58978
59829
|
this.sidecarTimeoutMs = options.sidecarTimeoutMs ?? DEFAULT_SIDECAR_TIMEOUT_MS;
|
|
@@ -59674,6 +60525,7 @@ function registerGlobalOptionForwarding(program) {
|
|
|
59674
60525
|
const options = actionCommand.optsWithGlobals();
|
|
59675
60526
|
const invocation = {
|
|
59676
60527
|
apiUrl: apiUrls.at(-1),
|
|
60528
|
+
org: typeof options.org === "string" && options.org ? options.org : void 0,
|
|
59677
60529
|
expectApiUrl: options.expectApiUrl,
|
|
59678
60530
|
expectOrg: options.expectOrg,
|
|
59679
60531
|
noInput: options.input === false || options.noInput === true,
|
|
@@ -59699,10 +60551,12 @@ function registerGlobalOptionForwarding(program) {
|
|
|
59699
60551
|
//#region src/index.ts
|
|
59700
60552
|
loadEnv();
|
|
59701
60553
|
const program = new Command();
|
|
59702
|
-
program.name("runtype").description("CLI for Runtype AI Platform").version(getCliVersion()).option("-v, --verbose", "Enable verbose output").option("--api-url <url>", "API URL (flag > env > saved config > credential endpoint > default)").option("--expect-api-url <url>", "Refuse actions unless the effective API URL matches").option("--expect-org <id>", "Require authenticated organization ID before API actions").option("--no-input", "Disable prompts; destructive delete/revoke/reset requires --yes").option("-y, --yes", "Explicitly consent to destructive commands in --no-input mode").option("--json", "Output as JSON").action(() => program.outputHelp());
|
|
60554
|
+
program.name("runtype").description("CLI for Runtype AI Platform").version(getCliVersion()).option("-v, --verbose", "Enable verbose output").option("--api-url <url>", "API URL (flag > env > project link > saved config > credential endpoint > default)").option("--org <id>", "Use the stored login for this organization (overrides RUNTYPE_ORG and .runtype/project.json)").option("--expect-api-url <url>", "Refuse actions unless the effective API URL matches").option("--expect-org <id>", "Require authenticated organization ID before API actions").option("--no-input", "Disable prompts; destructive delete/revoke/reset requires --yes").option("-y, --yes", "Explicitly consent to destructive commands in --no-input mode").option("--json", "Output as JSON").action(() => program.outputHelp());
|
|
59703
60555
|
program.addCommand(initCommand);
|
|
59704
60556
|
program.addCommand(loginCommand);
|
|
59705
60557
|
program.addCommand(authCommand);
|
|
60558
|
+
program.addCommand(linkCommand);
|
|
60559
|
+
program.addCommand(unlinkCommand);
|
|
59706
60560
|
program.addCommand(flowsCommand);
|
|
59707
60561
|
program.addCommand(recordsCommand);
|
|
59708
60562
|
program.addCommand(collectionsCommand);
|
|
@@ -59801,7 +60655,7 @@ run().catch((error) => {
|
|
|
59801
60655
|
}
|
|
59802
60656
|
});
|
|
59803
60657
|
async function handleNoCommand() {
|
|
59804
|
-
if (!await new CredentialStore().
|
|
60658
|
+
if (!((await new CredentialStore().listAccounts()).length > 0) && !process.env.RUNTYPE_API_KEY) {
|
|
59805
60659
|
console.log(chalk.cyan("\nWelcome to Runtype CLI!\n"));
|
|
59806
60660
|
console.log("It looks like this is your first time. Run the setup wizard:");
|
|
59807
60661
|
console.log(` ${chalk.green("runtype init")}\n`);
|