@ouro.bot/cli 0.1.0-alpha.178 → 0.1.0-alpha.179

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 CHANGED
@@ -1,6 +1,16 @@
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.179",
6
+ "changes": [
7
+ "feat(cli): generous vertical whitespace between TUI sections",
8
+ "feat(cli): image placeholder rendering (\ud83d\uddbc) for markdown images",
9
+ "feat(cli): Alt+Enter multi-line input with continuation prefix",
10
+ "feat(cli): terminal width safety (cap at 200, subtract margin)",
11
+ "fix(cli): settle/ponder/observe/rest fully hidden from imperative tool display"
12
+ ]
13
+ },
4
14
  {
5
15
  "version": "0.1.0-alpha.178",
6
16
  "changes": [
@@ -40,30 +40,27 @@ function ringColor(elapsedSec) {
40
40
  return OURO.scale;
41
41
  }
42
42
  // ─── Header ─────────────────────────────────────────────────────────
43
+ /** Safe terminal width: capped at 200, with 2-char margin */
44
+ function safeWidth() {
45
+ return Math.min(process.stdout.columns || 80, 200) - 2;
46
+ }
43
47
  function Header({ agentName, model, contextPercent, cwd }) {
44
48
  const showCtx = contextPercent > 0;
45
49
  const info = [agentName, model, cwd, showCtx ? `ctx ${contextPercent}%` : ""].filter(Boolean).join(" · ");
46
50
  // 3-segment snake: TAIL (fixed) + MIDDLE (stretches) + HEAD (fixed)
47
- // From the reference art, segments split at the dots:
48
- //
49
- // . . ____
50
- // __.__________________.____/ O \___/
51
- // <__.__________________._________/ \
52
- //
53
- // TAIL: line1="" line2=" __" line3="<__"
54
- // MIDDLE: line1=" " line2="_" line3="_" (or text)
55
- // HEAD: line1=" ____" line2="____/ O \\___/" line3="_________/ \\"
56
51
  const HEAD1 = " ____";
57
52
  const HEAD2 = "____/ O \\___/";
58
53
  const HEAD3 = "_________/ \\";
59
54
  const TAIL1 = " ";
60
55
  const TAIL2 = " __";
61
56
  const TAIL3 = "<__";
62
- const middleLen = Math.max(info.length, 20);
63
- const textPad = middleLen - info.length;
57
+ const fixedWidth = TAIL1.length + HEAD1.length;
58
+ const maxMiddle = safeWidth() - fixedWidth;
59
+ const middleLen = Math.max(Math.min(Math.max(info.length, 20), maxMiddle), 10);
60
+ const textPad = Math.max(middleLen - info.length, 0);
64
61
  const line1 = TAIL1 + " ".repeat(middleLen) + HEAD1;
65
62
  const line2 = TAIL2 + "_".repeat(middleLen) + HEAD2;
66
- const line3text = info + "_".repeat(textPad);
63
+ const line3text = (info.length > middleLen ? info.slice(0, middleLen) : info) + "_".repeat(textPad);
67
64
  return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.scale, children: line1 }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.scale, children: line2 }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { color: OURO.scale, children: [TAIL3, (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.glow, children: line3text }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.scale, children: HEAD3 })] })] }));
68
65
  }
69
66
  // ─── Message Rendering ──────────────────────────────────────────────
