@leuria/cli 0.1.2 → 0.1.3
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/dist/{chunk-JOCM56MT.js → chunk-JBVFUXX5.js} +185 -45
- package/dist/cli.js +5 -2
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -654,13 +654,13 @@ function compareVersions(a, b) {
|
|
|
654
654
|
}
|
|
655
655
|
|
|
656
656
|
// src/version.ts
|
|
657
|
-
var VERSION = true ? "0.1.
|
|
657
|
+
var VERSION = true ? "0.1.3" : "0.0.0-dev";
|
|
658
658
|
|
|
659
659
|
// src/auth.ts
|
|
660
660
|
import crossSpawn3 from "cross-spawn";
|
|
661
|
-
import { mkdtempSync as
|
|
662
|
-
import { tmpdir as
|
|
663
|
-
import { join as
|
|
661
|
+
import { mkdtempSync as mkdtempSync3 } from "fs";
|
|
662
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
663
|
+
import { join as join6 } from "path";
|
|
664
664
|
|
|
665
665
|
// src/acp/acp-client.ts
|
|
666
666
|
import { spawn } from "child_process";
|
|
@@ -710,6 +710,9 @@ var AcpLiveSession = class {
|
|
|
710
710
|
imagesSupported = false;
|
|
711
711
|
/** `initialize` result, once connected. */
|
|
712
712
|
info = null;
|
|
713
|
+
/** The sign-in the agent says it uses, from `_auth/status_update` (Claude): "account", "api_key", "none"… */
|
|
714
|
+
authKind;
|
|
715
|
+
authWaiters = [];
|
|
713
716
|
/** Models the agent offers, from `session/new` (and later updates). */
|
|
714
717
|
models = null;
|
|
715
718
|
get isAlive() {
|
|
@@ -778,6 +781,27 @@ var AcpLiveSession = class {
|
|
|
778
781
|
return { error: error instanceof Error ? error.message : String(error) };
|
|
779
782
|
}
|
|
780
783
|
}
|
|
784
|
+
/**
|
|
785
|
+
* The sign-in the agent reports (see `authKind`), waiting up to `ms` for its first
|
|
786
|
+
* report. Undefined when the agent doesn't report one, or not in time. Some agents
|
|
787
|
+
* (Claude) open a session while signed out and refuse only the first prompt, so a
|
|
788
|
+
* session alone doesn't prove a sign-in.
|
|
789
|
+
*/
|
|
790
|
+
async authStatus(ms) {
|
|
791
|
+
if (this.authKind !== void 0) return this.authKind;
|
|
792
|
+
if (!this.info?.agentCapabilities._meta?.authStatus) return void 0;
|
|
793
|
+
return new Promise((resolve) => {
|
|
794
|
+
const timer = setTimeout(() => {
|
|
795
|
+
this.authWaiters = this.authWaiters.filter((w) => w !== waiter);
|
|
796
|
+
resolve(void 0);
|
|
797
|
+
}, ms);
|
|
798
|
+
const waiter = (kind) => {
|
|
799
|
+
clearTimeout(timer);
|
|
800
|
+
resolve(kind);
|
|
801
|
+
};
|
|
802
|
+
this.authWaiters.push(waiter);
|
|
803
|
+
});
|
|
804
|
+
}
|
|
781
805
|
/** ACP `authenticate` with one of the advertised methods (the agent runs its own flow). */
|
|
782
806
|
async authenticate(methodId) {
|
|
783
807
|
if (!this.connection) return { error: "Not connected" };
|
|
@@ -919,7 +943,12 @@ ${file.text}
|
|
|
919
943
|
});
|
|
920
944
|
}
|
|
921
945
|
};
|
|
922
|
-
return sdk.client({ name: "leuria" }).onNotification(
|
|
946
|
+
return sdk.client({ name: "leuria" }).onNotification("_auth/status_update", rawParams, (ctx) => {
|
|
947
|
+
const kind = ctx.params?.authStatus?.kind;
|
|
948
|
+
if (typeof kind !== "string") return;
|
|
949
|
+
this.authKind = kind;
|
|
950
|
+
for (const waiter of this.authWaiters.splice(0)) waiter(kind);
|
|
951
|
+
}).onNotification(
|
|
923
952
|
"session/update",
|
|
924
953
|
rawParams,
|
|
925
954
|
(ctx) => handleSessionUpdate(ctx.params)
|
|
@@ -949,13 +978,105 @@ function killTree(child) {
|
|
|
949
978
|
}
|
|
950
979
|
}
|
|
951
980
|
|
|
981
|
+
// src/scratch.ts
|
|
982
|
+
import { rm } from "fs/promises";
|
|
983
|
+
function removeScratch(dir, onFailure) {
|
|
984
|
+
rm(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 500 }).catch((error) => onFailure?.(error));
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// src/terminal-window.ts
|
|
988
|
+
import { spawn as spawn2 } from "child_process";
|
|
989
|
+
import { chmodSync as chmodSync3, existsSync as existsSync4, mkdtempSync, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
990
|
+
import { tmpdir } from "os";
|
|
991
|
+
import { join as join4 } from "path";
|
|
992
|
+
function canOpenTerminalWindow() {
|
|
993
|
+
return process.platform === "win32" || process.platform === "darwin";
|
|
994
|
+
}
|
|
995
|
+
async function runInTerminalWindow(options) {
|
|
996
|
+
if (!canOpenTerminalWindow()) throw new Error("Leuria can't open a terminal window on this system");
|
|
997
|
+
const dir = mkdtempSync(join4(tmpdir(), "leuria-window-"));
|
|
998
|
+
const done = join4(dir, "exit-code");
|
|
999
|
+
try {
|
|
1000
|
+
if (process.platform === "win32") {
|
|
1001
|
+
const script = join4(dir, "sign-in.cmd");
|
|
1002
|
+
writeFileSync4(script, windowsScript(options, done), "utf-8");
|
|
1003
|
+
spawn2("cmd.exe", ["/d", "/c", `start "${options.title.replaceAll('"', "")}" "${script}"`], {
|
|
1004
|
+
detached: true,
|
|
1005
|
+
stdio: "ignore",
|
|
1006
|
+
windowsHide: false,
|
|
1007
|
+
windowsVerbatimArguments: true
|
|
1008
|
+
}).unref();
|
|
1009
|
+
} else {
|
|
1010
|
+
const script = join4(dir, "sign-in.command");
|
|
1011
|
+
writeFileSync4(script, macScript(options, done), "utf-8");
|
|
1012
|
+
chmodSync3(script, 448);
|
|
1013
|
+
spawn2("open", ["-a", "Terminal", script], { detached: true, stdio: "ignore" }).unref();
|
|
1014
|
+
}
|
|
1015
|
+
return await waitFor(done, options.signal, options.timeoutMs ?? 15 * 6e4);
|
|
1016
|
+
} finally {
|
|
1017
|
+
removeScratch(dir);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
function waitFor(file, signal, timeoutMs) {
|
|
1021
|
+
return new Promise((resolve) => {
|
|
1022
|
+
const finish = (code) => {
|
|
1023
|
+
clearInterval(poll);
|
|
1024
|
+
clearTimeout(timer);
|
|
1025
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1026
|
+
resolve(code);
|
|
1027
|
+
};
|
|
1028
|
+
const onAbort = () => finish(null);
|
|
1029
|
+
const poll = setInterval(() => {
|
|
1030
|
+
if (!existsSync4(file)) return;
|
|
1031
|
+
const code = Number.parseInt(readFileSync4(file, "utf-8").trim(), 10);
|
|
1032
|
+
finish(Number.isNaN(code) ? null : code);
|
|
1033
|
+
}, 1e3);
|
|
1034
|
+
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
1035
|
+
if (signal?.aborted) onAbort();
|
|
1036
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
function windowsScript(options, done) {
|
|
1040
|
+
const text = (value) => value.replaceAll("%", "%%");
|
|
1041
|
+
const quote = (value) => `"${text(value).replaceAll('"', '""')}"`;
|
|
1042
|
+
return [
|
|
1043
|
+
"@echo off",
|
|
1044
|
+
"chcp 65001 >nul",
|
|
1045
|
+
`title ${text(options.title).replace(/[&|<>^]/g, "")}`,
|
|
1046
|
+
`echo ${text(options.title).replace(/[&|<>^]/g, "")}`,
|
|
1047
|
+
"echo Leuria opened this window. Follow the steps below: it closes by itself when you're done.",
|
|
1048
|
+
"echo.",
|
|
1049
|
+
`cd /d ${quote(options.cwd)}`,
|
|
1050
|
+
...Object.entries(options.env).map(([key, value]) => `set "${key}=${text(value)}"`),
|
|
1051
|
+
[options.command, ...options.args].map(quote).join(" "),
|
|
1052
|
+
`echo %errorlevel%> ${quote(done)}`,
|
|
1053
|
+
""
|
|
1054
|
+
].join("\r\n");
|
|
1055
|
+
}
|
|
1056
|
+
function macScript(options, done) {
|
|
1057
|
+
const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
1058
|
+
return [
|
|
1059
|
+
"#!/bin/bash",
|
|
1060
|
+
"clear",
|
|
1061
|
+
`echo ${quote(options.title)}`,
|
|
1062
|
+
`echo ${quote("Leuria opened this window. Follow the steps below, then close it.")}`,
|
|
1063
|
+
"echo",
|
|
1064
|
+
`cd ${quote(options.cwd)}`,
|
|
1065
|
+
...Object.entries(options.env).map(([key, value]) => `export ${key}=${quote(value)}`),
|
|
1066
|
+
[options.command, ...options.args].map(quote).join(" "),
|
|
1067
|
+
`echo $? > ${quote(done)}`,
|
|
1068
|
+
`echo; echo ${quote("Done. You can close this window.")}`,
|
|
1069
|
+
""
|
|
1070
|
+
].join("\n");
|
|
1071
|
+
}
|
|
1072
|
+
|
|
952
1073
|
// src/browser-link.ts
|
|
953
1074
|
import { execFileSync } from "child_process";
|
|
954
|
-
import { chmodSync as
|
|
955
|
-
import { tmpdir } from "os";
|
|
956
|
-
import { delimiter, join as
|
|
1075
|
+
import { chmodSync as chmodSync4, existsSync as existsSync5, mkdirSync as mkdirSync5, mkdtempSync as mkdtempSync2, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
1076
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
1077
|
+
import { delimiter, join as join5 } from "path";
|
|
957
1078
|
function realCommand(name) {
|
|
958
|
-
if (name === "open" &&
|
|
1079
|
+
if (name === "open" && existsSync5("/usr/bin/open")) return "/usr/bin/open";
|
|
959
1080
|
try {
|
|
960
1081
|
return execFileSync("/bin/sh", ["-c", `command -v ${name}`], { encoding: "utf-8" }).trim() || null;
|
|
961
1082
|
} catch {
|
|
@@ -966,11 +1087,11 @@ function catchBrowserLinks(onUrl, options = {}) {
|
|
|
966
1087
|
if (process.platform === "win32") return null;
|
|
967
1088
|
const basePath = options.basePath ?? process.env.PATH ?? "";
|
|
968
1089
|
const findReal = options.real ?? realCommand;
|
|
969
|
-
const dir =
|
|
970
|
-
const bin =
|
|
971
|
-
const log =
|
|
1090
|
+
const dir = mkdtempSync2(join5(tmpdir2(), "leuria-open-"));
|
|
1091
|
+
const bin = join5(dir, "bin");
|
|
1092
|
+
const log = join5(dir, "opened.txt");
|
|
972
1093
|
mkdirSync5(bin);
|
|
973
|
-
|
|
1094
|
+
writeFileSync5(log, "");
|
|
974
1095
|
for (const name of ["open", "xdg-open"]) {
|
|
975
1096
|
const real = findReal(name);
|
|
976
1097
|
if (!real) continue;
|
|
@@ -978,14 +1099,14 @@ function catchBrowserLinks(onUrl, options = {}) {
|
|
|
978
1099
|
for arg in "$@"; do printf '%s\\n' "$arg" >> '${log}'; done
|
|
979
1100
|
exec '${real}' "$@"
|
|
980
1101
|
`;
|
|
981
|
-
|
|
982
|
-
|
|
1102
|
+
writeFileSync5(join5(bin, name), script);
|
|
1103
|
+
chmodSync4(join5(bin, name), 493);
|
|
983
1104
|
}
|
|
984
1105
|
const seen = /* @__PURE__ */ new Set();
|
|
985
1106
|
const timer = setInterval(() => {
|
|
986
1107
|
let lines;
|
|
987
1108
|
try {
|
|
988
|
-
lines =
|
|
1109
|
+
lines = readFileSync5(log, "utf-8").split("\n");
|
|
989
1110
|
} catch {
|
|
990
1111
|
return;
|
|
991
1112
|
}
|
|
@@ -1071,7 +1192,7 @@ function preferredMethod(methods, options = {}) {
|
|
|
1071
1192
|
}
|
|
1072
1193
|
async function open(id, onProgress, terminalAuth = false, extraEnv = {}) {
|
|
1073
1194
|
const agent = await ensureAgent(id, { onProgress });
|
|
1074
|
-
const sandbox =
|
|
1195
|
+
const sandbox = mkdtempSync3(join6(tmpdir3(), "leuria-auth-"));
|
|
1075
1196
|
const custom = profileFor(id)?.configure?.({ sandbox, mcpUrl: "http://127.0.0.1:9/unused", mcpToken: "unused" });
|
|
1076
1197
|
const env = { ...agent.env, ...custom?.env, ...extraEnv };
|
|
1077
1198
|
const session = new AcpLiveSession({
|
|
@@ -1085,7 +1206,7 @@ async function open(id, onProgress, terminalAuth = false, extraEnv = {}) {
|
|
|
1085
1206
|
});
|
|
1086
1207
|
const close = () => {
|
|
1087
1208
|
session.close();
|
|
1088
|
-
|
|
1209
|
+
removeScratch(sandbox);
|
|
1089
1210
|
};
|
|
1090
1211
|
return { agent, session, close, env, sandbox };
|
|
1091
1212
|
}
|
|
@@ -1114,7 +1235,7 @@ function signInStallMs() {
|
|
|
1114
1235
|
}
|
|
1115
1236
|
async function checkSignIn(id, onProgress, options = {}) {
|
|
1116
1237
|
if (isLlmId(id)) return checkLlm(id);
|
|
1117
|
-
const { session, close } = await open(id, onProgress);
|
|
1238
|
+
const { session, close } = await open(id, onProgress, true);
|
|
1118
1239
|
let timer;
|
|
1119
1240
|
const timedOut = new Promise((resolve) => {
|
|
1120
1241
|
timer = setTimeout(() => {
|
|
@@ -1132,22 +1253,25 @@ async function checkSignIn(id, onProgress, options = {}) {
|
|
|
1132
1253
|
async function check() {
|
|
1133
1254
|
const connected = await session.connect();
|
|
1134
1255
|
if (connected.error) return { ok: false, detail: `the agent did not start: ${connected.error}`, methods: [] };
|
|
1135
|
-
const
|
|
1256
|
+
const all = session.info?.authMethods ?? [];
|
|
1257
|
+
const methods = agentMethods(all);
|
|
1258
|
+
const window = canOpenTerminalWindow() && methods.length === 0 && all.some(isTerminal) ? { window: true } : {};
|
|
1136
1259
|
const created = await session.newSession();
|
|
1137
|
-
if (
|
|
1138
|
-
return { ok: false, detail:
|
|
1260
|
+
if (created.error) return { ok: false, detail: created.authRequired ? "not signed in" : created.error, methods, ...window };
|
|
1261
|
+
if (await session.authStatus(5e3) === "none") return { ok: false, detail: "not signed in", methods, ...window };
|
|
1262
|
+
return { ok: true, detail: "ready", methods, ...withChoice(id, session.models) };
|
|
1139
1263
|
}
|
|
1140
1264
|
}
|
|
1141
1265
|
async function signIn(id, options = {}) {
|
|
1142
1266
|
if (isLlmId(id)) return checkLlm(id);
|
|
1143
1267
|
let pageOpened = false;
|
|
1144
|
-
const link = options.onUrl && !options.terminal ? catchBrowserLinks((url) => {
|
|
1268
|
+
const link = options.onUrl && !options.terminal && !options.window ? catchBrowserLinks((url) => {
|
|
1145
1269
|
pageOpened = true;
|
|
1146
1270
|
options.onUrl?.(url);
|
|
1147
1271
|
}) : null;
|
|
1148
1272
|
let opened;
|
|
1149
1273
|
try {
|
|
1150
|
-
opened = await open(id, options.onProgress, options.terminal, link?.env);
|
|
1274
|
+
opened = await open(id, options.onProgress, options.terminal || options.window, link?.env);
|
|
1151
1275
|
} catch (error) {
|
|
1152
1276
|
link?.stop();
|
|
1153
1277
|
throw error;
|
|
@@ -1190,13 +1314,30 @@ async function signIn(id, options = {}) {
|
|
|
1190
1314
|
const connected = await session.connect();
|
|
1191
1315
|
if (connected.error) return { ok: false, detail: `the agent did not start: ${connected.error}`, methods: [] };
|
|
1192
1316
|
const all = session.info?.authMethods ?? [];
|
|
1193
|
-
const
|
|
1317
|
+
const terminal = options.terminal || options.window;
|
|
1318
|
+
const methods = terminal ? all : agentMethods(all);
|
|
1194
1319
|
if (methods.length === 0) {
|
|
1195
1320
|
return { ok: false, detail: `${agent.name} offers no sign-in method Leuria can run; sign in with its own tool.`, methods };
|
|
1196
1321
|
}
|
|
1197
|
-
const method = options.methodId ? methods.find((m) => m.id === options.methodId) : preferredMethod(methods, { terminal
|
|
1322
|
+
const method = options.methodId ? methods.find((m) => m.id === options.methodId) : preferredMethod(methods, { terminal });
|
|
1198
1323
|
if (!method) return { ok: false, detail: `Unknown method. Available: ${methods.map((m) => m.id).join(", ")}`, methods };
|
|
1199
1324
|
options.onProgress?.(`Signing in to ${agent.name} with ${method.name}${method.description ? ` (${method.description})` : ""}\u2026`);
|
|
1325
|
+
if (isTerminal(method) && options.window) {
|
|
1326
|
+
session.close();
|
|
1327
|
+
const code = await runInTerminalWindow({
|
|
1328
|
+
title: `Sign in to ${agentName(id)}`,
|
|
1329
|
+
command: agent.command,
|
|
1330
|
+
args: [...agent.launchArgs, ...method.args ?? []],
|
|
1331
|
+
// A compiled engine runs JavaScript agents as plain Bun, as sessions do.
|
|
1332
|
+
env: { ...env, ...method.env ?? {}, ...process.versions.bun ? { BUN_BE_BUN: "1" } : {} },
|
|
1333
|
+
cwd: sandbox,
|
|
1334
|
+
signal: options.signal
|
|
1335
|
+
});
|
|
1336
|
+
if (options.signal?.aborted) return { ok: false, detail: "cancelled", methods };
|
|
1337
|
+
if (code === null) return { ok: false, detail: "the sign-in window didn't finish", methods, window: true };
|
|
1338
|
+
if (code !== 0) return { ok: false, detail: `the sign-in ended with code ${code}`, methods, window: true };
|
|
1339
|
+
return checkSignIn(id, options.onProgress);
|
|
1340
|
+
}
|
|
1200
1341
|
if (isTerminal(method)) {
|
|
1201
1342
|
session.close();
|
|
1202
1343
|
const code = await new Promise((resolve, reject) => {
|
|
@@ -1582,9 +1723,9 @@ import cacache from "cacache";
|
|
|
1582
1723
|
// src/skill-sources.ts
|
|
1583
1724
|
import { createHash as createHash3 } from "crypto";
|
|
1584
1725
|
import { lookup } from "dns/promises";
|
|
1585
|
-
import { mkdtemp, readdir, readFile, rm, stat } from "fs/promises";
|
|
1586
|
-
import { tmpdir as
|
|
1587
|
-
import { extname, join as
|
|
1726
|
+
import { mkdtemp, readdir, readFile, rm as rm2, stat } from "fs/promises";
|
|
1727
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
1728
|
+
import { extname, join as join7, relative, sep } from "path";
|
|
1588
1729
|
import { downloadTemplate } from "giget";
|
|
1589
1730
|
import ipaddr from "ipaddr.js";
|
|
1590
1731
|
import { parse as parseYaml } from "yaml";
|
|
@@ -1658,7 +1799,7 @@ function parseSkillMd(text, files = {}) {
|
|
|
1658
1799
|
return { name, description: description.slice(0, MAX_DESCRIPTION), body: match[2].trim(), files };
|
|
1659
1800
|
}
|
|
1660
1801
|
async function fromGithub(source) {
|
|
1661
|
-
const dir = await mkdtemp(
|
|
1802
|
+
const dir = await mkdtemp(join7(tmpdir4(), "leuria-skills-"));
|
|
1662
1803
|
try {
|
|
1663
1804
|
const path2 = source.subpath ? `/${source.subpath}` : "";
|
|
1664
1805
|
await downloadTemplate(`gh:${source.repo}${path2}#${source.ref ?? "HEAD"}`, {
|
|
@@ -1678,21 +1819,21 @@ async function fromGithub(source) {
|
|
|
1678
1819
|
continue;
|
|
1679
1820
|
}
|
|
1680
1821
|
if (source.skill && skill.name !== source.skill) continue;
|
|
1681
|
-
skill.files = await readTextFiles(
|
|
1822
|
+
skill.files = await readTextFiles(join7(skillFile, ".."));
|
|
1682
1823
|
found.push(skill);
|
|
1683
1824
|
}
|
|
1684
1825
|
return found;
|
|
1685
1826
|
} finally {
|
|
1686
|
-
await
|
|
1827
|
+
await rm2(dir, { recursive: true, force: true });
|
|
1687
1828
|
}
|
|
1688
1829
|
}
|
|
1689
1830
|
async function findSkillFiles(dir, depth) {
|
|
1690
1831
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
1691
1832
|
const own = entries.find((e) => e.isFile() && e.name.toLowerCase() === "skill.md");
|
|
1692
|
-
if (own) return [
|
|
1833
|
+
if (own) return [join7(dir, own.name)];
|
|
1693
1834
|
if (depth >= MAX_DEPTH) return [];
|
|
1694
1835
|
const nested = await Promise.all(
|
|
1695
|
-
entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => findSkillFiles(
|
|
1836
|
+
entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => findSkillFiles(join7(dir, e.name), depth + 1))
|
|
1696
1837
|
);
|
|
1697
1838
|
return nested.flat();
|
|
1698
1839
|
}
|
|
@@ -1701,7 +1842,7 @@ async function readTextFiles(dir) {
|
|
|
1701
1842
|
let total = 0;
|
|
1702
1843
|
const walk = async (at, depth) => {
|
|
1703
1844
|
for (const entry of await readdir(at, { withFileTypes: true })) {
|
|
1704
|
-
const full =
|
|
1845
|
+
const full = join7(at, entry.name);
|
|
1705
1846
|
if (entry.isDirectory() && depth < MAX_DEPTH) await walk(full, depth + 1);
|
|
1706
1847
|
if (!entry.isFile() || !TEXT_FILES.has(extname(entry.name).toLowerCase())) continue;
|
|
1707
1848
|
const path2 = relative(dir, full).split(sep).join("/");
|
|
@@ -2432,9 +2573,9 @@ function handleStream(req, res, sm, sessionId) {
|
|
|
2432
2573
|
|
|
2433
2574
|
// src/session-manager.ts
|
|
2434
2575
|
import { randomUUID } from "crypto";
|
|
2435
|
-
import { mkdirSync as mkdirSync6
|
|
2436
|
-
import { tmpdir as
|
|
2437
|
-
import { join as
|
|
2576
|
+
import { mkdirSync as mkdirSync6 } from "fs";
|
|
2577
|
+
import { tmpdir as tmpdir5 } from "os";
|
|
2578
|
+
import { join as join8 } from "path";
|
|
2438
2579
|
|
|
2439
2580
|
// src/llm/llm-session.ts
|
|
2440
2581
|
var LlmSession = class {
|
|
@@ -2919,7 +3060,7 @@ var SessionManager = class {
|
|
|
2919
3060
|
* files (CLAUDE.md, repo) to act on.
|
|
2920
3061
|
*/
|
|
2921
3062
|
allocateSandbox(sessionId) {
|
|
2922
|
-
const dir =
|
|
3063
|
+
const dir = join8(tmpdir5(), `leuria-${sessionId}`);
|
|
2923
3064
|
mkdirSync6(dir, { recursive: true });
|
|
2924
3065
|
return dir;
|
|
2925
3066
|
}
|
|
@@ -2931,14 +3072,13 @@ var SessionManager = class {
|
|
|
2931
3072
|
session.live = void 0;
|
|
2932
3073
|
this.options.webMcpServer.removeChannel(session.id);
|
|
2933
3074
|
setTimeout(() => this.sessions.delete(session.id), FORGET_AFTER_MS).unref();
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
this.options.logger.warn("failed to remove sandbox", {
|
|
3075
|
+
removeScratch(
|
|
3076
|
+
session.cwd,
|
|
3077
|
+
(err) => this.options.logger.warn("failed to remove sandbox", {
|
|
2938
3078
|
cwd: session.cwd,
|
|
2939
3079
|
err: err instanceof Error ? err.message : String(err)
|
|
2940
|
-
})
|
|
2941
|
-
|
|
3080
|
+
})
|
|
3081
|
+
);
|
|
2942
3082
|
}
|
|
2943
3083
|
notify(session, event, data) {
|
|
2944
3084
|
for (const listener of session.listeners) {
|
package/dist/cli.js
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
signIn,
|
|
41
41
|
startEngine,
|
|
42
42
|
updateAgents
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-JBVFUXX5.js";
|
|
44
44
|
|
|
45
45
|
// src/cli.ts
|
|
46
46
|
import { spawn } from "child_process";
|
|
@@ -551,8 +551,10 @@ function createAdminHandler(ctx) {
|
|
|
551
551
|
const result = await signIn(signingInAgent, {
|
|
552
552
|
signal: controller.signal,
|
|
553
553
|
methodId: typeof body.methodId === "string" ? body.methodId : void 0,
|
|
554
|
-
// The app has no terminal:
|
|
554
|
+
// The app has no terminal: agent methods (the agent runs its own flow), or
|
|
555
|
+
// a terminal-only sign-in (Claude) in a window Leuria opens.
|
|
555
556
|
terminal: false,
|
|
557
|
+
window: body.window === true,
|
|
556
558
|
// Offered in the app too, in case the browser did not come up.
|
|
557
559
|
onUrl: (url) => ctx.emit({ event: "signin_url", url }),
|
|
558
560
|
onProgress: (message) => ctx.emit({ event: "progress", stage: "signin", message })
|
|
@@ -717,6 +719,7 @@ function createAdminHandler(ctx) {
|
|
|
717
719
|
// `message` is for the CLI; the app words each stage itself (no jargon for visitors).
|
|
718
720
|
onStep: (message) => ctx.emit({ event: "progress", stage: "check", step: ++checkStep, message })
|
|
719
721
|
});
|
|
722
|
+
if (!result.ok) ctx.logger.warn("check failed", { agent, steps: result.steps.filter((step) => !step.ok) });
|
|
720
723
|
sendJson(res, 200, result);
|
|
721
724
|
return true;
|
|
722
725
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -921,6 +921,11 @@ interface SignInStatus {
|
|
|
921
921
|
methods: AgentInfo["authMethods"];
|
|
922
922
|
/** Models the agent offers once signed in (from `session/new`), with the visitor's choice as `current`. */
|
|
923
923
|
models?: AgentModels;
|
|
924
|
+
/**
|
|
925
|
+
* The agent signs in only on the command line (Claude): Leuria can open a
|
|
926
|
+
* terminal window for it (`signIn(id, { window: true })`).
|
|
927
|
+
*/
|
|
928
|
+
window?: boolean;
|
|
924
929
|
}
|
|
925
930
|
/**
|
|
926
931
|
* Is the visitor signed in to this agent? Opens (and closes) a real ACP session, within a time limit.
|
|
@@ -934,6 +939,8 @@ declare function signIn(id: string, options?: {
|
|
|
934
939
|
methodId?: string;
|
|
935
940
|
/** The caller can run an interactive terminal method (a TTY). */
|
|
936
941
|
terminal?: boolean;
|
|
942
|
+
/** Run the agent's terminal sign-in in a new terminal window (the desktop app has no terminal). */
|
|
943
|
+
window?: boolean;
|
|
937
944
|
onProgress?: (message: string) => void;
|
|
938
945
|
/** Cancel a sign-in in progress: the agent is stopped (which frees its login callback). */
|
|
939
946
|
signal?: AbortSignal;
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leuria/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Bring your own AI to the web: the Leuria engine. Lets websites you approve use the AI on your machine, through tools the page provides.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|