agent-sh 0.14.5 → 0.14.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.
@@ -5,7 +5,8 @@ import { InputHandler } from "./input-handler.js";
5
5
  import { OutputParser } from "./output-parser.js";
6
6
  import { getSettings } from "../core/settings.js";
7
7
  import { clearOpost } from "../utils/tty.js";
8
- import { processTerminal } from "./terminal.js";
8
+ import { processTerminal, surfaceFromTerminal } from "./terminal.js";
9
+ import { TuiInputView } from "./tui-input-view.js";
9
10
  import { pickStrategy, FALLBACK_STRATEGY, SUPPORTED_SHELL_NAMES, } from "./strategies/index.js";
10
11
  export class Shell {
11
12
  ptyProcess;
@@ -96,6 +97,7 @@ export class Shell {
96
97
  bus: opts.bus,
97
98
  handlers: opts.handlers,
98
99
  onShowAgentInfo: opts.onShowAgentInfo ?? (() => ({ info: "" })),
100
+ view: new TuiInputView(surfaceFromTerminal(this.terminal)),
99
101
  });
100
102
  this.setupOutput();
101
103
  this.setupInput();
@@ -25,6 +25,32 @@ export interface Terminal {
25
25
  }
26
26
  /** Default Terminal: wraps process.stdin/stdout. */
27
27
  export declare function processTerminal(): Terminal;
