@ouro.bot/cli 0.1.0-alpha.181 → 0.1.0-alpha.183

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,23 @@
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.183",
6
+ "changes": [
7
+ "fix(cli): status/hints moved BELOW input box (between bottom separator and end)",
8
+ "fix(cli): Ctrl-C double-press requires 2s window (not any two presses ever)",
9
+ "feat(cli): /help command with all keyboard shortcuts",
10
+ "docs: CLI keyboard shortcuts documentation"
11
+ ]
12
+ },
13
+ {
14
+ "version": "0.1.0-alpha.182",
15
+ "changes": [
16
+ "fix(cli): remove persistent status bar from input box (header already shows info)",
17
+ "fix(cli): Meta+B/F word movement (Option+Arrow on macOS)",
18
+ "fix(cli): Meta+D delete word forward"
19
+ ]
20
+ },
4
21
  {
5
22
  "version": "0.1.0-alpha.181",
6
23
  "changes": [
@@ -142,7 +142,7 @@ function LiveArea({ live, elapsed }) {
142
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] }));
143
143
  }
144
144
  // ─── Input ──────────────────────────────────────────────────────────
145
- function InputArea({ onSubmit, onCtrlC, agentName, model, history }) {
145
+ function InputArea({ onSubmit, onCtrlC, history, agentName, model }) {
146
146
  const [input, setInput] = (0, react_1.useState)("");
147
147
  const [cursorPos, setCursorPos] = (0, react_1.useState)(0); // cursor position within input
148
148
  const [tooltip, setTooltip] = (0, react_1.useState)("");
@@ -287,6 +287,32 @@ function InputArea({ onSubmit, onCtrlC, agentName, model, history }) {
287
287
  }
288
288
  return;
289
289
  }
290
+ // Meta+B / Meta+F: word movement (emacs — Option+Arrow on macOS sends these)
291
+ if (key.meta && inputChar === "b") {
292
+ const before = inputRef.current.slice(0, cursorRef.current);
293
+ const match = before.match(/(?:^|\s)\S+\s*$/);
294
+ cursorRef.current = match ? Math.max(0, cursorRef.current - match[0].length + (match[0][0] === " " ? 1 : 0)) : 0;
295
+ setCursorPos(cursorRef.current);
296
+ return;
297
+ }
298
+ if (key.meta && inputChar === "f") {
299
+ const after = inputRef.current.slice(cursorRef.current);
300
+ const match = after.match(/^\s*\S+/);
301
+ cursorRef.current = match ? Math.min(inputRef.current.length, cursorRef.current + match[0].length) : inputRef.current.length;
302
+ setCursorPos(cursorRef.current);
303
+ return;
304
+ }
305
+ // Meta+D: delete word forward
306
+ if (key.meta && inputChar === "d") {
307
+ const after = inputRef.current.slice(cursorRef.current);
308
+ const match = after.match(/^\s*\S+/);
309
+ if (match) {
310
+ const before = inputRef.current.slice(0, cursorRef.current);
311
+ const rest = after.slice(match[0].length);
312
+ updateInput(before + rest, cursorRef.current);
313
+ }
314
+ return;
315
+ }
290
316
  // Ctrl+A / Ctrl+E: home / end
291
317
  if (key.ctrl && inputChar === "a") {
292
318
  cursorRef.current = 0;
@@ -310,7 +336,7 @@ function InputArea({ onSubmit, onCtrlC, agentName, model, history }) {
310
336
  const cols = safeWidth();
311
337
  const isMultiline = input.includes("\n");
312
338
  const inputLines = input.split("\n");
313
- 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.slice(0, cursorPos) }), cursorVisible ? (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.scale, children: "█" }) : (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 + (cursorVisible ? 0 : 1)) })] })), (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) })] }));
339
+ 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.slice(0, cursorPos) }), cursorVisible ? (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.scale, children: "█" }) : (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 + (cursorVisible ? 0 : 1)) })] })), (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.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 }) : null] })] }));
314
340
  }
315
341
  // ─── Main TUI Component ─────────────────────────────────────────────
316
342
  function OuroTui({ agentName, model, completedMessages, inputHistory, live, elapsedSeconds, contextPercent, onSubmit, onCtrlC, cwd, }) {
@@ -319,5 +345,5 @@ function OuroTui({ agentName, model, completedMessages, inputHistory, live, elap
319
345
  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"));
320
346
  }
321
347
  return (0, jsx_runtime_1.jsx)(MessageBlock, { msg: item }, item.id);
322
- } }), (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, onCtrlC: onCtrlC, agentName: agentName, model: model, history: inputHistory })] }));
348
+ } }), (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, onCtrlC: onCtrlC, history: inputHistory, agentName: agentName, model: model })] }));
323
349
  }
@@ -521,9 +521,9 @@ async function runCliSession(options) {
521
521
  // Idle empty (first): warn
522
522
  // Idle empty (second): exit
523
523
  let ctrlCWarned = false;
524
+ let ctrlCTimer = null;
524
525
  const handleCtrlC = (hasInput) => {
525
526
  if (currentAbort) {
526
- // During generation: abort the request
527
527
  currentAbort.abort();
528
528
  ctrlCWarned = false;
529
529
  return "abort";
@@ -534,11 +534,17 @@ async function runCliSession(options) {
534
534
  }
535
535
  if (ctrlCWarned) {
536
536
  ctrlCWarned = false;
537
+ if (ctrlCTimer) {
538
+ clearTimeout(ctrlCTimer);
539
+ ctrlCTimer = null;
540
+ }
537
541
  closed = true;
538
542
  inputQueue.close();
539
543
  return "exit";
540
544
  }
541
545
  ctrlCWarned = true;
546
+ // Reset after 2 seconds — must press twice within window
547
+ ctrlCTimer = setTimeout(() => { ctrlCWarned = false; }, 2000);
542
548
  return "warn";
543
549
  };
544
550
  // TUI root: subscribes to store, passes props to OuroTui
@@ -76,6 +76,33 @@ function registerDefaultCommands(registry) {
76
76
  return { action: "response", message: lines.join("\n") };
77
77
  },
78
78
  });
79
+ registry.register({
80
+ name: "help",
81
+ description: "show keyboard shortcuts and tips",
82
+ channels: ["cli"],
83
+ /* v8 ignore start -- integration: /help command tested via E2E @preserve */
84
+ handler: () => ({
85
+ action: "response",
86
+ message: [
87
+ "Keyboard shortcuts:",
88
+ " Ctrl-C abort current generation / clear input / exit (twice within 2s)",
89
+ " Escape clear input",
90
+ " ↑ / ↓ browse input history (includes previous sessions)",
91
+ " Option+← / → jump between words",
92
+ " Ctrl+A / E jump to start / end of line",
93
+ " Option+Delete delete word backward",
94
+ " Option+D delete word forward",
95
+ " Alt+Enter insert newline (multi-line input)",
96
+ "",
97
+ "Commands:",
98
+ " /help this help",
99
+ " /commands list all commands",
100
+ " /new start a new conversation",
101
+ " /exit quit",
102
+ ].join("\n"),
103
+ }),
104
+ /* v8 ignore stop */
105
+ });
79
106
  registry.register({
80
107
  name: "tool-required",
81
108
  description: "toggle tool_choice required mode (forces tool calls)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.181",
3
+ "version": "0.1.0-alpha.183",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",