cvc-tui 0.4.0 → 0.4.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.
Files changed (133) hide show
  1. package/dist/entry.js +71148 -61
  2. package/package.json +2 -2
  3. package/dist/app/completion.js +0 -102
  4. package/dist/app/createGatewayEventHandler.js +0 -508
  5. package/dist/app/createSlashHandler.js +0 -101
  6. package/dist/app/delegationStore.js +0 -51
  7. package/dist/app/gatewayContext.js +0 -17
  8. package/dist/app/historyStore.js +0 -123
  9. package/dist/app/inputBuffer.js +0 -120
  10. package/dist/app/inputSelectionStore.js +0 -8
  11. package/dist/app/inputStore.js +0 -28
  12. package/dist/app/interfaces.js +0 -6
  13. package/dist/app/overlayStore.js +0 -40
  14. package/dist/app/promptStore.js +0 -44
  15. package/dist/app/queueStore.js +0 -25
  16. package/dist/app/scroll.js +0 -44
  17. package/dist/app/setupHandoff.js +0 -28
  18. package/dist/app/slash/commands/core.js +0 -479
  19. package/dist/app/slash/commands/debug.js +0 -44
  20. package/dist/app/slash/commands/ops.js +0 -498
  21. package/dist/app/slash/commands/session.js +0 -431
  22. package/dist/app/slash/commands/setup.js +0 -20
  23. package/dist/app/slash/commands/toggles.js +0 -40
  24. package/dist/app/slash/registry.js +0 -18
  25. package/dist/app/slash/types.js +0 -1
  26. package/dist/app/spawnHistoryStore.js +0 -105
  27. package/dist/app/turnController.js +0 -650
  28. package/dist/app/turnStore.js +0 -48
  29. package/dist/app/uiStore.js +0 -36
  30. package/dist/app/useComposerState.js +0 -265
  31. package/dist/app/useConfigSync.js +0 -144
  32. package/dist/app/useInputHandlers.js +0 -403
  33. package/dist/app/useLongRunToolCharms.js +0 -50
  34. package/dist/app/useMainApp.js +0 -629
  35. package/dist/app/useSessionLifecycle.js +0 -175
  36. package/dist/app/useSubmission.js +0 -287
  37. package/dist/app.js +0 -15
  38. package/dist/banner.js +0 -57
  39. package/dist/components/agentsOverlay.js +0 -474
  40. package/dist/components/appChrome.js +0 -252
  41. package/dist/components/appLayout.js +0 -121
  42. package/dist/components/appOverlays.js +0 -65
  43. package/dist/components/branding.js +0 -97
  44. package/dist/components/fpsOverlay.js +0 -22
  45. package/dist/components/helpHint.js +0 -21
  46. package/dist/components/markdown.js +0 -501
  47. package/dist/components/maskedPrompt.js +0 -12
  48. package/dist/components/messageLine.js +0 -82
  49. package/dist/components/modelPicker.js +0 -254
  50. package/dist/components/overlayControls.js +0 -30
  51. package/dist/components/overlays/confirmPrompt.js +0 -25
  52. package/dist/components/overlays/helpOverlay.js +0 -76
  53. package/dist/components/overlays/historySearch.js +0 -49
  54. package/dist/components/overlays/modelPicker.js +0 -60
  55. package/dist/components/overlays/overlayUtils.js +0 -19
  56. package/dist/components/overlays/secretPrompt.js +0 -36
  57. package/dist/components/overlays/sessionPicker.js +0 -93
  58. package/dist/components/overlays/skillsHub.js +0 -71
  59. package/dist/components/prompts.js +0 -95
  60. package/dist/components/queuedMessages.js +0 -24
  61. package/dist/components/sessionPicker.js +0 -130
  62. package/dist/components/skillsHub.js +0 -165
  63. package/dist/components/streamingAssistant.js +0 -35
  64. package/dist/components/streamingMarkdown.js +0 -144
  65. package/dist/components/textInput.js +0 -794
  66. package/dist/components/themed.js +0 -12
  67. package/dist/components/thinking.js +0 -496
  68. package/dist/components/todoPanel.js +0 -40
  69. package/dist/components/transcript.js +0 -22
  70. package/dist/config/env.js +0 -18
  71. package/dist/config/limits.js +0 -22
  72. package/dist/config/timing.js +0 -18
  73. package/dist/content/charms.js +0 -5
  74. package/dist/content/faces.js +0 -21
  75. package/dist/content/fortunes.js +0 -29
  76. package/dist/content/hotkeys.js +0 -38
  77. package/dist/content/placeholders.js +0 -15
  78. package/dist/content/setup.js +0 -14
  79. package/dist/content/verbs.js +0 -41
  80. package/dist/domain/details.js +0 -53
  81. package/dist/domain/messages.js +0 -63
  82. package/dist/domain/paths.js +0 -16
  83. package/dist/domain/providers.js +0 -11
  84. package/dist/domain/roles.js +0 -6
  85. package/dist/domain/slash.js +0 -11
  86. package/dist/domain/usage.js +0 -1
  87. package/dist/domain/viewport.js +0 -33
  88. package/dist/gateway/client.js +0 -312
  89. package/dist/gatewayClient.js +0 -574
  90. package/dist/gatewayTypes.js +0 -1
  91. package/dist/hooks/useCompletion.js +0 -86
  92. package/dist/hooks/useGitBranch.js +0 -58
  93. package/dist/hooks/useInputHistory.js +0 -12
  94. package/dist/hooks/useQueue.js +0 -57
  95. package/dist/hooks/useVirtualHistory.js +0 -401
  96. package/dist/lib/circularBuffer.js +0 -43
  97. package/dist/lib/clipboard.js +0 -126
  98. package/dist/lib/editor.js +0 -41
  99. package/dist/lib/editor.test.js +0 -58
  100. package/dist/lib/emoji.js +0 -49
  101. package/dist/lib/externalCli.js +0 -11
  102. package/dist/lib/forceTruecolor.js +0 -26
  103. package/dist/lib/fpsStore.js +0 -36
  104. package/dist/lib/gracefulExit.js +0 -29
  105. package/dist/lib/history.js +0 -69
  106. package/dist/lib/inputMetrics.js +0 -143
  107. package/dist/lib/liveProgress.js +0 -51
  108. package/dist/lib/liveProgress.test.js +0 -89
  109. package/dist/lib/mathUnicode.js +0 -685
  110. package/dist/lib/memory.js +0 -123
  111. package/dist/lib/memoryMonitor.js +0 -76
  112. package/dist/lib/messages.js +0 -3
  113. package/dist/lib/messages.test.js +0 -25
  114. package/dist/lib/osc52.js +0 -53
  115. package/dist/lib/perfPane.js +0 -94
  116. package/dist/lib/platform.js +0 -312
  117. package/dist/lib/precisionWheel.js +0 -25
  118. package/dist/lib/reasoning.js +0 -39
  119. package/dist/lib/rpc.js +0 -26
  120. package/dist/lib/subagentTree.js +0 -287
  121. package/dist/lib/syntax.js +0 -89
  122. package/dist/lib/terminalModes.js +0 -46
  123. package/dist/lib/terminalParity.js +0 -48
  124. package/dist/lib/terminalSetup.js +0 -321
  125. package/dist/lib/text.js +0 -203
  126. package/dist/lib/text.test.js +0 -18
  127. package/dist/lib/todo.js +0 -2
  128. package/dist/lib/todo.test.js +0 -22
  129. package/dist/lib/viewportStore.js +0 -82
  130. package/dist/lib/virtualHeights.js +0 -61
  131. package/dist/lib/wheelAccel.js +0 -143
  132. package/dist/theme.js +0 -398
  133. package/dist/types.js +0 -1
