agent-from-zero 0.32.3 → 0.32.4

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.
Files changed (2) hide show
  1. package/dist/ink/app.js +29 -6
  2. package/package.json +1 -1
package/dist/ink/app.js CHANGED
@@ -98,6 +98,7 @@ export function App({ session, runTurn }) {
98
98
  const [status, setStatus] = useState(null); // the live spinner line
99
99
  const [live, setLive] = useState(null); // the streaming answer
100
100
  const [input, setInput] = useState(""); // the current input buffer
101
+ const [cursor, setCursor] = useState(0); // caret position WITHIN `input` (0..input.length), for ←/→ editing
101
102
  const [busy, setBusy] = useState(false); // a turn is in flight
102
103
  const [pending, setPending] = useState(null); // a prompt/menu blocking input
103
104
  const [menuSel, setMenuSel] = useState(0); // the select menu's cursor
@@ -465,6 +466,7 @@ export function App({ session, runTurn }) {
465
466
  setSubAgentFocus(null);
466
467
  const text = input.trim();
467
468
  setInput("");
469
+ setCursor(0);
468
470
  setHistIdx(null);
469
471
  if (!text)
470
472
  return;
@@ -472,13 +474,26 @@ export function App({ session, runTurn }) {
472
474
  history.push(text); // remember non-empty entries for ↑/↓
473
475
  void submitLine(text);
474
476
  }
477
+ else if (key.leftArrow) {
478
+ setCursor((c) => Math.max(0, c - 1)); // move the caret left within the line
479
+ }
480
+ else if (key.rightArrow) {
481
+ setCursor((c) => Math.min(input.length, c + 1)); // ...and right
482
+ }
483
+ else if (key.ctrl && char === "a") {
484
+ setCursor(0); // Home — jump to the start of the line
485
+ }
486
+ else if (key.ctrl && char === "e") {
487
+ setCursor(input.length); // End — jump to the end of the line
488
+ }
475
489
  else if (key.upArrow) {
476
- // ↑ recall an earlier prompt (newest-first), like a shell.
490
+ // ↑ recall an earlier prompt (newest-first), like a shell; caret to its end.
477
491
  if (!history.length)
478
492
  return;
479
493
  const idx = histIdx === null ? history.length - 1 : Math.max(0, histIdx - 1);
480
494
  setHistIdx(idx);
481
495
  setInput(history[idx]);
496
+ setCursor(history[idx].length);
482
497
  }
483
498
  else if (key.downArrow) {
484
499
  // ↓ walk back toward the fresh line.
@@ -488,24 +503,32 @@ export function App({ session, runTurn }) {
488
503
  if (idx >= history.length) {
489
504
  setHistIdx(null);
490
505
  setInput("");
506
+ setCursor(0);
491
507
  }
492
508
  else {
493
509
  setHistIdx(idx);
494
510
  setInput(history[idx]);
511
+ setCursor(history[idx].length);
495
512
  }
496
513
  }
497
514
  else if (key.backspace || key.delete) {
498
- setInput((s) => s.slice(0, -1));
515
+ // Delete the char BEFORE the caret. Both Backspace and macOS DEL land here,
516
+ // so this is always a backward delete (forward-delete is rare; we skip it).
517
+ if (cursor > 0) {
518
+ setInput(input.slice(0, cursor - 1) + input.slice(cursor));
519
+ setCursor(cursor - 1);
520
+ }
499
521
  }
500
522
  else if (char && !key.ctrl && !key.meta) {
501
- // A bulk chunk (a paste / a dropped file path) is normalized: a dropped
502
- // file becomes a clean absolute path; ordinary pasted prose is unchanged.
523
+ // Insert at the caret (a bulk chunk — paste / dropped file path — is
524
+ // normalized: a dropped file becomes a clean absolute path; prose unchanged).
503
525
  const add = char.length > 1 ? normalizeDroppedPaths(char) : char;
504
526
  setHistIdx(null);
505
- setInput((s) => s + add);
527
+ setInput(input.slice(0, cursor) + add + input.slice(cursor));
528
+ setCursor(cursor + add.length);
506
529
  }
507
530
  });
508
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: items, children: (item, i) => _jsx(ItemView, { item: item }, i) }), live !== null && (_jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsx(Text, { color: "green", children: "\u23FA " }), _jsx(Box, { flexGrow: 1, children: _jsx(Text, { children: live === "" ? chalk.dim("…") : liveTail(live) }) })] })), (status !== null || (busy && live === null)) && (_jsxs(Box, { marginTop: live === null ? 1 : 0, children: [_jsx(Spinner, {}), _jsxs(Text, { children: [" ", status ?? chalk.dim("working…")] })] })), pending?.kind === "select" && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "yellow", children: pending.header }), _jsx(Text, { children: renderMenu(pending.options, menuSel) }), _jsx(Text, { children: MENU_HINT })] })), pending?.kind === "form" && formState && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { children: renderForm(pending.questions, formState) }) })), _jsxs(Box, { borderStyle: "round", borderColor: planMode ? "magenta" : "cyan", paddingX: 1, marginTop: 1, children: [_jsx(Text, { color: planMode ? "magenta" : "cyan", children: planMode ? "plan ❯ " : "❯ " }), _jsx(Text, { children: input }), _jsx(Text, { inverse: true, children: " " })] }), (() => {
531
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: items, children: (item, i) => _jsx(ItemView, { item: item }, i) }), live !== null && (_jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsx(Text, { color: "green", children: "\u23FA " }), _jsx(Box, { flexGrow: 1, children: _jsx(Text, { children: live === "" ? chalk.dim("…") : liveTail(live) }) })] })), (status !== null || (busy && live === null)) && (_jsxs(Box, { marginTop: live === null ? 1 : 0, children: [_jsx(Spinner, {}), _jsxs(Text, { children: [" ", status ?? chalk.dim("working…")] })] })), pending?.kind === "select" && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "yellow", children: pending.header }), _jsx(Text, { children: renderMenu(pending.options, menuSel) }), _jsx(Text, { children: MENU_HINT })] })), pending?.kind === "form" && formState && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { children: renderForm(pending.questions, formState) }) })), _jsxs(Box, { borderStyle: "round", borderColor: planMode ? "magenta" : "cyan", paddingX: 1, marginTop: 1, children: [_jsx(Text, { color: planMode ? "magenta" : "cyan", children: planMode ? "plan ❯ " : "❯ " }), _jsx(Text, { children: input.slice(0, cursor) }), _jsx(Text, { inverse: true, children: input.slice(cursor, cursor + 1) || " " }), _jsx(Text, { children: input.slice(cursor + 1) })] }), (() => {
509
532
  const agents = listSubAgents();
510
533
  if (!agents.length)
511
534
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-from-zero",
3
- "version": "0.32.3",
3
+ "version": "0.32.4",
4
4
  "description": "Build a Claude Code-style CLI agent from scratch, with AI as your pair programmer — companion code for a 10-day tutorial (DeepSeek by default, any OpenAI-compatible model)",
5
5
  "type": "module",
6
6
  "bin": {