@opencode-cockpit/shell 0.1.4 → 0.2.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 (77) hide show
  1. package/README.md +100 -10
  2. package/dist/agent/plugin.js +180 -0
  3. package/dist/agent/tools/index.js +23 -0
  4. package/{src/tools/keys.ts → dist/agent/tools/keys.js} +11 -14
  5. package/dist/agent/tools/list.js +68 -0
  6. package/dist/agent/tools/read.js +59 -0
  7. package/dist/agent/tools/restart.js +44 -0
  8. package/dist/agent/tools/send.js +71 -0
  9. package/dist/agent/tools/shared.js +94 -0
  10. package/dist/agent/tools/start.js +140 -0
  11. package/dist/agent/tools/stop.js +42 -0
  12. package/dist/agent/tools/wait.js +79 -0
  13. package/dist/agent/tools/watch-args.js +12 -0
  14. package/dist/agent/tools/watch.js +74 -0
  15. package/dist/connect.js +31 -0
  16. package/dist/core/config.js +97 -0
  17. package/dist/core/find.js +69 -0
  18. package/dist/core/format.js +63 -0
  19. package/dist/core/kind.js +31 -0
  20. package/dist/server.js +2 -0
  21. package/dist/tui/components/badge.js +29 -0
  22. package/dist/tui/components/console.js +759 -0
  23. package/dist/tui/components/dock.js +270 -0
  24. package/dist/tui/components/sidebar.js +136 -0
  25. package/dist/tui/dialogs.js +163 -0
  26. package/dist/tui/index.js +205 -0
  27. package/dist/tui/lib/details.js +23 -0
  28. package/dist/tui/lib/keys.js +43 -0
  29. package/dist/tui/lib/search.js +39 -0
  30. package/dist/tui/lib/update.js +58 -0
  31. package/dist/tui/lib/view.js +191 -0
  32. package/dist/tui/state/store.js +158 -0
  33. package/package.json +21 -12
  34. package/types/agent/plugin.d.ts +12 -0
  35. package/types/agent/tools/index.d.ts +5 -0
  36. package/types/agent/tools/keys.d.ts +3 -0
  37. package/types/agent/tools/list.d.ts +3 -0
  38. package/types/agent/tools/read.d.ts +3 -0
  39. package/types/agent/tools/restart.d.ts +3 -0
  40. package/types/agent/tools/send.d.ts +3 -0
  41. package/types/agent/tools/shared.d.ts +40 -0
  42. package/types/agent/tools/start.d.ts +3 -0
  43. package/types/agent/tools/stop.d.ts +3 -0
  44. package/types/agent/tools/wait.d.ts +3 -0
  45. package/types/agent/tools/watch-args.d.ts +10 -0
  46. package/types/agent/tools/watch.d.ts +3 -0
  47. package/types/connect.d.ts +9 -0
  48. package/types/core/config.d.ts +59 -0
  49. package/types/core/find.d.ts +37 -0
  50. package/types/core/format.d.ts +8 -0
  51. package/types/core/kind.d.ts +9 -0
  52. package/types/server.d.ts +2 -0
  53. package/types/tui/components/badge.d.ts +9 -0
  54. package/types/tui/components/console.d.ts +22 -0
  55. package/types/tui/components/dock.d.ts +14 -0
  56. package/types/tui/components/sidebar.d.ts +14 -0
  57. package/types/tui/dialogs.d.ts +10 -0
  58. package/types/tui/index.d.ts +13 -0
  59. package/types/tui/lib/details.d.ts +3 -0
  60. package/types/tui/lib/keys.d.ts +8 -0
  61. package/types/tui/lib/search.d.ts +7 -0
  62. package/types/tui/lib/update.d.ts +25 -0
  63. package/types/tui/lib/view.d.ts +80 -0
  64. package/types/tui/state/store.d.ts +55 -0
  65. package/src/connect.ts +0 -24
  66. package/src/server.ts +0 -156
  67. package/src/tools/find.ts +0 -80
  68. package/src/tools/format.ts +0 -78
  69. package/src/tools/index.ts +0 -467
  70. package/src/tui/badge.tsx +0 -17
  71. package/src/tui/console.tsx +0 -438
  72. package/src/tui/dock.tsx +0 -130
  73. package/src/tui/index.tsx +0 -286
  74. package/src/tui/keys.ts +0 -52
  75. package/src/tui/sidebar.tsx +0 -44
  76. package/src/tui/store.ts +0 -185
  77. package/src/tui/view.ts +0 -161
