@agent-api/cli 0.4.22 → 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 +1 -1
- package/dist/runtime.js +1 -1
- package/dist/tui/clipboard.d.ts +35 -1
- package/dist/tui/clipboard.js +120 -15
- package/dist/tui/ink/app.js +55 -6
- package/dist/tui/ink/components.d.ts +4 -2
- package/dist/tui/ink/components.js +19 -7
- package/dist/tui/mouse.js +11 -3
- package/package.json +2 -2
package/dist/runtime.d.ts
CHANGED
package/dist/runtime.js
CHANGED
package/dist/tui/clipboard.d.ts
CHANGED
|
@@ -1 +1,35 @@
|
|
|
1
|
-
export
|
|
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>;
|
package/dist/tui/clipboard.js
CHANGED
|
@@ -1,9 +1,49 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { platform } from "node:os";
|
|
3
|
-
export async function
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
return
|
|
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 =
|
|
17
|
-
for (const
|
|
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
|
|
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 [
|
|
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(
|
|
38
|
-
commands.push(
|
|
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
|
+
}
|
package/dist/tui/ink/app.js
CHANGED
|
@@ -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
|
|
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
|
-
focusedPanel: "activity" | "input" | "transcript";
|
|
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, {
|
|
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, { 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, {
|
|
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: "
|
|
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,22 @@ 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: "
|
|
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({ contextEnabled, conversation, conversationId, conversationPreviousResponseId, conversationStatus, accessMode, model, pendingLocalLabel, preset, profile, renderMode, workdir, }) {
|
|
91
|
-
|
|
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))) }));
|
|
101
|
+
}
|
|
102
|
+
function panelBorderColor(focused) {
|
|
103
|
+
return focused ? "cyan" : "gray";
|
|
92
104
|
}
|
|
93
105
|
function Cursor({ text = " ", visible }) {
|
|
94
106
|
return visible ? _jsx(Text, { inverse: true, children: text }) : _jsx(Text, { children: text });
|
package/dist/tui/mouse.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const enableMouseReporting = "\x1b[?1000h\x1b[?1002h\x1b[?1006h";
|
|
2
2
|
export const disableMouseReporting = "\x1b[?1000l\x1b[?1002l\x1b[?1006l";
|
|
3
3
|
export function parseMouseEvent(input) {
|
|
4
|
-
const match =
|
|
4
|
+
const match = /(?:\x1b)?\[<(\d+);(\d+);(\d+)([mM])/.exec(input);
|
|
5
5
|
if (!match)
|
|
6
6
|
return null;
|
|
7
7
|
const code = Number(match[1]);
|
|
@@ -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
|
|
18
|
+
button: mouseButton(code),
|
|
19
19
|
column,
|
|
20
20
|
kind: "motion",
|
|
21
21
|
row,
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
return {
|
|
25
|
-
button: (code
|
|
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.
|
|
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.
|
|
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"
|