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,126 +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
- import { execFile, spawn } from 'node:child_process';
6
- import { promisify } from 'node:util';
7
- const execFileAsync = promisify(execFile);
8
- const CLIPBOARD_MAX_BUFFER = 4 * 1024 * 1024;
9
- const POWERSHELL_ARGS = ['-NoProfile', '-NonInteractive', '-Command', 'Get-Clipboard -Raw'];
10
- export function isUsableClipboardText(text) {
11
- if (!text || !/[^\s]/.test(text)) {
12
- return false;
13
- }
14
- if (text.includes('\u0000')) {
15
- return false;
16
- }
17
- let suspicious = 0;
18
- for (const ch of text) {
19
- const code = ch.charCodeAt(0);
20
- const isControl = code < 0x20 && ch !== '\n' && ch !== '\r' && ch !== '\t';
21
- if (isControl || ch === '\ufffd') {
22
- suspicious += 1;
23
- }
24
- }
25
- return suspicious <= Math.max(2, Math.floor(text.length * 0.02));
26
- }
27
- function readClipboardCommands(platform, env) {
28
- if (platform === 'darwin') {
29
- return [{ cmd: 'pbpaste', args: [] }];
30
- }
31
- if (platform === 'win32') {
32
- return [{ cmd: 'powershell', args: POWERSHELL_ARGS }];
33
- }
34
- const attempts = [];
35
- if (env.WSL_INTEROP || env.WSL_DISTRO_NAME) {
36
- attempts.push({ cmd: 'powershell.exe', args: POWERSHELL_ARGS });
37
- }
38
- if (env.WAYLAND_DISPLAY) {
39
- attempts.push({ cmd: 'wl-paste', args: ['--type', 'text'] });
40
- }
41
- attempts.push({ cmd: 'xclip', args: ['-selection', 'clipboard', '-out'] });
42
- return attempts;
43
- }
44
- /**
45
- * Read plain text from the system clipboard.
46
- *
47
- * Uses native platform tools in fallback order:
48
- * - macOS: pbpaste
49
- * - Windows: PowerShell Get-Clipboard -Raw
50
- * - WSL: powershell.exe Get-Clipboard -Raw
51
- * - Linux Wayland: wl-paste --type text
52
- * - Linux X11: xclip -selection clipboard -out
53
- */
54
- export async function readClipboardText(platform = process.platform, run = execFileAsync, env = process.env) {
55
- for (const attempt of readClipboardCommands(platform, env)) {
56
- try {
57
- const result = await run(attempt.cmd, [...attempt.args], {
58
- encoding: 'utf8',
59
- maxBuffer: CLIPBOARD_MAX_BUFFER,
60
- windowsHide: true
61
- });
62
- if (typeof result.stdout === 'string') {
63
- return result.stdout;
64
- }
65
- }
66
- catch {
67
- // Fall through to the next clipboard backend.
68
- }
69
- }
70
- return null;
71
- }
72
- function writeClipboardCommands(platform, env) {
73
- if (platform === 'darwin') {
74
- return [{ cmd: 'pbcopy', args: [] }];
75
- }
76
- if (platform === 'win32') {
77
- return [{ cmd: 'powershell', args: ['-NoProfile', '-NonInteractive', '-Command', 'Set-Clipboard -Value $input'] }];
78
- }
79
- const attempts = [];
80
- if (env.WSL_INTEROP || env.WSL_DISTRO_NAME) {
81
- attempts.push({
82
- cmd: 'powershell.exe',
83
- args: ['-NoProfile', '-NonInteractive', '-Command', 'Set-Clipboard -Value $input']
84
- });
85
- }
86
- if (env.WAYLAND_DISPLAY) {
87
- attempts.push({ cmd: 'wl-copy', args: ['--type', 'text/plain'] });
88
- }
89
- attempts.push({ cmd: 'xclip', args: ['-selection', 'clipboard', '-in'] });
90
- attempts.push({ cmd: 'xsel', args: ['--clipboard', '--input'] });
91
- return attempts;
92
- }
93
- /**
94
- * Write plain text to the system clipboard.
95
- *
96
- * Tries native platform tools in fallback order:
97
- * - macOS: pbcopy
98
- * - Windows: PowerShell Set-Clipboard
99
- * - WSL: powershell.exe Set-Clipboard
100
- * - Linux Wayland: wl-copy --type text/plain
101
- * - Linux X11: xclip -selection clipboard -in
102
- * - Linux X11 alt: xsel --clipboard --input
103
- *
104
- * Returns true if at least one backend succeeded, false otherwise
105
- * (callers should fall back to OSC52 on false).
106
- */
107
- export async function writeClipboardText(text, platform = process.platform, start = spawn, env = process.env) {
108
- const candidates = writeClipboardCommands(platform, env);
109
- for (const { cmd, args } of candidates) {
110
- try {
111
- const ok = await new Promise(resolve => {
112
- const child = start(cmd, [...args], { stdio: ['pipe', 'ignore', 'ignore'], windowsHide: true });
113
- child.once('error', () => resolve(false));
114
- child.once('close', code => resolve(code === 0));
115
- child.stdin?.end(text);
116
- });
117
- if (ok) {
118
- return true;
119
- }
120
- }
121
- catch {
122
- // Fall through to the next clipboard backend.
123
- }
124
- }
125
- return false;
126
- }
@@ -1,41 +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
- import { accessSync, constants } from 'node:fs';
6
- import { delimiter, join } from 'node:path';
7
- /**
8
- * Editor fallback chain when neither $VISUAL nor $EDITOR is set. Mirrors
9
- * prompt_toolkit's `Buffer.open_in_editor()` picker so the classic CLI and
10
- * the TUI launch the same editor on a given box.
11
- */
12
- const FALLBACKS = ['editor', 'nano', 'pico', 'vi', 'emacs'];
13
- const isExecutable = (path) => {
14
- try {
15
- accessSync(path, constants.X_OK);
16
- return true;
17
- }
18
- catch {
19
- return false;
20
- }
21
- };
22
- /**
23
- * Resolve the editor invocation argv (without the file argument).
24
- *
25
- * 1. $VISUAL / $EDITOR, shell-tokenized so `EDITOR="code --wait"` works
26
- * 2. on POSIX: first FALLBACKS entry resolvable on $PATH
27
- * 3. on Windows: `notepad.exe`
28
- * 4. literal `['vi']` as the last-resort POSIX floor
29
- */
30
- export const resolveEditor = (env = process.env, platform = process.platform) => {
31
- const explicit = env.VISUAL ?? env.EDITOR;
32
- if (explicit?.trim()) {
33
- return explicit.trim().split(/\s+/);
34
- }
35
- if (platform === 'win32') {
36
- return ['notepad.exe'];
37
- }
38
- const dirs = (env.PATH ?? '').split(delimiter).filter(Boolean);
39
- const found = FALLBACKS.flatMap(name => dirs.map(d => join(d, name))).find(isExecutable);
40
- return [found ?? 'vi'];
41
- };
@@ -1,58 +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
- import { chmodSync, mkdtempSync, writeFileSync } from 'node:fs';
6
- import { tmpdir } from 'node:os';
7
- import { delimiter, join } from 'node:path';
8
- import { beforeEach, describe, expect, it } from 'vitest';
9
- import { resolveEditor } from './editor.js';
10
- const exe = (dir, name) => {
11
- const path = join(dir, name);
12
- writeFileSync(path, '#!/bin/sh\nexit 0\n');
13
- chmodSync(path, 0o755);
14
- return path;
15
- };
16
- describe('resolveEditor', () => {
17
- let dir;
18
- beforeEach(() => {
19
- dir = mkdtempSync(join(tmpdir(), 'editor-test-'));
20
- });
21
- it('honors $VISUAL above all else', () => {
22
- expect(resolveEditor({ EDITOR: 'vim', PATH: dir, VISUAL: 'helix' })).toEqual(['helix']);
23
- });
24
- it('falls back to $EDITOR when $VISUAL is unset', () => {
25
- expect(resolveEditor({ EDITOR: 'nvim', PATH: dir })).toEqual(['nvim']);
26
- });
27
- it('shell-tokenizes editors with arguments', () => {
28
- expect(resolveEditor({ EDITOR: 'code --wait', PATH: dir })).toEqual(['code', '--wait']);
29
- expect(resolveEditor({ PATH: dir, VISUAL: 'emacsclient -t' })).toEqual(['emacsclient', '-t']);
30
- });
31
- it('ignores whitespace-only env vars', () => {
32
- const expected = exe(dir, 'editor');
33
- expect(resolveEditor({ EDITOR: ' ', PATH: dir, VISUAL: '' })).toEqual([expected]);
34
- });
35
- it('prefers `editor` over nano over vi on $PATH', () => {
36
- exe(dir, 'nano');
37
- exe(dir, 'vi');
38
- const expected = exe(dir, 'editor');
39
- expect(resolveEditor({ PATH: dir })).toEqual([expected]);
40
- });
41
- it('falls back to nano before vi when both exist', () => {
42
- exe(dir, 'vi');
43
- const expected = exe(dir, 'nano');
44
- expect(resolveEditor({ PATH: dir })).toEqual([expected]);
45
- });
46
- it('returns ["vi"] when $PATH is empty', () => {
47
- expect(resolveEditor({ PATH: '' })).toEqual(['vi']);
48
- });
49
- it('walks multi-entry $PATH', () => {
50
- const a = mkdtempSync(join(tmpdir(), 'editor-a-'));
51
- const b = mkdtempSync(join(tmpdir(), 'editor-b-'));
52
- const expected = exe(b, 'editor');
53
- expect(resolveEditor({ PATH: [a, b].join(delimiter) })).toEqual([expected]);
54
- });
55
- it('uses notepad.exe on Windows when no env override', () => {
56
- expect(resolveEditor({ PATH: dir }, 'win32')).toEqual(['notepad.exe']);
57
- });
58
- });
package/dist/lib/emoji.js DELETED
@@ -1,49 +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
- const VS15 = 0xfe0e;
6
- const VS16 = 0xfe0f;
7
- const KEYCAP = 0x20e3;
8
- const TEXT_DEFAULT_EMOJI = new Set([
9
- 0x00a9, 0x00ae, 0x203c, 0x2049, 0x2122, 0x2139, 0x2194, 0x2195, 0x2196, 0x2197, 0x2198, 0x2199, 0x21a9, 0x21aa,
10
- 0x2328, 0x23cf, 0x23ed, 0x23ee, 0x23ef, 0x23f1, 0x23f2, 0x23f8, 0x23f9, 0x23fa, 0x24c2, 0x25aa, 0x25ab, 0x25b6,
11
- 0x25c0, 0x25fb, 0x25fc, 0x2600, 0x2601, 0x2602, 0x2603, 0x2604, 0x260e, 0x2611, 0x2618, 0x261d, 0x2620, 0x2622,
12
- 0x2623, 0x2626, 0x262a, 0x262e, 0x262f, 0x2638, 0x2639, 0x263a, 0x2640, 0x2642, 0x265f, 0x2660, 0x2663, 0x2665,
13
- 0x2666, 0x2668, 0x267b, 0x267e, 0x2692, 0x2694, 0x2695, 0x2696, 0x2697, 0x2699, 0x269b, 0x269c, 0x26a0, 0x26a7,
14
- 0x26b0, 0x26b1, 0x26c8, 0x26cf, 0x26d1, 0x26d3, 0x26d4, 0x26e9, 0x26f0, 0x26f1, 0x26f4, 0x26f7, 0x26f8, 0x26f9,
15
- 0x2702, 0x2708, 0x2709, 0x270c, 0x270d, 0x270f, 0x2712, 0x2714, 0x2716, 0x271d, 0x2721, 0x2733, 0x2734, 0x2744,
16
- 0x2747, 0x2763, 0x2764, 0x27a1, 0x2934, 0x2935, 0x2b05, 0x2b06, 0x2b07, 0x3030, 0x303d, 0x3297, 0x3299
17
- ]);
18
- const MAYBE_TEXT_EMOJI_RE = /[\u00a9\u00ae\u203c\u2049\u2122\u2139\u2194-\u2199\u21a9\u21aa\u2328\u23cf\u23ed-\u23ef\u23f1\u23f2\u23f8-\u23fa\u24c2\u25aa\u25ab\u25b6\u25c0\u25fb\u25fc\u2600-\u2604\u260e\u2611\u2618\u261d\u2620\u2622\u2623\u2626\u262a\u262e\u262f\u2638-\u263a\u2640\u2642\u265f\u2660\u2663\u2665\u2666\u2668\u267b\u267e\u2692\u2694-\u2697\u2699\u269b\u269c\u26a0\u26a7\u26b0\u26b1\u26c8\u26cf\u26d1\u26d3\u26d4\u26e9\u26f0\u26f1\u26f4\u26f7-\u26f9\u2702\u2708\u2709\u270c\u270d\u270f\u2712\u2714\u2716\u271d\u2721\u2733\u2734\u2744\u2747\u2763\u2764\u27a1\u2934\u2935\u2b05-\u2b07\u3030\u303d\u3297\u3299]/;
19
- export function ensureEmojiPresentation(text) {
20
- if (!text || !MAYBE_TEXT_EMOJI_RE.test(text)) {
21
- return text;
22
- }
23
- // Lazy output: only start building when we actually need to insert VS16.
24
- // Short-circuits the whole walk for strings where every text-default emoji
25
- // is already followed by VS16/VS15, avoiding per-codepoint string growth.
26
- let out = null;
27
- let last = 0;
28
- let i = 0;
29
- while (i < text.length) {
30
- const cp = text.codePointAt(i);
31
- const size = cp > 0xffff ? 2 : 1;
32
- if (TEXT_DEFAULT_EMOJI.has(cp)) {
33
- const next = text.codePointAt(i + size);
34
- // Skip only when the sequence already carries an explicit presentation
35
- // selector. VS16 means the user (or a prior pass) already requested
36
- // emoji presentation; VS15 is an explicit text-presentation request so
37
- // leave it alone and don't pile VS16 on top of it. Inject before ZWJ
38
- // and KEYCAP so ZWJ-joined sequences (e.g. ❤️‍🔥) and digit keycaps
39
- // both render as emoji rather than text.
40
- if (next !== VS16 && next !== VS15) {
41
- out ??= '';
42
- out += text.slice(last, i + size) + '\uFE0F';
43
- last = i + size;
44
- }
45
- }
46
- i += size;
47
- }
48
- return out === null ? text : out + text.slice(last);
49
- }
@@ -1,11 +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
- import { spawn } from 'node:child_process';
6
- const resolveHermesBin = () => process.env.HERMES_BIN?.trim() || 'hermes';
7
- export const launchHermesCommand = (args) => new Promise(resolve => {
8
- const child = spawn(resolveHermesBin(), args, { stdio: 'inherit' });
9
- child.on('error', err => resolve({ code: null, error: err.message }));
10
- child.on('exit', code => resolve({ code }));
11
- });
@@ -1,26 +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
- /**
6
- * Targeted 24-bit truecolor override before chalk / supports-color imports.
7
- *
8
- * macOS Terminal.app before Tahoe 26 does not support RGB SGR, so do not
9
- * infer truecolor from TERM_PROGRAM=Apple_Terminal. Users can still opt in
10
- * explicitly on terminals that support RGB but do not advertise COLORTERM.
11
- */
12
- const TRUE_RE = /^(?:1|true|yes|on)$/i;
13
- const FALSE_RE = /^(?:0|false|no|off)$/i;
14
- export function shouldForceTruecolor(env = process.env) {
15
- const override = (env.CVC_TUI_TRUECOLOR ?? '').trim();
16
- if (FALSE_RE.test(override) || 'NO_COLOR' in env) {
17
- return false;
18
- }
19
- return TRUE_RE.test(override);
20
- }
21
- if (shouldForceTruecolor()) {
22
- if (!process.env.COLORTERM) {
23
- process.env.COLORTERM = 'truecolor';
24
- }
25
- process.env.FORCE_COLOR = '3';
26
- }
@@ -1,36 +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
- // Tiny FPS tracker fed by ink's onFrame callback. Each entry is an Ink
6
- // frame (React commit + drain-only frames) — the right notion for
7
- // user-perceived motion.
8
- //
9
- // Zero-cost when CVC_TUI_FPS is unset: trackFrame is undefined so the
10
- // onFrame callback short-circuits at the optional chain.
11
- import { atom } from 'nanostores';
12
- import { SHOW_FPS } from '../config/env.js';
13
- const WINDOW_SIZE = 30;
14
- export const $fpsState = atom({ fps: 0, lastDurationMs: 0, totalFrames: 0 });
15
- const timestamps = [];
16
- let totalFrames = 0;
17
- export const trackFrame = SHOW_FPS
18
- ? (durationMs) => {
19
- timestamps.push(performance.now());
20
- if (timestamps.length > WINDOW_SIZE) {
21
- timestamps.shift();
22
- }
23
- totalFrames++;
24
- if (timestamps.length < 2) {
25
- return;
26
- }
27
- const elapsed = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000;
28
- if (elapsed > 0) {
29
- $fpsState.set({
30
- fps: Math.round(((timestamps.length - 1) / elapsed) * 10) / 10,
31
- lastDurationMs: Math.round(durationMs * 100) / 100,
32
- totalFrames
33
- });
34
- }
35
- }
36
- : undefined;
@@ -1,29 +0,0 @@
1
- const SIGNAL_EXIT_CODE = {
2
- SIGHUP: 129,
3
- SIGINT: 130,
4
- SIGTERM: 143
5
- };
6
- let wired = false;
7
- export function setupGracefulExit({ cleanups = [], failsafeMs = 4000, onError, onSignal } = {}) {
8
- if (wired) {
9
- return;
10
- }
11
- wired = true;
12
- let shuttingDown = false;
13
- const exit = (code, signal) => {
14
- if (shuttingDown) {
15
- return;
16
- }
17
- shuttingDown = true;
18
- if (signal) {
19
- onSignal?.(signal);
20
- }
21
- setTimeout(() => process.exit(code), failsafeMs).unref?.();
22
- void Promise.allSettled(cleanups.map(fn => Promise.resolve().then(fn))).finally(() => process.exit(code));
23
- };
24
- for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
25
- process.on(sig, () => exit(SIGNAL_EXIT_CODE[sig], sig));
26
- }
27
- process.on('uncaughtException', err => onError?.('uncaughtException', err));
28
- process.on('unhandledRejection', reason => onError?.('unhandledRejection', reason));
29
- }
@@ -1,69 +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
- import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
6
- import { homedir } from 'node:os';
7
- import { join } from 'node:path';
8
- const MAX = 1000;
9
- const dir = process.env.HERMES_HOME ?? join(homedir(), '.hermes');
10
- const file = join(dir, '.hermes_history');
11
- let cache = null;
12
- export function load() {
13
- if (cache) {
14
- return cache;
15
- }
16
- try {
17
- if (!existsSync(file)) {
18
- cache = [];
19
- return cache;
20
- }
21
- const entries = [];
22
- let current = [];
23
- for (const line of readFileSync(file, 'utf8').split('\n')) {
24
- if (line.startsWith('+')) {
25
- current.push(line.slice(1));
26
- }
27
- else if (current.length) {
28
- entries.push(current.join('\n'));
29
- current = [];
30
- }
31
- }
32
- if (current.length) {
33
- entries.push(current.join('\n'));
34
- }
35
- cache = entries.slice(-MAX);
36
- }
37
- catch {
38
- cache = [];
39
- }
40
- return cache;
41
- }
42
- export function append(line) {
43
- const trimmed = line.trim();
44
- if (!trimmed) {
45
- return;
46
- }
47
- const items = load();
48
- if (items.at(-1) === trimmed) {
49
- return;
50
- }
51
- items.push(trimmed);
52
- if (items.length > MAX) {
53
- items.splice(0, items.length - MAX);
54
- }
55
- try {
56
- if (!existsSync(dir)) {
57
- mkdirSync(dir, { recursive: true });
58
- }
59
- const ts = new Date().toISOString().replace('T', ' ').replace('Z', '');
60
- const encoded = trimmed
61
- .split('\n')
62
- .map(l => `+${l}`)
63
- .join('\n');
64
- appendFileSync(file, `\n# ${ts}\n${encoded}\n`);
65
- }
66
- catch {
67
- void 0;
68
- }
69
- }
@@ -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
- import { stringWidth } from '@cvc/ink';
6
- export const COMPOSER_PROMPT_GAP_WIDTH = 1;
7
- let _seg = null;
8
- const seg = () => (_seg ??= new Intl.Segmenter(undefined, { granularity: 'grapheme' }));
9
- const isWhitespace = (value) => /\s/.test(value);
10
- const graphemes = (value) => [...seg().segment(value)].map(({ segment, index }) => ({
11
- end: index + segment.length,
12
- index,
13
- segment,
14
- width: Math.max(1, stringWidth(segment))
15
- }));
16
- function visualLines(value, cols) {
17
- const width = Math.max(1, cols);
18
- const lines = [];
19
- let sourceLineStart = 0;
20
- for (const sourceLine of value.split('\n')) {
21
- const parts = graphemes(sourceLine);
22
- if (!parts.length) {
23
- lines.push({ start: sourceLineStart, end: sourceLineStart });
24
- sourceLineStart += 1;
25
- continue;
26
- }
27
- let lineStartPart = 0;
28
- let lineStartOffset = sourceLineStart;
29
- let column = 0;
30
- let breakPart = null;
31
- let i = 0;
32
- while (i < parts.length) {
33
- const part = parts[i];
34
- const partStart = sourceLineStart + part.index;
35
- if (column + part.width > width && i > lineStartPart) {
36
- if (breakPart !== null && breakPart > lineStartPart) {
37
- const breakOffset = sourceLineStart + parts[breakPart - 1].end;
38
- lines.push({ start: lineStartOffset, end: breakOffset });
39
- lineStartPart = breakPart;
40
- lineStartOffset = breakOffset;
41
- }
42
- else {
43
- lines.push({ start: lineStartOffset, end: partStart });
44
- lineStartPart = i;
45
- lineStartOffset = partStart;
46
- }
47
- column = 0;
48
- breakPart = null;
49
- i = lineStartPart;
50
- continue;
51
- }
52
- column += part.width;
53
- if (isWhitespace(part.segment)) {
54
- breakPart = i + 1;
55
- }
56
- i += 1;
57
- if (column >= width && i < parts.length) {
58
- const next = parts[i];
59
- const nextStartsWord = !isWhitespace(next.segment);
60
- if (breakPart !== null && breakPart > lineStartPart && nextStartsWord) {
61
- const breakOffset = sourceLineStart + parts[breakPart - 1].end;
62
- lines.push({ start: lineStartOffset, end: breakOffset });
63
- lineStartPart = breakPart;
64
- lineStartOffset = breakOffset;
65
- column = 0;
66
- breakPart = null;
67
- i = lineStartPart;
68
- }
69
- }
70
- }
71
- lines.push({ start: lineStartOffset, end: sourceLineStart + sourceLine.length });
72
- sourceLineStart += sourceLine.length + 1;
73
- }
74
- return lines.length ? lines : [{ start: 0, end: 0 }];
75
- }
76
- function widthBetween(value, start, end) {
77
- let width = 0;
78
- for (const part of graphemes(value.slice(start, end))) {
79
- width += part.width;
80
- }
81
- return width;
82
- }
83
- /**
84
- * Mirrors the word-wrap behavior used by the composer TextInput.
85
- * Returns the zero-based visual line and column of the cursor cell.
86
- */
87
- export function cursorLayout(value, cursor, cols) {
88
- const pos = Math.max(0, Math.min(cursor, value.length));
89
- const w = Math.max(1, cols);
90
- const lines = visualLines(value, w);
91
- let lineIndex = 0;
92
- for (let i = 0; i < lines.length; i += 1) {
93
- if (lines[i].start <= pos) {
94
- lineIndex = i;
95
- }
96
- else {
97
- break;
98
- }
99
- }
100
- const line = lines[lineIndex];
101
- let column = widthBetween(value, line.start, Math.min(pos, line.end));
102
- // trailing cursor-cell overflows to the next row at the wrap column
103
- if (column >= w) {
104
- lineIndex++;
105
- column = 0;
106
- }
107
- return { column, line: lineIndex };
108
- }
109
- export function offsetFromPosition(value, row, col, cols) {
110
- if (!value.length) {
111
- return 0;
112
- }
113
- const lines = visualLines(value, cols);
114
- const target = lines[Math.max(0, Math.min(lines.length - 1, Math.floor(row)))];
115
- const targetCol = Math.max(0, Math.floor(col));
116
- let column = 0;
117
- for (const part of graphemes(value.slice(target.start, target.end))) {
118
- if (targetCol <= column + Math.max(0, part.width - 1)) {
119
- return target.start + part.index;
120
- }
121
- column += part.width;
122
- }
123
- return target.end;
124
- }
125
- export function inputVisualHeight(value, columns) {
126
- return cursorLayout(value, value.length, columns).line + 1;
127
- }
128
- export function composerPromptWidth(promptText) {
129
- return Math.max(1, stringWidth(promptText)) + COMPOSER_PROMPT_GAP_WIDTH;
130
- }
131
- export function transcriptGutterWidth(role, userPrompt) {
132
- return role === 'user' ? composerPromptWidth(userPrompt) : 3;
133
- }
134
- export function transcriptBodyWidth(totalCols, role, userPrompt) {
135
- return Math.max(20, totalCols - transcriptGutterWidth(role, userPrompt) - 2);
136
- }
137
- export function stableComposerColumns(totalCols, promptWidth) {
138
- // Physical render/wrap width. Always reserve outer composer padding and
139
- // prompt prefix. Only reserve the transcript scrollbar gutter when the
140
- // terminal is wide enough; on narrow panes, preserving input columns beats
141
- // keeping gutters visually aligned.
142
- return Math.max(1, totalCols - promptWidth - 2 - (totalCols - promptWidth >= 24 ? 2 : 0));
143
- }
@@ -1,51 +0,0 @@
1
- export const countPendingTodos = (todos) => todos.filter(todo => todo.status === 'in_progress' || todo.status === 'pending').length;
2
- export const isTodoDone = (todos) => todos.length > 0 && todos.every(todo => todo.status === 'completed' || todo.status === 'cancelled');
3
- export const isToolShelfMessage = (msg) => Boolean(msg?.kind === 'trail' && !msg.text && !msg.thinking?.trim() && msg.tools?.length);
4
- export const canHoldToolShelf = (msg) => Boolean(msg?.kind === 'trail' && !msg.text && (msg.thinking?.trim() || msg.tools?.length));
5
- export const mergeToolShelfInto = (target, source) => ({
6
- ...target,
7
- tools: [...(target.tools ?? []), ...(source.tools ?? [])]
8
- });
9
- const isBarrierMessage = (msg) => {
10
- if (!msg) {
11
- return true;
12
- }
13
- // Assistant text, user input, intro/panel rows all terminate the shelf.
14
- if (msg.kind === 'intro' || msg.kind === 'panel' || msg.kind === 'diff') {
15
- return true;
16
- }
17
- if (msg.role && msg.role !== 'system') {
18
- return true;
19
- }
20
- if (msg.text) {
21
- return true;
22
- }
23
- return false;
24
- };
25
- const isToolCarryingTrail = (msg) => Boolean(msg?.kind === 'trail' && !msg.text && msg.tools?.length);
26
- export const appendToolShelfMessage = (prev, msg) => {
27
- if (!isToolShelfMessage(msg)) {
28
- return [...prev, msg];
29
- }
30
- let fallbackHolder = null;
31
- for (let index = prev.length - 1; index >= 0; index--) {
32
- const candidate = prev[index];
33
- if (isToolCarryingTrail(candidate)) {
34
- const next = [...prev];
35
- next[index] = mergeToolShelfInto(candidate, msg);
36
- return next;
37
- }
38
- if (fallbackHolder === null && canHoldToolShelf(candidate)) {
39
- fallbackHolder = index;
40
- }
41
- if (isBarrierMessage(candidate)) {
42
- break;
43
- }
44
- }
45
- if (fallbackHolder !== null) {
46
- const next = [...prev];
47
- next[fallbackHolder] = mergeToolShelfInto(prev[fallbackHolder], msg);
48
- return next;
49
- }
50
- return [...prev, msg];
51
- };