amness 0.0.0-stage → 0.1.2
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 +237 -2
- package/dist/cli/acpcli.d.ts +7 -0
- package/dist/cli/acpcli.js +14 -0
- package/dist/cli/amness.d.ts +2 -0
- package/dist/cli/amness.js +694 -0
- package/dist/cli/benchcli.d.ts +5 -0
- package/dist/cli/benchcli.js +160 -0
- package/dist/cli/config.d.ts +79 -0
- package/dist/cli/config.js +111 -0
- package/dist/cli/demo.d.ts +13 -0
- package/dist/cli/demo.js +44 -0
- package/dist/cli/detect.d.ts +2 -0
- package/dist/cli/detect.js +20 -0
- package/dist/cli/evalcli.d.ts +15 -0
- package/dist/cli/evalcli.js +198 -0
- package/dist/cli/format.d.ts +5 -0
- package/dist/cli/format.js +55 -0
- package/dist/cli/headless.d.ts +2 -0
- package/dist/cli/headless.js +169 -0
- package/dist/cli/keystore.d.ts +9 -0
- package/dist/cli/keystore.js +108 -0
- package/dist/cli/model.d.ts +11 -0
- package/dist/cli/model.js +19 -0
- package/dist/cli/plugincli.d.ts +2 -0
- package/dist/cli/plugincli.js +176 -0
- package/dist/cli/schedulecli.d.ts +2 -0
- package/dist/cli/schedulecli.js +66 -0
- package/dist/cli/state.d.ts +11 -0
- package/dist/cli/state.js +19 -0
- package/dist/cli/tui/ansi.d.ts +38 -0
- package/dist/cli/tui/ansi.js +142 -0
- package/dist/cli/tui/app.d.ts +328 -0
- package/dist/cli/tui/app.js +1358 -0
- package/dist/cli/tui/blocks.d.ts +151 -0
- package/dist/cli/tui/blocks.js +128 -0
- package/dist/cli/tui/brand.d.ts +9 -0
- package/dist/cli/tui/brand.js +16 -0
- package/dist/cli/tui/canvas.d.ts +92 -0
- package/dist/cli/tui/canvas.js +199 -0
- package/dist/cli/tui/cards.d.ts +44 -0
- package/dist/cli/tui/cards.js +215 -0
- package/dist/cli/tui/clipboard.d.ts +5 -0
- package/dist/cli/tui/clipboard.js +29 -0
- package/dist/cli/tui/commands.d.ts +10 -0
- package/dist/cli/tui/commands.js +650 -0
- package/dist/cli/tui/complete.d.ts +39 -0
- package/dist/cli/tui/complete.js +113 -0
- package/dist/cli/tui/custom.d.ts +9 -0
- package/dist/cli/tui/custom.js +34 -0
- package/dist/cli/tui/diffview.d.ts +25 -0
- package/dist/cli/tui/diffview.js +130 -0
- package/dist/cli/tui/editor.d.ts +60 -0
- package/dist/cli/tui/editor.js +230 -0
- package/dist/cli/tui/full.d.ts +86 -0
- package/dist/cli/tui/full.js +630 -0
- package/dist/cli/tui/keys.d.ts +31 -0
- package/dist/cli/tui/keys.js +134 -0
- package/dist/cli/tui/markdown.d.ts +37 -0
- package/dist/cli/tui/markdown.js +155 -0
- package/dist/cli/tui/mentions.d.ts +15 -0
- package/dist/cli/tui/mentions.js +48 -0
- package/dist/cli/tui/modal.d.ts +26 -0
- package/dist/cli/tui/modal.js +107 -0
- package/dist/cli/tui/overlays.d.ts +98 -0
- package/dist/cli/tui/overlays.js +237 -0
- package/dist/cli/tui/pager.d.ts +15 -0
- package/dist/cli/tui/pager.js +66 -0
- package/dist/cli/tui/pricing.d.ts +9 -0
- package/dist/cli/tui/pricing.js +23 -0
- package/dist/cli/tui/rich.d.ts +16 -0
- package/dist/cli/tui/rich.js +72 -0
- package/dist/cli/tui/screen.d.ts +30 -0
- package/dist/cli/tui/screen.js +62 -0
- package/dist/cli/tui/session.d.ts +77 -0
- package/dist/cli/tui/session.js +103 -0
- package/dist/cli/tui/sidebar.d.ts +84 -0
- package/dist/cli/tui/sidebar.js +114 -0
- package/dist/cli/tui/text.d.ts +15 -0
- package/dist/cli/tui/text.js +94 -0
- package/dist/cli/tui/theme.d.ts +59 -0
- package/dist/cli/tui/theme.js +148 -0
- package/dist/cli/tui/themes.d.ts +56 -0
- package/dist/cli/tui/themes.js +80 -0
- package/dist/cli/tui/tokens.d.ts +39 -0
- package/dist/cli/tui/tokens.js +71 -0
- package/dist/cli/tui/view.d.ts +60 -0
- package/dist/cli/tui/view.js +178 -0
- package/dist/cli/ui.d.ts +34 -0
- package/dist/cli/ui.js +224 -0
- package/dist/src/acp.d.ts +28 -0
- package/dist/src/acp.js +111 -0
- package/dist/src/agents.d.ts +23 -0
- package/dist/src/agents.js +71 -0
- package/dist/src/anthropic.d.ts +30 -0
- package/dist/src/anthropic.js +91 -0
- package/dist/src/approver.d.ts +15 -0
- package/dist/src/approver.js +40 -0
- package/dist/src/bench.d.ts +67 -0
- package/dist/src/bench.js +201 -0
- package/dist/src/bundle.d.ts +36 -0
- package/dist/src/bundle.js +55 -0
- package/dist/src/chaos.d.ts +15 -0
- package/dist/src/chaos.js +26 -0
- package/dist/src/code.d.ts +187 -0
- package/dist/src/code.js +590 -0
- package/dist/src/codeindex.d.ts +65 -0
- package/dist/src/codeindex.js +300 -0
- package/dist/src/deep.d.ts +62 -0
- package/dist/src/deep.js +177 -0
- package/dist/src/diff.d.ts +8 -0
- package/dist/src/diff.js +74 -0
- package/dist/src/dynamic.d.ts +42 -0
- package/dist/src/dynamic.js +133 -0
- package/dist/src/embed.d.ts +8 -0
- package/dist/src/embed.js +25 -0
- package/dist/src/evalcases.d.ts +11 -0
- package/dist/src/evalcases.js +196 -0
- package/dist/src/evals.d.ts +197 -0
- package/dist/src/evals.js +340 -0
- package/dist/src/gitflow.d.ts +27 -0
- package/dist/src/gitflow.js +56 -0
- package/dist/src/grade.d.ts +42 -0
- package/dist/src/grade.js +96 -0
- package/dist/src/guidance.d.ts +15 -0
- package/dist/src/guidance.js +68 -0
- package/dist/src/hardcases.d.ts +2 -0
- package/dist/src/hardcases.js +94 -0
- package/dist/src/harness.d.ts +411 -0
- package/dist/src/harness.js +725 -0
- package/dist/src/hooks.d.ts +44 -0
- package/dist/src/hooks.js +116 -0
- package/dist/src/images.d.ts +13 -0
- package/dist/src/images.js +27 -0
- package/dist/src/index.d.ts +57 -0
- package/dist/src/index.js +57 -0
- package/dist/src/ledger.d.ts +58 -0
- package/dist/src/ledger.js +78 -0
- package/dist/src/live.d.ts +16 -0
- package/dist/src/live.js +80 -0
- package/dist/src/lsp.d.ts +31 -0
- package/dist/src/lsp.js +147 -0
- package/dist/src/memory.d.ts +128 -0
- package/dist/src/memory.js +390 -0
- package/dist/src/metrics.d.ts +3 -0
- package/dist/src/metrics.js +42 -0
- package/dist/src/mining.d.ts +49 -0
- package/dist/src/mining.js +126 -0
- package/dist/src/openai.d.ts +50 -0
- package/dist/src/openai.js +307 -0
- package/dist/src/parallel.d.ts +66 -0
- package/dist/src/parallel.js +135 -0
- package/dist/src/patch.d.ts +45 -0
- package/dist/src/patch.js +219 -0
- package/dist/src/permissions.d.ts +46 -0
- package/dist/src/permissions.js +245 -0
- package/dist/src/plugins.d.ts +148 -0
- package/dist/src/plugins.js +225 -0
- package/dist/src/pluginscan.d.ts +8 -0
- package/dist/src/pluginscan.js +20 -0
- package/dist/src/procs.d.ts +41 -0
- package/dist/src/procs.js +94 -0
- package/dist/src/profiles.d.ts +36 -0
- package/dist/src/profiles.js +61 -0
- package/dist/src/progress.d.ts +29 -0
- package/dist/src/progress.js +75 -0
- package/dist/src/prompts.d.ts +10 -0
- package/dist/src/prompts.js +28 -0
- package/dist/src/ratelimit.d.ts +34 -0
- package/dist/src/ratelimit.js +116 -0
- package/dist/src/replay.d.ts +25 -0
- package/dist/src/replay.js +99 -0
- package/dist/src/review.d.ts +20 -0
- package/dist/src/review.js +49 -0
- package/dist/src/route.d.ts +9 -0
- package/dist/src/route.js +30 -0
- package/dist/src/router.d.ts +61 -0
- package/dist/src/router.js +126 -0
- package/dist/src/sandbox.d.ts +13 -0
- package/dist/src/sandbox.js +68 -0
- package/dist/src/schedule.d.ts +20 -0
- package/dist/src/schedule.js +59 -0
- package/dist/src/schema.d.ts +13 -0
- package/dist/src/schema.js +36 -0
- package/dist/src/secrets.d.ts +10 -0
- package/dist/src/secrets.js +41 -0
- package/dist/src/shell.d.ts +34 -0
- package/dist/src/shell.js +95 -0
- package/dist/src/skills.d.ts +20 -0
- package/dist/src/skills.js +112 -0
- package/dist/src/snapshot.d.ts +20 -0
- package/dist/src/snapshot.js +93 -0
- package/dist/src/stats.d.ts +24 -0
- package/dist/src/stats.js +62 -0
- package/dist/src/strategies.d.ts +16 -0
- package/dist/src/strategies.js +35 -0
- package/dist/src/suites.d.ts +30 -0
- package/dist/src/suites.js +112 -0
- package/dist/src/trace.d.ts +63 -0
- package/dist/src/trace.js +153 -0
- package/dist/src/tune.d.ts +38 -0
- package/dist/src/tune.js +49 -0
- package/dist/src/web.d.ts +19 -0
- package/dist/src/web.js +56 -0
- package/dist/src/worktree.d.ts +24 -0
- package/dist/src/worktree.js +85 -0
- package/docs/agent-loop.md +222 -0
- package/docs/architecture.md +228 -0
- package/docs/bench.md +292 -0
- package/docs/budgets.md +185 -0
- package/docs/build-your-own-harness.md +424 -0
- package/docs/cookbook.md +375 -0
- package/docs/development.md +150 -0
- package/docs/evaluation.md +450 -0
- package/docs/features.md +1320 -0
- package/docs/getting-started.md +205 -0
- package/docs/headless.md +188 -0
- package/docs/images/approval.png +0 -0
- package/docs/images/chat.png +0 -0
- package/docs/images/daylight.png +0 -0
- package/docs/images/loop.png +0 -0
- package/docs/images/palette.png +0 -0
- package/docs/images/running.png +0 -0
- package/docs/images/subagent-done.png +0 -0
- package/docs/images/subagent-running.png +0 -0
- package/docs/images/welcome.png +0 -0
- package/docs/index.md +25 -0
- package/docs/library.md +267 -0
- package/docs/plugins.md +691 -0
- package/docs/providers.md +175 -0
- package/docs/safety.md +250 -0
- package/docs/usage.md +348 -0
- package/docs/why.md +155 -0
- package/docs/writing-guide.md +39 -0
- package/package.json +66 -4
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type Theme = {
|
|
2
|
+
name: string;
|
|
3
|
+
label: string;
|
|
4
|
+
dark: boolean;
|
|
5
|
+
bg: number;
|
|
6
|
+
surface: number;
|
|
7
|
+
surface2: number;
|
|
8
|
+
border: number;
|
|
9
|
+
borderStrong: number;
|
|
10
|
+
text: number;
|
|
11
|
+
dim: number;
|
|
12
|
+
faint: number;
|
|
13
|
+
accent: number;
|
|
14
|
+
accent2: number;
|
|
15
|
+
onAccent: number;
|
|
16
|
+
ok: number;
|
|
17
|
+
warn: number;
|
|
18
|
+
err: number;
|
|
19
|
+
info: number;
|
|
20
|
+
addBg: number;
|
|
21
|
+
addFg: number;
|
|
22
|
+
addStrong: number;
|
|
23
|
+
delBg: number;
|
|
24
|
+
delFg: number;
|
|
25
|
+
delStrong: number;
|
|
26
|
+
selection: number;
|
|
27
|
+
inlineCodeBg: number;
|
|
28
|
+
inlineCode: number;
|
|
29
|
+
link: number;
|
|
30
|
+
syn: {
|
|
31
|
+
keyword: number;
|
|
32
|
+
string: number;
|
|
33
|
+
number: number;
|
|
34
|
+
comment: number;
|
|
35
|
+
func: number;
|
|
36
|
+
type: number;
|
|
37
|
+
punct: number;
|
|
38
|
+
plain: number;
|
|
39
|
+
};
|
|
40
|
+
gradient: number[];
|
|
41
|
+
tool: {
|
|
42
|
+
read: number;
|
|
43
|
+
edit: number;
|
|
44
|
+
shell: number;
|
|
45
|
+
web: number;
|
|
46
|
+
memory: number;
|
|
47
|
+
plan: number;
|
|
48
|
+
explore: number;
|
|
49
|
+
generic: number;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
export declare const THEMES: Record<string, Theme>;
|
|
53
|
+
export declare const themeNames: () => string[];
|
|
54
|
+
export declare const getTheme: (name?: string) => Theme;
|
|
55
|
+
export declare const luminance: (c: number) => number;
|
|
56
|
+
export declare const contrast: (a: number, b: number) => number;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Semantic colour themes for the full-screen UI. Every colour is 0xRRGGBB (or -1 = the terminal's own colour, for the "terminal" theme).
|
|
2
|
+
import { DEFAULT, mix } from "./ansi.js";
|
|
3
|
+
const T = (t) => ({
|
|
4
|
+
borderStrong: mix(t.border, t.text, 0.25), selection: mix(t.surface2, t.accent, 0.45), inlineCodeBg: t.surface2, inlineCode: t.accent2, link: t.info,
|
|
5
|
+
tool: { read: t.info, edit: t.warn, shell: t.accent, web: t.accent2, memory: t.ok, plan: 0xff79c6, explore: 0xff9e64, generic: t.dim }, ...t,
|
|
6
|
+
});
|
|
7
|
+
export const THEMES = {
|
|
8
|
+
aurora: T({
|
|
9
|
+
name: "aurora", label: "Aurora (default)", dark: true,
|
|
10
|
+
bg: 0x0d1017, surface: 0x131824, surface2: 0x1a2133, border: 0x28324a,
|
|
11
|
+
text: 0xdbe2ef, dim: 0x93a0b8, faint: 0x5f6c86, accent: 0x8b7bff, accent2: 0x35d0e6, onAccent: 0x0d1017,
|
|
12
|
+
ok: 0x45d9a0, warn: 0xf6b04c, err: 0xff6b7a, info: 0x62aaff,
|
|
13
|
+
addBg: 0x0f2c21, addFg: 0x7ceab5, addStrong: 0x185a3f, delBg: 0x34161f, delFg: 0xff9aa6, delStrong: 0x6b2433,
|
|
14
|
+
syn: { keyword: 0xc4a7ff, string: 0xa6e07a, number: 0xffa56b, comment: 0x62708c, func: 0x7db4ff, type: 0xffd479, punct: 0x8fe1f2, plain: 0xdbe2ef },
|
|
15
|
+
gradient: [0x8b7bff, 0x35b6ff, 0x35e6b0],
|
|
16
|
+
}),
|
|
17
|
+
"tokyo-night": T({
|
|
18
|
+
name: "tokyo-night", label: "Tokyo Night", dark: true,
|
|
19
|
+
bg: 0x16161e, surface: 0x1a1b26, surface2: 0x24283b, border: 0x3b4261,
|
|
20
|
+
text: 0xc0caf5, dim: 0x9aa5ce, faint: 0x6b749b, accent: 0x7aa2f7, accent2: 0xbb9af7, onAccent: 0x16161e,
|
|
21
|
+
ok: 0x9ece6a, warn: 0xe0af68, err: 0xf7768e, info: 0x7dcfff,
|
|
22
|
+
addBg: 0x1c2f26, addFg: 0x9ece6a, addStrong: 0x2b5a3a, delBg: 0x37202b, delFg: 0xf7768e, delStrong: 0x6a2c3d,
|
|
23
|
+
syn: { keyword: 0xbb9af7, string: 0x9ece6a, number: 0xff9e64, comment: 0x6b749b, func: 0x7aa2f7, type: 0x2ac3de, punct: 0x89ddff, plain: 0xc0caf5 },
|
|
24
|
+
gradient: [0x7aa2f7, 0xbb9af7, 0x7dcfff],
|
|
25
|
+
}),
|
|
26
|
+
catppuccin: T({
|
|
27
|
+
name: "catppuccin", label: "Catppuccin Mocha", dark: true,
|
|
28
|
+
bg: 0x11111b, surface: 0x181825, surface2: 0x313244, border: 0x45475a,
|
|
29
|
+
text: 0xcdd6f4, dim: 0xa6adc8, faint: 0x6c7086, accent: 0xcba6f7, accent2: 0x89dceb, onAccent: 0x11111b,
|
|
30
|
+
ok: 0xa6e3a1, warn: 0xf9e2af, err: 0xf38ba8, info: 0x89b4fa,
|
|
31
|
+
addBg: 0x1d2e25, addFg: 0xa6e3a1, addStrong: 0x2f5a3e, delBg: 0x36212b, delFg: 0xf38ba8, delStrong: 0x6a3040,
|
|
32
|
+
syn: { keyword: 0xcba6f7, string: 0xa6e3a1, number: 0xfab387, comment: 0x6c7086, func: 0x89b4fa, type: 0xf9e2af, punct: 0x94e2d5, plain: 0xcdd6f4 },
|
|
33
|
+
gradient: [0xcba6f7, 0x89b4fa, 0x94e2d5],
|
|
34
|
+
}),
|
|
35
|
+
gruvbox: T({
|
|
36
|
+
name: "gruvbox", label: "Gruvbox Dark", dark: true,
|
|
37
|
+
bg: 0x1d2021, surface: 0x282828, surface2: 0x3c3836, border: 0x504945,
|
|
38
|
+
text: 0xebdbb2, dim: 0xbdae93, faint: 0x7c6f64, accent: 0xfabd2f, accent2: 0x8ec07c, onAccent: 0x1d2021,
|
|
39
|
+
ok: 0xb8bb26, warn: 0xfe8019, err: 0xfb4934, info: 0x83a598,
|
|
40
|
+
addBg: 0x2b3017, addFg: 0xb8bb26, addStrong: 0x4a5a1c, delBg: 0x3c1e1b, delFg: 0xfb7a68, delStrong: 0x6e2b25,
|
|
41
|
+
syn: { keyword: 0xfb5c4a, string: 0xb8bb26, number: 0xd3869b, comment: 0x928374, func: 0xfabd2f, type: 0x83a598, punct: 0x8ec07c, plain: 0xebdbb2 },
|
|
42
|
+
gradient: [0xfabd2f, 0xfe8019, 0xfb4934],
|
|
43
|
+
}),
|
|
44
|
+
nord: T({
|
|
45
|
+
name: "nord", label: "Nord", dark: true,
|
|
46
|
+
bg: 0x242933, surface: 0x2e3440, surface2: 0x3b4252, border: 0x4c566a,
|
|
47
|
+
text: 0xe5e9f0, dim: 0xa7b1c2, faint: 0x7d889c, accent: 0x88c0d0, accent2: 0xb48ead, onAccent: 0x242933,
|
|
48
|
+
ok: 0xa3be8c, warn: 0xebcb8b, err: 0xd9707a, info: 0x81a1c1,
|
|
49
|
+
addBg: 0x27382f, addFg: 0xa3be8c, addStrong: 0x3c5a45, delBg: 0x3d2c33, delFg: 0xe08a93, delStrong: 0x6b3d47,
|
|
50
|
+
syn: { keyword: 0x81a1c1, string: 0xa3be8c, number: 0xc29bbb, comment: 0x7d889c, func: 0x88c0d0, type: 0xebcb8b, punct: 0x8fbcbb, plain: 0xe5e9f0 },
|
|
51
|
+
gradient: [0x88c0d0, 0x81a1c1, 0xb48ead],
|
|
52
|
+
}),
|
|
53
|
+
daylight: T({
|
|
54
|
+
name: "daylight", label: "Daylight (light)", dark: false,
|
|
55
|
+
bg: 0xfbfbfd, surface: 0xf1f3f9, surface2: 0xe6eaf4, border: 0xcdd3e2,
|
|
56
|
+
text: 0x1f2430, dim: 0x4f5970, faint: 0x78819a, accent: 0x5b4bdb, accent2: 0x0b7d92, onAccent: 0xffffff,
|
|
57
|
+
ok: 0x1a7f4b, warn: 0x9a5700, err: 0xc4283c, info: 0x1d62c9,
|
|
58
|
+
addBg: 0xdcf3e5, addFg: 0x14663a, addStrong: 0xb3e3c6, delBg: 0xfde1e5, delFg: 0xa8202f, delStrong: 0xf6b5bd,
|
|
59
|
+
syn: { keyword: 0x7a3ec7, string: 0x2a7a2a, number: 0xb4530a, comment: 0x6f7993, func: 0x1d5fc4, type: 0x8a6100, punct: 0x096f82, plain: 0x1f2430 },
|
|
60
|
+
gradient: [0x5b4bdb, 0x1d62c9, 0x0b7d92],
|
|
61
|
+
}),
|
|
62
|
+
/** Uses the terminal's own background and palette: no painted surfaces, structure comes from borders only. */
|
|
63
|
+
terminal: T({
|
|
64
|
+
name: "terminal", label: "Terminal (transparent)", dark: true,
|
|
65
|
+
bg: DEFAULT, surface: DEFAULT, surface2: DEFAULT, border: 0x1000000 + 8,
|
|
66
|
+
text: DEFAULT, dim: 0x1000000 + 7, faint: 0x1000000 + 8, accent: 0x1000000 + 13, accent2: 0x1000000 + 14, onAccent: 0x1000000 + 0,
|
|
67
|
+
ok: 0x1000000 + 10, warn: 0x1000000 + 11, err: 0x1000000 + 9, info: 0x1000000 + 12,
|
|
68
|
+
addBg: DEFAULT, addFg: 0x1000000 + 10, addStrong: DEFAULT, delBg: DEFAULT, delFg: 0x1000000 + 9, delStrong: DEFAULT,
|
|
69
|
+
syn: { keyword: 0x1000000 + 13, string: 0x1000000 + 10, number: 0x1000000 + 11, comment: 0x1000000 + 8, func: 0x1000000 + 12, type: 0x1000000 + 14, punct: 0x1000000 + 6, plain: DEFAULT },
|
|
70
|
+
gradient: [0x1000000 + 13, 0x1000000 + 12, 0x1000000 + 14],
|
|
71
|
+
tool: { read: 0x1000000 + 12, edit: 0x1000000 + 11, shell: 0x1000000 + 13, web: 0x1000000 + 14, memory: 0x1000000 + 10, plan: 0x1000000 + 13, explore: 0x1000000 + 9, generic: 0x1000000 + 7 },
|
|
72
|
+
selection: 0x1000000 + 4,
|
|
73
|
+
}),
|
|
74
|
+
};
|
|
75
|
+
export const themeNames = () => Object.keys(THEMES);
|
|
76
|
+
export const getTheme = (name) => THEMES[name ?? ""] ?? THEMES.aurora;
|
|
77
|
+
/** WCAG relative luminance / contrast ratio, used by tests to keep every theme readable. */
|
|
78
|
+
const lin = (v) => { v /= 255; return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; };
|
|
79
|
+
export const luminance = (c) => 0.2126 * lin((c >> 16) & 255) + 0.7152 * lin((c >> 8) & 255) + 0.0722 * lin(c & 255);
|
|
80
|
+
export const contrast = (a, b) => { const [x, y] = [luminance(a), luminance(b)].sort((p, q) => q - p); return (x + 0.05) / (y + 0.05); };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Message } from "../../src/index.ts";
|
|
2
|
+
import { type RGB } from "./theme.ts";
|
|
3
|
+
export type ContextMap = {
|
|
4
|
+
parts: {
|
|
5
|
+
key: "system" | "you" | "assistant" | "tools" | "defs";
|
|
6
|
+
label: string;
|
|
7
|
+
tokens: number;
|
|
8
|
+
color: RGB;
|
|
9
|
+
}[];
|
|
10
|
+
total: number;
|
|
11
|
+
biggest?: {
|
|
12
|
+
name: string;
|
|
13
|
+
tokens: number;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Attribute the conversation's size to who produced it. Sizes are character-based estimates; when the API told us the real prompt size
|
|
18
|
+
* (`promptTokens`) the estimates are scaled so the total matches it.
|
|
19
|
+
*/
|
|
20
|
+
export declare function contextMap(messages: Message[], promptTokens?: number, toolDefChars?: number): ContextMap;
|
|
21
|
+
export type ReportInput = {
|
|
22
|
+
model: string;
|
|
23
|
+
messages: Message[];
|
|
24
|
+
usage: {
|
|
25
|
+
prompt: number;
|
|
26
|
+
completion: number;
|
|
27
|
+
calls: number;
|
|
28
|
+
cached?: number;
|
|
29
|
+
reasoning?: number;
|
|
30
|
+
};
|
|
31
|
+
series: number[];
|
|
32
|
+
lastPrompt: number;
|
|
33
|
+
turnTokens: number;
|
|
34
|
+
turnBudget: number;
|
|
35
|
+
width: number; /** size of the tool definitions sent with every request */
|
|
36
|
+
toolDefChars?: number;
|
|
37
|
+
};
|
|
38
|
+
/** The /tokens panel. */
|
|
39
|
+
export declare function tokenReport(r: ReportInput): string[];
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { contextWindow, estimateCost, fmtCost, fmtTokens, priceOf } from "./pricing.js";
|
|
2
|
+
import { bold, dimmed, fg, gauge, gaugeColor, palette, sparkline, stackedBar } from "./theme.js";
|
|
3
|
+
const approx = (chars) => Math.ceil(chars / 3.6);
|
|
4
|
+
/**
|
|
5
|
+
* Attribute the conversation's size to who produced it. Sizes are character-based estimates; when the API told us the real prompt size
|
|
6
|
+
* (`promptTokens`) the estimates are scaled so the total matches it.
|
|
7
|
+
*/
|
|
8
|
+
export function contextMap(messages, promptTokens = 0, toolDefChars = 0) {
|
|
9
|
+
const chars = { system: 0, you: 0, assistant: 0, tools: 0 };
|
|
10
|
+
const names = new Map();
|
|
11
|
+
let biggest;
|
|
12
|
+
for (const m of messages) {
|
|
13
|
+
const calls = (m.tool_calls ?? []).reduce((n, c) => n + c.function.arguments.length + c.function.name.length, 0);
|
|
14
|
+
if (m.role === "system")
|
|
15
|
+
chars.system += m.content?.length ?? 0;
|
|
16
|
+
else if (m.role === "user")
|
|
17
|
+
chars.you += m.content?.length ?? 0;
|
|
18
|
+
else if (m.role === "assistant") {
|
|
19
|
+
chars.assistant += (m.content?.length ?? 0) + calls;
|
|
20
|
+
for (const c of m.tool_calls ?? [])
|
|
21
|
+
names.set(c.id, c.function.name);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const n = m.content?.length ?? 0;
|
|
25
|
+
chars.tools += n;
|
|
26
|
+
if (!biggest || n > biggest.chars)
|
|
27
|
+
biggest = { name: names.get(m.tool_call_id ?? "") ?? "tool", chars: n };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const est = { system: approx(chars.system), you: approx(chars.you), assistant: approx(chars.assistant), tools: approx(chars.tools), defs: approx(toolDefChars) };
|
|
31
|
+
const sum = est.system + est.you + est.assistant + est.tools + est.defs, k = promptTokens && sum ? promptTokens / sum : 1;
|
|
32
|
+
const parts = [
|
|
33
|
+
{ key: "tools", label: "tool output", tokens: Math.round(est.tools * k), color: palette.read },
|
|
34
|
+
{ key: "assistant", label: "assistant", tokens: Math.round(est.assistant * k), color: palette.edit },
|
|
35
|
+
{ key: "you", label: "you", tokens: Math.round(est.you * k), color: palette.memory },
|
|
36
|
+
{ key: "system", label: "system", tokens: Math.round(est.system * k), color: palette.shell },
|
|
37
|
+
{ key: "defs", label: "tool defs", tokens: Math.round(est.defs * k), color: palette.web }, // the JSON schemas of every tool ride along on each request
|
|
38
|
+
];
|
|
39
|
+
return { parts, total: parts.reduce((n, p) => n + p.tokens, 0), biggest: biggest ? { name: biggest.name, tokens: Math.round(approx(biggest.chars) * k) } : undefined };
|
|
40
|
+
}
|
|
41
|
+
/** The /tokens panel. */
|
|
42
|
+
export function tokenReport(r) {
|
|
43
|
+
const win = contextWindow(r.model), used = r.lastPrompt || contextMap(r.messages, 0, r.toolDefChars).total, pct = Math.min(100, Math.round((used / win) * 100));
|
|
44
|
+
const map = contextMap(r.messages, r.lastPrompt, r.toolDefChars), barW = Math.max(16, Math.min(48, r.width - 12));
|
|
45
|
+
const sb = stackedBar(map.parts.map((p) => ({ label: p.label, value: p.tokens, color: p.color })), barW);
|
|
46
|
+
const cached = r.usage.cached ?? 0, reasoning = r.usage.reasoning ?? 0;
|
|
47
|
+
const cost = estimateCost(r.model, r.usage.prompt, r.usage.completion, cached), p = priceOf(r.model);
|
|
48
|
+
const perReq = r.usage.calls ? Math.round(r.usage.prompt / r.usage.calls) : 0;
|
|
49
|
+
const lines = [
|
|
50
|
+
"", ` ${bold("Tokens")}`,
|
|
51
|
+
` ${dimmed("context window")} ${gauge(pct, 20)} ${fg(gaugeColor(pct), pct + "%")} ${dimmed(`${fmtTokens(used)} of ${fmtTokens(win)}`)}`,
|
|
52
|
+
` ${dimmed("what fills it ")} ${sb.bar}`, ` ${sb.legend.join(" ")}`,
|
|
53
|
+
];
|
|
54
|
+
if (map.biggest && map.biggest.tokens > 800)
|
|
55
|
+
lines.push(` ${dimmed("biggest item ")} ${fg(palette.warn, "◆")} one ${map.biggest.name} result is ~${fmtTokens(map.biggest.tokens)} tokens`);
|
|
56
|
+
if (r.series.length)
|
|
57
|
+
lines.push(` ${dimmed("prompt size/req")} ${sparkline(r.series, 32)} ${dimmed(`${r.series.length} requests, latest ${fmtTokens(r.series.at(-1))}`)}`);
|
|
58
|
+
if (cached)
|
|
59
|
+
lines.push(` ${dimmed("prompt cache ")} ${fg(palette.ok, Math.round((cached / Math.max(1, r.usage.prompt)) * 100) + "%")} of input served from cache ${dimmed(`(${fmtTokens(cached)} tokens at ~10% price)`)}`);
|
|
60
|
+
if (reasoning)
|
|
61
|
+
lines.push(` ${dimmed("reasoning ")} ${fmtTokens(reasoning)} hidden reasoning tokens ${dimmed(`(${Math.round((reasoning / Math.max(1, r.usage.completion)) * 100)}% of output)`)}`);
|
|
62
|
+
lines.push(` ${dimmed("this chat ")} ${fmtTokens(r.usage.prompt)} in · ${fmtTokens(r.usage.completion)} out · ${r.usage.calls} request${r.usage.calls === 1 ? "" : "s"}${perReq ? dimmed(` (avg ${fmtTokens(perReq)} in)`) : ""}`, ` ${dimmed("est. cost ")} ${cost === null ? dimmed("unknown for this model") : bold(fmtCost(cost))}${p ? dimmed(` at $${p[1]}/$${p[2]} per M tokens${cached ? ", cached input at 10%" : ""}`) : ""}`);
|
|
63
|
+
if (r.turnBudget && r.turnTokens) {
|
|
64
|
+
const b = Math.min(100, Math.round((r.turnTokens / r.turnBudget) * 100));
|
|
65
|
+
lines.push(` ${dimmed("turn budget ")} ${gauge(b, 20)} ${fmtTokens(r.turnTokens)} of ${fmtTokens(r.turnBudget)}`);
|
|
66
|
+
}
|
|
67
|
+
const advice = pct >= 85 ? `${fg(palette.err, "!")} nearly full: run /compact now, or /clear to start fresh` : pct >= 60 ? `${fg(palette.warn, "↯")} getting full: /compact will summarise and free most of it` : map.parts[0].key === "tools" && map.parts[0].tokens > used * 0.5 ? `${fg(palette.read, "i")} mostly tool output: ask for narrower reads (line ranges, grep) to keep it lean` : "";
|
|
68
|
+
if (advice)
|
|
69
|
+
lines.push("", ` ${advice}`);
|
|
70
|
+
return lines;
|
|
71
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export declare const toolLabel: (name: string) => string;
|
|
2
|
+
/** Short, human description of a tool call's arguments. */
|
|
3
|
+
export declare function argSummary(name: string, raw: string): string;
|
|
4
|
+
export declare const toolCall: (name: string, raw: string) => string;
|
|
5
|
+
export declare const toolHeader: (name: string, raw: string, ok?: boolean | null, agent?: string) => string;
|
|
6
|
+
export declare const isFailure: (result: string) => boolean;
|
|
7
|
+
/** Result lines shown under a tool header (the "⎿" block). */
|
|
8
|
+
export declare function toolResult(name: string, raw: string, result: string, width: number): string[];
|
|
9
|
+
/** Unified diff with line numbers and +/- colouring, like an editor's change view. */
|
|
10
|
+
export declare function renderDiff(diff: string, width: number, maxLines?: number): string[];
|
|
11
|
+
export declare const editSummary: (e: {
|
|
12
|
+
created: boolean;
|
|
13
|
+
added: number;
|
|
14
|
+
removed: number;
|
|
15
|
+
}) => string;
|
|
16
|
+
export declare function userMessage(text: string, width: number, note?: string): string[];
|
|
17
|
+
/** One assistant block: first block of a message gets the "●" marker; later blocks are separated by a blank line. */
|
|
18
|
+
export declare function assistantBlock(md: string, width: number, first: boolean): string[];
|
|
19
|
+
export declare function todoList(todos: {
|
|
20
|
+
content: string;
|
|
21
|
+
status: string;
|
|
22
|
+
}[], width: number): string[];
|
|
23
|
+
export type Option = {
|
|
24
|
+
label: string;
|
|
25
|
+
hint?: string;
|
|
26
|
+
};
|
|
27
|
+
/** Numbered, arrow-selectable option list used by approval prompts and pickers. */
|
|
28
|
+
export declare function optionList(opts: Option[], sel: number): string[];
|
|
29
|
+
export declare function framed(title: string, body: string[], width: number, paint?: (s: string) => string): string[];
|
|
30
|
+
export type FooterInfo = {
|
|
31
|
+
mode: string;
|
|
32
|
+
model: string;
|
|
33
|
+
provider?: string;
|
|
34
|
+
ctxPct: number | null;
|
|
35
|
+
cost: string;
|
|
36
|
+
branch?: string;
|
|
37
|
+
hint?: string; /** no left-hand hints at all (a prompt owns the screen) */
|
|
38
|
+
quiet?: boolean; /** transient message, shown in place of the hint without hiding the mode */
|
|
39
|
+
notice?: string;
|
|
40
|
+
running?: boolean;
|
|
41
|
+
queued?: number;
|
|
42
|
+
};
|
|
43
|
+
export declare function footer(f: FooterInfo, width: number): string;
|
|
44
|
+
export declare const COMMAND_HELP_WIDTH = 14;
|
|
45
|
+
export declare function helpLines(cmds: {
|
|
46
|
+
name: string;
|
|
47
|
+
help: string;
|
|
48
|
+
}[], width: number): string[];
|
|
49
|
+
export declare function welcome(o: {
|
|
50
|
+
version: string;
|
|
51
|
+
model: string;
|
|
52
|
+
provider?: string;
|
|
53
|
+
cwd: string;
|
|
54
|
+
branch?: string;
|
|
55
|
+
notes: number;
|
|
56
|
+
verify?: string;
|
|
57
|
+
mode: string;
|
|
58
|
+
resumed?: string;
|
|
59
|
+
tip?: string;
|
|
60
|
+
}, width: number): string[];
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// Pure rendering helpers for the chat UI: every function returns terminal lines and never touches the terminal.
|
|
2
|
+
import { bold, cyan, dim, gray, green, red, yellow } from "../ui.js";
|
|
3
|
+
import { renderMarkdown } from "./markdown.js";
|
|
4
|
+
import { brandOf, fg, gauge, gaugeColor, logo, toolColor } from "./theme.js";
|
|
5
|
+
import { cellWidth, clipAnsi, clipWidth, strip, wrapAnsi } from "./text.js";
|
|
6
|
+
const ONE = (s, n) => { const l = s.replace(/\s+/g, " ").trim(); return cellWidth(l) > n ? clipWidth(l, n - 1) + "…" : l; };
|
|
7
|
+
const NAMES = { read_file: "Read", list_files: "List", grep: "Search", write_file: "Write", edit_file: "Update", apply_patch: "Patch", sh: "Bash", memory_save: "Memory", memory_search: "Memory",
|
|
8
|
+
memory_forget: "Memory", web_search: "WebSearch", fetch_url: "Fetch", task: "Explore", todo_write: "Todos", exit_plan_mode: "Plan", create_tool: "CreateTool", finish: "Finish" };
|
|
9
|
+
export const toolLabel = (name) => NAMES[name] ?? name;
|
|
10
|
+
/** Short, human description of a tool call's arguments. */
|
|
11
|
+
export function argSummary(name, raw) {
|
|
12
|
+
let a;
|
|
13
|
+
try {
|
|
14
|
+
a = JSON.parse(raw);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return ONE(raw, 70);
|
|
18
|
+
}
|
|
19
|
+
switch (name) {
|
|
20
|
+
case "sh": return ONE(a.cmd ?? "", 90);
|
|
21
|
+
case "read_file": return a.path + (a.start ? `:${a.start}${a.end ? "-" + a.end : ""}` : "");
|
|
22
|
+
case "grep": return `"${ONE(a.pattern ?? "", 40)}"${a.path ? " in " + a.path : ""}`;
|
|
23
|
+
case "list_files": return a.path ?? ".";
|
|
24
|
+
case "task": return ONE(a.description ?? "", 70);
|
|
25
|
+
case "apply_patch": {
|
|
26
|
+
const n = (String(a.patch ?? "").match(/^(?:\*\*\* (?:Update|Add|Delete) File:|\+\+\+ )/gm) ?? []).length;
|
|
27
|
+
return `${n || "?"} file${n === 1 ? "" : "s"}`;
|
|
28
|
+
}
|
|
29
|
+
case "web_search": return ONE(a.query ?? "", 70);
|
|
30
|
+
case "fetch_url": return ONE(a.url ?? "", 80);
|
|
31
|
+
case "memory_save": return a.name ?? "";
|
|
32
|
+
case "todo_write": return `${a.todos?.length ?? 0} items`;
|
|
33
|
+
case "exit_plan_mode": return "";
|
|
34
|
+
}
|
|
35
|
+
const named = a.path ?? a.name ?? a.query ?? a.description;
|
|
36
|
+
if (typeof named === "string")
|
|
37
|
+
return ONE(named, 80);
|
|
38
|
+
const entries = Object.entries(a ?? {}); // unknown tool (e.g. from MCP): show its arguments compactly
|
|
39
|
+
if (entries.length === 1)
|
|
40
|
+
return ONE(typeof entries[0][1] === "string" ? entries[0][1] : JSON.stringify(entries[0][1]), 80);
|
|
41
|
+
return ONE(entries.map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`).join(", "), 80);
|
|
42
|
+
}
|
|
43
|
+
export const toolCall = (name, raw) => `${bold(toolLabel(name))}${dim("(")}${argSummary(name, raw)}${dim(")")}`;
|
|
44
|
+
export const toolHeader = (name, raw, ok = null, agent) => `${agent && agent !== "lead" ? dim(" │ ") : ""}${ok === false ? red("●") : fg(toolColor(name), "●")} ${toolCall(name, raw)}`; // colour says what kind of work it was; red says it failed
|
|
45
|
+
export const isFailure = (result) => /^(ERROR|DENIED|REJECTED)/.test(result) || /^exit=(?!0\b)/.test(result);
|
|
46
|
+
const more = (n, hint = true) => dim(`… +${n} more line${n > 1 ? "s" : ""}${hint ? " (ctrl+o to view all)" : ""}`);
|
|
47
|
+
const hang = (lines, first = " ⎿ ", rest = " ") => lines.map((l, i) => dim(i ? rest : first) + l);
|
|
48
|
+
/** Result lines shown under a tool header (the "⎿" block). */
|
|
49
|
+
export function toolResult(name, raw, result, width) {
|
|
50
|
+
const body = result.split("\n"), w = width - 5;
|
|
51
|
+
const lines = result.trim() ? body : [];
|
|
52
|
+
if (isFailure(result)) {
|
|
53
|
+
const shown = body.filter(Boolean).slice(0, 6).map((l) => red(ONE(l, w)));
|
|
54
|
+
return hang([...shown, ...(body.filter(Boolean).length > 6 ? [more(body.filter(Boolean).length - 6)] : [])]);
|
|
55
|
+
}
|
|
56
|
+
switch (name) {
|
|
57
|
+
case "read_file": return hang([dim(`Read ${lines.length} line${lines.length === 1 ? "" : "s"}`)]);
|
|
58
|
+
case "list_files": return hang([dim(`${lines.filter(Boolean).length} file${lines.length === 1 ? "" : "s"}`)]);
|
|
59
|
+
case "grep": {
|
|
60
|
+
if (/^no matches/.test(result))
|
|
61
|
+
return hang([dim("No matches")]);
|
|
62
|
+
const hits = lines.filter(Boolean), files = new Set(hits.map((l) => l.split(":")[0]));
|
|
63
|
+
return hang([dim(`${hits.length} match${hits.length === 1 ? "" : "es"} in ${files.size} file${files.size === 1 ? "" : "s"}`)]);
|
|
64
|
+
}
|
|
65
|
+
case "sh": {
|
|
66
|
+
const out = body.slice(1).filter((l, i, a) => l.trim() || i < a.length - 1), shown = out.slice(0, 8).map((l) => gray(ONE(l, w)));
|
|
67
|
+
return hang(out.length ? [...shown, ...(out.length > 8 ? [more(out.length - 8)] : [])] : [dim("(no output)")]);
|
|
68
|
+
}
|
|
69
|
+
case "write_file":
|
|
70
|
+
case "edit_file": return []; // the diff block is rendered from the edit event
|
|
71
|
+
case "memory_save":
|
|
72
|
+
case "memory_forget": return hang([dim(ONE(result, w))]);
|
|
73
|
+
case "web_search": return hang([dim(`${(result.match(/^\d+\./gm) ?? []).length} results`)]);
|
|
74
|
+
case "fetch_url": return hang([dim(`${result.length.toLocaleString()} characters`)]);
|
|
75
|
+
case "task": {
|
|
76
|
+
const t = result.split("\n").filter(Boolean);
|
|
77
|
+
return hang([...t.slice(0, 8).map((l) => ONE(l, w)), ...(t.length > 8 ? [more(t.length - 8)] : [])]);
|
|
78
|
+
}
|
|
79
|
+
case "todo_write":
|
|
80
|
+
case "exit_plan_mode": return [];
|
|
81
|
+
}
|
|
82
|
+
const t = lines.filter(Boolean);
|
|
83
|
+
return t.length ? hang([ONE(t[0], w), ...(t.length > 1 ? [more(t.length - 1)] : [])]) : [];
|
|
84
|
+
}
|
|
85
|
+
/** Unified diff with line numbers and +/- colouring, like an editor's change view. */
|
|
86
|
+
export function renderDiff(diff, width, maxLines = 24) {
|
|
87
|
+
const out = [];
|
|
88
|
+
let a = 0, b = 0;
|
|
89
|
+
const body = diff.split("\n").filter((l) => !/^(---|\+\+\+)/.test(l));
|
|
90
|
+
const numW = String(Math.max(a, ...body.map((l) => { const m = l.match(/^@@ -(\d+),?\d* \+(\d+)/); return m ? Math.max(+m[1], +m[2]) + 400 : 0; })) || 1).length;
|
|
91
|
+
for (const l of body) {
|
|
92
|
+
const h = l.match(/^@@ -(\d+),?\d* \+(\d+)/);
|
|
93
|
+
if (h) {
|
|
94
|
+
a = +h[1];
|
|
95
|
+
b = +h[2];
|
|
96
|
+
if (out.length)
|
|
97
|
+
out.push(dim(" ⋮"));
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (!l)
|
|
101
|
+
continue;
|
|
102
|
+
const t = l[0], text = l.slice(1), room = Math.max(10, width - numW - 8);
|
|
103
|
+
const shown = cellWidth(text) > room ? clipWidth(text, room - 1) + "…" : text;
|
|
104
|
+
if (t === "+")
|
|
105
|
+
out.push(green(`${String(b).padStart(numW)} + ${shown}`)), b++;
|
|
106
|
+
else if (t === "-")
|
|
107
|
+
out.push(red(`${String(a).padStart(numW)} - ${shown}`)), a++;
|
|
108
|
+
else
|
|
109
|
+
out.push(gray(`${String(b).padStart(numW)} ${shown}`)), a++, b++;
|
|
110
|
+
}
|
|
111
|
+
const clipped = out.length > maxLines ? [...out.slice(0, maxLines), more(out.length - maxLines)] : out;
|
|
112
|
+
return clipped.map((l) => " " + l);
|
|
113
|
+
}
|
|
114
|
+
export const editSummary = (e) => e.created ? `Created with ${e.added} line${e.added === 1 ? "" : "s"}` : `Added ${e.added} line${e.added === 1 ? "" : "s"}, removed ${e.removed} line${e.removed === 1 ? "" : "s"}`;
|
|
115
|
+
export function userMessage(text, width, note = "") {
|
|
116
|
+
const rows = text.split("\n").flatMap((l, i) => wrapAnsi(l, width - 2, 0).map((r, j) => (i === 0 && j === 0 ? bold(cyan("❯ ")) : " ") + bold(r)));
|
|
117
|
+
return ["", ...rows, ...(note ? [" " + dim(note)] : [])];
|
|
118
|
+
}
|
|
119
|
+
/** One assistant block: first block of a message gets the "●" marker; later blocks are separated by a blank line. */
|
|
120
|
+
export function assistantBlock(md, width, first) {
|
|
121
|
+
const rows = renderMarkdown(md, width - 2);
|
|
122
|
+
if (!rows.length)
|
|
123
|
+
return [];
|
|
124
|
+
return [...(first ? [] : [""]), ...rows.map((r, i) => (first && i === 0 ? "● " : " ") + r)].map((r, i) => (first && i === 0 ? r.replace("●", bold("●")) : r));
|
|
125
|
+
}
|
|
126
|
+
export function todoList(todos, width) {
|
|
127
|
+
if (!todos.length)
|
|
128
|
+
return [];
|
|
129
|
+
const done = todos.filter((t) => t.status === "done").length;
|
|
130
|
+
return [dim(` ☰ Todos ${done}/${todos.length}`), ...todos.slice(0, 10).map((t) => " " + (t.status === "done" ? green("☒ ") + dim(strike(ONE(t.content, width - 8))) : t.status === "in_progress" ? yellow("▶ ") + bold(ONE(t.content, width - 8)) : "☐ " + ONE(t.content, width - 8))),
|
|
131
|
+
...(todos.length > 10 ? [dim(` … +${todos.length - 10} more`)] : [])];
|
|
132
|
+
}
|
|
133
|
+
const strike = (s) => `\x1b[9m${s}\x1b[29m`;
|
|
134
|
+
/** Numbered, arrow-selectable option list used by approval prompts and pickers. */
|
|
135
|
+
export function optionList(opts, sel) {
|
|
136
|
+
return opts.map((o, i) => `${i === sel ? cyan("❯") : " "} ${i === sel ? bold(cyan(`${i + 1}.`)) : dim(`${i + 1}.`)} ${i === sel ? bold(o.label) : o.label}${o.hint ? " " + dim(o.hint) : ""}`);
|
|
137
|
+
}
|
|
138
|
+
export function framed(title, body, width, paint = cyan) {
|
|
139
|
+
const inner = width - 4, rows = body.flatMap((l) => wrapAnsi(l, inner));
|
|
140
|
+
const top = paint("╭─ ") + bold(title) + paint(" " + "─".repeat(Math.max(0, width - 5 - cellWidth(strip(title)))) + "╮");
|
|
141
|
+
return [top, ...rows.map((r) => paint("│ ") + r + " ".repeat(Math.max(0, inner - cellWidth(r))) + paint(" │")), paint("╰" + "─".repeat(width - 2) + "╯")];
|
|
142
|
+
}
|
|
143
|
+
const MODE_LABEL = { default: "", acceptEdits: "⏵⏵ accept edits on", plan: "⏸ plan mode (read-only)", yolo: "⚠ yolo: no permission prompts" };
|
|
144
|
+
export function footer(f, width) {
|
|
145
|
+
const m = MODE_LABEL[f.mode] ?? f.mode;
|
|
146
|
+
const left = [m ? (f.mode === "yolo" ? red(m) : f.mode === "plan" ? yellow(m) : green(m)) : "", f.notice ? yellow(f.notice) : f.hint ? dim(f.hint) : ""].filter(Boolean).join(dim(" · ")) || (f.quiet ? "" : dim("? for shortcuts"));
|
|
147
|
+
const brand = brandOf(f.model, f.provider), ctx = f.ctxPct !== null ? `${gauge(f.ctxPct, 8)} ${fg(gaugeColor(f.ctxPct), f.ctxPct + "%")}` : "";
|
|
148
|
+
const right = [f.queued ? yellow(`${f.queued} queued`) : "", f.branch ? dim("⎇ " + f.branch) : "", `${fg(brand.color, brand.icon)} ${dim(f.model)}`, ctx, f.cost ? dim(f.cost) : ""].filter(Boolean).join(dim(" · "));
|
|
149
|
+
const avail = width - cellWidth(right) - 4; // the right-hand info (queue, model, context) wins over the hints on the left
|
|
150
|
+
if (avail < 8)
|
|
151
|
+
return " " + clipAnsi(left, width - 2);
|
|
152
|
+
const L = cellWidth(left) > avail ? clipAnsi(left, avail) : left;
|
|
153
|
+
return " " + L + " ".repeat(Math.max(2, width - cellWidth(L) - cellWidth(right) - 2)) + right;
|
|
154
|
+
}
|
|
155
|
+
export const COMMAND_HELP_WIDTH = 14;
|
|
156
|
+
export function helpLines(cmds, width) {
|
|
157
|
+
const keys = [["Enter", "send · queue while working"], ["\\ + Enter / Alt+Enter / Ctrl+J", "new line"], ["Esc", "interrupt · close popup · Esc Esc rewind"],
|
|
158
|
+
["Ctrl+C", "interrupt · clear input · exit (twice)"], ["Shift+Tab", "cycle permissions: ask → accept edits → plan"], ["↑ ↓", "history · menu"], ["Tab", "accept suggestion"],
|
|
159
|
+
["@path", "mention a file"], ["!cmd", "run a command now (for example !ls)"], ["#note", "remember a note"], ["Ctrl+P", "command palette"], ["Alt+M", "switch model"], ["Ctrl+O", "full transcript"], ["Ctrl+L", "redraw"], ["drag / dbl / triple click", "select and copy (Shift+drag: terminal selection)"], ["/copy", "copy the last reply (code · all)"]];
|
|
160
|
+
return [bold("Keys"), ...keys.map(([k, d]) => ` ${cyan(k.padEnd(31))} ${dim(d)}`), "", bold("Commands"),
|
|
161
|
+
...cmds.map((c) => ` ${cyan(("/" + c.name).padEnd(COMMAND_HELP_WIDTH))} ${dim(c.help)}`)].flatMap((l) => wrapAnsi(l, width - 2, 33));
|
|
162
|
+
}
|
|
163
|
+
export function welcome(o, width) {
|
|
164
|
+
const home = process.env.HOME ?? "";
|
|
165
|
+
let cwd = home && o.cwd.startsWith(home) ? "~" + o.cwd.slice(home.length) : o.cwd;
|
|
166
|
+
const room = Math.min(width, 66) - 4 - 9 - (o.branch ? o.branch.length + 4 : 0); // keep "project path ⎇ branch" on one line
|
|
167
|
+
if (cellWidth(cwd) > room)
|
|
168
|
+
cwd = "…" + cwd.slice(cwd.length - Math.max(8, room - 1));
|
|
169
|
+
const b = brandOf(o.model, o.provider);
|
|
170
|
+
const body = [
|
|
171
|
+
...logo(o.version), "",
|
|
172
|
+
`${dim("model")} ${fg(b.color, b.icon)} ${bold(o.model)}`, `${dim("project")} ${cwd}${o.branch ? dim(" ⎇ " + o.branch) : ""}`,
|
|
173
|
+
...(o.verify ? [`${dim("verify")} ${o.verify}`] : []), ...(o.notes ? [`${dim("memory")} ${o.notes} note${o.notes === 1 ? "" : "s"} loaded`] : []),
|
|
174
|
+
...(o.resumed ? [`${dim("resumed")} ${o.resumed}`] : []),
|
|
175
|
+
];
|
|
176
|
+
return [...framed("welcome", body, Math.min(width, 66)), ...wrapAnsi(dim(" /help for commands · @file to mention · shift+tab changes permissions"), width, 2),
|
|
177
|
+
...(o.tip ? wrapAnsi(` ${yellow("tip")} ${dim(o.tip)}`, width, 6) : []), ""];
|
|
178
|
+
}
|
package/dist/cli/ui.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export declare const bold: (s: string) => string, dim: (s: string) => string, red: (s: string) => string, green: (s: string) => string, yellow: (s: string) => string, blue: (s: string) => string, magenta: (s: string) => string, cyan: (s: string) => string, gray: (s: string) => string;
|
|
2
|
+
export declare const strip: (s: string) => string;
|
|
3
|
+
export declare const width: () => number;
|
|
4
|
+
/** Word-wrap one line (ANSI-aware) to `w` visible columns; continuation lines are indented 2 spaces. */
|
|
5
|
+
export declare function wrap(line: string, w: number): string[];
|
|
6
|
+
export declare function box(input: string[], paint?: (s: string) => string, title?: string): string;
|
|
7
|
+
/** Thrown when the user presses Esc inside a text prompt; screens let it bubble up to the menu. */
|
|
8
|
+
export declare class Cancel extends Error {
|
|
9
|
+
constructor();
|
|
10
|
+
}
|
|
11
|
+
export type Item<T> = {
|
|
12
|
+
label: string;
|
|
13
|
+
value: T;
|
|
14
|
+
hint?: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Arrow-key menu. ↑/↓ or j/k move, 1-9 jump, enter selects, esc goes back, "/" filters long lists, "?" toggles help.
|
|
18
|
+
* Long lists scroll inside the terminal height. Returns undefined on back.
|
|
19
|
+
*/
|
|
20
|
+
export declare function select<T>(title: string, items: Item<T>[], initial?: number): Promise<T | undefined>;
|
|
21
|
+
/**
|
|
22
|
+
* Text prompt. Esc cancels (throws Cancel), up/down recall `history`, and pasted multi-line text arrives as ONE answer
|
|
23
|
+
* instead of leaking into later prompts. With `multiline`, newlines in a paste are kept; otherwise they become spaces.
|
|
24
|
+
*/
|
|
25
|
+
export declare function ask(prompt: string, def?: string, o?: {
|
|
26
|
+
secret?: boolean;
|
|
27
|
+
multiline?: boolean;
|
|
28
|
+
history?: string[];
|
|
29
|
+
}): Promise<string>;
|
|
30
|
+
export declare const confirm: (q: string, def?: boolean) => Promise<boolean>;
|
|
31
|
+
export declare const pause: () => Promise<void>;
|
|
32
|
+
export declare const clearScreen: () => void;
|
|
33
|
+
export declare function log(line?: string): void;
|
|
34
|
+
export declare function spin(text: string | (() => string)): () => void;
|