@hncode/hncode 0.1.3 → 0.1.5

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/package.json CHANGED
@@ -1,35 +1,35 @@
1
- {
2
- "name": "@hncode/hncode",
3
- "version": "0.1.3",
4
- "description": "hncode — A cyan-blue TUI AI coding agent with zero runtime dependencies.",
5
- "type": "module",
6
- "main": "src/index.js",
7
- "bin": {
8
- "hncode": "bin/hncode"
9
- },
10
- "files": [
11
- "bin/",
12
- "src/",
13
- "LICENSE",
14
- "README.md",
15
- "README_CN.md",
16
- "package.json"
17
- ],
18
- "scripts": {
19
- "preinstall": "node bin/preinstall.js",
20
- "install": "node bin/install.js"
21
- },
22
- "engines": {
23
- "node": ">=20"
24
- },
25
- "license": "GPL-3.0-only",
26
- "author": "NiceHello666",
27
- "keywords": [
28
- "cli",
29
- "agent",
30
- "coding",
31
- "ai",
32
- "tui"
33
- ],
34
- "dependencies": {}
35
- }
1
+ {
2
+ "name": "@hncode/hncode",
3
+ "version": "0.1.5",
4
+ "description": "hncode — A cyan-blue TUI AI coding agent with zero runtime dependencies.",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "hncode": "bin/hncode"
9
+ },
10
+ "files": [
11
+ "bin/",
12
+ "src/",
13
+ "LICENSE",
14
+ "README.md",
15
+ "README_CN.md",
16
+ "package.json"
17
+ ],
18
+ "scripts": {
19
+ "preinstall": "node bin/preinstall.js",
20
+ "install": "node bin/install.js"
21
+ },
22
+ "engines": {
23
+ "node": ">=20"
24
+ },
25
+ "license": "GPL-3.0-only",
26
+ "author": "NiceHello666",
27
+ "keywords": [
28
+ "cli",
29
+ "agent",
30
+ "coding",
31
+ "ai",
32
+ "tui"
33
+ ],
34
+ "dependencies": {}
35
+ }
package/src/colors.js CHANGED
@@ -33,9 +33,23 @@ export const BORDER = rgb(70, 130, 180);
33
33
  // terminal's own palette show through for everything else; `dark`/`light` pick
34
34
  // accent triples that stay readable on that background.
35
35
  const THEMES = {
36
- auto: { teal: [0, 184, 219], cyan: [0, 215, 255], blue: [66, 135, 245], border: [70, 130, 180], hover: [190, 245, 255] },
37
- dark: { teal: [0, 200, 230], cyan: [90, 225, 255], blue: [90, 150, 255], border: [80, 140, 190], hover: [190, 245, 255] },
38
- light: { teal: [0, 120, 160], cyan: [0, 140, 200], blue: [40, 90, 200], border: [120, 160, 200], hover: [0, 80, 110] },
36
+ auto: {
37
+ fg: [0, 184, 219], bg: [0, 18, 26],
38
+ teal: [0, 184, 219], cyan: [0, 215, 255], blue: [66, 135, 245], border: [70, 130, 180],
39
+ hover: [190, 245, 255], selBg: [0, 70, 84]
40
+ },
41
+ dark: {
42
+ fg: [0, 200, 230], bg: [0, 18, 26],
43
+ teal: [0, 200, 230], cyan: [90, 225, 255], blue: [90, 150, 255], border: [80, 140, 190],
44
+ hover: [190, 245, 255], selBg: [0, 70, 84]
45
+ },
46
+ light: {
47
+ fg: [26, 26, 26], bg: [255, 255, 255],
48
+ teal: [0, 102, 128], cyan: [0, 122, 153], blue: [10, 77, 158], border: [138, 155, 176],
49
+ hover: [0, 78, 102], selBg: [200, 230, 239],
50
+ red: [179, 38, 30], green: [30, 122, 60], yellow: [160, 120, 0], orange: [178, 90, 0],
51
+ white: [26, 26, 26], gray: [85, 85, 85],
52
+ },
39
53
  };
40
54
  export const THEME_NAMES = Object.keys(THEMES);
41
55
 
