@ouro.bot/cli 0.1.0-alpha.256 → 0.1.0-alpha.258
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/changelog.json +15 -0
- package/dist/heart/daemon/cli-exec.js +15 -13
- package/dist/senses/cli/image-paste.js +66 -17
- package/dist/senses/cli/ouro-tui.js +34 -22
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.258",
|
|
6
|
+
"changes": [
|
|
7
|
+
"fix(tui): deferred ESC handling fixes arrow keys and Alt+Enter.",
|
|
8
|
+
"fix(tui): green cursor background.",
|
|
9
|
+
"fix(tui): clipboard fallback for macOS temp screenshots, proper path splitting and unescaping for image drag-drop."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"version": "0.1.0-alpha.257",
|
|
14
|
+
"changes": [
|
|
15
|
+
"test(daemon): convert all `daemon-cli.test.ts` auth/thoughts/config tests to use `os.tmpdir()` via the new `createTmpBundle()` helper. Previously these tests wrote real bundles to `~/AgentBundles/auth-local-${Date.now()}.ouro` etc., relying on `try/finally` cleanup that doesn't fire on test interruption \u2014 leaking bundle directories into the developer's home and inflating noise on the running daemon. The `REAL_BUNDLES_WRITE_ALLOWLIST` ratchet in `test-isolation.contract.test.ts` is now empty.",
|
|
16
|
+
"fix(daemon/cli-exec): plumb `bundlesRoot` and `secretsRoot` deps through the auth.run / auth.verify / auth.switch / config.model / config.models / thoughts handlers so tests can route reads/writes to a tmpdir without monkey-patching the identity module. Production code paths still default to `getAgentBundlesRoot()` and `~/.agentsecrets`."
|
|
17
|
+
]
|
|
18
|
+
},
|
|
4
19
|
{
|
|
5
20
|
"version": "0.1.0-alpha.256",
|
|
6
21
|
"changes": [
|
|
@@ -1345,7 +1345,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
1345
1345
|
}
|
|
1346
1346
|
// ── auth (local, no daemon socket needed) ──
|
|
1347
1347
|
if (command.kind === "auth.run") {
|
|
1348
|
-
const provider = command.provider ?? (0, auth_flow_1.readAgentConfigForAgent)(command.agent).config.humanFacing.provider;
|
|
1348
|
+
const provider = command.provider ?? (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot).config.humanFacing.provider;
|
|
1349
1349
|
/* v8 ignore next -- tests always inject runAuthFlow; default is for production @preserve */
|
|
1350
1350
|
const authRunner = deps.runAuthFlow ?? (await Promise.resolve().then(() => __importStar(require("../auth/auth-flow")))).runRuntimeAuthFlow;
|
|
1351
1351
|
const result = await authRunner({
|
|
@@ -1359,7 +1359,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
1359
1359
|
// Verify the credentials actually work by pinging the provider
|
|
1360
1360
|
/* v8 ignore start -- integration: real API ping after auth @preserve */
|
|
1361
1361
|
try {
|
|
1362
|
-
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent);
|
|
1362
|
+
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent, { secretsRoot: deps.secretsRoot });
|
|
1363
1363
|
const status = await verifyProviderCredentials(provider, secrets.providers);
|
|
1364
1364
|
deps.writeStdout(`${provider}: ${status}`);
|
|
1365
1365
|
}
|
|
@@ -1372,7 +1372,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
1372
1372
|
// ── auth verify (local, no daemon socket needed) ──
|
|
1373
1373
|
/* v8 ignore start -- auth verify/switch: tested in daemon-cli.test.ts but v8 traces differ in CI @preserve */
|
|
1374
1374
|
if (command.kind === "auth.verify") {
|
|
1375
|
-
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent);
|
|
1375
|
+
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent, { secretsRoot: deps.secretsRoot });
|
|
1376
1376
|
const providers = secrets.providers;
|
|
1377
1377
|
if (command.provider) {
|
|
1378
1378
|
const status = await verifyProviderCredentials(command.provider, providers);
|
|
@@ -1391,7 +1391,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
1391
1391
|
}
|
|
1392
1392
|
// ── auth switch (local, no daemon socket needed) ──
|
|
1393
1393
|
if (command.kind === "auth.switch") {
|
|
1394
|
-
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent);
|
|
1394
|
+
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent, { secretsRoot: deps.secretsRoot });
|
|
1395
1395
|
const providerSecrets = secrets.providers[command.provider];
|
|
1396
1396
|
if (!providerSecrets || !hasStoredCredentials(command.provider, providerSecrets)) {
|
|
1397
1397
|
const message = `no credentials stored for ${command.provider}. Run \`ouro auth --agent ${command.agent} --provider ${command.provider}\` first.`;
|
|
@@ -1406,11 +1406,11 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
1406
1406
|
return message;
|
|
1407
1407
|
}
|
|
1408
1408
|
if (command.facing) {
|
|
1409
|
-
(0, auth_flow_1.writeAgentProviderSelection)(command.agent, command.facing, command.provider);
|
|
1409
|
+
(0, auth_flow_1.writeAgentProviderSelection)(command.agent, command.facing, command.provider, deps.bundlesRoot);
|
|
1410
1410
|
}
|
|
1411
1411
|
else {
|
|
1412
|
-
(0, auth_flow_1.writeAgentProviderSelection)(command.agent, "human", command.provider);
|
|
1413
|
-
(0, auth_flow_1.writeAgentProviderSelection)(command.agent, "agent", command.provider);
|
|
1412
|
+
(0, auth_flow_1.writeAgentProviderSelection)(command.agent, "human", command.provider, deps.bundlesRoot);
|
|
1413
|
+
(0, auth_flow_1.writeAgentProviderSelection)(command.agent, "agent", command.provider, deps.bundlesRoot);
|
|
1414
1414
|
}
|
|
1415
1415
|
const message = `switched ${command.agent} to ${command.provider} (verified working)`;
|
|
1416
1416
|
deps.writeStdout(message);
|
|
@@ -1420,14 +1420,14 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
1420
1420
|
// ── config models (local, no daemon socket needed) ──
|
|
1421
1421
|
/* v8 ignore start -- config models: tested via daemon-cli.test.ts @preserve */
|
|
1422
1422
|
if (command.kind === "config.models") {
|
|
1423
|
-
const { config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent);
|
|
1423
|
+
const { config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot);
|
|
1424
1424
|
const provider = config.humanFacing.provider;
|
|
1425
1425
|
if (provider !== "github-copilot") {
|
|
1426
1426
|
const message = `model listing not available for ${provider} — check provider documentation.`;
|
|
1427
1427
|
deps.writeStdout(message);
|
|
1428
1428
|
return message;
|
|
1429
1429
|
}
|
|
1430
|
-
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent);
|
|
1430
|
+
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent, { secretsRoot: deps.secretsRoot });
|
|
1431
1431
|
const ghConfig = secrets.providers["github-copilot"];
|
|
1432
1432
|
if (!ghConfig.githubToken || !ghConfig.baseUrl) {
|
|
1433
1433
|
throw new Error(`github-copilot credentials not configured. Run \`ouro auth --agent ${command.agent} --provider github-copilot\` first.`);
|
|
@@ -1454,10 +1454,10 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
1454
1454
|
if (command.kind === "config.model") {
|
|
1455
1455
|
const facing = command.facing ?? "human";
|
|
1456
1456
|
// Validate model availability for github-copilot before writing
|
|
1457
|
-
const { config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent);
|
|
1457
|
+
const { config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot);
|
|
1458
1458
|
const facingConfig = facing === "human" ? config.humanFacing : config.agentFacing;
|
|
1459
1459
|
if (facingConfig.provider === "github-copilot") {
|
|
1460
|
-
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent);
|
|
1460
|
+
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent, { secretsRoot: deps.secretsRoot });
|
|
1461
1461
|
const ghConfig = secrets.providers["github-copilot"];
|
|
1462
1462
|
if (ghConfig.githubToken && ghConfig.baseUrl) {
|
|
1463
1463
|
const fetchFn = deps.fetchImpl ?? fetch;
|
|
@@ -1482,7 +1482,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
1482
1482
|
}
|
|
1483
1483
|
}
|
|
1484
1484
|
}
|
|
1485
|
-
const { provider, previousModel } = (0, auth_flow_1.writeAgentModel)(command.agent, facing, command.modelName);
|
|
1485
|
+
const { provider, previousModel } = (0, auth_flow_1.writeAgentModel)(command.agent, facing, command.modelName, { bundlesRoot: deps.bundlesRoot });
|
|
1486
1486
|
const message = previousModel
|
|
1487
1487
|
? `updated ${command.agent} model on ${provider}: ${previousModel} → ${command.modelName}`
|
|
1488
1488
|
: `set ${command.agent} model on ${provider}: ${command.modelName}`;
|
|
@@ -1569,7 +1569,9 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
1569
1569
|
if (command.kind === "thoughts") {
|
|
1570
1570
|
try {
|
|
1571
1571
|
const agentName = command.agent ?? (0, identity_1.getAgentName)();
|
|
1572
|
-
|
|
1572
|
+
/* v8 ignore next -- production fallback: tests always inject bundlesRoot via createTmpBundle @preserve */
|
|
1573
|
+
const bundlesRoot = deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
1574
|
+
const agentRoot = path.join(bundlesRoot, `${agentName}.ouro`);
|
|
1573
1575
|
const sessionFilePath = (0, thoughts_1.getInnerDialogSessionPath)(agentRoot);
|
|
1574
1576
|
if (command.json) {
|
|
1575
1577
|
try {
|
|
@@ -50,6 +50,7 @@ exports.resolveImageContent = resolveImageContent;
|
|
|
50
50
|
* files, and produces OpenAI-compatible `image_url` content blocks.
|
|
51
51
|
*/
|
|
52
52
|
const promises_1 = require("fs/promises");
|
|
53
|
+
const child_process_1 = require("child_process");
|
|
53
54
|
const path = __importStar(require("path"));
|
|
54
55
|
const runtime_1 = require("../../nerves/runtime");
|
|
55
56
|
/** Matches image file extensions (case-insensitive) */
|
|
@@ -58,10 +59,14 @@ exports.IMAGE_EXTENSION_REGEX = /\.(png|jpe?g|gif|webp)$/i;
|
|
|
58
59
|
function isImagePath(str) {
|
|
59
60
|
return exports.IMAGE_EXTENSION_REGEX.test(unescapePath(str));
|
|
60
61
|
}
|
|
61
|
-
/**
|
|
62
|
+
/** Strip shell escape backslashes from a path (macOS/Linux drag-drop format) */
|
|
62
63
|
function unescapePath(str) {
|
|
63
|
-
|
|
64
|
+
// Handle double-backslashes first (actual backslash in filename)
|
|
65
|
+
// then remove single escape backslashes
|
|
66
|
+
return str.replace(/\\(.)/g, "$1");
|
|
64
67
|
}
|
|
68
|
+
/** Detect temp screenshot paths from macOS screencapture */
|
|
69
|
+
const TEMP_SCREENSHOT_RE = /\/TemporaryItems\/.*screencaptureui.*\/Screenshot/i;
|
|
65
70
|
/** Map file extension to MIME media type */
|
|
66
71
|
function extensionToMediaType(ext) {
|
|
67
72
|
switch (ext.toLowerCase()) {
|
|
@@ -80,41 +85,85 @@ function extensionToMediaType(ext) {
|
|
|
80
85
|
}
|
|
81
86
|
/**
|
|
82
87
|
* Read an image file and return base64 + media type.
|
|
83
|
-
*
|
|
88
|
+
* Falls back to clipboard for temp screenshot paths (macOS screencapture
|
|
89
|
+
* creates ephemeral files in TemporaryItems that are cleaned up quickly).
|
|
90
|
+
* Returns null on any error.
|
|
84
91
|
*/
|
|
85
92
|
async function tryReadImage(absolutePath) {
|
|
93
|
+
// Try reading the file directly first
|
|
86
94
|
try {
|
|
87
95
|
const buffer = await (0, promises_1.readFile)(absolutePath);
|
|
88
|
-
|
|
96
|
+
if (buffer.length > 0) {
|
|
97
|
+
const ext = path.extname(absolutePath);
|
|
98
|
+
return {
|
|
99
|
+
base64: Buffer.from(buffer).toString("base64"),
|
|
100
|
+
mediaType: extensionToMediaType(ext),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// File not found or permission denied — try clipboard fallback
|
|
106
|
+
}
|
|
107
|
+
// Clipboard fallback for macOS temp screenshots
|
|
108
|
+
/* v8 ignore start -- clipboard fallback requires real macOS screencapture @preserve */
|
|
109
|
+
if (process.platform === "darwin" && TEMP_SCREENSHOT_RE.test(absolutePath)) {
|
|
110
|
+
const clipboardImage = await getImageFromClipboard();
|
|
111
|
+
if (clipboardImage)
|
|
112
|
+
return clipboardImage;
|
|
113
|
+
}
|
|
114
|
+
/* v8 ignore stop */
|
|
115
|
+
(0, runtime_1.emitNervesEvent)({
|
|
116
|
+
component: "senses",
|
|
117
|
+
event: "senses.image_read_error",
|
|
118
|
+
message: `Failed to read image: ${absolutePath}`,
|
|
119
|
+
meta: { path: absolutePath },
|
|
120
|
+
});
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Read image data from macOS clipboard via osascript.
|
|
125
|
+
* Used as fallback when temp screenshot files are already cleaned up.
|
|
126
|
+
*/
|
|
127
|
+
/* v8 ignore start -- macOS clipboard integration @preserve */
|
|
128
|
+
async function getImageFromClipboard() {
|
|
129
|
+
const tmpPath = "/tmp/ouro_clipboard_image.png";
|
|
130
|
+
try {
|
|
131
|
+
// Check if clipboard has image data
|
|
132
|
+
(0, child_process_1.execFileSync)("osascript", ["-e", "the clipboard as «class PNGf»"], { stdio: "pipe" });
|
|
133
|
+
// Save clipboard image to temp file
|
|
134
|
+
(0, child_process_1.execFileSync)("osascript", [
|
|
135
|
+
"-e", "set png_data to (the clipboard as «class PNGf»)",
|
|
136
|
+
"-e", `set fp to open for access POSIX file "${tmpPath}" with write permission`,
|
|
137
|
+
"-e", "write png_data to fp",
|
|
138
|
+
"-e", "close access fp",
|
|
139
|
+
], { stdio: "pipe" });
|
|
140
|
+
const buffer = await (0, promises_1.readFile)(tmpPath);
|
|
141
|
+
void (0, promises_1.unlink)(tmpPath).catch(() => { });
|
|
142
|
+
if (buffer.length === 0)
|
|
143
|
+
return null;
|
|
89
144
|
return {
|
|
90
145
|
base64: Buffer.from(buffer).toString("base64"),
|
|
91
|
-
mediaType:
|
|
146
|
+
mediaType: "image/png",
|
|
92
147
|
};
|
|
93
148
|
}
|
|
94
149
|
catch {
|
|
95
|
-
(0, runtime_1.emitNervesEvent)({
|
|
96
|
-
component: "senses",
|
|
97
|
-
event: "senses.image_read_error",
|
|
98
|
-
message: `Failed to read image: ${absolutePath}`,
|
|
99
|
-
meta: { path: absolutePath },
|
|
100
|
-
});
|
|
101
150
|
return null;
|
|
102
151
|
}
|
|
103
152
|
}
|
|
153
|
+
/* v8 ignore stop */
|
|
104
154
|
/** Format an image reference placeholder */
|
|
105
155
|
function formatImageRef(n) {
|
|
106
156
|
return `[Image #${n}]`;
|
|
107
157
|
}
|
|
108
158
|
/**
|
|
109
|
-
* Scan input for absolute
|
|
110
|
-
*
|
|
111
|
-
* ending with an image extension.
|
|
159
|
+
* Scan input for absolute image file paths and replace with [Image #N] references.
|
|
160
|
+
* Handles backslash-escaped characters (macOS drag-drop) and paths with spaces.
|
|
112
161
|
*/
|
|
113
162
|
function replacePathsWithRefs(input) {
|
|
114
163
|
const images = new Map();
|
|
115
|
-
// Match absolute paths
|
|
116
|
-
//
|
|
117
|
-
const pathRegex = /(
|
|
164
|
+
// Match absolute paths: start with /, contain non-whitespace or backslash-escaped chars,
|
|
165
|
+
// end with an image extension. The (?:\\.|\S) alternation allows `\ ` sequences.
|
|
166
|
+
const pathRegex = /(?:\/(?:\\.|[^\s])+\.(?:png|jpe?g|gif|webp))/gi;
|
|
118
167
|
let counter = 0;
|
|
119
168
|
const text = input.replace(pathRegex, (match) => {
|
|
120
169
|
counter++;
|
|
@@ -163,9 +163,10 @@ function InputArea({ onSubmit, onCtrlC, history, queuedInputs, onPopQueue, agent
|
|
|
163
163
|
const cursorRef = (0, react_1.useRef)(0);
|
|
164
164
|
const historyIdx = (0, react_1.useRef)(-1);
|
|
165
165
|
const savedInput = (0, react_1.useRef)("");
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
166
|
+
// Deferred ESC handling: Ink 3.2 fires escape events for ESC prefix of arrow
|
|
167
|
+
// keys (\x1b[D) and Alt+Enter (\x1b\r). We defer ESC actions by 80ms — if
|
|
168
|
+
// another key arrives in that window, it was an escape sequence, not standalone ESC.
|
|
169
|
+
const escTimerRef = (0, react_1.useRef)(null);
|
|
169
170
|
const lastEscTime = (0, react_1.useRef)(0);
|
|
170
171
|
const tooltipTimerRef = (0, react_1.useRef)(null);
|
|
171
172
|
(0, react_1.useEffect)(() => () => {
|
|
@@ -211,25 +212,36 @@ function InputArea({ onSubmit, onCtrlC, history, queuedInputs, onPopQueue, agent
|
|
|
211
212
|
setTooltip("");
|
|
212
213
|
if (key.escape) {
|
|
213
214
|
lastEscTime.current = Date.now();
|
|
214
|
-
if
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
215
|
+
// Defer ESC action — if another key arrives within 80ms, this was an escape
|
|
216
|
+
// sequence prefix (arrow key, Alt+Enter), not a standalone Escape press.
|
|
217
|
+
if (escTimerRef.current)
|
|
218
|
+
clearTimeout(escTimerRef.current);
|
|
219
|
+
escTimerRef.current = setTimeout(() => {
|
|
220
|
+
escTimerRef.current = null;
|
|
221
|
+
if (inputRef.current) {
|
|
222
|
+
updateInput("");
|
|
223
|
+
historyIdx.current = -1;
|
|
224
|
+
setTooltip("Esc again to clear");
|
|
225
|
+
if (tooltipTimerRef.current)
|
|
226
|
+
clearTimeout(tooltipTimerRef.current);
|
|
227
|
+
tooltipTimerRef.current = setTimeout(() => setTooltip(""), 2000);
|
|
228
|
+
}
|
|
229
|
+
else if (queuedInputs.length > 0) {
|
|
230
|
+
const items = onPopQueue();
|
|
231
|
+
updateInput(items.join("\n"));
|
|
232
|
+
historyIdx.current = -1;
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
setTooltip("");
|
|
236
|
+
}
|
|
237
|
+
}, 80);
|
|
231
238
|
return;
|
|
232
239
|
}
|
|
240
|
+
// Cancel pending ESC action — this key is part of an escape sequence
|
|
241
|
+
if (escTimerRef.current) {
|
|
242
|
+
clearTimeout(escTimerRef.current);
|
|
243
|
+
escTimerRef.current = null;
|
|
244
|
+
}
|
|
233
245
|
if (key.return) {
|
|
234
246
|
// Alt+Enter: detect via key.meta OR recent ESC (within 50ms — Ink splits \x1b\r)
|
|
235
247
|
const recentEsc = (Date.now() - lastEscTime.current) < 50;
|
|
@@ -389,8 +401,8 @@ function InputArea({ onSubmit, onCtrlC, history, queuedInputs, onPopQueue, agent
|
|
|
389
401
|
});
|
|
390
402
|
const isMultiline = input.includes("\n");
|
|
391
403
|
const inputLines = input.split("\n");
|
|
392
|
-
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(termWidth()) }), isMultiline ? ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", children: inputLines.map((line, i) => ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.teal, bold: true, children: i === 0 ? ") " : "· " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: line }), i === inputLines.length - 1 ? (cursorVisible ? (0, jsx_runtime_1.jsx)(ink_1.Text, {
|
|
393
|
-
? (0, jsx_runtime_1.jsx)(ink_1.Text, {
|
|
404
|
+
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(termWidth()) }), isMultiline ? ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", children: inputLines.map((line, i) => ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.teal, bold: true, children: i === 0 ? ") " : "· " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: line }), i === inputLines.length - 1 ? (cursorVisible ? (0, jsx_runtime_1.jsx)(ink_1.Text, { backgroundColor: OURO.scale, color: "#000000", children: " " }) : (0, jsx_runtime_1.jsx)(ink_1.Text, { children: " " })) : null] }, i))) })) : ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.teal, bold: true, children: ") " }), !input && queuedInputs.length > 0 ? ((0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, children: "Press up to edit queued messages" })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: input.slice(0, cursorPos) }), cursorVisible
|
|
405
|
+
? (0, jsx_runtime_1.jsx)(ink_1.Text, { backgroundColor: OURO.scale, color: "#000000", children: input[cursorPos] ?? " " })
|
|
394
406
|
: (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: input[cursorPos] ?? " " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: input.slice(cursorPos + 1) })] }))] })), (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(termWidth()) }), (0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [" ", agentName, model ? ` · ${model}` : "", " \u00B7 /help"] }), (0, jsx_runtime_1.jsx)(ink_1.Box, { flexGrow: 1 }), tooltip ? (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: tooltip }) : (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "opt+enter for newline" })] })] }));
|
|
395
407
|
}
|
|
396
408
|
// ─── Main TUI Component ─────────────────────────────────────────────
|