@@ -78,17 +75,18 @@ function ToolResultLine({ tc }) {
78
75
  function MessageBlock({ msg }) {
79
76
  if (msg.role === "tool") {
80
77
  const visibleCalls = msg.toolCalls?.filter(tc => !FLOW_CONTROL_TOOLS.has(tc.name));
78
+ // Flow control tools produce no visible output at all
81
79
  if (!visibleCalls || visibleCalls.length === 0)
82
- return (0, jsx_runtime_1.jsx)(ink_1.Box, { height: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: " " }) });
80
+ return (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" });
83
81
  return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", children: visibleCalls.map((tc, i) => (0, jsx_runtime_1.jsx)(ToolResultLine, { tc: tc }, i)) }));
84
82
  }
85
83
  if (msg.role === "user") {
86
- return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", marginTop: 1, children: [msg.content ? (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, bold: true, children: msg.content }) : null, (0, jsx_runtime_1.jsx)(ink_1.Box, { height: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: " " }) })] }));
84
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", marginTop: 1, children: [msg.content ? (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, bold: true, children: msg.content }) : null, (0, jsx_runtime_1.jsx)(ink_1.Box, { marginBottom: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) })] }));
87
85
  }
88
86
  if (msg.role === "assistant") {
89
- return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [msg.content ? (0, jsx_runtime_1.jsx)(streaming_markdown_1.StreamingMarkdown, { text: msg.content }) : null, (0, jsx_runtime_1.jsx)(ink_1.Box, { height: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: " " }) })] }));
87
+ return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", marginBottom: 1, children: msg.content ? (0, jsx_runtime_1.jsx)(streaming_markdown_1.StreamingMarkdown, { text: msg.content, maxWidth: safeWidth() }) : null }));
90
88
  }
91
- return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", marginTop: 1, children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, children: msg.content }), (0, jsx_runtime_1.jsx)(ink_1.Box, { height: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: " " }) })] }));
89
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", marginTop: 1, children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, children: msg.content }), (0, jsx_runtime_1.jsx)(ink_1.Box, { marginBottom: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) })] }));
92
90
  }
93
91
  // ─── Live Area (re-renders) ─────────────────────────────────────────
94
92
  function Spinner({ phrasePool, elapsed }) {
@@ -136,12 +134,12 @@ function formatActiveToolArgs(name, args) {
136
134
  function ActiveToolLine({ tool }) {
137
135
  // Hide flow control tools from in-progress display
138
136
  if (FLOW_CONTROL_TOOLS.has(tool.name))
139
- return (0, jsx_runtime_1.jsx)(ink_1.Box, { height: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: " " }) });
137
+ return (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" });
140
138
  const argStr = formatActiveToolArgs(tool.name, tool.args);
141
139
  return ((0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, children: "∙" }), " ", (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.teal, children: tool.name }), " ", (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, children: argStr })] }));
142
140
  }
143
141
  function LiveArea({ live, elapsed }) {
144
- return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [live.streamingText ? ((0, jsx_runtime_1.jsx)(streaming_markdown_1.StreamingMarkdown, { text: live.streamingText })) : null, live.activeTool ? ((0, jsx_runtime_1.jsx)(ActiveToolLine, { tool: live.activeTool })) : null, live.loading ? ((0, jsx_runtime_1.jsx)(Spinner, { phrasePool: live.spinnerPhrasePool, elapsed: elapsed })) : null, live.errorMessage ? ((0, jsx_runtime_1.jsxs)(ink_1.Text, { color: OURO.fang, children: ["✗ ", live.errorMessage] })) : null, live.kickMessage ? ((0, jsx_runtime_1.jsxs)(ink_1.Text, { color: OURO.gold, children: ["↻ ", live.kickMessage] })) : null] }));
142
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [live.streamingText ? ((0, jsx_runtime_1.jsx)(streaming_markdown_1.StreamingMarkdown, { text: live.streamingText, maxWidth: safeWidth() })) : null, live.activeTool ? ((0, jsx_runtime_1.jsx)(ActiveToolLine, { tool: live.activeTool })) : null, live.loading ? ((0, jsx_runtime_1.jsx)(Spinner, { phrasePool: live.spinnerPhrasePool, elapsed: elapsed })) : null, live.errorMessage ? ((0, jsx_runtime_1.jsxs)(ink_1.Text, { color: OURO.fang, children: ["✗ ", live.errorMessage] })) : null, live.kickMessage ? ((0, jsx_runtime_1.jsxs)(ink_1.Text, { color: OURO.gold, children: ["↻ ", live.kickMessage] })) : null] }));
145
143
  }
146
144
  // ─── Input ──────────────────────────────────────────────────────────
147
145
  function InputArea({ onSubmit, suppressed, onCtrlC, agentName, model, history }) {
@@ -194,6 +192,12 @@ function InputArea({ onSubmit, suppressed, onCtrlC, agentName, model, history })
194
192
  return;
195
193
  }
