agen-vektor 0.3.5 → 0.3.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/dist/tui/app.js CHANGED
@@ -78,6 +78,7 @@ class App {
78
78
  planToExecute = null;
79
79
  abortController = null;
80
80
  spinner = new theme_1.Spinner();
81
+ runStart = null;
81
82
  constructor(agent, opts) {
82
83
  this.agent = agent;
83
84
  this.opts = opts;
@@ -90,7 +91,6 @@ class App {
90
91
  start() {
91
92
  this.connected = true;
92
93
  this.status = 'Ready';
93
- this.addSystem('VectorHead ready. Type a request or press ? for help.');
94
94
  if (this.opts.continueSession && this.sessionName) {
95
95
  const sess = (0, session_1.loadSession)(this.sessionName);
96
96
  if (sess) {
@@ -241,6 +241,7 @@ class App {
241
241
  this.statusColor = (0, theme_1.statusColor)('Thinking');
242
242
  this.spinner.reset();
243
243
  this.abortController = new AbortController();
244
+ this.runStart = Date.now();
244
245
  // Show plan first if requested
245
246
  if (this.opts.showPlan) {
246
247
  this.status = 'Planning…';
@@ -356,6 +357,7 @@ class App {
356
357
  m.content = '(no response)';
357
358
  }
358
359
  this.running = false;
360
+ this.runStart = null;
359
361
  if (this.statusColor !== terminal_1.ANSI.red) {
360
362
  this.status = 'Ready';
361
363
  this.statusColor = terminal_1.ANSI.green;
@@ -725,6 +727,7 @@ class App {
725
727
  mode: this.agent.config.permissionMode,
726
728
  spinner: this.spinner.frame(),
727
729
  running: this.running,
730
+ elapsed: this.running && this.runStart !== null ? Math.floor((Date.now() - this.runStart) / 1000) : undefined,
728
731
  };
729
732
  const parts = [];
730
733
  // Row 1-2: header + project line
@@ -732,9 +735,9 @@ class App {
732
735
  parts.push((0, terminal_1.cursorTo)(2, 1) + terminal_1.ANSI.clearLineEnd + (0, statusbar_1.renderProjectBar)(cols, info));
733
736
  // Row 3: separator
734
737
  parts.push((0, terminal_1.cursorTo)(3, 1) + terminal_1.ANSI.clearLineEnd + theme_1.THEME.border + terminal_1.BOX.h.repeat(Math.min(cols, 120)) + theme_1.THEME.reset);
735
- // Chat area (rows 4..) — clean Freebuff-style message blocks
738
+ // Chat area (rows 4..) — Freebuff-style message blocks (welcome screen when empty)
736
739
  const chatH = this.chatHeight();
737
- const all = (0, chat_1.renderMessages)(this.messages, cols);
740
+ const all = this.messages.length === 0 ? (0, chat_1.renderWelcome)(cols, chatH, this.agent.cwd) : (0, chat_1.renderMessages)(this.messages, cols);
738
741
  const maxScroll = Math.max(0, all.length - chatH);
739
742
  this.scroll = Math.min(this.scroll, maxScroll);
740
743
  const visible = all.slice(this.scroll, this.scroll + chatH);
package/dist/tui/chat.js CHANGED
@@ -3,54 +3,98 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.renderMessages = renderMessages;
4
4
  exports.defaultScroll = defaultScroll;
5
5
  exports.clampScroll = clampScroll;
6
+ exports.renderWelcome = renderWelcome;
6
7
  /**
7
- * Chat view — renders conversation messages the Freebuff way:
8
- * clean plain-text blocks with a small colored label row. User messages
9
- * are right-aligned (chat-style bubble), everything else left-aligned.
8
+ * Chat view — renders conversation messages the Freebuff way (adopted from
9
+ * CodebuffAI/freebuff TUI):
10
+ * - user messages: green left bar + italic text (no label)
11
+ * - assistant messages: plain foreground text
12
+ * - tool calls: compact dim blocks (⚙ tool · preview)
10
13
  * No boxes or frames — stays aligned on narrow/mobile terminals.
11
14
  *
12
- * VectorHead · model
13
- * Inspecting project...
15
+ * ██╗...VECTORHEAD logo (welcome only)
16
+ * VectorHead will run commands on your behalf to help you build.
17
+ * Directory /home/x
14
18
  *
15
- * YOU
16
- * perbaiki error auth
19
+ * Inspecting project... (user, italic, green bar)
17
20
  *
18
- * shell · $ npm test
21
+ * Saya cek dulu file auth.ts. (assistant)
22
+ *
23
+ * ⚙ shell · $ npm test (tool, dim)
19
24
  */
20
25
  const theme_1 = require("./theme");
21
26
  const terminal_1 = require("../utils/terminal");
22
- /** Right-align a string to a fixed visible width (leading padding). */
23
- function padLeft(str, width) {
24
- return ' '.repeat(Math.max(0, width - (0, terminal_1.visibleWidth)(str))) + str;
27
+ /** Center a string (with ANSI) horizontally within `width`. */
28
+ function center(str, width) {
29
+ const pad = Math.max(0, Math.floor((width - (0, terminal_1.visibleWidth)(str)) / 2));
30
+ return ' '.repeat(pad) + str;
25
31
  }
26
- const KIND_STYLE = {
27
- user: { prefix: 'YOU', color: theme_1.THEME.gold },
28
- assistant: { prefix: 'VectorHead', color: theme_1.THEME.accent },
29
- tool: { prefix: 'TOOL', color: theme_1.THEME.warn },
30
- system: { prefix: '···', color: theme_1.THEME.muted },
31
- error: { prefix: '✗', color: theme_1.THEME.error },
32
- success: { prefix: '✓', color: theme_1.THEME.success },
33
- warning: { prefix: '⚠', color: theme_1.THEME.warn },
34
- };
35
- /** Render one message as a label row + wrapped content (user right-aligned). */
32
+ /** Render one message as Freebuff-style lines. */
36
33
  function renderMessage(m, width) {
37
- const style = KIND_STYLE[m.kind];
38
- const prefix = m.prefix || style.prefix;
39
- const dot = m.streaming ? `${theme_1.THEME.warn}◐${theme_1.THEME.reset}` : `${style.color}●${theme_1.THEME.reset}`;
40
- const meta = m.meta ? ` ${theme_1.THEME.dim}${m.meta}${theme_1.THEME.reset}` : '';
41
- const toolName = m.tool ? ` ${theme_1.THEME.warn}${m.tool}${theme_1.THEME.reset}` : '';
42
- const rightAlign = m.kind === 'user';
43
- // Label row: ● Label · tool · model (right-aligned for user, truncated to fit narrow widths)
44
- const label = `${dot} ${theme_1.THEME.bold}${style.color}${prefix}${theme_1.THEME.reset}${toolName}${meta}`;
45
- const out = [rightAlign ? padLeft((0, terminal_1.truncate)(label, width), width) : (0, terminal_1.truncate)(label, width)];
46
- // Content rows — wrapped to fit; user wraps the full width for a flush right edge
47
34
  const body = m.content || '(no content)';
48
- const wrapW = rightAlign ? width : Math.max(4, width - 2);
49
- for (const line of (0, terminal_1.wrapText)(body, wrapW)) {
50
- out.push(rightAlign ? padLeft(line, width) : ` ${line}`);
35
+ const out = [];
36
+ switch (m.kind) {
37
+ case 'user': {
38
+ // Green vertical bar + italic text — Freebuff's signature user style.
39
+ const bar = `${theme_1.THEME.userLine}│${theme_1.THEME.reset}`;
40
+ const inner = Math.max(4, width - 2);
41
+ for (const line of (0, terminal_1.wrapText)(body, inner)) {
42
+ out.push(`${bar} ${terminal_1.ANSI.italic}${line}${theme_1.THEME.reset}`);
43
+ }
44
+ out.push('');
45
+ return out;
46
+ }
47
+ case 'tool': {
48
+ // Compact dim block: ⚙ tool · preview, then dim result lines.
49
+ const label = m.tool ? `⚙ ${m.tool}` : '⚙';
50
+ const parts = body.split('\n');
51
+ const first = `${theme_1.THEME.warn}${label}${theme_1.THEME.reset} ${theme_1.THEME.faint}${(parts[0] || '').trim()}${theme_1.THEME.reset}`;
52
+ if (first.trim()) {
53
+ for (const line of (0, terminal_1.wrapText)(first, width))
54
+ out.push(line);
55
+ }
56
+ const rest = parts.slice(1).filter((l) => l.trim());
57
+ if (rest.length > 0) {
58
+ for (const line of (0, terminal_1.wrapText)(rest.join('\n'), Math.max(4, width - 2))) {
59
+ out.push(`${theme_1.THEME.faint}${line}${theme_1.THEME.reset}`);
60
+ }
61
+ }
62
+ out.push('');
63
+ return out;
64
+ }
65
+ case 'system':
66
+ for (const line of (0, terminal_1.wrapText)(body, width))
67
+ out.push(`${theme_1.THEME.faint}${line}${theme_1.THEME.reset}`);
68
+ out.push('');
69
+ return out;
70
+ case 'error':
71
+ for (const line of (0, terminal_1.wrapText)(body, width))
72
+ out.push(`${theme_1.THEME.error}${line}${theme_1.THEME.reset}`);
73
+ out.push('');
74
+ return out;
75
+ case 'success':
76
+ for (const line of (0, terminal_1.wrapText)(body, width))
77
+ out.push(`${theme_1.THEME.success}${line}${theme_1.THEME.reset}`);
78
+ out.push('');
79
+ return out;
80
+ case 'warning':
81
+ for (const line of (0, terminal_1.wrapText)(body, width))
82
+ out.push(`${theme_1.THEME.warn}${line}${theme_1.THEME.reset}`);
83
+ out.push('');
84
+ return out;
85
+ default: {
86
+ // Assistant: plain foreground text (Freebuff style — no label).
87
+ const marker = m.streaming ? `${theme_1.THEME.info}◐ ${theme_1.THEME.reset}` : '';
88
+ const lines = (0, terminal_1.wrapText)(body, width);
89
+ if (lines.length === 0)
90
+ lines.push('(no content)');
91
+ for (let i = 0; i < lines.length; i++) {
92
+ out.push(i === 0 ? `${marker}${lines[i]}` : lines[i]);
93
+ }
94
+ out.push('');
95
+ return out;
96
+ }
51
97
  }
52
- out.push(''); // blank separator between messages
53
- return out;
54
98
  }
55
99
  /** Precompute the full list of rendered lines for all messages. */
56
100
  function renderMessages(messages, width) {
@@ -69,3 +113,44 @@ function clampScroll(offset, messages, width, height) {
69
113
  const max = Math.max(0, all.length - height);
70
114
  return Math.min(Math.max(0, offset), max);
71
115
  }
116
+ /** Full-width VECTORHEAD ASCII logo (white, Freebuff block style). */
117
+ const VECTORHEAD_LOGO = [
118
+ '██╗ ██╗███████╗ ██████╗████████╗ ██████╗ ██████╗ ██╗ ██╗███████╗ █████╗ ██████╗',
119
+ '██║ ██║██╔════╝██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗██║ ██║██╔════╝██╔══██╗██╔══██╗',
120
+ '██║ ██║█████╗ ██║ ██║ ██║ ██║██████╔╝███████║█████╗ ███████║██║ ██║',
121
+ '╚██╗ ██╔╝██╔══╝ ██║ ██║ ██║ ██║██╔══██╗██╔══██║██╔══╝ ██╔══██║██║ ██║',
122
+ ' ╚████╔╝ ███████╗╚██████╗ ██║ ╚██████╔╝██║ ██║██║ ██║███████╗██║ ██║██████╔╝',
123
+ ' ╚═══╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═════╝ ',
124
+ ];
125
+ /**
126
+ * Freebuff-style welcome screen (shown while the chat is empty): logo,
127
+ * tagline and Directory line, centered in the chat area. On narrow
128
+ * terminals the full logo is replaced by a compact wordmark.
129
+ */
130
+ function renderWelcome(width, height, cwd) {
131
+ const lines = [];
132
+ if (width >= 85) { // full logo is ~82 cols; narrower terminals get the wordmark
133
+ for (const l of VECTORHEAD_LOGO)
134
+ lines.push(`${theme_1.THEME.textBright}${l}${theme_1.THEME.reset}`);
135
+ }
136
+ else {
137
+ lines.push(`${theme_1.THEME.accent}▮${theme_1.THEME.reset} ${theme_1.THEME.bold}${theme_1.THEME.textBright}VectorHead${theme_1.THEME.reset}`);
138
+ }
139
+ lines.push('');
140
+ for (const t of ['VectorHead will run commands on your behalf', 'to help you build.']) {
141
+ for (const line of (0, terminal_1.wrapText)(t, Math.max(10, width - 4))) {
142
+ lines.push(`${theme_1.THEME.muted}${line}${theme_1.THEME.reset}`);
143
+ }
144
+ }
145
+ lines.push('');
146
+ lines.push(`${theme_1.THEME.muted}Directory ${theme_1.THEME.directory}${(0, terminal_1.truncate)(cwd, Math.max(10, width - 12))}${theme_1.THEME.reset}`);
147
+ const centered = lines.map((l) => center(l, width));
148
+ const padTop = Math.max(0, Math.floor((height - centered.length) / 2));
149
+ const out = [];
150
+ for (let i = 0; i < padTop; i++)
151
+ out.push('');
152
+ out.push(...centered);
153
+ while (out.length < height)
154
+ out.push('');
155
+ return out.slice(0, Math.max(0, height));
156
+ }
@@ -51,13 +51,13 @@ function renderSelector(opts) {
51
51
  const top = Math.max(1, Math.floor((opts.rows - height) / 2));
52
52
  const left = Math.max(1, Math.floor((opts.cols - innerWidth - 2) / 2));
53
53
  const lines = [];
54
- const borderColor = terminal_1.ANSI.cyan;
54
+ const borderColor = theme_1.THEME.accent;
55
55
  const contentW = innerWidth - 2;
56
- // Title row: ╭── Title ────────────╮ (exactly innerWidth+2 wide)
56
+ // Title row: ╭── Title ────────────╮ (exactly innerWidth+2 wide, green accent)
57
57
  const t = (0, terminal_1.truncate)(opts.title, contentW);
58
58
  lines.push((0, terminal_1.cursorTo)(top, left) +
59
59
  borderColor + terminal_1.BOX.tl + terminal_1.BOX.h.repeat(2) +
60
- terminal_1.ANSI.bold + terminal_1.ANSI.white + t + terminal_1.ANSI.reset + borderColor +
60
+ terminal_1.ANSI.bold + theme_1.THEME.textBright + t + terminal_1.ANSI.reset + borderColor +
61
61
  terminal_1.BOX.h.repeat(Math.max(0, innerWidth - 2 - (0, terminal_1.visibleWidth)(t))) + terminal_1.BOX.tr);
62
62
  for (let i = 0; i < visible; i++) {
63
63
  const idx = start + i;
@@ -69,7 +69,7 @@ function renderSelector(opts) {
69
69
  const label = marker + option.label;
70
70
  const hint = option.hint ? terminal_1.ANSI.dim + ' — ' + option.hint + terminal_1.ANSI.reset : '';
71
71
  if (idx === opts.selected) {
72
- content = terminal_1.ANSI.bold + terminal_1.ANSI.cyan + (0, terminal_1.truncate)(label, contentW - (0, terminal_1.visibleWidth)(hint)) + terminal_1.ANSI.reset + hint;
72
+ content = terminal_1.ANSI.bold + theme_1.THEME.accent + (0, terminal_1.truncate)(label, contentW - (0, terminal_1.visibleWidth)(hint)) + terminal_1.ANSI.reset + hint;
73
73
  }
74
74
  else {
75
75
  content = (0, terminal_1.truncate)(label, contentW - (0, terminal_1.visibleWidth)(hint)) + hint;
package/dist/tui/input.js CHANGED
@@ -5,6 +5,7 @@ exports.InputBox = void 0;
5
5
  * Input box — single-line text input with cursor, history, and hints.
6
6
  */
7
7
  const terminal_1 = require("../utils/terminal");
8
+ const theme_1 = require("./theme");
8
9
  class InputBox {
9
10
  text = '';
10
11
  cursor = 0;
@@ -97,7 +98,7 @@ class InputBox {
97
98
  }
98
99
  /** Render the input line, returns the cursor column (0-based within line). */
99
100
  render(width) {
100
- const prefix = `${terminal_1.ANSI.fg(220)}❯${terminal_1.ANSI.reset} `;
101
+ const prefix = `${theme_1.THEME.accent}❯${terminal_1.ANSI.reset} `;
101
102
  const prefixWidth = 2; // ❯ + space (ANSI codes are invisible)
102
103
  const available = Math.max(1, width - prefixWidth - 1);
103
104
  let display = this.text;
@@ -4,9 +4,10 @@ exports.renderHeader = renderHeader;
4
4
  exports.renderProjectBar = renderProjectBar;
5
5
  exports.renderStatusBar = renderStatusBar;
6
6
  /**
7
- * Status bar — top header + project line + bottom hint bar.
8
- * Clean Freebuff-style lines (no frames), truncated to fit narrow/mobile
9
- * terminals.
7
+ * Status bar — top header + project line + bottom status bar.
8
+ * Freebuff-style clean lines (no shortcut hints), truncated to fit
9
+ * narrow/mobile terminals. Bottom bar shows live status, spinner and
10
+ * an elapsed timer while the agent is working.
10
11
  */
11
12
  const terminal_1 = require("../utils/terminal");
12
13
  const theme_1 = require("./theme");
@@ -28,7 +29,7 @@ function renderHeader(width, info) {
28
29
  const status = `${dot} ${(0, theme_1.statusColor)(info.status)}${info.status}${theme_1.THEME.reset}`;
29
30
  const left = `${brand} ${status}`;
30
31
  const provider = `${theme_1.THEME.dim}${info.provider}${theme_1.THEME.reset}`;
31
- const model = `${theme_1.THEME.accentDim}${info.model}${theme_1.THEME.reset}`;
32
+ const model = `${theme_1.THEME.faint}${info.model}${theme_1.THEME.reset}`;
32
33
  const right = `${provider} · ${model}`;
33
34
  const fit = fitTwo(left, right, width);
34
35
  const mid = Math.max(1, width - (0, terminal_1.stripAnsi)(fit.left).length - (0, terminal_1.stripAnsi)(fit.right).length - 1);
@@ -36,22 +37,29 @@ function renderHeader(width, info) {
36
37
  }
37
38
  /** Render the project line: Project: <path> ... Mode: ask */
38
39
  function renderProjectBar(width, info) {
39
- const label = `${theme_1.THEME.dim}Project:${theme_1.THEME.reset} ${theme_1.THEME.text}${info.project}${theme_1.THEME.reset}`;
40
- const modeColor = info.mode === 'yolo' ? theme_1.THEME.warn : theme_1.THEME.muted;
40
+ const label = `${theme_1.THEME.dim}Project:${theme_1.THEME.reset} ${theme_1.THEME.muted}${info.project}${theme_1.THEME.reset}`;
41
+ const modeColor = info.mode === 'yolo' ? theme_1.THEME.warn : theme_1.THEME.faint;
41
42
  const mode = `${theme_1.THEME.dim}Mode:${theme_1.THEME.reset} ${modeColor}${info.mode}${theme_1.THEME.reset}`;
42
43
  const fit = fitTwo(label, mode, width);
43
44
  const mid = Math.max(1, width - (0, terminal_1.stripAnsi)(fit.left).length - (0, terminal_1.stripAnsi)(fit.right).length - 1);
44
45
  return theme_1.THEME.bgBar + (0, terminal_1.pad)(`${fit.left}${' '.repeat(mid)}${fit.right}`, width) + theme_1.THEME.reset;
45
46
  }
46
- /** Render the bottom hint/status bar with live spinner. */
47
+ /** Render the bottom status bar: live status + spinner + elapsed timer. */
47
48
  function renderStatusBar(width, info) {
48
- const left = `ENTER ${theme_1.THEME.accent}Send${theme_1.THEME.reset} TAB ${theme_1.THEME.accent}Commands${theme_1.THEME.reset} CTRL+C ${theme_1.THEME.accent}Stop${theme_1.THEME.reset} ? ${theme_1.THEME.accent}Help${theme_1.THEME.reset}`;
49
- let right = '';
49
+ const statusColorCode = (0, theme_1.statusColor)(info.status);
50
+ let left;
50
51
  if (info.running && info.spinner) {
51
- right = `${(0, theme_1.statusColor)(info.status)}${info.spinner}${theme_1.THEME.reset} `;
52
+ left = `${statusColorCode}${info.spinner}${theme_1.THEME.reset} ${statusColorCode}${info.status}${theme_1.THEME.reset}`;
53
+ }
54
+ else {
55
+ left = `${statusColorCode}${info.status}${theme_1.THEME.reset}`;
56
+ }
57
+ let right = '';
58
+ if (info.running && info.elapsed !== undefined) {
59
+ right += `${theme_1.THEME.dim}${info.elapsed}s${theme_1.THEME.reset} `;
52
60
  }
53
61
  const conn = info.connected ? `${theme_1.THEME.success}●${theme_1.THEME.reset}` : `${theme_1.THEME.error}○${theme_1.THEME.reset}`;
54
- const mode = info.mode === 'yolo' ? `${theme_1.THEME.warn}YOLO${theme_1.THEME.reset}` : `${theme_1.THEME.muted}ask${theme_1.THEME.reset}`;
62
+ const mode = info.mode === 'yolo' ? `${theme_1.THEME.warn}YOLO${theme_1.THEME.reset}` : `${theme_1.THEME.faint}ask${theme_1.THEME.reset}`;
55
63
  right += `${conn} ${mode}`;
56
64
  const fit = fitTwo(left, right, width);
57
65
  const mid = Math.max(1, width - (0, terminal_1.stripAnsi)(fit.left).length - (0, terminal_1.stripAnsi)(fit.right).length - 1);
package/dist/tui/theme.js CHANGED
@@ -3,25 +3,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Spinner = exports.SPINNERS = exports.THEME = void 0;
4
4
  exports.statusColor = statusColor;
5
5
  /**
6
- * VectorHead theme — consistent premium palette for the TUI.
7
- * Warm dark palette with orange accent + gold highlights.
6
+ * VectorHead theme — Freebuff-style palette (adopted from CodebuffAI/freebuff dark theme).
7
+ * Primary brand green #9EFC62, slate neutrals, gray AI line, green user line.
8
8
  */
9
9
  exports.THEME = {
10
- accent: '\x1b[38;5;208m', // bright orange
11
- accentDim: '\x1b[38;5;172m',
12
- gold: '\x1b[38;5;220m',
13
- text: '\x1b[38;5;252m',
10
+ accent: '\x1b[38;5;156m', // Freebuff primary green (#9EFC62 ≈ 156)
11
+ accentDim: '\x1b[38;5;120m',
12
+ gold: '\x1b[38;5;220m', // markdown heading yellow (#facc15 ≈ 220)
13
+ text: '\x1b[38;5;255m', // foreground #f1f5f9 ≈ 255
14
14
  textBright: '\x1b[38;5;255m',
15
- muted: '\x1b[38;5;244m',
16
- faint: '\x1b[38;5;240m',
17
- border: '\x1b[38;5;94m',
18
- borderBright: '\x1b[38;5;208m',
15
+ muted: '\x1b[38;5;250m', // #acb3bf ≈ 250
16
+ faint: '\x1b[38;5;244m',
17
+ directory: '\x1b[38;5;249m', // #9CA3AF ≈ 249
18
+ border: '\x1b[38;5;60m', // slate #536175 ≈ 60
19
+ borderBright: '\x1b[38;5;156m',
19
20
  success: '\x1b[38;5;114m',
20
- warn: '\x1b[38;5;215m',
21
+ warn: '\x1b[38;5;215m', // #FFA500 ≈ 215
21
22
  error: '\x1b[38;5;203m',
22
- info: '\x1b[38;5;111m',
23
- bgBar: '\x1b[48;5;235m',
24
- bgPanel: '\x1b[48;5;236m',
23
+ info: '\x1b[38;5;156m',
24
+ // Message indicator lines (Freebuff): gray for AI, green for user
25
+ aiLine: '\x1b[38;5;244m',
26
+ userLine: '\x1b[38;5;156m',
27
+ bgBar: '\x1b[48;5;236m',
28
+ bgPanel: '\x1b[48;5;237m',
25
29
  bold: '\x1b[1m',
26
30
  dim: '\x1b[2m',
27
31
  reset: '\x1b[0m',
@@ -39,8 +43,8 @@ function statusColor(status) {
39
43
  return exports.THEME.success;
40
44
  if (s.includes('stopped') || s.includes('cancel'))
41
45
  return exports.THEME.muted;
42
- if (s.includes('thinking') || s.includes('plan'))
43
- return exports.THEME.info;
46
+ if (s.includes('thinking') || s.includes('plan') || s.includes('work'))
47
+ return exports.THEME.accent;
44
48
  if (s.includes('run') || s.includes('exec'))
45
49
  return exports.THEME.accent;
46
50
  return exports.THEME.info;
@@ -135,7 +135,25 @@ function wrapText(text, width) {
135
135
  const unit = String.fromCodePoint(cp);
136
136
  const w = cp > 0x2fff ? 2 : 1;
137
137
  if (visibleLen + w > width && visibleLen > 0) {
138
- flushLine();
138
+ // Word-aware wrap (Freebuff uses wrapMode: 'word'): break at the last
139
+ // space so words are not split mid-word. ANSI codes never contain a
140
+ // space, so lastIndexOf(' ') on the raw buffer is safe.
141
+ const lastSpace = current.lastIndexOf(' ');
142
+ if (lastSpace > 0) {
143
+ const carried = current.slice(lastSpace + 1);
144
+ const carriedW = visibleWidth(carried);
145
+ if (carriedW > 0 && carriedW + w <= width) {
146
+ lines.push(current.slice(0, lastSpace));
147
+ current = carried;
148
+ visibleLen = carriedW;
149
+ }
150
+ else {
151
+ flushLine();
152
+ }
153
+ }
154
+ else {
155
+ flushLine();
156
+ }
139
157
  }
140
158
  current += unit;
141
159
  visibleLen += w;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agen-vektor",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "VectorHead (agen-vektor) — AI Coding Agent CLI/TUI for Linux & Termux. Multi-provider, tool calling, session, permission system.",
5
5
  "type": "commonjs",
6
6
  "bin": {