@leuria/cli 0.1.1 → 0.1.2

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.
@@ -343,6 +343,7 @@ import { createHash } from "crypto";
343
343
  import { chmodSync as chmodSync2, existsSync as existsSync3, mkdirSync as mkdirSync4, readdirSync, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
344
344
  import { dirname, join as join3 } from "path";
345
345
  import { promisify as promisify2 } from "util";
346
+ import crossSpawn from "cross-spawn";
346
347
 
347
348
  // src/codex.ts
348
349
  import { execFile } from "child_process";
@@ -374,7 +375,11 @@ async function prepareCodexHome(agent) {
374
375
  "",
375
376
  "[features]",
376
377
  ...features.map((name) => `${name} = ${ENABLED_FEATURES.has(name)}`),
377
- ""
378
+ "",
379
+ // Windows: the sandbox that needs no administrator rights (a restricted token).
380
+ // The other one asks for them the first time: out of the blue, and a dead end
381
+ // on a work computer without them.
382
+ ...process.platform === "win32" ? ["[windows]", 'sandbox = "unelevated"', ""] : []
378
383
  ];
379
384
  writeFileSync2(join2(home, "config.toml"), lines.join("\n"), { mode: 384 });
380
385
  writeFileSync2(stamp, agent.version);
@@ -576,12 +581,7 @@ async function installPackage(dir, spec, args, env) {
576
581
  if (process.versions.bun) {
577
582
  await execFileAsync2(process.execPath, ["add", spec], { cwd: dir, env: { ...process.env, BUN_BE_BUN: "1" }, timeout: 5 * 6e4 });
578
583
  } else {
579
- const npm = process.platform === "win32" ? "npm.cmd" : "npm";
580
- await execFileAsync2(npm, ["install", "--no-audit", "--no-fund", "--omit=dev", "--loglevel=error", spec], {
581
- cwd: dir,
582
- timeout: 5 * 6e4,
583
- shell: process.platform === "win32"
584
- });
584
+ await run("npm", ["install", "--no-audit", "--no-fund", "--omit=dev", "--loglevel=error", spec], { cwd: dir, timeout: 5 * 6e4 });
585
585
  }
586
586
  const name = spec.lastIndexOf("@") > 0 ? spec.slice(0, spec.lastIndexOf("@")) : spec;
587
587
  const pkgDir = join3(dir, "node_modules", ...name.split("/"));
@@ -593,6 +593,25 @@ async function installPackage(dir, spec, args, env) {
593
593
  const isScript = /\.[cm]?js$/.test(entry) || /^#!.*\b(node|bun)\b/.test(readFileSync3(entry, "utf-8").slice(0, 100));
594
594
  return isScript ? { command: process.execPath, launchArgs: [entry], args, env } : { command: entry, launchArgs: [], args, env };
595
595
  }
596
+ function run(command, args, options) {
597
+ return new Promise((resolve, reject) => {
598
+ const child = crossSpawn(command, args, { cwd: options.cwd, stdio: ["ignore", "ignore", "pipe"], windowsHide: true });
599
+ let stderr = "";
600
+ child.stderr?.on("data", (chunk) => {
601
+ stderr += chunk.toString("utf-8");
602
+ });
603
+ const timer = setTimeout(() => child.kill(), options.timeout);
604
+ child.on("error", (error) => {
605
+ clearTimeout(timer);
606
+ reject(error);
607
+ });
608
+ child.on("close", (code) => {
609
+ clearTimeout(timer);
610
+ if (code === 0) resolve();
611
+ else reject(new Error(`${command} ${args.join(" ")} failed (${code ?? "stopped"})${stderr.trim() ? `: ${stderr.trim()}` : ""}`));
612
+ });
613
+ });
614
+ }
596
615
  async function installBinary(dir, target) {
597
616
  const res = await fetch(target.archive, { signal: AbortSignal.timeout(5 * 6e4) });
598
617
  if (!res.ok) throw new Error(`Download failed (${res.status}): ${target.archive}`);
@@ -635,16 +654,17 @@ function compareVersions(a, b) {
635
654
  }
636
655
 
637
656
  // src/version.ts
638
- var VERSION = true ? "0.1.0" : "0.0.0-dev";
657
+ var VERSION = true ? "0.1.2" : "0.0.0-dev";
639
658
 
640
659
  // src/auth.ts
641
- import { spawn as spawn2 } from "child_process";
660
+ import crossSpawn3 from "cross-spawn";
642
661
  import { mkdtempSync as mkdtempSync2, rmSync as rmSync3 } from "fs";
643
662
  import { tmpdir as tmpdir2 } from "os";
644
663
  import { join as join5 } from "path";
645
664
 
646
665
  // src/acp/acp-client.ts
647
666
  import { spawn } from "child_process";
667
+ import crossSpawn2 from "cross-spawn";
648
668
  import { Readable, Writable } from "stream";
649
669
  var rawParams = {
650
670
  // biome-ignore lint/suspicious/noExplicitAny: ACP payloads are untyped here
@@ -707,11 +727,11 @@ var AcpLiveSession = class {
707
727
  const env = { ...process.env, ...this.options.envVars };
708
728
  for (const key of REMOVED_ENV_VARS) delete env[key];
709
729
  if (process.versions.bun) env.BUN_BE_BUN = "1";
710
- const child = spawn(this.options.command, this.options.args, {
730
+ const child = crossSpawn2(this.options.command, this.options.args, {
711
731
  stdio: ["pipe", "pipe", "pipe"],
712
732
  env,
713
733
  cwd: this.options.cwd,
714
- shell: process.platform === "win32",
734
+ windowsHide: true,
715
735
  // Its own process group, so close() also stops what the agent starts
716
736
  // (Gemini relaunches itself as a child process).
717
737
  detached: process.platform !== "win32"
@@ -915,7 +935,7 @@ ${file.text}
915
935
  function killTree(child) {
916
936
  try {
917
937
  if (process.platform === "win32" && child.pid) {
918
- spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
938
+ spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
919
939
  } else if (child.pid) {
920
940
  process.kill(-child.pid, "SIGTERM");
921
941
  } else {
@@ -1161,11 +1181,11 @@ async function signIn(id, options = {}) {
1161
1181
  else options.signal?.addEventListener("abort", onAbort, { once: true });
1162
1182
  });
1163
1183
  try {
1164
- return await Promise.race([cancelled, run()]);
1184
+ return await Promise.race([cancelled, run2()]);
1165
1185
  } finally {
1166
1186
  close();
1167
1187
  }
1168
- async function run() {
1188
+ async function run2() {
1169
1189
  let session = opened.session;
1170
1190
  const connected = await session.connect();
1171
1191
  if (connected.error) return { ok: false, detail: `the agent did not start: ${connected.error}`, methods: [] };
@@ -1180,7 +1200,7 @@ async function signIn(id, options = {}) {
1180
1200
  if (isTerminal(method)) {
1181
1201
  session.close();
1182
1202
  const code = await new Promise((resolve, reject) => {
1183
- const child = spawn2(agent.command, [...agent.launchArgs, ...method.args ?? []], {
1203
+ const child = crossSpawn3(agent.command, [...agent.launchArgs, ...method.args ?? []], {
1184
1204
  stdio: "inherit",
1185
1205
  cwd: sandbox,
1186
1206
  env: { ...process.env, ...env, ...method.env ?? {} }
package/dist/cli.js CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  signIn,
41
41
  startEngine,
42
42
  updateAgents
43
- } from "./chunk-ES2YTJPL.js";
43
+ } from "./chunk-JOCM56MT.js";
44
44
 
45
45
  // src/cli.ts
46
46
  import { spawn } from "child_process";
@@ -1086,7 +1086,10 @@ function sites(args) {
1086
1086
  }
1087
1087
  }
1088
1088
  function openInBrowser(url) {
1089
- const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
1089
+ const [command, args] = process.platform === "darwin" ? ["open", [url]] : (
1090
+ // Not `cmd /c start`: cmd would read the & between query parameters as a new command.
1091
+ process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : ["xdg-open", [url]]
1092
+ );
1090
1093
  try {
1091
1094
  const child = spawn(command, args, { stdio: "ignore", detached: true });
1092
1095
  child.on("error", () => void 0);
@@ -1226,13 +1229,19 @@ async function startForApp(config, verbose, devPairing) {
1226
1229
  });
1227
1230
  setTimeout(updateAis, AI_UPDATE_DELAY_MS).unref();
1228
1231
  setInterval(updateAis, AI_UPDATE_EVERY_MS).unref();
1232
+ let stopping = false;
1229
1233
  const shutdown = async () => {
1234
+ if (stopping) return;
1235
+ stopping = true;
1230
1236
  await engine.close();
1231
1237
  process.exit(0);
1232
1238
  };
1233
1239
  process.on("SIGINT", shutdown);
1234
1240
  process.on("SIGTERM", shutdown);
1235
1241
  process.stdin.on("end", shutdown);
1242
+ process.stdin.on("data", (chunk) => {
1243
+ if (chunk.toString("utf-8").split(/\r?\n/).includes("quit")) void shutdown();
1244
+ });
1236
1245
  process.stdin.resume();
1237
1246
  }
1238
1247
  function out(text) {
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  resolveAgentCommand,
19
19
  signIn,
20
20
  startEngine
21
- } from "./chunk-ES2YTJPL.js";
21
+ } from "./chunk-JOCM56MT.js";
22
22
  export {
23
23
  DEFAULT_PORT,
24
24
  GrantStore,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leuria/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
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",
@@ -30,6 +30,7 @@
30
30
  "dependencies": {
31
31
  "@agentclientprotocol/sdk": "^1.5.0",
32
32
  "cacache": "^21.0.1",
33
+ "cross-spawn": "^7.0.6",
33
34
  "giget": "^3.3.1",
34
35
  "ipaddr.js": "^2.5.0",
35
36
  "ws": "^8.18.0",
@@ -37,6 +38,7 @@
37
38
  },
38
39
  "devDependencies": {
39
40
  "@types/cacache": "^20.0.1",
41
+ "@types/cross-spawn": "^6.0.6",
40
42
  "@types/node": "^22.0.0",
41
43
  "@types/ws": "^8.5.0",
42
44
  "tsup": "^8.4.0",