196
194
  if (key.return) {
195
+ // Alt+Enter: insert newline instead of submitting
196
+ if (key.meta) {
197
+ inputRef.current += "\n";
198
+ setInput(inputRef.current);
199
+ return;
200
+ }
197
201
  const text = inputRef.current;
198
202
  if (text.trim())
199
203
  onSubmit(text);
@@ -245,19 +249,21 @@ function InputArea({ onSubmit, suppressed, onCtrlC, agentName, model, history })
245
249
  }
246
250
  });
247
251
  // Get terminal width (capped for sanity)
248
- const cols = Math.min(process.stdout.columns || 80, 200);
252
+ const cols = safeWidth();
249
253
  if (suppressed) {
250
254
  // During model generation: show status bar but no input
251
255
  return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(cols) }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [agentName, model ? ` · ${model}` : ""] }), (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(cols) })] }));
252
256
  }
253
- return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(cols) }), (0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.teal, bold: true, children: ") " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: input }), cursorVisible ? (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.scale, children: "█" }) : (0, jsx_runtime_1.jsx)(ink_1.Text, { children: " " })] }), (0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [agentName, model ? ` · ${model}` : ""] }), (0, jsx_runtime_1.jsx)(ink_1.Box, { flexGrow: 1 }), tooltip ? (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: tooltip }) : null] }), (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(cols) })] }));
257
+ const isMultiline = input.includes("\n");
258
+ const inputLines = input.split("\n");
259
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(cols) }), 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, { color: OURO.scale, 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: ") " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: input }), cursorVisible ? (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.scale, children: "█" }) : (0, jsx_runtime_1.jsx)(ink_1.Text, { children: " " })] })), (0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [agentName, model ? ` · ${model}` : ""] }), (0, jsx_runtime_1.jsx)(ink_1.Box, { flexGrow: 1 }), tooltip ? (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: tooltip }) : null, isMultiline ? (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: " (multi-line)" }) : null] }), (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(cols) })] }));
254
260
  }
255
261
  // ─── Main TUI Component ─────────────────────────────────────────────
256
262
  function OuroTui({ agentName, model, completedMessages, inputHistory, live, elapsedSeconds, contextPercent, onSubmit, onCtrlC, cwd, }) {
257
263
  return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Static, { items: [{ id: "__header__" }, ...completedMessages], children: (item, index) => {
258
264
  if (index === 0) {
259
- return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { children: " " }), (0, jsx_runtime_1.jsx)(Header, { agentName: agentName, model: model, contextPercent: contextPercent, cwd: cwd }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, children: " Press Ctrl-C twice to exit \u00b7 \u2191\u2193 history \u00b7 Esc clear" }), (0, jsx_runtime_1.jsx)(ink_1.Text, { children: " " })] }, "header"));
265
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", marginBottom: 2, children: [(0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }), (0, jsx_runtime_1.jsx)(Header, { agentName: agentName, model: model, contextPercent: contextPercent, cwd: cwd }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, dimColor: true, children: " Ctrl-C twice to exit \u00b7 \u2191\u2193 history \u00b7 Esc clear \u00b7 Alt+Enter newline" })] }, "header"));
260
266
  }
261
267
  return (0, jsx_runtime_1.jsx)(MessageBlock, { msg: item }, item.id);
262
- } }), (live.loading || live.streamingText || live.activeTool) ? (0, jsx_runtime_1.jsx)(ink_1.Box, { height: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: " " }) }) : null, (0, jsx_runtime_1.jsx)(LiveArea, { live: live, elapsed: elapsedSeconds }), (live.loading || live.streamingText || live.activeTool) ? (0, jsx_runtime_1.jsx)(ink_1.Box, { height: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: " " }) }) : null, (0, jsx_runtime_1.jsx)(InputArea, { onSubmit: onSubmit, suppressed: live.inputSuppressed, onCtrlC: onCtrlC, agentName: agentName, model: model, history: inputHistory })] }));
268
+ } }), (live.loading || live.streamingText || live.activeTool) ? (0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }) : null, (0, jsx_runtime_1.jsx)(LiveArea, { live: live, elapsed: elapsedSeconds }), (live.loading || live.streamingText || live.activeTool) ? (0, jsx_runtime_1.jsx)(ink_1.Box, { marginBottom: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }) : null, (0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }), (0, jsx_runtime_1.jsx)(InputArea, { onSubmit: onSubmit, suppressed: live.inputSuppressed, onCtrlC: onCtrlC, agentName: agentName, model: model, history: inputHistory })] }));
263
269
  }
