@opencode-cockpit/subagents 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/agent/plugin.js +134 -0
- package/dist/cli/preview.js +99 -0
- package/dist/core/adapt/v1.js +341 -0
- package/dist/core/adapt/v2.js +379 -0
- package/dist/core/model/changes.js +17 -0
- package/dist/core/model/model.js +301 -0
- package/dist/core/sample.js +261 -0
- package/dist/core/view/markdown.js +211 -0
- package/dist/core/view/report.js +58 -0
- package/dist/core/view/rows.js +146 -0
- package/dist/core/view/screen.js +767 -0
- package/dist/core/view/sidebar.js +151 -0
- package/dist/server.js +2 -0
- package/dist/tui/index.js +991 -0
- package/dist/tui/render.js +103 -0
- package/dist/tui/source.js +228 -0
- package/dist/tui/view/overlay.js +87 -0
- package/dist/tui/view/sidebar.js +78 -0
- package/package.json +64 -0
- package/server.js +6 -0
- package/tui.js +6 -0
- package/types/agent/plugin.d.ts +32 -0
- package/types/cli/preview.d.ts +10 -0
- package/types/core/adapt/v1.d.ts +33 -0
- package/types/core/adapt/v2.d.ts +22 -0
- package/types/core/model/changes.d.ts +98 -0
- package/types/core/model/model.d.ts +99 -0
- package/types/core/sample.d.ts +8 -0
- package/types/core/view/markdown.d.ts +22 -0
- package/types/core/view/report.d.ts +15 -0
- package/types/core/view/rows.d.ts +43 -0
- package/types/core/view/screen.d.ts +101 -0
- package/types/core/view/sidebar.d.ts +30 -0
- package/types/server.d.ts +2 -0
- package/types/tui/index.d.ts +26 -0
- package/types/tui/render.d.ts +27 -0
- package/types/tui/source.d.ts +45 -0
- package/types/tui/view/overlay.d.ts +30 -0
- package/types/tui/view/sidebar.d.ts +22 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Codestz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# @opencode-cockpit/subagents
|
|
2
|
+
|
|
3
|
+
**See what your subagents are doing, while they do it — and reuse them.** When the main agent hands
|
|
4
|
+
work to a subagent, you get one line in the chat and nothing else. This puts every subagent in the
|
|
5
|
+
sidebar with what it is doing right now, opens its whole run in a pane — thinking, every tool call as
|
|
6
|
+
OpenCode draws its own, the answer — lets you message it, stop it or move it to the background, and
|
|
7
|
+
has the main agent continue the subagent that did the work instead of starting from nothing.
|
|
8
|
+
|
|
9
|
+
Part of [opencode-cockpit](https://github.com/Codestz/opencode-cockpit). Install it on its own, or
|
|
10
|
+
through the bundle. Works on OpenCode 1.18+ and 2.0.15+.
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
opencode plugin @opencode-cockpit/subagents@0.7.0 --global --force # OpenCode 1
|
|
14
|
+
opencode plugin add @opencode-cockpit/subagents@0.7.0 # OpenCode 2
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## What it does
|
|
18
|
+
|
|
19
|
+
**In the sidebar**, a Subagents block: each subagent in this conversation, its type and task, and
|
|
20
|
+
under it what it is doing now — `grep "session" src/auth/**`, `thinking`, `waiting for permission`,
|
|
21
|
+
`done · 2 rounds`, `cancelled` — with how many calls and how long on the right. A subagent that
|
|
22
|
+
launched its own has them indented under it.
|
|
23
|
+
|
|
24
|
+
**Click one** — or `ctrl+x w`, or `/subagents` — and its run opens in a pane on the right, half the
|
|
25
|
+
window or all of it: its model and who launched it, the task, then the run. A shell command or a file
|
|
26
|
+
change is a box with its output (ten lines, sixty open, all with `a`); reads and searches are one quiet
|
|
27
|
+
line each; thinking folds; the answer is drawn as markdown.
|
|
28
|
+
|
|
29
|
+
| Key | |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `j` `k` | Move the cursor through the run's items |
|
|
32
|
+
| `enter` · a click | Open or fold the item under it |
|
|
33
|
+
| `e` | Open, or fold, every call |
|
|
34
|
+
| `a` | A call's whole output — open shows its first 60 lines, whole up to 2,000 |
|
|
35
|
+
| `t` | Show or hide thinking — shown by default, and remembered |
|
|
36
|
+
| `m` | Write it a message, at the foot of the pane (pasting works) |
|
|
37
|
+
| `x` | Stop it (press twice) — or, once it has finished, remove it from the list |
|
|
38
|
+
| `X` | Remove every finished subagent from the list |
|
|
39
|
+
| `b` | Move it to the background, so the main agent carries on (OpenCode's own `ctrl+b`) |
|
|
40
|
+
| `i` | Details: model, what it is denied, calls by tool, tokens, cost |
|
|
41
|
+
| `w` | Half the window, or all of it (remembered) |
|
|
42
|
+
| `[` `]` | Another subagent of this conversation |
|
|
43
|
+
| `d` `u` · `g` `G` | Page down · up · to the start · follow the run |
|
|
44
|
+
| `esc` `q` | Back to the conversation |
|
|
45
|
+
|
|
46
|
+
**Follow-ups keep their context.** The main agent is asked to continue the subagent that did the work
|
|
47
|
+
(`task_id` on OpenCode 1, `sessionID` on 2) rather than launch a new one, and has a `subagents_list`
|
|
48
|
+
tool: each subagent's id, task, state and last answer — saying when one was cancelled, or ended on a
|
|
49
|
+
progress note rather than an answer. Each round shows in the pane under a "Round N" rule.
|
|
50
|
+
|
|
51
|
+
**Message it.** A subagent that is still working picks your message up in its current run and answers
|
|
52
|
+
it in its report, so the main agent sees it too. A finished one wakes up and answers you, and Cockpit
|
|
53
|
+
adds the exchange to the main conversation without starting a turn there, so the main agent knows it
|
|
54
|
+
next time. A message it finished without reading comes back to the field.
|
|
55
|
+
|
|
56
|
+
**Stop and remove.** `x` twice stops a working subagent — and first tells the main agent you stopped
|
|
57
|
+
it on purpose, so it reports the stop instead of launching the subagent again. `x` on a finished one
|
|
58
|
+
removes it from the list, `X` removes every finished one; the sessions stay in OpenCode.
|
|
59
|
+
|
|
60
|
+
**Background subagents.** The main agent is asked to launch independent subagents with
|
|
61
|
+
`background: true` when its tool offers it, so the conversation keeps going. OpenCode 2 offers it
|
|
62
|
+
always; **OpenCode 1 only when started with `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true`** — a
|
|
63
|
+
plugin cannot set that for you. `b` moves one already running in the foreground, the same way.
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
export OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true # in ~/.zshrc, then start OpenCode
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Settings
|
|
70
|
+
|
|
71
|
+
In the bundle's entry (`"subagents": { … }`) or this package's own:
|
|
72
|
+
|
|
73
|
+
| Setting | Default | |
|
|
74
|
+
| --- | --- | --- |
|
|
75
|
+
| `sidebarRows` | `6` | Subagents shown before the rest fold into a count — working ones first |
|
|
76
|
+
| `hideFinishedAfter` | unset | Minutes a finished subagent stays in the sidebar; unset keeps it for the conversation |
|
|
77
|
+
| `sidebarOrder` | `150` | Where the block sits in the sidebar; lower draws first |
|
|
78
|
+
| `keybinds` | `{ "cockpit.subagents.open": "<leader>w" }` | The key that opens the latest one |
|
|
79
|
+
| `guidance` | `true` | Tell the main agent about background subagents and follow-ups (agent side) |
|
|
80
|
+
|
|
81
|
+
## See it without OpenCode
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
bunx @opencode-cockpit/subagents preview
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Draws the sidebar block and the pane from a sample run, in your terminal.
|
|
88
|
+
|
|
89
|
+
## Troubleshooting
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
npx opencode-cockpit@latest doctor
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
checks OpenCode, its config, Cockpit's logs and the daemon, and prints the fix for anything wrong —
|
|
96
|
+
on OpenCode 1 and 2, and when Cockpit will not load at all ([what it checks](https://codestz.github.io/opencode-cockpit/help/doctor/)).
|
|
97
|
+
|
|
98
|
+
Everything Cockpit does inside OpenCode goes to one file — which OpenCode loaded which bay, and every
|
|
99
|
+
error with its stack:
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
tail -50 ~/.cache/opencode-cockpit/cockpit.log
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`COCKPIT_DEBUG=1 opencode` adds the detail. [Troubleshooting](https://codestz.github.io/opencode-cockpit/help/troubleshooting/) covers
|
|
106
|
+
the failures people hit and what to attach to an issue; [OpenCode 1 and 2](https://codestz.github.io/opencode-cockpit/start/opencode-versions/)
|
|
107
|
+
covers what differs between the two.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
import { claimFeature, duplicateFeatureMessage } from "@opencode-cockpit/client";
|
|
3
|
+
import { dualServer } from "@opencode-cockpit/client/server";
|
|
4
|
+
import { createV1Translator } from "../core/adapt/v1.js";
|
|
5
|
+
import { createV2Translator } from "../core/adapt/v2.js";
|
|
6
|
+
import { applyAll, emptyModel, subagentsOf } from "../core/model/model.js";
|
|
7
|
+
import { subagentReport } from "../core/view/report.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* What the agent is told about subagents, once per request.
|
|
11
|
+
*
|
|
12
|
+
* Measured on both OpenCodes (docs/opencode/agents.md): a subagent launched in the background lets the
|
|
13
|
+
* conversation carry on, and the main agent is told when it finishes. OpenCode 2 offers it on its
|
|
14
|
+
* `subagent` tool; OpenCode 1 only with `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true`, which a
|
|
15
|
+
* plugin cannot set — so the guidance says "if your tool offers it", and never promises it.
|
|
16
|
+
*
|
|
17
|
+
* Continuing a subagent was measured too: given its id, the same subagent picks up with everything it
|
|
18
|
+
* already read, and answers in seconds. Main agents rarely do it on their own — the id scrolls away —
|
|
19
|
+
* so the guidance asks for it, and `subagents_list` hands the ids back.
|
|
20
|
+
*/
|
|
21
|
+
export const GUIDANCE = `## Subagents (opencode-cockpit)
|
|
22
|
+
When you delegate independent work to a subagent, launch it with background: true if your task or subagent tool offers that option, so this conversation continues while it works; you are notified when it finishes. Work on something else meanwhile, or tell the user what you launched.
|
|
23
|
+
When the user asks for a fix or follow-up on work a subagent already did, continue that same subagent (task_id or sessionID) rather than launching a new one — it keeps its context. subagents_list gives each subagent's id, task and last answer.
|
|
24
|
+
The user can watch each subagent and message it directly; when they do, a note in this conversation tells you what they asked and what it answered.`;
|
|
25
|
+
const LIST = `List the subagents of this conversation: each one's id, agent, task, state and last answer.
|
|
26
|
+
|
|
27
|
+
Use it before following up on work a subagent did, to continue that same subagent by its id instead of starting a new one — it keeps everything it already read and tried.`;
|
|
28
|
+
export const SUBAGENTS_PACKAGE = "@opencode-cockpit/subagents";
|
|
29
|
+
/**
|
|
30
|
+
* What the agent side keeps of each run: enough to list it — not what calls printed, nor thinking, so
|
|
31
|
+
* a long session does not hold every file its subagents read.
|
|
32
|
+
*/
|
|
33
|
+
export function slim(changes) {
|
|
34
|
+
const out = [];
|
|
35
|
+
for (const change of changes) {
|
|
36
|
+
if (change.type === "thinking") continue;
|
|
37
|
+
if (change.type === "tool") {
|
|
38
|
+
const {
|
|
39
|
+
output: _output,
|
|
40
|
+
input: _input,
|
|
41
|
+
...rest
|
|
42
|
+
} = change;
|
|
43
|
+
out.push(rest);
|
|
44
|
+
} else out.push(change);
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The conversation's subagents: what the events said, and on OpenCode 1 any from before we started —
|
|
51
|
+
* with their history, read once. Listed bare, those read "working now · 0 calls" to the main agent,
|
|
52
|
+
* which then waits on work long finished, or does it again.
|
|
53
|
+
*/
|
|
54
|
+
async function nodesFor(host, model, history, sessionID) {
|
|
55
|
+
const listed = (await host.session.children?.(sessionID).catch(() => [])) ?? [];
|
|
56
|
+
const missing = listed.filter(child => (model.sessions.get(child.id)?.entries.length ?? 0) === 0);
|
|
57
|
+
for (const child of missing) {
|
|
58
|
+
const at = child.time?.updated ?? Date.now();
|
|
59
|
+
const messages = (await host.session.messages?.(child.id).catch(() => [])) ?? [];
|
|
60
|
+
applyAll(model, [{
|
|
61
|
+
type: "session",
|
|
62
|
+
id: child.id,
|
|
63
|
+
parentID: sessionID,
|
|
64
|
+
...(child.title ? {
|
|
65
|
+
title: child.title
|
|
66
|
+
} : {}),
|
|
67
|
+
at
|
|
68
|
+
}, ...slim(history(messages)), {
|
|
69
|
+
type: "status",
|
|
70
|
+
id: child.id,
|
|
71
|
+
status: "idle",
|
|
72
|
+
at
|
|
73
|
+
}]);
|
|
74
|
+
}
|
|
75
|
+
return subagentsOf(model, sessionID);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The agent side as a factory, so the `opencode-cockpit` bundle can include it. */
|
|
79
|
+
export function createSubagentsServer({
|
|
80
|
+
source = SUBAGENTS_PACKAGE
|
|
81
|
+
} = {}) {
|
|
82
|
+
return async (host, rawOptions) => {
|
|
83
|
+
const claim = claimFeature(host.scope, "subagents", source);
|
|
84
|
+
if (!claim.active) {
|
|
85
|
+
host.log.warn(duplicateFeatureMessage("Subagents", claim.owner, source));
|
|
86
|
+
return {};
|
|
87
|
+
}
|
|
88
|
+
const log = host.log.child("subagents");
|
|
89
|
+
const options = rawOptions ?? {};
|
|
90
|
+
const model = emptyModel();
|
|
91
|
+
const v1 = host.version === 1 ? createV1Translator() : undefined;
|
|
92
|
+
const translate = v1 ?? createV2Translator();
|
|
93
|
+
const history = messages => v1?.history(messages) ?? [];
|
|
94
|
+
const list = tool({
|
|
95
|
+
description: LIST,
|
|
96
|
+
args: {},
|
|
97
|
+
async execute(_args, context) {
|
|
98
|
+
const nodes = await nodesFor(host, model, history, context.sessionID);
|
|
99
|
+
log.debug("list", {
|
|
100
|
+
sessionID: context.sessionID,
|
|
101
|
+
count: nodes.length
|
|
102
|
+
});
|
|
103
|
+
return subagentReport({
|
|
104
|
+
nodes,
|
|
105
|
+
now: Date.now(),
|
|
106
|
+
version: host.version
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return {
|
|
111
|
+
...(options.guidance === false ? {} : {
|
|
112
|
+
system: async () => [GUIDANCE]
|
|
113
|
+
}),
|
|
114
|
+
tools: {
|
|
115
|
+
subagents_list: list
|
|
116
|
+
},
|
|
117
|
+
event: event => {
|
|
118
|
+
try {
|
|
119
|
+
const changes = slim(translate.event(event));
|
|
120
|
+
if (changes.length > 0) applyAll(model, changes);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
log.warn("event failed", {
|
|
123
|
+
error
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
sessionDeleted: async sessionID => {
|
|
128
|
+
model.sessions.delete(sessionID);
|
|
129
|
+
},
|
|
130
|
+
dispose: () => claim.release()
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export default dualServer("opencode-cockpit.subagents", createSubagentsServer());
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* The see-it loop: the sidebar block and the full screen, drawn in this terminal from a recorded run,
|
|
4
|
+
* with no OpenCode running. The same rows OpenCode draws — only the colours come from a fixed
|
|
5
|
+
* palette (OpenCode's default theme) instead of the user's.
|
|
6
|
+
*
|
|
7
|
+
* bunx @opencode-cockpit/subagents preview a sample run, mid-flight
|
|
8
|
+
* bunx @opencode-cockpit/subagents preview --width 34 the sidebar at another width
|
|
9
|
+
*/
|
|
10
|
+
import { applyAll, emptyModel, subagentsOf } from "../core/model/model.js";
|
|
11
|
+
import { SAMPLE_NOW, SAMPLE_ROOT, sample } from "../core/sample.js";
|
|
12
|
+
import { screenRows } from "../core/view/screen.js";
|
|
13
|
+
import { sidebarLines } from "../core/view/sidebar.js";
|
|
14
|
+
|
|
15
|
+
/** OpenCode's default theme, measured (docs/opencode/v2.md). */
|
|
16
|
+
const HEX = {
|
|
17
|
+
text: "#eeeeee",
|
|
18
|
+
muted: "#808080",
|
|
19
|
+
accent: "#9d7cd8",
|
|
20
|
+
info: "#56b6c2",
|
|
21
|
+
tool: "#fab283",
|
|
22
|
+
success: "#7fd88f",
|
|
23
|
+
error: "#e06c75",
|
|
24
|
+
warning: "#f5a742",
|
|
25
|
+
border: "#484848"
|
|
26
|
+
};
|
|
27
|
+
const FILL = {
|
|
28
|
+
band: "#141414",
|
|
29
|
+
block: "#1e1e1e",
|
|
30
|
+
card: "#1e1e1e",
|
|
31
|
+
selected: "#141414"
|
|
32
|
+
};
|
|
33
|
+
const rgb = hex => [1, 3, 5].map(i => Number.parseInt(hex.slice(i, i + 2), 16)).join(";");
|
|
34
|
+
const color = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
35
|
+
function paint(row) {
|
|
36
|
+
if (!color) return row.map(run => run.text).join("");
|
|
37
|
+
return row.map(run => {
|
|
38
|
+
const codes = [`38;2;${rgb(HEX[run.tone ?? "text"])}`];
|
|
39
|
+
if (run.fill && run.fill !== "none") codes.push(`48;2;${rgb(FILL[run.fill])}`);
|
|
40
|
+
if (run.bold) codes.push("1");
|
|
41
|
+
if (run.faint) codes.push("2");
|
|
42
|
+
return `\x1b[${codes.join(";")}m${run.text}\x1b[0m`;
|
|
43
|
+
}).join("");
|
|
44
|
+
}
|
|
45
|
+
const args = process.argv.slice(2);
|
|
46
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
47
|
+
process.stdout.write("Usage: subagents preview [--width <sidebar columns>]\n");
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
const at = args.indexOf("--width");
|
|
51
|
+
const sidebarWidth = at >= 0 ? Number(args[at + 1]) || 36 : 36;
|
|
52
|
+
const columns = Math.max(60, Math.min(process.stdout.columns || 100, 140));
|
|
53
|
+
const model = applyAll(emptyModel(), sample());
|
|
54
|
+
const nodes = subagentsOf(model, SAMPLE_ROOT);
|
|
55
|
+
const out = ["", "Sidebar", ""];
|
|
56
|
+
for (const line of sidebarLines({
|
|
57
|
+
nodes,
|
|
58
|
+
width: sidebarWidth,
|
|
59
|
+
now: SAMPLE_NOW,
|
|
60
|
+
frame: 2
|
|
61
|
+
})) out.push(paint(line.row));
|
|
62
|
+
const first = nodes[0]?.session;
|
|
63
|
+
if (first) {
|
|
64
|
+
const base = {
|
|
65
|
+
session: first,
|
|
66
|
+
nodes,
|
|
67
|
+
launcher: "build",
|
|
68
|
+
width: columns,
|
|
69
|
+
height: 30,
|
|
70
|
+
now: SAMPLE_NOW,
|
|
71
|
+
frame: 2,
|
|
72
|
+
open: new Set(),
|
|
73
|
+
closed: new Set(),
|
|
74
|
+
thinking: false,
|
|
75
|
+
details: false
|
|
76
|
+
};
|
|
77
|
+
out.push("", "The pane — the first subagent", "");
|
|
78
|
+
const folded = screenRows(base);
|
|
79
|
+
for (const row of folded.rows) out.push(paint(row));
|
|
80
|
+
/** The same, with the cursor on the first call and that call open. */
|
|
81
|
+
const call = folded.keys.find(key => key.startsWith("tool:"));
|
|
82
|
+
if (call) {
|
|
83
|
+
out.push("", "A call, selected and open", "");
|
|
84
|
+
const opened = screenRows({
|
|
85
|
+
...base,
|
|
86
|
+
selected: call,
|
|
87
|
+
open: new Set([call]),
|
|
88
|
+
top: 0
|
|
89
|
+
});
|
|
90
|
+
for (const row of opened.rows) out.push(paint(row));
|
|
91
|
+
}
|
|
92
|
+
out.push("", "Details", "");
|
|
93
|
+
for (const row of screenRows({
|
|
94
|
+
...base,
|
|
95
|
+
details: true,
|
|
96
|
+
height: 20
|
|
97
|
+
}).rows) out.push(paint(row));
|
|
98
|
+
}
|
|
99
|
+
process.stdout.write(`${out.join("\n")}\n\n`);
|