agen-vektor 0.3.3 → 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;
@@ -723,12 +727,20 @@ class App {
723
727
  };
724
728
  const frame = theme_1.THEME.borderBright;
725
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 : '+';
726
738
  const parts = [];
727
739
  // Row 1-2: framed header + project bar
728
740
  parts.push((0, terminal_1.cursorTo)(1, 1) + terminal_1.ANSI.clearLineEnd + (0, statusbar_1.renderHeader)(cols, info));
729
741
  parts.push((0, terminal_1.cursorTo)(2, 1) + terminal_1.ANSI.clearLineEnd + (0, statusbar_1.renderProjectBar)(cols, info));
730
742
  // Row 3: separator
731
- parts.push((0, terminal_1.cursorTo)(3, 1) + terminal_1.ANSI.clearLineEnd + frame + terminal_1.BOX.teeRight + terminal_1.BOX.h.repeat(innerW) + terminal_1.BOX.teeLeft + theme_1.THEME.reset);
743
+ parts.push((0, terminal_1.cursorTo)(3, 1) + terminal_1.ANSI.clearLineEnd + frame + trtee + hz.repeat(innerW) + tltee + theme_1.THEME.reset);
732
744
  // Chat area (rows 4..) — each line wrapped in the frame
733
745
  const chatH = this.chatHeight();
734
746
  const chatW = innerW;
@@ -740,22 +752,22 @@ class App {
740
752
  const empty = ' '.repeat(innerW);
741
753
  for (let i = 0; i < chatH; i++) {
742
754
  const content = visible[i] !== undefined ? visible[i] : empty;
743
- parts.push((0, terminal_1.cursorTo)(chatTop + i, 1) + terminal_1.ANSI.clearLineEnd + frame + terminal_1.BOX.v + theme_1.THEME.reset + ' ' + content + ' ' + frame + terminal_1.BOX.v + theme_1.THEME.reset);
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);
744
756
  }
745
757
  // Separator between chat and input
746
758
  const sepRow = chatTop + chatH;
747
- parts.push((0, terminal_1.cursorTo)(sepRow, 1) + terminal_1.ANSI.clearLineEnd + frame + terminal_1.BOX.teeRight + terminal_1.BOX.h.repeat(innerW) + terminal_1.BOX.teeLeft + theme_1.THEME.reset);
759
+ parts.push((0, terminal_1.cursorTo)(sepRow, 1) + terminal_1.ANSI.clearLineEnd + frame + trtee + hz.repeat(innerW) + tltee + theme_1.THEME.reset);
748
760
  // Input row
749
761
  const inputRow = sepRow + 1;
750
762
  const rendered = this.input.render(innerW);
751
- parts.push((0, terminal_1.cursorTo)(inputRow, 1) + terminal_1.ANSI.clearLineEnd + frame + terminal_1.BOX.v + theme_1.THEME.reset + ' ' + rendered.line + ' ' + frame + terminal_1.BOX.v + theme_1.THEME.reset);
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);
752
764
  const cursorCol = rendered.cursorCol + 3;
753
765
  // Status bar row
754
766
  const statusRow = inputRow + 1;
755
767
  parts.push((0, terminal_1.cursorTo)(statusRow, 1) + terminal_1.ANSI.clearLineEnd + (0, statusbar_1.renderStatusBar)(cols, info));
756
768
  // Bottom border
757
769
  const bottomRow = statusRow + 1;
758
- parts.push((0, terminal_1.cursorTo)(bottomRow, 1) + terminal_1.ANSI.clearLineEnd + frame + terminal_1.BOX.bl + terminal_1.BOX.h.repeat(innerW) + terminal_1.BOX.br + theme_1.THEME.reset);
770
+ parts.push((0, terminal_1.cursorTo)(bottomRow, 1) + terminal_1.ANSI.clearLineEnd + frame + bl + hz.repeat(innerW) + br + theme_1.THEME.reset);
759
771
  // Modal overlay
760
772
  if (this.modal.type !== 'none') {
761
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
  }
@@ -13,6 +13,26 @@ exports.renderStatusBar = renderStatusBar;
13
13
  const terminal_1 = require("../utils/terminal");
14
14
  const theme_1 = require("./theme");
15
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
+ }
16
36
  /** Render the top framed header line: ╭─ VectorHead ● status ... provider · model ─╮ */
17
37
  function renderHeader(width, info) {
18
38
  const brand = `${theme_1.THEME.bold}${theme_1.THEME.accent}VectorHead${theme_1.THEME.reset}`;
@@ -23,8 +43,10 @@ function renderHeader(width, info) {
23
43
  const model = `${theme_1.THEME.accentDim}${info.model}${theme_1.THEME.reset}`;
24
44
  const right = `${provider} · ${model}`;
25
45
  const inner = Math.max(2, width - 2);
26
- const mid = Math.max(1, inner - (0, terminal_1.stripAnsi)(left).length - (0, terminal_1.stripAnsi)(right).length - 3);
27
- return `${frame}${terminal_1.BOX.tl}${theme_1.THEME.reset} ${left}${' '.repeat(mid)}${right} ${frame}${terminal_1.BOX.tr}${theme_1.THEME.reset}`;
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}`;
28
50
  }
29
51
  /** Render the framed project line: │ Project: <path> ... Mode: ask │ */
30
52
  function renderProjectBar(width, info) {
@@ -32,8 +54,10 @@ function renderProjectBar(width, info) {
32
54
  const modeColor = info.mode === 'yolo' ? theme_1.THEME.warn : theme_1.THEME.muted;
33
55
  const mode = `${theme_1.THEME.dim}Mode:${theme_1.THEME.reset} ${modeColor}${info.mode}${theme_1.THEME.reset}`;
34
56
  const inner = Math.max(2, width - 2);
35
- const mid = Math.max(1, inner - (0, terminal_1.stripAnsi)(label).length - (0, terminal_1.stripAnsi)(mode).length - 3);
36
- return `${frame}${terminal_1.BOX.v}${theme_1.THEME.reset} ${label}${' '.repeat(mid)}${mode} ${frame}${terminal_1.BOX.v}${theme_1.THEME.reset}`;
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}`;
37
61
  }
38
62
  /** Render the framed bottom hint/status bar with live spinner. */
39
63
  function renderStatusBar(width, info) {
@@ -46,6 +70,8 @@ function renderStatusBar(width, info) {
46
70
  const mode = info.mode === 'yolo' ? `${theme_1.THEME.warn}YOLO${theme_1.THEME.reset}` : `${theme_1.THEME.muted}ask${theme_1.THEME.reset}`;
47
71
  right += `${conn} ${mode}`;
48
72
  const inner = Math.max(2, width - 2);
49
- const mid = Math.max(1, inner - (0, terminal_1.stripAnsi)(left).length - (0, terminal_1.stripAnsi)(right).length - 3);
50
- return `${frame}${terminal_1.BOX.v}${theme_1.THEME.reset} ${left}${' '.repeat(mid)}${right} ${frame}${terminal_1.BOX.v}${theme_1.THEME.reset}`;
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}`;
51
77
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agen-vektor",
3
- "version": "0.3.3",
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": {