@lunaroute/cli 0.2.3 → 0.2.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/index.js +88 -12
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { Command, InvalidArgumentError, Option } from "commander";
5
5
 
6
6
  // package.json
7
- var version = "0.2.3";
7
+ var version = "0.2.4";
8
8
 
9
9
  // src/login.ts
10
10
  import { createServer } from "http";
@@ -359,10 +359,16 @@ function logout(profile) {
359
359
  }
360
360
 
361
361
  // src/catalog.ts
362
- async function fetchModels(routingUrl) {
362
+ async function fetchModels(routingUrl, key) {
363
363
  const url = `${routingUrl}/v1/models`;
364
- const res = await fetch(url, { method: "GET" });
364
+ const headers = key ? { Authorization: `Bearer ${key}` } : {};
365
+ const res = await fetch(url, { method: "GET", headers });
365
366
  if (!res.ok) {
367
+ if (res.status === 401) {
368
+ throw new Error(
369
+ key ? `the routing service rejected that key (HTTP 401) \u2014 check that you copied the whole lr_ key` : `the routing service requires a key for /v1/models (HTTP 401) \u2014 run "lunaroute login" or pass --key`
370
+ );
371
+ }
366
372
  throw new Error(`failed to fetch models: HTTP ${res.status} from ${url}`);
367
373
  }
368
374
  const body = await res.json();
@@ -922,8 +928,25 @@ async function runSetup(harness, opts, deps = {
922
928
  );
923
929
  return 1;
924
930
  }
925
- const stored = loadProfile(opts.profile);
931
+ let stored = loadProfile(opts.profile);
926
932
  const settings = resolveSettings(opts.profile);
933
+ if (opts.key) {
934
+ if (!LR_KEY_RE.test(opts.key)) {
935
+ console.error(
936
+ "Invalid key: a LunaRoute routing key starts with lr_ (copy the whole key from the dashboard)."
937
+ );
938
+ return 1;
939
+ }
940
+ saveProfile(opts.profile, {
941
+ api_url: settings.api_url,
942
+ routing_url: opts.routingUrl || settings.routing_url,
943
+ front_url: settings.front_url,
944
+ org_id: stored?.org_id ?? "",
945
+ user_email: stored?.user_email ?? "",
946
+ routing_key: opts.key
947
+ });
948
+ stored = loadProfile(opts.profile);
949
+ }
927
950
  const routingUrlRaw = opts.routingUrl || stored?.routing_url || settings.routing_url;
928
951
  if (!routingUrlRaw) {
929
952
  console.error("No routing URL in profile; pass --routing-url.");
@@ -949,10 +972,12 @@ async function runSetup(harness, opts, deps = {
949
972
  }
950
973
  let models;
951
974
  try {
952
- models = await fetchModels(routingUrl);
975
+ models = await fetchModels(routingUrl, creds.routing_key);
953
976
  } catch (err) {
954
- console.error(`Could not fetch the model catalog: ${err instanceof Error ? err.message : err}`);
955
- return 1;
977
+ console.error(
978
+ `Could not fetch the model catalog (${err instanceof Error ? err.message : err}) \u2014 continuing; model ids show as <model> until it answers.`
979
+ );
980
+ models = [];
956
981
  }
957
982
  const ctx = {
958
983
  routingUrl,
@@ -981,6 +1006,7 @@ Wrote: ${summary.written.join(", ")}`);
981
1006
  }
982
1007
  }
983
1008
  var NOT_LOGGED_IN = 'Not logged in. Run "lunaroute login" first.';
1009
+ var LR_KEY_RE = /^lr_[A-Za-z0-9_-]{8,128}$/;
984
1010
  var OPENCODE_LOGIN_HINT = "Or re-run and choose the extension \u2014 it logs in via /connect inside opencode.";
985
1011
  var PI_LOGIN_HINT = "Or re-run and choose the extension \u2014 login happens via /login lunaroute inside pi.";
986
1012
  async function requireCreds(opts, deps, stored, hint) {
@@ -1014,6 +1040,38 @@ async function requireCreds(opts, deps, stored, hint) {
1014
1040
  }
1015
1041
  async function runPiSetup(opts, stored, routingUrl, deps) {
1016
1042
  try {
1043
+ if (opts.key) {
1044
+ const creds2 = await requireCreds(opts, deps, stored);
1045
+ if (!creds2) return 1;
1046
+ let models = [];
1047
+ let modelsSynced = true;
1048
+ try {
1049
+ models = await fetchModels(routingUrl, creds2.routing_key);
1050
+ } catch (err) {
1051
+ modelsSynced = false;
1052
+ console.warn(
1053
+ `Note: ${err instanceof Error ? err.message : err}`
1054
+ );
1055
+ console.warn(
1056
+ "Writing the provider block with no models. Your organization has no models enabled yet \u2014 contact hello@lunaroute.com to get them turned on, then re-run this command to sync them."
1057
+ );
1058
+ }
1059
+ const plan = buildPlan2({
1060
+ routingUrl,
1061
+ orgId: creds2.org_id,
1062
+ models,
1063
+ keyEnvVar: "LUNAROUTE_API_KEY"
1064
+ });
1065
+ const write = await deps.confirm(
1066
+ "Merge LunaRoute provider into ~/.pi/agent/models.json (backup kept, other providers preserved)?",
1067
+ { yes: opts.yes }
1068
+ );
1069
+ const code = await applyAndReport(plan, creds2.routing_key, write);
1070
+ if (code === 0 && modelsSynced) {
1071
+ console.log("\nDone. Start pi \u2014 it is signed in. Pick a model with /model.");
1072
+ }
1073
+ return code;
1074
+ }
1017
1075
  if (opts.extension) {
1018
1076
  const ok = await deps.confirm(
1019
1077
  "Install the LunaRoute Pi extension + MCP adapter via 'pi install'?",
@@ -1178,7 +1236,7 @@ async function runOpencodeSetup(stored, settings, routingUrl, opts, deps) {
1178
1236
  async function applyOpencodeProvider(creds, routingUrl, write) {
1179
1237
  let models;
1180
1238
  try {
1181
- models = await fetchModels(routingUrl);
1239
+ models = await fetchModels(routingUrl, creds.routing_key);
1182
1240
  } catch (err) {
1183
1241
  console.error(`Could not fetch the model catalog: ${err instanceof Error ? err.message : err}`);
1184
1242
  return 1;
@@ -1275,7 +1333,7 @@ async function launchPi(spawn) {
1275
1333
  async function applyPiModels(creds, routingUrl, write) {
1276
1334
  let models;
1277
1335
  try {
1278
- models = await fetchModels(routingUrl);
1336
+ models = await fetchModels(routingUrl, creds.routing_key);
1279
1337
  } catch (err) {
1280
1338
  console.error(`Could not fetch the model catalog: ${err instanceof Error ? err.message : err}`);
1281
1339
  return 1;
@@ -1316,6 +1374,8 @@ async function runModels(profile, opts) {
1316
1374
  }
1317
1375
  if (opts.json) {
1318
1376
  console.log(JSON.stringify(ids, null, 2));
1377
+ } else if (ids.length === 0) {
1378
+ console.log("// no models enabled for your org yet \u2014 contact hello@lunaroute.com to get models enabled");
1319
1379
  } else {
1320
1380
  for (const id of ids) console.log(id);
1321
1381
  }
@@ -1350,6 +1410,10 @@ async function runPricing(profile, opts) {
1350
1410
  console.log(JSON.stringify(rows, null, 2));
1351
1411
  return 0;
1352
1412
  }
1413
+ if (rows.length === 0) {
1414
+ console.log("// no models enabled for your org yet \u2014 contact hello@lunaroute.com to get models enabled");
1415
+ return 0;
1416
+ }
1353
1417
  const cell = (n) => n === void 0 ? "\u2014" : String(n);
1354
1418
  const table = renderTable(
1355
1419
  ["MODEL", "INPUT (cr/M)", "OUTPUT (cr/M)", "CACHED (cr/M)"],
@@ -1384,6 +1448,9 @@ async function runUsage(profile, opts) {
1384
1448
  console.log(
1385
1449
  `Wallet: available ${w.available_credits} (balance ${w.balance_credits}, reserved ${w.reserved_credits})`
1386
1450
  );
1451
+ if (w.available_credits === 0) {
1452
+ console.log("// wallet is empty \u2014 add credits in the dashboard \u2192 Billing");
1453
+ }
1387
1454
  console.log("");
1388
1455
  const table = renderTable(
1389
1456
  ["TIME", "MODEL", "IN", "OUT", "CACHED", "\u0394CREDITS", "BALANCE"],
@@ -1872,7 +1939,7 @@ async function runRun(harness, opts, deps = realDeps) {
1872
1939
  if (!model) {
1873
1940
  let models;
1874
1941
  try {
1875
- models = await deps.fetchModels(creds.routing_url);
1942
+ models = await deps.fetchModels(creds.routing_url, creds.routing_key);
1876
1943
  } catch (err) {
1877
1944
  console.error(`Could not fetch the model catalog: ${err instanceof Error ? err.message : err}`);
1878
1945
  return 1;
@@ -1905,6 +1972,14 @@ async function runRun(harness, opts, deps = realDeps) {
1905
1972
  }
1906
1973
 
1907
1974
  // src/index.ts
1975
+ var MIN_NODE_MAJOR = 20;
1976
+ var nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
1977
+ if (nodeMajor < MIN_NODE_MAJOR) {
1978
+ console.error(
1979
+ `LunaRoute CLI requires Node.js ${MIN_NODE_MAJOR}+ \u2014 you're running ${process.versions.node}. Upgrade from https://nodejs.org/en/download and try again.`
1980
+ );
1981
+ process.exit(1);
1982
+ }
1908
1983
  var program = new Command();
1909
1984
  program.name("lunaroute").description("LunaRoute CLI \u2014 configure coding harnesses and manage your account.").version(version).option("-p, --profile <name>", "credential profile to use", "default");
1910
1985
  program.command("login").description("Authorize this device via the browser and store a routing key.").action(async () => {
@@ -1916,14 +1991,15 @@ program.command("whoami").description("Show the signed-in user and organization.
1916
1991
  program.command("logout").description("Remove stored credentials for the active profile.").action(() => {
1917
1992
  logout(program.opts().profile);
1918
1993
  });
1919
- program.command("setup <harness>").description("Configure a coding harness (opencode | pi | hermes | claude-code | copilot-cli | openclaw | generic).").option("--print", "print the config instead of writing files", false).option("--routing-url <url>", "override the routing base URL").option("--yes", "accept all confirmation prompts without a TTY (for scripts)", false).option("--extension", "pi only: jump straight to installing the Pi extension + MCP adapter").option("--models", "pi only: jump straight to writing the models.json provider block").action(async (harness, opts) => {
1994
+ program.command("setup <harness>").description("Configure a coding harness (opencode | pi | hermes | claude-code | copilot-cli | openclaw | generic).").option("--print", "print the config instead of writing files", false).option("--routing-url <url>", "override the routing base URL").option("--yes", "accept all confirmation prompts without a TTY (for scripts)", false).option("--extension", "pi only: jump straight to installing the Pi extension + MCP adapter").option("--models", "pi only: jump straight to writing the models.json provider block").option("--key <key>", "a LunaRoute routing key (lr_\u2026), e.g. from the dashboard's onboarding page \u2014 stores it and skips the browser login").action(async (harness, opts) => {
1920
1995
  const code = await runSetup(harness, {
1921
1996
  profile: program.opts().profile,
1922
1997
  print: opts.print,
1923
1998
  routingUrl: opts.routingUrl,
1924
1999
  yes: opts.yes,
1925
2000
  extension: opts.extension,
1926
- models: opts.models
2001
+ models: opts.models,
2002
+ key: opts.key
1927
2003
  });
1928
2004
  if (code !== 0) process.exit(code);
1929
2005
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunaroute/cli",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "LunaRoute CLI — configure coding harnesses and manage your LunaRoute account.",
5
5
  "repository": {
6
6
  "type": "git",