@@ -1,61 +0,0 @@
1
- import { transcriptBodyWidth } from './inputMetrics.js';
2
- import { boundedHistoryRenderText } from './text.js';
3
- const hashText = (text) => {
4
- let h = 5381;
5
- for (let i = 0; i < text.length; i++) {
6
- h = ((h << 5) + h) ^ text.charCodeAt(i);
7
- }
8
- return (h >>> 0).toString(36);
9
- };
10
- export const messageHeightKey = (msg) => {
11
- const todoSig = msg.todos?.map(t => `${t.status}:${t.content}`).join('\u0001') ?? '';
12
- const panelSig = msg.panelData?.sections
13
- .map(s => `${s.title ?? ''}:${s.text?.length ?? 0}:${s.items?.length ?? 0}:${s.rows?.length ?? 0}`)
14
- .join('\u0001') ?? '';
15
- const introSig = msg.kind === 'intro' ? (msg.info?.version ?? '') : '';
16
- return [
17
- msg.role,
18
- msg.kind ?? '',
19
- hashText([msg.text, msg.thinking ?? '', msg.tools?.join('\n') ?? '', todoSig, panelSig, introSig].join('\0'))
20
- ].join(':');
21
- };
22
- export const wrappedLines = (text, width) => {
23
- const w = Math.max(1, width);
24
- return text.split('\n').reduce((n, line) => n + Math.max(1, Math.ceil(line.length / w)), 0);
25
- };
26
- export const estimatedMsgHeight = (msg, cols, { compact, details, limitHistory = false, userPrompt = '', withSeparator = false }) => {
27
- if (msg.kind === 'intro') {
28
- return msg.info?.version ? 9 : 5;
29
- }
30
- if (msg.kind === 'panel') {
31
- return Math.max(3, (msg.panelData?.sections.length ?? 1) * 2 + 1);
32
- }
33
- if (msg.kind === 'trail' && msg.todos?.length) {
34
- if (msg.todoCollapsedByDefault) {
35
- return 2;
36
- }
37
- return Math.max(2, msg.todos.length + 2);
38
- }
39
- const bodyWidth = transcriptBodyWidth(cols, msg.role, userPrompt);
40
- const text = msg.role === 'assistant' && limitHistory ? boundedHistoryRenderText(msg.text) : msg.text;
41
- let h = wrappedLines(text || ' ', bodyWidth);
42
- if (!compact && msg.role === 'assistant') {
43
- h += Math.min(6, (text.match(/\n\s*\n/g) ?? []).length);
44
- }
45
- if (details) {
46
- h += (msg.tools?.length ?? 0) + wrappedLines(msg.thinking ?? '', bodyWidth);
47
- }
48
- if (msg.role === 'user' || msg.kind === 'diff') {
49
- h += 2;
50
- }
51
- else if (msg.kind === 'slash') {
52
- h++;
53
- }
54
- // Inter-turn separator above non-first user messages (1 rule row + 1
55
- // top-margin row). The render-side gate is in appLayout.tsx; we trust
56
- // the caller to pass `withSeparator` only when it matches that gate.
57
- if (withSeparator) {
58
- h += 2;
59
- }
60
- return Math.max(1, h);
61
- };
@@ -1,143 +0,0 @@
1
- // @ts-nocheck
2
- // SPDX-License-Identifier: MIT
3
- // Ported from CVC Agent (https://github.com/NousResearch/cvc)
4
- // Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
5
- // Wheel-scroll acceleration state machine.
6
- //
7
- // One event = 1 row feels sluggish on trackpads (200+ ev/s) and sustained
8
- // mouse-wheel; one event = 6 rows teleports and ruins precision.
9
- // Heuristic on inter-event gap + direction flips:
10
- //
11
- // gap < 5ms → same-batch burst → 1 row/event
12
- // gap < 40ms (native) → ramp +0.3, cap 6
13
- // gap 80-500ms (xterm.js) → mult = 1 + (mult-1)·0.5^(gap/150) + 5·decay
14
- // cap 3 slow / 6 fast
15
- // gap > 500ms → reset (deliberate click stays responsive)
16
- // flip + flip-back ≤200ms → encoder bounce → engage wheel-mode (sticky cap)
17
- // 5 consecutive <5ms events → trackpad flick → disengage wheel-mode
18
- //
19
- // Native terminals (Ghostty, iTerm2) and xterm.js embedders (VS Code,
20
- // Cursor) emit wheel events with different cadences, hence two paths.
21
- import { isXtermJs } from '@cvc/ink';
22
- // ── Native (ghostty, iTerm2, WezTerm, …) ───────────────────────────────
23
- const WHEEL_ACCEL_WINDOW_MS = 40;
24
- const WHEEL_ACCEL_STEP = 0.3;
25
- const WHEEL_ACCEL_MAX = 6;
26
- // ── Encoder bounce / wheel-mode (mechanical wheels) ────────────────────
27
- const WHEEL_BOUNCE_GAP_MAX_MS = 200;
28
- const WHEEL_MODE_STEP = 15;
29
- const WHEEL_MODE_CAP = 15;
30
- const WHEEL_MODE_RAMP = 3;
31
- const WHEEL_MODE_IDLE_DISENGAGE_MS = 1500;
32
- // ── xterm.js (VS Code / Cursor / browser terminals) ────────────────────
33
- const WHEEL_DECAY_HALFLIFE_MS = 150;
34
- const WHEEL_DECAY_STEP = 5;
35
- const WHEEL_BURST_MS = 5;
36
- const WHEEL_DECAY_GAP_MS = 80;
37
- const WHEEL_DECAY_CAP_SLOW = 3;
38
- const WHEEL_DECAY_CAP_FAST = 6;
39
- const WHEEL_DECAY_IDLE_MS = 500;
40
- export function initWheelAccel(xtermJs = false, base = 1) {
41
- return { burstCount: 0, base, dir: 0, frac: 0, mult: base, pendingFlip: false, time: 0, wheelMode: false, xtermJs };
42
- }
43
- /** CVC_TUI_SCROLL_SPEED (or CLAUDE_CODE_SCROLL_SPEED for portability).
44
- * Default 1, clamped (0, 20]. */
45
- export function readScrollSpeedBase() {
46
- const n = parseFloat(process.env.CVC_TUI_SCROLL_SPEED ?? process.env.CLAUDE_CODE_SCROLL_SPEED ?? '');
47
- return Number.isFinite(n) && n > 0 ? Math.min(n, 20) : 1;
48
- }
49
- export function initWheelAccelForHost() {
50
- return initWheelAccel(isXtermJs(), readScrollSpeedBase());
51
- }
52
- /** Compute rows for one wheel event, mutating `state`. Returns 0 when a
53
- * direction flip is deferred for bounce detection — call sites should
54
- * no-op on 0. */
55
- export function computeWheelStep(state, dir, now) {
56
- return state.xtermJs ? xtermJsStep(state, dir, now) : nativeStep(state, dir, now);
57
- }
58
- function nativeStep(state, dir, now) {
59
- // Idle disengage runs first so a pending bounce can't mask "user paused
60
- // 1.5s then mouse-clicked" as a real reversal.
61
- if (state.wheelMode && now - state.time > WHEEL_MODE_IDLE_DISENGAGE_MS) {
62
- state.wheelMode = false;
63
- state.burstCount = 0;
64
- state.mult = state.base;
65
- }
66
- if (state.pendingFlip) {
67
- state.pendingFlip = false;
68
- if (dir !== state.dir || now - state.time > WHEEL_BOUNCE_GAP_MAX_MS) {
69
- // Real reversal (flip persisted OR flip-back too late). Commit.
70
- // The deferred event's 1 row is lost — acceptable latency.
71
- state.dir = dir;
72
- state.time = now;
73
- state.mult = state.base;
74
- return Math.floor(state.mult);
75
- }
76
- state.wheelMode = true;
77
- }
78
- const gap = now - state.time;
79
- if (dir !== state.dir && state.dir !== 0) {
80
- state.pendingFlip = true;
81
- state.time = now;
82
- return 0;
83
- }
84
- state.dir = dir;
85
- state.time = now;
86
- if (state.wheelMode) {
87
- if (gap < WHEEL_BURST_MS) {
88
- // Same-batch burst (SGR proportional) OR trackpad flick. 1 row/event;
89
- // trackpad flick trips the burst-count disengage.
90
- if (++state.burstCount >= 5) {
91
- state.wheelMode = false;
92
- state.burstCount = 0;
93
- state.mult = state.base;
94
- }
95
- else {
96
- return 1;
97
- }
98
- }
99
- else {
100
- state.burstCount = 0;
101
- }
102
- }
103
- if (state.wheelMode) {
104
- const m = Math.pow(0.5, gap / WHEEL_DECAY_HALFLIFE_MS);
105
- const cap = Math.max(WHEEL_MODE_CAP, state.base * 2);
106
- const next = 1 + (state.mult - 1) * m + WHEEL_MODE_STEP * m;
107
- state.mult = Math.min(cap, next, state.mult + WHEEL_MODE_RAMP);
108
- return Math.floor(state.mult);
109
- }
110
- // Trackpad / hi-res native: tight 40ms window — sub-window ramps,
111
- // anything slower resets to baseline.
112
- if (gap > WHEEL_ACCEL_WINDOW_MS) {
113
- state.mult = state.base;
114
- }
115
- else {
116
- const cap = Math.max(WHEEL_ACCEL_MAX, state.base * 2);
117
- state.mult = Math.min(cap, state.mult + WHEEL_ACCEL_STEP);
118
- }
119
- return Math.floor(state.mult);
120
- }
121
- function xtermJsStep(state, dir, now) {
122
- const gap = now - state.time;
123
- const sameDir = dir === state.dir;
124
- state.time = now;
125
- state.dir = dir;
126
- if (sameDir && gap < WHEEL_BURST_MS) {
127
- return 1;
128
- }
129
- if (!sameDir || gap > WHEEL_DECAY_IDLE_MS) {
130
- // Reversal or long idle — start at 2 so first click after a pause moves visibly.
131
- state.mult = 2;
132
- state.frac = 0;
133
- }
134
- else {
135
- const m = Math.pow(0.5, gap / WHEEL_DECAY_HALFLIFE_MS);
136
- const cap = gap >= WHEEL_DECAY_GAP_MS ? WHEEL_DECAY_CAP_SLOW : WHEEL_DECAY_CAP_FAST;
137
- state.mult = Math.min(cap, 1 + (state.mult - 1) * m + WHEEL_DECAY_STEP * m);
138
- }
139
- const total = state.mult + state.frac;
140
- const rows = Math.floor(total);
141
- state.frac = total - rows;
142
- return rows;
143
- }
package/dist/theme.js DELETED
@@ -1,398 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- // Ported from CVC Agent (https://github.com/NousResearch/cvc)
3
- // Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
4
- // ── Color math ───────────────────────────────────────────────────────
5
- function parseHex(h) {
6
- const m = /^#?([0-9a-f]{6})$/i.exec(h);
7
- if (!m) {
8
- return null;
9
- }
10
- const n = parseInt(m[1], 16);
11
- return [(n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff];
12
- }
13
- function mix(a, b, t) {
14
- const pa = parseHex(a);
15
- const pb = parseHex(b);
16
- if (!pa || !pb) {
17
- return a;
18
- }
19
- const lerp = (i) => Math.round(pa[i] + (pb[i] - pa[i]) * t);
20
- return '#' + ((1 << 24) | (lerp(0) << 16) | (lerp(1) << 8) | lerp(2)).toString(16).slice(1);
21
- }
22
- const XTERM_6_LEVELS = [0, 95, 135, 175, 215, 255];
23
- const ANSI_LIGHT_MAX_LUMINANCE = 0.72;
24
- const ANSI_LIGHT_TARGET_LUMINANCE = 0.34;
25
- const ANSI_LIGHT_MIN_SATURATION = 0.22;
26
- const ANSI_MUTED_BUCKET = 245;
27
- const ANSI_NORMALIZED_FOREGROUNDS = [
28
- 'text',
29
- 'label',
30
- 'ok',
31
- 'error',
32
- 'warn',
33
- 'prompt',
34
- 'statusFg',
35
- 'statusGood',
36
- 'statusWarn',
37
- 'statusBad',
38
- 'statusCritical',
39
- 'shellDollar'
40
- ];
41
- const ANSI_MUTED_FOREGROUNDS = ['muted', 'sessionLabel', 'sessionBorder'];
42
- function xtermEightBitRgb(colorNumber) {
43
- if (colorNumber >= 232) {
44
- const value = 8 + (colorNumber - 232) * 10;
45
- return [value, value, value];
46
- }
47
- if (colorNumber >= 16) {
48
- const offset = colorNumber - 16;
49
- return [
50
- XTERM_6_LEVELS[Math.floor(offset / 36) % 6],
51
- XTERM_6_LEVELS[Math.floor(offset / 6) % 6],
52
- XTERM_6_LEVELS[offset % 6]
53
- ];
54
- }
55
- return [0, 0, 0];
56
- }
57
- function channelLuminance(value) {
58
- const normalized = value / 255;
59
- return normalized <= 0.03928 ? normalized / 12.92 : ((normalized + 0.055) / 1.055) ** 2.4;
60
- }
61
- function relativeLuminance(red, green, blue) {
62
- return 0.2126 * channelLuminance(red) + 0.7152 * channelLuminance(green) + 0.0722 * channelLuminance(blue);
63
- }
64
- function rgbToHsl(red, green, blue) {
65
- const rn = red / 255;
66
- const gn = green / 255;
67
- const bn = blue / 255;
68
- const max = Math.max(rn, gn, bn);
69
- const min = Math.min(rn, gn, bn);
70
- const lightness = (max + min) / 2;
71
- if (max === min) {
72
- return [0, 0, lightness];
73
- }
74
- const delta = max - min;
75
- const saturation = lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min);
76
- const hue = max === rn
77
- ? (gn - bn) / delta + (gn < bn ? 6 : 0)
78
- : max === gn
79
- ? (bn - rn) / delta + 2
80
- : (rn - gn) / delta + 4;
81
- return [hue / 6, saturation, lightness];
82
- }
83
- function circularDistance(a, b) {
84
- const distance = Math.abs(a - b);
85
- return Math.min(distance, 1 - distance);
86
- }
87
- // Mirrors @cvc/ink's colorize.ts. Keep local: app code compiles from
88
- // ui-tui/src, while @cvc/ink is bundled separately from packages/.
89
- function richEightBitColorNumber(red, green, blue) {
90
- const [, saturation, lightness] = rgbToHsl(red, green, blue);
91
- if (saturation < 0.15) {
92
- const gray = Math.round(lightness * 25);
93
- return gray === 0 ? 16 : gray === 25 ? 231 : 231 + gray;
94
- }
95
- const sixRed = red < 95 ? red / 95 : 1 + (red - 95) / 40;
96
- const sixGreen = green < 95 ? green / 95 : 1 + (green - 95) / 40;
97
- const sixBlue = blue < 95 ? blue / 95 : 1 + (blue - 95) / 40;
98
- return 16 + 36 * Math.round(sixRed) + 6 * Math.round(sixGreen) + Math.round(sixBlue);
99
- }
100
- function bestReadableAnsiColor(red, green, blue) {
101
- const [hue, saturation, lightness] = rgbToHsl(red, green, blue);
102
- let bestColor = richEightBitColorNumber(red, green, blue);
103
- let bestScore = Number.POSITIVE_INFINITY;
104
- for (let colorNumber = 16; colorNumber <= 255; colorNumber += 1) {
105
- const [candidateRed, candidateGreen, candidateBlue] = xtermEightBitRgb(colorNumber);
106
- const candidateLuminance = relativeLuminance(candidateRed, candidateGreen, candidateBlue);
107
- if (candidateLuminance > ANSI_LIGHT_MAX_LUMINANCE) {
108
- continue;
109
- }
110
- const [candidateHue, candidateSaturation, candidateLightness] = rgbToHsl(candidateRed, candidateGreen, candidateBlue);
111
- const saturationFloorPenalty = candidateSaturation < ANSI_LIGHT_MIN_SATURATION ? (ANSI_LIGHT_MIN_SATURATION - candidateSaturation) * 3 : 0;
112
- const score = circularDistance(candidateHue, hue) * 4 +
113
- Math.abs(candidateSaturation - Math.max(ANSI_LIGHT_MIN_SATURATION, saturation)) * 0.8 +
114
- Math.abs(candidateLightness - Math.min(lightness, ANSI_LIGHT_TARGET_LUMINANCE)) * 2 +
115
- saturationFloorPenalty;
116
- if (score < bestScore) {
117
- bestColor = colorNumber;
118
- bestScore = score;
119
- }
120
- }
121
- return bestColor;
122
- }
123
- function normalizeAnsiForeground(color) {
124
- const rgb = parseHex(color);
125
- if (!rgb) {
126
- return color;
127
- }
128
- const richAnsi = richEightBitColorNumber(rgb[0], rgb[1], rgb[2]);
129
- const richRgb = xtermEightBitRgb(richAnsi);
130
- const ansi = relativeLuminance(richRgb[0], richRgb[1], richRgb[2]) > ANSI_LIGHT_MAX_LUMINANCE
131
- ? bestReadableAnsiColor(rgb[0], rgb[1], rgb[2])
132
- : richAnsi;
133
- return `ansi256(${ansi})`;
134
- }
135
- // ── Defaults ─────────────────────────────────────────────────────────
136
- const BRAND = {
137
- name: 'CVC',
138
- icon: '◆',
139
- prompt: '❯',
140
- welcome: 'Type /help for commands · Ctrl+C to exit',
141
- goodbye: 'Goodbye! ◆',
142
- tool: '┊',
143
- helpHeader: '(^_^)? Commands'
144
- };
145
- const cleanPromptSymbol = (s, fallback) => {
146
- const cleaned = String(s ?? '')
147
- .replace(/\s+/g, ' ')
148
- .trim();
149
- return cleaned || fallback;
150
- };
151
- export const DARK_THEME = {
152
- color: {
153
- primary: '#e63946',
154
- accent: '#4a9eff',
155
- border: '#a4202c',
156
- text: '#FFF8DC',
157
- muted: '#8B7070',
158
- completionBg: '#0d1b2a',
159
- completionCurrentBg: '#1b2d44',
160
- completionMetaBg: '#0d1b2a',
161
- completionMetaCurrentBg: '#1b2d44',
162
- label: '#e63946',
163
- ok: '#4caf50',
164
- error: '#ef5350',
165
- warn: '#ffa726',
166
- prompt: '#FFF8DC',
167
- sessionLabel: '#8B7070',
168
- sessionBorder: '#8B7070',
169
- statusBg: '#0d1b2a',
170
- statusFg: '#C0C0C0',
171
- statusGood: '#8FBC8F',
172
- statusWarn: '#e63946',
173
- statusBad: '#FF8C00',
174
- statusCritical: '#FF6B6B',
175
- selectionBg: '#1b2d44',
176
- diffAdded: 'rgb(220,255,220)',
177
- diffRemoved: 'rgb(255,220,220)',
178
- diffAddedWord: 'rgb(36,138,61)',
179
- diffRemovedWord: 'rgb(207,34,46)',
180
- shellDollar: '#4a9eff'
181
- },
182
- brand: BRAND,
183
- bannerLogo: '',
184
- bannerHero: ''
185
- };
186
- // Light-terminal palette: deeper CVC reds + navy accents for legibility on
187
- // white backgrounds. Same shape as DARK_THEME so `fromSkin` still layers on
188
- // top cleanly.
189
- export const LIGHT_THEME = {
190
- color: {
191
- primary: '#a4202c',
192
- accent: '#1f5dab',
193
- border: '#7a1820',
194
- text: '#1a1a2e',
195
- muted: '#5a4a4a',
196
- completionBg: '#F5F5F5',
197
- completionCurrentBg: mix('#F5F5F5', '#1f5dab', 0.25),
198
- completionMetaBg: '#F5F5F5',
199
- completionMetaCurrentBg: mix('#F5F5F5', '#1f5dab', 0.25),
200
- label: '#a4202c',
201
- ok: '#2E7D32',
202
- error: '#C62828',
203
- warn: '#E65100',
204
- prompt: '#1a1a2e',
205
- sessionLabel: '#5a4a4a',
206
- sessionBorder: '#5a4a4a',
207
- statusBg: '#F5F5F5',
208
- statusFg: '#333333',
209
- statusGood: '#2E7D32',
210
- statusWarn: '#a4202c',
211
- statusBad: '#D84315',
212
- statusCritical: '#B71C1C',
213
- selectionBg: '#D4E4F7',
214
- diffAdded: 'rgb(200,240,200)',
215
- diffRemoved: 'rgb(240,200,200)',
216
- diffAddedWord: 'rgb(27,94,32)',
217
- diffRemovedWord: 'rgb(183,28,28)',
218
- shellDollar: '#1565C0'
219
- },
220
- brand: BRAND,
221
- bannerLogo: '',
222
- bannerHero: ''
223
- };
224
- const TRUE_RE = /^(?:1|true|yes|on)$/;
225
- const FALSE_RE = /^(?:0|false|no|off)$/;
226
- // TERM_PROGRAM fallback allow-list for terminals whose default profile is
227
- // light and which may not expose COLORFGBG. This currently includes Apple
228
- // Terminal. Explicit CVC_TUI_THEME / COLORFGBG signals above still win,
229
- // so dark Apple Terminal profiles that advertise a dark background stay dark.
230
- const LIGHT_DEFAULT_TERM_PROGRAMS = new Set(['Apple_Terminal']);
231
- // Best-effort RGB → luminance check. Currently only accepts a 3- or
232
- // 6-digit hex value (with or without a leading `#`); the env var name
233
- // `CVC_TUI_BACKGROUND` is intentionally generic so a future OSC11
234
- // query helper can cache its answer there too, but additional formats
235
- // (rgb()/hsl()/named colours) would need explicit parsing here first.
236
- const LUMA_LIGHT_THRESHOLD = 0.6;
237
- // Strict allow-list: parseInt(..., 16) silently truncates at the first
238
- // non-hex character (e.g. `fffgff` would parse as `fff` and yield a
239
- // false-positive "white" reading), so reject anything that doesn't match
240
- // the canonical 3- or 6-digit shape up front.
241
- const HEX_3_RE = /^[0-9a-f]{3}$/;
242
- const HEX_6_RE = /^[0-9a-f]{6}$/;
243
- function backgroundLuminance(raw) {
244
- const v = raw.trim().toLowerCase();
245
- if (!v) {
246
- return null;
247
- }
248
- const hex = v.startsWith('#') ? v.slice(1) : v;
249
- const rgb = HEX_6_RE.test(hex)
250
- ? [parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16), parseInt(hex.slice(4, 6), 16)]
251
- : HEX_3_RE.test(hex)
252
- ? [parseInt(hex[0] + hex[0], 16), parseInt(hex[1] + hex[1], 16), parseInt(hex[2] + hex[2], 16)]
253
- : null;
254
- if (!rgb) {
255
- return null;
256
- }
257
- // Rec. 709 luma — close enough for "is this background bright".
258
- return (0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]) / 255;
259
- }
260
- // Pick light vs dark with ordered, explainable signals (#11300):
261
- //
262
- // 1. `CVC_TUI_LIGHT` boolean — `1`/`true`/`yes`/`on` → light;
263
- // `0`/`false`/`no`/`off` → dark. Either explicit value wins
264
- // regardless of any later signal.
265
- // 2. `CVC_TUI_THEME` named override — `light` / `dark` win over
266
- // every signal below.
267
- // 3. `CVC_TUI_BACKGROUND` hex hint (3- or 6-digit) — luminance
268
- // ≥ LUMA_LIGHT_THRESHOLD → light.
269
- // 4. `COLORFGBG` last field — XFCE / rxvt / Terminal.app emit
270
- // slot 7 or 15 on light profiles; 0–15 ranges are otherwise
271
- // treated as authoritatively dark so the TERM_PROGRAM
272
- // allow-list below cannot override an explicit dark profile.
273
- // 5. `TERM_PROGRAM` light-default allow-list.
274
- //
275
- // Anything we can't decide stays dark — the default CVC palette
276
- // is the dark one.
277
- export function detectLightMode(env = process.env,
278
- // Injectable so tests can prove the COLORFGBG-over-TERM_PROGRAM
279
- // precedence rule even though the production allow-list is empty.
280
- lightDefaultTermPrograms = LIGHT_DEFAULT_TERM_PROGRAMS) {
281
- const lightFlag = (env.CVC_TUI_LIGHT ?? '').trim().toLowerCase();
282
- if (TRUE_RE.test(lightFlag)) {
283
- return true;
284
- }
285
- if (FALSE_RE.test(lightFlag)) {
286
- return false;
287
- }
288
- const themeFlag = (env.CVC_TUI_THEME ?? '').trim().toLowerCase();
289
- if (themeFlag === 'light') {
290
- return true;
291
- }
292
- if (themeFlag === 'dark') {
293
- return false;
294
- }
295
- const bgHint = backgroundLuminance(env.CVC_TUI_BACKGROUND ?? '');
296
- if (bgHint !== null) {
297
- return bgHint >= LUMA_LIGHT_THRESHOLD;
298
- }
299
- const colorfgbg = (env.COLORFGBG ?? '').trim();
300
- if (colorfgbg) {
301
- // Validate as a decimal integer before coercing — `Number('')` is 0,
302
- // so a malformed `COLORFGBG='15;'` would otherwise look like an
303
- // authoritative dark slot and incorrectly block the TERM_PROGRAM
304
- // allow-list. Anything that isn't pure digits falls through.
305
- const lastField = colorfgbg.split(';').at(-1) ?? '';
306
- if (/^\d+$/.test(lastField)) {
307
- const bg = Number(lastField);
308
- if (bg === 7 || bg === 15) {
309
- return true;
310
- }
311
- // Slots 0–6 and 8–14 are the dark half of the 0–15 ANSI range.
312
- // When COLORFGBG is set we trust it as authoritative — a non-light
313
- // value here shouldn't get overridden by the TERM_PROGRAM allow-list.
314
- if (bg >= 0 && bg < 16) {
315
- return false;
316
- }
317
- }
318
- }
319
- const termProgram = (env.TERM_PROGRAM ?? '').trim();
320
- return lightDefaultTermPrograms.has(termProgram);
321
- }
322
- function shouldNormalizeAnsiLightTheme(env = process.env, isLight = detectLightMode(env)) {
323
- const colorTerm = (env.COLORTERM ?? '').trim().toLowerCase();
324
- const termProgram = (env.TERM_PROGRAM ?? '').trim();
325
- return termProgram === 'Apple_Terminal' && colorTerm !== 'truecolor' && colorTerm !== '24bit' && isLight;
326
- }
327
- export function normalizeThemeForAnsiLightTerminal(theme, env = process.env, isLight = detectLightMode(env)) {
328
- if (!shouldNormalizeAnsiLightTheme(env, isLight)) {
329
- return theme;
330
- }
331
- const color = { ...theme.color };
332
- for (const key of ANSI_NORMALIZED_FOREGROUNDS) {
333
- color[key] = normalizeAnsiForeground(color[key]);
334
- }
335
- for (const key of ANSI_MUTED_FOREGROUNDS) {
336
- color[key] = `ansi256(${ANSI_MUTED_BUCKET})`;
337
- }
338
- return { ...theme, color };
339
- }
340
- const DEFAULT_LIGHT_MODE = detectLightMode();
341
- export const DEFAULT_THEME = normalizeThemeForAnsiLightTerminal(DEFAULT_LIGHT_MODE ? LIGHT_THEME : DARK_THEME, process.env, DEFAULT_LIGHT_MODE);
342
- // ── Skin → Theme ─────────────────────────────────────────────────────
343
- export function fromSkin(colors, branding, bannerLogo = '', bannerHero = '', toolPrefix = '', helpHeader = '') {
344
- const d = DEFAULT_THEME;
345
- const c = (k) => colors[k];
346
- const hasSkinColors = Object.keys(colors).length > 0;
347
- const accent = c('ui_accent') ?? c('banner_accent') ?? d.color.accent;
348
- const bannerAccent = c('banner_accent') ?? c('banner_title') ?? d.color.accent;
349
- const muted = c('banner_dim') ?? d.color.muted;
350
- const completionBg = c('completion_menu_bg') ?? d.color.completionBg;
351
- const completionCurrentBg = c('completion_menu_current_bg') ??
352
- (hasSkinColors ? mix(completionBg, bannerAccent, 0.25) : d.color.completionCurrentBg);
353
- const completionMetaBg = c('completion_menu_meta_bg') ?? completionBg;
354
- const completionMetaCurrentBg = c('completion_menu_meta_current_bg') ?? completionCurrentBg;
355
- return normalizeThemeForAnsiLightTerminal({
356
- color: {
357
- primary: c('ui_primary') ?? c('banner_title') ?? d.color.primary,
358
- accent,
359
- border: c('ui_border') ?? c('banner_border') ?? d.color.border,
360
- text: c('ui_text') ?? c('banner_text') ?? d.color.text,
361
- muted,
362
- completionBg,
363
- completionCurrentBg,
364
- completionMetaBg,
365
- completionMetaCurrentBg,
366
- label: c('ui_label') ?? d.color.label,
367
- ok: c('ui_ok') ?? d.color.ok,
368
- error: c('ui_error') ?? d.color.error,
369
- warn: c('ui_warn') ?? d.color.warn,
370
- prompt: c('prompt') ?? c('banner_text') ?? d.color.prompt,
371
- sessionLabel: c('session_label') ?? muted,
372
- sessionBorder: c('session_border') ?? muted,
373
- statusBg: d.color.statusBg,
374
- statusFg: d.color.statusFg,
375
- statusGood: c('ui_ok') ?? d.color.statusGood,
376
- statusWarn: c('ui_warn') ?? d.color.statusWarn,
377
- statusBad: d.color.statusBad,
378
- statusCritical: d.color.statusCritical,
379
- selectionBg: c('selection_bg') ?? c('completion_menu_current_bg') ?? (hasSkinColors ? completionCurrentBg : d.color.selectionBg),
380
- diffAdded: d.color.diffAdded,
381
- diffRemoved: d.color.diffRemoved,
382
- diffAddedWord: d.color.diffAddedWord,
383
- diffRemovedWord: d.color.diffRemovedWord,
384
- shellDollar: c('shell_dollar') ?? d.color.shellDollar
385
- },
386
- brand: {
387
- name: branding.agent_name ?? d.brand.name,
388
- icon: d.brand.icon,
389
- prompt: cleanPromptSymbol(branding.prompt_symbol, d.brand.prompt),
390
- welcome: branding.welcome ?? d.brand.welcome,
391
- goodbye: branding.goodbye ?? d.brand.goodbye,
392
- tool: toolPrefix || d.brand.tool,
393
- helpHeader: branding.help_header ?? (helpHeader || d.brand.helpHeader)
394
- },
395
- bannerLogo,
396
- bannerHero
397
- }, process.env, DEFAULT_LIGHT_MODE);
398
- }
package/dist/types.js DELETED
@@ -1 +0,0 @@
1
- export {};