@h-rig/cli 0.0.6-alpha.82 → 0.0.6-alpha.84

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 (206) hide show
  1. package/dist/bin/build-rig-binaries.js +40 -8
  2. package/dist/bin/rig.js +23402 -14577
  3. package/dist/src/app/board.js +217 -41
  4. package/dist/src/app-opentui/adapters/command.d.ts +2 -0
  5. package/dist/src/app-opentui/adapters/command.js +329 -0
  6. package/dist/src/app-opentui/adapters/common.js +2 -2
  7. package/dist/src/app-opentui/adapters/doctor.d.ts +0 -2
  8. package/dist/src/app-opentui/adapters/doctor.js +64 -39
  9. package/dist/src/app-opentui/adapters/family.d.ts +62 -0
  10. package/dist/src/app-opentui/adapters/family.js +14305 -0
  11. package/dist/src/app-opentui/adapters/fleet.d.ts +0 -2
  12. package/dist/src/app-opentui/adapters/fleet.js +90 -60
  13. package/dist/src/app-opentui/adapters/inbox.d.ts +12 -2
  14. package/dist/src/app-opentui/adapters/inbox.js +137 -78
  15. package/dist/src/app-opentui/adapters/init.d.ts +0 -2
  16. package/dist/src/app-opentui/adapters/init.js +85 -47
  17. package/dist/src/app-opentui/adapters/inspect.d.ts +52 -0
  18. package/dist/src/app-opentui/adapters/inspect.js +1024 -0
  19. package/dist/src/app-opentui/adapters/pi-attach.d.ts +15 -6
  20. package/dist/src/app-opentui/adapters/pi-attach.js +442 -125
  21. package/dist/src/app-opentui/adapters/pi.d.ts +23 -0
  22. package/dist/src/app-opentui/adapters/pi.js +363 -0
  23. package/dist/src/app-opentui/adapters/plugin.d.ts +84 -0
  24. package/dist/src/app-opentui/adapters/plugin.js +544 -0
  25. package/dist/src/app-opentui/adapters/repo.d.ts +37 -0
  26. package/dist/src/app-opentui/adapters/repo.js +186 -0
  27. package/dist/src/app-opentui/adapters/run-detail.d.ts +9 -2
  28. package/dist/src/app-opentui/adapters/run-detail.js +180 -63
  29. package/dist/src/app-opentui/adapters/server.d.ts +10 -2
  30. package/dist/src/app-opentui/adapters/server.js +191 -45
  31. package/dist/src/app-opentui/adapters/tasks.d.ts +11 -2
  32. package/dist/src/app-opentui/adapters/tasks.js +1123 -143
  33. package/dist/src/app-opentui/adapters/workspace.d.ts +49 -0
  34. package/dist/src/app-opentui/adapters/workspace.js +333 -0
  35. package/dist/src/app-opentui/autocomplete.d.ts +20 -0
  36. package/dist/src/app-opentui/autocomplete.js +576 -0
  37. package/dist/src/app-opentui/bootstrap.d.ts +1 -6
  38. package/dist/src/app-opentui/bootstrap.js +25252 -16474
  39. package/dist/src/app-opentui/command-palette.d.ts +3 -0
  40. package/dist/src/app-opentui/command-palette.js +1010 -0
  41. package/dist/src/app-opentui/command-pty-host.d.ts +62 -0
  42. package/dist/src/app-opentui/command-pty-host.js +248 -0
  43. package/dist/src/app-opentui/drone.js +8 -6
  44. package/dist/src/app-opentui/events.js +1 -1
  45. package/dist/src/app-opentui/fleet-stats.d.ts +32 -0
  46. package/dist/src/app-opentui/fleet-stats.js +114 -0
  47. package/dist/src/app-opentui/focus-manager.d.ts +14 -0
  48. package/dist/src/app-opentui/focus-manager.js +24 -0
  49. package/dist/src/app-opentui/index.js +5431 -2797
  50. package/dist/src/app-opentui/intent.js +179 -50
  51. package/dist/src/app-opentui/keymap.d.ts +21 -0
  52. package/dist/src/app-opentui/keymap.js +1748 -0
  53. package/dist/src/app-opentui/launch-routing.d.ts +16 -0
  54. package/dist/src/app-opentui/launch-routing.js +55 -0
  55. package/dist/src/app-opentui/layout.d.ts +7 -0
  56. package/dist/src/app-opentui/layout.js +13 -6
  57. package/dist/src/app-opentui/list-search.d.ts +24 -0
  58. package/dist/src/app-opentui/list-search.js +88 -1
  59. package/dist/src/app-opentui/pi-host-child.js +99 -17
  60. package/dist/src/app-opentui/pi-pty-host.d.ts +14 -0
  61. package/dist/src/app-opentui/pi-pty-host.js +30 -14
  62. package/dist/src/app-opentui/react/App.d.ts +9 -0
  63. package/dist/src/app-opentui/react/App.js +5144 -0
  64. package/dist/src/app-opentui/react/Backdrop.d.ts +5 -0
  65. package/dist/src/app-opentui/react/Backdrop.js +1834 -0
  66. package/dist/src/app-opentui/react/ChromeHost.d.ts +5 -0
  67. package/dist/src/app-opentui/react/ChromeHost.js +2942 -0
  68. package/dist/src/app-opentui/react/SceneFrameView.d.ts +7 -0
  69. package/dist/src/app-opentui/react/SceneFrameView.js +529 -0
  70. package/dist/src/app-opentui/react/context.d.ts +17 -0
  71. package/dist/src/app-opentui/react/context.js +37 -0
  72. package/dist/src/app-opentui/react/launch.d.ts +2 -0
  73. package/dist/src/app-opentui/react/launch.js +5743 -0
  74. package/dist/src/app-opentui/react/nav.d.ts +18 -0
  75. package/dist/src/app-opentui/react/nav.js +54 -0
  76. package/dist/src/app-opentui/react/scroll.d.ts +12 -0
  77. package/dist/src/app-opentui/react/scroll.js +21 -0
  78. package/dist/src/app-opentui/react/syntax.d.ts +2 -0
  79. package/dist/src/app-opentui/react/syntax.js +64 -0
  80. package/dist/src/app-opentui/registry.js +20428 -4828
  81. package/dist/src/app-opentui/render/ascii-fleet.d.ts +15 -0
  82. package/dist/src/app-opentui/render/ascii-fleet.js +82 -0
  83. package/dist/src/app-opentui/render/constants.d.ts +31 -0
  84. package/dist/src/app-opentui/render/constants.js +66 -0
  85. package/dist/src/app-opentui/render/graphics.d.ts +2 -1
  86. package/dist/src/app-opentui/render/graphics.js +228 -46
  87. package/dist/src/app-opentui/render/image-visual-layer-worker.js +469 -930
  88. package/dist/src/app-opentui/render/image-visual-layer.d.ts +25 -10
  89. package/dist/src/app-opentui/render/image-visual-layer.js +448 -329
  90. package/dist/src/app-opentui/render/native-host.d.ts +37 -0
  91. package/dist/src/app-opentui/render/native-host.js +179 -0
  92. package/dist/src/app-opentui/render/panel-layout.d.ts +38 -0
  93. package/dist/src/app-opentui/render/panel-layout.js +48 -0
  94. package/dist/src/app-opentui/render/panels.d.ts +2 -0
  95. package/dist/src/app-opentui/render/panels.js +78 -38
  96. package/dist/src/app-opentui/render/preloader.d.ts +10 -0
  97. package/dist/src/app-opentui/render/preloader.js +165 -0
  98. package/dist/src/app-opentui/render/scene.d.ts +53 -1
  99. package/dist/src/app-opentui/render/scene.js +195 -6
  100. package/dist/src/app-opentui/render/text.d.ts +7 -1
  101. package/dist/src/app-opentui/render/text.js +15 -8
  102. package/dist/src/app-opentui/render/type-bar.d.ts +2 -1
  103. package/dist/src/app-opentui/render/type-bar.js +113 -39
  104. package/dist/src/app-opentui/runtime-resources.d.ts +16 -0
  105. package/dist/src/app-opentui/runtime-resources.js +62 -0
  106. package/dist/src/app-opentui/runtime.d.ts +44 -1
  107. package/dist/src/app-opentui/runtime.js +5415 -2738
  108. package/dist/src/app-opentui/scenes/command.d.ts +3 -0
  109. package/dist/src/app-opentui/scenes/command.js +117 -0
  110. package/dist/src/app-opentui/scenes/doctor.d.ts +2 -1
  111. package/dist/src/app-opentui/scenes/doctor.js +221 -17
  112. package/dist/src/app-opentui/scenes/error.js +60 -20
  113. package/dist/src/app-opentui/scenes/family-domains/agent.d.ts +2 -0
  114. package/dist/src/app-opentui/scenes/family-domains/agent.js +348 -0
  115. package/dist/src/app-opentui/scenes/family-domains/browser.d.ts +2 -0
  116. package/dist/src/app-opentui/scenes/family-domains/browser.js +195 -0
  117. package/dist/src/app-opentui/scenes/family-domains/dist.d.ts +2 -0
  118. package/dist/src/app-opentui/scenes/family-domains/dist.js +243 -0
  119. package/dist/src/app-opentui/scenes/family-domains/git.d.ts +2 -0
  120. package/dist/src/app-opentui/scenes/family-domains/git.js +195 -0
  121. package/dist/src/app-opentui/scenes/family-domains/github.d.ts +2 -0
  122. package/dist/src/app-opentui/scenes/family-domains/github.js +274 -0
  123. package/dist/src/app-opentui/scenes/family-domains/harness.d.ts +2 -0
  124. package/dist/src/app-opentui/scenes/family-domains/harness.js +152 -0
  125. package/dist/src/app-opentui/scenes/family-domains/index.d.ts +4 -0
  126. package/dist/src/app-opentui/scenes/family-domains/index.js +1679 -0
  127. package/dist/src/app-opentui/scenes/family-domains/kit.d.ts +76 -0
  128. package/dist/src/app-opentui/scenes/family-domains/kit.js +305 -0
  129. package/dist/src/app-opentui/scenes/family-domains/profile.d.ts +2 -0
  130. package/dist/src/app-opentui/scenes/family-domains/profile.js +212 -0
  131. package/dist/src/app-opentui/scenes/family-domains/queue.d.ts +2 -0
  132. package/dist/src/app-opentui/scenes/family-domains/queue.js +146 -0
  133. package/dist/src/app-opentui/scenes/family-domains/remote.d.ts +2 -0
  134. package/dist/src/app-opentui/scenes/family-domains/remote.js +518 -0
  135. package/dist/src/app-opentui/scenes/family-domains/review.d.ts +2 -0
  136. package/dist/src/app-opentui/scenes/family-domains/review.js +280 -0
  137. package/dist/src/app-opentui/scenes/family-domains/setup.d.ts +2 -0
  138. package/dist/src/app-opentui/scenes/family-domains/setup.js +267 -0
  139. package/dist/src/app-opentui/scenes/family-domains/stats.d.ts +2 -0
  140. package/dist/src/app-opentui/scenes/family-domains/stats.js +370 -0
  141. package/dist/src/app-opentui/scenes/family.d.ts +3 -0
  142. package/dist/src/app-opentui/scenes/family.js +2144 -0
  143. package/dist/src/app-opentui/scenes/fleet.js +552 -43
  144. package/dist/src/app-opentui/scenes/handoff.js +342 -35
  145. package/dist/src/app-opentui/scenes/help.js +640 -56
  146. package/dist/src/app-opentui/scenes/inbox.d.ts +2 -1
  147. package/dist/src/app-opentui/scenes/inbox.js +329 -21
  148. package/dist/src/app-opentui/scenes/init.d.ts +2 -1
  149. package/dist/src/app-opentui/scenes/init.js +120 -34
  150. package/dist/src/app-opentui/scenes/inspect.d.ts +3 -0
  151. package/dist/src/app-opentui/scenes/inspect.js +793 -0
  152. package/dist/src/app-opentui/scenes/main.d.ts +2 -1
  153. package/dist/src/app-opentui/scenes/main.js +264 -29
  154. package/dist/src/app-opentui/scenes/pi.d.ts +3 -0
  155. package/dist/src/app-opentui/scenes/pi.js +508 -0
  156. package/dist/src/app-opentui/scenes/plugin.d.ts +3 -0
  157. package/dist/src/app-opentui/scenes/plugin.js +486 -0
  158. package/dist/src/app-opentui/scenes/repo.d.ts +3 -0
  159. package/dist/src/app-opentui/scenes/repo.js +424 -0
  160. package/dist/src/app-opentui/scenes/run-detail.d.ts +2 -1
  161. package/dist/src/app-opentui/scenes/run-detail.js +362 -35
  162. package/dist/src/app-opentui/scenes/server.d.ts +2 -1
  163. package/dist/src/app-opentui/scenes/server.js +243 -26
  164. package/dist/src/app-opentui/scenes/tasks.js +518 -41
  165. package/dist/src/app-opentui/scenes/workspace.d.ts +3 -0
  166. package/dist/src/app-opentui/scenes/workspace.js +426 -0
  167. package/dist/src/app-opentui/selectable.d.ts +19 -0
  168. package/dist/src/app-opentui/selectable.js +79 -0
  169. package/dist/src/app-opentui/state.js +129 -36
  170. package/dist/src/app-opentui/surface-catalog.d.ts +20 -0
  171. package/dist/src/app-opentui/surface-catalog.js +540 -0
  172. package/dist/src/app-opentui/terminal-capabilities.d.ts +7 -0
  173. package/dist/src/app-opentui/terminal-capabilities.js +15 -0
  174. package/dist/src/app-opentui/theme.d.ts +28 -6
  175. package/dist/src/app-opentui/theme.js +61 -8
  176. package/dist/src/app-opentui/types.d.ts +130 -5
  177. package/dist/src/commands/_authority-runs.d.ts +1 -1
  178. package/dist/src/commands/_authority-runs.js +2 -12
  179. package/dist/src/commands/_doctor-checks.js +62 -13
  180. package/dist/src/commands/_help-catalog.js +95 -15
  181. package/dist/src/commands/_operator-view.js +97 -15
  182. package/dist/src/commands/_pi-frontend.d.ts +2 -0
  183. package/dist/src/commands/_pi-frontend.js +97 -13
  184. package/dist/src/commands/_preflight.js +64 -14
  185. package/dist/src/commands/_server-client.js +82 -18
  186. package/dist/src/commands/_server-events.d.ts +26 -0
  187. package/dist/src/commands/_server-events.js +310 -0
  188. package/dist/src/commands/_snapshot-upload.js +62 -13
  189. package/dist/src/commands/agent.js +2 -12
  190. package/dist/src/commands/connect.js +7 -1
  191. package/dist/src/commands/doctor.js +62 -13
  192. package/dist/src/commands/github.js +144 -23
  193. package/dist/src/commands/inbox.js +62 -13
  194. package/dist/src/commands/init.js +82 -18
  195. package/dist/src/commands/inspect.js +62 -13
  196. package/dist/src/commands/run.js +100 -15
  197. package/dist/src/commands/server.js +71 -26
  198. package/dist/src/commands/setup.js +62 -13
  199. package/dist/src/commands/stats.js +157 -28
  200. package/dist/src/commands/task-run-driver.js +64 -25
  201. package/dist/src/commands/task.js +196 -43
  202. package/dist/src/commands.js +419 -123
  203. package/dist/src/index.js +426 -130
  204. package/package.json +11 -10
  205. package/dist/src/app-opentui/render/image-visual-layer-native-canvas.d.ts +0 -2
  206. package/dist/src/app-opentui/render/image-visual-layer-native-canvas.js +0 -1484
