@standardagents/code 0.10.0 → 0.11.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/index.js CHANGED
@@ -356,9 +356,13 @@ var ApiClient = class {
356
356
  ).catch(() => [])
357
357
  )
358
358
  ]);
359
- const arr = pages.flatMap((res) => Array.isArray(res) ? res : res.threads || []);
359
+ const arr = pages.flatMap((res, i) => {
360
+ const list = Array.isArray(res) ? res : res.threads || [];
361
+ return list.map((t) => ({ ...t, agent_id: t.agent_id ?? ids[i] }));
362
+ });
360
363
  return arr.map((t) => ({
361
364
  id: t.id,
365
+ agent_id: t.agent_id,
362
366
  tags: Array.isArray(t.tags) ? t.tags : [],
363
367
  created_at: t.created_at,
364
368
  title: t.title,
@@ -1026,7 +1030,7 @@ function truncateMiddle(text, maxColumns) {
1026
1030
  const headBudget = Math.ceil(available / 2);
1027
1031
  const tailBudget = Math.floor(available / 2);
1028
1032
  const head = [];
1029
- const tail = [];
1033
+ const tail2 = [];
1030
1034
  let headWidth = 0;
1031
1035
  let tailWidth = 0;
1032
1036
  for (let i = 0; i < chars.length; i++) {
@@ -1038,12 +1042,12 @@ function truncateMiddle(text, maxColumns) {
1038
1042
  for (let i = chars.length - 1; i >= head.length; i--) {
1039
1043
  const width = terminalGlyphWidth(chars[i]);
1040
1044
  if (tailWidth + width > tailBudget) break;
1041
- tail.unshift(chars[i]);
1045
+ tail2.unshift(chars[i]);
1042
1046
  tailWidth += width;
1043
1047
  }
1044
1048
  const isSeparator = (ch) => ch === "/" || ch === "\\";
1045
1049
  let headEnd = head.length;
1046
- let tailStart = chars.length - tail.length;
1050
+ let tailStart = chars.length - tail2.length;
1047
1051
  for (let i = headEnd - 1; i >= 0; i--) {
1048
1052
  if (isSeparator(chars[i])) {
1049
1053
  headEnd = i + 1;
@@ -1059,7 +1063,7 @@ function truncateMiddle(text, maxColumns) {
1059
1063
  if (headEnd < tailStart && headEnd > 0 && tailStart < chars.length) {
1060
1064
  return `${chars.slice(0, headEnd).join("")}\u2026${chars.slice(tailStart).join("")}`;
1061
1065
  }
1062
- return `${head.join("")}\u2026${tail.join("")}`;
1066
+ return `${head.join("")}\u2026${tail2.join("")}`;
1063
1067
  }
1064
1068
  function clamp(s, max) {
1065
1069
  return s.length > max ? s.slice(0, Math.max(0, max - 1)) + "\u2026" : s;
@@ -1243,8 +1247,8 @@ var Bridge = class {
1243
1247
  // Durable forwarded calls we've started handling, so a server re-send (after a
1244
1248
  // reconnect) doesn't prompt or run them twice.
1245
1249
  handledDurable = /* @__PURE__ */ new Set();
1246
- // Raw TCP tunnels (Sama One's direct egress): the Worker terminates TLS over
1247
- // these; we only shuttle ciphertext. Bound to this socket, torn down with it.
1250
+ // Raw TCP tunnels: the provider owns TLS over these runtime bytes; this
1251
+ // execution-owner bridge only shuttles ciphertext and tears it down on close.
1248
1252
  tunnels;
1249
1253
  // Durable, exactly-once ledger of completed forwarded calls. Survives a crash
1250
1254
  // AFTER execution but BEFORE delivery: on redelivery we return the cached
@@ -2164,14 +2168,14 @@ ${truncated}`
2164
2168
  }
2165
2169
  if (!pid) return { ok: false, error: "Process failed to start (no pid)." };
2166
2170
  if (earlyExit !== void 0 || !isAlive2(pid)) {
2167
- const tail = await readLogTail(logPath, 15);
2171
+ const tail2 = await readLogTail(logPath, 15);
2168
2172
  const code = earlyExit ?? "unknown";
2169
2173
  return {
2170
2174
  ok: false,
2171
- error: `The process exited immediately (exit code ${code}) \u2014 it did not stay running, so nothing was started or tracked.` + (tail ? `
2175
+ error: `The process exited immediately (exit code ${code}) \u2014 it did not stay running, so nothing was started or tracked.` + (tail2 ? `
2172
2176
 
2173
2177
  Output:
2174
- ${tail}` : " No output was captured.")
2178
+ ${tail2}` : " No output was captured.")
2175
2179
  };
2176
2180
  }
2177
2181
  child.removeListener("exit", onEarlyExit);
@@ -2288,8 +2292,8 @@ ${tail}` : " No output was captured.")
2288
2292
  } catch {
2289
2293
  return { ok: true, result: `(no output captured yet for ${id})` };
2290
2294
  }
2291
- const tail = content.split("\n").slice(-maxLines).join("\n");
2292
- return { ok: true, result: tail || `(no output yet for ${id})` };
2295
+ const tail2 = content.split("\n").slice(-maxLines).join("\n");
2296
+ return { ok: true, result: tail2 || `(no output yet for ${id})` };
2293
2297
  }
2294
2298
  if (action === "stop") {
2295
2299
  if (proc.status !== "running") {
@@ -2673,7 +2677,7 @@ var McpManager = class {
2673
2677
  }
2674
2678
  }
2675
2679
  closeAll() {
2676
- for (const [, c4] of this.clients) c4.close();
2680
+ for (const [, c5] of this.clients) c5.close();
2677
2681
  this.clients.clear();
2678
2682
  }
2679
2683
  get(name) {
@@ -2684,13 +2688,13 @@ var McpManager = class {
2684
2688
  }
2685
2689
  toolCount() {
2686
2690
  let n = 0;
2687
- for (const [, c4] of this.clients) n += c4.tools.length;
2691
+ for (const [, c5] of this.clients) n += c5.tools.length;
2688
2692
  return n;
2689
2693
  }
2690
2694
  /** A JSON-serializable catalog of every connected server for the KV/context. */
2691
2695
  catalog() {
2692
2696
  return {
2693
- servers: Array.from(this.clients.values()).map((c4) => c4.catalogEntry()),
2697
+ servers: Array.from(this.clients.values()).map((c5) => c5.catalogEntry()),
2694
2698
  generatedAt: Date.now()
2695
2699
  };
2696
2700
  }
@@ -2780,9 +2784,9 @@ function flattenContent(content, structured) {
2780
2784
  }
2781
2785
  function flattenResourceContents(contents) {
2782
2786
  const parts = [];
2783
- for (const c4 of contents || []) {
2784
- if (typeof c4.text === "string") parts.push(c4.text);
2785
- else if (typeof c4.blob === "string") parts.push(`[binary resource ${String(c4.uri ?? "")} (${c4.blob.length} b64 chars)]`);
2787
+ for (const c5 of contents || []) {
2788
+ if (typeof c5.text === "string") parts.push(c5.text);
2789
+ else if (typeof c5.blob === "string") parts.push(`[binary resource ${String(c5.uri ?? "")} (${c5.blob.length} b64 chars)]`);
2786
2790
  }
2787
2791
  return parts.join("\n").trim();
2788
2792
  }
@@ -3593,8 +3597,8 @@ function parseStreamingMarkdown(source) {
3593
3597
  let codeEnd = closing?.start ?? source.length;
3594
3598
  if (!closing) {
3595
3599
  const tailStart = Math.max(codeStart, source.lastIndexOf("\n") + 1);
3596
- const tail = source.slice(tailStart);
3597
- const pending = /^ {0,3}(`+|~+)[ \t]*$/.exec(tail);
3600
+ const tail2 = source.slice(tailStart);
3601
+ const pending = /^ {0,3}(`+|~+)[ \t]*$/.exec(tail2);
3598
3602
  if (pending && pending[1][0] === opening.marker && pending[1].length < opening.length) codeEnd = tailStart;
3599
3603
  }
3600
3604
  if (codeEnd > codeStart && source[codeEnd - 1] === "\n") codeEnd--;
@@ -3813,17 +3817,17 @@ function wrapBlock(out, cols2, leadFirst, leadRest, leadWidth, text) {
3813
3817
  function renderTable(rows) {
3814
3818
  const cols2 = Math.max(...rows.map((r) => r.length));
3815
3819
  const widths = [];
3816
- for (let c4 = 0; c4 < cols2; c4++) {
3817
- widths[c4] = Math.max(...rows.map((r) => visibleWidth(inline(r[c4] ?? ""))));
3820
+ for (let c5 = 0; c5 < cols2; c5++) {
3821
+ widths[c5] = Math.max(...rows.map((r) => visibleWidth(inline(r[c5] ?? ""))));
3818
3822
  }
3819
3823
  const sep = `${GRAY} \u2502 ${R}`;
3820
3824
  const out = [];
3821
3825
  rows.forEach((r, ri) => {
3822
3826
  const cells = [];
3823
- for (let c4 = 0; c4 < cols2; c4++) {
3824
- const raw = r[c4] ?? "";
3827
+ for (let c5 = 0; c5 < cols2; c5++) {
3828
+ const raw = r[c5] ?? "";
3825
3829
  const styled = ri === 0 ? `${BOLD}${inline(raw)}${R}` : inline(raw);
3826
- cells.push(padEndVisible(styled, widths[c4]));
3830
+ cells.push(padEndVisible(styled, widths[c5]));
3827
3831
  }
3828
3832
  out.push((" " + cells.join(sep)).replace(/\s+$/, ""));
3829
3833
  if (ri === 0) {
@@ -4260,7 +4264,11 @@ function buildInputBoxRows(opts) {
4260
4264
  if (phase == null || !Number.isFinite(phase)) {
4261
4265
  const dots2 = borderLevelDots(level);
4262
4266
  const dotsStyled2 = borderLevelDotsStyled(level, levelColor);
4263
- const top2 = buildBoxTop(geo.boxW, dots2, borderColor, levelColor, "\x1B[0m", dotsStyled2);
4267
+ const agent2 = (opts.agentLabel ?? "").trim();
4268
+ const withAgent2 = agent2.length > 0 && ` ${agent2} ${dots2} `.length <= geo.boxW - 3;
4269
+ const levelText = withAgent2 ? `${agent2} ${dots2}` : dots2;
4270
+ const levelStyled = withAgent2 ? `\x1B[2m${themeGray}${agent2}\x1B[0m ${dotsStyled2}` : dotsStyled2;
4271
+ const top2 = buildBoxTop(geo.boxW, levelText, borderColor, levelColor, "\x1B[0m", levelStyled);
4264
4272
  const bottom2 = buildBoxBottom(geo.boxW, borderColor);
4265
4273
  return [pad + top2, ...body.map((b) => pad + buildBoxBody(b, geo.contentW, borderColor)), pad + bottom2];
4266
4274
  }
@@ -4271,7 +4279,11 @@ function buildInputBoxRows(opts) {
4271
4279
  const colorAt = (i) => rotatingBorderCellColor(i, P, phase);
4272
4280
  const dots = borderLevelDots(level);
4273
4281
  borderLevelDotsStyled(level, levelColor);
4274
- const labelPlain = ` ${dots} `;
4282
+ const agent = (opts.agentLabel ?? "").trim();
4283
+ const withAgent = agent.length > 0 && ` ${agent} ${dots} `.length <= W - 3;
4284
+ const labelPlain = withAgent ? ` ${agent} ${dots} ` : ` ${dots} `;
4285
+ const agentSpanStart = withAgent ? 1 : -1;
4286
+ const agentSpanEnd = withAgent ? 1 + agent.length : -1;
4275
4287
  const rightFillN = 1;
4276
4288
  const budget = Math.max(1, W - 2 - rightFillN);
4277
4289
  const plain = labelPlain.length > budget ? labelPlain.slice(0, Math.max(1, budget)) : labelPlain;
@@ -4285,15 +4297,20 @@ function buildInputBoxRows(opts) {
4285
4297
  {
4286
4298
  const plainChars = [...plain];
4287
4299
  let di = 0;
4288
- for (const ch of plainChars) {
4289
- const c4 = colorAt(perimeterIndex("top", xi++, W, bodyH));
4300
+ for (let ci = 0; ci < plainChars.length; ci++) {
4301
+ const ch = plainChars[ci];
4302
+ const c5 = colorAt(perimeterIndex("top", xi++, W, bodyH));
4303
+ if (ci >= agentSpanStart && ci < agentSpanEnd) {
4304
+ top += `\x1B[2m${themeGray}${ch}\x1B[0m`;
4305
+ continue;
4306
+ }
4290
4307
  if (ch === "\u25CF" || ch === "\u25CB") {
4291
4308
  const levelN = Math.max(1, Math.min(5, level));
4292
4309
  const filled = di < levelN;
4293
4310
  di++;
4294
- top += (filled ? levelColor || c4 : "\x1B[38;5;240m") + (filled ? "\u25CF" : "\u25CB") + reset;
4311
+ top += (filled ? levelColor || c5 : "\x1B[38;5;240m") + (filled ? "\u25CF" : "\u25CB") + reset;
4295
4312
  } else {
4296
- top += c4 + ch + reset;
4313
+ top += c5 + ch + reset;
4297
4314
  }
4298
4315
  }
4299
4316
  }
@@ -4314,10 +4331,10 @@ function buildInputBoxRows(opts) {
4314
4331
  let bottom = "";
4315
4332
  for (let x = 0; x < W; x++) {
4316
4333
  const idx = perimeterIndex("bottom", W - 1 - x, W, bodyH);
4317
- const c4 = colorAt(idx);
4318
- if (x === 0) bottom += c4 + "\u2570" + reset;
4319
- else if (x === W - 1) bottom += c4 + "\u256F" + reset;
4320
- else bottom += c4 + "\u2500" + reset;
4334
+ const c5 = colorAt(idx);
4335
+ if (x === 0) bottom += c5 + "\u2570" + reset;
4336
+ else if (x === W - 1) bottom += c5 + "\u256F" + reset;
4337
+ else bottom += c5 + "\u2500" + reset;
4321
4338
  }
4322
4339
  return [pad + top, ...bodyRows, pad + bottom];
4323
4340
  }
@@ -4334,6 +4351,36 @@ var C = {
4334
4351
  gray: themeGray,
4335
4352
  teal: "\x1B[38;5;37m"
4336
4353
  };
4354
+ function renderSelectItemRows(it, sel, contentW, busyGlyph) {
4355
+ const hint = it.hint ?? "";
4356
+ const hintW = boxVisibleWidth(hint);
4357
+ const pointerW = 2;
4358
+ const labelMax = Math.max(4, contentW - pointerW - (hintW ? hintW + 2 : 0));
4359
+ let label = it.label.replace(/\s+/g, " ").trim();
4360
+ if (boxVisibleWidth(label) > labelMax) {
4361
+ while (label.length > 0 && boxVisibleWidth(label) > labelMax - 1) label = label.slice(0, -1);
4362
+ label += "\u2026";
4363
+ }
4364
+ const pointer = busyGlyph ? `${busyGlyph} ` : sel ? `${C.magenta}\u276F${C.reset} ` : " ";
4365
+ const styledLabel = it.disabled ? `${C.gray}${label}${C.reset}` : sel ? `${C.bold}${C.cyan}${label}${C.reset}` : `${C.dim}${label}${C.reset}`;
4366
+ let content = `${pointer}${styledLabel}`;
4367
+ if (hintW) {
4368
+ const used = pointerW + boxVisibleWidth(label);
4369
+ const gap = Math.max(1, contentW - used - hintW);
4370
+ content += `${" ".repeat(gap)}${sel ? C.gray : C.dim}${hint}${C.reset}`;
4371
+ }
4372
+ const rows = [content];
4373
+ if (it.detail) {
4374
+ let detail = it.detail.replace(/\s+/g, " ").trim();
4375
+ const detailMax = Math.max(4, contentW - pointerW - 1);
4376
+ if (boxVisibleWidth(detail) > detailMax) {
4377
+ while (detail.length > 0 && boxVisibleWidth(detail) > detailMax - 1) detail = detail.slice(0, -1);
4378
+ detail += "\u2026";
4379
+ }
4380
+ rows.push(`${" ".repeat(pointerW)}${sel ? `${C.dim}${C.cyan}` : `${C.dim}${C.gray}`}${detail}${C.reset}`);
4381
+ }
4382
+ return rows;
4383
+ }
4337
4384
  var SYNC_OUTPUT_BEGIN = "\x1B[?2026h";
4338
4385
  var SYNC_OUTPUT_END = "\x1B[?2026l";
4339
4386
  var CURSOR_HIDE = "\x1B[?25l";
@@ -4486,7 +4533,7 @@ var Tui = class _Tui {
4486
4533
  // event hooks (wired by index.ts)
4487
4534
  onSubmit = () => {
4488
4535
  };
4489
- /** Shift+Return sends a steering input through the portable messaging endpoint. */
4536
+ /** Option/Alt+Return sends a steering input through the portable messaging endpoint. */
4490
4537
  onSteer = () => {
4491
4538
  };
4492
4539
  onInterrupt = () => {
@@ -4584,7 +4631,7 @@ var Tui = class _Tui {
4584
4631
  dispatch(str, key) {
4585
4632
  const seq = key && key.sequence || str || "";
4586
4633
  if (seq === "\n" || seq === "\x1B[13;2u" || seq === "\x1B[27;2;13~") {
4587
- if (!this.takeoverHandler && !this.pasting && !this.paletteOpen()) this.submitInput(true);
4634
+ if (!this.takeoverHandler && !this.pasting && !this.paletteOpen()) this.insertAtCursor("\n");
4588
4635
  return;
4589
4636
  }
4590
4637
  if (key && key.ctrl && key.name === "c") {
@@ -4717,11 +4764,11 @@ var Tui = class _Tui {
4717
4764
  }
4718
4765
  if (key.name === "return" || key.name === "enter") {
4719
4766
  if (key.meta) {
4720
- this.insertAtCursor("\n");
4767
+ this.submitInput(true);
4721
4768
  return;
4722
4769
  }
4723
4770
  if (key.shift) {
4724
- this.submitInput(true);
4771
+ this.insertAtCursor("\n");
4725
4772
  return;
4726
4773
  }
4727
4774
  this.submitInput(false);
@@ -5022,7 +5069,7 @@ var Tui = class _Tui {
5022
5069
  const q = this.inputBuffer.slice(1).trim().toLowerCase();
5023
5070
  if (q === "") return this.commands;
5024
5071
  return this.commands.filter(
5025
- (c4) => c4.name.startsWith(q) || c4.name.includes(q) || c4.label.toLowerCase().includes(q)
5072
+ (c5) => c5.name.startsWith(q) || c5.name.includes(q) || c5.label.toLowerCase().includes(q)
5026
5073
  );
5027
5074
  }
5028
5075
  runCommand(cmd) {
@@ -5276,7 +5323,8 @@ var Tui = class _Tui {
5276
5323
  level: this.level,
5277
5324
  levelColor: this.levelColor(),
5278
5325
  borderColor: inputBoxBorderColor(),
5279
- borderPhase
5326
+ borderPhase,
5327
+ agentLabel: this.agentLabel
5280
5328
  });
5281
5329
  const boxTop = boxRows[0];
5282
5330
  const boxBottom = boxRows[boxRows.length - 1];
@@ -5550,6 +5598,14 @@ var Tui = class _Tui {
5550
5598
  });
5551
5599
  this.print("");
5552
5600
  }
5601
+ // ─── agent label (under the input box) ─────────────────────────────────────
5602
+ /** The loaded agent's display title, shown low-contrast under the input. */
5603
+ agentLabel = "";
5604
+ setAgentLabel(label) {
5605
+ if (label === this.agentLabel) return;
5606
+ this.agentLabel = label;
5607
+ this.renderBottom();
5608
+ }
5553
5609
  // ─── working indicator (turn state) ───────────────────────────────────────
5554
5610
  setWorking(on) {
5555
5611
  if (on === this.working) return;
@@ -5930,8 +5986,13 @@ var Tui = class _Tui {
5930
5986
  * Arrow-key selection menu (slash menu, process menu, resume). Pauses input.
5931
5987
  * Boxed chrome matches the bottom input HUD: side margin + grey rounded
5932
5988
  * border, selected row with a brand-tinted pointer.
5989
+ *
5990
+ * An item may carry a `detail` — a dim second physical line under its label
5991
+ * for context that doesn't fit the one-row label+hint layout (e.g. a
5992
+ * machine's hostname · daemon status). `opts.spaced` inserts a blank row
5993
+ * between items for readability in short, dense menus.
5933
5994
  */
5934
- select(title, items) {
5995
+ select(title, items, opts) {
5935
5996
  return new Promise((resolve) => {
5936
5997
  let idx = Math.max(0, items.findIndex((it) => !it.disabled));
5937
5998
  this.beginTakeover();
@@ -5944,41 +6005,28 @@ var Tui = class _Tui {
5944
6005
  ${pad}${title}
5945
6006
  `);
5946
6007
  else process.stdout.write("\n");
5947
- const renderItemContent = (i) => {
5948
- const it = items[i];
5949
- const sel = i === idx;
5950
- const hint = it.hint ?? "";
5951
- const hintW = hint.length;
5952
- const pointerW = 2;
5953
- const labelMax = Math.max(4, geo.contentW - pointerW - (hintW ? hintW + 2 : 0));
5954
- let label = it.label.replace(/\s+/g, " ").trim();
5955
- if (label.length > labelMax) label = label.slice(0, Math.max(0, labelMax - 1)) + "\u2026";
5956
- const pointer = sel ? `${C.magenta}\u276F${C.reset} ` : " ";
5957
- const styledLabel = it.disabled ? `${C.gray}${label}${C.reset}` : sel ? `${C.bold}${C.cyan}${label}${C.reset}` : `${C.dim}${label}${C.reset}`;
5958
- let content = `${pointer}${styledLabel}`;
5959
- if (hintW) {
5960
- const used = pointerW + label.length;
5961
- const gap = Math.max(1, geo.contentW - used - hintW);
5962
- content += `${" ".repeat(gap)}${sel ? C.gray : C.dim}${hint}${C.reset}`;
5963
- }
5964
- return content;
5965
- };
6008
+ const renderItemRows = (i) => renderSelectItemRows(items[i], i === idx, geo.contentW);
5966
6009
  const writeRow = (line) => {
5967
6010
  const row = this.clampVisible(sanitizeHudRow(line), rowCap);
5968
6011
  process.stdout.write(`\r\x1B[K${row}
5969
6012
  `);
5970
6013
  };
6014
+ const spaced = opts?.spaced ?? false;
6015
+ const contentRows = items.reduce((n, it) => n + 1 + (it.detail ? 1 : 0), 0) + (spaced ? Math.max(0, items.length - 1) : 0);
5971
6016
  const draw = (moveUp) => {
5972
- if (moveUp) process.stdout.write(`\x1B[${items.length + 1}A`);
6017
+ if (moveUp) process.stdout.write(`\x1B[${contentRows + 1}A`);
5973
6018
  else writeRow(pad + buildBoxTopPlain(geo.boxW, border));
5974
6019
  for (let i = 0; i < items.length; i++) {
5975
- writeRow(pad + buildBoxBody(renderItemContent(i), geo.contentW, border));
6020
+ if (spaced && i > 0) writeRow(pad + buildBoxBody("", geo.contentW, border));
6021
+ for (const row of renderItemRows(i)) {
6022
+ writeRow(pad + buildBoxBody(row, geo.contentW, border));
6023
+ }
5976
6024
  }
5977
6025
  writeRow(pad + buildBoxBottom(geo.boxW, border));
5978
6026
  };
5979
6027
  draw(false);
5980
6028
  const titleRows = title ? 2 : 1;
5981
- const boxRows = 1 + items.length + 1;
6029
+ const boxRows = 1 + contentRows + 1;
5982
6030
  const erase = () => {
5983
6031
  process.stdout.write("\r");
5984
6032
  const up = titleRows + boxRows;
@@ -6006,6 +6054,182 @@ ${pad}${title}
6006
6054
  };
6007
6055
  });
6008
6056
  }
6057
+ /**
6058
+ * A select menu that stays OPEN while its contents change — the primitive
6059
+ * behind in-place navigation UIs like the directory browser. Instead of
6060
+ * resolving once like `select`, it returns a handle:
6061
+ *
6062
+ * - `next()` awaits the next interaction: a pick, a registered extra key
6063
+ * (e.g. "." to toggle hidden files), or Escape (cancel).
6064
+ * - `update()` swaps title/items/footer and redraws the SAME screen region
6065
+ * (erase + repaint in one write), so navigating levels never feels like
6066
+ * leaving the menu.
6067
+ * - `setBusy(i)` animates a spinner in row i's pointer column while the
6068
+ * caller loads what's behind it; input (except Escape) is swallowed until
6069
+ * the next update()/setBusy(null).
6070
+ * - `close()` erases the region and releases the takeover.
6071
+ *
6072
+ * The optional `footer` is a dim line under the box — the home for key
6073
+ * hints and transient error text.
6074
+ */
6075
+ openLiveSelect(opts) {
6076
+ let { title, items } = opts;
6077
+ let footer = opts.footer ?? "";
6078
+ const spaced = opts.spaced ?? false;
6079
+ const extraKeys = opts.keys ?? [];
6080
+ let idx = Math.max(0, items.findIndex((it) => !it.disabled));
6081
+ let busyIdx = null;
6082
+ let busyTimer = null;
6083
+ let closed = false;
6084
+ let edit = null;
6085
+ this.beginTakeover();
6086
+ const cols2 = process.stdout.columns || 80;
6087
+ const geo = inputBoxGeometry(cols2);
6088
+ const border = inputBoxBorderColor();
6089
+ const pad = " ".repeat(geo.margin);
6090
+ const rowCap = Math.max(1, cols2 - 1);
6091
+ let regionRows = 0;
6092
+ let lifted = 0;
6093
+ const draw = () => {
6094
+ if (closed) return;
6095
+ const rows = [""];
6096
+ let editCursor = null;
6097
+ if (title) rows.push(pad + title);
6098
+ rows.push(pad + buildBoxTopPlain(geo.boxW, border));
6099
+ for (let i = 0; i < items.length; i++) {
6100
+ const it = items[i];
6101
+ if (spaced && i > 0) rows.push(pad + buildBoxBody("", geo.contentW, border));
6102
+ const glyph = busyIdx === i ? this.spinnerFrame() : void 0;
6103
+ if (edit && edit.index === i) {
6104
+ const pointer = `${glyph ?? `${C.magenta}\u276F${C.reset}`} `;
6105
+ const max = Math.max(4, geo.contentW - 2 - boxVisibleWidth(edit.prefix) - 1);
6106
+ let buf = edit.buf;
6107
+ while (buf.length > 0 && boxVisibleWidth(buf) > max) buf = buf.slice(1);
6108
+ const body = buf ? `${C.bold}${C.cyan}${buf}${C.reset}` : `${C.dim}${edit.placeholder}${C.reset}`;
6109
+ rows.push(pad + buildBoxBody(`${pointer}${C.dim}${edit.prefix}${C.reset}${body}`, geo.contentW, border));
6110
+ if (!glyph) {
6111
+ editCursor = {
6112
+ rowIndex: rows.length - 1,
6113
+ // 1-based ANSI column: margin + "│ " + pointer(2) + prefix + typed text.
6114
+ col: geo.margin + 2 + 2 + boxVisibleWidth(edit.prefix) + boxVisibleWidth(buf) + 1
6115
+ };
6116
+ }
6117
+ continue;
6118
+ }
6119
+ for (const r of renderSelectItemRows(it, i === idx, geo.contentW, glyph)) {
6120
+ rows.push(pad + buildBoxBody(r, geo.contentW, border));
6121
+ }
6122
+ }
6123
+ rows.push(pad + buildBoxBottom(geo.boxW, border));
6124
+ if (footer) rows.push(pad + footer);
6125
+ let out = lifted > 0 ? `\x1B[${lifted}B` : "";
6126
+ lifted = 0;
6127
+ out += regionRows > 0 ? `\r\x1B[${regionRows}A\x1B[J` : "";
6128
+ for (const line of rows) out += `\r\x1B[K${this.clampVisible(sanitizeHudRow(line), rowCap)}
6129
+ `;
6130
+ if (editCursor) {
6131
+ lifted = rows.length - editCursor.rowIndex;
6132
+ out += `\x1B[${lifted}A\x1B[${editCursor.col}G\x1B[?25h`;
6133
+ } else {
6134
+ out += "\x1B[?25l";
6135
+ }
6136
+ process.stdout.write(out);
6137
+ regionRows = rows.length;
6138
+ };
6139
+ draw();
6140
+ const queue = [];
6141
+ let waiter = null;
6142
+ const emit = (e) => {
6143
+ if (waiter) {
6144
+ const w = waiter;
6145
+ waiter = null;
6146
+ w(e);
6147
+ } else queue.push(e);
6148
+ };
6149
+ const stopSpinner = () => {
6150
+ if (busyTimer) clearInterval(busyTimer);
6151
+ busyTimer = null;
6152
+ busyIdx = null;
6153
+ };
6154
+ this.takeoverHandler = (str, key) => {
6155
+ if (edit && busyIdx === null) {
6156
+ if (key?.name === "escape") {
6157
+ edit = null;
6158
+ draw();
6159
+ } else if (key?.name === "return" || key?.name === "enter") {
6160
+ const text = edit.buf.trim();
6161
+ if (!text) {
6162
+ edit = null;
6163
+ draw();
6164
+ } else {
6165
+ emit({ kind: "edit", text, index: edit.index });
6166
+ }
6167
+ } else if (key?.name === "backspace") {
6168
+ edit.buf = edit.buf.slice(0, -1);
6169
+ draw();
6170
+ } else if (str && !key?.ctrl && !key?.meta) {
6171
+ const clean2 = str.replace(/[\x00-\x1f\x7f]/g, "");
6172
+ if (clean2) {
6173
+ edit.buf += clean2;
6174
+ draw();
6175
+ }
6176
+ }
6177
+ return;
6178
+ }
6179
+ if (key?.name === "escape") return emit({ kind: "cancel" });
6180
+ if (busyIdx !== null) return;
6181
+ if (key?.name === "up" || str === "k") {
6182
+ idx = (idx - 1 + items.length) % items.length;
6183
+ draw();
6184
+ } else if (key?.name === "down" || str === "j") {
6185
+ idx = (idx + 1) % items.length;
6186
+ draw();
6187
+ } else if (key?.name === "return" || key?.name === "enter") {
6188
+ if (!items[idx]?.disabled) emit({ kind: "pick", value: items[idx].value, index: idx });
6189
+ } else if (str && extraKeys.includes(str)) {
6190
+ emit({ kind: "key", name: str, index: idx });
6191
+ }
6192
+ };
6193
+ return {
6194
+ next: () => queue.length > 0 ? Promise.resolve(queue.shift()) : new Promise((r) => waiter = r),
6195
+ update: (state) => {
6196
+ stopSpinner();
6197
+ edit = null;
6198
+ if (state.title !== void 0) title = state.title;
6199
+ if (state.items) {
6200
+ items = state.items;
6201
+ if (idx >= items.length || items[idx]?.disabled) {
6202
+ idx = Math.max(0, items.findIndex((it) => !it.disabled));
6203
+ }
6204
+ }
6205
+ if (state.footer !== void 0) footer = state.footer;
6206
+ draw();
6207
+ },
6208
+ setBusy: (index) => {
6209
+ stopSpinner();
6210
+ if (index !== null) {
6211
+ busyIdx = index;
6212
+ busyTimer = setInterval(draw, SPINNER_FRAME_MS);
6213
+ }
6214
+ draw();
6215
+ },
6216
+ beginEdit: (index, editOpts) => {
6217
+ stopSpinner();
6218
+ edit = { index, buf: "", placeholder: editOpts?.placeholder ?? "", prefix: editOpts?.prefix ?? "" };
6219
+ idx = index;
6220
+ draw();
6221
+ },
6222
+ close: () => {
6223
+ if (closed) return;
6224
+ closed = true;
6225
+ stopSpinner();
6226
+ if (lifted > 0) process.stdout.write(`\x1B[${lifted}B`);
6227
+ lifted = 0;
6228
+ if (regionRows > 0) process.stdout.write(`\r\x1B[${regionRows}A\x1B[J`);
6229
+ this.endTakeover();
6230
+ }
6231
+ };
6232
+ }
6009
6233
  /**
6010
6234
  * Free-text prompt (single line). Pauses the main input and reads a line —
6011
6235
  * used where a menu can't, e.g. entering an MCP server command. Enter submits,
@@ -6257,6 +6481,7 @@ var NAME_SUFFIX = ".name";
6257
6481
  var ICON_SUFFIX = ".icon";
6258
6482
  var FSREQ_SUFFIX = ".fsreq";
6259
6483
  var FSRES_SUFFIX = ".fsres";
6484
+ var PROJNAMES_SUFFIX = ".projnames";
6260
6485
  var DAEMON_ONLINE_WINDOW_MS = 90 * 1e3;
6261
6486
  function machineKey(machineId) {
6262
6487
  return `${KEY_PREFIX}${machineId}`;
@@ -6270,6 +6495,9 @@ function nameKey(machineId) {
6270
6495
  function iconKey(machineId) {
6271
6496
  return `${KEY_PREFIX}${machineId}${ICON_SUFFIX}`;
6272
6497
  }
6498
+ function projectNamesKey(machineId) {
6499
+ return `${KEY_PREFIX}${machineId}${PROJNAMES_SUFFIX}`;
6500
+ }
6273
6501
  function fsRequestKey(machineId) {
6274
6502
  return `${KEY_PREFIX}${machineId}${FSREQ_SUFFIX}`;
6275
6503
  }
@@ -6312,25 +6540,6 @@ async function readFsRequest(api, machineId) {
6312
6540
  async function writeFsResponse(api, machineId, res) {
6313
6541
  await api.userKvSet(fsResponseKey(machineId), { ...res, responded_at: Date.now() });
6314
6542
  }
6315
- async function resolveRemoteProjectPath(api, machineId, typedPath, timeoutMs = 12e3) {
6316
- const trimmed = typedPath.trim();
6317
- if (!trimmed.startsWith("~")) return trimmed;
6318
- const nonce = crypto.randomBytes(8).toString("hex");
6319
- try {
6320
- await writeFsRequest(api, machineId, { nonce, op: "list", path: trimmed });
6321
- const deadline = Date.now() + timeoutMs;
6322
- while (Date.now() < deadline) {
6323
- await new Promise((r) => setTimeout(r, 700));
6324
- const res = await readFsResponse(api, machineId).catch(() => null);
6325
- if (res && res.nonce === nonce) {
6326
- const resolved = res.result && typeof res.result.path === "string" ? res.result.path : "";
6327
- return resolved || trimmed;
6328
- }
6329
- }
6330
- } catch {
6331
- }
6332
- return trimmed;
6333
- }
6334
6543
  function machineIcon(record2) {
6335
6544
  if (record2.icon && record2.icon.trim()) return record2.icon.trim();
6336
6545
  if (record2.platform === "darwin") return "\u{1F4BB}";
@@ -6360,7 +6569,10 @@ function parseMachineRecord(value) {
6360
6569
  arch: typeof r.arch === "string" ? r.arch : "",
6361
6570
  version: typeof r.version === "string" ? r.version : void 0,
6362
6571
  daemon: r.daemon && typeof r.daemon === "object" && !Array.isArray(r.daemon) ? r.daemon : null,
6363
- projects: r.projects && typeof r.projects === "object" && !Array.isArray(r.projects) ? r.projects : {},
6572
+ // Clone rather than alias the caller's object: read-side overlays (e.g.
6573
+ // user-assigned project names) mutate the parsed record's entries and
6574
+ // must never write through into the source value.
6575
+ projects: r.projects && typeof r.projects === "object" && !Array.isArray(r.projects) ? structuredClone(r.projects) : {},
6364
6576
  created_at: typeof r.created_at === "number" ? r.created_at : 0,
6365
6577
  updated_at: typeof r.updated_at === "number" ? r.updated_at : 0
6366
6578
  };
@@ -6373,6 +6585,7 @@ async function loadMachines(api) {
6373
6585
  const records = [];
6374
6586
  const names = /* @__PURE__ */ new Map();
6375
6587
  const icons = /* @__PURE__ */ new Map();
6588
+ const projNames = /* @__PURE__ */ new Map();
6376
6589
  for (const e of entries) {
6377
6590
  const rest = e.key.slice(KEY_PREFIX.length);
6378
6591
  if (rest.endsWith(CMD_SUFFIX) || rest.endsWith(FSREQ_SUFFIX) || rest.endsWith(FSRES_SUFFIX)) continue;
@@ -6386,6 +6599,12 @@ async function loadMachines(api) {
6386
6599
  if (typeof e.value === "string" && e.value.trim()) icons.set(id, e.value.trim());
6387
6600
  continue;
6388
6601
  }
6602
+ if (rest.endsWith(PROJNAMES_SUFFIX)) {
6603
+ const id = rest.slice(0, -PROJNAMES_SUFFIX.length);
6604
+ const map = parseProjectNames(e.value);
6605
+ if (map) projNames.set(id, map);
6606
+ continue;
6607
+ }
6389
6608
  const rec = parseMachineRecord(e.value);
6390
6609
  if (rec) records.push(rec);
6391
6610
  }
@@ -6394,9 +6613,58 @@ async function loadMachines(api) {
6394
6613
  if (override) rec.name = override;
6395
6614
  const icon = icons.get(rec.id);
6396
6615
  if (icon) rec.icon = icon;
6616
+ overlayProjectNames(rec, projNames.get(rec.id));
6397
6617
  }
6398
6618
  return records;
6399
6619
  }
6620
+ function parseProjectNames(value) {
6621
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
6622
+ const out = {};
6623
+ for (const [dir2, name] of Object.entries(value)) {
6624
+ if (typeof name === "string" && name.trim()) out[dir2] = name.trim();
6625
+ }
6626
+ return out;
6627
+ }
6628
+ function overlayProjectNames(record2, names) {
6629
+ if (!names) return;
6630
+ for (const [dir2, name] of Object.entries(names)) {
6631
+ const project = record2.projects[dir2];
6632
+ if (project) project.name = name;
6633
+ }
6634
+ }
6635
+ function projectDisplayName(dir2, project) {
6636
+ return project?.name?.trim() || path4.basename(dir2) || dir2;
6637
+ }
6638
+ function projectDisplayLabels(projects) {
6639
+ const names = /* @__PURE__ */ new Map();
6640
+ const counts = /* @__PURE__ */ new Map();
6641
+ for (const [dir2, p] of Object.entries(projects)) {
6642
+ const n = projectDisplayName(dir2, p);
6643
+ names.set(dir2, n);
6644
+ counts.set(n, (counts.get(n) ?? 0) + 1);
6645
+ }
6646
+ const out = /* @__PURE__ */ new Map();
6647
+ for (const [dir2, n] of names) {
6648
+ if ((counts.get(n) ?? 0) > 1) {
6649
+ const parts = dir2.split(/[\\/]/).filter(Boolean);
6650
+ const parent = parts.length > 1 ? parts[parts.length - 2] : "";
6651
+ out.set(dir2, parent ? `${parent}/${n}` : n);
6652
+ } else {
6653
+ out.set(dir2, n);
6654
+ }
6655
+ }
6656
+ return out;
6657
+ }
6658
+ async function getProjectNames(api, machineId) {
6659
+ return parseProjectNames(await api.userKvGet(projectNamesKey(machineId))) ?? {};
6660
+ }
6661
+ async function setProjectName(api, machineId, dir2, name) {
6662
+ const map = await getProjectNames(api, machineId);
6663
+ const trimmed = name.trim();
6664
+ if (trimmed) map[dir2] = trimmed;
6665
+ else delete map[dir2];
6666
+ await api.userKvSet(projectNamesKey(machineId), Object.keys(map).length ? map : null);
6667
+ }
6400
6668
  async function getMachineIcon(api, machineId) {
6401
6669
  const v = await api.userKvGet(iconKey(machineId));
6402
6670
  return typeof v === "string" && v.trim() ? v.trim() : null;
@@ -6408,9 +6676,14 @@ async function setMachineIcon(api, machineId, icon) {
6408
6676
  async function loadMachine(api, machineId) {
6409
6677
  const rec = await loadRawMachine(api, machineId);
6410
6678
  if (!rec) return null;
6411
- const [override, icon] = await Promise.all([getMachineName(api, machineId), getMachineIcon(api, machineId)]);
6679
+ const [override, icon, projNames] = await Promise.all([
6680
+ getMachineName(api, machineId),
6681
+ getMachineIcon(api, machineId),
6682
+ getProjectNames(api, machineId)
6683
+ ]);
6412
6684
  if (override) rec.name = override;
6413
6685
  if (icon) rec.icon = icon;
6686
+ overlayProjectNames(rec, projNames);
6414
6687
  return rec;
6415
6688
  }
6416
6689
  function daemonOnline(record2, now = Date.now()) {
@@ -6508,7 +6781,7 @@ async function clearDaemon(api, identity) {
6508
6781
  function parseCommands(value) {
6509
6782
  if (!Array.isArray(value)) return [];
6510
6783
  return value.filter(
6511
- (c4) => !!c4 && typeof c4 === "object" && typeof c4.id === "string" && typeof c4.kind === "string"
6784
+ (c5) => !!c5 && typeof c5 === "object" && typeof c5.id === "string" && typeof c5.kind === "string"
6512
6785
  );
6513
6786
  }
6514
6787
  async function enqueueMachineCommand(api, machineId, kind, args) {
@@ -6531,7 +6804,7 @@ async function clearMachineCommands(api, machineId, appliedIds) {
6531
6804
  if (appliedIds.length === 0) return;
6532
6805
  const key = commandKey(machineId);
6533
6806
  const remaining = parseCommands(await api.userKvGet(key)).filter(
6534
- (c4) => !appliedIds.includes(c4.id)
6807
+ (c5) => !appliedIds.includes(c5.id)
6535
6808
  );
6536
6809
  await api.userKvSet(key, remaining.length ? remaining : null);
6537
6810
  }
@@ -6555,6 +6828,290 @@ async function applyMachineCommand(api, identity, cmd) {
6555
6828
  return `unknown command ${cmd.kind}`;
6556
6829
  }
6557
6830
  }
6831
+ var PROJECT_MARKERS = [
6832
+ ".git",
6833
+ "package.json",
6834
+ "pyproject.toml",
6835
+ "Cargo.toml",
6836
+ "go.mod",
6837
+ "pom.xml",
6838
+ "build.gradle",
6839
+ "Gemfile",
6840
+ "composer.json",
6841
+ "requirements.txt",
6842
+ ".standardagents"
6843
+ ];
6844
+ var MAX_ENTRIES3 = 500;
6845
+ function resolveBrowsePath(input3) {
6846
+ const home = os8.homedir();
6847
+ let p = (input3 ?? "").trim();
6848
+ if (!p) return home;
6849
+ if (p === "~") return home;
6850
+ if (p.startsWith("~/")) p = path4.join(home, p.slice(2));
6851
+ return path4.resolve(p);
6852
+ }
6853
+ function markers(dirPath) {
6854
+ let repo = false;
6855
+ let project = false;
6856
+ for (const marker of PROJECT_MARKERS) {
6857
+ let hit = false;
6858
+ try {
6859
+ hit = fs5.existsSync(path4.join(dirPath, marker));
6860
+ } catch {
6861
+ hit = false;
6862
+ }
6863
+ if (!hit) continue;
6864
+ project = true;
6865
+ if (marker === ".git") repo = true;
6866
+ if (repo) break;
6867
+ }
6868
+ return { project, repo };
6869
+ }
6870
+ function browseDirectory(input3, opts = {}) {
6871
+ const home = os8.homedir();
6872
+ const abs = resolveBrowsePath(input3);
6873
+ const parent = path4.dirname(abs);
6874
+ const base = {
6875
+ path: abs,
6876
+ parent: parent === abs ? null : parent,
6877
+ home
6878
+ };
6879
+ let dirents;
6880
+ try {
6881
+ const stat = fs5.statSync(abs);
6882
+ if (!stat.isDirectory()) {
6883
+ return { ...base, entries: [], truncated: false, error: "Not a directory" };
6884
+ }
6885
+ dirents = fs5.readdirSync(abs, { withFileTypes: true });
6886
+ } catch (err) {
6887
+ const code = err?.code;
6888
+ const message = code === "EACCES" || code === "EPERM" ? "Permission denied" : code === "ENOENT" ? "Folder not found" : "Could not read this folder";
6889
+ return { ...base, entries: [], truncated: false, error: message };
6890
+ }
6891
+ const showHidden = !!opts.showHidden;
6892
+ const rawDirs = [];
6893
+ const rawFiles = [];
6894
+ for (const d of dirents) {
6895
+ const name = d.name;
6896
+ if (!showHidden && name.startsWith(".")) continue;
6897
+ let isDir = d.isDirectory();
6898
+ if (d.isSymbolicLink()) {
6899
+ try {
6900
+ isDir = fs5.statSync(path4.join(abs, name)).isDirectory();
6901
+ } catch {
6902
+ isDir = false;
6903
+ }
6904
+ }
6905
+ if (isDir) {
6906
+ const { project, repo } = markers(path4.join(abs, name));
6907
+ rawDirs.push({ name, dir: true, project: project || void 0, repo: repo || void 0 });
6908
+ } else {
6909
+ rawFiles.push({ name, dir: false });
6910
+ }
6911
+ }
6912
+ const cmp = (a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase());
6913
+ rawDirs.sort(cmp);
6914
+ rawFiles.sort(cmp);
6915
+ const all = [...rawDirs, ...rawFiles];
6916
+ const truncated = all.length > MAX_ENTRIES3;
6917
+ return { ...base, entries: truncated ? all.slice(0, MAX_ENTRIES3) : all, truncated };
6918
+ }
6919
+ function mkdirDirectory(parentInput, name, opts = {}) {
6920
+ const parent = resolveBrowsePath(parentInput);
6921
+ const clean2 = (name ?? "").trim();
6922
+ const listParent = () => browseDirectory(parent, opts);
6923
+ if (!clean2 || clean2 === "." || clean2 === ".." || clean2.includes("/") || clean2.includes("\\") || clean2.includes("\0")) {
6924
+ return { ...listParent(), error: "Invalid folder name." };
6925
+ }
6926
+ const target = path4.join(parent, clean2);
6927
+ if (path4.dirname(target) !== parent) {
6928
+ return { ...listParent(), error: "Invalid folder name." };
6929
+ }
6930
+ try {
6931
+ fs5.mkdirSync(target, { recursive: false });
6932
+ } catch (err) {
6933
+ const code = err?.code;
6934
+ const message = code === "EEXIST" ? "A folder with that name already exists." : code === "EACCES" || code === "EPERM" ? "Permission denied." : code === "ENOENT" ? "The parent folder no longer exists." : "Could not create the folder.";
6935
+ return { ...listParent(), error: message };
6936
+ }
6937
+ return listParent();
6938
+ }
6939
+
6940
+ // src/dir-picker.ts
6941
+ var c2 = {
6942
+ reset: "\x1B[0m",
6943
+ dim: "\x1B[2m",
6944
+ bold: "\x1B[1m",
6945
+ gray: themeGray,
6946
+ yellow: "\x1B[33m",
6947
+ red: "\x1B[31m",
6948
+ teal: "\x1B[38;5;37m"
6949
+ };
6950
+ function localBrowseBackend(label = "this machine") {
6951
+ return {
6952
+ label,
6953
+ list: async (p, showHidden) => browseDirectory(p, { showHidden }),
6954
+ mkdir: async (parent, name, showHidden) => mkdirDirectory(parent, name, { showHidden })
6955
+ };
6956
+ }
6957
+ function parseBrowseResult(value) {
6958
+ if (!value || typeof value !== "object") return null;
6959
+ const r = value;
6960
+ if (typeof r.path !== "string" || !Array.isArray(r.entries)) return null;
6961
+ return {
6962
+ path: r.path,
6963
+ parent: typeof r.parent === "string" ? r.parent : null,
6964
+ home: typeof r.home === "string" ? r.home : "",
6965
+ entries: r.entries.filter(
6966
+ (e) => !!e && typeof e.name === "string"
6967
+ ),
6968
+ truncated: r.truncated === true,
6969
+ error: typeof r.error === "string" ? r.error : void 0
6970
+ };
6971
+ }
6972
+ function remoteBrowseBackend(api, machineId, label) {
6973
+ const rpc = async (req) => {
6974
+ const nonce = crypto.randomBytes(8).toString("hex");
6975
+ await writeFsRequest(api, machineId, { nonce, ...req });
6976
+ const deadline = Date.now() + 15e3;
6977
+ while (Date.now() < deadline) {
6978
+ await new Promise((r) => setTimeout(r, 700));
6979
+ const res = await readFsResponse(api, machineId).catch(() => null);
6980
+ if (res && res.nonce === nonce) {
6981
+ if (!res.ok) throw new Error(res.error || "Browse failed on the remote machine.");
6982
+ const parsed = parseBrowseResult(res.result);
6983
+ if (!parsed) throw new Error("The remote machine sent an unreadable listing.");
6984
+ return parsed;
6985
+ }
6986
+ }
6987
+ throw new Error(`${label} didn't answer \u2014 is its daemon online?`);
6988
+ };
6989
+ return {
6990
+ label,
6991
+ list: (p, showHidden) => rpc({ op: "list", path: p ?? "~", show_hidden: showHidden }),
6992
+ mkdir: (parent, name, showHidden) => rpc({ op: "mkdir", path: parent, name, show_hidden: showHidden })
6993
+ };
6994
+ }
6995
+ function joinBrowsed(parent, name) {
6996
+ const sep = parent.includes("\\") && !parent.includes("/") ? "\\" : "/";
6997
+ return parent.endsWith(sep) ? parent + name : parent + sep + name;
6998
+ }
6999
+ function tail(p, max = 44) {
7000
+ return p.length > max ? "\u2026" + p.slice(-(max - 1)) : p;
7001
+ }
7002
+ var KEY_HINTS = `enter open \xB7 s use this directory \xB7 . hidden files \xB7 esc back`;
7003
+ async function pickDirectory(tui, backend, opts = {}) {
7004
+ let showHidden = false;
7005
+ let page = 0;
7006
+ const spin = opts.loader?.(`Reading ${backend.label}`);
7007
+ let listing;
7008
+ try {
7009
+ listing = await backend.list(opts.startPath ?? null, showHidden);
7010
+ } catch (e) {
7011
+ spin?.stop();
7012
+ tui.print(`${c2.red}\u2717${c2.reset} ${c2.gray}${e instanceof Error ? e.message : String(e)}${c2.reset}`);
7013
+ return null;
7014
+ }
7015
+ spin?.stop();
7016
+ const title = () => `${c2.bold}${gradientText(`Browse ${backend.label}`)}${c2.reset} ${c2.teal}${tail(listing.path, 48)}${c2.reset}` + (showHidden ? ` ${c2.dim}\xB7 hidden shown${c2.reset}` : "");
7017
+ const footer = (error) => error ? `${c2.yellow}${error}${c2.reset} ${c2.dim}${KEY_HINTS}${c2.reset}` : `${c2.dim}${KEY_HINTS}${c2.reset}`;
7018
+ const buildItems = () => {
7019
+ const pageSize = Math.max(5, Math.min(14, (process.stdout.rows || 24) - 9));
7020
+ const pages = Math.max(1, Math.ceil(listing.entries.length / pageSize));
7021
+ if (page >= pages) page = 0;
7022
+ const slice = listing.entries.slice(page * pageSize, (page + 1) * pageSize);
7023
+ const items = [];
7024
+ if (listing.parent) items.push({ label: "..", hint: "up a level", value: { kind: "up", to: listing.parent } });
7025
+ for (const entry of slice) {
7026
+ if (entry.dir) {
7027
+ items.push({
7028
+ label: `${entry.name}/`,
7029
+ hint: entry.repo ? "git repo" : entry.project ? "project" : "",
7030
+ value: { kind: "open", to: joinBrowsed(listing.path, entry.name) }
7031
+ });
7032
+ } else {
7033
+ items.push({ label: entry.name, value: { kind: "noop" }, disabled: true });
7034
+ }
7035
+ }
7036
+ if (listing.entries.length === 0) {
7037
+ items.push({ label: "(empty directory)", value: { kind: "noop" }, disabled: true });
7038
+ }
7039
+ if (pages > 1) {
7040
+ const remaining = listing.entries.length - slice.length;
7041
+ items.push({
7042
+ label: `\u2192 more (page ${page + 1}/${pages}, ${remaining} more entr${remaining === 1 ? "y" : "ies"})`,
7043
+ value: { kind: "page" }
7044
+ });
7045
+ }
7046
+ if (listing.truncated) {
7047
+ items.push({ label: "\u2026listing capped \u2014 very large directory", value: { kind: "noop" }, disabled: true });
7048
+ }
7049
+ items.push({ label: "\uFF0B New folder here\u2026", value: { kind: "mkdir" } });
7050
+ return items;
7051
+ };
7052
+ const menu = tui.openLiveSelect({
7053
+ title: title(),
7054
+ items: buildItems(),
7055
+ footer: footer(listing.error),
7056
+ keys: ["s", "."]
7057
+ });
7058
+ const refresh = () => menu.update({ title: title(), items: buildItems(), footer: footer(listing.error) });
7059
+ const navigate = async (to, index) => {
7060
+ menu.setBusy(index);
7061
+ try {
7062
+ listing = await backend.list(to, showHidden);
7063
+ page = 0;
7064
+ refresh();
7065
+ } catch (e) {
7066
+ menu.update({ footer: footer(e instanceof Error ? e.message : String(e)) });
7067
+ }
7068
+ };
7069
+ for (; ; ) {
7070
+ const ev = await menu.next();
7071
+ if (ev.kind === "cancel") {
7072
+ menu.close();
7073
+ return null;
7074
+ }
7075
+ if (ev.kind === "key") {
7076
+ if (ev.name === "s") {
7077
+ menu.close();
7078
+ return listing.path;
7079
+ }
7080
+ if (ev.name === ".") {
7081
+ showHidden = !showHidden;
7082
+ await navigate(listing.path, ev.index);
7083
+ }
7084
+ continue;
7085
+ }
7086
+ if (ev.kind === "edit") {
7087
+ menu.setBusy(ev.index);
7088
+ try {
7089
+ const after = await backend.mkdir(listing.path, ev.text, showHidden);
7090
+ if (after.error) {
7091
+ listing = after;
7092
+ page = 0;
7093
+ menu.update({ title: title(), items: buildItems(), footer: footer(after.error) });
7094
+ } else {
7095
+ listing = await backend.list(joinBrowsed(listing.path, ev.text), showHidden);
7096
+ page = 0;
7097
+ refresh();
7098
+ }
7099
+ } catch (e) {
7100
+ menu.update({ footer: footer(e instanceof Error ? e.message : String(e)) });
7101
+ }
7102
+ continue;
7103
+ }
7104
+ const move = ev.value;
7105
+ if (move.kind === "up" || move.kind === "open") {
7106
+ await navigate(move.to, ev.index);
7107
+ } else if (move.kind === "page") {
7108
+ page += 1;
7109
+ refresh();
7110
+ } else if (move.kind === "mkdir") {
7111
+ menu.beginEdit(ev.index, { placeholder: "folder name\u2026", prefix: "\uFF0B " });
7112
+ }
7113
+ }
7114
+ }
6558
7115
 
6559
7116
  // src/relay.ts
6560
7117
  var APPROVAL_REQUEST_KEY = "approval_request";
@@ -6704,114 +7261,6 @@ var HubSocket = class {
6704
7261
  this.ws?.close();
6705
7262
  }
6706
7263
  };
6707
- var PROJECT_MARKERS = [
6708
- ".git",
6709
- "package.json",
6710
- "pyproject.toml",
6711
- "Cargo.toml",
6712
- "go.mod",
6713
- "pom.xml",
6714
- "build.gradle",
6715
- "Gemfile",
6716
- "composer.json",
6717
- "requirements.txt",
6718
- ".standardagents"
6719
- ];
6720
- var MAX_ENTRIES3 = 500;
6721
- function resolveBrowsePath(input3) {
6722
- const home = os8.homedir();
6723
- let p = (input3 ?? "").trim();
6724
- if (!p) return home;
6725
- if (p === "~") return home;
6726
- if (p.startsWith("~/")) p = path4.join(home, p.slice(2));
6727
- return path4.resolve(p);
6728
- }
6729
- function markers(dirPath) {
6730
- let repo = false;
6731
- let project = false;
6732
- for (const marker of PROJECT_MARKERS) {
6733
- let hit = false;
6734
- try {
6735
- hit = fs5.existsSync(path4.join(dirPath, marker));
6736
- } catch {
6737
- hit = false;
6738
- }
6739
- if (!hit) continue;
6740
- project = true;
6741
- if (marker === ".git") repo = true;
6742
- if (repo) break;
6743
- }
6744
- return { project, repo };
6745
- }
6746
- function browseDirectory(input3, opts = {}) {
6747
- const home = os8.homedir();
6748
- const abs = resolveBrowsePath(input3);
6749
- const parent = path4.dirname(abs);
6750
- const base = {
6751
- path: abs,
6752
- parent: parent === abs ? null : parent,
6753
- home
6754
- };
6755
- let dirents;
6756
- try {
6757
- const stat = fs5.statSync(abs);
6758
- if (!stat.isDirectory()) {
6759
- return { ...base, entries: [], truncated: false, error: "Not a directory" };
6760
- }
6761
- dirents = fs5.readdirSync(abs, { withFileTypes: true });
6762
- } catch (err) {
6763
- const code = err?.code;
6764
- const message = code === "EACCES" || code === "EPERM" ? "Permission denied" : code === "ENOENT" ? "Folder not found" : "Could not read this folder";
6765
- return { ...base, entries: [], truncated: false, error: message };
6766
- }
6767
- const showHidden = !!opts.showHidden;
6768
- const rawDirs = [];
6769
- const rawFiles = [];
6770
- for (const d of dirents) {
6771
- const name = d.name;
6772
- if (!showHidden && name.startsWith(".")) continue;
6773
- let isDir = d.isDirectory();
6774
- if (d.isSymbolicLink()) {
6775
- try {
6776
- isDir = fs5.statSync(path4.join(abs, name)).isDirectory();
6777
- } catch {
6778
- isDir = false;
6779
- }
6780
- }
6781
- if (isDir) {
6782
- const { project, repo } = markers(path4.join(abs, name));
6783
- rawDirs.push({ name, dir: true, project: project || void 0, repo: repo || void 0 });
6784
- } else {
6785
- rawFiles.push({ name, dir: false });
6786
- }
6787
- }
6788
- const cmp = (a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase());
6789
- rawDirs.sort(cmp);
6790
- rawFiles.sort(cmp);
6791
- const all = [...rawDirs, ...rawFiles];
6792
- const truncated = all.length > MAX_ENTRIES3;
6793
- return { ...base, entries: truncated ? all.slice(0, MAX_ENTRIES3) : all, truncated };
6794
- }
6795
- function mkdirDirectory(parentInput, name, opts = {}) {
6796
- const parent = resolveBrowsePath(parentInput);
6797
- const clean2 = (name ?? "").trim();
6798
- const listParent = () => browseDirectory(parent, opts);
6799
- if (!clean2 || clean2 === "." || clean2 === ".." || clean2.includes("/") || clean2.includes("\\") || clean2.includes("\0")) {
6800
- return { ...listParent(), error: "Invalid folder name." };
6801
- }
6802
- const target = path4.join(parent, clean2);
6803
- if (path4.dirname(target) !== parent) {
6804
- return { ...listParent(), error: "Invalid folder name." };
6805
- }
6806
- try {
6807
- fs5.mkdirSync(target, { recursive: false });
6808
- } catch (err) {
6809
- const code = err?.code;
6810
- const message = code === "EEXIST" ? "A folder with that name already exists." : code === "EACCES" || code === "EPERM" ? "Permission denied." : code === "ENOENT" ? "The parent folder no longer exists." : "Could not create the folder.";
6811
- return { ...listParent(), error: message };
6812
- }
6813
- return listParent();
6814
- }
6815
7264
  var PKG_NAME = "@standardagents/code";
6816
7265
  var REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PKG_NAME)}`;
6817
7266
  var CACHE_REL_DIR = ".config/standardagents-cli";
@@ -7576,7 +8025,7 @@ function serviceStatus() {
7576
8025
  }
7577
8026
 
7578
8027
  // src/daemon-cli.ts
7579
- var c2 = {
8028
+ var c3 = {
7580
8029
  reset: "\x1B[0m",
7581
8030
  dim: "\x1B[2m",
7582
8031
  bold: "\x1B[1m",
@@ -7589,9 +8038,9 @@ function usage() {
7589
8038
  stdout.write(
7590
8039
  [
7591
8040
  "",
7592
- `${c2.bold}standardcode daemon${c2.reset} \u2014 headless execution client for this machine`,
8041
+ `${c3.bold}standardcode daemon${c3.reset} \u2014 headless execution client for this machine`,
7593
8042
  "",
7594
- `${c2.bold}Commands${c2.reset}`,
8043
+ `${c3.bold}Commands${c3.reset}`,
7595
8044
  " install [--endpoint url] Sign in (if needed), name this machine, and install",
7596
8045
  " the always-on service (launchd / systemd).",
7597
8046
  " uninstall Stop and remove the service.",
@@ -7631,11 +8080,11 @@ async function ensureSignedIn(endpoint) {
7631
8080
  const api2 = new ApiClient(endpoint, stored.access_token);
7632
8081
  const check2 = await api2.verifyDetailed();
7633
8082
  if (check2.ok) return api2;
7634
- stdout.write(`${c2.yellow}Saved sign-in for ${host} failed:${c2.reset} ${check2.reason}
8083
+ stdout.write(`${c3.yellow}Saved sign-in for ${host} failed:${c3.reset} ${check2.reason}
7635
8084
 
7636
8085
  `);
7637
8086
  }
7638
- stdout.write(`${c2.bold}Sign in to Standard Code${c2.reset} ${c2.dim}(${host})${c2.reset}
8087
+ stdout.write(`${c3.bold}Sign in to Standard Code${c3.reset} ${c3.dim}(${host})${c3.reset}
7639
8088
 
7640
8089
  `);
7641
8090
  const token = await deviceLogin(endpoint);
@@ -7646,7 +8095,7 @@ async function ensureSignedIn(endpoint) {
7646
8095
  { endpoint, access_token: token, token_type: "Bearer", saved_at: Date.now() },
7647
8096
  { updateDefault: false }
7648
8097
  );
7649
- stdout.write(`${c2.green}\u2713${c2.reset} Signed in to ${c2.teal}${host}${c2.reset}
8098
+ stdout.write(`${c3.green}\u2713${c3.reset} Signed in to ${c3.teal}${host}${c3.reset}
7650
8099
 
7651
8100
  `);
7652
8101
  return api;
@@ -7659,26 +8108,26 @@ async function installCommand(endpointFlag) {
7659
8108
  const suggested = machineDisplayName(existing ?? { hostname: os8.hostname(), id: identity.machine_id });
7660
8109
  const rl = readline2.createInterface({ input: stdin, output: stdout });
7661
8110
  const answer = (await rl.question(
7662
- `${c2.bold}Machine name${c2.reset} ${c2.dim}(shown in the session picker)${c2.reset} [${suggested}]: `
8111
+ `${c3.bold}Machine name${c3.reset} ${c3.dim}(shown in the session picker)${c3.reset} [${suggested}]: `
7663
8112
  )).trim();
7664
8113
  rl.close();
7665
8114
  if (answer) await setMachineName(api, identity.machine_id, answer);
7666
8115
  await updateOwnMachineRecord(api, identity);
7667
8116
  const displayName = answer || suggested;
7668
- stdout.write(`${c2.dim}Installing the always-on service\u2026${c2.reset}
8117
+ stdout.write(`${c3.dim}Installing the always-on service\u2026${c3.reset}
7669
8118
  `);
7670
8119
  const extra = endpointFlag ? ["--endpoint", endpoint] : [];
7671
8120
  const result = installService(resolveDaemonCommand(extra), endpointFlag ? endpoint : void 0);
7672
8121
  if (!result.ok) {
7673
- stdout.write(`${c2.red}\u2717${c2.reset} ${result.detail}
8122
+ stdout.write(`${c3.red}\u2717${c3.reset} ${result.detail}
7674
8123
  `);
7675
- if (result.manualHint) stdout.write(`${c2.dim}${result.manualHint}${c2.reset}
8124
+ if (result.manualHint) stdout.write(`${c3.dim}${result.manualHint}${c3.reset}
7676
8125
  `);
7677
8126
  process.exit(1);
7678
8127
  }
7679
- stdout.write(`${c2.green}\u2713${c2.reset} ${result.detail}
8128
+ stdout.write(`${c3.green}\u2713${c3.reset} ${result.detail}
7680
8129
  `);
7681
- stdout.write(`${c2.dim}Waiting for the daemon's first heartbeat\u2026${c2.reset}
8130
+ stdout.write(`${c3.dim}Waiting for the daemon's first heartbeat\u2026${c3.reset}
7682
8131
  `);
7683
8132
  const deadline = Date.now() + 6e4;
7684
8133
  let alive = false;
@@ -7692,17 +8141,17 @@ async function installCommand(endpointFlag) {
7692
8141
  }
7693
8142
  if (alive) {
7694
8143
  stdout.write(
7695
- `${c2.green}\u2713${c2.reset} ${c2.bold}${displayName}${c2.reset} is online.
8144
+ `${c3.green}\u2713${c3.reset} ${c3.bold}${displayName}${c3.reset} is online.
7696
8145
 
7697
8146
  Sessions started elsewhere can now run on this machine.
7698
- ${c2.dim}Projects register automatically when you run standardcode in a directory here,
7699
- or add one now: standardcode daemon add-project <path>${c2.reset}
8147
+ ${c3.dim}Projects register automatically when you run standardcode in a directory here,
8148
+ or add one now: standardcode daemon add-project <path>${c3.reset}
7700
8149
  `
7701
8150
  );
7702
8151
  } else {
7703
8152
  stdout.write(
7704
- `${c2.yellow}\u26A0${c2.reset} The service installed but no heartbeat arrived yet.
7705
- ${c2.dim}Check ~/.standardagents/daemon.log and \`standardcode daemon status\`.${c2.reset}
8153
+ `${c3.yellow}\u26A0${c3.reset} The service installed but no heartbeat arrived yet.
8154
+ ${c3.dim}Check ~/.standardagents/daemon.log and \`standardcode daemon status\`.${c3.reset}
7706
8155
  `
7707
8156
  );
7708
8157
  }
@@ -7710,16 +8159,16 @@ ${c2.dim}Check ~/.standardagents/daemon.log and \`standardcode daemon status\`.$
7710
8159
  async function statusCommand() {
7711
8160
  const status = serviceStatus();
7712
8161
  const identity = loadMachineIdentity();
7713
- stdout.write(`${c2.bold}Service:${c2.reset} ${status.detail}
8162
+ stdout.write(`${c3.bold}Service:${c3.reset} ${status.detail}
7714
8163
  `);
7715
8164
  const endpoint = resolveEndpoint();
7716
8165
  const cred = getCredential(endpoint);
7717
8166
  if (!cred) {
7718
8167
  stdout.write(
7719
- `${c2.bold}Machine:${c2.reset} ${os8.hostname()} ${c2.dim}(${identity.machine_id})${c2.reset}
8168
+ `${c3.bold}Machine:${c3.reset} ${os8.hostname()} ${c3.dim}(${identity.machine_id})${c3.reset}
7720
8169
  `
7721
8170
  );
7722
- stdout.write(`${c2.bold}Account:${c2.reset} ${c2.yellow}not signed in to ${endpoint}${c2.reset}
8171
+ stdout.write(`${c3.bold}Account:${c3.reset} ${c3.yellow}not signed in to ${endpoint}${c3.reset}
7723
8172
  `);
7724
8173
  return;
7725
8174
  }
@@ -7727,33 +8176,33 @@ async function statusCommand() {
7727
8176
  const api = new ApiClient(endpoint, cred.access_token);
7728
8177
  const record2 = await loadMachine(api, identity.machine_id).catch(() => null);
7729
8178
  stdout.write(
7730
- `${c2.bold}Machine:${c2.reset} ${machineDisplayName(record2 ?? { hostname: os8.hostname(), id: identity.machine_id })} ${c2.dim}(${identity.machine_id})${c2.reset}
8179
+ `${c3.bold}Machine:${c3.reset} ${machineDisplayName(record2 ?? { hostname: os8.hostname(), id: identity.machine_id })} ${c3.dim}(${identity.machine_id})${c3.reset}
7731
8180
  `
7732
8181
  );
7733
8182
  if (!record2) {
7734
- stdout.write(`${c2.bold}Registry:${c2.reset} not registered yet
8183
+ stdout.write(`${c3.bold}Registry:${c3.reset} not registered yet
7735
8184
  `);
7736
8185
  return;
7737
8186
  }
7738
8187
  const online = daemonOnline(record2);
7739
8188
  const seen = record2.daemon ? `${Math.round((Date.now() - record2.daemon.last_seen_at) / 1e3)}s ago (v${record2.daemon.version})` : "never";
7740
- stdout.write(`${c2.bold}Registry:${c2.reset} ${online ? `${c2.green}online${c2.reset}` : `${c2.yellow}offline${c2.reset}`} \xB7 last heartbeat ${seen}
8189
+ stdout.write(`${c3.bold}Registry:${c3.reset} ${online ? `${c3.green}online${c3.reset}` : `${c3.yellow}offline${c3.reset}`} \xB7 last heartbeat ${seen}
7741
8190
  `);
7742
8191
  const projects = Object.keys(record2.projects);
7743
- stdout.write(`${c2.bold}Projects:${c2.reset} ${projects.length ? "" : c2.dim + "none registered" + c2.reset}
8192
+ stdout.write(`${c3.bold}Projects:${c3.reset} ${projects.length ? "" : c3.dim + "none registered" + c3.reset}
7744
8193
  `);
7745
- for (const p of projects.sort()) stdout.write(` ${c2.dim}${p}${c2.reset}
8194
+ for (const p of projects.sort()) stdout.write(` ${c3.dim}${p}${c3.reset}
7746
8195
  `);
7747
8196
  }
7748
8197
  async function projectCommand(action, target) {
7749
8198
  if (!target) {
7750
- stdout.write(`${c2.red}\u2717${c2.reset} Expected a project path.
8199
+ stdout.write(`${c3.red}\u2717${c3.reset} Expected a project path.
7751
8200
  `);
7752
8201
  process.exit(1);
7753
8202
  }
7754
8203
  const dir2 = path4.resolve(target);
7755
8204
  if (action === "add" && !fs5.existsSync(dir2)) {
7756
- stdout.write(`${c2.red}\u2717${c2.reset} ${dir2} does not exist on this machine.
8205
+ stdout.write(`${c3.red}\u2717${c3.reset} ${dir2} does not exist on this machine.
7757
8206
  `);
7758
8207
  process.exit(1);
7759
8208
  }
@@ -7764,11 +8213,11 @@ async function projectCommand(action, target) {
7764
8213
  const displayName = machineDisplayName(record2 ?? { hostname: os8.hostname(), id: identity.machine_id });
7765
8214
  if (action === "add") {
7766
8215
  await registerProject(api, identity, dir2);
7767
- stdout.write(`${c2.green}\u2713${c2.reset} Registered ${dir2} for remote sessions on ${displayName}.
8216
+ stdout.write(`${c3.green}\u2713${c3.reset} Registered ${dir2} for remote sessions on ${displayName}.
7768
8217
  `);
7769
8218
  } else {
7770
8219
  await unregisterProject(api, identity, dir2);
7771
- stdout.write(`${c2.green}\u2713${c2.reset} Removed ${dir2} from this machine's projects.
8220
+ stdout.write(`${c3.green}\u2713${c3.reset} Removed ${dir2} from this machine's projects.
7772
8221
  `);
7773
8222
  }
7774
8223
  }
@@ -7784,7 +8233,7 @@ async function runDaemonCommand(argv) {
7784
8233
  return;
7785
8234
  case "uninstall": {
7786
8235
  const result = uninstallService();
7787
- stdout.write(`${result.ok ? c2.green + "\u2713" : c2.red + "\u2717"}${c2.reset} ${result.detail}
8236
+ stdout.write(`${result.ok ? c3.green + "\u2713" : c3.red + "\u2717"}${c3.reset} ${result.detail}
7788
8237
  `);
7789
8238
  const ep = resolveEndpoint(endpoint);
7790
8239
  const cred = getCredential(ep);
@@ -7817,7 +8266,7 @@ async function runDaemonCommand(argv) {
7817
8266
  }
7818
8267
 
7819
8268
  // src/index.ts
7820
- var c3 = {
8269
+ var c4 = {
7821
8270
  reset: "\x1B[0m",
7822
8271
  dim: "\x1B[2m",
7823
8272
  bold: "\x1B[1m",
@@ -7846,10 +8295,10 @@ function printUsage() {
7846
8295
  stdout.write(
7847
8296
  [
7848
8297
  "",
7849
- `${c3.bold}Usage${c3.reset}`,
8298
+ `${c4.bold}Usage${c4.reset}`,
7850
8299
  " standardcode [options] [dir]",
7851
8300
  "",
7852
- `${c3.bold}Options${c3.reset}`,
8301
+ `${c4.bold}Options${c4.reset}`,
7853
8302
  " -e, --endpoint [url] Use a different Standard Agents instance for this run",
7854
8303
  " (default: https://api.standardcode.ai).",
7855
8304
  " If url is omitted, prompt for it.",
@@ -7917,15 +8366,15 @@ function parseArgs2(args) {
7917
8366
  }
7918
8367
  function printCommandBlock(tui, command, output4, ok, where) {
7919
8368
  tui.print("");
7920
- const note = where ? ` ${c3.dim}(ran on ${where})${c3.reset}` : "";
7921
- tui.print(`${c3.magenta}!${c3.reset} ${c3.bold}${command}${c3.reset}${note}`);
8369
+ const note = where ? ` ${c4.dim}(ran on ${where})${c4.reset}` : "";
8370
+ tui.print(`${c4.magenta}!${c4.reset} ${c4.bold}${command}${c4.reset}${note}`);
7922
8371
  const body = (output4 ?? "").replace(/\s+$/, "");
7923
8372
  if (body) {
7924
8373
  for (const line of body.split("\n")) {
7925
- tui.print(` ${ok ? c3.dim : c3.red}${line}${c3.reset}`);
8374
+ tui.print(` ${ok ? c4.dim : c4.red}${line}${c4.reset}`);
7926
8375
  }
7927
8376
  } else {
7928
- tui.print(` ${c3.dim}(no output)${c3.reset}`);
8377
+ tui.print(` ${c4.dim}(no output)${c4.reset}`);
7929
8378
  }
7930
8379
  tui.print("");
7931
8380
  }
@@ -7936,7 +8385,7 @@ function printAssistant(tui, text) {
7936
8385
  let dotted = false;
7937
8386
  for (const line of renderStreamingMarkdown(text, cols2)) {
7938
8387
  if (!dotted && line.trim()) {
7939
- tui.print(`${c3.gray}\u2022${c3.reset} ${line}`);
8388
+ tui.print(`${c4.gray}\u2022${c4.reset} ${line}`);
7940
8389
  dotted = true;
7941
8390
  } else {
7942
8391
  tui.print(` ${line}`);
@@ -7951,7 +8400,7 @@ function startLoader(label) {
7951
8400
  const draw = () => {
7952
8401
  const now = Date.now();
7953
8402
  const f = frames[Math.floor(now / 70) % frames.length];
7954
- stdout.write(`\r\x1B[K${pad}${brandCycleColor(now)}${f}${c3.reset} ${c3.dim}${label}\u2026${c3.reset}`);
8403
+ stdout.write(`\r\x1B[K${pad}${brandCycleColor(now)}${f}${c4.reset} ${c4.dim}${label}\u2026${c4.reset}`);
7955
8404
  };
7956
8405
  draw();
7957
8406
  const timer = setInterval(draw, 70);
@@ -7967,12 +8416,12 @@ function farewell(stoppedProcs = 0) {
7967
8416
  if (stoppedProcs > 0) {
7968
8417
  stdout.write(
7969
8418
  `
7970
- ${c3.cyan}\u2699${c3.reset} Stopped ${stoppedProcs} background process${stoppedProcs === 1 ? "" : "es"}.
8419
+ ${c4.cyan}\u2699${c4.reset} Stopped ${stoppedProcs} background process${stoppedProcs === 1 ? "" : "es"}.
7971
8420
  `
7972
8421
  );
7973
8422
  }
7974
8423
  stdout.write(`
7975
- ${c3.teal}\u25C7${c3.reset} ${c3.dim}Standard Code \u2014 see you soon.${c3.reset}
8424
+ ${c4.teal}\u25C7${c4.reset} ${c4.dim}Standard Code \u2014 see you soon.${c4.reset}
7976
8425
  `);
7977
8426
  }
7978
8427
  function printWelcome(endpoint, projectDir) {
@@ -7986,10 +8435,10 @@ function printWelcome(endpoint, projectDir) {
7986
8435
  const metaWidth = Math.max(1, terminalColumns - pad.length - markWidth - 3 - 1);
7987
8436
  const displayDir = truncateMiddle(dir2, metaWidth);
7988
8437
  const meta = [
7989
- `${c3.bold}${gradientText("Standard Code")}${c3.reset}${version ? ` ${c3.dim}v${version}${c3.reset}` : ""}`,
7990
- `${c3.dim}terminal coding agent${c3.reset}`,
7991
- ...endpoint === PRODUCTION_ENDPOINT ? [] : [`${c3.teal}${host}${c3.reset}`],
7992
- `${c3.dim}${displayDir}${c3.reset}`
8438
+ `${c4.bold}${gradientText("Standard Code")}${c4.reset}${version ? ` ${c4.dim}v${version}${c4.reset}` : ""}`,
8439
+ `${c4.dim}terminal coding agent${c4.reset}`,
8440
+ ...endpoint === PRODUCTION_ENDPOINT ? [] : [`${c4.teal}${host}${c4.reset}`],
8441
+ `${c4.dim}${displayDir}${c4.reset}`
7993
8442
  ];
7994
8443
  const metaTop = Math.floor((LOGO_MARK.length - meta.length) / 2);
7995
8444
  stdout.write("\n");
@@ -8003,11 +8452,11 @@ function printWelcome(endpoint, projectDir) {
8003
8452
  }
8004
8453
  function colorActivity(line) {
8005
8454
  const m = line.match(/^(\s*)([✓✗⛔])\s?([\s\S]*)$/);
8006
- if (!m) return `${c3.dim}${line}${c3.reset}`;
8455
+ if (!m) return `${c4.dim}${line}${c4.reset}`;
8007
8456
  const [, indent, glyph, rest] = m;
8008
8457
  if (glyph === "\u2713") {
8009
- const body = rest.replace(/\s(\([^()]*\))\s*$/, ` ${c3.dim}$1${c3.reset}`);
8010
- return `${indent}${c3.green}\u2713${c3.reset} ${body}`;
8458
+ const body = rest.replace(/\s(\([^()]*\))\s*$/, ` ${c4.dim}$1${c4.reset}`);
8459
+ return `${indent}${c4.green}\u2713${c4.reset} ${body}`;
8011
8460
  }
8012
8461
  if (glyph === "\u2717") {
8013
8462
  const ERR_MAX_LINES = 7;
@@ -8015,15 +8464,15 @@ function colorActivity(line) {
8015
8464
  const shown = lines.slice(0, ERR_MAX_LINES);
8016
8465
  const hidden = lines.length - shown.length;
8017
8466
  const body = shown.map(
8018
- (l, i) => i === 0 ? `${indent}${c3.red}\u2717 ${l}${c3.reset}` : `${indent}${c3.red}${c3.dim}${l}${c3.reset}`
8467
+ (l, i) => i === 0 ? `${indent}${c4.red}\u2717 ${l}${c4.reset}` : `${indent}${c4.red}${c4.dim}${l}${c4.reset}`
8019
8468
  ).join("\n");
8020
8469
  if (hidden > 0) {
8021
8470
  return `${body}
8022
- ${indent}${c3.dim}\u2026 +${hidden} more line${hidden === 1 ? "" : "s"}${c3.reset}`;
8471
+ ${indent}${c4.dim}\u2026 +${hidden} more line${hidden === 1 ? "" : "s"}${c4.reset}`;
8023
8472
  }
8024
8473
  return body;
8025
8474
  }
8026
- return `${indent}${c3.yellow}\u26D4 ${rest}${c3.reset}`;
8475
+ return `${indent}${c4.yellow}\u26D4 ${rest}${c4.reset}`;
8027
8476
  }
8028
8477
  async function main() {
8029
8478
  if (process.argv[2] === "daemon") {
@@ -8034,7 +8483,7 @@ async function main() {
8034
8483
  try {
8035
8484
  cliArgs = parseArgs2(process.argv.slice(2));
8036
8485
  } catch (error) {
8037
- stdout.write(`${c3.red}error:${c3.reset} ${error instanceof Error ? error.message : String(error)}
8486
+ stdout.write(`${c4.red}error:${c4.reset} ${error instanceof Error ? error.message : String(error)}
8038
8487
  `);
8039
8488
  printUsage();
8040
8489
  process.exit(1);
@@ -8046,7 +8495,7 @@ async function main() {
8046
8495
  const endpointArg = cliArgs.endpoint;
8047
8496
  const endpointOverride = cliArgs.promptEndpoint || typeof endpointArg === "string" && endpointArg.trim() !== "";
8048
8497
  const dirArg = cliArgs.dir;
8049
- const projectDir = path4.resolve(dirArg || process.cwd());
8498
+ let projectDir = path4.resolve(dirArg || process.cwd());
8050
8499
  const machine = os8.hostname();
8051
8500
  const reader = { rl: null };
8052
8501
  let handoffClosing = false;
@@ -8061,7 +8510,7 @@ async function main() {
8061
8510
  }
8062
8511
  preflightArmed = true;
8063
8512
  stdout.write(`
8064
- ${c3.dim}Press Control-C again to exit${c3.reset}
8513
+ ${c4.dim}Press Control-C again to exit${c4.reset}
8065
8514
  `);
8066
8515
  preflightTimer = setTimeout(() => {
8067
8516
  preflightArmed = false;
@@ -8083,10 +8532,10 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
8083
8532
  const askEndpoint = async () => {
8084
8533
  for (; ; ) {
8085
8534
  const answer = (await ask(
8086
- `${c3.cyan}Standard Agents instance URL${c3.reset} (e.g. http://localhost:5178): `
8535
+ `${c4.cyan}Standard Agents instance URL${c4.reset} (e.g. http://localhost:5178): `
8087
8536
  )).trim();
8088
8537
  if (answer) return answer;
8089
- stdout.write(`${c3.dim}An endpoint URL is required.${c3.reset}
8538
+ stdout.write(`${c4.dim}An endpoint URL is required.${c4.reset}
8090
8539
  `);
8091
8540
  }
8092
8541
  };
@@ -8101,7 +8550,7 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
8101
8550
  const tlsRelaxed = relaxTlsForLocalEndpoint(endpoint);
8102
8551
  printWelcome(endpoint, projectDir);
8103
8552
  if (tlsRelaxed) {
8104
- stdout.write(`${c3.dim} TLS verification relaxed for local endpoint.${c3.reset}
8553
+ stdout.write(`${c4.dim} TLS verification relaxed for local endpoint.${c4.reset}
8105
8554
 
8106
8555
  `);
8107
8556
  }
@@ -8113,7 +8562,7 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
8113
8562
  loading.stop();
8114
8563
  const applied = consumeAppliedUpdate(version);
8115
8564
  if (applied) {
8116
- stdout.write(` ${c3.green}\u2713${c3.reset} ${c3.dim}Standard Code updated to v${version}.${c3.reset}
8565
+ stdout.write(` ${c4.green}\u2713${c4.reset} ${c4.dim}Standard Code updated to v${version}.${c4.reset}
8117
8566
 
8118
8567
  `);
8119
8568
  }
@@ -8122,21 +8571,21 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
8122
8571
  const decision = decideAutoUpdate(updateAvailable, { state: readAutoUpdateState(), pm });
8123
8572
  if (decision === "start" && pm && startBackgroundUpdate(updateAvailable.latest, pm)) {
8124
8573
  stdout.write(
8125
- ` ${c3.teal}\u27F3${c3.reset} ${c3.dim}Standard Code ${c3.reset}${c3.bold}v${updateAvailable.latest}${c3.reset}${c3.dim} is installing in the background \u2014 it applies on your next launch.${c3.reset}
8574
+ ` ${c4.teal}\u27F3${c4.reset} ${c4.dim}Standard Code ${c4.reset}${c4.bold}v${updateAvailable.latest}${c4.reset}${c4.dim} is installing in the background \u2014 it applies on your next launch.${c4.reset}
8126
8575
 
8127
8576
  `
8128
8577
  );
8129
8578
  } else if (decision === "in_flight") {
8130
8579
  stdout.write(
8131
- ` ${c3.teal}\u27F3${c3.reset} ${c3.dim}Standard Code v${updateAvailable.latest} is still installing in the background.${c3.reset}
8580
+ ` ${c4.teal}\u27F3${c4.reset} ${c4.dim}Standard Code v${updateAvailable.latest} is still installing in the background.${c4.reset}
8132
8581
 
8133
8582
  `
8134
8583
  );
8135
8584
  } else {
8136
8585
  const display = updateCommand(pm ?? "npm").display;
8137
8586
  stdout.write(
8138
- ` ${c3.teal}\u25C7${c3.reset} ${c3.dim}Update available:${c3.reset} ${c3.dim}v${updateAvailable.current}${c3.reset} \u2192 ${c3.bold}v${updateAvailable.latest}${c3.reset}
8139
- ${c3.dim}Run ${c3.reset}${c3.bold}${display}${c3.reset}${c3.dim} to update${c3.reset}
8587
+ ` ${c4.teal}\u25C7${c4.reset} ${c4.dim}Update available:${c4.reset} ${c4.dim}v${updateAvailable.current}${c4.reset} \u2192 ${c4.bold}v${updateAvailable.latest}${c4.reset}
8588
+ ${c4.dim}Run ${c4.reset}${c4.bold}${display}${c4.reset}${c4.dim} to update${c4.reset}
8140
8589
 
8141
8590
  `
8142
8591
  );
@@ -8154,39 +8603,39 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
8154
8603
  if (!api || !storedCheck?.ok) {
8155
8604
  const host = endpoint.replace(/^https?:\/\//, "").replace(/\/$/, "");
8156
8605
  if (storedCheck && !storedCheck.ok) {
8157
- stdout.write(`${c3.red}\u2717${c3.reset} ${c3.dim}Saved sign-in for this endpoint failed:${c3.reset} ${storedCheck.reason}
8606
+ stdout.write(`${c4.red}\u2717${c4.reset} ${c4.dim}Saved sign-in for this endpoint failed:${c4.reset} ${storedCheck.reason}
8158
8607
  `);
8159
- if (storedCheck.hint) stdout.write(` ${c3.dim}${storedCheck.hint}${c3.reset}
8608
+ if (storedCheck.hint) stdout.write(` ${c4.dim}${storedCheck.hint}${c4.reset}
8160
8609
  `);
8161
8610
  stdout.write("\n");
8162
8611
  }
8163
8612
  const explainFailure = (result, prefix) => {
8164
- stdout.write(`${c3.red}\u2717${c3.reset} ${prefix}${result.reason}
8613
+ stdout.write(`${c4.red}\u2717${c4.reset} ${prefix}${result.reason}
8165
8614
  `);
8166
- if (result.hint) stdout.write(` ${c3.dim}${result.hint}${c3.reset}
8615
+ if (result.hint) stdout.write(` ${c4.dim}${result.hint}${c4.reset}
8167
8616
  `);
8168
8617
  };
8169
- stdout.write(`${c3.bold}${gradientText("Sign in to Standard Code")}${c3.reset}
8618
+ stdout.write(`${c4.bold}${gradientText("Sign in to Standard Code")}${c4.reset}
8170
8619
  `);
8171
8620
  if (`https://${host}` !== PRODUCTION_ENDPOINT) {
8172
- stdout.write(`${c3.dim}Connecting to${c3.reset} ${c3.teal}${host}${c3.reset}
8621
+ stdout.write(`${c4.dim}Connecting to${c4.reset} ${c4.teal}${host}${c4.reset}
8173
8622
  `);
8174
8623
  }
8175
8624
  stdout.write(
8176
- `${c3.dim}You'll only need to do this once on this machine.${c3.reset}
8625
+ `${c4.dim}You'll only need to do this once on this machine.${c4.reset}
8177
8626
 
8178
8627
  `
8179
8628
  );
8180
8629
  stdout.write(
8181
- `${c3.white}Press ${c3.bold}Enter${c3.reset}${c3.white} to open your browser and sign in.${c3.reset} ${c3.dim}(or paste an API token)${c3.reset}
8630
+ `${c4.white}Press ${c4.bold}Enter${c4.reset}${c4.white} to open your browser and sign in.${c4.reset} ${c4.dim}(or paste an API token)${c4.reset}
8182
8631
 
8183
8632
  `
8184
8633
  );
8185
8634
  for (; ; ) {
8186
- const token = (await ask(`${c3.teal}\u276F${c3.reset} `)).trim();
8635
+ const token = (await ask(`${c4.teal}\u276F${c4.reset} `)).trim();
8187
8636
  if (!token) {
8188
8637
  const got = await deviceLogin(endpoint).catch((e) => {
8189
- stdout.write(`${c3.red}\u2717${c3.reset} ${c3.dim}${e instanceof Error ? e.message : String(e)}${c3.reset}
8638
+ stdout.write(`${c4.red}\u2717${c4.reset} ${c4.dim}${e instanceof Error ? e.message : String(e)}${c4.reset}
8190
8639
  `);
8191
8640
  return null;
8192
8641
  });
@@ -8200,7 +8649,7 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
8200
8649
  { endpoint, access_token: got, token_type: "Bearer", saved_at: Date.now() },
8201
8650
  { updateDefault: !endpointOverride }
8202
8651
  );
8203
- stdout.write(`${c3.green}\u2713${c3.reset} Connected to ${c3.teal}${host}${c3.reset}
8652
+ stdout.write(`${c4.green}\u2713${c4.reset} Connected to ${c4.teal}${host}${c4.reset}
8204
8653
  `);
8205
8654
  break;
8206
8655
  }
@@ -8216,7 +8665,7 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
8216
8665
  { endpoint, access_token: token, token_type: "Bearer", saved_at: Date.now() },
8217
8666
  { updateDefault: !endpointOverride }
8218
8667
  );
8219
- stdout.write(`${c3.green}\u2713${c3.reset} Connected to ${c3.teal}${host}${c3.reset}
8668
+ stdout.write(`${c4.green}\u2713${c4.reset} Connected to ${c4.teal}${host}${c4.reset}
8220
8669
  `);
8221
8670
  break;
8222
8671
  }
@@ -8232,92 +8681,30 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
8232
8681
  void registerProject(api, identity, projectDir).catch(() => {
8233
8682
  });
8234
8683
  const tui = new Tui(1);
8235
- let selectedAgent = AGENT_ID;
8684
+ let agentOverride;
8236
8685
  if (cliArgs.agent) {
8237
8686
  const wanted = cliArgs.agent.toLowerCase();
8238
8687
  if (["sama", "opensama", "sama_one", OPENSAMA_AGENT_ID].includes(wanted)) {
8239
- selectedAgent = OPENSAMA_AGENT_ID;
8688
+ agentOverride = OPENSAMA_AGENT_ID;
8240
8689
  } else if (["unlimited", "standard", "unlimited_one", AGENT_ID].includes(wanted)) {
8241
- selectedAgent = AGENT_ID;
8690
+ agentOverride = AGENT_ID;
8242
8691
  } else {
8243
- stdout.write(`${c3.red}error:${c3.reset} Unknown agent "${cliArgs.agent}". Use unlimited or sama.
8692
+ stdout.write(`${c4.red}error:${c4.reset} Unknown agent "${cliArgs.agent}". Use unlimited or sama.
8244
8693
  `);
8245
8694
  process.exit(1);
8246
8695
  }
8247
- } else {
8248
- const picked = await tui.select(
8249
- `${c3.bold}${gradientText("Which agent?")}${c3.reset} ${c3.dim}\u2191\u2193 \xB7 enter \xB7 esc${c3.reset}`,
8250
- AGENT_CHOICES.map((choice) => ({
8251
- label: choice.title,
8252
- hint: choice.description,
8253
- value: choice.id,
8254
- disabled: choice.disabled
8255
- }))
8256
- );
8257
- if (!picked) process.exit(0);
8258
- selectedAgent = picked;
8259
8696
  }
8260
- if (selectedAgent === OPENSAMA_AGENT_ID) {
8261
- const ok = await ensureSamaAuth(tui, api);
8262
- if (!ok) process.exit(0);
8263
- }
8264
- const home = os8.homedir();
8265
- const tildeDir = projectDir.startsWith(home) ? "~" + projectDir.slice(home.length) : projectDir;
8266
- const shortDir = tildeDir.length > 38 ? "\u2026" + tildeDir.slice(-37) : tildeDir;
8697
+ let selectedAgent = agentOverride ?? AGENT_ID;
8267
8698
  const session = { mode: "local", identity };
8268
- {
8269
- const loadingMachines = startLoader("Checking your machines");
8270
- const machines = await loadMachines(api).catch(() => []);
8271
- loadingMachines.stop();
8272
- session.suggestDaemonInstall = machines.every((m) => !m.daemon);
8273
- const remoteTargets = machines.filter((m) => m.id !== identity.machine_id && daemonOnline(m));
8274
- if (remoteTargets.length > 0) {
8275
- const where = await tui.select(
8276
- `${c3.bold}${gradientText("Where should this session run?")}${c3.reset} ${c3.dim}\u2191\u2193 \xB7 enter \xB7 esc${c3.reset}`,
8277
- [
8278
- { label: `\u{1F5A5}\uFE0F This machine \u2014 ${shortDir}`, hint: "tools run locally", value: null },
8279
- ...remoteTargets.map((m) => ({
8280
- label: `${machineIcon(m)} ${m.name}`,
8281
- hint: `${m.hostname} \xB7 daemon online${m.daemon ? ` \xB7 v${m.daemon.version}` : ""}`,
8282
- value: m
8283
- }))
8284
- ]
8285
- );
8286
- if (where) {
8287
- const remotePath = await pickRemoteProject(tui, api, where);
8288
- if (remotePath) {
8289
- session.mode = "remote";
8290
- session.runner = where;
8291
- session.remotePath = remotePath;
8292
- }
8293
- }
8294
- }
8295
- }
8296
- let tags;
8297
- let resumeTags;
8298
- if (session.mode === "remote" && session.runner && session.remotePath) {
8299
- tags = [
8300
- `path:${session.remotePath}`,
8301
- `machine:${session.runner.hostname || session.runner.name}`,
8302
- `runner:${session.runner.id}`
8303
- ];
8304
- resumeTags = [`path:${session.remotePath}`, `runner:${session.runner.id}`];
8305
- } else {
8306
- tags = [`path:${projectDir}`, `machine:${machine}`, `runner:${identity.machine_id}`];
8307
- resumeTags = [`path:${projectDir}`, `machine:${machine}`];
8308
- }
8309
- const loadingSessions = startLoader("Loading sessions");
8310
- let existing = [];
8311
- try {
8312
- existing = await api.listThreads(
8313
- selectedAgent === OPENSAMA_AGENT_ID ? [OPENSAMA_AGENT_ID] : AGENT_ID_VARIANTS,
8314
- resumeTags
8315
- );
8316
- } catch {
8317
- existing = [];
8318
- }
8319
- const summaries = existing.length > 0 ? await summarizeThreads(api, existing.slice(0, 8)) : [];
8320
- loadingSessions.stop();
8699
+ const loadingMachines = startLoader("Checking your machines");
8700
+ const machines = await loadMachines(api).catch(() => []);
8701
+ loadingMachines.stop();
8702
+ session.suggestDaemonInstall = machines.every((m) => !m.daemon);
8703
+ const remoteTargets = machines.filter((m) => m.id !== identity.machine_id && daemonOnline(m));
8704
+ const self = machines.find((m) => m.id === identity.machine_id);
8705
+ const launchDir = projectDir;
8706
+ let tags = [];
8707
+ let resumeTags = [];
8321
8708
  const createSessionThread = async () => {
8322
8709
  const id = await api.createThread(selectedAgent, tags);
8323
8710
  if (session.mode === "remote" && session.runner && session.remotePath) {
@@ -8330,33 +8717,161 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
8330
8717
  }
8331
8718
  return id;
8332
8719
  };
8333
- let threadId;
8720
+ let threadId = "";
8334
8721
  let resumed = false;
8335
8722
  let historySeed;
8336
- if (existing.length > 0) {
8337
- const items = summaries.map((s) => ({
8338
- label: s.label,
8339
- hint: s.hint,
8340
- value: s.id
8341
- }));
8342
- items.push({ label: "\uFF0B Start a new session", value: null });
8343
- const whereLabel = session.mode === "remote" && session.runner ? `${session.runner.name}:${shortenPath(session.remotePath ?? "")}` : shortDir;
8344
- const picked = await tui.select(
8345
- `${c3.bold}${gradientText("Resume a session")}${c3.reset} ${c3.gray}${whereLabel}${c3.reset} ${c3.dim}\u2191\u2193 \xB7 enter \xB7 esc${c3.reset}`,
8346
- items
8347
- );
8348
- if (typeof picked === "string") {
8349
- threadId = picked;
8350
- resumed = true;
8723
+ let existing = [];
8724
+ let hadResumeMenu = false;
8725
+ let where = null;
8726
+ let step = remoteTargets.length > 0 ? "machine" : "thread";
8727
+ flow: for (; ; ) {
8728
+ if (step === "machine") {
8729
+ const picked = await tui.select(
8730
+ `${c4.bold}${gradientText("Where should this session run?")}${c4.reset} ${c4.dim}\u2191\u2193 \xB7 enter \xB7 esc${c4.reset}`,
8731
+ [
8732
+ {
8733
+ label: `This machine \u2014 ${self ? machineDisplayName(self) : machine}`,
8734
+ detail: "tools run locally",
8735
+ value: null
8736
+ },
8737
+ ...remoteTargets.map((m) => ({
8738
+ label: machineDisplayName(m),
8739
+ detail: `${m.hostname} \xB7 daemon online${m.daemon ? ` \xB7 v${m.daemon.version}` : ""}`,
8740
+ value: m
8741
+ }))
8742
+ ],
8743
+ { spaced: true }
8744
+ );
8745
+ if (picked === void 0) process.exit(0);
8746
+ where = picked;
8747
+ step = "project";
8748
+ } else if (step === "project") {
8749
+ session.mode = "local";
8750
+ session.runner = void 0;
8751
+ session.remotePath = void 0;
8752
+ projectDir = launchDir;
8753
+ if (where) {
8754
+ const remotePath = await pickRemoteProject(tui, api, where);
8755
+ if (!remotePath) {
8756
+ step = "machine";
8757
+ continue;
8758
+ }
8759
+ session.mode = "remote";
8760
+ session.runner = where;
8761
+ session.remotePath = remotePath;
8762
+ } else {
8763
+ const localPath = await pickLocalProject(tui, api, self, launchDir, machine);
8764
+ if (localPath === null) {
8765
+ step = "machine";
8766
+ continue;
8767
+ }
8768
+ if (localPath !== launchDir) {
8769
+ projectDir = localPath;
8770
+ void registerProject(api, identity, projectDir).catch(() => {
8771
+ });
8772
+ }
8773
+ }
8774
+ step = "thread";
8775
+ } else if (step === "thread") {
8776
+ if (session.mode === "remote" && session.runner && session.remotePath) {
8777
+ tags = [
8778
+ `path:${session.remotePath}`,
8779
+ `machine:${session.runner.hostname || session.runner.name}`,
8780
+ `runner:${session.runner.id}`
8781
+ ];
8782
+ resumeTags = [`path:${session.remotePath}`, `runner:${session.runner.id}`];
8783
+ } else {
8784
+ tags = [`path:${projectDir}`, `machine:${machine}`, `runner:${identity.machine_id}`];
8785
+ resumeTags = [`path:${projectDir}`, `machine:${machine}`];
8786
+ }
8787
+ const loadingSessions = startLoader("Loading sessions");
8788
+ try {
8789
+ const listIds = agentOverride ? agentOverride === OPENSAMA_AGENT_ID ? [OPENSAMA_AGENT_ID] : AGENT_ID_VARIANTS : [...AGENT_ID_VARIANTS, OPENSAMA_AGENT_ID];
8790
+ existing = await api.listThreads(listIds, resumeTags);
8791
+ existing.sort((a, b) => (b.created_at ?? 0) - (a.created_at ?? 0));
8792
+ } catch {
8793
+ existing = [];
8794
+ }
8795
+ const summaries = existing.length > 0 ? await summarizeThreads(api, existing.slice(0, 8)) : [];
8796
+ loadingSessions.stop();
8797
+ if (existing.length === 0) {
8798
+ hadResumeMenu = false;
8799
+ step = "agent";
8800
+ continue;
8801
+ }
8802
+ hadResumeMenu = true;
8803
+ const home = os8.homedir();
8804
+ const tildeDir = projectDir.startsWith(home) ? "~" + projectDir.slice(home.length) : projectDir;
8805
+ const shortDir = tildeDir.length > 38 ? "\u2026" + tildeDir.slice(-37) : tildeDir;
8806
+ const items = summaries.map((s) => ({
8807
+ label: s.label,
8808
+ hint: s.hint,
8809
+ value: s.id
8810
+ }));
8811
+ items.push({ label: "\uFF0B Start a new session", value: null });
8812
+ const whereLabel = session.mode === "remote" && session.runner ? `${session.runner.name}:${shortenPath(session.remotePath ?? "")}` : shortDir;
8813
+ const picked = await tui.select(
8814
+ `${c4.bold}${gradientText("Resume a session")}${c4.reset} ${c4.gray}${whereLabel}${c4.reset} ${c4.dim}\u2191\u2193 \xB7 enter \xB7 esc${c4.reset}`,
8815
+ items
8816
+ );
8817
+ if (picked === void 0) {
8818
+ if (remoteTargets.length > 0) {
8819
+ step = "project";
8820
+ continue;
8821
+ }
8822
+ process.exit(0);
8823
+ }
8824
+ if (typeof picked === "string") {
8825
+ threadId = picked;
8826
+ resumed = true;
8827
+ const threadAgent = existing.find((t) => t.id === picked)?.agent_id;
8828
+ selectedAgent = threadAgent === OPENSAMA_AGENT_ID ? OPENSAMA_AGENT_ID : AGENT_ID;
8829
+ if (selectedAgent === OPENSAMA_AGENT_ID) {
8830
+ const ok = await ensureSamaAuth(tui, api);
8831
+ if (!ok) continue;
8832
+ }
8833
+ break flow;
8834
+ }
8835
+ historySeed = existing[0]?.id;
8836
+ step = "agent";
8351
8837
  } else {
8838
+ if (!agentOverride) {
8839
+ const picked = await tui.select(
8840
+ `${c4.bold}${gradientText("Which agent?")}${c4.reset} ${c4.dim}\u2191\u2193 \xB7 enter \xB7 esc${c4.reset}`,
8841
+ AGENT_CHOICES.map((choice) => ({
8842
+ label: choice.title,
8843
+ hint: choice.description,
8844
+ value: choice.id,
8845
+ disabled: choice.disabled
8846
+ }))
8847
+ );
8848
+ if (!picked) {
8849
+ if (hadResumeMenu) {
8850
+ step = "thread";
8851
+ continue;
8852
+ }
8853
+ if (remoteTargets.length > 0) {
8854
+ step = "project";
8855
+ continue;
8856
+ }
8857
+ process.exit(0);
8858
+ }
8859
+ selectedAgent = picked;
8860
+ }
8861
+ if (selectedAgent === OPENSAMA_AGENT_ID) {
8862
+ const ok = await ensureSamaAuth(tui, api);
8863
+ if (!ok) {
8864
+ if (agentOverride) process.exit(0);
8865
+ continue;
8866
+ }
8867
+ }
8352
8868
  threadId = await createSessionThread();
8353
- historySeed = existing[0]?.id;
8869
+ break flow;
8354
8870
  }
8355
- } else {
8356
- threadId = await createSessionThread();
8357
8871
  }
8358
8872
  for (; ; ) {
8359
- await runInteractive(tui, api, threadId, projectDir, machine, resumed, session, historySeed);
8873
+ const agentTitle = AGENT_CHOICES.find((a) => a.id === selectedAgent)?.title ?? selectedAgent;
8874
+ await runInteractive(tui, api, threadId, projectDir, machine, resumed, session, agentTitle, historySeed);
8360
8875
  historySeed = threadId;
8361
8876
  threadId = await createSessionThread();
8362
8877
  await api.kvSet(threadId, "lease_supersedes", historySeed);
@@ -8372,7 +8887,7 @@ async function ensureSamaAuth(tui, api) {
8372
8887
  checking.stop();
8373
8888
  if (already) return true;
8374
8889
  const picked = await tui.select(
8375
- `${c3.bold}${gradientText("Authenticate with ChatGPT")}${c3.reset} ${c3.dim}Sama One runs on OpenAI using your own ChatGPT Pro account${c3.reset}`,
8890
+ `${c4.bold}${gradientText("Authenticate with ChatGPT")}${c4.reset} ${c4.dim}Sama One runs on OpenAI using your own ChatGPT Pro account${c4.reset}`,
8376
8891
  [
8377
8892
  {
8378
8893
  label: "Continue with ChatGPT",
@@ -8385,7 +8900,7 @@ async function ensureSamaAuth(tui, api) {
8385
8900
  if (picked !== "continue") return false;
8386
8901
  openUrl("https://standardcode.ai/app?connect=sama");
8387
8902
  tui.print(
8388
- `${c3.dim}Finish connecting ChatGPT in the browser \u2014 waiting here for the authorization to land on your account\u2026${c3.reset}`
8903
+ `${c4.dim}Finish connecting ChatGPT in the browser \u2014 waiting here for the authorization to land on your account\u2026${c4.reset}`
8389
8904
  );
8390
8905
  const waiting = startLoader("Waiting for your ChatGPT authorization");
8391
8906
  const deadline = Date.now() + 5 * 60 * 1e3;
@@ -8394,20 +8909,20 @@ async function ensureSamaAuth(tui, api) {
8394
8909
  if (await api.openSamaStatus().catch(() => false)) {
8395
8910
  waiting.stop();
8396
8911
  tui.print(
8397
- `${c3.green}\u2713${c3.reset} ChatGPT connected \u2014 Sama One is now unlocked on your account (terminal, web, and macOS app).`
8912
+ `${c4.green}\u2713${c4.reset} ChatGPT connected \u2014 Sama One is now unlocked on your account (terminal, web, and macOS app).`
8398
8913
  );
8399
8914
  return true;
8400
8915
  }
8401
8916
  }
8402
8917
  waiting.stop();
8403
8918
  tui.print(
8404
- `${c3.yellow}Still not connected.${c3.reset} Finish the flow at ${c3.teal}standardcode.ai/app${c3.reset} and pick Sama One again.`
8919
+ `${c4.yellow}Still not connected.${c4.reset} Finish the flow at ${c4.teal}standardcode.ai/app${c4.reset} and pick Sama One again.`
8405
8920
  );
8406
8921
  return false;
8407
8922
  }
8408
8923
  async function runAgentSwitchMenu(tui, api, threadId) {
8409
8924
  const picked = await tui.select(
8410
- `${c3.bold}${gradientText("Switch agent")}${c3.reset} ${c3.dim}takes effect on the next message${c3.reset}`,
8925
+ `${c4.bold}${gradientText("Switch agent")}${c4.reset} ${c4.dim}takes effect on the next message${c4.reset}`,
8411
8926
  AGENT_CHOICES.map((choice) => ({
8412
8927
  label: choice.title,
8413
8928
  hint: choice.description,
@@ -8423,41 +8938,68 @@ async function runAgentSwitchMenu(tui, api, threadId) {
8423
8938
  const title = AGENT_CHOICES.find((choice) => choice.id === picked)?.title ?? picked;
8424
8939
  try {
8425
8940
  await api.setThreadAgent(threadId, picked);
8426
- tui.print(`${c3.green}\u2713${c3.reset} Session handed to ${c3.bold}${title}${c3.reset} \u2014 applies from your next message.`);
8941
+ tui.setAgentLabel(title);
8942
+ tui.print(`${c4.green}\u2713${c4.reset} Session handed to ${c4.bold}${title}${c4.reset} \u2014 applies from your next message.`);
8427
8943
  } catch (e) {
8428
8944
  tui.print(
8429
- `${c3.red}\u2717 couldn't switch agent:${c3.reset} ${c3.gray}${e instanceof Error ? e.message : String(e)}${c3.reset}`
8945
+ `${c4.red}\u2717 couldn't switch agent:${c4.reset} ${c4.gray}${e instanceof Error ? e.message : String(e)}${c4.reset}`
8430
8946
  );
8431
8947
  }
8432
8948
  }
8949
+ async function pickLocalProject(tui, api, self, cwd, machineName) {
8950
+ const NEW = "__new__";
8951
+ const label = self ? machineDisplayName(self) : machineName;
8952
+ const others = Object.entries(self?.projects ?? {}).filter(([dir2]) => dir2 !== cwd).sort((a, b) => (b[1]?.last_used_at ?? 0) - (a[1]?.last_used_at ?? 0));
8953
+ const labels = projectDisplayLabels({ ...self?.projects ?? {}, [cwd]: self?.projects?.[cwd] });
8954
+ const items = [
8955
+ {
8956
+ label: labels.get(cwd) ?? projectDisplayName(cwd, self?.projects?.[cwd]),
8957
+ hint: "current directory",
8958
+ detail: shortenPath(cwd, 60),
8959
+ value: cwd
8960
+ },
8961
+ ...others.map(([dir2, p]) => ({
8962
+ label: labels.get(dir2) ?? projectDisplayName(dir2, p),
8963
+ hint: p?.last_used_at ? relativeTime(p.last_used_at / 1e3) : "",
8964
+ detail: shortenPath(dir2, 60),
8965
+ value: dir2
8966
+ })),
8967
+ { label: "\uFF0B New project", hint: "browse or create a directory", value: NEW }
8968
+ ];
8969
+ const picked = await tui.select(
8970
+ `${c4.bold}${gradientText(`Project on ${label}`)}${c4.reset} ${c4.dim}\u2191\u2193 \xB7 enter \xB7 esc${c4.reset}`,
8971
+ items,
8972
+ { spaced: true }
8973
+ );
8974
+ if (!picked) return null;
8975
+ if (picked !== NEW) return picked;
8976
+ return await pickDirectory(tui, localBrowseBackend(label), { startPath: cwd, loader: startLoader });
8977
+ }
8433
8978
  async function pickRemoteProject(tui, api, runner) {
8434
8979
  const ENTER_PATH = "__enter_path__";
8435
8980
  const projects = Object.entries(runner.projects).sort(
8436
8981
  (a, b) => (b[1]?.last_used_at ?? 0) - (a[1]?.last_used_at ?? 0)
8437
8982
  );
8438
- const items = projects.map(([dir2, p]) => ({
8439
- label: shortenPath(dir2, 48),
8440
- hint: p?.last_used_at ? relativeTime(p.last_used_at / 1e3) : "",
8441
- value: dir2
8442
- }));
8443
- items.push({ label: `\uFF0B Another path on ${runner.name}\u2026`, hint: "type a directory", value: ENTER_PATH });
8983
+ const labels = projectDisplayLabels(runner.projects);
8984
+ const items = projects.map(
8985
+ ([dir2, p]) => ({
8986
+ label: labels.get(dir2) ?? projectDisplayName(dir2, p),
8987
+ hint: p?.last_used_at ? relativeTime(p.last_used_at / 1e3) : "",
8988
+ detail: shortenPath(dir2, 60),
8989
+ value: dir2
8990
+ })
8991
+ );
8992
+ items.push({ label: "\uFF0B New project", hint: "browse or create a directory", value: ENTER_PATH });
8444
8993
  const picked = await tui.select(
8445
- `${c3.bold}${gradientText(`Project on ${runner.name}`)}${c3.reset} ${c3.dim}\u2191\u2193 \xB7 enter \xB7 esc${c3.reset}`,
8446
- items
8994
+ `${c4.bold}${gradientText(`Project on ${runner.name}`)}${c4.reset} ${c4.dim}\u2191\u2193 \xB7 enter \xB7 esc${c4.reset}`,
8995
+ items,
8996
+ { spaced: true }
8447
8997
  );
8448
8998
  if (!picked) return null;
8449
8999
  if (picked !== ENTER_PATH) return picked;
8450
- const typed = await tui.prompt(
8451
- `Directory on ${runner.name} (absolute, created if missing)`,
8452
- "~/projects/my-app"
8453
- );
8454
- if (!typed) return null;
8455
- const trimmed = typed.trim();
8456
- if (!trimmed.startsWith("/") && !trimmed.startsWith("~")) {
8457
- tui.print(`${c3.yellow}Use an absolute path (starting with / or ~).${c3.reset}`);
8458
- return null;
8459
- }
8460
- return await resolveRemoteProjectPath(api, runner.id, trimmed);
9000
+ return await pickDirectory(tui, remoteBrowseBackend(api, runner.id, runner.name), {
9001
+ loader: startLoader
9002
+ });
8461
9003
  }
8462
9004
  function isSilentMessage(m) {
8463
9005
  return m?.silent === true || m?.metadata?.silent === true;
@@ -8474,14 +9016,15 @@ async function summarizeThreads(api, threads) {
8474
9016
  }
8475
9017
  const label = preview ? preview.length > 64 ? preview.slice(0, 63) + "\u2026" : preview : "(empty session)";
8476
9018
  const when = t.created_at ? relativeTime(t.created_at) : "";
8477
- const hint = [t.id.slice(0, 8), when].filter(Boolean).join(" \xB7 ");
9019
+ const agentTag = t.agent_id === OPENSAMA_AGENT_ID ? AGENT_CHOICES.find((a) => a.id === OPENSAMA_AGENT_ID)?.title ?? "Sama One" : "";
9020
+ const hint = [t.id.slice(0, 8), when, agentTag].filter(Boolean).join(" \xB7 ");
8478
9021
  return { id: t.id, label, hint };
8479
9022
  })
8480
9023
  );
8481
9024
  }
8482
9025
  function subagentLabel(s, titles) {
8483
9026
  const agentName = (s.agent_name || "").trim();
8484
- const title = (s.title || "").trim() || titles.get(agentName) || (agentName ? agentName.replace(/[_-]+/g, " ").replace(/\b\w/g, (c4) => c4.toUpperCase()) : "Subagent");
9027
+ const title = (s.title || "").trim() || titles.get(agentName) || (agentName ? agentName.replace(/[_-]+/g, " ").replace(/\b\w/g, (c5) => c5.toUpperCase()) : "Subagent");
8485
9028
  const tagged = (s.threadName || "").trim();
8486
9029
  return tagged ? `${title} \xB7 ${tagged}` : title;
8487
9030
  }
@@ -8504,8 +9047,8 @@ async function printHistory(api, threadId, tui) {
8504
9047
  ).sort((a, b) => (a.created_at ?? 0) - (b.created_at ?? 0));
8505
9048
  if (!convo.length) return;
8506
9049
  const shown = convo.slice(-24);
8507
- tui.print(`${c3.dim}\u2500\u2500 resuming session \xB7 ${convo.length} message${convo.length === 1 ? "" : "s"} \u2500\u2500${c3.reset}`);
8508
- if (shown.length < convo.length) tui.print(`${c3.dim} \u2026 earlier messages omitted${c3.reset}`);
9050
+ tui.print(`${c4.dim}\u2500\u2500 resuming session \xB7 ${convo.length} message${convo.length === 1 ? "" : "s"} \u2500\u2500${c4.reset}`);
9051
+ if (shown.length < convo.length) tui.print(`${c4.dim} \u2026 earlier messages omitted${c4.reset}`);
8509
9052
  for (const m of shown) {
8510
9053
  if (m.metadata?.user_command) {
8511
9054
  printCommandBlock(
@@ -8522,9 +9065,12 @@ async function printHistory(api, threadId, tui) {
8522
9065
  else printAssistant(tui, text);
8523
9066
  }
8524
9067
  }
8525
- async function runInteractive(tui, api, threadId, projectDir, machine, resumed, session, historySeedThreadId) {
9068
+ async function runInteractive(tui, api, threadId, projectDir, machine, resumed, session, agentTitle, historySeedThreadId) {
8526
9069
  const remote = session.mode === "remote";
8527
9070
  const runnerName = session.runner?.name ?? "the remote machine";
9071
+ let currentOwner = null;
9072
+ let ownershipKnown = false;
9073
+ const describeExecOwner = (o) => o?.client_kind === "daemon" ? "the daemon on this machine" : o?.client_name || "another client";
8528
9074
  const registry = new ProcessRegistry(api, threadId, machine);
8529
9075
  const refreshBgCount = () => {
8530
9076
  void registry.runningCount().then((n) => tui.setBackgroundCount(n)).catch(() => {
@@ -8550,6 +9096,7 @@ async function runInteractive(tui, api, threadId, projectDir, machine, resumed,
8550
9096
  saveApprovals(api, threadId, perm);
8551
9097
  const attaching = startLoader("Attaching to thread");
8552
9098
  let busy = false;
9099
+ let optimisticBusyUntil = 0;
8553
9100
  let sharedMessaging = emptySharedMessagingSnapshot();
8554
9101
  let mirroredDraftRefs = [];
8555
9102
  let sharedMessagingReady = false;
@@ -8609,26 +9156,28 @@ async function runInteractive(tui, api, threadId, projectDir, machine, resumed,
8609
9156
  if (detail) for (const d of detail) tui.print(d);
8610
9157
  refreshBgCount();
8611
9158
  },
8612
- onOwnership: (isOwner, owner) => {
9159
+ onOwnership: (isOwner, owner2) => {
9160
+ const wasKnown = ownershipKnown;
9161
+ ownershipKnown = true;
9162
+ currentOwner = owner2;
8613
9163
  if (isOwner) {
8614
9164
  bridge?.setClaim("takeover");
8615
9165
  exec?.writeSessionInfo();
8616
- } else {
8617
- tui.print(
8618
- `${c3.yellow}\u26A0 Another client${owner?.client_name ? ` (${owner.client_name})` : ""} is executing tools for this session \u2014 this terminal is watching.${c3.reset}`
8619
- );
9166
+ if (wasKnown) tui.print(`${c4.dim}Tool execution moved to this terminal.${c4.reset}`);
9167
+ } else if (wasKnown) {
9168
+ tui.print(`${c4.dim}Tool execution moved to ${describeExecOwner(owner2)}.${c4.reset}`);
8620
9169
  }
8621
9170
  },
8622
9171
  onClaimRefused: (reason) => {
8623
9172
  if (reason === "in_flight") {
8624
9173
  tui.print(
8625
- `${c3.yellow}\u26A0 The session's current client is mid-operation \u2014 execution can't move here until it finishes. Watching for now.${c3.reset}`
9174
+ `${c4.dim}The session's current client is mid-operation \u2014 execution stays there until it finishes.${c4.reset}`
8626
9175
  );
8627
9176
  }
8628
9177
  },
8629
9178
  onSuperseded: () => {
8630
9179
  tui.print(
8631
- `${c3.yellow}\u26A0 Another Standard Code process on this machine took over this session \u2014 this terminal is watching.${c3.reset}`
9180
+ `${c4.yellow}\u26A0 Another Standard Code process on this machine took over this session \u2014 this terminal is watching.${c4.reset}`
8632
9181
  );
8633
9182
  },
8634
9183
  onStatus: (id, summary) => {
@@ -8786,7 +9335,7 @@ why: ${req.requestPermission}` : ""}`,
8786
9335
  const logout = async () => {
8787
9336
  deleteCredential(api.origin);
8788
9337
  const instanceHost = api.origin.replace(/^https?:\/\//, "");
8789
- tui.print(`${c3.gray}Signed out \u2014 removed the saved token for ${c3.teal}${instanceHost}${c3.reset}${c3.gray}. Run standardcode to sign in again.${c3.reset}`);
9338
+ tui.print(`${c4.gray}Signed out \u2014 removed the saved token for ${c4.teal}${instanceHost}${c4.reset}${c4.gray}. Run standardcode to sign in again.${c4.reset}`);
8790
9339
  await quit();
8791
9340
  };
8792
9341
  const bgMgr = {
@@ -8794,7 +9343,7 @@ why: ${req.requestPermission}` : ""}`,
8794
9343
  stop: async (id) => {
8795
9344
  if (!host) {
8796
9345
  tui.print(
8797
- `${c3.gray}That process runs on ${runnerName} \u2014 ask the agent to stop it (it manages processes there).${c3.reset}`
9346
+ `${c4.gray}That process runs on ${runnerName} \u2014 ask the agent to stop it (it manages processes there).${c4.reset}`
8798
9347
  );
8799
9348
  return;
8800
9349
  }
@@ -8875,7 +9424,7 @@ why: ${req.requestPermission}` : ""}`,
8875
9424
  } catch (error) {
8876
9425
  if (!sharedMessagingReady && !sharedMessagingUnavailableShown) {
8877
9426
  sharedMessagingUnavailableShown = true;
8878
- tui.print(`${c3.dim}shared messaging unavailable: ${error instanceof Error ? error.message : String(error)}${c3.reset}`);
9427
+ tui.print(`${c4.dim}shared messaging unavailable: ${error instanceof Error ? error.message : String(error)}${c4.reset}`);
8879
9428
  }
8880
9429
  }
8881
9430
  };
@@ -8885,7 +9434,7 @@ why: ${req.requestPermission}` : ""}`,
8885
9434
  applySharedMessaging(snapshot, false);
8886
9435
  return true;
8887
9436
  }).catch((error) => {
8888
- tui.print(`${c3.dim}shared messaging failed: ${error instanceof Error ? error.message : String(error)}${c3.reset}`);
9437
+ tui.print(`${c4.dim}shared messaging failed: ${error instanceof Error ? error.message : String(error)}${c4.reset}`);
8889
9438
  return false;
8890
9439
  });
8891
9440
  const appendSharedPending = (text, images, refs = []) => applySharedMutation(api.appendPendingInput(threadId, {
@@ -8913,28 +9462,32 @@ why: ${req.requestPermission}` : ""}`,
8913
9462
  tui.printUserMessage(text || `\u{1F4CE} ${refs.length + images.length} attachment(s)`);
8914
9463
  const key = text.trim();
8915
9464
  pendingSent.set(key, (pendingSent.get(key) ?? 0) + 1);
9465
+ busy = true;
9466
+ optimisticBusyUntil = Date.now() + 8e3;
9467
+ tui.setWorking(true);
8916
9468
  try {
8917
9469
  await api.sendMessage(threadId, text, [...refs, ...toAttachments(images)]);
8918
9470
  } catch (e) {
8919
9471
  const n = (pendingSent.get(key) ?? 1) - 1;
8920
9472
  if (n > 0) pendingSent.set(key, n);
8921
9473
  else pendingSent.delete(key);
8922
- tui.print(`${c3.dim}failed to send: ${e instanceof Error ? e.message : String(e)}${c3.reset}`);
9474
+ busy = false;
9475
+ optimisticBusyUntil = 0;
9476
+ tui.setWorking(false);
9477
+ tui.print(`${c4.dim}failed to send: ${e instanceof Error ? e.message : String(e)}${c4.reset}`);
8923
9478
  return false;
8924
9479
  }
8925
- busy = true;
8926
- tui.setWorking(true);
8927
9480
  return true;
8928
9481
  };
8929
9482
  const whereLabel = remote ? runnerName : "this machine";
8930
9483
  let bangRunning = false;
8931
9484
  const runBangCommand = async (command) => {
8932
9485
  if (bangRunning) {
8933
- tui.print(`${c3.dim}a command is already running \u2014 one at a time.${c3.reset}`);
9486
+ tui.print(`${c4.dim}a command is already running \u2014 one at a time.${c4.reset}`);
8934
9487
  return;
8935
9488
  }
8936
9489
  bangRunning = true;
8937
- tui.print(`${c3.magenta}!${c3.reset} ${c3.dim}running on ${whereLabel}\u2026${c3.reset}`);
9490
+ tui.print(`${c4.magenta}!${c4.reset} ${c4.dim}running on ${whereLabel}\u2026${c4.reset}`);
8938
9491
  try {
8939
9492
  const res = await api.runCommand(threadId, command);
8940
9493
  if (res.messageId) shownIds.add(res.messageId);
@@ -8948,7 +9501,7 @@ why: ${req.requestPermission}` : ""}`,
8948
9501
  const openPendingMenu = async () => {
8949
9502
  const items = sharedMessaging.pending.items;
8950
9503
  if (!items.length) {
8951
- tui.print(`${c3.dim}No pending messages.${c3.reset}`);
9504
+ tui.print(`${c4.dim}No pending messages.${c4.reset}`);
8952
9505
  return;
8953
9506
  }
8954
9507
  const picked = await tui.select("Pending messages", items.map((item, index) => ({
@@ -8978,16 +9531,16 @@ why: ${req.requestPermission}` : ""}`,
8978
9531
  try {
8979
9532
  await api.compact(threadId);
8980
9533
  } catch (err) {
8981
- tui.print(`${c3.red}\u2717${c3.reset} couldn't start compaction: ${err.message}`);
9534
+ tui.print(`${c4.red}\u2717${c4.reset} couldn't start compaction: ${err.message}`);
8982
9535
  }
8983
9536
  };
8984
9537
  const runAccountCommand = async () => {
8985
- tui.print(`${c3.gray}Opening your account\u2026${c3.reset}`);
9538
+ tui.print(`${c4.gray}Opening your account\u2026${c4.reset}`);
8986
9539
  const link = await api.accountLink(threadId).catch(() => null);
8987
9540
  const target = link?.url ?? "https://standardcode.ai/account";
8988
9541
  openUrl(target);
8989
9542
  tui.print(
8990
- link?.preauthed ? `${c3.gray}\u2192 account dashboard opened in your browser (signed in)${c3.reset}` : `${c3.gray}\u2192 opened ${target} \u2014 sign in with your account email${c3.reset}`
9543
+ link?.preauthed ? `${c4.gray}\u2192 account dashboard opened in your browser (signed in)${c4.reset}` : `${c4.gray}\u2192 opened ${target} \u2014 sign in with your account email${c4.reset}`
8991
9544
  );
8992
9545
  };
8993
9546
  const ordinal = (n) => {
@@ -8998,24 +9551,24 @@ why: ${req.requestPermission}` : ""}`,
8998
9551
  const renderUpgradePanel = (q) => {
8999
9552
  const dots = [];
9000
9553
  for (let i = 0; i < q.max; i++) {
9001
- if (i < q.current) dots.push(`${c3.teal}\u25CF${c3.reset}`);
9002
- else if (i === q.current) dots.push(`${c3.bold}${gradientText("\uFF0B")}${c3.reset}`);
9003
- else dots.push(`${c3.dim}\xB7${c3.reset}`);
9554
+ if (i < q.current) dots.push(`${c4.teal}\u25CF${c4.reset}`);
9555
+ else if (i === q.current) dots.push(`${c4.bold}${gradientText("\uFF0B")}${c4.reset}`);
9556
+ else dots.push(`${c4.dim}\xB7${c4.reset}`);
9004
9557
  }
9005
9558
  const cost = fmtCost(q);
9006
9559
  const lines = [
9007
9560
  "",
9008
- `${c3.bold}${gradientText("\u2726 Add a parallel session")}${c3.reset}`,
9561
+ `${c4.bold}${gradientText("\u2726 Add a parallel session")}${c4.reset}`,
9009
9562
  "",
9010
- `${dots.join(" ")} ${c3.dim}${q.current} of ${q.current} session${q.current === 1 ? "" : "s"} in use${c3.reset}`
9563
+ `${dots.join(" ")} ${c4.dim}${q.current} of ${q.current} session${q.current === 1 ? "" : "s"} in use${c4.reset}`
9011
9564
  ];
9012
9565
  if (q.ends_trial) {
9013
9566
  lines.push(
9014
- `${c3.yellow}Your $5 trial covers 1 session. Adding a ${ordinal(q.sessions)} ends the trial${c3.reset}`,
9015
- `${c3.yellow}and starts your $49/mo plan now${cost ? ` \u2014 ${c3.bold}${cost} charged today${c3.reset}${c3.yellow}` : ""}.${c3.reset}`
9567
+ `${c4.yellow}Your $5 trial covers 1 session. Adding a ${ordinal(q.sessions)} ends the trial${c4.reset}`,
9568
+ `${c4.yellow}and starts your $49/mo plan now${cost ? ` \u2014 ${c4.bold}${cost} charged today${c4.reset}${c4.yellow}` : ""}.${c4.reset}`
9016
9569
  );
9017
9570
  } else if (cost) {
9018
- lines.push(`Add a ${ordinal(q.sessions)} session at $49/mo \u2014 ${c3.bold}${cost} charged now${c3.reset}.`);
9571
+ lines.push(`Add a ${ordinal(q.sessions)} session at $49/mo \u2014 ${c4.bold}${cost} charged now${c4.reset}.`);
9019
9572
  } else {
9020
9573
  lines.push(`Add a ${ordinal(q.sessions)} session at $49/mo \u2014 prorated on your next invoice.`);
9021
9574
  }
@@ -9028,7 +9581,7 @@ why: ${req.requestPermission}` : ""}`,
9028
9581
  try {
9029
9582
  if (opts.auto) {
9030
9583
  tui.print(
9031
- `${c3.yellow}You're out of simultaneous sessions \u2014 another Standard Code session is using your slot.${c3.reset}`
9584
+ `${c4.yellow}You're out of simultaneous sessions \u2014 another Standard Code session is using your slot.${c4.reset}`
9032
9585
  );
9033
9586
  }
9034
9587
  const quote = await api.sessionsQuote(threadId);
@@ -9036,16 +9589,16 @@ why: ${req.requestPermission}` : ""}`,
9036
9589
  const link = await api.accountLink(threadId).catch(() => null);
9037
9590
  const target = link?.url ?? "https://standardcode.ai/account";
9038
9591
  tui.print(
9039
- `${c3.gray}Close the other session (its slot frees within ~90s) \u2014 or add another simultaneous session to your plan, then resend your message.${c3.reset}`
9592
+ `${c4.gray}Close the other session (its slot frees within ~90s) \u2014 or add another simultaneous session to your plan, then resend your message.${c4.reset}`
9040
9593
  );
9041
9594
  openUrl(target);
9042
- tui.print(`${c3.gray}\u2192 opened ${target} to manage your plan${c3.reset}`);
9595
+ tui.print(`${c4.gray}\u2192 opened ${target} to manage your plan${c4.reset}`);
9043
9596
  return;
9044
9597
  }
9045
9598
  if (quote.current >= quote.max) {
9046
9599
  tui.print(
9047
- `${c3.yellow}You're at the maximum of ${quote.max} parallel session${quote.max === 1 ? "" : "s"}.${c3.reset}
9048
- ${c3.gray}Close another session (its slot frees within ~90s), then resend your message.${c3.reset}`
9600
+ `${c4.yellow}You're at the maximum of ${quote.max} parallel session${quote.max === 1 ? "" : "s"}.${c4.reset}
9601
+ ${c4.gray}Close another session (its slot frees within ~90s), then resend your message.${c4.reset}`
9049
9602
  );
9050
9603
  return;
9051
9604
  }
@@ -9057,25 +9610,25 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
9057
9610
  { label: "Not now", value: "no" }
9058
9611
  ]);
9059
9612
  if (choice !== "go") {
9060
- tui.print(`${c3.gray}No change made \u2014 you can upgrade anytime with /upgrade.${c3.reset}`);
9613
+ tui.print(`${c4.gray}No change made \u2014 you can upgrade anytime with /upgrade.${c4.reset}`);
9061
9614
  return;
9062
9615
  }
9063
- tui.print(`${c3.gray}Applying\u2026${c3.reset}`);
9616
+ tui.print(`${c4.gray}Applying\u2026${c4.reset}`);
9064
9617
  let applied;
9065
9618
  try {
9066
9619
  applied = await api.sessionsUpgrade(threadId, quote.sessions);
9067
9620
  } catch (e) {
9068
- tui.print(`${c3.red}\u2717${c3.reset} Upgrade failed: ${e instanceof Error ? e.message : String(e)}`);
9621
+ tui.print(`${c4.red}\u2717${c4.reset} Upgrade failed: ${e instanceof Error ? e.message : String(e)}`);
9069
9622
  return;
9070
9623
  }
9071
9624
  if (!applied?.ok) {
9072
- tui.print(`${c3.red}\u2717${c3.reset} Upgrade failed: ${applied?.error ?? "unknown error"}`);
9625
+ tui.print(`${c4.red}\u2717${c4.reset} Upgrade failed: ${applied?.error ?? "unknown error"}`);
9073
9626
  return;
9074
9627
  }
9075
9628
  const n = applied.sessions ?? quote.sessions;
9076
- tui.print(`${c3.green}\u2713${c3.reset} ${c3.bold}${gradientText(`You now have ${n} parallel session${n === 1 ? "" : "s"}.`)}${c3.reset}`);
9629
+ tui.print(`${c4.green}\u2713${c4.reset} ${c4.bold}${gradientText(`You now have ${n} parallel session${n === 1 ? "" : "s"}.`)}${c4.reset}`);
9077
9630
  if (opts.auto && lastSent) {
9078
- tui.print(`${c3.gray}Continuing\u2026${c3.reset}`);
9631
+ tui.print(`${c4.gray}Continuing\u2026${c4.reset}`);
9079
9632
  await sendNow(lastSent.text, lastSent.images, lastSent.refs);
9080
9633
  }
9081
9634
  } finally {
@@ -9217,7 +9770,7 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
9217
9770
  const submitComposer = async (text, images, steer) => {
9218
9771
  const draftRefs = mirroredDraftRefs;
9219
9772
  if (!sharedMessagingReady && (busy || steer || editingPendingId !== null)) {
9220
- tui.print(`${c3.dim}Restoring shared message state \u2014 try again in a moment.${c3.reset}`);
9773
+ tui.print(`${c4.dim}Restoring shared message state \u2014 try again in a moment.${c4.reset}`);
9221
9774
  tui.setExternalAttachmentNames(draftRefs.map((attachment) => attachment.name));
9222
9775
  tui.setInput(text, images);
9223
9776
  return;
@@ -9240,7 +9793,7 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
9240
9793
  const item = sharedMessaging.pending.items.find((candidate) => candidate.id === pendingId);
9241
9794
  editingPendingId = null;
9242
9795
  if (!item) {
9243
- tui.print(`${c3.dim}That pending message was already dispatched or dismissed.${c3.reset}`);
9796
+ tui.print(`${c4.dim}That pending message was already dispatched or dismissed.${c4.reset}`);
9244
9797
  return;
9245
9798
  }
9246
9799
  const updated = await editSharedPending(item, text, images, draftRefs);
@@ -9253,7 +9806,7 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
9253
9806
  return;
9254
9807
  }
9255
9808
  if (steer) {
9256
- tui.print(`${c3.yellow}\u21AA steering at the next safe model boundary:${c3.reset} ${text}`);
9809
+ tui.print(`${c4.yellow}\u21AA steering at the next safe model boundary:${c4.reset} ${text}`);
9257
9810
  if (!await steerSharedInput(text, images, draftRefs)) {
9258
9811
  mirroredDraftRefs = draftRefs;
9259
9812
  tui.setExternalAttachmentNames(draftRefs.map((attachment) => attachment.name));
@@ -9263,7 +9816,7 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
9263
9816
  }
9264
9817
  if (busy) {
9265
9818
  if (await appendSharedPending(text, images, draftRefs)) {
9266
- tui.print(`${c3.gray}\u23F3 pending:${c3.reset} ${text} ${c3.dim}(/queue to edit, steer, or dismiss)${c3.reset}`);
9819
+ tui.print(`${c4.gray}\u23F3 pending:${c4.reset} ${text} ${c4.dim}(/queue to edit, steer, or dismiss)${c4.reset}`);
9267
9820
  } else {
9268
9821
  mirroredDraftRefs = draftRefs;
9269
9822
  tui.setExternalAttachmentNames(draftRefs.map((attachment) => attachment.name));
@@ -9286,17 +9839,17 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
9286
9839
  tui.onInterrupt = () => {
9287
9840
  const firstPending = sharedMessaging.pending.items[0];
9288
9841
  if (!busy && firstPending) {
9289
- tui.print(`${c3.yellow}\u21AA steering the first pending message\u2026${c3.reset}`);
9842
+ tui.print(`${c4.yellow}\u21AA steering the first pending message\u2026${c4.reset}`);
9290
9843
  void promoteSharedPending(firstPending);
9291
9844
  return;
9292
9845
  }
9293
9846
  if (busy) {
9294
9847
  if (!sharedMessagingReady) {
9295
- tui.print(`${c3.dim}Shared messaging is not connected; the session was not stopped.${c3.reset}`);
9848
+ tui.print(`${c4.dim}Shared messaging is not connected; the session was not stopped.${c4.reset}`);
9296
9849
  return;
9297
9850
  }
9298
9851
  const advancing = sharedMessaging.pending.items.length > 0;
9299
- tui.print(`${c3.yellow}${advancing ? "[stopping; next pending message will run]" : "[stopping at the next safe boundary]"}${c3.reset}`);
9852
+ tui.print(`${c4.yellow}${advancing ? "[stopping; next pending message will run]" : "[stopping at the next safe boundary]"}${c4.reset}`);
9300
9853
  void applySharedMutation(api.requestSharedStop(threadId, messagingOrigin));
9301
9854
  }
9302
9855
  };
@@ -9320,20 +9873,32 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
9320
9873
  void api.getGoal(threadId).then((g) => tui.setGoal(g)).catch(() => {
9321
9874
  });
9322
9875
  attaching.stop();
9876
+ const header = `${c4.bold}${c4.magenta}Standard Code${c4.reset} ${c4.dim}\u2014 ${agentTitle}${c4.reset}`;
9877
+ const remoteDaemonV = session.runner?.daemon?.version;
9878
+ const owner = currentOwner;
9879
+ let localOwnerDesc = ownsExecution ? "this terminal" : describeExecOwner(owner);
9880
+ if (!remote && !ownsExecution && owner?.client_kind === "daemon") {
9881
+ const selfRec = await loadMachine(api, session.identity.machine_id).catch(() => null);
9882
+ if (selfRec?.daemon?.version) localOwnerDesc = `the daemon on this machine (v${selfRec.daemon.version})`;
9883
+ }
9884
+ const execLine = remote ? `${c4.gray}tool execution:${c4.reset} daemon${remoteDaemonV ? ` v${remoteDaemonV}` : ""} on ${runnerName}` : `${c4.gray}tool execution:${c4.reset} ${localOwnerDesc}`;
9885
+ tui.setAgentLabel(agentTitle);
9323
9886
  tui.banner(
9324
9887
  remote ? [
9325
- `${c3.bold}${c3.magenta}Standard Code${c3.reset} ${c3.dim}\u2014 coding agent${c3.reset}`,
9326
- `${c3.gray}project:${c3.reset} ${session.remotePath ?? "?"} ${c3.teal}on ${runnerName}${c3.reset}`,
9327
- `${c3.gray}runs on:${c3.reset} ${runnerName} ${c3.dim}(daemon executes tools; you're watching from ${machine})${c3.reset} ${c3.gray}thread:${c3.reset} ${threadId.slice(0, 8)}`
9888
+ header,
9889
+ `${c4.gray}project:${c4.reset} ${session.remotePath ?? "?"} ${c4.teal}on ${runnerName}${c4.reset}`,
9890
+ `${c4.gray}machine:${c4.reset} ${runnerName} ${c4.gray}thread:${c4.reset} ${threadId.slice(0, 8)}`,
9891
+ execLine
9328
9892
  ] : [
9329
- `${c3.bold}${c3.magenta}Standard Code${c3.reset} ${c3.dim}\u2014 coding agent${c3.reset}`,
9330
- `${c3.gray}project:${c3.reset} ${projectDir}`,
9331
- `${c3.gray}machine:${c3.reset} ${machine} ${c3.gray}thread:${c3.reset} ${threadId.slice(0, 8)}`
9893
+ header,
9894
+ `${c4.gray}project:${c4.reset} ${projectDir}`,
9895
+ `${c4.gray}machine:${c4.reset} ${machine} ${c4.gray}thread:${c4.reset} ${threadId.slice(0, 8)}`,
9896
+ execLine
9332
9897
  ]
9333
9898
  );
9334
9899
  if (!remote && session.suggestDaemonInstall && process.platform !== "win32" && !serviceStatus().installed) {
9335
9900
  tui.print(
9336
- `${c3.dim}Tip: install the always-on daemon (${c3.reset}standardcode daemon install${c3.dim}) to start sessions on this machine from anywhere.${c3.reset}`
9901
+ `${c4.dim}Tip: install the always-on daemon (${c4.reset}standardcode daemon install${c4.dim}) to start sessions on this machine from anywhere.${c4.reset}`
9337
9902
  );
9338
9903
  }
9339
9904
  if (resumed) await printHistory(api, threadId, tui);
@@ -9344,17 +9909,17 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
9344
9909
  const runningProcs = (await registry.list()).filter((p) => p.status === "running");
9345
9910
  if (runningProcs.length) {
9346
9911
  tui.print(
9347
- `${c3.cyan}\u2699 ${runningProcs.length} background process${runningProcs.length === 1 ? "" : "es"} running:${c3.reset}`
9912
+ `${c4.cyan}\u2699 ${runningProcs.length} background process${runningProcs.length === 1 ? "" : "es"} running:${c4.reset}`
9348
9913
  );
9349
- for (const p of runningProcs) tui.print(`${c3.gray} ${p.id} ${p.description || p.command}${c3.reset}`);
9914
+ for (const p of runningProcs) tui.print(`${c4.gray} ${p.id} ${p.description || p.command}${c4.reset}`);
9350
9915
  }
9351
9916
  refreshBgCount();
9352
9917
  if (exec && ownsExecution) {
9353
9918
  for (const res of await exec.connectEnabledMcpServers()) {
9354
9919
  if (res.ok) {
9355
- tui.print(`${c3.cyan}\u26A1 MCP "${res.name}" connected${c3.reset} ${c3.gray}(${res.tools} tool${res.tools === 1 ? "" : "s"})${c3.reset}`);
9920
+ tui.print(`${c4.cyan}\u26A1 MCP "${res.name}" connected${c4.reset} ${c4.gray}(${res.tools} tool${res.tools === 1 ? "" : "s"})${c4.reset}`);
9356
9921
  } else {
9357
- tui.print(`${c3.red}\u26A0 MCP "${res.name}" failed:${c3.reset} ${c3.gray}${res.error}${c3.reset}`);
9922
+ tui.print(`${c4.red}\u26A0 MCP "${res.name}" failed:${c4.reset} ${c4.gray}${res.error}${c4.reset}`);
9358
9923
  }
9359
9924
  }
9360
9925
  }
@@ -9370,8 +9935,8 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
9370
9935
  try {
9371
9936
  const { choice, reason } = await tui.approval(
9372
9937
  `${request.summary}${request.permission ? `
9373
- ${c3.bold}why: ${request.permission}${c3.reset}` : ""}
9374
- ${c3.dim}runs on ${request.machine || runnerName}${c3.reset}`,
9938
+ ${c4.bold}why: ${request.permission}${c4.reset}` : ""}
9939
+ ${c4.dim}runs on ${request.machine || runnerName}${c4.reset}`,
9375
9940
  request.risk
9376
9941
  );
9377
9942
  answeredApprovals.add(request.tool_call_id);
@@ -9422,7 +9987,7 @@ ${c3.dim}runs on ${request.machine || runnerName}${c3.reset}`,
9422
9987
  continue;
9423
9988
  }
9424
9989
  if (m.role === "assistant" && text) printAssistant(tui, text);
9425
- else if (m.role === "system" && text) tui.print(`${c3.dim}${text}${c3.reset}`);
9990
+ else if (m.role === "system" && text) tui.print(`${c4.dim}${text}${c4.reset}`);
9426
9991
  else if (m.role === "user" && text) {
9427
9992
  const pending = pendingSent.get(text) ?? 0;
9428
9993
  if (pending > 0) {
@@ -9444,7 +10009,8 @@ ${c3.dim}runs on ${request.machine || runnerName}${c3.reset}`,
9444
10009
  activeSteps.clear();
9445
10010
  refreshStatus();
9446
10011
  }
9447
- busy = polledBusy;
10012
+ if (polledBusy) optimisticBusyUntil = 0;
10013
+ busy = polledBusy || Date.now() < optimisticBusyUntil;
9448
10014
  tui.setWorking(busy);
9449
10015
  if (!busy) {
9450
10016
  if (activeSteps.size) activeSteps.clear();
@@ -9515,16 +10081,16 @@ ${c3.dim}runs on ${request.machine || runnerName}${c3.reset}`,
9515
10081
  tui.setStep(null, 0);
9516
10082
  tui.setBackgroundCount(0);
9517
10083
  if (killed > 0) {
9518
- tui.print(`${c3.cyan}\u2699${c3.reset} Stopped ${killed} background process${killed === 1 ? "" : "es"}.`);
10084
+ tui.print(`${c4.cyan}\u2699${c4.reset} Stopped ${killed} background process${killed === 1 ? "" : "es"}.`);
9519
10085
  }
9520
- tui.print(`${c3.dim}\u2500\u2500 conversation cleared \u2014 starting a fresh session \u2500\u2500${c3.reset}`);
10086
+ tui.print(`${c4.dim}\u2500\u2500 conversation cleared \u2014 starting a fresh session \u2500\u2500${c4.reset}`);
9521
10087
  }
9522
10088
  async function runSkillsMenu(tui, skills) {
9523
10089
  let list;
9524
10090
  try {
9525
10091
  list = await skills.list();
9526
10092
  } catch (e) {
9527
- tui.print(`${c3.red}\u2717 couldn't load skills:${c3.reset} ${c3.gray}${e instanceof Error ? e.message : String(e)}${c3.reset}`);
10093
+ tui.print(`${c4.red}\u2717 couldn't load skills:${c4.reset} ${c4.gray}${e instanceof Error ? e.message : String(e)}${c4.reset}`);
9528
10094
  return;
9529
10095
  }
9530
10096
  const INSTALL = "__install__";
@@ -9535,7 +10101,7 @@ async function runSkillsMenu(tui, skills) {
9535
10101
  }));
9536
10102
  items.push({ label: "\uFF0B Install a skill\u2026", hint: "find & install", value: INSTALL });
9537
10103
  const picked = await tui.select(
9538
- `${c3.bold}Agent skills${c3.reset} ${c3.dim}(\u2191/\u2193 \xB7 enter \xB7 esc to close)${c3.reset}`,
10104
+ `${c4.bold}Agent skills${c4.reset} ${c4.dim}(\u2191/\u2193 \xB7 enter \xB7 esc to close)${c4.reset}`,
9539
10105
  items
9540
10106
  );
9541
10107
  if (!picked) return;
@@ -9548,8 +10114,8 @@ async function runSkillsMenu(tui, skills) {
9548
10114
  return;
9549
10115
  }
9550
10116
  const skill = list.find((s) => s.name === picked);
9551
- tui.print(`${c3.cyan}${skill.name}${c3.reset}${skill.version ? ` ${c3.dim}v${skill.version}${c3.reset}` : ""} ${c3.gray}\u2014 ${skill.description}${c3.reset}`);
9552
- const action = await tui.select(`${c3.bold}${picked}${c3.reset}`, [
10117
+ tui.print(`${c4.cyan}${skill.name}${c4.reset}${skill.version ? ` ${c4.dim}v${skill.version}${c4.reset}` : ""} ${c4.gray}\u2014 ${skill.description}${c4.reset}`);
10118
+ const action = await tui.select(`${c4.bold}${picked}${c4.reset}`, [
9553
10119
  skill.enabled ? { label: "Disable (hide from the agent)", value: "disable" } : { label: "Enable", value: "enable" },
9554
10120
  { label: "View files", value: "files" },
9555
10121
  { label: "Remove this skill", value: "remove" },
@@ -9558,20 +10124,20 @@ async function runSkillsMenu(tui, skills) {
9558
10124
  try {
9559
10125
  if (action === "enable" || action === "disable") {
9560
10126
  await skills.setEnabled(picked, action === "enable");
9561
- tui.print(`${c3.gray}${action}d ${picked}${c3.reset}`);
10127
+ tui.print(`${c4.gray}${action}d ${picked}${c4.reset}`);
9562
10128
  } else if (action === "files") {
9563
- for (const f of skill.files) tui.print(` ${c3.gray}${f}${c3.reset}`);
10129
+ for (const f of skill.files) tui.print(` ${c4.gray}${f}${c4.reset}`);
9564
10130
  } else if (action === "remove") {
9565
10131
  await skills.remove(picked);
9566
- tui.print(`${c3.gray}removed ${picked}${c3.reset}`);
10132
+ tui.print(`${c4.gray}removed ${picked}${c4.reset}`);
9567
10133
  }
9568
10134
  } catch (e) {
9569
- tui.print(`${c3.red}\u2717 ${e instanceof Error ? e.message : String(e)}${c3.reset}`);
10135
+ tui.print(`${c4.red}\u2717 ${e instanceof Error ? e.message : String(e)}${c4.reset}`);
9570
10136
  }
9571
10137
  }
9572
10138
  async function runLevelMenu(tui, perm) {
9573
10139
  const picked = await tui.select(
9574
- `${c3.bold}Auto-accept level${c3.reset} ${c3.dim}(\u2191/\u2193 \xB7 enter \xB7 shift-tab cycles)${c3.reset}`,
10140
+ `${c4.bold}Auto-accept level${c4.reset} ${c4.dim}(\u2191/\u2193 \xB7 enter \xB7 shift-tab cycles)${c4.reset}`,
9575
10141
  LEVELS.map((l) => ({
9576
10142
  label: levelLabel(l),
9577
10143
  hint: l === tui.level ? "current" : "",
@@ -9588,16 +10154,16 @@ async function runMachinesMenu(tui, api, self) {
9588
10154
  try {
9589
10155
  machines = await loadMachines(api);
9590
10156
  } catch (e) {
9591
- tui.print(`${c3.red}\u2717 couldn't load machines:${c3.reset} ${c3.gray}${e instanceof Error ? e.message : String(e)}${c3.reset}`);
10157
+ tui.print(`${c4.red}\u2717 couldn't load machines:${c4.reset} ${c4.gray}${e instanceof Error ? e.message : String(e)}${c4.reset}`);
9592
10158
  return;
9593
10159
  }
9594
10160
  if (!machines.length) {
9595
- tui.print(`${c3.gray}No machines registered yet. Run standardcode on a machine (or install its daemon) to register it.${c3.reset}`);
10161
+ tui.print(`${c4.gray}No machines registered yet. Run standardcode on a machine (or install its daemon) to register it.${c4.reset}`);
9596
10162
  return;
9597
10163
  }
9598
10164
  machines.sort((a, b) => (b.updated_at ?? 0) - (a.updated_at ?? 0));
9599
10165
  const picked = await tui.select(
9600
- `${c3.bold}Your machines${c3.reset} ${c3.dim}(\u2191/\u2193 \xB7 enter \xB7 esc)${c3.reset}`,
10166
+ `${c4.bold}Your machines${c4.reset} ${c4.dim}(\u2191/\u2193 \xB7 enter \xB7 esc)${c4.reset}`,
9601
10167
  machines.map((m) => {
9602
10168
  const isSelf = m.id === self.machine_id;
9603
10169
  const online = daemonOnline(m);
@@ -9631,26 +10197,26 @@ async function manageMachine(tui, api, self, machine) {
9631
10197
  options.push({ label: "Back", value: "back" });
9632
10198
  if (!isSelf && !machine.daemon) {
9633
10199
  tui.print(
9634
- `${c3.dim}${machine.name} has no daemon \u2014 you can rename it here; update and project changes need its daemon installed.${c3.reset}`
10200
+ `${c4.dim}${machine.name} has no daemon \u2014 you can rename it here; update and project changes need its daemon installed.${c4.reset}`
9635
10201
  );
9636
10202
  } else if (!isSelf && machine.daemon && !online) {
9637
10203
  tui.print(
9638
- `${c3.dim}${machine.name}'s daemon is offline \u2014 queued changes apply when it next comes online.${c3.reset}`
10204
+ `${c4.dim}${machine.name}'s daemon is offline \u2014 queued changes apply when it next comes online.${c4.reset}`
9639
10205
  );
9640
10206
  }
9641
- const action = await tui.select(`${c3.bold}${machineIcon(machine)} ${machine.name}${c3.reset}`, options);
10207
+ const action = await tui.select(`${c4.bold}${machineIcon(machine)} ${machine.name}${c4.reset}`, options);
9642
10208
  if (!action || action === "back") return;
9643
10209
  if (action === "icon") {
9644
10210
  const current = machine.icon ?? "";
9645
10211
  const emoji = await tui.prompt(
9646
- `${c3.bold}Icon for ${machine.name}${c3.reset} ${c3.dim}(paste an emoji, blank to reset)${c3.reset}`,
10212
+ `${c4.bold}Icon for ${machine.name}${c4.reset} ${c4.dim}(paste an emoji, blank to reset)${c4.reset}`,
9647
10213
  current
9648
10214
  );
9649
10215
  if (emoji !== null) {
9650
10216
  const trimmed = emoji.trim();
9651
10217
  await setMachineIcon(api, machine.id, trimmed);
9652
10218
  machine.icon = trimmed || void 0;
9653
- tui.print(`${c3.green}\u2713${c3.reset} icon ${trimmed ? `set to ${trimmed}` : "reset"} for ${machine.name}`);
10219
+ tui.print(`${c4.green}\u2713${c4.reset} icon ${trimmed ? `set to ${trimmed}` : "reset"} for ${machine.name}`);
9654
10220
  }
9655
10221
  return manageMachine(tui, api, self, machine);
9656
10222
  }
@@ -9666,7 +10232,7 @@ async function manageMachine(tui, api, self, machine) {
9666
10232
  const name = await tui.prompt(`New name for ${machine.name}`, machine.name);
9667
10233
  if (name === null || !name.trim()) return;
9668
10234
  await setMachineName(api, machine.id, name.trim());
9669
- tui.print(`${c3.green}\u2713${c3.reset} Renamed ${c3.bold}${machine.name}${c3.reset} \u2192 ${c3.bold}${name.trim()}${c3.reset}.`);
10235
+ tui.print(`${c4.green}\u2713${c4.reset} Renamed ${c4.bold}${machine.name}${c4.reset} \u2192 ${c4.bold}${name.trim()}${c4.reset}.`);
9670
10236
  } else if (action === "update") {
9671
10237
  if (isSelf) {
9672
10238
  await runUpdateCommand(tui);
@@ -9677,7 +10243,7 @@ async function manageMachine(tui, api, self, machine) {
9677
10243
  ]);
9678
10244
  if (go !== "yes") return;
9679
10245
  await dispatch("update");
9680
- tui.print(`${c3.green}\u2713${c3.reset} Update ${c3.gray}${applyNote} (its daemon updates and restarts on the new version).${c3.reset}`);
10246
+ tui.print(`${c4.green}\u2713${c4.reset} Update ${c4.gray}${applyNote} (its daemon updates and restarts on the new version).${c4.reset}`);
9681
10247
  }
9682
10248
  } else if (action === "projects") {
9683
10249
  await manageMachineProjects(tui, api, self, machine, dispatch, applyNote);
@@ -9686,78 +10252,103 @@ async function manageMachine(tui, api, self, machine) {
9686
10252
  async function manageMachineProjects(tui, api, self, machine, dispatch, applyNote) {
9687
10253
  const ADD = "__add__";
9688
10254
  const paths = Object.keys(machine.projects).sort();
10255
+ const projLabels = projectDisplayLabels(machine.projects);
9689
10256
  const picked = await tui.select(
9690
- `${c3.bold}Projects on ${machine.name}${c3.reset} ${c3.dim}(enter to remove \xB7 esc)${c3.reset}`,
10257
+ `${c4.bold}Projects on ${machine.name}${c4.reset} ${c4.dim}(\u2191\u2193 \xB7 enter \xB7 esc)${c4.reset}`,
9691
10258
  [
9692
- ...paths.map((p) => ({ label: p, hint: "enter to remove", value: p })),
10259
+ ...paths.map((p) => ({
10260
+ label: projLabels.get(p) ?? projectDisplayName(p, machine.projects[p]),
10261
+ detail: p,
10262
+ value: p
10263
+ })),
9693
10264
  { label: "\uFF0B Add a project directory\u2026", hint: "absolute path", value: ADD }
9694
- ]
10265
+ ],
10266
+ { spaced: true }
9695
10267
  );
9696
10268
  if (!picked) return;
9697
10269
  if (picked === ADD) {
9698
- const path15 = await tui.prompt(
9699
- `Absolute project path on ${machine.name}`,
9700
- machine.id === self.machine_id ? process.cwd() : "/home/you/project"
10270
+ const isSelf = machine.id === self.machine_id;
10271
+ const backend = isSelf ? localBrowseBackend(machine.name) : remoteBrowseBackend(api, machine.id, machine.name);
10272
+ const chosen = await pickDirectory(tui, backend, {
10273
+ startPath: isSelf ? process.cwd() : null,
10274
+ loader: startLoader
10275
+ });
10276
+ if (!chosen) return;
10277
+ await dispatch("add_project", { path: chosen });
10278
+ tui.print(`${c4.green}\u2713${c4.reset} Add ${chosen} ${c4.gray}${applyNote}.${c4.reset}`);
10279
+ return;
10280
+ }
10281
+ const project = machine.projects[picked];
10282
+ const displayName = projectDisplayName(picked, project);
10283
+ const action = await tui.select(`${c4.bold}${displayName}${c4.reset} ${c4.dim}${shortenPath(picked, 48)}${c4.reset}`, [
10284
+ { label: "Rename", hint: "display name only \u2014 the directory is untouched", value: "rename" },
10285
+ { label: "Remove from this machine's projects", hint: "doesn't delete the directory", value: "remove" },
10286
+ { label: "Back", value: "back" }
10287
+ ]);
10288
+ if (!action || action === "back") return;
10289
+ if (action === "rename") {
10290
+ const name = await tui.prompt(
10291
+ `New name for ${displayName} (blank resets to the directory name)`,
10292
+ displayName
9701
10293
  );
9702
- if (!path15 || !path15.trim()) return;
9703
- const trimmed = path15.trim();
9704
- if (!trimmed.startsWith("/") && !trimmed.startsWith("~")) {
9705
- tui.print(`${c3.yellow}Use an absolute path (starting with / or ~).${c3.reset}`);
9706
- return;
9707
- }
9708
- await dispatch("add_project", { path: trimmed });
9709
- tui.print(`${c3.green}\u2713${c3.reset} Add ${trimmed} ${c3.gray}${applyNote}.${c3.reset}`);
10294
+ if (name === null) return;
10295
+ await setProjectName(api, machine.id, picked, name);
10296
+ const now = name.trim() || projectDisplayName(picked, null);
10297
+ if (project) project.name = now;
10298
+ tui.print(`${c4.green}\u2713${c4.reset} Renamed ${c4.bold}${displayName}${c4.reset} \u2192 ${c4.bold}${now}${c4.reset}.`);
9710
10299
  } else {
9711
10300
  await dispatch("remove_project", { path: picked });
9712
- tui.print(`${c3.green}\u2713${c3.reset} Remove ${picked} ${c3.gray}${applyNote}.${c3.reset}`);
10301
+ tui.print(`${c4.green}\u2713${c4.reset} Remove ${picked} ${c4.gray}${applyNote}.${c4.reset}`);
9713
10302
  }
9714
10303
  }
9715
10304
  function showDaemonInfo(tui, session) {
9716
10305
  if (session.mode === "remote" && session.runner) {
9717
10306
  tui.print(
9718
- `${c3.gray}This session runs on${c3.reset} ${c3.bold}${session.runner.name}${c3.reset} ${c3.gray}(${session.runner.hostname}) \u2014 its daemon executes the tools.${c3.reset}`
10307
+ `${c4.gray}This session runs on${c4.reset} ${c4.bold}${session.runner.name}${c4.reset} ${c4.gray}(${session.runner.hostname}) \u2014 its daemon executes the tools.${c4.reset}`
9719
10308
  );
9720
10309
  } else {
9721
- tui.print(`${c3.gray}This session runs on this machine.${c3.reset}`);
10310
+ tui.print(`${c4.gray}This session runs on this machine.${c4.reset}`);
9722
10311
  }
9723
10312
  const status = serviceStatus();
9724
10313
  tui.print(
9725
- `${c3.gray}Daemon on this machine:${c3.reset} ${status.installed ? status.detail : "not installed"}`
10314
+ `${c4.gray}Daemon on this machine:${c4.reset} ${status.installed ? status.detail : "not installed"}`
9726
10315
  );
9727
10316
  if (!status.installed) {
9728
10317
  tui.print(
9729
- `${c3.gray}Install it to start sessions on this machine from anywhere:${c3.reset} ${c3.bold}standardcode daemon install${c3.reset}`
10318
+ `${c4.gray}Install it to start sessions on this machine from anywhere:${c4.reset} ${c4.bold}standardcode daemon install${c4.reset}`
9730
10319
  );
9731
10320
  tui.print(
9732
- `${c3.dim}The daemon keeps running after you close the terminal \u2014 it self-restarts, self-updates, and executes sessions you start from other machines.${c3.reset}`
10321
+ `${c4.dim}The daemon keeps running after you close the terminal \u2014 it self-restarts, self-updates, and executes sessions you start from other machines.${c4.reset}`
9733
10322
  );
9734
10323
  } else {
9735
- tui.print(`${c3.dim}Manage it with standardcode daemon status | uninstall | add-project <path>.${c3.reset}`);
10324
+ tui.print(`${c4.dim}Manage it with standardcode daemon status | uninstall | add-project <path>.${c4.reset}`);
9736
10325
  }
9737
10326
  }
9738
10327
  function showKeybindings(tui) {
9739
- tui.print(`${c3.gray}shortcuts:${c3.reset}`);
9740
- tui.print(`${c3.gray} shift-tab${c3.reset} cycle auto-accept level (1\u20135)`);
9741
- tui.print(`${c3.gray} /${c3.reset} open the command palette (type to filter)`);
9742
- tui.print(`${c3.gray} !cmd${c3.reset} run a shell command on the session's machine (e.g. !ls); !! to send a literal !`);
9743
- tui.print(`${c3.gray} ctrl-v${c3.reset} paste an image from the clipboard ([#Image 1])`);
9744
- tui.print(`${c3.gray} \u2191 / \u2193${c3.reset} cycle past messages (on the input's top line)`);
9745
- tui.print(`${c3.gray} \u2190${c3.reset} from the start of the input: select the [\u2699 n bg] badge (enter opens it)`);
9746
- tui.print(`${c3.gray} ctrl-c${c3.reset} quit`);
10328
+ tui.print(`${c4.gray}shortcuts:${c4.reset}`);
10329
+ tui.print(`${c4.gray} shift-tab${c4.reset} cycle auto-accept level (1\u20135)`);
10330
+ tui.print(`${c4.gray} shift-\u23CE${c4.reset} insert a newline (multiline input)`);
10331
+ tui.print(`${c4.gray} option-\u23CE${c4.reset} steer the working agent at the next safe boundary`);
10332
+ tui.print(`${c4.gray} /${c4.reset} open the command palette (type to filter)`);
10333
+ tui.print(`${c4.gray} !cmd${c4.reset} run a shell command on the session's machine (e.g. !ls); !! to send a literal !`);
10334
+ tui.print(`${c4.gray} ctrl-v${c4.reset} paste an image from the clipboard ([#Image 1])`);
10335
+ tui.print(`${c4.gray} \u2191 / \u2193${c4.reset} cycle past messages (on the input's top line)`);
10336
+ tui.print(`${c4.gray} \u2190${c4.reset} from the start of the input: select the [\u2699 n bg] badge (enter opens it)`);
10337
+ tui.print(`${c4.gray} ctrl-c${c4.reset} quit`);
9747
10338
  }
9748
10339
  async function runUpdateCommand(tui) {
9749
10340
  const version = readVersion();
9750
10341
  const result = await forceCheckForUpdate(version);
9751
10342
  if (!result) {
9752
- tui.print(`${c3.green}\u2713${c3.reset} ${c3.gray}@standardagents/code${c3.reset} is up to date (v${version})`);
10343
+ tui.print(`${c4.green}\u2713${c4.reset} ${c4.gray}@standardagents/code${c4.reset} is up to date (v${version})`);
9753
10344
  return;
9754
10345
  }
9755
10346
  const { latest } = result;
9756
10347
  tui.print(`
9757
- ${c3.yellow}\u27F3${c3.reset} Update available: ${c3.gray}v${version}${c3.reset} \u2192 ${c3.green}v${latest}${c3.reset}`);
10348
+ ${c4.yellow}\u27F3${c4.reset} Update available: ${c4.gray}v${version}${c4.reset} \u2192 ${c4.green}v${latest}${c4.reset}`);
9758
10349
  const pm = detectPackageManager();
9759
10350
  if (!pm) {
9760
- tui.print(` ${c3.gray}This is a source checkout \u2014 pull the repo to update.${c3.reset}`);
10351
+ tui.print(` ${c4.gray}This is a source checkout \u2014 pull the repo to update.${c4.reset}`);
9761
10352
  return;
9762
10353
  }
9763
10354
  const { display } = updateCommand(pm);
@@ -9766,28 +10357,28 @@ async function runUpdateCommand(tui) {
9766
10357
  { label: "No, skip", value: "no" }
9767
10358
  ]);
9768
10359
  if (choice === "yes") {
9769
- tui.print(` ${c3.gray}Running ${display}\u2026${c3.reset}`);
10360
+ tui.print(` ${c4.gray}Running ${display}\u2026${c4.reset}`);
9770
10361
  const { ok, output: pmOutput } = await runUpdate(pm);
9771
10362
  if (ok) {
9772
- tui.print(` ${c3.green}\u2713${c3.reset} Updated to v${latest}. Restart to use the new version.`);
10363
+ tui.print(` ${c4.green}\u2713${c4.reset} Updated to v${latest}. Restart to use the new version.`);
9773
10364
  } else {
9774
- tui.print(` ${c3.red}\u2717${c3.reset} Update failed:`);
10365
+ tui.print(` ${c4.red}\u2717${c4.reset} Update failed:`);
9775
10366
  for (const line of pmOutput.trim().split("\n").slice(-6)) {
9776
- tui.print(` ${c3.dim}${line}${c3.reset}`);
10367
+ tui.print(` ${c4.dim}${line}${c4.reset}`);
9777
10368
  }
9778
10369
  }
9779
10370
  } else {
9780
- tui.print(` ${c3.gray}Skipped. Run /update later.${c3.reset}`);
10371
+ tui.print(` ${c4.gray}Skipped. Run /update later.${c4.reset}`);
9781
10372
  }
9782
10373
  }
9783
10374
  async function runProcessMenu(tui, bg) {
9784
10375
  const procs = await bg.list();
9785
10376
  if (!procs.length) {
9786
- tui.print(`${c3.gray}No background processes for this session.${c3.reset}`);
10377
+ tui.print(`${c4.gray}No background processes for this session.${c4.reset}`);
9787
10378
  return;
9788
10379
  }
9789
10380
  const items = procs.map((p) => {
9790
- const status = p.status === "running" ? `${c3.green}running${c3.reset}` : `${c3.gray}${p.status}${typeof p.exitCode === "number" ? ` (exit ${p.exitCode})` : ""}${c3.reset}`;
10381
+ const status = p.status === "running" ? `${c4.green}running${c4.reset}` : `${c4.gray}${p.status}${typeof p.exitCode === "number" ? ` (exit ${p.exitCode})` : ""}${c4.reset}`;
9791
10382
  return {
9792
10383
  label: `${p.description || p.command}`,
9793
10384
  hint: `${p.id} \xB7 ${status}`,
@@ -9795,22 +10386,22 @@ async function runProcessMenu(tui, bg) {
9795
10386
  };
9796
10387
  });
9797
10388
  const picked = await tui.select(
9798
- `${c3.bold}Background processes${c3.reset} ${c3.dim}(\u2191/\u2193 \xB7 enter to manage \xB7 esc to close)${c3.reset}`,
10389
+ `${c4.bold}Background processes${c4.reset} ${c4.dim}(\u2191/\u2193 \xB7 enter to manage \xB7 esc to close)${c4.reset}`,
9799
10390
  items
9800
10391
  );
9801
10392
  if (!picked) return;
9802
10393
  const proc = procs.find((p) => p.id === picked);
9803
10394
  if (!proc || proc.status !== "running") {
9804
- tui.print(`${c3.gray}${picked} is not running.${c3.reset}`);
10395
+ tui.print(`${c4.gray}${picked} is not running.${c4.reset}`);
9805
10396
  return;
9806
10397
  }
9807
- const action = await tui.select(`${c3.bold}${proc.description || proc.command}${c3.reset}`, [
10398
+ const action = await tui.select(`${c4.bold}${proc.description || proc.command}${c4.reset}`, [
9808
10399
  { label: "Stop this process", value: "stop" },
9809
10400
  { label: "Leave it running", value: "leave" }
9810
10401
  ]);
9811
10402
  if (action === "stop") {
9812
10403
  await bg.stop(picked);
9813
- tui.print(`${c3.gray}stopped ${picked}${c3.reset}`);
10404
+ tui.print(`${c4.gray}stopped ${picked}${c4.reset}`);
9814
10405
  }
9815
10406
  }
9816
10407
  async function runApprovalsMenu(tui, perm, save) {
@@ -9818,7 +10409,7 @@ async function runApprovalsMenu(tui, perm, save) {
9818
10409
  const risks = Array.from(perm.allowRisk).sort((a, b) => a - b);
9819
10410
  if (!tools.length && !risks.length) {
9820
10411
  tui.print(
9821
- `${c3.gray}No pre-approved commands. At a permission prompt, choose "Always allow this tool" or "Allow all level N" to add some.${c3.reset}`
10412
+ `${c4.gray}No pre-approved commands. At a permission prompt, choose "Always allow this tool" or "Allow all level N" to add some.${c4.reset}`
9822
10413
  );
9823
10414
  return;
9824
10415
  }
@@ -9828,22 +10419,22 @@ async function runApprovalsMenu(tui, perm, save) {
9828
10419
  { label: "Clear all approvals", hint: "", value: "clear" }
9829
10420
  ];
9830
10421
  const picked = await tui.select(
9831
- `${c3.bold}Approved commands${c3.reset} ${c3.dim}(enter to revoke \xB7 esc to close)${c3.reset}`,
10422
+ `${c4.bold}Approved commands${c4.reset} ${c4.dim}(enter to revoke \xB7 esc to close)${c4.reset}`,
9832
10423
  items
9833
10424
  );
9834
10425
  if (!picked) return;
9835
10426
  if (picked === "clear") {
9836
10427
  perm.alwaysAllow.clear();
9837
10428
  perm.allowRisk.clear();
9838
- tui.print(`${c3.gray}cleared all approvals${c3.reset}`);
10429
+ tui.print(`${c4.gray}cleared all approvals${c4.reset}`);
9839
10430
  } else if (picked.startsWith("tool:")) {
9840
10431
  const t = picked.slice(5);
9841
10432
  perm.alwaysAllow.delete(t);
9842
- tui.print(`${c3.gray}revoked tool ${t}${c3.reset}`);
10433
+ tui.print(`${c4.gray}revoked tool ${t}${c4.reset}`);
9843
10434
  } else if (picked.startsWith("risk:")) {
9844
10435
  const r = Number(picked.slice(5));
9845
10436
  perm.allowRisk.delete(r);
9846
- tui.print(`${c3.gray}revoked level ${r}${c3.reset}`);
10437
+ tui.print(`${c4.gray}revoked level ${r}${c4.reset}`);
9847
10438
  }
9848
10439
  save();
9849
10440
  }
@@ -9861,7 +10452,7 @@ async function runMcpMenu(tui, mcp) {
9861
10452
  items.push({ label: "\uFF0B Install a new MCP server\u2026", hint: "find & install", value: INSTALL });
9862
10453
  items.push({ label: "Add manually (name: command)\u2026", hint: "advanced", value: ADD_MANUAL });
9863
10454
  const picked = await tui.select(
9864
- `${c3.bold}MCP servers${c3.reset} ${c3.dim}(\u2191/\u2193 \xB7 enter \xB7 esc to close)${c3.reset}`,
10455
+ `${c4.bold}MCP servers${c4.reset} ${c4.dim}(\u2191/\u2193 \xB7 enter \xB7 esc to close)${c4.reset}`,
9865
10456
  items
9866
10457
  );
9867
10458
  if (!picked) return;
@@ -9875,7 +10466,7 @@ async function runMcpMenu(tui, mcp) {
9875
10466
  }
9876
10467
  const server = configured.find((s) => s.name === picked);
9877
10468
  const isConnected = connected.has(picked);
9878
- const action = await tui.select(`${c3.bold}${picked}${c3.reset}`, [
10469
+ const action = await tui.select(`${c4.bold}${picked}${c4.reset}`, [
9879
10470
  { label: "View tools", value: "tools" },
9880
10471
  isConnected ? { label: "Disconnect", value: "disconnect" } : { label: "Connect", value: "connect" },
9881
10472
  server.enabled ? { label: "Disable (don't auto-connect)", value: "disable" } : { label: "Enable (auto-connect on start)", value: "enable" },
@@ -9885,29 +10476,29 @@ async function runMcpMenu(tui, mcp) {
9885
10476
  if (action === "tools") {
9886
10477
  const entry = mcp.catalog().servers.find((e) => e.name === picked);
9887
10478
  if (!entry || entry.status !== "connected") {
9888
- tui.print(`${c3.gray}${picked} is not connected \u2014 connect it to list tools.${c3.reset}`);
10479
+ tui.print(`${c4.gray}${picked} is not connected \u2014 connect it to list tools.${c4.reset}`);
9889
10480
  return;
9890
10481
  }
9891
- if (!entry.tools.length) tui.print(`${c3.gray}${picked} exposes no tools.${c3.reset}`);
9892
- for (const t of entry.tools) tui.print(` ${c3.cyan}${t.name}${c3.reset}${t.description ? ` ${c3.gray}\u2014 ${t.description}${c3.reset}` : ""}`);
9893
- if (entry.resources.length) tui.print(` ${c3.gray}${entry.resources.length} resource(s)${c3.reset}`);
10482
+ if (!entry.tools.length) tui.print(`${c4.gray}${picked} exposes no tools.${c4.reset}`);
10483
+ for (const t of entry.tools) tui.print(` ${c4.cyan}${t.name}${c4.reset}${t.description ? ` ${c4.gray}\u2014 ${t.description}${c4.reset}` : ""}`);
10484
+ if (entry.resources.length) tui.print(` ${c4.gray}${entry.resources.length} resource(s)${c4.reset}`);
9894
10485
  } else if (action === "connect") {
9895
10486
  const res = await mcp.connect(server);
9896
- tui.print(res.ok ? `${c3.cyan}\u26A1 connected (${res.tools} tools)${c3.reset}` : `${c3.red}\u26A0 ${res.error}${c3.reset}`);
10487
+ tui.print(res.ok ? `${c4.cyan}\u26A1 connected (${res.tools} tools)${c4.reset}` : `${c4.red}\u26A0 ${res.error}${c4.reset}`);
9897
10488
  } else if (action === "disconnect") {
9898
10489
  mcp.disconnect(picked);
9899
- tui.print(`${c3.gray}disconnected ${picked}${c3.reset}`);
10490
+ tui.print(`${c4.gray}disconnected ${picked}${c4.reset}`);
9900
10491
  } else if (action === "enable") {
9901
10492
  mcp.setEnabled(picked, true);
9902
10493
  const res = await mcp.connect(server);
9903
- tui.print(res.ok ? `${c3.cyan}\u26A1 enabled + connected (${res.tools} tools)${c3.reset}` : `${c3.red}\u26A0 enabled but failed: ${res.error}${c3.reset}`);
10494
+ tui.print(res.ok ? `${c4.cyan}\u26A1 enabled + connected (${res.tools} tools)${c4.reset}` : `${c4.red}\u26A0 enabled but failed: ${res.error}${c4.reset}`);
9904
10495
  } else if (action === "disable") {
9905
10496
  mcp.setEnabled(picked, false);
9906
10497
  mcp.disconnect(picked);
9907
- tui.print(`${c3.gray}disabled + disconnected ${picked}${c3.reset}`);
10498
+ tui.print(`${c4.gray}disabled + disconnected ${picked}${c4.reset}`);
9908
10499
  } else if (action === "remove") {
9909
10500
  mcp.remove(picked);
9910
- tui.print(`${c3.gray}removed ${picked}${c3.reset}`);
10501
+ tui.print(`${c4.gray}removed ${picked}${c4.reset}`);
9911
10502
  }
9912
10503
  }
9913
10504
  async function addMcpServer(tui, mcp) {
@@ -9918,13 +10509,13 @@ async function addMcpServer(tui, mcp) {
9918
10509
  if (!spec) return;
9919
10510
  const cfg = parseServerSpec(spec);
9920
10511
  if (!cfg) {
9921
- tui.print(`${c3.yellow}couldn't parse that. Use name: command [args]${c3.reset}`);
10512
+ tui.print(`${c4.yellow}couldn't parse that. Use name: command [args]${c4.reset}`);
9922
10513
  return;
9923
10514
  }
9924
- tui.print(`${c3.gray}connecting MCP "${cfg.name}" (${cfg.command} ${cfg.args.join(" ")})\u2026${c3.reset}`);
10515
+ tui.print(`${c4.gray}connecting MCP "${cfg.name}" (${cfg.command} ${cfg.args.join(" ")})\u2026${c4.reset}`);
9925
10516
  const res = await mcp.add(cfg);
9926
- if (res.ok) tui.print(`${c3.cyan}\u26A1 MCP "${cfg.name}" connected${c3.reset} ${c3.gray}(${res.tools} tool${res.tools === 1 ? "" : "s"})${c3.reset}`);
9927
- else tui.print(`${c3.red}\u26A0 MCP "${cfg.name}" failed:${c3.reset} ${c3.gray}${res.error}${c3.reset} ${c3.dim}(saved; retry from the MCP menu)${c3.reset}`);
10517
+ if (res.ok) tui.print(`${c4.cyan}\u26A1 MCP "${cfg.name}" connected${c4.reset} ${c4.gray}(${res.tools} tool${res.tools === 1 ? "" : "s"})${c4.reset}`);
10518
+ else tui.print(`${c4.red}\u26A0 MCP "${cfg.name}" failed:${c4.reset} ${c4.gray}${res.error}${c4.reset} ${c4.dim}(saved; retry from the MCP menu)${c4.reset}`);
9928
10519
  }
9929
10520
  async function installMcpServerFlow(tui, mcp) {
9930
10521
  const query = await tui.prompt(