@@ -0,0 +1,759 @@
1
+ import { memo as _$memo } from "@opentui/solid";
2
+ import { use as _$use } from "@opentui/solid";
3
+ import { effect as _$effect } from "@opentui/solid";
4
+ import { createTextNode as _$createTextNode } from "@opentui/solid";
5
+ import { insertNode as _$insertNode } from "@opentui/solid";
6
+ import { insert as _$insert } from "@opentui/solid";
7
+ import { createComponent as _$createComponent } from "@opentui/solid";
8
+ import { setProp as _$setProp } from "@opentui/solid";
9
+ import { createElement as _$createElement } from "@opentui/solid";
10
+ /** @jsxImportSource @opentui/solid */
11
+
12
+ import { useBindings } from "@opentui/keymap/solid";
13
+ import { useTerminalDimensions } from "@opentui/solid";
14
+ import { createEffect, createMemo, createSignal, For, on, onCleanup, Show } from "solid-js";
15
+ import { detailRows } from "../lib/details.js";
16
+ import { isReleaseKey, keyToBytes } from "../lib/keys.js";
17
+ import { friendlyError, splitMatches } from "../lib/search.js";
18
+ import { displayCommand, kindColor, kindOf, relativeCwd, statusDetail, tailLines, tailRuns, truncate, watchColor, watchLabel, wrapText } from "../lib/view.js";
19
+ import { useScreen } from "../state/store.js";
20
+ import { Badge } from "./badge.js";
21
+ /** Daemon errors name ids and internal states; say what happened instead. */
22
+ const COMMAND_LINES = 3;
23
+
24
+ /** Splits a line into plain and matching parts so a filtered log can highlight what matched. */
25
+
26
+ /**
27
+ * Keyboard-first shell console in an overlay. Normal mode: single-key actions. Typing mode:
28
+ * every key goes to the program (ctrl+c included); ctrl+] returns to normal mode.
29
+ */
30
+ export function Console(props) {
31
+ const theme = () => props.api.theme.current;
32
+ const dims = useTerminalDimensions();
33
+ const shell = () => props.store.selected();
34
+ const [view, setView] = createSignal(props.defaultView ?? "screen");
35
+ const [typing, setTyping] = createSignal(props.typing ?? false);
36
+ const [log, setLog] = createSignal([]);
37
+ const [notice, setNotice] = createSignal();
38
+ /** Applied log filter, and the query being typed for it. */
39
+ const [filter, setFilter] = createSignal("");
40
+ const [searching, setSearching] = createSignal(false);
41
+ const [draft, setDraft] = createSignal("");
42
+ const {
43
+ screen
44
+ } = useScreen(props.store, () => shell()?.id);
45
+ let scroll;
46
+ const running = () => shell()?.status === "running";
47
+ const client = props.store.client;
48
+
49
+ // Layout budget. The host dialog starts at 1/4 of the height and is 116 columns wide (xlarge),
50
+ // capped by the screen; the body grows with content up to what the header leaves.
51
+ const frameRows = () => Math.max(10, dims().height - Math.floor(dims().height / 4) - 3);
52
+ const bodyCols = () => Math.max(20, Math.min(116, dims().width - 2) - 7);
53
+ const commandLines = createMemo(() => {
54
+ const s = shell();
55
+ return s ? wrapText(displayCommand(s), bodyCols() - 2, COMMAND_LINES) : [];
56
+ });
57
+ const folder = createMemo(() => {
58
+ const s = shell();
59
+ return s ? relativeCwd(s.cwd, props.store.project()) : "";
60
+ });
61
+ const failure = createMemo(() => {
62
+ const s = shell();
63
+ if (!s?.summary) return undefined;
64
+ const kind = kindOf(s);
65
+ return kind === "fail" || kind === "stop" ? s.summary : undefined;
66
+ });
67
+ const bodyRows = () => {
68
+ const header = 1 + commandLines().length + (folder() ? 1 : 0) + (failure() ? 1 : 0);
69
+ return Math.max(3, frameRows() - header - 4); // margins (2) + footer (1) + bottom padding (1)
70
+ };
71
+
72
+ // Transient footer message. Replaces the key hints while shown, then clears itself.
73
+ let noticeTimer;
74
+ const flash = (text, tone, ms) => {
75
+ clearTimeout(noticeTimer);
76
+ setNotice({
77
+ text,
78
+ tone
79
+ });
80
+ if (ms) noticeTimer = setTimeout(() => setNotice(undefined), ms);
81
+ };
82
+ onCleanup(() => clearTimeout(noticeTimer));
83
+ const act = (label, fn) => {
84
+ flash(`${label}…`, "info");
85
+ fn().then(result => typeof result === "string" ? flash(result, "success", 2500) : setNotice(undefined)).catch(err => flash(`${label} failed: ${friendlyError(err)}`, "error", 5000));
86
+ };
87
+
88
+ /** Actions that need a live process explain themselves instead of failing. */
89
+ const whileRunning = (label, fn) => () => {
90
+ const s = shell();
91
+ if (!s) return;
92
+ if (s.status !== "running") {
93
+ flash(`can't ${label}: this shell already ${kindOf(s) === "done" ? "finished" : "ended"} · r runs it again`, "info", 3000);
94
+ return;
95
+ }
96
+ fn(s.id);
97
+ };
98
+
99
+ // Log view: reload on selection, view switch and new output.
100
+ createEffect(on([() => shell()?.id, view, filter, () => screen()], () => {
101
+ const id = shell()?.id;
102
+ if (!id || view() !== "log") return;
103
+ // The daemon greps server-side, so filtering a 40k-line log costs one call, not a transfer.
104
+ void client.call("shell.read", {
105
+ id,
106
+ tail: 2000,
107
+ limit: 2000,
108
+ grep: filter() || undefined,
109
+ ignoreCase: true
110
+ }).then(page => setLog(page.lines)).catch(() => setLog([]));
111
+ }));
112
+
113
+ // Typing mode: size the PTY to what we show, then forward every key.
114
+ createEffect(on(typing, on => {
115
+ const id = shell()?.id;
116
+ if (!on || !id || !running()) return;
117
+ setView("screen");
118
+ void client.call("shell.resize", {
119
+ id,
120
+ cols: bodyCols(),
121
+ rows: bodyRows()
122
+ }).catch(() => {});
123
+ }));
124
+ createEffect(() => {
125
+ if (typing() && !running()) setTyping(false);
126
+ });
127
+ const release = props.api.keymap.intercept("key", ctx => {
128
+ // Search takes keys first: a typed query must reach neither the program nor the dialog.
129
+ if (searching()) {
130
+ const event = ctx.event;
131
+ ctx.consume({
132
+ preventDefault: true,
133
+ stopPropagation: true
134
+ });
135
+ if (event.name === "escape") return setSearching(false);
136
+ if (event.name === "return" || event.name === "enter") {
137
+ setFilter(draft().trim());
138
+ return setSearching(false);
139
+ }
140
+ if (event.name === "backspace") return setDraft(value => value.slice(0, -1));
141
+ if (event.sequence && !event.ctrl && !event.meta && event.sequence >= " ") {
142
+ setDraft(value => value + event.sequence);
143
+ }
144
+ return;
145
+ }
146
+ if (!typing()) return;
147
+ const event = ctx.event;
148
+ ctx.consume({
149
+ preventDefault: true,
150
+ stopPropagation: true
151
+ });
152
+ if (isReleaseKey(event)) {
153
+ setTyping(false);
154
+ return;
155
+ }
156
+ const bytes = keyToBytes(event);
157
+ const id = shell()?.id;
158
+ if (bytes && id) void client.call("shell.write", {
159
+ id,
160
+ data: bytes
161
+ }).catch(() => setTyping(false));
162
+ }, {
163
+ priority: 10_000
164
+ });
165
+ onCleanup(release);
166
+ const withShell = fn => () => {
167
+ const id = shell()?.id;
168
+ if (id) fn(id);
169
+ };
170
+ const toggle = next => setView(v => v === next ? "screen" : next);
171
+ useBindings(() => ({
172
+ enabled: () => !typing(),
173
+ commands: [{
174
+ name: "cockpit.console.type",
175
+ title: "Type into shell",
176
+ run: whileRunning("type", () => setTyping(true))
177
+ }, {
178
+ name: "cockpit.console.interrupt",
179
+ title: "Send ctrl+c",
180
+ run: whileRunning("interrupt", id => act("interrupt", async () => {
181
+ await client.call("shell.write", {
182
+ id,
183
+ data: "\x03"
184
+ });
185
+ return "sent ctrl+c";
186
+ }))
187
+ }, {
188
+ name: "cockpit.console.restart",
189
+ title: "Restart shell",
190
+ run: withShell(id => act("restart", () => client.call("shell.restart", {
191
+ id
192
+ })))
193
+ }, {
194
+ name: "cockpit.console.stop",
195
+ title: "Stop shell",
196
+ run: whileRunning("stop", id => act("stop", async () => {
197
+ await client.call("shell.stop", {
198
+ id,
199
+ signal: "SIGTERM",
200
+ graceMs: 3000
201
+ });
202
+ return "stopped";
203
+ }))
204
+ }, {
205
+ name: "cockpit.console.remove",
206
+ title: "Remove shell",
207
+ run: withShell(id => act("remove", () => client.call("shell.remove", {
208
+ id
209
+ })))
210
+ }, {
211
+ name: "cockpit.console.clear",
212
+ title: "Clear finished shells",
213
+ run: () => {
214
+ if (finished() === 0) {
215
+ flash("nothing to clear: no finished shells", "info", 2500);
216
+ return;
217
+ }
218
+ act("clear", async () => {
219
+ const n = await props.store.clearFinished();
220
+ return `cleared ${n} finished shell${n === 1 ? "" : "s"}`;
221
+ });
222
+ }
223
+ }, {
224
+ name: "cockpit.console.view",
225
+ title: "Toggle screen/log",
226
+ run: () => setView(v => v === "log" ? "screen" : "log")
227
+ }, {
228
+ name: "cockpit.console.search",
229
+ title: "Search this shell's log",
230
+ run: () => {
231
+ setView("log");
232
+ setDraft(filter());
233
+ setSearching(true);
234
+ }
235
+ }, {
236
+ name: "cockpit.console.searchClear",
237
+ title: "Clear the log filter",
238
+ run: () => {
239
+ setFilter("");
240
+ setDraft("");
241
+ }
242
+ }, {
243
+ name: "cockpit.console.details",
244
+ title: "Toggle details",
245
+ run: () => toggle("details")
246
+ }, {
247
+ name: "cockpit.console.next",
248
+ title: "Next shell",
249
+ run: () => props.store.step(1)
250
+ }, {
251
+ name: "cockpit.console.prev",
252
+ title: "Previous shell",
253
+ run: () => props.store.step(-1)
254
+ }, {
255
+ name: "cockpit.console.new",
256
+ title: "New shell",
257
+ run: () => props.onNewShell()
258
+ }, {
259
+ name: "cockpit.console.down",
260
+ title: "Scroll down",
261
+ run: () => scroll?.scrollBy(3)
262
+ }, {
263
+ name: "cockpit.console.up",
264
+ title: "Scroll up",
265
+ run: () => scroll?.scrollBy(-3)
266
+ }, {
267
+ name: "cockpit.console.bottom",
268
+ title: "Scroll to end",
269
+ run: () => scroll?.scrollTo(scroll.scrollHeight)
270
+ }, {
271
+ name: "cockpit.console.top",
272
+ title: "Scroll to top",
273
+ run: () => scroll?.scrollTo(0)
274
+ }, {
275
+ name: "cockpit.console.close",
276
+ title: "Close console",
277
+ run: () => props.onClose()
278
+ }],
279
+ bindings: [{
280
+ key: "i,return",
281
+ cmd: "cockpit.console.type",
282
+ desc: "Type"
283
+ }, {
284
+ key: "c",
285
+ cmd: "cockpit.console.interrupt",
286
+ desc: "^C"
287
+ }, {
288
+ key: "r",
289
+ cmd: "cockpit.console.restart",
290
+ desc: "Restart"
291
+ }, {
292
+ key: "x",
293
+ cmd: "cockpit.console.stop",
294
+ desc: "Stop"
295
+ }, {
296
+ key: "d",
297
+ cmd: "cockpit.console.remove",
298
+ desc: "Remove"
299
+ }, {
300
+ key: "shift+d",
301
+ cmd: "cockpit.console.clear",
302
+ desc: "Clear finished"
303
+ }, {
304
+ key: "tab",
305
+ cmd: "cockpit.console.view",
306
+ desc: "Screen/log"
307
+ }, {
308
+ key: "/",
309
+ cmd: "cockpit.console.search",
310
+ desc: "Search log"
311
+ }, {
312
+ key: "backspace",
313
+ cmd: "cockpit.console.searchClear",
314
+ desc: "Clear filter"
315
+ }, {
316
+ key: "?,shift+/",
317
+ cmd: "cockpit.console.details",
318
+ desc: "Details"
319
+ }, {
320
+ key: "],l,right",
321
+ cmd: "cockpit.console.next",
322
+ desc: "Next"
323
+ }, {
324
+ key: "[,h,left",
325
+ cmd: "cockpit.console.prev",
326
+ desc: "Prev"
327
+ }, {
328
+ key: "n",
329
+ cmd: "cockpit.console.new",
330
+ desc: "New"
331
+ }, {
332
+ key: "j,down",
333
+ cmd: "cockpit.console.down",
334
+ desc: "Down"
335
+ }, {
336
+ key: "k,up",
337
+ cmd: "cockpit.console.up",
338
+ desc: "Up"
339
+ }, {
340
+ key: "shift+g,end",
341
+ cmd: "cockpit.console.bottom",
342
+ desc: "End"
343
+ }, {
344
+ key: "g,home",
345
+ cmd: "cockpit.console.top",
346
+ desc: "Top"
347
+ }, {
348
+ key: "q",
349
+ cmd: "cockpit.console.close",
350
+ desc: "Close"
351
+ }]
352
+ }));
353
+ const screenText = createMemo(() => tailLines(screen()?.text, bodyRows(), bodyCols()));
354
+ // Colour when the daemon sent styled rows; the plain text stays the fallback.
355
+ const screenRuns = createMemo(() => props.colors === false ? [] : tailRuns(screen()?.styled, bodyRows(), bodyCols()));
356
+ const details = createMemo(() => shell() ? detailRows(shell(), props.store.now(), bodyCols()) : []);
357
+ const bodyHeight = createMemo(() => {
358
+ if (typing()) return bodyRows();
359
+ const content = view() === "log" ? log().length : view() === "details" ? details().length : screenText().split("\n").length;
360
+ return Math.min(bodyRows(), Math.max(6, content));
361
+ });
362
+ const finished = createMemo(() => props.store.shells().filter(s => s.status !== "running").length);
363
+ const position = createMemo(() => {
364
+ const list = props.store.shells();
365
+ const index = list.findIndex(x => x.id === shell()?.id);
366
+ return list.length > 1 ? `${index + 1}/${list.length}` : "";
367
+ });
368
+ // Only the keys that do something for the selected shell.
369
+ /** Only the keys that do something right now: no sidebar concepts, no actions with no target. */
370
+ const hint = createMemo(() => {
371
+ if (searching()) return `search: ${draft()}▏· enter filters · esc cancels`;
372
+ if (typing()) return "TYPING: keys go to the shell (ctrl+c included) · ctrl+] stop typing";
373
+ const wide = dims().width >= 110;
374
+ const label = (long, short) => wide ? long : short;
375
+ const keys = [];
376
+ if (!shell()) return ["n new", "esc close"].join(" · ");
377
+ keys.push(...(running() ? [label("i type", "i"), label("c ^C", "c"), label("r restart", "r"), label("x stop", "x")] : [label("r run again", "r"), label("d remove", "d")]));
378
+ if (view() === "log" && filter()) keys.push(label("backspace clear filter", "⌫ filter"));
379
+ keys.push(label(`tab ${view() === "log" ? "screen" : "log"}`, "tab"));
380
+ if (view() !== "details") keys.push(label("/ search log", "/"));
381
+ keys.push(label("? details", "?"));
382
+ if (props.store.shells().length > 1) keys.push(label("[ ] switch", "[ ]"));
383
+ if (finished() > 0) keys.push(label("D clear done", "D"));
384
+ keys.push(label("n new", "n"), "esc");
385
+ return keys.join(" · ");
386
+ });
387
+ return (() => {
388
+ var _el$ = _$createElement("box"),
389
+ _el$2 = _$createElement("box");
390
+ _$insertNode(_el$, _el$2);
391
+ _$setProp(_el$, "flexDirection", "column");
392
+ _$setProp(_el$, "paddingLeft", 2);
393
+ _$setProp(_el$, "paddingRight", 2);
394
+ _$setProp(_el$, "paddingBottom", 1);
395
+ _$setProp(_el$, "overflow", "hidden");
396
+ _$insert(_el$, _$createComponent(Show, {
397
+ get when() {
398
+ return shell();
399
+ },
400
+ get fallback() {
401
+ return (() => {
402
+ var _el$3 = _$createElement("box"),
403
+ _el$4 = _$createElement("text"),
404
+ _el$5 = _$createElement("b"),
405
+ _el$7 = _$createElement("text");
406
+ _$insertNode(_el$3, _el$4);
407
+ _$insertNode(_el$3, _el$7);
408
+ _$setProp(_el$3, "flexDirection", "column");
409
+ _$insertNode(_el$4, _el$5);
410
+ _$setProp(_el$4, "wrapMode", "none");
411
+ _$insertNode(_el$5, _$createTextNode(`No shells in this project`));
412
+ _$setProp(_el$7, "wrapMode", "none");
413
+ _$insert(_el$7, () => truncate("Press n to start one. The agent starts its own with shell_start.", bodyCols()));
414
+ _$effect(_p$ => {
415
+ var _v$ = theme().text,
416
+ _v$2 = theme().textMuted;
417
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$4, "fg", _v$, _p$.e));
418
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$7, "fg", _v$2, _p$.t));
419
+ return _p$;
420
+ }, {
421
+ e: undefined,
422
+ t: undefined
423
+ });
424
+ return _el$3;
425
+ })();
426
+ },
427
+ children: s => [(() => {
428
+ var _el$8 = _$createElement("box"),
429
+ _el$9 = _$createElement("text"),
430
+ _el$0 = _$createTextNode(` `),
431
+ _el$1 = _$createElement("b"),
432
+ _el$10 = _$createElement("box"),
433
+ _el$14 = _$createElement("text");
434
+ _$insertNode(_el$8, _el$9);
435
+ _$insertNode(_el$8, _el$10);
436
+ _$insertNode(_el$8, _el$14);
437
+ _$setProp(_el$8, "height", 1);
438
+ _$setProp(_el$8, "flexShrink", 0);
439
+ _$setProp(_el$8, "flexDirection", "row");
440
+ _$setProp(_el$8, "overflow", "hidden");
441
+ _$insert(_el$8, _$createComponent(Badge, {
442
+ get api() {
443
+ return props.api;
444
+ },
445
+ get shell() {
446
+ return s();
447
+ },
448
+ get frame() {
449
+ return props.store.frame();
450
+ }
451
+ }), _el$9);
452
+ _$insertNode(_el$9, _el$0);
453
+ _$insertNode(_el$9, _el$1);
454
+ _$setProp(_el$9, "wrapMode", "none");
455
+ _$setProp(_el$9, "flexShrink", 1);
456
+ _$insert(_el$1, () => truncate(s().title, Math.max(10, bodyCols() - 50)));
457
+ _$setProp(_el$10, "flexGrow", 1);
458
+ _$insert(_el$8, _$createComponent(Show, {
459
+ get when() {
460
+ return watchLabel(s());
461
+ },
462
+ get children() {
463
+ var _el$11 = _$createElement("text"),
464
+ _el$12 = _$createTextNode(` `),
465
+ _el$13 = _$createTextNode(` `);
466
+ _$insertNode(_el$11, _el$12);
467
+ _$insertNode(_el$11, _el$13);
468
+ _$setProp(_el$11, "wrapMode", "none");
469
+ _$setProp(_el$11, "flexShrink", 0);
470
+ _$insert(_el$11, () => watchLabel(s()), _el$13);
471
+ _$effect(_$p => _$setProp(_el$11, "fg", watchColor(theme(), s()), _$p));
472
+ return _el$11;
473
+ }
474
+ }), _el$14);
475
+ _$setProp(_el$14, "wrapMode", "none");
476
+ _$setProp(_el$14, "flexShrink", 0);
477
+ _$insert(_el$14, () => [statusDetail(s(), props.store.now()), s().run > 1 ? `run ${s().run}` : "", position()].filter(Boolean).join(" · "));
478
+ _$effect(_p$ => {
479
+ var _v$3 = theme().text,
480
+ _v$4 = theme().textMuted;
481
+ _v$3 !== _p$.e && (_p$.e = _$setProp(_el$9, "fg", _v$3, _p$.e));
482
+ _v$4 !== _p$.t && (_p$.t = _$setProp(_el$14, "fg", _v$4, _p$.t));
483
+ return _p$;
484
+ }, {
485
+ e: undefined,
486
+ t: undefined
487
+ });
488
+ return _el$8;
489
+ })(), _$createComponent(For, {
490
+ get each() {
491
+ return commandLines();
492
+ },
493
+ children: (line, index) => (() => {
494
+ var _el$23 = _$createElement("text"),
495
+ _el$24 = _$createElement("span");
496
+ _$insertNode(_el$23, _el$24);
497
+ _$setProp(_el$23, "wrapMode", "none");
498
+ _$setProp(_el$23, "flexShrink", 0);
499
+ _$insert(_el$24, () => index() === 0 ? "$ " : " ");
500
+ _$insert(_el$23, line, null);
501
+ _$effect(_p$ => {
502
+ var _v$7 = theme().text,
503
+ _v$8 = {
504
+ fg: theme().textMuted
505
+ };
506
+ _v$7 !== _p$.e && (_p$.e = _$setProp(_el$23, "fg", _v$7, _p$.e));
507
+ _v$8 !== _p$.t && (_p$.t = _$setProp(_el$24, "style", _v$8, _p$.t));
508
+ return _p$;
509
+ }, {
510
+ e: undefined,
511
+ t: undefined
512
+ });
513
+ return _el$23;
514
+ })()
515
+ }), _$createComponent(Show, {
516
+ get when() {
517
+ return folder();
518
+ },
519
+ get children() {
520
+ var _el$15 = _$createElement("text");
521
+ _$setProp(_el$15, "wrapMode", "none");
522
+ _$setProp(_el$15, "flexShrink", 0);
523
+ _$insert(_el$15, () => truncate(`in ${folder()}`, bodyCols()));
524
+ _$effect(_$p => _$setProp(_el$15, "fg", theme().textMuted, _$p));
525
+ return _el$15;
526
+ }
527
+ }), _$createComponent(Show, {
528
+ get when() {
529
+ return failure();
530
+ },
531
+ children: summary => (() => {
532
+ var _el$25 = _$createElement("text");
533
+ _$setProp(_el$25, "wrapMode", "none");
534
+ _$setProp(_el$25, "flexShrink", 0);
535
+ _$insert(_el$25, () => truncate(`${kindOf(s()) === "fail" ? "error" : "last output"}: ${summary()}`, bodyCols()));
536
+ _$effect(_$p => _$setProp(_el$25, "fg", kindColor(theme(), kindOf(s())), _$p));
537
+ return _el$25;
538
+ })()
539
+ }), (() => {
540
+ var _el$16 = _$createElement("box");
541
+ _$setProp(_el$16, "flexShrink", 0);
542
+ _$setProp(_el$16, "marginTop", 1);
543
+ _$setProp(_el$16, "marginBottom", 1);
544
+ _$setProp(_el$16, "border", ["left"]);
545
+ _$setProp(_el$16, "paddingLeft", 1);
546
+ _$setProp(_el$16, "overflow", "hidden");
547
+ _$insert(_el$16, _$createComponent(Show, {
548
+ get when() {
549
+ return view() === "screen";
550
+ },
551
+ get children() {
552
+ return _$createComponent(Show, {
553
+ get when() {
554
+ return screenRuns().length > 0;
555
+ },
556
+ get fallback() {
557
+ return (() => {
558
+ var _el$26 = _$createElement("text");
559
+ _$setProp(_el$26, "wrapMode", "none");
560
+ _$insert(_el$26, () => screenText() || " ");
561
+ _$effect(_$p => _$setProp(_el$26, "fg", theme().text, _$p));
562
+ return _el$26;
563
+ })();
564
+ },
565
+ get children() {
566
+ var _el$17 = _$createElement("box");
567
+ _$setProp(_el$17, "flexDirection", "column");
568
+ _$insert(_el$17, _$createComponent(For, {
569
+ get each() {
570
+ return screenRuns();
571
+ },
572
+ children: row => (() => {
573
+ var _el$27 = _$createElement("text"),
574
+ _el$28 = _$createTextNode(` `);
575
+ _$insertNode(_el$27, _el$28);
576
+ _$setProp(_el$27, "wrapMode", "none");
577
+ _$insert(_el$27, _$createComponent(For, {
578
+ each: row,
579
+ children: run => (() => {
580
+ var _el$29 = _$createElement("span");
581
+ _$insert(_el$29, () => run.text);
582
+ _$effect(_$p => _$setProp(_el$29, "style", {
583
+ fg: run.fg,
584
+ bg: run.bg,
585
+ bold: run.bold,
586
+ italic: run.italic
587
+ }, _$p));
588
+ return _el$29;
589
+ })()
590
+ }), _el$28);
591
+ _$effect(_$p => _$setProp(_el$27, "fg", theme().text, _$p));
592
+ return _el$27;
593
+ })()
594
+ }));
595
+ return _el$17;
596
+ }
597
+ });
598
+ }
599
+ }), null);
600
+ _$insert(_el$16, _$createComponent(Show, {
601
+ get when() {
602
+ return view() === "details";
603
+ },
604
+ get children() {
605
+ var _el$18 = _$createElement("box");
606
+ _$setProp(_el$18, "flexDirection", "column");
607
+ _$insert(_el$18, _$createComponent(For, {
608
+ get each() {
609
+ return details();
610
+ },
611
+ children: ([key, value]) => (() => {
612
+ var _el$30 = _$createElement("text"),
613
+ _el$31 = _$createElement("span");
614
+ _$insertNode(_el$30, _el$31);
615
+ _$setProp(_el$30, "wrapMode", "none");
616
+ _$insert(_el$31, () => key.padEnd(10));
617
+ _$insert(_el$30, value, null);
618
+ _$effect(_p$ => {
619
+ var _v$9 = theme().text,
620
+ _v$0 = {
621
+ fg: theme().textMuted
622
+ };
623
+ _v$9 !== _p$.e && (_p$.e = _$setProp(_el$30, "fg", _v$9, _p$.e));
624
+ _v$0 !== _p$.t && (_p$.t = _$setProp(_el$31, "style", _v$0, _p$.t));
625
+ return _p$;
626
+ }, {
627
+ e: undefined,
628
+ t: undefined
629
+ });
630
+ return _el$30;
631
+ })()
632
+ }));
633
+ return _el$18;
634
+ }
635
+ }), null);
636
+ _$insert(_el$16, _$createComponent(Show, {
637
+ get when() {
638
+ return view() === "log";
639
+ },
640
+ get children() {
641
+ var _el$19 = _$createElement("scrollbox");
642
+ _$use(el => {
643
+ scroll = el;
644
+ }, _el$19);
645
+ _$setProp(_el$19, "flexGrow", 1);
646
+ _$setProp(_el$19, "stickyScroll", true);
647
+ _$setProp(_el$19, "stickyStart", "bottom");
648
+ _$setProp(_el$19, "verticalScrollbarOptions", {
649
+ visible: false
650
+ });
651
+ _$setProp(_el$19, "horizontalScrollbarOptions", {
652
+ visible: false
653
+ });
654
+ _$insert(_el$19, _$createComponent(For, {
655
+ get each() {
656
+ return log();
657
+ },
658
+ children: line => (() => {
659
+ var _el$32 = _$createElement("text"),
660
+ _el$33 = _$createElement("span"),
661
+ _el$34 = _$createTextNode(` `);
662
+ _$insertNode(_el$32, _el$33);
663
+ _$setProp(_el$32, "wrapMode", "none");
664
+ _$insertNode(_el$33, _el$34);
665
+ _$insert(_el$33, () => String(line.n).padStart(5), _el$34);
666
+ _$insert(_el$32, _$createComponent(For, {
667
+ get each() {
668
+ return splitMatches(truncate(line.text, bodyCols() - 7), filter());
669
+ },
670
+ children: part => part.match ? (() => {
671
+ var _el$35 = _$createElement("span");
672
+ _$insert(_el$35, () => part.text);
673
+ _$effect(_$p => _$setProp(_el$35, "style", {
674
+ fg: theme().background,
675
+ bg: theme().warning
676
+ }, _$p));
677
+ return _el$35;
678
+ })() : (() => {
679
+ var _el$36 = _$createElement("span");
680
+ _$insert(_el$36, () => part.text);
681
+ return _el$36;
682
+ })()
683
+ }), null);
684
+ _$effect(_p$ => {
685
+ var _v$1 = theme().text,
686
+ _v$10 = {
687
+ fg: theme().textMuted
688
+ };
689
+ _v$1 !== _p$.e && (_p$.e = _$setProp(_el$32, "fg", _v$1, _p$.e));
690
+ _v$10 !== _p$.t && (_p$.t = _$setProp(_el$33, "style", _v$10, _p$.t));
691
+ return _p$;
692
+ }, {
693
+ e: undefined,
694
+ t: undefined
695
+ });
696
+ return _el$32;
697
+ })()
698
+ }), null);
699
+ _$insert(_el$19, _$createComponent(Show, {
700
+ get when() {
701
+ return _$memo(() => !!filter())() && log().length === 0;
702
+ },
703
+ get children() {
704
+ var _el$20 = _$createElement("text"),
705
+ _el$21 = _$createTextNode(`no lines match "`),
706
+ _el$22 = _$createTextNode(`"`);
707
+ _$insertNode(_el$20, _el$21);
708
+ _$insertNode(_el$20, _el$22);
709
+ _$insert(_el$20, filter, _el$22);
710
+ _$effect(_$p => _$setProp(_el$20, "fg", theme().textMuted, _$p));
711
+ return _el$20;
712
+ }
713
+ }), null);
714
+ return _el$19;
715
+ }
716
+ }), null);
717
+ _$effect(_p$ => {
718
+ var _v$5 = bodyHeight(),
719
+ _v$6 = typing() ? theme().accent : theme().border;
720
+ _v$5 !== _p$.e && (_p$.e = _$setProp(_el$16, "height", _v$5, _p$.e));
721
+ _v$6 !== _p$.t && (_p$.t = _$setProp(_el$16, "borderColor", _v$6, _p$.t));
722
+ return _p$;
723
+ }, {
724
+ e: undefined,
725
+ t: undefined
726
+ });
727
+ return _el$16;
728
+ })()]
729
+ }), _el$2);
730
+ _$setProp(_el$2, "height", 1);
731
+ _$setProp(_el$2, "flexShrink", 0);
732
+ _$setProp(_el$2, "flexDirection", "row");
733
+ _$setProp(_el$2, "overflow", "hidden");
734
+ _$insert(_el$2, _$createComponent(Show, {
735
+ get when() {
736
+ return notice();
737
+ },
738
+ get fallback() {
739
+ return (() => {
740
+ var _el$37 = _$createElement("text");
741
+ _$setProp(_el$37, "wrapMode", "none");
742
+ _$insert(_el$37, () => truncate(hint(), bodyCols()));
743
+ _$effect(_$p => _$setProp(_el$37, "fg", typing() ? theme().accent : theme().textMuted, _$p));
744
+ return _el$37;
745
+ })();
746
+ },
747
+ children: n => (() => {
748
+ var _el$38 = _$createElement("text");
749
+ _$setProp(_el$38, "wrapMode", "none");
750
+ _$insert(_el$38, () => truncate(n().text, bodyCols()));
751
+ _$effect(_$p => _$setProp(_el$38, "fg", n().tone === "error" ? theme().error : n().tone === "success" ? theme().success : theme().warning, _$p));
752
+ return _el$38;
753
+ })()
754
+ }));
755
+ return _el$;
756
+ })();
757
+ }
758
+
759
+ /** Everything about a shell, with the full command wrapped rather than cut. */