oharness 0.1.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/dist/bin/sync.d.ts +3 -0
- package/dist/bin/sync.js +4 -0
- package/dist/bin/sync.js.map +1 -0
- package/dist/bin/web.d.ts +10 -0
- package/dist/bin/web.js +11 -0
- package/dist/bin/web.js.map +1 -0
- package/dist/headless/main.d.ts +2 -0
- package/dist/headless/main.js +133 -0
- package/dist/headless/main.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/providers.d.ts +2 -0
- package/dist/providers.js +3 -0
- package/dist/providers.js.map +1 -0
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +3 -0
- package/dist/tools.js.map +1 -0
- package/dist/ui/artifact.d.ts +32 -0
- package/dist/ui/artifact.js +49 -0
- package/dist/ui/artifact.js.map +1 -0
- package/dist/ui/auth.d.ts +14 -0
- package/dist/ui/auth.js +177 -0
- package/dist/ui/auth.js.map +1 -0
- package/dist/ui/colors.d.ts +21 -0
- package/dist/ui/colors.js +28 -0
- package/dist/ui/colors.js.map +1 -0
- package/dist/ui/ink/app.d.ts +13 -0
- package/dist/ui/ink/app.js +418 -0
- package/dist/ui/ink/app.js.map +1 -0
- package/dist/ui/ink/logo.d.ts +6 -0
- package/dist/ui/ink/logo.js +20 -0
- package/dist/ui/ink/logo.js.map +1 -0
- package/dist/ui/ink/main.d.ts +2 -0
- package/dist/ui/ink/main.js +55 -0
- package/dist/ui/ink/main.js.map +1 -0
- package/dist/ui/ink/screen.d.ts +15 -0
- package/dist/ui/ink/screen.js +76 -0
- package/dist/ui/ink/screen.js.map +1 -0
- package/dist/ui/ink/select.d.ts +24 -0
- package/dist/ui/ink/select.js +74 -0
- package/dist/ui/ink/select.js.map +1 -0
- package/dist/ui/main.d.ts +2 -0
- package/dist/ui/main.js +1062 -0
- package/dist/ui/main.js.map +1 -0
- package/dist/ui/render.d.ts +15 -0
- package/dist/ui/render.js +130 -0
- package/dist/ui/render.js.map +1 -0
- package/dist/ui/select.d.ts +26 -0
- package/dist/ui/select.js +107 -0
- package/dist/ui/select.js.map +1 -0
- package/dist/ui/tui/app.d.ts +96 -0
- package/dist/ui/tui/app.js +476 -0
- package/dist/ui/tui/app.js.map +1 -0
- package/dist/ui/tui/editor.d.ts +40 -0
- package/dist/ui/tui/editor.js +124 -0
- package/dist/ui/tui/editor.js.map +1 -0
- package/dist/ui/tui/keys.d.ts +25 -0
- package/dist/ui/tui/keys.js +133 -0
- package/dist/ui/tui/keys.js.map +1 -0
- package/dist/ui/tui/layout.d.ts +63 -0
- package/dist/ui/tui/layout.js +202 -0
- package/dist/ui/tui/layout.js.map +1 -0
- package/dist/ui/tui/logo.d.ts +18 -0
- package/dist/ui/tui/logo.js +33 -0
- package/dist/ui/tui/logo.js.map +1 -0
- package/dist/ui/tui/screen.d.ts +48 -0
- package/dist/ui/tui/screen.js +150 -0
- package/dist/ui/tui/screen.js.map +1 -0
- package/dist/ui/tui/width.d.ts +30 -0
- package/dist/ui/tui/width.js +197 -0
- package/dist/ui/tui/width.js.map +1 -0
- package/dist/ui/wordmark.d.ts +30 -0
- package/dist/ui/wordmark.js +63 -0
- package/dist/ui/wordmark.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning raw terminal bytes into keys.
|
|
3
|
+
*
|
|
4
|
+
* A single read can carry several keystrokes, one keystroke split across two
|
|
5
|
+
* reads, or a whole pasted document. Anything that assumes "one read is one
|
|
6
|
+
* key" mangles fast typing and pasted text, so this parser consumes a buffer
|
|
7
|
+
* and reports what it could resolve, keeping the remainder for next time.
|
|
8
|
+
*/
|
|
9
|
+
export interface Key {
|
|
10
|
+
name: string;
|
|
11
|
+
/** The literal characters, for printable input. */
|
|
12
|
+
text?: string;
|
|
13
|
+
ctrl?: boolean;
|
|
14
|
+
alt?: boolean;
|
|
15
|
+
shift?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** Bracketed paste: the terminal brackets pasted text so it is not typed. */
|
|
18
|
+
export declare const PASTE_START = "\u001B[200~";
|
|
19
|
+
export declare const PASTE_END = "\u001B[201~";
|
|
20
|
+
export interface ParseResult {
|
|
21
|
+
keys: Key[];
|
|
22
|
+
/** Bytes that may be the start of a longer sequence; feed them back in. */
|
|
23
|
+
rest: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function parseKeys(input: string): ParseResult;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning raw terminal bytes into keys.
|
|
3
|
+
*
|
|
4
|
+
* A single read can carry several keystrokes, one keystroke split across two
|
|
5
|
+
* reads, or a whole pasted document. Anything that assumes "one read is one
|
|
6
|
+
* key" mangles fast typing and pasted text, so this parser consumes a buffer
|
|
7
|
+
* and reports what it could resolve, keeping the remainder for next time.
|
|
8
|
+
*/
|
|
9
|
+
const ESC = "\u001b";
|
|
10
|
+
/** Bracketed paste: the terminal brackets pasted text so it is not typed. */
|
|
11
|
+
export const PASTE_START = `${ESC}[200~`;
|
|
12
|
+
export const PASTE_END = `${ESC}[201~`;
|
|
13
|
+
const CSI_KEYS = {
|
|
14
|
+
A: "up",
|
|
15
|
+
B: "down",
|
|
16
|
+
C: "right",
|
|
17
|
+
D: "left",
|
|
18
|
+
H: "home",
|
|
19
|
+
F: "end",
|
|
20
|
+
Z: "backtab",
|
|
21
|
+
};
|
|
22
|
+
const TILDE_KEYS = {
|
|
23
|
+
"1": "home",
|
|
24
|
+
"2": "insert",
|
|
25
|
+
"3": "delete",
|
|
26
|
+
"4": "end",
|
|
27
|
+
"5": "pageup",
|
|
28
|
+
"6": "pagedown",
|
|
29
|
+
"7": "home",
|
|
30
|
+
"8": "end",
|
|
31
|
+
};
|
|
32
|
+
export function parseKeys(input) {
|
|
33
|
+
const keys = [];
|
|
34
|
+
let i = 0;
|
|
35
|
+
while (i < input.length) {
|
|
36
|
+
const char = input[i];
|
|
37
|
+
if (char === ESC) {
|
|
38
|
+
const consumed = parseEscape(input, i, keys);
|
|
39
|
+
if (consumed === -1) {
|
|
40
|
+
// An incomplete sequence: keep it rather than reporting a bare Escape,
|
|
41
|
+
// which would move the cursor or cancel a dialog by accident.
|
|
42
|
+
return { keys, rest: input.slice(i) };
|
|
43
|
+
}
|
|
44
|
+
i += consumed;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (char === "\r" || char === "\n") {
|
|
48
|
+
keys.push({ name: "enter" });
|
|
49
|
+
i++;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (char === "\t") {
|
|
53
|
+
keys.push({ name: "tab" });
|
|
54
|
+
i++;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (char === "\u007f" || char === "\b") {
|
|
58
|
+
keys.push({ name: "backspace" });
|
|
59
|
+
i++;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const code = char.charCodeAt(0);
|
|
63
|
+
if (code < 32) {
|
|
64
|
+
// Ctrl-A is 1, Ctrl-B is 2, and so on.
|
|
65
|
+
keys.push({ name: String.fromCharCode(code + 96), ctrl: true });
|
|
66
|
+
i++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
// Printable: take the whole run at once so pasted or fast-typed text does
|
|
70
|
+
// not become one event per character.
|
|
71
|
+
let run = "";
|
|
72
|
+
while (i < input.length) {
|
|
73
|
+
const next = input[i];
|
|
74
|
+
if (next === ESC || next.charCodeAt(0) < 32 || next === "\u007f")
|
|
75
|
+
break;
|
|
76
|
+
run += next;
|
|
77
|
+
i++;
|
|
78
|
+
}
|
|
79
|
+
keys.push({ name: "text", text: run });
|
|
80
|
+
}
|
|
81
|
+
return { keys, rest: "" };
|
|
82
|
+
}
|
|
83
|
+
/** Returns how many characters were consumed, or -1 if the sequence is partial. */
|
|
84
|
+
function parseEscape(input, start, keys) {
|
|
85
|
+
const rest = input.slice(start);
|
|
86
|
+
// A lone Escape at the end of the buffer is ambiguous: it may be the start of
|
|
87
|
+
// a sequence whose remainder has not arrived. Callers resolve this with a
|
|
88
|
+
// timeout; here it is reported as incomplete.
|
|
89
|
+
if (rest.length === 1)
|
|
90
|
+
return -1;
|
|
91
|
+
if (rest.startsWith(PASTE_START)) {
|
|
92
|
+
const end = rest.indexOf(PASTE_END);
|
|
93
|
+
if (end === -1)
|
|
94
|
+
return -1; // paste still arriving
|
|
95
|
+
keys.push({ name: "paste", text: rest.slice(PASTE_START.length, end) });
|
|
96
|
+
return end + PASTE_END.length;
|
|
97
|
+
}
|
|
98
|
+
if (rest[1] === "[" || rest[1] === "O") {
|
|
99
|
+
const match = /^\u001b[[O]([0-9;]*)([A-Za-z~])/.exec(rest);
|
|
100
|
+
if (!match)
|
|
101
|
+
return rest.length > 12 ? rest.length : -1;
|
|
102
|
+
const [full, params = "", final = ""] = match;
|
|
103
|
+
const modifiers = modifierBits(params);
|
|
104
|
+
if (final === "~") {
|
|
105
|
+
const name = TILDE_KEYS[params.split(";")[0] ?? ""];
|
|
106
|
+
if (name)
|
|
107
|
+
keys.push({ name, ...modifiers });
|
|
108
|
+
return full.length;
|
|
109
|
+
}
|
|
110
|
+
const name = CSI_KEYS[final];
|
|
111
|
+
if (name)
|
|
112
|
+
keys.push({ name, ...modifiers });
|
|
113
|
+
return full.length;
|
|
114
|
+
}
|
|
115
|
+
// Alt-<key>: the terminal sends Escape followed by the key itself.
|
|
116
|
+
const following = parseKeys(rest.slice(1));
|
|
117
|
+
const first = following.keys[0];
|
|
118
|
+
if (first) {
|
|
119
|
+
keys.push({ ...first, alt: true });
|
|
120
|
+
return rest.length - following.rest.length;
|
|
121
|
+
}
|
|
122
|
+
return -1;
|
|
123
|
+
}
|
|
124
|
+
/** xterm encodes modifiers as a trailing `;N` parameter. */
|
|
125
|
+
function modifierBits(params) {
|
|
126
|
+
const modifier = Number.parseInt(params.split(";")[1] ?? "1", 10) - 1;
|
|
127
|
+
return {
|
|
128
|
+
shift: (modifier & 1) !== 0,
|
|
129
|
+
alt: (modifier & 2) !== 0,
|
|
130
|
+
ctrl: (modifier & 4) !== 0,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../../src/ui/tui/keys.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning application state into the exact rows the screen should show.
|
|
3
|
+
*
|
|
4
|
+
* Kept apart from the screen and the event loop so it is a pure function of
|
|
5
|
+
* state: a layout bug can be reproduced by calling `composeFrame` with the
|
|
6
|
+
* state that triggered it, rather than by driving a terminal.
|
|
7
|
+
*/
|
|
8
|
+
export interface Block {
|
|
9
|
+
kind: "logo" | "user" | "assistant" | "thinking" | "tool" | "notice" | "log" | "error" | "artifact";
|
|
10
|
+
text: string;
|
|
11
|
+
/** Shown dimmed after the title, e.g. a tool's arguments or duration. */
|
|
12
|
+
detail?: string;
|
|
13
|
+
done?: boolean;
|
|
14
|
+
/** Centred in the frame rather than set flush left. Used by the opening screen. */
|
|
15
|
+
center?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Second dimmed line, on its own row rather than appended to the first.
|
|
18
|
+
* An artifact the terminal cannot render says so here, and the sentence is
|
|
19
|
+
* long enough that truncating it onto the title line would eat the title.
|
|
20
|
+
*/
|
|
21
|
+
note?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface StatusInfo {
|
|
24
|
+
model: string;
|
|
25
|
+
mode: string;
|
|
26
|
+
/** Fraction of the context window in use, 0–1. */
|
|
27
|
+
contextUsed?: number;
|
|
28
|
+
costUsd?: number;
|
|
29
|
+
busy?: boolean;
|
|
30
|
+
/** Turns the spinner; the caller advances it. */
|
|
31
|
+
tick?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface Overlay {
|
|
34
|
+
title: string;
|
|
35
|
+
detail?: string[];
|
|
36
|
+
choices: {
|
|
37
|
+
label: string;
|
|
38
|
+
hint?: string;
|
|
39
|
+
current?: boolean;
|
|
40
|
+
}[];
|
|
41
|
+
selected: number;
|
|
42
|
+
}
|
|
43
|
+
export interface FrameState {
|
|
44
|
+
blocks: Block[];
|
|
45
|
+
input: string;
|
|
46
|
+
/** Caret offset in columns from the start of the input text. */
|
|
47
|
+
cursorColumns: number;
|
|
48
|
+
status: StatusInfo;
|
|
49
|
+
/** Rows scrolled back from the bottom; 0 means pinned to the latest. */
|
|
50
|
+
scroll: number;
|
|
51
|
+
overlay?: Overlay;
|
|
52
|
+
hint?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface Frame {
|
|
55
|
+
lines: string[];
|
|
56
|
+
cursor?: {
|
|
57
|
+
row: number;
|
|
58
|
+
column: number;
|
|
59
|
+
};
|
|
60
|
+
/** Rows of history that exist above the viewport, for the scroll indicator. */
|
|
61
|
+
hiddenAbove: number;
|
|
62
|
+
}
|
|
63
|
+
export declare function composeFrame(state: FrameState, rows: number, columns: number): Frame;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { c } from "../render.js";
|
|
2
|
+
import { logoRows } from "./logo.js";
|
|
3
|
+
import { truncate, visibleWidth, wrap } from "./width.js";
|
|
4
|
+
const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
5
|
+
const MIN_COLUMNS = 24;
|
|
6
|
+
const MIN_ROWS = 8;
|
|
7
|
+
export function composeFrame(state, rows, columns) {
|
|
8
|
+
if (columns < MIN_COLUMNS || rows < MIN_ROWS) {
|
|
9
|
+
// Still a full-height frame: a short one leaves whatever the last frame
|
|
10
|
+
// drew below it on screen, which looks like a half-crashed program.
|
|
11
|
+
return { lines: padTo(tooSmall(rows, columns), rows), hiddenAbove: 0 };
|
|
12
|
+
}
|
|
13
|
+
const inputRows = renderInput(state, columns);
|
|
14
|
+
const statusRow = renderStatus(state.status, columns);
|
|
15
|
+
const historyHeight = Math.max(1, rows - inputRows.length - 1);
|
|
16
|
+
const history = state.blocks.flatMap((block) => renderBlock(block, columns));
|
|
17
|
+
// Scrolling is measured from the bottom, so new output does not yank the
|
|
18
|
+
// view when the user has deliberately scrolled back.
|
|
19
|
+
const maxScroll = Math.max(0, history.length - historyHeight);
|
|
20
|
+
const scroll = Math.min(state.scroll, maxScroll);
|
|
21
|
+
const end = history.length - scroll;
|
|
22
|
+
const start = Math.max(0, end - historyHeight);
|
|
23
|
+
const visible = history.slice(start, end);
|
|
24
|
+
const lines = [
|
|
25
|
+
...(isIntro(state.blocks) ? centreIn(visible, historyHeight) : padTo(visible, historyHeight)),
|
|
26
|
+
...inputRows,
|
|
27
|
+
statusRow,
|
|
28
|
+
];
|
|
29
|
+
if (state.overlay) {
|
|
30
|
+
return { lines: applyOverlay(lines, state.overlay, rows, columns), hiddenAbove: start };
|
|
31
|
+
}
|
|
32
|
+
// The caret sits inside the input box: one row down for the top border, two
|
|
33
|
+
// columns in for the border and the prompt marker.
|
|
34
|
+
const promptWidth = 4;
|
|
35
|
+
const inner = Math.max(1, columns - promptWidth - 2);
|
|
36
|
+
const cursorRow = historyHeight + 2 + Math.floor(state.cursorColumns / inner);
|
|
37
|
+
const cursorColumn = promptWidth + 1 + (state.cursorColumns % inner);
|
|
38
|
+
return {
|
|
39
|
+
lines,
|
|
40
|
+
cursor: { row: Math.min(cursorRow, rows), column: Math.min(cursorColumn, columns) },
|
|
41
|
+
hiddenAbove: start,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function tooSmall(rows, columns) {
|
|
45
|
+
// Drawing a layout that does not fit produces garbage that looks like a
|
|
46
|
+
// crash. Saying so is more useful — wrapped rather than truncated, because
|
|
47
|
+
// at these widths a truncated message loses the part that explains itself.
|
|
48
|
+
const message = `too small: ${columns}x${rows}, need ${MIN_COLUMNS}x${MIN_ROWS}`;
|
|
49
|
+
return wrap(message, Math.max(1, columns)).slice(0, Math.max(1, rows));
|
|
50
|
+
}
|
|
51
|
+
/** Kinds that are the program talking about itself rather than a conversation. */
|
|
52
|
+
const CHROME = new Set(["logo", "notice", "log"]);
|
|
53
|
+
/**
|
|
54
|
+
* Whether the transcript is still only the opening screen.
|
|
55
|
+
*
|
|
56
|
+
* History grows from the bottom, which is right once there is a conversation
|
|
57
|
+
* to follow — but it makes the opening screen sit in the last few rows with
|
|
58
|
+
* the whole terminal empty above it, reading as something that already
|
|
59
|
+
* scrolled past. Until the first turn there is nothing to anchor, so it is
|
|
60
|
+
* centred instead.
|
|
61
|
+
*
|
|
62
|
+
* A background diagnostic does not end that state. A sync that cannot reach
|
|
63
|
+
* its endpoint retries on a timer, and letting the first of those warnings
|
|
64
|
+
* slam the opening screen to the bottom of the terminal makes an idle prompt
|
|
65
|
+
* look like it has already been used.
|
|
66
|
+
*/
|
|
67
|
+
function isIntro(blocks) {
|
|
68
|
+
return blocks.every((block) => CHROME.has(block.kind));
|
|
69
|
+
}
|
|
70
|
+
function centreIn(lines, height) {
|
|
71
|
+
if (lines.length >= height)
|
|
72
|
+
return padTo(lines, height);
|
|
73
|
+
const top = Math.floor((height - lines.length) / 2);
|
|
74
|
+
return [...Array(top).fill(""), ...lines, ...Array(height - top - lines.length).fill("")];
|
|
75
|
+
}
|
|
76
|
+
function padTo(lines, height) {
|
|
77
|
+
const padded = [...lines];
|
|
78
|
+
while (padded.length < height)
|
|
79
|
+
padded.unshift("");
|
|
80
|
+
return padded.slice(-height);
|
|
81
|
+
}
|
|
82
|
+
function renderBlock(block, columns) {
|
|
83
|
+
switch (block.kind) {
|
|
84
|
+
// The mark carries no text of its own: it is drawn from the width it is
|
|
85
|
+
// handed, so a resize re-centres it rather than stranding it left.
|
|
86
|
+
case "logo":
|
|
87
|
+
return logoRows(columns);
|
|
88
|
+
case "user":
|
|
89
|
+
return wrap(block.text, columns - 2).map((line, i) => i === 0 ? `${c.cyan("›")} ${c.bold(line)}` : ` ${line}`);
|
|
90
|
+
case "assistant":
|
|
91
|
+
return wrap(block.text, columns);
|
|
92
|
+
case "thinking":
|
|
93
|
+
return wrap(block.text, columns - 2).map((line) => c.gray(` ${line}`));
|
|
94
|
+
case "tool": {
|
|
95
|
+
const mark = block.done === false ? c.yellow("◐") : c.green("●");
|
|
96
|
+
const head = `${mark} ${c.bold(block.text)}`;
|
|
97
|
+
const detail = block.detail ? c.gray(` ${truncate(block.detail, columns - visibleWidth(block.text) - 4)}`) : "";
|
|
98
|
+
return [truncate(head + detail, columns)];
|
|
99
|
+
}
|
|
100
|
+
case "artifact": {
|
|
101
|
+
// `text` already carries its own colour from artifactSummary, so it is
|
|
102
|
+
// wrapped rather than re-coloured — nesting the codes would leave the
|
|
103
|
+
// reset from the inner one turning the outer colour off mid-line.
|
|
104
|
+
const head = `${c.magenta("◆")} ${block.text}`;
|
|
105
|
+
const rows = [truncate(head, columns)];
|
|
106
|
+
if (block.note)
|
|
107
|
+
rows.push(...wrap(c.gray(` ${block.note}`), columns));
|
|
108
|
+
return rows;
|
|
109
|
+
}
|
|
110
|
+
// Yellow-green, not magenta: the banner and the compaction notes are the
|
|
111
|
+
// program talking about itself, and sharing a hue with `error` red made
|
|
112
|
+
// an ordinary startup read as something having gone wrong.
|
|
113
|
+
case "notice": {
|
|
114
|
+
const rows = wrap(c.lime(block.text), columns);
|
|
115
|
+
// Only a line that fits is centred. Centring the fragments of a wrapped
|
|
116
|
+
// one staggers them around the middle, which reads as damage rather
|
|
117
|
+
// than as layout.
|
|
118
|
+
return block.center && rows.length === 1 ? [centreRow(rows[0], columns)] : rows;
|
|
119
|
+
}
|
|
120
|
+
case "log":
|
|
121
|
+
return wrap(c.gray(block.text), columns);
|
|
122
|
+
case "error":
|
|
123
|
+
return wrap(c.red(`✗ ${block.text}`), columns);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Indent a row so its printed width sits in the middle of the frame. */
|
|
127
|
+
function centreRow(line, columns) {
|
|
128
|
+
const indent = Math.floor((columns - visibleWidth(line)) / 2);
|
|
129
|
+
return indent > 0 ? `${" ".repeat(indent)}${line}` : line;
|
|
130
|
+
}
|
|
131
|
+
function renderInput(state, columns) {
|
|
132
|
+
const inner = Math.max(1, columns - 6);
|
|
133
|
+
const textRows = state.input === "" ? [""] : wrap(state.input, inner);
|
|
134
|
+
// Cap the box so a long paste cannot push the history off the screen.
|
|
135
|
+
const shown = textRows.slice(-6);
|
|
136
|
+
const top = c.gray(`╭${"─".repeat(Math.max(0, columns - 2))}╮`);
|
|
137
|
+
const bottom = c.gray(`╰${"─".repeat(Math.max(0, columns - 2))}╯`);
|
|
138
|
+
const body = shown.map((line, i) => {
|
|
139
|
+
const marker = i === 0 ? c.cyan(" › ") : " ";
|
|
140
|
+
const padding = " ".repeat(Math.max(0, inner - visibleWidth(line)));
|
|
141
|
+
return `${c.gray("│")}${marker}${line}${padding} ${c.gray("│")}`;
|
|
142
|
+
});
|
|
143
|
+
return [top, ...body, bottom];
|
|
144
|
+
}
|
|
145
|
+
function renderStatus(status, columns) {
|
|
146
|
+
const parts = [];
|
|
147
|
+
if (status.busy)
|
|
148
|
+
parts.push(c.yellow(SPINNER[(status.tick ?? 0) % SPINNER.length]));
|
|
149
|
+
parts.push(c.bold(status.model));
|
|
150
|
+
parts.push(c.magenta(status.mode));
|
|
151
|
+
if (status.contextUsed !== undefined) {
|
|
152
|
+
const percent = Math.round(status.contextUsed * 100);
|
|
153
|
+
// Colour the number rather than hiding it: knowing compaction is close
|
|
154
|
+
// explains why the next reply may lose earlier detail.
|
|
155
|
+
const paint = percent >= 90 ? c.red : percent >= 70 ? c.yellow : c.gray;
|
|
156
|
+
parts.push(paint(`ctx ${percent}%`));
|
|
157
|
+
}
|
|
158
|
+
if (status.costUsd !== undefined && status.costUsd > 0) {
|
|
159
|
+
parts.push(c.gray(`$${status.costUsd.toFixed(4)}`));
|
|
160
|
+
}
|
|
161
|
+
const left = parts.join(c.gray(" · "));
|
|
162
|
+
const right = c.gray("/help");
|
|
163
|
+
const gap = Math.max(1, columns - visibleWidth(left) - visibleWidth(right));
|
|
164
|
+
return truncate(`${left}${" ".repeat(gap)}${right}`, columns);
|
|
165
|
+
}
|
|
166
|
+
/** Draw a centred box over the frame, for permission prompts and pickers. */
|
|
167
|
+
function applyOverlay(lines, overlay, rows, columns) {
|
|
168
|
+
const width = Math.min(columns - 4, 72);
|
|
169
|
+
const inner = width - 4;
|
|
170
|
+
const body = [];
|
|
171
|
+
body.push(c.bold(truncate(overlay.title, inner)));
|
|
172
|
+
for (const line of overlay.detail ?? []) {
|
|
173
|
+
for (const wrapped of wrap(line, inner))
|
|
174
|
+
body.push(c.gray(wrapped));
|
|
175
|
+
}
|
|
176
|
+
body.push("");
|
|
177
|
+
overlay.choices.forEach((choice, i) => {
|
|
178
|
+
const active = i === overlay.selected;
|
|
179
|
+
const mark = choice.current ? c.green("✓") : " ";
|
|
180
|
+
const label = active ? c.cyan(`❯ ${choice.label}`) : ` ${choice.label}`;
|
|
181
|
+
const hint = choice.hint ? c.gray(` — ${choice.hint}`) : "";
|
|
182
|
+
body.push(truncate(`${mark} ${c.gray(String(i + 1))} ${label}${hint}`, inner));
|
|
183
|
+
});
|
|
184
|
+
const boxed = [
|
|
185
|
+
c.gray(`╭${"─".repeat(width - 2)}╮`),
|
|
186
|
+
...body.map((line) => {
|
|
187
|
+
const padding = " ".repeat(Math.max(0, inner - visibleWidth(line)));
|
|
188
|
+
return `${c.gray("│")} ${line}${padding} ${c.gray("│")}`;
|
|
189
|
+
}),
|
|
190
|
+
c.gray(`╰${"─".repeat(width - 2)}╯`),
|
|
191
|
+
];
|
|
192
|
+
const top = Math.max(0, Math.floor((rows - boxed.length) / 2));
|
|
193
|
+
const left = Math.max(0, Math.floor((columns - width) / 2));
|
|
194
|
+
const out = [...lines];
|
|
195
|
+
boxed.forEach((line, i) => {
|
|
196
|
+
const row = top + i;
|
|
197
|
+
if (row < out.length)
|
|
198
|
+
out[row] = " ".repeat(left) + line;
|
|
199
|
+
});
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../../src/ui/tui/layout.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The mark and the name as frame rows, centred for the width they are given.
|
|
3
|
+
*
|
|
4
|
+
* Computed per frame rather than stored on the block, so narrowing the window
|
|
5
|
+
* swaps in the plain name instead of leaving the fragments a wrap would make
|
|
6
|
+
* of a letterform.
|
|
7
|
+
*
|
|
8
|
+
* The logo takes no colour of its own. The SVG is drawn in `currentColor` and
|
|
9
|
+
* the favicon flips between near-black and near-white with the scheme, so the
|
|
10
|
+
* mark is monochrome by design — in a terminal that means the default
|
|
11
|
+
* foreground, which is already the right one on a light or a dark theme, and
|
|
12
|
+
* is the only choice that survives `NO_COLOR`.
|
|
13
|
+
*
|
|
14
|
+
* A blank row trails the logo so the banner beneath it is not welded to the
|
|
15
|
+
* letterform — as a row of the block rather than a separate one, because an
|
|
16
|
+
* empty `notice` would still carry its colour codes into the frame.
|
|
17
|
+
*/
|
|
18
|
+
export declare function logoRows(columns: number): string[];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { c } from "../render.js";
|
|
2
|
+
import { MARK, MARK_GAP, NAME, wordmark } from "../wordmark.js";
|
|
3
|
+
/**
|
|
4
|
+
* The mark and the name as frame rows, centred for the width they are given.
|
|
5
|
+
*
|
|
6
|
+
* Computed per frame rather than stored on the block, so narrowing the window
|
|
7
|
+
* swaps in the plain name instead of leaving the fragments a wrap would make
|
|
8
|
+
* of a letterform.
|
|
9
|
+
*
|
|
10
|
+
* The logo takes no colour of its own. The SVG is drawn in `currentColor` and
|
|
11
|
+
* the favicon flips between near-black and near-white with the scheme, so the
|
|
12
|
+
* mark is monochrome by design — in a terminal that means the default
|
|
13
|
+
* foreground, which is already the right one on a light or a dark theme, and
|
|
14
|
+
* is the only choice that survives `NO_COLOR`.
|
|
15
|
+
*
|
|
16
|
+
* A blank row trails the logo so the banner beneath it is not welded to the
|
|
17
|
+
* letterform — as a row of the block rather than a separate one, because an
|
|
18
|
+
* empty `notice` would still carry its colour codes into the frame.
|
|
19
|
+
*/
|
|
20
|
+
export function logoRows(columns) {
|
|
21
|
+
const { rows, width } = wordmark();
|
|
22
|
+
// Too narrow for the letterform: the mark collapses to the one part of it
|
|
23
|
+
// that survives at a single row, and the name is set in ordinary type.
|
|
24
|
+
if (columns < width + 4) {
|
|
25
|
+
const compact = c.bold(`● ${NAME}`);
|
|
26
|
+
const indent = Math.max(0, Math.floor((columns - NAME.length - 2) / 2));
|
|
27
|
+
return [`${" ".repeat(indent)}${compact}`, ""];
|
|
28
|
+
}
|
|
29
|
+
const pad = " ".repeat(Math.floor((columns - width) / 2));
|
|
30
|
+
const gap = " ".repeat(MARK_GAP);
|
|
31
|
+
return [...rows.map((row, i) => c.bold(`${pad}${MARK[i]}${gap}${row}`)), ""];
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=logo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logo.js","sourceRoot":"","sources":["../../../src/ui/tui/logo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAEhE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAC;IAEnC,0EAA0E;IAC1E,uEAAuE;IACvE,IAAI,OAAO,GAAG,KAAK,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QACpC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACxE,OAAO,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;IACjD,CAAC;IAED,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC1D,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACjC,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,CAAE,GAAG,GAAG,GAAG,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAChF,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal ownership: the alternate buffer, raw mode, and getting out again.
|
|
3
|
+
*
|
|
4
|
+
* The failure that matters here is not a misdrawn frame. It is exiting without
|
|
5
|
+
* restoring: the user is left in raw mode with no echo and no cursor, facing a
|
|
6
|
+
* terminal that appears frozen, and their only recovery is to type `reset`
|
|
7
|
+
* blind. So restoration is wired to every exit path there is — normal return,
|
|
8
|
+
* thrown exception, unhandled rejection, and the signals a terminal or a
|
|
9
|
+
* supervisor can deliver — and it is idempotent, because several of those can
|
|
10
|
+
* fire together.
|
|
11
|
+
*/
|
|
12
|
+
export interface Screen {
|
|
13
|
+
readonly rows: number;
|
|
14
|
+
readonly columns: number;
|
|
15
|
+
/** Draw a whole frame. `cursor` is a 1-based row/column to leave it at. */
|
|
16
|
+
render(lines: string[], cursor?: {
|
|
17
|
+
row: number;
|
|
18
|
+
column: number;
|
|
19
|
+
}): void;
|
|
20
|
+
onKey(handler: (key: string) => void): void;
|
|
21
|
+
onResize(handler: () => void): void;
|
|
22
|
+
/** Restore the terminal. Safe to call more than once. */
|
|
23
|
+
close(): void;
|
|
24
|
+
}
|
|
25
|
+
export interface ScreenOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Work to finish before a signal ends the process.
|
|
28
|
+
*
|
|
29
|
+
* The screen has no idea what this does — it only guarantees the promise
|
|
30
|
+
* gets a bounded chance to settle. The app uses it to stop an in-flight turn
|
|
31
|
+
* and let the agent write its tool results, which otherwise die with the
|
|
32
|
+
* process and leave an unanswered tool call on disk.
|
|
33
|
+
*/
|
|
34
|
+
beforeExit?: () => Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
/** The parts of the terminal streams a screen actually uses. */
|
|
37
|
+
export interface ScreenStreams {
|
|
38
|
+
out: Pick<NodeJS.WriteStream, "write" | "rows" | "columns" | "on" | "off">;
|
|
39
|
+
input: Pick<NodeJS.ReadStream, "setRawMode" | "isRaw" | "isTTY" | "resume" | "pause" | "on" | "off">;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Streams are injectable so the restore path can be tested.
|
|
43
|
+
*
|
|
44
|
+
* Not a general-purpose seam: it exists because "does this leave the terminal
|
|
45
|
+
* usable" is the one behaviour here worth proving, and proving it against the
|
|
46
|
+
* real process streams would wreck the terminal running the tests.
|
|
47
|
+
*/
|
|
48
|
+
export declare function openScreen(streams?: Partial<ScreenStreams>, opts?: ScreenOptions): Screen;
|