agen-vektor 0.3.2 → 0.3.4

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
@@ -184,6 +184,10 @@ class App {
184
184
  this.scroll = 0;
185
185
  this.addSystem('Conversation cleared.');
186
186
  break;
187
+ case 'ctrl_d':
188
+ // EOF / Ctrl+D — another way to quit (handy on mobile where Ctrl is awkward)
189
+ this.exitRequested = true;
190
+ break;
187
191
  case 'tab':
188
192
  this.modal = { type: 'commands' };
189
193
  break;
@@ -706,7 +710,7 @@ class App {
706
710
  return (0, terminal_1.getTerminalSize)().cols;
707
711
  }
708
712
  chatHeight() {
709
- return Math.max(1, (0, terminal_1.getTerminalSize)().rows - 6);
713
+ return Math.max(1, (0, terminal_1.getTerminalSize)().rows - 7);
710
714
  }
711
715
  render() {
712
716
  const { rows, cols } = (0, terminal_1.getTerminalSize)();
@@ -721,32 +725,49 @@ class App {
721
725
  spinner: this.spinner.frame(),
722
726
  running: this.running,
723
727
  };
728
+ const frame = theme_1.THEME.borderBright;
729
+ const innerW = Math.max(4, cols - 2);
730
+ // Narrow terminals (mobile portrait) fall back to ASCII borders.
731
+ const wide = cols >= 60;
732
+ const bl = wide ? terminal_1.BOX.bl : '+';
733
+ const br = wide ? terminal_1.BOX.br : '+';
734
+ const hz = wide ? terminal_1.BOX.h : '-';
735
+ const vr = wide ? terminal_1.BOX.v : '|';
736
+ const trtee = wide ? terminal_1.BOX.teeRight : '+';
737
+ const tltee = wide ? terminal_1.BOX.teeLeft : '+';
724
738
  const parts = [];
725
- parts.push((0, statusbar_1.renderHeader)(cols, info));
726
- parts.push((0, statusbar_1.renderProjectBar)(cols, info));
727
- // Chat area
739
+ // Row 1-2: framed header + project bar
740
+ parts.push((0, terminal_1.cursorTo)(1, 1) + terminal_1.ANSI.clearLineEnd + (0, statusbar_1.renderHeader)(cols, info));
741
+ parts.push((0, terminal_1.cursorTo)(2, 1) + terminal_1.ANSI.clearLineEnd + (0, statusbar_1.renderProjectBar)(cols, info));
742
+ // Row 3: separator
743
+ parts.push((0, terminal_1.cursorTo)(3, 1) + terminal_1.ANSI.clearLineEnd + frame + trtee + hz.repeat(innerW) + tltee + theme_1.THEME.reset);
744
+ // Chat area (rows 4..) — each line wrapped in the frame
728
745
  const chatH = this.chatHeight();
729
- const chatW = cols;
746
+ const chatW = innerW;
730
747
  const all = (0, chat_1.renderMessages)(this.messages, chatW);
731
748
  const maxScroll = Math.max(0, all.length - chatH);
732
749
  this.scroll = Math.min(this.scroll, maxScroll);
733
750
  const visible = all.slice(this.scroll, this.scroll + chatH);
734
- const chatTop = 3;
751
+ const chatTop = 4;
752
+ const empty = ' '.repeat(innerW);
735
753
  for (let i = 0; i < chatH; i++) {
736
- const content = visible[i] !== undefined ? visible[i] : '';
737
- parts.push((0, terminal_1.cursorTo)(chatTop + i, 1) + terminal_1.ANSI.clearLineEnd + content);
754
+ const content = visible[i] !== undefined ? visible[i] : empty;
755
+ parts.push((0, terminal_1.cursorTo)(chatTop + i, 1) + terminal_1.ANSI.clearLineEnd + frame + vr + theme_1.THEME.reset + ' ' + content + ' ' + frame + vr + theme_1.THEME.reset);
738
756
  }
739
- // Separator
757
+ // Separator between chat and input
740
758
  const sepRow = chatTop + chatH;
741
- parts.push((0, terminal_1.cursorTo)(sepRow, 1) + terminal_1.ANSI.clearLineEnd + theme_1.THEME.border + terminal_1.BOX.h.repeat(Math.min(cols, 120)) + theme_1.THEME.reset);
742
- // Input
759
+ parts.push((0, terminal_1.cursorTo)(sepRow, 1) + terminal_1.ANSI.clearLineEnd + frame + trtee + hz.repeat(innerW) + tltee + theme_1.THEME.reset);
760
+ // Input row
743
761
  const inputRow = sepRow + 1;
744
- const rendered = this.input.render(cols);
745
- parts.push((0, terminal_1.cursorTo)(inputRow, 1) + terminal_1.ANSI.clearLineEnd + rendered.line);
746
- const cursorCol = rendered.cursorCol + 1;
747
- // Status bar
762
+ const rendered = this.input.render(innerW);
763
+ parts.push((0, terminal_1.cursorTo)(inputRow, 1) + terminal_1.ANSI.clearLineEnd + frame + vr + theme_1.THEME.reset + ' ' + rendered.line + ' ' + frame + vr + theme_1.THEME.reset);
764
+ const cursorCol = rendered.cursorCol + 3;
765
+ // Status bar row
748
766
  const statusRow = inputRow + 1;
749
767
  parts.push((0, terminal_1.cursorTo)(statusRow, 1) + terminal_1.ANSI.clearLineEnd + (0, statusbar_1.renderStatusBar)(cols, info));
768
+ // Bottom border
769
+ const bottomRow = statusRow + 1;
770
+ parts.push((0, terminal_1.cursorTo)(bottomRow, 1) + terminal_1.ANSI.clearLineEnd + frame + bl + hz.repeat(innerW) + br + theme_1.THEME.reset);
750
771
  // Modal overlay
751
772
  if (this.modal.type !== 'none') {
752
773
  const overlay = this.renderModal(rows, cols);
package/dist/tui/chat.js CHANGED
@@ -33,18 +33,26 @@ function boxMessage(m, width) {
33
33
  const toolName = m.tool ? ` ${theme_1.THEME.warn}${m.tool}${theme_1.THEME.reset}` : '';
34
34
  const inner = Math.max(8, width - 4);
35
35
  const border = style.color;
36
+ // Narrow terminals (mobile) fall back to ASCII so borders stay aligned.
37
+ const ascii = width < 58;
38
+ const tl = ascii ? '+' : terminal_1.BOX.tl;
39
+ const tr = ascii ? '+' : terminal_1.BOX.tr;
40
+ const bl = ascii ? '+' : terminal_1.BOX.bl;
41
+ const br = ascii ? '+' : terminal_1.BOX.br;
42
+ const hz = ascii ? '-' : terminal_1.BOX.h;
43
+ const vr = ascii ? '|' : terminal_1.BOX.v;
36
44
  const out = [];
37
45
  // Top border
38
- out.push(border + terminal_1.BOX.tl + terminal_1.BOX.h.repeat(inner + 2) + terminal_1.BOX.tr + theme_1.THEME.reset);
39
- // Title row: ● prefix · model / tool badge
40
- out.push(`${border}${terminal_1.BOX.v}${theme_1.THEME.reset} ` + (0, terminal_1.pad)(title + toolName + meta, inner) + ` ${border}${terminal_1.BOX.v}${theme_1.THEME.reset}`);
46
+ out.push(border + tl + hz.repeat(inner + 2) + tr + theme_1.THEME.reset);
47
+ // Title row: ● prefix · model / tool badge (truncated to fit narrow widths)
48
+ out.push(`${border}${vr}${theme_1.THEME.reset} ` + (0, terminal_1.pad)((0, terminal_1.truncate)(title + toolName + meta, inner), inner) + ` ${border}${vr}${theme_1.THEME.reset}`);
41
49
  // Content rows
42
50
  const body = m.content || '(no content)';
43
51
  for (const line of (0, terminal_1.wrapText)(body, inner)) {
44
- out.push(`${border}${terminal_1.BOX.v}${theme_1.THEME.reset} ` + (0, terminal_1.pad)(line, inner) + ` ${border}${terminal_1.BOX.v}${theme_1.THEME.reset}`);
52
+ out.push(`${border}${vr}${theme_1.THEME.reset} ` + (0, terminal_1.pad)(line, inner) + ` ${border}${vr}${theme_1.THEME.reset}`);
45
53
  }
46
54
  // Bottom border + blank separator
47
- out.push(border + terminal_1.BOX.bl + terminal_1.BOX.h.repeat(inner + 2) + terminal_1.BOX.br + theme_1.THEME.reset);
55
+ out.push(border + bl + hz.repeat(inner + 2) + br + theme_1.THEME.reset);
48
56
  out.push('');
49
57
  return out;
50
58
  }
@@ -5,11 +5,35 @@ exports.renderProjectBar = renderProjectBar;
5
5
  exports.renderStatusBar = renderStatusBar;
6
6
  /**
7
7
  * Status bar — top header + project bar + bottom hint bar.
8
- * Uses the VectorHead theme with a live spinner while busy.
8
+ * Renders inside the VectorHead frame (Freebuff-style borders):
9
+ * ╭─ VectorHead ● status ─── provider · model ─╮
10
+ * │ Project: ~/x Mode: ask │
11
+ * │ ENTER Send · TAB Commands · ? Help ● ask │
9
12
  */
10
13
  const terminal_1 = require("../utils/terminal");
11
14
  const theme_1 = require("./theme");
12
- /** Render the top header line: VectorHead ● status .... provider · model */
15
+ const frame = theme_1.THEME.borderBright;
16
+ /**
17
+ * Frame characters. Narrow terminals (mobile portrait, ~<60 cols) fall back
18
+ * to ASCII so borders never misalign on fonts without box-drawing glyphs.
19
+ */
20
+ function frameChars(width) {
21
+ if (width < 60)
22
+ return { tl: '+', tr: '+', bl: '+', br: '+', v: '|' };
23
+ return { tl: terminal_1.BOX.tl, tr: terminal_1.BOX.tr, bl: terminal_1.BOX.bl, br: terminal_1.BOX.br, v: terminal_1.BOX.v };
24
+ }
25
+ /** Split two ANSI strings across `inner` columns, truncating with … if needed. */
26
+ function fitTwo(left, right, inner) {
27
+ const avail = inner - 3; // spaces around both sides
28
+ const l = (0, terminal_1.stripAnsi)(left).length;
29
+ const r = (0, terminal_1.stripAnsi)(right).length;
30
+ if (l + r <= avail)
31
+ return { left, right };
32
+ const maxL = Math.max(0, Math.floor((avail * l) / (l + r)));
33
+ const maxR = Math.max(0, avail - maxL);
34
+ return { left: (0, terminal_1.truncate)(left, maxL), right: (0, terminal_1.truncate)(right, maxR) };
35
+ }
36
+ /** Render the top framed header line: ╭─ VectorHead ● status ... provider · model ─╮ */
13
37
  function renderHeader(width, info) {
14
38
  const brand = `${theme_1.THEME.bold}${theme_1.THEME.accent}VectorHead${theme_1.THEME.reset}`;
15
39
  const dot = info.connected ? `${theme_1.THEME.success}●${theme_1.THEME.reset}` : `${theme_1.THEME.error}○${theme_1.THEME.reset}`;
@@ -18,20 +42,24 @@ function renderHeader(width, info) {
18
42
  const provider = `${theme_1.THEME.dim}${info.provider}${theme_1.THEME.reset}`;
19
43
  const model = `${theme_1.THEME.accentDim}${info.model}${theme_1.THEME.reset}`;
20
44
  const right = `${provider} · ${model}`;
21
- const rightWidth = (0, terminal_1.stripAnsi)(right).length;
22
- const leftWidth = Math.max(0, width - rightWidth - 2);
23
- const line = (0, terminal_1.pad)(left, leftWidth) + ' ' + right;
24
- return theme_1.THEME.bgBar + (0, terminal_1.pad)(line, width) + theme_1.THEME.reset;
45
+ const inner = Math.max(2, width - 2);
46
+ const fit = fitTwo(left, right, inner);
47
+ const mid = Math.max(1, inner - (0, terminal_1.stripAnsi)(fit.left).length - (0, terminal_1.stripAnsi)(fit.right).length - 3);
48
+ const c = frameChars(width);
49
+ return `${frame}${c.tl}${theme_1.THEME.reset} ${fit.left}${' '.repeat(mid)}${fit.right} ${frame}${c.tr}${theme_1.THEME.reset}`;
25
50
  }
26
- /** Render the project line: Project: <path> .... Mode: ask|yolo */
51
+ /** Render the framed project line: Project: <path> ... Mode: ask */
27
52
  function renderProjectBar(width, info) {
28
53
  const label = `${theme_1.THEME.dim}Project:${theme_1.THEME.reset} ${theme_1.THEME.text}${info.project}${theme_1.THEME.reset}`;
29
54
  const modeColor = info.mode === 'yolo' ? theme_1.THEME.warn : theme_1.THEME.muted;
30
55
  const mode = `${theme_1.THEME.dim}Mode:${theme_1.THEME.reset} ${modeColor}${info.mode}${theme_1.THEME.reset}`;
31
- const line = `${label} ${mode}`;
32
- return theme_1.THEME.bgBar + (0, terminal_1.pad)(line, width) + theme_1.THEME.reset;
56
+ const inner = Math.max(2, width - 2);
57
+ const fit = fitTwo(label, mode, inner);
58
+ const mid = Math.max(1, inner - (0, terminal_1.stripAnsi)(fit.left).length - (0, terminal_1.stripAnsi)(fit.right).length - 3);
59
+ const c = frameChars(width);
60
+ return `${frame}${c.v}${theme_1.THEME.reset} ${fit.left}${' '.repeat(mid)}${fit.right} ${frame}${c.v}${theme_1.THEME.reset}`;
33
61
  }
34
- /** Render the bottom hint/status bar with live spinner. */
62
+ /** Render the framed bottom hint/status bar with live spinner. */
35
63
  function renderStatusBar(width, info) {
36
64
  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}`;
37
65
  let right = '';
@@ -41,7 +69,9 @@ function renderStatusBar(width, info) {
41
69
  const conn = info.connected ? `${theme_1.THEME.success}●${theme_1.THEME.reset}` : `${theme_1.THEME.error}○${theme_1.THEME.reset}`;
42
70
  const mode = info.mode === 'yolo' ? `${theme_1.THEME.warn}YOLO${theme_1.THEME.reset}` : `${theme_1.THEME.muted}ask${theme_1.THEME.reset}`;
43
71
  right += `${conn} ${mode}`;
44
- const rightWidth = (0, terminal_1.stripAnsi)(right).length;
45
- const leftWidth = Math.max(0, width - rightWidth - 1);
46
- return theme_1.THEME.bgBar + (0, terminal_1.pad)(left, leftWidth) + ' ' + right + theme_1.THEME.reset;
72
+ const inner = Math.max(2, width - 2);
73
+ const fit = fitTwo(left, right, inner);
74
+ const mid = Math.max(1, inner - (0, terminal_1.stripAnsi)(fit.left).length - (0, terminal_1.stripAnsi)(fit.right).length - 3);
75
+ const c = frameChars(width);
76
+ return `${frame}${c.v}${theme_1.THEME.reset} ${fit.left}${' '.repeat(mid)}${fit.right} ${frame}${c.v}${theme_1.THEME.reset}`;
47
77
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agen-vektor",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
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": {