@@ -213,7 +213,21 @@ function parseInline(text) {
213
213
  placeholders.push({ idx, segment: { text: restoreEscapes(code, escapes), code: true } });
214
214
  return `\x00PH${idx}\x00`;
215
215
  });
216
- // Step 3: extract links [text](url)
216
+ // Step 3a: extract image references ![alt](url) -> placeholder
217
+ processed = processed.replace(/!\[([^\]]*)\]\(([^)]*)\)/g, (_m, alt, _url) => {
218
+ const idx = placeholders.length;
219
+ const label = alt ? `\ud83d\uddbc ${restoreEscapes(alt, escapes)}` : "\ud83d\uddbc image";
220
+ placeholders.push({ idx, segment: { text: label, image: true } });
221
+ return `\x00PH${idx}\x00`;
222
+ });
223
+ // Step 3b: extract [Image: ...] references
224
+ processed = processed.replace(/\[Image:\s*([^\]]*)\]/gi, (_m, desc) => {
225
+ const idx = placeholders.length;
226
+ const label = desc.trim() ? `\ud83d\uddbc ${restoreEscapes(desc.trim(), escapes)}` : "\ud83d\uddbc image";
227
+ placeholders.push({ idx, segment: { text: label, image: true } });
228
+ return `\x00PH${idx}\x00`;
229
+ });
230
+ // Step 3c: extract links [text](url)
217
231
  processed = processed.replace(/\[([^\]]*)\]\(([^)]*)\)/g, (_m, linkText, url) => {
218
232
  const idx = placeholders.length;
219
233
  const restoredText = restoreEscapes(linkText, escapes);
@@ -364,6 +378,9 @@ function wrapText(text, maxWidth) {
364
378
  }
365
379
  // ─── Segment Renderer ──────────────────────────────────────────────
366
380
  function SegmentRenderer({ segment }) {
381
+ if (segment.image) {
382
+ return (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: segment.text });
383
+ }
367
384
  if (segment.codeBlock) {
368
385
  return (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: segment.text });
369
386
  }
@@ -13,6 +13,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.writeToolStart = writeToolStart;
14
14
  exports.writeToolEnd = writeToolEnd;
15
15
  const runtime_1 = require("../../nerves/runtime");
16
+ // Flow control tools are invisible to the user — internal agent mechanics
17
+ const FLOW_CONTROL_TOOLS = new Set(["settle", "ponder", "observe", "rest"]);
16
18
  // Ouroboros teal: #4ec9b0 -> RGB escape
17
19
  const OURO_TEAL = "\x1b[38;2;78;201;176m";
18
20
  const GREEN = "\x1b[32m";
@@ -56,6 +58,8 @@ function getPrimaryArg(name, args) {
56
58
  * Shows: [teal tool name] [dim primary arg]
57
59
  */
58
60
  function writeToolStart(name, args) {
61
+ if (FLOW_CONTROL_TOOLS.has(name))
62
+ return;
59
63
  const primary = getPrimaryArg(name, args);
60
64
  const argDisplay = primary ? ` ${DIM}${primary}${RESET}` : "";
61
65
  process.stderr.write(`\r\x1b[K${OURO_TEAL}${BOLD}${name}${RESET}${argDisplay}\n`);
@@ -65,6 +69,8 @@ function writeToolStart(name, args) {
65
69
  * Shows: [green check / red cross] [teal tool name] [dim summary]
66
70
  */
67
71
  function writeToolEnd(name, argSummary, success) {
72
+ if (FLOW_CONTROL_TOOLS.has(name))
73
+ return;
68
74
  const icon = success ? `${GREEN}\u2713` : `${RED}\u2717`;
69
75
  const summary = argSummary ? ` ${DIM}${truncate(argSummary, 100)}${RESET}` : "";
70
76
  process.stderr.write(`${icon}${RESET} ${OURO_TEAL}${name}${RESET}${summary}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.178",
3
+ "version": "0.1.0-alpha.179",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",