@aragon-agent/cli 0.4.3
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/CHANGELOG.md +292 -0
- package/README.md +558 -0
- package/dist/agent/coalesce.d.ts +14 -0
- package/dist/agent/coalesce.js +41 -0
- package/dist/agent/controller.d.ts +132 -0
- package/dist/agent/controller.js +332 -0
- package/dist/agent/headless.d.ts +22 -0
- package/dist/agent/headless.js +74 -0
- package/dist/agent/reducer.d.ts +181 -0
- package/dist/agent/reducer.js +420 -0
- package/dist/agent/system-prompt.d.ts +30 -0
- package/dist/agent/system-prompt.js +58 -0
- package/dist/agent/usage.d.ts +13 -0
- package/dist/agent/usage.js +41 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +482 -0
- package/dist/commands/builtins.d.ts +16 -0
- package/dist/commands/builtins.js +223 -0
- package/dist/commands/registry.d.ts +48 -0
- package/dist/commands/registry.js +45 -0
- package/dist/commands/skills.d.ts +22 -0
- package/dist/commands/skills.js +625 -0
- package/dist/config/app-paths.d.ts +11 -0
- package/dist/config/app-paths.js +16 -0
- package/dist/config/env.d.ts +28 -0
- package/dist/config/env.js +92 -0
- package/dist/config/load.d.ts +48 -0
- package/dist/config/load.js +158 -0
- package/dist/config/migrate-legacy-state.d.ts +78 -0
- package/dist/config/migrate-legacy-state.js +181 -0
- package/dist/config/schema.d.ts +249 -0
- package/dist/config/schema.js +247 -0
- package/dist/config/store.d.ts +44 -0
- package/dist/config/store.js +126 -0
- package/dist/input/keymap.d.ts +20 -0
- package/dist/input/keymap.js +85 -0
- package/dist/session/persist.d.ts +24 -0
- package/dist/session/persist.js +51 -0
- package/dist/skills/archive.d.ts +41 -0
- package/dist/skills/archive.js +152 -0
- package/dist/skills/cli-commands.d.ts +52 -0
- package/dist/skills/cli-commands.js +502 -0
- package/dist/skills/fetch-source.d.ts +104 -0
- package/dist/skills/fetch-source.js +448 -0
- package/dist/skills/installer.d.ts +117 -0
- package/dist/skills/installer.js +414 -0
- package/dist/skills/lock.d.ts +75 -0
- package/dist/skills/lock.js +223 -0
- package/dist/skills/manifest.d.ts +47 -0
- package/dist/skills/manifest.js +95 -0
- package/dist/skills/node-host.d.ts +23 -0
- package/dist/skills/node-host.js +59 -0
- package/dist/skills/paths.d.ts +61 -0
- package/dist/skills/paths.js +107 -0
- package/dist/skills/service.d.ts +256 -0
- package/dist/skills/service.js +730 -0
- package/dist/skills/tools.d.ts +35 -0
- package/dist/skills/tools.js +164 -0
- package/dist/skills/updater.d.ts +101 -0
- package/dist/skills/updater.js +330 -0
- package/dist/skills/usage.d.ts +72 -0
- package/dist/skills/usage.js +216 -0
- package/dist/tools/bash-tool.d.ts +12 -0
- package/dist/tools/bash-tool.js +131 -0
- package/dist/tools/diff.d.ts +15 -0
- package/dist/tools/diff.js +50 -0
- package/dist/tools/fs-tools.d.ts +16 -0
- package/dist/tools/fs-tools.js +215 -0
- package/dist/tools/index.d.ts +93 -0
- package/dist/tools/index.js +157 -0
- package/dist/tools/search-tools.d.ts +11 -0
- package/dist/tools/search-tools.js +225 -0
- package/dist/ui/App.d.ts +24 -0
- package/dist/ui/App.js +522 -0
- package/dist/ui/AutocompletePopup.d.ts +21 -0
- package/dist/ui/AutocompletePopup.js +19 -0
- package/dist/ui/Composer.d.ts +40 -0
- package/dist/ui/Composer.js +61 -0
- package/dist/ui/Header.d.ts +30 -0
- package/dist/ui/Header.js +25 -0
- package/dist/ui/Logo.d.ts +50 -0
- package/dist/ui/Logo.js +85 -0
- package/dist/ui/Markdown.d.ts +23 -0
- package/dist/ui/Markdown.js +122 -0
- package/dist/ui/PromptInput.d.ts +81 -0
- package/dist/ui/PromptInput.js +383 -0
- package/dist/ui/SessionOpener.d.ts +30 -0
- package/dist/ui/SessionOpener.js +12 -0
- package/dist/ui/StatusBar.d.ts +39 -0
- package/dist/ui/StatusBar.js +43 -0
- package/dist/ui/ToastStack.d.ts +24 -0
- package/dist/ui/ToastStack.js +38 -0
- package/dist/ui/Transcript.d.ts +62 -0
- package/dist/ui/Transcript.js +128 -0
- package/dist/ui/capabilities.d.ts +25 -0
- package/dist/ui/capabilities.js +59 -0
- package/dist/ui/console-bridge.d.ts +19 -0
- package/dist/ui/console-bridge.js +52 -0
- package/dist/ui/density.d.ts +26 -0
- package/dist/ui/density.js +34 -0
- package/dist/ui/entries/AssistantEntry.d.ts +23 -0
- package/dist/ui/entries/AssistantEntry.js +13 -0
- package/dist/ui/entries/EntryFrame.d.ts +23 -0
- package/dist/ui/entries/EntryFrame.js +6 -0
- package/dist/ui/entries/ToolCard.d.ts +38 -0
- package/dist/ui/entries/ToolCard.js +72 -0
- package/dist/ui/entries/ToolPreview.d.ts +57 -0
- package/dist/ui/entries/ToolPreview.js +144 -0
- package/dist/ui/entries/UserEntry.d.ts +14 -0
- package/dist/ui/entries/UserEntry.js +5 -0
- package/dist/ui/exit-snapshot.d.ts +27 -0
- package/dist/ui/exit-snapshot.js +26 -0
- package/dist/ui/gauge.d.ts +24 -0
- package/dist/ui/gauge.js +36 -0
- package/dist/ui/glyphs.d.ts +87 -0
- package/dist/ui/glyphs.js +127 -0
- package/dist/ui/gradient.d.ts +11 -0
- package/dist/ui/gradient.js +50 -0
- package/dist/ui/layout/AppShell.d.ts +24 -0
- package/dist/ui/layout/AppShell.js +9 -0
- package/dist/ui/layout/Gutter.d.ts +67 -0
- package/dist/ui/layout/Gutter.js +42 -0
- package/dist/ui/layout/OverlayFrame.d.ts +62 -0
- package/dist/ui/layout/OverlayFrame.js +92 -0
- package/dist/ui/layout/ScrollViewport.d.ts +33 -0
- package/dist/ui/layout/ScrollViewport.js +71 -0
- package/dist/ui/layout/budget.d.ts +42 -0
- package/dist/ui/layout/budget.js +47 -0
- package/dist/ui/layout/frame.d.ts +58 -0
- package/dist/ui/layout/frame.js +75 -0
- package/dist/ui/layout/overlay-window.d.ts +43 -0
- package/dist/ui/layout/overlay-window.js +51 -0
- package/dist/ui/layout/scroll.d.ts +28 -0
- package/dist/ui/layout/scroll.js +76 -0
- package/dist/ui/layout/useTerminalSize.d.ts +13 -0
- package/dist/ui/layout/useTerminalSize.js +50 -0
- package/dist/ui/markdown-blocks.d.ts +62 -0
- package/dist/ui/markdown-blocks.js +120 -0
- package/dist/ui/overlays/ConfirmDialog.d.ts +30 -0
- package/dist/ui/overlays/ConfirmDialog.js +25 -0
- package/dist/ui/overlays/HelpOverlay.d.ts +17 -0
- package/dist/ui/overlays/HelpOverlay.js +60 -0
- package/dist/ui/overlays/ModelPicker.d.ts +29 -0
- package/dist/ui/overlays/ModelPicker.js +29 -0
- package/dist/ui/overlays/SettingsScreen.d.ts +37 -0
- package/dist/ui/overlays/SettingsScreen.js +106 -0
- package/dist/ui/palettes.d.ts +79 -0
- package/dist/ui/palettes.js +115 -0
- package/dist/ui/screen.d.ts +27 -0
- package/dist/ui/screen.js +57 -0
- package/dist/ui/theme.d.ts +78 -0
- package/dist/ui/theme.js +127 -0
- package/dist/ui/transcript-text.d.ts +27 -0
- package/dist/ui/transcript-text.js +110 -0
- package/package.json +73 -0
- package/skills/skill-creator/SKILL.md +96 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Palettes (spec §4.5) — the raw hex data behind `getTheme()`, split out of
|
|
3
|
+
* `theme.ts` so the color decisions live in one readable place and can be
|
|
4
|
+
* contrast-tested without pulling in the degradation machinery.
|
|
5
|
+
*
|
|
6
|
+
* `WARM` is the new default. The old Tokyo-Night-derived palette survives under
|
|
7
|
+
* its accurate name `COOL` and stays reachable via `--theme cool`, so nobody's
|
|
8
|
+
* screen changes without them being able to change it back (R-4).
|
|
9
|
+
*
|
|
10
|
+
* Theme names describe COLOR TEMPERATURE on purpose. Naming a theme after
|
|
11
|
+
* another company's product would imply an affiliation or an asset licence that
|
|
12
|
+
* does not exist; `warm` / `cool` are accurate and carry no such claim.
|
|
13
|
+
*/
|
|
14
|
+
import type { ThemeName } from '../config/schema.js';
|
|
15
|
+
export interface Palette {
|
|
16
|
+
primary: string;
|
|
17
|
+
accent: string;
|
|
18
|
+
user: string;
|
|
19
|
+
assistant: string;
|
|
20
|
+
thinking: string;
|
|
21
|
+
toolPending: string;
|
|
22
|
+
toolRunning: string;
|
|
23
|
+
toolDone: string;
|
|
24
|
+
toolError: string;
|
|
25
|
+
noticeInfo: string;
|
|
26
|
+
noticeWarn: string;
|
|
27
|
+
noticeError: string;
|
|
28
|
+
muted: string;
|
|
29
|
+
border: string;
|
|
30
|
+
code: string;
|
|
31
|
+
focusBorder: string;
|
|
32
|
+
idleBorder: string;
|
|
33
|
+
hintFg: string;
|
|
34
|
+
logoShadow: string;
|
|
35
|
+
gradient: [string, string, string];
|
|
36
|
+
gauge: {
|
|
37
|
+
track: string;
|
|
38
|
+
low: string;
|
|
39
|
+
mid: string;
|
|
40
|
+
high: string;
|
|
41
|
+
};
|
|
42
|
+
diff: {
|
|
43
|
+
add: string;
|
|
44
|
+
remove: string;
|
|
45
|
+
meta: string;
|
|
46
|
+
context: string;
|
|
47
|
+
};
|
|
48
|
+
toast: {
|
|
49
|
+
info: string;
|
|
50
|
+
warn: string;
|
|
51
|
+
error: string;
|
|
52
|
+
success: string;
|
|
53
|
+
};
|
|
54
|
+
chip: {
|
|
55
|
+
fg: string;
|
|
56
|
+
bg: string;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The reference dark background these palettes are contrast-checked against.
|
|
61
|
+
* `palettes.test.ts` (A-7) pins `assistant` / `muted` / `hintFg` at WCAG AA
|
|
62
|
+
* (>= 4.5:1) here so a later "just nudging the hue" cannot quietly make body
|
|
63
|
+
* text unreadable.
|
|
64
|
+
*/
|
|
65
|
+
export declare const DARK_SURFACE = "#1c1917";
|
|
66
|
+
/** Terracotta + amber over warm neutrals — the v0.4.0 default. */
|
|
67
|
+
export declare const WARM: Palette;
|
|
68
|
+
/** The v0.3.0 palette, renamed. Reachable via `--theme cool` / `theme: "dark"`. */
|
|
69
|
+
export declare const COOL: Palette;
|
|
70
|
+
export declare const LIGHT: Palette;
|
|
71
|
+
/**
|
|
72
|
+
* Resolve a theme name to its palette.
|
|
73
|
+
*
|
|
74
|
+
* `auto` never guesses light: terminals cannot reliably report their background
|
|
75
|
+
* colour, and guessing wrong makes the app unreadable. `dark` is accepted here
|
|
76
|
+
* as well as in `clampTheme` because a persisted config written by v0.3.0 can
|
|
77
|
+
* still be sitting on disk when this runs.
|
|
78
|
+
*/
|
|
79
|
+
export declare function resolvePalette(name: ThemeName | 'dark'): Palette;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Palettes (spec §4.5) — the raw hex data behind `getTheme()`, split out of
|
|
3
|
+
* `theme.ts` so the color decisions live in one readable place and can be
|
|
4
|
+
* contrast-tested without pulling in the degradation machinery.
|
|
5
|
+
*
|
|
6
|
+
* `WARM` is the new default. The old Tokyo-Night-derived palette survives under
|
|
7
|
+
* its accurate name `COOL` and stays reachable via `--theme cool`, so nobody's
|
|
8
|
+
* screen changes without them being able to change it back (R-4).
|
|
9
|
+
*
|
|
10
|
+
* Theme names describe COLOR TEMPERATURE on purpose. Naming a theme after
|
|
11
|
+
* another company's product would imply an affiliation or an asset licence that
|
|
12
|
+
* does not exist; `warm` / `cool` are accurate and carry no such claim.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* The reference dark background these palettes are contrast-checked against.
|
|
16
|
+
* `palettes.test.ts` (A-7) pins `assistant` / `muted` / `hintFg` at WCAG AA
|
|
17
|
+
* (>= 4.5:1) here so a later "just nudging the hue" cannot quietly make body
|
|
18
|
+
* text unreadable.
|
|
19
|
+
*/
|
|
20
|
+
export const DARK_SURFACE = '#1c1917';
|
|
21
|
+
/** Terracotta + amber over warm neutrals — the v0.4.0 default. */
|
|
22
|
+
export const WARM = {
|
|
23
|
+
primary: '#d08c60',
|
|
24
|
+
accent: '#c9a227',
|
|
25
|
+
user: '#7fb08a',
|
|
26
|
+
assistant: '#e6ddd1',
|
|
27
|
+
thinking: '#a2968a',
|
|
28
|
+
toolPending: '#8a7f74',
|
|
29
|
+
toolRunning: '#c9a227',
|
|
30
|
+
toolDone: '#7fb08a',
|
|
31
|
+
toolError: '#cf6b5c',
|
|
32
|
+
noticeInfo: '#d08c60',
|
|
33
|
+
noticeWarn: '#c9a227',
|
|
34
|
+
noticeError: '#cf6b5c',
|
|
35
|
+
muted: '#a2968a',
|
|
36
|
+
border: '#4a423b',
|
|
37
|
+
code: '#c9a97a',
|
|
38
|
+
focusBorder: '#d08c60',
|
|
39
|
+
idleBorder: '#4a423b',
|
|
40
|
+
hintFg: '#9c9086',
|
|
41
|
+
logoShadow: '#4a423b',
|
|
42
|
+
gradient: ['#d08c60', '#c9a227', '#b0916a'],
|
|
43
|
+
gauge: { track: '#4a423b', low: '#7fb08a', mid: '#c9a227', high: '#cf6b5c' },
|
|
44
|
+
diff: { add: '#7fb08a', remove: '#cf6b5c', meta: '#d08c60', context: '#a2968a' },
|
|
45
|
+
toast: { info: '#d08c60', warn: '#c9a227', error: '#cf6b5c', success: '#7fb08a' },
|
|
46
|
+
chip: { fg: '#1c1917', bg: '#d08c60' },
|
|
47
|
+
};
|
|
48
|
+
/** The v0.3.0 palette, renamed. Reachable via `--theme cool` / `theme: "dark"`. */
|
|
49
|
+
export const COOL = {
|
|
50
|
+
primary: '#7aa2f7',
|
|
51
|
+
accent: '#bb9af7',
|
|
52
|
+
user: '#7dcfff',
|
|
53
|
+
assistant: '#c0caf5',
|
|
54
|
+
thinking: '#7a86b8',
|
|
55
|
+
toolPending: '#565f89',
|
|
56
|
+
toolRunning: '#e0af68',
|
|
57
|
+
toolDone: '#9ece6a',
|
|
58
|
+
toolError: '#f7768e',
|
|
59
|
+
noticeInfo: '#7aa2f7',
|
|
60
|
+
noticeWarn: '#e0af68',
|
|
61
|
+
noticeError: '#f7768e',
|
|
62
|
+
muted: '#565f89',
|
|
63
|
+
border: '#3b4261',
|
|
64
|
+
code: '#9ece6a',
|
|
65
|
+
focusBorder: '#7aa2f7',
|
|
66
|
+
idleBorder: '#3b4261',
|
|
67
|
+
hintFg: '#565f89',
|
|
68
|
+
logoShadow: '#3b4261',
|
|
69
|
+
gradient: ['#7aa2f7', '#bb9af7', '#7dcfff'],
|
|
70
|
+
gauge: { track: '#3b4261', low: '#9ece6a', mid: '#e0af68', high: '#f7768e' },
|
|
71
|
+
diff: { add: '#9ece6a', remove: '#f7768e', meta: '#7aa2f7', context: '#7a86b8' },
|
|
72
|
+
toast: { info: '#7aa2f7', warn: '#e0af68', error: '#f7768e', success: '#9ece6a' },
|
|
73
|
+
chip: { fg: '#1a1b26', bg: '#7aa2f7' },
|
|
74
|
+
};
|
|
75
|
+
export const LIGHT = {
|
|
76
|
+
primary: '#2959aa',
|
|
77
|
+
accent: '#8c4bc9',
|
|
78
|
+
user: '#0f7490',
|
|
79
|
+
assistant: '#1f2430',
|
|
80
|
+
thinking: '#6b7280',
|
|
81
|
+
toolPending: '#9aa0ab',
|
|
82
|
+
toolRunning: '#b5730f',
|
|
83
|
+
toolDone: '#2e7d32',
|
|
84
|
+
toolError: '#c62828',
|
|
85
|
+
noticeInfo: '#2959aa',
|
|
86
|
+
noticeWarn: '#b5730f',
|
|
87
|
+
noticeError: '#c62828',
|
|
88
|
+
muted: '#6b7280',
|
|
89
|
+
border: '#c8cdd6',
|
|
90
|
+
code: '#3f7f2f',
|
|
91
|
+
focusBorder: '#2959aa',
|
|
92
|
+
idleBorder: '#c8cdd6',
|
|
93
|
+
hintFg: '#6b7280',
|
|
94
|
+
logoShadow: '#c8cdd6',
|
|
95
|
+
gradient: ['#2959aa', '#8c4bc9', '#0f7490'],
|
|
96
|
+
gauge: { track: '#c8cdd6', low: '#2e7d32', mid: '#b5730f', high: '#c62828' },
|
|
97
|
+
diff: { add: '#2e7d32', remove: '#c62828', meta: '#2959aa', context: '#6b7280' },
|
|
98
|
+
toast: { info: '#2959aa', warn: '#b5730f', error: '#c62828', success: '#2e7d32' },
|
|
99
|
+
chip: { fg: '#ffffff', bg: '#2959aa' },
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Resolve a theme name to its palette.
|
|
103
|
+
*
|
|
104
|
+
* `auto` never guesses light: terminals cannot reliably report their background
|
|
105
|
+
* colour, and guessing wrong makes the app unreadable. `dark` is accepted here
|
|
106
|
+
* as well as in `clampTheme` because a persisted config written by v0.3.0 can
|
|
107
|
+
* still be sitting on disk when this runs.
|
|
108
|
+
*/
|
|
109
|
+
export function resolvePalette(name) {
|
|
110
|
+
if (name === 'light')
|
|
111
|
+
return LIGHT;
|
|
112
|
+
if (name === 'cool' || name === 'dark')
|
|
113
|
+
return COOL;
|
|
114
|
+
return WARM;
|
|
115
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alternate screen buffer take-over (spec §4.4).
|
|
3
|
+
*
|
|
4
|
+
* Why the alt-screen and not a clear: `\x1b[3J` destroys the user's scrollback
|
|
5
|
+
* (their build log, their stack trace) — an unacceptable side effect for a
|
|
6
|
+
* developer tool. The alt-screen is non-destructive: the main buffer is covered,
|
|
7
|
+
* not erased, and comes back untouched on exit. It also parks the cursor at
|
|
8
|
+
* (1,1), which gives the frame accounting in §4.2 a deterministic origin.
|
|
9
|
+
*
|
|
10
|
+
* CALL SITE IS LOAD-BEARING: only `cli.tsx::runInteractive()` may call this.
|
|
11
|
+
* Hoisting it into `buildProgram()` / `parseAsync` would push `\x1b[?1049h`
|
|
12
|
+
* into `aragon -p "…" > out.txt`, `aragon config set`, and `aragon --version`.
|
|
13
|
+
*/
|
|
14
|
+
export interface ScreenHandle {
|
|
15
|
+
/** Leave the alternate screen. Idempotent — safe from every exit path. */
|
|
16
|
+
restore(): void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Enter the alternate screen. A non-TTY stdout is a hard no-op (tests, pipes,
|
|
20
|
+
* CI) — writing screen-control sequences into a redirected file corrupts it.
|
|
21
|
+
*/
|
|
22
|
+
export declare function enterAltScreen(stdout: NodeJS.WriteStream | undefined): ScreenHandle;
|
|
23
|
+
/**
|
|
24
|
+
* Replay the session summary into the normal buffer after `restore()`, so the
|
|
25
|
+
* conversation does not evaporate along with the alternate screen.
|
|
26
|
+
*/
|
|
27
|
+
export declare function writeExitTranscript(stdout: NodeJS.WriteStream | undefined, text: string): void;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alternate screen buffer take-over (spec §4.4).
|
|
3
|
+
*
|
|
4
|
+
* Why the alt-screen and not a clear: `\x1b[3J` destroys the user's scrollback
|
|
5
|
+
* (their build log, their stack trace) — an unacceptable side effect for a
|
|
6
|
+
* developer tool. The alt-screen is non-destructive: the main buffer is covered,
|
|
7
|
+
* not erased, and comes back untouched on exit. It also parks the cursor at
|
|
8
|
+
* (1,1), which gives the frame accounting in §4.2 a deterministic origin.
|
|
9
|
+
*
|
|
10
|
+
* CALL SITE IS LOAD-BEARING: only `cli.tsx::runInteractive()` may call this.
|
|
11
|
+
* Hoisting it into `buildProgram()` / `parseAsync` would push `\x1b[?1049h`
|
|
12
|
+
* into `aragon -p "…" > out.txt`, `aragon config set`, and `aragon --version`.
|
|
13
|
+
*/
|
|
14
|
+
const ENTER_ALT = '\x1b[?1049h';
|
|
15
|
+
const LEAVE_ALT = '\x1b[?1049l';
|
|
16
|
+
const CURSOR_HOME = '\x1b[H';
|
|
17
|
+
const CLEAR_VISIBLE = '\x1b[2J';
|
|
18
|
+
/** Ink's log-update hides the cursor; a signal kill never gives it back. */
|
|
19
|
+
const SHOW_CURSOR = '\x1b[?25h';
|
|
20
|
+
const NOOP_HANDLE = { restore() { } };
|
|
21
|
+
function safeWrite(stdout, data) {
|
|
22
|
+
try {
|
|
23
|
+
stdout.write(data);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// A closed/broken stdout must never turn into a crash on the exit path.
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Enter the alternate screen. A non-TTY stdout is a hard no-op (tests, pipes,
|
|
31
|
+
* CI) — writing screen-control sequences into a redirected file corrupts it.
|
|
32
|
+
*/
|
|
33
|
+
export function enterAltScreen(stdout) {
|
|
34
|
+
if (!stdout || !stdout.isTTY || typeof stdout.write !== 'function')
|
|
35
|
+
return NOOP_HANDLE;
|
|
36
|
+
safeWrite(stdout, ENTER_ALT + CLEAR_VISIBLE + CURSOR_HOME);
|
|
37
|
+
let restored = false;
|
|
38
|
+
return {
|
|
39
|
+
restore() {
|
|
40
|
+
if (restored)
|
|
41
|
+
return;
|
|
42
|
+
restored = true;
|
|
43
|
+
safeWrite(stdout, SHOW_CURSOR + LEAVE_ALT);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Replay the session summary into the normal buffer after `restore()`, so the
|
|
49
|
+
* conversation does not evaporate along with the alternate screen.
|
|
50
|
+
*/
|
|
51
|
+
export function writeExitTranscript(stdout, text) {
|
|
52
|
+
if (!stdout || typeof stdout.write !== 'function')
|
|
53
|
+
return;
|
|
54
|
+
if (text.length === 0)
|
|
55
|
+
return;
|
|
56
|
+
safeWrite(stdout, text.endsWith('\n') ? text : `${text}\n`);
|
|
57
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme — palette + symbols used across the Ink component tree (spec §3.2).
|
|
3
|
+
*
|
|
4
|
+
* A real theming engine: distinct `WARM` / `COOL` / `LIGHT` hex palettes, an
|
|
5
|
+
* `auto` resolution that is deterministic (`auto -> warm`, since terminals
|
|
6
|
+
* cannot reliably report their background), color degradation across terminal
|
|
7
|
+
* depth (truecolor -> 256 -> 16 -> monochrome), and an ASCII symbol swap for
|
|
8
|
+
* terminals without Unicode support.
|
|
9
|
+
*
|
|
10
|
+
* Two things used to live here and no longer do (spec §4.1 / §4.5):
|
|
11
|
+
* - the three symbol tables moved to `glyphs.ts`, which is now the single
|
|
12
|
+
* source of truth for every user-visible character. `symbols` is a VIEW of
|
|
13
|
+
* `pickGlyphs(caps)`, so every existing `theme.symbols.x` consumer is
|
|
14
|
+
* unchanged;
|
|
15
|
+
* - the palette data moved to `palettes.ts`.
|
|
16
|
+
*/
|
|
17
|
+
import type { ThemeName } from '../config/schema.js';
|
|
18
|
+
import type { TermCapabilities } from './capabilities.js';
|
|
19
|
+
import { type Glyphs } from './glyphs.js';
|
|
20
|
+
/** A resolved color: a hex/name string, or `undefined` (Ink renders plain). */
|
|
21
|
+
type Color = string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Retained name for the symbol view on `Theme`. It is exactly `Glyphs`: the
|
|
24
|
+
* split is by ownership (glyphs.ts owns the data), not by shape.
|
|
25
|
+
*/
|
|
26
|
+
export type ThemeSymbols = Glyphs;
|
|
27
|
+
export interface Theme {
|
|
28
|
+
name: ThemeName;
|
|
29
|
+
symbols: ThemeSymbols;
|
|
30
|
+
primary: Color;
|
|
31
|
+
accent: Color;
|
|
32
|
+
user: Color;
|
|
33
|
+
assistant: Color;
|
|
34
|
+
thinking: Color;
|
|
35
|
+
toolPending: Color;
|
|
36
|
+
toolRunning: Color;
|
|
37
|
+
toolDone: Color;
|
|
38
|
+
toolError: Color;
|
|
39
|
+
noticeInfo: Color;
|
|
40
|
+
noticeWarn: Color;
|
|
41
|
+
noticeError: Color;
|
|
42
|
+
muted: Color;
|
|
43
|
+
border: Color;
|
|
44
|
+
code: Color;
|
|
45
|
+
/** Composer border while the input holds a draft. */
|
|
46
|
+
focusBorder: Color;
|
|
47
|
+
/** Composer border while idle and empty (matches the legacy `border`). */
|
|
48
|
+
idleBorder: Color;
|
|
49
|
+
/** Hint lines, collapse notices, and the off-bottom indicator. */
|
|
50
|
+
hintFg: Color;
|
|
51
|
+
/** Single-color wordmark fallback below ansi-256. */
|
|
52
|
+
logoShadow: Color;
|
|
53
|
+
gradient: string[];
|
|
54
|
+
gauge: {
|
|
55
|
+
track: Color;
|
|
56
|
+
low: Color;
|
|
57
|
+
mid: Color;
|
|
58
|
+
high: Color;
|
|
59
|
+
};
|
|
60
|
+
diff: {
|
|
61
|
+
add: Color;
|
|
62
|
+
remove: Color;
|
|
63
|
+
meta: Color;
|
|
64
|
+
context: Color;
|
|
65
|
+
};
|
|
66
|
+
toast: {
|
|
67
|
+
info: Color;
|
|
68
|
+
warn: Color;
|
|
69
|
+
error: Color;
|
|
70
|
+
success: Color;
|
|
71
|
+
};
|
|
72
|
+
chip: {
|
|
73
|
+
fg: Color;
|
|
74
|
+
bg: Color;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export declare function getTheme(name: ThemeName, caps: TermCapabilities): Theme;
|
|
78
|
+
export {};
|
package/dist/ui/theme.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme — palette + symbols used across the Ink component tree (spec §3.2).
|
|
3
|
+
*
|
|
4
|
+
* A real theming engine: distinct `WARM` / `COOL` / `LIGHT` hex palettes, an
|
|
5
|
+
* `auto` resolution that is deterministic (`auto -> warm`, since terminals
|
|
6
|
+
* cannot reliably report their background), color degradation across terminal
|
|
7
|
+
* depth (truecolor -> 256 -> 16 -> monochrome), and an ASCII symbol swap for
|
|
8
|
+
* terminals without Unicode support.
|
|
9
|
+
*
|
|
10
|
+
* Two things used to live here and no longer do (spec §4.1 / §4.5):
|
|
11
|
+
* - the three symbol tables moved to `glyphs.ts`, which is now the single
|
|
12
|
+
* source of truth for every user-visible character. `symbols` is a VIEW of
|
|
13
|
+
* `pickGlyphs(caps)`, so every existing `theme.symbols.x` consumer is
|
|
14
|
+
* unchanged;
|
|
15
|
+
* - the palette data moved to `palettes.ts`.
|
|
16
|
+
*/
|
|
17
|
+
import { pickGlyphs } from './glyphs.js';
|
|
18
|
+
import { resolvePalette } from './palettes.js';
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Color degradation (truecolor -> 256 -> 16 -> monochrome)
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
/** The 16 standard ANSI colours as chalk names + representative RGB. */
|
|
23
|
+
const ANSI16 = [
|
|
24
|
+
['black', [0, 0, 0]],
|
|
25
|
+
['red', [205, 49, 49]],
|
|
26
|
+
['green', [13, 188, 121]],
|
|
27
|
+
['yellow', [229, 229, 16]],
|
|
28
|
+
['blue', [36, 114, 200]],
|
|
29
|
+
['magenta', [188, 63, 188]],
|
|
30
|
+
['cyan', [17, 168, 205]],
|
|
31
|
+
['white', [229, 229, 229]],
|
|
32
|
+
['gray', [102, 102, 102]],
|
|
33
|
+
['redBright', [241, 76, 76]],
|
|
34
|
+
['greenBright', [35, 209, 139]],
|
|
35
|
+
['yellowBright', [245, 245, 67]],
|
|
36
|
+
['blueBright', [59, 142, 234]],
|
|
37
|
+
['magentaBright', [214, 112, 214]],
|
|
38
|
+
['cyanBright', [41, 184, 219]],
|
|
39
|
+
['whiteBright', [255, 255, 255]],
|
|
40
|
+
];
|
|
41
|
+
function hexToRgb(hex) {
|
|
42
|
+
let h = hex.replace('#', '').trim();
|
|
43
|
+
if (h.length === 3)
|
|
44
|
+
h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
|
45
|
+
const n = Number.parseInt(h, 16);
|
|
46
|
+
return [(n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff];
|
|
47
|
+
}
|
|
48
|
+
/** Nearest of the 16 ANSI colours (chalk name) to an arbitrary hex. */
|
|
49
|
+
function nearestAnsi16(hex) {
|
|
50
|
+
const [r, g, b] = hexToRgb(hex);
|
|
51
|
+
let best = ANSI16[0][0];
|
|
52
|
+
let bestDist = Infinity;
|
|
53
|
+
for (const [name, [cr, cg, cb]] of ANSI16) {
|
|
54
|
+
const d = (r - cr) ** 2 + (g - cg) ** 2 + (b - cb) ** 2;
|
|
55
|
+
if (d < bestDist) {
|
|
56
|
+
bestDist = d;
|
|
57
|
+
best = name;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return best;
|
|
61
|
+
}
|
|
62
|
+
/** Degrade a hex colour to the terminal's depth; `undefined` at level 0. */
|
|
63
|
+
function degrade(hex, level) {
|
|
64
|
+
if (level === 0)
|
|
65
|
+
return undefined;
|
|
66
|
+
if (level === 1)
|
|
67
|
+
return nearestAnsi16(hex);
|
|
68
|
+
return hex; // level 2/3: Ink/chalk downsamples the hex as needed
|
|
69
|
+
}
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// getTheme (memoized)
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
const cache = new Map();
|
|
74
|
+
export function getTheme(name, caps) {
|
|
75
|
+
const key = `${name}|${caps.colorLevel}|${caps.unicode}`;
|
|
76
|
+
const hit = cache.get(key);
|
|
77
|
+
if (hit)
|
|
78
|
+
return hit;
|
|
79
|
+
const palette = resolvePalette(name);
|
|
80
|
+
const level = caps.colorLevel;
|
|
81
|
+
const d = (hex) => degrade(hex, level);
|
|
82
|
+
const theme = {
|
|
83
|
+
name,
|
|
84
|
+
symbols: pickGlyphs(caps),
|
|
85
|
+
primary: d(palette.primary),
|
|
86
|
+
accent: d(palette.accent),
|
|
87
|
+
user: d(palette.user),
|
|
88
|
+
assistant: d(palette.assistant),
|
|
89
|
+
thinking: d(palette.thinking),
|
|
90
|
+
toolPending: d(palette.toolPending),
|
|
91
|
+
toolRunning: d(palette.toolRunning),
|
|
92
|
+
toolDone: d(palette.toolDone),
|
|
93
|
+
toolError: d(palette.toolError),
|
|
94
|
+
noticeInfo: d(palette.noticeInfo),
|
|
95
|
+
noticeWarn: d(palette.noticeWarn),
|
|
96
|
+
noticeError: d(palette.noticeError),
|
|
97
|
+
muted: d(palette.muted),
|
|
98
|
+
border: d(palette.border),
|
|
99
|
+
code: d(palette.code),
|
|
100
|
+
focusBorder: d(palette.focusBorder),
|
|
101
|
+
idleBorder: d(palette.idleBorder),
|
|
102
|
+
hintFg: d(palette.hintFg),
|
|
103
|
+
logoShadow: d(palette.logoShadow),
|
|
104
|
+
gradient: level >= 2 ? [...palette.gradient] : [],
|
|
105
|
+
gauge: {
|
|
106
|
+
track: d(palette.gauge.track),
|
|
107
|
+
low: d(palette.gauge.low),
|
|
108
|
+
mid: d(palette.gauge.mid),
|
|
109
|
+
high: d(palette.gauge.high),
|
|
110
|
+
},
|
|
111
|
+
diff: {
|
|
112
|
+
add: d(palette.diff.add),
|
|
113
|
+
remove: d(palette.diff.remove),
|
|
114
|
+
meta: d(palette.diff.meta),
|
|
115
|
+
context: d(palette.diff.context),
|
|
116
|
+
},
|
|
117
|
+
toast: {
|
|
118
|
+
info: d(palette.toast.info),
|
|
119
|
+
warn: d(palette.toast.warn),
|
|
120
|
+
error: d(palette.toast.error),
|
|
121
|
+
success: d(palette.toast.success),
|
|
122
|
+
},
|
|
123
|
+
chip: { fg: d(palette.chip.fg), bg: d(palette.chip.bg) },
|
|
124
|
+
};
|
|
125
|
+
cache.set(key, theme);
|
|
126
|
+
return theme;
|
|
127
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain-text session rendering (spec §4.4). Pure, colorless, ANSI-free, so the
|
|
3
|
+
* exit replay can be piped, diffed, or pasted into an issue.
|
|
4
|
+
*
|
|
5
|
+
* Used only by the full-screen exit replay: inline mode already left the real
|
|
6
|
+
* transcript in the terminal's native scrollback, and replaying there would
|
|
7
|
+
* simply print everything twice.
|
|
8
|
+
*/
|
|
9
|
+
import type { Entry, UsageTotal } from '../agent/reducer.js';
|
|
10
|
+
import type { Glyphs } from './glyphs.js';
|
|
11
|
+
export interface TranscriptTextOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Glyph set for the terminal being replayed into. The exit replay lands in
|
|
14
|
+
* the SAME terminal the TUI just left, so it degrades by the same rule; it
|
|
15
|
+
* used to spell 11 Unicode literals of its own and wrote mojibake straight
|
|
16
|
+
* into the user's normal buffer on a legacy console.
|
|
17
|
+
*/
|
|
18
|
+
glyphs: Glyphs;
|
|
19
|
+
usageTotal: UsageTotal;
|
|
20
|
+
model: string;
|
|
21
|
+
provider: string;
|
|
22
|
+
elapsedMs: number;
|
|
23
|
+
/** Entries beyond this count collapse into a single "… N omitted" line. */
|
|
24
|
+
maxEntries?: number;
|
|
25
|
+
}
|
|
26
|
+
/** Render the whole session (summary line + entries) as plain text. */
|
|
27
|
+
export declare function renderTranscriptText(entries: Entry[], opts: TranscriptTextOptions): string;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain-text session rendering (spec §4.4). Pure, colorless, ANSI-free, so the
|
|
3
|
+
* exit replay can be piped, diffed, or pasted into an issue.
|
|
4
|
+
*
|
|
5
|
+
* Used only by the full-screen exit replay: inline mode already left the real
|
|
6
|
+
* transcript in the terminal's native scrollback, and replaying there would
|
|
7
|
+
* simply print everything twice.
|
|
8
|
+
*/
|
|
9
|
+
import { formatCost, formatDuration, formatTokens } from '../agent/usage.js';
|
|
10
|
+
const DEFAULT_MAX_ENTRIES = 200;
|
|
11
|
+
const CONTINUATION_INDENT = ' ';
|
|
12
|
+
const TOOL_INDENT = ' ';
|
|
13
|
+
const MAX_ARG_CHARS = 60;
|
|
14
|
+
/** Prefix the first line with `marker`, indent the rest to line up under it. */
|
|
15
|
+
function block(marker, text) {
|
|
16
|
+
const lines = text.replace(/\r\n/g, '\n').split('\n');
|
|
17
|
+
const head = `${marker} ${lines[0] ?? ''}`.trimEnd();
|
|
18
|
+
const rest = lines.slice(1).map((l) => `${CONTINUATION_INDENT}${l}`.trimEnd());
|
|
19
|
+
return [head, ...rest];
|
|
20
|
+
}
|
|
21
|
+
/** A short, single-line rendering of the tool's arguments. */
|
|
22
|
+
function argSummary(entry, glyphs) {
|
|
23
|
+
const source = entry.args ?? safeParse(entry.argsRaw);
|
|
24
|
+
if (!source)
|
|
25
|
+
return '';
|
|
26
|
+
const parts = [];
|
|
27
|
+
for (const value of Object.values(source)) {
|
|
28
|
+
if (typeof value === 'string')
|
|
29
|
+
parts.push(value);
|
|
30
|
+
else if (typeof value === 'number' || typeof value === 'boolean')
|
|
31
|
+
parts.push(String(value));
|
|
32
|
+
if (parts.length >= 2)
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
const joined = parts.join(' ').replace(/\s+/g, ' ').trim();
|
|
36
|
+
if (joined.length === 0)
|
|
37
|
+
return '';
|
|
38
|
+
return joined.length > MAX_ARG_CHARS
|
|
39
|
+
? `${joined.slice(0, MAX_ARG_CHARS - 1)}${glyphs.ellipsis}`
|
|
40
|
+
: joined;
|
|
41
|
+
}
|
|
42
|
+
function safeParse(raw) {
|
|
43
|
+
if (!raw || raw.trim().length === 0)
|
|
44
|
+
return null;
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(raw);
|
|
47
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function renderEntry(entry, glyphs) {
|
|
54
|
+
switch (entry.kind) {
|
|
55
|
+
case 'user':
|
|
56
|
+
return block(glyphs.user, entry.text);
|
|
57
|
+
case 'assistant': {
|
|
58
|
+
const suffix = entry.aborted ? ' (aborted)' : '';
|
|
59
|
+
return block(glyphs.assistant, `${entry.text}${suffix}`);
|
|
60
|
+
}
|
|
61
|
+
case 'tool': {
|
|
62
|
+
const glyph = entry.isError || entry.status === 'error' ? glyphs.toolError : glyphs.toolDone;
|
|
63
|
+
const args = argSummary(entry, glyphs);
|
|
64
|
+
const duration = entry.durationMs === undefined ? '' : ` (${entry.durationMs}ms)`;
|
|
65
|
+
return [`${TOOL_INDENT}${glyph} ${entry.label || entry.name}${args ? ` ${args}` : ''}${duration}`];
|
|
66
|
+
}
|
|
67
|
+
case 'notice': {
|
|
68
|
+
const glyph = entry.level === 'error'
|
|
69
|
+
? glyphs.error
|
|
70
|
+
: entry.level === 'warn'
|
|
71
|
+
? glyphs.warn
|
|
72
|
+
: glyphs.info;
|
|
73
|
+
return block(`${TOOL_INDENT}${glyph}`, entry.text);
|
|
74
|
+
}
|
|
75
|
+
default:
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Render the whole session (summary line + entries) as plain text. */
|
|
80
|
+
export function renderTranscriptText(entries, opts) {
|
|
81
|
+
const turns = entries.filter((e) => e.kind === 'user').length;
|
|
82
|
+
const g = opts.glyphs;
|
|
83
|
+
const tokens = `${formatTokens(opts.usageTotal.inputTokens)}${g.arrowUp} ${formatTokens(opts.usageTotal.outputTokens)}${g.arrowDown}`;
|
|
84
|
+
const summary = [
|
|
85
|
+
'aragon',
|
|
86
|
+
`${opts.provider}:${opts.model}`,
|
|
87
|
+
`${turns} ${turns === 1 ? 'turn' : 'turns'}`,
|
|
88
|
+
tokens,
|
|
89
|
+
formatCost(opts.usageTotal.costUsd),
|
|
90
|
+
formatDuration(Math.max(0, opts.elapsedMs)),
|
|
91
|
+
].join(` ${g.midDot} `);
|
|
92
|
+
const max = opts.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
93
|
+
const lines = [summary];
|
|
94
|
+
if (entries.length > max && max > 1) {
|
|
95
|
+
const head = Math.ceil(max / 2);
|
|
96
|
+
const tail = max - head;
|
|
97
|
+
const omitted = entries.length - max;
|
|
98
|
+
for (const entry of entries.slice(0, head))
|
|
99
|
+
lines.push(...renderEntry(entry, g));
|
|
100
|
+
lines.push(`${g.ellipsis} ${omitted} entries omitted ${g.midDot} use /save before exiting`);
|
|
101
|
+
for (const entry of entries.slice(entries.length - tail)) {
|
|
102
|
+
lines.push(...renderEntry(entry, g));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
for (const entry of entries)
|
|
107
|
+
lines.push(...renderEntry(entry, g));
|
|
108
|
+
}
|
|
109
|
+
return `${lines.join('\n')}\n`;
|
|
110
|
+
}
|