28
+ /**
29
+ * No-op terminal for non-rendering hosts (tests, agent-only embeds).
30
+ * Writes are discarded; input/resize never fire.
31
+ */
32
+ export declare function headlessTerminal(cols?: number, rows?: number): Terminal;
33
+ /**
34
+ * Pipe-based terminal for embedders that own their own renderer (web hubs
35
+ * via xterm.js, electron windows, recording harnesses). Bytes from the
36
+ * Shell flow through `onWrite`; the host drives `pushInput`/`pushResize`
37
+ * to forward keystrokes and viewport changes back.
38
+ */
39
+ export declare class BridgedTerminal implements Terminal {
40
+ private readonly onWrite;
41
+ private inputCbs;
42
+ private resizeCbs;
43
+ private _cols;
44
+ private _rows;
45
+ constructor(onWrite: (data: string) => void, cols?: number, rows?: number);
46
+ write(data: string): void;
47
+ onInput(cb: (d: string) => void): () => void;
48
+ onResize(cb: (c: number, r: number) => void): () => void;
49
+ cols(): number;
50
+ rows(): number;
51
+ pushInput(data: string): void;
52
+ pushResize(cols: number, rows: number): void;
53
+ }
28
54
  /**
29
55
  * Adapt a Terminal to a RenderSurface (the compositor's sink type). Adds
30
56
  * the OPOST-cleared `\n` → `\r\n` translation that StdoutSurface applies,
@@ -45,6 +45,50 @@ export function processTerminal() {
45
45
  },
46
46
  };
47
47
  }
48
+ /**
49
+ * No-op terminal for non-rendering hosts (tests, agent-only embeds).
50
+ * Writes are discarded; input/resize never fire.
51
+ */
52
+ export function headlessTerminal(cols = 100, rows = 30) {
53
+ return {
54
+ write() { },
55
+ onInput: () => () => { },
56
+ onResize: () => () => { },
57
+ cols: () => cols,
58
+ rows: () => rows,
59
+ };
60
+ }
61
+ /**
62
+ * Pipe-based terminal for embedders that own their own renderer (web hubs
63
+ * via xterm.js, electron windows, recording harnesses). Bytes from the
64
+ * Shell flow through `onWrite`; the host drives `pushInput`/`pushResize`
65
+ * to forward keystrokes and viewport changes back.
66
+ */
67
+ export class BridgedTerminal {
68
+ onWrite;
69
+ inputCbs = new Set();
70
+ resizeCbs = new Set();
71
+ _cols;
72
+ _rows;
73
+ constructor(onWrite, cols = 100, rows = 30) {
74
+ this.onWrite = onWrite;
75
+ this._cols = cols;
76
+ this._rows = rows;
77
+ }
78
+ write(data) { this.onWrite(data); }
79
+ onInput(cb) { this.inputCbs.add(cb); return () => { this.inputCbs.delete(cb); }; }
80
+ onResize(cb) { this.resizeCbs.add(cb); return () => { this.resizeCbs.delete(cb); }; }
81
+ cols() { return this._cols; }
82
+ rows() { return this._rows; }
83
+ pushInput(data) { for (const cb of this.inputCbs)
84
+ cb(data); }
85
+ pushResize(cols, rows) {
86
+ this._cols = cols;
87
+ this._rows = rows;
88
+ for (const cb of this.resizeCbs)
89
+ cb(cols, rows);
90
+ }
91
+ }
48
92
  /**
49
93
  * Adapt a Terminal to a RenderSurface (the compositor's sink type). Adds
50
94
  * the OPOST-cleared `\n` → `\r\n` translation that StdoutSurface applies,
@@ -43,16 +43,13 @@ export class StatusFooter extends Container {
43
43
  const contentWidth = width > 0 ? Math.max(1, width - 2) : 0;
44
44
  const right = this.buildRight();
45
45
  const rightWidth = visibleWidth(right);
46
- const join = (left: string): string => {
47
- if (!right) return left;
48
- const leftWidth = visibleWidth(left);
49
- const gap = Math.max(1, contentWidth - leftWidth - rightWidth);
50
- return `${left}${" ".repeat(gap)}${right}`;
51
- };
52
- const full = this.buildLine("full");
53
- const fullFits = contentWidth === 0
54
- || visibleWidth(full) + (right ? rightWidth + 1 : 0) <= contentWidth;
55
- this.text.setText(fullFits ? join(full) : join(this.buildLine("basename")));
46
+ const left = this.buildLine();
47
+ if (!right) {
48
+ this.text.setText(left);
49
+ return;
50
+ }
51
+ const gap = Math.max(1, contentWidth - visibleWidth(left) - rightWidth);
52
+ this.text.setText(`${left}${" ".repeat(gap)}${right}`);
56
53
  }
57
54
 
58
55
  private buildRight(): string {
@@ -62,7 +59,7 @@ export class StatusFooter extends Container {
62
59
  return "";
63
60
  }
64
61
 
65
- private buildLine(cwdMode: "full" | "basename"): string {
62
+ private buildLine(): string {
66
63
  const { model, provider, contextWindow, cwd, branch, leaf, tokens, compactions, thinking } = this.fields;
67
64
  const sep = theme.fg("dim", " | ");
68
65
  const parts: string[] = [];
@@ -73,7 +70,7 @@ export class StatusFooter extends Container {
73
70
  } else if (provider) {
74
71
  parts.push(theme.fg("muted", `@${provider}`));
75
72
  }
76
- if (cwd) parts.push(theme.fg("muted", formatCwd(cwd, cwdMode)));
73
+ if (cwd) parts.push(theme.fg("muted", basename(cwd) || cwd));
77
74
  if (branch) parts.push(theme.fg("muted", `⎇ ${branch}`));
78
75
  if (leaf != null && leaf > 0) parts.push(theme.fg("muted", `#${leaf}`));
79
76
  if (tokens != null) {
@@ -86,14 +83,6 @@ export class StatusFooter extends Container {
86
83
  }
87
84
  }
88
85
 
89
- function formatCwd(cwd: string, mode: "full" | "basename"): string {
90
- if (mode === "basename") return basename(cwd) || cwd;
91
- const home = process.env.HOME;
92
- if (home && cwd.startsWith(`${home}/`)) return `~/${cwd.slice(home.length + 1)}`;
93
- if (home && cwd === home) return "~";
94
- return cwd;
95
- }
96
-
97
86
  function fmtTokens(n: number): string {
98
87
  if (n < 1000) return String(n);
99
88
  if (n < 100_000) return `${(n / 1000).toFixed(1)}k`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-sh",
3
- "version": "0.14.5",
3
+ "version": "0.14.6",
4
4
  "description": "A shell-first terminal where AI is one keystroke away",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -41,6 +41,10 @@
41
41
  "types": "./dist/shell/shell.d.ts",
42
42
  "default": "./dist/shell/shell.js"
43
43
  },
44
+ "./shell/host": {
45
+ "types": "./dist/shell/index.d.ts",
46
+ "default": "./dist/shell/index.js"
47
+ },
44
48
  "./shell/strategies": {
45
49
  "types": "./dist/shell/strategies/index.d.ts",
46
50
  "default": "./dist/shell/strategies/index.js"