@@ -101,20 +115,55 @@ export function lerpColor(r1, g1, b1, r2, g2, b2, t) {
101
115
  export function setTheme(name) {
102
116
  const t = THEMES[name];
103
117
  if (!t) return false;
118
+
119
+ // Update foreground colors
104
120
  C.teal = rgb(...t.teal);
105
- C.fg = C.teal;
121
+ C.fg = rgb(...t.fg);
106
122
  C.cyan = rgb(...t.cyan);
107
123
  C.blue = rgb(...t.blue);
108
124
  C.border = rgb(...t.border);
125
+
126
+ // Update background colors
127
+ C.bg = brgb(...t.bg);
109
128
  C.bgTeal = brgb(...t.teal);
129
+ C.bgPanel = brgb(...t.bg);
130
+ C.selBg = brgb(...t.selBg);
131
+
132
+ // Scrollbar colors - adjust based on theme brightness
133
+ const isLight = t.bg[0] > 128; // Simple brightness check
134
+ C.scrollTrack = isLight ? '\x1b[90m' : '\x1b[90m'; // dim gutter (works for both)
135
+ C.scrollThumb = rgb(...t.teal);
136
+ C.scrollThumbHover = rgb(...t.hover);
137
+ C.scrollThumbActive = rgb(t.teal[0] * 0.7, t.teal[1] * 0.7, t.teal[2] * 0.7);
138
+
110
139
  // Hover stays visible on this theme's background (bright on dark, dark on light).
111
140
  C.hover = '\x1b[1m' + rgb(...(t.hover || [190, 245, 255]));
141
+
142
+ // Semantic colors — swap to deep versions on light backgrounds.
143
+ if (t.white) C.white = rgb(...t.white);
144
+ if (t.gray) C.gray = rgb(...t.gray);
145
+ if (t.red) C.red = rgb(...t.red);
146
+ if (t.green) C.green = rgb(...t.green);
147
+ if (t.yellow) C.yellow = rgb(...t.yellow);
148
+ if (t.orange) C.orange = rgb(...t.orange);
149
+
112
150
  return true;
113
151
  }
114
152
 
115
153
  export function clearScreen() {
116
154
  return '\x1b[3J\x1b[H\x1b[2J';
117
155
  }
156
+
157
+ // Clear screen and set background color based on current theme
158
+ export function clearAndSetBg() {
159
+ // Reset all attributes first
160
+ const reset = '\x1b[0m';
161
+ // Clear screen
162
+ const clear = '\x1b[3J\x1b[H\x1b[2J';
163
+ // Set background color using current C.bg value
164
+ const bg = C.bg || '\x1b[48;2;0;18;26m'; // Default dark background
165
+ return reset + clear + bg;
166
+ }
118
167
  export function eraseLine() {
119
168
  return '\x1b[2K';
120
169
  }
package/src/tui.js CHANGED
@@ -18,7 +18,7 @@ import fs from 'node:fs';
18
18
  import os from 'node:os';
19
19
  import cp from 'node:child_process';
20
20
  import {
21
- C, blockCursor, showCursor, hideCursor, alternateScreen, clearScreen, setTheme, THEME_NAMES, lerpColor,
21
+ C, blockCursor, showCursor, hideCursor, alternateScreen, clearScreen, clearAndSetBg, setTheme, THEME_NAMES, lerpColor,
22
22
  } from './colors.js';
23
23
  import { copyText, readText, warmClipboard, readImage } from './clipboard.js';
24
24
  import { visualWidth, estimateTokens, estimateMessagesTokens, expandTabs } from './term.js';
@@ -707,14 +707,15 @@ export function isFailureResult(text) {
707
707
 
708
708
  function msgColorFor(role, text) {
709
709
  if (role === 'system' && typeof text === 'string' && text.startsWith('[turn took')) return C.gray;
710
- if (role === 'system') return C.green;
710
+ if (role === 'system') return C.teal;
711
711
  if (role === 'user') return C.cyan;
712
712
  if (role === 'warn') return C.orange; // unfinished-turn warning
713
713
  if (role === 'queued') return C.gray; // pending, not yet sent
714
714
  if (role === 'steer') return C.yellow; // injected into the running turn
715
- if (role === 'tool') return C.white;
715
+ if (role === 'tool') return C.blue;
716
716
  if (role === 'tool_result') return C.gray;
717
- return C.white;
717
+ // Default foreground color based on theme
718
+ return C.fg;
718
719
  }
719
720
 
720
721
  // ---- tool call display: "Using Name (keyArg)" / "Used Name (keyArg) ----
@@ -2400,7 +2401,7 @@ export function makeState({ cfg, session, opts }) {
2400
2401
  planPath: (session && session.planPath) || null,
2401
2402
  focus: !!(session && session.focus),
2402
2403
  effort: (session && session.effort) || cfg.effort || (cfg.reasoning ? 'on' : 'off'),
2403
- theme: (cfg.raw && cfg.raw.theme) || 'auto',
2404
+ // Theme removed - forced dark only
2404
2405
  addDirs: [],
2405
2406
  tasks: {},
2406
2407
  slMode: true, slModel: true, slEffort: true, slCwd: true, slTasks: true, slTips: true,
@@ -3161,6 +3162,18 @@ async function dispatch(cmdRaw, arg, state, cfg, session, h) {
3161
3162
  : `theme = "${name}"\n` + txt;
3162
3163
  fs.writeFileSync(f, next, 'utf8');
3163
3164
  } catch { }
3165
+
3166
+ // Force a complete screen refresh with new theme colors
3167
+ // We need to access stdout from the host context
3168
+ if (host && typeof host.renderFrame === 'function') {
3169
+ // Trigger immediate re-render which will use new theme colors
3170
+ host.renderFrame();
3171
+ } else {
3172
+ // Fallback: just show message and let normal flow handle it
3173
+ app(`Theme set to "${name}".`);
3174
+ return true;
3175
+ }
3176
+
3164
3177
  app(`Theme set to "${name}".`);
3165
3178
  return true;
3166
3179
  };
@@ -4262,7 +4275,7 @@ export async function startTUI(opts) {
4262
4275
  }
4263
4276
  // Async so the notice can be painted BEFORE the clipboard read: a cold
4264
4277
  // PowerShell took ~3s, which read as "the shortcut does nothing".
4265
- async function pasteFromClipboard() {
4278
+ async function pasteFromClipboard(fastPathOnly = false) {
4266
4279
  notice('Pasting…', 'info');
4267
4280
  const imgBase64 = readImage();
4268
4281
  if (imgBase64) {
@@ -4272,10 +4285,26 @@ export async function startTUI(opts) {
4272
4285
  renderFrame();
4273
4286
  return;
4274
4287
  }
4275
- const { text, via } = await readText();
4288
+
4289
+ let text = '';
4290
+ let via = 'none';
4291
+
4292
+ // Try fast path first (oneshot)
4293
+ try {
4294
+ ({ text, via } = { text: readOnce(), via: 'oneshot' });
4295
+ } catch {}
4296
+
4297
+ // If fast path failed and we need to try slow path
4298
+ if (!text && !fastPathOnly) {
4299
+ const result = await readText();
4300
+ text = result.text;
4301
+ via = result.via;
4302
+ }
4303
+
4276
4304
  if (!text) { notice('Clipboard empty or unavailable', 'error'); renderFrame(); return; }
4277
4305
  insertComposerPaste(text);
4278
- if (via === 'oneshot') notice(`Pasted (slow path: ${via}); retry for instant paste`, 'info');
4306
+ if (via === 'helper') notice(`Pasted via helper (${via})`, 'info');
4307
+ else if (via === 'oneshot') notice('Pasted instantly!', 'info');
4279
4308
  renderFrame();
4280
4309
  }
4281
4310
  // Ctrl-S: inject the queued messages (plus the current draft) into the RUNNING
@@ -5406,7 +5435,7 @@ export async function startTUI(opts) {
5406
5435
  session.planPath = state.planPath || null;
5407
5436
  session.focus = !!state.focus;
5408
5437
  session.effort = state.effort || '';
5409
- session.theme = state.theme || 'auto';
5438
+ // Theme removed - forced dark only
5410
5439
  session.steps = state.steps || 0;
5411
5440
  session.rounds = state.rounds || 0;
5412
5441
  if (state.lastTurnMs) session.lastTurnMs = state.lastTurnMs;