nexrall-code 0.5.82 → 0.5.84

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 +74 -33
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -55882,6 +55882,12 @@ function stopRowCount() {
55882
55882
  rowCounter = null;
55883
55883
  return rows;
55884
55884
  }
55885
+ function addRowCount(text) {
55886
+ if (!rowCounter)
55887
+ return;
55888
+ const columns = process.stdout.columns || 80;
55889
+ rowCounter.rows += rowsOccupied(text, columns);
55890
+ }
55885
55891
  var NEX_LOGO = [
55886
55892
  "\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557",
55887
55893
  "\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D",
@@ -55925,7 +55931,7 @@ function renderBanner(info) {
55925
55931
  const PAD = 1;
55926
55932
  const CHROME = 3 + 4 * PAD;
55927
55933
  const LOGO_W = Math.max(...NEX_LOGO.map(visibleLen));
55928
- const termW = process.stdout.columns || 100;
55934
+ const termW = info.columns ?? process.stdout.columns ?? 100;
55929
55935
  const totalW = Math.max(LOGO_W + CHROME + 24, termW - 1);
55930
55936
  const headerText = `Nexrall Code v${info.version}`;
55931
55937
  const LEFT_W = Math.max(LOGO_W, visibleLen(headerText));
@@ -64945,11 +64951,14 @@ function repaintAfterResizeSettle() {
64945
64951
  if (inkInstance !== instance)
64946
64952
  return;
64947
64953
  process.stdout.write("\x1B[0J");
64954
+ forceFullRedrawRef?.();
64948
64955
  });
64949
64956
  }
64957
+ var forceFullRedrawRef = null;
64950
64958
  var DEFAULT_PROMPT = colors.primary.bold("> ");
64951
64959
  var App2 = ({ onReady }) => {
64952
64960
  const [items, setItems] = (0, import_react35.useState)([]);
64961
+ const [staticKey, setStaticKey] = (0, import_react35.useState)(0);
64953
64962
  const [footer, setFooterState] = (0, import_react35.useState)({ mode: "auto", autoApprove: false });
64954
64963
  const [busy, setBusyState] = (0, import_react35.useState)(false);
64955
64964
  const [line, setLineState] = (0, import_react35.useState)("");
@@ -64979,8 +64988,12 @@ var App2 = ({ onReady }) => {
64979
64988
  setQuitArmedState(at);
64980
64989
  }, []);
64981
64990
  const [live, setLiveState] = (0, import_react35.useState)("");
64982
- const print = (0, import_react35.useCallback)((text) => {
64983
- setItems((prev) => [...prev, { id: idCounter++, content: text }]);
64991
+ const print = (0, import_react35.useCallback)((text, opts) => {
64992
+ setItems((prev) => [...prev, { id: idCounter++, content: text, isBanner: opts?.isBanner }]);
64993
+ }, []);
64994
+ const bannerRegeneratorRef = (0, import_react35.useRef)(null);
64995
+ const setBannerRegenerator = (0, import_react35.useCallback)((fn) => {
64996
+ bannerRegeneratorRef.current = fn;
64984
64997
  }, []);
64985
64998
  const setFooter = (0, import_react35.useCallback)((info) => setFooterState(info), []);
64986
64999
  const setLive = (0, import_react35.useCallback)((text) => setLiveState(text), []);
@@ -65115,10 +65128,28 @@ var App2 = ({ onReady }) => {
65115
65128
  return s2.slice(0, pos) + char + s2.slice(pos);
65116
65129
  });
65117
65130
  });
