@runneth/cli 0.0.0-sha.08ddca3b68de.production

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,3732 @@
1
+ #!/usr/bin/env node
2
+ import { $ as resolveOAuthClientCredentialOptions, A as RunnethCliReplacementInterruptedError, D as sendRunnethCliShutdownRequest, E as sendRunnethCliRequest, G as resolveRunnethConversationOptions, I as runRunnethConversationMachineCommand, K as runRunnethConversation, L as resolveCliDisplayRequest, M as runWithRunnethCliReplacement, N as writeRunnethCliFileAtomically, O as resolveRunnethCliDaemonInstallationPath, P as require_proper_lockfile, Q as readOAuthCredentialStatus, R as writeCliDisplay, S as ensureDaemon, V as fetchConversationSshTarget, W as parseRunnethConversationArgs, X as logoutOAuthCredential, Y as loginWithOAuth, _ as assertRunnethVmCopyTargetsUseSameResource, a as normalizeRunnethSshTargetName, at as __require, b as RunnethCliRequestTimeoutError, g as setDefaultRunnethSshTarget, h as saveRunnethSshTarget, i as installRunnethSshAccess, it as __commonJSMin, j as assertRunnethCliReplacementStopped, k as runWithRunnethCliDaemonGate, m as runRunnethSshProxy, n as installRunnethSkills, nt as resolveRunnethCliHome, o as readRunnethSshTargetStore, ot as __toESM, p as runOpenSsh, rt as resolveSocketPath, s as removeRunnethSshTarget, tt as resolveOAuthToken, u as resolveRunnethSshTarget, v as runRunnethVmCopy, w as normalizeSessionName, y as RunnethCliDaemonRuntime, z as RUNNETH_CONVERSATION_DEFAULT_POLL_INTERVAL_MS } from "./src.js";
3
+ import { resolveBuildDefaultMcpResourceUrl } from "./build-defaults.js";
4
+ import { mkdir, readFile, realpath, rm } from "node:fs/promises";
5
+ import path from "node:path";
6
+ import process$1 from "node:process";
7
+ import { fileURLToPath } from "node:url";
8
+ import { createHash } from "node:crypto";
9
+ import { spawn } from "node:child_process";
10
+ import os from "node:os";
11
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
12
+ import { emitKeypressEvents } from "node:readline";
13
+ //#region src/copy-progress.ts
14
+ const COPY_PROGRESS_RENDER_INTERVAL_MS = 200;
15
+ const COPY_PROGRESS_RENDER_INTERVAL_NON_TTY_MS = 1e4;
16
+ const formatErrorMessage$1 = (error) => {
17
+ return error instanceof Error ? error.message : String(error);
18
+ };
19
+ const writeOutput = async (output, chunk) => {
20
+ await new Promise((resolve, reject) => {
21
+ let settled = false;
22
+ const rejectWrite = (error) => {
23
+ if (settled) return;
24
+ settled = true;
25
+ reject(error);
26
+ };
27
+ const onError = (error) => {
28
+ rejectWrite(error);
29
+ };
30
+ output.once("error", onError);
31
+ try {
32
+ output.write(chunk, (error) => {
33
+ if (error !== null && error !== void 0) {
34
+ rejectWrite(error);
35
+ return;
36
+ }
37
+ if (settled) return;
38
+ settled = true;
39
+ output.off("error", onError);
40
+ resolve();
41
+ });
42
+ } catch (error) {
43
+ output.off("error", onError);
44
+ rejectWrite(error);
45
+ }
46
+ });
47
+ };
48
+ const formatCopyByteCount = (bytes) => {
49
+ const units = [
50
+ "B",
51
+ "KiB",
52
+ "MiB",
53
+ "GiB",
54
+ "TiB"
55
+ ];
56
+ let unitIndex = 0;
57
+ let value = bytes;
58
+ while (value >= 1024 && unitIndex < units.length - 1) {
59
+ value /= 1024;
60
+ unitIndex += 1;
61
+ }
62
+ const unit = units[unitIndex];
63
+ if (unit === void 0) throw new Error(`Copy byte count unit index out of range: ${String(unitIndex)}`);
64
+ return `${unitIndex === 0 ? String(value) : value.toFixed(value >= 100 ? 0 : 1)} ${unit}`;
65
+ };
66
+ const formatCopyDuration = (seconds) => {
67
+ if (!Number.isFinite(seconds) || seconds < 0) throw new Error(`Invalid copy duration: ${String(seconds)}`);
68
+ const totalSeconds = Math.ceil(seconds);
69
+ if (totalSeconds < 60) return `${String(totalSeconds)}s`;
70
+ const minutes = Math.floor(totalSeconds / 60);
71
+ const remainingSeconds = totalSeconds % 60;
72
+ return `${String(minutes)}m${String(remainingSeconds).padStart(2, "0")}s`;
73
+ };
74
+ const createCopyProgressReporter = (input) => {
75
+ let lastRenderAtMs = 0;
76
+ let lastRenderedLine = "";
77
+ let uploadStartedAtMs = null;
78
+ const clearLiveLine = async () => {
79
+ if (input.interactive && lastRenderedLine.length > 0) {
80
+ await writeOutput(input.output, `\r${" ".repeat(lastRenderedLine.length)}\r`);
81
+ lastRenderedLine = "";
82
+ }
83
+ };
84
+ const render = async (line, force, completeLine = false) => {
85
+ const now = Date.now();
86
+ const interval = input.interactive ? COPY_PROGRESS_RENDER_INTERVAL_MS : COPY_PROGRESS_RENDER_INTERVAL_NON_TTY_MS;
87
+ if (!force && now - lastRenderAtMs < interval) return;
88
+ lastRenderAtMs = now;
89
+ if (input.interactive) {
90
+ const padding = Math.max(0, lastRenderedLine.length - line.length);
91
+ await writeOutput(input.output, `\r${line}${" ".repeat(padding)}${completeLine ? "\n" : ""}`);
92
+ lastRenderedLine = completeLine ? "" : line;
93
+ return;
94
+ }
95
+ await writeOutput(input.output, `${line}\n`);
96
+ };
97
+ const requireUploadStartedAtMs = (label) => {
98
+ if (uploadStartedAtMs === null) throw new Error(`Copy progress ${label} arrived before upload-started`);
99
+ return uploadStartedAtMs;
100
+ };
101
+ const report = async (event) => {
102
+ if (event.type === "archive-preparing") {
103
+ await render("Runneth VM copy: preparing source archive", true);
104
+ return;
105
+ }
106
+ if (event.type === "archive-ready") {
107
+ await render(`Runneth VM copy: archived ${formatCopyByteCount(event.bytesArchived)} in ${String(event.entriesArchived)} entries (${formatCopyByteCount(event.archiveByteLength)} archive); preparing destination upload`, true);
108
+ return;
109
+ }
110
+ if (event.type === "upload-started") {
111
+ uploadStartedAtMs = Date.now();
112
+ await render(`Runneth VM copy: uploading 0 B / ${formatCopyByteCount(event.totalBytes)} (0%)`, true);
113
+ return;
114
+ }
115
+ if (event.type === "range-uploaded") {
116
+ const startedAtMs = requireUploadStartedAtMs(`event ${event.type}`);
117
+ const elapsedSeconds = Math.max(.001, (Date.now() - startedAtMs) / 1e3);
118
+ const bytesPerSecond = event.uploadedBytes / elapsedSeconds;
119
+ const percent = Math.min(100, Math.floor(event.uploadedBytes / event.totalBytes * 100));
120
+ const etaSeconds = (event.totalBytes - event.uploadedBytes) / bytesPerSecond;
121
+ await render(`Runneth VM copy: uploading ${formatCopyByteCount(event.uploadedBytes)} / ${formatCopyByteCount(event.totalBytes)} (${String(percent)}%) at ${formatCopyByteCount(bytesPerSecond)}/s, eta ${formatCopyDuration(etaSeconds)}`, event.uploadedBytes >= event.totalBytes);
122
+ return;
123
+ }
124
+ if (event.type === "archive-applying") {
125
+ await render(`Runneth VM copy: uploaded ${formatCopyByteCount(event.archiveByteLength)}; applying archive`, true, true);
126
+ return;
127
+ }
128
+ throw new Error(`Unknown copy progress event: ${JSON.stringify(event)}`);
129
+ };
130
+ return {
131
+ finishFailure: async (error) => {
132
+ try {
133
+ await clearLiveLine();
134
+ } catch (cleanupError) {
135
+ throw new AggregateError([error, cleanupError], `Runneth VM copy failed: ${formatErrorMessage$1(error)}; progress output cleanup failed: ${formatErrorMessage$1(cleanupError)}`);
136
+ }
137
+ throw error;
138
+ },
139
+ report
140
+ };
141
+ };
142
+ //#endregion
143
+ //#region src/ssh-stdio.ts
144
+ const ALLOWED_PROCESS_SIGNALS = [
145
+ "SIGHUP",
146
+ "SIGINT",
147
+ "SIGKILL",
148
+ "SIGTERM"
149
+ ];
150
+ const DEFAULT_EXEC_TIMEOUT_MS = 6e5;
151
+ const DEFAULT_MASTER_READY_TIMEOUT_MS = 1e4;
152
+ const DEFAULT_MAX_FRAME_BYTES = 1048576;
153
+ const DEFAULT_MAX_PROCESSES = 16;
154
+ const PROCESS_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/u;
155
+ const nowIso = () => {
156
+ return (/* @__PURE__ */ new Date()).toISOString();
157
+ };
158
+ const isRecord$4 = (value) => {
159
+ return typeof value === "object" && value !== null && !Array.isArray(value);
160
+ };
161
+ const errorMessage = (error) => {
162
+ return error instanceof Error ? error.message : String(error);
163
+ };
164
+ const parseString = (record, key) => {
165
+ const value = record[key];
166
+ if (typeof value !== "string" || value.length === 0) throw new Error(`${key} must be a non-empty string`);
167
+ return value;
168
+ };
169
+ const parseProcessId = (value) => {
170
+ if (!PROCESS_ID_PATTERN.test(value)) throw new Error(`Invalid process id: ${value}. Use letters, numbers, dot, underscore, dash, and colon only.`);
171
+ return value;
172
+ };
173
+ const parsePositiveInteger = (record, key) => {
174
+ const value = record[key];
175
+ if (value === void 0) return;
176
+ if (!Number.isInteger(value) || typeof value !== "number" || value <= 0) throw new Error(`${key} must be a positive integer`);
177
+ return value;
178
+ };
179
+ const parseParams = (record) => {
180
+ const params = record.params;
181
+ if (!isRecord$4(params)) throw new Error("params must be a JSON object");
182
+ return params;
183
+ };
184
+ const parseProcessSignal = (record) => {
185
+ const value = parseString(record, "signal");
186
+ if (!ALLOWED_PROCESS_SIGNALS.includes(value)) throw new Error(`Unsupported signal: ${value}`);
187
+ return value;
188
+ };
189
+ const parseRunnethSshStdioRequest = (value) => {
190
+ if (!isRecord$4(value)) throw new Error("Request must be a JSON object");
191
+ const id = parseProcessId(parseString(value, "id"));
192
+ const method = parseString(value, "method");
193
+ switch (method) {
194
+ case "exec": {
195
+ const params = parseParams(value);
196
+ const timeoutMs = parsePositiveInteger(params, "timeoutMs");
197
+ return {
198
+ id,
199
+ method,
200
+ params: {
201
+ command: parseString(params, "command"),
202
+ ...timeoutMs === void 0 ? {} : { timeoutMs }
203
+ }
204
+ };
205
+ }
206
+ case "spawn": return {
207
+ id,
208
+ method,
209
+ params: { command: parseString(parseParams(value), "command") }
210
+ };
211
+ case "stdin": {
212
+ const data = parseParams(value).data;
213
+ if (typeof data !== "string") throw new Error("data must be a string");
214
+ return {
215
+ id,
216
+ method,
217
+ params: { data }
218
+ };
219
+ }
220
+ case "signal": return {
221
+ id,
222
+ method,
223
+ params: { signal: parseProcessSignal(parseParams(value)) }
224
+ };
225
+ case "close":
226
+ case "list":
227
+ case "ping": return {
228
+ id,
229
+ method
230
+ };
231
+ default: throw new Error(`Unknown SSH stdio method: ${method}`);
232
+ }
233
+ };
234
+ const writeChunk = async (stream, chunk) => {
235
+ if (stream.write(chunk)) return;
236
+ await new Promise((resolve) => {
237
+ stream.once("drain", () => {
238
+ resolve();
239
+ });
240
+ });
241
+ };
242
+ var JsonLineWriter = class {
243
+ #stream;
244
+ #queue = Promise.resolve();
245
+ constructor(stream) {
246
+ this.#stream = stream;
247
+ }
248
+ async flush() {
249
+ await this.#queue;
250
+ }
251
+ write(payload) {
252
+ const line = `${JSON.stringify(payload)}\n`;
253
+ const next = this.#queue.then(async () => {
254
+ await writeChunk(this.#stream, line);
255
+ });
256
+ this.#queue = next.then(() => void 0, () => void 0);
257
+ return next;
258
+ }
259
+ };
260
+ const waitForChildExit = async (child) => {
261
+ return await new Promise((resolve, reject) => {
262
+ child.once("error", reject);
263
+ child.once("close", (code, signal) => {
264
+ resolve({
265
+ code,
266
+ signal
267
+ });
268
+ });
269
+ });
270
+ };
271
+ const runProcessExitCode = async (input) => {
272
+ return await new Promise((resolve, reject) => {
273
+ const child = spawn(input.sshCommand, [...input.args], {
274
+ env: input.env,
275
+ stdio: [
276
+ "ignore",
277
+ "ignore",
278
+ "ignore"
279
+ ]
280
+ });
281
+ child.once("error", reject);
282
+ child.once("close", (code, signal) => {
283
+ if (signal !== null) {
284
+ resolve(1);
285
+ return;
286
+ }
287
+ resolve(code ?? 1);
288
+ });
289
+ });
290
+ };
291
+ var RunnethSshControlMaster = class {
292
+ #controlPath;
293
+ #env;
294
+ #masterReadyTimeoutMs;
295
+ #setup;
296
+ #sshCommand;
297
+ #master = null;
298
+ #masterClosed = false;
299
+ #masterExit = null;
300
+ #masterStderr = "";
301
+ constructor(options) {
302
+ this.#controlPath = options.controlPath;
303
+ this.#env = options.env;
304
+ this.#masterReadyTimeoutMs = options.masterReadyTimeoutMs;
305
+ this.#setup = options.setup;
306
+ this.#sshCommand = options.sshCommand;
307
+ }
308
+ async start() {
309
+ await rm(this.#controlPath, { force: true });
310
+ const master = spawn(this.#sshCommand, [
311
+ "-F",
312
+ this.#setup.configPath,
313
+ "-S",
314
+ this.#controlPath,
315
+ "-o",
316
+ "ControlMaster=yes",
317
+ "-o",
318
+ "ControlPersist=no",
319
+ "-N",
320
+ this.#setup.hostAlias
321
+ ], {
322
+ env: this.#env,
323
+ stdio: [
324
+ "ignore",
325
+ "ignore",
326
+ "pipe"
327
+ ]
328
+ });
329
+ this.#master = master;
330
+ master.stderr?.on("data", (chunk) => {
331
+ this.#masterStderr += String(chunk);
332
+ });
333
+ this.#masterExit = waitForChildExit(master).then((exit) => {
334
+ this.#masterClosed = true;
335
+ return exit;
336
+ });
337
+ const deadline = Date.now() + this.#masterReadyTimeoutMs;
338
+ while (Date.now() < deadline) {
339
+ if (this.#masterClosed) throw new Error(this.#formatMasterExitError());
340
+ if (await this.check() === 0) return;
341
+ await setTimeout$1(50);
342
+ }
343
+ throw new Error(this.#formatMasterExitError("Timed out opening SSH master"));
344
+ }
345
+ async check() {
346
+ return await runProcessExitCode({
347
+ args: [
348
+ "-F",
349
+ this.#setup.configPath,
350
+ "-S",
351
+ this.#controlPath,
352
+ "-O",
353
+ "check",
354
+ this.#setup.hostAlias
355
+ ],
356
+ env: this.#env,
357
+ sshCommand: this.#sshCommand
358
+ });
359
+ }
360
+ async requireReady() {
361
+ if (this.#masterClosed || await this.check() !== 0) throw new Error(this.#formatMasterExitError("SSH master is not running"));
362
+ }
363
+ spawnRemoteCommand(command) {
364
+ return spawn(this.#sshCommand, [
365
+ "-F",
366
+ this.#setup.configPath,
367
+ "-S",
368
+ this.#controlPath,
369
+ "-o",
370
+ "ControlMaster=no",
371
+ this.#setup.hostAlias,
372
+ command
373
+ ], {
374
+ env: this.#env,
375
+ stdio: "pipe"
376
+ });
377
+ }
378
+ async stop() {
379
+ const master = this.#master;
380
+ if (master === null || this.#masterExit === null || this.#masterClosed) {
381
+ await rm(this.#controlPath, { force: true });
382
+ return;
383
+ }
384
+ master.kill("SIGTERM");
385
+ const timeout = setTimeout$1(5e3).then(() => {
386
+ if (!this.#masterClosed) master.kill("SIGKILL");
387
+ });
388
+ await Promise.race([this.#masterExit, timeout]);
389
+ await this.#masterExit;
390
+ await rm(this.#controlPath, { force: true });
391
+ }
392
+ #formatMasterExitError(prefix = "SSH master exited before it was ready") {
393
+ const stderr = this.#masterStderr.trim();
394
+ if (stderr.length === 0) return prefix;
395
+ return `${prefix}: ${stderr}`;
396
+ }
397
+ };
398
+ var RunnethSshStdioController = class {
399
+ #defaultTimeoutMs;
400
+ #master;
401
+ #maxFrameBytes;
402
+ #maxProcesses;
403
+ #processes = /* @__PURE__ */ new Map();
404
+ #setup;
405
+ #stdin;
406
+ #stderr;
407
+ #writer;
408
+ #stopping = false;
409
+ constructor(options) {
410
+ const setup = options.setup;
411
+ const env = options.env ?? process.env;
412
+ const controlPath = path.join(path.dirname(setup.configPath), "control.sock");
413
+ this.#defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
414
+ this.#maxFrameBytes = options.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES;
415
+ this.#maxProcesses = options.maxProcesses ?? DEFAULT_MAX_PROCESSES;
416
+ this.#setup = setup;
417
+ this.#stdin = options.streams?.stdin ?? process.stdin;
418
+ this.#stderr = options.streams?.stderr ?? process.stderr;
419
+ this.#writer = new JsonLineWriter(options.streams?.stdout ?? process.stdout);
420
+ this.#master = new RunnethSshControlMaster({
421
+ controlPath,
422
+ env,
423
+ masterReadyTimeoutMs: options.masterReadyTimeoutMs ?? DEFAULT_MASTER_READY_TIMEOUT_MS,
424
+ setup,
425
+ sshCommand: options.sshCommand ?? "ssh"
426
+ });
427
+ }
428
+ async start() {
429
+ await this.#master.start();
430
+ await writeChunk(this.#stderr, `SSH stdio master ${this.#setup.hostAlias} ready\n`);
431
+ await this.#writer.write({
432
+ hostAlias: this.#setup.hostAlias,
433
+ ok: true,
434
+ protocol: "runneth-ssh-stdio",
435
+ type: "ready",
436
+ version: 1,
437
+ ...this.#setup.targetName === void 0 ? {} : { targetName: this.#setup.targetName }
438
+ });
439
+ }
440
+ async run() {
441
+ let buffer = "";
442
+ for await (const chunk of this.#stdin) {
443
+ buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
444
+ let newlineIndex = buffer.indexOf("\n");
445
+ while (newlineIndex !== -1) {
446
+ const rawLine = buffer.slice(0, newlineIndex);
447
+ buffer = buffer.slice(newlineIndex + 1);
448
+ if (Buffer.byteLength(rawLine, "utf8") > this.#maxFrameBytes) {
449
+ await this.#writer.write({
450
+ message: `SSH stdio frame exceeds ${String(this.#maxFrameBytes)} bytes`,
451
+ ok: false,
452
+ type: "error"
453
+ });
454
+ return;
455
+ }
456
+ if (rawLine.trim().length > 0) await this.#handleLine(rawLine);
457
+ newlineIndex = buffer.indexOf("\n");
458
+ }
459
+ if (Buffer.byteLength(buffer, "utf8") > this.#maxFrameBytes) {
460
+ await this.#writer.write({
461
+ message: `SSH stdio frame exceeds ${String(this.#maxFrameBytes)} bytes`,
462
+ ok: false,
463
+ type: "error"
464
+ });
465
+ return;
466
+ }
467
+ }
468
+ }
469
+ async stop() {
470
+ this.#stopping = true;
471
+ for (const processInfo of this.#processes.values()) processInfo.child.kill("SIGTERM");
472
+ await this.#master.stop();
473
+ await this.#writer.flush();
474
+ }
475
+ async #handleLine(rawLine) {
476
+ let request;
477
+ try {
478
+ request = parseRunnethSshStdioRequest(JSON.parse(rawLine));
479
+ } catch (error) {
480
+ await this.#writer.write({
481
+ message: errorMessage(error),
482
+ ok: false,
483
+ type: "error"
484
+ });
485
+ return;
486
+ }
487
+ try {
488
+ switch (request.method) {
489
+ case "exec":
490
+ this.#exec(request).catch((error) => {
491
+ this.#writeRequestError(request.id, error);
492
+ });
493
+ return;
494
+ case "spawn":
495
+ await this.#spawn(request);
496
+ return;
497
+ case "stdin":
498
+ await this.#writeProcessStdin(request);
499
+ return;
500
+ case "signal":
501
+ await this.#signalProcess(request);
502
+ return;
503
+ case "close":
504
+ await this.#closeProcess(request.id);
505
+ return;
506
+ case "list":
507
+ await this.#writer.write({
508
+ id: request.id,
509
+ ok: true,
510
+ processes: [...this.#processes.values()].map((processInfo) => {
511
+ return {
512
+ command: processInfo.command,
513
+ processId: processInfo.processId,
514
+ startedAt: processInfo.startedAt
515
+ };
516
+ }),
517
+ type: "list"
518
+ });
519
+ return;
520
+ case "ping":
521
+ await this.#writer.write({
522
+ id: request.id,
523
+ ok: true,
524
+ type: "pong"
525
+ });
526
+ return;
527
+ default: return request;
528
+ }
529
+ } catch (error) {
530
+ await this.#writeRequestError(request.id, error);
531
+ }
532
+ }
533
+ async #exec(request) {
534
+ await this.#master.requireReady();
535
+ const child = this.#master.spawnRemoteCommand(request.params.command);
536
+ const timeoutMs = request.params.timeoutMs ?? this.#defaultTimeoutMs;
537
+ let timedOut = false;
538
+ const timeout = setTimeout(() => {
539
+ timedOut = true;
540
+ this.#writer.write({
541
+ id: request.id,
542
+ message: `Command timed out after ${String(timeoutMs)}ms`,
543
+ ok: false,
544
+ type: "error"
545
+ });
546
+ child.kill("SIGTERM");
547
+ }, timeoutMs);
548
+ child.stdout.on("data", (chunk) => {
549
+ this.#writer.write({
550
+ data: String(chunk),
551
+ id: request.id,
552
+ type: "stdout"
553
+ });
554
+ });
555
+ child.stderr.on("data", (chunk) => {
556
+ this.#writer.write({
557
+ data: String(chunk),
558
+ id: request.id,
559
+ type: "stderr"
560
+ });
561
+ });
562
+ child.once("error", (error) => {
563
+ clearTimeout(timeout);
564
+ this.#writeRequestError(request.id, error);
565
+ });
566
+ child.once("close", (code, signal) => {
567
+ clearTimeout(timeout);
568
+ this.#writer.write({
569
+ code,
570
+ id: request.id,
571
+ ok: !timedOut,
572
+ signal,
573
+ type: "exit"
574
+ });
575
+ });
576
+ }
577
+ async #spawn(request) {
578
+ if (this.#processes.has(request.id)) throw new Error(`Process is already running: ${request.id}`);
579
+ if (this.#processes.size >= this.#maxProcesses) throw new Error(`SSH stdio process limit reached: ${String(this.#maxProcesses)}`);
580
+ await this.#master.requireReady();
581
+ const child = this.#master.spawnRemoteCommand(request.params.command);
582
+ const processInfo = {
583
+ child,
584
+ command: request.params.command,
585
+ processId: request.id,
586
+ startedAt: nowIso()
587
+ };
588
+ this.#processes.set(request.id, processInfo);
589
+ child.once("spawn", () => {
590
+ this.#writer.write({
591
+ command: request.params.command,
592
+ id: request.id,
593
+ ok: true,
594
+ processId: request.id,
595
+ type: "started"
596
+ });
597
+ });
598
+ child.stdout.on("data", (chunk) => {
599
+ this.#writer.write({
600
+ data: String(chunk),
601
+ id: request.id,
602
+ processId: request.id,
603
+ type: "stdout"
604
+ });
605
+ });
606
+ child.stderr.on("data", (chunk) => {
607
+ this.#writer.write({
608
+ data: String(chunk),
609
+ id: request.id,
610
+ processId: request.id,
611
+ type: "stderr"
612
+ });
613
+ });
614
+ child.once("error", (error) => {
615
+ this.#processes.delete(request.id);
616
+ this.#writeRequestError(request.id, error);
617
+ });
618
+ child.once("close", (code, signal) => {
619
+ this.#processes.delete(request.id);
620
+ if (this.#stopping) return;
621
+ this.#writer.write({
622
+ code,
623
+ id: request.id,
624
+ ok: true,
625
+ processId: request.id,
626
+ signal,
627
+ type: "exit"
628
+ });
629
+ });
630
+ }
631
+ async #writeProcessStdin(request) {
632
+ await writeChunk(this.#requireProcess(request.id).child.stdin, request.params.data);
633
+ await this.#writer.write({
634
+ id: request.id,
635
+ ok: true,
636
+ type: "ack"
637
+ });
638
+ }
639
+ async #signalProcess(request) {
640
+ if (!this.#requireProcess(request.id).child.kill(request.params.signal)) throw new Error(`Unable to signal process: ${request.id}`);
641
+ await this.#writer.write({
642
+ id: request.id,
643
+ ok: true,
644
+ signal: request.params.signal,
645
+ type: "ack"
646
+ });
647
+ }
648
+ async #closeProcess(processId) {
649
+ if (!this.#requireProcess(processId).child.kill("SIGTERM")) throw new Error(`Unable to close process: ${processId}`);
650
+ await this.#writer.write({
651
+ id: processId,
652
+ ok: true,
653
+ type: "ack"
654
+ });
655
+ }
656
+ #requireProcess(processId) {
657
+ const processInfo = this.#processes.get(processId);
658
+ if (processInfo === void 0) throw new Error(`Process is not running: ${processId}`);
659
+ return processInfo;
660
+ }
661
+ async #writeRequestError(id, error) {
662
+ await this.#writer.write({
663
+ id,
664
+ message: errorMessage(error),
665
+ ok: false,
666
+ type: "error"
667
+ });
668
+ }
669
+ };
670
+ const runRunnethSshStdio = async (options) => {
671
+ const controller = new RunnethSshStdioController(options);
672
+ await controller.start();
673
+ try {
674
+ await controller.run();
675
+ } finally {
676
+ await controller.stop();
677
+ }
678
+ };
679
+ //#endregion
680
+ //#region src/update-invocation.ts
681
+ const isRunnethCliUpdatePromptInvocation = (argv) => {
682
+ const separatorIndex = argv.indexOf("--");
683
+ const optionArguments = separatorIndex === -1 ? argv : argv.slice(0, separatorIndex);
684
+ if (optionArguments.includes("--help") || optionArguments.includes("-h")) return false;
685
+ if (argv[0] === "chat") return true;
686
+ if (argv[0] !== "ssh" || argv[1] === "proxy" || argv[1] === "stdio" || argv[1] === "target") return false;
687
+ return separatorIndex === -1 || separatorIndex === argv.length - 1;
688
+ };
689
+ //#endregion
690
+ //#region ../../node_modules/.pnpm/signal-exit@4.1.0/node_modules/signal-exit/dist/mjs/signals.js
691
+ /**
692
+ * This is not the set of all possible signals.
693
+ *
694
+ * It IS, however, the set of all signals that trigger
695
+ * an exit on either Linux or BSD systems. Linux is a
696
+ * superset of the signal names supported on BSD, and
697
+ * the unknown signals just fail to register, so we can
698
+ * catch that easily enough.
699
+ *
700
+ * Windows signals are a different set, since there are
701
+ * signals that terminate Windows processes, but don't
702
+ * terminate (or don't even exist) on Posix systems.
703
+ *
704
+ * Don't bother with SIGKILL. It's uncatchable, which
705
+ * means that we can't fire any callbacks anyway.
706
+ *
707
+ * If a user does happen to register a handler on a non-
708
+ * fatal signal like SIGWINCH or something, and then
709
+ * exit, it'll end up firing `process.emit('exit')`, so
710
+ * the handler will be fired anyway.
711
+ *
712
+ * SIGBUS, SIGFPE, SIGSEGV and SIGILL, when not raised
713
+ * artificially, inherently leave the process in a
714
+ * state from which it is not safe to try and enter JS
715
+ * listeners.
716
+ */
717
+ const signals = [];
718
+ signals.push("SIGHUP", "SIGINT", "SIGTERM");
719
+ if (process.platform !== "win32") signals.push("SIGALRM", "SIGABRT", "SIGVTALRM", "SIGXCPU", "SIGXFSZ", "SIGUSR2", "SIGTRAP", "SIGSYS", "SIGQUIT", "SIGIOT");
720
+ if (process.platform === "linux") signals.push("SIGIO", "SIGPOLL", "SIGPWR", "SIGSTKFLT");
721
+ //#endregion
722
+ //#region ../../node_modules/.pnpm/signal-exit@4.1.0/node_modules/signal-exit/dist/mjs/index.js
723
+ const processOk = (process) => !!process && typeof process === "object" && typeof process.removeListener === "function" && typeof process.emit === "function" && typeof process.reallyExit === "function" && typeof process.listeners === "function" && typeof process.kill === "function" && typeof process.pid === "number" && typeof process.on === "function";
724
+ const kExitEmitter = Symbol.for("signal-exit emitter");
725
+ const global$1 = globalThis;
726
+ const ObjectDefineProperty = Object.defineProperty.bind(Object);
727
+ var Emitter = class {
728
+ emitted = {
729
+ afterExit: false,
730
+ exit: false
731
+ };
732
+ listeners = {
733
+ afterExit: [],
734
+ exit: []
735
+ };
736
+ count = 0;
737
+ id = Math.random();
738
+ constructor() {
739
+ if (global$1[kExitEmitter]) return global$1[kExitEmitter];
740
+ ObjectDefineProperty(global$1, kExitEmitter, {
741
+ value: this,
742
+ writable: false,
743
+ enumerable: false,
744
+ configurable: false
745
+ });
746
+ }
747
+ on(ev, fn) {
748
+ this.listeners[ev].push(fn);
749
+ }
750
+ removeListener(ev, fn) {
751
+ const list = this.listeners[ev];
752
+ const i = list.indexOf(fn);
753
+ /* c8 ignore start */
754
+ if (i === -1) return;
755
+ /* c8 ignore stop */
756
+ if (i === 0 && list.length === 1) list.length = 0;
757
+ else list.splice(i, 1);
758
+ }
759
+ emit(ev, code, signal) {
760
+ if (this.emitted[ev]) return false;
761
+ this.emitted[ev] = true;
762
+ let ret = false;
763
+ for (const fn of this.listeners[ev]) ret = fn(code, signal) === true || ret;
764
+ if (ev === "exit") ret = this.emit("afterExit", code, signal) || ret;
765
+ return ret;
766
+ }
767
+ };
768
+ var SignalExitBase = class {};
769
+ const signalExitWrap = (handler) => {
770
+ return {
771
+ onExit(cb, opts) {
772
+ return handler.onExit(cb, opts);
773
+ },
774
+ load() {
775
+ return handler.load();
776
+ },
777
+ unload() {
778
+ return handler.unload();
779
+ }
780
+ };
781
+ };
782
+ var SignalExitFallback = class extends SignalExitBase {
783
+ onExit() {
784
+ return () => {};
785
+ }
786
+ load() {}
787
+ unload() {}
788
+ };
789
+ var SignalExit = class extends SignalExitBase {
790
+ /* c8 ignore start */
791
+ #hupSig = process$2.platform === "win32" ? "SIGINT" : "SIGHUP";
792
+ /* c8 ignore stop */
793
+ #emitter = new Emitter();
794
+ #process;
795
+ #originalProcessEmit;
796
+ #originalProcessReallyExit;
797
+ #sigListeners = {};
798
+ #loaded = false;
799
+ constructor(process) {
800
+ super();
801
+ this.#process = process;
802
+ this.#sigListeners = {};
803
+ for (const sig of signals) this.#sigListeners[sig] = () => {
804
+ const listeners = this.#process.listeners(sig);
805
+ let { count } = this.#emitter;
806
+ /* c8 ignore start */
807
+ const p = process;
808
+ if (typeof p.__signal_exit_emitter__ === "object" && typeof p.__signal_exit_emitter__.count === "number") count += p.__signal_exit_emitter__.count;
809
+ /* c8 ignore stop */
810
+ if (listeners.length === count) {
811
+ this.unload();
812
+ const ret = this.#emitter.emit("exit", null, sig);
813
+ /* c8 ignore start */
814
+ const s = sig === "SIGHUP" ? this.#hupSig : sig;
815
+ if (!ret) process.kill(process.pid, s);
816
+ }
817
+ };
818
+ this.#originalProcessReallyExit = process.reallyExit;
819
+ this.#originalProcessEmit = process.emit;
820
+ }
821
+ onExit(cb, opts) {
822
+ /* c8 ignore start */
823
+ if (!processOk(this.#process)) return () => {};
824
+ /* c8 ignore stop */
825
+ if (this.#loaded === false) this.load();
826
+ const ev = opts?.alwaysLast ? "afterExit" : "exit";
827
+ this.#emitter.on(ev, cb);
828
+ return () => {
829
+ this.#emitter.removeListener(ev, cb);
830
+ if (this.#emitter.listeners["exit"].length === 0 && this.#emitter.listeners["afterExit"].length === 0) this.unload();
831
+ };
832
+ }
833
+ load() {
834
+ if (this.#loaded) return;
835
+ this.#loaded = true;
836
+ this.#emitter.count += 1;
837
+ for (const sig of signals) try {
838
+ const fn = this.#sigListeners[sig];
839
+ if (fn) this.#process.on(sig, fn);
840
+ } catch (_) {}
841
+ this.#process.emit = (ev, ...a) => {
842
+ return this.#processEmit(ev, ...a);
843
+ };
844
+ this.#process.reallyExit = (code) => {
845
+ return this.#processReallyExit(code);
846
+ };
847
+ }
848
+ unload() {
849
+ if (!this.#loaded) return;
850
+ this.#loaded = false;
851
+ signals.forEach((sig) => {
852
+ const listener = this.#sigListeners[sig];
853
+ /* c8 ignore start */
854
+ if (!listener) throw new Error("Listener not defined for signal: " + sig);
855
+ /* c8 ignore stop */
856
+ try {
857
+ this.#process.removeListener(sig, listener);
858
+ } catch (_) {}
859
+ /* c8 ignore stop */
860
+ });
861
+ this.#process.emit = this.#originalProcessEmit;
862
+ this.#process.reallyExit = this.#originalProcessReallyExit;
863
+ this.#emitter.count -= 1;
864
+ }
865
+ #processReallyExit(code) {
866
+ /* c8 ignore start */
867
+ if (!processOk(this.#process)) return 0;
868
+ this.#process.exitCode = code || 0;
869
+ /* c8 ignore stop */
870
+ this.#emitter.emit("exit", this.#process.exitCode, null);
871
+ return this.#originalProcessReallyExit.call(this.#process, this.#process.exitCode);
872
+ }
873
+ #processEmit(ev, ...args) {
874
+ const og = this.#originalProcessEmit;
875
+ if (ev === "exit" && processOk(this.#process)) {
876
+ if (typeof args[0] === "number") this.#process.exitCode = args[0];
877
+ /* c8 ignore start */
878
+ const ret = og.call(this.#process, ev, ...args);
879
+ /* c8 ignore start */
880
+ this.#emitter.emit("exit", this.#process.exitCode, null);
881
+ /* c8 ignore stop */
882
+ return ret;
883
+ } else return og.call(this.#process, ev, ...args);
884
+ }
885
+ };
886
+ const process$2 = globalThis.process;
887
+ const { onExit, load, unload } = signalExitWrap(processOk(process$2) ? new SignalExit(process$2) : new SignalExitFallback());
888
+ //#endregion
889
+ //#region src/update-prompt.ts
890
+ const DEFAULT_TERMINAL_COLUMNS = 80;
891
+ const OPTIONS = [
892
+ {
893
+ label: "Update now",
894
+ selection: "update"
895
+ },
896
+ {
897
+ label: "Not now",
898
+ selection: "not-now"
899
+ },
900
+ {
901
+ label: "Do not remind me for this version",
902
+ selection: "dismiss"
903
+ }
904
+ ];
905
+ const actionFromKeypress = (character, key) => {
906
+ if (key.name === "up" || character === "k") return "up";
907
+ if (key.name === "down" || character === "j") return "down";
908
+ if (key.name === "return" || key.name === "enter") return "select";
909
+ if (key.name === "escape" || key.ctrl === true && (key.name === "c" || key.name === "d")) return "escape";
910
+ return null;
911
+ };
912
+ const moveRunnethCliUpdatePromptHighlight = (highlighted, direction) => {
913
+ switch (highlighted) {
914
+ case 0: return direction === "down" ? 1 : 2;
915
+ case 1: return direction === "down" ? 2 : 0;
916
+ case 2: return direction === "down" ? 0 : 1;
917
+ default: return highlighted;
918
+ }
919
+ };
920
+ const renderOptions = (output, highlighted, previousRows) => {
921
+ if (previousRows > 0) output.write(`\u001b[${String(previousRows)}A\r\u001b[0J`);
922
+ const columns = typeof output.columns === "number" && Number.isInteger(output.columns) && output.columns > 0 ? output.columns : DEFAULT_TERMINAL_COLUMNS;
923
+ let renderedRows = 0;
924
+ OPTIONS.forEach((option, index) => {
925
+ const line = `${index === highlighted ? "›" : " "} ${String(index + 1)}. ${option.label}`;
926
+ renderedRows += Math.ceil(line.length / columns);
927
+ output.write(`\r${line}\n`);
928
+ });
929
+ return renderedRows;
930
+ };
931
+ const runRunnethCliUpdatePrompt = async (input) => {
932
+ if (input.input.isTTY !== true || input.output.isTTY !== true) throw new Error("Runneth CLI update prompt requires an interactive TTY");
933
+ input.output.write(`\n✨ Update available! ${input.currentVersion} -> ${input.targetVersion}\n\nUpdate now runs \`${input.updateCommand}\`\n\n`);
934
+ let highlighted = 0;
935
+ let renderedRows = renderOptions(input.output, highlighted, 0);
936
+ const wasFlowing = input.input.readableFlowing === true;
937
+ const wasRaw = input.input.isRaw;
938
+ const restoreInput = () => {
939
+ input.input.setRawMode(wasRaw);
940
+ if (!wasFlowing) input.input.pause();
941
+ };
942
+ const removeExitCleanup = onExit(restoreInput);
943
+ input.input.setRawMode(true);
944
+ input.input.resume();
945
+ emitKeypressEvents(input.input);
946
+ return await new Promise((resolve) => {
947
+ const finish = (selection) => {
948
+ input.input.off("keypress", onKeypress);
949
+ restoreInput();
950
+ removeExitCleanup();
951
+ input.output.write("\n");
952
+ resolve(selection);
953
+ };
954
+ const onKeypress = (character, key) => {
955
+ const action = actionFromKeypress(character, key);
956
+ switch (action) {
957
+ case "up":
958
+ case "down":
959
+ highlighted = moveRunnethCliUpdatePromptHighlight(highlighted, action);
960
+ renderedRows = renderOptions(input.output, highlighted, renderedRows);
961
+ return;
962
+ case "select":
963
+ finish(OPTIONS[highlighted].selection);
964
+ return;
965
+ case "escape":
966
+ finish("not-now");
967
+ return;
968
+ case null: return;
969
+ default: return action;
970
+ }
971
+ };
972
+ input.input.on("keypress", onKeypress);
973
+ });
974
+ };
975
+ //#endregion
976
+ //#region ../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.js
977
+ var require_windows = /* @__PURE__ */ __commonJSMin(((exports, module) => {
978
+ module.exports = isexe;
979
+ isexe.sync = sync;
980
+ var fs$2 = __require("fs");
981
+ function checkPathExt(path, options) {
982
+ var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
983
+ if (!pathext) return true;
984
+ pathext = pathext.split(";");
985
+ if (pathext.indexOf("") !== -1) return true;
986
+ for (var i = 0; i < pathext.length; i++) {
987
+ var p = pathext[i].toLowerCase();
988
+ if (p && path.substr(-p.length).toLowerCase() === p) return true;
989
+ }
990
+ return false;
991
+ }
992
+ function checkStat(stat, path, options) {
993
+ if (!stat.isSymbolicLink() && !stat.isFile()) return false;
994
+ return checkPathExt(path, options);
995
+ }
996
+ function isexe(path, options, cb) {
997
+ fs$2.stat(path, function(er, stat) {
998
+ cb(er, er ? false : checkStat(stat, path, options));
999
+ });
1000
+ }
1001
+ function sync(path, options) {
1002
+ return checkStat(fs$2.statSync(path), path, options);
1003
+ }
1004
+ }));
1005
+ //#endregion
1006
+ //#region ../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.js
1007
+ var require_mode = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1008
+ module.exports = isexe;
1009
+ isexe.sync = sync;
1010
+ var fs$1 = __require("fs");
1011
+ function isexe(path, options, cb) {
1012
+ fs$1.stat(path, function(er, stat) {
1013
+ cb(er, er ? false : checkStat(stat, options));
1014
+ });
1015
+ }
1016
+ function sync(path, options) {
1017
+ return checkStat(fs$1.statSync(path), options);
1018
+ }
1019
+ function checkStat(stat, options) {
1020
+ return stat.isFile() && checkMode(stat, options);
1021
+ }
1022
+ function checkMode(stat, options) {
1023
+ var mod = stat.mode;
1024
+ var uid = stat.uid;
1025
+ var gid = stat.gid;
1026
+ var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
1027
+ var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
1028
+ var u = parseInt("100", 8);
1029
+ var g = parseInt("010", 8);
1030
+ var o = parseInt("001", 8);
1031
+ var ug = u | g;
1032
+ return mod & o || mod & g && gid === myGid || mod & u && uid === myUid || mod & ug && myUid === 0;
1033
+ }
1034
+ }));
1035
+ //#endregion
1036
+ //#region ../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js
1037
+ var require_isexe = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1038
+ __require("fs");
1039
+ var core;
1040
+ if (process.platform === "win32" || global.TESTING_WINDOWS) core = require_windows();
1041
+ else core = require_mode();
1042
+ module.exports = isexe;
1043
+ isexe.sync = sync;
1044
+ function isexe(path, options, cb) {
1045
+ if (typeof options === "function") {
1046
+ cb = options;
1047
+ options = {};
1048
+ }
1049
+ if (!cb) {
1050
+ if (typeof Promise !== "function") throw new TypeError("callback not provided");
1051
+ return new Promise(function(resolve, reject) {
1052
+ isexe(path, options || {}, function(er, is) {
1053
+ if (er) reject(er);
1054
+ else resolve(is);
1055
+ });
1056
+ });
1057
+ }
1058
+ core(path, options || {}, function(er, is) {
1059
+ if (er) {
1060
+ if (er.code === "EACCES" || options && options.ignoreErrors) {
1061
+ er = null;
1062
+ is = false;
1063
+ }
1064
+ }
1065
+ cb(er, is);
1066
+ });
1067
+ }
1068
+ function sync(path, options) {
1069
+ try {
1070
+ return core.sync(path, options || {});
1071
+ } catch (er) {
1072
+ if (options && options.ignoreErrors || er.code === "EACCES") return false;
1073
+ else throw er;
1074
+ }
1075
+ }
1076
+ }));
1077
+ //#endregion
1078
+ //#region ../../node_modules/.pnpm/which@2.0.2/node_modules/which/which.js
1079
+ var require_which = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1080
+ const isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
1081
+ const path$3 = __require("path");
1082
+ const COLON = isWindows ? ";" : ":";
1083
+ const isexe = require_isexe();
1084
+ const getNotFoundError = (cmd) => Object.assign(/* @__PURE__ */ new Error(`not found: ${cmd}`), { code: "ENOENT" });
1085
+ const getPathInfo = (cmd, opt) => {
1086
+ const colon = opt.colon || COLON;
1087
+ const pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? [""] : [...isWindows ? [process.cwd()] : [], ...(opt.path || process.env.PATH || "").split(colon)];
1088
+ const pathExtExe = isWindows ? opt.pathExt || process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM" : "";
1089
+ const pathExt = isWindows ? pathExtExe.split(colon) : [""];
1090
+ if (isWindows) {
1091
+ if (cmd.indexOf(".") !== -1 && pathExt[0] !== "") pathExt.unshift("");
1092
+ }
1093
+ return {
1094
+ pathEnv,
1095
+ pathExt,
1096
+ pathExtExe
1097
+ };
1098
+ };
1099
+ const which = (cmd, opt, cb) => {
1100
+ if (typeof opt === "function") {
1101
+ cb = opt;
1102
+ opt = {};
1103
+ }
1104
+ if (!opt) opt = {};
1105
+ const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
1106
+ const found = [];
1107
+ const step = (i) => new Promise((resolve, reject) => {
1108
+ if (i === pathEnv.length) return opt.all && found.length ? resolve(found) : reject(getNotFoundError(cmd));
1109
+ const ppRaw = pathEnv[i];
1110
+ const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
1111
+ const pCmd = path$3.join(pathPart, cmd);
1112
+ resolve(subStep(!pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd, i, 0));
1113
+ });
1114
+ const subStep = (p, i, ii) => new Promise((resolve, reject) => {
1115
+ if (ii === pathExt.length) return resolve(step(i + 1));
1116
+ const ext = pathExt[ii];
1117
+ isexe(p + ext, { pathExt: pathExtExe }, (er, is) => {
1118
+ if (!er && is) if (opt.all) found.push(p + ext);
1119
+ else return resolve(p + ext);
1120
+ return resolve(subStep(p, i, ii + 1));
1121
+ });
1122
+ });
1123
+ return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
1124
+ };
1125
+ const whichSync = (cmd, opt) => {
1126
+ opt = opt || {};
1127
+ const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
1128
+ const found = [];
1129
+ for (let i = 0; i < pathEnv.length; i++) {
1130
+ const ppRaw = pathEnv[i];
1131
+ const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
1132
+ const pCmd = path$3.join(pathPart, cmd);
1133
+ const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
1134
+ for (let j = 0; j < pathExt.length; j++) {
1135
+ const cur = p + pathExt[j];
1136
+ try {
1137
+ if (isexe.sync(cur, { pathExt: pathExtExe })) if (opt.all) found.push(cur);
1138
+ else return cur;
1139
+ } catch (ex) {}
1140
+ }
1141
+ }
1142
+ if (opt.all && found.length) return found;
1143
+ if (opt.nothrow) return null;
1144
+ throw getNotFoundError(cmd);
1145
+ };
1146
+ module.exports = which;
1147
+ which.sync = whichSync;
1148
+ }));
1149
+ //#endregion
1150
+ //#region ../../node_modules/.pnpm/path-key@3.1.1/node_modules/path-key/index.js
1151
+ var require_path_key = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1152
+ const pathKey = (options = {}) => {
1153
+ const environment = options.env || process.env;
1154
+ if ((options.platform || process.platform) !== "win32") return "PATH";
1155
+ return Object.keys(environment).reverse().find((key) => key.toUpperCase() === "PATH") || "Path";
1156
+ };
1157
+ module.exports = pathKey;
1158
+ module.exports.default = pathKey;
1159
+ }));
1160
+ //#endregion
1161
+ //#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js
1162
+ var require_resolveCommand = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1163
+ const path$2 = __require("path");
1164
+ const which = require_which();
1165
+ const getPathKey = require_path_key();
1166
+ function resolveCommandAttempt(parsed, withoutPathExt) {
1167
+ const env = parsed.options.env || process.env;
1168
+ const cwd = process.cwd();
1169
+ const hasCustomCwd = parsed.options.cwd != null;
1170
+ const shouldSwitchCwd = hasCustomCwd && process.chdir !== void 0 && !process.chdir.disabled;
1171
+ if (shouldSwitchCwd) try {
1172
+ process.chdir(parsed.options.cwd);
1173
+ } catch (err) {}
1174
+ let resolved;
1175
+ try {
1176
+ resolved = which.sync(parsed.command, {
1177
+ path: env[getPathKey({ env })],
1178
+ pathExt: withoutPathExt ? path$2.delimiter : void 0
1179
+ });
1180
+ } catch (e) {} finally {
1181
+ if (shouldSwitchCwd) process.chdir(cwd);
1182
+ }
1183
+ if (resolved) resolved = path$2.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
1184
+ return resolved;
1185
+ }
1186
+ function resolveCommand(parsed) {
1187
+ return resolveCommandAttempt(parsed) || resolveCommandAttempt(parsed, true);
1188
+ }
1189
+ module.exports = resolveCommand;
1190
+ }));
1191
+ //#endregion
1192
+ //#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/escape.js
1193
+ var require_escape = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1194
+ const metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g;
1195
+ function escapeCommand(arg) {
1196
+ arg = arg.replace(metaCharsRegExp, "^$1");
1197
+ return arg;
1198
+ }
1199
+ function escapeArgument(arg, doubleEscapeMetaChars) {
1200
+ arg = `${arg}`;
1201
+ arg = arg.replace(/(?=(\\+?)?)\1"/g, "$1$1\\\"");
1202
+ arg = arg.replace(/(?=(\\+?)?)\1$/, "$1$1");
1203
+ arg = `"${arg}"`;
1204
+ arg = arg.replace(metaCharsRegExp, "^$1");
1205
+ if (doubleEscapeMetaChars) arg = arg.replace(metaCharsRegExp, "^$1");
1206
+ return arg;
1207
+ }
1208
+ module.exports.command = escapeCommand;
1209
+ module.exports.argument = escapeArgument;
1210
+ }));
1211
+ //#endregion
1212
+ //#region ../../node_modules/.pnpm/shebang-regex@3.0.0/node_modules/shebang-regex/index.js
1213
+ var require_shebang_regex = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1214
+ module.exports = /^#!(.*)/;
1215
+ }));
1216
+ //#endregion
1217
+ //#region ../../node_modules/.pnpm/shebang-command@2.0.0/node_modules/shebang-command/index.js
1218
+ var require_shebang_command = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1219
+ const shebangRegex = require_shebang_regex();
1220
+ module.exports = (string = "") => {
1221
+ const match = string.match(shebangRegex);
1222
+ if (!match) return null;
1223
+ const [path, argument] = match[0].replace(/#! ?/, "").split(" ");
1224
+ const binary = path.split("/").pop();
1225
+ if (binary === "env") return argument;
1226
+ return argument ? `${binary} ${argument}` : binary;
1227
+ };
1228
+ }));
1229
+ //#endregion
1230
+ //#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js
1231
+ var require_readShebang = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1232
+ const fs = __require("fs");
1233
+ const shebangCommand = require_shebang_command();
1234
+ function readShebang(command) {
1235
+ const size = 150;
1236
+ const buffer = Buffer.alloc(size);
1237
+ let fd;
1238
+ try {
1239
+ fd = fs.openSync(command, "r");
1240
+ fs.readSync(fd, buffer, 0, size, 0);
1241
+ fs.closeSync(fd);
1242
+ } catch (e) {}
1243
+ return shebangCommand(buffer.toString());
1244
+ }
1245
+ module.exports = readShebang;
1246
+ }));
1247
+ //#endregion
1248
+ //#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js
1249
+ var require_parse$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1250
+ const path$1 = __require("path");
1251
+ const resolveCommand = require_resolveCommand();
1252
+ const escape = require_escape();
1253
+ const readShebang = require_readShebang();
1254
+ const isWin = process.platform === "win32";
1255
+ const isExecutableRegExp = /\.(?:com|exe)$/i;
1256
+ const isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
1257
+ function detectShebang(parsed) {
1258
+ parsed.file = resolveCommand(parsed);
1259
+ const shebang = parsed.file && readShebang(parsed.file);
1260
+ if (shebang) {
1261
+ parsed.args.unshift(parsed.file);
1262
+ parsed.command = shebang;
1263
+ return resolveCommand(parsed);
1264
+ }
1265
+ return parsed.file;
1266
+ }
1267
+ function parseNonShell(parsed) {
1268
+ if (!isWin) return parsed;
1269
+ const commandFile = detectShebang(parsed);
1270
+ const needsShell = !isExecutableRegExp.test(commandFile);
1271
+ if (parsed.options.forceShell || needsShell) {
1272
+ const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
1273
+ parsed.command = path$1.normalize(parsed.command);
1274
+ parsed.command = escape.command(parsed.command);
1275
+ parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
1276
+ parsed.args = [
1277
+ "/d",
1278
+ "/s",
1279
+ "/c",
1280
+ `"${[parsed.command].concat(parsed.args).join(" ")}"`
1281
+ ];
1282
+ parsed.command = process.env.comspec || "cmd.exe";
1283
+ parsed.options.windowsVerbatimArguments = true;
1284
+ }
1285
+ return parsed;
1286
+ }
1287
+ function parse(command, args, options) {
1288
+ if (args && !Array.isArray(args)) {
1289
+ options = args;
1290
+ args = null;
1291
+ }
1292
+ args = args ? args.slice(0) : [];
1293
+ options = Object.assign({}, options);
1294
+ const parsed = {
1295
+ command,
1296
+ args,
1297
+ options,
1298
+ file: void 0,
1299
+ original: {
1300
+ command,
1301
+ args
1302
+ }
1303
+ };
1304
+ return options.shell ? parsed : parseNonShell(parsed);
1305
+ }
1306
+ module.exports = parse;
1307
+ }));
1308
+ //#endregion
1309
+ //#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/enoent.js
1310
+ var require_enoent = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1311
+ const isWin = process.platform === "win32";
1312
+ function notFoundError(original, syscall) {
1313
+ return Object.assign(/* @__PURE__ */ new Error(`${syscall} ${original.command} ENOENT`), {
1314
+ code: "ENOENT",
1315
+ errno: "ENOENT",
1316
+ syscall: `${syscall} ${original.command}`,
1317
+ path: original.command,
1318
+ spawnargs: original.args
1319
+ });
1320
+ }
1321
+ function hookChildProcess(cp, parsed) {
1322
+ if (!isWin) return;
1323
+ const originalEmit = cp.emit;
1324
+ cp.emit = function(name, arg1) {
1325
+ if (name === "exit") {
1326
+ const err = verifyENOENT(arg1, parsed);
1327
+ if (err) return originalEmit.call(cp, "error", err);
1328
+ }
1329
+ return originalEmit.apply(cp, arguments);
1330
+ };
1331
+ }
1332
+ function verifyENOENT(status, parsed) {
1333
+ if (isWin && status === 1 && !parsed.file) return notFoundError(parsed.original, "spawn");
1334
+ return null;
1335
+ }
1336
+ function verifyENOENTSync(status, parsed) {
1337
+ if (isWin && status === 1 && !parsed.file) return notFoundError(parsed.original, "spawnSync");
1338
+ return null;
1339
+ }
1340
+ module.exports = {
1341
+ hookChildProcess,
1342
+ verifyENOENT,
1343
+ verifyENOENTSync,
1344
+ notFoundError
1345
+ };
1346
+ }));
1347
+ //#endregion
1348
+ //#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/index.js
1349
+ var require_cross_spawn = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1350
+ const cp$1 = __require("child_process");
1351
+ const parse = require_parse$1();
1352
+ const enoent = require_enoent();
1353
+ function spawn(command, args, options) {
1354
+ const parsed = parse(command, args, options);
1355
+ const spawned = cp$1.spawn(parsed.command, parsed.args, parsed.options);
1356
+ enoent.hookChildProcess(spawned, parsed);
1357
+ return spawned;
1358
+ }
1359
+ function spawnSync(command, args, options) {
1360
+ const parsed = parse(command, args, options);
1361
+ const result = cp$1.spawnSync(parsed.command, parsed.args, parsed.options);
1362
+ result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
1363
+ return result;
1364
+ }
1365
+ module.exports = spawn;
1366
+ module.exports.spawn = spawn;
1367
+ module.exports.sync = spawnSync;
1368
+ module.exports._parse = parse;
1369
+ module.exports._enoent = enoent;
1370
+ }));
1371
+ //#endregion
1372
+ //#region ../../node_modules/.pnpm/semver@7.8.0/node_modules/semver/internal/debug.js
1373
+ var require_debug = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1374
+ module.exports = typeof process === "object" && process.env && process.env.NODE_DEBUG && /\bsemver\b/i.test(process.env.NODE_DEBUG) ? (...args) => console.error("SEMVER", ...args) : () => {};
1375
+ }));
1376
+ //#endregion
1377
+ //#region ../../node_modules/.pnpm/semver@7.8.0/node_modules/semver/internal/constants.js
1378
+ var require_constants = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1379
+ const SEMVER_SPEC_VERSION = "2.0.0";
1380
+ const MAX_LENGTH = 256;
1381
+ const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
1382
+ module.exports = {
1383
+ MAX_LENGTH,
1384
+ MAX_SAFE_COMPONENT_LENGTH: 16,
1385
+ MAX_SAFE_BUILD_LENGTH: MAX_LENGTH - 6,
1386
+ MAX_SAFE_INTEGER,
1387
+ RELEASE_TYPES: [
1388
+ "major",
1389
+ "premajor",
1390
+ "minor",
1391
+ "preminor",
1392
+ "patch",
1393
+ "prepatch",
1394
+ "prerelease"
1395
+ ],
1396
+ SEMVER_SPEC_VERSION,
1397
+ FLAG_INCLUDE_PRERELEASE: 1,
1398
+ FLAG_LOOSE: 2
1399
+ };
1400
+ }));
1401
+ //#endregion
1402
+ //#region ../../node_modules/.pnpm/semver@7.8.0/node_modules/semver/internal/re.js
1403
+ var require_re = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1404
+ const { MAX_SAFE_COMPONENT_LENGTH, MAX_SAFE_BUILD_LENGTH, MAX_LENGTH } = require_constants();
1405
+ const debug = require_debug();
1406
+ exports = module.exports = {};
1407
+ const re = exports.re = [];
1408
+ const safeRe = exports.safeRe = [];
1409
+ const src = exports.src = [];
1410
+ const safeSrc = exports.safeSrc = [];
1411
+ const t = exports.t = {};
1412
+ let R = 0;
1413
+ const LETTERDASHNUMBER = "[a-zA-Z0-9-]";
1414
+ const safeRegexReplacements = [
1415
+ ["\\s", 1],
1416
+ ["\\d", MAX_LENGTH],
1417
+ [LETTERDASHNUMBER, MAX_SAFE_BUILD_LENGTH]
1418
+ ];
1419
+ const makeSafeRegex = (value) => {
1420
+ for (const [token, max] of safeRegexReplacements) value = value.split(`${token}*`).join(`${token}{0,${max}}`).split(`${token}+`).join(`${token}{1,${max}}`);
1421
+ return value;
1422
+ };
1423
+ const createToken = (name, value, isGlobal) => {
1424
+ const safe = makeSafeRegex(value);
1425
+ const index = R++;
1426
+ debug(name, index, value);
1427
+ t[name] = index;
1428
+ src[index] = value;
1429
+ safeSrc[index] = safe;
1430
+ re[index] = new RegExp(value, isGlobal ? "g" : void 0);
1431
+ safeRe[index] = new RegExp(safe, isGlobal ? "g" : void 0);
1432
+ };
1433
+ createToken("NUMERICIDENTIFIER", "0|[1-9]\\d*");
1434
+ createToken("NUMERICIDENTIFIERLOOSE", "\\d+");
1435
+ createToken("NONNUMERICIDENTIFIER", `\\d*[a-zA-Z-]${LETTERDASHNUMBER}*`);
1436
+ createToken("MAINVERSION", `(${src[t.NUMERICIDENTIFIER]})\\.(${src[t.NUMERICIDENTIFIER]})\\.(${src[t.NUMERICIDENTIFIER]})`);
1437
+ createToken("MAINVERSIONLOOSE", `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.(${src[t.NUMERICIDENTIFIERLOOSE]})\\.(${src[t.NUMERICIDENTIFIERLOOSE]})`);
1438
+ createToken("PRERELEASEIDENTIFIER", `(?:${src[t.NONNUMERICIDENTIFIER]}|${src[t.NUMERICIDENTIFIER]})`);
1439
+ createToken("PRERELEASEIDENTIFIERLOOSE", `(?:${src[t.NONNUMERICIDENTIFIER]}|${src[t.NUMERICIDENTIFIERLOOSE]})`);
1440
+ createToken("PRERELEASE", `(?:-(${src[t.PRERELEASEIDENTIFIER]}(?:\\.${src[t.PRERELEASEIDENTIFIER]})*))`);
1441
+ createToken("PRERELEASELOOSE", `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE]}(?:\\.${src[t.PRERELEASEIDENTIFIERLOOSE]})*))`);
1442
+ createToken("BUILDIDENTIFIER", `${LETTERDASHNUMBER}+`);
1443
+ createToken("BUILD", `(?:\\+(${src[t.BUILDIDENTIFIER]}(?:\\.${src[t.BUILDIDENTIFIER]})*))`);
1444
+ createToken("FULLPLAIN", `v?${src[t.MAINVERSION]}${src[t.PRERELEASE]}?${src[t.BUILD]}?`);
1445
+ createToken("FULL", `^${src[t.FULLPLAIN]}$`);
1446
+ createToken("LOOSEPLAIN", `[v=\\s]*${src[t.MAINVERSIONLOOSE]}${src[t.PRERELEASELOOSE]}?${src[t.BUILD]}?`);
1447
+ createToken("LOOSE", `^${src[t.LOOSEPLAIN]}$`);
1448
+ createToken("GTLT", "((?:<|>)?=?)");
1449
+ createToken("XRANGEIDENTIFIERLOOSE", `${src[t.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`);
1450
+ createToken("XRANGEIDENTIFIER", `${src[t.NUMERICIDENTIFIER]}|x|X|\\*`);
1451
+ createToken("XRANGEPLAIN", `[v=\\s]*(${src[t.XRANGEIDENTIFIER]})(?:\\.(${src[t.XRANGEIDENTIFIER]})(?:\\.(${src[t.XRANGEIDENTIFIER]})(?:${src[t.PRERELEASE]})?${src[t.BUILD]}?)?)?`);
1452
+ createToken("XRANGEPLAINLOOSE", `[v=\\s]*(${src[t.XRANGEIDENTIFIERLOOSE]})(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})(?:${src[t.PRERELEASELOOSE]})?${src[t.BUILD]}?)?)?`);
1453
+ createToken("XRANGE", `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAIN]}$`);
1454
+ createToken("XRANGELOOSE", `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAINLOOSE]}$`);
1455
+ createToken("COERCEPLAIN", `(^|[^\\d])(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}})(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?`);
1456
+ createToken("COERCE", `${src[t.COERCEPLAIN]}(?:$|[^\\d])`);
1457
+ createToken("COERCEFULL", src[t.COERCEPLAIN] + `(?:${src[t.PRERELEASE]})?(?:${src[t.BUILD]})?(?:$|[^\\d])`);
1458
+ createToken("COERCERTL", src[t.COERCE], true);
1459
+ createToken("COERCERTLFULL", src[t.COERCEFULL], true);
1460
+ createToken("LONETILDE", "(?:~>?)");
1461
+ createToken("TILDETRIM", `(\\s*)${src[t.LONETILDE]}\\s+`, true);
1462
+ exports.tildeTrimReplace = "$1~";
1463
+ createToken("TILDE", `^${src[t.LONETILDE]}${src[t.XRANGEPLAIN]}$`);
1464
+ createToken("TILDELOOSE", `^${src[t.LONETILDE]}${src[t.XRANGEPLAINLOOSE]}$`);
1465
+ createToken("LONECARET", "(?:\\^)");
1466
+ createToken("CARETTRIM", `(\\s*)${src[t.LONECARET]}\\s+`, true);
1467
+ exports.caretTrimReplace = "$1^";
1468
+ createToken("CARET", `^${src[t.LONECARET]}${src[t.XRANGEPLAIN]}$`);
1469
+ createToken("CARETLOOSE", `^${src[t.LONECARET]}${src[t.XRANGEPLAINLOOSE]}$`);
1470
+ createToken("COMPARATORLOOSE", `^${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]})$|^$`);
1471
+ createToken("COMPARATOR", `^${src[t.GTLT]}\\s*(${src[t.FULLPLAIN]})$|^$`);
1472
+ createToken("COMPARATORTRIM", `(\\s*)${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]}|${src[t.XRANGEPLAIN]})`, true);
1473
+ exports.comparatorTrimReplace = "$1$2$3";
1474
+ createToken("HYPHENRANGE", `^\\s*(${src[t.XRANGEPLAIN]})\\s+-\\s+(${src[t.XRANGEPLAIN]})\\s*$`);
1475
+ createToken("HYPHENRANGELOOSE", `^\\s*(${src[t.XRANGEPLAINLOOSE]})\\s+-\\s+(${src[t.XRANGEPLAINLOOSE]})\\s*$`);
1476
+ createToken("STAR", "(<|>)?=?\\s*\\*");
1477
+ createToken("GTE0", "^\\s*>=\\s*0\\.0\\.0\\s*$");
1478
+ createToken("GTE0PRE", "^\\s*>=\\s*0\\.0\\.0-0\\s*$");
1479
+ }));
1480
+ //#endregion
1481
+ //#region ../../node_modules/.pnpm/semver@7.8.0/node_modules/semver/internal/parse-options.js
1482
+ var require_parse_options = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1483
+ const looseOption = Object.freeze({ loose: true });
1484
+ const emptyOpts = Object.freeze({});
1485
+ const parseOptions = (options) => {
1486
+ if (!options) return emptyOpts;
1487
+ if (typeof options !== "object") return looseOption;
1488
+ return options;
1489
+ };
1490
+ module.exports = parseOptions;
1491
+ }));
1492
+ //#endregion
1493
+ //#region ../../node_modules/.pnpm/semver@7.8.0/node_modules/semver/internal/identifiers.js
1494
+ var require_identifiers = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1495
+ const numeric = /^[0-9]+$/;
1496
+ const compareIdentifiers = (a, b) => {
1497
+ if (typeof a === "number" && typeof b === "number") return a === b ? 0 : a < b ? -1 : 1;
1498
+ const anum = numeric.test(a);
1499
+ const bnum = numeric.test(b);
1500
+ if (anum && bnum) {
1501
+ a = +a;
1502
+ b = +b;
1503
+ }
1504
+ return a === b ? 0 : anum && !bnum ? -1 : bnum && !anum ? 1 : a < b ? -1 : 1;
1505
+ };
1506
+ const rcompareIdentifiers = (a, b) => compareIdentifiers(b, a);
1507
+ module.exports = {
1508
+ compareIdentifiers,
1509
+ rcompareIdentifiers
1510
+ };
1511
+ }));
1512
+ //#endregion
1513
+ //#region ../../node_modules/.pnpm/semver@7.8.0/node_modules/semver/classes/semver.js
1514
+ var require_semver = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1515
+ const debug = require_debug();
1516
+ const { MAX_LENGTH, MAX_SAFE_INTEGER } = require_constants();
1517
+ const { safeRe: re, t } = require_re();
1518
+ const parseOptions = require_parse_options();
1519
+ const { compareIdentifiers } = require_identifiers();
1520
+ module.exports = class SemVer {
1521
+ constructor(version, options) {
1522
+ options = parseOptions(options);
1523
+ if (version instanceof SemVer) if (version.loose === !!options.loose && version.includePrerelease === !!options.includePrerelease) return version;
1524
+ else version = version.version;
1525
+ else if (typeof version !== "string") throw new TypeError(`Invalid version. Must be a string. Got type "${typeof version}".`);
1526
+ if (version.length > MAX_LENGTH) throw new TypeError(`version is longer than ${MAX_LENGTH} characters`);
1527
+ debug("SemVer", version, options);
1528
+ this.options = options;
1529
+ this.loose = !!options.loose;
1530
+ this.includePrerelease = !!options.includePrerelease;
1531
+ const m = version.trim().match(options.loose ? re[t.LOOSE] : re[t.FULL]);
1532
+ if (!m) throw new TypeError(`Invalid Version: ${version}`);
1533
+ this.raw = version;
1534
+ this.major = +m[1];
1535
+ this.minor = +m[2];
1536
+ this.patch = +m[3];
1537
+ if (this.major > MAX_SAFE_INTEGER || this.major < 0) throw new TypeError("Invalid major version");
1538
+ if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) throw new TypeError("Invalid minor version");
1539
+ if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) throw new TypeError("Invalid patch version");
1540
+ if (!m[4]) this.prerelease = [];
1541
+ else this.prerelease = m[4].split(".").map((id) => {
1542
+ if (/^[0-9]+$/.test(id)) {
1543
+ const num = +id;
1544
+ if (num >= 0 && num < MAX_SAFE_INTEGER) return num;
1545
+ }
1546
+ return id;
1547
+ });
1548
+ this.build = m[5] ? m[5].split(".") : [];
1549
+ this.format();
1550
+ }
1551
+ format() {
1552
+ this.version = `${this.major}.${this.minor}.${this.patch}`;
1553
+ if (this.prerelease.length) this.version += `-${this.prerelease.join(".")}`;
1554
+ return this.version;
1555
+ }
1556
+ toString() {
1557
+ return this.version;
1558
+ }
1559
+ compare(other) {
1560
+ debug("SemVer.compare", this.version, this.options, other);
1561
+ if (!(other instanceof SemVer)) {
1562
+ if (typeof other === "string" && other === this.version) return 0;
1563
+ other = new SemVer(other, this.options);
1564
+ }
1565
+ if (other.version === this.version) return 0;
1566
+ return this.compareMain(other) || this.comparePre(other);
1567
+ }
1568
+ compareMain(other) {
1569
+ if (!(other instanceof SemVer)) other = new SemVer(other, this.options);
1570
+ if (this.major < other.major) return -1;
1571
+ if (this.major > other.major) return 1;
1572
+ if (this.minor < other.minor) return -1;
1573
+ if (this.minor > other.minor) return 1;
1574
+ if (this.patch < other.patch) return -1;
1575
+ if (this.patch > other.patch) return 1;
1576
+ return 0;
1577
+ }
1578
+ comparePre(other) {
1579
+ if (!(other instanceof SemVer)) other = new SemVer(other, this.options);
1580
+ if (this.prerelease.length && !other.prerelease.length) return -1;
1581
+ else if (!this.prerelease.length && other.prerelease.length) return 1;
1582
+ else if (!this.prerelease.length && !other.prerelease.length) return 0;
1583
+ let i = 0;
1584
+ do {
1585
+ const a = this.prerelease[i];
1586
+ const b = other.prerelease[i];
1587
+ debug("prerelease compare", i, a, b);
1588
+ if (a === void 0 && b === void 0) return 0;
1589
+ else if (b === void 0) return 1;
1590
+ else if (a === void 0) return -1;
1591
+ else if (a === b) continue;
1592
+ else return compareIdentifiers(a, b);
1593
+ } while (++i);
1594
+ }
1595
+ compareBuild(other) {
1596
+ if (!(other instanceof SemVer)) other = new SemVer(other, this.options);
1597
+ let i = 0;
1598
+ do {
1599
+ const a = this.build[i];
1600
+ const b = other.build[i];
1601
+ debug("build compare", i, a, b);
1602
+ if (a === void 0 && b === void 0) return 0;
1603
+ else if (b === void 0) return 1;
1604
+ else if (a === void 0) return -1;
1605
+ else if (a === b) continue;
1606
+ else return compareIdentifiers(a, b);
1607
+ } while (++i);
1608
+ }
1609
+ inc(release, identifier, identifierBase) {
1610
+ if (release.startsWith("pre")) {
1611
+ if (!identifier && identifierBase === false) throw new Error("invalid increment argument: identifier is empty");
1612
+ if (identifier) {
1613
+ const match = `-${identifier}`.match(this.options.loose ? re[t.PRERELEASELOOSE] : re[t.PRERELEASE]);
1614
+ if (!match || match[1] !== identifier) throw new Error(`invalid identifier: ${identifier}`);
1615
+ }
1616
+ }
1617
+ switch (release) {
1618
+ case "premajor":
1619
+ this.prerelease.length = 0;
1620
+ this.patch = 0;
1621
+ this.minor = 0;
1622
+ this.major++;
1623
+ this.inc("pre", identifier, identifierBase);
1624
+ break;
1625
+ case "preminor":
1626
+ this.prerelease.length = 0;
1627
+ this.patch = 0;
1628
+ this.minor++;
1629
+ this.inc("pre", identifier, identifierBase);
1630
+ break;
1631
+ case "prepatch":
1632
+ this.prerelease.length = 0;
1633
+ this.inc("patch", identifier, identifierBase);
1634
+ this.inc("pre", identifier, identifierBase);
1635
+ break;
1636
+ case "prerelease":
1637
+ if (this.prerelease.length === 0) this.inc("patch", identifier, identifierBase);
1638
+ this.inc("pre", identifier, identifierBase);
1639
+ break;
1640
+ case "release":
1641
+ if (this.prerelease.length === 0) throw new Error(`version ${this.raw} is not a prerelease`);
1642
+ this.prerelease.length = 0;
1643
+ break;
1644
+ case "major":
1645
+ if (this.minor !== 0 || this.patch !== 0 || this.prerelease.length === 0) this.major++;
1646
+ this.minor = 0;
1647
+ this.patch = 0;
1648
+ this.prerelease = [];
1649
+ break;
1650
+ case "minor":
1651
+ if (this.patch !== 0 || this.prerelease.length === 0) this.minor++;
1652
+ this.patch = 0;
1653
+ this.prerelease = [];
1654
+ break;
1655
+ case "patch":
1656
+ if (this.prerelease.length === 0) this.patch++;
1657
+ this.prerelease = [];
1658
+ break;
1659
+ case "pre": {
1660
+ const base = Number(identifierBase) ? 1 : 0;
1661
+ if (this.prerelease.length === 0) this.prerelease = [base];
1662
+ else {
1663
+ let i = this.prerelease.length;
1664
+ while (--i >= 0) if (typeof this.prerelease[i] === "number") {
1665
+ this.prerelease[i]++;
1666
+ i = -2;
1667
+ }
1668
+ if (i === -1) {
1669
+ if (identifier === this.prerelease.join(".") && identifierBase === false) throw new Error("invalid increment argument: identifier already exists");
1670
+ this.prerelease.push(base);
1671
+ }
1672
+ }
1673
+ if (identifier) {
1674
+ let prerelease = [identifier, base];
1675
+ if (identifierBase === false) prerelease = [identifier];
1676
+ if (compareIdentifiers(this.prerelease[0], identifier) === 0) {
1677
+ if (isNaN(this.prerelease[1])) this.prerelease = prerelease;
1678
+ } else this.prerelease = prerelease;
1679
+ }
1680
+ break;
1681
+ }
1682
+ default: throw new Error(`invalid increment argument: ${release}`);
1683
+ }
1684
+ this.raw = this.format();
1685
+ if (this.build.length) this.raw += `+${this.build.join(".")}`;
1686
+ return this;
1687
+ }
1688
+ };
1689
+ }));
1690
+ //#endregion
1691
+ //#region ../../node_modules/.pnpm/semver@7.8.0/node_modules/semver/functions/parse.js
1692
+ var require_parse = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1693
+ const SemVer = require_semver();
1694
+ const parse = (version, options, throwErrors = false) => {
1695
+ if (version instanceof SemVer) return version;
1696
+ try {
1697
+ return new SemVer(version, options);
1698
+ } catch (er) {
1699
+ if (!throwErrors) return null;
1700
+ throw er;
1701
+ }
1702
+ };
1703
+ module.exports = parse;
1704
+ }));
1705
+ //#endregion
1706
+ //#region ../../node_modules/.pnpm/semver@7.8.0/node_modules/semver/functions/valid.js
1707
+ var require_valid = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1708
+ const parse = require_parse();
1709
+ const valid = (version, options) => {
1710
+ const v = parse(version, options);
1711
+ return v ? v.version : null;
1712
+ };
1713
+ module.exports = valid;
1714
+ }));
1715
+ //#endregion
1716
+ //#region src/update-version.ts
1717
+ var import_cross_spawn = /* @__PURE__ */ __toESM(require_cross_spawn(), 1);
1718
+ var import_valid = /* @__PURE__ */ __toESM(require_valid(), 1);
1719
+ const parseRunnethCliVersion = (value, label) => {
1720
+ if (typeof value !== "string") throw new Error(`${label} must be a semantic version`);
1721
+ const parsed = (0, import_valid.default)(value);
1722
+ if (parsed === null || parsed !== value) throw new Error(`${label} must be a semantic version`);
1723
+ return parsed;
1724
+ };
1725
+ const parseRunnethCliPublicationTime = (value, label) => {
1726
+ if (typeof value !== "string") throw new Error(`${label} must be an ISO date string`);
1727
+ const timestamp = Date.parse(value);
1728
+ if (!Number.isFinite(timestamp)) throw new Error(`${label} must be an ISO date string`);
1729
+ return new Date(timestamp).toISOString();
1730
+ };
1731
+ const isRunnethCliReleaseNewer = (input) => {
1732
+ return input.candidateVersion !== input.installedVersion && Date.parse(input.candidatePublishedAt) > Date.parse(input.installedPublishedAt);
1733
+ };
1734
+ //#endregion
1735
+ //#region src/package-info.ts
1736
+ const isRecord$3 = (value) => {
1737
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1738
+ };
1739
+ const parseNonEmptyString$1 = (record, key) => {
1740
+ const value = record[key];
1741
+ if (typeof value !== "string" || value.trim().length === 0) throw new Error(`Runneth CLI package.json ${key} must be a non-empty string`);
1742
+ return value;
1743
+ };
1744
+ const parseUpdateTarget = (manifest, packageName) => {
1745
+ const value = manifest.runnethCliUpdate;
1746
+ if (!isRecord$3(value) || typeof value.mode !== "string") throw new Error("Runneth CLI package.json runnethCliUpdate must be an object with a mode");
1747
+ if (value.mode === "source") return null;
1748
+ if (value.mode !== "registry") throw new Error(`Unsupported Runneth CLI update mode: ${value.mode}`);
1749
+ const updatePackageName = parseNonEmptyString$1(value, "packageName");
1750
+ if (updatePackageName !== packageName) throw new Error(`Runneth CLI update package ${updatePackageName} does not match manifest package ${packageName}`);
1751
+ if (typeof value.tag !== "string" || !/^[a-z][a-z0-9-]*$/u.test(value.tag)) throw new Error(`Unsupported Runneth CLI update tag: ${String(value.tag)}`);
1752
+ return {
1753
+ packageName: updatePackageName,
1754
+ tag: value.tag
1755
+ };
1756
+ };
1757
+ const readRunnethCliPackageInfo = async (moduleUrl) => {
1758
+ const cliPath = await realpath(fileURLToPath(moduleUrl));
1759
+ const packageRoot = path.resolve(path.dirname(cliPath), "..");
1760
+ const manifestPath = path.join(packageRoot, "package.json");
1761
+ const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
1762
+ if (!isRecord$3(manifest)) throw new Error(`Runneth CLI package.json must contain an object: ${manifestPath}`);
1763
+ const name = parseNonEmptyString$1(manifest, "name");
1764
+ return {
1765
+ cliPath,
1766
+ name,
1767
+ packageRoot,
1768
+ updateTarget: parseUpdateTarget(manifest, name),
1769
+ version: parseRunnethCliVersion(manifest.version, "Runneth CLI package.json version")
1770
+ };
1771
+ };
1772
+ const resolveRunnethCliUpdateTarget = (packageInfo) => {
1773
+ return packageInfo.updateTarget;
1774
+ };
1775
+ //#endregion
1776
+ //#region src/update-state.ts
1777
+ var import_proper_lockfile = require_proper_lockfile();
1778
+ const RUNNETH_CLI_UPDATE_CHECK_ATTEMPT_SCHEMA_VERSION = 1;
1779
+ const RUNNETH_CLI_UPDATE_DISMISSAL_SCHEMA_VERSION = 1;
1780
+ const RUNNETH_CLI_UPDATE_CHECK_INTERVAL_MS = 1200 * 60 * 1e3;
1781
+ const RUNNETH_CLI_UPDATE_LOCK_STALE_MS = 3e4;
1782
+ const RUNNETH_CLI_UPDATE_LOCK_REFRESH_MS = 1e4;
1783
+ const RUNNETH_CLI_UPDATE_MUTATION_LOCK_RETRIES = 2400;
1784
+ const RUNNETH_CLI_UPDATE_MUTATION_LOCK_RETRY_MS = 250;
1785
+ const isRecord$2 = (value) => {
1786
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1787
+ };
1788
+ const isErrnoCode = (error, code) => {
1789
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
1790
+ };
1791
+ const parseNonEmptyString = (value, label) => {
1792
+ if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${label} must be a non-empty string`);
1793
+ return value;
1794
+ };
1795
+ const parseAbsolutePath = (value, label) => {
1796
+ const parsed = parseNonEmptyString(value, label);
1797
+ if (!path.isAbsolute(parsed)) throw new Error(`${label} must be an absolute path`);
1798
+ return path.resolve(parsed);
1799
+ };
1800
+ const parseIsoDate = (value, label) => {
1801
+ const parsed = parseNonEmptyString(value, label);
1802
+ if (!Number.isFinite(Date.parse(parsed))) throw new Error(`${label} must be an ISO date string`);
1803
+ return parsed;
1804
+ };
1805
+ const parseInstallationIdentity = (value, label) => {
1806
+ const manager = value.manager;
1807
+ if (manager !== "npm" && manager !== "pnpm") throw new Error(`${label} manager must be npm or pnpm`);
1808
+ return {
1809
+ installationRoot: parseAbsolutePath(value.installationRoot, `${label} installationRoot`),
1810
+ manager,
1811
+ packageName: parseNonEmptyString(value.packageName, `${label} packageName`),
1812
+ tag: parseNonEmptyString(value.tag, `${label} tag`)
1813
+ };
1814
+ };
1815
+ const parseIdentity = (value, label) => {
1816
+ return {
1817
+ installedVersion: parseRunnethCliVersion(value.installedVersion, `${label} installedVersion`),
1818
+ packageName: parseNonEmptyString(value.packageName, `${label} packageName`),
1819
+ packageRoot: parseAbsolutePath(value.packageRoot, `${label} packageRoot`),
1820
+ tag: parseNonEmptyString(value.tag, `${label} tag`)
1821
+ };
1822
+ };
1823
+ const assertInstallationIdentity = (actual, expected, label) => {
1824
+ if (actual.installationRoot !== path.resolve(expected.installationRoot) || actual.manager !== expected.manager || actual.packageName !== expected.packageName || actual.tag !== expected.tag) throw new Error(`${label} identity does not match ${expected.manager} installation ${expected.packageName}@${expected.tag} at ${path.resolve(expected.installationRoot)}`);
1825
+ };
1826
+ const assertCacheIdentity = (actual, expected, label) => {
1827
+ if (actual.packageName !== expected.packageName || actual.packageRoot !== path.resolve(expected.packageRoot) || actual.tag !== expected.tag || actual.installedVersion !== expected.installedVersion) throw new Error(`${label} identity does not match ${expected.packageName}@${expected.tag} version ${expected.installedVersion} at ${path.resolve(expected.packageRoot)}`);
1828
+ };
1829
+ const parseRunnethCliUpdateCache = (value, identity) => {
1830
+ if (!isRecord$2(value)) throw new Error("Runneth CLI update cache must be an object");
1831
+ if (value.schemaVersion !== 2) throw new Error(`Unsupported Runneth CLI update cache schema version: ${String(value.schemaVersion)}`);
1832
+ const parsedIdentity = parseIdentity(value, "Runneth CLI update cache");
1833
+ assertCacheIdentity(parsedIdentity, identity, "Runneth CLI update cache");
1834
+ if (value.manager === "npm" && value.pnpmGlobalBinDirectory !== null) throw new Error("An npm update cache must not specify a pnpm executable directory");
1835
+ return {
1836
+ ...parsedIdentity,
1837
+ ...parseInstallationIdentity(value, "Runneth CLI update cache"),
1838
+ pnpmGlobalBinDirectory: value.manager === "pnpm" ? parseAbsolutePath(value.pnpmGlobalBinDirectory, "Runneth CLI update cache pnpmGlobalBinDirectory") : null,
1839
+ installedVersionPublishedAt: parseRunnethCliPublicationTime(value.installedVersionPublishedAt, "Runneth CLI update cache installedVersionPublishedAt"),
1840
+ lastCheckedAt: parseIsoDate(value.lastCheckedAt, "Runneth CLI update cache lastCheckedAt"),
1841
+ latestVersion: parseRunnethCliVersion(value.latestVersion, "Runneth CLI update cache latestVersion"),
1842
+ latestVersionPublishedAt: parseRunnethCliPublicationTime(value.latestVersionPublishedAt, "Runneth CLI update cache latestVersionPublishedAt"),
1843
+ schemaVersion: 2
1844
+ };
1845
+ };
1846
+ const parseRunnethCliUpdateCheckAttempt = (value, identity) => {
1847
+ if (!isRecord$2(value)) throw new Error("Runneth CLI update check attempt state must be an object");
1848
+ if (value.schemaVersion !== RUNNETH_CLI_UPDATE_CHECK_ATTEMPT_SCHEMA_VERSION) throw new Error(`Unsupported Runneth CLI update check attempt schema version: ${String(value.schemaVersion)}`);
1849
+ const parsedIdentity = parseInstallationIdentity(value, "Runneth CLI update check attempt");
1850
+ assertInstallationIdentity(parsedIdentity, identity, "Runneth CLI update check attempt");
1851
+ return {
1852
+ ...parsedIdentity,
1853
+ attemptedAt: parseIsoDate(value.attemptedAt, "Runneth CLI update check attempt attemptedAt"),
1854
+ schemaVersion: RUNNETH_CLI_UPDATE_CHECK_ATTEMPT_SCHEMA_VERSION
1855
+ };
1856
+ };
1857
+ const parseRunnethCliUpdateDismissal = (value, identity, targetVersion) => {
1858
+ if (!isRecord$2(value)) throw new Error("Runneth CLI update dismissal must be an object");
1859
+ if (value.schemaVersion !== RUNNETH_CLI_UPDATE_DISMISSAL_SCHEMA_VERSION) throw new Error(`Unsupported Runneth CLI update dismissal schema version: ${String(value.schemaVersion)}`);
1860
+ const parsedIdentity = parseInstallationIdentity(value, "Runneth CLI update dismissal");
1861
+ assertInstallationIdentity(parsedIdentity, identity, "Runneth CLI update dismissal");
1862
+ const parsedTargetVersion = parseRunnethCliVersion(value.targetVersion, "Runneth CLI update dismissal targetVersion");
1863
+ if (parsedTargetVersion !== targetVersion) throw new Error(`Runneth CLI update dismissal target does not match ${targetVersion}`);
1864
+ return {
1865
+ ...parsedIdentity,
1866
+ schemaVersion: RUNNETH_CLI_UPDATE_DISMISSAL_SCHEMA_VERSION,
1867
+ targetVersion: parsedTargetVersion
1868
+ };
1869
+ };
1870
+ const updateIdentityKey = (identity) => {
1871
+ return createHash("sha256").update(JSON.stringify([
1872
+ identity.packageName,
1873
+ identity.tag,
1874
+ path.resolve(identity.packageRoot),
1875
+ identity.installedVersion
1876
+ ])).digest("hex");
1877
+ };
1878
+ const updateInstallationKey = (identity) => {
1879
+ return createHash("sha256").update(JSON.stringify([
1880
+ identity.manager,
1881
+ identity.packageName,
1882
+ identity.tag,
1883
+ path.resolve(identity.installationRoot)
1884
+ ])).digest("hex");
1885
+ };
1886
+ const updateMutationKey = (identity) => {
1887
+ return createHash("sha256").update(JSON.stringify([
1888
+ identity.manager,
1889
+ identity.packageName,
1890
+ path.resolve(identity.installationRoot)
1891
+ ])).digest("hex");
1892
+ };
1893
+ const updateDiscoveryKey = (identity) => {
1894
+ return createHash("sha256").update(JSON.stringify([
1895
+ identity.packageName,
1896
+ identity.tag,
1897
+ path.resolve(identity.packageRoot)
1898
+ ])).digest("hex");
1899
+ };
1900
+ const dismissalIdentityKey = (identity, targetVersion) => {
1901
+ return createHash("sha256").update(JSON.stringify([updateInstallationKey(identity), targetVersion])).digest("hex");
1902
+ };
1903
+ const resolveRunnethCliUpdateCachePath = (identity, homePath = resolveRunnethCliHome()) => {
1904
+ return path.join(homePath, "update-cache", `${updateIdentityKey(identity)}.json`);
1905
+ };
1906
+ const resolveRunnethCliUpdateCheckAttemptPath = (identity, homePath = resolveRunnethCliHome()) => {
1907
+ return path.join(homePath, "update-check-attempts", `${updateInstallationKey(identity)}.json`);
1908
+ };
1909
+ const resolveRunnethCliUpdateDismissalPath = (identity, targetVersion, homePath) => {
1910
+ return path.join(homePath, "update-dismissals", `${dismissalIdentityKey(identity, targetVersion)}.json`);
1911
+ };
1912
+ const resolveRunnethCliUpdateCheckErrorPath = (homePath = resolveRunnethCliHome()) => {
1913
+ return path.join(homePath, "update-check-error.log");
1914
+ };
1915
+ const resolveRunnethCliUpdateMutationLockPath = (identity, lockRootPath = path.join(os.userInfo().homedir, ".runneth-cli-update-locks")) => {
1916
+ return path.join(path.resolve(lockRootPath), updateMutationKey(identity));
1917
+ };
1918
+ const withRunnethCliUpdateMutationLock = async (input, operation) => {
1919
+ const lockPath = resolveRunnethCliUpdateMutationLockPath(input.identity, input.lockRootPath);
1920
+ await mkdir(path.dirname(lockPath), { recursive: true });
1921
+ const release = await (0, import_proper_lockfile.lock)(lockPath, {
1922
+ realpath: false,
1923
+ retries: {
1924
+ factor: 1,
1925
+ maxTimeout: RUNNETH_CLI_UPDATE_MUTATION_LOCK_RETRY_MS,
1926
+ minTimeout: RUNNETH_CLI_UPDATE_MUTATION_LOCK_RETRY_MS,
1927
+ retries: RUNNETH_CLI_UPDATE_MUTATION_LOCK_RETRIES
1928
+ },
1929
+ stale: RUNNETH_CLI_UPDATE_LOCK_STALE_MS,
1930
+ update: RUNNETH_CLI_UPDATE_LOCK_REFRESH_MS
1931
+ });
1932
+ try {
1933
+ await assertRunnethCliReplacementStopped(input.replacementRootPath);
1934
+ return await operation();
1935
+ } finally {
1936
+ await release();
1937
+ }
1938
+ };
1939
+ const runWithRunnethCliUpdateDiscoveryLock = async (input, operation) => {
1940
+ const homePath = input.homePath ?? resolveRunnethCliHome();
1941
+ const lockPath = path.join(homePath, "update-discovery", updateDiscoveryKey(input.identity));
1942
+ await mkdir(path.dirname(lockPath), { recursive: true });
1943
+ let release;
1944
+ try {
1945
+ release = await (0, import_proper_lockfile.lock)(lockPath, {
1946
+ realpath: false,
1947
+ retries: 0,
1948
+ stale: RUNNETH_CLI_UPDATE_LOCK_STALE_MS,
1949
+ update: RUNNETH_CLI_UPDATE_LOCK_REFRESH_MS
1950
+ });
1951
+ } catch (error) {
1952
+ if (isErrnoCode(error, "ELOCKED")) return false;
1953
+ throw error;
1954
+ }
1955
+ try {
1956
+ await operation();
1957
+ return true;
1958
+ } finally {
1959
+ await release();
1960
+ }
1961
+ };
1962
+ const readJson = async (filePath) => {
1963
+ try {
1964
+ return JSON.parse(await readFile(filePath, "utf8"));
1965
+ } catch (error) {
1966
+ if (isErrnoCode(error, "ENOENT")) return null;
1967
+ throw error;
1968
+ }
1969
+ };
1970
+ const writeJsonAtomically = async (filePath, value) => {
1971
+ await writeRunnethCliFileAtomically(filePath, `${JSON.stringify(value, null, 2)}\n`);
1972
+ };
1973
+ const claimRunnethCliUpdateCheckAttempt = async (input) => {
1974
+ const homePath = input.homePath ?? resolveRunnethCliHome();
1975
+ const attemptPath = resolveRunnethCliUpdateCheckAttemptPath(input.identity, homePath);
1976
+ await mkdir(path.dirname(attemptPath), { recursive: true });
1977
+ let release;
1978
+ try {
1979
+ release = await (0, import_proper_lockfile.lock)(attemptPath, {
1980
+ realpath: false,
1981
+ retries: 0,
1982
+ stale: RUNNETH_CLI_UPDATE_LOCK_STALE_MS,
1983
+ update: RUNNETH_CLI_UPDATE_LOCK_REFRESH_MS
1984
+ });
1985
+ } catch (error) {
1986
+ if (isErrnoCode(error, "ELOCKED")) return false;
1987
+ throw error;
1988
+ }
1989
+ try {
1990
+ const value = await readJson(attemptPath);
1991
+ const previous = value === null ? null : parseRunnethCliUpdateCheckAttempt(value, input.identity);
1992
+ const now = input.now ?? /* @__PURE__ */ new Date();
1993
+ if (previous !== null && now.getTime() - Date.parse(previous.attemptedAt) < RUNNETH_CLI_UPDATE_CHECK_INTERVAL_MS) return false;
1994
+ await writeJsonAtomically(attemptPath, {
1995
+ ...input.identity,
1996
+ attemptedAt: now.toISOString(),
1997
+ installationRoot: path.resolve(input.identity.installationRoot),
1998
+ schemaVersion: RUNNETH_CLI_UPDATE_CHECK_ATTEMPT_SCHEMA_VERSION
1999
+ });
2000
+ return true;
2001
+ } finally {
2002
+ await release();
2003
+ }
2004
+ };
2005
+ const readRunnethCliUpdateCache = async (identity, homePath = resolveRunnethCliHome()) => {
2006
+ const value = await readJson(resolveRunnethCliUpdateCachePath(identity, homePath));
2007
+ return value === null ? null : parseRunnethCliUpdateCache(value, identity);
2008
+ };
2009
+ const writeRunnethCliUpdateCache = async (cache, homePath = resolveRunnethCliHome()) => {
2010
+ const identity = {
2011
+ installedVersion: cache.installedVersion,
2012
+ packageName: cache.packageName,
2013
+ packageRoot: cache.packageRoot,
2014
+ tag: cache.tag
2015
+ };
2016
+ await writeJsonAtomically(resolveRunnethCliUpdateCachePath(identity, homePath), parseRunnethCliUpdateCache(cache, identity));
2017
+ };
2018
+ const isRunnethCliUpdateDismissed = async (input) => {
2019
+ const homePath = input.homePath ?? resolveRunnethCliHome();
2020
+ const value = await readJson(resolveRunnethCliUpdateDismissalPath(input.identity, input.targetVersion, homePath));
2021
+ if (value === null) return false;
2022
+ parseRunnethCliUpdateDismissal(value, input.identity, input.targetVersion);
2023
+ return true;
2024
+ };
2025
+ const dismissRunnethCliUpdateVersion = async (input) => {
2026
+ const homePath = input.homePath ?? resolveRunnethCliHome();
2027
+ const dismissal = {
2028
+ ...input.identity,
2029
+ installationRoot: path.resolve(input.identity.installationRoot),
2030
+ schemaVersion: RUNNETH_CLI_UPDATE_DISMISSAL_SCHEMA_VERSION,
2031
+ targetVersion: input.targetVersion
2032
+ };
2033
+ await writeJsonAtomically(resolveRunnethCliUpdateDismissalPath(input.identity, input.targetVersion, homePath), parseRunnethCliUpdateDismissal(dismissal, input.identity, input.targetVersion));
2034
+ };
2035
+ const clearRunnethCliUpdateCheckError = async (homePath = resolveRunnethCliHome()) => {
2036
+ await rm(resolveRunnethCliUpdateCheckErrorPath(homePath), { force: true });
2037
+ };
2038
+ const writeRunnethCliUpdateCheckError = async (message, homePath = resolveRunnethCliHome()) => {
2039
+ if (message.trim().length === 0) throw new Error("Runneth CLI update check error must be non-empty");
2040
+ await writeRunnethCliFileAtomically(resolveRunnethCliUpdateCheckErrorPath(homePath), `${message.trim()}\n`);
2041
+ };
2042
+ //#endregion
2043
+ //#region src/update.ts
2044
+ var RunnethCliUpdateCommandError = class extends Error {
2045
+ exitCode;
2046
+ constructor(exitCode, operation = "Runneth CLI update") {
2047
+ super(`${operation} failed with exit code ${String(exitCode)}`);
2048
+ this.name = "RunnethCliUpdateCommandError";
2049
+ this.exitCode = exitCode;
2050
+ }
2051
+ };
2052
+ const runRunnethCliCommand = async (input) => {
2053
+ return await new Promise((resolve, reject) => {
2054
+ const stdio = input.stdio ?? "pipe";
2055
+ const child = (0, import_cross_spawn.default)(input.command, [...input.args], {
2056
+ env: input.env ?? process$1.env,
2057
+ stdio: stdio === "inherit" ? "inherit" : [
2058
+ "ignore",
2059
+ "pipe",
2060
+ "pipe"
2061
+ ],
2062
+ windowsHide: true
2063
+ });
2064
+ let commandError = null;
2065
+ let interruptedSignal = null;
2066
+ let stderr = "";
2067
+ let stdout = "";
2068
+ const signalListeners = [
2069
+ "SIGINT",
2070
+ "SIGTERM",
2071
+ "SIGHUP"
2072
+ ].map((signal) => ({
2073
+ signal,
2074
+ listener: () => {
2075
+ interruptedSignal ??= signal;
2076
+ child.kill(signal);
2077
+ }
2078
+ }));
2079
+ child.once("error", (error) => {
2080
+ commandError = error;
2081
+ });
2082
+ if (stdio === "inherit") for (const { signal, listener } of signalListeners) process$1.on(signal, listener);
2083
+ if (stdio === "pipe") if (child.stdout === null || child.stderr === null) commandError = /* @__PURE__ */ new Error(`${input.command} did not expose piped output streams`);
2084
+ else {
2085
+ child.stdout.setEncoding("utf8");
2086
+ child.stderr.setEncoding("utf8");
2087
+ child.stdout.on("data", (chunk) => {
2088
+ stdout += chunk;
2089
+ });
2090
+ child.stderr.on("data", (chunk) => {
2091
+ stderr += chunk;
2092
+ });
2093
+ }
2094
+ child.once("close", (code, signal) => {
2095
+ for (const { signal, listener } of signalListeners) process$1.off(signal, listener);
2096
+ if (interruptedSignal !== null) {
2097
+ reject(new RunnethCliUpdateCommandError(128 + os.constants.signals[interruptedSignal], input.command));
2098
+ return;
2099
+ }
2100
+ if (commandError !== null) {
2101
+ reject(commandError);
2102
+ return;
2103
+ }
2104
+ if (signal !== null) {
2105
+ reject(/* @__PURE__ */ new Error(`${input.command} exited from signal ${signal}`));
2106
+ return;
2107
+ }
2108
+ if (code === null) {
2109
+ reject(/* @__PURE__ */ new Error(`${input.command} exited without a status code`));
2110
+ return;
2111
+ }
2112
+ resolve({
2113
+ exitCode: code,
2114
+ stderr,
2115
+ stdout
2116
+ });
2117
+ });
2118
+ });
2119
+ };
2120
+ const isPathWithin = (candidatePath, parentPath) => {
2121
+ const relative = path.relative(parentPath, candidatePath);
2122
+ return relative.length === 0 || !relative.startsWith("..") && !path.isAbsolute(relative);
2123
+ };
2124
+ const isCommandUnavailable = (error) => {
2125
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
2126
+ };
2127
+ const parseCommandOutputPath = (result, command) => {
2128
+ if (result.exitCode !== 0) throw new RunnethCliUpdateCommandError(result.exitCode, command);
2129
+ const outputPath = result.stdout.trim();
2130
+ if (outputPath.length === 0 || outputPath.includes("\n") || outputPath.includes("\r") || !path.isAbsolute(outputPath)) throw new Error(`${command} returned an invalid global package root`);
2131
+ return path.resolve(outputPath);
2132
+ };
2133
+ const resolveNpmGlobalPrefix = (globalRoot, platform = process$1.platform) => {
2134
+ if (path.basename(globalRoot) !== "node_modules") throw new Error(`npm root --global returned an unsupported package root: ${globalRoot}`);
2135
+ const parentPath = path.dirname(globalRoot);
2136
+ if (platform === "win32") return parentPath;
2137
+ if (path.basename(parentPath) !== "lib") throw new Error(`npm root --global returned an unsupported package root: ${globalRoot}`);
2138
+ return path.dirname(parentPath);
2139
+ };
2140
+ const resolvePnpmGlobalDir = (globalPackageRoot) => {
2141
+ const globalDir = path.dirname(globalPackageRoot);
2142
+ if (globalDir === globalPackageRoot) throw new Error(`pnpm list --global returned an unsupported global package root: ${globalPackageRoot}`);
2143
+ return globalDir;
2144
+ };
2145
+ const resolveNpmGlobalRoot = async (input) => {
2146
+ return await realpath(parseCommandOutputPath(await input.runner({
2147
+ args: ["root", "--global"],
2148
+ command: "npm",
2149
+ env: input.env
2150
+ }), "npm root --global"));
2151
+ };
2152
+ const isNpmExecInvocation = (entryPath, env) => {
2153
+ if (env.npm_command === "exec") return true;
2154
+ const configuredCache = env.npm_config_cache?.trim();
2155
+ if (configuredCache === void 0 || configuredCache.length === 0) return false;
2156
+ return isPathWithin(path.resolve(path.dirname(entryPath), ".."), path.join(path.resolve(configuredCache), "_npx"));
2157
+ };
2158
+ const parsePnpmGlobalPackage = (result, packageName) => {
2159
+ if (result.exitCode !== 0) throw new Error(`pnpm list --global failed with exit code ${String(result.exitCode)}`);
2160
+ const value = JSON.parse(result.stdout);
2161
+ if (!Array.isArray(value) || value.length !== 1) throw new Error("pnpm list --global returned an invalid package list");
2162
+ const globalProject = value[0];
2163
+ if (typeof globalProject !== "object" || globalProject === null || Array.isArray(globalProject) || !("path" in globalProject) || typeof globalProject.path !== "string" || !path.isAbsolute(globalProject.path)) throw new Error("pnpm list --global returned an invalid global project");
2164
+ if (!("dependencies" in globalProject)) return null;
2165
+ const dependencies = globalProject.dependencies;
2166
+ if (typeof dependencies !== "object" || dependencies === null || Array.isArray(dependencies)) throw new Error("pnpm list --global returned invalid dependencies");
2167
+ const dependency = dependencies[packageName];
2168
+ if (dependency === void 0) return null;
2169
+ if (typeof dependency !== "object" || dependency === null || Array.isArray(dependency) || !("path" in dependency) || typeof dependency.path !== "string" || !path.isAbsolute(dependency.path) || !("version" in dependency) || typeof dependency.version !== "string" || dependency.version.trim().length === 0) throw new Error(`pnpm list --global returned invalid package data for ${packageName}`);
2170
+ return {
2171
+ installationRoot: globalProject.path,
2172
+ packagePath: dependency.path,
2173
+ version: dependency.version
2174
+ };
2175
+ };
2176
+ const resolvePnpmGlobalInstallation = async (input) => {
2177
+ let result;
2178
+ try {
2179
+ result = await input.runner({
2180
+ args: [
2181
+ "list",
2182
+ "--global",
2183
+ input.target.packageName,
2184
+ "--depth=0",
2185
+ "--json"
2186
+ ],
2187
+ command: "pnpm",
2188
+ env: input.env
2189
+ });
2190
+ } catch (error) {
2191
+ if (isCommandUnavailable(error)) return { kind: "no-match" };
2192
+ throw error;
2193
+ }
2194
+ if (result.exitCode !== 0) throw new RunnethCliUpdateCommandError(result.exitCode, "pnpm list --global");
2195
+ const packageRoot = await realpath(input.packageInfo.packageRoot);
2196
+ const installedPackage = parsePnpmGlobalPackage(result, input.target.packageName);
2197
+ if (installedPackage === null) return { kind: "no-match" };
2198
+ const installedPackageRoot = await realpath(installedPackage.packagePath);
2199
+ const installationRoot = await realpath(installedPackage.installationRoot);
2200
+ if (installedPackageRoot !== packageRoot) return { kind: "no-match" };
2201
+ if (installedPackage.version !== input.packageInfo.version) throw new Error(`The active Runneth CLI version ${input.packageInfo.version} does not match the pnpm global version ${installedPackage.version}`);
2202
+ return {
2203
+ context: {
2204
+ installationRoot,
2205
+ kind: "global",
2206
+ manager: "pnpm",
2207
+ pnpmGlobalBinDirectory: parseCommandOutputPath(await input.runner({
2208
+ command: "pnpm",
2209
+ args: ["bin", "--global"],
2210
+ env: input.env
2211
+ }), "pnpm bin --global"),
2212
+ packageInfo: input.packageInfo,
2213
+ target: input.target
2214
+ },
2215
+ kind: "match"
2216
+ };
2217
+ };
2218
+ const resolveNpmGlobalInstallation = async (input) => {
2219
+ const npmGlobalRoot = await resolveNpmGlobalRoot(input);
2220
+ const packageRoot = await realpath(input.packageInfo.packageRoot);
2221
+ if (!isPathWithin(packageRoot, npmGlobalRoot)) return { kind: "no-match" };
2222
+ if (await realpath(path.join(npmGlobalRoot, ...input.target.packageName.split("/"))) !== packageRoot) throw new Error("The active Runneth CLI does not match the npm global installation");
2223
+ return {
2224
+ kind: "match",
2225
+ context: {
2226
+ installationRoot: npmGlobalRoot,
2227
+ kind: "global",
2228
+ manager: "npm",
2229
+ pnpmGlobalBinDirectory: null,
2230
+ packageInfo: input.packageInfo,
2231
+ target: input.target
2232
+ }
2233
+ };
2234
+ };
2235
+ const detectRunnethCliInstallContext = async (input) => {
2236
+ const env = input.env ?? process$1.env;
2237
+ const runner = input.runner ?? runRunnethCliCommand;
2238
+ const target = resolveRunnethCliUpdateTarget(input.packageInfo);
2239
+ if (target === null) return {
2240
+ kind: "source",
2241
+ packageInfo: input.packageInfo
2242
+ };
2243
+ if (isNpmExecInvocation(input.entryPath, env)) return {
2244
+ kind: "npm-exec",
2245
+ packageInfo: input.packageInfo,
2246
+ target
2247
+ };
2248
+ const probe = {
2249
+ env,
2250
+ packageInfo: input.packageInfo,
2251
+ runner,
2252
+ target
2253
+ };
2254
+ const resolutions = await Promise.allSettled([resolvePnpmGlobalInstallation(probe), resolveNpmGlobalInstallation(probe)]);
2255
+ for (const resolution of resolutions) if (resolution.status === "fulfilled" && resolution.value.kind === "match") return resolution.value.context;
2256
+ for (const resolution of resolutions) if (resolution.status === "rejected") throw resolution.reason;
2257
+ return {
2258
+ kind: "local",
2259
+ packageInfo: input.packageInfo,
2260
+ target
2261
+ };
2262
+ };
2263
+ const resolveApplicableContext = (context) => {
2264
+ switch (context.kind) {
2265
+ case "global": return context;
2266
+ case "source": throw new Error("Source checkouts do not self-update");
2267
+ case "local": throw new Error("Project-local Runneth CLI installations must be updated through the project package manager");
2268
+ case "npm-exec": throw new Error(`npm-exec installations are ephemeral. Rerun with \`npx ${context.target.packageName}@${context.target.tag}\`.`);
2269
+ default: return context;
2270
+ }
2271
+ };
2272
+ const isRecord$1 = (value) => {
2273
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2274
+ };
2275
+ const requirePnpmGlobalBinDirectory = (directory) => {
2276
+ if (directory === null || !path.isAbsolute(directory)) throw new Error("The pnpm global executable directory is unavailable");
2277
+ return directory;
2278
+ };
2279
+ const actionFromContext = (context, packageSpecifier) => {
2280
+ const packageReference = `${context.target.packageName}@${packageSpecifier}`;
2281
+ const args = context.manager === "npm" ? [
2282
+ "install",
2283
+ "--global",
2284
+ packageReference,
2285
+ "--prefix",
2286
+ resolveNpmGlobalPrefix(context.installationRoot)
2287
+ ] : [
2288
+ "add",
2289
+ "--global",
2290
+ packageReference,
2291
+ "--global-dir",
2292
+ resolvePnpmGlobalDir(context.installationRoot),
2293
+ "--global-bin-dir",
2294
+ requirePnpmGlobalBinDirectory(context.pnpmGlobalBinDirectory)
2295
+ ];
2296
+ return {
2297
+ args,
2298
+ command: context.manager,
2299
+ displayCommand: [context.manager, ...args].join(" "),
2300
+ expectedInstalledVersion: context.packageInfo.version,
2301
+ installation: {
2302
+ installationRoot: context.installationRoot,
2303
+ manager: context.manager,
2304
+ packageName: context.target.packageName,
2305
+ tag: context.target.tag
2306
+ }
2307
+ };
2308
+ };
2309
+ const resolveRunnethCliUpdateAction = async (input) => {
2310
+ const env = input.env ?? process$1.env;
2311
+ const runner = input.runner ?? runRunnethCliCommand;
2312
+ const packageInfo = await readRunnethCliPackageInfo(input.moduleUrl);
2313
+ const context = resolveApplicableContext(await detectRunnethCliInstallContext({
2314
+ entryPath: input.entryPath,
2315
+ env,
2316
+ packageInfo,
2317
+ runner
2318
+ }));
2319
+ return actionFromContext(context, context.target.tag);
2320
+ };
2321
+ const readInstalledVersionForUpdate = async (input) => {
2322
+ const installation = input.action.installation;
2323
+ if (installation.manager === "pnpm") {
2324
+ const result = await input.runner({
2325
+ args: [
2326
+ "list",
2327
+ "--global",
2328
+ installation.packageName,
2329
+ "--depth=0",
2330
+ "--json",
2331
+ "--global-dir",
2332
+ resolvePnpmGlobalDir(installation.installationRoot)
2333
+ ],
2334
+ command: "pnpm",
2335
+ env: input.env
2336
+ });
2337
+ if (result.exitCode !== 0) throw new RunnethCliUpdateCommandError(result.exitCode, "pnpm list --global");
2338
+ const installedPackage = parsePnpmGlobalPackage(result, installation.packageName);
2339
+ if (installedPackage === null) throw new Error(`The pnpm global installation does not contain ${installation.packageName}`);
2340
+ const activeInstallationRoot = await realpath(installedPackage.installationRoot);
2341
+ if (activeInstallationRoot !== installation.installationRoot) throw new Error(`The active pnpm global installation moved from ${installation.installationRoot} to ${activeInstallationRoot}`);
2342
+ return parseRunnethCliVersion(installedPackage.version, "Installed Runneth CLI version");
2343
+ }
2344
+ const packageRoot = path.join(installation.installationRoot, ...installation.packageName.split("/"));
2345
+ const manifestPath = path.join(packageRoot, "package.json");
2346
+ const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
2347
+ if (!isRecord$1(manifest)) throw new Error(`Installed Runneth CLI package manifest must be an object: ${manifestPath}`);
2348
+ if (manifest.name !== installation.packageName) throw new Error(`Installed Runneth CLI package name does not match ${installation.packageName}`);
2349
+ return parseRunnethCliVersion(manifest.version, "Installed Runneth CLI version");
2350
+ };
2351
+ const runRunnethCliUpdateAction = async (input) => {
2352
+ const env = input.env ?? process$1.env;
2353
+ const runner = input.runner ?? runRunnethCliCommand;
2354
+ await withRunnethCliUpdateMutationLock({
2355
+ identity: input.action.installation,
2356
+ lockRootPath: input.mutationLockRootPath,
2357
+ replacementRootPath: input.replacementRootPath
2358
+ }, async () => {
2359
+ const installedVersion = await readInstalledVersionForUpdate({
2360
+ action: input.action,
2361
+ env,
2362
+ runner
2363
+ });
2364
+ if (installedVersion !== input.action.expectedInstalledVersion) throw new Error(`Runneth CLI changed from ${input.action.expectedInstalledVersion} to ${installedVersion} before the update could start. Rerun the command to use the current installation.`);
2365
+ const result = await runWithRunnethCliReplacement({
2366
+ args: input.action.args,
2367
+ command: input.action.command,
2368
+ installationRoot: input.action.installation.installationRoot,
2369
+ packageName: input.action.installation.packageName,
2370
+ rootPath: input.replacementRootPath
2371
+ }, async () => await runner({
2372
+ args: input.action.args,
2373
+ command: input.action.command,
2374
+ env,
2375
+ stdio: "inherit"
2376
+ }));
2377
+ if (result.exitCode !== 0) throw new RunnethCliUpdateCommandError(result.exitCode);
2378
+ });
2379
+ };
2380
+ const parseRegistryVersion = (result, packageReference) => {
2381
+ if (result.exitCode !== 0) throw new Error(`Failed to resolve ${packageReference}: npm exited with code ${String(result.exitCode)}`);
2382
+ return parseRunnethCliVersion(JSON.parse(result.stdout), `Failed to resolve ${packageReference}: registry version`);
2383
+ };
2384
+ const parseRegistryPublicationTimes = (result, packageName, installedVersion, latestVersion) => {
2385
+ if (result.exitCode !== 0) throw new Error(`Failed to resolve ${packageName} publication times: npm exited with code ${String(result.exitCode)}`);
2386
+ const value = JSON.parse(result.stdout);
2387
+ if (!isRecord$1(value)) throw new Error(`Failed to resolve ${packageName} publication times: registry time metadata must be an object`);
2388
+ return {
2389
+ installedVersionPublishedAt: parseRunnethCliPublicationTime(value[installedVersion], `Failed to resolve ${packageName}@${installedVersion} publication time`),
2390
+ latestVersionPublishedAt: parseRunnethCliPublicationTime(value[latestVersion], `Failed to resolve ${packageName}@${latestVersion} publication time`)
2391
+ };
2392
+ };
2393
+ const installationIdentity = (context) => {
2394
+ return {
2395
+ installationRoot: context.installationRoot,
2396
+ manager: context.manager,
2397
+ packageName: context.target.packageName,
2398
+ tag: context.target.tag
2399
+ };
2400
+ };
2401
+ const cacheIdentity = (context) => {
2402
+ return {
2403
+ installedVersion: context.packageInfo.version,
2404
+ packageName: context.target.packageName,
2405
+ packageRoot: context.packageInfo.packageRoot,
2406
+ tag: context.target.tag
2407
+ };
2408
+ };
2409
+ const discoverAndCacheRunnethCliUpdate = async (input) => {
2410
+ const identity = cacheIdentity(input.context);
2411
+ const packageReference = `${input.context.target.packageName}@${input.context.target.tag}`;
2412
+ const latestVersion = parseRegistryVersion(await input.runner({
2413
+ args: [
2414
+ "view",
2415
+ packageReference,
2416
+ "version",
2417
+ "--json"
2418
+ ],
2419
+ command: "npm",
2420
+ env: input.env
2421
+ }), packageReference);
2422
+ const publicationTimes = parseRegistryPublicationTimes(await input.runner({
2423
+ args: [
2424
+ "view",
2425
+ input.context.target.packageName,
2426
+ "time",
2427
+ "--json"
2428
+ ],
2429
+ command: "npm",
2430
+ env: input.env
2431
+ }), input.context.target.packageName, input.context.packageInfo.version, latestVersion);
2432
+ const cache = {
2433
+ ...identity,
2434
+ ...installationIdentity(input.context),
2435
+ ...publicationTimes,
2436
+ pnpmGlobalBinDirectory: input.context.pnpmGlobalBinDirectory,
2437
+ lastCheckedAt: input.now.toISOString(),
2438
+ latestVersion,
2439
+ schemaVersion: 2
2440
+ };
2441
+ await writeRunnethCliUpdateCache(cache, input.homePath);
2442
+ return cache;
2443
+ };
2444
+ const runRunnethCliBackgroundUpdateCheckAttempt = async (input) => {
2445
+ if (isNpmExecInvocation(input.entryPath, input.env)) return;
2446
+ const packageInfo = await readRunnethCliPackageInfo(input.moduleUrl);
2447
+ const target = resolveRunnethCliUpdateTarget(packageInfo);
2448
+ if (target === null) return;
2449
+ await runWithRunnethCliUpdateDiscoveryLock({
2450
+ homePath: input.homePath,
2451
+ identity: {
2452
+ packageName: target.packageName,
2453
+ packageRoot: packageInfo.packageRoot,
2454
+ tag: target.tag
2455
+ }
2456
+ }, async () => {
2457
+ const context = await detectRunnethCliInstallContext({
2458
+ entryPath: input.entryPath,
2459
+ env: input.env,
2460
+ packageInfo,
2461
+ runner: input.runner
2462
+ });
2463
+ if (context.kind !== "global") return;
2464
+ if (!await claimRunnethCliUpdateCheckAttempt({
2465
+ homePath: input.homePath,
2466
+ identity: installationIdentity(context),
2467
+ now: input.now
2468
+ })) return;
2469
+ await clearRunnethCliUpdateCheckError(input.homePath);
2470
+ await discoverAndCacheRunnethCliUpdate({
2471
+ context,
2472
+ env: input.env,
2473
+ homePath: input.homePath,
2474
+ now: input.now,
2475
+ runner: input.runner
2476
+ });
2477
+ });
2478
+ };
2479
+ const runRunnethCliBackgroundUpdateCheck = async (input) => {
2480
+ const homePath = input.homePath ?? resolveRunnethCliHome();
2481
+ try {
2482
+ await runRunnethCliBackgroundUpdateCheckAttempt({
2483
+ entryPath: input.entryPath,
2484
+ env: input.env ?? process$1.env,
2485
+ homePath,
2486
+ moduleUrl: input.moduleUrl,
2487
+ now: input.now ?? /* @__PURE__ */ new Date(),
2488
+ runner: input.runner ?? runRunnethCliCommand
2489
+ });
2490
+ } catch (error) {
2491
+ await writeRunnethCliUpdateCheckError(`Runneth CLI update check failed: ${error instanceof Error ? error.message : String(error)}`, homePath);
2492
+ throw error;
2493
+ }
2494
+ };
2495
+ const startRunnethCliBackgroundUpdateCheck = (input) => {
2496
+ const child = spawn(process$1.execPath, [
2497
+ input.entryPath,
2498
+ "__update-check",
2499
+ input.entryPath
2500
+ ], {
2501
+ detached: true,
2502
+ env: input.env,
2503
+ stdio: "ignore",
2504
+ windowsHide: true
2505
+ });
2506
+ child.once("error", (error) => {
2507
+ process$1.stderr.write(`Unable to start the Runneth CLI background update check: ${error.message}\n`);
2508
+ });
2509
+ child.unref();
2510
+ };
2511
+ const scheduleRunnethCliUpdateCheck = (input) => {
2512
+ const env = input.env ?? process$1.env;
2513
+ if (isNpmExecInvocation(input.entryPath, env)) return false;
2514
+ startRunnethCliBackgroundUpdateCheck({
2515
+ entryPath: input.entryPath,
2516
+ env
2517
+ });
2518
+ return true;
2519
+ };
2520
+ const readRunnethCliCachedUpdate = async (input) => {
2521
+ if (isNpmExecInvocation(input.entryPath, input.env ?? process$1.env)) return null;
2522
+ const packageInfo = await readRunnethCliPackageInfo(input.moduleUrl);
2523
+ const target = resolveRunnethCliUpdateTarget(packageInfo);
2524
+ if (target === null) return null;
2525
+ const cache = await readRunnethCliUpdateCache({
2526
+ installedVersion: packageInfo.version,
2527
+ packageName: target.packageName,
2528
+ packageRoot: packageInfo.packageRoot,
2529
+ tag: target.tag
2530
+ }, input.homePath ?? resolveRunnethCliHome());
2531
+ if (cache === null || !isRunnethCliReleaseNewer({
2532
+ candidatePublishedAt: cache.latestVersionPublishedAt,
2533
+ candidateVersion: cache.latestVersion,
2534
+ installedPublishedAt: cache.installedVersionPublishedAt,
2535
+ installedVersion: packageInfo.version
2536
+ }) || await isRunnethCliUpdateDismissed({
2537
+ homePath: input.homePath,
2538
+ identity: cache,
2539
+ targetVersion: cache.latestVersion
2540
+ })) return null;
2541
+ return {
2542
+ action: {
2543
+ args: cache.manager === "npm" ? [
2544
+ "install",
2545
+ "--global",
2546
+ `${cache.packageName}@${cache.latestVersion}`,
2547
+ "--prefix",
2548
+ resolveNpmGlobalPrefix(cache.installationRoot)
2549
+ ] : [
2550
+ "add",
2551
+ "--global",
2552
+ `${cache.packageName}@${cache.latestVersion}`,
2553
+ "--global-dir",
2554
+ resolvePnpmGlobalDir(cache.installationRoot),
2555
+ "--global-bin-dir",
2556
+ requirePnpmGlobalBinDirectory(cache.pnpmGlobalBinDirectory)
2557
+ ],
2558
+ command: cache.manager,
2559
+ displayCommand: cache.manager === "npm" ? `npm install --global ${cache.packageName}@${cache.latestVersion} --prefix ${resolveNpmGlobalPrefix(cache.installationRoot)}` : `pnpm add --global ${cache.packageName}@${cache.latestVersion} --global-dir ${resolvePnpmGlobalDir(cache.installationRoot)} --global-bin-dir ${requirePnpmGlobalBinDirectory(cache.pnpmGlobalBinDirectory)}`,
2560
+ expectedInstalledVersion: packageInfo.version,
2561
+ installation: cache
2562
+ },
2563
+ currentVersion: packageInfo.version,
2564
+ dismissal: {
2565
+ identity: cache,
2566
+ targetVersion: cache.latestVersion
2567
+ },
2568
+ latestVersion: cache.latestVersion
2569
+ };
2570
+ };
2571
+ const dismissRunnethCliUpdate = async (input) => {
2572
+ await dismissRunnethCliUpdateVersion({
2573
+ homePath: input.homePath,
2574
+ identity: input.dismissal.identity,
2575
+ targetVersion: input.dismissal.targetVersion
2576
+ });
2577
+ };
2578
+ //#endregion
2579
+ //#region src/cli.ts
2580
+ const DEFAULT_TIMEOUT_MS = 6e5;
2581
+ const DEFAULT_OAUTH_CLIENT_NAME = "Runneth MCP";
2582
+ const MAX_STDIN_BYTES = 1048576;
2583
+ const RUNNETH_OAUTH_CLIENT_ID_ENV = "RUNNETH_OAUTH_CLIENT_ID";
2584
+ const RUNNETH_OAUTH_CLIENT_SECRET_ENV = "RUNNETH_OAUTH_CLIENT_SECRET";
2585
+ const resolveCliEntryPath = () => {
2586
+ const entryPath = process$1.argv[1];
2587
+ if (entryPath === void 0) throw new Error("Runneth CLI entry path is unavailable");
2588
+ return path.resolve(entryPath);
2589
+ };
2590
+ const isRecord = (value) => {
2591
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2592
+ };
2593
+ const parseStringField = (record, key) => {
2594
+ const value = record[key];
2595
+ if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${key} must be a non-empty string`);
2596
+ return value;
2597
+ };
2598
+ const parseOptionalStringField = (record, key) => {
2599
+ const value = record[key];
2600
+ if (value === void 0) return;
2601
+ if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${key} must be a non-empty string`);
2602
+ return value;
2603
+ };
2604
+ const resolveDefaultResourceUrl = () => {
2605
+ return resolveBuildDefaultMcpResourceUrl();
2606
+ };
2607
+ const requireResourceUrl = (resourceUrl, commandDescription) => {
2608
+ if (resourceUrl === void 0) throw new Error(`${commandDescription} requires --resource <url> because this CLI build does not include MONDRIAN_API_URL`);
2609
+ return resourceUrl;
2610
+ };
2611
+ const readStdin = async () => {
2612
+ const chunks = [];
2613
+ let totalBytes = 0;
2614
+ for await (const chunk of process$1.stdin) {
2615
+ const buffer = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
2616
+ totalBytes += buffer.length;
2617
+ if (totalBytes > MAX_STDIN_BYTES) throw new Error(`stdin input exceeds ${String(MAX_STDIN_BYTES)} bytes`);
2618
+ chunks.push(buffer);
2619
+ }
2620
+ return Buffer.concat(chunks, totalBytes).toString("utf8");
2621
+ };
2622
+ const parseNumber = (rawValue, label) => {
2623
+ const value = Number(rawValue);
2624
+ if (!Number.isInteger(value) || value <= 0) throw new Error(`${label} must be a positive integer`);
2625
+ return value;
2626
+ };
2627
+ const resolveCliOAuthClientCredentialOptions = (parsed) => {
2628
+ return resolveOAuthClientCredentialOptions({
2629
+ clientId: parsed.clientId ?? process$1.env[RUNNETH_OAUTH_CLIENT_ID_ENV],
2630
+ clientSecret: parsed.clientSecret ?? process$1.env[RUNNETH_OAUTH_CLIENT_SECRET_ENV]
2631
+ });
2632
+ };
2633
+ const parseArgs = (argv) => {
2634
+ const values = [];
2635
+ let cwd;
2636
+ let name = "default";
2637
+ let shell;
2638
+ let stdin = false;
2639
+ let timeoutMs = DEFAULT_TIMEOUT_MS;
2640
+ let parseOptions = true;
2641
+ for (let index = 0; index < argv.length; index += 1) {
2642
+ const token = argv[index];
2643
+ if (parseOptions && token === "--") {
2644
+ parseOptions = false;
2645
+ continue;
2646
+ }
2647
+ if (!parseOptions || !token.startsWith("--")) {
2648
+ values.push(token);
2649
+ continue;
2650
+ }
2651
+ const readValue = (label) => {
2652
+ const value = argv[index + 1];
2653
+ if (value === void 0) throw new Error(`Missing value for ${label}`);
2654
+ index += 1;
2655
+ return value;
2656
+ };
2657
+ switch (token) {
2658
+ case "--cwd":
2659
+ cwd = readValue(token);
2660
+ break;
2661
+ case "--name":
2662
+ name = normalizeSessionName(readValue(token));
2663
+ break;
2664
+ case "--shell":
2665
+ shell = readValue(token);
2666
+ break;
2667
+ case "--stdin":
2668
+ stdin = true;
2669
+ break;
2670
+ case "--timeout-ms":
2671
+ timeoutMs = parseNumber(readValue(token), token);
2672
+ break;
2673
+ default: throw new Error(`Unknown option: ${token}`);
2674
+ }
2675
+ }
2676
+ return {
2677
+ cwd,
2678
+ name,
2679
+ shell,
2680
+ stdin,
2681
+ timeoutMs,
2682
+ values
2683
+ };
2684
+ };
2685
+ const parseSkillAgent = (value) => {
2686
+ if (value === "all") return ["claude", "codex"];
2687
+ if (value === "claude" || value === "codex") return [value];
2688
+ throw new Error(`Invalid skill agent: ${value}. Use claude, codex, or all.`);
2689
+ };
2690
+ const parseSkillsArgs = (argv) => {
2691
+ let agents = ["claude", "codex"];
2692
+ const values = [];
2693
+ for (let index = 0; index < argv.length; index += 1) {
2694
+ const token = argv[index];
2695
+ if (token === "--agent") {
2696
+ const value = argv[index + 1];
2697
+ if (value === void 0) throw new Error("Missing value for --agent");
2698
+ agents = parseSkillAgent(value);
2699
+ index += 1;
2700
+ continue;
2701
+ }
2702
+ values.push(token);
2703
+ }
2704
+ if (values.length > 0) throw new Error("runneth skills install only accepts --agent");
2705
+ return { agents };
2706
+ };
2707
+ const parseOAuthArgs = (argv) => {
2708
+ const values = [];
2709
+ let clientId;
2710
+ let clientName = DEFAULT_OAUTH_CLIENT_NAME;
2711
+ let clientSecret;
2712
+ let openBrowser = true;
2713
+ let resourceUrl;
2714
+ let scope;
2715
+ let timeoutMs = DEFAULT_TIMEOUT_MS;
2716
+ let parseOptions = true;
2717
+ for (let index = 0; index < argv.length; index += 1) {
2718
+ const token = argv[index];
2719
+ if (parseOptions && token === "--") {
2720
+ parseOptions = false;
2721
+ continue;
2722
+ }
2723
+ if (!parseOptions || !token.startsWith("--")) {
2724
+ values.push(token);
2725
+ continue;
2726
+ }
2727
+ const readValue = (label) => {
2728
+ const value = argv[index + 1];
2729
+ if (value === void 0) throw new Error(`Missing value for ${label}`);
2730
+ index += 1;
2731
+ return value;
2732
+ };
2733
+ switch (token) {
2734
+ case "--client-id":
2735
+ case "--clientid":
2736
+ clientId = readValue(token);
2737
+ break;
2738
+ case "--client-name":
2739
+ clientName = readValue(token);
2740
+ break;
2741
+ case "--client-secret":
2742
+ case "--secret":
2743
+ clientSecret = readValue(token);
2744
+ break;
2745
+ case "--no-open":
2746
+ openBrowser = false;
2747
+ break;
2748
+ case "--resource":
2749
+ resourceUrl = readValue(token);
2750
+ break;
2751
+ case "--scope":
2752
+ scope = readValue(token);
2753
+ break;
2754
+ case "--timeout-ms":
2755
+ timeoutMs = parseNumber(readValue(token), token);
2756
+ break;
2757
+ default: throw new Error(`Unknown option: ${token}`);
2758
+ }
2759
+ }
2760
+ return {
2761
+ ...clientId === void 0 ? {} : { clientId },
2762
+ clientName,
2763
+ ...clientSecret === void 0 ? {} : { clientSecret },
2764
+ openBrowser,
2765
+ resourceUrl,
2766
+ scope,
2767
+ timeoutMs,
2768
+ values
2769
+ };
2770
+ };
2771
+ const parseSshArgs = (argv) => {
2772
+ const values = [];
2773
+ let apiBaseUrl;
2774
+ let authMode = "mondrian";
2775
+ let clientId;
2776
+ let clientName = DEFAULT_OAUTH_CLIENT_NAME;
2777
+ let clientSecret;
2778
+ let conversationId;
2779
+ let identityFilePath;
2780
+ let makeDefault = false;
2781
+ let openBrowser = true;
2782
+ let resourceUrl;
2783
+ let scope;
2784
+ let sshHost;
2785
+ let sshUrl;
2786
+ let targetName;
2787
+ let tenanthId = "motion";
2788
+ let timeoutMs = DEFAULT_TIMEOUT_MS;
2789
+ let tokenValue;
2790
+ let uniqueKey = false;
2791
+ let workspaceId;
2792
+ let argumentSeparatorSeen = false;
2793
+ let parseOptions = true;
2794
+ for (let index = 0; index < argv.length; index += 1) {
2795
+ const token = argv[index];
2796
+ if (parseOptions && token === "--") {
2797
+ argumentSeparatorSeen = true;
2798
+ parseOptions = false;
2799
+ continue;
2800
+ }
2801
+ if (!parseOptions || !token.startsWith("--")) {
2802
+ values.push(token);
2803
+ continue;
2804
+ }
2805
+ const readValue = (label) => {
2806
+ const value = argv[index + 1];
2807
+ if (value === void 0) throw new Error(`Missing value for ${label}`);
2808
+ index += 1;
2809
+ return value;
2810
+ };
2811
+ switch (token) {
2812
+ case "--api":
2813
+ apiBaseUrl = readValue(token);
2814
+ break;
2815
+ case "--auth": {
2816
+ const value = readValue(token);
2817
+ if (value !== "builder" && value !== "mondrian") throw new Error("--auth must be builder or mondrian");
2818
+ authMode = value;
2819
+ break;
2820
+ }
2821
+ case "--client-id":
2822
+ case "--clientid":
2823
+ clientId = readValue(token);
2824
+ break;
2825
+ case "--client-name":
2826
+ clientName = readValue(token);
2827
+ break;
2828
+ case "--client-secret":
2829
+ case "--secret":
2830
+ clientSecret = readValue(token);
2831
+ break;
2832
+ case "--conversation":
2833
+ conversationId = readValue(token);
2834
+ break;
2835
+ case "--default":
2836
+ makeDefault = true;
2837
+ break;
2838
+ case "--host":
2839
+ sshHost = readValue(token);
2840
+ break;
2841
+ case "--identity-file":
2842
+ identityFilePath = readValue(token);
2843
+ break;
2844
+ case "--no-open":
2845
+ openBrowser = false;
2846
+ break;
2847
+ case "--resource":
2848
+ resourceUrl = readValue(token);
2849
+ break;
2850
+ case "--scope":
2851
+ scope = readValue(token);
2852
+ break;
2853
+ case "--ssh-url":
2854
+ sshUrl = readValue(token);
2855
+ break;
2856
+ case "--target":
2857
+ targetName = readValue(token);
2858
+ break;
2859
+ case "--tenanth":
2860
+ tenanthId = readValue(token);
2861
+ break;
2862
+ case "--timeout-ms":
2863
+ timeoutMs = parseNumber(readValue(token), token);
2864
+ break;
2865
+ case "--token":
2866
+ tokenValue = readValue(token);
2867
+ break;
2868
+ case "--unique-key":
2869
+ uniqueKey = true;
2870
+ break;
2871
+ case "--workspace":
2872
+ workspaceId = readValue(token);
2873
+ break;
2874
+ default: throw new Error(`Unknown option: ${token}`);
2875
+ }
2876
+ }
2877
+ return {
2878
+ ...apiBaseUrl === void 0 ? {} : { apiBaseUrl },
2879
+ argumentSeparatorSeen,
2880
+ authMode,
2881
+ ...clientId === void 0 ? {} : { clientId },
2882
+ clientName,
2883
+ ...clientSecret === void 0 ? {} : { clientSecret },
2884
+ ...conversationId === void 0 ? {} : { conversationId },
2885
+ ...identityFilePath === void 0 ? {} : { identityFilePath },
2886
+ makeDefault,
2887
+ openBrowser,
2888
+ resourceUrl,
2889
+ scope,
2890
+ ...sshHost === void 0 ? {} : { sshHost },
2891
+ sshUrl,
2892
+ ...targetName === void 0 ? {} : { targetName },
2893
+ tenanthId,
2894
+ timeoutMs,
2895
+ ...tokenValue === void 0 ? {} : { token: tokenValue },
2896
+ uniqueKey,
2897
+ values,
2898
+ ...workspaceId === void 0 ? {} : { workspaceId }
2899
+ };
2900
+ };
2901
+ const parseCopyArgs = (argv) => {
2902
+ const values = [];
2903
+ let clientId;
2904
+ let clientName = DEFAULT_OAUTH_CLIENT_NAME;
2905
+ let clientSecret;
2906
+ let destinationTargetName;
2907
+ const ignoredPaths = [];
2908
+ let openBrowser = true;
2909
+ let scope;
2910
+ let sourceTargetName;
2911
+ let timeoutMs = DEFAULT_TIMEOUT_MS;
2912
+ let parseOptions = true;
2913
+ for (let index = 0; index < argv.length; index += 1) {
2914
+ const token = argv[index];
2915
+ if (parseOptions && token === "--") {
2916
+ parseOptions = false;
2917
+ continue;
2918
+ }
2919
+ if (!parseOptions || !token.startsWith("--")) {
2920
+ values.push(token);
2921
+ continue;
2922
+ }
2923
+ const readValue = (label) => {
2924
+ const value = argv[index + 1];
2925
+ if (value === void 0) throw new Error(`Missing value for ${label}`);
2926
+ index += 1;
2927
+ return value;
2928
+ };
2929
+ switch (token) {
2930
+ case "--client-id":
2931
+ case "--clientid":
2932
+ clientId = readValue(token);
2933
+ break;
2934
+ case "--client-name":
2935
+ clientName = readValue(token);
2936
+ break;
2937
+ case "--client-secret":
2938
+ case "--secret":
2939
+ clientSecret = readValue(token);
2940
+ break;
2941
+ case "--from":
2942
+ sourceTargetName = normalizeRunnethSshTargetName(readValue(token));
2943
+ break;
2944
+ case "--ignore":
2945
+ ignoredPaths.push(readValue(token));
2946
+ break;
2947
+ case "--no-open":
2948
+ openBrowser = false;
2949
+ break;
2950
+ case "--scope":
2951
+ scope = readValue(token);
2952
+ break;
2953
+ case "--timeout-ms":
2954
+ timeoutMs = parseNumber(readValue(token), token);
2955
+ break;
2956
+ case "--to":
2957
+ destinationTargetName = normalizeRunnethSshTargetName(readValue(token));
2958
+ break;
2959
+ default: throw new Error(`Unknown option: ${token}`);
2960
+ }
2961
+ }
2962
+ if (sourceTargetName === void 0) throw new Error("runneth copy requires --from <target>");
2963
+ if (destinationTargetName === void 0) throw new Error("runneth copy requires --to <target>");
2964
+ return {
2965
+ ...clientId === void 0 ? {} : { clientId },
2966
+ clientName,
2967
+ ...clientSecret === void 0 ? {} : { clientSecret },
2968
+ destinationTargetName,
2969
+ ignoredPaths,
2970
+ openBrowser,
2971
+ scope,
2972
+ sourceTargetName,
2973
+ timeoutMs,
2974
+ values
2975
+ };
2976
+ };
2977
+ const resolveOAuthResourceUrl = (parsed) => {
2978
+ return requireResourceUrl(parsed.resourceUrl ?? parsed.values[0] ?? resolveDefaultResourceUrl(), "runneth oauth");
2979
+ };
2980
+ const normalizeSshHost = (value) => {
2981
+ const sshHost = value.trim();
2982
+ if (sshHost.length === 0 || sshHost.includes("/") || sshHost.includes("?") || sshHost.includes("#") || sshHost.includes("@") || /^[A-Za-z][A-Za-z0-9+.-]*:\/\//u.test(sshHost) || /\s/u.test(sshHost)) throw new Error("SSH host must be a full hostname without scheme or path");
2983
+ const url = new URL(`https://${sshHost}/runneth/ssh`);
2984
+ if (url.hostname.length === 0) throw new Error("SSH host must be a full hostname without scheme or path");
2985
+ return url.host;
2986
+ };
2987
+ const resolveSshUrlFromHost = (host) => {
2988
+ return `https://${normalizeSshHost(host)}/runneth/ssh`;
2989
+ };
2990
+ const resolveSshUrlOption = (parsed) => {
2991
+ if (parsed.sshUrl !== void 0 && parsed.sshHost !== void 0) throw new Error("Use either --ssh-url or --host, not both");
2992
+ if (parsed.sshHost === void 0) return parsed.sshUrl;
2993
+ return resolveSshUrlFromHost(parsed.sshHost);
2994
+ };
2995
+ const resolveSshResourceUrlOption = (parsed) => {
2996
+ if (parsed.resourceUrl !== void 0) return parsed.resourceUrl;
2997
+ if (parsed.conversationId !== void 0 || parsed.sshHost !== void 0 || parsed.sshUrl !== void 0) return resolveDefaultResourceUrl();
2998
+ };
2999
+ const hasSshConversationOptions = (parsed) => {
3000
+ return parsed.apiBaseUrl !== void 0 || parsed.authMode !== "mondrian" || parsed.conversationId !== void 0 || parsed.tenanthId !== "motion" || parsed.token !== void 0 || parsed.workspaceId !== void 0;
3001
+ };
3002
+ const assertNoSshConversationOptions = (parsed, commandDescription) => {
3003
+ if (hasSshConversationOptions(parsed)) throw new Error(`${commandDescription} does not accept conversation target options`);
3004
+ };
3005
+ const resolveSshTargetForCommand = async (parsed) => {
3006
+ assertNoSshConversationOptions(parsed, "This SSH command");
3007
+ const sshUrl = resolveSshUrlOption(parsed);
3008
+ const resourceUrl = resolveSshResourceUrlOption(parsed);
3009
+ if (sshUrl !== void 0 && resourceUrl === void 0) requireResourceUrl(resourceUrl, "runneth ssh");
3010
+ return await resolveRunnethSshTarget({
3011
+ resourceUrl,
3012
+ sshUrl,
3013
+ targetName: parsed.targetName
3014
+ });
3015
+ };
3016
+ const resolveSshCommandContext = async (parsed) => {
3017
+ if (parsed.conversationId === void 0) {
3018
+ assertNoSshConversationOptions(parsed, "runneth ssh");
3019
+ const target = await resolveSshTargetForCommand(parsed);
3020
+ return {
3021
+ oauthToken: await resolveSshOAuthToken(parsed, target.resourceUrl),
3022
+ target
3023
+ };
3024
+ }
3025
+ if (parsed.sshHost !== void 0 || parsed.sshUrl !== void 0 || parsed.targetName !== void 0) throw new Error("Use either --conversation or --target/--host/--ssh-url, not both");
3026
+ const resourceUrl = requireResourceUrl(resolveSshResourceUrlOption(parsed), "runneth ssh --conversation");
3027
+ const oauthToken = await resolveSshOAuthToken(parsed, resourceUrl);
3028
+ return {
3029
+ oauthToken,
3030
+ target: {
3031
+ resourceUrl,
3032
+ sshUrl: (await fetchConversationSshTarget(await resolveRunnethConversationOptions({
3033
+ ...parsed.apiBaseUrl === void 0 ? {} : { apiBaseUrl: parsed.apiBaseUrl },
3034
+ authMode: parsed.authMode,
3035
+ clientId: parsed.clientId,
3036
+ clientName: parsed.clientName,
3037
+ clientSecret: parsed.clientSecret,
3038
+ conversationId: parsed.conversationId,
3039
+ openBrowser: parsed.openBrowser,
3040
+ pollIntervalMs: RUNNETH_CONVERSATION_DEFAULT_POLL_INTERVAL_MS,
3041
+ resourceUrl,
3042
+ scope: parsed.scope,
3043
+ tenanthId: parsed.tenanthId,
3044
+ timeoutMs: parsed.timeoutMs,
3045
+ token: parsed.token,
3046
+ values: [],
3047
+ workspaceId: parsed.workspaceId
3048
+ }, { oauthAccessToken: oauthToken.accessToken }), parsed.conversationId)).url
3049
+ }
3050
+ };
3051
+ };
3052
+ const assertNoSshTargetDefaultFlag = (parsed) => {
3053
+ if (parsed.makeDefault) throw new Error("--default is only valid for runneth ssh target add");
3054
+ };
3055
+ const assertSshRemoteCommandSeparator = (parsed) => {
3056
+ if (parsed.values.length > 0 && !parsed.argumentSeparatorSeen) throw new Error("Remote SSH commands must follow --");
3057
+ };
3058
+ const assertNoSshKeyOptions = (parsed, commandDescription) => {
3059
+ if (parsed.identityFilePath !== void 0 || parsed.uniqueKey) throw new Error(`${commandDescription} does not accept SSH key options`);
3060
+ };
3061
+ const resolveSshKeyInstallOptions = (parsed) => {
3062
+ if (parsed.identityFilePath !== void 0 && parsed.uniqueKey) throw new Error("Use either --identity-file or --unique-key, not both");
3063
+ if (parsed.identityFilePath !== void 0) return { identityFilePath: parsed.identityFilePath };
3064
+ if (parsed.uniqueKey) return { keyMode: "unique" };
3065
+ return {};
3066
+ };
3067
+ const parseSshTargetImportEntry = (payload, index) => {
3068
+ if (!isRecord(payload)) throw new Error(`targets[${String(index)}] must be a JSON object`);
3069
+ const name = normalizeRunnethSshTargetName(parseStringField(payload, "name"));
3070
+ const host = parseOptionalStringField(payload, "host");
3071
+ const resourceUrl = parseOptionalStringField(payload, "resourceUrl");
3072
+ const sshUrl = parseOptionalStringField(payload, "sshUrl");
3073
+ if (host === void 0 === (sshUrl === void 0)) throw new Error(`targets[${String(index)}] requires exactly one of host or sshUrl`);
3074
+ return {
3075
+ ...host === void 0 ? {} : { host },
3076
+ name,
3077
+ ...resourceUrl === void 0 ? {} : { resourceUrl },
3078
+ ...sshUrl === void 0 ? {} : { sshUrl }
3079
+ };
3080
+ };
3081
+ const parseSshTargetImportFile = (payload) => {
3082
+ if (!isRecord(payload)) throw new Error("SSH target import file must be a JSON object");
3083
+ const rawTargets = payload.targets;
3084
+ if (!Array.isArray(rawTargets) || rawTargets.length === 0) throw new Error("SSH target import file requires a non-empty targets array");
3085
+ const defaultTargetValue = parseOptionalStringField(payload, "defaultTarget");
3086
+ const resourceUrl = parseOptionalStringField(payload, "resourceUrl");
3087
+ return {
3088
+ ...defaultTargetValue === void 0 ? {} : { defaultTarget: normalizeRunnethSshTargetName(defaultTargetValue) },
3089
+ ...resourceUrl === void 0 ? {} : { resourceUrl },
3090
+ targets: rawTargets.map((entry, index) => parseSshTargetImportEntry(entry, index))
3091
+ };
3092
+ };
3093
+ const readSshTargetImportFile = async (filePath) => {
3094
+ const resolvedPath = path.resolve(filePath);
3095
+ return parseSshTargetImportFile(JSON.parse(await readFile(resolvedPath, "utf8")));
3096
+ };
3097
+ const resolveImportEntryResourceUrl = (input) => {
3098
+ return requireResourceUrl(input.entry.resourceUrl ?? input.fileResourceUrl ?? input.commandResourceUrl ?? resolveDefaultResourceUrl(), "runneth ssh target import");
3099
+ };
3100
+ const resolveImportEntrySshUrl = (entry) => {
3101
+ if (entry.sshUrl !== void 0) return entry.sshUrl;
3102
+ if (entry.host === void 0) throw new Error(`Imported SSH target is missing host: ${entry.name}`);
3103
+ return resolveSshUrlFromHost(entry.host);
3104
+ };
3105
+ const resolveSshTargetImportEntries = (input) => {
3106
+ const names = /* @__PURE__ */ new Set();
3107
+ for (const target of input.file.targets) {
3108
+ if (names.has(target.name)) throw new Error(`Duplicate SSH target in import file: ${target.name}`);
3109
+ names.add(target.name);
3110
+ }
3111
+ if (input.file.defaultTarget !== void 0 && !names.has(input.file.defaultTarget)) throw new Error(`Import defaultTarget does not match an imported target: ${input.file.defaultTarget}`);
3112
+ return input.file.targets.map((entry, index) => {
3113
+ return {
3114
+ makeDefault: entry.name === input.file.defaultTarget || input.file.defaultTarget === void 0 && input.makeFirstDefault && index === 0,
3115
+ name: entry.name,
3116
+ resourceUrl: resolveImportEntryResourceUrl({
3117
+ entry,
3118
+ ...input.commandResourceUrl === void 0 ? {} : { commandResourceUrl: input.commandResourceUrl },
3119
+ ...input.file.resourceUrl === void 0 ? {} : { fileResourceUrl: input.file.resourceUrl }
3120
+ }),
3121
+ sshUrl: resolveImportEntrySshUrl(entry)
3122
+ };
3123
+ });
3124
+ };
3125
+ const resolveSshOAuthToken = async (parsed, resourceUrl) => {
3126
+ return await resolveOAuthToken({
3127
+ authorizationUrlHandler: (authorizationUrl) => {
3128
+ process$1.stderr.write(`Authorize runneth here:\n${authorizationUrl}\n`);
3129
+ },
3130
+ clientName: parsed.clientName,
3131
+ ...resolveCliOAuthClientCredentialOptions(parsed),
3132
+ openBrowser: parsed.openBrowser,
3133
+ resourceUrl,
3134
+ scope: parsed.scope,
3135
+ timeoutMs: parsed.timeoutMs
3136
+ });
3137
+ };
3138
+ const writeJson = (response) => {
3139
+ process$1.stdout.write(`${JSON.stringify(response, null, 2)}\n`);
3140
+ };
3141
+ const isDaemonSocketUnavailable = (error) => {
3142
+ return typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ECONNREFUSED" || error.code === "ECONNRESET" || error.code === "EPIPE");
3143
+ };
3144
+ const assertRunnethCliDaemonStopped = async (gate) => {
3145
+ const registeredSockets = new Set((await gate.readRegistrations()).map((registration) => registration.socketPath));
3146
+ const socketPaths = new Set([...registeredSockets, resolveSocketPath()]);
3147
+ for (const socketPath of socketPaths) {
3148
+ try {
3149
+ await sendRunnethCliRequest({
3150
+ request: { op: "ping" },
3151
+ socketPath,
3152
+ timeoutMs: 500
3153
+ });
3154
+ } catch (error) {
3155
+ if (isDaemonSocketUnavailable(error)) {
3156
+ if (registeredSockets.has(socketPath)) await gate.removeRegistration(socketPath);
3157
+ continue;
3158
+ }
3159
+ if (!(error instanceof RunnethCliRequestTimeoutError)) throw error;
3160
+ }
3161
+ throw new Error("Stop every local Runneth CLI daemon for this installation before updating. Close persistent sessions, run `runneth shutdown` in each CLI home, then retry `runneth update`.");
3162
+ }
3163
+ };
3164
+ const waitForRunnethCliDaemonStop = async (socketPath) => {
3165
+ const deadline = Date.now() + 5e3;
3166
+ while (Date.now() < deadline) {
3167
+ try {
3168
+ await sendRunnethCliRequest({
3169
+ request: { op: "ping" },
3170
+ socketPath,
3171
+ timeoutMs: Math.max(1, Math.min(500, deadline - Date.now()))
3172
+ });
3173
+ } catch (error) {
3174
+ if (isDaemonSocketUnavailable(error)) return;
3175
+ if (!(error instanceof RunnethCliRequestTimeoutError)) throw error;
3176
+ }
3177
+ await new Promise((resolve) => {
3178
+ setTimeout(resolve, 25);
3179
+ });
3180
+ }
3181
+ throw new Error("Timed out waiting for the local Runneth CLI daemon to stop");
3182
+ };
3183
+ const applyRunnethCliUpdate = async (action) => {
3184
+ process$1.stdout.write(`Updating Runneth CLI via \`${action.displayCommand}\`...\n`);
3185
+ await runWithRunnethCliDaemonGate(await resolveRunnethCliDaemonInstallationPath(resolveCliEntryPath()), async (gate) => {
3186
+ await assertRunnethCliDaemonStopped(gate);
3187
+ await runRunnethCliUpdateAction({ action });
3188
+ });
3189
+ process$1.stdout.write("Update ran successfully. Please rerun your Runneth command.\nThe next daemon-backed command starts from the updated installation.\n");
3190
+ };
3191
+ const formatNullableIdentityValue = (value) => {
3192
+ return value === null ? "(none)" : value;
3193
+ };
3194
+ const formatSshSetupAccount = (setup) => {
3195
+ return `${setup.provider}:${setup.providerAccountId}`;
3196
+ };
3197
+ const writeSshSetupSummary = (setup) => {
3198
+ const identityLines = [
3199
+ ` account: ${formatSshSetupAccount(setup)}`,
3200
+ ` vmUser: ${setup.userId}`,
3201
+ ` organization: ${setup.organizationId}`,
3202
+ ` sandbox: ${setup.sandboxId}`,
3203
+ ` workspace: ${formatNullableIdentityValue(setup.workspaceId)}`,
3204
+ ` defaultConversation: ${formatNullableIdentityValue(setup.defaultConversationId)}`
3205
+ ];
3206
+ process$1.stderr.write([
3207
+ `SSH target ${setup.targetName ?? setup.hostAlias} configured`,
3208
+ ` config: ${setup.configPath}`,
3209
+ ` hostAlias: ${setup.hostAlias}`,
3210
+ ` sshUser: ${setup.sshUser}`,
3211
+ ...identityLines,
3212
+ ""
3213
+ ].join("\n"));
3214
+ };
3215
+ const writeSshProxySummary = (result) => {
3216
+ process$1.stderr.write([
3217
+ "SSH tunnel closed",
3218
+ ` transport: ${result.transport}`,
3219
+ ` heartbeat pongs: ${String(result.heartbeatPongCount)}`,
3220
+ ""
3221
+ ].join("\n"));
3222
+ };
3223
+ const writeCopyTargetSummary = (input) => {
3224
+ process$1.stderr.write([
3225
+ "Runneth VM copy target binding",
3226
+ ` source: ${input.source.targetName ?? input.source.sshUrl}`,
3227
+ ` sourceResource: ${input.source.resourceUrl}`,
3228
+ ` sourceSsh: ${input.source.sshUrl}`,
3229
+ ` sourcePath: ${input.sourcePath}`,
3230
+ ` destination: ${input.destination.targetName ?? input.destination.sshUrl}`,
3231
+ ` destinationResource: ${input.destination.resourceUrl}`,
3232
+ ` destinationSsh: ${input.destination.sshUrl}`,
3233
+ ` destinationPath: ${input.destinationPath}`,
3234
+ ""
3235
+ ].join("\n"));
3236
+ };
3237
+ const runDaemon = async (argv) => {
3238
+ if (argv.length !== 2) throw new Error("Internal Runneth CLI daemon start requires its socket and installation paths");
3239
+ const socketPath = argv[0];
3240
+ const installationPath = argv[1];
3241
+ if (socketPath === void 0 || installationPath === void 0) throw new Error("Internal Runneth CLI daemon start requires its socket and installation paths");
3242
+ await new RunnethCliDaemonRuntime().startWithGateHeld({
3243
+ installationPath,
3244
+ socketPath
3245
+ });
3246
+ };
3247
+ const request = async (payload, timeoutMs = DEFAULT_TIMEOUT_MS) => {
3248
+ const socketPath = resolveSocketPath();
3249
+ if (payload.op === "shutdown") return await sendRunnethCliShutdownRequest({
3250
+ socketPath,
3251
+ timeoutMs
3252
+ });
3253
+ if (payload.op !== "close") {
3254
+ const cliPath = resolveCliEntryPath();
3255
+ await ensureDaemon({
3256
+ cliPath,
3257
+ installationPath: await resolveRunnethCliDaemonInstallationPath(cliPath),
3258
+ socketPath
3259
+ });
3260
+ }
3261
+ return await sendRunnethCliRequest({
3262
+ request: payload,
3263
+ socketPath,
3264
+ timeoutMs
3265
+ });
3266
+ };
3267
+ const run = async (argv) => {
3268
+ const command = argv[0];
3269
+ if (command === "__update-check") {
3270
+ const updateEntryPath = argv[1];
3271
+ if (argv.length !== 2 || updateEntryPath === void 0 || !path.isAbsolute(updateEntryPath)) throw new Error("Internal Runneth CLI update check requires its absolute entry path");
3272
+ await runRunnethCliBackgroundUpdateCheck({
3273
+ entryPath: updateEntryPath,
3274
+ moduleUrl: import.meta.url
3275
+ });
3276
+ return;
3277
+ }
3278
+ if (command === "daemon") {
3279
+ await runDaemon(argv.slice(1));
3280
+ return;
3281
+ }
3282
+ if (command === "update") {
3283
+ if (argv.slice(1).length > 0) throw new Error("runneth-cli update accepts no arguments");
3284
+ await applyRunnethCliUpdate(await resolveRunnethCliUpdateAction({
3285
+ entryPath: resolveCliEntryPath(),
3286
+ moduleUrl: import.meta.url
3287
+ }));
3288
+ return;
3289
+ }
3290
+ if (command === "open") {
3291
+ const parsed = parseArgs(argv.slice(1));
3292
+ writeJson(await request({
3293
+ cwd: parsed.cwd ?? process$1.cwd(),
3294
+ name: parsed.name,
3295
+ op: "open",
3296
+ ...parsed.shell === void 0 ? {} : { shell: parsed.shell }
3297
+ }));
3298
+ return;
3299
+ }
3300
+ if (command === "send") {
3301
+ const parsed = parseArgs(argv.slice(1));
3302
+ const shellCommand = parsed.stdin ? await readStdin() : parsed.values.join(" ");
3303
+ if (shellCommand.trim().length === 0) throw new Error("runneth send requires a command or --stdin");
3304
+ const response = await request({
3305
+ command: shellCommand,
3306
+ cwd: parsed.cwd ?? process$1.cwd(),
3307
+ name: parsed.name,
3308
+ op: "send",
3309
+ ...parsed.shell === void 0 ? {} : { shell: parsed.shell }
3310
+ }, parsed.timeoutMs);
3311
+ writeJson(response);
3312
+ if (response.ok && response.op === "send" && response.exitCode !== 0) process$1.exitCode = response.exitCode;
3313
+ return;
3314
+ }
3315
+ if (command === "status") {
3316
+ writeJson(await request({
3317
+ name: parseArgs(argv.slice(1)).name,
3318
+ op: "status"
3319
+ }));
3320
+ return;
3321
+ }
3322
+ if (command === "list") {
3323
+ if (argv.length !== 1) throw new Error("runneth list does not accept arguments. Use runneth list --help.");
3324
+ writeJson(await request({ op: "list" }));
3325
+ return;
3326
+ }
3327
+ if (command === "chat") {
3328
+ await runRunnethConversation(await resolveRunnethConversationOptions(parseRunnethConversationArgs(argv.slice(1), parseNumber)));
3329
+ return;
3330
+ }
3331
+ if (command === "conversation") {
3332
+ await runRunnethConversationMachineCommand({
3333
+ argv: argv.slice(1),
3334
+ parseNumber
3335
+ });
3336
+ return;
3337
+ }
3338
+ if (command === "oauth") {
3339
+ const subcommand = argv[1];
3340
+ const parsed = parseOAuthArgs(argv.slice(2));
3341
+ const resourceUrl = resolveOAuthResourceUrl(parsed);
3342
+ if (subcommand === "login") {
3343
+ const result = await loginWithOAuth({
3344
+ authorizationUrlHandler: (authorizationUrl) => {
3345
+ process$1.stderr.write(`Authorize runneth here:\n${authorizationUrl}\n`);
3346
+ },
3347
+ clientName: parsed.clientName,
3348
+ ...resolveCliOAuthClientCredentialOptions(parsed),
3349
+ openBrowser: parsed.openBrowser,
3350
+ resourceUrl,
3351
+ scope: parsed.scope,
3352
+ timeoutMs: parsed.timeoutMs
3353
+ });
3354
+ writeJson({
3355
+ accessToken: result.accessToken,
3356
+ credentialPath: result.credentialPath,
3357
+ expiresAt: result.expiresAt,
3358
+ ok: true,
3359
+ op: "oauth-login",
3360
+ resource: result.credential.resource,
3361
+ scope: result.credential.token.scope ?? result.credential.scope,
3362
+ tokenType: result.tokenType
3363
+ });
3364
+ return;
3365
+ }
3366
+ if (subcommand === "status") {
3367
+ writeJson({
3368
+ ok: true,
3369
+ op: "oauth-status",
3370
+ ...await readOAuthCredentialStatus({ resourceUrl })
3371
+ });
3372
+ return;
3373
+ }
3374
+ if (subcommand === "logout") {
3375
+ writeJson({
3376
+ ok: true,
3377
+ op: "oauth-logout",
3378
+ ...await logoutOAuthCredential({ resourceUrl })
3379
+ });
3380
+ return;
3381
+ }
3382
+ throw new Error(`Unknown oauth command: ${subcommand}`);
3383
+ }
3384
+ if (command === "copy") {
3385
+ const parsed = parseCopyArgs(argv.slice(1));
3386
+ const sourcePath = parsed.values[0];
3387
+ const destinationPath = parsed.values[1];
3388
+ if (sourcePath === void 0 || destinationPath === void 0 || parsed.values.length !== 2) throw new Error("runneth copy requires exactly <source-path> and <destination-path>");
3389
+ const sourceTarget = await resolveRunnethSshTarget({ targetName: parsed.sourceTargetName });
3390
+ const destinationTarget = await resolveRunnethSshTarget({ targetName: parsed.destinationTargetName });
3391
+ assertRunnethVmCopyTargetsUseSameResource({
3392
+ destination: destinationTarget,
3393
+ source: sourceTarget
3394
+ });
3395
+ writeCopyTargetSummary({
3396
+ destination: destinationTarget,
3397
+ destinationPath,
3398
+ source: sourceTarget,
3399
+ sourcePath
3400
+ });
3401
+ const oauthToken = await resolveSshOAuthToken(parsed, sourceTarget.resourceUrl);
3402
+ const copyProgressReporter = createCopyProgressReporter({
3403
+ interactive: process$1.stderr.isTTY === true,
3404
+ output: process$1.stderr
3405
+ });
3406
+ try {
3407
+ writeJson({
3408
+ ok: true,
3409
+ op: "copy",
3410
+ ...await runRunnethVmCopy({
3411
+ destination: destinationTarget,
3412
+ destinationPath,
3413
+ ignoredPaths: parsed.ignoredPaths,
3414
+ oauthToken,
3415
+ onProgress: copyProgressReporter.report,
3416
+ source: sourceTarget,
3417
+ sourcePath,
3418
+ timeoutMs: parsed.timeoutMs
3419
+ })
3420
+ });
3421
+ } catch (error) {
3422
+ await copyProgressReporter.finishFailure(error);
3423
+ }
3424
+ return;
3425
+ }
3426
+ if (command === "skills") {
3427
+ const subcommand = argv[1];
3428
+ if (subcommand === "install") {
3429
+ writeJson({
3430
+ ok: true,
3431
+ op: "skills-install",
3432
+ ...await installRunnethSkills({ agents: parseSkillsArgs(argv.slice(2)).agents })
3433
+ });
3434
+ return;
3435
+ }
3436
+ throw new Error(`Unknown skills command: ${subcommand}`);
3437
+ }
3438
+ if (command === "ssh") {
3439
+ const subcommand = argv[1];
3440
+ if (subcommand === "target") {
3441
+ const targetCommand = argv[2];
3442
+ if (targetCommand === "add") {
3443
+ const parsed = parseSshArgs(argv.slice(3));
3444
+ assertNoSshConversationOptions(parsed, "runneth ssh target add");
3445
+ assertNoSshKeyOptions(parsed, "runneth ssh target add");
3446
+ const name = parsed.values[0];
3447
+ if (name === void 0 || parsed.values.length !== 1) throw new Error("runneth ssh target add requires one target name");
3448
+ if (parsed.sshHost === void 0 && parsed.sshUrl === void 0) throw new Error("runneth ssh target add requires --host <host> or --ssh-url <url>");
3449
+ if (parsed.targetName !== void 0) throw new Error("--target is not valid for ssh target add");
3450
+ const saved = await saveRunnethSshTarget({
3451
+ makeDefault: parsed.makeDefault,
3452
+ name,
3453
+ resourceUrl: requireResourceUrl(parsed.resourceUrl ?? resolveDefaultResourceUrl(), "runneth ssh target add"),
3454
+ sshUrl: resolveSshUrlOption(parsed)
3455
+ });
3456
+ writeJson({
3457
+ defaultTarget: saved.store.defaultTarget,
3458
+ ok: true,
3459
+ op: "ssh-target-add",
3460
+ target: saved.target
3461
+ });
3462
+ return;
3463
+ }
3464
+ if (targetCommand === "import") {
3465
+ const parsed = parseSshArgs(argv.slice(3));
3466
+ assertNoSshConversationOptions(parsed, "runneth ssh target import");
3467
+ assertNoSshKeyOptions(parsed, "runneth ssh target import");
3468
+ const filePath = parsed.values[0];
3469
+ if (filePath === void 0 || parsed.values.length !== 1) throw new Error("runneth ssh target import requires one JSON file path");
3470
+ if (parsed.targetName !== void 0) throw new Error("--target is not valid for ssh target import");
3471
+ if (parsed.sshHost !== void 0 || parsed.sshUrl !== void 0) throw new Error("runneth ssh target import reads host and sshUrl from the file");
3472
+ const importFile = await readSshTargetImportFile(filePath);
3473
+ if (parsed.makeDefault && importFile.defaultTarget !== void 0) throw new Error("Use either import file defaultTarget or --default, not both");
3474
+ const entries = resolveSshTargetImportEntries({
3475
+ ...parsed.resourceUrl === void 0 ? {} : { commandResourceUrl: parsed.resourceUrl },
3476
+ file: importFile,
3477
+ makeFirstDefault: parsed.makeDefault
3478
+ });
3479
+ const targets = [];
3480
+ let defaultTarget;
3481
+ for (const entry of entries) {
3482
+ const saved = await saveRunnethSshTarget({
3483
+ makeDefault: entry.makeDefault,
3484
+ name: entry.name,
3485
+ resourceUrl: entry.resourceUrl,
3486
+ sshUrl: entry.sshUrl
3487
+ });
3488
+ targets.push(saved.target);
3489
+ defaultTarget = saved.store.defaultTarget;
3490
+ }
3491
+ writeJson({
3492
+ defaultTarget,
3493
+ ok: true,
3494
+ op: "ssh-target-import",
3495
+ targets
3496
+ });
3497
+ return;
3498
+ }
3499
+ if (targetCommand === "list") {
3500
+ const parsed = parseSshArgs(argv.slice(3));
3501
+ assertNoSshConversationOptions(parsed, "runneth ssh target list");
3502
+ assertNoSshKeyOptions(parsed, "runneth ssh target list");
3503
+ if (parsed.values.length > 0) throw new Error("runneth ssh target list does not accept names");
3504
+ const store = await readRunnethSshTargetStore({});
3505
+ writeJson({
3506
+ defaultTarget: store.defaultTarget,
3507
+ ok: true,
3508
+ op: "ssh-target-list",
3509
+ targets: store.targets
3510
+ });
3511
+ return;
3512
+ }
3513
+ if (targetCommand === "use") {
3514
+ const parsed = parseSshArgs(argv.slice(3));
3515
+ assertNoSshConversationOptions(parsed, "runneth ssh target use");
3516
+ assertNoSshKeyOptions(parsed, "runneth ssh target use");
3517
+ const name = parsed.values[0];
3518
+ if (name === void 0 || parsed.values.length !== 1) throw new Error("runneth ssh target use requires one target name");
3519
+ const store = await setDefaultRunnethSshTarget({ name });
3520
+ const target = store.targets.find((item) => item.name === name);
3521
+ writeJson({
3522
+ defaultTarget: store.defaultTarget,
3523
+ ok: true,
3524
+ op: "ssh-target-use",
3525
+ target
3526
+ });
3527
+ return;
3528
+ }
3529
+ if (targetCommand === "remove") {
3530
+ const parsed = parseSshArgs(argv.slice(3));
3531
+ assertNoSshConversationOptions(parsed, "runneth ssh target remove");
3532
+ assertNoSshKeyOptions(parsed, "runneth ssh target remove");
3533
+ const name = parsed.values[0];
3534
+ if (name === void 0 || parsed.values.length !== 1) throw new Error("runneth ssh target remove requires one target name");
3535
+ const store = await removeRunnethSshTarget({ name });
3536
+ writeJson({
3537
+ defaultTarget: store.defaultTarget,
3538
+ ok: true,
3539
+ op: "ssh-target-remove",
3540
+ targetName: name,
3541
+ targets: store.targets
3542
+ });
3543
+ return;
3544
+ }
3545
+ throw new Error(`Unknown ssh target command: ${targetCommand}`);
3546
+ }
3547
+ if (subcommand === "proxy") {
3548
+ const parsed = parseSshArgs(argv.slice(2));
3549
+ if (parsed.values.length > 0) throw new Error("runneth ssh proxy does not accept extra arguments");
3550
+ assertNoSshTargetDefaultFlag(parsed);
3551
+ assertNoSshKeyOptions(parsed, "runneth ssh proxy");
3552
+ const target = await resolveSshTargetForCommand(parsed);
3553
+ writeSshProxySummary(await runRunnethSshProxy({
3554
+ resourceUrl: target.resourceUrl,
3555
+ sshUrl: target.sshUrl
3556
+ }));
3557
+ return;
3558
+ }
3559
+ if (subcommand === "stdio") {
3560
+ const parsed = parseSshArgs(argv.slice(2));
3561
+ if (parsed.values.length > 0) throw new Error("runneth ssh stdio does not accept extra arguments");
3562
+ assertNoSshTargetDefaultFlag(parsed);
3563
+ const { oauthToken, target } = await resolveSshCommandContext(parsed);
3564
+ const setup = await installRunnethSshAccess({
3565
+ cliPath: fileURLToPath(import.meta.url),
3566
+ oauthToken,
3567
+ resourceUrl: target.resourceUrl,
3568
+ sshUrl: target.sshUrl,
3569
+ ...resolveSshKeyInstallOptions(parsed),
3570
+ ...target.targetName === void 0 ? {} : { targetName: target.targetName }
3571
+ });
3572
+ writeSshSetupSummary(setup);
3573
+ await runRunnethSshStdio({
3574
+ defaultTimeoutMs: parsed.timeoutMs,
3575
+ setup
3576
+ });
3577
+ return;
3578
+ }
3579
+ const parsed = parseSshArgs(argv.slice(1));
3580
+ assertNoSshTargetDefaultFlag(parsed);
3581
+ assertSshRemoteCommandSeparator(parsed);
3582
+ const { oauthToken, target } = await resolveSshCommandContext(parsed);
3583
+ const setup = await installRunnethSshAccess({
3584
+ cliPath: fileURLToPath(import.meta.url),
3585
+ oauthToken,
3586
+ resourceUrl: target.resourceUrl,
3587
+ sshUrl: target.sshUrl,
3588
+ ...resolveSshKeyInstallOptions(parsed),
3589
+ ...target.targetName === void 0 ? {} : { targetName: target.targetName }
3590
+ });
3591
+ writeSshSetupSummary(setup);
3592
+ process$1.exitCode = await runOpenSsh({
3593
+ extraArgs: parsed.values,
3594
+ setup
3595
+ });
3596
+ return;
3597
+ }
3598
+ if (command === "close") {
3599
+ writeJson(await request({
3600
+ name: parseArgs(argv.slice(1)).name,
3601
+ op: "close"
3602
+ }));
3603
+ return;
3604
+ }
3605
+ if (command === "shutdown") {
3606
+ if (argv.length !== 1) throw new Error("runneth shutdown does not accept arguments. Use runneth shutdown --help.");
3607
+ const socketPath = resolveSocketPath();
3608
+ writeJson(await request({ op: "shutdown" }));
3609
+ await waitForRunnethCliDaemonStop(socketPath);
3610
+ return;
3611
+ }
3612
+ throw new Error(`Unknown command: ${command}. Run runneth --help to see available commands.`);
3613
+ };
3614
+ const isSshProxyInvocation = (argv) => {
3615
+ return argv[0] === "ssh" && argv[1] === "proxy";
3616
+ };
3617
+ const isSshStdioInvocation = (argv) => {
3618
+ return argv[0] === "ssh" && argv[1] === "stdio";
3619
+ };
3620
+ const isConversationSessionInvocation = (argv) => {
3621
+ return argv[0] === "conversation" && argv[1] === "session";
3622
+ };
3623
+ const formatErrorMessage = (error) => {
3624
+ return error instanceof Error ? error.message : String(error);
3625
+ };
3626
+ const formatSshProxyErrorMessages = (error) => {
3627
+ if (!(error instanceof AggregateError)) return [formatErrorMessage(error)];
3628
+ return error.errors.flatMap((nestedError) => formatSshProxyErrorMessages(nestedError));
3629
+ };
3630
+ const shouldSkipStartupUpdateCheck = (argv) => {
3631
+ return argv[0] === "__update-check" || argv[0] === "daemon" || argv[0] === "update" || isConversationSessionInvocation(argv) || (argv[0] === "chat" || argv[0] === "ssh") && !isRunnethCliUpdatePromptInvocation(argv);
3632
+ };
3633
+ const handleUpdatePromptSelection = async (selection, candidate) => {
3634
+ switch (selection) {
3635
+ case "not-now": return "continue";
3636
+ case "dismiss":
3637
+ await dismissRunnethCliUpdate({ dismissal: candidate.dismissal });
3638
+ return "continue";
3639
+ case "update":
3640
+ await applyRunnethCliUpdate(candidate.action);
3641
+ return "updated";
3642
+ default: return selection;
3643
+ }
3644
+ };
3645
+ const argv = process$1.argv.slice(2);
3646
+ const runWithUpdateCheck = async () => {
3647
+ const display = resolveCliDisplayRequest(argv);
3648
+ if (display !== null) {
3649
+ await writeCliDisplay(display, process$1.stdout);
3650
+ return;
3651
+ }
3652
+ if (shouldSkipStartupUpdateCheck(argv)) {
3653
+ await run(argv);
3654
+ return;
3655
+ }
3656
+ const entryPath = resolveCliEntryPath();
3657
+ const isPromptEligible = isRunnethCliUpdatePromptInvocation(argv) && process$1.stdin.isTTY === true && process$1.stderr.isTTY === true;
3658
+ let cachedUpdate = null;
3659
+ if (isPromptEligible) try {
3660
+ cachedUpdate = await readRunnethCliCachedUpdate({
3661
+ entryPath,
3662
+ moduleUrl: import.meta.url
3663
+ });
3664
+ } catch (error) {
3665
+ const message = error instanceof Error ? error.message : String(error);
3666
+ process$1.stderr.write(`Runneth CLI update notification unavailable: ${message}\n`);
3667
+ }
3668
+ if (cachedUpdate !== null) {
3669
+ if (await handleUpdatePromptSelection(await runRunnethCliUpdatePrompt({
3670
+ currentVersion: cachedUpdate.currentVersion,
3671
+ input: process$1.stdin,
3672
+ output: process$1.stderr,
3673
+ targetVersion: cachedUpdate.latestVersion,
3674
+ updateCommand: cachedUpdate.action.displayCommand
3675
+ }), cachedUpdate) === "updated") {
3676
+ process$1.stdin.pause();
3677
+ return;
3678
+ }
3679
+ }
3680
+ scheduleRunnethCliUpdateCheck({ entryPath });
3681
+ await run(argv);
3682
+ };
3683
+ runWithUpdateCheck().catch((error) => {
3684
+ process$1.stdin.pause();
3685
+ const message = formatErrorMessage(error);
3686
+ const commandError = error instanceof RunnethCliReplacementInterruptedError ? error.cause : error;
3687
+ if (commandError instanceof RunnethCliUpdateCommandError) {
3688
+ process$1.stderr.write(`${message}\n`);
3689
+ process$1.exitCode = commandError.exitCode;
3690
+ return;
3691
+ }
3692
+ if (argv[0] === "__update-check") {
3693
+ process$1.stderr.write(`Runneth CLI update check failed: ${message}\n`);
3694
+ process$1.exitCode = 1;
3695
+ return;
3696
+ }
3697
+ if (isSshProxyInvocation(argv)) {
3698
+ process$1.stderr.write(`${formatSshProxyErrorMessages(error).join("\n")}\n`);
3699
+ process$1.exitCode = 1;
3700
+ return;
3701
+ }
3702
+ if (isSshStdioInvocation(argv)) {
3703
+ process$1.stdout.write(`${JSON.stringify({
3704
+ message,
3705
+ ok: false,
3706
+ type: "error"
3707
+ })}\n`);
3708
+ process$1.exitCode = 1;
3709
+ return;
3710
+ }
3711
+ if (isConversationSessionInvocation(argv)) {
3712
+ process$1.stdout.write(`${JSON.stringify({
3713
+ message,
3714
+ ok: false,
3715
+ type: "error"
3716
+ })}\n`);
3717
+ process$1.exitCode = 1;
3718
+ return;
3719
+ }
3720
+ if (argv[0] === "update") {
3721
+ process$1.stderr.write(`${message}\n`);
3722
+ process$1.exitCode = 1;
3723
+ return;
3724
+ }
3725
+ process$1.stdout.write(`${JSON.stringify({
3726
+ error: message,
3727
+ ok: false
3728
+ }, null, 2)}\n`);
3729
+ process$1.exitCode = 1;
3730
+ });
3731
+ //#endregion
3732
+ export {};