@@ -11,15 +11,17 @@ var RIG_UI = {
11
11
  bg: "#070809",
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
- panel2: "#101115",
15
- glass: "#14161b",
16
- ink: "#f2f3f6",
17
- ink2: "#aeb0ba",
18
- ink3: "#6c6e79",
19
- ink4: "#44464f",
14
+ panel2: "#14161b",
15
+ glass: "#1e2230",
16
+ border: "#2a2e3a",
17
+ ink: "#f4f5f8",
18
+ ink2: "#c7c9d3",
19
+ ink3: "#9aa0ae",
20
+ ink4: "#787b86",
20
21
  lime: "#ccff4d",
21
22
  limeDim: "#a9d63f",
22
23
  cyan: "#56d8ff",
24
+ cyanDim: "#3f9fbd",
23
25
  red: "#ff5d5d",
24
26
  yellow: "#ffd24d",
25
27
  magenta: "#ff79b0"
@@ -38,11 +40,11 @@ var styles = {
38
40
  };
39
41
  function statusColor(status) {
40
42
  switch (status) {
41
- case "running":
42
43
  case "success":
43
44
  case "completed":
44
45
  case "merged":
45
46
  return RIG_UI.lime;
47
+ case "running":
46
48
  case "preparing":
47
49
  case "created":
48
50
  case "validating":
@@ -62,15 +64,225 @@ function statusColor(status) {
62
64
  return RIG_UI.ink3;
63
65
  }
64
66
  }
67
+ function statusGlyph(status) {
68
+ switch (status) {
69
+ case "success":
70
+ case "completed":
71
+ case "merged":
72
+ return "\u2713";
73
+ case "running":
74
+ return "\u25CF";
75
+ case "preparing":
76
+ case "created":
77
+ case "validating":
78
+ case "reviewing":
79
+ case "closing-out":
80
+ case "stopping":
81
+ case "loading":
82
+ case "action":
83
+ return "\u25D0";
84
+ case "needs-attention":
85
+ case "needs_attention":
86
+ return "\u26A0";
87
+ case "failed":
88
+ case "error":
89
+ return "\u2717";
90
+ default:
91
+ return "\xB7";
92
+ }
93
+ }
94
+ function noColorActive() {
95
+ const noColor = process.env.NO_COLOR;
96
+ if (noColor !== undefined && noColor !== "") {
97
+ return true;
98
+ }
99
+ return process.env.RIG_NO_COLOR !== undefined;
100
+ }
101
+ function statusLabel(status) {
102
+ return {
103
+ glyph: statusGlyph(status),
104
+ color: noColorActive() ? RIG_UI.ink : statusColor(status)
105
+ };
106
+ }
107
+
108
+ // packages/cli/src/app-opentui/drone.ts
109
+ import { RGBA, StyledText, TextAttributes as TextAttributes2 } from "@opentui/core";
110
+ var MINI_DRONE = [
111
+ "(!!!) (!!!)",
112
+ " \\%==%/ ",
113
+ " %%?%% ",
114
+ " /%==%\\ ",
115
+ "(!!!) (!!!)"
116
+ ];
117
+ var LEAD_MARK = [
118
+ " .-=-. ",
119
+ "=%%?%%=",
120
+ " '-=-' "
121
+ ];
122
+ var BLADE_FRAMES = ["---", "\\\\\\", "|||", "///"];
123
+ var EYE_FRAMES = ["o", "@", "\u2022", "."];
124
+ var COLOR = {
125
+ body: RGBA.fromHex(RIG_UI.lime),
126
+ mini: RGBA.fromHex(RIG_UI.limeDim),
127
+ rotor: RGBA.fromHex(RIG_UI.cyan),
128
+ path: RGBA.fromHex(RIG_UI.cyan),
129
+ eye: RGBA.fromHex(RIG_UI.ink),
130
+ dim: RGBA.fromHex(RIG_UI.ink4),
131
+ ink: RGBA.fromHex(RIG_UI.ink2)
132
+ };
133
+ function bladeForTick(tick, phase = 0) {
134
+ return BLADE_FRAMES[(Math.floor(tick / 3) + phase) % BLADE_FRAMES.length];
135
+ }
136
+ function eyeForTick(tick, phase = 0) {
137
+ const pulse = Math.sin(tick * 0.09 + phase);
138
+ return pulse > 0.55 ? EYE_FRAMES[1] : pulse > 0.1 ? EYE_FRAMES[0] : pulse > -0.45 ? EYE_FRAMES[2] : EYE_FRAMES[3];
139
+ }
140
+ function chunk(text, fg, bold = false, dim = false) {
141
+ let attributes = TextAttributes2.NONE;
142
+ if (bold)
143
+ attributes |= TextAttributes2.BOLD;
144
+ if (dim)
145
+ attributes |= TextAttributes2.DIM;
146
+ return { __isChunk: true, text, fg, ...attributes !== TextAttributes2.NONE ? { attributes } : {} };
147
+ }
148
+ function styledLine(text, colorFor) {
149
+ const chunks = [];
150
+ let run = "";
151
+ let runColor = null;
152
+ const flush = () => {
153
+ if (!run || !runColor)
154
+ return;
155
+ chunks.push(chunk(run, COLOR[runColor], runColor === "body" || runColor === "eye", runColor === "dim"));
156
+ run = "";
157
+ };
158
+ for (const char of text) {
159
+ const next = colorFor(char);
160
+ if (next !== runColor) {
161
+ flush();
162
+ runColor = next;
163
+ }
164
+ run += char;
165
+ }
166
+ flush();
167
+ return new StyledText(chunks.length > 0 ? chunks : [chunk("", COLOR.ink)]);
168
+ }
169
+ function droneColor(char) {
170
+ if (char === "?" || char === "o" || char === "@" || char === "\u2022")
171
+ return "eye";
172
+ if (char === "%" || char === "=")
173
+ return "mini";
174
+ if (char === "$")
175
+ return "body";
176
+ if (char === "(" || char === ")" || char === "/" || char === "\\" || char === "|" || char === "-")
177
+ return "rotor";
178
+ if (char === "\xB7" || char === "\u2022" || char === "'" || char === ".")
179
+ return "path";
180
+ if (char.trim() === "")
181
+ return "dim";
182
+ return "ink";
183
+ }
184
+ function motionColor(char) {
185
+ if (char === "\u25CF" || char === "\u25C6" || char === "\u25C9")
186
+ return "body";
187
+ if (char === "\u2022" || char === "\xB7" || char === "\u2500" || char === "\u2502" || char === "\u2571" || char === "\u2572" || char === "\u256D" || char === "\u256E" || char === "\u2570" || char === "\u256F")
188
+ return "path";
189
+ if (char === "\u258C" || char === "\u2590" || char === "\u2581" || char === "\u2594")
190
+ return "mini";
191
+ return droneColor(char);
192
+ }
193
+ function materializeDrone(lines, tick, phase = 0) {
194
+ const blade = bladeForTick(tick, phase);
195
+ const eye = eyeForTick(tick, phase);
196
+ return lines.map((line) => line.replaceAll("!!!", blade).replaceAll("?", eye));
197
+ }
198
+ function miniDroneLines(tick, align = "left", phase = 0) {
199
+ return materializeDrone(MINI_DRONE, tick, phase).map((text) => ({
200
+ text,
201
+ styledText: styledLine(text, droneColor),
202
+ fg: RIG_UI.limeDim,
203
+ align
204
+ }));
205
+ }
206
+ function brandFleetLines(tick, align = "center") {
207
+ const left = materializeDrone(MINI_DRONE, tick, 0);
208
+ const right = materializeDrone(MINI_DRONE, tick, 2);
209
+ const lead = materializeDrone(LEAD_MARK, tick, 1);
210
+ const packet = ["\xB7", "\u2022", "\u25CF", "\u2022"][Math.floor(tick / 2) % 4];
211
+ const bridge = [
212
+ ` ${left[0]} ${lead[0]} ${right[0]}`,
213
+ ` ${left[1]} \xB7\u2500\u2500${packet}\u2500\u2500\xB7 ${lead[1]} \xB7\u2500\u2500${packet}\u2500\u2500\xB7 ${right[1]}`,
214
+ ` ${left[2]} \xB7' '${lead[2]}' '\xB7 ${right[2]}`,
215
+ ` ${left[3]} \u2572 signal bus \u2571 ${right[3]}`,
216
+ ` ${left[4]} \xB7\u2500\u2500\u2500${packet}\u2500\u2500\u2500\u2500\u2500\u2500${packet}\u2500\u2500\u2500\xB7 ${right[4]}`
217
+ ];
218
+ return bridge.map((text) => ({
219
+ text,
220
+ styledText: styledLine(text, motionColor),
221
+ fg: RIG_UI.limeDim,
222
+ align
223
+ }));
224
+ }
225
+ function actionDrone(tick, phase = 0) {
226
+ const blade = bladeForTick(tick, phase);
227
+ const eye = eyeForTick(tick, phase);
228
+ return `(${blade})\u2572${eye}\u2571(${blade})`;
229
+ }
230
+ function actionDroneLine(tick, text, fg = RIG_UI.ink2) {
231
+ const prefix = actionDrone(tick);
232
+ const content = `${prefix} ${text}`;
233
+ return { text: content, styledText: styledLine(content, motionColor), fg, align: "center" };
234
+ }
65
235
 
66
236
  // packages/cli/src/app-opentui/render/scene.ts
67
237
  function line(text, options = {}) {
68
238
  return { text, ...options };
69
239
  }
240
+ function blank() {
241
+ return { text: "" };
242
+ }
243
+ function deckRow(input) {
244
+ const prefix = input.active ? "\u25B8" : " ";
245
+ const label = input.label.toUpperCase().padEnd(10);
246
+ return line(`${prefix} ${label} ${input.detail}`, {
247
+ fg: input.active ? RIG_UI.lime : RIG_UI.ink2,
248
+ bold: input.active,
249
+ selectableIndex: input.index,
250
+ activateOnClick: input.activateOnClick
251
+ });
252
+ }
253
+ function loadingRows(label, tick = 0, compact = false) {
254
+ const mascot = compact ? miniDroneLines(tick, "center") : brandFleetLines(tick, "center");
255
+ return [
256
+ blank(),
257
+ ...mascot,
258
+ blank(),
259
+ { ...actionDroneLine(tick, `loading ${label}\u2026`, RIG_UI.cyan), align: "center" }
260
+ ];
261
+ }
262
+ function errorBanner(message, hint) {
263
+ return [
264
+ line(`\u2717 failed to load \u2014 ${message}`, { fg: RIG_UI.red, bold: true }),
265
+ ...hint ? [line(` ${hint}`, { fg: RIG_UI.ink3 })] : [],
266
+ line(" select refresh/retry below", { fg: RIG_UI.ink4 }),
267
+ line("", { fg: RIG_UI.ink3 })
268
+ ];
269
+ }
270
+ function lastRefreshError(state) {
271
+ const value = state.data.lastRefreshError;
272
+ return typeof value === "string" && value.trim() ? value : undefined;
273
+ }
70
274
  function makeSceneFrame(input) {
71
275
  return input;
72
276
  }
73
277
 
278
+ // packages/cli/src/app-opentui/selectable.ts
279
+ function selectableDeckRow(deck, item) {
280
+ return { ...deckRow({ ...deck, activateOnClick: true }), selectable: item };
281
+ }
282
+ function withSelectable(line2, item) {
283
+ return { ...line2, selectable: item, activateOnClick: true };
284
+ }
285
+
74
286
  // packages/cli/src/app-opentui/list-search.ts
75
287
  function isListSearchScene(scene) {
76
288
  return scene === "fleet" || scene === "tasks";
@@ -103,8 +315,86 @@ function filterTasksForSearch(tasks, query) {
103
315
  function searchSummary(kind, query, visible, total) {
104
316
  return query.trim() ? `search ${JSON.stringify(query)} \xB7 ${visible}/${total} ${kind}` : `${total} ${kind}`;
105
317
  }
318
+ function taskSortSpec(value) {
319
+ if (!value || typeof value !== "object" || Array.isArray(value))
320
+ return null;
321
+ const record = value;
322
+ const key = record.key;
323
+ const dir = record.dir;
324
+ if ((key === "status" || key === "id" || key === "priority" || key === "assignee" || key === "title") && (dir === "asc" || dir === "desc")) {
325
+ return { key, dir };
326
+ }
327
+ return null;
328
+ }
329
+ function cycleTaskSort(current, key) {
330
+ if (!current || current.key !== key)
331
+ return { key, dir: "asc" };
332
+ if (current.dir === "asc")
333
+ return { key, dir: "desc" };
334
+ return null;
335
+ }
336
+ function applyDir(rows, compare, dir) {
337
+ const sorted = [...rows].sort(compare);
338
+ return dir === "desc" ? sorted.reverse() : sorted;
339
+ }
340
+ function sortTasks(tasks, spec) {
341
+ if (!spec)
342
+ return [...tasks];
343
+ const { key, dir } = spec;
344
+ const compare = (a, b) => {
345
+ if (key === "status")
346
+ return a.status.localeCompare(b.status) || a.id.localeCompare(b.id, undefined, { numeric: true });
347
+ if (key === "priority") {
348
+ const left = a.priority ?? Number.NEGATIVE_INFINITY;
349
+ const right = b.priority ?? Number.NEGATIVE_INFINITY;
350
+ return right - left || a.id.localeCompare(b.id, undefined, { numeric: true });
351
+ }
352
+ if (key === "assignee")
353
+ return (a.assignees[0] ?? "").localeCompare(b.assignees[0] ?? "") || a.id.localeCompare(b.id, undefined, { numeric: true });
354
+ if (key === "title")
355
+ return a.title.localeCompare(b.title, undefined, { sensitivity: "base" }) || a.id.localeCompare(b.id, undefined, { numeric: true });
356
+ return a.id.localeCompare(b.id, undefined, { numeric: true });
357
+ };
358
+ return applyDir(tasks, compare, dir);
359
+ }
106
360
 
107
361
  // packages/cli/src/app-opentui/scenes/tasks.ts
362
+ var TASK_RECOVERY_ITEMS = [
363
+ { id: "refresh", label: "refresh", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Refresh tasks" } }, message: "refresh task source" },
364
+ { id: "next", label: "next", intent: { scene: "command", argv: ["task", "run", "--next"], action: { kind: "command-run", label: "Run next task" } }, message: "dispatch next runnable task" },
365
+ { id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Repair task source" } }, message: "repair project/task source" },
366
+ { id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Open server controls" } }, message: "server controls" },
367
+ { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose task source" }
368
+ ];
369
+ var COL = {
370
+ glyph: 1,
371
+ id: 11,
372
+ status: 12,
373
+ priority: 5,
374
+ assignee: 12
375
+ };
376
+ var GUTTER = " ";
377
+ var TASK_COLUMNS = [
378
+ { key: "status", label: "status", hint: "by task state" },
379
+ { key: "id", label: "task-id", hint: "by id" },
380
+ { key: "priority", label: "priority", hint: "by priority" },
381
+ { key: "assignee", label: "assignee", hint: "by assignee" },
382
+ { key: "title", label: "title", hint: "by title" }
383
+ ];
384
+ function taskSelectableItem(task) {
385
+ const taskId = task.id?.trim();
386
+ if (!taskId)
387
+ return null;
388
+ const title = (task.title || "task").trim() || "task";
389
+ const activeRunId = task.activeRun?.runId?.trim();
390
+ return {
391
+ id: taskId,
392
+ label: title,
393
+ data: { taskDetailId: taskId, selectedTaskId: taskId, ...activeRunId ? { selectedRunId: activeRunId } : {} },
394
+ intent: { scene: "tasks", argv: ["tasks", taskId], action: { kind: "refresh", label: `Open ${title}`, payload: { taskDetailId: taskId } } },
395
+ message: `open ${title} detail`
396
+ };
397
+ }
108
398
  function taskView(state) {
109
399
  const tasks = state.data.tasks;
110
400
  return tasks && typeof tasks === "object" && !Array.isArray(tasks) ? tasks : null;
@@ -113,6 +403,32 @@ function taskRecords(state) {
113
403
  const records = taskView(state)?.records;
114
404
  return Array.isArray(records) ? records : [];
115
405
  }
406
+ function dispatchingFor(state) {
407
+ const value = state.data.dispatchingRun;
408
+ if (!value || typeof value !== "object" || Array.isArray(value))
409
+ return null;
410
+ const record = value;
411
+ if (typeof record.taskId !== "string" || !record.taskId.trim())
412
+ return null;
413
+ return { taskId: record.taskId, status: typeof record.status === "string" ? record.status : "dispatching" };
414
+ }
415
+ function updatedAgoLabel(state) {
416
+ const refreshedAt = taskView(state)?.refreshedAt;
417
+ if (typeof refreshedAt !== "string")
418
+ return;
419
+ const parsed = Date.parse(refreshedAt);
420
+ if (!Number.isFinite(parsed))
421
+ return;
422
+ const seconds = Math.max(0, Math.round((Date.now() - parsed) / 1000));
423
+ if (seconds < 1)
424
+ return "updated just now";
425
+ if (seconds < 60)
426
+ return `updated ${seconds}s ago`;
427
+ const minutes = Math.round(seconds / 60);
428
+ if (minutes < 60)
429
+ return `updated ${minutes}m ago`;
430
+ return `updated ${Math.round(minutes / 60)}h ago`;
431
+ }
116
432
  function panelWidth(layout) {
117
433
  return layout?.centerWidth ?? 100;
118
434
  }
@@ -124,9 +440,19 @@ function clip(value, width) {
124
440
  return "";
125
441
  return value.length <= width ? value : `${value.slice(0, Math.max(0, width - 1))}\u2026`;
126
442
  }
443
+ function cell(value, width) {
444
+ return clip(value, width).padEnd(width);
445
+ }
127
446
  function formatAssignee(value) {
128
447
  return value.startsWith("@") ? value : `@${value}`;
129
448
  }
449
+ function assigneeCell(task) {
450
+ const first = task.assignees[0];
451
+ return first ? formatAssignee(first) : "\u2014";
452
+ }
453
+ function priorityCell(task) {
454
+ return task.priority === undefined ? "\u2014" : `P${String(task.priority).slice(0, 3)}`;
455
+ }
130
456
  function taskViewLabel(view) {
131
457
  const filters = view?.filters && typeof view.filters === "object" && !Array.isArray(view.filters) ? view.filters : {};
132
458
  const filter = typeof filters.preset === "string" && filters.preset !== "custom" ? filters.preset : [
@@ -142,67 +468,215 @@ function taskViewLabel(view) {
142
468
  const count = visible !== undefined && total !== undefined ? `${visible}/${total}` : "syncing";
143
469
  return `filter ${filter} \xB7 sort ${sort} \xB7 ${count}`;
144
470
  }
145
- function taskRow(width, task, index, active) {
146
- const id = task.id.slice(0, 10).padEnd(11);
147
- const status = task.status.replace(/[_-]/g, " ").slice(0, 11).padEnd(12);
148
- const priority = task.priority === undefined ? "prio --" : `prio ${String(task.priority).slice(0, 3).padStart(2)}`;
471
+ function sortArrow(spec, key) {
472
+ if (!spec || spec.key !== key)
473
+ return "";
474
+ return spec.dir === "asc" ? "\u25B2" : "\u25BC";
475
+ }
476
+ function headerRow(spec) {
477
+ const text = [
478
+ " ",
479
+ cell(`task-id${sortArrow(spec, "id")}`, COL.id),
480
+ cell(`status${sortArrow(spec, "status")}`, COL.status),
481
+ cell(`prio${sortArrow(spec, "priority")}`, COL.priority),
482
+ cell(`assignee${sortArrow(spec, "assignee")}`, COL.assignee),
483
+ `title${sortArrow(spec, "title")}`
484
+ ].join(GUTTER);
485
+ return line(text, { fg: RIG_UI.ink, bold: true, bg: RIG_UI.glass });
486
+ }
487
+ function sortToggleRow(column, current) {
488
+ const arrow = sortArrow(current, column.key);
489
+ const active = current?.key === column.key;
490
+ const next = cycleTaskSort(current, column.key);
491
+ const marker = active ? "\u25B8" : " ";
492
+ const label = `${column.label}${arrow ? ` ${arrow}` : ""}`.padEnd(12);
493
+ const row = line(`${marker} ${label} ${column.hint}`, {
494
+ fg: active ? RIG_UI.lime : RIG_UI.ink2,
495
+ bold: active
496
+ });
497
+ return withSelectable(row, {
498
+ id: `task-sort-${column.key}`,
499
+ label: `sort by ${column.label}`,
500
+ data: { taskSort: next },
501
+ message: `sort tasks ${next ? `${column.label} ${next.dir}` : "default"}`
502
+ });
503
+ }
504
+ function sortStrip(spec) {
505
+ return [
506
+ line("SORT", { fg: RIG_UI.ink3, bold: true }),
507
+ ...TASK_COLUMNS.map((column) => sortToggleRow(column, spec)),
508
+ line("", { fg: RIG_UI.ink3 })
509
+ ];
510
+ }
511
+ function taskRow(width, task, active, optimistic) {
512
+ const effectiveStatus = optimistic ?? task.status;
513
+ const { glyph } = statusLabel(effectiveStatus);
149
514
  const marker = active ? "\u258C" : " ";
150
- const prefix = `${marker} ${id} ${status} ${priority.padEnd(8)} `;
151
- const titleWidth = Math.max(12, width - prefix.length - 2);
152
- return line(`${prefix}${clip(task.title, titleWidth)}`, {
153
- fg: active ? RIG_UI.ink : statusColor(task.status),
515
+ const status = effectiveStatus.replace(/[_-]/g, " ");
516
+ const fixed = `${marker} ${glyph}${GUTTER}${cell(task.id, COL.id)}${GUTTER}${cell(status, COL.status)}${GUTTER}${cell(priorityCell(task), COL.priority)}${GUTTER}${cell(assigneeCell(task), COL.assignee)}${GUTTER}`;
517
+ const activeRun = task.activeRun;
518
+ const activeSuffix = activeRun ? ` \xB7 attach ${activeRun.runId.slice(0, 8)}${activeRun.status ? ` ${activeRun.status}` : ""}` : "";
519
+ const titleWidth = Math.max(8, width - fixed.length - 2);
520
+ const row = line(`${fixed}${clip(`${task.title}${activeSuffix}`, titleWidth)}`, {
521
+ fg: active ? RIG_UI.ink : RIG_UI.ink2,
154
522
  bold: active,
155
- selectableIndex: index
523
+ ...active ? { bg: RIG_UI.glass } : {}
156
524
  });
525
+ const item = taskSelectableItem(task);
526
+ return item ? withSelectable(row, item) : row;
527
+ }
528
+ var RECOVERY_DECKS = [
529
+ { label: "refresh", detail: "reload task source" },
530
+ { label: "next", detail: "dispatch next runnable task" },
531
+ { label: "repair", detail: "repair project/task source link" },
532
+ { label: "server", detail: "open server controls" },
533
+ { label: "doctor", detail: "diagnose task source" }
534
+ ];
535
+ function recoveryRows(selected, reason) {
536
+ return [
537
+ ...reason ? [line(reason, { fg: RIG_UI.yellow }), line("", { fg: RIG_UI.ink3 })] : [],
538
+ line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
539
+ ...RECOVERY_DECKS.map((deck, index) => selectableDeckRow({ ...deck, index, active: selected === index }, TASK_RECOVERY_ITEMS[index]))
540
+ ];
157
541
  }
158
- function emptyRows(query, total) {
542
+ function emptyRows(query, total, selected) {
543
+ if (!query.trim() && total === 0)
544
+ return recoveryRows(selected, "No tasks loaded.");
159
545
  if (query.trim() && total > 0) {
160
546
  return [
161
- line("No matching tasks.", { fg: RIG_UI.ink2, bold: true }),
162
- line(`search ${JSON.stringify(query)} returned 0/${total}`, { fg: RIG_UI.ink3 }),
163
- line("escape clears search", { fg: RIG_UI.ink4 })
547
+ ...recoveryRows(selected, `No matching tasks for ${JSON.stringify(query)} (${total} total).`)
164
548
  ];
165
549
  }
166
- return [
167
- line("No tasks loaded.", { fg: RIG_UI.ink2, bold: true }),
168
- line("Check the task source or switch filters from Help.", { fg: RIG_UI.ink3 }),
169
- line("tab switches screens", { fg: RIG_UI.ink4 })
170
- ];
550
+ return recoveryRows(selected, "No tasks are visible with this filter.");
171
551
  }
172
- function degradedRows(state) {
552
+ function degradedRows(state, selected) {
173
553
  if (!state.error)
174
554
  return [];
175
- return [
176
- line(`status: ${state.error.message}`, { fg: RIG_UI.red, bold: true }),
177
- ...state.error.hint ? [line(state.error.hint, { fg: RIG_UI.yellow })] : [],
178
- line("", { fg: RIG_UI.ink3 })
555
+ return recoveryRows(selected, state.error.message);
556
+ }
557
+ function rawString(raw, key) {
558
+ const value = raw?.[key];
559
+ return typeof value === "string" ? value : "";
560
+ }
561
+ function taskBody(state, task) {
562
+ const preview = state.data.taskPreviewRaw;
563
+ if (preview && typeof preview === "object" && !Array.isArray(preview)) {
564
+ const fromPreview = rawString(preview, "body");
565
+ if (fromPreview.trim())
566
+ return fromPreview;
567
+ }
568
+ return rawString(task?.raw, "body");
569
+ }
570
+ function renderTaskDetail(state, taskId, layout) {
571
+ const task = taskRecords(state).find((entry) => entry.id === taskId);
572
+ const title = (task?.title || taskId).trim();
573
+ const status = task?.status ?? "unknown";
574
+ const { glyph, color } = statusLabel(status);
575
+ const body = taskBody(state, task);
576
+ const activeRunId = task?.activeRun?.runId?.trim();
577
+ const width = panelWidth(layout);
578
+ const selected = Math.max(0, state.selection.index);
579
+ const headerLines = [
580
+ line(`${glyph} ${title}`, { fg: color, bold: true }),
581
+ line(`task ${taskId} \xB7 ${status}`, { fg: RIG_UI.ink3 }),
582
+ ...task && task.labels.length > 0 ? [line(`labels: ${task.labels.join(", ")}`, { fg: RIG_UI.ink4 })] : [],
583
+ ...task && task.assignees.length > 0 ? [line(`assignees: ${task.assignees.join(", ")}`, { fg: RIG_UI.ink4 })] : [],
584
+ line("", { fg: RIG_UI.ink3 }),
585
+ line("ACTIONS", { fg: RIG_UI.ink3, bold: true })
179
586
  ];
587
+ let index = 0;
588
+ const actionRows = [];
589
+ actionRows.push(selectableDeckRow({ label: "back", detail: "return to the task list", index, active: selected === index }, { id: "task-detail-back", label: "back", data: { taskDetailId: undefined }, message: "back to task list" }));
590
+ index += 1;
591
+ actionRows.push(selectableDeckRow({ label: "dispatch", detail: "submit a Pi run for this task", index, active: selected === index }, { id: `task-detail-run:${taskId}`, label: `dispatch ${taskId}`, intent: { scene: "tasks", argv: ["run", taskId], action: { kind: "task-run-id", payload: { task: taskId }, label: `Dispatching ${taskId}` } }, message: `dispatch task ${taskId}` }));
592
+ index += 1;
593
+ if (activeRunId) {
594
+ actionRows.push(selectableDeckRow({ label: "attach", detail: `attach the Pi console to run ${activeRunId.slice(0, 8)}`, index, active: selected === index }, { id: `task-detail-attach:${taskId}`, label: `attach ${activeRunId.slice(0, 8)}`, intent: { scene: "handoff", argv: ["attach", activeRunId], action: { kind: "run-attach", payload: { runId: activeRunId }, label: `Attach Pi ${activeRunId.slice(0, 8)}` } }, message: `attach Pi to ${activeRunId.slice(0, 8)}` }));
595
+ index += 1;
596
+ }
597
+ const bodyHeading = [line("", { fg: RIG_UI.ink3 }), line("BODY", { fg: RIG_UI.ink3, bold: true })];
598
+ const bodyLines = body.trim() ? body.split(`
599
+ `).slice(0, 400).map((text) => line(text, { fg: RIG_UI.ink2 })) : [line("(no description)", { fg: RIG_UI.ink4 })];
600
+ const panelLines = [...headerLines, ...actionRows, ...bodyHeading, ...bodyLines];
601
+ const paddingX = layout?.compact ? 2 : 3;
602
+ const chromeTopInset = 3 + 1;
603
+ const headerCount = headerLines.length + actionRows.length + bodyHeading.length;
604
+ const native = body.trim() ? {
605
+ kind: "markdown",
606
+ content: body,
607
+ top: (layout?.centerTop ?? 1) + chromeTopInset + headerCount,
608
+ left: (layout?.centerLeft ?? 2) + paddingX,
609
+ width: Math.max(20, width - paddingX * 2),
610
+ height: Math.max(6, panelHeight(layout, 0) - chromeTopInset - headerCount - 1)
611
+ } : undefined;
612
+ return makeSceneFrame({
613
+ scene: "tasks",
614
+ title: "Task",
615
+ lines: [],
616
+ ...native ? { native } : {},
617
+ panels: [{
618
+ id: "task-detail",
619
+ top: 0,
620
+ width,
621
+ height: panelHeight(layout, 0),
622
+ lines: panelLines,
623
+ backgroundColor: RIG_UI.panel,
624
+ backgroundAlpha: 184,
625
+ opacity: 1,
626
+ border: false,
627
+ stickyScroll: true,
628
+ stickyStart: "top",
629
+ chrome: "ad-terminal",
630
+ headerText: `task ${taskId}`,
631
+ headerHeight: 3,
632
+ paddingX,
633
+ paddingY: 1
634
+ }],
635
+ footer: { run: activeRunId ? `${activeRunId.slice(0, 8)} active` : undefined, message: "back returns to the list \xB7 dispatch runs the task" },
636
+ live: state.status === "action"
637
+ });
180
638
  }
181
639
  function renderTasksScene(state, layout) {
640
+ const detailId = typeof state.data.taskDetailId === "string" ? state.data.taskDetailId.trim() : "";
641
+ if (detailId)
642
+ return renderTaskDetail(state, detailId, layout);
182
643
  const width = panelWidth(layout);
183
644
  const view = taskView(state);
184
645
  const allTasks = taskRecords(state);
185
646
  const query = listSearchQuery(state);
186
- const tasks = filterTasksForSearch(allTasks, query);
647
+ const spec = taskSortSpec(state.data.taskSort);
648
+ const tasks = sortTasks(filterTasksForSearch(allTasks, query), spec);
187
649
  const selected = typeof state.data.selectedTaskId === "string" && tasks.some((task) => task.id === state.data.selectedTaskId) ? state.data.selectedTaskId : tasks[0]?.id;
188
650
  const selectedIndex = Math.max(0, tasks.findIndex((task) => task.id === selected));
189
- const dispatch = state.data.dispatchingRun;
190
- const dispatchLine = dispatch && typeof dispatch === "object" && !Array.isArray(dispatch) ? ` \xB7 ${dispatch.runId ?? "new"} ${dispatch.status ?? "dispatching"}` : "";
651
+ const recoveryIndex = Math.max(0, Math.min(4, state.selection.index));
652
+ const dispatching = dispatchingFor(state);
653
+ const dispatchLine = dispatching ? ` \xB7 ${state.data.dispatchingRun?.runId ?? "new"} ${dispatching.status}` : "";
654
+ const updatedAgo = updatedAgoLabel(state);
191
655
  const panelTop = 0;
192
656
  const contentWidth = Math.max(12, width - (layout?.compact ? 8 : 10));
657
+ const loading = state.status === "loading" && allTasks.length === 0;
658
+ const refreshError = lastRefreshError(state);
659
+ const banner = refreshError && !state.error ? errorBanner(refreshError) : [];
193
660
  const panelLines = [
194
- line("TASK STATUS PRIORITY TITLE", { fg: RIG_UI.ink3, bold: true }),
195
- line("", { fg: RIG_UI.ink3 }),
196
- ...degradedRows(state),
197
- ...query.trim() ? [line(searchSummary("tasks", query, tasks.length, allTasks.length), { fg: RIG_UI.ink4 }), line("", { fg: RIG_UI.ink3 })] : [],
198
- ...tasks.length > 0 ? tasks.map((task, index) => taskRow(contentWidth, task, index, index === selectedIndex)) : emptyRows(query, allTasks.length)
661
+ ...banner,
662
+ ...tasks.length > 0 ? [
663
+ ...degradedRows(state, recoveryIndex),
664
+ ...query.trim() ? [line(searchSummary("tasks", query, tasks.length, allTasks.length), { fg: RIG_UI.ink4 }), line("", { fg: RIG_UI.ink3 })] : [],
665
+ ...sortStrip(spec),
666
+ headerRow(spec),
667
+ line("", { fg: RIG_UI.ink3 }),
668
+ ...tasks.map((task, index) => taskRow(contentWidth, task, index === selectedIndex, dispatching && dispatching.taskId === task.id ? dispatching.status : undefined))
669
+ ] : state.error ? degradedRows(state, recoveryIndex) : loading ? loadingRows("tasks", state.tick) : [
670
+ ...query.trim() ? [line(searchSummary("tasks", query, tasks.length, allTasks.length), { fg: RIG_UI.ink4 }), line("", { fg: RIG_UI.ink3 })] : [],
671
+ ...emptyRows(query, allTasks.length, recoveryIndex)
672
+ ]
199
673
  ];
200
674
  return makeSceneFrame({
201
675
  scene: "tasks",
202
676
  title: "Tasks",
203
677
  lines: [
204
678
  line("", { fg: RIG_UI.ink4 }),
205
- line(query.trim() ? ` rig tasks --search ${JSON.stringify(query)}` : ` rig tasks \xB7 ${taskViewLabel(view)}${dispatchLine}`, { fg: RIG_UI.ink3 })
679
+ line(`${query.trim() ? ` tasks --search ${JSON.stringify(query)}` : ` tasks \xB7 ${taskViewLabel(view)}${dispatchLine}`}${updatedAgo ? ` \xB7 ${updatedAgo}` : ""}`, { fg: RIG_UI.ink3 })
206
680
  ],
207
681
  panels: [{
208
682
  id: "tasks-list",
@@ -215,12 +689,15 @@ function renderTasksScene(state, layout) {
215
689
  opacity: 1,
216
690
  border: false,
217
691
  chrome: "ad-terminal",
218
- headerText: query.trim() ? `rig tasks --search ${JSON.stringify(query)}` : `rig tasks \xB7 ${taskViewLabel(view)}${dispatchLine}`,
692
+ headerText: query.trim() ? `tasks --search ${JSON.stringify(query)}` : `tasks \xB7 ${taskViewLabel(view)}${dispatchLine}`,
219
693
  headerHeight: 3,
220
694
  paddingX: layout?.compact ? 2 : 3,
221
695
  paddingY: 1
222
696
  }],
223
- footer: { message: query.trim() ? searchSummary("tasks", query, tasks.length, allTasks.length) : taskViewLabel(view) },
697
+ footer: {
698
+ message: query.trim() ? searchSummary("tasks", query, tasks.length, allTasks.length) : taskViewLabel(view),
699
+ ...updatedAgo ? { updated: updatedAgo } : {}
700
+ },
224
701
  typeBarPlaceholder: "search tasks\u2026",
225
702
  live: true
226
703
  });
@@ -0,0 +1,3 @@
1
+ import type { AppSceneFrame, AppState } from "../types";
2
+ import type { StageLayout } from "../layout";
3
+ export declare function renderWorkspaceScene(state: AppState, layout?: StageLayout): AppSceneFrame;