@runtypelabs/cli 2.47.9 → 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 +1814 -1061
- package/package.json +8 -7
package/dist/index.js
CHANGED
|
@@ -2,32 +2,32 @@
|
|
|
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";
|
|
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";
|
|
23
28
|
import { access, chmod, copyFile, mkdir, open as open$1, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
24
29
|
import TextInput from "ink-text-input";
|
|
25
|
-
import SelectInput from "ink-select-input";
|
|
26
30
|
import { execSync, spawn, spawnSync } from "node:child_process";
|
|
27
|
-
import * as crypto$2 from "node:crypto";
|
|
28
|
-
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
29
|
-
import * as os$2 from "node:os";
|
|
30
|
-
import os$1, { constants as constants$1 } from "node:os";
|
|
31
31
|
import { AnimatedVariant, LoadingAnimation, StartupGridIconCompactLightInverted } from "@runtypelabs/terminal-animations";
|
|
32
32
|
import { createJiti } from "jiti";
|
|
33
33
|
import micromatch from "micromatch";
|
|
@@ -35,6 +35,7 @@ import { parse } from "yaml";
|
|
|
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.
|
|
@@ -12511,7 +12140,7 @@ const CREATOR_TO_PROVIDER = {
|
|
|
12511
12140
|
"unknown": "openai"
|
|
12512
12141
|
};
|
|
12513
12142
|
//#endregion
|
|
12514
|
-
//#region ../shared/dist/chunk-
|
|
12143
|
+
//#region ../shared/dist/chunk-ykhTBHKy.mjs
|
|
12515
12144
|
/**
|
|
12516
12145
|
* The reserved model-facing name of the agent state write tool
|
|
12517
12146
|
* (`packages/runtime/src/engine/state-write-tool.ts`). The model is told to call
|
|
@@ -12915,7 +12544,7 @@ function durableCrawlBudget(plan) {
|
|
|
12915
12544
|
};
|
|
12916
12545
|
}
|
|
12917
12546
|
//#endregion
|
|
12918
|
-
//#region ../shared/dist/chunk-
|
|
12547
|
+
//#region ../shared/dist/chunk-BcuKxfZa.mjs
|
|
12919
12548
|
/**
|
|
12920
12549
|
* Unwraps a single {{variableName}} template to its bare variable name, preserving array/object identity instead of stringifying via the template engine.
|
|
12921
12550
|
* Returns source unchanged when it is not a lone {{...}} wrapper.
|
|
@@ -13022,7 +12651,7 @@ strictObject({
|
|
|
13022
12651
|
additionalProperties: boolean().optional()
|
|
13023
12652
|
}).superRefine((schema, ctx) => addSemanticIssues(schema, ctx));
|
|
13024
12653
|
//#endregion
|
|
13025
|
-
//#region ../shared/dist/chunk-
|
|
12654
|
+
//#region ../shared/dist/chunk-DoQn4_pS.mjs
|
|
13026
12655
|
/** The Tenancy Assurance Levels, in the order `tenancyAssuranceLevelSchema` is built from. */
|
|
13027
12656
|
const TENANCY_ASSURANCE_LEVELS = [
|
|
13028
12657
|
"anonymous",
|
|
@@ -25176,7 +24805,7 @@ const AgentInputSchema = object({
|
|
|
25176
24805
|
durability: agentDurabilityConfigSchema.optional(),
|
|
25177
24806
|
tenancyStrategy: agentRuntimeConfigSchema.shape.tenancyStrategy
|
|
25178
24807
|
}).superRefine((val, ctx) => {
|
|
25179
|
-
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;
|
|
25180
24809
|
if (unsupported) {
|
|
25181
24810
|
const [path, message] = unsupported;
|
|
25182
24811
|
ctx.addIssue({
|
|
@@ -26043,7 +25672,10 @@ object({
|
|
|
26043
25672
|
metering: _enum(["active", "legacy-untracked"]),
|
|
26044
25673
|
sleepPolicy: sandboxDeploySleepPolicySchema.optional(),
|
|
26045
25674
|
autoStopInterval: number$1().optional(),
|
|
26046
|
-
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.")
|
|
26047
25679
|
});
|
|
26048
25680
|
Object.freeze({
|
|
26049
25681
|
/** Automatic attempts per incident, counted in OPERATIONS rather than requests. */
|
|
@@ -26578,97 +26210,550 @@ object({
|
|
|
26578
26210
|
diagnosticId: string().max(64)
|
|
26579
26211
|
});
|
|
26580
26212
|
//#endregion
|
|
26581
|
-
//#region src/
|
|
26582
|
-
|
|
26583
|
-
|
|
26584
|
-
|
|
26585
|
-
function createConf(encryptionKey) {
|
|
26586
|
-
return new Conf({
|
|
26587
|
-
projectName: "runtype-cli",
|
|
26588
|
-
projectSuffix: "",
|
|
26589
|
-
configName: "credentials",
|
|
26590
|
-
encryptionKey,
|
|
26591
|
-
cwd: CREDENTIALS_DIR
|
|
26592
|
-
});
|
|
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");
|
|
26593
26217
|
}
|
|
26594
|
-
|
|
26595
|
-
|
|
26596
|
-
const
|
|
26597
|
-
|
|
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;
|
|
26598
26229
|
}
|
|
26599
|
-
|
|
26600
|
-
|
|
26601
|
-
|
|
26602
|
-
|
|
26603
|
-
|
|
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() {
|
|
26604
26288
|
try {
|
|
26605
|
-
|
|
26606
|
-
|
|
26607
|
-
|
|
26608
|
-
|
|
26609
|
-
if (fs$1.existsSync(CREDENTIALS_PATH)) fs$1.unlinkSync(CREDENTIALS_PATH);
|
|
26610
|
-
} catch {}
|
|
26611
|
-
this.config = createConf(this.encryptionKey);
|
|
26612
|
-
} else throw error;
|
|
26289
|
+
const parsed = JSON.parse(fs.readFileSync(this.filePath, "utf8"));
|
|
26290
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
26291
|
+
} catch {
|
|
26292
|
+
return {};
|
|
26613
26293
|
}
|
|
26614
26294
|
}
|
|
26615
|
-
|
|
26616
|
-
|
|
26617
|
-
|
|
26618
|
-
|
|
26619
|
-
|
|
26620
|
-
|
|
26621
|
-
|
|
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 });
|
|
26622
26312
|
}
|
|
26623
|
-
|
|
26624
|
-
|
|
26625
|
-
|
|
26626
|
-
|
|
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
|
+
});
|
|
26491
|
+
}
|
|
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`;
|
|
26627
26510
|
}
|
|
26628
26511
|
/**
|
|
26629
|
-
*
|
|
26630
|
-
*
|
|
26631
|
-
* 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.
|
|
26632
26514
|
*/
|
|
26633
|
-
async
|
|
26634
|
-
const
|
|
26635
|
-
|
|
26636
|
-
|
|
26637
|
-
|
|
26638
|
-
|
|
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: []
|
|
26639
26590
|
};
|
|
26640
|
-
|
|
26641
|
-
|
|
26642
|
-
|
|
26643
|
-
|
|
26644
|
-
|
|
26645
|
-
|
|
26646
|
-
|
|
26647
|
-
|
|
26648
|
-
|
|
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
|
+
}
|
|
26649
26701
|
};
|
|
26650
|
-
if (state.claimEmail !== void 0) data.claimEmail = state.claimEmail;
|
|
26651
|
-
if (state.credentialExpires !== void 0) data.credentialExpires = state.credentialExpires;
|
|
26652
|
-
if (state.claimTokenExpires !== void 0) data.claimTokenExpires = state.claimTokenExpires;
|
|
26653
|
-
if (state.apiUrl !== void 0) data.claimApiUrl = state.apiUrl;
|
|
26654
|
-
return data;
|
|
26655
26702
|
}
|
|
26656
26703
|
async getApiKey() {
|
|
26657
|
-
|
|
26658
|
-
if (!encrypted) return null;
|
|
26659
|
-
this.config.set("lastUsed", (/* @__PURE__ */ new Date()).toISOString());
|
|
26660
|
-
return this.decrypt(encrypted);
|
|
26704
|
+
return (await this.getCredentials())?.apiKey ?? null;
|
|
26661
26705
|
}
|
|
26662
26706
|
async getCredentials() {
|
|
26663
|
-
|
|
26664
|
-
|
|
26665
|
-
|
|
26666
|
-
|
|
26667
|
-
|
|
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
|
|
26668
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;
|
|
26669
26737
|
}
|
|
26738
|
+
/** Remove the active login (logout); other stored logins are untouched. */
|
|
26670
26739
|
async clearCredentials() {
|
|
26671
|
-
|
|
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
|
+
};
|
|
26672
26757
|
}
|
|
26673
26758
|
/**
|
|
26674
26759
|
* Persist agent-auth claim ceremony state alongside the pre-claim credential.
|
|
@@ -26676,54 +26761,129 @@ var CredentialStore = class {
|
|
|
26676
26761
|
* The claim token is encrypted like the API key since it can mint the post-claim credential.
|
|
26677
26762
|
*/
|
|
26678
26763
|
async saveClaimState(state) {
|
|
26679
|
-
|
|
26680
|
-
|
|
26681
|
-
|
|
26682
|
-
|
|
26683
|
-
|
|
26684
|
-
if (!claimToken || !registrationId) return null;
|
|
26685
|
-
return {
|
|
26686
|
-
claimToken: this.decrypt(claimToken),
|
|
26687
|
-
registrationId,
|
|
26688
|
-
claimEmail: this.config.get("claimEmail"),
|
|
26689
|
-
credentialExpires: this.config.get("credentialExpires"),
|
|
26690
|
-
claimTokenExpires: this.config.get("claimTokenExpires"),
|
|
26691
|
-
apiUrl: this.config.get("claimApiUrl")
|
|
26692
|
-
};
|
|
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();
|
|
26693
26769
|
}
|
|
26694
26770
|
async clearClaimState() {
|
|
26695
|
-
const
|
|
26696
|
-
|
|
26697
|
-
|
|
26698
|
-
|
|
26699
|
-
delete store.credentialExpires;
|
|
26700
|
-
delete store.claimTokenExpires;
|
|
26701
|
-
delete store.claimApiUrl;
|
|
26702
|
-
this.replaceStore(store);
|
|
26771
|
+
const account = await this.pendingClaimAccount();
|
|
26772
|
+
if (!account) return;
|
|
26773
|
+
await this.accounts.setClaim(account.id, null);
|
|
26774
|
+
resetEffectiveContext();
|
|
26703
26775
|
}
|
|
26704
|
-
async
|
|
26705
|
-
return
|
|
26706
|
-
}
|
|
26707
|
-
|
|
26708
|
-
|
|
26709
|
-
|
|
26710
|
-
|
|
26711
|
-
|
|
26712
|
-
|
|
26713
|
-
|
|
26714
|
-
|
|
26715
|
-
|
|
26716
|
-
|
|
26717
|
-
|
|
26718
|
-
|
|
26719
|
-
|
|
26720
|
-
const
|
|
26721
|
-
|
|
26722
|
-
|
|
26723
|
-
|
|
26724
|
-
|
|
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";
|
|
26725
26807
|
}
|
|
26726
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
|
+
}
|
|
26727
26887
|
//#endregion
|
|
26728
26888
|
//#region src/config/effective-context.ts
|
|
26729
26889
|
var CliContextError = class extends Error {
|
|
@@ -26744,11 +26904,15 @@ function getCliConfig() {
|
|
|
26744
26904
|
projectName: "runtype-cli",
|
|
26745
26905
|
projectSuffix: "",
|
|
26746
26906
|
configName: "config",
|
|
26747
|
-
cwd:
|
|
26907
|
+
cwd: getRuntypeHomeDir()
|
|
26748
26908
|
});
|
|
26749
26909
|
}
|
|
26750
26910
|
function setInvocationOptions(options) {
|
|
26751
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() {
|
|
26752
26916
|
effectiveContext = void 0;
|
|
26753
26917
|
identityCheck = void 0;
|
|
26754
26918
|
verifiedIdentity = void 0;
|
|
@@ -26767,11 +26931,12 @@ function normalizeApiUrl(value) {
|
|
|
26767
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.");
|
|
26768
26932
|
return url.toString().replace(/\/+$/, "");
|
|
26769
26933
|
}
|
|
26770
|
-
/** API target precedence:
|
|
26934
|
+
/** API target precedence: flag > environment > project link > saved config > credential endpoint > production. */
|
|
26771
26935
|
function resolveApiTarget(values) {
|
|
26772
26936
|
const [apiUrlSource, value] = [
|
|
26773
26937
|
["flag", values.flag],
|
|
26774
26938
|
["environment", values.environment],
|
|
26939
|
+
["project", values.project],
|
|
26775
26940
|
["config", values.config],
|
|
26776
26941
|
["credential", values.credential],
|
|
26777
26942
|
["default", "https://api.runtype.com"]
|
|
@@ -26781,36 +26946,83 @@ function resolveApiTarget(values) {
|
|
|
26781
26946
|
apiUrlSource
|
|
26782
26947
|
};
|
|
26783
26948
|
}
|
|
26784
|
-
function
|
|
26785
|
-
|
|
26786
|
-
|
|
26787
|
-
|
|
26788
|
-
|
|
26789
|
-
|
|
26790
|
-
|
|
26791
|
-
|
|
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
|
+
};
|
|
26792
27018
|
}
|
|
26793
27019
|
function getEffectiveApiUrl() {
|
|
26794
|
-
return effectiveContext?.apiUrl ??
|
|
27020
|
+
return effectiveContext?.apiUrl ?? resolveApiTarget(explicitApiTarget(invocationOptions.apiUrl).target).apiUrl;
|
|
26795
27021
|
}
|
|
26796
27022
|
async function loadEffectiveContext(apiUrl) {
|
|
26797
|
-
if (!effectiveContext)
|
|
26798
|
-
|
|
26799
|
-
|
|
26800
|
-
effectiveContext = {
|
|
26801
|
-
...resolveTarget(apiUrl, credentials),
|
|
26802
|
-
credentials,
|
|
26803
|
-
apiKey: envKey || credentials?.apiKey,
|
|
26804
|
-
credentialSource: envKey ? "environment" : credentials?.apiKey ? "stored" : "none"
|
|
26805
|
-
};
|
|
26806
|
-
} else if (apiUrl) {
|
|
26807
|
-
const target = resolveTarget(apiUrl, effectiveContext.credentials);
|
|
26808
|
-
if (target.apiUrl !== effectiveContext.apiUrl) effectiveContext = {
|
|
26809
|
-
...effectiveContext,
|
|
26810
|
-
...target
|
|
26811
|
-
};
|
|
26812
|
-
else effectiveContext.apiUrlSource = target.apiUrlSource;
|
|
26813
|
-
}
|
|
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";
|
|
26814
27026
|
return effectiveContext;
|
|
26815
27027
|
}
|
|
26816
27028
|
function useStoredCredential(context) {
|
|
@@ -26851,7 +27063,11 @@ function redactEffectiveContext(context) {
|
|
|
26851
27063
|
apiUrlSource: context.apiUrlSource,
|
|
26852
27064
|
credentialSource: context.credentialSource,
|
|
26853
27065
|
orgId: storedOrgApplies ? context.credentials?.orgId ?? null : null,
|
|
26854
|
-
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
|
|
26855
27071
|
};
|
|
26856
27072
|
}
|
|
26857
27073
|
//#endregion
|
|
@@ -27013,6 +27229,437 @@ var ApiKeyManager = class {
|
|
|
27013
27229
|
}
|
|
27014
27230
|
};
|
|
27015
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
|
|
27016
27663
|
//#region src/lib/ensure-auth.ts
|
|
27017
27664
|
const verifiedUsers = /* @__PURE__ */ new WeakMap();
|
|
27018
27665
|
async function getAuthenticatedUser() {
|
|
@@ -27081,7 +27728,7 @@ async function ensureAuth(options) {
|
|
|
27081
27728
|
}
|
|
27082
27729
|
if (options?.interactive === false || process.stdin.isTTY !== true || invocation.noInput || strictIdentity) {
|
|
27083
27730
|
if (options?.exitOnFailure === false) return null;
|
|
27084
|
-
const message =
|
|
27731
|
+
const message = authRequiredMessage(context);
|
|
27085
27732
|
console.error(invocation.json ? JSON.stringify({
|
|
27086
27733
|
success: false,
|
|
27087
27734
|
error: {
|
|
@@ -27092,39 +27739,39 @@ async function ensureAuth(options) {
|
|
|
27092
27739
|
process.exit(1);
|
|
27093
27740
|
return null;
|
|
27094
27741
|
}
|
|
27095
|
-
console.log(chalk.yellow(
|
|
27742
|
+
console.log(chalk.yellow(`\n${authRequiredMessage(context)}`));
|
|
27096
27743
|
if (!await promptConfirm$2("Would you like to log in now?", { default: true })) {
|
|
27097
27744
|
console.log(chalk.gray("Run \"runtype auth login\" when you're ready."));
|
|
27098
27745
|
if (options?.exitOnFailure !== false) process.exit(1);
|
|
27099
27746
|
return null;
|
|
27100
27747
|
}
|
|
27101
|
-
const apiKey = await handleBrowserLogin(
|
|
27748
|
+
const apiKey = await handleBrowserLogin(context.apiUrl);
|
|
27102
27749
|
if (apiKey) {
|
|
27103
27750
|
context.apiKey = apiKey;
|
|
27104
27751
|
context.credentialSource = "stored";
|
|
27105
27752
|
}
|
|
27106
27753
|
return apiKey;
|
|
27107
27754
|
}
|
|
27108
|
-
|
|
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) {
|
|
27109
27762
|
console.log(chalk.gray("Opening browser for authentication..."));
|
|
27110
27763
|
try {
|
|
27111
|
-
const
|
|
27112
|
-
|
|
27113
|
-
|
|
27114
|
-
});
|
|
27115
|
-
console.log(chalk.gray("Waiting for browser authentication..."));
|
|
27116
|
-
const sessionToken = await oauth.authenticate("sign-in");
|
|
27117
|
-
console.log(chalk.gray("Getting API key..."));
|
|
27118
|
-
const { key, userId, orgId } = await new ApiKeyManager().exchangeSessionForApiKey(sessionToken, apiUrl || getApiUrl());
|
|
27119
|
-
console.log(chalk.gray("Storing credentials..."));
|
|
27120
|
-
await store.saveCredentials({
|
|
27121
|
-
apiKey: key,
|
|
27122
|
-
userId,
|
|
27123
|
-
orgId,
|
|
27124
|
-
apiUrl: apiUrl || getApiUrl()
|
|
27764
|
+
const login = await browserLogin({
|
|
27765
|
+
apiUrl,
|
|
27766
|
+
onProgress: (label) => console.log(chalk.gray(label))
|
|
27125
27767
|
});
|
|
27768
|
+
const warning = orgMismatchWarning(login);
|
|
27769
|
+
if (warning) {
|
|
27770
|
+
console.log(chalk.yellow(warning));
|
|
27771
|
+
return null;
|
|
27772
|
+
}
|
|
27126
27773
|
console.log(chalk.green("Logged in successfully!"));
|
|
27127
|
-
return
|
|
27774
|
+
return login.apiKey;
|
|
27128
27775
|
} catch (error) {
|
|
27129
27776
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
27130
27777
|
console.log(chalk.red("Login failed"));
|
|
@@ -27355,6 +28002,25 @@ function MutationResult({ loading, loadingLabel = "Processing...", success, succ
|
|
|
27355
28002
|
return null;
|
|
27356
28003
|
}
|
|
27357
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
|
|
27358
28024
|
//#region src/auth/agent-auth-client.ts
|
|
27359
28025
|
/**
|
|
27360
28026
|
* Protocol error with the spec error code and an agent-actionable hint.
|
|
@@ -27609,7 +28275,7 @@ async function registerAction(options) {
|
|
|
27609
28275
|
claimTokenExpires: registration.claimTokenExpires,
|
|
27610
28276
|
apiUrl
|
|
27611
28277
|
};
|
|
27612
|
-
await store.saveCredentials({
|
|
28278
|
+
const registered = await store.saveCredentials({
|
|
27613
28279
|
apiKey: registration.credential,
|
|
27614
28280
|
userId: identity.userId,
|
|
27615
28281
|
orgId: identity.orgId,
|
|
@@ -27627,7 +28293,8 @@ async function registerAction(options) {
|
|
|
27627
28293
|
const claim = await initiateClaim(registration.claimToken, options.email, apiUrl);
|
|
27628
28294
|
await store.saveClaimState({
|
|
27629
28295
|
...claimState,
|
|
27630
|
-
claimEmail: options.email
|
|
28296
|
+
claimEmail: options.email,
|
|
28297
|
+
accountId: registered.id
|
|
27631
28298
|
});
|
|
27632
28299
|
printJson({
|
|
27633
28300
|
...base,
|
|
@@ -27708,13 +28375,15 @@ async function verifyAction(code, options) {
|
|
|
27708
28375
|
try {
|
|
27709
28376
|
identity = await fetchIdentity(completed.credential, apiUrl);
|
|
27710
28377
|
} catch {}
|
|
27711
|
-
|
|
28378
|
+
const preClaimAccountId = claimState.accountId;
|
|
28379
|
+
const wasDefault = (await store.getDefaultAccount())?.id === preClaimAccountId;
|
|
28380
|
+
const saved = await store.saveCredentials({
|
|
27712
28381
|
apiKey: completed.credential,
|
|
27713
28382
|
userId: identity.userId,
|
|
27714
28383
|
orgId: identity.orgId,
|
|
27715
28384
|
apiUrl
|
|
27716
|
-
});
|
|
27717
|
-
await store.
|
|
28385
|
+
}, void 0, { makeDefault: wasDefault });
|
|
28386
|
+
if (preClaimAccountId && preClaimAccountId !== saved.id) await store.removeAccount(preClaimAccountId);
|
|
27718
28387
|
printJson({
|
|
27719
28388
|
status: "authenticated",
|
|
27720
28389
|
accountState: "full",
|
|
@@ -27763,149 +28432,122 @@ async function getBillingInfo(apiKey, apiUrl) {
|
|
|
27763
28432
|
return null;
|
|
27764
28433
|
}
|
|
27765
28434
|
}
|
|
27766
|
-
|
|
27767
|
-
|
|
27768
|
-
refuseForcedTtyLogin(options);
|
|
27769
|
-
if (!isTTY(options)) {
|
|
27770
|
-
console.error(JSON.stringify(NON_TTY_LOGIN_GUIDANCE, null, 2));
|
|
27771
|
-
process.exit(1);
|
|
27772
|
-
}
|
|
28435
|
+
/** Run one async step under the shared spinner/result chrome. */
|
|
28436
|
+
async function renderTask(loadingLabel, task) {
|
|
27773
28437
|
const App = () => {
|
|
27774
28438
|
const [loading, setLoading] = useState(true);
|
|
27775
|
-
const [
|
|
28439
|
+
const [label, setLabel] = useState(loadingLabel);
|
|
27776
28440
|
const [success, setSuccess] = useState(null);
|
|
27777
28441
|
const [error, setError] = useState(null);
|
|
28442
|
+
const [outcome, setOutcome] = useState(null);
|
|
27778
28443
|
useEffect(() => {
|
|
27779
|
-
|
|
27780
|
-
|
|
27781
|
-
|
|
27782
|
-
|
|
27783
|
-
|
|
27784
|
-
|
|
27785
|
-
|
|
27786
|
-
const sessionToken = await oauth.authenticate("sign-in");
|
|
27787
|
-
setLoadingLabel("Creating API key...");
|
|
27788
|
-
const { key, userId, orgId } = await new ApiKeyManager().exchangeSessionForApiKey(sessionToken, options.apiUrl || getApiUrl());
|
|
27789
|
-
setLoadingLabel("Storing credentials securely...");
|
|
27790
|
-
await new CredentialStore().saveCredentials({
|
|
27791
|
-
apiKey: key,
|
|
27792
|
-
userId,
|
|
27793
|
-
orgId,
|
|
27794
|
-
apiUrl: options.apiUrl || getApiUrl()
|
|
27795
|
-
});
|
|
27796
|
-
setSuccess(true);
|
|
27797
|
-
setLoading(false);
|
|
27798
|
-
} catch (err) {
|
|
27799
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
27800
|
-
setSuccess(false);
|
|
27801
|
-
setLoading(false);
|
|
27802
|
-
}
|
|
27803
|
-
};
|
|
27804
|
-
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));
|
|
27805
28451
|
}, []);
|
|
27806
28452
|
return React.createElement(MutationResult, {
|
|
27807
28453
|
loading,
|
|
27808
|
-
loadingLabel,
|
|
28454
|
+
loadingLabel: label,
|
|
27809
28455
|
success,
|
|
27810
|
-
successMessage:
|
|
28456
|
+
successMessage: outcome?.message ?? "",
|
|
27811
28457
|
error,
|
|
27812
|
-
result:
|
|
27813
|
-
label: "Next",
|
|
27814
|
-
value: "runtype flows list"
|
|
27815
|
-
}] })
|
|
28458
|
+
result: outcome?.result
|
|
27816
28459
|
});
|
|
27817
28460
|
};
|
|
27818
28461
|
const { waitUntilExit } = render(React.createElement(App));
|
|
27819
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
|
+
})));
|
|
27820
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
|
+
}
|
|
27821
28532
|
async function loginAction(options) {
|
|
27822
28533
|
await loadEffectiveContext(options.apiUrl);
|
|
27823
|
-
const store = new CredentialStore();
|
|
27824
28534
|
if (options.apiKey) {
|
|
28535
|
+
const apiKey = options.apiKey;
|
|
27825
28536
|
if (!isTTY(options)) {
|
|
28537
|
+
let login;
|
|
27826
28538
|
try {
|
|
27827
|
-
|
|
27828
|
-
if (!await apiKeyManager.validateApiKey(options.apiKey, options.apiUrl)) {
|
|
27829
|
-
console.error(JSON.stringify({
|
|
27830
|
-
status: "error",
|
|
27831
|
-
error: "Invalid API key"
|
|
27832
|
-
}, null, 2));
|
|
27833
|
-
process.exit(1);
|
|
27834
|
-
}
|
|
27835
|
-
const userData = await apiKeyManager.getCurrentUser(options.apiKey, options.apiUrl);
|
|
27836
|
-
await store.saveCredentials({
|
|
27837
|
-
apiKey: options.apiKey,
|
|
27838
|
-
userId: userData.userId,
|
|
27839
|
-
orgId: userData.orgId ?? void 0,
|
|
27840
|
-
apiUrl: options.apiUrl || getApiUrl()
|
|
27841
|
-
});
|
|
27842
|
-
console.log(JSON.stringify({
|
|
27843
|
-
status: "authenticated",
|
|
27844
|
-
userId: userData.userId,
|
|
27845
|
-
next: "Continue with setup: runtype install-mcp (then confirm with: runtype auth whoami)"
|
|
27846
|
-
}, null, 2));
|
|
28539
|
+
login = await apiKeyLogin(apiKey, { apiUrl: options.apiUrl });
|
|
27847
28540
|
} catch (error) {
|
|
27848
|
-
|
|
27849
|
-
|
|
27850
|
-
status: "error",
|
|
27851
|
-
code: "api_unreachable",
|
|
27852
|
-
error: error.message,
|
|
27853
|
-
next: API_UNREACHABLE_NEXT
|
|
27854
|
-
}, null, 2));
|
|
27855
|
-
process.exit(1);
|
|
27856
|
-
}
|
|
27857
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
27858
|
-
console.error(JSON.stringify({
|
|
27859
|
-
status: "error",
|
|
27860
|
-
error: message
|
|
27861
|
-
}, null, 2));
|
|
27862
|
-
process.exit(1);
|
|
28541
|
+
failApiKeyLogin(error);
|
|
28542
|
+
return;
|
|
27863
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));
|
|
27864
28548
|
return;
|
|
27865
28549
|
}
|
|
27866
|
-
|
|
27867
|
-
const [loading, setLoading] = useState(true);
|
|
27868
|
-
const [success, setSuccess] = useState(null);
|
|
27869
|
-
const [error, setError] = useState(null);
|
|
27870
|
-
const [userId, setUserId] = useState(null);
|
|
27871
|
-
useEffect(() => {
|
|
27872
|
-
const run = async () => {
|
|
27873
|
-
try {
|
|
27874
|
-
const apiKeyManager = new ApiKeyManager();
|
|
27875
|
-
if (!await apiKeyManager.validateApiKey(options.apiKey, options.apiUrl)) {
|
|
27876
|
-
setError(/* @__PURE__ */ new Error("Invalid API key"));
|
|
27877
|
-
setSuccess(false);
|
|
27878
|
-
setLoading(false);
|
|
27879
|
-
return;
|
|
27880
|
-
}
|
|
27881
|
-
const userData = await apiKeyManager.getCurrentUser(options.apiKey, options.apiUrl);
|
|
27882
|
-
await store.saveCredentials({
|
|
27883
|
-
apiKey: options.apiKey,
|
|
27884
|
-
userId: userData.userId,
|
|
27885
|
-
orgId: userData.orgId ?? void 0,
|
|
27886
|
-
apiUrl: options.apiUrl || getApiUrl()
|
|
27887
|
-
});
|
|
27888
|
-
setUserId(userData.userId);
|
|
27889
|
-
setSuccess(true);
|
|
27890
|
-
setLoading(false);
|
|
27891
|
-
} catch (err) {
|
|
27892
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
27893
|
-
setSuccess(false);
|
|
27894
|
-
setLoading(false);
|
|
27895
|
-
}
|
|
27896
|
-
};
|
|
27897
|
-
run();
|
|
27898
|
-
}, []);
|
|
27899
|
-
return React.createElement(MutationResult, {
|
|
27900
|
-
loading,
|
|
27901
|
-
loadingLabel: "Validating API key...",
|
|
27902
|
-
success,
|
|
27903
|
-
successMessage: `Logged in successfully!${userId ? ` User ID: ${userId}` : ""}`,
|
|
27904
|
-
error
|
|
27905
|
-
});
|
|
27906
|
-
};
|
|
27907
|
-
const { waitUntilExit } = render(React.createElement(App));
|
|
27908
|
-
await waitUntilExit();
|
|
28550
|
+
await renderTask("Validating API key...", async () => loginOutcome(await apiKeyLogin(apiKey, { apiUrl: options.apiUrl })));
|
|
27909
28551
|
return;
|
|
27910
28552
|
}
|
|
27911
28553
|
refuseForcedTtyLogin(options);
|
|
@@ -27913,73 +28555,54 @@ async function loginAction(options) {
|
|
|
27913
28555
|
console.error(JSON.stringify(NON_TTY_LOGIN_GUIDANCE, null, 2));
|
|
27914
28556
|
process.exit(1);
|
|
27915
28557
|
}
|
|
27916
|
-
|
|
27917
|
-
|
|
27918
|
-
|
|
27919
|
-
|
|
27920
|
-
|
|
27921
|
-
|
|
27922
|
-
|
|
27923
|
-
|
|
27924
|
-
|
|
27925
|
-
|
|
27926
|
-
|
|
27927
|
-
|
|
27928
|
-
|
|
27929
|
-
|
|
27930
|
-
|
|
27931
|
-
setLoadingLabel("Getting API key...");
|
|
27932
|
-
const { key, userId: uid, orgId } = await new ApiKeyManager().exchangeSessionForApiKey(sessionToken, options.apiUrl || getApiUrl());
|
|
27933
|
-
setLoadingLabel("Storing credentials...");
|
|
27934
|
-
await store.saveCredentials({
|
|
27935
|
-
apiKey: key,
|
|
27936
|
-
userId: uid,
|
|
27937
|
-
orgId,
|
|
27938
|
-
apiUrl: options.apiUrl || getApiUrl()
|
|
27939
|
-
});
|
|
27940
|
-
setUserId(uid);
|
|
27941
|
-
setSuccess(true);
|
|
27942
|
-
setLoading(false);
|
|
27943
|
-
} catch (err) {
|
|
27944
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
27945
|
-
setSuccess(false);
|
|
27946
|
-
setLoading(false);
|
|
27947
|
-
}
|
|
27948
|
-
};
|
|
27949
|
-
run();
|
|
27950
|
-
}, []);
|
|
27951
|
-
return React.createElement(MutationResult, {
|
|
27952
|
-
loading,
|
|
27953
|
-
loadingLabel,
|
|
27954
|
-
success,
|
|
27955
|
-
successMessage: `Logged in successfully!${userId ? ` User ID: ${userId}` : ""}`,
|
|
27956
|
-
error
|
|
27957
|
-
});
|
|
27958
|
-
};
|
|
27959
|
-
const { waitUntilExit } = render(React.createElement(App));
|
|
27960
|
-
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
|
+
}
|
|
27961
28573
|
}
|
|
27962
|
-
authCommand.command("
|
|
27963
|
-
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);
|
|
27964
|
-
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) => {
|
|
27965
28575
|
const store = new CredentialStore();
|
|
27966
28576
|
if (!isTTY(options)) {
|
|
27967
28577
|
try {
|
|
27968
|
-
|
|
28578
|
+
const context = await loadEffectiveContext();
|
|
28579
|
+
const storedLogins = (await store.listAccounts()).length;
|
|
28580
|
+
const credentials = context.credentials;
|
|
28581
|
+
if (!credentials) {
|
|
27969
28582
|
console.log(JSON.stringify({
|
|
27970
28583
|
status: "unauthenticated",
|
|
27971
|
-
|
|
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>."
|
|
27972
28589
|
}, null, 2));
|
|
27973
28590
|
return;
|
|
27974
28591
|
}
|
|
27975
|
-
const
|
|
27976
|
-
const claimState =
|
|
28592
|
+
const pendingClaim = await store.getClaimState();
|
|
28593
|
+
const claimState = pendingClaim?.accountId === credentials.accountId ? pendingClaim : null;
|
|
27977
28594
|
console.log(JSON.stringify({
|
|
27978
28595
|
status: "authenticated",
|
|
27979
28596
|
accountState: claimState ? "temporary" : "full",
|
|
27980
|
-
userId: credentials
|
|
27981
|
-
apiUrl: credentials
|
|
27982
|
-
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." } : {},
|
|
27983
28606
|
...claimState ? {
|
|
27984
28607
|
registrationId: claimState.registrationId,
|
|
27985
28608
|
credentialExpires: claimState.credentialExpires,
|
|
@@ -27996,74 +28619,158 @@ authCommand.command("status").description("Show authentication status").option("
|
|
|
27996
28619
|
}
|
|
27997
28620
|
return;
|
|
27998
28621
|
}
|
|
27999
|
-
|
|
28000
|
-
const
|
|
28001
|
-
const
|
|
28002
|
-
const
|
|
28003
|
-
|
|
28004
|
-
|
|
28005
|
-
|
|
28006
|
-
|
|
28007
|
-
|
|
28008
|
-
|
|
28009
|
-
|
|
28010
|
-
|
|
28011
|
-
|
|
28012
|
-
|
|
28013
|
-
|
|
28014
|
-
|
|
28015
|
-
|
|
28016
|
-
|
|
28017
|
-
|
|
28018
|
-
|
|
28019
|
-
|
|
28020
|
-
|
|
28021
|
-
|
|
28022
|
-
{
|
|
28023
|
-
label: "Status",
|
|
28024
|
-
value: "Authenticated",
|
|
28025
|
-
color: "green"
|
|
28026
|
-
},
|
|
28027
|
-
{
|
|
28028
|
-
label: "User ID",
|
|
28029
|
-
value: credentials.userId
|
|
28030
|
-
},
|
|
28031
|
-
{
|
|
28032
|
-
label: "API URL",
|
|
28033
|
-
value: credentials.apiUrl || "default"
|
|
28034
|
-
},
|
|
28035
|
-
{
|
|
28036
|
-
label: "API Key",
|
|
28037
|
-
value: credentials.apiKey ? credentials.apiKey.substring(0, 12) + "..." : "missing"
|
|
28038
|
-
},
|
|
28039
|
-
{
|
|
28040
|
-
label: "Org ID",
|
|
28041
|
-
value: credentials.orgId || "none"
|
|
28042
|
-
}
|
|
28043
|
-
] }));
|
|
28044
|
-
setSuccess(true);
|
|
28045
|
-
setLoading(false);
|
|
28046
|
-
} catch (err) {
|
|
28047
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
28048
|
-
setSuccess(false);
|
|
28049
|
-
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"
|
|
28050
28645
|
}
|
|
28051
|
-
}
|
|
28052
|
-
|
|
28053
|
-
|
|
28054
|
-
|
|
28055
|
-
|
|
28056
|
-
|
|
28057
|
-
|
|
28058
|
-
|
|
28059
|
-
|
|
28060
|
-
|
|
28061
|
-
|
|
28062
|
-
|
|
28063
|
-
|
|
28064
|
-
|
|
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
|
+
});
|
|
28065
28692
|
});
|
|
28066
|
-
authCommand.command("
|
|
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));
|
|
28772
|
+
});
|
|
28773
|
+
authCommand.command("export-key").description("Print the active login's API key to stdout (for use in scripts and .env files)").action(async () => {
|
|
28067
28774
|
const apiKey = await new CredentialStore().getApiKey();
|
|
28068
28775
|
if (!apiKey) {
|
|
28069
28776
|
console.error("Not authenticated. Run `runtype auth login` first.");
|
|
@@ -28073,52 +28780,43 @@ authCommand.command("export-key").description("Print the stored API key to stdou
|
|
|
28073
28780
|
process.stdout.write(apiKey);
|
|
28074
28781
|
});
|
|
28075
28782
|
registerAgentAuthCommands(authCommand);
|
|
28076
|
-
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) => {
|
|
28077
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
|
+
};
|
|
28078
28799
|
if (!isTTY(options)) {
|
|
28079
|
-
|
|
28800
|
+
const { removed } = await logout();
|
|
28801
|
+
if (removed.length === 0) {
|
|
28080
28802
|
console.log(JSON.stringify({ status: "no_credentials" }, null, 2));
|
|
28081
28803
|
return;
|
|
28082
28804
|
}
|
|
28083
|
-
|
|
28084
|
-
|
|
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));
|
|
28085
28813
|
return;
|
|
28086
28814
|
}
|
|
28087
|
-
|
|
28088
|
-
const
|
|
28089
|
-
|
|
28090
|
-
|
|
28091
|
-
|
|
28092
|
-
useEffect(() => {
|
|
28093
|
-
const run = async () => {
|
|
28094
|
-
try {
|
|
28095
|
-
if (!await store.hasCredentials()) {
|
|
28096
|
-
setMsg("No stored credentials found");
|
|
28097
|
-
setSuccess(true);
|
|
28098
|
-
setLoading(false);
|
|
28099
|
-
return;
|
|
28100
|
-
}
|
|
28101
|
-
await store.clearCredentials();
|
|
28102
|
-
setSuccess(true);
|
|
28103
|
-
setLoading(false);
|
|
28104
|
-
} catch (err) {
|
|
28105
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
28106
|
-
setSuccess(false);
|
|
28107
|
-
setLoading(false);
|
|
28108
|
-
}
|
|
28109
|
-
};
|
|
28110
|
-
run();
|
|
28111
|
-
}, []);
|
|
28112
|
-
return React.createElement(MutationResult, {
|
|
28113
|
-
loading,
|
|
28114
|
-
loadingLabel: "Logging out...",
|
|
28115
|
-
success,
|
|
28116
|
-
successMessage: msg,
|
|
28117
|
-
error
|
|
28118
|
-
});
|
|
28119
|
-
};
|
|
28120
|
-
const { waitUntilExit } = render(React.createElement(App));
|
|
28121
|
-
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
|
+
});
|
|
28122
28820
|
});
|
|
28123
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) => {
|
|
28124
28822
|
const apiKey = await ensureAuth({
|
|
@@ -28132,10 +28830,16 @@ authCommand.command("whoami").description("Display current user information").op
|
|
|
28132
28830
|
apiUrl: context.apiUrl
|
|
28133
28831
|
};
|
|
28134
28832
|
if (!credentials || !credentials.apiKey) {
|
|
28135
|
-
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));
|
|
28136
28840
|
else {
|
|
28137
|
-
console.log(chalk.yellow("Not logged in"));
|
|
28138
|
-
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");
|
|
28139
28843
|
}
|
|
28140
28844
|
return;
|
|
28141
28845
|
}
|
|
@@ -28147,6 +28851,9 @@ authCommand.command("whoami").description("Display current user information").op
|
|
|
28147
28851
|
userId: user.userId,
|
|
28148
28852
|
orgId: user.orgId,
|
|
28149
28853
|
apiUrl: credentials.apiUrl || getApiUrl(),
|
|
28854
|
+
credentialSource: context.credentialSource,
|
|
28855
|
+
accountSource: context.accountSource,
|
|
28856
|
+
projectLink: context.projectLink?.path ?? null,
|
|
28150
28857
|
billing: billingInfo
|
|
28151
28858
|
}, null, 2));
|
|
28152
28859
|
} catch (error) {
|
|
@@ -28159,81 +28866,142 @@ authCommand.command("whoami").description("Display current user information").op
|
|
|
28159
28866
|
}
|
|
28160
28867
|
return;
|
|
28161
28868
|
}
|
|
28162
|
-
|
|
28163
|
-
const
|
|
28164
|
-
const
|
|
28165
|
-
const
|
|
28166
|
-
|
|
28167
|
-
|
|
28168
|
-
|
|
28169
|
-
|
|
28170
|
-
|
|
28171
|
-
|
|
28172
|
-
|
|
28173
|
-
|
|
28174
|
-
|
|
28175
|
-
|
|
28176
|
-
|
|
28177
|
-
|
|
28178
|
-
|
|
28179
|
-
|
|
28180
|
-
|
|
28181
|
-
|
|
28182
|
-
|
|
28183
|
-
|
|
28184
|
-
|
|
28185
|
-
|
|
28186
|
-
|
|
28187
|
-
|
|
28188
|
-
|
|
28189
|
-
|
|
28190
|
-
|
|
28191
|
-
|
|
28192
|
-
|
|
28193
|
-
|
|
28194
|
-
|
|
28195
|
-
|
|
28196
|
-
|
|
28197
|
-
|
|
28198
|
-
|
|
28199
|
-
|
|
28200
|
-
|
|
28201
|
-
|
|
28202
|
-
|
|
28203
|
-
|
|
28204
|
-
|
|
28205
|
-
|
|
28206
|
-
|
|
28207
|
-
|
|
28208
|
-
|
|
28209
|
-
|
|
28210
|
-
|
|
28211
|
-
|
|
28212
|
-
|
|
28213
|
-
|
|
28214
|
-
|
|
28215
|
-
|
|
28216
|
-
|
|
28217
|
-
|
|
28218
|
-
|
|
28219
|
-
|
|
28220
|
-
|
|
28221
|
-
|
|
28222
|
-
|
|
28223
|
-
|
|
28224
|
-
|
|
28225
|
-
|
|
28226
|
-
|
|
28227
|
-
|
|
28228
|
-
|
|
28229
|
-
|
|
28230
|
-
|
|
28231
|
-
|
|
28232
|
-
|
|
28233
|
-
|
|
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
|
|
28234
28946
|
};
|
|
28235
|
-
|
|
28236
|
-
|
|
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."));
|
|
28237
29005
|
});
|
|
28238
29006
|
//#endregion
|
|
28239
29007
|
//#region src/lib/file-utils.ts
|
|
@@ -31499,6 +32267,9 @@ configCommand.command("context").description("Show the effective API target and
|
|
|
31499
32267
|
console.log(`API URL: ${context.apiUrl} (${context.apiUrlSource})`);
|
|
31500
32268
|
console.log(`Credential source: ${context.credentialSource}`);
|
|
31501
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}`);
|
|
31502
32273
|
});
|
|
31503
32274
|
configCommand.command("get [key]").description("Get configuration value").action((key) => {
|
|
31504
32275
|
const config = getCliConfig();
|
|
@@ -34200,21 +34971,16 @@ function InitApp({ apiUrl, onComplete }) {
|
|
|
34200
34971
|
loadingLabel: storedKeyNotice ? `${storedKeyNotice} Opening browser to sign in instead...` : "Opening browser to sign in..."
|
|
34201
34972
|
}));
|
|
34202
34973
|
try {
|
|
34203
|
-
const
|
|
34204
|
-
|
|
34205
|
-
|
|
34206
|
-
|
|
34207
|
-
|
|
34208
|
-
|
|
34209
|
-
loadingLabel: "Exchanging credentials..."
|
|
34210
|
-
}));
|
|
34211
|
-
const { key, userId, orgId } = await new ApiKeyManager().exchangeSessionForApiKey(sessionToken, apiUrl || getApiUrl());
|
|
34212
|
-
await store.saveCredentials({
|
|
34213
|
-
apiKey: key,
|
|
34214
|
-
userId,
|
|
34215
|
-
orgId,
|
|
34216
|
-
apiUrl: apiUrl || getApiUrl()
|
|
34974
|
+
const login = await browserLogin({
|
|
34975
|
+
apiUrl,
|
|
34976
|
+
onProgress: (label) => setState((s) => ({
|
|
34977
|
+
...s,
|
|
34978
|
+
loadingLabel: label
|
|
34979
|
+
}))
|
|
34217
34980
|
});
|
|
34981
|
+
const mismatch = orgMismatchWarning(login);
|
|
34982
|
+
if (mismatch) throw new Error(mismatch);
|
|
34983
|
+
const { apiKey: key, userId, orgId } = login;
|
|
34218
34984
|
setState((s) => ({
|
|
34219
34985
|
...s,
|
|
34220
34986
|
apiKey: key,
|
|
@@ -37759,7 +38525,7 @@ function applyMaxLines(raw, maxLines) {
|
|
|
37759
38525
|
function readFileContent(filePath, snapshots, liveTools, maxLines = 5e3) {
|
|
37760
38526
|
try {
|
|
37761
38527
|
return {
|
|
37762
|
-
...applyMaxLines(fs$
|
|
38528
|
+
...applyMaxLines(fs$1.readFileSync(filePath, "utf-8"), maxLines),
|
|
37763
38529
|
source: "disk"
|
|
37764
38530
|
};
|
|
37765
38531
|
} catch {}
|
|
@@ -38064,7 +38830,7 @@ const deployLifecycleParameters = {
|
|
|
38064
38830
|
description: "Runtype destruction backstop. Omitted: Daytona 10m, Runtype Sandbox unlimited. Provider sleep/loss can end availability earlier."
|
|
38065
38831
|
},
|
|
38066
38832
|
sleepPolicy: {
|
|
38067
|
-
description: "Idle compute policy, independent of retention.
|
|
38833
|
+
description: "Idle compute policy, independent of retention.",
|
|
38068
38834
|
oneOf: [{
|
|
38069
38835
|
type: "object",
|
|
38070
38836
|
properties: { mode: { const: "never" } },
|
|
@@ -38187,7 +38953,7 @@ function createDeploySandboxLocalTool(client, debugMode) {
|
|
|
38187
38953
|
let activeCfSandboxId;
|
|
38188
38954
|
function createDeployCfSandboxLocalTool(client, debugMode) {
|
|
38189
38955
|
return {
|
|
38190
|
-
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.",
|
|
38191
38957
|
parametersSchema: {
|
|
38192
38958
|
type: "object",
|
|
38193
38959
|
properties: {
|
|
@@ -38196,6 +38962,10 @@ function createDeployCfSandboxLocalTool(client, debugMode) {
|
|
|
38196
38962
|
description: "Code to deploy (main entry point file)"
|
|
38197
38963
|
},
|
|
38198
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
|
+
},
|
|
38199
38969
|
packageJson: {
|
|
38200
38970
|
type: "object",
|
|
38201
38971
|
description: "package.json content as a JSON object, e.g. { \"dependencies\": { \"hono\": \"^4.0.0\" } }"
|
|
@@ -38289,14 +39059,14 @@ function createEventLogWriter(stateFilePath, initialSessionIndex) {
|
|
|
38289
39059
|
let stream = null;
|
|
38290
39060
|
function ensureDir() {
|
|
38291
39061
|
if (!dirCreated) {
|
|
38292
|
-
fs$
|
|
39062
|
+
fs$1.mkdirSync(eventsDir, { recursive: true });
|
|
38293
39063
|
dirCreated = true;
|
|
38294
39064
|
}
|
|
38295
39065
|
}
|
|
38296
39066
|
function ensureStream() {
|
|
38297
39067
|
if (!stream || stream.destroyed) {
|
|
38298
39068
|
ensureDir();
|
|
38299
|
-
stream = fs$
|
|
39069
|
+
stream = fs$1.createWriteStream(currentPath, { flags: "a" });
|
|
38300
39070
|
stream.on("error", () => {});
|
|
38301
39071
|
}
|
|
38302
39072
|
}
|
|
@@ -38325,8 +39095,8 @@ function createEventLogWriter(stateFilePath, initialSessionIndex) {
|
|
|
38325
39095
|
* Returns an empty array if the file doesn't exist. */
|
|
38326
39096
|
function readSessionEventLog(stateFilePath, sessionIndex) {
|
|
38327
39097
|
const logPath = sessionEventLogPath(stateFilePath, sessionIndex);
|
|
38328
|
-
if (!fs$
|
|
38329
|
-
const content = fs$
|
|
39098
|
+
if (!fs$1.existsSync(logPath)) return [];
|
|
39099
|
+
const content = fs$1.readFileSync(logPath, "utf-8");
|
|
38330
39100
|
const events = [];
|
|
38331
39101
|
for (const line of content.split("\n")) {
|
|
38332
39102
|
if (!line.trim()) continue;
|
|
@@ -38361,7 +39131,7 @@ function isTreeLogEntry(value) {
|
|
|
38361
39131
|
function loadTreeLog(filePath) {
|
|
38362
39132
|
let raw;
|
|
38363
39133
|
try {
|
|
38364
|
-
raw = fs$
|
|
39134
|
+
raw = fs$1.readFileSync(filePath, "utf-8");
|
|
38365
39135
|
} catch {
|
|
38366
39136
|
return null;
|
|
38367
39137
|
}
|
|
@@ -38398,8 +39168,8 @@ function loadTreeLog(filePath) {
|
|
|
38398
39168
|
};
|
|
38399
39169
|
}
|
|
38400
39170
|
function appendLine(log, entry) {
|
|
38401
|
-
fs$
|
|
38402
|
-
fs$
|
|
39171
|
+
fs$1.mkdirSync(path$2.dirname(log.filePath), { recursive: true });
|
|
39172
|
+
fs$1.appendFileSync(log.filePath, JSON.stringify(entry) + "\n");
|
|
38403
39173
|
log.entries.push(entry);
|
|
38404
39174
|
if (entry.type !== "head") {
|
|
38405
39175
|
log.byId.set(entry.id, entry);
|
|
@@ -38464,7 +39234,7 @@ function appendArtifact(log, payload) {
|
|
|
38464
39234
|
type: "artifact",
|
|
38465
39235
|
payload: {
|
|
38466
39236
|
...payload,
|
|
38467
|
-
host: payload.host || os$
|
|
39237
|
+
host: payload.host || os$1.hostname()
|
|
38468
39238
|
}
|
|
38469
39239
|
};
|
|
38470
39240
|
appendLine(log, entry);
|
|
@@ -41540,7 +42310,7 @@ function MarathonApp({ taskName, agentId, model, sandbox, goal, initialSessionCo
|
|
|
41540
42310
|
setShowSessionMenu(false);
|
|
41541
42311
|
try {
|
|
41542
42312
|
let json;
|
|
41543
|
-
if (stateFilePath && fs$
|
|
42313
|
+
if (stateFilePath && fs$1.existsSync(stateFilePath)) json = fs$1.readFileSync(stateFilePath, "utf-8");
|
|
41544
42314
|
else json = JSON.stringify({
|
|
41545
42315
|
sessionSnapshots,
|
|
41546
42316
|
content: state.content,
|
|
@@ -41563,8 +42333,8 @@ function MarathonApp({ taskName, agentId, model, sandbox, goal, initialSessionCo
|
|
|
41563
42333
|
setShowSessionMenu(false);
|
|
41564
42334
|
try {
|
|
41565
42335
|
let json;
|
|
41566
|
-
if (stateFilePath && fs$
|
|
41567
|
-
const raw = fs$
|
|
42336
|
+
if (stateFilePath && fs$1.existsSync(stateFilePath)) {
|
|
42337
|
+
const raw = fs$1.readFileSync(stateFilePath, "utf-8");
|
|
41568
42338
|
const parsed = JSON.parse(raw);
|
|
41569
42339
|
json = JSON.stringify(compactMarathonStateJsonForContextExport(parsed), null, 2);
|
|
41570
42340
|
} else {
|
|
@@ -41590,7 +42360,7 @@ function MarathonApp({ taskName, agentId, model, sandbox, goal, initialSessionCo
|
|
|
41590
42360
|
]);
|
|
41591
42361
|
const handleOpenStateFile = useCallback(() => {
|
|
41592
42362
|
setShowSessionMenu(false);
|
|
41593
|
-
if (stateFilePath && fs$
|
|
42363
|
+
if (stateFilePath && fs$1.existsSync(stateFilePath)) {
|
|
41594
42364
|
open(stateFilePath);
|
|
41595
42365
|
showFlash("Opening state file...");
|
|
41596
42366
|
} else showFlash("No state file yet");
|
|
@@ -41985,7 +42755,7 @@ function MarathonApp({ taskName, agentId, model, sandbox, goal, initialSessionCo
|
|
|
41985
42755
|
const file = detailFile ?? trackedFiles[fileCursor];
|
|
41986
42756
|
if (file) {
|
|
41987
42757
|
const filePath = path$2.resolve(file.path);
|
|
41988
|
-
if (fs$
|
|
42758
|
+
if (fs$1.existsSync(filePath)) {
|
|
41989
42759
|
open(filePath);
|
|
41990
42760
|
showFlash(`Opening ${file.path}`);
|
|
41991
42761
|
} else showFlash("File not found on disk");
|
|
@@ -42500,7 +43270,7 @@ function MarathonApp({ taskName, agentId, model, sandbox, goal, initialSessionCo
|
|
|
42500
43270
|
onOpenDashboard: handleOpenDashboard,
|
|
42501
43271
|
onClose: () => setShowSessionMenu(false),
|
|
42502
43272
|
hasDashboard: !!agentPageUrl,
|
|
42503
|
-
hasStateFile: !!stateFilePath && fs$
|
|
43273
|
+
hasStateFile: !!stateFilePath && fs$1.existsSync(stateFilePath)
|
|
42504
43274
|
})
|
|
42505
43275
|
}),
|
|
42506
43276
|
showUpgradeModal && upgradePrompt && /* @__PURE__ */ jsx(Box, {
|
|
@@ -43041,28 +43811,6 @@ function setMarathonTitle() {
|
|
|
43041
43811
|
setTerminalTitle(`Runtype: Marathon (${getFolderName()})`);
|
|
43042
43812
|
}
|
|
43043
43813
|
//#endregion
|
|
43044
|
-
//#region src/config/paths.ts
|
|
43045
|
-
/** Root state directory: RUNTYPE_STATE_DIR or ~/.runtype */
|
|
43046
|
-
function getRuntypeHomeDir() {
|
|
43047
|
-
return process.env.RUNTYPE_STATE_DIR || path$2.join(os$2.homedir(), ".runtype");
|
|
43048
|
-
}
|
|
43049
|
-
/** Per-project state dir: ~/.runtype/projects/{hash}/ */
|
|
43050
|
-
function getProjectStateDir(projectDir) {
|
|
43051
|
-
const dir = projectDir || process.cwd();
|
|
43052
|
-
const hash = crypto$2.createHash("sha256").update(dir).digest("hex").slice(0, 12);
|
|
43053
|
-
const stateDir = path$2.join(getRuntypeHomeDir(), "projects", hash);
|
|
43054
|
-
const breadcrumb = path$2.join(stateDir, "project-path.txt");
|
|
43055
|
-
if (!fs$2.existsSync(breadcrumb)) {
|
|
43056
|
-
fs$2.mkdirSync(stateDir, { recursive: true });
|
|
43057
|
-
fs$2.writeFileSync(breadcrumb, dir);
|
|
43058
|
-
}
|
|
43059
|
-
return stateDir;
|
|
43060
|
-
}
|
|
43061
|
-
/** Marathon state dir: ~/.runtype/projects/{hash}/marathons/ */
|
|
43062
|
-
function getMarathonStateDir(projectDir) {
|
|
43063
|
-
return path$2.join(getProjectStateDir(projectDir), "marathons");
|
|
43064
|
-
}
|
|
43065
|
-
//#endregion
|
|
43066
43814
|
//#region src/marathon/checkpoint.ts
|
|
43067
43815
|
function defaultStateDir$2() {
|
|
43068
43816
|
return getMarathonStateDir();
|
|
@@ -43083,14 +43831,14 @@ function ensureMarathonFileCheckpoint(taskName, targetPath, stateDir, stateFileP
|
|
|
43083
43831
|
const normalizedTargetPath = path$2.resolve(targetPath);
|
|
43084
43832
|
const relativeTargetPath = path$2.relative(process.cwd(), normalizedTargetPath);
|
|
43085
43833
|
if (!relativeTargetPath || relativeTargetPath.startsWith("..")) return void 0;
|
|
43086
|
-
if (!fs$
|
|
43087
|
-
if (!fs$
|
|
43834
|
+
if (!fs$1.existsSync(normalizedTargetPath)) return void 0;
|
|
43835
|
+
if (!fs$1.statSync(normalizedTargetPath).isFile()) return void 0;
|
|
43088
43836
|
if (relativeTargetPath.replace(/\\/g, "/").startsWith(".runtype/")) return void 0;
|
|
43089
43837
|
if (normalizedTargetPath.startsWith(getRuntypeHomeDir() + path$2.sep)) return void 0;
|
|
43090
43838
|
const checkpointPath = resolveMarathonCheckpointPath(taskName, normalizedTargetPath, stateDir, stateFilePath);
|
|
43091
|
-
if (fs$
|
|
43092
|
-
fs$
|
|
43093
|
-
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);
|
|
43094
43842
|
return checkpointPath;
|
|
43095
43843
|
}
|
|
43096
43844
|
function restoreMarathonFileCheckpoint(taskName, targetPath, stateDir, stateFilePath) {
|
|
@@ -43101,13 +43849,13 @@ function restoreMarathonFileCheckpoint(taskName, targetPath, stateDir, stateFile
|
|
|
43101
43849
|
error: `Cannot restore checkpoint outside the current repository: ${normalizedTargetPath}`
|
|
43102
43850
|
};
|
|
43103
43851
|
const checkpointPath = resolveMarathonCheckpointPath(taskName, normalizedTargetPath, stateDir, stateFilePath);
|
|
43104
|
-
if (!fs$
|
|
43852
|
+
if (!fs$1.existsSync(checkpointPath)) return {
|
|
43105
43853
|
restored: false,
|
|
43106
43854
|
checkpointPath,
|
|
43107
43855
|
error: `No checkpoint found for ${normalizedTargetPath}`
|
|
43108
43856
|
};
|
|
43109
|
-
fs$
|
|
43110
|
-
fs$
|
|
43857
|
+
fs$1.mkdirSync(path$2.dirname(normalizedTargetPath), { recursive: true });
|
|
43858
|
+
fs$1.copyFileSync(checkpointPath, normalizedTargetPath);
|
|
43111
43859
|
return {
|
|
43112
43860
|
restored: true,
|
|
43113
43861
|
checkpointPath
|
|
@@ -43637,7 +44385,7 @@ function normalizeMarathonStatePath(candidatePath) {
|
|
|
43637
44385
|
}
|
|
43638
44386
|
function marathonStatePathExists(candidatePath) {
|
|
43639
44387
|
if (!candidatePath) return false;
|
|
43640
|
-
return fs$
|
|
44388
|
+
return fs$1.existsSync(path$2.resolve(candidatePath));
|
|
43641
44389
|
}
|
|
43642
44390
|
function isMarathonArtifactStatePath(candidatePath) {
|
|
43643
44391
|
if (!candidatePath) return false;
|
|
@@ -43798,7 +44546,7 @@ function sanitizeLoadedMarathonState(state) {
|
|
|
43798
44546
|
}
|
|
43799
44547
|
function loadState(filePath) {
|
|
43800
44548
|
try {
|
|
43801
|
-
const raw = fs$
|
|
44549
|
+
const raw = fs$1.readFileSync(filePath, "utf-8");
|
|
43802
44550
|
return sanitizeLoadedMarathonState(JSON.parse(raw));
|
|
43803
44551
|
} catch {
|
|
43804
44552
|
return null;
|
|
@@ -43806,7 +44554,7 @@ function loadState(filePath) {
|
|
|
43806
44554
|
}
|
|
43807
44555
|
function saveState(filePath, state, options) {
|
|
43808
44556
|
const dir = path$2.dirname(filePath);
|
|
43809
|
-
fs$
|
|
44557
|
+
fs$1.mkdirSync(dir, { recursive: true });
|
|
43810
44558
|
const stateToWrite = options?.stripSnapshotEvents && state.sessionSnapshots?.length ? {
|
|
43811
44559
|
...state,
|
|
43812
44560
|
sessionSnapshots: state.sessionSnapshots.map((snapshot) => ({
|
|
@@ -43814,7 +44562,7 @@ function saveState(filePath, state, options) {
|
|
|
43814
44562
|
rawEvents: []
|
|
43815
44563
|
}))
|
|
43816
44564
|
} : state;
|
|
43817
|
-
fs$
|
|
44565
|
+
fs$1.writeFileSync(filePath, JSON.stringify(stateToWrite, null, 2));
|
|
43818
44566
|
syncTreeLogWithState(filePath, state);
|
|
43819
44567
|
}
|
|
43820
44568
|
function extractRunTaskResumeState(state) {
|
|
@@ -43860,11 +44608,11 @@ function offsetNewContextCompactionSummaries(summaries, existingSummaryIds, sess
|
|
|
43860
44608
|
function findStateFile(name, stateDir) {
|
|
43861
44609
|
if (stateDir) return stateFilePath(name, stateDir);
|
|
43862
44610
|
const homePath = stateFilePath(name, getMarathonStateDir());
|
|
43863
|
-
if (fs$
|
|
44611
|
+
if (fs$1.existsSync(homePath)) return homePath;
|
|
43864
44612
|
const oldMarathonPath = stateFilePath(name, path$2.join(process.cwd(), ".runtype", "marathons"));
|
|
43865
|
-
if (fs$
|
|
44613
|
+
if (fs$1.existsSync(oldMarathonPath)) return oldMarathonPath;
|
|
43866
44614
|
const oldTaskPath = stateFilePath(name, path$2.join(process.cwd(), ".runtype", "tasks"));
|
|
43867
|
-
if (fs$
|
|
44615
|
+
if (fs$1.existsSync(oldTaskPath)) return oldTaskPath;
|
|
43868
44616
|
return homePath;
|
|
43869
44617
|
}
|
|
43870
44618
|
function findLatestStateFile(stateDir) {
|
|
@@ -43875,11 +44623,11 @@ function findLatestStateFile(stateDir) {
|
|
|
43875
44623
|
];
|
|
43876
44624
|
let latest = null;
|
|
43877
44625
|
for (const dir of dirs) {
|
|
43878
|
-
if (!fs$
|
|
43879
|
-
const files = fs$
|
|
44626
|
+
if (!fs$1.existsSync(dir)) continue;
|
|
44627
|
+
const files = fs$1.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
43880
44628
|
for (const file of files) {
|
|
43881
44629
|
const fullPath = path$2.join(dir, file);
|
|
43882
|
-
const stat = fs$
|
|
44630
|
+
const stat = fs$1.statSync(fullPath);
|
|
43883
44631
|
if (!latest || stat.mtimeMs > latest.mtime) latest = {
|
|
43884
44632
|
name: file.replace(".json", ""),
|
|
43885
44633
|
filePath: fullPath,
|
|
@@ -43963,7 +44711,7 @@ async function resolveMarathonStateResolution(options, taskName, filePath, promp
|
|
|
43963
44711
|
};
|
|
43964
44712
|
}
|
|
43965
44713
|
if (options.fresh) return { action: "fresh" };
|
|
43966
|
-
if (!fs$
|
|
44714
|
+
if (!fs$1.existsSync(filePath)) return { action: "fresh" };
|
|
43967
44715
|
if (!allowPrompt) throw new Error(`Local marathon state already exists at ${filePath}. Use --resume to continue it or --fresh to start over.`);
|
|
43968
44716
|
const choice = await promptForChoice({
|
|
43969
44717
|
reason: "existing-state",
|
|
@@ -44243,9 +44991,9 @@ function shouldIgnoreRepoEntry(entryPath) {
|
|
|
44243
44991
|
function safeReadTextFile(filePath) {
|
|
44244
44992
|
try {
|
|
44245
44993
|
if (isSensitivePath(normalizeToolPath(filePath).replace(/\\/g, "/"))) return null;
|
|
44246
|
-
const stat = fs$
|
|
44994
|
+
const stat = fs$1.statSync(filePath);
|
|
44247
44995
|
if (!stat.isFile() || stat.size > 1048576) return null;
|
|
44248
|
-
const buffer = fs$
|
|
44996
|
+
const buffer = fs$1.readFileSync(filePath);
|
|
44249
44997
|
if (buffer.includes(0)) return null;
|
|
44250
44998
|
return buffer.toString("utf-8");
|
|
44251
44999
|
} catch {
|
|
@@ -44253,14 +45001,14 @@ function safeReadTextFile(filePath) {
|
|
|
44253
45001
|
}
|
|
44254
45002
|
}
|
|
44255
45003
|
function walkRepo(startPath, visitor) {
|
|
44256
|
-
if (!fs$
|
|
45004
|
+
if (!fs$1.existsSync(startPath) || !fs$1.statSync(startPath).isDirectory()) return;
|
|
44257
45005
|
const stack = [startPath];
|
|
44258
45006
|
while (stack.length > 0) {
|
|
44259
45007
|
const currentDir = stack.pop();
|
|
44260
45008
|
if (!currentDir) continue;
|
|
44261
45009
|
let entries;
|
|
44262
45010
|
try {
|
|
44263
|
-
entries = fs$
|
|
45011
|
+
entries = fs$1.readdirSync(currentDir, { withFileTypes: true });
|
|
44264
45012
|
} catch {
|
|
44265
45013
|
continue;
|
|
44266
45014
|
}
|
|
@@ -44300,7 +45048,7 @@ function buildTree(dirPath, maxDepth, depth = 0) {
|
|
|
44300
45048
|
if (depth > maxDepth || shouldIgnoreRepoEntry(dirPath)) return [];
|
|
44301
45049
|
let entries;
|
|
44302
45050
|
try {
|
|
44303
|
-
entries = fs$
|
|
45051
|
+
entries = fs$1.readdirSync(dirPath, { withFileTypes: true });
|
|
44304
45052
|
} catch (error) {
|
|
44305
45053
|
const message = error instanceof Error ? error.message : String(error);
|
|
44306
45054
|
return [`${" ".repeat(depth)}[error reading ${normalizeToolPath(dirPath)}: ${message}]`];
|
|
@@ -44337,8 +45085,8 @@ function resolveCanonicalToolPath(toolPath, allowMissing) {
|
|
|
44337
45085
|
let remainingSegments = absolutePath.slice(canonicalPath.length).split(path$2.sep);
|
|
44338
45086
|
let symlinksFollowed = 0;
|
|
44339
45087
|
try {
|
|
44340
|
-
if (fs$
|
|
44341
|
-
canonicalPath: fs$
|
|
45088
|
+
if (fs$1.existsSync(absolutePath)) return {
|
|
45089
|
+
canonicalPath: fs$1.realpathSync.native(absolutePath),
|
|
44342
45090
|
exists: true
|
|
44343
45091
|
};
|
|
44344
45092
|
if (!allowMissing) return null;
|
|
@@ -44350,25 +45098,25 @@ function resolveCanonicalToolPath(toolPath, allowMissing) {
|
|
|
44350
45098
|
continue;
|
|
44351
45099
|
}
|
|
44352
45100
|
const candidatePath = path$2.join(canonicalPath, segment);
|
|
44353
|
-
const entry = fs$
|
|
45101
|
+
const entry = fs$1.lstatSync(candidatePath, { throwIfNoEntry: false });
|
|
44354
45102
|
if (!entry) {
|
|
44355
45103
|
canonicalPath = candidatePath;
|
|
44356
45104
|
continue;
|
|
44357
45105
|
}
|
|
44358
45106
|
if (entry.isSymbolicLink()) {
|
|
44359
45107
|
if (++symlinksFollowed > MAX_TOOL_PATH_SYMLINKS) return null;
|
|
44360
|
-
const target = fs$
|
|
45108
|
+
const target = fs$1.readlinkSync(candidatePath);
|
|
44361
45109
|
const targetRoot = path$2.isAbsolute(target) ? path$2.parse(target).root : "";
|
|
44362
45110
|
if (targetRoot) canonicalPath = targetRoot;
|
|
44363
45111
|
remainingSegments = [...target.slice(targetRoot.length).split(path$2.sep), ...remainingSegments];
|
|
44364
45112
|
continue;
|
|
44365
45113
|
}
|
|
44366
45114
|
if (remainingSegments.length > 0 && !entry.isDirectory()) return null;
|
|
44367
|
-
canonicalPath = fs$
|
|
45115
|
+
canonicalPath = fs$1.realpathSync.native(candidatePath);
|
|
44368
45116
|
}
|
|
44369
45117
|
return {
|
|
44370
45118
|
canonicalPath,
|
|
44371
|
-
exists: fs$
|
|
45119
|
+
exists: fs$1.existsSync(canonicalPath)
|
|
44372
45120
|
};
|
|
44373
45121
|
} catch {
|
|
44374
45122
|
return null;
|
|
@@ -44394,7 +45142,7 @@ function resolveToolPath(toolPath, options = {}) {
|
|
|
44394
45142
|
ok: false,
|
|
44395
45143
|
error: `Path does not exist: ${requestedPath}`
|
|
44396
45144
|
};
|
|
44397
|
-
const workspaceRoot = fs$
|
|
45145
|
+
const workspaceRoot = fs$1.realpathSync.native(process.cwd());
|
|
44398
45146
|
const extraRoots = (options.allowedRoots || []).map((rootPath) => canonicalizeAllowedRoot(rootPath));
|
|
44399
45147
|
const matchedRoot = [...extraRoots, workspaceRoot].find((rootPath) => isPathWithinRoot(resolved.canonicalPath, rootPath));
|
|
44400
45148
|
if (!matchedRoot) return {
|
|
@@ -44415,7 +45163,7 @@ function resolveToolPath(toolPath, options = {}) {
|
|
|
44415
45163
|
}
|
|
44416
45164
|
}
|
|
44417
45165
|
if (resolved.exists) {
|
|
44418
|
-
const stat = fs$
|
|
45166
|
+
const stat = fs$1.statSync(resolved.canonicalPath);
|
|
44419
45167
|
if (options.requireFile && !stat.isFile()) return {
|
|
44420
45168
|
ok: false,
|
|
44421
45169
|
error: `Path is not a file: ${requestedPath}`
|
|
@@ -44463,7 +45211,7 @@ function createDefaultLocalTools(context) {
|
|
|
44463
45211
|
allowedRoots: allowedReadRoots
|
|
44464
45212
|
});
|
|
44465
45213
|
if (!resolvedPath.ok) return `Error: ${resolvedPath.error}`;
|
|
44466
|
-
return fs$
|
|
45214
|
+
return fs$1.readFileSync(resolvedPath.resolvedPath, "utf-8");
|
|
44467
45215
|
}
|
|
44468
45216
|
},
|
|
44469
45217
|
write_file: {
|
|
@@ -44486,10 +45234,10 @@ function createDefaultLocalTools(context) {
|
|
|
44486
45234
|
const resolvedPath = resolveToolPath(String(args.path || ""), { allowMissing: true });
|
|
44487
45235
|
if (!resolvedPath.ok) return `Error: ${resolvedPath.error}`;
|
|
44488
45236
|
const content = String(args.content || "");
|
|
44489
|
-
if (fs$
|
|
45237
|
+
if (fs$1.existsSync(resolvedPath.resolvedPath) && fs$1.statSync(resolvedPath.resolvedPath).isDirectory()) return `Error: Path is a directory: ${String(args.path || "")}`;
|
|
44490
45238
|
const dir = path$2.dirname(resolvedPath.resolvedPath);
|
|
44491
|
-
fs$
|
|
44492
|
-
fs$
|
|
45239
|
+
fs$1.mkdirSync(dir, { recursive: true });
|
|
45240
|
+
fs$1.writeFileSync(resolvedPath.resolvedPath, content);
|
|
44493
45241
|
return "ok";
|
|
44494
45242
|
}
|
|
44495
45243
|
},
|
|
@@ -44505,7 +45253,7 @@ function createDefaultLocalTools(context) {
|
|
|
44505
45253
|
execute: async (args) => {
|
|
44506
45254
|
const resolvedPath = resolveToolPath(String(args.path || "."), { requireDirectory: true });
|
|
44507
45255
|
if (!resolvedPath.ok) return `Error: ${resolvedPath.error}`;
|
|
44508
|
-
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");
|
|
44509
45257
|
}
|
|
44510
45258
|
},
|
|
44511
45259
|
search_repo: {
|
|
@@ -44669,8 +45417,8 @@ function createCheckpointedWriteFileTool(taskName, stateDir, planPath, stateFile
|
|
|
44669
45417
|
const content = String(args.content || "");
|
|
44670
45418
|
ensureMarathonFileCheckpoint(taskName, resolvedPath.resolvedPath, stateDir, stateFilePath);
|
|
44671
45419
|
const dir = path$2.dirname(resolvedPath.resolvedPath);
|
|
44672
|
-
fs$
|
|
44673
|
-
fs$
|
|
45420
|
+
fs$1.mkdirSync(dir, { recursive: true });
|
|
45421
|
+
fs$1.writeFileSync(resolvedPath.resolvedPath, content);
|
|
44674
45422
|
const ext = path$2.extname(resolvedPath.resolvedPath).toLowerCase();
|
|
44675
45423
|
if (ext === ".js" || ext === ".jsx") {
|
|
44676
45424
|
const syntaxError = quickJsSyntaxCheck(content);
|
|
@@ -44746,7 +45494,7 @@ function createEditFileTool(taskName, stateDir, planPath, stateFilePath) {
|
|
|
44746
45494
|
if (oldString === newString) return "Error: old_string and new_string are identical";
|
|
44747
45495
|
let content;
|
|
44748
45496
|
try {
|
|
44749
|
-
content = fs$
|
|
45497
|
+
content = fs$1.readFileSync(resolvedPath.resolvedPath, "utf-8");
|
|
44750
45498
|
} catch {
|
|
44751
45499
|
return `Error: Could not read file: ${String(args.path || "")}`;
|
|
44752
45500
|
}
|
|
@@ -44757,7 +45505,7 @@ function createEditFileTool(taskName, stateDir, planPath, stateFilePath) {
|
|
|
44757
45505
|
}
|
|
44758
45506
|
ensureMarathonFileCheckpoint(taskName, resolvedPath.resolvedPath, stateDir, stateFilePath);
|
|
44759
45507
|
const updated = replaceAll ? content.split(oldString).join(newString) : content.replace(oldString, newString);
|
|
44760
|
-
fs$
|
|
45508
|
+
fs$1.writeFileSync(resolvedPath.resolvedPath, updated);
|
|
44761
45509
|
const ext = path$2.extname(resolvedPath.resolvedPath).toLowerCase();
|
|
44762
45510
|
if (ext === ".js" || ext === ".jsx") {
|
|
44763
45511
|
const syntaxError = quickJsSyntaxCheck(updated);
|
|
@@ -44809,13 +45557,13 @@ function createReadOffloadedOutputTool(taskName, stateDir, stateFilePath) {
|
|
|
44809
45557
|
};
|
|
44810
45558
|
const idEmbeddedSha256 = /^art_([0-9a-f]{64})$/.exec(outputId)?.[1];
|
|
44811
45559
|
const outputPath = path$2.join(getOffloadedOutputDir(taskName, stateDir, stateFilePath), `${outputId}.txt`);
|
|
44812
|
-
if (!fs$
|
|
45560
|
+
if (!fs$1.existsSync(outputPath) || !fs$1.statSync(outputPath).isFile()) {
|
|
44813
45561
|
const resolvedStateFilePath = getStateFilePathForTask(taskName, stateDir, stateFilePath);
|
|
44814
45562
|
const log = loadTreeLog(treeLogPathForStateFile(resolvedStateFilePath));
|
|
44815
45563
|
const artifact = log ? findArtifactPayload(log, outputId) : void 0;
|
|
44816
45564
|
if (artifact) {
|
|
44817
45565
|
const ledgerPath = path$2.resolve(path$2.dirname(treeLogPathForStateFile(resolvedStateFilePath)), artifact.relativePath);
|
|
44818
|
-
if (fs$
|
|
45566
|
+
if (fs$1.existsSync(ledgerPath) && fs$1.statSync(ledgerPath).isFile()) return verifyContent(fs$1.readFileSync(ledgerPath, "utf-8"), artifact.sha256);
|
|
44819
45567
|
return [
|
|
44820
45568
|
`Error: offloaded output recorded in the context ledger but missing locally: ${outputId}`,
|
|
44821
45569
|
artifact.host ? `Stored on host: ${artifact.host}` : void 0,
|
|
@@ -44824,7 +45572,7 @@ function createReadOffloadedOutputTool(taskName, stateDir, stateFilePath) {
|
|
|
44824
45572
|
}
|
|
44825
45573
|
return `Error: offloaded output not found: ${outputId}`;
|
|
44826
45574
|
}
|
|
44827
|
-
return verifyContent(fs$
|
|
45575
|
+
return verifyContent(fs$1.readFileSync(outputPath, "utf-8"), idEmbeddedSha256);
|
|
44828
45576
|
}
|
|
44829
45577
|
};
|
|
44830
45578
|
}
|
|
@@ -45008,7 +45756,7 @@ function resolveLedgerToolResult(tool, stateFilePath, log) {
|
|
|
45008
45756
|
if (!artifact) return result;
|
|
45009
45757
|
const artifactPath = path$2.resolve(path$2.dirname(treeLogPathForStateFile(stateFilePath)), artifact.relativePath);
|
|
45010
45758
|
try {
|
|
45011
|
-
if (fs$
|
|
45759
|
+
if (fs$1.existsSync(artifactPath) && fs$1.statSync(artifactPath).isFile()) return fs$1.readFileSync(artifactPath, "utf-8");
|
|
45012
45760
|
} catch {}
|
|
45013
45761
|
return result;
|
|
45014
45762
|
}
|
|
@@ -45222,8 +45970,8 @@ function offloadToolOutput(taskName, toolName, output, options = {}, stateDir) {
|
|
|
45222
45970
|
const dir = artifactStoreDirForStateFile(stateFilePath);
|
|
45223
45971
|
const fileName = `${outputId}.txt`;
|
|
45224
45972
|
const filePath = path$2.join(dir, fileName);
|
|
45225
|
-
fs$
|
|
45226
|
-
if (!fs$
|
|
45973
|
+
fs$1.mkdirSync(dir, { recursive: true });
|
|
45974
|
+
if (!fs$1.existsSync(filePath)) fs$1.writeFileSync(filePath, output, "utf-8");
|
|
45227
45975
|
recordOffloadedArtifact({
|
|
45228
45976
|
taskName,
|
|
45229
45977
|
toolName,
|
|
@@ -45373,14 +46121,14 @@ const RULES_DIR = ".marathon/rules";
|
|
|
45373
46121
|
function loadRules(cwd) {
|
|
45374
46122
|
const baseCwd = cwd || process.cwd();
|
|
45375
46123
|
const rulesDir = path$2.resolve(baseCwd, RULES_DIR);
|
|
45376
|
-
if (!fs$
|
|
46124
|
+
if (!fs$1.existsSync(rulesDir)) return [];
|
|
45377
46125
|
const rules = [];
|
|
45378
46126
|
try {
|
|
45379
|
-
const entries = fs$
|
|
46127
|
+
const entries = fs$1.readdirSync(rulesDir).filter((f) => f.endsWith(".md"));
|
|
45380
46128
|
for (const entry of entries) {
|
|
45381
46129
|
const filePath = path$2.join(rulesDir, entry);
|
|
45382
46130
|
try {
|
|
45383
|
-
const parsed = parseRuleFile(fs$
|
|
46131
|
+
const parsed = parseRuleFile(fs$1.readFileSync(filePath, "utf-8"));
|
|
45384
46132
|
rules.push({
|
|
45385
46133
|
...parsed,
|
|
45386
46134
|
filePath
|
|
@@ -45516,7 +46264,7 @@ function resolveErrorHandlingForPhase(phase, cliFallbackModel, milestoneFallback
|
|
|
45516
46264
|
//#region src/marathon/playbook-loader.ts
|
|
45517
46265
|
const PLAYBOOKS_DIR = ".runtype/marathons/playbooks";
|
|
45518
46266
|
function getCandidatePaths(nameOrPath, cwd) {
|
|
45519
|
-
const home = os$
|
|
46267
|
+
const home = os$1.homedir();
|
|
45520
46268
|
return [
|
|
45521
46269
|
path$2.resolve(cwd, nameOrPath),
|
|
45522
46270
|
path$2.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.yaml`),
|
|
@@ -45556,7 +46304,7 @@ async function loadModulePlaybook(filePath) {
|
|
|
45556
46304
|
return config;
|
|
45557
46305
|
}
|
|
45558
46306
|
function parsePlaybookFile(filePath) {
|
|
45559
|
-
const raw = fs$
|
|
46307
|
+
const raw = fs$1.readFileSync(filePath, "utf-8");
|
|
45560
46308
|
if (path$2.extname(filePath).toLowerCase() === ".json") return JSON.parse(raw);
|
|
45561
46309
|
return parse(raw);
|
|
45562
46310
|
}
|
|
@@ -45618,7 +46366,7 @@ async function loadPlaybookPlugins(plugins, playbookFilePath, playbookName) {
|
|
|
45618
46366
|
const resolved = path$2.resolve(baseDir, plugin);
|
|
45619
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.`);
|
|
45620
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.`);
|
|
45621
|
-
if (!fs$
|
|
46369
|
+
if (!fs$1.existsSync(resolved)) throw new Error(`Playbook '${playbookName}': plugin "${plugin}" not found at ${resolved}.`);
|
|
45622
46370
|
const mod = await import(pathToFileURL(resolved).href);
|
|
45623
46371
|
if (typeof mod.default !== "function") throw new Error(`Playbook '${playbookName}': plugin "${plugin}" must export a default function: export default ({ registerWorkflowHook }) => { ... }`);
|
|
45624
46372
|
await mod.default({ registerWorkflowHook });
|
|
@@ -45651,7 +46399,7 @@ function toWorkflowConfig(config) {
|
|
|
45651
46399
|
async function loadPlaybook(nameOrPath, cwd) {
|
|
45652
46400
|
const candidates = getCandidatePaths(nameOrPath, cwd || process.cwd());
|
|
45653
46401
|
for (const candidate of candidates) {
|
|
45654
|
-
if (!fs$
|
|
46402
|
+
if (!fs$1.existsSync(candidate) || fs$1.statSync(candidate).isDirectory()) continue;
|
|
45655
46403
|
const ext = path$2.extname(candidate).toLowerCase();
|
|
45656
46404
|
const config = MODULE_PLAYBOOK_EXTENSIONS.has(ext) ? await loadModulePlaybook(candidate) : parsePlaybookFile(candidate);
|
|
45657
46405
|
validatePlaybook(config, candidate);
|
|
@@ -46043,7 +46791,7 @@ async function taskAction(agent, options) {
|
|
|
46043
46791
|
forcedResumeFilePath = stateResolution.filePath;
|
|
46044
46792
|
} else {
|
|
46045
46793
|
resumeRequested = false;
|
|
46046
|
-
if (options.fresh && fs$
|
|
46794
|
+
if (options.fresh && fs$1.existsSync(filePath)) {
|
|
46047
46795
|
if (useStartupShell) setStartupStatus("starting fresh");
|
|
46048
46796
|
else console.log(chalk.gray(`Starting fresh and ignoring saved local state at ${filePath}`));
|
|
46049
46797
|
}
|
|
@@ -48877,17 +49625,17 @@ const deployCommand = new Command("deploy").description("Export an agent and sca
|
|
|
48877
49625
|
}
|
|
48878
49626
|
slugSet.add(slug);
|
|
48879
49627
|
}
|
|
48880
|
-
fs$
|
|
48881
|
-
fs$
|
|
49628
|
+
fs$1.mkdirSync(path$2.join(outDir, "agents"), { recursive: true });
|
|
49629
|
+
fs$1.mkdirSync(path$2.join(outDir, "packages"), { recursive: true });
|
|
48882
49630
|
for (const { name, def } of agentDefs) {
|
|
48883
49631
|
const filename = `${slugify(name)}.json`;
|
|
48884
|
-
fs$
|
|
49632
|
+
fs$1.writeFileSync(path$2.join(outDir, "agents", filename), JSON.stringify(def, null, 2));
|
|
48885
49633
|
console.log(` Wrote agents/${filename}`);
|
|
48886
49634
|
}
|
|
48887
49635
|
const agentNames = agentDefs.map((a) => a.name);
|
|
48888
49636
|
let monorepoRoot = process.cwd();
|
|
48889
49637
|
for (let i = 0; i < 8; i++) {
|
|
48890
|
-
if (fs$
|
|
49638
|
+
if (fs$1.existsSync(path$2.join(monorepoRoot, "pnpm-workspace.yaml"))) break;
|
|
48891
49639
|
const parent = path$2.dirname(monorepoRoot);
|
|
48892
49640
|
if (parent === monorepoRoot) break;
|
|
48893
49641
|
monorepoRoot = parent;
|
|
@@ -48903,35 +49651,35 @@ const deployCommand = new Command("deploy").description("Export an agent and sca
|
|
|
48903
49651
|
const secretNames = Array.from(allSecrets);
|
|
48904
49652
|
const optionalSettings = Array.from(allOptionalSettings.values());
|
|
48905
49653
|
if (target === "cloudflare") {
|
|
48906
|
-
fs$
|
|
48907
|
-
fs$
|
|
48908
|
-
fs$
|
|
48909
|
-
fs$
|
|
48910
|
-
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());
|
|
48911
49659
|
const setupPath = path$2.join(outDir, "setup.sh");
|
|
48912
|
-
fs$
|
|
48913
|
-
fs$
|
|
48914
|
-
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));
|
|
48915
49663
|
} else if (target === "vercel") {
|
|
48916
|
-
fs$
|
|
48917
|
-
fs$
|
|
48918
|
-
fs$
|
|
48919
|
-
fs$
|
|
48920
|
-
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());
|
|
48921
49669
|
const setupPath = path$2.join(outDir, "setup.sh");
|
|
48922
|
-
fs$
|
|
48923
|
-
fs$
|
|
48924
|
-
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));
|
|
48925
49673
|
} else {
|
|
48926
|
-
fs$
|
|
48927
|
-
fs$
|
|
48928
|
-
fs$
|
|
48929
|
-
fs$
|
|
48930
|
-
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:*"));
|
|
48931
49679
|
const setupPath = path$2.join(outDir, "setup.sh");
|
|
48932
|
-
fs$
|
|
48933
|
-
fs$
|
|
48934
|
-
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));
|
|
48935
49683
|
}
|
|
48936
49684
|
console.log("");
|
|
48937
49685
|
console.log(chalk.green(`✓ Scaffold written to ${outDir}`));
|
|
@@ -49000,13 +49748,13 @@ const agentExportRuntimeCommand = new Command("export-runtime").description("Exp
|
|
|
49000
49748
|
if (options.format === "json") {
|
|
49001
49749
|
const exported = await client.agents.exportRuntime(id, selector.params);
|
|
49002
49750
|
const body = `${JSON.stringify(exported, null, 2)}\n`;
|
|
49003
|
-
if (options.output) fs$
|
|
49751
|
+
if (options.output) fs$1.writeFileSync(path$2.resolve(options.output), body);
|
|
49004
49752
|
else process.stdout.write(body);
|
|
49005
49753
|
return;
|
|
49006
49754
|
}
|
|
49007
49755
|
const bundle = await client.agents.exportRuntimeBundle(id, selector.params);
|
|
49008
49756
|
const outPath = path$2.resolve(options.output ?? bundle.fileName);
|
|
49009
|
-
fs$
|
|
49757
|
+
fs$1.writeFileSync(outPath, bundle.bytes);
|
|
49010
49758
|
console.log(JSON.stringify({
|
|
49011
49759
|
path: outPath,
|
|
49012
49760
|
bytes: bundle.bytes.byteLength,
|
|
@@ -56536,7 +57284,7 @@ npm install -g @runtypelabs/cli
|
|
|
56536
57284
|
runtype auth status
|
|
56537
57285
|
\`\`\`
|
|
56538
57286
|
|
|
56539
|
-
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.
|
|
56540
57288
|
|
|
56541
57289
|
### 2. Sign the user up from the terminal
|
|
56542
57290
|
|
|
@@ -56625,7 +57373,9 @@ The signup commands wrap the agent auth protocol documented at \`https://runtype
|
|
|
56625
57373
|
- \`runtype auth claim <email>\` — re-send or change the email for a pending signup
|
|
56626
57374
|
- \`runtype auth verify <code>\` — finish signup with the 6-digit code
|
|
56627
57375
|
- \`runtype auth login --api-key <key>\` — privately store an existing credential; not an account-creation command
|
|
56628
|
-
- \`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)
|
|
56629
57379
|
- \`runtype auth whoami --no-tty\` — verify the effective credential (environment key first, then stored login)
|
|
56630
57380
|
|
|
56631
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.
|
|
@@ -58497,7 +59247,7 @@ function readIdentity(value) {
|
|
|
58497
59247
|
function resolveDeviceListenName(requested) {
|
|
58498
59248
|
const explicit = requested?.trim();
|
|
58499
59249
|
if (explicit) return explicit.slice(0, 120);
|
|
58500
|
-
const hostname = os
|
|
59250
|
+
const hostname = os.hostname().trim();
|
|
58501
59251
|
if (!hostname) return "This computer";
|
|
58502
59252
|
return hostname.slice(0, 120);
|
|
58503
59253
|
}
|
|
@@ -58517,8 +59267,8 @@ async function signDeviceListenChallenge(identity, input) {
|
|
|
58517
59267
|
});
|
|
58518
59268
|
}
|
|
58519
59269
|
function deviceListenEncryptionKey() {
|
|
58520
|
-
const hostname = os
|
|
58521
|
-
const username = os
|
|
59270
|
+
const hostname = os.hostname();
|
|
59271
|
+
const username = os.userInfo().username;
|
|
58522
59272
|
return createHash("sha256").update(`runtype-device-listen-${hostname}-${username}`).digest("hex");
|
|
58523
59273
|
}
|
|
58524
59274
|
function isCorruptStoreError(error) {
|
|
@@ -59072,8 +59822,8 @@ var MacOSDeviceProvider = class {
|
|
|
59072
59822
|
constructor(options = {}) {
|
|
59073
59823
|
this.platform = options.platform ?? process.platform;
|
|
59074
59824
|
this.architecture = options.architecture ?? process.arch;
|
|
59075
|
-
this.operatingSystemRelease = options.operatingSystemRelease ?? os
|
|
59076
|
-
this.homeDirectory = options.homeDirectory ?? os
|
|
59825
|
+
this.operatingSystemRelease = options.operatingSystemRelease ?? os.release();
|
|
59826
|
+
this.homeDirectory = options.homeDirectory ?? os.homedir();
|
|
59077
59827
|
this.workingDirectory = options.workingDirectory ?? process.cwd();
|
|
59078
59828
|
this.environment = options.environment ?? process.env;
|
|
59079
59829
|
this.sidecarTimeoutMs = options.sidecarTimeoutMs ?? DEFAULT_SIDECAR_TIMEOUT_MS;
|
|
@@ -59775,6 +60525,7 @@ function registerGlobalOptionForwarding(program) {
|
|
|
59775
60525
|
const options = actionCommand.optsWithGlobals();
|
|
59776
60526
|
const invocation = {
|
|
59777
60527
|
apiUrl: apiUrls.at(-1),
|
|
60528
|
+
org: typeof options.org === "string" && options.org ? options.org : void 0,
|
|
59778
60529
|
expectApiUrl: options.expectApiUrl,
|
|
59779
60530
|
expectOrg: options.expectOrg,
|
|
59780
60531
|
noInput: options.input === false || options.noInput === true,
|
|
@@ -59800,10 +60551,12 @@ function registerGlobalOptionForwarding(program) {
|
|
|
59800
60551
|
//#region src/index.ts
|
|
59801
60552
|
loadEnv();
|
|
59802
60553
|
const program = new Command();
|
|
59803
|
-
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());
|
|
59804
60555
|
program.addCommand(initCommand);
|
|
59805
60556
|
program.addCommand(loginCommand);
|
|
59806
60557
|
program.addCommand(authCommand);
|
|
60558
|
+
program.addCommand(linkCommand);
|
|
60559
|
+
program.addCommand(unlinkCommand);
|
|
59807
60560
|
program.addCommand(flowsCommand);
|
|
59808
60561
|
program.addCommand(recordsCommand);
|
|
59809
60562
|
program.addCommand(collectionsCommand);
|
|
@@ -59902,7 +60655,7 @@ run().catch((error) => {
|
|
|
59902
60655
|
}
|
|
59903
60656
|
});
|
|
59904
60657
|
async function handleNoCommand() {
|
|
59905
|
-
if (!await new CredentialStore().
|
|
60658
|
+
if (!((await new CredentialStore().listAccounts()).length > 0) && !process.env.RUNTYPE_API_KEY) {
|
|
59906
60659
|
console.log(chalk.cyan("\nWelcome to Runtype CLI!\n"));
|
|
59907
60660
|
console.log("It looks like this is your first time. Run the setup wizard:");
|
|
59908
60661
|
console.log(` ${chalk.green("runtype init")}\n`);
|