@agent-api/cli 0.4.23 → 0.4.24

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/runtime.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export declare const cliName = "agent-tui";
2
2
  export declare const cliAuthor = "AgentsWay";
3
- export declare const cliVersion = "0.4.23";
3
+ export declare const cliVersion = "0.4.24";
4
4
  export declare const legacyCliName = "agent-api-cli";
package/dist/runtime.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export const cliName = "agent-tui";
2
2
  export const cliAuthor = "AgentsWay";
3
- export const cliVersion = "0.4.23";
3
+ export const cliVersion = "0.4.24";
4
4
  export const legacyCliName = "agent-api-cli";
@@ -1 +1,35 @@
1
- export declare function writeClipboard(text: string, stdout?: NodeJS.WriteStream): Promise<boolean>;
1
+ export interface ClipboardCapabilities {
2
+ nativeRead: {
3
+ available: boolean;
4
+ label?: string;
5
+ };
6
+ nativeWrite: {
7
+ available: boolean;
8
+ label?: string;
9
+ };
10
+ osc52Write: {
11
+ available: boolean;
12
+ reliable: false;
13
+ };
14
+ }
15
+ export interface ClipboardWriteResult {
16
+ method: "native" | "osc52" | null;
17
+ ok: boolean;
18
+ reliable: boolean;
19
+ }
20
+ export declare function detectClipboardCapabilities(stdout?: NodeJS.WriteStream): Promise<ClipboardCapabilities>;
21
+ export declare function formatClipboardCapabilities(capabilities: ClipboardCapabilities): string;
22
+ export declare function writeClipboard(text: string, stdout?: NodeJS.WriteStream, capabilities?: ClipboardCapabilities | null): Promise<{
23
+ method: "native";
24
+ ok: true;
25
+ reliable: true;
26
+ } | {
27
+ method: "osc52";
28
+ ok: true;
29
+ reliable: false;
30
+ } | {
31
+ method: null;
32
+ ok: false;
33
+ reliable: false;
34
+ }>;
35
+ export declare function readClipboard(capabilities?: ClipboardCapabilities | null): Promise<string | null>;
@@ -1,9 +1,49 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { platform } from "node:os";
3
- export async function writeClipboard(text, stdout = process.stdout) {
4
- const osc52Written = writeOsc52(text, stdout);
5
- const nativeWritten = await writeNativeClipboard(text);
6
- return osc52Written || nativeWritten;
3
+ export async function detectClipboardCapabilities(stdout = process.stdout) {
4
+ const nativeWrite = await firstAvailableCommand(nativeClipboardWriteCommands());
5
+ const nativeRead = await firstAvailableCommand(nativeClipboardReadCommands());
6
+ return {
7
+ nativeRead: nativeRead ? { available: true, label: nativeRead.label } : { available: false },
8
+ nativeWrite: nativeWrite ? { available: true, label: nativeWrite.label } : { available: false },
9
+ osc52Write: { available: Boolean(stdout.isTTY), reliable: false },
10
+ };
11
+ }
12
+ export function formatClipboardCapabilities(capabilities) {
13
+ const write = capabilities.nativeWrite.available
14
+ ? `write=${capabilities.nativeWrite.label}`
15
+ : capabilities.osc52Write.available
16
+ ? "write=OSC52"
17
+ : "write=none";
18
+ const read = capabilities.nativeRead.available ? `read=${capabilities.nativeRead.label}` : "read=none";
19
+ const note = capabilities.nativeWrite.available
20
+ ? ""
21
+ : capabilities.osc52Write.available
22
+ ? " (terminal may block OSC52)"
23
+ : "";
24
+ return `Clipboard: ${write}, ${read}${note}`;
25
+ }
26
+ export async function writeClipboard(text, stdout = process.stdout, capabilities) {
27
+ const nativeWritten = capabilities?.nativeWrite.available === false
28
+ ? false
29
+ : await writeNativeClipboard(text, capabilities?.nativeWrite.label);
30
+ if (nativeWritten)
31
+ return { method: "native", ok: true, reliable: true };
32
+ const osc52Written = capabilities?.osc52Write.available === false ? false : writeOsc52(text, stdout);
33
+ if (osc52Written)
34
+ return { method: "osc52", ok: true, reliable: false };
35
+ return { method: null, ok: false, reliable: false };
36
+ }
37
+ export async function readClipboard(capabilities) {
38
+ if (capabilities?.nativeRead.available === false)
39
+ return null;
40
+ const commands = nativeClipboardReadCommands(capabilities?.nativeRead.label);
41
+ for (const candidate of commands) {
42
+ const text = await readCommand(candidate.command, candidate.args);
43
+ if (text !== null)
44
+ return text;
45
+ }
46
+ return null;
7
47
  }
8
48
  function writeOsc52(text, stdout) {
9
49
  if (!stdout.isTTY)
@@ -12,32 +52,88 @@ function writeOsc52(text, stdout) {
12
52
  stdout.write(process.env.TMUX || process.env.STY ? `\x1bPtmux;\x1b${sequence}\x1b\\` : sequence);
13
53
  return true;
14
54
  }
15
- async function writeNativeClipboard(text) {
16
- const commands = nativeClipboardCommands();
17
- for (const [command, args] of commands) {
18
- if (await writeCommand(command, args, text))
55
+ async function writeNativeClipboard(text, preferredLabel) {
56
+ const commands = nativeClipboardWriteCommands(preferredLabel);
57
+ for (const candidate of commands) {
58
+ if (await writeCommand(candidate.command, candidate.args, text))
19
59
  return true;
20
60
  }
21
61
  return false;
22
62
  }
23
- function nativeClipboardCommands() {
63
+ function nativeClipboardWriteCommands(preferredLabel) {
64
+ const commands = nativeClipboardWriteCandidates();
65
+ return preferredLabel ? prioritizeCommand(commands, preferredLabel) : commands;
66
+ }
67
+ function nativeClipboardWriteCandidates() {
24
68
  if (platform() === "darwin")
25
- return [["pbcopy", []]];
69
+ return [{ args: [], command: "pbcopy", label: "pbcopy" }];
26
70
  if (platform() === "win32") {
27
- return [["powershell.exe", [
71
+ return [{
72
+ args: [
28
73
  "-NonInteractive",
29
74
  "-NoProfile",
30
75
  "-Command",
31
76
  "[Console]::InputEncoding = [System.Text.Encoding]::UTF8; Set-Clipboard -Value ([Console]::In.ReadToEnd())",
32
- ]]];
77
+ ],
78
+ command: "powershell.exe",
79
+ label: "PowerShell",
80
+ }];
33
81
  }
34
82
  const commands = [];
35
83
  if (process.env.WAYLAND_DISPLAY)
36
- commands.push(["wl-copy", []]);
37
- commands.push(["xclip", ["-selection", "clipboard"]]);
38
- commands.push(["xsel", ["--clipboard", "--input"]]);
84
+ commands.push({ args: [], command: "wl-copy", label: "wl-copy" });
85
+ commands.push({ args: ["-selection", "clipboard"], command: "xclip", label: "xclip" });
86
+ commands.push({ args: ["--clipboard", "--input"], command: "xsel", label: "xsel" });
39
87
  return commands;
40
88
  }
89
+ function nativeClipboardReadCommands(preferredLabel) {
90
+ const commands = nativeClipboardReadCandidates();
91
+ return preferredLabel ? prioritizeCommand(commands, preferredLabel) : commands;
92
+ }
93
+ function nativeClipboardReadCandidates() {
94
+ if (platform() === "darwin")
95
+ return [{ args: [], command: "pbpaste", label: "pbpaste" }];
96
+ if (platform() === "win32") {
97
+ return [{
98
+ args: [
99
+ "-NonInteractive",
100
+ "-NoProfile",
101
+ "-Command",
102
+ "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Clipboard -Raw",
103
+ ],
104
+ command: "powershell.exe",
105
+ label: "PowerShell",
106
+ }];
107
+ }
108
+ const commands = [];
109
+ if (process.env.WAYLAND_DISPLAY)
110
+ commands.push({ args: ["--no-newline"], command: "wl-paste", label: "wl-paste" });
111
+ commands.push({ args: ["-selection", "clipboard", "-out"], command: "xclip", label: "xclip" });
112
+ commands.push({ args: ["--clipboard", "--output"], command: "xsel", label: "xsel" });
113
+ return commands;
114
+ }
115
+ function prioritizeCommand(commands, label) {
116
+ return [
117
+ ...commands.filter((command) => command.label === label),
118
+ ...commands.filter((command) => command.label !== label),
119
+ ];
120
+ }
121
+ async function firstAvailableCommand(commands) {
122
+ for (const command of commands) {
123
+ if (await commandAvailable(command.command))
124
+ return command;
125
+ }
126
+ return null;
127
+ }
128
+ function commandAvailable(command) {
129
+ const checker = platform() === "win32" ? "where" : "command";
130
+ const args = platform() === "win32" ? [command] : ["-v", command];
131
+ return new Promise((resolve) => {
132
+ const child = spawn(checker, args, { shell: platform() !== "win32", stdio: "ignore" });
133
+ child.on("error", () => resolve(false));
134
+ child.on("close", (code) => resolve(code === 0));
135
+ });
136
+ }
41
137
  function writeCommand(command, args, input) {
42
138
  return new Promise((resolve) => {
43
139
  const child = spawn(command, args, { stdio: ["pipe", "ignore", "ignore"] });
@@ -46,3 +142,12 @@ function writeCommand(command, args, input) {
46
142
  child.stdin.end(input);
47
143
  });
48
144
  }
145
+ function readCommand(command, args) {
146
+ return new Promise((resolve) => {
147
+ const child = spawn(command, args, { stdio: ["ignore", "pipe", "ignore"] });
148
+ const chunks = [];
149
+ child.on("error", () => resolve(null));
150
+ child.stdout.on("data", (chunk) => chunks.push(chunk));
151
+ child.on("close", (code) => resolve(code === 0 ? Buffer.concat(chunks).toString("utf8") : null));
152
+ });
153
+ }
@@ -3,9 +3,9 @@ import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from "reac
3
3
  import { useApp, useInput, useStdout } from "ink";
4
4
  import { createAgentEngine, defaultBaseURL, } from "@agent-api/app-engine/core";
5
5
  import { createWorkbenchAuthController, createWorkbenchAuthGateController, parseWorkbenchCommand, } from "@agent-api/app-engine/workbench";
6
- import { buildWorkbenchRenderModel, copyTextFromActivitySelection, copyTextFromRenderModel, copyTextFromTranscriptSelection, createWorkbenchTerminalController, initialWorkbenchTerminalState, normalizeTerminalState, selectedPanelRange, } from "@agent-api/app-engine/terminal";
6
+ import { buildWorkbenchRenderModel, copyTextFromActivitySelection, copyTextFromHeaderSelection, copyTextFromRenderModel, copyTextFromTranscriptSelection, createWorkbenchTerminalController, initialWorkbenchTerminalState, normalizeTerminalState, selectedPanelRange, } from "@agent-api/app-engine/terminal";
7
7
  import { InkAuthGate, InkWorkbenchScreen } from "./components.js";
8
- import { writeClipboard } from "../clipboard.js";
8
+ import { detectClipboardCapabilities, formatClipboardCapabilities, readClipboard, writeClipboard, } from "../clipboard.js";
9
9
  import { disableMouseReporting, enableMouseReporting, parseMouseEvent } from "../mouse.js";
10
10
  export function ChatApp({ options }) {
11
11
  return _jsx(AuthenticatedChatApp, { options: options });
@@ -137,6 +137,7 @@ function WorkbenchApp({ authController, onLogin, onLogout, onDeleteProfile, onSw
137
137
  const app = useApp();
138
138
  const { stdout } = useStdout();
139
139
  const terminalSize = useTerminalSize(stdout);
140
+ const [clipboardCapabilities, setClipboardCapabilities] = useState(null);
140
141
  const [terminalState, setTerminalState] = useState(() => initialWorkbenchTerminalState());
141
142
  const [spinnerFrame, setSpinnerFrame] = useState(0);
142
143
  const agentEngineRef = useRef(null);
@@ -195,23 +196,53 @@ function WorkbenchApp({ authController, onLogin, onLogout, onDeleteProfile, onSw
195
196
  return;
196
197
  }
197
198
  try {
198
- const copied = await writeClipboard(text, stdout);
199
+ const copied = await writeClipboard(text, stdout, clipboardCapabilities);
199
200
  dispatch({
200
201
  type: "activity.add",
201
- level: copied ? "success" : "warning",
202
- text: copied ? `Copied ${target} to clipboard` : `Clipboard unavailable for ${target}`,
202
+ level: copied.reliable ? "success" : copied.ok ? "warning" : "warning",
203
+ text: copied.reliable
204
+ ? `Copied ${target} to clipboard`
205
+ : copied.ok
206
+ ? `Sent ${target} copy request to terminal clipboard (OSC52); your terminal may block it`
207
+ : `Clipboard unavailable for ${target}`,
203
208
  });
204
209
  }
205
210
  catch (error) {
206
211
  dispatch({ type: "activity.add", level: "error", text: `Copy failed: ${userFacingError(error)}` });
207
212
  }
208
213
  }
214
+ async function pasteClipboardIntoInput() {
215
+ try {
216
+ const text = await readClipboard(clipboardCapabilities);
217
+ if (!text) {
218
+ dispatch({ type: "activity.add", level: "warning", text: "Clipboard paste unavailable" });
219
+ return;
220
+ }
221
+ setTerminalState((current) => {
222
+ const normalized = normalizeTerminalState({ ...current, focusedPanel: "input" }, renderModel);
223
+ const result = terminalController.handle(text, {}, normalized, {
224
+ busy: state.busy,
225
+ renderModel,
226
+ });
227
+ return result.state;
228
+ });
229
+ dispatch({ type: "activity.add", level: "success", text: "Pasted clipboard into input" });
230
+ }
231
+ catch (error) {
232
+ dispatch({ type: "activity.add", level: "error", text: `Paste failed: ${userFacingError(error)}` });
233
+ }
234
+ }
209
235
  function copyTextForTarget(target) {
210
236
  if (target === "transcript" || target === "page") {
211
237
  const selection = selectedPanelRange(terminalState.transcriptSelectionAnchor, terminalState.transcriptCursor);
212
238
  if (selection)
213
239
  return copyTextFromTranscriptSelection(renderModel.transcript.lines, selection);
214
240
  }
241
+ if (target === "header") {
242
+ const selection = selectedPanelRange(terminalState.headerSelectionAnchor, terminalState.headerCursor);
243
+ if (selection)
244
+ return copyTextFromHeaderSelection(renderModel.header.lines, selection);
245
+ }
215
246
  if (target === "activity") {
216
247
  const selection = selectedPanelRange(terminalState.activitySelectionAnchor, terminalState.activityCursor);
217
248
  if (selection)
@@ -228,6 +259,18 @@ function WorkbenchApp({ authController, onLogin, onLogout, onDeleteProfile, onSw
228
259
  mounted = false;
229
260
  };
230
261
  }, [agentEngine]);
262
+ useEffect(() => {
263
+ let mounted = true;
264
+ detectClipboardCapabilities(stdout).then((capabilities) => {
265
+ if (!mounted)
266
+ return;
267
+ setClipboardCapabilities(capabilities);
268
+ dispatch({ type: "activity.add", text: formatClipboardCapabilities(capabilities) });
269
+ });
270
+ return () => {
271
+ mounted = false;
272
+ };
273
+ }, [dispatch, stdout]);
231
274
  useEffect(() => {
232
275
  if (!state.contextEnabled || state.workdir)
233
276
  return;
@@ -301,6 +344,9 @@ function WorkbenchApp({ authController, onLogin, onLogout, onDeleteProfile, onSw
301
344
  case "copy":
302
345
  void copyPanelText(effect.target);
303
346
  break;
347
+ case "paste":
348
+ void pasteClipboardIntoInput();
349
+ break;
304
350
  }
305
351
  }
306
352
  });
@@ -320,7 +366,7 @@ function WorkbenchApp({ authController, onLogin, onLogout, onDeleteProfile, onSw
320
366
  useEffect(() => {
321
367
  return () => agentEngine.dispose();
322
368
  }, [agentEngine]);
323
- return (_jsx(InkWorkbenchScreen, { activityCursor: terminalState.activityCursor, activitySelection: selectedPanelRange(terminalState.activitySelectionAnchor, terminalState.activityCursor), focusedPanel: terminalState.focusedPanel, renderModel: renderModel, spinnerFrame: spinnerFrame, transcriptCursor: terminalState.transcriptCursor, transcriptSelection: selectedPanelRange(terminalState.transcriptSelectionAnchor, terminalState.transcriptCursor) }));
369
+ return (_jsx(InkWorkbenchScreen, { activityCursor: terminalState.activityCursor, activitySelection: selectedPanelRange(terminalState.activitySelectionAnchor, terminalState.activityCursor), focusedPanel: terminalState.focusedPanel, headerCursor: terminalState.headerCursor, headerSelection: selectedPanelRange(terminalState.headerSelectionAnchor, terminalState.headerCursor), renderModel: renderModel, spinnerFrame: spinnerFrame, transcriptCursor: terminalState.transcriptCursor, transcriptSelection: selectedPanelRange(terminalState.transcriptSelectionAnchor, terminalState.transcriptCursor) }));
324
370
  }
325
371
  function useTerminalSize(stdout) {
326
372
  const [size, setSize] = useState(() => ({
@@ -361,6 +407,9 @@ function sameTerminalState(a, b) {
361
407
  && a.cursor === b.cursor
362
408
  && a.draft === b.draft
363
409
  && a.focusedPanel === b.focusedPanel
410
+ && a.headerCursor.line === b.headerCursor.line
411
+ && a.headerCursor.column === b.headerCursor.column
412
+ && samePositionOrNull(a.headerSelectionAnchor, b.headerSelectionAnchor)
364
413
  && a.mouseDragPanel === b.mouseDragPanel
365
414
  && a.selectionAnchor === b.selectionAnchor
366
415
  && a.transcriptCursor.line === b.transcriptCursor.line
@@ -1,10 +1,12 @@
1
1
  import React from "react";
2
2
  import { type WorkbenchPanelPosition, type WorkbenchPanelSelection, type WorkbenchRenderModel } from "@agent-api/app-engine/terminal";
3
3
  import { type AuthGateState } from "@agent-api/app-engine/workbench";
4
- export declare function InkWorkbenchScreen({ activityCursor, activitySelection, focusedPanel, renderModel, spinnerFrame, transcriptCursor, transcriptSelection, }: {
4
+ export declare function InkWorkbenchScreen({ activityCursor, activitySelection, focusedPanel, headerCursor, headerSelection, renderModel, spinnerFrame, transcriptCursor, transcriptSelection, }: {
5
5
  activityCursor: WorkbenchPanelPosition;
6
6
  activitySelection: WorkbenchPanelSelection | null;
7
7
  focusedPanel: "activity" | "header" | "input" | "transcript";
8
+ headerCursor: WorkbenchPanelPosition;
9
+ headerSelection: WorkbenchPanelSelection | null;
8
10
  renderModel: WorkbenchRenderModel;
9
11
  spinnerFrame: number;
10
12
  transcriptCursor: WorkbenchPanelPosition;
@@ -2,15 +2,15 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { Box, Text } from "ink";
3
3
  import { activityColor, busySpinner, } from "@agent-api/app-engine/terminal";
4
4
  import { authMethods, } from "@agent-api/app-engine/workbench";
5
- export function InkWorkbenchScreen({ activityCursor, activitySelection, focusedPanel, renderModel, spinnerFrame, transcriptCursor, transcriptSelection, }) {
6
- const activity = (_jsxs(Box, { flexDirection: "column", width: renderModel.layout === "wide" ? "28%" : "100%", height: renderModel.activityHeight, borderStyle: "single", borderColor: panelBorderColor(focusedPanel === "activity"), paddingX: 1, children: [_jsx(Text, { bold: true, color: focusedPanel === "activity" ? "cyan" : undefined, wrap: "truncate", children: "Activity" }), renderModel.visibleActivities.map((activity, index) => {
5
+ export function InkWorkbenchScreen({ activityCursor, activitySelection, focusedPanel, headerCursor, headerSelection, renderModel, spinnerFrame, transcriptCursor, transcriptSelection, }) {
6
+ const activity = (_jsxs(Box, { borderColor: panelBorderColor(focusedPanel === "activity"), borderStyle: "round", flexDirection: "column", height: renderModel.activityHeight, marginLeft: renderModel.layout === "wide" ? 1 : 0, paddingX: 1, width: renderModel.layout === "wide" ? "27%" : "100%", children: [_jsx(Text, { bold: true, color: focusedPanel === "activity" ? "cyan" : undefined, wrap: "truncate", children: "Activity" }), renderModel.visibleActivities.map((activity, index) => {
7
7
  const cursor = focusedPanel === "activity" && index === activityCursor.line;
8
8
  const text = `${new Date(activity.timestamp).toLocaleTimeString()} ${activity.text}`;
9
9
  return (_jsxs(Text, { color: activityColor(activity.level), wrap: "truncate", children: [cursor ? _jsx(Text, { color: "cyan", children: "\u203A " }) : _jsx(Text, { children: " " }), _jsx(SelectableText, { cursorColumn: cursor && !activitySelection ? activityCursor.column : null, selection: lineSelection(index, activitySelection), text: text || " " })] }, activity.id));
10
10
  })] }));
11
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, { focused: focusedPanel === "header", contextEnabled: renderModel.header.contextEnabled, conversation: renderModel.header.conversation, conversationId: renderModel.header.conversationId, conversationPreviousResponseId: renderModel.header.conversationPreviousResponseId, conversationStatus: renderModel.header.conversationStatus, model: renderModel.header.model, accessMode: renderModel.header.accessMode, pendingLocalLabel: renderModel.header.pendingLocalLabel, preset: renderModel.header.preset, profile: renderModel.header.profile, renderMode: renderModel.header.renderMode, workdir: renderModel.header.workdir }), _jsxs(Box, { height: renderModel.viewportHeight, flexDirection: renderModel.layout === "wide" ? "row" : "column", children: [_jsxs(Box, { borderStyle: "single", borderColor: panelBorderColor(focusedPanel === "transcript"), flexDirection: "column", height: renderModel.transcript.viewportHeight + 2, paddingX: 1, width: renderModel.layout === "wide" ? "72%" : "100%", children: [renderModel.transcript.visibleLines.map((line, index) => (_jsx(TranscriptText, { cursorColumn: focusedPanel === "transcript" && renderModel.transcript.startLine + index - 1 === transcriptCursor.line && !transcriptSelection
11
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, { focused: focusedPanel === "header", cursor: headerCursor, selection: headerSelection, contextEnabled: renderModel.header.contextEnabled, conversation: renderModel.header.conversation, conversationId: renderModel.header.conversationId, conversationPreviousResponseId: renderModel.header.conversationPreviousResponseId, conversationStatus: renderModel.header.conversationStatus, lines: renderModel.header.lines, model: renderModel.header.model, accessMode: renderModel.header.accessMode, pendingLocalLabel: renderModel.header.pendingLocalLabel, preset: renderModel.header.preset, profile: renderModel.header.profile, renderMode: renderModel.header.renderMode, workdir: renderModel.header.workdir }), _jsxs(Box, { height: renderModel.viewportHeight, flexDirection: renderModel.layout === "wide" ? "row" : "column", children: [_jsxs(Box, { borderStyle: "round", borderColor: panelBorderColor(focusedPanel === "transcript"), flexDirection: "column", height: renderModel.transcript.viewportHeight + 2, paddingX: 1, width: renderModel.layout === "wide" ? "72%" : "100%", children: [renderModel.transcript.visibleLines.map((line, index) => (_jsx(TranscriptText, { cursorColumn: focusedPanel === "transcript" && renderModel.transcript.startLine + index - 1 === transcriptCursor.line && !transcriptSelection
12
12
  ? transcriptCursor.column
13
- : null, line: line, lineSelection: lineSelection(renderModel.transcript.startLine + index - 1, transcriptSelection), lineCursor: focusedPanel === "transcript" && renderModel.transcript.startLine + index - 1 === transcriptCursor.line }, line.id))), renderModel.transcript.visibleLines.length === 0 && _jsx(Text, { color: "gray", children: "No transcript lines." })] }), activity] }), _jsxs(Box, { borderStyle: "single", borderColor: panelBorderColor(focusedPanel === "input"), paddingX: 1, flexDirection: "column", children: [_jsxs(Box, { children: [renderModel.input.fullAccess && (_jsx(Text, { color: "red", bold: true, inverse: true, children: "FULL ACCESS" })), renderModel.input.fullAccess && _jsx(Text, { children: " " }), _jsx(Text, { color: renderModel.input.busy ? "yellow" : "green", children: renderModel.input.label }), renderModel.input.statusText && (_jsxs(Text, { color: "yellow", children: [" ", busySpinner(spinnerFrame), " ", renderModel.input.statusText] }))] }), _jsx(Box, { flexDirection: "column", children: renderModel.input.lines.map((line, index) => (_jsx(Text, { wrap: "truncate", children: line.spans.map((span, spanIndex) => (_jsx(Text, { inverse: span.inverse, children: span.text }, spanIndex))) }, index))) })] }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { color: "gray", wrap: "truncate", children: renderModel.footerText }) })] }));
13
+ : null, line: line, lineSelection: lineSelection(renderModel.transcript.startLine + index - 1, transcriptSelection), lineCursor: focusedPanel === "transcript" && renderModel.transcript.startLine + index - 1 === transcriptCursor.line }, line.id))), renderModel.transcript.visibleLines.length === 0 && _jsx(Text, { color: "gray", children: "No transcript lines." })] }), activity] }), _jsxs(Box, { borderStyle: "round", borderColor: panelBorderColor(focusedPanel === "input"), paddingX: 1, flexDirection: "column", children: [_jsxs(Box, { children: [renderModel.input.fullAccess && (_jsx(Text, { color: "red", bold: true, inverse: true, children: "FULL ACCESS" })), renderModel.input.fullAccess && _jsx(Text, { children: " " }), _jsx(Text, { color: renderModel.input.busy ? "yellow" : "green", children: renderModel.input.label }), renderModel.input.statusText && (_jsxs(Text, { color: "yellow", children: [" ", busySpinner(spinnerFrame), " ", renderModel.input.statusText] }))] }), _jsx(Box, { flexDirection: "column", children: renderModel.input.lines.map((line, index) => (_jsx(Text, { wrap: "truncate", children: line.spans.map((span, spanIndex) => (_jsx(Text, { inverse: span.inverse, children: span.text }, spanIndex))) }, index))) })] }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { color: "gray", wrap: "truncate", children: renderModel.footerText }) })] }));
14
14
  }
15
15
  function TranscriptText({ cursorColumn, line, lineCursor, lineSelection, }) {
16
16
  const anchor = lineCursor ? _jsx(Text, { color: "cyan", children: "\u203A " }) : line.anchor ? _jsx(Text, { color: "cyan", children: "\u25B8 " }) : _jsx(Text, { children: " " });
@@ -85,10 +85,19 @@ export function InkAuthGate({ cursorVisible, state }) {
85
85
  return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Agent API Workbench" }), _jsx(Text, { color: "gray", children: "Authentication required before starting the conversation UI." })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: state.error ? "red" : "gray", children: state.error || state.message }), state.status === "checking" && _jsx(Text, { color: "yellow", children: "Checking..." }), state.status === "select" && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [authMethods.map((method, index) => (_jsxs(Text, { color: index === state.selectedMethod ? "green" : "gray", children: [index === state.selectedMethod ? "›" : " ", " ", method.label, " - ", method.description] }, method.method))), _jsx(Text, { color: "gray", children: "Use \u2191/\u2193 and Enter." })] })), state.status === "api_profile" && _jsx(AuthPrompt, { cursorVisible: cursorVisible, label: "Profile", value: state.profile }), state.status === "api_base_url" && _jsx(AuthPrompt, { cursorVisible: cursorVisible, label: "Base URL", value: state.baseURL }), state.status === "api_key" && _jsx(AuthPrompt, { cursorVisible: cursorVisible, label: "API key", value: state.apiKey ? "•".repeat(Math.min(state.apiKey.length, 32)) : "" }), state.status === "browser_profile" && _jsx(AuthPrompt, { cursorVisible: cursorVisible, label: "Profile", value: state.profile }), state.status === "browser_base_url" && _jsx(AuthPrompt, { cursorVisible: cursorVisible, label: "Base URL", value: state.baseURL }), state.status === "browser_waiting" && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [state.browserURL && _jsxs(Text, { children: ["URL: ", state.browserURL] }), state.browserCode && _jsxs(Text, { children: ["Code: ", state.browserCode] }), _jsx(Text, { color: "yellow", children: "Waiting for browser approval..." })] }))] })] }));
86
86
  }
87
87
  function AuthPrompt({ cursorVisible, label, value }) {
88
- return (_jsxs(Box, { borderStyle: "single", borderColor: "green", paddingX: 1, marginTop: 1, children: [_jsxs(Text, { color: "green", children: [label, ": "] }), _jsxs(Text, { children: [value, _jsx(Cursor, { visible: cursorVisible })] })] }));
88
+ return (_jsxs(Box, { borderStyle: "round", borderColor: "green", paddingX: 1, marginTop: 1, children: [_jsxs(Text, { color: "green", children: [label, ": "] }), _jsxs(Text, { children: [value, _jsx(Cursor, { visible: cursorVisible })] })] }));
89
89
  }
90
- function Header({ focused, contextEnabled, conversation, conversationId, conversationPreviousResponseId, conversationStatus, accessMode, model, pendingLocalLabel, preset, profile, renderMode, workdir, }) {
91
- return (_jsxs(Box, { borderStyle: "round", borderColor: panelBorderColor(focused), paddingX: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, color: focused ? "cyan" : undefined, children: "Agent API Workbench" }), _jsxs(Text, { color: "gray", wrap: "truncate", children: ["profile=", profile, " conversation=", conversation, " id=", conversationId, " preset=", preset, " model=", model] }), _jsxs(Text, { color: conversationStatus === "continued" ? "yellow" : conversationStatus === "fresh" ? "green" : "gray", wrap: "truncate", children: ["conversation_state=", conversationStatus, conversationPreviousResponseId ? ` previous=${conversationPreviousResponseId}` : ""] }), _jsxs(Text, { color: "gray", wrap: "truncate", children: ["workdir=", workdir, " access=", accessMode, " local_tools=", contextEnabled ? "on" : "off", " render=", renderMode, " pending=", pendingLocalLabel] })] }));
90
+ function Header({ cursor, focused, selection, contextEnabled, conversation, conversationId, conversationPreviousResponseId, conversationStatus, lines, accessMode, model, pendingLocalLabel, preset, profile, renderMode, workdir, }) {
91
+ const renderedLines = [
92
+ { bold: true, text: lines[0] ?? "Agent API Workbench" },
93
+ { color: "gray", text: lines[1] ?? `profile=${profile} conversation=${conversation} id=${conversationId} preset=${preset} model=${model}` },
94
+ {
95
+ color: conversationStatus === "continued" ? "yellow" : conversationStatus === "fresh" ? "green" : "gray",
96
+ text: lines[2] ?? `conversation_state=${conversationStatus}${conversationPreviousResponseId ? ` previous=${conversationPreviousResponseId}` : ""}`,
97
+ },
98
+ { color: "gray", text: lines[3] ?? `workdir=${workdir} access=${accessMode} local_tools=${contextEnabled ? "on" : "off"} render=${renderMode} pending=${pendingLocalLabel}` },
99
+ ];
100
+ return (_jsx(Box, { borderStyle: "round", borderColor: panelBorderColor(focused), paddingX: 1, flexDirection: "column", children: renderedLines.map((line, index) => (_jsxs(Text, { bold: line.bold || (focused && index === cursor.line), color: focused && index === 0 ? "cyan" : line.color, wrap: "truncate", children: [focused && index === cursor.line ? _jsx(Text, { color: "cyan", children: "\u203A " }) : _jsx(Text, { children: " " }), _jsx(SelectableText, { bold: line.bold, color: line.color, cursorColumn: focused && index === cursor.line && !selection ? cursor.column : null, selection: lineSelection(index, selection), text: line.text })] }, index))) }));
92
101
  }
93
102
  function panelBorderColor(focused) {
94
103
  return focused ? "cyan" : "gray";
package/dist/tui/mouse.js CHANGED
@@ -15,16 +15,24 @@ export function parseMouseEvent(input) {
15
15
  return { button: "wheel_down", column, kind: "wheel", row };
16
16
  if ((code & 32) === 32) {
17
17
  return {
18
- button: (code & 3) === 0 ? "left" : "unknown",
18
+ button: mouseButton(code),
19
19
  column,
20
20
  kind: "motion",
21
21
  row,
22
22
  };
23
23
  }
24
24
  return {
25
- button: (code & 3) === 0 ? "left" : "unknown",
25
+ button: mouseButton(code),
26
26
  column,
27
27
  kind: match[4] === "m" ? "release" : "press",
28
28
  row,
29
29
  };
30
30
  }
31
+ function mouseButton(code) {
32
+ const button = code & 3;
33
+ if (button === 0)
34
+ return "left";
35
+ if (button === 2)
36
+ return "right";
37
+ return "unknown";
38
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-api/cli",
3
- "version": "0.4.23",
3
+ "version": "0.4.24",
4
4
  "description": "First-class command line interface for Agent API",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/scalebox-dev/agent-tui#readme",
@@ -35,7 +35,7 @@
35
35
  "test": "npm run sync-version && npm run build && npm run smoke -w @agent-api/app-engine && node --test test/*.test.mjs"
36
36
  },
37
37
  "dependencies": {
38
- "@agent-api/app-engine": "^0.1.22",
38
+ "@agent-api/app-engine": "^0.1.23",
39
39
  "commander": "^14.0.3",
40
40
  "ink": "^6.8.0",
41
41
  "react": "^19.2.7"