@giovannijecha/jecode 0.3.2 → 0.5.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/README.md +63 -23
- package/dist/batch.js +22 -2
- package/dist/cli-info.js +3 -0
- package/dist/command-settings.js +19 -0
- package/dist/commands.js +11 -14
- package/dist/config.js +2 -0
- package/dist/controller.js +3 -0
- package/dist/conversation.js +208 -0
- package/dist/credential-commands.js +63 -80
- package/dist/credentials.js +7 -1
- package/dist/launch.js +19 -0
- package/dist/model-command.js +171 -0
- package/dist/permission-command.js +52 -53
- package/dist/provider-commands.js +71 -228
- package/dist/provider-errors.js +4 -3
- package/dist/provider-label.js +2 -2
- package/dist/providers/ollama.js +8 -3
- package/dist/sessions/codec.js +344 -0
- package/dist/sessions/lease.js +76 -0
- package/dist/sessions/runtime.js +73 -0
- package/dist/sessions/store.js +368 -0
- package/dist/settings-command.js +43 -98
- package/dist/start.js +67 -4
- package/dist/transcript-types.js +6 -0
- package/dist/tui/activity.js +3 -0
- package/dist/tui/app-input.js +9 -6
- package/dist/tui/app-workflows.js +37 -8
- package/dist/tui/app.js +72 -20
- package/dist/tui/components/composer.js +1 -1
- package/dist/tui/components/footer.js +1 -1
- package/dist/tui/components/menu.js +27 -15
- package/dist/tui/components/messages.js +1 -15
- package/dist/tui/components/prompt.js +2 -2
- package/dist/tui/components/status.js +5 -2
- package/dist/tui/components/tool.js +22 -5
- package/dist/tui/editor.js +54 -7
- package/dist/tui/feedback.js +5 -2
- package/dist/tui/field.js +1 -1
- package/dist/tui/help.js +4 -1
- package/dist/tui/input.js +4 -0
- package/dist/tui/keys.js +14 -3
- package/dist/tui/overlay.js +6 -0
- package/dist/tui/picker.js +26 -10
- package/dist/tui/resume.js +24 -0
- package/dist/tui/session-view.js +2 -2
- package/dist/tui/turn.js +2 -3
- package/dist/tui/view.js +1 -1
- package/dist/ui/diff.js +2 -0
- package/dist/ui/inline.js +2 -2
- package/dist/ui/markdown.js +7 -7
- package/dist/ui/render.js +2 -0
- package/dist/ui/theme.js +4 -4
- package/dist/usage.js +9 -0
- package/package.json +1 -1
package/dist/tui/feedback.js
CHANGED
|
@@ -50,6 +50,9 @@ export function commandFeedback(block) {
|
|
|
50
50
|
}
|
|
51
51
|
/** Explain why a model turn cannot start, without exposing configuration internals. */
|
|
52
52
|
export function turnBlocker(session) {
|
|
53
|
+
if (session.persistence?.failure !== undefined) {
|
|
54
|
+
return { text: "session could not be saved · /new to retry", tone: "error" };
|
|
55
|
+
}
|
|
53
56
|
const blocked = session.provider.blocked();
|
|
54
57
|
const auth = session.provider.auth;
|
|
55
58
|
const expected = auth.kind === "api-key"
|
|
@@ -61,8 +64,8 @@ export function turnBlocker(session) {
|
|
|
61
64
|
if (blocked !== undefined) {
|
|
62
65
|
return {
|
|
63
66
|
text: auth.kind === "oauth"
|
|
64
|
-
? `${providerLabel(session.provider.id)} needs
|
|
65
|
-
: `${providerLabel(session.provider.id)} needs an API key · /
|
|
67
|
+
? `${providerLabel(session.provider.id)} needs sign-in · /providers`
|
|
68
|
+
: `${providerLabel(session.provider.id)} needs an API key · /providers`,
|
|
66
69
|
tone: "warn",
|
|
67
70
|
};
|
|
68
71
|
}
|
package/dist/tui/field.js
CHANGED
|
@@ -7,7 +7,7 @@ import { row } from "../ui/render.js";
|
|
|
7
7
|
import { promptCursor, promptLine } from "./components/prompt.js";
|
|
8
8
|
export function panel(field, width, pal) {
|
|
9
9
|
const { ink } = pal;
|
|
10
|
-
const head = row(width, field.title, field.right === undefined ? [] : [{ text: field.right, fg: ink.
|
|
10
|
+
const head = row(width, field.title, field.right === undefined ? [] : [{ text: field.right, fg: ink.dim }]);
|
|
11
11
|
const line = promptLine(field.editor.text, field.editor.cursor, width, pal, {
|
|
12
12
|
secret: field.secret,
|
|
13
13
|
}).row;
|
package/dist/tui/help.js
CHANGED
|
@@ -4,6 +4,9 @@ import { textWidth } from "../ui/width.js";
|
|
|
4
4
|
const CONTROL_WIDTH = 18;
|
|
5
5
|
const CONTROLS = [
|
|
6
6
|
{ key: "up / down", description: "move through menus or history" },
|
|
7
|
+
{ key: "left / right", description: "move cursor or change a value" },
|
|
8
|
+
{ key: "ctrl+left / right", description: "move cursor by word" },
|
|
9
|
+
{ key: "ctrl+backspace/del", description: "delete a word" },
|
|
7
10
|
{ key: "enter / tab", description: "select or send · complete" },
|
|
8
11
|
{ key: "alt+enter", description: "insert a new line" },
|
|
9
12
|
{ key: "esc", description: "close UI or interrupt work" },
|
|
@@ -16,7 +19,7 @@ export function panel(width, pal, maxRows = CONTROLS.length + 1) {
|
|
|
16
19
|
const heading = row(width, [
|
|
17
20
|
{ text: "help ", fg: pal.accent, bold: true },
|
|
18
21
|
{ text: "keyboard controls", fg: pal.ink.fg },
|
|
19
|
-
], [{ text: "esc close", fg: pal.ink.
|
|
22
|
+
], [{ text: "esc close", fg: pal.ink.dim }]);
|
|
20
23
|
const controls = CONTROLS.map((control) => {
|
|
21
24
|
const gap = Math.max(2, CONTROL_WIDTH - textWidth(control.key));
|
|
22
25
|
return row(width, [
|
package/dist/tui/input.js
CHANGED
|
@@ -26,6 +26,10 @@ export function applyKey(state, key) {
|
|
|
26
26
|
return edit.wordLeft(state);
|
|
27
27
|
case "wordright":
|
|
28
28
|
return edit.wordRight(state);
|
|
29
|
+
case "deletewordleft":
|
|
30
|
+
return edit.killWord(state);
|
|
31
|
+
case "deletewordright":
|
|
32
|
+
return edit.killNextWord(state);
|
|
29
33
|
case "home":
|
|
30
34
|
return edit.home(state);
|
|
31
35
|
case "end":
|
package/dist/tui/keys.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// guessed at; and a bracketed paste arrives as a delimited run that must not be
|
|
6
6
|
// interpreted key by key, or a pasted newline submits half the paste.
|
|
7
7
|
const ESC = String.fromCharCode(27);
|
|
8
|
+
const BS = String.fromCharCode(8);
|
|
8
9
|
const DEL = String.fromCharCode(127);
|
|
9
10
|
const PASTE_START = "[200~";
|
|
10
11
|
const PASTE_END = "[201~";
|
|
@@ -35,10 +36,18 @@ const SEQUENCES = {
|
|
|
35
36
|
"[7~": "home",
|
|
36
37
|
"[8~": "end",
|
|
37
38
|
"[3~": "delete",
|
|
39
|
+
"[3;5~": "deletewordright",
|
|
38
40
|
"[5~": "pageup",
|
|
39
41
|
"[6~": "pagedown",
|
|
40
42
|
"[1;5C": "wordright",
|
|
41
43
|
"[1;5D": "wordleft",
|
|
44
|
+
// Explicit modified-key forms used by terminals with CSI-u support.
|
|
45
|
+
"[127;5u": "deletewordleft",
|
|
46
|
+
"[8;5u": "deletewordleft",
|
|
47
|
+
// Readline-compatible Alt bindings. VS Code also sends Alt+D for
|
|
48
|
+
// Ctrl+Delete and Ctrl+W for Ctrl+Backspace in its integrated terminal.
|
|
49
|
+
d: "deletewordright",
|
|
50
|
+
[DEL]: "deletewordleft",
|
|
42
51
|
"[Z": "backtab",
|
|
43
52
|
// alt+enter, which arrives as an escape followed by the return itself. It is
|
|
44
53
|
// how a multi-line message gets written when enter is what sends one.
|
|
@@ -48,10 +57,10 @@ const CONTROL = {
|
|
|
48
57
|
"\r": "enter",
|
|
49
58
|
"\n": "enter",
|
|
50
59
|
"\t": "tab",
|
|
51
|
-
|
|
60
|
+
[BS]: "backspace",
|
|
52
61
|
[DEL]: "backspace",
|
|
53
62
|
};
|
|
54
|
-
export function decoder() {
|
|
63
|
+
export function decoder(options = {}) {
|
|
55
64
|
let held = "";
|
|
56
65
|
let pasting = false;
|
|
57
66
|
let pasted = "";
|
|
@@ -121,7 +130,9 @@ export function decoder() {
|
|
|
121
130
|
keys.push({ name: "escape", text: "", ctrl: false });
|
|
122
131
|
continue;
|
|
123
132
|
}
|
|
124
|
-
const named =
|
|
133
|
+
const named = ch === BS && options.ctrlBackspaceIsBs === true
|
|
134
|
+
? "deletewordleft"
|
|
135
|
+
: CONTROL[ch];
|
|
125
136
|
if (named !== undefined) {
|
|
126
137
|
held = held.slice(1);
|
|
127
138
|
keys.push({ name: named, text: "", ctrl: false });
|
package/dist/tui/overlay.js
CHANGED
|
@@ -47,6 +47,12 @@ function handlePicker(open, key) {
|
|
|
47
47
|
case "down":
|
|
48
48
|
open.picker = picker.move(open.picker, 1);
|
|
49
49
|
break;
|
|
50
|
+
case "left":
|
|
51
|
+
open.picker = picker.adjust(open.picker, -1);
|
|
52
|
+
break;
|
|
53
|
+
case "right":
|
|
54
|
+
open.picker = picker.adjust(open.picker, 1);
|
|
55
|
+
break;
|
|
50
56
|
case "home":
|
|
51
57
|
open.picker = picker.edge(open.picker, "home");
|
|
52
58
|
break;
|
package/dist/tui/picker.js
CHANGED
|
@@ -19,6 +19,12 @@ export function edge(picker, end) {
|
|
|
19
19
|
export function page(picker, direction, rows = WINDOW) {
|
|
20
20
|
return move(picker, direction * Math.max(1, rows));
|
|
21
21
|
}
|
|
22
|
+
export function adjust(picker, step) {
|
|
23
|
+
const index = selected(picker);
|
|
24
|
+
return index === undefined || picker.adjust === undefined
|
|
25
|
+
? picker
|
|
26
|
+
: picker.adjust(index, step);
|
|
27
|
+
}
|
|
22
28
|
export function type(picker, text) {
|
|
23
29
|
if (picker.searchable !== true || text === "")
|
|
24
30
|
return picker;
|
|
@@ -45,18 +51,21 @@ export function byKey(picker, text) {
|
|
|
45
51
|
const found = picker.options.findIndex((option) => option.key === typed);
|
|
46
52
|
return found === -1 ? undefined : found;
|
|
47
53
|
}
|
|
48
|
-
export function panel(picker, width, pal, maxRows = WINDOW + 3) {
|
|
54
|
+
export function panel(picker, width, pal, maxRows = (picker.visible ?? WINDOW) + 3) {
|
|
49
55
|
return layout(picker, width, pal, maxRows).rows;
|
|
50
56
|
}
|
|
51
57
|
/** Caret for the shared query row, relative to the unframed picker body. */
|
|
52
|
-
export function caret(picker, width, maxRows = WINDOW + 3) {
|
|
58
|
+
export function caret(picker, width, maxRows = (picker.visible ?? WINDOW) + 3) {
|
|
53
59
|
return layout(picker, width, undefined, maxRows).cursor;
|
|
54
60
|
}
|
|
55
61
|
function layout(picker, width, pal, maxRows) {
|
|
56
62
|
const found = matches(picker);
|
|
57
63
|
const note = picker.description ?? picker.footer;
|
|
58
|
-
const
|
|
59
|
-
const
|
|
64
|
+
const hasTitle = picker.title.length > 0;
|
|
65
|
+
const fixed = (hasTitle ? 1 : 0) +
|
|
66
|
+
(note === undefined ? 0 : 1) +
|
|
67
|
+
(picker.searchable === true ? 1 : 0);
|
|
68
|
+
const optionRoom = Math.max(1, Math.min(picker.visible ?? WINDOW, maxRows - fixed));
|
|
60
69
|
const selectedAt = Math.max(0, found.findIndex((entry) => entry.index === picker.index));
|
|
61
70
|
const { first, last } = menuWindow(found.length, selectedAt, optionRoom);
|
|
62
71
|
const shown = found.slice(first, last);
|
|
@@ -67,14 +76,19 @@ function layout(picker, width, pal, maxRows) {
|
|
|
67
76
|
? [row(width, [{ text: "no matches", fg: colors.ink.muted }])]
|
|
68
77
|
: renderMenuRows(shown.map(({ option, index }) => ({
|
|
69
78
|
label: option.label,
|
|
79
|
+
description: option.description,
|
|
70
80
|
hint: option.hint,
|
|
81
|
+
value: option.value,
|
|
82
|
+
adjustable: option.adjustable,
|
|
71
83
|
selected: index === picker.index,
|
|
72
84
|
})), width, colors);
|
|
73
85
|
const progress = found.length > shown.length || picker.searchable === true
|
|
74
86
|
? `${found.length === 0 ? "0" : `${first + 1}–${first + shown.length}`} / ${found.length}` +
|
|
75
87
|
(found.length === picker.options.length ? "" : ` · ${picker.options.length} total`)
|
|
76
88
|
: "";
|
|
77
|
-
const titleRight =
|
|
89
|
+
const titleRight = hasTitle
|
|
90
|
+
? picker.right ?? (picker.searchable === true ? undefined : progress)
|
|
91
|
+
: undefined;
|
|
78
92
|
const visibleTitleRight = titleRight === undefined
|
|
79
93
|
? undefined
|
|
80
94
|
: elide(titleRight, Math.max(1, Math.floor(width * 0.55)));
|
|
@@ -87,14 +101,16 @@ function layout(picker, width, pal, maxRows) {
|
|
|
87
101
|
const queryCursor = picker.searchable === true
|
|
88
102
|
? promptCursor(picker.query ?? "", (picker.query ?? "").length, width, { right: progress })
|
|
89
103
|
: undefined;
|
|
90
|
-
const queryOffset = 1 + (note === undefined ? 0 : 1);
|
|
104
|
+
const queryOffset = (hasTitle ? 1 : 0) + (note === undefined ? 0 : 1);
|
|
91
105
|
return {
|
|
92
106
|
rows: colors === undefined
|
|
93
107
|
? []
|
|
94
108
|
: [
|
|
95
|
-
|
|
96
|
-
? [
|
|
97
|
-
|
|
109
|
+
...(hasTitle
|
|
110
|
+
? [row(width, picker.title, visibleTitleRight === undefined || visibleTitleRight === ""
|
|
111
|
+
? []
|
|
112
|
+
: [{ text: visibleTitleRight, fg: colors.ink.dim }])]
|
|
113
|
+
: []),
|
|
98
114
|
...(note === undefined ? [] : [row(width, [{ text: note, fg: colors.ink.muted }])]),
|
|
99
115
|
...(queryRow === undefined ? [] : [queryRow.row]),
|
|
100
116
|
...options,
|
|
@@ -113,7 +129,7 @@ export function heading(label, about, pal) {
|
|
|
113
129
|
function matches(picker) {
|
|
114
130
|
const query = (picker.query ?? "").trim().toLocaleLowerCase();
|
|
115
131
|
return picker.options.flatMap((option, index) => {
|
|
116
|
-
const haystack = `${option.label} ${option.hint ?? ""}`.toLocaleLowerCase();
|
|
132
|
+
const haystack = `${option.label} ${option.hint ?? ""} ${option.value ?? ""}`.toLocaleLowerCase();
|
|
117
133
|
return query === "" || haystack.includes(query) ? [{ option, index }] : [];
|
|
118
134
|
});
|
|
119
135
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { heading } from "./picker.js";
|
|
2
|
+
export function resumePicker(candidates, palette) {
|
|
3
|
+
return {
|
|
4
|
+
title: heading("resume", "saved conversations", palette),
|
|
5
|
+
searchable: true,
|
|
6
|
+
visible: 8,
|
|
7
|
+
options: candidates.map((candidate) => ({
|
|
8
|
+
label: candidate.preview,
|
|
9
|
+
hint: stamp(candidate.updatedAt),
|
|
10
|
+
value: `${candidate.turns} ${candidate.turns === 1 ? "turn" : "turns"}`,
|
|
11
|
+
})),
|
|
12
|
+
index: 0,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function stamp(value) {
|
|
16
|
+
const date = new Date(value);
|
|
17
|
+
if (!Number.isFinite(date.getTime()))
|
|
18
|
+
return value.replace("T", " ").slice(0, 16);
|
|
19
|
+
const two = (part) => String(part).padStart(2, "0");
|
|
20
|
+
return [
|
|
21
|
+
`${date.getFullYear()}-${two(date.getMonth() + 1)}-${two(date.getDate())}`,
|
|
22
|
+
`${two(date.getHours())}:${two(date.getMinutes())}`,
|
|
23
|
+
].join(" ");
|
|
24
|
+
}
|
package/dist/tui/session-view.js
CHANGED
|
@@ -28,13 +28,13 @@ export function turnFailure(session, error, aborted) {
|
|
|
28
28
|
if (/\b401\b/.test(text)) {
|
|
29
29
|
const auth = session.provider.auth;
|
|
30
30
|
if (auth.kind === "oauth") {
|
|
31
|
-
text += ` · reconnect ${auth.label} in /
|
|
31
|
+
text += ` · reconnect ${auth.label} in /providers`;
|
|
32
32
|
}
|
|
33
33
|
else {
|
|
34
34
|
const source = credentialSource(auth.keyVar);
|
|
35
35
|
text += source === "environment"
|
|
36
36
|
? ` · update ${auth.keyVar} in the environment and restart`
|
|
37
|
-
: " · check
|
|
37
|
+
: " · check access with /providers";
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
return { kind: "notice", text, tone: "error" };
|
package/dist/tui/turn.js
CHANGED
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
// neither has to know how the other is built.
|
|
6
6
|
import { condense, diff } from "../ui/diff.js";
|
|
7
7
|
import { promptFor } from "./approve.js";
|
|
8
|
-
// Semantic activity labels
|
|
9
|
-
//
|
|
10
|
-
// the live work in the transcript itself.
|
|
8
|
+
// Semantic activity labels feed the footer's compact state and timer while
|
|
9
|
+
// reasoning and tools keep the detailed work visible in the transcript.
|
|
11
10
|
const WAITING = "Waiting";
|
|
12
11
|
const THINKING = "Thinking";
|
|
13
12
|
const WRITING = "Writing";
|
package/dist/tui/view.js
CHANGED
|
@@ -79,7 +79,7 @@ function tooSmall(height, width, view) {
|
|
|
79
79
|
]);
|
|
80
80
|
if (middle + 1 < height) {
|
|
81
81
|
rows[middle + 1] = row(width, [
|
|
82
|
-
{ text: elide(`need ${MIN_COLS}×${MIN_ROWS}`, Math.max(1, width)), fg: view.pal.ink.
|
|
82
|
+
{ text: elide(`need ${MIN_COLS}×${MIN_ROWS}`, Math.max(1, width)), fg: view.pal.ink.dim },
|
|
83
83
|
]);
|
|
84
84
|
}
|
|
85
85
|
return { rows, maxScroll: 0 };
|
package/dist/ui/diff.js
CHANGED
|
@@ -96,6 +96,8 @@ function common(a, b) {
|
|
|
96
96
|
// A trailing newline is a property of the file, not a line of it: counting it
|
|
97
97
|
// as one would report every append as touching two lines instead of one.
|
|
98
98
|
function lines(text) {
|
|
99
|
+
if (text === "")
|
|
100
|
+
return [];
|
|
99
101
|
const split = text.split("\n");
|
|
100
102
|
if (split.length > 1 && split[split.length - 1] === "")
|
|
101
103
|
split.pop();
|
package/dist/ui/inline.js
CHANGED
|
@@ -21,7 +21,7 @@ export function inline(text, base, pal, bold = false) {
|
|
|
21
21
|
}
|
|
22
22
|
const [, mono, strong, strongAlt, emphasis, label] = match;
|
|
23
23
|
if (mono !== undefined)
|
|
24
|
-
segs.push({ text: mono, fg: pal.
|
|
24
|
+
segs.push({ text: mono, fg: pal.technical });
|
|
25
25
|
else if (strong !== undefined)
|
|
26
26
|
segs.push({ text: strong, fg: ink.bright, bold: true });
|
|
27
27
|
else if (strongAlt !== undefined)
|
|
@@ -29,7 +29,7 @@ export function inline(text, base, pal, bold = false) {
|
|
|
29
29
|
else if (emphasis !== undefined)
|
|
30
30
|
segs.push({ text: emphasis, fg: ink.bright });
|
|
31
31
|
else if (label !== undefined)
|
|
32
|
-
segs.push({ text: label, fg: pal.
|
|
32
|
+
segs.push({ text: label, fg: pal.technical });
|
|
33
33
|
last = match.index + match[0].length;
|
|
34
34
|
}
|
|
35
35
|
if (last < text.length) {
|
package/dist/ui/markdown.js
CHANGED
|
@@ -63,7 +63,7 @@ export function markdown(text, max, pal, proseMax = max) {
|
|
|
63
63
|
const depth = Math.min(2, Math.floor(bullet[1].length / 2));
|
|
64
64
|
const mark = [
|
|
65
65
|
{ text: " ".repeat(depth) },
|
|
66
|
-
{ text: "- ", fg: pal.
|
|
66
|
+
{ text: "- ", fg: pal.technical },
|
|
67
67
|
];
|
|
68
68
|
const hang = [{ text: `${" ".repeat(depth)} ` }];
|
|
69
69
|
rows.push(...emit(inline(bullet[2], ink.fg, pal), prose - depth * 2 - 2, mark, hang));
|
|
@@ -72,7 +72,7 @@ export function markdown(text, max, pal, proseMax = max) {
|
|
|
72
72
|
const ordered = ORDERED.exec(line);
|
|
73
73
|
if (ordered !== null) {
|
|
74
74
|
const label = `${ordered[2]}. `;
|
|
75
|
-
const mark = [{ text: label, fg: pal.
|
|
75
|
+
const mark = [{ text: label, fg: pal.technical }];
|
|
76
76
|
const hang = [{ text: " ".repeat(label.length) }];
|
|
77
77
|
rows.push(...emit(inline(ordered[3], ink.fg, pal), prose - label.length, mark, hang));
|
|
78
78
|
continue;
|
|
@@ -120,13 +120,13 @@ function code(lines, start, max, pal, out) {
|
|
|
120
120
|
break;
|
|
121
121
|
body.push(elide(line.replace(/\t/g, " "), max - 2));
|
|
122
122
|
}
|
|
123
|
-
out.push({ segs: [{ text: `\`\`\`${lang}`, fg: ink.
|
|
123
|
+
out.push({ segs: [{ text: `\`\`\`${lang}`, fg: ink.dim }] });
|
|
124
124
|
const roles = {
|
|
125
|
-
plain: ink.
|
|
126
|
-
comment: ink.
|
|
125
|
+
plain: ink.fg,
|
|
126
|
+
comment: ink.dim,
|
|
127
127
|
string: ink.added,
|
|
128
128
|
number: ink.attention,
|
|
129
|
-
keyword: pal.
|
|
129
|
+
keyword: pal.technical,
|
|
130
130
|
};
|
|
131
131
|
for (const tokens of highlight(body, lang)) {
|
|
132
132
|
out.push({
|
|
@@ -136,7 +136,7 @@ function code(lines, start, max, pal, out) {
|
|
|
136
136
|
],
|
|
137
137
|
});
|
|
138
138
|
}
|
|
139
|
-
out.push({ segs: [{ text: "```", fg: ink.
|
|
139
|
+
out.push({ segs: [{ text: "```", fg: ink.dim }] });
|
|
140
140
|
return i;
|
|
141
141
|
}
|
|
142
142
|
/** Flow one inline run into rows, with an opener and a hanging indent. */
|
package/dist/ui/render.js
CHANGED
package/dist/ui/theme.js
CHANGED
|
@@ -5,20 +5,20 @@
|
|
|
5
5
|
// Components depend on these roles rather than embedding presentation values.
|
|
6
6
|
export const STEEL = {
|
|
7
7
|
accent: [102, 155, 210],
|
|
8
|
-
|
|
8
|
+
technical: [78, 201, 232],
|
|
9
9
|
focus: [102, 155, 210],
|
|
10
10
|
rule: [53, 80, 110],
|
|
11
11
|
ink: {
|
|
12
|
-
fg: [
|
|
12
|
+
fg: [220, 224, 229],
|
|
13
13
|
bright: [235, 239, 244],
|
|
14
|
-
muted: [
|
|
14
|
+
muted: [156, 169, 183],
|
|
15
|
+
dim: [112, 124, 137],
|
|
15
16
|
attention: [230, 191, 95],
|
|
16
17
|
added: [134, 203, 146],
|
|
17
18
|
removed: [232, 112, 112],
|
|
18
19
|
},
|
|
19
20
|
surface: {
|
|
20
21
|
subtle: [31, 38, 47],
|
|
21
|
-
inset: [42, 52, 66],
|
|
22
22
|
added: [22, 55, 34],
|
|
23
23
|
removed: [62, 24, 27],
|
|
24
24
|
attention: [62, 50, 19],
|
package/dist/usage.js
CHANGED
|
@@ -19,6 +19,15 @@ export function recordUsage(total, next) {
|
|
|
19
19
|
total.cacheWriteInputTokens += next.cacheWriteInputTokens;
|
|
20
20
|
total.reasoningTokens += next.reasoningTokens;
|
|
21
21
|
}
|
|
22
|
+
export function usageFromHistory(messages) {
|
|
23
|
+
const total = emptyUsage();
|
|
24
|
+
for (const message of messages) {
|
|
25
|
+
if (message.role === "assistant" && message.usage !== undefined) {
|
|
26
|
+
recordUsage(total, message.usage);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return total;
|
|
30
|
+
}
|
|
22
31
|
export function formatTokens(value) {
|
|
23
32
|
if (value < 1_000)
|
|
24
33
|
return String(value);
|