@standardagents/code 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import os6 from 'os';
2
+ import os6, { homedir } from 'os';
3
3
  import fs4 from 'fs';
4
4
  import path3 from 'path';
5
5
  import readline2 from 'readline/promises';
@@ -572,6 +572,20 @@ function gradAt(t) {
572
572
  Math.round(a[2] + (b[2] - a[2]) * k)
573
573
  ];
574
574
  }
575
+ function cyclePos(t) {
576
+ const p = t - Math.floor(t);
577
+ return p < 0.5 ? p * 2 : 2 - p * 2;
578
+ }
579
+ function colorAtCycle(tri, tc) {
580
+ if (tc) {
581
+ const [r, g, b] = gradAt(tri);
582
+ return `\x1B[38;2;${r};${g};${b}m`;
583
+ }
584
+ return `\x1B[38;5;${GRAD_256[Math.min(GRAD_256.length - 1, Math.floor(tri * GRAD_256.length))]}m`;
585
+ }
586
+ function brandCycleColor(ms, periodMs = 1200) {
587
+ return colorAtCycle(cyclePos(ms / periodMs), truecolor());
588
+ }
575
589
  function gradientText(text, phase = 0) {
576
590
  const chars = [...text];
577
591
  const visible = chars.filter((ch) => ch.trim().length > 0).length;
@@ -1832,6 +1846,130 @@ var SystemEvents = class {
1832
1846
  }
1833
1847
  };
1834
1848
 
1849
+ // src/wordmill.ts
1850
+ var MILL_WORDS = [
1851
+ "Working",
1852
+ "Thinking",
1853
+ "Building",
1854
+ "Brewing",
1855
+ "Crafting",
1856
+ "Scheming",
1857
+ "Tinkering",
1858
+ "Noodling",
1859
+ "Pondering",
1860
+ "Wrangling",
1861
+ "Conjuring",
1862
+ "Percolating",
1863
+ "Assembling",
1864
+ "Cogitating",
1865
+ "Hatching",
1866
+ "Forging",
1867
+ // The silly shelf — irreverent, never profane.
1868
+ "Pickling",
1869
+ "Spooning",
1870
+ "Marinating",
1871
+ "Squishing",
1872
+ "Wiggling",
1873
+ "Frolicking",
1874
+ "Moisturizing",
1875
+ "Bamboozling",
1876
+ "Skedaddling",
1877
+ "Discombobulating",
1878
+ "Waffling",
1879
+ "Yodeling",
1880
+ "Shimmying",
1881
+ "Galumphing",
1882
+ "Snorkeling",
1883
+ "Bedazzling"
1884
+ ];
1885
+ var FLIP_POOL = "abcdefghjkmnopqrstuvwxyz#$%&@!*+=.:~";
1886
+ var HOLD_MS = 2600;
1887
+ var LAZY_MS = 320;
1888
+ var LAZY_PERIOD = 200;
1889
+ var FAST_PERIOD = 70;
1890
+ var BOLD = "\x1B[1m";
1891
+ var DIM2 = "\x1B[2m";
1892
+ var OFF = "\x1B[22m";
1893
+ function glyphAt(slot, bucket) {
1894
+ let h = (slot + 1) * 2654435761 ^ (bucket + 1) * 40503;
1895
+ h = Math.imul(h ^ h >>> 13, 1274126177);
1896
+ h ^= h >>> 16;
1897
+ return FLIP_POOL[(h >>> 0) % FLIP_POOL.length];
1898
+ }
1899
+ var WordMill = class {
1900
+ word = MILL_WORDS[0];
1901
+ target = null;
1902
+ // non-null while morphing
1903
+ phaseAt = 0;
1904
+ // clock time the current phase began
1905
+ slots = [];
1906
+ morphLen = 0;
1907
+ rng;
1908
+ constructor(rng = Math.random) {
1909
+ this.rng = rng;
1910
+ }
1911
+ /** Restart at "Working" (called when a turn begins). */
1912
+ reset(now) {
1913
+ this.word = MILL_WORDS[0];
1914
+ this.target = null;
1915
+ this.phaseAt = now;
1916
+ }
1917
+ /** The plain word currently displayed or being formed (for tests). */
1918
+ get current() {
1919
+ return this.target ?? this.word;
1920
+ }
1921
+ /** The styled label for this frame (contains only bold/dim ANSI). */
1922
+ text(now) {
1923
+ if (this.phaseAt === 0) this.phaseAt = now;
1924
+ if (!this.target) {
1925
+ if (now - this.phaseAt >= HOLD_MS) this.beginMorph(now);
1926
+ else return `${BOLD}${this.word}${OFF}`;
1927
+ }
1928
+ return this.morphFrame(now);
1929
+ }
1930
+ beginMorph(now) {
1931
+ const options = MILL_WORDS.filter((w) => w !== this.word);
1932
+ this.target = options[Math.floor(this.rng() * options.length)];
1933
+ this.phaseAt = now;
1934
+ const n = Math.max(this.word.length, this.target.length);
1935
+ this.slots = [];
1936
+ let maxLand = 0;
1937
+ for (let i = 0; i < n; i++) {
1938
+ const start = this.rng() * 500;
1939
+ const land = 750 + i * 90 + this.rng() * 350;
1940
+ this.slots.push({ start, land });
1941
+ if (land > maxLand) maxLand = land;
1942
+ }
1943
+ this.morphLen = maxLand + 60;
1944
+ }
1945
+ morphFrame(now) {
1946
+ const target = this.target;
1947
+ const t = now - this.phaseAt;
1948
+ if (t >= this.morphLen) {
1949
+ this.word = target;
1950
+ this.target = null;
1951
+ this.phaseAt = now;
1952
+ return `${BOLD}${this.word}${OFF}`;
1953
+ }
1954
+ let out = "";
1955
+ for (let i = 0; i < this.slots.length; i++) {
1956
+ const s = this.slots[i];
1957
+ if (t < s.start) {
1958
+ const ch = this.word[i];
1959
+ if (ch) out += `${BOLD}${ch}${OFF}`;
1960
+ } else if (t < s.land) {
1961
+ const age = t - s.start;
1962
+ const period = age < LAZY_MS ? LAZY_PERIOD : FAST_PERIOD;
1963
+ out += `${DIM2}${glyphAt(i, Math.floor(t / period))}${OFF}`;
1964
+ } else {
1965
+ const ch = target[i];
1966
+ if (ch) out += `${BOLD}${ch}${OFF}`;
1967
+ }
1968
+ }
1969
+ return out;
1970
+ }
1971
+ };
1972
+
1835
1973
  // src/types.ts
