nexrall-code 0.5.24 → 0.5.27

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 +163 -5
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -12673,7 +12673,7 @@ Resolve the conflict (remove <<<<<<< / ======= / >>>>>>> markers and keep the in
12673
12673
  var _bgCounter = 0;
12674
12674
  function startBackgroundShell(command, displayCommand, workDir, note) {
12675
12675
  const id = `bg_${++_bgCounter}`;
12676
- const child = (0, child_process_1.spawn)(command, { shell: true, cwd: workDir ?? process.cwd(), env: process.env, detached: true });
12676
+ const child = (0, child_process_1.spawn)(command, { shell: true, cwd: workDir ?? process.cwd(), env: process.env, detached: true, stdio: ["ignore", "pipe", "pipe"] });
12677
12677
  const shell = {
12678
12678
  id,
12679
12679
  command: displayCommand,
@@ -12835,7 +12835,8 @@ ${r2.output}`;
12835
12835
  shell: true,
12836
12836
  cwd: workDir ?? process.cwd(),
12837
12837
  env: process.env,
12838
- detached: true
12838
+ detached: true,
12839
+ stdio: ["ignore", "pipe", "pipe"]
12839
12840
  });
12840
12841
  const killTree = (sig) => {
12841
12842
  try {
@@ -23468,6 +23469,77 @@ function formatUsage(outputTokens) {
23468
23469
  return source_default.dim(`\u2193 ${tok} tokens`);
23469
23470
  }
23470
23471
 
23472
+ // src/ui/paste.ts
23473
+ var PASTE_START = "\x1B[200~";
23474
+ var PASTE_END = "\x1B[201~";
23475
+ function enableBracketedPaste() {
23476
+ if (process.stdout.isTTY)
23477
+ process.stdout.write("\x1B[?2004h");
23478
+ }
23479
+ function disableBracketedPaste() {
23480
+ if (process.stdout.isTTY)
23481
+ process.stdout.write("\x1B[?2004l");
23482
+ }
23483
+ function installPasteHandling(rl, stdin) {
23484
+ const originalListeners = stdin.listeners("data");
23485
+ for (const l of originalListeners)
23486
+ stdin.removeListener("data", l);
23487
+ const forward = (buf) => {
23488
+ if (!buf.length)
23489
+ return;
23490
+ for (const l of originalListeners)
23491
+ l.call(stdin, buf);
23492
+ };
23493
+ let buffering = false;
23494
+ let buffer = "";
23495
+ const insertPastedText = (text) => {
23496
+ if (!text)
23497
+ return;
23498
+ const flattened = text.replace(/\r\n|\r|\n/g, " ");
23499
+ const anyRl = rl;
23500
+ if (typeof anyRl._insertString === "function") {
23501
+ anyRl._insertString(flattened);
23502
+ } else {
23503
+ rl.write(flattened);
23504
+ }
23505
+ };
23506
+ const onData = (chunk) => {
23507
+ let text = chunk.toString("utf-8");
23508
+ for (; ; ) {
23509
+ if (!buffering) {
23510
+ const startIdx = text.indexOf(PASTE_START);
23511
+ if (startIdx === -1) {
23512
+ forward(Buffer.from(text, "utf-8"));
23513
+ return;
23514
+ }
23515
+ const before = text.slice(0, startIdx);
23516
+ forward(Buffer.from(before, "utf-8"));
23517
+ text = text.slice(startIdx + PASTE_START.length);
23518
+ buffering = true;
23519
+ buffer = "";
23520
+ }
23521
+ const endIdx = text.indexOf(PASTE_END);
23522
+ if (endIdx === -1) {
23523
+ buffer += text;
23524
+ return;
23525
+ }
23526
+ buffer += text.slice(0, endIdx);
23527
+ insertPastedText(buffer);
23528
+ buffer = "";
23529
+ buffering = false;
23530
+ text = text.slice(endIdx + PASTE_END.length);
23531
+ if (!text)
23532
+ return;
23533
+ }
23534
+ };
23535
+ stdin.on("data", onData);
23536
+ return () => {
23537
+ stdin.removeListener("data", onData);
23538
+ for (const l of originalListeners)
23539
+ stdin.on("data", l);
23540
+ };
23541
+ }
23542
+
23471
23543
  // src/permissions/handler.ts
23472
23544
  var readline2 = __toESM(require("readline"));
23473
23545
  var fs2 = __toESM(require("fs"));
@@ -24249,7 +24321,7 @@ async function requestPermission(req) {
24249
24321
  var fs3 = __toESM(require("fs"));
24250
24322
  var path = __toESM(require("path"));
24251
24323
  var os2 = __toESM(require("os"));
24252
- var SESSIONS_DIR = path.join(os2.homedir(), ".nexrall", "cli-sessions");
24324
+ var SESSIONS_DIR = process.env.NEXRALL_CLI_SESSIONS_DIR || path.join(os2.homedir(), ".nexrall", "cli-sessions");
24253
24325
  function ensureDir() {
24254
24326
  fs3.mkdirSync(SESSIONS_DIR, { recursive: true });
24255
24327
  }
@@ -24565,10 +24637,17 @@ var Spinner = class {
24565
24637
  interval = null;
24566
24638
  frames = ["\u25D0", "\u25D3", "\u25D1", "\u25D2"];
24567
24639
  i = 0;
24640
+ startedAt = 0;
24641
+ baseText = "";
24568
24642
  start(text) {
24569
24643
  this.stop();
24644
+ this.baseText = text;
24645
+ this.startedAt = Date.now();
24570
24646
  this.interval = setInterval(() => {
24571
- process.stdout.write(`\r${source_default.cyan(this.frames[this.i % this.frames.length])} ${source_default.dim(text)}`);
24647
+ const elapsedSec = Math.floor((Date.now() - this.startedAt) / 1e3);
24648
+ const timer = source_default.dim(` (${elapsedSec}s)`);
24649
+ const hint = elapsedSec >= 15 ? source_default.dim(" \xB7 still running, no output yet is normal for quiet commands") : "";
24650
+ process.stdout.write(`\r\x1B[K${source_default.cyan(this.frames[this.i % this.frames.length])} ${source_default.dim(this.baseText)}${timer}${hint}`);
24572
24651
  this.i++;
24573
24652
  }, 100);
24574
24653
  }
@@ -24889,6 +24968,7 @@ function printHelp() {
24889
24968
  ["/yolo", "Auto-approve all permissions"],
24890
24969
  ["/balance", "Show wallet balance"],
24891
24970
  ["/add <filepath>", "Add a file to conversation context"],
24971
+ ["/image <filepath> [caption]", "Attach an image or PDF (png/jpg/gif/webp/pdf, max 10MB) and send it to the model"],
24892
24972
  ["/skills", "List skills (.nexrall/skills/<name>/SKILL.md or .nexrall/commands/*.md) \u2014 the model can also auto-invoke these"],
24893
24973
  ["/plugins", "List installed plugins (.nexrall/plugins/)"],
24894
24974
  ["/mcp", "Show MCP server connection status"],
@@ -25103,6 +25183,9 @@ ${text}` : "");
25103
25183
  prompt: colors.primary.bold("> ")
25104
25184
  });
25105
25185
  setReadlineInterface(rl);
25186
+ enableBracketedPaste();
25187
+ process.on("exit", disableBracketedPaste);
25188
+ const uninstallPasteHandling = installPasteHandling(rl, process.stdin);
25106
25189
  rl.prompt();
25107
25190
  let inputBuffer = "";
25108
25191
  rl.on("line", async (rawLine) => {
@@ -25461,6 +25544,80 @@ ${content}
25461
25544
  rl.prompt();
25462
25545
  return;
25463
25546
  }
25547
+ case "/image": {
25548
+ if (agentRunning) {
25549
+ console.log(source_default.red(" Cannot attach while agent is running."));
25550
+ rl.prompt();
25551
+ return;
25552
+ }
25553
+ if (!arg) {
25554
+ console.log(source_default.red(" Usage: /image <filepath> [caption]"));
25555
+ rl.prompt();
25556
+ return;
25557
+ }
25558
+ const [rawPath, ...captionParts] = arg.split(/\s+/);
25559
+ const caption = captionParts.join(" ").trim();
25560
+ const filePath = path3.isAbsolute(rawPath) ? rawPath : path3.join(workDir, rawPath);
25561
+ const ext = path3.extname(filePath).toLowerCase();
25562
+ const IMAGE_MIME = {
25563
+ ".png": "image/png",
25564
+ ".jpg": "image/jpeg",
25565
+ ".jpeg": "image/jpeg",
25566
+ ".gif": "image/gif",
25567
+ ".webp": "image/webp"
25568
+ };
25569
+ const isPdf = ext === ".pdf";
25570
+ const mimeType = IMAGE_MIME[ext];
25571
+ if (!mimeType && !isPdf) {
25572
+ console.log(source_default.red(` Unsupported file type: ${ext || "(none)"}. Supported: png, jpg, jpeg, gif, webp, pdf.`));
25573
+ rl.prompt();
25574
+ return;
25575
+ }
25576
+ let buf;
25577
+ try {
25578
+ buf = fs5.readFileSync(filePath);
25579
+ } catch (err) {
25580
+ console.error(source_default.red(" Failed: ") + String(err.message));
25581
+ rl.prompt();
25582
+ return;
25583
+ }
25584
+ const MAX_BYTES = 10 * 1024 * 1024;
25585
+ if (buf.length > MAX_BYTES) {
25586
+ console.log(source_default.red(` File too large (${(buf.length / 1024 / 1024).toFixed(1)}MB) \u2014 max 10MB.`));
25587
+ rl.prompt();
25588
+ return;
25589
+ }
25590
+ const rel = path3.relative(workDir, filePath);
25591
+ const data = buf.toString("base64");
25592
+ const label = caption ? `[Attached: ${rel}]
25593
+ ${caption}` : `[Attached: ${rel}]`;
25594
+ const content = [{ type: "text", text: label }];
25595
+ content.push(
25596
+ isPdf ? { type: "document", source: { type: "base64", media_type: "application/pdf", data } } : { type: "image", source: { type: "base64", media_type: mimeType, data } }
25597
+ );
25598
+ console.log(source_default.dim(` Attached ${rel} (${(buf.length / 1024).toFixed(0)}KB) \u2014 sending\u2026`));
25599
+ checkpoints.beginTurn(`/image ${rel}${caption ? " " + caption : ""}`, messages.length);
25600
+ messages.push({ role: "user", content });
25601
+ rl.pause();
25602
+ abortSignal.aborted = false;
25603
+ agentRunning = true;
25604
+ try {
25605
+ const result = await runTurn(messages, modelAlias, workDir, abortSignal, env2, nexrallMd, agentMode, effortLevel, checkpoints, saveProgress);
25606
+ messages = result.messages;
25607
+ updateTitle();
25608
+ saveSession(sessionId, sessionTitle, workDir, messages);
25609
+ } catch (err) {
25610
+ messages = recoverTurn(err, messages);
25611
+ updateTitle();
25612
+ saveSession(sessionId, sessionTitle || "Untitled", workDir, messages);
25613
+ } finally {
25614
+ agentRunning = false;
25615
+ checkpoints.commitTurn();
25616
+ }
25617
+ rl.resume();
25618
+ rl.prompt();
25619
+ return;
25620
+ }
25464
25621
  case "/update": {
25465
25622
  rl.close();
25466
25623
  await updateCommand({ yes: false });
@@ -25584,6 +25741,7 @@ ${content}
25584
25741
  rl.prompt();
25585
25742
  });
25586
25743
  rl.on("close", () => {
25744
+ uninstallPasteHandling();
25587
25745
  updateTitle();
25588
25746
  if (messages.length)
25589
25747
  saveSession(sessionId, sessionTitle || "Untitled", workDir, messages);
@@ -25797,7 +25955,7 @@ function pluginListCommand() {
25797
25955
 
25798
25956
  // src/index.ts
25799
25957
  var program2 = new Command();
25800
- program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.24");
25958
+ program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.27");
25801
25959
  program2.command("auth").description("Login to your Nexrall account").action(authCommand);
25802
25960
  program2.command("logout").description("Log out of your Nexrall account").action(logoutCommand);
25803
25961
  program2.command("update").description("Update nex to the latest version").option("-c, --check", "Check for updates without installing").option("-y, --yes", "Skip confirmation prompt").action(async (opts) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexrall-code",
3
- "version": "0.5.24",
3
+ "version": "0.5.27",
4
4
  "description": "Nexrall Code — AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
5
5
  "keywords": [
6
6
  "ai",
@@ -37,7 +37,7 @@
37
37
  "ora": "^8.0.1",
38
38
  "prompts": "^2.4.2",
39
39
  "readline": "^1.3.0",
40
- "@nexrall/code-core": "1.4.19"
40
+ "@nexrall/code-core": "1.4.21"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@aws-sdk/client-s3": "^3.600.0",
@@ -52,6 +52,7 @@
52
52
  "build": "node build.js",
53
53
  "dev": "tsx src/index.ts",
54
54
  "start": "node dist/index.js",
55
+ "test": "tsc --noEmit && tsc --noEmit -p test/tsconfig.json && node --import tsx --test test/*.test.ts test/*.test.mts",
55
56
  "release": "node build.js && node scripts/upload-release.js"
56
57
  }
57
58
  }