nexrall-code 0.5.77 → 0.5.78

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/index.js +110 -25
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -55817,7 +55817,18 @@ function prepareSessionScreen() {
55817
55817
  return;
55818
55818
  process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
55819
55819
  }
55820
- var BOTTOM_CHROME_ROWS = 3;
55820
+ function bottomChromeRows(opts = {
55821
+ footerText: ""
55822
+ }) {
55823
+ const columns = opts.columns ?? process.stdout.columns ?? 80;
55824
+ const separatorRows = 1;
55825
+ const inputRows = rowsOccupied(
55826
+ " ".repeat(opts.promptWidth ?? 2) + (opts.inputText ?? ""),
55827
+ columns
55828
+ );
55829
+ const footerRows = rowsOccupied(opts.footerText, columns);
55830
+ return separatorRows + inputRows + footerRows;
55831
+ }
55821
55832
  function rowsOccupied(text, columns = process.stdout.columns || 80) {
55822
55833
  let rows = 0;
55823
55834
  for (const line of text.split("\n")) {
@@ -55826,11 +55837,12 @@ function rowsOccupied(text, columns = process.stdout.columns || 80) {
55826
55837
  }
55827
55838
  return rows;
55828
55839
  }
55829
- function padToBottom(rowsAlreadyPrinted) {
55840
+ function padToBottom(rowsAlreadyPrinted, footerText2 = "") {
55830
55841
  const rows = process.stdout.rows;
55831
55842
  if (!process.stdout.isTTY || !rows)
55832
55843
  return;
55833
- const filler = rows - rowsAlreadyPrinted - BOTTOM_CHROME_ROWS - 2;
55844
+ const chromeRows = bottomChromeRows({ footerText: footerText2, columns: process.stdout.columns ?? 80 });
55845
+ const filler = rows - rowsAlreadyPrinted - chromeRows - 2;
55834
55846
  const MIN_WORTHWHILE_FILLER = 3;
55835
55847
  if (filler < MIN_WORTHWHILE_FILLER)
55836
55848
  return;
@@ -55841,19 +55853,26 @@ function startRowCount() {
55841
55853
  if (rowCounter)
55842
55854
  return;
55843
55855
  const columns = process.stdout.columns || 80;
55844
- const original = console.log;
55845
- const state = { rows: 0, original };
55856
+ const originalLog = console.log;
55857
+ const originalError = console.error;
55858
+ const state = { rows: 0, originalLog, originalError };
55846
55859
  rowCounter = state;
55860
+ const measure = (args) => args.length === 0 ? 1 : rowsOccupied(args.map(String).join(" "), columns);
55847
55861
  console.log = (...args) => {
55848
- state.rows += args.length === 0 ? 1 : rowsOccupied(args.map(String).join(" "), columns);
55849
- original(...args);
55862
+ state.rows += measure(args);
55863
+ originalLog(...args);
55864
+ };
55865
+ console.error = (...args) => {
55866
+ state.rows += measure(args);
55867
+ originalError(...args);
55850
55868
  };
55851
55869
  }
55852
55870
  function stopRowCount() {
55853
55871
  if (!rowCounter)
55854
55872
  return 0;
55855
- const { rows, original } = rowCounter;
55856
- console.log = original;
55873
+ const { rows, originalLog, originalError } = rowCounter;
55874
+ console.log = originalLog;
55875
+ console.error = originalError;
55857
55876
  rowCounter = null;
55858
55877
  return rows;
55859
55878
  }
@@ -64873,6 +64892,26 @@ function nextGraphemeBoundary(text, pos) {
64873
64892
  }
64874
64893
  return text.length;
64875
64894
  }
64895
+ function renderInputSlices(line, cursorPos) {
64896
+ const end = nextGraphemeBoundary(line, cursorPos);
64897
+ return {
64898
+ before: line.slice(0, cursorPos),
64899
+ atCursor: line.slice(cursorPos, end) || " ",
64900
+ after: line.slice(end)
64901
+ };
64902
+ }
64903
+ var CTRL_C_QUIT_WINDOW_MS = 3e3;
64904
+ var CTRL_C_HINT = "Press Ctrl+C again to exit";
64905
+ function decideCtrlC(state) {
64906
+ if (state.hasPendingQuestion)
64907
+ return "answer-prompt";
64908
+ if (state.busy)
64909
+ return "interrupt-turn";
64910
+ if (state.quitArmedAt !== null && state.now - state.quitArmedAt <= CTRL_C_QUIT_WINDOW_MS) {
64911
+ return "quit";
64912
+ }
64913
+ return "arm-quit";
64914
+ }
64876
64915
  var handle = null;
64877
64916
  var inkInstance = null;
64878
64917
  var idCounter = 0;
@@ -64902,6 +64941,12 @@ var App2 = ({ onReady }) => {
64902
64941
  const onLineRef = (0, import_react35.useRef)(null);
64903
64942
  const onQueuedLineRef = (0, import_react35.useRef)(null);
64904
64943
  const busyRef = (0, import_react35.useRef)(false);
64944
+ const [quitArmed, setQuitArmedState] = (0, import_react35.useState)(null);
64945
+ const quitArmedAtRef = (0, import_react35.useRef)(null);
64946
+ const setQuitArmed = (0, import_react35.useCallback)((at) => {
64947
+ quitArmedAtRef.current = at;
64948
+ setQuitArmedState(at);
64949
+ }, []);
64905
64950
  const [live, setLiveState] = (0, import_react35.useState)("");
64906
64951
  const print = (0, import_react35.useCallback)((text) => {
64907
64952
  setItems((prev) => [...prev, { id: idCounter++, content: text }]);
@@ -64933,23 +64978,49 @@ var App2 = ({ onReady }) => {
64933
64978
  const onInterrupt = (0, import_react35.useCallback)((cb) => {
64934
64979
  onInterruptRef.current = cb;
64935
64980
  }, []);
64981
+ const onExitRef = (0, import_react35.useRef)(null);
64982
+ const onExit = (0, import_react35.useCallback)((cb) => {
64983
+ onExitRef.current = cb;
64984
+ }, []);
64985
+ const exitedRef = (0, import_react35.useRef)(false);
64986
+ const requestExit = (0, import_react35.useCallback)((notify) => {
64987
+ if (exitedRef.current)
64988
+ return;
64989
+ exitedRef.current = true;
64990
+ if (notify)
64991
+ onExitRef.current?.();
64992
+ exit();
64993
+ }, []);
64936
64994
  use_input_default((char, key) => {
64937
64995
  if (key.ctrl && char === "c") {
64938
- const askResolve = askResolverRef.current;
64939
- if (askResolve) {
64996
+ const action = decideCtrlC({
64997
+ hasPendingQuestion: askResolverRef.current !== null,
64998
+ busy: busyRef.current && onInterruptRef.current !== null,
64999
+ quitArmedAt: quitArmedAtRef.current,
65000
+ now: Date.now()
65001
+ });
65002
+ if (action === "answer-prompt") {
65003
+ const askResolve = askResolverRef.current;
64940
65004
  askResolverRef.current = null;
64941
65005
  setLine("");
64942
65006
  setCursorPos(0);
64943
65007
  askResolve("n");
64944
65008
  return;
64945
65009
  }
64946
- if (busyRef.current && onInterruptRef.current) {
64947
- onInterruptRef.current();
65010
+ if (action === "interrupt-turn") {
65011
+ setQuitArmed(null);
65012
+ onInterruptRef.current?.();
64948
65013
  return;
64949
65014
  }
64950
- exit();
65015
+ if (action === "arm-quit") {
65016
+ setQuitArmed(Date.now());
65017
+ return;
65018
+ }
65019
+ requestExit(true);
64951
65020
  return;
64952
65021
  }
65022
+ if (quitArmedAtRef.current !== null)
65023
+ setQuitArmed(null);
64953
65024
  if (key.backspace || key.delete) {
64954
65025
  if (key.delete) {
64955
65026
  setLine((s2) => {
@@ -65022,11 +65093,13 @@ var App2 = ({ onReady }) => {
65022
65093
  onLine,
65023
65094
  onQueuedLine,
65024
65095
  onInterrupt,
65096
+ onExit,
65025
65097
  setBusy,
65026
- close: () => exit()
65098
+ close: () => requestExit(false)
65027
65099
  });
65028
65100
  }, []);
65029
- return /* @__PURE__ */ import_react35.default.createElement(Box_default, { flexDirection: "column" }, /* @__PURE__ */ import_react35.default.createElement(Static, { items }, (item) => /* @__PURE__ */ import_react35.default.createElement(Text, { key: item.id }, item.content)), live ? /* @__PURE__ */ import_react35.default.createElement(Text, null, live) : null, /* @__PURE__ */ import_react35.default.createElement(Text, { dimColor: true }, "\u2500".repeat(Math.max(1, columns - 1))), /* @__PURE__ */ import_react35.default.createElement(Box_default, null, /* @__PURE__ */ import_react35.default.createElement(Text, null, prompt2), /* @__PURE__ */ import_react35.default.createElement(Text, null, line.slice(0, cursorPos)), /* @__PURE__ */ import_react35.default.createElement(Text, { inverse: true }, line.slice(cursorPos, cursorPos + 1) || " "), /* @__PURE__ */ import_react35.default.createElement(Text, null, line.slice(cursorPos + 1))), /* @__PURE__ */ import_react35.default.createElement(Text, { dimColor: true }, footerText(footer), busy ? " \xB7 agent working\u2026" : ""));
65101
+ const slices = renderInputSlices(line, cursorPos);
65102
+ return /* @__PURE__ */ import_react35.default.createElement(Box_default, { flexDirection: "column" }, /* @__PURE__ */ import_react35.default.createElement(Static, { items }, (item) => /* @__PURE__ */ import_react35.default.createElement(Text, { key: item.id }, item.content)), live ? /* @__PURE__ */ import_react35.default.createElement(Text, null, live) : null, /* @__PURE__ */ import_react35.default.createElement(Text, { dimColor: true }, "\u2500".repeat(Math.max(1, columns - 1))), /* @__PURE__ */ import_react35.default.createElement(Box_default, null, /* @__PURE__ */ import_react35.default.createElement(Text, null, prompt2), /* @__PURE__ */ import_react35.default.createElement(Text, null, slices.before), /* @__PURE__ */ import_react35.default.createElement(Text, { inverse: true }, slices.atCursor), /* @__PURE__ */ import_react35.default.createElement(Text, null, slices.after)), quitArmed !== null ? /* @__PURE__ */ import_react35.default.createElement(Text, { color: "yellow" }, CTRL_C_HINT) : /* @__PURE__ */ import_react35.default.createElement(Text, { dimColor: true }, footerText(footer), busy ? " \xB7 agent working\u2026" : ""));
65030
65103
  };
65031
65104
  function startInkTerminal() {
65032
65105
  if (handle)
@@ -65110,6 +65183,9 @@ var InkReadlineAdapter = class extends EventEmitter3 {
65110
65183
  this.ink.onInterrupt(() => {
65111
65184
  this.emit("interrupt");
65112
65185
  });
65186
+ this.ink.onExit(() => {
65187
+ this.emit("close");
65188
+ });
65113
65189
  }
65114
65190
  prompt(_preserveCursor) {
65115
65191
  }
@@ -65868,15 +65944,20 @@ Set ${TRUST_ENV_VAR}=1 to confirm non-interactively, or run \`nex\` interactivel
65868
65944
  let skills = (0, import_code_core3.loadSkills)(workDir);
65869
65945
  if (skills.length && !headless)
65870
65946
  console.log(source_default.dim(` ${skills.length} skill(s) loaded`));
65871
- try {
65872
- await initMcp(workDir);
65873
- const connected = _mcpManager?.serverNames.length ?? 0;
65874
- const total = _mcpConfig ? Object.keys(_mcpConfig.mcpServers).length : 0;
65875
- if (total && !headless) {
65876
- console.log(source_default.dim(` ${connected}/${total} MCP server(s) connected`) + source_default.dim(" (/mcp for details)"));
65947
+ const mcpStarted = (async () => {
65948
+ try {
65949
+ await initMcp(workDir);
65950
+ const connected = _mcpManager?.serverNames.length ?? 0;
65951
+ const total = _mcpConfig ? Object.keys(_mcpConfig.mcpServers).length : 0;
65952
+ if (total && !headless) {
65953
+ console.log(source_default.dim(` ${connected}/${total} MCP server(s) connected`) + source_default.dim(" (/mcp for details)"));
65954
+ }
65955
+ } catch {
65877
65956
  }
65878
- } catch {
65879
- }
65957
+ })();
65958
+ const mcpReady = () => mcpStarted;
65959
+ if (!interactive)
65960
+ await mcpStarted;
65880
65961
  if (headless && !options.prompt && !options.stdinText) {
65881
65962
  process.stdout.write(JSON.stringify({ type: "error", error: "--output-format json/stream-json requires a one-shot prompt (or piped stdin)." }) + "\n");
65882
65963
  process.exit(1);
@@ -65999,7 +66080,7 @@ ${text}` : "");
65999
66080
  console.log();
66000
66081
  if (interactive) {
66001
66082
  await flushInkFrame();
66002
- padToBottom(stopRowCount());
66083
+ padToBottom(stopRowCount(), footerText({ mode: agentMode, autoApprove: isYoloMode() }));
66003
66084
  }
66004
66085
  let agentRunning = false;
66005
66086
  const abortSignal = { aborted: false };
@@ -66469,6 +66550,7 @@ ${text}
66469
66550
  rl.pause();
66470
66551
  abortSignal.aborted = false;
66471
66552
  agentRunning = true;
66553
+ await mcpReady();
66472
66554
  try {
66473
66555
  const result = await runTurn(messages, modelAlias, workDir, abortSignal, env3, nexrallMd, agentMode, effortLevel, checkpoints, saveProgress, takePendingInput);
66474
66556
  messages = result.messages;
@@ -66510,6 +66592,7 @@ ${text}
66510
66592
  return;
66511
66593
  }
66512
66594
  case "/mcp": {
66595
+ await mcpReady();
66513
66596
  console.log();
66514
66597
  console.log(source_default.bold(" MCP servers:"));
66515
66598
  console.log(formatMcpStatus());
@@ -66589,6 +66672,7 @@ ${text}
66589
66672
  rl.pause();
66590
66673
  abortSignal.aborted = false;
66591
66674
  agentRunning = true;
66675
+ await mcpReady();
66592
66676
  try {
66593
66677
  const result = await runTurn(messages, turnModel, workDir, abortSignal, env3, nexrallMd, turnMode, effortLevel, checkpoints, saveProgress, takePendingInput);
66594
66678
  messages = result.messages;
@@ -66613,6 +66697,7 @@ ${text}
66613
66697
  rl.pause();
66614
66698
  abortSignal.aborted = false;
66615
66699
  agentRunning = true;
66700
+ await mcpReady();
66616
66701
  try {
66617
66702
  const result = await runTurn(messages, modelAlias, workDir, abortSignal, env3, nexrallMd, agentMode, effortLevel, checkpoints, saveProgress, takePendingInput);
66618
66703
  messages = result.messages;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexrall-code",
3
- "version": "0.5.77",
3
+ "version": "0.5.78",
4
4
  "description": "Nexrall Code — AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
5
5
  "keywords": [
6
6
  "ai",
@@ -41,7 +41,7 @@
41
41
  "release": "node build.js && node scripts/upload-release.cjs"
42
42
  },
43
43
  "dependencies": {
44
- "@nexrall/code-core": "^1.4.45",
44
+ "@nexrall/code-core": "^1.4.46",
45
45
  "chalk": "^5.3.0",
46
46
  "commander": "^12.0.0",
47
47
  "diff": "^5.2.0",