1836
1974
  var LEVELS = [1, 2, 3, 4, 5];
1837
1975
  var LEVEL_DETAIL = {
@@ -1950,6 +2088,7 @@ var C = {
1950
2088
  teal: "\x1B[38;5;37m"
1951
2089
  };
1952
2090
  var FRAMES = ["\u28F7", "\u28EF", "\u28DF", "\u287F", "\u28BF", "\u28FB", "\u28FD", "\u28FE"];
2091
+ var SPINNER_MS = 70;
1953
2092
  var SUBAGENT_COLORS = [
1954
2093
  "\x1B[35m",
1955
2094
  // magenta
@@ -1966,6 +2105,19 @@ var SUBAGENT_COLORS = [
1966
2105
  ];
1967
2106
  var COMPACTION_COLOR = "\x1B[38;5;208m";
1968
2107
  var COMPACTION_AGENT = "compaction_agent";
2108
+ function isWideCodePoint(cp) {
2109
+ return cp >= 4352 && cp <= 4447 || // Hangul Jamo
2110
+ cp >= 11904 && cp <= 42191 || // CJK radicals … Yi
2111
+ cp >= 44032 && cp <= 55203 || // Hangul syllables
2112
+ cp >= 63744 && cp <= 64255 || // CJK compatibility ideographs
2113
+ cp >= 65072 && cp <= 65103 || // CJK compatibility forms
2114
+ cp >= 65280 && cp <= 65376 || // fullwidth forms
2115
+ cp >= 65504 && cp <= 65510 || cp >= 127744 && cp <= 129791 || // emoji
2116
+ cp >= 131072;
2117
+ }
2118
+ function sanitizeHudRow(s) {
2119
+ return s.replace(/\x1b(?!\[)/g, "").replace(/[\x00-\x1a\x1c-\x1f\x7f]/g, " ");
2120
+ }
1969
2121
  var Tui = class _Tui {
1970
2122
  constructor(level = 1) {
1971
2123
  this.level = level;
@@ -1986,6 +2138,8 @@ var Tui = class _Tui {
1986
2138
  // caret's row offset from the input region top, last render
1987
2139
  working = false;
1988
2140
  workingStart = 0;
2141
+ // Slot-machine morph for the working label (Working → Brewing → …).
2142
+ wordMill = new WordMill();
1989
2143
  spinnerTimer = null;
1990
2144
  bgCount = 0;
1991
2145
  // The `[⚙ n bg]` prompt badge can be selected with ← from the start of the
@@ -2495,7 +2649,8 @@ var Tui = class _Tui {
2495
2649
  return `${(n / 1e6).toFixed(2)}M`;
2496
2650
  }
2497
2651
  spinnerFrame() {
2498
- return `${C.bold}${C.cyan}${FRAMES[Math.floor(Date.now() / 100) % FRAMES.length]}${C.reset}`;
2652
+ const now = Date.now();
2653
+ return `${C.bold}${brandCycleColor(now)}${FRAMES[Math.floor(now / SPINNER_MS) % FRAMES.length]}${C.reset}`;
2499
2654
  }
2500
2655
  /** "↑X ↓Y" cumulative token totals (greyed — low-priority). */
2501
2656
  tokensText() {
@@ -2568,7 +2723,7 @@ var Tui = class _Tui {
2568
2723
  if (this.working) {
2569
2724
  const el = this.formatElapsed(Date.now() - this.workingStart);
2570
2725
  const right = `${C.dim}${el}${C.reset}${tk ? " " + tk : ""}`;
2571
- const head = `${this.spinnerFrame()} ${C.bold}Working${C.reset}`;
2726
+ const head = `${this.spinnerFrame()} ${this.wordMill.text(Date.now())}`;
2572
2727
  const avail = Math.max(
2573
2728
  0,
2574
2729
  cols2 - this.visibleWidth(head) - this.visibleWidth(right) - 2 - gaugeReserve
@@ -2599,7 +2754,50 @@ var Tui = class _Tui {
2599
2754
  return `${q}${bg}${this.levelColor()}\u276F${C.reset} `;
2600
2755
  }
2601
2756
  visibleWidth(s) {
2602
- return s.replace(/\x1b\[[0-9;]*m/g, "").length;
2757
+ let w = 0;
2758
+ for (const ch of s.replace(/\x1b\[[0-9;]*m/g, "")) {
2759
+ w += isWideCodePoint(ch.codePointAt(0)) ? 2 : 1;
2760
+ }
2761
+ return w;
2762
+ }
2763
+ /**
2764
+ * Truncate a styled string to at most `max` VISIBLE columns, copying ANSI
2765
+ * escape sequences through without counting them and appending a reset if it
2766
+ * cut mid-style. The redraw's whole height math assumes every HUD row is
2767
+ * exactly ONE physical terminal row — true only while each row's visible
2768
+ * width is strictly less than `cols`. A row of width == cols hits the
2769
+ * terminal's auto-wrap boundary: many terminals push the cursor to the next
2770
+ * line, so the following CR/LF yields an EXTRA physical row we didn't count,
2771
+ * and the region marches down the screen leaving a trail on every tick. Every
2772
+ * HUD row is clamped through here to keep that invariant no matter what any
2773
+ * individual line builder produces.
2774
+ */
2775
+ clampVisible(s, max) {
2776
+ let out = "";
2777
+ let width = 0;
2778
+ let i = 0;
2779
+ let truncated = false;
2780
+ while (i < s.length) {
2781
+ if (s[i] === "\x1B" && s[i + 1] === "[") {
2782
+ let j = i + 2;
2783
+ while (j < s.length && !/[a-zA-Z]/.test(s[j])) j++;
2784
+ out += s.slice(i, j + 1);
2785
+ i = j + 1;
2786
+ continue;
2787
+ }
2788
+ const cp = s.codePointAt(i);
2789
+ const chLen = cp > 65535 ? 2 : 1;
2790
+ const chWidth = isWideCodePoint(cp) ? 2 : 1;
2791
+ if (width + chWidth > max) {
2792
+ truncated = true;
2793
+ break;
2794
+ }
2795
+ out += s.slice(i, i + chLen);
2796
+ width += chWidth;
2797
+ i += chLen;
2798
+ }
2799
+ if (truncated) out += C.reset;
2800
+ return out;
2603
2801
  }
2604
2802
  /**
2605
2803
  * The slash-palette block rendered BELOW the input. While the palette is open
@@ -2612,7 +2810,7 @@ var Tui = class _Tui {
2612
2810
  */
2613
2811
  paletteBlockLines(cols2) {
2614
2812
  if (!this.paletteOpen()) return [];
2615
- const rows = this.paletteLines(cols2);
2813
+ const rows = this.paletteLines(cols2).map((r) => this.clampVisible(r, Math.max(1, cols2 - 1)));
2616
2814
  const reserved = Math.max(this.commands.length, rows.length);
2617
2815
  while (rows.length < reserved) rows.push("");
2618
2816
  return rows;
@@ -2681,11 +2879,13 @@ var Tui = class _Tui {
2681
2879
  if (!this.started || this.takeoverHandler) return;
2682
2880
  const cols2 = process.stdout.columns || 80;
2683
2881
  this.moveToRegionTop();
2684
- process.stdout.write("\x1B[J");
2685
2882
  const hudWidths = [];
2883
+ const hudRows = [];
2884
+ const rowCap = Math.max(1, cols2 - 1);
2686
2885
  const writeHudRow = (line) => {
2687
- hudWidths.push(this.visibleWidth(line));
2688
- process.stdout.write(line + "\r\n");
2886
+ const row = this.clampVisible(sanitizeHudRow(line), rowCap);
2887
+ hudWidths.push(this.visibleWidth(row));
2888
+ hudRows.push(row);
2689
2889
  };
2690
2890
  const previewLines = this.streamPreviewLines(cols2);
2691
2891
  for (const line of previewLines) writeHudRow(line);
@@ -2697,7 +2897,7 @@ var Tui = class _Tui {
2697
2897
  const quitLine = this.quitArmed ? `${C.dim}Press Control-C again to exit${C.reset}` : null;
2698
2898
  const quitRows = quitLine ? 1 : 0;
2699
2899
  if (quitLine) writeHudRow(quitLine);
2700
- const frame = FRAMES[Math.floor(Date.now() / 100) % FRAMES.length];
2900
+ const frame = FRAMES[Math.floor(Date.now() / SPINNER_MS) % FRAMES.length];
2701
2901
  for (const sub of this.subagents) {
2702
2902
  const color = sub.agentName === COMPACTION_AGENT ? COMPACTION_COLOR : SUBAGENT_COLORS[this.subagentColorByID.get(sub.id) ?? 0];
2703
2903
  const budget = cols2 - 10;
@@ -2716,8 +2916,8 @@ var Tui = class _Tui {
2716
2916
  const prefix = this.promptPrefix();
2717
2917
  const pw = this.visibleWidth(prefix);
2718
2918
  const lines = this.inputBuffer.split("\n");
2719
- process.stdout.write(prefix + lines[0]);
2720
- for (let i = 1; i < lines.length; i++) process.stdout.write("\r\n" + lines[i]);
2919
+ const tail = [prefix + lines[0]];
2920
+ for (let i = 1; i < lines.length; i++) tail.push("\r\n" + lines[i]);
2721
2921
  const rowsOf = (len, lead) => Math.max(1, Math.ceil((lead + len) / cols2));
2722
2922
  const lineRows = lines.map((l, i) => rowsOf(l.length, i === 0 ? pw : 0));
2723
2923
  const inputRows = lineRows.reduce((a, b) => a + b, 0);
@@ -2732,18 +2932,18 @@ var Tui = class _Tui {
2732
2932
  for (let i = 0; i < caretLine; i++) caretRow += lineRows[i];
2733
2933
  const caretCol = caretCell % cols2;
2734
2934
  const paletteBlock = this.paletteBlockLines(cols2);
2735
- for (const line of paletteBlock) process.stdout.write("\r\n" + line);
2935
+ for (const line of paletteBlock) tail.push("\r\n" + line);
2736
2936
  if (paletteBlock.length > 0) {
2737
- process.stdout.write("\r");
2937
+ tail.push("\r");
2738
2938
  const up = inputRows - 1 + paletteBlock.length - caretRow;
2739
- if (up > 0) process.stdout.write(`\x1B[${up}A`);
2740
- if (caretCol > 0) process.stdout.write(`\x1B[${caretCol}C`);
2939
+ if (up > 0) tail.push(`\x1B[${up}A`);
2940
+ if (caretCol > 0) tail.push(`\x1B[${caretCol}C`);
2741
2941
  this.lastCursorRow = aboveRows + caretRow;
2742
2942
  } else if (this.cursorPos < this.inputBuffer.length) {
2743
- process.stdout.write("\r");
2943
+ tail.push("\r");
2744
2944
  const up = inputRows - 1 - caretRow;
2745
- if (up > 0) process.stdout.write(`\x1B[${up}A`);
2746
- if (caretCol > 0) process.stdout.write(`\x1B[${caretCol}C`);
2945
+ if (up > 0) tail.push(`\x1B[${up}A`);
2946
+ if (caretCol > 0) tail.push(`\x1B[${caretCol}C`);
2747
2947
  this.lastCursorRow = aboveRows + caretRow;
2748
2948
  } else {
2749
2949
  this.lastCursorRow = aboveRows + (inputRows - 1);
@@ -2751,6 +2951,8 @@ var Tui = class _Tui {
2751
2951
  this.drawnHudWidths = hudWidths;
2752
2952
  this.lastDrawnCols = cols2;
2753
2953
  this.bottomDrawn = true;
2954
+ const out = "\x1B[J" + hudRows.join("\r\n") + "\r\n" + tail.join("");
2955
+ process.stdout.write(out);
2754
2956
  }
2755
2957
  clearBottom() {
2756
2958
  if (!this.bottomDrawn) return;
@@ -2986,6 +3188,7 @@ var Tui = class _Tui {
2986
3188
  if (on && !this.working) {
2987
3189
  this.working = true;
2988
3190
  this.workingStart = Date.now();
3191
+ this.wordMill.reset(this.workingStart);
2989
3192
  this.goalComplete = false;
2990
3193
  } else if (!on) {
2991
3194
  this.working = false;
@@ -3017,7 +3220,7 @@ var Tui = class _Tui {
3017
3220
  syncSpinner() {
3018
3221
  const spinning = this.working || this.subagents.length > 0;
3019
3222
  if (spinning && !this.spinnerTimer) {
3020
- this.spinnerTimer = setInterval(() => this.renderBottom(), 100);
3223
+ this.spinnerTimer = setInterval(() => this.renderBottom(), SPINNER_MS);
3021
3224
  } else if (!spinning && this.spinnerTimer) {
3022
3225
  clearInterval(this.spinnerTimer);
3023
3226
  this.spinnerTimer = null;
@@ -3063,7 +3266,7 @@ var Tui = class _Tui {
3063
3266
  * resets whenever the label changes.
3064
3267
  */
3065
3268
  setStep(label, outTokens) {
3066
- const next = label && label.trim() ? label.trim() : null;
3269
+ const next = label && label.trim() ? label.replace(/\s+/g, " ").trim() : null;
3067
3270
  if (next !== this.step) {
3068
3271
  this.step = next;
3069
3272
  this.stepStart = Date.now();
@@ -3296,8 +3499,8 @@ function appendHistory(store, threadId, history, text) {
3296
3499
  // src/markdown.ts
3297
3500
  var ESC = "\x1B[";
3298
3501
  var R = ESC + "0m";
3299
- var BOLD = ESC + "1m";
3300
- var DIM2 = ESC + "2m";
3502
+ var BOLD2 = ESC + "1m";
3503
+ var DIM3 = ESC + "2m";
3301
3504
  var ITAL = ESC + "3m";
3302
3505
  var UNDER = ESC + "4m";
3303
3506
  var TEAL = ESC + "38;5;37m";
@@ -3319,11 +3522,11 @@ function inline(s) {
3319
3522
  });
3320
3523
  s = s.replace(
3321
3524
  /\[([^\]]+)\]\(([^)\s]+)\)/g,
3322
- (_, text, url) => `${CYAN}${UNDER}${text}${R} ${DIM2}${url}${R}`
3525
+ (_, text, url) => `${CYAN}${UNDER}${text}${R} ${DIM3}${url}${R}`
3323
3526
  );
3324
- s = s.replace(/\*\*([^*]+)\*\*/g, (_, t) => `${BOLD}${t}${R}`);
3527
+ s = s.replace(/\*\*([^*]+)\*\*/g, (_, t) => `${BOLD2}${t}${R}`);
3325
3528
  s = s.replace(/\*([^*\n]+)\*/g, (_, t) => `${ITAL}${t}${R}`);
3326
- s = s.replace(/~~([^~]+)~~/g, (_, t) => `${DIM2}${t}${R}`);
3529
+ s = s.replace(/~~([^~]+)~~/g, (_, t) => `${DIM3}${t}${R}`);
3327
3530
  s = s.replace(/\x00(\d+)\x00/g, (_, i) => `${TEAL}${codes[+i].replace(/ /g, String.fromCharCode(160))}${R}`);
3328
3531
  return s;
3329
3532
  }
@@ -3376,7 +3579,7 @@ function renderTable(rows) {
3376
3579
  const cells = [];
3377
3580
  for (let c2 = 0; c2 < cols2; c2++) {
3378
3581
  const raw = r[c2] ?? "";
3379
- const styled = ri === 0 ? `${BOLD}${inline(raw)}${R}` : inline(raw);
3582
+ const styled = ri === 0 ? `${BOLD2}${inline(raw)}${R}` : inline(raw);
3380
3583
  cells.push(padEndVisible(styled, widths[c2]));
3381
3584
  }
3382
3585
  out.push((" " + cells.join(sep)).replace(/\s+$/, ""));
@@ -3416,7 +3619,7 @@ function renderMarkdown(src, cols2 = 80) {
3416
3619
  }
3417
3620
  const heading = line.match(/^(#{1,6})\s+(.*)$/);
3418
3621
  if (heading) {
3419
- for (const ln of wrapStyled(heading[2].trim(), cols2)) out.push(`${BOLD}${TEAL}${ln}${R}`);
3622
+ for (const ln of wrapStyled(heading[2].trim(), cols2)) out.push(`${BOLD2}${TEAL}${ln}${R}`);
3420
3623
  i++;
3421
3624
  continue;
3422
3625
  }
@@ -3428,7 +3631,7 @@ function renderMarkdown(src, cols2 = 80) {
3428
3631
  const quote = line.match(/^\s*>\s?(.*)$/);
3429
3632
  if (quote) {
3430
3633
  for (const ln of wrapStyled(inline(quote[1]), Math.max(8, cols2 - 2))) {
3431
- out.push(`${GRAY}\u2502${R} ${DIM2}${ln}${R}`);
3634
+ out.push(`${GRAY}\u2502${R} ${DIM3}${ln}${R}`);
3432
3635
  }
3433
3636
  i++;
3434
3637
  continue;
@@ -3444,7 +3647,7 @@ function renderMarkdown(src, cols2 = 80) {
3444
3647
  if (numbered) {
3445
3648
  const marker = `${numbered[2]}${numbered[3]}`;
3446
3649
  const leadWidth = numbered[1].length + marker.length + 1;
3447
- wrapBlock(out, cols2, `${numbered[1]}${BOLD}${marker}${R} `, " ".repeat(leadWidth), leadWidth, inline(numbered[4]));
3650
+ wrapBlock(out, cols2, `${numbered[1]}${BOLD2}${marker}${R} `, " ".repeat(leadWidth), leadWidth, inline(numbered[4]));
3448
3651
  i++;
3449
3652
  continue;
3450
3653
  }
@@ -3733,7 +3936,7 @@ var McpManager = class {
3733
3936
  }));
3734
3937
  return {
3735
3938
  ok: true,
3736
- result: servers.length ? JSON.stringify({ servers }, null, 2) : "No MCP servers are connected. The user can add one with the /mcp command, or you can install one with install_mcp."
3939
+ result: servers.length ? JSON.stringify({ servers }, null, 2) : 'No MCP servers are connected. The user can add one with the /mcp command, or you can install one with the mcp tool (action "install").'
3737
3940
  };
3738
3941
  }
3739
3942
  if (action === "list_tools") {
@@ -3889,6 +4092,107 @@ function saveDefaultEndpoint(endpoint) {
3889
4092
  } catch {
3890
4093
  }
3891
4094
  }
4095
+ var PKG_NAME = "@standardagents/code";
4096
+ var REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PKG_NAME)}`;
4097
+ var CACHE_REL_DIR = ".config/standardagents-cli";
4098
+ var CACHE_FILE = "update-check.json";
4099
+ var CACHE_TTL_MS = 1e3 * 60 * 60 * 24;
4100
+ var CHECK_TIMEOUT_MS = 4e3;
4101
+ function cacheDir() {
4102
+ return path3.join(homedir(), CACHE_REL_DIR);
4103
+ }
4104
+ function cachePath() {
4105
+ return path3.join(cacheDir(), CACHE_FILE);
4106
+ }
4107
+ function readCache() {
4108
+ try {
4109
+ const raw = fs4.readFileSync(cachePath(), "utf-8");
4110
+ return JSON.parse(raw);
4111
+ } catch {
4112
+ return null;
4113
+ }
4114
+ }
4115
+ function writeCache(latest) {
4116
+ try {
4117
+ const dir = cacheDir();
4118
+ if (!fs4.existsSync(dir)) fs4.mkdirSync(dir, { recursive: true });
4119
+ fs4.writeFileSync(cachePath(), JSON.stringify({ latest, timestamp: Date.now() }));
4120
+ } catch {
4121
+ }
4122
+ }
4123
+ async function checkForUpdate(currentVersion) {
4124
+ if (!currentVersion) return null;
4125
+ if (process.env.NO_UPDATE_NOTIFIER || process.env.CI) return null;
4126
+ const cached = readCache();
4127
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
4128
+ if (cached.latest !== currentVersion) {
4129
+ return { current: currentVersion, latest: cached.latest };
4130
+ }
4131
+ return null;
4132
+ }
4133
+ const controller = new AbortController();
4134
+ const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);
4135
+ try {
4136
+ const res = await fetch(REGISTRY_URL, {
4137
+ signal: controller.signal,
4138
+ headers: {
4139
+ Accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*",
4140
+ "User-Agent": `${PKG_NAME}/${currentVersion}`
4141
+ }
4142
+ });
4143
+ if (!res.ok) return null;
4144
+ const data = await res.json();
4145
+ const latest = data["dist-tags"]?.latest;
4146
+ if (!latest) return null;
4147
+ writeCache(latest);
4148
+ if (latest !== currentVersion) {
4149
+ return { current: currentVersion, latest };
4150
+ }
4151
+ return null;
4152
+ } catch {
4153
+ return null;
4154
+ } finally {
4155
+ clearTimeout(timeout);
4156
+ }
4157
+ }
4158
+ async function forceCheckForUpdate(currentVersion) {
4159
+ if (!currentVersion) return null;
4160
+ if (process.env.NO_UPDATE_NOTIFIER || process.env.CI) return null;
4161
+ const controller = new AbortController();
4162
+ const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);
4163
+ try {
4164
+ const res = await fetch(REGISTRY_URL, {
4165
+ signal: controller.signal,
4166
+ headers: {
4167
+ Accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*",
4168
+ "User-Agent": `${PKG_NAME}/${currentVersion}`
4169
+ }
4170
+ });
4171
+ if (!res.ok) return null;
4172
+ const data = await res.json();
4173
+ const latest = data["dist-tags"]?.latest;
4174
+ if (!latest) return null;
4175
+ writeCache(latest);
4176
+ if (latest !== currentVersion) {
4177
+ return { current: currentVersion, latest };
4178
+ }
4179
+ return null;
4180
+ } catch {
4181
+ return null;
4182
+ } finally {
4183
+ clearTimeout(timeout);
4184
+ }
4185
+ }
4186
+ function runNpmUpdate() {
4187
+ return new Promise((resolve) => {
4188
+ const child = spawn("npm", ["i", "-g", `${PKG_NAME}@latest`], {
4189
+ stdio: "inherit",
4190
+ shell: true
4191
+ });
4192
+ child.on("close", (code) => resolve(code === 0));
4193
+ child.on("error", () => resolve(false));
4194
+ });
4195
+ }
3892
4196
 
3893
4197
  // src/index.ts
3894
4198
  var AGENT_ID = "standard_code_agent";
@@ -3992,7 +4296,25 @@ function printAssistant(tui, text) {
3992
4296
  }
3993
4297
  tui.print("");
3994
4298
  }
4299
+ function startLoader(label) {
4300
+ const frames = ["\u28F7", "\u28EF", "\u28DF", "\u287F", "\u28BF", "\u28FB", "\u28FD", "\u28FE"];
4301
+ stdout.write("\x1B[?25l");
4302
+ const draw = () => {
4303
+ const now = Date.now();
4304
+ const f = frames[Math.floor(now / 70) % frames.length];
4305
+ stdout.write(`\r\x1B[K${brandCycleColor(now)}${f}${c.reset} ${c.dim}${label}\u2026${c.reset}`);
4306
+ };
4307
+ draw();
4308
+ const timer = setInterval(draw, 70);
4309
+ return {
4310
+ stop: () => {
4311
+ clearInterval(timer);
4312
+ stdout.write("\r\x1B[K\x1B[?25h");
4313
+ }
4314
+ };
4315
+ }
3995
4316
  function farewell(stoppedProcs = 0) {
4317
+ stdout.write("\x1B[?25h");
3996
4318
  if (stoppedProcs > 0) {
3997
4319
  stdout.write(
3998
4320
  `
@@ -4041,7 +4363,7 @@ function printWelcome(endpoint, projectDir) {
4041
4363
  const meta = [
4042
4364
  `${c.bold}${gradientText("Standard Code")}${c.reset}${version ? ` ${c.dim}v${version}${c.reset}` : ""}`,
4043
4365
  `${c.dim}terminal coding agent${c.reset}`,
4044
- `${c.teal}${host}${c.reset}`,
4366
+ ...endpoint === PRODUCTION_ENDPOINT ? [] : [`${c.teal}${host}${c.reset}`],
4045
4367
  `${c.dim}${dir}${c.reset}`
4046
4368
  ];
4047
4369
  const markWidth = Math.max(...LOGO_MARK.map((l) => [...l].length));
@@ -4155,9 +4477,29 @@ ${c.dim}Press Control-C again to exit${c.reset}
4155
4477
 
4156
4478
  `);
4157
4479
  }
4480
+ const version = readVersion();
4481
+ let updateAvailable = null;
4482
+ {
4483
+ const loading = startLoader("Checking for updates");
4484
+ updateAvailable = await checkForUpdate(version);
4485
+ loading.stop();
4486
+ if (updateAvailable) {
4487
+ stdout.write(
4488
+ ` ${c.teal}\u25C7${c.reset} ${c.dim}Update available:${c.reset} ${c.dim}v${updateAvailable.current}${c.reset} \u2192 ${c.bold}v${updateAvailable.latest}${c.reset}
4489
+ ${c.dim}Run ${c.reset}${c.bold}npm i -g @standardagents/code@latest${c.reset}${c.dim} to update${c.reset}
4490
+
4491
+ `
4492
+ );
4493
+ }
4494
+ }
4158
4495
  const stored = getCredential(endpoint);
4159
4496
  let api = stored ? new ApiClient(endpoint, stored.access_token) : null;
4160
- const storedCheck = api ? await api.verifyDetailed() : null;
4497
+ let storedCheck = null;
4498
+ if (api) {
4499
+ const connecting = startLoader("Connecting to Standard Agents");
4500
+ storedCheck = await api.verifyDetailed();
4501
+ connecting.stop();
4502
+ }
4161
4503
  if (!api || !storedCheck?.ok) {
4162
4504
  const host = endpoint.replace(/^https?:\/\//, "").replace(/\/$/, "");
4163
4505
  if (storedCheck && !storedCheck.ok) {
@@ -4199,7 +4541,9 @@ ${c.dim}Press Control-C again to exit${c.reset}
4199
4541
  });
4200
4542
  if (!got) continue;
4201
4543
  api = new ApiClient(endpoint, got);
4544
+ const connecting2 = startLoader("Connecting to Standard Agents");
4202
4545
  const check2 = await api.verifyDetailed();
4546
+ connecting2.stop();
4203
4547
  if (check2.ok) {
4204
4548
  saveCredential(
4205
4549
  { endpoint, access_token: got, token_type: "Bearer", saved_at: Date.now() },
@@ -4213,7 +4557,9 @@ ${c.dim}Press Control-C again to exit${c.reset}
4213
4557
  continue;
4214
4558
  }
4215
4559
  api = new ApiClient(endpoint, token);
4560
+ const connecting = startLoader("Connecting to Standard Agents");
4216
4561
  const check = await api.verifyDetailed();
4562
+ connecting.stop();
4217
4563
  if (check.ok) {
4218
4564
  saveCredential(
4219
4565
  { endpoint, access_token: token, token_type: "Bearer", saved_at: Date.now() },
@@ -4232,18 +4578,20 @@ ${c.dim}Press Control-C again to exit${c.reset}
4232
4578
  handoffClosing = true;
4233
4579
  reader.rl?.close();
4234
4580
  const tags = [`path:${projectDir}`, `machine:${machine}`];
4581
+ const loadingSessions = startLoader("Loading sessions");
4235
4582
  let existing = [];
4236
4583
  try {
4237
4584
  existing = await api.listThreads(AGENT_ID_VARIANTS, tags);
4238
4585
  } catch {
4239
4586
  existing = [];
4240
4587
  }
4588
+ const summaries = existing.length > 0 ? await summarizeThreads(api, existing.slice(0, 8)) : [];
4589
+ loadingSessions.stop();
4241
4590
  const tui = new Tui(1);
4242
4591
  let threadId;
4243
4592
  let resumed = false;
4244
4593
  let historySeed;
4245
4594
  if (existing.length > 0) {
4246
- const summaries = await summarizeThreads(api, existing.slice(0, 8));
4247
4595
  const items = summaries.map((s) => ({
4248
4596
  label: s.label,
4249
4597
  hint: s.hint,
@@ -4409,6 +4757,7 @@ async function runInteractive(tui, api, threadId, projectDir, machine, resumed,
4409
4757
  saveApprovals(api, threadId, perm);
4410
4758
  void api.kvSet(threadId, "session_info", { cwd: projectDir, machine }).catch(() => {
4411
4759
  });
4760
+ const attaching = startLoader("Attaching to thread");
4412
4761
  let busy = false;
4413
4762
  let interrupting = false;
4414
4763
  const queued = [];
@@ -4502,7 +4851,8 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
4502
4851
  for (const s of subs) {
4503
4852
  const status = (s.status || "").trim();
4504
4853
  if (status === "idle" || status === "terminated") continue;
4505
- const detail = status && status !== "running" ? ` \u2014 ${status.slice(0, 80)}` : "";
4854
+ const oneLineStatus = status.replace(/\s+/g, " ");
4855
+ const detail = oneLineStatus && oneLineStatus !== "running" ? ` \u2014 ${oneLineStatus.slice(0, 80)}` : "";
4506
4856
  activeSubagents.set(s.id, {
4507
4857
  label: `${subagentLabel(s, agentTitles)}${detail}`,
4508
4858
  agentName: s.agent_name ?? void 0
@@ -4599,7 +4949,8 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
4599
4949
  return mcpCtl.connect(cfg);
4600
4950
  },
4601
4951
  // Seed the request into the main chat — the agent researches + installs it
4602
- // there (using research_agent + install_mcp), visible in the transcript.
4952
+ // there (using research_agent + the mcp tool's install action), visible in
4953
+ // the transcript.
4603
4954
  requestInstall: (query) => {
4604
4955
  void sendNow(
4605
4956
  `Install an MCP server for me: ${query}. Research the best one and its exact launch command, then install it.`
@@ -4649,10 +5000,11 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
4649
5000
  setEnabled: (name, enabled) => api.setSkillEnabled(name, enabled),
4650
5001
  remove: (name) => api.removeSkill(name),
4651
5002
  // Seed the request into the main chat — the agent researches or authors
4652
- // the skill there (research_agent + install_skill), visible in the transcript.
5003
+ // the skill there (research_agent + the skill tool's install action),
5004
+ // visible in the transcript.
4653
5005
  requestInstall: (query) => {
4654
5006
  void sendNow(
4655
- `Install a skill for me: ${query}. Find the skill's published files (or author a proper SKILL.md from your research), install it with install_skill, then tell me what it can do.`
5007
+ `Install a skill for me: ${query}. Find the skill's published files (or author a proper SKILL.md from your research), install it, then tell me what it can do.`
4656
5008
  );
4657
5009
  }
4658
5010
  };
@@ -4696,6 +5048,7 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
4696
5048
  },
4697
5049
  { name: "background", label: "Background processes", hint: "list / stop", run: () => runProcessMenu(tui, bgMgr) },
4698
5050
  { name: "keybindings", label: "Keyboard shortcuts", run: () => showKeybindings(tui) },
5051
+ { name: "update", label: "Check for updates", hint: "check npm for a newer version", run: () => runUpdateCommand(tui) },
4699
5052
  { name: "logout", label: "Sign out", hint: "delete the saved token & quit", run: () => logout() },
4700
5053
  { name: "quit", label: "Quit", run: () => quit() }
4701
5054
  ]);
@@ -4751,6 +5104,7 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
4751
5104
  await Promise.all([bridge.connect(), stream.connect()]);
4752
5105
  void api.getGoal(threadId).then((g) => tui.setGoal(g)).catch(() => {
4753
5106
  });
5107
+ attaching.stop();
4754
5108
  tui.banner([
4755
5109
  `${c.bold}${c.magenta}Standard Code${c.reset} ${c.dim}\u2014 coding agent${c.reset}`,
4756
5110
  `${c.gray}project:${c.reset} ${projectDir}`,
@@ -4951,6 +5305,36 @@ function showKeybindings(tui) {
4951
5305
  tui.print(`${c.gray} \u2190${c.reset} from the start of the input: select the [\u2699 n bg] badge (enter opens it)`);
4952
5306
  tui.print(`${c.gray} ctrl-c${c.reset} quit`);
4953
5307
  }
5308
+ async function runUpdateCommand(tui) {
5309
+ const version = readVersion();
5310
+ const result = await forceCheckForUpdate(version);
5311
+ if (!result) {
5312
+ tui.print(`${c.green}\u2713${c.reset} ${c.gray}@standardagents/code${c.reset} is up to date (v${version})`);
5313
+ return;
5314
+ }
5315
+ const { latest } = result;
5316
+ tui.print(`
5317
+ ${c.yellow}\u27F3${c.reset} Update available: ${c.gray}v${version}${c.reset} \u2192 ${c.green}v${latest}${c.reset}`);
5318
+ const choice = await tui.select(`Update now with \`npm i -g @standardagents/code@latest\`?`, [
5319
+ { label: "Yes, update now", value: "yes" },
5320
+ { label: "No, skip", value: "no" }
5321
+ ]);
5322
+ if (choice === "yes") {
5323
+ tui.print(` ${c.gray}Running npm i -g @standardagents/code@latest\u2026${c.reset}`);
5324
+ try {
5325
+ const ok = await runNpmUpdate();
5326
+ if (ok) {
5327
+ tui.print(` ${c.green}\u2713${c.reset} Updated to v${latest}. Restart to use the new version.`);
5328
+ } else {
5329
+ tui.print(` ${c.red}\u2717${c.reset} Update failed.`);
5330
+ }
5331
+ } catch (e) {
5332
+ tui.print(` ${c.red}\u2717${c.reset} Update failed: ${e}`);
5333
+ }
5334
+ } else {
5335
+ tui.print(` ${c.gray}Skipped. Run /update later.${c.reset}`);
5336
+ }
5337
+ }
4954
5338
  async function runProcessMenu(tui, bg) {
4955
5339
  const procs = await bg.list();
4956
5340
  if (!procs.length) {