maesttro 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/maesttro.js CHANGED
@@ -1,4 +1,95 @@
1
1
  #!/usr/bin/env node
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
12
+ var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
20
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
21
+ const to = isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
+ if (mod && typeof mod === "object" || typeof mod === "function") {
23
+ for (let key of __getOwnPropNames(mod))
24
+ if (!__hasOwnProp.call(to, key))
25
+ __defProp(to, key, {
26
+ get: __accessProp.bind(mod, key),
27
+ enumerable: true
28
+ });
29
+ }
30
+ if (canCache)
31
+ cache.set(mod, to);
32
+ return to;
33
+ };
34
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
35
+
36
+ // ../node_modules/.bun/sisteransi@1.0.5/node_modules/sisteransi/src/index.js
37
+ var require_src = __commonJS(function(exports, module) {
38
+ var ESC = "\x1B";
39
+ var CSI = `${ESC}[`;
40
+ var beep = "\x07";
41
+ var cursor = {
42
+ to(x, y) {
43
+ if (!y)
44
+ return `${CSI}${x + 1}G`;
45
+ return `${CSI}${y + 1};${x + 1}H`;
46
+ },
47
+ move(x, y) {
48
+ let ret = "";
49
+ if (x < 0)
50
+ ret += `${CSI}${-x}D`;
51
+ else if (x > 0)
52
+ ret += `${CSI}${x}C`;
53
+ if (y < 0)
54
+ ret += `${CSI}${-y}A`;
55
+ else if (y > 0)
56
+ ret += `${CSI}${y}B`;
57
+ return ret;
58
+ },
59
+ up: (count = 1) => `${CSI}${count}A`,
60
+ down: (count = 1) => `${CSI}${count}B`,
61
+ forward: (count = 1) => `${CSI}${count}C`,
62
+ backward: (count = 1) => `${CSI}${count}D`,
63
+ nextLine: (count = 1) => `${CSI}E`.repeat(count),
64
+ prevLine: (count = 1) => `${CSI}F`.repeat(count),
65
+ left: `${CSI}G`,
66
+ hide: `${CSI}?25l`,
67
+ show: `${CSI}?25h`,
68
+ save: `${ESC}7`,
69
+ restore: `${ESC}8`
70
+ };
71
+ var scroll = {
72
+ up: (count = 1) => `${CSI}S`.repeat(count),
73
+ down: (count = 1) => `${CSI}T`.repeat(count)
74
+ };
75
+ var erase = {
76
+ screen: `${CSI}2J`,
77
+ up: (count = 1) => `${CSI}1J`.repeat(count),
78
+ down: (count = 1) => `${CSI}J`.repeat(count),
79
+ line: `${CSI}2K`,
80
+ lineEnd: `${CSI}K`,
81
+ lineStart: `${CSI}1K`,
82
+ lines(count) {
83
+ let clear = "";
84
+ for (let i = 0;i < count; i++)
85
+ clear += this.line + (i < count - 1 ? cursor.up() : "");
86
+ if (count)
87
+ clear += cursor.left;
88
+ return clear;
89
+ }
90
+ };
91
+ module.exports = { cursor, scroll, erase, beep };
92
+ });
2
93
 
3
94
  // src/credential-store.ts
