@versot/vaguspi 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.
package/dist/bin.js CHANGED
@@ -8,7 +8,8 @@ import { parseArgs } from "node:util";
8
8
  import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { homedir as homedir2 } from "node:os";
11
- import { join as join8, normalize, resolve as resolve3, sep as sep2 } from "node:path";
11
+ import { join as join8, normalize, resolve as resolve3, sep as sep3 } from "node:path";
12
+ import { EnvHttpProxyAgent, setGlobalDispatcher } from "undici";
12
13
 
13
14
  // packages/host/config/dist/config-store.js
14
15
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
@@ -301,8 +302,9 @@ var VagModelsStore = class {
301
302
  // packages/host/engine/dist/vagus-engine.js
302
303
  import { createAgentSession, DefaultResourceLoader, ModelRuntime, SessionManager as SessionManager2, SettingsManager } from "@earendil-works/pi-coding-agent";
303
304
  import { homedir } from "node:os";
304
- import { basename, dirname as dirname3, isAbsolute as isAbsolute2, join as join5, resolve } from "node:path";
305
- import { appendFileSync as appendFileSync2, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync4, renameSync, rmSync, statSync, writeFileSync as writeFileSync3 } from "node:fs";
305
+ import { basename, dirname as dirname3, isAbsolute as isAbsolute2, join as join5, resolve, sep } from "node:path";
306
+ import { randomUUID as randomUUID2 } from "node:crypto";
307
+ import { appendFileSync as appendFileSync2, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync4, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
306
308
 
307
309
  // packages/host/engine/dist/display-utils.js
308
310
  import * as Diff from "diff";
@@ -448,6 +450,10 @@ function messageToText(message) {
448
450
 
449
451
  // packages/host/engine/dist/usage-stats.js
450
452
  import { SessionManager } from "@earendil-works/pi-coding-agent";
453
+ function dayAt(ts) {
454
+ const d = new Date(ts);
455
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
456
+ }
451
457
  async function aggregateUsageStats() {
452
458
  const infos = await SessionManager.listAll();
453
459
  let totalTokens = 0;
@@ -461,11 +467,10 @@ async function aggregateUsageStats() {
461
467
  const activeDates = /* @__PURE__ */ new Set();
462
468
  const DAY = 864e5;
463
469
  const dayMap = /* @__PURE__ */ new Map();
464
- const dayAt = (ts) => Math.floor(ts / DAY);
465
470
  const ensureDay = (day) => {
466
471
  let p = dayMap.get(day);
467
472
  if (!p) {
468
- p = { tokens: 0, messages: 0, sessions: 0, byModel: /* @__PURE__ */ new Map() };
473
+ p = { tokens: 0, messages: 0, sessions: 0, cost: 0, byModel: /* @__PURE__ */ new Map() };
469
474
  dayMap.set(day, p);
470
475
  }
471
476
  return p;
@@ -489,12 +494,13 @@ async function aggregateUsageStats() {
489
494
  if (m.role !== "assistant" || m.usage === void 0)
490
495
  continue;
491
496
  const u = m.usage;
492
- const tokens = typeof u.totalTokens === "number" ? u.totalTokens : Number(u.input ?? 0) + Number(u.output ?? 0) + Number(u.cacheRead ?? 0) + Number(u.cacheWrite ?? 0);
497
+ const tokens = Number(u.input ?? 0) + Number(u.output ?? 0);
493
498
  const cost = typeof u.cost?.total === "number" ? u.cost.total : 0;
494
499
  totalTokens += tokens;
495
500
  totalCost += cost;
496
501
  sessionTokens += tokens;
497
502
  dayPoint.tokens += tokens;
503
+ dayPoint.cost += cost;
498
504
  const key = `${String(m.provider ?? "unknown")}/${String(m.model ?? "unknown")}`;
499
505
  const cur = byModel.get(key) ?? { tokens: 0, cost: 0 };
500
506
  byModel.set(key, { tokens: cur.tokens + tokens, cost: cur.cost + cost });
@@ -513,8 +519,9 @@ async function aggregateUsageStats() {
513
519
  }
514
520
  }
515
521
  const daily = [...dayMap.entries()].toSorted((a, b) => a[0] - b[0]).map(([day, p]) => ({
516
- ts: day * DAY,
522
+ ts: day,
517
523
  tokens: p.tokens,
524
+ cost: p.cost,
518
525
  messages: p.messages,
519
526
  sessions: p.sessions,
520
527
  byModel: Object.fromEntries(p.byModel)
@@ -524,14 +531,14 @@ async function aggregateUsageStats() {
524
531
  let longestStreak = 0;
525
532
  let run = 0;
526
533
  for (let i = 0; i < dates.length; i++) {
527
- run = i === 0 || dates[i] === (dates[i - 1] ?? 0) + 1 ? run + 1 : 1;
534
+ run = i === 0 || dates[i] === (dates[i - 1] ?? 0) + DAY ? run + 1 : 1;
528
535
  if (run > longestStreak)
529
536
  longestStreak = run;
530
537
  }
531
- const today = Math.floor(Date.now() / DAY);
538
+ const today = new Date((/* @__PURE__ */ new Date()).getFullYear(), (/* @__PURE__ */ new Date()).getMonth(), (/* @__PURE__ */ new Date()).getDate()).getTime();
532
539
  let currentStreak = 0;
533
540
  for (let i = dates.length - 1; i >= 0; i--) {
534
- if (dates[i] === today - currentStreak) {
541
+ if (dates[i] === today - currentStreak * DAY) {
535
542
  currentStreak++;
536
543
  } else {
537
544
  break;
@@ -1691,9 +1698,17 @@ var VagusEngine = class {
1691
1698
  closeSessionForCwd(cwd) {
1692
1699
  for (const [sid, session] of this.sessions) {
1693
1700
  if (session.sessionManager?.getCwd() === cwd) {
1694
- session.dispose();
1701
+ process.stderr.write(`vagus: disposing session ${sid} (cwd=${cwd})
1702
+ `);
1703
+ try {
1704
+ session.dispose();
1705
+ } catch (err) {
1706
+ process.stderr.write(`vagus: dispose threw: ${err instanceof Error ? err.stack : String(err)}
1707
+ `);
1708
+ }
1695
1709
  this.sessions.delete(sid);
1696
- void this.options.bus.emit("session.closed", { type: "session.closed", sessionId: sid });
1710
+ void this.options.bus.emit("session.closed", { type: "session.closed", sessionId: sid }).catch(() => {
1711
+ });
1697
1712
  }
1698
1713
  }
1699
1714
  }
@@ -1755,6 +1770,37 @@ var VagusEngine = class {
1755
1770
  }
1756
1771
  }
1757
1772
  /** Restores an archived project: moves its session files back under `sessions/`. */
1773
+ /** Restores an archived project by its encoded dir name (unique identity). */
1774
+ async unarchiveProjectByDir(dirKey) {
1775
+ if (!dirKey || dirKey.includes("/") || dirKey.includes("\\") || dirKey.includes(".."))
1776
+ return;
1777
+ const agentDir = this.agentDirPath();
1778
+ const src = join5(agentDir, "archived", dirKey);
1779
+ if (!existsSync3(src))
1780
+ return;
1781
+ let cwd;
1782
+ try {
1783
+ cwd = this.sessionCwd(readdirSync2(src).filter((f) => f.endsWith(".jsonl"))[0] ? join5(src, readdirSync2(src).filter((f) => f.endsWith(".jsonl"))[0]) : "");
1784
+ } catch {
1785
+ cwd = void 0;
1786
+ }
1787
+ if (cwd) {
1788
+ this.closeSessionForCwd(cwd);
1789
+ const dst = join5(agentDir, "sessions", this.encodeCwd(cwd));
1790
+ mkdirSync4(dst, { recursive: true });
1791
+ for (const entry of readdirSync2(src)) {
1792
+ if (!entry.endsWith(".jsonl"))
1793
+ continue;
1794
+ const from = join5(src, entry);
1795
+ try {
1796
+ if (statSync(from).isFile())
1797
+ renameSync(from, join5(dst, entry));
1798
+ } catch {
1799
+ }
1800
+ }
1801
+ this.rmDirSafe(src);
1802
+ }
1803
+ }
1758
1804
  async restoreProject(cwd) {
1759
1805
  const agentDir = this.agentDirPath();
1760
1806
  const src = join5(agentDir, "archived", this.encodeCwd(cwd));
@@ -1773,16 +1819,99 @@ var VagusEngine = class {
1773
1819
  } catch {
1774
1820
  }
1775
1821
  }
1776
- rmSync(src, { recursive: true, force: true });
1822
+ this.rmDirSafe(src);
1823
+ }
1824
+ /** Permanently deletes an archived project dir by its encoded dir name.
1825
+ * dirKey (not cwd) is the identity — cwds can repeat across archive dirs. */
1826
+ async deleteArchivedProjectByDir(dirKey) {
1827
+ if (!dirKey || dirKey.includes("/") || dirKey.includes("\\") || dirKey.includes(".."))
1828
+ return;
1829
+ const agentDir = this.agentDirPath();
1830
+ const dir = join5(agentDir, "archived", dirKey);
1831
+ process.stderr.write(`vagus: deleteArchivedProjectByDir dirKey=${dirKey}
1832
+ `);
1833
+ const prefix = dir + sep;
1834
+ for (const [sid, session] of this.sessions) {
1835
+ const file = session.sessionManager?.getSessionFile();
1836
+ if (file && (file === dir || file.startsWith(prefix))) {
1837
+ try {
1838
+ session.dispose();
1839
+ } catch {
1840
+ }
1841
+ this.sessions.delete(sid);
1842
+ void this.options.bus.emit("session.closed", { type: "session.closed", sessionId: sid }).catch(() => {
1843
+ });
1844
+ }
1845
+ }
1846
+ process.stderr.write(`vagus: sessions closed, removing dir...
1847
+ `);
1848
+ this.rmDirSafe(dir);
1849
+ process.stderr.write(`vagus: archived dir removed OK
1850
+ `);
1777
1851
  }
1778
1852
  /** Permanently deletes an archived project's session dir (JSONL). */
1779
1853
  async deleteArchivedProject(cwd) {
1780
1854
  const agentDir = this.agentDirPath();
1781
1855
  const dir = join5(agentDir, "archived", this.encodeCwd(cwd));
1856
+ process.stderr.write(`vagus: deleteArchivedProject cwd=${cwd} dir=${dir}
1857
+ `);
1782
1858
  this.closeSessionForCwd(cwd);
1783
- rmSync(dir, { recursive: true, force: true });
1859
+ process.stderr.write(`vagus: sessions closed, removing dir...
1860
+ `);
1861
+ this.rmDirSafe(dir);
1862
+ process.stderr.write(`vagus: archived dir removed OK
1863
+ `);
1864
+ }
1865
+ /**
1866
+ * rmSync(recursive) has crashed the daemon natively (0xC0000409 fastfail)
1867
+ * on Windows when deleting archived dirs with non-ASCII names — JS guards
1868
+ * cannot catch a native abort. Delete file-by-file instead: plain unlink
1869
+ * calls are individually catchable and skip whatever is locked/unreadable.
1870
+ *
1871
+ * Iterative (explicit stack, deepest-first) — no JS recursion limit, so
1872
+ * arbitrarily deep trees are safe.
1873
+ */
1874
+ rmDirSafe(root) {
1875
+ const pendingDirs = [{ path: root, expanded: false }];
1876
+ while (pendingDirs.length > 0) {
1877
+ const top = pendingDirs[pendingDirs.length - 1];
1878
+ if (top.expanded) {
1879
+ pendingDirs.pop();
1880
+ try {
1881
+ rmdirSync(top.path);
1882
+ } catch (err) {
1883
+ process.stderr.write(`vagus: rmDirSafe rmdir failed ${top.path}: ${err instanceof Error ? err.message : String(err)}
1884
+ `);
1885
+ }
1886
+ continue;
1887
+ }
1888
+ let entries;
1889
+ try {
1890
+ entries = readdirSync2(top.path);
1891
+ } catch {
1892
+ pendingDirs.pop();
1893
+ continue;
1894
+ }
1895
+ top.expanded = true;
1896
+ for (const entry of entries) {
1897
+ const full = join5(top.path, entry);
1898
+ try {
1899
+ if (statSync(full).isDirectory()) {
1900
+ pendingDirs.push({ path: full, expanded: false });
1901
+ } else {
1902
+ unlinkSync(full);
1903
+ }
1904
+ } catch (err) {
1905
+ process.stderr.write(`vagus: rmDirSafe skipped ${full}: ${err instanceof Error ? err.message : String(err)}
1906
+ `);
1907
+ }
1908
+ }
1909
+ }
1784
1910
  }
1785
1911
  /** Lists archived projects with their sessions (scans the `archived/` dir). */
1912
+ /** Lists archived projects with their sessions (scans the `archived/` dir).
1913
+ * dirKey is the encoded directory name — the unique identity of an archived
1914
+ * group (cwd from session headers can repeat across dirs). */
1786
1915
  async listArchivedProjects() {
1787
1916
  const agentDir = this.agentDirPath();
1788
1917
  const root = join5(agentDir, "archived");
@@ -1800,6 +1929,7 @@ var VagusEngine = class {
1800
1929
  continue;
1801
1930
  out.push({
1802
1931
  cwd,
1932
+ dirKey: entry,
1803
1933
  sessions: infos.map((i) => ({
1804
1934
  id: i.id,
1805
1935
  path: i.path,
@@ -1882,6 +2012,54 @@ var VagusEngine = class {
1882
2012
  const result = await session.navigateTree(targetId, options);
1883
2013
  return { editorText: result.editorText, cancelled: result.cancelled };
1884
2014
  }
2015
+ /**
2016
+ * Creates a new session forked from a specific user message.
2017
+ * The new session contains only the conversation up to and including that
2018
+ * user message — everything after it is dropped. The original session is
2019
+ * untouched. Returns the new session's info (sessionId, sessionFile, cwd).
2020
+ */
2021
+ async forkSession(sessionId, entryId) {
2022
+ const session = this.requireSession(sessionId);
2023
+ const sourceFile = session.sessionManager?.getSessionFile();
2024
+ if (!sourceFile)
2025
+ throw new Error("source session has no file path");
2026
+ if (!existsSync3(sourceFile))
2027
+ throw new Error(`source session file not found: ${sourceFile}`);
2028
+ const raw = readFileSync4(sourceFile, "utf8");
2029
+ const lines = raw.split("\n").filter((l) => l.trim().length > 0);
2030
+ const entries = lines.map((l) => JSON.parse(l));
2031
+ const header = entries[0];
2032
+ if (!header || header.type !== "session")
2033
+ throw new Error("source session has no header");
2034
+ const targetIdx = entries.findIndex((e) => e.id === entryId);
2035
+ if (targetIdx < 0)
2036
+ throw new Error(`entry ${entryId} not found in source session`);
2037
+ let endIdx = targetIdx - 1;
2038
+ if (endIdx < 1)
2039
+ endIdx = 1;
2040
+ const cwd = session.sessionManager.getCwd() || this.options.cwd;
2041
+ const sessionDir = dirname3(sourceFile);
2042
+ mkdirSync4(sessionDir, { recursive: true });
2043
+ const newSessionId = randomUUID2();
2044
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2045
+ const fileTimestamp = timestamp.replace(/[:.]/g, "-");
2046
+ const newSessionFile = join5(sessionDir, `${fileTimestamp}_${newSessionId}.jsonl`);
2047
+ const newHeader = {
2048
+ type: "session",
2049
+ version: header.version ?? 1,
2050
+ id: newSessionId,
2051
+ timestamp,
2052
+ cwd,
2053
+ parentSession: sourceFile
2054
+ };
2055
+ const outLines = [JSON.stringify(newHeader)];
2056
+ for (let i = 1; i <= endIdx; i++) {
2057
+ outLines.push(JSON.stringify(entries[i]));
2058
+ }
2059
+ writeFileSync3(newSessionFile, outLines.join("\n") + "\n", "utf8");
2060
+ const manager = SessionManager2.open(newSessionFile);
2061
+ return this.startSession({ cwd, sessionManager: manager });
2062
+ }
1885
2063
  /**
1886
2064
  * Exports the active branch to JSONL (/export). Returns the output path.
1887
2065
  */
@@ -6557,7 +6735,7 @@ import { WebSocketServer as WsServer } from "ws";
6557
6735
 
6558
6736
  // packages/host/rpc/dist/static-server.js
6559
6737
  import { createReadStream, existsSync as existsSync4, statSync as statSync2 } from "node:fs";
6560
- import { extname, join as join6, resolve as resolve2, sep } from "node:path";
6738
+ import { extname, join as join6, resolve as resolve2, sep as sep2 } from "node:path";
6561
6739
  var MIME_TYPES = {
6562
6740
  ".html": "text/html; charset=utf-8",
6563
6741
  ".js": "text/javascript; charset=utf-8",
@@ -6576,7 +6754,7 @@ function resolveStaticFile(rootDir, pathname) {
6576
6754
  const root = resolve2(rootDir);
6577
6755
  const name = pathname === "/" ? "index.html" : pathname;
6578
6756
  const candidate = resolve2(join6(root, name));
6579
- if (candidate !== root && !candidate.startsWith(root + sep)) {
6757
+ if (candidate !== root && !candidate.startsWith(root + sep2)) {
6580
6758
  return void 0;
6581
6759
  }
6582
6760
  if (existsSync4(candidate)) {
@@ -6901,6 +7079,28 @@ function requireString(value, label) {
6901
7079
  }
6902
7080
  return value;
6903
7081
  }
7082
+ function applyHttpProxy() {
7083
+ try {
7084
+ let proxy;
7085
+ const settingsFile = join8(piAgentDir(), "settings.json");
7086
+ if (existsSync6(settingsFile)) {
7087
+ const s = JSON.parse(readFileSync6(settingsFile, "utf8"));
7088
+ if (typeof s.httpProxy === "string" && s.httpProxy.trim()) proxy = s.httpProxy.trim();
7089
+ }
7090
+ proxy ??= process.env.HTTPS_PROXY ?? process.env.HTTP_PROXY;
7091
+ if (proxy) {
7092
+ const normalized = /^https?:\/\//.test(proxy) ? proxy : `http://${proxy}`;
7093
+ process.env.HTTPS_PROXY = normalized;
7094
+ process.env.HTTP_PROXY = normalized;
7095
+ setGlobalDispatcher(new EnvHttpProxyAgent());
7096
+ process.stderr.write(`vagus: http proxy enabled \u2192 ${normalized}
7097
+ `);
7098
+ }
7099
+ } catch (err) {
7100
+ process.stderr.write(`vagus: proxy setup skipped: ${err instanceof Error ? err.message : String(err)}
7101
+ `);
7102
+ }
7103
+ }
6904
7104
  function probeRequestFor(apiType, url, apiKey, modelId) {
6905
7105
  const headers = { "Content-Type": "application/json" };
6906
7106
  const base = url.replace(/\/chat\/completions$/, "");
@@ -6934,6 +7134,7 @@ function builtinExtensionPaths() {
6934
7134
  return out;
6935
7135
  }
6936
7136
  async function runDaemon() {
7137
+ applyHttpProxy();
6937
7138
  const stateDir = defaultStateDir();
6938
7139
  mkdirSync6(stateDir, { recursive: true });
6939
7140
  const config = new ConfigStore({ dir: stateDir });
@@ -6992,19 +7193,21 @@ async function runDaemon() {
6992
7193
  return host.archiveProject(cwd);
6993
7194
  });
6994
7195
  srv.registerMethod("project.unarchive", (params) => {
6995
- const cwd = requireString(params?.cwd, "cwd");
6996
- return host.restoreProject(cwd);
7196
+ const { cwd, dirKey } = params ?? {};
7197
+ if (typeof dirKey === "string" && dirKey) return host.unarchiveProjectByDir(dirKey);
7198
+ return host.restoreProject(requireString(cwd, "cwd"));
6997
7199
  });
6998
7200
  srv.registerMethod("project.archived", () => host.listArchivedProjects());
6999
7201
  srv.registerMethod("project.delete", (params) => {
7000
- const cwd = requireString(params?.cwd, "cwd");
7001
- return host.deleteArchivedProject(cwd);
7202
+ const { cwd, dirKey } = params ?? {};
7203
+ if (typeof dirKey === "string" && dirKey) return host.deleteArchivedProjectByDir(dirKey);
7204
+ return host.deleteArchivedProject(requireString(cwd, "cwd"));
7002
7205
  });
7003
7206
  srv.registerMethod("project.roots", () => {
7004
7207
  const home = homedir2();
7005
7208
  const places = [
7006
7209
  { name: "Home", path: home, isDirectory: true },
7007
- { name: "Root", path: sep2, isDirectory: true }
7210
+ { name: "Root", path: sep3, isDirectory: true }
7008
7211
  ];
7009
7212
  const candidates = ["Desktop", "Downloads", "Documents", "Pictures", "Music", "Videos"];
7010
7213
  for (const name of candidates) {
@@ -7377,6 +7580,10 @@ async function runDaemon() {
7377
7580
  const { sessionId } = params ?? {};
7378
7581
  return host.listForkPoints(requireString(sessionId, "sessionId"));
7379
7582
  });
7583
+ srv.registerMethod("session.fork", async (params) => {
7584
+ const { sessionId, entryId } = params ?? {};
7585
+ return host.forkSession(requireString(sessionId, "sessionId"), requireString(entryId, "entryId"));
7586
+ });
7380
7587
  srv.registerMethod("session.tree", (params) => {
7381
7588
  const { sessionId } = params ?? {};
7382
7589
  return host.getSessionTree(requireString(sessionId, "sessionId"));
@@ -7479,9 +7686,12 @@ async function runDaemon() {
7479
7686
  wsHost.broadcast(event);
7480
7687
  });
7481
7688
  let shuttingDown = false;
7482
- const shutdown = () => {
7689
+ const shutdown = (signal) => {
7483
7690
  if (shuttingDown) return;
7484
7691
  shuttingDown = true;
7692
+ process.stderr.write(`vagus: daemon shutting down (${signal})
7693
+ ${new Error("shutdown trace").stack}
7694
+ `);
7485
7695
  void (async () => {
7486
7696
  try {
7487
7697
  wsHost.close();
@@ -7492,11 +7702,27 @@ async function runDaemon() {
7492
7702
  }
7493
7703
  })();
7494
7704
  };
7495
- process.on("SIGINT", shutdown);
7496
- process.on("SIGTERM", shutdown);
7705
+ process.on("SIGINT", () => shutdown("SIGINT"));
7706
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
7707
+ process.on("beforeExit", (code) => {
7708
+ process.stderr.write(`vagus: daemon beforeExit (event loop empty), code=${code}
7709
+ `);
7710
+ });
7711
+ process.on("exit", (code) => {
7712
+ process.stderr.write(`vagus: daemon exit, code=${code}
7713
+ `);
7714
+ });
7497
7715
  transport.start();
7498
7716
  process.stderr.write(`pi-web daemon ready (state: ${stateDir})
7499
7717
  `);
7718
+ process.on("uncaughtException", (err) => {
7719
+ process.stderr.write(`vagus: uncaught exception: ${err?.stack ?? String(err)}
7720
+ `);
7721
+ });
7722
+ process.on("unhandledRejection", (reason) => {
7723
+ process.stderr.write(`vagus: unhandled rejection: ${reason instanceof Error ? reason.stack ?? reason.message : String(reason)}
7724
+ `);
7725
+ });
7500
7726
  return new Promise(() => {
7501
7727
  });
7502
7728
  }
@@ -7509,8 +7735,38 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
7509
7735
 
7510
7736
  // apps/cli/src/daemon.ts
7511
7737
  import { spawn } from "node:child_process";
7738
+ import { execSync } from "node:child_process";
7512
7739
  import { existsSync as existsSync7 } from "node:fs";
7513
7740
  import { fileURLToPath as fileURLToPath2 } from "node:url";
7741
+ function detectSystemProxy() {
7742
+ try {
7743
+ if (process.platform === "win32") {
7744
+ const key = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
7745
+ const out = execSync(`reg query "${key}" /v ProxyEnable`, { encoding: "utf8", timeout: 2e3 });
7746
+ if (!/0x1/.test(out)) return void 0;
7747
+ const srv = execSync(`reg query "${key}" /v ProxyServer`, { encoding: "utf8", timeout: 2e3 });
7748
+ const m = srv.match(/ProxyServer\s+REG_SZ\s+(\S+)/);
7749
+ if (!m?.[1]) return void 0;
7750
+ let addr;
7751
+ if (m[1].includes("=")) {
7752
+ const https = m[1].split(";").find((p) => p.startsWith("https="));
7753
+ addr = (https ?? m[1].split(";")[0])?.split("=")[1] || void 0;
7754
+ } else {
7755
+ addr = m[1];
7756
+ }
7757
+ return addr && /^https?:\/\//.test(addr) ? addr : addr ? `http://${addr}` : void 0;
7758
+ }
7759
+ if (process.platform === "darwin") {
7760
+ const out = execSync("scutil --proxy", { encoding: "utf8", timeout: 2e3 });
7761
+ const enabled = /HTTPSEnable\s*:\s*1/.test(out);
7762
+ const host = out.match(/HTTPSProxy\s*:\s*(\S+)/)?.[1];
7763
+ const port = out.match(/HTTPSPort\s*:\s*(\d+)/)?.[1];
7764
+ return enabled && host && port ? `http://${host}:${port}` : void 0;
7765
+ }
7766
+ } catch {
7767
+ }
7768
+ return void 0;
7769
+ }
7514
7770
  function daemonEntryPath() {
7515
7771
  const base = fileURLToPath2(new URL("./bin", import.meta.url));
7516
7772
  const source = `${base}.ts`;
@@ -7519,9 +7775,27 @@ function daemonEntryPath() {
7519
7775
  function spawnDaemon(options = {}) {
7520
7776
  const entry = daemonEntryPath();
7521
7777
  const args = entry.endsWith(".ts") ? ["--import", "tsx", entry, "daemon"] : [entry, "daemon"];
7778
+ const injected = {};
7779
+ injected.NODE_USE_ENV_PROXY = "1";
7780
+ if (!process.env.HTTPS_PROXY && !process.env.HTTP_PROXY) {
7781
+ const sysProxy = detectSystemProxy();
7782
+ if (sysProxy) {
7783
+ injected.HTTPS_PROXY = sysProxy;
7784
+ injected.HTTP_PROXY = sysProxy;
7785
+ injected.NO_PROXY = process.env.NO_PROXY ?? "localhost,127.0.0.1";
7786
+ process.stderr.write(`vagus: following system proxy \u2192 ${sysProxy}
7787
+ `);
7788
+ } else {
7789
+ process.stderr.write(`vagus: no system proxy detected (direct connections)
7790
+ `);
7791
+ }
7792
+ } else if (process.env.HTTPS_PROXY || process.env.HTTP_PROXY) {
7793
+ process.stderr.write(`vagus: proxy env already set (HTTPS_PROXY=${process.env.HTTPS_PROXY ?? process.env.HTTP_PROXY})
7794
+ `);
7795
+ }
7522
7796
  return spawn(process.execPath, args, {
7523
7797
  stdio: options.stdio ?? ["pipe", "pipe", "pipe"],
7524
- env: { ...process.env, ...options.env }
7798
+ env: { ...process.env, ...injected, ...options.env }
7525
7799
  });
7526
7800
  }
7527
7801
 
@@ -7574,6 +7848,9 @@ async function runWeb() {
7574
7848
  child.on("exit", (code, signal) => {
7575
7849
  if (!opened && code !== 0) {
7576
7850
  process.stderr.write(`pi-web: daemon exited (code ${code ?? "?"}, signal ${signal ?? "none"}) before the UI was ready.
7851
+ `);
7852
+ } else {
7853
+ process.stderr.write(`pi-web: daemon exited (code ${code ?? "?"}, signal ${signal ?? "none"}).
7577
7854
  `);
7578
7855
  }
7579
7856
  process.exit(code ?? 0);