@opencode-cockpit/shell 0.1.3 → 0.1.5

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.
@@ -0,0 +1,214 @@
1
+ import { effect as _$effect } from "@opentui/solid";
2
+ import { insert as _$insert } from "@opentui/solid";
3
+ import { memo as _$memo } from "@opentui/solid";
4
+ import { createComponent as _$createComponent } from "@opentui/solid";
5
+ import { createTextNode as _$createTextNode } from "@opentui/solid";
6
+ import { insertNode as _$insertNode } from "@opentui/solid";
7
+ import { setProp as _$setProp } from "@opentui/solid";
8
+ import { createElement as _$createElement } from "@opentui/solid";
9
+ /** @jsxImportSource @opentui/solid */
10
+
11
+ import { useTerminalDimensions } from "@opentui/solid";
12
+ import { createMemo, For, Show } from "solid-js";
13
+ import { Badge } from "./badge.js";
14
+ import { useScreen } from "./store.js";
15
+ import { displayCommand, kindColor, kindOf, statusDetail, tailLines, truncate } from "./view.js";
16
+ /** Split pane under the chat: status tabs for shells and the selected shell's live screen. */
17
+ export function Dock(props) {
18
+ const theme = () => props.api.theme.current;
19
+ const dims = useTerminalDimensions();
20
+ const {
21
+ screen
22
+ } = useScreen(props.store, () => props.store.selected()?.id);
23
+ const bodyRows = () => Math.max(2, props.height - 3);
24
+ // Tabs share one row: keep them to what fits, the rest lives behind the "N more" chip.
25
+ const tabLimit = () => Math.max(1, Math.floor((dims().width - 26) / 30));
26
+ const tabs = createMemo(() => (props.store.showAll() ? props.store.shells() : props.store.visible()).slice(0, tabLimit()));
27
+ const overflow = createMemo(() => props.store.shells().length - tabs().length);
28
+ const body = createMemo(() => tailLines(screen()?.text, bodyRows(), Math.max(10, dims().width - 4)));
29
+ return (() => {
30
+ var _el$ = _$createElement("box"),
31
+ _el$2 = _$createElement("box"),
32
+ _el$3 = _$createElement("text"),
33
+ _el$4 = _$createElement("b"),
34
+ _el$7 = _$createElement("box"),
35
+ _el$8 = _$createElement("text");
36
+ _$insertNode(_el$, _el$2);
37
+ _$setProp(_el$, "flexShrink", 0);
38
+ _$setProp(_el$, "flexDirection", "column");
39
+ _$setProp(_el$, "border", ["top"]);
40
+ _$insertNode(_el$2, _el$3);
41
+ _$insertNode(_el$2, _el$7);
42
+ _$insertNode(_el$2, _el$8);
43
+ _$setProp(_el$2, "flexDirection", "row");
44
+ _$setProp(_el$2, "gap", 1);
45
+ _$setProp(_el$2, "paddingLeft", 1);
46
+ _$setProp(_el$2, "paddingRight", 1);
47
+ _$setProp(_el$2, "flexShrink", 0);
48
+ _$setProp(_el$2, "height", 1);
49
+ _$setProp(_el$2, "overflow", "hidden");
50
+ _$insertNode(_el$3, _el$4);
51
+ _$setProp(_el$3, "flexShrink", 0);
52
+ _$insertNode(_el$4, _$createTextNode(`Shells`));
53
+ _$insert(_el$2, _$createComponent(Show, {
54
+ get when() {
55
+ return props.store.shells().length > 0;
56
+ },
57
+ get fallback() {
58
+ return (() => {
59
+ var _el$9 = _$createElement("text");
60
+ _$insertNode(_el$9, _$createTextNode(`none yet · the agent starts them, or /shell-new`));
61
+ _$setProp(_el$9, "wrapMode", "none");
62
+ _$effect(_$p => _$setProp(_el$9, "fg", theme().textMuted, _$p));
63
+ return _el$9;
64
+ })();
65
+ },
66
+ get children() {
67
+ return [_$createComponent(For, {
68
+ get each() {
69
+ return tabs();
70
+ },
71
+ children: shell => {
72
+ const active = () => props.store.selected()?.id === shell.id;
73
+ return (() => {
74
+ var _el$1 = _$createElement("box"),
75
+ _el$10 = _$createElement("text"),
76
+ _el$11 = _$createTextNode(` `);
77
+ _$insertNode(_el$1, _el$10);
78
+ _$setProp(_el$1, "flexDirection", "row");
79
+ _$setProp(_el$1, "flexShrink", 0);
80
+ _$setProp(_el$1, "paddingRight", 1);
81
+ _$setProp(_el$1, "onMouseUp", () => active() ? props.onOpenConsole(shell.id) : props.store.select(shell.id));
82
+ _$insert(_el$1, _$createComponent(Badge, {
83
+ get api() {
84
+ return props.api;
85
+ },
86
+ shell: shell,
87
+ get frame() {
88
+ return props.store.frame();
89
+ }
90
+ }), _el$10);
91
+ _$insertNode(_el$10, _el$11);
92
+ _$setProp(_el$10, "wrapMode", "none");
93
+ _$insert(_el$10, (() => {
94
+ var _c$2 = _$memo(() => !!active());
95
+ return () => _c$2() ? (() => {
96
+ var _el$12 = _$createElement("b");
97
+ _$insert(_el$12, () => truncate(shell.title, 22));
98
+ return _el$12;
99
+ })() : truncate(shell.title, 22);
100
+ })(), null);
101
+ _$effect(_p$ => {
102
+ var _v$6 = active() ? theme().backgroundElement : theme().backgroundPanel,
103
+ _v$7 = active() ? theme().text : theme().textMuted;
104
+ _v$6 !== _p$.e && (_p$.e = _$setProp(_el$1, "backgroundColor", _v$6, _p$.e));
105
+ _v$7 !== _p$.t && (_p$.t = _$setProp(_el$10, "fg", _v$7, _p$.t));
106
+ return _p$;
107
+ }, {
108
+ e: undefined,
109
+ t: undefined
110
+ });
111
+ return _el$1;
112
+ })();
113
+ }
114
+ }), _$createComponent(Show, {
115
+ get when() {
116
+ return overflow() > 0 || props.store.showAll();
117
+ },
118
+ get children() {
119
+ var _el$6 = _$createElement("text");
120
+ _$setProp(_el$6, "wrapMode", "none");
121
+ _$setProp(_el$6, "flexShrink", 0);
122
+ _$setProp(_el$6, "onMouseUp", () => props.store.toggleAll());
123
+ _$insert(_el$6, (() => {
124
+ var _c$ = _$memo(() => !!props.store.showAll());
125
+ return () => _c$() ? "▾ fewer" : `▸ ${overflow()} more`;
126
+ })());
127
+ _$effect(_$p => _$setProp(_el$6, "fg", theme().textMuted, _$p));
128
+ return _el$6;
129
+ }
130
+ })];
131
+ }
132
+ }), _el$7);
133
+ _$setProp(_el$7, "flexGrow", 1);
134
+ _$setProp(_el$8, "wrapMode", "none");
135
+ _$setProp(_el$8, "flexShrink", 0);
136
+ _$insert(_el$8, () => props.hint());
137
+ _$insert(_el$, _$createComponent(Show, {
138
+ get when() {
139
+ return props.store.selected();
140
+ },
141
+ children: shell => [(() => {
142
+ var _el$13 = _$createElement("box"),
143
+ _el$14 = _$createElement("text");
144
+ _$insertNode(_el$13, _el$14);
145
+ _$setProp(_el$13, "flexGrow", 1);
146
+ _$setProp(_el$13, "paddingLeft", 2);
147
+ _$setProp(_el$13, "paddingRight", 1);
148
+ _$setProp(_el$13, "minHeight", 0);
149
+ _$setProp(_el$13, "overflow", "hidden");
150
+ _$setProp(_el$13, "onMouseUp", () => props.onOpenConsole(shell().id));
151
+ _$setProp(_el$14, "wrapMode", "none");
152
+ _$insert(_el$14, () => body() || " ");
153
+ _$effect(_$p => _$setProp(_el$14, "fg", theme().text, _$p));
154
+ return _el$13;
155
+ })(), (() => {
156
+ var _el$15 = _$createElement("box"),
157
+ _el$16 = _$createElement("text");
158
+ _$insertNode(_el$15, _el$16);
159
+ _$setProp(_el$15, "flexDirection", "row");
160
+ _$setProp(_el$15, "gap", 1);
161
+ _$setProp(_el$15, "paddingLeft", 2);
162
+ _$setProp(_el$15, "flexShrink", 0);
163
+ _$setProp(_el$15, "height", 1);
164
+ _$setProp(_el$15, "overflow", "hidden");
165
+ _$setProp(_el$16, "wrapMode", "none");
166
+ _$setProp(_el$16, "flexShrink", 0);
167
+ _$insert(_el$16, () => statusDetail(shell(), props.store.now()));
168
+ _$insert(_el$15, _$createComponent(Show, {
169
+ get when() {
170
+ return _$memo(() => kindOf(shell()) === "fail")() && shell().summary;
171
+ },
172
+ get fallback() {
173
+ return (() => {
174
+ var _el$18 = _$createElement("text");
175
+ _$setProp(_el$18, "wrapMode", "none");
176
+ _$insert(_el$18, () => truncate(`$ ${displayCommand(shell())}`, Math.max(20, dims().width - 40)));
177
+ _$effect(_$p => _$setProp(_el$18, "fg", theme().textMuted, _$p));
178
+ return _el$18;
179
+ })();
180
+ },
181
+ get children() {
182
+ var _el$17 = _$createElement("text");
183
+ _$setProp(_el$17, "wrapMode", "none");
184
+ _$insert(_el$17, () => truncate(`${shell().summary}`, Math.max(20, dims().width - 40)));
185
+ _$effect(_$p => _$setProp(_el$17, "fg", theme().error, _$p));
186
+ return _el$17;
187
+ }
188
+ }), null);
189
+ _$effect(_$p => _$setProp(_el$16, "fg", kindColor(theme(), kindOf(shell())), _$p));
190
+ return _el$15;
191
+ })()]
192
+ }), null);
193
+ _$effect(_p$ => {
194
+ var _v$ = props.height,
195
+ _v$2 = theme().border,
196
+ _v$3 = theme().backgroundPanel,
197
+ _v$4 = theme().text,
198
+ _v$5 = theme().textMuted;
199
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$, "height", _v$, _p$.e));
200
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$, "borderColor", _v$2, _p$.t));
201
+ _v$3 !== _p$.a && (_p$.a = _$setProp(_el$, "backgroundColor", _v$3, _p$.a));
202
+ _v$4 !== _p$.o && (_p$.o = _$setProp(_el$3, "fg", _v$4, _p$.o));
203
+ _v$5 !== _p$.i && (_p$.i = _$setProp(_el$8, "fg", _v$5, _p$.i));
204
+ return _p$;
205
+ }, {
206
+ e: undefined,
207
+ t: undefined,
208
+ a: undefined,
209
+ o: undefined,
210
+ i: undefined
211
+ });
212
+ return _el$;
213
+ })();
214
+ }
@@ -0,0 +1,264 @@
1
+ import { memo as _$memo } from "@opentui/solid";
2
+ import { createComponent as _$createComponent } from "@opentui/solid";
3
+ /** @jsxImportSource @opentui/solid */
4
+ import { createBindingLookup } from "@opencode-ai/plugin/tui";
5
+ import { claimFeature, duplicateFeatureMessage } from "@opencode-cockpit/client";
6
+ import { createSignal } from "solid-js";
7
+ import { createClient } from "../connect.js";
8
+ import { Console } from "./console.js";
9
+ import { Dock } from "./dock.js";
10
+ import { SidebarShells } from "./sidebar.js";
11
+ import { createShellStore } from "./store.js";
12
+ import { BADGE_LABEL, displayCommand, kindOf, order } from "./view.js";
13
+ const DEFAULT_KEYS = {
14
+ "cockpit.shells.dock": "<leader>o",
15
+ "cockpit.shells.console": "<leader>i"
16
+ };
17
+ const SHELL_PACKAGE = "@opencode-cockpit/shell";
18
+
19
+ /** Shell's TUI half as a factory, so bundles such as `opencode-cockpit` can include it. */
20
+ export function createShellTui({
21
+ source = SHELL_PACKAGE
22
+ } = {}) {
23
+ return async (api, rawOptions, meta) => {
24
+ // The renderer is shared by every TUI plugin in this OpenCode window.
25
+ const claim = claimFeature(api.renderer, "shell", source);
26
+ if (!claim.active) {
27
+ api.ui.toast({
28
+ variant: "warning",
29
+ title: "opencode-cockpit",
30
+ message: duplicateFeatureMessage("Shell", claim.owner, source),
31
+ duration: 10_000
32
+ });
33
+ return;
34
+ }
35
+ api.lifecycle.onDispose(() => claim.release());
36
+ await shellTui(api, rawOptions, meta);
37
+ };
38
+ }
39
+ const shellTui = async (api, rawOptions) => {
40
+ const options = rawOptions ?? {};
41
+ const client = createClient("opencode-cockpit/tui");
42
+ const store = createShellStore(api, client, {
43
+ historyMinutes: options.historyMinutes
44
+ });
45
+ const keys = createBindingLookup({
46
+ ...DEFAULT_KEYS,
47
+ ...options.keybinds
48
+ });
49
+ const [dockOpen, setDockOpen] = createSignal(api.kv.get("cockpit.dock.open", options.dockOpen ?? false));
50
+ const toggleDock = () => {
51
+ const next = !dockOpen();
52
+ setDockOpen(next);
53
+ api.kv.set("cockpit.dock.open", next);
54
+ };
55
+ const shortcut = command => {
56
+ const bindings = api.keymap.getCommandBindings({
57
+ visibility: "registered",
58
+ commands: [command]
59
+ });
60
+ return api.keys.formatBindings(bindings.get(command)) ?? "";
61
+ };
62
+ const openConsole = (id, typing = false) => {
63
+ if (id) store.select(id);
64
+ api.ui.dialog.replace(() => _$createComponent(Console, {
65
+ api: api,
66
+ store: store,
67
+ typing: typing,
68
+ onClose: () => api.ui.dialog.clear(),
69
+ onNewShell: () => newShell(api, store, openConsole)
70
+ }), () => {});
71
+ api.ui.dialog.setSize("xlarge");
72
+ };
73
+ api.keymap.registerLayer({
74
+ commands: [{
75
+ name: "cockpit.shells.dock",
76
+ title: "Toggle shells panel",
77
+ category: "Shells",
78
+ namespace: "palette",
79
+ slashName: "shells",
80
+ run: () => toggleDock()
81
+ }, {
82
+ name: "cockpit.shells.console",
83
+ title: "Open shell console",
84
+ category: "Shells",
85
+ namespace: "palette",
86
+ slashName: "shell",
87
+ run: () => openConsole()
88
+ }, {
89
+ name: "cockpit.shells.new",
90
+ title: "New background shell",
91
+ category: "Shells",
92
+ namespace: "palette",
93
+ slashName: "shell-new",
94
+ run: () => newShell(api, store, openConsole)
95
+ }, {
96
+ name: "cockpit.shells.clear",
97
+ title: "Clear finished shells",
98
+ category: "Shells",
99
+ namespace: "palette",
100
+ slashName: "shells-clear",
101
+ run: () => {
102
+ void store.clearFinished().then(n => api.ui.toast({
103
+ variant: "success",
104
+ title: "Shells",
105
+ message: `Cleared ${n} finished shell${n === 1 ? "" : "s"}`
106
+ })).catch(err => api.ui.toast({
107
+ variant: "error",
108
+ title: "Shells",
109
+ message: String(err)
110
+ }));
111
+ }
112
+ }, {
113
+ name: "cockpit.shells.restartDaemon",
114
+ title: "Restart shell daemon",
115
+ category: "Shells",
116
+ namespace: "palette",
117
+ slashName: "shells-restart-daemon",
118
+ run: () => restartDaemon(api, store)
119
+ }, {
120
+ name: "cockpit.shells.pick",
121
+ title: "Switch shell",
122
+ category: "Shells",
123
+ namespace: "palette",
124
+ run: () => pickShell(api, store, openConsole)
125
+ }],
126
+ bindings: keys.gather("cockpit", Object.keys(DEFAULT_KEYS))
127
+ });
128
+ const height = () => Math.max(6, Math.min(options.dockHeight ?? 14, Math.floor(api.renderer.height * 0.45)));
129
+ api.slots.register({
130
+ order: 150,
131
+ slots: {
132
+ app_bottom() {
133
+ return _$memo(() => _$memo(() => !!dockOpen())() ? _$createComponent(Dock, {
134
+ api: api,
135
+ store: store,
136
+ get height() {
137
+ return height();
138
+ },
139
+ hint: () => `${shortcut("cockpit.shells.console")} console · ${shortcut("cockpit.shells.dock")} hide`,
140
+ onOpenConsole: id => openConsole(id)
141
+ }) : null);
142
+ },
143
+ sidebar_content() {
144
+ return _$createComponent(SidebarShells, {
145
+ api: api,
146
+ store: store,
147
+ get rows() {
148
+ return options.sidebarRows;
149
+ },
150
+ onOpen: id => openConsole(id),
151
+ consoleShortcut: () => shortcut("cockpit.shells.console")
152
+ });
153
+ }
154
+ }
155
+ });
156
+
157
+ // A daemon from older plugin code is kept only while it runs shells; say so once.
158
+ const offOutdated = client.onOutdated(info => {
159
+ if (!info) return;
160
+ api.ui.toast({
161
+ variant: "warning",
162
+ title: "Shells",
163
+ message: "The shell daemon is running older code because shells are still running. Run /shells-restart-daemon when convenient.",
164
+ duration: 8000
165
+ });
166
+ });
167
+ api.lifecycle.onDispose(() => {
168
+ offOutdated();
169
+ store.dispose();
170
+ client.close();
171
+ });
172
+ };
173
+ function newShell(api, store, open) {
174
+ const DialogPrompt = api.ui.DialogPrompt;
175
+ api.ui.dialog.replace(() => _$createComponent(DialogPrompt, {
176
+ title: "New background shell",
177
+ placeholder: "npm run dev",
178
+ onConfirm: value => {
179
+ const command = value.trim();
180
+ if (!command) return api.ui.dialog.clear();
181
+ const shell = process.env.SHELL && /(bash|zsh|fish|sh)$/.test(process.env.SHELL) ? process.env.SHELL : "/bin/bash";
182
+ store.client.call("shell.start", {
183
+ command: shell,
184
+ args: ["-c", command],
185
+ cwd: store.project(),
186
+ title: command.slice(0, 60),
187
+ owner: {
188
+ project: store.project()
189
+ },
190
+ reuse: true
191
+ }).then(info => {
192
+ void store.refresh();
193
+ open(info.id);
194
+ }).catch(err => {
195
+ api.ui.dialog.clear();
196
+ api.ui.toast({
197
+ variant: "error",
198
+ title: "Shell",
199
+ message: err instanceof Error ? err.message : String(err)
200
+ });
201
+ });
202
+ },
203
+ onCancel: () => api.ui.dialog.clear()
204
+ }));
205
+ }
206
+ function restartDaemon(api, store) {
207
+ const running = store.shells().filter(s => s.status === "running").length;
208
+ const restart = force => {
209
+ api.ui.dialog.clear();
210
+ store.client.restartDaemon({
211
+ force
212
+ }).then(ok => {
213
+ void store.refresh();
214
+ api.ui.toast({
215
+ variant: ok ? "success" : "warning",
216
+ title: "Shells",
217
+ message: ok ? "Shell daemon restarted" : "Shells are running; restart was not forced"
218
+ });
219
+ }).catch(err => api.ui.toast({
220
+ variant: "error",
221
+ title: "Shells",
222
+ message: String(err)
223
+ }));
224
+ };
225
+ if (running === 0) return restart(false);
226
+ const DialogConfirm = api.ui.DialogConfirm;
227
+ api.ui.dialog.replace(() => _$createComponent(DialogConfirm, {
228
+ title: "Restart shell daemon?",
229
+ message: `${running} running shell${running === 1 ? "" : "s"} will be stopped.`,
230
+ onConfirm: () => restart(true),
231
+ onCancel: () => api.ui.dialog.clear()
232
+ }));
233
+ }
234
+ function pickShell(api, store, open) {
235
+ const DialogSelect = api.ui.DialogSelect;
236
+ const shells = order(store.shells());
237
+ if (shells.length === 0) {
238
+ api.ui.toast({
239
+ variant: "info",
240
+ title: "Shells",
241
+ message: "No shells in this project yet"
242
+ });
243
+ return;
244
+ }
245
+ api.ui.dialog.replace(() => _$createComponent(DialogSelect, {
246
+ title: "Shells",
247
+ get current() {
248
+ return store.selected()?.id;
249
+ },
250
+ get options() {
251
+ return shells.map(s => ({
252
+ title: s.title,
253
+ value: s.id,
254
+ description: `${BADGE_LABEL[kindOf(s)]} · ${displayCommand(s).slice(0, 60)}`
255
+ }));
256
+ },
257
+ onSelect: option => open(option.value)
258
+ }));
259
+ }
260
+ const plugin = {
261
+ id: "opencode-cockpit.shell",
262
+ tui: createShellTui()
263
+ };
264
+ export default plugin;
@@ -0,0 +1,43 @@
1
+ const NAMED = {
2
+ return: "\r",
3
+ enter: "\r",
4
+ linefeed: "\n",
5
+ tab: "\t",
6
+ backspace: "\x7f",
7
+ escape: "\x1b",
8
+ space: " ",
9
+ up: "\x1b[A",
10
+ down: "\x1b[B",
11
+ right: "\x1b[C",
12
+ left: "\x1b[D",
13
+ home: "\x1b[H",
14
+ end: "\x1b[F",
15
+ pageup: "\x1b[5~",
16
+ pagedown: "\x1b[6~",
17
+ delete: "\x1b[3~",
18
+ insert: "\x1b[2~"
19
+ };
20
+
21
+ /**
22
+ * Re-encodes a parsed key as legacy terminal bytes. The TUI may receive keys via the kitty
23
+ * protocol, whose raw form programs in the PTY would not understand.
24
+ */
25
+ export function keyToBytes(e) {
26
+ if (e.eventType === "release") return undefined;
27
+ const name = e.name?.toLowerCase() ?? "";
28
+ if (e.name === "tab" && e.shift) return "\x1b[Z";
29
+ const named = NAMED[name];
30
+ if (named !== undefined) return e.meta || e.option ? `\x1b${named}` : named;
31
+ if (e.ctrl && name.length === 1) {
32
+ const code = name.toUpperCase().charCodeAt(0);
33
+ if (code >= 64 && code <= 95) return String.fromCharCode(code - 64);
34
+ }
35
+ const text = e.sequence && !e.sequence.startsWith("\x1b") ? e.sequence : name.length === 1 ? e.shift ? name.toUpperCase() : name : undefined;
36
+ if (!text) return undefined;
37
+ return e.meta || e.option ? `\x1b${text}` : text;
38
+ }
39
+
40
+ /** ctrl+] releases typing mode (the telnet escape), since esc and ctrl+c belong to the program. */
41
+ export function isReleaseKey(e) {
42
+ return e.ctrl && (e.name === "]" || e.sequence === "\x1d");
43
+ }
@@ -0,0 +1,121 @@
1
+ import { effect as _$effect } from "@opentui/solid";
2
+ import { memo as _$memo } from "@opentui/solid";
3
+ import { createComponent as _$createComponent } from "@opentui/solid";
4
+ import { insert as _$insert } from "@opentui/solid";
5
+ import { createTextNode as _$createTextNode } from "@opentui/solid";
6
+ import { insertNode as _$insertNode } from "@opentui/solid";
7
+ import { setProp as _$setProp } from "@opentui/solid";
8
+ import { createElement as _$createElement } from "@opentui/solid";
9
+ /** @jsxImportSource @opentui/solid */
10
+
11
+ import { createMemo, For, Show } from "solid-js";
12
+ import { Badge } from "./badge.js";
13
+ import { kindOf, shortDetail, truncate } from "./view.js";
14
+ export function SidebarShells(props) {
15
+ const theme = () => props.api.theme.current;
16
+ const limit = () => Math.max(1, props.store.showAll() ? props.expandedRows ?? 12 : props.rows ?? 5);
17
+ const counts = createMemo(() => {
18
+ const list = props.store.shells();
19
+ const of = kind => list.filter(s => kindOf(s) === kind).length;
20
+ // The sidebar column is ~26 characters wide; keep the summary short enough to survive it.
21
+ return [of("run") ? `${of("run")} run` : "", of("fail") ? `${of("fail")} fail` : "", of("done") + of("stop") ? `${of("done") + of("stop")} done` : ""].filter(Boolean).join(" · ");
22
+ });
23
+
24
+ // Running shells and recent failures first; everything else only when expanded.
25
+ const candidates = createMemo(() => props.store.showAll() ? props.store.shells() : props.store.visible());
26
+ const shown = createMemo(() => candidates().slice(0, limit()));
27
+ const overflow = createMemo(() => props.store.shells().length - shown().length);
28
+ return _$createComponent(Show, {
29
+ get when() {
30
+ return props.store.shells().length > 0;
31
+ },
32
+ get children() {
33
+ var _el$ = _$createElement("box"),
34
+ _el$2 = _$createElement("text"),
35
+ _el$3 = _$createElement("b"),
36
+ _el$5 = _$createElement("span"),
37
+ _el$6 = _$createTextNode(` `);
38
+ _$insertNode(_el$, _el$2);
39
+ _$insertNode(_el$2, _el$3);
40
+ _$insertNode(_el$2, _el$5);
41
+ _$setProp(_el$2, "wrapMode", "none");
42
+ _$insertNode(_el$3, _$createTextNode(`Shells`));
43
+ _$insertNode(_el$5, _el$6);
44
+ _$insert(_el$5, counts, null);
45
+ _$insert(_el$, _$createComponent(For, {
46
+ get each() {
47
+ return shown();
48
+ },
49
+ children: shell => // Mouse-up, not mouse-down: the host dialog closes on the mouse-up that follows, so
50
+ // opening the console on press would need the button held down.
51
+ (() => {
52
+ var _el$8 = _$createElement("box"),
53
+ _el$9 = _$createElement("text"),
54
+ _el$0 = _$createTextNode(` `),
55
+ _el$1 = _$createTextNode(` `),
56
+ _el$10 = _$createElement("span");
57
+ _$insertNode(_el$8, _el$9);
58
+ _$setProp(_el$8, "flexDirection", "row");
59
+ _$setProp(_el$8, "onMouseUp", () => props.onOpen(shell.id));
60
+ _$insert(_el$8, _$createComponent(Badge, {
61
+ get api() {
62
+ return props.api;
63
+ },
64
+ shell: shell,
65
+ get frame() {
66
+ return props.store.frame();
67
+ }
68
+ }), _el$9);
69
+ _$insertNode(_el$9, _el$0);
70
+ _$insertNode(_el$9, _el$1);
71
+ _$insertNode(_el$9, _el$10);
72
+ _$setProp(_el$9, "wrapMode", "none");
73
+ _$insert(_el$9, () => truncate(shell.title, 20), _el$1);
74
+ _$insert(_el$10, () => shortDetail(shell, props.store.now()));
75
+ _$effect(_p$ => {
76
+ var _v$3 = theme().text,
77
+ _v$4 = {
78
+ fg: theme().textMuted
79
+ };
80
+ _v$3 !== _p$.e && (_p$.e = _$setProp(_el$9, "fg", _v$3, _p$.e));
81
+ _v$4 !== _p$.t && (_p$.t = _$setProp(_el$10, "style", _v$4, _p$.t));
82
+ return _p$;
83
+ }, {
84
+ e: undefined,
85
+ t: undefined
86
+ });
87
+ return _el$8;
88
+ })()
89
+ }), null);
90
+ _$insert(_el$, _$createComponent(Show, {
91
+ get when() {
92
+ return overflow() > 0 || props.store.showAll();
93
+ },
94
+ get children() {
95
+ var _el$7 = _$createElement("text");
96
+ _$setProp(_el$7, "wrapMode", "none");
97
+ _$setProp(_el$7, "onMouseUp", () => props.store.toggleAll());
98
+ _$insert(_el$7, (() => {
99
+ var _c$ = _$memo(() => !!props.store.showAll());
100
+ return () => _c$() ? _$memo(() => overflow() > 0)() ? `▾ ${overflow()} more in ${props.consoleShortcut()} console` : "▾ show fewer" : `▸ ${overflow()} more`;
101
+ })());
102
+ _$effect(_$p => _$setProp(_el$7, "fg", theme().textMuted, _$p));
103
+ return _el$7;
104
+ }
105
+ }), null);
106
+ _$effect(_p$ => {
107
+ var _v$ = theme().text,
108
+ _v$2 = {
109
+ fg: theme().textMuted
110
+ };
111
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$2, "fg", _v$, _p$.e));
112
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$5, "style", _v$2, _p$.t));
113
+ return _p$;
114
+ }, {
115
+ e: undefined,
116
+ t: undefined
117
+ });
118
+ return _el$;
119
+ }
120
+ });
121
+ }