@sealant/mend 0.29.2 → 0.31.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.
Files changed (80) hide show
  1. package/README.md +3 -0
  2. package/dist/{chunk-AIW6XFT5.js → chunk-J3TR4UAG.js} +486 -66
  3. package/dist/{dashboard-AREQGMNK.js → dashboard-3HNFSQZD.js} +511 -234
  4. package/dist/main.js +1839 -830
  5. package/man/mend-accounts.1 +1 -1
  6. package/man/mend-adopt.1 +1 -1
  7. package/man/mend-attach.1 +9 -3
  8. package/man/mend-codex.1 +13 -3
  9. package/man/mend-completions.1 +1 -1
  10. package/man/mend-connect.1 +1 -1
  11. package/man/mend-continue.1 +1 -1
  12. package/man/mend-doctor.1 +1 -1
  13. package/man/mend-dotfiles-repo.1 +38 -0
  14. package/man/mend-dotfiles-sync.1 +3 -3
  15. package/man/mend-dotfiles.1 +3 -3
  16. package/man/mend-env-cluster.1 +1 -1
  17. package/man/mend-env-load.1 +1 -1
  18. package/man/mend-env-show.1 +1 -1
  19. package/man/mend-folder-create.1 +1 -1
  20. package/man/mend-folder-list.1 +1 -1
  21. package/man/mend-folder-push.1 +1 -1
  22. package/man/mend-folder-rm.1 +1 -1
  23. package/man/mend-git-author.1 +24 -0
  24. package/man/mend-help.1 +1 -1
  25. package/man/mend-invite.1 +1 -1
  26. package/man/mend-keys-init.1 +1 -1
  27. package/man/mend-keys-mode.1 +1 -1
  28. package/man/mend-keys-share.1 +1 -1
  29. package/man/mend-keys-show.1 +1 -1
  30. package/man/mend-land.1 +48 -0
  31. package/man/mend-login.1 +1 -1
  32. package/man/mend-logout.1 +1 -1
  33. package/man/mend-man.1 +1 -1
  34. package/man/mend-members.1 +1 -1
  35. package/man/mend-operator-exposure.1 +1 -1
  36. package/man/mend-operator-gate.1 +1 -1
  37. package/man/mend-operator-grant-owner.1 +1 -1
  38. package/man/mend-operator-org-create.1 +1 -1
  39. package/man/mend-operator-org-invite-owner.1 +1 -1
  40. package/man/mend-operator-org-list.1 +1 -1
  41. package/man/mend-operator-org-rename.1 +1 -1
  42. package/man/mend-operator-reset-link.1 +1 -1
  43. package/man/mend-pair.1 +1 -1
  44. package/man/mend-projects.1 +1 -1
  45. package/man/mend-pull.1 +29 -0
  46. package/man/mend-refresh.1 +1 -1
  47. package/man/mend-rejoin.1 +7 -2
  48. package/man/mend-resume.1 +1 -1
  49. package/man/mend-run.1 +1 -1
  50. package/man/mend-server-logs.1 +1 -1
  51. package/man/mend-server-restart.1 +1 -1
  52. package/man/mend-server-setup.1 +1 -1
  53. package/man/mend-server-start.1 +1 -1
  54. package/man/mend-server-status.1 +1 -1
  55. package/man/mend-server-stop.1 +1 -1
  56. package/man/mend-server-upgrade.1 +1 -1
  57. package/man/mend-server.1 +1 -1
  58. package/man/mend-service-add.1 +1 -1
  59. package/man/mend-service-connect.1 +3 -1
  60. package/man/mend-service-init.1 +1 -1
  61. package/man/mend-service-list.1 +1 -1
  62. package/man/mend-service-logs.1 +1 -1
  63. package/man/mend-service-restart.1 +1 -1
  64. package/man/mend-service-run.1 +1 -1
  65. package/man/mend-service-stop.1 +1 -1
  66. package/man/mend-session-share.1 +1 -1
  67. package/man/mend-sessions.1 +1 -1
  68. package/man/mend-shell.1 +1 -1
  69. package/man/mend-skills-push.1 +1 -1
  70. package/man/mend-skills.1 +1 -1
  71. package/man/mend-snake.1 +13 -0
  72. package/man/mend-ssh-setup.1 +1 -1
  73. package/man/mend-ssh.1 +1 -1
  74. package/man/mend-stop.1 +8 -1
  75. package/man/mend-ui.1 +9 -3
  76. package/man/mend-uninstall.1 +1 -1
  77. package/man/mend-version.1 +1 -1
  78. package/man/mend-worktrees.1 +1 -1
  79. package/man/mend.1 +17 -2
  80. package/package.json +1 -1
@@ -2,13 +2,18 @@ import {
2
2
  HARNESS_COMMANDS,
3
3
  LIVE_STATUSES,
4
4
  RepositoryCloneUrl,
5
+ agentIsLive,
6
+ agentOutcome,
7
+ createSseParser,
5
8
  cwdFacts,
9
+ eventFamilies,
6
10
  isPendingId,
7
11
  matchProjectByCwd,
8
12
  normalizeProjectName,
9
13
  pendingId,
10
- repositoryCloneUrlIssue
11
- } from "./chunk-AIW6XFT5.js";
14
+ repositoryCloneUrlIssue,
15
+ servicesHoldLine
16
+ } from "./chunk-J3TR4UAG.js";
12
17
 
13
18
  // src/dashboard.tsx
14
19
  import { CliRenderEvents, createCliRenderer } from "@opentui/core";
@@ -20,7 +25,7 @@ import {
20
25
  useQuery as useQuery2,
21
26
  useQueryClient as useQueryClient2
22
27
  } from "@tanstack/react-query";
23
- import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2, useSyncExternalStore } from "react";
28
+ import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2, useState as useState3, useSyncExternalStore } from "react";
24
29
 
25
30
  // src/dashboard-adoption.ts
