@lunaroute/cli 0.2.4 → 0.2.5
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.
- package/README.md +3 -1
- package/dist/index.js +180 -65
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,9 @@ Requires Node.js 20+.
|
|
|
18
18
|
lunaroute setup pi # or: opencode | claude-code | copilot-cli | generic
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
One command from a fresh machine: `setup` asks before touching anything — for `pi` it first offers to install the LunaRoute Pi extension
|
|
21
|
+
One command from a fresh machine: `setup` asks before touching anything — for `pi` it first offers to install the LunaRoute Pi extension (recommended — models then auto-sync, and you sign in with `/login lunaroute` inside pi), then offers to start pi for you. `opencode` works the same way with its extension (`/connect` to sign in). Decline an extension — or pick a harness whose config needs a key — and `setup` offers to run `lunaroute login` right there (it opens your browser) before writing. The fallback config finishes with an `export LUNAROUTE_API_KEY=…` line — run it in your shell, then start your agent and it routes through LunaRoute.
|
|
22
|
+
|
|
23
|
+
Already have a key from the dashboard? `lunaroute setup pi --key lr_… --yes` is the whole onboarding: it checks the key against the catalog, installs the Pi extension, and stores the key in `~/.pi/agent/auth.json`, so pi starts signed in with models syncing from `/v1/models`. No models.json edit and no shell export. A stale `providers.lunaroute` block from an older CLI is removed in the same run (backup kept).
|
|
22
24
|
|
|
23
25
|
Prefer a script? `setup pi --extension` / `--models` pick a flow, `--yes` accepts all prompts, and `--print` previews without writing.
|
|
24
26
|
|
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.
|
|
7
|
+
var version = "0.2.5";
|
|
8
8
|
|
|
9
9
|
// src/login.ts
|
|
10
10
|
import { createServer } from "http";
|
|
@@ -359,13 +359,26 @@ function logout(profile) {
|
|
|
359
359
|
}
|
|
360
360
|
|
|
361
361
|
// src/catalog.ts
|
|
362
|
+
var KeyRejectedError = class extends Error {
|
|
363
|
+
};
|
|
364
|
+
var NON_CHAT_CAPABILITIES = [
|
|
365
|
+
"image_generation",
|
|
366
|
+
"embeddings",
|
|
367
|
+
"rerank"
|
|
368
|
+
];
|
|
369
|
+
function isChatModel(m) {
|
|
370
|
+
for (const c of NON_CHAT_CAPABILITIES) {
|
|
371
|
+
if (m.capabilities?.[c] === true) return false;
|
|
372
|
+
}
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
362
375
|
async function fetchModels(routingUrl, key) {
|
|
363
376
|
const url = `${routingUrl}/v1/models`;
|
|
364
377
|
const headers = key ? { Authorization: `Bearer ${key}` } : {};
|
|
365
378
|
const res = await fetch(url, { method: "GET", headers });
|
|
366
379
|
if (!res.ok) {
|
|
367
380
|
if (res.status === 401) {
|
|
368
|
-
throw new
|
|
381
|
+
throw new KeyRejectedError(
|
|
369
382
|
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
383
|
);
|
|
371
384
|
}
|
|
@@ -379,9 +392,9 @@ async function fetchModels(routingUrl, key) {
|
|
|
379
392
|
max_output_tokens: m.max_output_tokens,
|
|
380
393
|
capabilities: m.capabilities,
|
|
381
394
|
client_compat: m.client_compat
|
|
382
|
-
})).filter((m) => m.id.length > 0);
|
|
395
|
+
})).filter((m) => m.id.length > 0 && isChatModel(m));
|
|
383
396
|
if (models.length === 0) {
|
|
384
|
-
throw new Error(`no models available from ${url}`);
|
|
397
|
+
throw new Error(`no chat models available from ${url}`);
|
|
385
398
|
}
|
|
386
399
|
return models.sort((a, b) => a.id.localeCompare(b.id));
|
|
387
400
|
}
|
|
@@ -584,6 +597,9 @@ function openclawConfigPath() {
|
|
|
584
597
|
function piModelsPath() {
|
|
585
598
|
return join2(homedir2(), ".pi", "agent", "models.json");
|
|
586
599
|
}
|
|
600
|
+
function piAuthPath() {
|
|
601
|
+
return join2(homedir2(), ".pi", "agent", "auth.json");
|
|
602
|
+
}
|
|
587
603
|
var LUNAROUTE_SKILL_NAME = "lunaroute-memory";
|
|
588
604
|
function claudeUserConfigPath() {
|
|
589
605
|
return join2(homedir2(), ".claude.json");
|
|
@@ -694,15 +710,17 @@ function providerPlan(ctx) {
|
|
|
694
710
|
}
|
|
695
711
|
|
|
696
712
|
// src/setup/adapters/pi.ts
|
|
713
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
697
714
|
function buildPiModelEntry(m) {
|
|
715
|
+
if (!m.context_window_tokens || !m.max_output_tokens) return null;
|
|
698
716
|
const reasoning = m.capabilities?.reasoning === true;
|
|
699
717
|
const entry = {
|
|
700
718
|
id: m.id,
|
|
701
719
|
name: m.display_name ?? m.id,
|
|
702
720
|
reasoning,
|
|
703
721
|
input: m.capabilities?.vision ? ["text", "image"] : ["text"],
|
|
704
|
-
contextWindow: m.context_window_tokens
|
|
705
|
-
maxTokens: m.max_output_tokens
|
|
722
|
+
contextWindow: m.context_window_tokens,
|
|
723
|
+
maxTokens: m.max_output_tokens
|
|
706
724
|
};
|
|
707
725
|
if (!reasoning) return entry;
|
|
708
726
|
const pi = m.client_compat?.pi;
|
|
@@ -718,13 +736,32 @@ function buildPiModelEntry(m) {
|
|
|
718
736
|
return entry;
|
|
719
737
|
}
|
|
720
738
|
function buildPlan2(ctx) {
|
|
739
|
+
const skipped = [];
|
|
740
|
+
const models = [];
|
|
741
|
+
for (const m of ctx.models) {
|
|
742
|
+
const entry = buildPiModelEntry(m);
|
|
743
|
+
if (entry) models.push(entry);
|
|
744
|
+
else skipped.push(m.id);
|
|
745
|
+
}
|
|
746
|
+
if (models.length === 0) {
|
|
747
|
+
throw new Error(
|
|
748
|
+
`no LunaRoute model has a context window in the catalog${skipped.length ? ` (skipped: ${skipped.join(", ")})` : ""}`
|
|
749
|
+
);
|
|
750
|
+
}
|
|
721
751
|
const block = {
|
|
722
752
|
baseUrl: `${ctx.routingUrl}/v1`,
|
|
723
753
|
api: "openai-completions",
|
|
724
754
|
// Real key; rides the native Authorization: Bearer header.
|
|
725
755
|
apiKey: `$${ctx.keyEnvVar}`,
|
|
726
|
-
models
|
|
756
|
+
models
|
|
727
757
|
};
|
|
758
|
+
const notes = [
|
|
759
|
+
"\npi: open /model to pick a LunaRoute model (models.json hot-reloads).",
|
|
760
|
+
"Tip: the lunaroute-pi-extension auto-registers models from /v1/models so you can skip this file \u2014 `pi install npm:@lunaroute/pi-extension` then `/login lunaroute`."
|
|
761
|
+
];
|
|
762
|
+
if (skipped.length > 0) {
|
|
763
|
+
notes.push(`Skipped (no context window in the catalog): ${skipped.join(", ")}`);
|
|
764
|
+
}
|
|
728
765
|
return {
|
|
729
766
|
fileWrites: [
|
|
730
767
|
{
|
|
@@ -739,12 +776,60 @@ function buildPlan2(ctx) {
|
|
|
739
776
|
}
|
|
740
777
|
],
|
|
741
778
|
exports: [{ name: ctx.keyEnvVar, value: "__KEY__" }],
|
|
779
|
+
notes
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
function stalePiProviderExists() {
|
|
783
|
+
try {
|
|
784
|
+
const obj = JSON.parse(readFileSync3(piModelsPath(), "utf8"));
|
|
785
|
+
return Boolean(obj.providers?.lunaroute);
|
|
786
|
+
} catch {
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
function buildStaleProviderCleanupPlan() {
|
|
791
|
+
return {
|
|
792
|
+
fileWrites: [
|
|
793
|
+
{
|
|
794
|
+
kind: "json",
|
|
795
|
+
path: piModelsPath(),
|
|
796
|
+
merge: (existing) => {
|
|
797
|
+
const obj = existing ?? {};
|
|
798
|
+
const providers = { ...obj.providers };
|
|
799
|
+
delete providers.lunaroute;
|
|
800
|
+
const next = { ...obj };
|
|
801
|
+
if (Object.keys(providers).length > 0) next.providers = providers;
|
|
802
|
+
else delete next.providers;
|
|
803
|
+
return next;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
],
|
|
807
|
+
exports: [],
|
|
742
808
|
notes: [
|
|
743
|
-
"
|
|
744
|
-
"Tip: the lunaroute-pi-extension auto-registers models from /v1/models so you can skip this file \u2014 `pi install npm:@lunaroute/pi-extension` then `/login lunaroute`."
|
|
809
|
+
"pi: removed the stale providers.lunaroute block from ~/.pi/agent/models.json (backup kept) \u2014 the extension syncs models live."
|
|
745
810
|
]
|
|
746
811
|
};
|
|
747
812
|
}
|
|
813
|
+
var KEY_EXPIRES_MS = 10 * 365 * 24 * 60 * 60 * 1e3;
|
|
814
|
+
function buildKeyPlan(key) {
|
|
815
|
+
return {
|
|
816
|
+
fileWrites: [
|
|
817
|
+
{
|
|
818
|
+
kind: "json",
|
|
819
|
+
path: piAuthPath(),
|
|
820
|
+
merge: (existing) => {
|
|
821
|
+
const obj = existing ?? {};
|
|
822
|
+
return {
|
|
823
|
+
...obj,
|
|
824
|
+
lunaroute: { type: "oauth", access: key, refresh: "", expires: Date.now() + KEY_EXPIRES_MS }
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
],
|
|
829
|
+
exports: [],
|
|
830
|
+
notes: ["pi: key stored in ~/.pi/agent/auth.json \u2014 start pi, then /model to pick a lunaroute/* model."]
|
|
831
|
+
};
|
|
832
|
+
}
|
|
748
833
|
|
|
749
834
|
// src/setup/adapters/claudeCode.ts
|
|
750
835
|
function buildPlan3(ctx) {
|
|
@@ -911,7 +996,8 @@ var ADAPTERS = {
|
|
|
911
996
|
"copilot-cli": buildPlan4,
|
|
912
997
|
generic: buildPlan5
|
|
913
998
|
};
|
|
914
|
-
var PI_INSTALL_PACKAGES = ["npm:@lunaroute/pi-extension"
|
|
999
|
+
var PI_INSTALL_PACKAGES = ["npm:@lunaroute/pi-extension"];
|
|
1000
|
+
var PI_INSTALL_HINT = "Install pi first: npm install -g --ignore-scripts @earendil-works/pi-coding-agent (https://pi.dev)";
|
|
915
1001
|
async function runSetup(harness, opts, deps = {
|
|
916
1002
|
confirm,
|
|
917
1003
|
spawn: makeRealSpawn(),
|
|
@@ -921,6 +1007,10 @@ async function runSetup(harness, opts, deps = {
|
|
|
921
1007
|
console.error("Choose one of --extension or --models.");
|
|
922
1008
|
return 1;
|
|
923
1009
|
}
|
|
1010
|
+
if (opts.key && (opts.extension || opts.models)) {
|
|
1011
|
+
console.error("--key runs the full pi flow; drop --extension/--models.");
|
|
1012
|
+
return 1;
|
|
1013
|
+
}
|
|
924
1014
|
const adapter = ADAPTERS[harness];
|
|
925
1015
|
if (!adapter && harness !== "hermes") {
|
|
926
1016
|
console.error(
|
|
@@ -930,22 +1020,11 @@ async function runSetup(harness, opts, deps = {
|
|
|
930
1020
|
}
|
|
931
1021
|
let stored = loadProfile(opts.profile);
|
|
932
1022
|
const settings = resolveSettings(opts.profile);
|
|
933
|
-
if (opts.key) {
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
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);
|
|
1023
|
+
if (opts.key && !LR_KEY_RE.test(opts.key)) {
|
|
1024
|
+
console.error(
|
|
1025
|
+
"Invalid key: a LunaRoute routing key starts with lr_ (copy the whole key from the dashboard)."
|
|
1026
|
+
);
|
|
1027
|
+
return 1;
|
|
949
1028
|
}
|
|
950
1029
|
const routingUrlRaw = opts.routingUrl || stored?.routing_url || settings.routing_url;
|
|
951
1030
|
if (!routingUrlRaw) {
|
|
@@ -959,6 +1038,29 @@ async function runSetup(harness, opts, deps = {
|
|
|
959
1038
|
console.error(err instanceof Error ? err.message : err);
|
|
960
1039
|
return 1;
|
|
961
1040
|
}
|
|
1041
|
+
if (opts.key) {
|
|
1042
|
+
try {
|
|
1043
|
+
await fetchModels(routingUrl, opts.key);
|
|
1044
|
+
} catch (err) {
|
|
1045
|
+
if (err instanceof KeyRejectedError) {
|
|
1046
|
+
console.error(err.message);
|
|
1047
|
+
return 1;
|
|
1048
|
+
}
|
|
1049
|
+
console.warn(`Note: ${err instanceof Error ? err.message : err}`);
|
|
1050
|
+
console.warn(
|
|
1051
|
+
"Continuing without a synced catalog. If your organization has no chat models enabled yet, contact hello@lunaroute.com \u2014 models appear on the next refresh once they are on."
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
saveProfile(opts.profile, {
|
|
1055
|
+
api_url: settings.api_url,
|
|
1056
|
+
routing_url: opts.routingUrl || settings.routing_url,
|
|
1057
|
+
front_url: settings.front_url,
|
|
1058
|
+
org_id: stored?.org_id ?? "",
|
|
1059
|
+
user_email: stored?.user_email ?? "",
|
|
1060
|
+
routing_key: opts.key
|
|
1061
|
+
});
|
|
1062
|
+
stored = loadProfile(opts.profile);
|
|
1063
|
+
}
|
|
962
1064
|
if (harness === "pi" && !opts.print) {
|
|
963
1065
|
return runPiSetup(opts, stored, routingUrl, deps);
|
|
964
1066
|
}
|
|
@@ -987,7 +1089,7 @@ async function runSetup(harness, opts, deps = {
|
|
|
987
1089
|
};
|
|
988
1090
|
let plan;
|
|
989
1091
|
try {
|
|
990
|
-
plan = adapter(ctx);
|
|
1092
|
+
plan = harness === "pi" && opts.key ? buildKeyPlan("<your-api-key>") : adapter(ctx);
|
|
991
1093
|
} catch (err) {
|
|
992
1094
|
console.error(err instanceof Error ? err.message : err);
|
|
993
1095
|
return 1;
|
|
@@ -1043,38 +1145,36 @@ async function runPiSetup(opts, stored, routingUrl, deps) {
|
|
|
1043
1145
|
if (opts.key) {
|
|
1044
1146
|
const creds2 = await requireCreds(opts, deps, stored);
|
|
1045
1147
|
if (!creds2) return 1;
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
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."
|
|
1148
|
+
const install = await deps.confirm(
|
|
1149
|
+
"Install the LunaRoute Pi extension via 'pi install'?",
|
|
1150
|
+
{ yes: opts.yes }
|
|
1151
|
+
);
|
|
1152
|
+
if (!install) {
|
|
1153
|
+
return applyPiModels(
|
|
1154
|
+
creds2,
|
|
1155
|
+
routingUrl,
|
|
1156
|
+
await deps.confirm("Merge LunaRoute provider into ~/.pi/agent/models.json (backup kept, other providers preserved)?", { yes: opts.yes })
|
|
1057
1157
|
);
|
|
1058
1158
|
}
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
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)?",
|
|
1159
|
+
const code = await installPiPackages(deps);
|
|
1160
|
+
if (code !== 0) return code;
|
|
1161
|
+
const store = await deps.confirm(
|
|
1162
|
+
"Store the key in ~/.pi/agent/auth.json (replaces any existing LunaRoute login; other credentials preserved)?",
|
|
1067
1163
|
{ yes: opts.yes }
|
|
1068
1164
|
);
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1165
|
+
if (!store) {
|
|
1166
|
+
console.log("\nSkipped. Inside pi, run /login lunaroute to sign in.");
|
|
1167
|
+
return 0;
|
|
1072
1168
|
}
|
|
1073
|
-
|
|
1169
|
+
const plan = combinePlans(buildKeyPlan(creds2.routing_key), stalePiProviderExists() ? buildStaleProviderCleanupPlan() : null);
|
|
1170
|
+
const wcode = await applyAndReport(plan, creds2.routing_key, true);
|
|
1171
|
+
if (wcode !== 0) return wcode;
|
|
1172
|
+
console.log("\nDone. Start pi \u2014 it is signed in. Pick a model with /model.");
|
|
1173
|
+
return 0;
|
|
1074
1174
|
}
|
|
1075
1175
|
if (opts.extension) {
|
|
1076
1176
|
const ok = await deps.confirm(
|
|
1077
|
-
"Install the LunaRoute Pi extension
|
|
1177
|
+
"Install the LunaRoute Pi extension via 'pi install'?",
|
|
1078
1178
|
{ yes: opts.yes }
|
|
1079
1179
|
);
|
|
1080
1180
|
if (!ok) {
|
|
@@ -1094,7 +1194,7 @@ async function runPiSetup(opts, stored, routingUrl, deps) {
|
|
|
1094
1194
|
);
|
|
1095
1195
|
}
|
|
1096
1196
|
if (await deps.confirm(
|
|
1097
|
-
"Install the LunaRoute Pi extension
|
|
1197
|
+
"Install the LunaRoute Pi extension via 'pi install' (recommended \u2014 auto-registers models)?",
|
|
1098
1198
|
{ yes: opts.yes }
|
|
1099
1199
|
)) {
|
|
1100
1200
|
return installPiExtension(deps, opts);
|
|
@@ -1249,6 +1349,14 @@ async function applyOpencodeProvider(creds, routingUrl, write) {
|
|
|
1249
1349
|
});
|
|
1250
1350
|
return applyAndReport(plan, creds.routing_key, write);
|
|
1251
1351
|
}
|
|
1352
|
+
function combinePlans(a, b) {
|
|
1353
|
+
if (!b) return a;
|
|
1354
|
+
return {
|
|
1355
|
+
fileWrites: [...a.fileWrites, ...b.fileWrites],
|
|
1356
|
+
exports: [...a.exports, ...b.exports],
|
|
1357
|
+
notes: [...a.notes, ...b.notes]
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1252
1360
|
async function applyAndReport(plan, key, write) {
|
|
1253
1361
|
try {
|
|
1254
1362
|
const summary = await applyPlan(plan, { print: !write, key });
|
|
@@ -1280,16 +1388,12 @@ async function launchOpencode(spawn) {
|
|
|
1280
1388
|
});
|
|
1281
1389
|
});
|
|
1282
1390
|
}
|
|
1283
|
-
async function
|
|
1391
|
+
async function installPiPackages(deps) {
|
|
1284
1392
|
for (const pkg of PI_INSTALL_PACKAGES) {
|
|
1285
1393
|
const code = await new Promise((resolve) => {
|
|
1286
1394
|
const child = deps.spawn("pi", ["install", pkg]);
|
|
1287
1395
|
child.on("error", (err) => {
|
|
1288
|
-
const { message, exitCode } = describeSpawnError(
|
|
1289
|
-
"pi",
|
|
1290
|
-
"Install pi first: https://pi.dev",
|
|
1291
|
-
err
|
|
1292
|
-
);
|
|
1396
|
+
const { message, exitCode } = describeSpawnError("pi", PI_INSTALL_HINT, err);
|
|
1293
1397
|
console.error(message);
|
|
1294
1398
|
resolve(exitCode);
|
|
1295
1399
|
});
|
|
@@ -1300,11 +1404,26 @@ async function installPiExtension(deps, opts) {
|
|
|
1300
1404
|
if (code !== 0) {
|
|
1301
1405
|
const done = PI_INSTALL_PACKAGES.slice(0, PI_INSTALL_PACKAGES.indexOf(pkg));
|
|
1302
1406
|
console.error(
|
|
1303
|
-
`'pi install ${pkg}' failed (exit ${code}). Installed so far: ${done.length ? done.join(", ") : "nothing"}. Re-run
|
|
1407
|
+
`'pi install ${pkg}' failed (exit ${code}). Installed so far: ${done.length ? done.join(", ") : "nothing"}. Re-run the same command to retry.`
|
|
1304
1408
|
);
|
|
1305
1409
|
return code || 1;
|
|
1306
1410
|
}
|
|
1307
1411
|
}
|
|
1412
|
+
return 0;
|
|
1413
|
+
}
|
|
1414
|
+
async function installPiExtension(deps, opts) {
|
|
1415
|
+
const code = await installPiPackages(deps);
|
|
1416
|
+
if (code !== 0) return code;
|
|
1417
|
+
if (stalePiProviderExists()) {
|
|
1418
|
+
const remove = await deps.confirm(
|
|
1419
|
+
"Remove the stale LunaRoute block from ~/.pi/agent/models.json (backup kept, other providers preserved)?",
|
|
1420
|
+
{ yes: opts.yes }
|
|
1421
|
+
);
|
|
1422
|
+
if (remove) {
|
|
1423
|
+
const ccode = await applyAndReport(buildStaleProviderCleanupPlan(), "", true);
|
|
1424
|
+
if (ccode !== 0) return ccode;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1308
1427
|
if (await deps.confirm("Start pi now? (then run /login lunaroute to sign in)", { yes: opts.yes })) {
|
|
1309
1428
|
return launchPi(deps.spawn);
|
|
1310
1429
|
}
|
|
@@ -1317,11 +1436,7 @@ async function launchPi(spawn) {
|
|
|
1317
1436
|
return new Promise((resolve) => {
|
|
1318
1437
|
const child = spawn("pi", []);
|
|
1319
1438
|
child.on("error", (err) => {
|
|
1320
|
-
const { message, exitCode } = describeSpawnError(
|
|
1321
|
-
"pi",
|
|
1322
|
-
"Install pi first: https://pi.dev",
|
|
1323
|
-
err
|
|
1324
|
-
);
|
|
1439
|
+
const { message, exitCode } = describeSpawnError("pi", PI_INSTALL_HINT, err);
|
|
1325
1440
|
console.error(message);
|
|
1326
1441
|
resolve(exitCode);
|
|
1327
1442
|
});
|
|
@@ -1991,7 +2106,7 @@ program.command("whoami").description("Show the signed-in user and organization.
|
|
|
1991
2106
|
program.command("logout").description("Remove stored credentials for the active profile.").action(() => {
|
|
1992
2107
|
logout(program.opts().profile);
|
|
1993
2108
|
});
|
|
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
|
|
2109
|
+
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").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) => {
|
|
1995
2110
|
const code = await runSetup(harness, {
|
|
1996
2111
|
profile: program.opts().profile,
|
|
1997
2112
|
print: opts.print,
|