agent-sh 0.1.0

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.
Files changed (50) hide show
  1. package/README.md +659 -0
  2. package/dist/acp-client.d.ts +76 -0
  3. package/dist/acp-client.js +507 -0
  4. package/dist/context-manager.d.ts +45 -0
  5. package/dist/context-manager.js +405 -0
  6. package/dist/core.d.ts +41 -0
  7. package/dist/core.js +76 -0
  8. package/dist/event-bus.d.ts +140 -0
  9. package/dist/event-bus.js +79 -0
  10. package/dist/executor.d.ts +31 -0
  11. package/dist/executor.js +116 -0
  12. package/dist/extension-loader.d.ts +16 -0
  13. package/dist/extension-loader.js +164 -0
  14. package/dist/extensions/file-autocomplete.d.ts +2 -0
  15. package/dist/extensions/file-autocomplete.js +63 -0
  16. package/dist/extensions/shell-recall.d.ts +9 -0
  17. package/dist/extensions/shell-recall.js +8 -0
  18. package/dist/extensions/slash-commands.d.ts +2 -0
  19. package/dist/extensions/slash-commands.js +105 -0
  20. package/dist/extensions/tui-renderer.d.ts +2 -0
  21. package/dist/extensions/tui-renderer.js +354 -0
  22. package/dist/index.d.ts +2 -0
  23. package/dist/index.js +159 -0
  24. package/dist/input-handler.d.ts +48 -0
  25. package/dist/input-handler.js +302 -0
  26. package/dist/output-parser.d.ts +55 -0
  27. package/dist/output-parser.js +166 -0
  28. package/dist/shell.d.ts +54 -0
  29. package/dist/shell.js +219 -0
  30. package/dist/types.d.ts +71 -0
  31. package/dist/types.js +1 -0
  32. package/dist/utils/ansi.d.ts +12 -0
  33. package/dist/utils/ansi.js +23 -0
  34. package/dist/utils/box-frame.d.ts +21 -0
  35. package/dist/utils/box-frame.js +60 -0
  36. package/dist/utils/diff-renderer.d.ts +20 -0
  37. package/dist/utils/diff-renderer.js +506 -0
  38. package/dist/utils/diff.d.ts +24 -0
  39. package/dist/utils/diff.js +122 -0
  40. package/dist/utils/file-watcher.d.ts +31 -0
  41. package/dist/utils/file-watcher.js +101 -0
  42. package/dist/utils/markdown.d.ts +39 -0
  43. package/dist/utils/markdown.js +248 -0
  44. package/dist/utils/palette.d.ts +32 -0
  45. package/dist/utils/palette.js +36 -0
  46. package/dist/utils/tool-display.d.ts +33 -0
  47. package/dist/utils/tool-display.js +141 -0
  48. package/examples/extensions/interactive-prompts.ts +161 -0
  49. package/examples/extensions/solarized-theme.ts +27 -0
  50. package/package.json +72 -0
