nexo-code 0.1.2 → 0.1.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/nexo.mjs +76 -23
  2. package/package.json +1 -1
package/nexo.mjs CHANGED
@@ -14550,7 +14550,7 @@ var require_cross_spawn = __commonJS({
14550
14550
  var cp = __require("child_process");
14551
14551
  var parse4 = require_parse();
14552
14552
  var enoent = require_enoent();
14553
- function spawn5(command, args, options) {
14553
+ function spawn6(command, args, options) {
14554
14554
  const parsed = parse4(command, args, options);
14555
14555
  const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
14556
14556
  enoent.hookChildProcess(spawned, parsed);
@@ -14562,8 +14562,8 @@ var require_cross_spawn = __commonJS({
14562
14562
  result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
14563
14563
  return result;
14564
14564
  }
14565
- module.exports = spawn5;
14566
- module.exports.spawn = spawn5;
14565
+ module.exports = spawn6;
14566
+ module.exports.spawn = spawn6;
14567
14567
  module.exports.sync = spawnSync3;
14568
14568
  module.exports._parse = parse4;
14569
14569
  module.exports._enoent = enoent;
@@ -14690,7 +14690,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
14690
14690
  import { join } from "node:path";
14691
14691
 
14692
14692
  // dist/version.js
14693
- var VERSION = "0.1.2";
14693
+ var VERSION = "0.1.4";
14694
14694
 
14695
14695
  // dist/update.js
14696
14696
  var PACKAGE = "nexo-code";
@@ -35138,6 +35138,8 @@ var SpeakSchema = external_exports.strictObject({
35138
35138
  var loginEmail = external_exports.string().trim().toLowerCase().pipe(external_exports.email().max(320));
35139
35139
  var EmailLoginStartSchema = external_exports.strictObject({ email: loginEmail, adult: external_exports.literal(true) });
35140
35140
  var EmailLoginVerifySchema = external_exports.strictObject({ email: loginEmail, code: external_exports.string().trim().regex(/^\d{6}$/, "six digits") });
35141
+ var DeviceApproveSchema = external_exports.strictObject({ userCode: external_exports.string().trim().toUpperCase().regex(/^[A-Z]{4}-[A-Z]{4}$/, "a code like ABCD-EFGH") });
35142
+ var DevicePollSchema = external_exports.strictObject({ deviceCode: external_exports.string().regex(/^[A-Za-z0-9_-]{43}$/) });
35141
35143
 
35142
35144
  // ../../packages/core/dist/sse.js
35143
35145
  var MAX_SSE_FRAME_CHARS = 2e6;
@@ -35894,6 +35896,7 @@ function formatCost(micros2) {
35894
35896
  const shown = micros2 === 0 ? "0%" : pct < 0.1 ? "under 0.1%" : pct < 10 ? `${pct.toFixed(1)}%` : `${Math.round(pct)}%`;
35895
35897
  return `${shown} of your ${shareOf.label}`;
35896
35898
  }
35899
+ var tierLabel = (tier) => tier === "strong" ? "Strong" : tier === "cheap" ? "Auto" : tier;
35897
35900
  function formatUsd(micros2) {
35898
35901
  const dollars = micros2 / 1e6;
35899
35902
  return dollars !== 0 && Math.abs(dollars) < 0.01 ? `$${dollars.toFixed(4)}` : `$${dollars.toFixed(2)}`;
@@ -37269,7 +37272,7 @@ function summaryLine(s, r, opts = {}) {
37269
37272
  const cached2 = r.tokens.cacheRead + r.tokens.cacheWrite;
37270
37273
  const parts = [
37271
37274
  ...opts.quiet && r.outcome === "done" ? [] : [status],
37272
- s.accent(r.tier),
37275
+ s.accent(tierLabel(r.tier)),
37273
37276
  `${formatTokens(r.tokens.input + cached2)} in${cached2 ? ` (${formatTokens(cached2)} cached)` : ""} / ${formatTokens(r.tokens.output)} out`,
37274
37277
  formatCost(r.costMicros),
37275
37278
  `${(r.elapsedMs / 1e3).toFixed(1)}s`
@@ -37503,7 +37506,7 @@ async function runCascade(task2, deps, opts, state = newCascadeState()) {
37503
37506
  }
37504
37507
  const reason = stoppedEarly ? "retry_limit" : "checks_failed";
37505
37508
  state.escalated = reason;
37506
- deps.log(s.accent(`${s.sym.busy} escalating to the strong model (${stoppedEarly ? "cheap attempt stopped early and checks fail" : "checks failed"})`));
37509
+ deps.log(s.accent(`${s.sym.busy} switching to the Strong model (${stoppedEarly ? "the first try stopped early and checks fail" : "checks failed"})`));
37507
37510
  let second;
37508
37511
  try {
37509
37512
  second = await attempt(escalationPrompt(task2, first, checks), "strong", reason);
@@ -37514,7 +37517,7 @@ async function runCascade(task2, deps, opts, state = newCascadeState()) {
37514
37517
  throw err;
37515
37518
  attempts.pop();
37516
37519
  state.escalated = null;
37517
- deps.log(s.warning(`${s.sym.warn} ${err.message} Trying again on the cheap model with the failure output.`));
37520
+ deps.log(s.warning(`${s.sym.warn} ${err.message} Trying again with Auto and the failure output.`));
37518
37521
  second = await attempt(escalationPrompt(task2, first, checks), "cheap");
37519
37522
  }
37520
37523
  const secondStopped = stoppedAt(second.outcome);
@@ -37554,7 +37557,7 @@ function cascadeLine(s, r, state) {
37554
37557
  const testFiles = files.filter((f) => TEST_FILE.test(f));
37555
37558
  const label = LABEL[r.outcome];
37556
37559
  const status = r.outcome === "passed" || r.outcome === "done_unchecked" ? s.success(`${s.sym.ok} ${label}`) : r.outcome === "failed" || r.outcome === "checks_failed" ? s.error(`${s.sym.fail} ${label}`) : s.warning(`${s.sym.warn} ${label}`);
37557
- const tiers = ran.map((a) => a.tier).join("\u2192") || r.attempts[0]?.tier || "cheap";
37560
+ const tiers = ran.map((a) => tierLabel(a.tier)).join("\u2192") || tierLabel(r.attempts[0]?.tier ?? "cheap");
37558
37561
  const why2 = r.escalated ? ` (escalated: ${r.escalated.replace("_", " ")})` : state?.firstReason ? ` (${state.firstReason.replace("_", " ")})` : "";
37559
37562
  const parts = [
37560
37563
  status,
@@ -37665,6 +37668,7 @@ async function errorFromResponse(res) {
37665
37668
  }
37666
37669
 
37667
37670
  // dist/commands.js
37671
+ import { spawn as spawn3 } from "node:child_process";
37668
37672
  import { join as join15 } from "node:path";
37669
37673
 
37670
37674
  // dist/project.js
@@ -44247,7 +44251,8 @@ async function login(io, opts) {
44247
44251
  io.err(`${s.error(s.sym.fail)} Use an https:// API URL (plain http is only allowed for localhost).`);
44248
44252
  return 2;
44249
44253
  }
44250
- const token = !opts.token && io.interactive && io.readLine ? await emailSignIn(io, apiUrl) : await io.readSecret(`Paste your Nexo token (input hidden): `);
44254
+ const terminal = !opts.token && io.interactive && io.readLine;
44255
+ const token = !terminal ? await io.readSecret(`Paste your Nexo token (input hidden): `) : opts.email ? await emailSignIn(io, apiUrl) : await browserSignIn(io, apiUrl);
44251
44256
  if (token === null)
44252
44257
  return 1;
44253
44258
  if (!TOKEN2.test(token)) {
@@ -44261,6 +44266,50 @@ async function login(io, opts) {
44261
44266
  io.out(s.dim(` user ${me.userId}`));
44262
44267
  return 0;
44263
44268
  }
44269
+ async function browserSignIn(io, apiUrl) {
44270
+ const { style: s } = io;
44271
+ const post = (path2, body) => (io.fetch ?? fetch)(new URL(path2, apiUrl), { method: "POST", headers: { "content-type": "application/json", "x-nexo-client": "cli" }, body: JSON.stringify(body), signal: io.signal });
44272
+ const res = await post("/v1/auth/device/start", {});
44273
+ if (!res.ok)
44274
+ throw await errorFromResponse(res);
44275
+ const start = await res.json();
44276
+ io.err(`${s.bold("Sign in to Nexo")} ${s.dim("(Ctrl+C to cancel)")}`);
44277
+ io.err(`Your code: ${s.bold(s.primary(start.userCode))} ${s.dim("(check that the browser shows the same)")}`);
44278
+ const opened = /^https:\/\//.test(start.verifyUrl) && (io.openUrl ?? openInBrowser)(start.verifyUrl);
44279
+ io.err(opened ? s.dim(`Opening your browser. If it did not open, go to ${start.verifyUrl}`) : `Open this page to approve: ${start.verifyUrl}`);
44280
+ io.err(s.dim(`${s.sym.busy} waiting for you to approve in the browser`));
44281
+ const until = Date.now() + start.expiresInMs;
44282
+ while (Date.now() < until) {
44283
+ await new Promise((resolve3) => {
44284
+ const t = setTimeout(resolve3, Math.max(1e3, start.intervalMs));
44285
+ io.signal.addEventListener("abort", () => (clearTimeout(t), resolve3()), { once: true });
44286
+ });
44287
+ if (io.signal.aborted)
44288
+ return null;
44289
+ const r = await post("/v1/auth/device/poll", { deviceCode: start.deviceCode }).catch(() => null);
44290
+ if (!r)
44291
+ continue;
44292
+ const body = await r.json().catch(() => ({}));
44293
+ if (body.status === "done" && body.token)
44294
+ return body.token;
44295
+ if (body.status === "expired" || r.status === 410)
44296
+ break;
44297
+ }
44298
+ io.err(`${s.error(s.sym.fail)} The sign-in was not approved in time. Run nexo login again.`);
44299
+ return null;
44300
+ }
44301
+ function openInBrowser(url2) {
44302
+ try {
44303
+ const [cmd, args] = process.platform === "win32" ? ["rundll32.exe", ["url.dll,FileProtocolHandler", url2]] : process.platform === "darwin" ? ["open", [url2]] : ["xdg-open", [url2]];
44304
+ const child = spawn3(cmd, args, { stdio: "ignore", detached: true, windowsHide: true });
44305
+ child.on("error", () => {
44306
+ });
44307
+ child.unref();
44308
+ return true;
44309
+ } catch {
44310
+ return false;
44311
+ }
44312
+ }
44264
44313
  async function emailSignIn(io, apiUrl) {
44265
44314
  const { style: s } = io;
44266
44315
  io.err(`${s.bold("Sign in to Nexo")} ${s.dim("(Ctrl+C to cancel)")}`);
@@ -44341,7 +44390,7 @@ async function logout(io) {
44341
44390
  }
44342
44391
 
44343
44392
  // dist/statusline.js
44344
- import { spawn as spawn3 } from "node:child_process";
44393
+ import { spawn as spawn4 } from "node:child_process";
44345
44394
  var TIMEOUT_MS2 = 2e3;
44346
44395
  var MAX_CHARS = 160;
44347
44396
  function runStatusLine(command, input2, cwd) {
@@ -44357,7 +44406,7 @@ function runStatusLine(command, input2, cwd) {
44357
44406
  };
44358
44407
  let child;
44359
44408
  try {
44360
- child = spawn3(command, { cwd, shell: true, windowsHide: true, stdio: ["pipe", "pipe", "ignore"] });
44409
+ child = spawn4(command, { cwd, shell: true, windowsHide: true, stdio: ["pipe", "pipe", "ignore"] });
44361
44410
  } catch {
44362
44411
  return end(null);
44363
44412
  }
@@ -44661,7 +44710,7 @@ function skillsText(skills) {
44661
44710
  }
44662
44711
 
44663
44712
  // dist/notify.js
44664
- import { spawn as spawn4 } from "node:child_process";
44713
+ import { spawn as spawn5 } from "node:child_process";
44665
44714
  import { join as join18 } from "node:path";
44666
44715
  var NOTIFY_AFTER_MS = 3e4;
44667
44716
  function notificationCommand(title, body, platform = process.platform) {
@@ -44691,7 +44740,7 @@ function notify(write2, title, body) {
44691
44740
  if (!cmd)
44692
44741
  return;
44693
44742
  try {
44694
- const child = spawn4(cmd[0], cmd.slice(1), { stdio: "ignore", detached: true, windowsHide: true });
44743
+ const child = spawn5(cmd[0], cmd.slice(1), { stdio: "ignore", detached: true, windowsHide: true });
44695
44744
  child.on("error", () => {
44696
44745
  });
44697
44746
  child.unref();
@@ -44759,7 +44808,7 @@ function header(s, opts) {
44759
44808
  const logo = logoRows(opts);
44760
44809
  const text2 = [
44761
44810
  `${s.bold("Nexo Code")} ${s.dim(`v${opts.version}`)}`,
44762
- s.dim(`cheap model first ${s.sym.dot} /strong for hard problems ${s.sym.dot} /help`),
44811
+ s.dim(`Auto picks the model ${s.sym.dot} /strong for hard problems ${s.sym.dot} /help`),
44763
44812
  s.dim(opts.root),
44764
44813
  ""
44765
44814
  ];
@@ -45212,8 +45261,9 @@ function ttyLineReader(s, color, stdin = process.stdin, out = process.stderr, op
45212
45261
  }
45213
45262
  draw();
45214
45263
  };
45264
+ const height = renderBox(s, state, columns(), status, "").lines.length;
45215
45265
  out.write(`
45216
- ${CSI}?2004h`);
45266
+ ${CSI}?2004h${CSI}999B${height > 1 ? `${CSI}${height - 1}A` : ""}\r`);
45217
45267
  out.on?.("resize", draw);
45218
45268
  stdin.setRawMode(true);
45219
45269
  stdin.resume();
@@ -45430,7 +45480,7 @@ async function chat(io, opts) {
45430
45480
  let team = null;
45431
45481
  if (!checks.length)
45432
45482
  io.err(s.dim(` Tip: add checks to ${PROJECT_FILE} so changes are tested.`));
45433
- const modelName = () => pinned?.name ?? tier;
45483
+ const modelName = () => pinned?.name ?? tierLabel(tier);
45434
45484
  let customStatus = null;
45435
45485
  const refreshStatus = async () => {
45436
45486
  const command = io.settings?.statusLine;
@@ -45495,7 +45545,7 @@ async function chat(io, opts) {
45495
45545
  if (io.settings?.statusLine)
45496
45546
  await refreshStatus();
45497
45547
  for (; ; ) {
45498
- const prefix = `${mode === "plan" ? s.warning("plan ") : ""}${pinned ? s.accent(`${pinned.name} `) : tier === "strong" ? s.accent("strong ") : ""}`;
45548
+ const prefix = `${mode === "plan" ? s.warning("plan ") : ""}${pinned ? s.accent(`${pinned.name} `) : tier === "strong" ? s.accent("Strong ") : ""}`;
45499
45549
  const line2 = await io.readLine(`
45500
45550
  ${prefix}${s.primary("\u203A")} `, statusLine(), () => {
45501
45551
  mode = nextMode(mode);
@@ -45537,7 +45587,7 @@ ${prefix}${s.primary("\u203A")} `, statusLine(), () => {
45537
45587
  if (command === "/strong") {
45538
45588
  pinned = null;
45539
45589
  tier = tier === "strong" ? "cheap" : "strong";
45540
- io.err(s.dim(`${s.sym.dot} Using the ${tier} model${tier === "strong" ? " (costs more; /strong again to switch back)" : ""}.`));
45590
+ io.err(s.dim(`${s.sym.dot} ${tier === "strong" ? "Using the Strong model (uses more of your plan; /strong again to go back to Auto)" : "Back to Auto: Nexo picks the model"}.`));
45541
45591
  continue;
45542
45592
  }
45543
45593
  if (verb === "/theme" || command === "/vim" || verb === "/statusline") {
@@ -46105,7 +46155,7 @@ ${answer}` : ""}`
46105
46155
  if (err instanceof NexoError && err.code === "RATE_LIMITED") {
46106
46156
  const quota = (err.retryAfterMs ?? 0) > QUOTA_WAIT_MS;
46107
46157
  const wait = err.retryAfterMs ? formatWait(err.retryAfterMs) : "a minute";
46108
- io.err(s.dim(quota ? ` The ${tier} model's usage limit is used up for ${wait}.${tier === "strong" ? " Type /strong to switch back to the cheap model." : ""}` : ` Wait ${wait} and send your message again.`));
46158
+ io.err(s.dim(quota ? ` Your ${tier === "strong" ? "Strong model " : ""}limit is used up for ${wait}.${tier === "strong" ? " Type /strong to go back to Auto." : ""}` : ` Wait ${wait} and send your message again.`));
46109
46159
  }
46110
46160
  if (!(err instanceof NexoError))
46111
46161
  throw err;
@@ -46477,7 +46527,7 @@ Usage:
46477
46527
  nexo remote Take tasks from the Nexo app (Code) in this folder: they run here, and
46478
46528
  every file change and command is approved on your phone
46479
46529
  nexo models The models you can put in a team (nexo run --team)
46480
- nexo login Sign in with your email and the code it receives (--token to paste a token)
46530
+ nexo login Sign in: approve in your browser (--email for a code by email, --token to paste one)
46481
46531
  nexo auth status Show login and this month's usage against your cap
46482
46532
  nexo logout Revoke the token and remove it from this machine
46483
46533
  nexo update Install the latest Nexo Code (npm install -g nexo-code@latest)
@@ -46580,6 +46630,7 @@ async function run2(argv, io) {
46580
46630
  output: { type: "string" },
46581
46631
  "api-url": { type: "string" },
46582
46632
  token: { type: "boolean" },
46633
+ email: { type: "boolean" },
46583
46634
  "no-color": { type: "boolean" },
46584
46635
  debug: { type: "boolean" }
46585
46636
  }
@@ -46672,7 +46723,7 @@ async function run2(argv, io) {
46672
46723
  });
46673
46724
  }
46674
46725
  if (cmd === "update") {
46675
- const npm = spawnSync2(process.platform === "win32" ? "npm.cmd" : "npm", ["install", "-g", `${PACKAGE}@latest`], { stdio: "inherit", shell: process.platform === "win32" });
46726
+ const npm = process.platform === "win32" ? spawnSync2(`npm.cmd install -g --prefer-online ${PACKAGE}@latest`, { stdio: "inherit", shell: true }) : spawnSync2("npm", ["install", "-g", "--prefer-online", `${PACKAGE}@latest`], { stdio: "inherit" });
46676
46727
  if (npm.error) {
46677
46728
  io.err(`${io.style.error(io.style.sym.fail)} Could not run npm. Install the update yourself: npm install -g ${PACKAGE}@latest`);
46678
46729
  return 1;
@@ -46693,8 +46744,10 @@ async function run2(argv, io) {
46693
46744
  }
46694
46745
  return remote(io, budget);
46695
46746
  }
46747
+ if (cmd === "login" && process.platform === "win32" && true)
46748
+ dropBlockedLauncher();
46696
46749
  if (cmd === "login")
46697
- return login(io, { ...values["api-url"] ? { apiUrl: values["api-url"] } : {}, ...values.token ? { token: true } : {} });
46750
+ return login(io, { ...values["api-url"] ? { apiUrl: values["api-url"] } : {}, ...values.token ? { token: true } : {}, ...values.email ? { email: true } : {} });
46698
46751
  if (cmd === "models")
46699
46752
  return listModels(io);
46700
46753
  if (cmd === "logout")
@@ -46776,7 +46829,7 @@ function dropBlockedLauncher() {
46776
46829
  const policy = spawnSync2("powershell.exe", ["-NoProfile", "-Command", "Get-ExecutionPolicy"], { encoding: "utf8", windowsHide: true }).stdout.trim();
46777
46830
  if (policy !== "Restricted" && policy !== "AllSigned")
46778
46831
  return;
46779
- const prefix = spawnSync2("npm.cmd", ["prefix", "-g"], { encoding: "utf8", shell: true, windowsHide: true }).stdout.trim();
46832
+ const prefix = spawnSync2("npm.cmd prefix -g", { encoding: "utf8", shell: true, windowsHide: true }).stdout.trim();
46780
46833
  if (prefix)
46781
46834
  rmSync(join19(prefix, "nexo.ps1"), { force: true });
46782
46835
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-code",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Nexo Code: an AI coding agent for your terminal. Sign in with your Nexo account.",
5
5
  "homepage": "https://usenexoai.com",
6
6
  "license": "UNLICENSED",