26
31
  var deriveAdoptOffer = (facts) => {
@@ -265,17 +270,31 @@ var createLaunchGate = () => {
265
270
  var markSessionStopped = (data, sessionId) => {
266
271
  const processesBySession = new Map(data.processesBySession);
267
272
  processesBySession.delete(sessionId);
268
- const servicesBySession = new Map(data.servicesBySession);
269
- servicesBySession.delete(sessionId);
270
273
  return {
271
274
  ...mapWorkbenchSessions(
272
275
  data,
273
276
  (session) => session.id === sessionId ? { ...session, status: "stopped" } : session
274
277
  ),
275
- processesBySession,
276
- servicesBySession
278
+ processesBySession
277
279
  };
278
280
  };
281
+ var markServicesStopped = (data, sessionId) => {
282
+ const servicesBySession = new Map(data.servicesBySession);
283
+ servicesBySession.delete(sessionId);
284
+ return { ...data, servicesBySession };
285
+ };
286
+ var AGENT_WORKING = /* @__PURE__ */ new Set(["starting", "running", "waiting"]);
287
+ var sessionHold = (item) => {
288
+ const agent = item.annotation?.currentAgent ?? null;
289
+ const agentProcessLive = item.processes.some(
290
+ (process2) => process2.kind !== "shell" && process2.exitedAt === null
291
+ );
292
+ return servicesHoldLine({
293
+ agentLive: agent === null ? agentProcessLive || AGENT_WORKING.has(item.session.status) : agentIsLive(item.session, agent),
294
+ agentOutcome: agentOutcome(agent),
295
+ liveServices: Math.max(item.annotation?.liveServices ?? 0, item.services.length)
296
+ });
297
+ };
279
298
  var removeWorktreeGroup = (data, projectId, group) => {
280
299
  const memberIds = new Set(group.sessions.map((item) => item.session.id));
281
300
  const detail = data.details.get(projectId);
@@ -993,6 +1012,7 @@ var stateColor = (state) => state === "draft" || state === "open" ? INK : state
993
1012
  var anchorOf = (comment) => comment.file === null ? "change" : `${comment.file}${comment.line === null ? "" : `:${comment.line}${comment.endLine === null || comment.endLine === comment.line ? "" : `-${comment.endLine}`}`}`;
994
1013
  var passFact = (pass) => {
995
1014
  const label = pass.kind === "suggest" ? "suggestions" : pass.kind;
1015
+ if (pass.status === "queued") return { text: `${label} queued`, color: MUTED };
996
1016
  if (pass.status === "running") return { text: `${label} running`, color: INK_2 };
997
1017
  if (pass.status === "failed") return { text: `${label} failed`, color: ERROR };
998
1018
  return {
@@ -1948,56 +1968,181 @@ function ReviewScreen({
1948
1968
  );
1949
1969
  }
1950
1970
 
1951
- // src/workbench-events.ts
1952
- var createSseParser = () => {
1953
- let buffer = "";
1971
+ // src/snake.tsx
1972
+ import { useEffect as useEffect2, useState as useState2 } from "react";
1973
+
1974
+ // src/snake-game.ts
1975
+ var OPPOSITE = {
1976
+ up: "down",
1977
+ down: "up",
1978
+ left: "right",
1979
+ right: "left"
1980
+ };
1981
+ var STEP = {
1982
+ up: { x: 0, y: -1 },
1983
+ down: { x: 0, y: 1 },
1984
+ left: { x: -1, y: 0 },
1985
+ right: { x: 1, y: 0 }
1986
+ };
1987
+ var same = (a, b) => a.x === b.x && a.y === b.y;
1988
+ var placeFood = (width, height, snake, random) => {
1989
+ const free = [];
1990
+ for (let y = 0; y < height; y += 1)
1991
+ for (let x = 0; x < width; x += 1)
1992
+ if (!snake.some((cell) => cell.x === x && cell.y === y)) free.push({ x, y });
1993
+ if (free.length === 0) return snake[0] ?? { x: 0, y: 0 };
1994
+ return free[Math.min(free.length - 1, Math.floor(random() * free.length))] ?? free[0];
1995
+ };
1996
+ var newSnakeGame = (width, height, random) => {
1997
+ const w = Math.max(8, width);
1998
+ const h = Math.max(4, height);
1999
+ const y = Math.floor(h / 2);
2000
+ const x = Math.floor(w / 2);
2001
+ const snake = [
2002
+ { x, y },
2003
+ { x: x - 1, y },
2004
+ { x: x - 2, y }
2005
+ ];
1954
2006
  return {
1955
- push: (chunk) => {
1956
- buffer += chunk;
1957
- const payloads = [];
1958
- for (; ; ) {
1959
- const boundary = buffer.indexOf("\n\n");
1960
- if (boundary === -1) break;
1961
- const frame = buffer.slice(0, boundary);
1962
- buffer = buffer.slice(boundary + 2);
1963
- const data = frame.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice("data:".length).replace(/^ /, "")).join("\n");
1964
- if (data !== "") payloads.push(data);
1965
- }
1966
- return payloads;
1967
- }
2007
+ width: w,
2008
+ height: h,
2009
+ snake,
2010
+ direction: "right",
2011
+ pending: null,
2012
+ food: placeFood(w, h, snake, random),
2013
+ score: 0,
2014
+ over: false
1968
2015
  };
1969
2016
  };
1970
- var eventFamilies = (payload) => {
1971
- let parsed;
1972
- try {
1973
- parsed = JSON.parse(payload);
1974
- } catch {
1975
- return [];
1976
- }
1977
- if (typeof parsed !== "object" || parsed === null || !("type" in parsed)) return [];
1978
- switch (parsed.type) {
1979
- case "project":
1980
- case "worktree":
1981
- case "session-process":
1982
- return ["workbench"];
1983
- // Settlement refreshes the final transcript immediately. Conversation
1984
- // items can arrive many times per second; the live preview's bounded
1985
- // polling reads those instead of repeatedly fetching the whole record.
1986
- case "session":
1987
- return ["workbench", "review", "transcript"];
1988
- case "agent-conversation":
1989
- return ["workbench", "review"];
1990
- // A comment moves the review AND the dashboard's open-comment counts.
1991
- case "session-change":
1992
- case "review-comment":
1993
- return ["workbench", "review"];
1994
- default:
1995
- return [];
2017
+ var turn = (game, direction) => {
2018
+ if (game.over) return game;
2019
+ if (direction === OPPOSITE[game.direction] || direction === game.direction) return game;
2020
+ return { ...game, pending: direction };
2021
+ };
2022
+ var tick = (game, random) => {
2023
+ if (game.over) return game;
2024
+ const direction = game.pending ?? game.direction;
2025
+ const head = game.snake[0];
2026
+ const next = {
2027
+ x: (head.x + STEP[direction].x + game.width) % game.width,
2028
+ y: (head.y + STEP[direction].y + game.height) % game.height
2029
+ };
2030
+ const eats = same(next, game.food);
2031
+ const body = eats ? game.snake : game.snake.slice(0, -1);
2032
+ if (body.some((cell) => same(cell, next))) {
2033
+ return { ...game, direction, pending: null, over: true };
1996
2034
  }
2035
+ const snake = [next, ...body];
2036
+ return {
2037
+ ...game,
2038
+ snake,
2039
+ direction,
2040
+ pending: null,
2041
+ score: eats ? game.score + 1 : game.score,
2042
+ food: eats ? placeFood(game.width, game.height, snake, random) : game.food
2043
+ };
2044
+ };
2045
+ var render = (game) => {
2046
+ const rows = [];
2047
+ for (let y = 0; y < game.height; y += 1) rows.push(Array.from({ length: game.width }, () => "\xB7"));
2048
+ rows[game.food.y][game.food.x] = "\u25CF";
2049
+ game.snake.forEach((cell, index) => {
2050
+ rows[cell.y][cell.x] = index === 0 ? "\u25C6" : "\u2588";
2051
+ });
2052
+ return rows.map((row) => row.join(""));
1997
2053
  };
1998
2054
 
1999
- // src/dashboard.tsx
2055
+ // src/snake.tsx
2000
2056
  import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "@opentui/react/jsx-runtime";
2057
+ var TICK_MS = 140;
2058
+ var useSnake = (options) => {
2059
+ const { width, height, enabled } = options;
2060
+ const [game, setGame] = useState2(() => newSnakeGame(width, height, Math.random));
2061
+ const [paused, setPaused] = useState2(false);
2062
+ useEffect2(() => {
2063
+ if (enabled) {
2064
+ setGame(newSnakeGame(width, height, Math.random));
2065
+ setPaused(false);
2066
+ }
2067
+ }, [width, height, enabled]);
2068
+ useEffect2(() => {
2069
+ if (!enabled || paused) return;
2070
+ const timer = setInterval(() => setGame((current) => tick(current, Math.random)), TICK_MS);
2071
+ return () => clearInterval(timer);
2072
+ }, [enabled, paused]);
2073
+ const steer = (direction) => {
2074
+ setPaused(false);
2075
+ setGame(
2076
+ (current) => current.over ? newSnakeGame(current.width, current.height, Math.random) : turn(current, direction)
2077
+ );
2078
+ };
2079
+ const togglePause = () => setPaused((current) => !current);
2080
+ return { game, paused, steer, togglePause };
2081
+ };
2082
+ var SnakeHeading = ({
2083
+ handle,
2084
+ hint
2085
+ }) => {
2086
+ const { game, paused } = handle;
2087
+ const state = game.over ? "over \xB7 any arrow starts again" : paused ? "paused" : null;
2088
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
2089
+ /* @__PURE__ */ jsxs2("text", { height: 1, bg: "transparent", children: [
2090
+ /* @__PURE__ */ jsx2("span", { children: " " }),
2091
+ /* @__PURE__ */ jsx2("span", { fg: INK, children: "play snake while you wait" }),
2092
+ /* @__PURE__ */ jsx2("span", { fg: FAINT, children: " \xB7 score " }),
2093
+ /* @__PURE__ */ jsx2("span", { fg: game.over ? MUTED : ACCENT, children: String(game.score) }),
2094
+ state === null ? null : /* @__PURE__ */ jsx2("span", { fg: FAINT, children: ` \xB7 ${state}` })
2095
+ ] }),
2096
+ /* @__PURE__ */ jsx2("text", { height: 1, bg: "transparent", fg: FAINT, children: ` ${hint}` })
2097
+ ] });
2098
+ };
2099
+ var SnakeBoard = ({
2100
+ handle,
2101
+ focused
2102
+ }) => {
2103
+ const { game } = handle;
2104
+ const rows = render(game);
2105
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
2106
+ /* @__PURE__ */ jsx2(
2107
+ SnakeHeading,
2108
+ {
2109
+ handle,
2110
+ hint: focused ? "arrows steer \xB7 space pauses \xB7 esc puts it away" : "focus this pane to play \xB7 esc puts it away"
2111
+ }
2112
+ ),
2113
+ /* @__PURE__ */ jsx2(
2114
+ "box",
2115
+ {
2116
+ border: true,
2117
+ borderStyle: "rounded",
2118
+ borderColor: focused ? ACCENT : RULE,
2119
+ width: game.width + 2,
2120
+ height: game.height + 2,
2121
+ flexShrink: 0,
2122
+ marginLeft: 2,
2123
+ children: rows.map((row, index) => /* @__PURE__ */ jsx2("text", { height: 1, bg: "transparent", children: [...row].map((cell, x) => /* @__PURE__ */ jsx2(
2124
+ "span",
2125
+ {
2126
+ fg: cell === "\u25C6" ? ACCENT : cell === "\u2588" ? GREEN : cell === "\u25CF" ? INK : FAINT,
2127
+ children: cell === "\xB7" ? " " : cell
2128
+ },
2129
+ x
2130
+ )) }, index))
2131
+ }
2132
+ )
2133
+ ] });
2134
+ };
2135
+ var SnakeRows = ({ game }) => /* @__PURE__ */ jsx2(Fragment2, { children: render(game).map((row, index) => /* @__PURE__ */ jsx2("text", { height: 1, bg: "transparent", children: [...row].map((cell, x) => /* @__PURE__ */ jsx2(
2136
+ "span",
2137
+ {
2138
+ fg: cell === "\u25C6" ? ACCENT : cell === "\u2588" ? GREEN : cell === "\u25CF" ? INK : FAINT,
2139
+ children: cell === "\xB7" ? " " : cell
2140
+ },
2141
+ x
2142
+ )) }, index)) });
2143
+
2144
+ // src/dashboard.tsx
2145
+ import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "@opentui/react/jsx-runtime";
2001
2146
  var STATUS_COLOR = {
2002
2147
  starting: INK_2,
2003
2148
  running: INK,
@@ -2028,7 +2173,7 @@ var COLUMN_TITLE = {
2028
2173
  };
2029
2174
  var WORKTREE_ROW_HEIGHT = 2;
2030
2175
  var SESSION_ROW_HEIGHT = 2;
2031
- var Gutter = ({ selected }) => /* @__PURE__ */ jsx2("span", { fg: selected ? ACCENT : FAINT, children: selected ? "\u258C " : " " });
2176
+ var Gutter = ({ selected }) => /* @__PURE__ */ jsx3("span", { fg: selected ? ACCENT : FAINT, children: selected ? "\u258C " : " " });
2032
2177
  var Pane = ({
2033
2178
  title,
2034
2179
  focused,
@@ -2036,7 +2181,7 @@ var Pane = ({
2036
2181
  width,
2037
2182
  height,
2038
2183
  grow
2039
- }) => /* @__PURE__ */ jsx2(
2184
+ }) => /* @__PURE__ */ jsx3(
2040
2185
  "box",
2041
2186
  {
2042
2187
  border: true,
@@ -2062,10 +2207,10 @@ var SummaryRow = ({
2062
2207
  }) => {
2063
2208
  const factWidth = fact === "" ? 0 : Math.min(fact.length, Math.max(0, width - 8));
2064
2209
  const nameWidth = Math.max(4, width - factWidth - 3);
2065
- return /* @__PURE__ */ jsxs2("text", { height: 1, bg: "transparent", children: [
2066
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: " " }),
2067
- /* @__PURE__ */ jsx2("span", { fg: empty ? FAINT : INK_2, children: fit(name, nameWidth - 1).padEnd(nameWidth) }),
2068
- factWidth === 0 ? null : /* @__PURE__ */ jsx2("span", { fg: FAINT, children: fit(fact, factWidth) })
2210
+ return /* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
2211
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " " }),
2212
+ /* @__PURE__ */ jsx3("span", { fg: empty ? FAINT : INK_2, children: fit(name, nameWidth - 1).padEnd(nameWidth) }),
2213
+ factWidth === 0 ? null : /* @__PURE__ */ jsx3("span", { fg: FAINT, children: fit(fact, factWidth) })
2069
2214
  ] });
2070
2215
  };
2071
2216
  var paneScrollStyle = {
@@ -2074,17 +2219,17 @@ var paneScrollStyle = {
2074
2219
  viewportOptions: { backgroundColor: "transparent" },
2075
2220
  contentOptions: { backgroundColor: "transparent" }
2076
2221
  };
2077
- var EmptyNote = ({ text }) => /* @__PURE__ */ jsx2("text", { height: 1, fg: FAINT, bg: "transparent", children: ` ${text}` });
2222
+ var EmptyNote = ({ text }) => /* @__PURE__ */ jsx3("text", { height: 1, fg: FAINT, bg: "transparent", children: ` ${text}` });
2078
2223
  var ProjectRow = ({
2079
2224
  item,
2080
2225
  selected,
2081
2226
  width
2082
2227
  }) => {
2083
2228
  const nameWidth = Math.max(6, width - 9);
2084
- return /* @__PURE__ */ jsx2("box", { height: 1, flexShrink: 0, backgroundColor: selected ? WASH : "transparent", children: /* @__PURE__ */ jsxs2("text", { height: 1, bg: "transparent", children: [
2085
- /* @__PURE__ */ jsx2(Gutter, { selected }),
2086
- /* @__PURE__ */ jsx2("span", { fg: INK, children: fit(item.project.name, nameWidth).padEnd(nameWidth + 1) }),
2087
- /* @__PURE__ */ jsx2("span", { fg: item.live > 0 ? MUTED : FAINT, children: item.live > 0 ? `${item.live}/${item.total}` : String(item.total) })
2229
+ return /* @__PURE__ */ jsx3("box", { height: 1, flexShrink: 0, backgroundColor: selected ? WASH : "transparent", children: /* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
2230
+ /* @__PURE__ */ jsx3(Gutter, { selected }),
2231
+ /* @__PURE__ */ jsx3("span", { fg: INK, children: fit(item.project.name, nameWidth).padEnd(nameWidth + 1) }),
2232
+ /* @__PURE__ */ jsx3("span", { fg: item.live > 0 ? MUTED : FAINT, children: item.live > 0 ? `${item.live}/${item.total}` : String(item.total) })
2088
2233
  ] }) });
2089
2234
  };
2090
2235
  var WorktreeRow = ({
@@ -2109,13 +2254,13 @@ var WorktreeRow = ({
2109
2254
  ],
2110
2255
  factWidth
2111
2256
  );
2112
- return /* @__PURE__ */ jsxs2("box", { flexShrink: 0, flexDirection: "column", backgroundColor: selected ? WASH : "transparent", children: [
2113
- /* @__PURE__ */ jsx2("box", { height: 1, flexShrink: 0, backgroundColor: "transparent", children: /* @__PURE__ */ jsxs2("text", { height: 1, bg: "transparent", children: [
2114
- /* @__PURE__ */ jsx2(Gutter, { selected }),
2115
- /* @__PURE__ */ jsx2("span", { fg: INK, children: fit(group.name, nameWidth - 1).padEnd(nameWidth) }),
2116
- /* @__PURE__ */ jsx2("span", { fg: color, children: status })
2257
+ return /* @__PURE__ */ jsxs3("box", { flexShrink: 0, flexDirection: "column", backgroundColor: selected ? WASH : "transparent", children: [
2258
+ /* @__PURE__ */ jsx3("box", { height: 1, flexShrink: 0, backgroundColor: "transparent", children: /* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
2259
+ /* @__PURE__ */ jsx3(Gutter, { selected }),
2260
+ /* @__PURE__ */ jsx3("span", { fg: INK, children: fit(group.name, nameWidth - 1).padEnd(nameWidth) }),
2261
+ /* @__PURE__ */ jsx3("span", { fg: color, children: status })
2117
2262
  ] }) }),
2118
- /* @__PURE__ */ jsx2("box", { height: 1, flexShrink: 0, backgroundColor: "transparent", children: /* @__PURE__ */ jsx2("text", { height: 1, bg: "transparent", fg: FAINT, children: ` ${facts}` }) })
2263
+ /* @__PURE__ */ jsx3("box", { height: 1, flexShrink: 0, backgroundColor: "transparent", children: /* @__PURE__ */ jsx3("text", { height: 1, bg: "transparent", fg: FAINT, children: ` ${facts}` }) })
2119
2264
  ] });
2120
2265
  };
2121
2266
  var SessionRow = ({
@@ -2130,33 +2275,35 @@ var SessionRow = ({
2130
2275
  const shells = processes.filter((process2) => process2.kind === "shell").length;
2131
2276
  const nameWidth = Math.max(6, width - 5 - status.length);
2132
2277
  const factWidth = Math.max(8, width - 3);
2278
+ const hold = sessionHold(item);
2133
2279
  const facts = fitHints(
2134
2280
  [
2281
+ ...hold === null ? [] : [hold],
2135
2282
  session.harness,
2136
2283
  shortAge(session.createdAt),
2137
2284
  ...shells > 0 ? [`${shells} shell`] : [],
2138
2285
  ...agents > 0 ? [`${agents} agent`] : [],
2139
- ...services.length > 0 ? [`${services.length} service`] : []
2286
+ ...hold === null && services.length > 0 ? [`${services.length} service`] : []
2140
2287
  ],
2141
2288
  factWidth
2142
2289
  );
2143
- return /* @__PURE__ */ jsxs2("box", { flexShrink: 0, flexDirection: "column", backgroundColor: selected ? WASH : "transparent", children: [
2144
- /* @__PURE__ */ jsx2("box", { height: 1, flexShrink: 0, backgroundColor: "transparent", children: /* @__PURE__ */ jsxs2("text", { height: 1, bg: "transparent", children: [
2145
- /* @__PURE__ */ jsx2(Gutter, { selected }),
2146
- /* @__PURE__ */ jsx2("span", { fg: INK, children: fit(sessionDisplayName(session), nameWidth - 1).padEnd(nameWidth) }),
2147
- /* @__PURE__ */ jsx2("span", { fg: color, children: status })
2290
+ return /* @__PURE__ */ jsxs3("box", { flexShrink: 0, flexDirection: "column", backgroundColor: selected ? WASH : "transparent", children: [
2291
+ /* @__PURE__ */ jsx3("box", { height: 1, flexShrink: 0, backgroundColor: "transparent", children: /* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
2292
+ /* @__PURE__ */ jsx3(Gutter, { selected }),
2293
+ /* @__PURE__ */ jsx3("span", { fg: INK, children: fit(sessionDisplayName(session), nameWidth - 1).padEnd(nameWidth) }),
2294
+ /* @__PURE__ */ jsx3("span", { fg: color, children: status })
2148
2295
  ] }) }),
2149
- /* @__PURE__ */ jsx2("box", { height: 1, flexShrink: 0, backgroundColor: "transparent", children: /* @__PURE__ */ jsx2("text", { height: 1, bg: "transparent", fg: FAINT, children: ` ${facts}` }) })
2296
+ /* @__PURE__ */ jsx3("box", { height: 1, flexShrink: 0, backgroundColor: "transparent", children: /* @__PURE__ */ jsx3("text", { height: 1, bg: "transparent", fg: FAINT, children: ` ${facts}` }) })
2150
2297
  ] });
2151
2298
  };
2152
2299
  var HarnessRow = ({
2153
2300
  item,
2154
2301
  selected,
2155
2302
  background
2156
- }) => /* @__PURE__ */ jsx2("box", { height: 1, flexShrink: 0, backgroundColor: selected ? WASH : background, children: /* @__PURE__ */ jsxs2("text", { height: 1, bg: selected ? WASH : background, children: [
2157
- /* @__PURE__ */ jsx2(Gutter, { selected }),
2158
- /* @__PURE__ */ jsx2("span", { fg: INK, children: item.label.padEnd(12) }),
2159
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: item.hint })
2303
+ }) => /* @__PURE__ */ jsx3("box", { height: 1, flexShrink: 0, backgroundColor: selected ? WASH : background, children: /* @__PURE__ */ jsxs3("text", { height: 1, bg: selected ? WASH : background, children: [
2304
+ /* @__PURE__ */ jsx3(Gutter, { selected }),
2305
+ /* @__PURE__ */ jsx3("span", { fg: INK, children: item.label.padEnd(12) }),
2306
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: item.hint })
2160
2307
  ] }) });
2161
2308
  var PREVIEW_GLYPH = {
2162
2309
  user: "\u203A ",
@@ -2176,68 +2323,77 @@ var PREVIEW_COLOR = {
2176
2323
  meta: FAINT,
2177
2324
  blank: FAINT
2178
2325
  };
2179
- var PreviewRow = ({ line }) => /* @__PURE__ */ jsx2("box", { height: 1, flexShrink: 0, backgroundColor: "transparent", children: /* @__PURE__ */ jsxs2("text", { height: 1, bg: "transparent", children: [
2180
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: line.lead ? PREVIEW_GLYPH[line.kind] : " " }),
2181
- /* @__PURE__ */ jsx2("span", { fg: PREVIEW_COLOR[line.kind], children: line.text })
2326
+ var PreviewRow = ({ line }) => /* @__PURE__ */ jsx3("box", { height: 1, flexShrink: 0, backgroundColor: "transparent", children: /* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
2327
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: line.lead ? PREVIEW_GLYPH[line.kind] : " " }),
2328
+ /* @__PURE__ */ jsx3("span", { fg: PREVIEW_COLOR[line.kind], children: line.text })
2182
2329
  ] }) });
2183
2330
  var SessionFacts = ({
2184
2331
  group,
2185
2332
  item,
2186
- rows
2333
+ rows,
2334
+ tunnels
2187
2335
  }) => {
2188
2336
  const { session, annotation, services } = item;
2189
2337
  const color = STATUS_COLOR[session.status] ?? MUTED;
2338
+ const hold = sessionHold(item);
2190
2339
  const summary = session.summary?.split("\n")[0] ?? null;
2191
2340
  const change = annotation ?? group?.annotation;
2192
2341
  const lines = [
2193
- /* @__PURE__ */ jsxs2("text", { height: 1, bg: "transparent", children: [
2194
- /* @__PURE__ */ jsx2("span", { children: " " }),
2195
- /* @__PURE__ */ jsx2("span", { fg: INK, children: sessionDisplayName(session) }),
2196
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: ` \xB7 ${session.harness}` })
2342
+ /* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
2343
+ /* @__PURE__ */ jsx3("span", { children: " " }),
2344
+ /* @__PURE__ */ jsx3("span", { fg: INK, children: sessionDisplayName(session) }),
2345
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: ` \xB7 ${session.harness}` })
2197
2346
  ] }, "name"),
2198
- /* @__PURE__ */ jsxs2("text", { height: 1, bg: "transparent", children: [
2199
- /* @__PURE__ */ jsx2("span", { children: " " }),
2200
- /* @__PURE__ */ jsx2("span", { fg: color, children: session.status }),
2201
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: " \xB7 " }),
2202
- /* @__PURE__ */ jsx2("span", { fg: MUTED, children: session.branch }),
2203
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: session.baseSha === "" ? "" : ` vs ${session.baseRef ?? session.baseSha.slice(0, 12)}` })
2347
+ /* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
2348
+ /* @__PURE__ */ jsx3("span", { children: " " }),
2349
+ /* @__PURE__ */ jsx3("span", { fg: color, children: session.status }),
2350
+ hold === null ? null : /* @__PURE__ */ jsxs3(Fragment3, { children: [
2351
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \xB7 " }),
2352
+ /* @__PURE__ */ jsx3("span", { fg: INK_2, children: hold })
2353
+ ] }),
2354
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \xB7 " }),
2355
+ /* @__PURE__ */ jsx3("span", { fg: MUTED, children: session.branch }),
2356
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: session.baseSha === "" ? "" : ` vs ${session.baseRef ?? session.baseSha.slice(0, 12)}` })
2204
2357
  ] }, "status"),
2205
- /* @__PURE__ */ jsx2("text", { height: 1, bg: "transparent", fg: FAINT, children: ` started ${timeAgo(session.createdAt)} \xB7 ${isPendingId(session.id) ? "provisioning" : session.id.slice(0, 8)}` }, "started"),
2206
- /* @__PURE__ */ jsxs2("text", { height: 1, bg: "transparent", children: [
2207
- /* @__PURE__ */ jsx2("span", { children: " " }),
2208
- services.length === 0 ? /* @__PURE__ */ jsx2("span", { fg: FAINT, children: "no services running" }) : services.slice(0, 2).map((service, index) => /* @__PURE__ */ jsxs2("span", { children: [
2209
- index > 0 ? /* @__PURE__ */ jsx2("span", { fg: FAINT, children: " \xB7 " }) : null,
2210
- /* @__PURE__ */ jsx2("span", { fg: service.status === "reachable" ? INK_2 : MUTED, children: `${service.label ?? service.id.slice(0, 6)} :${service.workspacePort ?? "?"}${service.protocol === "udp" ? "u" : ""}\u2192${service.hostPort ?? "?"} ${service.status}` })
2211
- ] }, service.id)),
2212
- services.length > 2 ? /* @__PURE__ */ jsx2("span", { fg: FAINT, children: ` \xB7 +${services.length - 2} more` }) : null
2358
+ /* @__PURE__ */ jsx3("text", { height: 1, bg: "transparent", fg: FAINT, children: ` started ${timeAgo(session.createdAt)} \xB7 ${isPendingId(session.id) ? "provisioning" : session.id.slice(0, 8)}` }, "started"),
2359
+ /* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
2360
+ /* @__PURE__ */ jsx3("span", { children: " " }),
2361
+ services.length === 0 ? /* @__PURE__ */ jsx3("span", { fg: FAINT, children: "no services running" }) : services.slice(0, 2).map((service, index) => {
2362
+ const tunnel = tunnels.find((candidate) => candidate.service.id === service.id);
2363
+ return /* @__PURE__ */ jsxs3("span", { children: [
2364
+ index > 0 ? /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \xB7 " }) : null,
2365
+ /* @__PURE__ */ jsx3("span", { fg: service.status === "reachable" ? INK_2 : MUTED, children: tunnel === void 0 ? `${service.label ?? service.id.slice(0, 6)} :${service.workspacePort ?? "?"}${service.protocol === "udp" ? "u" : ""}\u2192${service.hostPort ?? "?"} ${service.status}` : tunnel.line })
2366
+ ] }, service.id);
2367
+ }),
2368
+ services.length > 2 ? /* @__PURE__ */ jsx3("span", { fg: FAINT, children: ` \xB7 +${services.length - 2} more` }) : null
2213
2369
  ] }, "services"),
2214
- /* @__PURE__ */ jsxs2("text", { height: 1, bg: "transparent", children: [
2215
- /* @__PURE__ */ jsx2("span", { children: " " }),
2216
- change === void 0 || change.openComments === 0 ? /* @__PURE__ */ jsx2("span", { fg: FAINT, children: "no open review comments" }) : /* @__PURE__ */ jsxs2("span", { fg: INK_2, children: [
2370
+ /* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
2371
+ /* @__PURE__ */ jsx3("span", { children: " " }),
2372
+ change === void 0 || change.openComments === 0 ? /* @__PURE__ */ jsx3("span", { fg: FAINT, children: "no open review comments" }) : /* @__PURE__ */ jsxs3("span", { fg: INK_2, children: [
2217
2373
  change.openComments,
2218
2374
  " open comment",
2219
2375
  change.openComments === 1 ? "" : "s"
2220
2376
  ] }),
2221
- change?.pendingFollowUp === true ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
2222
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: " \xB7 " }),
2223
- /* @__PURE__ */ jsx2("span", { fg: INK_2, children: "follow-up pending" })
2377
+ change?.pendingFollowUp === true ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
2378
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \xB7 " }),
2379
+ /* @__PURE__ */ jsx3("span", { fg: INK_2, children: "follow-up pending" })
2224
2380
  ] }) : null,
2225
- change?.changeId == null ? null : /* @__PURE__ */ jsxs2(Fragment2, { children: [
2226
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: " \xB7 " }),
2227
- /* @__PURE__ */ jsx2("span", { fg: MUTED, children: "v reviews the change" })
2381
+ change?.changeId == null ? null : /* @__PURE__ */ jsxs3(Fragment3, { children: [
2382
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \xB7 " }),
2383
+ /* @__PURE__ */ jsx3("span", { fg: MUTED, children: "v reviews the change" })
2228
2384
  ] })
2229
2385
  ] }, "comments"),
2230
- /* @__PURE__ */ jsx2("text", { height: 1, bg: "transparent", fg: summary === null ? FAINT : MUTED, children: ` ${fit(summary ?? "no summary yet", 200)}` }, "summary")
2386
+ /* @__PURE__ */ jsx3("text", { height: 1, bg: "transparent", fg: summary === null ? FAINT : MUTED, children: ` ${fit(summary ?? "no summary yet", 200)}` }, "summary")
2231
2387
  ];
2232
- return /* @__PURE__ */ jsx2("box", { flexShrink: 0, flexDirection: "column", backgroundColor: "transparent", children: lines.slice(0, Math.max(0, rows)) });
2388
+ return /* @__PURE__ */ jsx3("box", { flexShrink: 0, flexDirection: "column", backgroundColor: "transparent", children: lines.slice(0, Math.max(0, rows)) });
2233
2389
  };
2234
2390
  var StatusLine = ({
2235
2391
  busy,
2236
2392
  busyStarted,
2237
2393
  status
2238
2394
  }) => {
2239
- const [now, setNow] = useState2(() => Date.now());
2240
- useEffect2(() => {
2395
+ const [now, setNow] = useState3(() => Date.now());
2396
+ useEffect3(() => {
2241
2397
  if (busy !== null) {
2242
2398
  const timer = setInterval(() => setNow(Date.now()), 1e3);
2243
2399
  return () => clearInterval(timer);
@@ -2249,7 +2405,7 @@ var StatusLine = ({
2249
2405
  return void 0;
2250
2406
  }, [busy, status]);
2251
2407
  const text = busy === null ? status !== null && Date.now() - status.at < 5e3 ? ` ${status.text}` : "" : ` ${busy} ${Math.max(0, Math.round((now - busyStarted) / 1e3))}s`;
2252
- return /* @__PURE__ */ jsx2("text", { height: 1, fg: INK_2, bg: "transparent", children: text });
2408
+ return /* @__PURE__ */ jsx3("text", { height: 1, fg: INK_2, bg: "transparent", children: text });
2253
2409
  };
2254
2410
  var errorText = (error) => error instanceof Error ? error.message : String(error);
2255
2411
  var keepSpanVisible = (scroll, top, height, viewRows) => {
@@ -2261,6 +2417,8 @@ var keepSpanVisible = (scroll, top, height, viewRows) => {
2261
2417
  var PREVIEW_MAX_LINES = 300;
2262
2418
  var SESSION_FACT_ROWS = 6;
2263
2419
  var noShare = () => "off";
2420
+ var NO_TUNNELS = [];
2421
+ var noTunnels = () => NO_TUNNELS;
2264
2422
  var App = ({ ctx, onQuit }) => {
2265
2423
  const renderer = useRenderer();
2266
2424
  const queryClient = useQueryClient2();
@@ -2271,30 +2429,35 @@ var App = ({ ctx, onQuit }) => {
2271
2429
  retry: true,
2272
2430
  retryDelay: 1e3
2273
2431
  });
2274
- const [focus, setFocusState] = useState2("sessions");
2275
- const [lastNav, setLastNav] = useState2("sessions");
2276
- const [projectKey, setProjectKey] = useState2(null);
2277
- const [worktreeKey, setWorktreeKey] = useState2(null);
2278
- const [sessionKey, setSessionKey] = useState2(null);
2279
- const [picker, setPicker] = useState2(null);
2280
- const [pickerIndex, setPickerIndex] = useState2(0);
2281
- const [busy, setBusy] = useState2(null);
2282
- const [busyStarted, setBusyStarted] = useState2(0);
2283
- const [status, setStatus] = useState2(null);
2432
+ const [focus, setFocusState] = useState3("sessions");
2433
+ const [lastNav, setLastNav] = useState3("sessions");
2434
+ const [projectKey, setProjectKey] = useState3(null);
2435
+ const [worktreeKey, setWorktreeKey] = useState3(null);
2436
+ const [sessionKey, setSessionKey] = useState3(null);
2437
+ const [picker, setPicker] = useState3(null);
2438
+ const [pickerIndex, setPickerIndex] = useState3(0);
2439
+ const [busy, setBusy] = useState3(null);
2440
+ const [busyStarted, setBusyStarted] = useState3(0);
2441
+ const [status, setStatus] = useState3(null);
2284
2442
  const shareState = useSyncExternalStore(
2285
2443
  (onChange) => ctx.agentShare?.subscribe(() => onChange()) ?? (() => {
2286
2444
  }),
2287
2445
  ctx.agentShare === null ? noShare : ctx.agentShare.snapshot
2288
2446
  );
2289
- const [editing, setEditing] = useState2(null);
2290
- const [stopArmed, setStopArmed] = useState2(null);
2291
- const [removeArmed, setRemoveArmed] = useState2(null);
2292
- const [creating, setCreating] = useState2(null);
2293
- const [adoptOffer, setAdoptOffer] = useState2(null);
2447
+ const openTunnels = useSyncExternalStore(
2448
+ (onChange) => ctx.tunnels?.subscribe(onChange) ?? (() => {
2449
+ }),
2450
+ ctx.tunnels === null ? noTunnels : ctx.tunnels.current
2451
+ );
2452
+ const [editing, setEditing] = useState3(null);
2453
+ const [stopArmed, setStopArmed] = useState3(null);
2454
+ const [removeArmed, setRemoveArmed] = useState3(null);
2455
+ const [creating, setCreating] = useState3(null);
2456
+ const [adoptOffer, setAdoptOffer] = useState3(null);
2294
2457
  const adoptOfferDecided = useRef2(false);
2295
- const [reviewing, setReviewing] = useState2(null);
2458
+ const [reviewing, setReviewing] = useState3(null);
2296
2459
  const lockRef = useRef2(false);
2297
- const [gate] = useState2(createLaunchGate);
2460
+ const [gate] = useState3(createLaunchGate);
2298
2461
  const creatingRef = useRef2(creating);
2299
2462
  creatingRef.current = creating;
2300
2463
  const branchRequest = useRef2(0);
@@ -2325,6 +2488,12 @@ var App = ({ ctx, onQuit }) => {
2325
2488
  const sectionRows = (section) => layout.sections.find((entry) => entry.section === section)?.rows ?? 0;
2326
2489
  const previewSessionId = selectedSession !== null && !isPendingId(selectedSession.id) ? selectedSession.id : null;
2327
2490
  const previewLive = selectedSession !== null && LIVE_STATUSES.has(selectedSession.status);
2491
+ useEffect3(() => {
2492
+ const tunnels = ctx.tunnels;
2493
+ if (tunnels === null) return;
2494
+ const timer = setTimeout(() => void tunnels.focus(previewSessionId), 600);
2495
+ return () => clearTimeout(timer);
2496
+ }, [ctx.tunnels, previewSessionId]);
2328
2497
  const transcript = useQuery2({
2329
2498
  queryKey: TRANSCRIPT_KEY(previewSessionId ?? "none"),
2330
2499
  queryFn: () => fetchTranscript(ctx.api, previewSessionId ?? ""),
@@ -2342,9 +2511,26 @@ var App = ({ ctx, onQuit }) => {
2342
2511
  }),
2343
2512
  [transcript.data, detailWidth]
2344
2513
  );
2345
- const [previewOffset, setPreviewOffset] = useState2(0);
2514
+ const [previewOffset, setPreviewOffset] = useState3(0);
2346
2515
  const factRows = Math.max(0, Math.min(SESSION_FACT_ROWS, layout.detailRows - 2));
2347
2516
  const showFactRule = factRows > 0 && layout.detailRows - factRows > 1;
2517
+ const waiting = selectedSession !== null && (selectedSession.status === "starting" || isPendingId(selectedSession.id));
2518
+ const snake = useSnake({
2519
+ width: Math.max(8, Math.min(40, detailWidth - 4)),
2520
+ // The pane minus the facts, the rule, the starting line, two lines of hints and the border.
2521
+ height: Math.max(4, Math.min(14, layout.detailRows - factRows - 8)),
2522
+ enabled: waiting
2523
+ });
2524
+ const [snakeAwayFor, setSnakeAwayFor] = useState3(null);
2525
+ const snakeShown = waiting && snakeAwayFor !== selectedSession?.id;
2526
+ const [snakeOverlay, setSnakeOverlay] = useState3(ctx.openSnake === true);
2527
+ const overlayWidth = Math.max(16, Math.min(60, terminalCols - 8));
2528
+ const overlayHeight = Math.max(6, Math.min(20, terminalRows - 10));
2529
+ const overlaySnake = useSnake({
2530
+ width: overlayWidth,
2531
+ height: overlayHeight,
2532
+ enabled: snakeOverlay
2533
+ });
2348
2534
  const previewRows = Math.max(1, layout.detailRows - factRows - (showFactRule ? 1 : 0));
2349
2535
  const previewView = previewWindow(preview, previewRows, previewOffset);
2350
2536
  const setFocus = (column) => {
@@ -2357,7 +2543,7 @@ var App = ({ ctx, onQuit }) => {
2357
2543
  };
2358
2544
  const say = (text) => setStatus({ text, at: Date.now() });
2359
2545
  const refetch = () => void queryClient.invalidateQueries({ queryKey: WORKBENCH_KEY });
2360
- useEffect2(() => {
2546
+ useEffect3(() => {
2361
2547
  const share = ctx.agentShare;
2362
2548
  if (share === null) return;
2363
2549
  return share.subscribe((event) => {
@@ -2368,7 +2554,7 @@ var App = ({ ctx, onQuit }) => {
2368
2554
  else if (event.kind === "not-signed") say(`\u2717 not signed \u2014 ${event.message}`);
2369
2555
  });
2370
2556
  }, [ctx.agentShare]);
2371
- useEffect2(() => {
2557
+ useEffect3(() => {
2372
2558
  if (data === void 0 || adoptOfferDecided.current) return;
2373
2559
  if (homeProject !== void 0) return;
2374
2560
  adoptOfferDecided.current = true;
@@ -2380,10 +2566,10 @@ var App = ({ ctx, onQuit }) => {
2380
2566
  const projectRows = sectionRows("projects");
2381
2567
  const worktreeRows = sectionRows("worktrees");
2382
2568
  const sessionRows = sectionRows("sessions");
2383
- useEffect2(() => {
2569
+ useEffect3(() => {
2384
2570
  keepSpanVisible(projectScrollRef.current, projectIndex, 1, projectRows);
2385
2571
  }, [projectIndex, projectRows]);
2386
- useEffect2(() => {
2572
+ useEffect3(() => {
2387
2573
  keepSpanVisible(
2388
2574
  worktreeScrollRef.current,
2389
2575
  worktreeIndex * WORKTREE_ROW_HEIGHT,
@@ -2391,7 +2577,7 @@ var App = ({ ctx, onQuit }) => {
2391
2577
  worktreeRows
2392
2578
  );
2393
2579
  }, [worktreeIndex, worktreeRows]);
2394
- useEffect2(() => {
2580
+ useEffect3(() => {
2395
2581
  keepSpanVisible(
2396
2582
  sessionScrollRef.current,
2397
2583
  sessionIndex * SESSION_ROW_HEIGHT,
@@ -2403,10 +2589,14 @@ var App = ({ ctx, onQuit }) => {
2403
2589
  const short = session.id.slice(0, 8);
2404
2590
  lockRef.current = true;
2405
2591
  renderer.suspend();
2406
- process.stdout.write(`
2592
+ const tunneled = openTunnels.filter((tunnel) => tunnel.service.sessionId === session.id).map((tunnel) => `\u25CF ${tunnel.line} \xB7 tunnel
2593
+ `).join("");
2594
+ process.stdout.write(
2595
+ `
2407
2596
  attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
2408
-
2409
- `);
2597
+ ${tunneled}
2598
+ `
2599
+ );
2410
2600
  let outcome;
2411
2601
  try {
2412
2602
  outcome = await ctx.attachTty(session.id, session.harness);
@@ -2614,6 +2804,23 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
2614
2804
  },
2615
2805
  onSettled: settleRefetch
2616
2806
  });
2807
+ const stopServicesMutation = useMutation2({
2808
+ mutationFn: (session) => ctx.api("POST", `/sessions/${session.id}/services/stop`),
2809
+ onMutate: async (session) => {
2810
+ await queryClient.cancelQueries({ queryKey: WORKBENCH_KEY });
2811
+ patchWorkbench((current) => markServicesStopped(current, session.id));
2812
+ },
2813
+ onSuccess: (result, session) => {
2814
+ say(
2815
+ `stopped ${result.stopped} service${result.stopped === 1 ? "" : "s"} \xB7 ${sessionDisplayName(session)} \u2014 the workspace ends once nothing is live`
2816
+ );
2817
+ },
2818
+ onError: (error) => {
2819
+ say(errorText(error));
2820
+ refetch();
2821
+ },
2822
+ onSettled: settleRefetch
2823
+ });
2617
2824
  const stopMutation = useMutation2({
2618
2825
  mutationFn: (session) => ctx.api("POST", `/sessions/${session.id}/stop`),
2619
2826
  onMutate: async (session) => {
@@ -2778,6 +2985,17 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
2778
2985
  }
2779
2986
  const session = selectedSession;
2780
2987
  if (session === null || isPendingId(session.id)) return;
2988
+ const hold = selectedItem === null ? null : sessionHold(selectedItem);
2989
+ if (hold !== null) {
2990
+ if (stopArmed === `svc:${session.id}` && confirmationVisible("press \u21E7K again")) {
2991
+ setStopArmed(null);
2992
+ stopServicesMutation.mutate(session);
2993
+ return;
2994
+ }
2995
+ setStopArmed(`svc:${session.id}`);
2996
+ say(`press \u21E7K again to stop the services \xB7 ${sessionDisplayName(session)} \u2014 ${hold}`);
2997
+ return;
2998
+ }
2781
2999
  if (!LIVE_STATUSES.has(session.status)) {
2782
3000
  say("nothing to stop \u2014 the session is settled");
2783
3001
  return;
@@ -3022,6 +3240,28 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3022
3240
  if (reviewing !== null) return;
3023
3241
  if (lockRef.current) return;
3024
3242
  if (key.ctrl && key.name === "c") return onQuit();
3243
+ if (snakeOverlay) {
3244
+ if (key.name === "up" || key.name === "down" || key.name === "left" || key.name === "right") {
3245
+ overlaySnake.steer(key.name);
3246
+ } else if (key.name === "space") {
3247
+ overlaySnake.togglePause();
3248
+ } else if (key.name === "escape" || key.name === "q") {
3249
+ setSnakeOverlay(false);
3250
+ }
3251
+ return;
3252
+ }
3253
+ if (waiting && focus === "detail" && picker === null && editing === null && creating === null) {
3254
+ if (snakeShown) {
3255
+ if (key.name === "up" || key.name === "down" || key.name === "left" || key.name === "right") {
3256
+ snake.steer(key.name);
3257
+ return;
3258
+ }
3259
+ if (key.name === "space") return snake.togglePause();
3260
+ if (key.name === "escape") return setSnakeAwayFor(selectedSession?.id ?? null);
3261
+ } else if (key.name === "space") {
3262
+ return setSnakeAwayFor(null);
3263
+ }
3264
+ }
3025
3265
  const verb = verbForKey(key.name ?? "", key.shift === true);
3026
3266
  if (verb !== "stop") setStopArmed(null);
3027
3267
  if (verb !== "remove") setRemoveArmed(null);
@@ -3222,7 +3462,7 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3222
3462
  const footerText = adoptOffer === null ? editing === null ? creating === null ? picker === null ? ` ${fitHints(verbHints(focus), Math.max(12, terminalCols - 2))}` : " \u2191\u2193 move \xB7 enter start \xB7 esc cancel" : creating.step === "name" ? " enter continue \xB7 esc cancel" : creating.step === "base" ? " type to filter \xB7 \u2191\u2193 move \xB7 enter choose base \xB7 esc back" : " \u2191\u2193 move \xB7 enter launch \xB7 esc back" : " enter save \xB7 esc cancel" : " enter adopt \xB7 \u2190\u2192 auth mode \xB7 esc not now";
3223
3463
  const loadFailure = data === void 0 && failureReason !== null ? errorText(failureReason) : null;
3224
3464
  if (reviewing !== null) {
3225
- return /* @__PURE__ */ jsx2(
3465
+ return /* @__PURE__ */ jsx3(
3226
3466
  ReviewScreen,
3227
3467
  {
3228
3468
  ctx,
@@ -3258,13 +3498,13 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3258
3498
  const renderSection = (entry) => {
3259
3499
  const inner = Math.max(4, layout.sidebarWidth - 2);
3260
3500
  if (!entry.expanded) {
3261
- return /* @__PURE__ */ jsx2(
3501
+ return /* @__PURE__ */ jsx3(
3262
3502
  Pane,
3263
3503
  {
3264
3504
  title: paneTitle(entry.section, layout.sidebarWidth),
3265
3505
  focused: false,
3266
3506
  height: entry.height,
3267
- children: /* @__PURE__ */ jsx2(
3507
+ children: /* @__PURE__ */ jsx3(
3268
3508
  SummaryRow,
3269
3509
  {
3270
3510
  name: breadcrumbValue(entry.section),
@@ -3280,13 +3520,13 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3280
3520
  const focused = focus === entry.section;
3281
3521
  switch (entry.section) {
3282
3522
  case "projects":
3283
- return /* @__PURE__ */ jsx2(
3523
+ return /* @__PURE__ */ jsx3(
3284
3524
  Pane,
3285
3525
  {
3286
3526
  title: paneTitle("projects", layout.sidebarWidth),
3287
3527
  focused,
3288
3528
  grow: true,
3289
- children: /* @__PURE__ */ jsxs2(
3529
+ children: /* @__PURE__ */ jsxs3(
3290
3530
  "scrollbox",
3291
3531
  {
3292
3532
  ref: projectScrollRef,
@@ -3295,7 +3535,7 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3295
3535
  minHeight: 0,
3296
3536
  style: paneScrollStyle,
3297
3537
  children: [
3298
- projectItems.map((item, index) => /* @__PURE__ */ jsx2(
3538
+ projectItems.map((item, index) => /* @__PURE__ */ jsx3(
3299
3539
  ProjectRow,
3300
3540
  {
3301
3541
  item,
@@ -3304,7 +3544,7 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3304
3544
  },
3305
3545
  item.project.id
3306
3546
  )),
3307
- data !== void 0 && projectItems.length === 0 ? /* @__PURE__ */ jsx2(EmptyNote, { text: "none adopted yet" }) : null
3547
+ data !== void 0 && projectItems.length === 0 ? /* @__PURE__ */ jsx3(EmptyNote, { text: "none adopted yet" }) : null
3308
3548
  ]
3309
3549
  }
3310
3550
  )
@@ -3312,13 +3552,13 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3312
3552
  entry.section
3313
3553
  );
3314
3554
  case "worktrees":
3315
- return /* @__PURE__ */ jsx2(
3555
+ return /* @__PURE__ */ jsx3(
3316
3556
  Pane,
3317
3557
  {
3318
3558
  title: paneTitle("worktrees", layout.sidebarWidth),
3319
3559
  focused,
3320
3560
  grow: true,
3321
- children: /* @__PURE__ */ jsxs2(
3561
+ children: /* @__PURE__ */ jsxs3(
3322
3562
  "scrollbox",
3323
3563
  {
3324
3564
  ref: worktreeScrollRef,
@@ -3327,7 +3567,7 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3327
3567
  minHeight: 0,
3328
3568
  style: paneScrollStyle,
3329
3569
  children: [
3330
- worktreeGroups.map((group, index) => /* @__PURE__ */ jsx2(
3570
+ worktreeGroups.map((group, index) => /* @__PURE__ */ jsx3(
3331
3571
  WorktreeRow,
3332
3572
  {
3333
3573
  group,
@@ -3336,7 +3576,7 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3336
3576
  },
3337
3577
  group.key
3338
3578
  )),
3339
- data !== void 0 && worktreeGroups.length === 0 ? /* @__PURE__ */ jsx2(EmptyNote, { text: "no worktrees \u2014 w starts one" }) : null
3579
+ data !== void 0 && worktreeGroups.length === 0 ? /* @__PURE__ */ jsx3(EmptyNote, { text: "no worktrees \u2014 w starts one" }) : null
3340
3580
  ]
3341
3581
  }
3342
3582
  )
@@ -3344,13 +3584,13 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3344
3584
  entry.section
3345
3585
  );
3346
3586
  case "sessions":
3347
- return /* @__PURE__ */ jsx2(
3587
+ return /* @__PURE__ */ jsx3(
3348
3588
  Pane,
3349
3589
  {
3350
3590
  title: paneTitle("sessions", layout.sidebarWidth),
3351
3591
  focused,
3352
3592
  grow: true,
3353
- children: /* @__PURE__ */ jsxs2(
3593
+ children: /* @__PURE__ */ jsxs3(
3354
3594
  "scrollbox",
3355
3595
  {
3356
3596
  ref: sessionScrollRef,
@@ -3359,7 +3599,7 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3359
3599
  minHeight: 0,
3360
3600
  style: paneScrollStyle,
3361
3601
  children: [
3362
- sessionItems.map((item, index) => /* @__PURE__ */ jsx2(
3602
+ sessionItems.map((item, index) => /* @__PURE__ */ jsx3(
3363
3603
  SessionRow,
3364
3604
  {
3365
3605
  item,
@@ -3368,13 +3608,13 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3368
3608
  },
3369
3609
  item.session.id
3370
3610
  )),
3371
- data !== void 0 && sessionItems.length === 0 ? /* @__PURE__ */ jsx2(
3611
+ data !== void 0 && sessionItems.length === 0 ? /* @__PURE__ */ jsx3(
3372
3612
  EmptyNote,
3373
3613
  {
3374
3614
  text: selectedGroup === null ? "no worktree selected" : "no sessions \u2014 n starts one"
3375
3615
  }
3376
3616
  ) : null,
3377
- loadFailure === null ? null : /* @__PURE__ */ jsx2("text", { height: 1, fg: INK_2, bg: "transparent", children: ` ${loadFailure} \u2014 retrying` })
3617
+ loadFailure === null ? null : /* @__PURE__ */ jsx3("text", { height: 1, fg: INK_2, bg: "transparent", children: ` ${loadFailure} \u2014 retrying` })
3378
3618
  ]
3379
3619
  }
3380
3620
  )
@@ -3383,52 +3623,67 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3383
3623
  );
3384
3624
  }
3385
3625
  };
3386
- const renderDetail = () => /* @__PURE__ */ jsx2(Pane, { title: paneTitle("detail", detailWidth + 2), focused: focus === "detail", grow: true, children: selectedItem === null ? /* @__PURE__ */ jsx2(EmptyNote, { text: "no session selected \u2014 n starts one" }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
3387
- /* @__PURE__ */ jsx2(SessionFacts, { group: selectedGroup, item: selectedItem, rows: factRows }),
3388
- showFactRule ? /* @__PURE__ */ jsx2("text", { height: 1, bg: "transparent", fg: FAINT, children: ` ${"\u2500".repeat(Math.max(4, detailWidth - 4))}` }) : null,
3389
- /* @__PURE__ */ jsx2("box", { flexGrow: 1, flexShrink: 1, minHeight: 0, flexDirection: "column", children: previewSessionId === null ? /* @__PURE__ */ jsx2(EmptyNote, { text: "provisioning \u2014 no record yet" }) : selectedSession?.harness === "shell" ? /* @__PURE__ */ jsx2(EmptyNote, { text: "shell \u2014 no conversation record; a attaches if live" }) : transcript.isPending ? /* @__PURE__ */ jsx2(EmptyNote, { text: "reading the record\u2026" }) : transcript.error === null ? previewView.lines.length === 0 ? /* @__PURE__ */ jsx2(EmptyNote, { text: "no conversation recorded yet" }) : previewView.lines.map((line, index) => /* @__PURE__ */ jsx2(PreviewRow, { line }, `${index}-${line.kind}`)) : /* @__PURE__ */ jsx2("text", { height: 1, fg: INK_2, bg: "transparent", children: ` could not read the record \u2014 ${errorText(transcript.error)}` }) })
3626
+ const renderDetail = () => /* @__PURE__ */ jsx3(Pane, { title: paneTitle("detail", detailWidth + 2), focused: focus === "detail", grow: true, children: selectedItem === null ? /* @__PURE__ */ jsx3(EmptyNote, { text: "no session selected \u2014 n starts one" }) : /* @__PURE__ */ jsxs3(Fragment3, { children: [
3627
+ /* @__PURE__ */ jsx3(
3628
+ SessionFacts,
3629
+ {
3630
+ group: selectedGroup,
3631
+ item: selectedItem,
3632
+ rows: factRows,
3633
+ tunnels: openTunnels
3634
+ }
3635
+ ),
3636
+ showFactRule ? /* @__PURE__ */ jsx3("text", { height: 1, bg: "transparent", fg: FAINT, children: ` ${"\u2500".repeat(Math.max(4, detailWidth - 4))}` }) : null,
3637
+ /* @__PURE__ */ jsx3("box", { flexGrow: 1, flexShrink: 1, minHeight: 0, flexDirection: "column", children: waiting ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
3638
+ /* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
3639
+ /* @__PURE__ */ jsx3("span", { children: " " }),
3640
+ /* @__PURE__ */ jsx3("span", { fg: INK_2, children: "starting" }),
3641
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \xB7 the image builds, then the session boots \xB7 a first build on a new setup takes about 7 minutes" })
3642
+ ] }),
3643
+ snakeShown ? /* @__PURE__ */ jsx3(SnakeBoard, { handle: snake, focused: focus === "detail" }) : /* @__PURE__ */ jsx3(EmptyNote, { text: "snake is put away \xB7 space brings it back" })
3644
+ ] }) : previewSessionId === null ? /* @__PURE__ */ jsx3(EmptyNote, { text: "provisioning \u2014 no record yet" }) : selectedSession?.harness === "shell" ? /* @__PURE__ */ jsx3(EmptyNote, { text: "shell \u2014 no conversation record; a attaches if live" }) : transcript.isPending ? /* @__PURE__ */ jsx3(EmptyNote, { text: "reading the record\u2026" }) : transcript.error === null ? previewView.lines.length === 0 ? /* @__PURE__ */ jsx3(EmptyNote, { text: "no conversation recorded yet" }) : previewView.lines.map((line, index) => /* @__PURE__ */ jsx3(PreviewRow, { line }, `${index}-${line.kind}`)) : /* @__PURE__ */ jsx3("text", { height: 1, fg: INK_2, bg: "transparent", children: ` could not read the record \u2014 ${errorText(transcript.error)}` }) })
3390
3645
  ] }) });
3391
3646
  const offNav = layout.offscreen.filter(isNavSection);
3392
3647
  const detailOffscreen = layout.offscreen.includes("detail");
3393
- return /* @__PURE__ */ jsxs2("box", { flexGrow: 1, flexDirection: "column", backgroundColor: CANVAS, children: [
3394
- /* @__PURE__ */ jsxs2("box", { height: 1, flexDirection: "row", justifyContent: "space-between", children: [
3395
- /* @__PURE__ */ jsxs2("text", { height: 1, bg: "transparent", children: [
3396
- /* @__PURE__ */ jsx2("span", { fg: INK, children: " mend" }),
3397
- /* @__PURE__ */ jsxs2("span", { fg: MUTED, children: [
3648
+ return /* @__PURE__ */ jsxs3("box", { flexGrow: 1, flexDirection: "column", backgroundColor: CANVAS, children: [
3649
+ /* @__PURE__ */ jsxs3("box", { height: 1, flexDirection: "row", justifyContent: "space-between", children: [
3650
+ /* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
3651
+ /* @__PURE__ */ jsx3("span", { fg: INK, children: " mend" }),
3652
+ /* @__PURE__ */ jsxs3("span", { fg: MUTED, children: [
3398
3653
  " ",
3399
3654
  projectItems.length,
3400
3655
  " project",
3401
3656
  projectItems.length === 1 ? "" : "s"
3402
3657
  ] }),
3403
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: " \xB7 " }),
3404
- /* @__PURE__ */ jsxs2("span", { fg: liveTotal > 0 ? MUTED : FAINT, children: [
3658
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \xB7 " }),
3659
+ /* @__PURE__ */ jsxs3("span", { fg: liveTotal > 0 ? MUTED : FAINT, children: [
3405
3660
  liveTotal,
3406
3661
  " live"
3407
3662
  ] }),
3408
- gate.count() > 0 ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
3409
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: " \xB7 " }),
3410
- /* @__PURE__ */ jsxs2("span", { fg: INK_2, children: [
3663
+ gate.count() > 0 ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
3664
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \xB7 " }),
3665
+ /* @__PURE__ */ jsxs3("span", { fg: INK_2, children: [
3411
3666
  gate.count(),
3412
3667
  " starting"
3413
3668
  ] })
3414
3669
  ] }) : null,
3415
- shareState === "off" ? null : /* @__PURE__ */ jsxs2(Fragment2, { children: [
3416
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: " \xB7 " }),
3417
- /* @__PURE__ */ jsx2("span", { fg: shareState === "connected" ? MUTED : FAINT, children: shareState === "connected" ? "agent shared" : "agent share reconnecting" })
3670
+ shareState === "off" ? null : /* @__PURE__ */ jsxs3(Fragment3, { children: [
3671
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \xB7 " }),
3672
+ /* @__PURE__ */ jsx3("span", { fg: shareState === "connected" ? MUTED : FAINT, children: shareState === "connected" ? "agent shared" : "agent share reconnecting" })
3418
3673
  ] })
3419
3674
  ] }),
3420
- /* @__PURE__ */ jsx2("text", { height: 1, fg: FAINT, bg: "transparent", children: `${ctx.config.url} ` })
3675
+ /* @__PURE__ */ jsx3("text", { height: 1, fg: FAINT, bg: "transparent", children: `${ctx.config.url} ` })
3421
3676
  ] }),
3422
- !layout.breadcrumb ? null : /* @__PURE__ */ jsxs2("text", { height: 1, bg: "transparent", children: [
3423
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: " " }),
3424
- offNav.map((column, index) => /* @__PURE__ */ jsxs2("span", { children: [
3425
- index > 0 ? /* @__PURE__ */ jsx2("span", { fg: FAINT, children: " \u25B8 " }) : null,
3426
- /* @__PURE__ */ jsx2("span", { fg: index === offNav.length - 1 ? INK_2 : MUTED, children: fit(breadcrumbValue(column), 28) })
3677
+ !layout.breadcrumb ? null : /* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
3678
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " " }),
3679
+ offNav.map((column, index) => /* @__PURE__ */ jsxs3("span", { children: [
3680
+ index > 0 ? /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \u25B8 " }) : null,
3681
+ /* @__PURE__ */ jsx3("span", { fg: index === offNav.length - 1 ? INK_2 : MUTED, children: fit(breadcrumbValue(column), 28) })
3427
3682
  ] }, column)),
3428
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: detailOffscreen ? `${offNav.length > 0 ? " \xB7 " : ""}record hidden \xB7 \u2192 opens it` : " \u25B8" })
3683
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: detailOffscreen ? `${offNav.length > 0 ? " \xB7 " : ""}record hidden \xB7 \u2192 opens it` : " \u25B8" })
3429
3684
  ] }),
3430
- /* @__PURE__ */ jsxs2("box", { flexGrow: 1, flexShrink: 1, minHeight: 0, flexDirection: "row", children: [
3431
- layout.sidebarWidth === 0 ? null : /* @__PURE__ */ jsx2(
3685
+ /* @__PURE__ */ jsxs3("box", { flexGrow: 1, flexShrink: 1, minHeight: 0, flexDirection: "row", children: [
3686
+ layout.sidebarWidth === 0 ? null : /* @__PURE__ */ jsx3(
3432
3687
  "box",
3433
3688
  {
3434
3689
  width: layout.sidebarWidth,
@@ -3441,7 +3696,29 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3441
3696
  ),
3442
3697
  detailWidth === 0 ? null : renderDetail()
3443
3698
  ] }),
3444
- picker === null ? null : /* @__PURE__ */ jsx2(
3699
+ snakeOverlay ? /* @__PURE__ */ jsxs3(
3700
+ "box",
3701
+ {
3702
+ position: "absolute",
3703
+ zIndex: 13,
3704
+ left: Math.max(1, Math.floor((terminalCols - (overlayWidth + 4)) / 2)),
3705
+ top: Math.max(1, Math.floor((terminalRows - (overlayHeight + 5)) / 2)),
3706
+ width: overlayWidth + 4,
3707
+ height: overlayHeight + 5,
3708
+ border: true,
3709
+ borderStyle: "rounded",
3710
+ borderColor: ACCENT,
3711
+ title: " snake ",
3712
+ titleAlignment: "left",
3713
+ backgroundColor: SURFACE,
3714
+ flexDirection: "column",
3715
+ children: [
3716
+ /* @__PURE__ */ jsx3(SnakeHeading, { handle: overlaySnake, hint: "arrows steer \xB7 space pauses \xB7 esc closes" }),
3717
+ /* @__PURE__ */ jsx3("box", { marginLeft: 1, flexDirection: "column", children: /* @__PURE__ */ jsx3(SnakeRows, { game: overlaySnake.game }) })
3718
+ ]
3719
+ }
3720
+ ) : null,
3721
+ picker === null ? null : /* @__PURE__ */ jsx3(
3445
3722
  "box",
3446
3723
  {
3447
3724
  position: "absolute",
@@ -3457,7 +3734,7 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3457
3734
  titleAlignment: "left",
3458
3735
  backgroundColor: SURFACE,
3459
3736
  flexDirection: "column",
3460
- children: pickerItems.map((item, index) => /* @__PURE__ */ jsx2(
3737
+ children: pickerItems.map((item, index) => /* @__PURE__ */ jsx3(
3461
3738
  HarnessRow,
3462
3739
  {
3463
3740
  item,
@@ -3468,7 +3745,7 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3468
3745
  ))
3469
3746
  }
3470
3747
  ),
3471
- editing === null ? null : /* @__PURE__ */ jsx2(
3748
+ editing === null ? null : /* @__PURE__ */ jsx3(
3472
3749
  "box",
3473
3750
  {
3474
3751
  position: "absolute",
@@ -3484,7 +3761,7 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3484
3761
  backgroundColor: SURFACE,
3485
3762
  height: 3,
3486
3763
  flexShrink: 0,
3487
- children: /* @__PURE__ */ jsx2(
3764
+ children: /* @__PURE__ */ jsx3(
3488
3765
  "input",
3489
3766
  {
3490
3767
  focused: true,
@@ -3504,7 +3781,7 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3504
3781
  )
3505
3782
  }
3506
3783
  ),
3507
- adoptOffer === null ? null : /* @__PURE__ */ jsxs2(
3784
+ adoptOffer === null ? null : /* @__PURE__ */ jsxs3(
3508
3785
  "box",
3509
3786
  {
3510
3787
  position: "absolute",
@@ -3521,25 +3798,25 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3521
3798
  backgroundColor: SURFACE,
3522
3799
  flexDirection: "column",
3523
3800
  children: [
3524
- /* @__PURE__ */ jsxs2("text", { height: 1, bg: SURFACE, children: [
3525
- /* @__PURE__ */ jsx2("span", { children: " " }),
3526
- /* @__PURE__ */ jsx2("span", { fg: INK, children: adoptOffer.name }),
3527
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: " \xB7 not in the store yet" })
3801
+ /* @__PURE__ */ jsxs3("text", { height: 1, bg: SURFACE, children: [
3802
+ /* @__PURE__ */ jsx3("span", { children: " " }),
3803
+ /* @__PURE__ */ jsx3("span", { fg: INK, children: adoptOffer.name }),
3804
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \xB7 not in the store yet" })
3528
3805
  ] }),
3529
- /* @__PURE__ */ jsx2("text", { height: 1, bg: SURFACE, fg: MUTED, children: ` ${adoptOffer.source}` }),
3530
- /* @__PURE__ */ jsxs2("text", { height: 1, bg: SURFACE, children: [
3531
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: " auth " }),
3532
- ADOPT_AUTH_MODES.map((candidate, index) => /* @__PURE__ */ jsxs2("span", { children: [
3533
- index > 0 ? /* @__PURE__ */ jsx2("span", { fg: FAINT, children: " \xB7 " }) : null,
3534
- /* @__PURE__ */ jsx2("span", { fg: index === adoptOffer.modeIndex ? ACCENT : FAINT, children: index === adoptOffer.modeIndex ? `\u25B8 ${candidate.label}` : candidate.label })
3806
+ /* @__PURE__ */ jsx3("text", { height: 1, bg: SURFACE, fg: MUTED, children: ` ${adoptOffer.source}` }),
3807
+ /* @__PURE__ */ jsxs3("text", { height: 1, bg: SURFACE, children: [
3808
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " auth " }),
3809
+ ADOPT_AUTH_MODES.map((candidate, index) => /* @__PURE__ */ jsxs3("span", { children: [
3810
+ index > 0 ? /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \xB7 " }) : null,
3811
+ /* @__PURE__ */ jsx3("span", { fg: index === adoptOffer.modeIndex ? ACCENT : FAINT, children: index === adoptOffer.modeIndex ? `\u25B8 ${candidate.label}` : candidate.label })
3535
3812
  ] }, candidate.mode))
3536
3813
  ] }),
3537
- /* @__PURE__ */ jsx2("text", { height: 1, bg: SURFACE, fg: FAINT, children: ` ${ADOPT_AUTH_MODES[adoptOffer.modeIndex]?.hint ?? ""}` }),
3538
- /* @__PURE__ */ jsx2("text", { height: 1, bg: SURFACE, fg: FAINT, children: " enter adopt \xB7 \u2190\u2192 auth mode \xB7 esc not now" })
3814
+ /* @__PURE__ */ jsx3("text", { height: 1, bg: SURFACE, fg: FAINT, children: ` ${ADOPT_AUTH_MODES[adoptOffer.modeIndex]?.hint ?? ""}` }),
3815
+ /* @__PURE__ */ jsx3("text", { height: 1, bg: SURFACE, fg: FAINT, children: " enter adopt \xB7 \u2190\u2192 auth mode \xB7 esc not now" })
3539
3816
  ]
3540
3817
  }
3541
3818
  ),
3542
- creating === null ? null : /* @__PURE__ */ jsxs2(
3819
+ creating === null ? null : /* @__PURE__ */ jsxs3(
3543
3820
  "box",
3544
3821
  {
3545
3822
  position: "absolute",
@@ -3556,14 +3833,14 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3556
3833
  backgroundColor: SURFACE,
3557
3834
  flexDirection: "column",
3558
3835
  children: [
3559
- /* @__PURE__ */ jsxs2("box", { height: 1, flexShrink: 0, flexDirection: "row", backgroundColor: SURFACE, children: [
3560
- /* @__PURE__ */ jsxs2("text", { height: 1, bg: SURFACE, children: [
3561
- /* @__PURE__ */ jsx2(Gutter, { selected: creating.step === "name" }),
3562
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: "name " }),
3563
- creating.step === "name" ? null : /* @__PURE__ */ jsx2("span", { fg: INK, children: creating.name === "" ? "auto" : creating.name }),
3564
- creating.joins ? /* @__PURE__ */ jsx2("span", { fg: FAINT, children: " \xB7 joins the existing worktree" }) : null
3836
+ /* @__PURE__ */ jsxs3("box", { height: 1, flexShrink: 0, flexDirection: "row", backgroundColor: SURFACE, children: [
3837
+ /* @__PURE__ */ jsxs3("text", { height: 1, bg: SURFACE, children: [
3838
+ /* @__PURE__ */ jsx3(Gutter, { selected: creating.step === "name" }),
3839
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: "name " }),
3840
+ creating.step === "name" ? null : /* @__PURE__ */ jsx3("span", { fg: INK, children: creating.name === "" ? "auto" : creating.name }),
3841
+ creating.joins ? /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \xB7 joins the existing worktree" }) : null
3565
3842
  ] }),
3566
- creating.step === "name" ? /* @__PURE__ */ jsx2(
3843
+ creating.step === "name" ? /* @__PURE__ */ jsx3(
3567
3844
  "input",
3568
3845
  {
3569
3846
  focused: true,
@@ -3588,13 +3865,13 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3588
3865
  }
3589
3866
  ) : null
3590
3867
  ] }),
3591
- /* @__PURE__ */ jsxs2("box", { height: 1, flexShrink: 0, flexDirection: "row", backgroundColor: SURFACE, children: [
3592
- /* @__PURE__ */ jsxs2("text", { height: 1, bg: SURFACE, children: [
3593
- /* @__PURE__ */ jsx2(Gutter, { selected: creating.step === "base" }),
3594
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: "base " }),
3595
- creating.step === "base" ? null : /* @__PURE__ */ jsx2("span", { fg: creating.step === "name" && !creating.joins ? FAINT : INK, children: creating.joins ? "fixed by the existing worktree" : creating.base ?? "default branch" })
3868
+ /* @__PURE__ */ jsxs3("box", { height: 1, flexShrink: 0, flexDirection: "row", backgroundColor: SURFACE, children: [
3869
+ /* @__PURE__ */ jsxs3("text", { height: 1, bg: SURFACE, children: [
3870
+ /* @__PURE__ */ jsx3(Gutter, { selected: creating.step === "base" }),
3871
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: "base " }),
3872
+ creating.step === "base" ? null : /* @__PURE__ */ jsx3("span", { fg: creating.step === "name" && !creating.joins ? FAINT : INK, children: creating.joins ? "fixed by the existing worktree" : creating.base ?? "default branch" })
3596
3873
  ] }),
3597
- creating.step === "base" && creating.branches !== null ? /* @__PURE__ */ jsx2(
3874
+ creating.step === "base" && creating.branches !== null ? /* @__PURE__ */ jsx3(
3598
3875
  "input",
3599
3876
  {
3600
3877
  focused: true,
@@ -3622,7 +3899,7 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3622
3899
  const active = creating.step === "base";
3623
3900
  if (branch === void 0) {
3624
3901
  const notice = creating.joins ? null : baseStepNotice(creating);
3625
- return /* @__PURE__ */ jsx2(
3902
+ return /* @__PURE__ */ jsx3(
3626
3903
  "text",
3627
3904
  {
3628
3905
  height: 1,
@@ -3634,42 +3911,42 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3634
3911
  );
3635
3912
  }
3636
3913
  const highlighted = active && index === creating.baseIndex;
3637
- return /* @__PURE__ */ jsx2(
3914
+ return /* @__PURE__ */ jsx3(
3638
3915
  "box",
3639
3916
  {
3640
3917
  height: 1,
3641
3918
  flexShrink: 0,
3642
3919
  backgroundColor: highlighted ? WASH : SURFACE,
3643
- children: /* @__PURE__ */ jsxs2("text", { height: 1, bg: highlighted ? WASH : SURFACE, children: [
3644
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: " " }),
3645
- /* @__PURE__ */ jsx2(Gutter, { selected: highlighted }),
3646
- /* @__PURE__ */ jsx2("span", { fg: active ? INK : FAINT, children: branch.name.slice(0, 40).padEnd(41) }),
3647
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: branch.sha.slice(0, 8).padEnd(10) }),
3648
- /* @__PURE__ */ jsx2("span", { fg: active ? MUTED : FAINT, children: shortAge(branch.committedAt).padEnd(6) }),
3649
- branch.isDefault ? /* @__PURE__ */ jsx2("span", { fg: FAINT, children: "default" }) : null
3920
+ children: /* @__PURE__ */ jsxs3("text", { height: 1, bg: highlighted ? WASH : SURFACE, children: [
3921
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " " }),
3922
+ /* @__PURE__ */ jsx3(Gutter, { selected: highlighted }),
3923
+ /* @__PURE__ */ jsx3("span", { fg: active ? INK : FAINT, children: branch.name.slice(0, 40).padEnd(41) }),
3924
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: branch.sha.slice(0, 8).padEnd(10) }),
3925
+ /* @__PURE__ */ jsx3("span", { fg: active ? MUTED : FAINT, children: shortAge(branch.committedAt).padEnd(6) }),
3926
+ branch.isDefault ? /* @__PURE__ */ jsx3("span", { fg: FAINT, children: "default" }) : null
3650
3927
  ] })
3651
3928
  },
3652
3929
  branch.name
3653
3930
  );
3654
3931
  }),
3655
- /* @__PURE__ */ jsxs2("text", { height: 1, bg: SURFACE, children: [
3656
- /* @__PURE__ */ jsx2(Gutter, { selected: creating.step === "harness" }),
3657
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: "harness" })
3932
+ /* @__PURE__ */ jsxs3("text", { height: 1, bg: SURFACE, children: [
3933
+ /* @__PURE__ */ jsx3(Gutter, { selected: creating.step === "harness" }),
3934
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: "harness" })
3658
3935
  ] }),
3659
3936
  deriveHarnesses(null).map((item, index) => {
3660
3937
  const active = creating.step === "harness";
3661
3938
  const highlighted = active && index === creating.harnessIndex;
3662
- return /* @__PURE__ */ jsx2(
3939
+ return /* @__PURE__ */ jsx3(
3663
3940
  "box",
3664
3941
  {
3665
3942
  height: 1,
3666
3943
  flexShrink: 0,
3667
3944
  backgroundColor: highlighted ? WASH : SURFACE,
3668
- children: /* @__PURE__ */ jsxs2("text", { height: 1, bg: highlighted ? WASH : SURFACE, children: [
3669
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: " " }),
3670
- /* @__PURE__ */ jsx2(Gutter, { selected: highlighted }),
3671
- /* @__PURE__ */ jsx2("span", { fg: active ? INK : FAINT, children: item.label.padEnd(12) }),
3672
- /* @__PURE__ */ jsx2("span", { fg: FAINT, children: active ? item.hint : "" })
3945
+ children: /* @__PURE__ */ jsxs3("text", { height: 1, bg: highlighted ? WASH : SURFACE, children: [
3946
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " " }),
3947
+ /* @__PURE__ */ jsx3(Gutter, { selected: highlighted }),
3948
+ /* @__PURE__ */ jsx3("span", { fg: active ? INK : FAINT, children: item.label.padEnd(12) }),
3949
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: active ? item.hint : "" })
3673
3950
  ] })
3674
3951
  },
3675
3952
  String(item.harness)
@@ -3678,8 +3955,8 @@ attached \xB7 ${session.harness} \xB7 ${short} \xB7 detach: Ctrl+]
3678
3955
  ]
3679
3956
  }
3680
3957
  ),
3681
- loadFailure === null ? /* @__PURE__ */ jsx2(StatusLine, { busy, busyStarted, status }) : /* @__PURE__ */ jsx2("text", { height: 1, fg: ERROR, bg: "transparent", children: ` could not load workbench \u2014 ${loadFailure} \xB7 retrying` }),
3682
- /* @__PURE__ */ jsx2("text", { height: 1, fg: FAINT, bg: "transparent", children: footerText })
3958
+ loadFailure === null ? /* @__PURE__ */ jsx3(StatusLine, { busy, busyStarted, status }) : /* @__PURE__ */ jsx3("text", { height: 1, fg: ERROR, bg: "transparent", children: ` could not load workbench \u2014 ${loadFailure} \xB7 retrying` }),
3959
+ /* @__PURE__ */ jsx3("text", { height: 1, fg: FAINT, bg: "transparent", children: footerText })
3683
3960
  ] });
3684
3961
  };
3685
3962
  var runDashboard = async (ctx) => {
@@ -3743,7 +4020,7 @@ var runDashboard = async (ctx) => {
3743
4020
  };
3744
4021
  void watch();
3745
4022
  createRoot(renderer).render(
3746
- /* @__PURE__ */ jsx2(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx2(App, { ctx, onQuit: quit }) })
4023
+ /* @__PURE__ */ jsx3(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx3(App, { ctx, onQuit: quit }) })
3747
4024
  );
3748
4025
  await new Promise((resolve) => {
3749
4026
  renderer.once(CliRenderEvents.DESTROY, () => resolve());