@@ -0,0 +1,354 @@
1
+ /**
2
+ * TUI renderer extension.
3
+ *
4
+ * Subscribes to EventBus events and renders agent output to the terminal:
5
+ * bordered markdown responses, spinner, tool call display, streaming
6
+ * command output, error/info messages.
7
+ *
8
+ * Without this extension loaded, agent-sh runs headlessly — PTY
9
+ * passthrough, agent queries, tool execution all function; output is
10
+ * silently dropped. Alternative renderers (web UI, logging, minimal)
11
+ * can subscribe to the same events.
12
+ */
13
+ import { MarkdownRenderer } from "../utils/markdown.js";
14
+ import { palette as p } from "../utils/palette.js";
15
+ import { renderToolCall, renderToolResult, startSpinner, stopSpinner as stopToolSpinner, } from "../utils/tool-display.js";
16
+ import { renderDiff } from "../utils/diff-renderer.js";
17
+ import { renderBoxFrame } from "../utils/box-frame.js";
18
+ const MAX_COMMAND_OUTPUT_LINES = 30;
19
+ export default function activate({ bus }) {
20
+ let spinner = null;
21
+ let renderer = null;
22
+ let commandOutputBuffer = "";
23
+ let commandOutputLineCount = 0;
24
+ let commandOutputOverflow = 0;
25
+ let lastCommand = "";
26
+ let isThinking = false;
27
+ let showThinkingText = false;
28
+ let lastTruncatedDiff = null;
29
+ // ── Event subscriptions ─────────────────────────────────────
30
+ bus.on("agent:query", (e) => {
31
+ showUserQuery(e.query);
32
+ startAgentResponse();
33
+ startThinkingSpinner();
34
+ });
35
+ bus.on("agent:thinking-chunk", (e) => {
36
+ if (!isThinking) {
37
+ isThinking = true;
38
+ stopCurrentSpinner();
39
+ if (showThinkingText) {
40
+ if (!renderer)
41
+ startAgentResponse();
42
+ renderer.writeLine(`${p.dim}${p.bold}💭 Thinking${p.reset}`);
43
+ }
44
+ else {
45
+ startThinkingSpinner("Thinking");
46
+ }
47
+ }
48
+ if (showThinkingText && e.text) {
49
+ if (!renderer)
50
+ startAgentResponse();
51
+ renderer.push(`${p.dim}${e.text}${p.reset}`);
52
+ flushOutput();
53
+ }
54
+ });
55
+ bus.on("agent:response-chunk", (e) => writeAgentText(e.text));
56
+ bus.on("agent:response-done", () => {
57
+ isThinking = false;
58
+ endAgentResponse();
59
+ });
60
+ bus.on("agent:tool-call", (e) => {
61
+ lastCommand = e.tool;
62
+ });
63
+ bus.on("agent:tool-started", (e) => {
64
+ stopCurrentSpinner();
65
+ showToolCall(e.title, lastCommand);
66
+ lastCommand = "";
67
+ });
68
+ bus.on("agent:tool-completed", (e) => showToolComplete(e.exitCode));
69
+ bus.on("agent:tool-output-chunk", (e) => writeCommandOutput(e.chunk));
70
+ bus.on("agent:tool-output", () => flushCommandOutput());
71
+ bus.on("agent:cancelled", () => {
72
+ isThinking = false;
73
+ stopCurrentSpinner();
74
+ showInfo("(cancelled)");
75
+ endAgentResponse();
76
+ });
77
+ bus.on("agent:error", (e) => showError(e.message));
78
+ // Flush rendering state and show inline diff for file writes
79
+ bus.on("permission:request", (e) => {
80
+ stopCurrentSpinner();
81
+ flushCommandOutput();
82
+ renderer?.flush();
83
+ if (e.kind === "file-write" && e.metadata?.diff) {
84
+ showFileDiff(e.title, e.metadata.diff);
85
+ }
86
+ else {
87
+ // Non-file permission (e.g. tool-call) — end response box
88
+ // so interactive extensions can render their own UI
89
+ endAgentResponse();
90
+ }
91
+ });
92
+ bus.on("input:keypress", (e) => {
93
+ if (e.key === "\x0f")
94
+ expandLastDiff(); // Ctrl+O
95
+ if (e.key === "\x14")
96
+ toggleThinkingDisplay(); // Ctrl+T
97
+ });
98
+ bus.on("ui:info", (e) => showInfo(e.message));
99
+ bus.on("ui:error", (e) => showError(e.message));
100
+ // ── Rendering functions ─────────────────────────────────────
101
+ function flushOutput() {
102
+ if (process.stdout.writable) {
103
+ try {
104
+ process.stdout.write("");
105
+ }
106
+ catch { }
107
+ }
108
+ }
109
+ function startAgentResponse() {
110
+ renderer = new MarkdownRenderer();
111
+ process.stdout.write("\n");
112
+ renderer.printTopBorder();
113
+ }
114
+ function endAgentResponse() {
115
+ if (renderer) {
116
+ renderer.flush();
117
+ renderer.printBottomBorder();
118
+ renderer = null;
119
+ }
120
+ }
121
+ function showUserQuery(query) {
122
+ const termW = process.stdout.columns || 80;
123
+ const boxW = Math.min(84, termW);
124
+ const contentW = boxW - 4; // inside box padding
125
+ // Wrap long queries to fit within box
126
+ const lines = [];
127
+ for (const raw of query.split("\n")) {
128
+ if (raw.length <= contentW) {
129
+ lines.push(`${p.accent}${raw}${p.reset}`);
130
+ }
131
+ else {
132
+ // Simple word wrap
133
+ let remaining = raw;
134
+ while (remaining.length > contentW) {
135
+ let breakAt = remaining.lastIndexOf(" ", contentW);
136
+ if (breakAt <= 0)
137
+ breakAt = contentW;
138
+ lines.push(`${p.accent}${remaining.slice(0, breakAt)}${p.reset}`);
139
+ remaining = remaining.slice(breakAt).trimStart();
140
+ }
141
+ if (remaining)
142
+ lines.push(`${p.accent}${remaining}${p.reset}`);
143
+ }
144
+ }
145
+ const framed = renderBoxFrame(lines, {
146
+ width: boxW,
147
+ style: "rounded",
148
+ borderColor: p.accent,
149
+ title: `${p.accent}${p.bold}❯${p.reset}`,
150
+ });
151
+ process.stdout.write("\n");
152
+ for (const line of framed) {
153
+ process.stdout.write(line + "\n");
154
+ }
155
+ }
156
+ function writeAgentText(text) {
157
+ if (isThinking) {
158
+ isThinking = false;
159
+ if (showThinkingText && renderer) {
160
+ renderer.flush();
161
+ const termW = process.stdout.columns || 80;
162
+ const w = Math.min(80, termW);
163
+ renderer.writeLine(`${p.dim}${"─".repeat(w)}${p.reset}`);
164
+ }
165
+ }
166
+ stopCurrentSpinner();
167
+ if (!renderer)
168
+ startAgentResponse();
169
+ renderer.push(text);
170
+ flushOutput();
171
+ }
172
+ function showToolCall(title, command) {
173
+ stopCurrentSpinner();
174
+ if (!renderer)
175
+ startAgentResponse();
176
+ renderer.flush();
177
+ const termW = process.stdout.columns || 80;
178
+ const lines = renderToolCall({ title, command: command || undefined }, termW);
179
+ for (const line of lines) {
180
+ renderer.writeLine(line);
181
+ }
182
+ // Reset output tracking for the new tool
183
+ commandOutputLineCount = 0;
184
+ commandOutputOverflow = 0;
185
+ }
186
+ function showToolComplete(exitCode) {
187
+ if (!renderer)
188
+ return;
189
+ const termW = process.stdout.columns || 80;
190
+ const lines = renderToolResult({ exitCode }, termW);
191
+ for (const line of lines) {
192
+ renderer.writeLine(line);
193
+ }
194
+ }
195
+ function startThinkingSpinner(label = "Thinking") {
196
+ stopCurrentSpinner();
197
+ spinner = startSpinner(label);
198
+ }
199
+ function stopCurrentSpinner() {
200
+ if (spinner) {
201
+ stopToolSpinner(spinner);
202
+ spinner = null;
203
+ }
204
+ }
205
+ function writeCommandOutput(chunk) {
206
+ if (!renderer)
207
+ return;
208
+ commandOutputBuffer += chunk;
209
+ const lines = commandOutputBuffer.split("\n");
210
+ commandOutputBuffer = lines.pop();
211
+ for (const line of lines) {
212
+ if (commandOutputLineCount < MAX_COMMAND_OUTPUT_LINES) {
213
+ renderer.writeLine(`${p.dim} ${line}${p.reset}`);
214
+ commandOutputLineCount++;
215
+ }
216
+ else {
217
+ commandOutputOverflow++;
218
+ }
219
+ }
220
+ }
221
+ function flushCommandOutput() {
222
+ if (!renderer)
223
+ return;
224
+ if (commandOutputBuffer) {
225
+ if (commandOutputLineCount < MAX_COMMAND_OUTPUT_LINES) {
226
+ renderer.writeLine(`${p.dim} ${commandOutputBuffer}${p.reset}`);
227
+ commandOutputLineCount++;
228
+ }
229
+ else {
230
+ commandOutputOverflow++;
231
+ }
232
+ commandOutputBuffer = "";
233
+ }
234
+ if (commandOutputOverflow > 0) {
235
+ renderer.writeLine(`${p.dim} … ${commandOutputOverflow} more lines${p.reset}`);
236
+ commandOutputOverflow = 0;
237
+ }
238
+ }
239
+ const DIFF_MAX_LINES = 20;
240
+ function diffTitle(filePath, diff) {
241
+ const stats = diff.isNewFile
242
+ ? `${p.success}+${diff.added}${p.reset}`
243
+ : `${p.success}+${diff.added}${p.reset} ${p.error}-${diff.removed}${p.reset}`;
244
+ return `${p.dim}${filePath}${p.reset} ${stats}`;
245
+ }
246
+ function showFileDiff(filePath, diff) {
247
+ if (diff.isIdentical)
248
+ return;
249
+ const termW = process.stdout.columns || 80;
250
+ const boxW = Math.min(84, termW);
251
+ const contentW = boxW - 4;
252
+ const diffLines = renderDiff(diff, {
253
+ width: contentW,
254
+ filePath,
255
+ maxLines: DIFF_MAX_LINES,
256
+ trueColor: true,
257
+ mode: "unified",
258
+ });
259
+ const lastLine = diffLines[diffLines.length - 1] ?? "";
260
+ const isTruncated = lastLine.includes("… ");
261
+ if (isTruncated) {
262
+ lastTruncatedDiff = { filePath, diff, expanded: false };
263
+ }
264
+ else {
265
+ lastTruncatedDiff = null;
266
+ }
267
+ const body = diffLines.length > 1 ? ["", ...diffLines.slice(1), ""] : diffLines;
268
+ const footer = isTruncated
269
+ ? [` ${p.dim}ctrl+o to expand${p.reset}`]
270
+ : undefined;
271
+ const framed = renderBoxFrame(body, {
272
+ width: boxW,
273
+ style: "rounded",
274
+ borderColor: p.dim,
275
+ title: diffTitle(filePath, diff),
276
+ footer,
277
+ });
278
+ if (!renderer)
279
+ startAgentResponse();
280
+ for (const line of framed) {
281
+ renderer.writeLine(line);
282
+ }
283
+ }
284
+ function expandLastDiff() {
285
+ if (!lastTruncatedDiff)
286
+ return;
287
+ const entry = lastTruncatedDiff;
288
+ entry.expanded = !entry.expanded;
289
+ if (!entry.expanded) {
290
+ showFileDiffCached(entry);
291
+ return;
292
+ }
293
+ if (!entry.expandedLines) {
294
+ const { filePath, diff } = entry;
295
+ const termW = process.stdout.columns || 80;
296
+ const boxW = Math.min(120, termW);
297
+ const contentW = boxW - 4;
298
+ const diffLines = renderDiff(diff, {
299
+ width: contentW,
300
+ filePath,
301
+ maxLines: 500,
302
+ trueColor: true,
303
+ });
304
+ const body = diffLines.length > 1 ? ["", ...diffLines.slice(1), ""] : diffLines;
305
+ entry.expandedLines = renderBoxFrame(body, {
306
+ width: boxW,
307
+ style: "rounded",
308
+ borderColor: p.dim,
309
+ title: diffTitle(filePath, diff),
310
+ footer: [` ${p.dim}ctrl+o to collapse${p.reset}`],
311
+ });
312
+ }
313
+ process.stdout.write("\n");
314
+ for (const line of entry.expandedLines) {
315
+ process.stdout.write(line + "\n");
316
+ }
317
+ }
318
+ function showFileDiffCached(entry) {
319
+ const { filePath, diff } = entry;
320
+ const termW = process.stdout.columns || 80;
321
+ const boxW = Math.min(84, termW);
322
+ const contentW = boxW - 4;
323
+ const diffLines = renderDiff(diff, {
324
+ width: contentW,
325
+ filePath,
326
+ maxLines: DIFF_MAX_LINES,
327
+ trueColor: true,
328
+ mode: "unified",
329
+ });
330
+ const body = diffLines.length > 1 ? ["", ...diffLines.slice(1), ""] : diffLines;
331
+ const framed = renderBoxFrame(body, {
332
+ width: boxW,
333
+ style: "rounded",
334
+ borderColor: p.dim,
335
+ title: diffTitle(filePath, diff),
336
+ footer: [` ${p.dim}ctrl+o to expand${p.reset}`],
337
+ });
338
+ process.stdout.write("\n");
339
+ for (const line of framed) {
340
+ process.stdout.write(line + "\n");
341
+ }
342
+ }
343
+ function toggleThinkingDisplay() {
344
+ showThinkingText = !showThinkingText;
345
+ const state = showThinkingText ? "on" : "off";
346
+ process.stdout.write(`\n${p.dim}Thinking display: ${state}${p.reset}\n`);
347
+ }
348
+ function showError(message) {
349
+ process.stdout.write(`\n${p.error}Error: ${message}${p.reset}\n`);
350
+ }
351
+ function showInfo(message) {
352
+ process.stdout.write(`${p.muted}${message}${p.reset}\n`);
353
+ }
354
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env node
2
+ import { Shell } from "./shell.js";
3
+ import { createCore } from "./core.js";
4
+ import { palette as p } from "./utils/palette.js";
5
+ import tuiRenderer from "./extensions/tui-renderer.js";
6
+ import slashCommands from "./extensions/slash-commands.js";
7
+ import fileAutocomplete from "./extensions/file-autocomplete.js";
8
+ import shellRecall from "./extensions/shell-recall.js";
9
+ import { loadExtensions } from "./extension-loader.js";
10
+ function parseArgs(argv) {
11
+ // Priority: CLI args > Environment variables > Config file > Defaults
12
+ const defaultAgent = process.env.AGENT_SH_AGENT || "pi-acp";
13
+ let agentCommand = defaultAgent;
14
+ let agentArgs = [];
15
+ let model;
16
+ let extensions;
17
+ const shell = process.env.SHELL || "/bin/bash";
18
+ for (let i = 0; i < argv.length; i++) {
19
+ const arg = argv[i];
20
+ if (arg === "--agent" && argv[i + 1]) {
21
+ agentCommand = argv[++i];
22
+ }
23
+ else if (arg === "--agent-args" && argv[i + 1]) {
24
+ const argsString = argv[++i];
25
+ agentArgs = argsString.split(" ");
26
+ // Extract model from agent args if provided
27
+ const modelArgIndex = agentArgs.findIndex(a => a === "--model" || a === "-m");
28
+ if (modelArgIndex !== -1 && agentArgs[modelArgIndex + 1]) {
29
+ model = agentArgs[modelArgIndex + 1];
30
+ }
31
+ }
32
+ else if (arg === "--shell" && argv[i + 1]) {
33
+ return { agentCommand, agentArgs, shell: argv[++i], model, extensions };
34
+ }
35
+ else if ((arg === "--extensions" || arg === "-e") && argv[i + 1]) {
36
+ const exts = argv[++i].split(",").map(s => s.trim());
37
+ extensions = extensions ? [...extensions, ...exts] : exts;
38
+ }
39
+ else if (arg === "--help" || arg === "-h") {
40
+ console.log(`agent-sh — a shell-first terminal with ACP agent access
41
+
42
+ Usage: agent-sh [options]
43
+
44
+ Quick Start:
45
+ npm start Start with default agent (pi-acp)
46
+ npm run pi Start with pi-acp agent
47
+ npm run claude Start with Claude agent
48
+
49
+ Options:
50
+ --agent <cmd> Agent command to launch (default: $AGENT_SH_AGENT or "pi-acp")
51
+ --agent-args <args> Arguments for the agent (space-separated, quoted)
52
+ --shell <path> Shell to use (default: $SHELL or /bin/bash)
53
+ -e, --extensions Extensions to load (comma-separated, repeatable)
54
+ -h, --help Show this help
55
+
56
+ Extensions:
57
+ Extensions are loaded from (in order):
58
+ 1. -e flags: npm packages or file paths
59
+ 2. settings: ~/.agent-sh/settings.json → "extensions": [...]
60
+ 3. directory: ~/.agent-sh/extensions/ (files or dirs with index.ts)
61
+
62
+ Environment Variables:
63
+ AGENT_SH_AGENT Default agent to use (e.g., "pi-acp", "claude")
64
+
65
+ Examples:
66
+ npm start --agent pi-acp
67
+ npm start -- -e my-extension-package
68
+ npm start -- -e ./local-ext.ts -e another-package
69
+
70
+ Inside the shell:
71
+ Type normally Commands run in your real shell
72
+ > <query> Send query to the AI agent
73
+ > /help Show available slash commands
74
+ Ctrl-C Cancel agent response (or signal shell as usual)
75
+ `);
76
+ process.exit(0);
77
+ }
78
+ }
79
+ return { agentCommand, agentArgs, shell, model, extensions };
80
+ }
81
+ function formatAgentInfo(agentInfo, model) {
82
+ const name = agentInfo.name.replace(/-acp$/, "").replace(/-/g, " ");
83
+ let infoStr = `${p.dim}${name}${p.reset}`;
84
+ if (model) {
85
+ const cleanModel = model
86
+ .replace(/^openai\//i, "")
87
+ .replace(/^anthropic\//i, "")
88
+ .replace(/^google\//i, "");
89
+ infoStr += ` ${p.dim}(${cleanModel})${p.reset}`;
90
+ }
91
+ return `${infoStr} ${p.success}●${p.reset}`;
92
+ }
93
+ async function main() {
94
+ const config = parseArgs(process.argv.slice(2));
95
+ // ── Core (frontend-agnostic) ──────────────────────────────────
96
+ const core = createCore(config);
97
+ const { bus, client } = core;
98
+ // ── Interactive frontend ──────────────────────────────────────
99
+ process.stdout.write(`\x1b]0;agent-sh\x07`);
100
+ const cols = process.stdout.columns || 80;
101
+ const rows = process.stdout.rows || 24;
102
+ const cleanup = () => {
103
+ core.kill();
104
+ shell.kill();
105
+ if (process.stdin.isTTY) {
106
+ process.stdin.setRawMode(false);
107
+ }
108
+ process.exit(0);
109
+ };
110
+ const shell = new Shell({
111
+ bus,
112
+ cols,
113
+ rows,
114
+ shell: config.shell || process.env.SHELL || "/bin/bash",
115
+ cwd: process.cwd(),
116
+ onShowAgentInfo: () => {
117
+ if (client.isConnected()) {
118
+ const agentInfo = client.getAgentInfo();
119
+ const model = client.getModel();
120
+ if (agentInfo) {
121
+ return { info: formatAgentInfo(agentInfo, model) };
122
+ }
123
+ }
124
+ return { info: "" };
125
+ },
126
+ });
127
+ // ── Extensions ────────────────────────────────────────────────
128
+ const extCtx = core.extensionContext({ quit: cleanup });
129
+ tuiRenderer(extCtx);
130
+ slashCommands(extCtx);
131
+ fileAutocomplete(extCtx);
132
+ shellRecall(extCtx);
133
+ await loadExtensions(extCtx, config.extensions);
134
+ // ── Agent connection (async — don't block shell startup) ──────
135
+ core.start().catch((err) => {
136
+ console.error(`Failed to connect to ${config.agentCommand}:`, err);
137
+ });
138
+ // ── Terminal lifecycle ────────────────────────────────────────
139
+ process.on("SIGTERM", cleanup);
140
+ process.on("SIGHUP", cleanup);
141
+ process.stdout.on("resize", () => {
142
+ shell.resize(process.stdout.columns || 80, process.stdout.rows || 24);
143
+ });
144
+ shell.onExit((e) => {
145
+ core.kill();
146
+ if (process.stdin.isTTY) {
147
+ process.stdin.setRawMode(false);
148
+ }
149
+ process.exit(e.exitCode);
150
+ });
151
+ if (process.stdin.isTTY) {
152
+ process.stdin.setRawMode(true);
153
+ }
154
+ process.stdin.resume();
155
+ }
156
+ main().catch((err) => {
157
+ console.error("Fatal:", err);
158
+ process.exit(1);
159
+ });
@@ -0,0 +1,48 @@
1
+ import type { EventBus } from "./event-bus.js";
2
+ /**
3
+ * Narrow contract between InputHandler and its host (Shell).
4
+ * InputHandler never touches the PTY or EventBus directly —
5
+ * it goes through this interface for all cross-cutting concerns.
6
+ */
7
+ export interface InputContext {
8
+ isForegroundBusy(): boolean;
9
+ getCwd(): string;
10
+ isAgentActive(): boolean;
11
+ writeToPty(data: string): void;
12
+ onCommandEntered(command: string, cwd: string): void;
13
+ redrawPrompt(): void;
14
+ freshPrompt(): void;
15
+ }
16
+ export declare class InputHandler {
17
+ private ctx;
18
+ private lineBuffer;
19
+ private agentInputMode;
20
+ private agentInputBuffer;
21
+ private autocompleteActive;
22
+ private autocompleteIndex;
23
+ private autocompleteItems;
24
+ private autocompleteLines;
25
+ private bus;
26
+ private onShowAgentInfo;
27
+ constructor(opts: {
28
+ ctx: InputContext;
29
+ bus: EventBus;
30
+ onShowAgentInfo: () => {
31
+ info: string;
32
+ model?: string;
33
+ };
34
+ });
35
+ /** Write the agent prompt line (clear + info prefix + ❯ + buffer text). */
36
+ private writeAgentPromptLine;
37
+ handleInput(data: string): void;
38
+ private enterAgentInputMode;
39
+ private exitAgentInputMode;
40
+ printPrompt(): void;
41
+ private renderAgentInput;
42
+ private updateAutocomplete;
43
+ private renderAutocomplete;
44
+ private clearAutocompleteLines;
45
+ private applyAutocomplete;
46
+ private dismissAutocomplete;
47
+ private handleAgentInput;
48
+ }