4
95
  import {
@@ -832,6 +923,13 @@ function runnerInstructions(context) {
832
923
  "- Work in this directory. Anything outside it is not persisted.",
833
924
  "- Commit on a branch you create here. The user pushes it from their machine with `maesttro push`; the runner cannot push, merge, or delete remote branches.",
834
925
  "",
926
+ "## Installing tools",
927
+ "",
928
+ "- Preinstalled: Node 24 (npm, pnpm, yarn), TypeScript (`tsc`), Bun, Python 3.14 and uv (`uv venv`, `uv pip`, `uv tool`; plain `pip` and `python -m venv` work too), the .NET 10 SDK, a C/C++ build toolchain, and common CLI tools (jq, ripgrep, fd, unzip, zip, sqlite3).",
929
+ "- You can install more yourself without root: `npm i -g`, `bun add -g`, `pip install`, `uv tool install`, `uv python install`, `dotnet tool install -g`. They work as usual and are on `PATH` right away.",
930
+ "- Installs last only until this container exits; install again when a new session needs them. Don't install into your home directory to make them stick.",
931
+ "- System packages are unavailable: there is no root, `sudo` or `apt`. If a task needs one, tell the user which package and why.",
932
+ "",
835
933
  "## Pushing",
836
934
  "",
837
935
  "- `git push` does not work here: the runner has read-only access to the remote. This is deliberate.",
@@ -1043,6 +1141,30 @@ function purgeLegacyTokenOnce({
1043
1141
  saveConfig({ ...config, legacyTokenPurged: true });
1044
1142
  }
1045
1143
 
1144
+ // src/network-probe.ts
1145
+ import { connect } from "node:net";
1146
+ async function reachable(host, port, {
1147
+ timeoutMs = 2000,
1148
+ connectImpl = connect
1149
+ } = {}) {
1150
+ return new Promise((resolve) => {
1151
+ const socket = connectImpl(port, host);
1152
+ const timer = setTimeout(() => {
1153
+ socket.destroy();
1154
+ resolve(false);
1155
+ }, timeoutMs);
1156
+ socket.once("connect", () => {
1157
+ clearTimeout(timer);
1158
+ socket.destroy();
1159
+ resolve(true);
1160
+ });
1161
+ socket.once("error", () => {
1162
+ clearTimeout(timer);
1163
+ resolve(false);
1164
+ });
1165
+ });
1166
+ }
1167
+
1046
1168
  // src/repo.ts
1047
1169
  import { appendFileSync, mkdirSync as mkdirSync5, realpathSync } from "node:fs";
1048
1170
  import { join as join5 } from "node:path";
@@ -1227,6 +1349,16 @@ async function doctor() {
1227
1349
  return broker;
1228
1350
  }
1229
1351
  ],
1352
+ [
1353
+ "dgx-spark reachable",
1354
+ async () => {
1355
+ const { hostname, port } = new URL(DGX_SPARK_URL);
1356
+ if (!await reachable(hostname, Number(port))) {
1357
+ throw new Error(`${DGX_SPARK_URL} (on the tailnet?)`);
1358
+ }
1359
+ return DGX_SPARK_URL;
1360
+ }
1361
+ ],
1230
1362
  [
1231
1363
  "Personal SSH key NOT visible inside runner (isolation intact)",
1232
1364
  () => {
@@ -1265,8 +1397,14 @@ Usage:
1265
1397
  Same, in strict mode: no GitHub token reaches the runner, so \`gh\` is off
1266
1398
  maesttro resume <id> Continue a kept session in its clone, in the mode it started with
1267
1399
  (\`--strict\` or \`--no-strict\` after the id changes it)
1268
- maesttro sessions List this repo's sessions (\`sessions remove <id>\` to drop one)
1269
- maesttro push [branch] Push a branch a session created, after showing what goes out
1400
+ maesttro sessions List this repo's sessions
1401
+ maesttro sessions remove <id>
1402
+ Drop one. Preserves its branches; asks before discarding commits that
1403
+ aren't on a branch (--force skips the ask)
1404
+ maesttro sessions remove --all
1405
+ Drop all of this repo's sessions
1406
+ maesttro push [branch] [--force]
1407
+ Push a branch a session created, after showing what goes out
1270
1408
  maesttro list Show available clients
1271
1409
  maesttro setup One-time: build image, create volume, log in to GitHub
1272
1410
  maesttro login Log in to GitHub (device flow) so sessions get repo access
@@ -1660,6 +1798,14 @@ function stripControl(text) {
1660
1798
  }
1661
1799
  return out;
1662
1800
  }
1801
+ var ESC = String.fromCharCode(27);
1802
+ var BEL = String.fromCharCode(7);
1803
+ var ANSI = new RegExp(`${ESC}(?:\\[[0-9;?]*[a-zA-Z]|\\][^${BEL}]*${BEL})?`, "g");
1804
+ function cleanOutput(text) {
1805
+ return text.split(`
1806
+ `).map((line) => stripControl(line.replace(ANSI, "")).replace(/\r+$/, "")).join(`
1807
+ `).trim();
1808
+ }
1663
1809
  var VALID_BRANCH = /^[A-Za-z0-9](?:[A-Za-z0-9._/-]*[A-Za-z0-9_-])?$/;
1664
1810
  function isSafeBranchName(name) {
1665
1811
  if (name.length === 0 || name.length > 255)
@@ -2277,6 +2423,1049 @@ async function launch({
2277
2423
 
2278
2424
  // src/push.ts
2279
2425
  import { spawnSync as spawnSync3 } from "node:child_process";
2426
+
2427
+ // ../node_modules/.bun/@clack+core@1.5.1/node_modules/@clack/core/dist/index.mjs
2428
+ import { stdout, stdin } from "node:process";
2429
+ import * as l from "node:readline";
2430
+ import l__default from "node:readline";
2431
+
2432
+ // ../node_modules/.bun/fast-string-truncated-width@3.0.3/node_modules/fast-string-truncated-width/dist/utils.js
2433
+ var getCodePointsLength = (() => {
2434
+ const SURROGATE_PAIR_RE = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
2435
+ return (input) => {
2436
+ let surrogatePairsNr = 0;
2437
+ SURROGATE_PAIR_RE.lastIndex = 0;
2438
+ while (SURROGATE_PAIR_RE.test(input)) {
2439
+ surrogatePairsNr += 1;
2440
+ }
2441
+ return input.length - surrogatePairsNr;
2442
+ };
2443
+ })();
2444
+ var isFullWidth = (x) => {
2445
+ return x === 12288 || x >= 65281 && x <= 65376 || x >= 65504 && x <= 65510;
2446
+ };
2447
+ var isWideNotCJKTNotEmoji = (x) => {
2448
+ return x === 8987 || x === 9001 || x >= 12272 && x <= 12287 || x >= 12289 && x <= 12350 || x >= 12441 && x <= 12543 || x >= 12549 && x <= 12591 || x >= 12593 && x <= 12686 || x >= 12688 && x <= 12771 || x >= 12783 && x <= 12830 || x >= 12832 && x <= 12871 || x >= 12880 && x <= 19903 || x >= 65040 && x <= 65049 || x >= 65072 && x <= 65106 || x >= 65108 && x <= 65126 || x >= 65128 && x <= 65131 || x >= 127488 && x <= 127490 || x >= 127504 && x <= 127547 || x >= 127552 && x <= 127560 || x >= 131072 && x <= 196605 || x >= 196608 && x <= 262141;
2449
+ };
2450
+
2451
+ // ../node_modules/.bun/fast-string-truncated-width@3.0.3/node_modules/fast-string-truncated-width/dist/index.js
2452
+ var ANSI_RE = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]|\u001b\]8;[^;]*;.*?(?:\u0007|\u001b\u005c)/y;
2453
+ var CONTROL_RE = /[\x00-\x08\x0A-\x1F\x7F-\x9F]{1,1000}/y;
2454
+ var CJKT_WIDE_RE = /(?:(?![\uFF61-\uFF9F\uFF00-\uFFEF])[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Tangut}]){1,1000}/yu;
2455
+ var TAB_RE = /\t{1,1000}/y;
2456
+ var EMOJI_RE = /[\u{1F1E6}-\u{1F1FF}]{2}|\u{1F3F4}[\u{E0061}-\u{E007A}]{2}[\u{E0030}-\u{E0039}\u{E0061}-\u{E007A}]{1,3}\u{E007F}|(?:\p{Emoji}\uFE0F\u20E3?|\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation})(?:\u200D(?:\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F\u20E3?))*/yu;
2457
+ var LATIN_RE = /(?:[\x20-\x7E\xA0-\xFF](?!\uFE0F)){1,1000}/y;
2458
+ var MODIFIER_RE = /\p{M}+/gu;
2459
+ var NO_TRUNCATION = { limit: Infinity, ellipsis: "" };
2460
+ var getStringTruncatedWidth = (input, truncationOptions = {}, widthOptions = {}) => {
2461
+ const LIMIT = truncationOptions.limit ?? Infinity;
2462
+ const ELLIPSIS = truncationOptions.ellipsis ?? "";
2463
+ const ELLIPSIS_WIDTH = truncationOptions?.ellipsisWidth ?? (ELLIPSIS ? getStringTruncatedWidth(ELLIPSIS, NO_TRUNCATION, widthOptions).width : 0);
2464
+ const ANSI_WIDTH = 0;
2465
+ const CONTROL_WIDTH = widthOptions.controlWidth ?? 0;
2466
+ const TAB_WIDTH = widthOptions.tabWidth ?? 8;
2467
+ const EMOJI_WIDTH = widthOptions.emojiWidth ?? 2;
2468
+ const FULL_WIDTH_WIDTH = 2;
2469
+ const REGULAR_WIDTH = widthOptions.regularWidth ?? 1;
2470
+ const WIDE_WIDTH = widthOptions.wideWidth ?? FULL_WIDTH_WIDTH;
2471
+ const PARSE_BLOCKS = [
2472
+ [LATIN_RE, REGULAR_WIDTH],
2473
+ [ANSI_RE, ANSI_WIDTH],
2474
+ [CONTROL_RE, CONTROL_WIDTH],
2475
+ [TAB_RE, TAB_WIDTH],
2476
+ [EMOJI_RE, EMOJI_WIDTH],
2477
+ [CJKT_WIDE_RE, WIDE_WIDTH]
2478
+ ];
2479
+ let indexPrev = 0;
2480
+ let index = 0;
2481
+ let length = input.length;
2482
+ let lengthExtra = 0;
2483
+ let truncationEnabled = false;
2484
+ let truncationIndex = length;
2485
+ let truncationLimit = Math.max(0, LIMIT - ELLIPSIS_WIDTH);
2486
+ let unmatchedStart = 0;
2487
+ let unmatchedEnd = 0;
2488
+ let width = 0;
2489
+ let widthExtra = 0;
2490
+ outer:
2491
+ while (true) {
2492
+ if (unmatchedEnd > unmatchedStart || index >= length && index > indexPrev) {
2493
+ const unmatched = input.slice(unmatchedStart, unmatchedEnd) || input.slice(indexPrev, index);
2494
+ lengthExtra = 0;
2495
+ for (const char of unmatched.replaceAll(MODIFIER_RE, "")) {
2496
+ const codePoint = char.codePointAt(0) || 0;
2497
+ if (isFullWidth(codePoint)) {
2498
+ widthExtra = FULL_WIDTH_WIDTH;
2499
+ } else if (isWideNotCJKTNotEmoji(codePoint)) {
2500
+ widthExtra = WIDE_WIDTH;
2501
+ } else {
2502
+ widthExtra = REGULAR_WIDTH;
2503
+ }
2504
+ if (width + widthExtra > truncationLimit) {
2505
+ truncationIndex = Math.min(truncationIndex, Math.max(unmatchedStart, indexPrev) + lengthExtra);
2506
+ }
2507
+ if (width + widthExtra > LIMIT) {
2508
+ truncationEnabled = true;
2509
+ break outer;
2510
+ }
2511
+ lengthExtra += char.length;
2512
+ width += widthExtra;
2513
+ }
2514
+ unmatchedStart = unmatchedEnd = 0;
2515
+ }
2516
+ if (index >= length) {
2517
+ break outer;
2518
+ }
2519
+ for (let i = 0, l = PARSE_BLOCKS.length;i < l; i++) {
2520
+ const [BLOCK_RE, BLOCK_WIDTH] = PARSE_BLOCKS[i];
2521
+ BLOCK_RE.lastIndex = index;
2522
+ if (BLOCK_RE.test(input)) {
2523
+ lengthExtra = BLOCK_RE === CJKT_WIDE_RE ? getCodePointsLength(input.slice(index, BLOCK_RE.lastIndex)) : BLOCK_RE === EMOJI_RE ? 1 : BLOCK_RE.lastIndex - index;
2524
+ widthExtra = lengthExtra * BLOCK_WIDTH;
2525
+ if (width + widthExtra > truncationLimit) {
2526
+ truncationIndex = Math.min(truncationIndex, index + Math.floor((truncationLimit - width) / BLOCK_WIDTH));
2527
+ }
2528
+ if (width + widthExtra > LIMIT) {
2529
+ truncationEnabled = true;
2530
+ break outer;
2531
+ }
2532
+ width += widthExtra;
2533
+ unmatchedStart = indexPrev;
2534
+ unmatchedEnd = index;
2535
+ index = indexPrev = BLOCK_RE.lastIndex;
2536
+ continue outer;
2537
+ }
2538
+ }
2539
+ index += 1;
2540
+ }
2541
+ return {
2542
+ width: truncationEnabled ? truncationLimit : width,
2543
+ index: truncationEnabled ? truncationIndex : length,
2544
+ truncated: truncationEnabled,
2545
+ ellipsed: truncationEnabled && LIMIT >= ELLIPSIS_WIDTH
2546
+ };
2547
+ };
2548
+ var dist_default = getStringTruncatedWidth;
2549
+
2550
+ // ../node_modules/.bun/fast-string-width@3.0.2/node_modules/fast-string-width/dist/index.js
2551
+ var NO_TRUNCATION2 = {
2552
+ limit: Infinity,
2553
+ ellipsis: "",
2554
+ ellipsisWidth: 0
2555
+ };
2556
+ var fastStringWidth = (input, options = {}) => {
2557
+ return dist_default(input, NO_TRUNCATION2, options).width;
2558
+ };
2559
+ var dist_default2 = fastStringWidth;
2560
+
2561
+ // ../node_modules/.bun/fast-wrap-ansi@0.2.2/node_modules/fast-wrap-ansi/lib/main.js
2562
+ var ESC2 = "\x1B";
2563
+ var CSI = "›";
2564
+ var END_CODE = 39;
2565
+ var ANSI_ESCAPE_BELL = "\x07";
2566
+ var ANSI_CSI = "[";
2567
+ var ANSI_OSC = "]";
2568
+ var ANSI_SGR_TERMINATOR = "m";
2569
+ var ANSI_ESCAPE_LINK = `${ANSI_OSC}8;;`;
2570
+ var GROUP_REGEX = new RegExp(`(?:\\${ANSI_CSI}(?<code>\\d+)m|\\${ANSI_ESCAPE_LINK}(?<uri>.*)${ANSI_ESCAPE_BELL})`, "y");
2571
+ var getClosingCode = (openingCode) => {
2572
+ if (openingCode >= 30 && openingCode <= 37)
2573
+ return 39;
2574
+ if (openingCode >= 90 && openingCode <= 97)
2575
+ return 39;
2576
+ if (openingCode >= 40 && openingCode <= 47)
2577
+ return 49;
2578
+ if (openingCode >= 100 && openingCode <= 107)
2579
+ return 49;
2580
+ if (openingCode === 1 || openingCode === 2)
2581
+ return 22;
2582
+ if (openingCode === 3)
2583
+ return 23;
2584
+ if (openingCode === 4)
2585
+ return 24;
2586
+ if (openingCode === 7)
2587
+ return 27;
2588
+ if (openingCode === 8)
2589
+ return 28;
2590
+ if (openingCode === 9)
2591
+ return 29;
2592
+ if (openingCode === 0)
2593
+ return 0;
2594
+ return;
2595
+ };
2596
+ var wrapAnsiCode = (code) => `${ESC2}${ANSI_CSI}${code}${ANSI_SGR_TERMINATOR}`;
2597
+ var wrapAnsiHyperlink = (url) => `${ESC2}${ANSI_ESCAPE_LINK}${url}${ANSI_ESCAPE_BELL}`;
2598
+ var wrapWord = (rows, word, columns) => {
2599
+ const characters = word[Symbol.iterator]();
2600
+ let isInsideEscape = false;
2601
+ let isInsideLinkEscape = false;
2602
+ let lastRow = rows.at(-1);
2603
+ let visible = lastRow === undefined ? 0 : dist_default2(lastRow);
2604
+ let currentCharacter = characters.next();
2605
+ let nextCharacter = characters.next();
2606
+ let rawCharacterIndex = 0;
2607
+ while (!currentCharacter.done) {
2608
+ const character = currentCharacter.value;
2609
+ const characterLength = dist_default2(character);
2610
+ if (visible + characterLength <= columns) {
2611
+ rows[rows.length - 1] += character;
2612
+ } else {
2613
+ rows.push(character);
2614
+ visible = 0;
2615
+ }
2616
+ if (character === ESC2 || character === CSI) {
2617
+ isInsideEscape = true;
2618
+ isInsideLinkEscape = word.startsWith(ANSI_ESCAPE_LINK, rawCharacterIndex + 1);
2619
+ }
2620
+ if (isInsideEscape) {
2621
+ if (isInsideLinkEscape) {
2622
+ if (character === ANSI_ESCAPE_BELL) {
2623
+ isInsideEscape = false;
2624
+ isInsideLinkEscape = false;
2625
+ }
2626
+ } else if (character === ANSI_SGR_TERMINATOR) {
2627
+ isInsideEscape = false;
2628
+ }
2629
+ } else {
2630
+ visible += characterLength;
2631
+ if (visible === columns && !nextCharacter.done) {
2632
+ rows.push("");
2633
+ visible = 0;
2634
+ }
2635
+ }
2636
+ currentCharacter = nextCharacter;
2637
+ nextCharacter = characters.next();
2638
+ rawCharacterIndex += character.length;
2639
+ }
2640
+ lastRow = rows.at(-1);
2641
+ if (!visible && lastRow !== undefined && lastRow.length && rows.length > 1) {
2642
+ rows[rows.length - 2] += rows.pop();
2643
+ }
2644
+ };
2645
+ var stringVisibleTrimSpacesRight = (string) => {
2646
+ const words = string.split(" ");
2647
+ let last = words.length;
2648
+ while (last) {
2649
+ if (dist_default2(words[last - 1])) {
2650
+ break;
2651
+ }
2652
+ last--;
2653
+ }
2654
+ if (last === words.length) {
2655
+ return string;
2656
+ }
2657
+ return words.slice(0, last).join(" ") + words.slice(last).join("");
2658
+ };
2659
+ var exec = (string, columns, options = {}) => {
2660
+ if (options.trim !== false && string.trim() === "") {
2661
+ return "";
2662
+ }
2663
+ let returnValue = "";
2664
+ let escapeCode;
2665
+ let escapeUrl;
2666
+ const words = string.split(" ");
2667
+ let rows = [""];
2668
+ let rowLength = 0;
2669
+ for (let index = 0;index < words.length; index++) {
2670
+ const word = words[index];
2671
+ if (options.trim !== false) {
2672
+ const row = rows.at(-1) ?? "";
2673
+ const trimmed = row.trimStart();
2674
+ if (row.length !== trimmed.length) {
2675
+ rows[rows.length - 1] = trimmed;
2676
+ rowLength = dist_default2(trimmed);
2677
+ }
2678
+ }
2679
+ if (index !== 0) {
2680
+ if (rowLength >= columns && (options.wordWrap === false || options.trim === false)) {
2681
+ rows.push("");
2682
+ rowLength = 0;
2683
+ }
2684
+ if (rowLength || options.trim === false) {
2685
+ rows[rows.length - 1] += " ";
2686
+ rowLength++;
2687
+ }
2688
+ }
2689
+ const wordLength = dist_default2(word);
2690
+ if (options.hard && wordLength > columns) {
2691
+ const remainingColumns = columns - rowLength;
2692
+ const breaksStartingThisLine = 1 + Math.floor((wordLength - remainingColumns - 1) / columns);
2693
+ const breaksStartingNextLine = Math.floor((wordLength - 1) / columns);
2694
+ if (breaksStartingNextLine < breaksStartingThisLine) {
2695
+ rows.push("");
2696
+ }
2697
+ wrapWord(rows, word, columns);
2698
+ rowLength = dist_default2(rows.at(-1) ?? "");
2699
+ continue;
2700
+ }
2701
+ if (rowLength + wordLength > columns && rowLength && wordLength) {
2702
+ if (options.wordWrap === false && rowLength < columns) {
2703
+ wrapWord(rows, word, columns);
2704
+ rowLength = dist_default2(rows.at(-1) ?? "");
2705
+ continue;
2706
+ }
2707
+ rows.push("");
2708
+ rowLength = 0;
2709
+ }
2710
+ if (rowLength + wordLength > columns && options.wordWrap === false) {
2711
+ wrapWord(rows, word, columns);
2712
+ rowLength = dist_default2(rows.at(-1) ?? "");
2713
+ continue;
2714
+ }
2715
+ rows[rows.length - 1] += word;
2716
+ rowLength += wordLength;
2717
+ }
2718
+ if (options.trim !== false) {
2719
+ rows = rows.map((row) => stringVisibleTrimSpacesRight(row));
2720
+ }
2721
+ const preString = rows.join(`
2722
+ `);
2723
+ let inSurrogate = false;
2724
+ for (let i = 0;i < preString.length; i++) {
2725
+ const character = preString[i];
2726
+ returnValue += character;
2727
+ if (!inSurrogate) {
2728
+ inSurrogate = character >= "\uD800" && character <= "\uDBFF";
2729
+ if (inSurrogate) {
2730
+ continue;
2731
+ }
2732
+ } else {
2733
+ inSurrogate = false;
2734
+ }
2735
+ if (character === ESC2 || character === CSI) {
2736
+ GROUP_REGEX.lastIndex = i + 1;
2737
+ const groupsResult = GROUP_REGEX.exec(preString);
2738
+ const groups = groupsResult?.groups;
2739
+ if (groups?.code !== undefined) {
2740
+ const code = Number.parseFloat(groups.code);
2741
+ escapeCode = code === END_CODE ? undefined : code;
2742
+ } else if (groups?.uri !== undefined) {
2743
+ escapeUrl = groups.uri.length === 0 ? undefined : groups.uri;
2744
+ }
2745
+ }
2746
+ if (preString[i + 1] === `
2747
+ `) {
2748
+ if (escapeUrl) {
2749
+ returnValue += wrapAnsiHyperlink("");
2750
+ }
2751
+ const closingCode = escapeCode ? getClosingCode(escapeCode) : undefined;
2752
+ if (escapeCode && closingCode) {
2753
+ returnValue += wrapAnsiCode(closingCode);
2754
+ }
2755
+ } else if (character === `
2756
+ `) {
2757
+ if (escapeCode && getClosingCode(escapeCode)) {
2758
+ returnValue += wrapAnsiCode(escapeCode);
2759
+ }
2760
+ if (escapeUrl) {
2761
+ returnValue += wrapAnsiHyperlink(escapeUrl);
2762
+ }
2763
+ }
2764
+ }
2765
+ return returnValue;
2766
+ };
2767
+ var CRLF_OR_LF = /\r?\n/;
2768
+ function wrapAnsi(string, columns, options) {
2769
+ return String(string).normalize().split(CRLF_OR_LF).map((line) => exec(line, columns, options)).join(`
2770
+ `);
2771
+ }
2772
+
2773
+ // ../node_modules/.bun/@clack+core@1.5.1/node_modules/@clack/core/dist/index.mjs
2774
+ var import_sisteransi = __toESM(require_src(), 1);
2775
+ import { ReadStream } from "node:tty";
2776
+ function findCursor(s, o, l) {
2777
+ if (!l.some((r) => !r.disabled))
2778
+ return s;
2779
+ const t = s + o, n = Math.max(l.length - 1, 0), e = t < 0 ? n : t > n ? 0 : t;
2780
+ return l[e]?.disabled ? findCursor(e, o < 0 ? -1 : 1, l) : e;
2781
+ }
2782
+ var a$1 = ["up", "down", "left", "right", "space", "enter", "cancel"];
2783
+ var t = [
2784
+ "January",
2785
+ "February",
2786
+ "March",
2787
+ "April",
2788
+ "May",
2789
+ "June",
2790
+ "July",
2791
+ "August",
2792
+ "September",
2793
+ "October",
2794
+ "November",
2795
+ "December"
2796
+ ];
2797
+ var settings = {
2798
+ actions: new Set(a$1),
2799
+ aliases: /* @__PURE__ */ new Map([
2800
+ ["k", "up"],
2801
+ ["j", "down"],
2802
+ ["h", "left"],
2803
+ ["l", "right"],
2804
+ ["\x03", "cancel"],
2805
+ ["escape", "cancel"]
2806
+ ]),
2807
+ messages: {
2808
+ cancel: "Canceled",
2809
+ error: "Something went wrong"
2810
+ },
2811
+ withGuide: true,
2812
+ accessible: undefined,
2813
+ date: {
2814
+ monthNames: [...t],
2815
+ messages: {
2816
+ required: "Please enter a valid date",
2817
+ invalidMonth: "There are only 12 months in a year",
2818
+ invalidDay: (n, e) => `There are only ${n} days in ${e}`,
2819
+ afterMin: (n) => `Date must be on or after ${n.toISOString().slice(0, 10)}`,
2820
+ beforeMax: (n) => `Date must be on or before ${n.toISOString().slice(0, 10)}`
2821
+ }
2822
+ }
2823
+ };
2824
+ function isAccessible(n) {
2825
+ if (n !== undefined)
2826
+ return n;
2827
+ if (settings.accessible !== undefined)
2828
+ return settings.accessible;
2829
+ const e = process.env.ACCESSIBLE;
2830
+ return e !== undefined && e !== "" && e !== "0" && e !== "false";
2831
+ }
2832
+ function isActionKey(n, e) {
2833
+ if (typeof n == "string")
2834
+ return settings.aliases.get(n) === e;
2835
+ for (const s of n)
2836
+ if (s !== undefined && isActionKey(s, e))
2837
+ return true;
2838
+ return false;
2839
+ }
2840
+ function diffLines(i, s) {
2841
+ if (i === s)
2842
+ return;
2843
+ const e = i.split(`
2844
+ `), t = s.split(`
2845
+ `), r = Math.max(e.length, t.length), f = [];
2846
+ for (let n = 0;n < r; n++)
2847
+ e[n] !== t[n] && f.push(n);
2848
+ return {
2849
+ lines: f,
2850
+ numLinesBefore: e.length,
2851
+ numLinesAfter: t.length,
2852
+ numLines: r
2853
+ };
2854
+ }
2855
+ var R = globalThis.process.platform.startsWith("win");
2856
+ var CANCEL_SYMBOL = Symbol("clack:cancel");
2857
+ function isCancel(e) {
2858
+ return e === CANCEL_SYMBOL;
2859
+ }
2860
+ function setRawMode(e, r) {
2861
+ const o = e;
2862
+ o.isTTY && o.setRawMode(r);
2863
+ }
2864
+ function block({
2865
+ input: e = stdin,
2866
+ output: r = stdout,
2867
+ overwrite: o = true,
2868
+ hideCursor: n = true
2869
+ } = {}) {
2870
+ const s = l.createInterface({
2871
+ input: e,
2872
+ output: r,
2873
+ prompt: "",
2874
+ tabSize: 1
2875
+ });
2876
+ l.emitKeypressEvents(e, s), e instanceof ReadStream && e.isTTY && e.setRawMode(true);
2877
+ const t = (f, { name: a, sequence: w }) => {
2878
+ const c = String(f);
2879
+ if (isActionKey([c, a, w], "cancel")) {
2880
+ n && r.write(import_sisteransi.cursor.show), process.exit(0);
2881
+ return;
2882
+ }
2883
+ if (!o)
2884
+ return;
2885
+ const i = a === "return" ? 0 : -1, m = a === "return" ? -1 : 0;
2886
+ l.moveCursor(r, i, m, () => {
2887
+ l.clearLine(r, 1, () => {
2888
+ e.once("keypress", t);
2889
+ });
2890
+ });
2891
+ };
2892
+ return n && r.write(import_sisteransi.cursor.hide), e.once("keypress", t), () => {
2893
+ e.off("keypress", t), n && r.write(import_sisteransi.cursor.show), e instanceof ReadStream && e.isTTY && !R && e.setRawMode(false), s.terminal = false, s.close();
2894
+ };
2895
+ }
2896
+ var getColumns = (e) => ("columns" in e) && typeof e.columns == "number" ? e.columns : 80;
2897
+ var getRows = (e) => ("rows" in e) && typeof e.rows == "number" ? e.rows : 20;
2898
+ function wrapTextWithPrefix(e, r, o, n = o, s = o, t) {
2899
+ const f = getColumns(e ?? stdout);
2900
+ return wrapAnsi(r, f - o.length, {
2901
+ hard: true,
2902
+ trim: false
2903
+ }).split(`
2904
+ `).map((c, i, m) => {
2905
+ const d = t ? t(c, i) : c;
2906
+ return i === 0 ? `${n}${d}` : i === m.length - 1 ? `${s}${d}` : `${o}${d}`;
2907
+ }).join(`
2908
+ `);
2909
+ }
2910
+ function runValidation(e, a) {
2911
+ if ("~standard" in e) {
2912
+ const n = e["~standard"].validate(a);
2913
+ return n instanceof Promise ? n.then((r) => r.issues?.at(0)?.message) : n.issues?.at(0)?.message;
2914
+ }
2915
+ return e(a);
2916
+ }
2917
+
2918
+ class y {
2919
+ input;
2920
+ output;
2921
+ _abortSignal;
2922
+ rl;
2923
+ opts;
2924
+ _render;
2925
+ _track = false;
2926
+ _prevFrame = "";
2927
+ _subscribers = /* @__PURE__ */ new Map;
2928
+ _cursor = 0;
2929
+ state = "initial";
2930
+ error = "";
2931
+ value;
2932
+ userInput = "";
2933
+ get accessible() {
2934
+ return isAccessible(this.opts.accessible);
2935
+ }
2936
+ constructor(t, e = true) {
2937
+ const { input: i = stdin, output: s = stdout, render: r, signal: n, ...o } = t;
2938
+ this.opts = o, this.onKeypress = this.onKeypress.bind(this), this.close = this.close.bind(this), this.render = this.render.bind(this), this._render = r.bind(this), this._track = e, this._abortSignal = n, this.input = i, this.output = s;
2939
+ }
2940
+ unsubscribe() {
2941
+ this._subscribers.clear();
2942
+ }
2943
+ setSubscriber(t, e) {
2944
+ const i = this._subscribers.get(t) ?? [];
2945
+ i.push(e), this._subscribers.set(t, i);
2946
+ }
2947
+ on(t, e) {
2948
+ this.setSubscriber(t, { cb: e });
2949
+ }
2950
+ once(t, e) {
2951
+ this.setSubscriber(t, { cb: e, once: true });
2952
+ }
2953
+ emit(t, ...e) {
2954
+ const i = this._subscribers.get(t) ?? [], s = [];
2955
+ for (const r of i)
2956
+ r.cb(...e), r.once && s.push(() => i.splice(i.indexOf(r), 1));
2957
+ for (const r of s)
2958
+ r();
2959
+ }
2960
+ prompt() {
2961
+ return new Promise((t) => {
2962
+ if (this._abortSignal) {
2963
+ if (this._abortSignal.aborted)
2964
+ return this.state = "cancel", this.close(), t(CANCEL_SYMBOL);
2965
+ this._abortSignal.addEventListener("abort", () => {
2966
+ this.state = "cancel", this.close();
2967
+ }, { once: true });
2968
+ }
2969
+ this.rl = l__default.createInterface({
2970
+ input: this.input,
2971
+ tabSize: 2,
2972
+ prompt: "",
2973
+ escapeCodeTimeout: 50,
2974
+ terminal: true
2975
+ }), this.rl.prompt(), this.opts.initialUserInput !== undefined && this._setUserInput(this.opts.initialUserInput, true), this.input.on("keypress", this.onKeypress), setRawMode(this.input, true), this.output.on("resize", this.render), this.render(), this.once("submit", () => {
2976
+ this.output.write(import_sisteransi.cursor.show), this.output.off("resize", this.render), setRawMode(this.input, false), t(this.value);
2977
+ }), this.once("cancel", () => {
2978
+ this.output.write(import_sisteransi.cursor.show), this.output.off("resize", this.render), setRawMode(this.input, false), t(CANCEL_SYMBOL);
2979
+ });
2980
+ });
2981
+ }
2982
+ _isActionKey(t, e) {
2983
+ return t === "\t";
2984
+ }
2985
+ _shouldSubmit(t, e) {
2986
+ return true;
2987
+ }
2988
+ _setValue(t) {
2989
+ this.value = t, this.emit("value", this.value);
2990
+ }
2991
+ _setUserInput(t, e) {
2992
+ this.userInput = t ?? "", this.emit("userInput", this.userInput), e && this._track && this.rl && (this.rl.write(this.userInput), this._cursor = this.rl.cursor);
2993
+ }
2994
+ _clearUserInput() {
2995
+ this.rl?.write(null, { ctrl: true, name: "u" }), this._setUserInput("");
2996
+ }
2997
+ async onKeypress(t, e) {
2998
+ if (this.state !== "validating") {
2999
+ if (this._track && e.name !== "return" && (e.name && this._isActionKey(t, e) && this.rl?.write(null, { ctrl: true, name: "h" }), this._cursor = this.rl?.cursor ?? 0, this._setUserInput(this.rl?.line)), this.state === "error" && (this.state = "active"), e?.name && (!this._track && settings.aliases.has(e.name) && this.emit("cursor", settings.aliases.get(e.name)), settings.actions.has(e.name) && this.emit("cursor", e.name)), t && (t.toLowerCase() === "y" || t.toLowerCase() === "n") && this.emit("confirm", t.toLowerCase() === "y"), this.emit("key", t, e), e?.name === "return" && this._shouldSubmit(t, e)) {
3000
+ if (this.opts.validate) {
3001
+ const i = runValidation(this.opts.validate, this.value);
3002
+ let s;
3003
+ i instanceof Promise ? (this.state = "validating", this.render(), s = await i) : s = i, s && (this.error = s instanceof Error ? s.message : s, this.state = "error", this.rl?.write(this.userInput));
3004
+ }
3005
+ this.state !== "error" && (this.state = "submit");
3006
+ }
3007
+ isActionKey([t, e?.name, e?.sequence], "cancel") && (this.state = "cancel"), (this.state === "submit" || this.state === "cancel") && this.emit("finalize"), this.render(), (this.state === "submit" || this.state === "cancel") && this.close();
3008
+ }
3009
+ }
3010
+ close() {
3011
+ this.input.unpipe(), this.input.removeListener("keypress", this.onKeypress), this.output.write(`
3012
+ `), setRawMode(this.input, false), this.rl?.close(), this.rl = undefined, this.emit(`${this.state}`, this.value), this.unsubscribe();
3013
+ }
3014
+ restoreCursor() {
3015
+ const t = wrapAnsi(this._prevFrame, process.stdout.columns, { hard: true, trim: false }).split(`
3016
+ `).length - 1;
3017
+ this.output.write(import_sisteransi.cursor.move(-999, t * -1));
3018
+ }
3019
+ render() {
3020
+ const t = wrapAnsi(this._render(this) ?? "", process.stdout.columns, {
3021
+ hard: true,
3022
+ trim: false
3023
+ });
3024
+ if (t !== this._prevFrame) {
3025
+ if (this.state === "initial")
3026
+ this.output.write(import_sisteransi.cursor.hide);
3027
+ else {
3028
+ const e = diffLines(this._prevFrame, t), i = getRows(this.output);
3029
+ if (this.restoreCursor(), e) {
3030
+ const s = Math.max(0, e.numLinesAfter - i), r = Math.max(0, e.numLinesBefore - i);
3031
+ let n = e.lines.find((o) => o >= s);
3032
+ if (n === undefined) {
3033
+ this._prevFrame = t;
3034
+ return;
3035
+ }
3036
+ if (e.lines.length === 1) {
3037
+ this.output.write(import_sisteransi.cursor.move(0, n - r)), this.output.write(import_sisteransi.erase.lines(1));
3038
+ const o = t.split(`
3039
+ `);
3040
+ this.output.write(o[n]), this._prevFrame = t, this.output.write(import_sisteransi.cursor.move(0, o.length - n - 1));
3041
+ return;
3042
+ } else if (e.lines.length > 1) {
3043
+ if (s < r)
3044
+ n = s;
3045
+ else {
3046
+ const h = n - r;
3047
+ h > 0 && this.output.write(import_sisteransi.cursor.move(0, h));
3048
+ }
3049
+ this.output.write(import_sisteransi.erase.down());
3050
+ const f = t.split(`
3051
+ `).slice(n);
3052
+ this.output.write(f.join(`
3053
+ `)), this._prevFrame = t;
3054
+ return;
3055
+ }
3056
+ }
3057
+ this.output.write(import_sisteransi.erase.down());
3058
+ }
3059
+ this.output.write(t), this.state === "initial" && (this.state = "active"), this._prevFrame = t;
3060
+ }
3061
+ }
3062
+ }
3063
+ class r extends y {
3064
+ get cursor() {
3065
+ return this.value ? 0 : 1;
3066
+ }
3067
+ get _value() {
3068
+ return this.cursor === 0;
3069
+ }
3070
+ constructor(t) {
3071
+ super(t, false), this.value = !!t.initialValue, this.on("userInput", () => {
3072
+ this.value = this._value;
3073
+ }), this.on("confirm", (i) => {
3074
+ this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = i, this.state = "submit", this.close();
3075
+ }), this.on("cursor", () => {
3076
+ this.value = !this.value;
3077
+ });
3078
+ }
3079
+ }
3080
+ var n$1 = class n extends y {
3081
+ options;
3082
+ cursor = 0;
3083
+ get _selectedValue() {
3084
+ return this.options[this.cursor];
3085
+ }
3086
+ changeValue() {
3087
+ const e = this._selectedValue;
3088
+ this.value = e === undefined ? undefined : e.value;
3089
+ }
3090
+ constructor(e) {
3091
+ super(e, false), this.options = e.options;
3092
+ const o = this.options.findIndex(({ value: s }) => s === e.initialValue), t = o === -1 ? 0 : o;
3093
+ this.cursor = this.options[t]?.disabled ? findCursor(t, 1, this.options) : t, this.changeValue(), this.on("cursor", (s) => {
3094
+ switch (s) {
3095
+ case "left":
3096
+ case "up":
3097
+ this.cursor = findCursor(this.cursor, -1, this.options);
3098
+ break;
3099
+ case "down":
3100
+ case "right":
3101
+ this.cursor = findCursor(this.cursor, 1, this.options);
3102
+ break;
3103
+ }
3104
+ this.changeValue();
3105
+ });
3106
+ }
3107
+ };
3108
+
3109
+ // ../node_modules/.bun/@clack+prompts@1.8.1/node_modules/@clack/prompts/dist/index.mjs
3110
+ import { styleText, stripVTControlCharacters } from "node:util";
3111
+ import process$1 from "node:process";
3112
+ var import_sisteransi2 = __toESM(require_src(), 1);
3113
+ function isUnicodeSupported() {
3114
+ if (process$1.platform !== "win32") {
3115
+ return process$1.env.TERM !== "linux";
3116
+ }
3117
+ return Boolean(process$1.env.CI) || Boolean(process$1.env.WT_SESSION) || Boolean(process$1.env.TERMINUS_SUBLIME) || process$1.env.ConEmuTask === "{cmd::Cmder}" || process$1.env.TERM_PROGRAM === "Terminus-Sublime" || process$1.env.TERM_PROGRAM === "vscode" || process$1.env.TERM === "xterm-256color" || process$1.env.TERM === "alacritty" || process$1.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
3118
+ }
3119
+ var unicode = isUnicodeSupported();
3120
+ var isCI = () => process.env.CI === "true";
3121
+ var unicodeOr = (o, e) => unicode ? o : e;
3122
+ var S_STEP_ACTIVE = unicodeOr("◆", "*");
3123
+ var S_STEP_CANCEL = unicodeOr("■", "x");
3124
+ var S_STEP_ERROR = unicodeOr("▲", "x");
3125
+ var S_STEP_SUBMIT = unicodeOr("◇", "o");
3126
+ var S_BAR_START = unicodeOr("┌", "T");
3127
+ var S_BAR = unicodeOr("│", "|");
3128
+ var S_BAR_END = unicodeOr("└", "—");
3129
+ var S_BAR_START_RIGHT = unicodeOr("┐", "T");
3130
+ var S_BAR_END_RIGHT = unicodeOr("┘", "—");
3131
+ var S_RADIO_ACTIVE = unicodeOr("●", ">");
3132
+ var S_RADIO_INACTIVE = unicodeOr("○", " ");
3133
+ var S_CHECKBOX_ACTIVE = unicodeOr("◻", "[•]");
3134
+ var S_CHECKBOX_SELECTED = unicodeOr("◼", "[+]");
3135
+ var S_CHECKBOX_INACTIVE = unicodeOr("◻", "[ ]");
3136
+ var S_PASSWORD_MASK = unicodeOr("▪", "•");
3137
+ var S_BAR_H = unicodeOr("─", "-");
3138
+ var S_CORNER_TOP_RIGHT = unicodeOr("╮", "+");
3139
+ var S_CONNECT_LEFT = unicodeOr("├", "+");
3140
+ var S_CORNER_BOTTOM_RIGHT = unicodeOr("╯", "+");
3141
+ var S_CORNER_BOTTOM_LEFT = unicodeOr("╰", "+");
3142
+ var S_CORNER_TOP_LEFT = unicodeOr("╭", "+");
3143
+ var S_INFO = unicodeOr("●", "•");
3144
+ var S_SUCCESS = unicodeOr("◆", "*");
3145
+ var S_WARN = unicodeOr("▲", "!");
3146
+ var S_ERROR = unicodeOr("■", "x");
3147
+ var symbol = (o) => {
3148
+ switch (o) {
3149
+ case "initial":
3150
+ case "active":
3151
+ return styleText("cyan", S_STEP_ACTIVE);
3152
+ case "cancel":
3153
+ return styleText("red", S_STEP_CANCEL);
3154
+ case "error":
3155
+ return styleText("yellow", S_STEP_ERROR);
3156
+ case "submit":
3157
+ return styleText("green", S_STEP_SUBMIT);
3158
+ case "validating":
3159
+ return styleText("dim", S_STEP_ACTIVE);
3160
+ }
3161
+ };
3162
+ var symbolBar = (o) => {
3163
+ switch (o) {
3164
+ case "initial":
3165
+ case "active":
3166
+ return styleText("cyan", S_BAR);
3167
+ case "cancel":
3168
+ return styleText("red", S_BAR);
3169
+ case "error":
3170
+ return styleText("yellow", S_BAR);
3171
+ case "submit":
3172
+ return styleText("green", S_BAR);
3173
+ }
3174
+ };
3175
+ function formatInstructionFooter(o, e) {
3176
+ const r = [`${e ? `${styleText("cyan", S_BAR)} ` : ""}${o.join(" • ")}`];
3177
+ return e && r.push(styleText("cyan", S_BAR_END)), r;
3178
+ }
3179
+ var I = (l, e, w, p, b, C = false) => {
3180
+ let r = e, O = 0;
3181
+ if (C)
3182
+ for (let i = p - 1;i >= w; i--) {
3183
+ const m = l[i];
3184
+ if (m && (r -= m.length), O++, r <= b)
3185
+ break;
3186
+ }
3187
+ else
3188
+ for (let i = w;i < p; i++) {
3189
+ const m = l[i];
3190
+ if (m && (r -= m.length), O++, r <= b)
3191
+ break;
3192
+ }
3193
+ return { lineCount: r, removals: O };
3194
+ };
3195
+ var limitOptions = ({
3196
+ cursor: l,
3197
+ options: e,
3198
+ style: w,
3199
+ output: p = process.stdout,
3200
+ maxItems: b = Number.POSITIVE_INFINITY,
3201
+ columnPadding: C = 0,
3202
+ rowPadding: r = 4
3203
+ }) => {
3204
+ const i = getColumns(p) - C, m = getRows(p), M = styleText("dim", "..."), v = Math.max(m - r, 0), a = Math.max(Math.min(b, v), 5);
3205
+ let f = 0;
3206
+ l >= a - 3 && (f = Math.max(Math.min(l - a + 3, e.length - a), 0));
3207
+ let d = a < e.length && f > 0, c = a < e.length && f + a < e.length;
3208
+ const W = Math.min(f + a, e.length), s = [];
3209
+ let g = 0;
3210
+ d && g++, c && g++;
3211
+ const T = f + (d ? 1 : 0), y = W - (c ? 1 : 0);
3212
+ for (let t = T;t < y; t++) {
3213
+ const n = e[t], o = n ? w(n, t === l) : "", h = wrapAnsi(o, i, {
3214
+ hard: true,
3215
+ trim: false
3216
+ }).split(`
3217
+ `);
3218
+ s.push(h), g += h.length;
3219
+ }
3220
+ if (g > v) {
3221
+ let t = 0, n = 0, o = g;
3222
+ const h = l - T;
3223
+ let u = v;
3224
+ const L = () => I(s, o, 0, h, u), E = () => I(s, o, h + 1, s.length, u, true);
3225
+ d ? ({ lineCount: o, removals: t } = L(), o > u && (c || (u -= 1), { lineCount: o, removals: n } = E())) : (c || (u -= 1), { lineCount: o, removals: n } = E(), o > u && (u -= 1, { lineCount: o, removals: t } = L())), t > 0 && (d = true, s.splice(0, t)), n > 0 && (c = true, s.splice(s.length - n, n));
3226
+ }
3227
+ const x = [];
3228
+ d && x.push(M);
3229
+ for (const t of s)
3230
+ for (const n of t)
3231
+ x.push(n);
3232
+ return c && x.push(M), x;
3233
+ };
3234
+ var confirm = (e) => {
3235
+ const a = e.active ?? "Yes", o = e.inactive ?? "No";
3236
+ return new r({
3237
+ active: a,
3238
+ inactive: o,
3239
+ signal: e.signal,
3240
+ input: e.input,
3241
+ output: e.output,
3242
+ initialValue: e.initialValue ?? true,
3243
+ render() {
3244
+ const i = e.withGuide ?? settings.withGuide, u = `${symbol(this.state)} `, l = i ? `${styleText("gray", S_BAR)} ` : "", f = wrapTextWithPrefix(e.output, e.message, l, u), s = `${i ? `${styleText("gray", S_BAR)}
3245
+ ` : ""}${f}
3246
+ `, c = this.value ? a : o;
3247
+ switch (this.state) {
3248
+ case "submit": {
3249
+ const r = i ? `${styleText("gray", S_BAR)} ` : "";
3250
+ return `${s}${r}${styleText("dim", c)}`;
3251
+ }
3252
+ case "cancel": {
3253
+ const r = i ? `${styleText("gray", S_BAR)} ` : "";
3254
+ return `${s}${r}${styleText(["strikethrough", "dim"], c)}${i ? `
3255
+ ${styleText("gray", S_BAR)}` : ""}`;
3256
+ }
3257
+ default: {
3258
+ const r = i ? `${styleText("cyan", S_BAR)} ` : "", g = i ? styleText("cyan", S_BAR_END) : "";
3259
+ return `${s}${r}${this.value ? `${styleText("green", S_RADIO_ACTIVE)} ${a}` : `${styleText("dim", S_RADIO_INACTIVE)} ${styleText("dim", a)}`}${e.vertical ? i ? `
3260
+ ${styleText("cyan", S_BAR)} ` : `
3261
+ ` : ` ${styleText("dim", "/")} `}${this.value ? `${styleText("dim", S_RADIO_INACTIVE)} ${styleText("dim", o)}` : `${styleText("green", S_RADIO_ACTIVE)} ${o}`}
3262
+ ${g}
3263
+ `;
3264
+ }
3265
+ }
3266
+ }
3267
+ }).prompt();
3268
+ };
3269
+ var MULTISELECT_INSTRUCTIONS = [
3270
+ `${styleText("dim", "↑/↓")} to navigate`,
3271
+ `${styleText("dim", "Space:")} select`,
3272
+ `${styleText("dim", "Enter:")} confirm`
3273
+ ];
3274
+ var intro = (o = "", t) => {
3275
+ const i = t?.output ?? process.stdout, e = t?.withGuide ?? settings.withGuide ? `${styleText("gray", S_BAR_START)} ` : "";
3276
+ i.write(`${e}${o}
3277
+ `);
3278
+ };
3279
+ var outro = (o = "", t) => {
3280
+ const i = t?.output ?? process.stdout, e = t?.withGuide ?? settings.withGuide ? `${styleText("gray", S_BAR)}
3281
+ ${styleText("gray", S_BAR_END)} ` : "";
3282
+ i.write(`${e}${o}
3283
+
3284
+ `);
3285
+ };
3286
+ var W = (l) => styleText("magenta", l);
3287
+ var spinner = ({
3288
+ indicator: l = "dots",
3289
+ onCancel: h,
3290
+ output: n = process.stdout,
3291
+ cancelMessage: G,
3292
+ errorMessage: O,
3293
+ frames: E = unicode ? ["◒", "◐", "◓", "◑"] : ["•", "o", "O", "0"],
3294
+ delay: F = unicode ? 80 : 120,
3295
+ signal: m,
3296
+ ...I
3297
+ } = {}) => {
3298
+ const u = isCI();
3299
+ let M, T, d = false, S = false, s = "", p, w = performance.now();
3300
+ const x = getColumns(n), k = I?.styleFrame ?? W, g = (e) => {
3301
+ const r = e > 1 ? O ?? settings.messages.error : G ?? settings.messages.cancel;
3302
+ S = e === 1, d && (a(r, e), S && typeof h == "function" && h());
3303
+ }, f = () => g(2), i = () => g(1), A = () => {
3304
+ process.on("uncaughtExceptionMonitor", f), process.on("unhandledRejection", f), process.on("SIGINT", i), process.on("SIGTERM", i), process.on("exit", g), m && m.addEventListener("abort", i);
3305
+ }, H = () => {
3306
+ process.removeListener("uncaughtExceptionMonitor", f), process.removeListener("unhandledRejection", f), process.removeListener("SIGINT", i), process.removeListener("SIGTERM", i), process.removeListener("exit", g), m && m.removeEventListener("abort", i);
3307
+ }, y = () => {
3308
+ if (p === undefined)
3309
+ return;
3310
+ u && n.write(`
3311
+ `);
3312
+ const r = wrapAnsi(p, x, {
3313
+ hard: true,
3314
+ trim: false
3315
+ }).split(`
3316
+ `);
3317
+ r.length > 1 && n.write(import_sisteransi2.cursor.up(r.length - 1)), n.write(import_sisteransi2.cursor.to(0)), n.write(import_sisteransi2.erase.down());
3318
+ }, C = (e) => e.replace(/\.+$/, ""), _ = (e) => {
3319
+ const r = (performance.now() - e) / 1000, t = Math.floor(r / 60), o = Math.floor(r % 60);
3320
+ return t > 0 ? `[${t}m ${o}s]` : `[${o}s]`;
3321
+ }, N = I.withGuide ?? settings.withGuide, P = (e = "") => {
3322
+ d = true, M = block({ output: n }), s = C(e), w = performance.now(), N && n.write(`${styleText("gray", S_BAR)}
3323
+ `);
3324
+ let r = 0, t = 0;
3325
+ A(), T = setInterval(() => {
3326
+ if (u && s === p)
3327
+ return;
3328
+ y(), p = s;
3329
+ const o = k(E[r]);
3330
+ let v;
3331
+ if (u)
3332
+ v = `${o} ${s}...`;
3333
+ else if (l === "timer")
3334
+ v = `${o} ${s} ${_(w)}`;
3335
+ else {
3336
+ const B = ".".repeat(Math.floor(t)).slice(0, 3);
3337
+ v = `${o} ${s}${B}`;
3338
+ }
3339
+ const j = wrapAnsi(v, x, {
3340
+ hard: true,
3341
+ trim: false
3342
+ });
3343
+ n.write(j), r = r + 1 < E.length ? r + 1 : 0, t = t < 4 ? t + 0.125 : 0;
3344
+ }, F);
3345
+ }, a = (e = "", r = 0, t = false) => {
3346
+ if (!d)
3347
+ return;
3348
+ d = false, clearInterval(T), y();
3349
+ const o = r === 0 ? styleText("green", S_STEP_SUBMIT) : r === 1 ? styleText("red", S_STEP_CANCEL) : styleText("red", S_STEP_ERROR);
3350
+ s = e ?? s, t || (l === "timer" ? n.write(`${o} ${s} ${_(w)}
3351
+ `) : n.write(`${o} ${s}
3352
+ `)), H(), M();
3353
+ };
3354
+ return {
3355
+ start: P,
3356
+ stop: (e = "") => a(e, 0),
3357
+ message: (e = "") => {
3358
+ s = C(e ?? s);
3359
+ },
3360
+ cancel: (e = "") => a(e, 1),
3361
+ error: (e = "") => a(e, 2),
3362
+ clear: () => a("", 0, true),
3363
+ get isCancelled() {
3364
+ return S;
3365
+ }
3366
+ };
3367
+ };
3368
+ var u2 = {
3369
+ light: unicodeOr("─", "-"),
3370
+ heavy: unicodeOr("━", "="),
3371
+ block: unicodeOr("█", "#")
3372
+ };
3373
+ var SELECT_INSTRUCTIONS = [
3374
+ `${styleText("dim", "↑/↓")} to navigate`,
3375
+ `${styleText("dim", "Enter:")} confirm`
3376
+ ];
3377
+ var c = (t, o) => t.includes(`
3378
+ `) ? t.split(`
3379
+ `).map((d) => o(d)).join(`
3380
+ `) : o(t);
3381
+ var select = (t) => {
3382
+ const o = (n, m) => {
3383
+ if (n === undefined)
3384
+ return "";
3385
+ const s = n.label ?? String(n.value);
3386
+ switch (m) {
3387
+ case "disabled":
3388
+ return `${styleText("gray", S_RADIO_INACTIVE)} ${c(s, (i) => styleText("gray", i))}${n.hint ? ` ${styleText("dim", `(${n.hint ?? "disabled"})`)}` : ""}`;
3389
+ case "selected":
3390
+ return `${c(s, (i) => styleText("dim", i))}`;
3391
+ case "active":
3392
+ return `${styleText("green", S_RADIO_ACTIVE)} ${s}${n.hint ? ` ${styleText("dim", `(${n.hint})`)}` : ""}`;
3393
+ case "cancelled":
3394
+ return `${c(s, (i) => styleText(["strikethrough", "dim"], i))}`;
3395
+ default:
3396
+ return `${styleText("dim", S_RADIO_INACTIVE)} ${c(s, (i) => styleText("dim", i))}`;
3397
+ }
3398
+ }, d = t.showInstructions ?? true;
3399
+ return new n$1({
3400
+ options: t.options,
3401
+ signal: t.signal,
3402
+ input: t.input,
3403
+ output: t.output,
3404
+ initialValue: t.initialValue,
3405
+ render() {
3406
+ const n = t.withGuide ?? settings.withGuide, m = `${symbol(this.state)} `, s = `${symbolBar(this.state)} `, i = wrapTextWithPrefix(t.output, t.message, s, m), u = `${n ? `${styleText("gray", S_BAR)}
3407
+ ` : ""}${i}
3408
+ `;
3409
+ switch (this.state) {
3410
+ case "submit": {
3411
+ const r = n ? `${styleText("gray", S_BAR)} ` : "", a = wrapTextWithPrefix(t.output, o(this.options[this.cursor], "selected"), r);
3412
+ return `${u}${a}`;
3413
+ }
3414
+ case "cancel": {
3415
+ const r = n ? `${styleText("gray", S_BAR)} ` : "", a = wrapTextWithPrefix(t.output, o(this.options[this.cursor], "cancelled"), r);
3416
+ return `${u}${a}${n ? `
3417
+ ${styleText("gray", S_BAR)}` : ""}`;
3418
+ }
3419
+ default: {
3420
+ const r = n ? `${styleText("cyan", S_BAR)} ` : "", a = u.split(`
3421
+ `).length, p = d ? formatInstructionFooter(SELECT_INSTRUCTIONS, n) : n ? [styleText("cyan", S_BAR_END)] : [], f = p.join(`
3422
+ `), b = p.length + 1;
3423
+ return `${u}${r}${limitOptions({
3424
+ output: t.output,
3425
+ cursor: this.cursor,
3426
+ options: this.options,
3427
+ maxItems: t.maxItems,
3428
+ columnPadding: r.length,
3429
+ rowPadding: a + b,
3430
+ style: (g, x) => o(g, g.disabled ? "disabled" : x ? "active" : "inactive")
3431
+ }).join(`
3432
+ ${r}`)}
3433
+ ${f}
3434
+ `;
3435
+ }
3436
+ }
3437
+ }
3438
+ }).prompt();
3439
+ };
3440
+ var i = `${styleText("gray", S_BAR)} `;
3441
+
3442
+ // src/push.ts
3443
+ var CONFIRM_FLAGS = new Set(["--yes", "-y"]);
3444
+ var FORWARDED_GIT_FLAGS = new Set(["--force"]);
3445
+ function parsePushArgs(args) {
3446
+ const positionals = args.filter((arg) => !arg.startsWith("-"));
3447
+ if (positionals.length > 1) {
3448
+ return {
3449
+ ok: false,
3450
+ reason: `Expected at most one branch, got ${positionals.join(", ")}.`
3451
+ };
3452
+ }
3453
+ const flags = args.filter((arg) => arg.startsWith("-"));
3454
+ const unsupported = flags.filter((flag) => !CONFIRM_FLAGS.has(flag) && !FORWARDED_GIT_FLAGS.has(flag));
3455
+ if (unsupported.length > 0) {
3456
+ return {
3457
+ ok: false,
3458
+ reason: `maesttro push doesn't accept ${unsupported.join(", ")}; it takes --yes and --force.`
3459
+ };
3460
+ }
3461
+ const [requested] = positionals;
3462
+ return {
3463
+ ok: true,
3464
+ ...requested === undefined ? {} : { requested },
3465
+ yes: flags.some((flag) => CONFIRM_FLAGS.has(flag)),
3466
+ gitFlags: flags.filter((flag) => FORWARDED_GIT_FLAGS.has(flag))
3467
+ };
3468
+ }
2280
3469
  function pushCandidates(sessions) {
2281
3470
  const candidates = new Map;
2282
3471
  for (const session of sessions) {
@@ -2320,43 +3509,78 @@ function planPush({
2320
3509
  reason: names.length === 0 ? "No session in this repo has a branch ready to push." : "Several session branches are ready; pick one."
2321
3510
  };
2322
3511
  }
2323
- async function choose(candidates) {
2324
- if (!process.stdin.isTTY)
2325
- return;
2326
- console.log(candidates.map((name, index) => ` ${index + 1}) ${name}`).join(`
2327
- `));
2328
- const answer = await ask("Branch to push: ");
2329
- return candidates[Number(answer) - 1];
2330
- }
2331
- function describe(run, root, branch, base, tip) {
2332
- const commits = git(run, root, [
2333
- "log",
2334
- "--format= %h %s (%an / %cn)",
2335
- `${base}..${tip}`
3512
+ function branchHint(run, candidate) {
3513
+ const count = git(run, candidate.dir, [
3514
+ "rev-list",
3515
+ "--count",
3516
+ `${candidate.baseSha}..${candidate.branch}`
2336
3517
  ]).out;
2337
- const stat = git(run, root, ["diff", "--stat", `${base}...${tip}`]).out;
3518
+ const stat = cleanOutput(git(run, candidate.dir, [
3519
+ "diff",
3520
+ "--no-color",
3521
+ "--stat",
3522
+ `${candidate.baseSha}...${candidate.branch}`
3523
+ ]).out);
3524
+ const files = stat.split(`
3525
+ `).pop() ?? "";
2338
3526
  return [
2339
- `Pushing ${branch} to origin (compared with ${base}):`,
2340
- stripControl(commits) || " (no new commits)",
3527
+ `${count || "0"} commit${count === "1" ? "" : "s"}`,
3528
+ files || "no file changes"
3529
+ ].join(" | ");
3530
+ }
3531
+ async function chooseInteractive(run, names, candidates) {
3532
+ return select({
3533
+ message: "Branch to push",
3534
+ options: names.map((name) => ({
3535
+ value: name,
3536
+ label: name,
3537
+ hint: branchHint(run, candidates.get(name))
3538
+ }))
3539
+ });
3540
+ }
3541
+ function describe(run, root, branch, base, tip, gitFlags) {
3542
+ const commits = cleanOutput(git(run, root, ["log", "--format= %h %s (%an / %cn)", `${base}..${tip}`]).out);
3543
+ const stat = cleanOutput(git(run, root, ["diff", "--no-color", "--stat", `${base}...${tip}`]).out);
3544
+ return [
3545
+ `Pushing ${branch} to origin${gitFlags.length ? ` with ${gitFlags.join(" ")}` : ""} (compared with ${base}):`,
3546
+ `
3547
+ Commits:`,
3548
+ commits || " (no new commits)",
2341
3549
  stat ? `
2342
- ${stripControl(stat)}` : ""
3550
+ Changed files:
3551
+ ${stat}` : ""
2343
3552
  ].join(`
2344
3553
  `);
2345
3554
  }
2346
3555
  async function push({ args }) {
2347
3556
  const run = runCommand;
2348
- const yes = args.includes("--yes") || args.includes("-y");
2349
- const requested = args.find((arg) => !arg.startsWith("-"));
3557
+ const parsed = parsePushArgs(args);
3558
+ if (!parsed.ok) {
3559
+ console.error(parsed.reason);
3560
+ process.exitCode = 1;
3561
+ return;
3562
+ }
3563
+ const { requested, yes, gitFlags } = parsed;
2350
3564
  let root;
2351
3565
  try {
2352
3566
  root = repoRoot(run, process.cwd());
2353
3567
  } catch (error) {
2354
3568
  throw error instanceof LaunchError ? error : new LaunchError(String(error));
2355
3569
  }
3570
+ const tty = process.stdin.isTTY === true;
2356
3571
  const candidates = pushCandidates(sessionsForRepo(root));
2357
3572
  let plan = planPush({ requested, candidates });
2358
3573
  if (!plan.ok && !requested && plan.candidates.length > 1) {
2359
- const picked = await choose(plan.candidates);
3574
+ let picked;
3575
+ if (tty) {
3576
+ intro("maesttro push");
3577
+ const chosen = await chooseInteractive(run, plan.candidates, candidates);
3578
+ if (isCancel(chosen)) {
3579
+ console.log("Not pushed.");
3580
+ return;
3581
+ }
3582
+ picked = chosen;
3583
+ }
2360
3584
  if (picked)
2361
3585
  plan = planPush({ requested: picked, candidates });
2362
3586
  }
@@ -2366,62 +3590,125 @@ async function push({ args }) {
2366
3590
  return;
2367
3591
  }
2368
3592
  const { branch, base, baseSha, dir, sessionId } = plan.candidate;
3593
+ const prepare = tty ? spinner() : undefined;
3594
+ prepare?.start(`Preparing ${branch}...`);
2369
3595
  const tip = branchTip(dir, branch);
2370
3596
  if (!tip || !importObjects(run, root, dir, tip)) {
3597
+ prepare?.stop(`Could not prepare ${branch}.`);
2371
3598
  console.error(`Could not read ${branch} from session ${sessionId}.`);
2372
3599
  process.exitCode = 1;
2373
3600
  return;
2374
3601
  }
2375
3602
  git(run, root, ["update-ref", `refs/maesttro/${sessionId}/${branch}`, tip]);
2376
- console.log(describe(run, root, branch, base, tip));
3603
+ prepare?.stop(`Prepared ${branch}.`);
3604
+ console.log(describe(run, root, branch, base, tip, gitFlags));
2377
3605
  if (!yes) {
2378
- if (!process.stdin.isTTY) {
3606
+ if (!tty) {
2379
3607
  console.error("Not a terminal; re-run with --yes to push without confirming.");
2380
3608
  process.exitCode = 1;
2381
3609
  return;
2382
3610
  }
2383
- const answer = await ask(`
2384
- Push? [y/N] `);
2385
- if (!/^y(es)?$/i.test(answer)) {
3611
+ const answer = await confirm({ message: `Push ${branch} to origin?` });
3612
+ if (isCancel(answer) || !answer) {
2386
3613
  console.log("Not pushed.");
2387
3614
  return;
2388
3615
  }
2389
3616
  }
2390
- const pushed = spawnSync3("git", ["-C", root, "push", "origin", `${tip}:refs/heads/${branch}`], { stdio: "inherit" });
3617
+ const pushed = spawnSync3("git", ["-C", root, "push", ...gitFlags, "origin", `${tip}:refs/heads/${branch}`], { stdio: "inherit" });
2391
3618
  if (pushed.status === 0) {
2392
- console.log(`Pushed ${branch}. Base for comparison was ${baseSha.slice(0, 8)}.`);
3619
+ const done = `Pushed ${branch}. Base for comparison was ${baseSha.slice(0, 8)}.`;
3620
+ if (tty && !yes)
3621
+ outro(done);
3622
+ else
3623
+ console.log(done);
2393
3624
  }
2394
3625
  process.exitCode = pushed.status ?? 1;
2395
3626
  }
2396
3627
 
2397
3628
  // src/sessions-command.ts
2398
3629
  import { existsSync as existsSync5, rmSync as rmSync7 } from "node:fs";
2399
- function sessionsCommand({ args }) {
3630
+ function parseRemoveArgs(args) {
3631
+ let all = false;
3632
+ let force = false;
3633
+ let id;
3634
+ for (const arg of args) {
3635
+ if (arg === "--all")
3636
+ all = true;
3637
+ else if (arg === "--force")
3638
+ force = true;
3639
+ else if (id === undefined)
3640
+ id = arg;
3641
+ }
3642
+ return { all, force, id };
3643
+ }
3644
+ async function removeSession(run, session, force, ask, interactive) {
3645
+ if (existsSync5(session.worktree)) {
3646
+ const preserved = preserveBranches(run, session);
3647
+ const head = cloneHead(session.worktree);
3648
+ const unbranched = preserved.length === 0 && head?.detached !== undefined && head.detached !== session.baseSha;
3649
+ if (unbranched && !force) {
3650
+ let proceed;
3651
+ if (interactive) {
3652
+ const answer = await ask(`Session ${session.id} has commits on a detached HEAD not on any branch. Removing it discards them. Remove anyway? [y/N] `);
3653
+ proceed = /^(y(es)?)?$/i.test(answer);
3654
+ } else {
3655
+ proceed = false;
3656
+ console.error(`Session ${session.id} has commits on a detached HEAD not on any branch. Resume and create a branch, or re-run with --force.`);
3657
+ }
3658
+ if (!proceed) {
3659
+ console.log(`Kept session ${session.id}.`);
3660
+ return "kept";
3661
+ }
3662
+ }
3663
+ rmSync7(session.worktree, { recursive: true, force: true });
3664
+ const kept = preserved.length > 0 ? ` Its branches are kept as refs/maesttro/${session.id}/*.` : "";
3665
+ console.log(`Removed session ${session.id}.${kept}`);
3666
+ } else {
3667
+ console.log(`Removed session ${session.id}.`);
3668
+ }
3669
+ deleteSession(session.id);
3670
+ return "removed";
3671
+ }
3672
+ async function sessionsCommand({
3673
+ args,
3674
+ ask: ask2 = ask,
3675
+ interactive = process.stdin.isTTY === true
3676
+ }) {
2400
3677
  const run = runCommand;
2401
3678
  const root = repoRoot(run, process.cwd());
2402
- const [action, id] = args;
3679
+ const [action, ...rest] = args;
2403
3680
  if (action === "remove") {
2404
- const session = id ? readSession(id) : undefined;
2405
- if (!session || session.repoRoot !== root) {
2406
- console.error(`No session ${id ?? "(missing id)"} in this repo.`);
2407
- process.exitCode = 1;
2408
- return;
2409
- }
2410
- if (existsSync5(session.worktree)) {
2411
- const preserved = preserveBranches(run, session);
2412
- const head = cloneHead(session.worktree);
2413
- if (preserved.length === 0 && head?.detached !== undefined && head.detached !== session.baseSha) {
2414
- console.error(`${session.id} has commits on a detached HEAD not on any branch; resume and create a branch first.`);
3681
+ const { all, force, id } = parseRemoveArgs(rest);
3682
+ if (all) {
3683
+ if (id !== undefined) {
3684
+ console.error("`maesttro sessions remove` takes either `--all` or a session id, not both.");
2415
3685
  process.exitCode = 1;
2416
3686
  return;
2417
3687
  }
2418
- rmSync7(session.worktree, { recursive: true, force: true });
2419
- const kept = preserved.length > 0 ? ` Its branches are kept as refs/maesttro/${session.id}/*.` : "";
2420
- console.log(`Removed session ${session.id}.${kept}`);
2421
- } else {
2422
- console.log(`Removed session ${session.id}.`);
3688
+ const sessions = sessionsForRepo(root);
3689
+ if (sessions.length === 0) {
3690
+ console.log("No maesttro sessions in this repo.");
3691
+ return;
3692
+ }
3693
+ for (const session of sessions) {
3694
+ await removeSession(run, session, force, ask2, interactive);
3695
+ }
3696
+ return;
2423
3697
  }
2424
- deleteSession(session.id);
3698
+ if (id === undefined) {
3699
+ console.error("Usage: maesttro sessions remove <id> | maesttro sessions remove --all");
3700
+ process.exitCode = 1;
3701
+ return;
3702
+ }
3703
+ const session = readSession(id);
3704
+ if (!session || session.repoRoot !== root) {
3705
+ console.error(`No session ${id} in this repo.`);
3706
+ process.exitCode = 1;
3707
+ return;
3708
+ }
3709
+ const result = await removeSession(run, session, force, ask2, interactive);
3710
+ if (result === "kept")
3711
+ process.exitCode = 1;
2425
3712
  return;
2426
3713
  }
2427
3714
  const sessions = sessionsForRepo(root);
@@ -2482,6 +3769,78 @@ async function setup({ args }) {
2482
3769
  // src/update.ts
2483
3770
  import { execSync as execSync5 } from "node:child_process";
2484
3771
 
3772
+ // src/runner-images.ts
3773
+ var RUNNER_LABEL = "org.maesttro.runner";
3774
+ var RUNNER_ENTRYPOINT = "/usr/local/bin/maesttro-entrypoint";
3775
+ var INSPECT_FORMAT = "{{.Id}}\t{{json .Config}}\t{{json .RepoTags}}";
3776
+ function parseJson(text, fallback) {
3777
+ try {
3778
+ return JSON.parse(text ?? "") ?? fallback;
3779
+ } catch {
3780
+ return fallback;
3781
+ }
3782
+ }
3783
+ function parseImageRows(stdout) {
3784
+ return stdout.split(`
3785
+ `).filter((line) => line.trim() !== "").map((line) => {
3786
+ const [id = "", config, tags] = line.split("\t");
3787
+ const { Entrypoint, Labels } = parseJson(config, {});
3788
+ return {
3789
+ id,
3790
+ entrypoint: Entrypoint ?? [],
3791
+ labels: Labels ?? {},
3792
+ tags: parseJson(tags, [])
3793
+ };
3794
+ });
3795
+ }
3796
+ function isRunnerImage(row) {
3797
+ return row.labels[RUNNER_LABEL] !== undefined || row.entrypoint.includes(RUNNER_ENTRYPOINT);
3798
+ }
3799
+ function staleRunnerImages({
3800
+ rows,
3801
+ latestId
3802
+ }) {
3803
+ if (!latestId)
3804
+ return [];
3805
+ return rows.filter((row) => row.id !== latestId && isRunnerImage(row));
3806
+ }
3807
+ function currentLatestId(run) {
3808
+ const result = run("docker", [
3809
+ "image",
3810
+ "inspect",
3811
+ "--format",
3812
+ "{{.Id}}",
3813
+ IMAGE
3814
+ ]);
3815
+ return result.status === 0 ? result.stdout.trim() || undefined : undefined;
3816
+ }
3817
+ function removeStaleRunnerImages({
3818
+ run = runCommand,
3819
+ latestId = () => currentLatestId(run)
3820
+ } = {}) {
3821
+ const listed = run("docker", ["image", "ls", "--quiet", "--no-trunc"]);
3822
+ if (listed.status !== 0)
3823
+ return 0;
3824
+ const ids = [
3825
+ ...new Set(listed.stdout.split(`
3826
+ `).map((id) => id.trim()).filter(Boolean))
3827
+ ];
3828
+ if (ids.length === 0)
3829
+ return 0;
3830
+ const inspected = run("docker", [
3831
+ "image",
3832
+ "inspect",
3833
+ "--format",
3834
+ INSPECT_FORMAT,
3835
+ ...ids
3836
+ ]);
3837
+ const stale = staleRunnerImages({
3838
+ rows: parseImageRows(inspected.stdout),
3839
+ latestId: latestId()
3840
+ });
3841
+ return stale.filter((image) => run("docker", ["rmi", ...image.tags, image.id]).status === 0).length;
3842
+ }
3843
+
2485
3844
  // src/version-check.ts
2486
3845
  import { execFile } from "node:child_process";
2487
3846
  import { existsSync as existsSync6, readFileSync as readFileSync8 } from "node:fs";
@@ -2541,6 +3900,10 @@ function update() {
2541
3900
  -> Rebuilding runner image with refreshed client configs...`);
2542
3901
  execSync5(`docker build -t ${IMAGE} "${DOCKERFILE_DIR}"`, { stdio: "inherit" });
2543
3902
  console.log(`
3903
+ -> Removing older runner images...`);
3904
+ const removed = removeStaleRunnerImages();
3905
+ console.log(removed > 0 ? ` Removed ${removed} older runner image${removed === 1 ? "" : "s"}.` : " None to remove.");
3906
+ console.log(`
2544
3907
  Update complete. Bot identity and your project files are untouched —`);
2545
3908
  console.log("only the runner image (client versions + curated configs) was refreshed.");
2546
3909
  }