@hncode/hncode 0.1.4 → 0.1.6

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@hncode/hncode",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "hncode — A cyan-blue TUI AI coding agent with zero runtime dependencies.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
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';
@@ -38,7 +38,7 @@ const VERSION = '0.1.0';
38
38
  export const COMMANDS = [
39
39
  { name: 'yolo', aliases: ['yes'], desc: 'Ask When Needed mode: anything inside the workspace (edits, writes, commands) runs automatically; paths outside it, destructive commands, questions and plans still ask.', priority: 101 },
40
40
  { name: 'permission', desc: 'Select permission mode', priority: 100 },
41
- { name: 'settings', aliases: ['config'], desc: 'Open settings (model / permission / theme / statusline)', priority: 100 },
41
+ { name: 'settings', aliases: ['config'], desc: 'Open settings (model / permission / statusline)', priority: 100 },
42
42
  { name: 'plan', desc: 'Toggle plan mode', priority: 100, argumentHint: '[on|off|clear]' },
43
43
  { name: 'focus', desc: 'Toggle Focus mode (minimal tools first, full tools after)', priority: 98, argumentHint: '[on|off]' },
44
44
  { name: 'auto', desc: 'Never Ask mode: never interrupts you; everything runs and is decided automatically.', priority: 99 },
@@ -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,
@@ -2561,7 +2562,6 @@ async function dispatch(cmdRaw, arg, state, cfg, session, h) {
2561
2562
  { label: 'effort', sub: 'switch thinking effort' },
2562
2563
  { label: 'permission', sub: 'select permission mode' },
2563
2564
  { label: 'provider', sub: 'manage AI providers' },
2564
- { label: 'theme', sub: 'set the terminal UI theme' },
2565
2565
  { label: 'statusline', sub: 'configure status line items' },
2566
2566
  { label: 'add-dir', sub: 'add an additional workspace directory' },
2567
2567
  ],
@@ -3161,6 +3161,18 @@ async function dispatch(cmdRaw, arg, state, cfg, session, h) {
3161
3161
  : `theme = "${name}"\n` + txt;
3162
3162
  fs.writeFileSync(f, next, 'utf8');
3163
3163
  } catch { }
3164
+
3165
+ // Force a complete screen refresh with new theme colors
3166
+ // We need to access stdout from the host context
3167
+ if (host && typeof host.renderFrame === 'function') {
3168
+ // Trigger immediate re-render which will use new theme colors
3169
+ host.renderFrame();
3170
+ } else {
3171
+ // Fallback: just show message and let normal flow handle it
3172
+ app(`Theme set to "${name}".`);
3173
+ return true;
3174
+ }
3175
+
3164
3176
  app(`Theme set to "${name}".`);
3165
3177
  return true;
3166
3178
  };
@@ -5422,7 +5434,7 @@ export async function startTUI(opts) {
5422
5434
  session.planPath = state.planPath || null;
5423
5435
  session.focus = !!state.focus;
5424
5436
  session.effort = state.effort || '';
5425
- session.theme = state.theme || 'auto';
5437
+ // Theme removed - forced dark only
5426
5438
  session.steps = state.steps || 0;
5427
5439
  session.rounds = state.rounds || 0;
5428
5440
  if (state.lastTurnMs) session.lastTurnMs = state.lastTurnMs;