65131
+ const forceFullRedraw = (0, import_react35.useCallback)(() => {
65132
+ process.stdout.write("\x1B[2J\x1B[H");
65133
+ const regen = bannerRegeneratorRef.current;
65134
+ if (regen) {
65135
+ const columns = process.stdout.columns || 80;
65136
+ setItems((prev) => prev.map(
65137
+ (it) => it.isBanner ? { ...it, content: regen(columns) } : it
65138
+ ));
65139
+ }
65140
+ setStaticKey((k) => k + 1);
65141
+ }, []);
65142
+ import_react35.default.useEffect(() => {
65143
+ forceFullRedrawRef = forceFullRedraw;
65144
+ return () => {
65145
+ forceFullRedrawRef = null;
65146
+ };
65147
+ }, [forceFullRedraw]);
65118
65148
  import_react35.default.useEffect(() => {
65119
65149
  onReady({
65120
65150
  print,
65121
65151
  setFooter,
65152
+ setBannerRegenerator,
65122
65153
  setLive,
65123
65154
  askLine,
65124
65155
  onLine,
@@ -65130,7 +65161,7 @@ var App2 = ({ onReady }) => {
65130
65161
  });
65131
65162
  }, []);
65132
65163
  const slices = renderInputSlices(line, cursorPos);
65133
- 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(
65164
+ return /* @__PURE__ */ import_react35.default.createElement(Box_default, { flexDirection: "column" }, /* @__PURE__ */ import_react35.default.createElement(Static, { items, key: staticKey }, (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(
65134
65165
  Box_default,
65135
65166
  {
65136
65167
  borderStyle: "single",
@@ -65140,7 +65171,7 @@ var App2 = ({ onReady }) => {
65140
65171
  borderRight: false,
65141
65172
  borderDimColor: true
65142
65173
  }
65143
- ), /* @__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)), /* @__PURE__ */ import_react35.default.createElement(
65174
+ ), /* @__PURE__ */ import_react35.default.createElement(Box_default, null, /* @__PURE__ */ import_react35.default.createElement(Text, 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))), /* @__PURE__ */ import_react35.default.createElement(
65144
65175
  Box_default,
65145
65176
  {
65146
65177
  borderStyle: "single",
@@ -65168,34 +65199,36 @@ function startInkTerminal() {
65168
65199
  }
65169
65200
  ),
65170
65201
  {
65171
- exitOnCtrlC: false,
65172
- // Opt into the Kitty keyboard protocol so `key.shift` is actually
65173
- // populated for Enter (see the Shift+Enter handling in useInput
65174
- // above) instead of always being false/undefined.
65202
+ exitOnCtrlC: false
65203
+ // `kittyKeyboard` is deliberately OMITTED no option object at all,
65204
+ // matching the real `claude` binary exactly (verified: capturing
65205
+ // claude's raw startup bytes in a pty shows no `\x1b[>1u` enable
65206
+ // sequence and no `\x1b[?u` query anywhere in its output; it relies
65207
+ // purely on ordinary UTF-8 keystrokes). Ink's own opt-in check
65208
+ // (`if (!this.options.kittyKeyboard) return;` in ink.js) then does
65209
+ // nothing at all: no enable sequence is ever written to the terminal.
65175
65210
  //
65176
- // MUST be 'enabled', not 'auto'. 'auto' has Ink send a CSI `?u` query
65177
- // to the terminal and then listen on `stdin`'s 'data' event for the
65178
- // reply (see ink/build/ink.js confirmKittySupport). That listener
65179
- // races Ink's own input pipeline, which reads stdin via 'readable' +
65180
- // `.read()` (paused mode) for its normal keystroke handling Node
65181
- // streams do not support mixing 'data' (flowing) and 'readable'
65182
- // (paused) consumers safely, and in practice the 'readable' consumer
65183
- // wins the terminal's query response. The reply itself (`\x1b[?1u`)
65184
- // then gets fed through Ink's normal key parser, which doesn't
65185
- // recognise it as any special key and inserts it as literal text —
65186
- // this is exactly the "[?1u" that appears in the input box right
65187
- // after Ink mounts (e.g. right after confirming the trust prompt).
65211
+ // This used to be `{ mode: 'enabled', flags: [...] }`, to populate
65212
+ // `key.shift` on Enter for Shift+Enter detection. That traded away
65213
+ // more than the tradeoff docs here previously accounted for: the
65214
+ // protocol doesn't just risk an inert unrecognised escape sequence on
65215
+ // unsupporting terminals on a REAL Mac Terminal.app with the
65216
+ // system's built-in Vietnamese Telex input source active, enabling it
65217
+ // broke typing accented characters entirely (reported: chào/việt
65218
+ // could be typed in `claude` but not in `nex`, same machine, same
65219
+ // input source, only the enable sequence differed between the two
65220
+ // binaries confirmed by pty-capturing both). The Kitty protocol
65221
+ // shifts key delivery from "already-composed UTF-8 text" to
65222
+ // raw keycodes, which is exactly the layer an IME's composition
65223
+ // depends on; some terminals that don't fully implement the protocol
65224
+ // still react to the enable sequence by changing how they hand off
65225
+ // composed IME text, rather than silently ignoring it as intended.
65188
65226
  //
65189
- // 'enabled' skips the query/response dance entirely: Ink just writes
65190
- // the enable sequence once (`\x1b[>1u`) and never listens for a
65191
- // reply, so there is nothing to leak into the input box. Terminals
65192
- // that don't support the protocol silently ignore the unrecognised
65193
- // escape sequence (the same way they ignore bracketed-paste mode),
65194
- // so Shift+Enter still safely falls back to the '\' continuation
65195
- // there — this trades "verify support first" for "assume support,
65196
- // no-op if absent", which is safe here because the only visible
65197
- // effect of enabling on an unsupporting terminal is nothing at all.
65198
- kittyKeyboard: { mode: "enabled", flags: ["disambiguateEscapeCodes"] }
65227
+ // Shift+Enter therefore no longer gets a distinguishable `key.shift`
65228
+ // on any terminal it always submits, same as plain Enter, exactly
65229
+ // matching claude's own behavior. The existing "\" continuation for
65230
+ // multi-line input (chat.ts) remains the way to enter a literal
65231
+ // newline, unchanged.
65199
65232
  }
65200
65233
  );
65201
65234
  });
@@ -65986,14 +66019,22 @@ Set ${TRUST_ENV_VAR}=1 to confirm non-interactively, or run \`nex\` interactivel
65986
66019
  if (branch) {
65987
66020
  tips.push(`On branch ${branch}${changedLines > 0 ? ` (${changedLines} changed)` : ""}.`);
65988
66021
  }
65989
- console.log(renderBanner({
66022
+ const bannerInfo = {
65990
66023
  version: CLI_VERSION,
65991
66024
  userLabel: currentUserLabel(),
65992
66025
  modelLabel: resolveModelLabel(modelAlias),
65993
66026
  workDir,
65994
66027
  nexrallMdLoaded: !!nexrallMd,
65995
66028
  tips
65996
- }));
66029
+ };
66030
+ if (interactive) {
66031
+ const bannerCard = renderBanner({ ...bannerInfo, columns: process.stdout.columns ?? 80 });
66032
+ getInkTerminal().print(bannerCard, { isBanner: true });
66033
+ getInkTerminal().setBannerRegenerator((columns) => renderBanner({ ...bannerInfo, columns }));
66034
+ addRowCount(bannerCard);
66035
+ } else {
66036
+ console.log(renderBanner({ ...bannerInfo, columns: process.stdout.columns ?? 80 }));
66037
+ }
65997
66038
  console.log();
65998
66039
  }
65999
66040
  let messages = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexrall-code",
3
- "version": "0.5.82",
3
+ "version": "0.5.84",
4
4
  "description": "Nexrall Code — AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
5
5
  "keywords": [
6
6
  "ai",