agent-yes 1.115.0 → 1.116.0

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.
@@ -1,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-B62nAAfY.js";
1
+ import { t as CLIS_CONFIG } from "./ts-DvnOmOAf.js";
2
2
 
3
3
  //#region ts/SUPPORTED_CLIS.ts
4
4
  const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
5
5
 
6
6
  //#endregion
7
7
  export { SUPPORTED_CLIS as t };
8
- //# sourceMappingURL=SUPPORTED_CLIS-VayLM5qX.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-DuCEAzP9.js.map
@@ -0,0 +1,8 @@
1
+ import "./ts-DvnOmOAf.js";
2
+ import "./logger-B9h0djqx.js";
3
+ import "./versionChecker-DeXuTfJ0.js";
4
+ import "./pidStore-DBjlqzo8.js";
5
+ import "./globalPidIndex-yVd3mbsV.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DuCEAzP9.js";
7
+
8
+ export { SUPPORTED_CLIS };
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  import { n as logger } from "./logger-B9h0djqx.js";
3
- import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-BdkE7S2A.js";
3
+ import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-DeXuTfJ0.js";
4
4
  import { argv } from "process";
5
5
  import { execFileSync, spawn } from "child_process";
6
6
  import ms from "ms";
@@ -482,7 +482,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
482
482
  {
483
483
  const rawArg = process.argv[2];
484
484
  const isHelpFlag = rawArg === "-h" || rawArg === "--help";
485
- const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-SOHKtDbk.js");
485
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-CxHjPXEH.js");
486
486
  if (isHelpFlag && process.argv.length === 3) {
487
487
  cmdHelp();
488
488
  process.exit(0);
@@ -515,7 +515,7 @@ if (config.useRust) {
515
515
  }
516
516
  }
517
517
  if (rustBinary) {
518
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-DUVB1HyL.js");
518
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-vFwB3bnK.js");
519
519
  const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
520
520
  if (config.verbose) {
521
521
  console.log(`[rust] Using binary: ${rustBinary}`);
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-B62nAAfY.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-DvnOmOAf.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-BdkE7S2A.js";
3
+ import "./versionChecker-DeXuTfJ0.js";
4
4
  import "./pidStore-DBjlqzo8.js";
5
5
  import "./globalPidIndex-yVd3mbsV.js";
6
6
 
@@ -1,11 +1,11 @@
1
- import "./ts-B62nAAfY.js";
1
+ import "./ts-DvnOmOAf.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-BdkE7S2A.js";
3
+ import { r as getInstalledPackage } from "./versionChecker-DeXuTfJ0.js";
4
4
  import "./pidStore-DBjlqzo8.js";
5
5
  import "./globalPidIndex-yVd3mbsV.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-VayLM5qX.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DuCEAzP9.js";
7
7
  import "./remotes-C3xPRtfg.js";
8
- import { c as listRecords, d as renderRawLog, f as resolveOne, g as writeToIpc, m as snapshotStatus, r as controlCodeFromName, u as readNotes } from "./subcommands-t1uOb17r.js";
8
+ import { c as listRecords, d as renderRawLog, f as resolveOne, g as writeToIpc, m as snapshotStatus, r as controlCodeFromName, u as readNotes } from "./subcommands-DLJqD_Yj.js";
9
9
  import yargs from "yargs";
10
10
  import { mkdir, open, readFile, writeFile } from "fs/promises";
11
11
  import { homedir, hostname, userInfo } from "os";
@@ -52,6 +52,68 @@ const defaultOpts = (overrides = {}) => ({
52
52
  ...overrides
53
53
  });
54
54
  const DAEMON_NAME = "agent-yes";
55
+ async function ensureBootAutostart(oxmgrBin) {
56
+ try {
57
+ return await Bun.spawn([
58
+ oxmgrBin,
59
+ "service",
60
+ "install"
61
+ ], { stdio: [
62
+ "ignore",
63
+ "ignore",
64
+ "ignore"
65
+ ] }).exited === 0;
66
+ } catch {
67
+ return false;
68
+ }
69
+ }
70
+ async function spawnExit(cmd) {
71
+ try {
72
+ return await Bun.spawn(cmd, { stdio: [
73
+ "ignore",
74
+ "ignore",
75
+ "ignore"
76
+ ] }).exited ?? 1;
77
+ } catch {
78
+ return 1;
79
+ }
80
+ }
81
+ async function readDaemonServeArgs(oxmgrBin) {
82
+ try {
83
+ const p = Bun.spawn([
84
+ oxmgrBin,
85
+ "status",
86
+ DAEMON_NAME
87
+ ], {
88
+ stdout: "pipe",
89
+ stderr: "ignore"
90
+ });
91
+ const out = await new Response(p.stdout).text();
92
+ if (await p.exited !== 0) return null;
93
+ const m = /Command:\s*(.+)/.exec(out);
94
+ if (!m) return null;
95
+ const after = /\bserve\b\s*(.*)$/.exec(m[1].trim());
96
+ return after ? after[1].split(/\s+/).filter(Boolean) : [];
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+ function portFromArgs(args) {
102
+ const m = /--port[=\s](\d+)/.exec(args.join(" "));
103
+ return m ? Number(m[1]) : DEFAULT_PORT;
104
+ }
105
+ async function fetchDaemonVersion(port, token) {
106
+ try {
107
+ const r = await fetch(`http://127.0.0.1:${port}/api/version`, {
108
+ headers: { Authorization: `Bearer ${token}` },
109
+ signal: AbortSignal.timeout(3e3)
110
+ });
111
+ if (!r.ok) return null;
112
+ return (await r.json()).version ?? null;
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
55
117
  async function cmdServeDaemon(sub, args) {
56
118
  const oxmgrBin = Bun.which("oxmgr");
57
119
  if (!oxmgrBin) {
@@ -60,11 +122,33 @@ async function cmdServeDaemon(sub, args) {
60
122
  }
61
123
  if (sub === "install") {
62
124
  const token = await loadOrCreateToken(void 0);
125
+ const priorArgs = await readDaemonServeArgs(oxmgrBin);
126
+ const effArgs = args.length ? args : priorArgs ?? [];
127
+ const current = getInstalledPackage().version;
128
+ if (priorArgs !== null) {
129
+ const runningVer = await fetchDaemonVersion(portFromArgs(effArgs), token);
130
+ if (runningVer === current) {
131
+ await ensureBootAutostart(oxmgrBin);
132
+ process.stdout.write(`'${DAEMON_NAME}' already running v${current} (up to date)\n`);
133
+ return 0;
134
+ }
135
+ process.stdout.write(`rolling '${DAEMON_NAME}' ${runningVer ? `v${runningVer}` : "(unknown)"} → v${current}…\n`);
136
+ await spawnExit([
137
+ oxmgrBin,
138
+ "stop",
139
+ DAEMON_NAME
140
+ ]);
141
+ await spawnExit([
142
+ oxmgrBin,
143
+ "delete",
144
+ DAEMON_NAME
145
+ ]);
146
+ }
63
147
  const ayBin = Bun.which("ay");
64
148
  const serveCmd = [
65
149
  ...ayBin ? [process.execPath, ayBin] : ["ay"],
66
150
  "serve",
67
- ...args
151
+ ...effArgs
68
152
  ].join(" ");
69
153
  const code = await Bun.spawn([
70
154
  oxmgrBin,
@@ -80,11 +164,12 @@ async function cmdServeDaemon(sub, args) {
80
164
  "inherit"
81
165
  ] }).exited;
82
166
  if (code === 0) {
83
- const portM = /--port[=\s](\d+)/.exec(args.join(" "));
84
- const port = portM ? Number(portM[1]) : DEFAULT_PORT;
85
- const webrtcish = args.some((a) => a.startsWith("--webrtc") || a.startsWith("--share"));
86
- const httpish = args.some((a) => a.startsWith("--http") || a.startsWith("--share")) || !args.some((a) => a.startsWith("--webrtc"));
87
- process.stdout.write(`\ninstalled '${DAEMON_NAME}' as a daemon via oxmgr\n`);
167
+ const onBoot = await ensureBootAutostart(oxmgrBin);
168
+ const port = portFromArgs(effArgs);
169
+ const webrtcish = effArgs.some((a) => a.startsWith("--webrtc") || a.startsWith("--share"));
170
+ const httpish = effArgs.some((a) => a.startsWith("--http") || a.startsWith("--share")) || !effArgs.some((a) => a.startsWith("--webrtc"));
171
+ process.stdout.write(`\n${priorArgs !== null ? `rolled '${DAEMON_NAME}' forward to` : `installed '${DAEMON_NAME}' as a daemon via oxmgr —`} v${current}\n`);
172
+ process.stdout.write(onBoot ? `start-on-boot: enabled (oxmgr registered with the system init)\n` : `start-on-boot: not registered — run \`oxmgr service install\` to enable\n`);
88
173
  process.stdout.write(`token: ${token}\n\n`);
89
174
  if (httpish) {
90
175
  process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
@@ -372,6 +457,7 @@ Options:
372
457
  const host = hostname();
373
458
  return Response.json({ host: user ? `${user}@${host}` : host });
374
459
  }
460
+ if (req.method === "GET" && p === "/api/version") return Response.json({ version: getInstalledPackage().version });
375
461
  if (req.method === "GET" && p === "/api/notes") {
376
462
  const notes = await readNotes();
377
463
  return Response.json(Object.fromEntries(notes));
@@ -687,4 +773,4 @@ Options:
687
773
 
688
774
  //#endregion
689
775
  export { cmdServe };
690
- //# sourceMappingURL=serve-CP61tKuJ.js.map
776
+ //# sourceMappingURL=serve-CSupgu7Q.js.map
@@ -1,6 +1,6 @@
1
1
  import "./logger-B9h0djqx.js";
2
2
  import "./globalPidIndex-yVd3mbsV.js";
3
3
  import "./remotes-C3xPRtfg.js";
4
- import { a as finalizedLines, c as listRecords, d as renderRawLog, f as resolveOne, g as writeToIpc, h as stopTipForCli, i as cursorAbs, l as matchKeyword, m as snapshotStatus, n as cmdHelp, o as isPidAlive, p as runSubcommand, r as controlCodeFromName, s as isSubcommand, t as GRACEFUL_EXIT_COMMANDS, u as readNotes } from "./subcommands-t1uOb17r.js";
4
+ import { a as finalizedLines, c as listRecords, d as renderRawLog, f as resolveOne, g as writeToIpc, h as stopTipForCli, i as cursorAbs, l as matchKeyword, m as snapshotStatus, n as cmdHelp, o as isPidAlive, p as runSubcommand, r as controlCodeFromName, s as isSubcommand, t as GRACEFUL_EXIT_COMMANDS, u as readNotes } from "./subcommands-DLJqD_Yj.js";
5
5
 
6
6
  export { cmdHelp, isSubcommand, runSubcommand };
@@ -163,7 +163,7 @@ async function runSubcommand(argv) {
163
163
  case "restart": return await cmdRestart(rest);
164
164
  case "note": return await cmdNote(rest);
165
165
  case "serve": {
166
- const { cmdServe } = await import("./serve-CP61tKuJ.js");
166
+ const { cmdServe } = await import("./serve-CSupgu7Q.js");
167
167
  return cmdServe(rest);
168
168
  }
169
169
  case "setup": {
@@ -1595,4 +1595,4 @@ async function cmdStatus(rest) {
1595
1595
 
1596
1596
  //#endregion
1597
1597
  export { finalizedLines as a, listRecords as c, renderRawLog as d, resolveOne as f, writeToIpc as g, stopTipForCli as h, cursorAbs as i, matchKeyword as l, snapshotStatus as m, cmdHelp as n, isPidAlive as o, runSubcommand as p, controlCodeFromName as r, isSubcommand as s, GRACEFUL_EXIT_COMMANDS as t, readNotes as u };
1598
- //# sourceMappingURL=subcommands-t1uOb17r.js.map
1598
+ //# sourceMappingURL=subcommands-DLJqD_Yj.js.map
@@ -1,5 +1,5 @@
1
1
  import { n as logger, t as addTransport } from "./logger-B9h0djqx.js";
2
- import { r as getInstalledPackage } from "./versionChecker-BdkE7S2A.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-DeXuTfJ0.js";
3
3
  import { n as agentYesHome, t as PidStore } from "./pidStore-DBjlqzo8.js";
4
4
  import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-CJxsoGdb.js";
5
5
  import { i as readGlobalPids } from "./globalPidIndex-yVd3mbsV.js";
@@ -10,7 +10,7 @@ import { mkdir, readFile, readdir, unlink, writeFile } from "fs/promises";
10
10
  import { homedir } from "os";
11
11
  import path, { dirname, join } from "path";
12
12
  import { fileURLToPath } from "url";
13
- import { execaCommandSync, parseCommandString } from "execa";
13
+ import { execaCommandSync, execaSync, parseCommandString } from "execa";
14
14
  import { fromReadable, fromWritable } from "from-node-stream";
15
15
  import DIE from "phpdie";
16
16
  import sflow from "sflow";
@@ -378,11 +378,10 @@ async function createLinuxFifo(cli, customPath) {
378
378
  logger.warn(`[${cli}-yes] Failed to create FIFO directory: ${dirError}`);
379
379
  return null;
380
380
  }
381
- const escapedPath = fifoPath.replace(/'/g, "'\"'\"'");
382
- const mkfifoResult = execaCommandSync(`mkfifo '${escapedPath}'`, { reject: false });
381
+ const mkfifoResult = execaSync("mkfifo", [fifoPath], { reject: false });
383
382
  if (mkfifoResult.exitCode !== 0) {
384
383
  logger.warn(`[${cli}-yes] mkfifo command failed with exit code ${mkfifoResult.exitCode}`);
385
- logger.warn(`[${cli}-yes] Command: mkfifo '${escapedPath}'`);
384
+ logger.warn(`[${cli}-yes] Command: mkfifo ${fifoPath}`);
386
385
  if (mkfifoResult.stderr) logger.warn(`[${cli}-yes] mkfifo stderr: ${mkfifoResult.stderr}`);
387
386
  if (mkfifoResult.stdout) logger.warn(`[${cli}-yes] mkfifo stdout: ${mkfifoResult.stdout}`);
388
387
  return null;
@@ -1714,4 +1713,4 @@ function sleep(ms) {
1714
1713
 
1715
1714
  //#endregion
1716
1715
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1717
- //# sourceMappingURL=ts-B62nAAfY.js.map
1716
+ //# sourceMappingURL=ts-DvnOmOAf.js.map
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
7
7
 
8
8
  //#region package.json
9
9
  var name = "agent-yes";
10
- var version = "1.115.0";
10
+ var version = "1.116.0";
11
11
 
12
12
  //#endregion
13
13
  //#region ts/versionChecker.ts
@@ -221,4 +221,4 @@ async function displayVersion() {
221
221
 
222
222
  //#endregion
223
223
  export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
224
- //# sourceMappingURL=versionChecker-BdkE7S2A.js.map
224
+ //# sourceMappingURL=versionChecker-DeXuTfJ0.js.map
package/lab/ui/index.html CHANGED
@@ -1618,10 +1618,11 @@
1618
1618
  });
1619
1619
  // Adapt: drive the agent's PTY to the browser terminal size (POST
1620
1620
  // /api/resize → winsize + SIGWINCH) so its TUI reflows to match what we
1621
- // render. Suppressed while we're merely adopting the agent's OWN size.
1622
- let adoptingAgentSize = false;
1621
+ // render. Suppressed during the initial fit below, which does its own
1622
+ // one-shot "push only if the agent is out of sync" check.
1623
+ let suppressPush = true;
1623
1624
  const pushSize = () => {
1624
- if (term && sel === e._key && !adoptingAgentSize)
1625
+ if (term && sel === e._key && !suppressPush)
1625
1626
  tx.post("/api/resize/" + encodeURIComponent(pid), {
1626
1627
  cols: term.cols,
1627
1628
  rows: term.rows,
@@ -1642,28 +1643,32 @@
1642
1643
  };
1643
1644
  term.onData(fwd);
1644
1645
  term.onBinary(fwd);
1645
- // Render the existing buffer at the AGENT's current width first so its
1646
- // wrapping is correct, instead of forcing our viewport width onto stale
1647
- // content. The user adapts to the window by resizing it (fit push).
1646
+ // On switch, render at OUR pane size and make the agent match it: fit
1647
+ // xterm to the viewport, then if the agent's PTY winsize differs from
1648
+ // what we're now rendering push a resize so its TUI reflows to fill the
1649
+ // pane. The agent may have been sized by a different viewer/terminal, or
1650
+ // our window changed while this agent was in the background; either way a
1651
+ // mismatch leaves the TUI clipped or boxed-in until something nudges it.
1652
+ // Only push when it actually differs, so an already-matching agent isn't
1653
+ // poked with a redundant SIGWINCH. After this, live window resizes push
1654
+ // automatically (suppressPush flips off).
1648
1655
  const selKey = e._key;
1656
+ const fitAndSync = (sz) => {
1657
+ if (sel !== selKey || !term) return;
1658
+ try {
1659
+ fit.fit();
1660
+ } catch {}
1661
+ if (!sz || sz.cols !== term.cols || sz.rows !== term.rows) {
1662
+ tx.post("/api/resize/" + encodeURIComponent(pid), {
1663
+ cols: term.cols,
1664
+ rows: term.rows,
1665
+ }).catch(() => {});
1666
+ }
1667
+ suppressPush = false;
1668
+ };
1649
1669
  tx.fetchJSON("/api/size/" + encodeURIComponent(pid))
1650
- .then((sz) => {
1651
- if (sel !== selKey || !term) return;
1652
- if (sz && sz.cols && sz.rows) {
1653
- adoptingAgentSize = true;
1654
- term.resize(sz.cols, sz.rows);
1655
- adoptingAgentSize = false;
1656
- } else {
1657
- try {
1658
- fit.fit();
1659
- } catch {}
1660
- }
1661
- })
1662
- .catch(() => {
1663
- try {
1664
- fit.fit();
1665
- } catch {}
1666
- });
1670
+ .then(fitAndSync)
1671
+ .catch(() => fitAndSync(null));
1667
1672
 
1668
1673
  // True live tail via ay serve's SSE stream. First event is an xterm-rendered
1669
1674
  // tail snapshot; later events are incremental deltas. We normalise terminal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-yes",
3
- "version": "1.115.0",
3
+ "version": "1.116.0",
4
4
  "description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
5
5
  "keywords": [
6
6
  "ai",
package/ts/serve.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  type CommonOpts,
17
17
  } from "./subcommands.ts";
18
18
  import { SUPPORTED_CLIS } from "./SUPPORTED_CLIS.ts";
19
+ import { getInstalledPackage } from "./versionChecker.ts";
19
20
 
20
21
  const DEFAULT_PORT = 7432;
21
22
 
@@ -70,6 +71,71 @@ const defaultOpts = (overrides: Partial<CommonOpts> = {}): CommonOpts => ({
70
71
 
71
72
  const DAEMON_NAME = "agent-yes";
72
73
 
74
+ // Register the oxmgr daemon with the platform init system (launchd on macOS,
75
+ // systemd on Linux, Task Scheduler on Windows) so managed processes — including
76
+ // the agent-yes daemon — come back after a *reboot*, not just a crash. Idempotent:
77
+ // a no-op if the service is already installed. Best-effort: returns false on any
78
+ // failure (e.g. a system-level systemd unit that needs sudo) without aborting the
79
+ // install — the process is still managed, just not boot-persistent.
80
+ async function ensureBootAutostart(oxmgrBin: string): Promise<boolean> {
81
+ try {
82
+ // --system defaults to "auto" (launchd/systemd/Task Scheduler by platform);
83
+ // it's a `service`-level flag, so passing it after `install` is rejected.
84
+ const svc = Bun.spawn([oxmgrBin, "service", "install"], {
85
+ stdio: ["ignore", "ignore", "ignore"],
86
+ });
87
+ return (await svc.exited) === 0;
88
+ } catch {
89
+ return false;
90
+ }
91
+ }
92
+
93
+ async function spawnExit(cmd: string[]): Promise<number> {
94
+ try {
95
+ return (await Bun.spawn(cmd, { stdio: ["ignore", "ignore", "ignore"] }).exited) ?? 1;
96
+ } catch {
97
+ return 1;
98
+ }
99
+ }
100
+
101
+ // The `serve` args the running daemon was started with, parsed out of oxmgr's
102
+ // stored command line (`… ay serve --share --port 7433`). null when no daemon is
103
+ // registered. Lets a bare `ay serve install` re-launch with the SAME args.
104
+ async function readDaemonServeArgs(oxmgrBin: string): Promise<string[] | null> {
105
+ try {
106
+ const p = Bun.spawn([oxmgrBin, "status", DAEMON_NAME], { stdout: "pipe", stderr: "ignore" });
107
+ const out = await new Response(p.stdout).text();
108
+ if ((await p.exited) !== 0) return null;
109
+ const m = /Command:\s*(.+)/.exec(out);
110
+ if (!m) return null;
111
+ const after = /\bserve\b\s*(.*)$/.exec(m[1]!.trim());
112
+ return after ? after[1]!.split(/\s+/).filter(Boolean) : [];
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
117
+
118
+ function portFromArgs(args: string[]): number {
119
+ const m = /--port[=\s](\d+)/.exec(args.join(" "));
120
+ return m ? Number(m[1]) : DEFAULT_PORT;
121
+ }
122
+
123
+ // Ask the live daemon its version over the local HTTP API. null if it's not
124
+ // listening (webrtc-only) or too old to expose /api/version — both of which we
125
+ // treat as "outdated" so a re-install rolls it forward.
126
+ async function fetchDaemonVersion(port: number, token: string): Promise<string | null> {
127
+ try {
128
+ const r = await fetch(`http://127.0.0.1:${port}/api/version`, {
129
+ headers: { Authorization: `Bearer ${token}` },
130
+ signal: AbortSignal.timeout(3000),
131
+ });
132
+ if (!r.ok) return null;
133
+ return ((await r.json()) as { version?: string }).version ?? null;
134
+ } catch {
135
+ return null;
136
+ }
137
+ }
138
+
73
139
  async function cmdServeDaemon(sub: string, args: string[]): Promise<number> {
74
140
  const oxmgrBin = Bun.which("oxmgr");
75
141
  if (!oxmgrBin) {
@@ -83,25 +149,61 @@ async function cmdServeDaemon(sub: string, args: string[]): Promise<number> {
83
149
 
84
150
  if (sub === "install") {
85
151
  const token = await loadOrCreateToken(undefined);
152
+
153
+ // Re-running install rolls a stale daemon forward: reuse the args it was
154
+ // started with (so a bare `ay serve install` stays "the same daemon"), unless
155
+ // new args are given. The persisted room + token mean the share link is
156
+ // unchanged across the restart.
157
+ const priorArgs = await readDaemonServeArgs(oxmgrBin);
158
+ const effArgs = args.length ? args : (priorArgs ?? []);
159
+ const current = getInstalledPackage().version;
160
+
161
+ if (priorArgs !== null) {
162
+ // A daemon already exists — only disturb it if it's actually outdated.
163
+ const runningVer = await fetchDaemonVersion(portFromArgs(effArgs), token);
164
+ if (runningVer === current) {
165
+ await ensureBootAutostart(oxmgrBin);
166
+ process.stdout.write(`'${DAEMON_NAME}' already running v${current} (up to date)\n`);
167
+ return 0;
168
+ }
169
+ // Outdated (or unreachable/too-old to report) → graceful roll-forward.
170
+ // `oxmgr stop` sends SIGTERM, which cmdServe handles cleanly (closing share
171
+ // peers so browsers reconnect fast), then we re-create with the new binary.
172
+ process.stdout.write(
173
+ `rolling '${DAEMON_NAME}' ${runningVer ? `v${runningVer}` : "(unknown)"} → v${current}…\n`,
174
+ );
175
+ await spawnExit([oxmgrBin, "stop", DAEMON_NAME]);
176
+ await spawnExit([oxmgrBin, "delete", DAEMON_NAME]);
177
+ }
178
+
86
179
  // Build the ay serve command with forwarded args (port, host, --webrtc, etc.).
87
180
  // Absolute paths: oxmgr's daemon environment may not have ~/.bun/bin in
88
181
  // PATH, so a bare `ay` (or its `#!/usr/bin/env bun` shebang) fails to spawn.
89
182
  const ayBin = Bun.which("ay");
90
- const serveCmd = [...(ayBin ? [process.execPath, ayBin] : ["ay"]), "serve", ...args].join(" ");
183
+ const serveCmd = [...(ayBin ? [process.execPath, ayBin] : ["ay"]), "serve", ...effArgs].join(
184
+ " ",
185
+ );
91
186
  const proc = Bun.spawn(
92
187
  [oxmgrBin, "start", serveCmd, "--name", DAEMON_NAME, "--restart", "always"],
93
188
  { stdio: ["ignore", "inherit", "inherit"] },
94
189
  );
95
190
  const code = await proc.exited;
96
191
  if (code === 0) {
97
- const portM = /--port[=\s](\d+)/.exec(args.join(" "));
98
- const port = portM ? Number(portM[1]) : DEFAULT_PORT;
192
+ const onBoot = await ensureBootAutostart(oxmgrBin);
193
+ const port = portFromArgs(effArgs);
99
194
  // Mirror cmdServe's mode resolution: webrtc-only daemons open no HTTP port.
100
- const webrtcish = args.some((a) => a.startsWith("--webrtc") || a.startsWith("--share"));
195
+ const webrtcish = effArgs.some((a) => a.startsWith("--webrtc") || a.startsWith("--share"));
101
196
  const httpish =
102
- args.some((a) => a.startsWith("--http") || a.startsWith("--share")) ||
103
- !args.some((a) => a.startsWith("--webrtc"));
104
- process.stdout.write(`\ninstalled '${DAEMON_NAME}' as a daemon via oxmgr\n`);
197
+ effArgs.some((a) => a.startsWith("--http") || a.startsWith("--share")) ||
198
+ !effArgs.some((a) => a.startsWith("--webrtc"));
199
+ process.stdout.write(
200
+ `\n${priorArgs !== null ? `rolled '${DAEMON_NAME}' forward to` : `installed '${DAEMON_NAME}' as a daemon via oxmgr —`} v${current}\n`,
201
+ );
202
+ process.stdout.write(
203
+ onBoot
204
+ ? `start-on-boot: enabled (oxmgr registered with the system init)\n`
205
+ : `start-on-boot: not registered — run \`oxmgr service install\` to enable\n`,
206
+ );
105
207
  process.stdout.write(`token: ${token}\n\n`);
106
208
  if (httpish) {
107
209
  process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
@@ -486,6 +588,13 @@ export async function cmdServe(rest: string[]): Promise<number> {
486
588
  return Response.json({ host: user ? `${user}@${host}` : host });
487
589
  }
488
590
 
591
+ // GET /api/version — the running daemon's package version, so a re-run of
592
+ // `ay serve install` can tell whether the live daemon is stale and roll it
593
+ // forward. A daemon too old to expose this just 404s → treated as outdated.
594
+ if (req.method === "GET" && p === "/api/version") {
595
+ return Response.json({ version: getInstalledPackage().version });
596
+ }
597
+
489
598
  // GET /api/notes
490
599
  if (req.method === "GET" && p === "/api/notes") {
491
600
  const notes = await readNotes();
@@ -1,8 +0,0 @@
1
- import "./ts-B62nAAfY.js";
2
- import "./logger-B9h0djqx.js";
3
- import "./versionChecker-BdkE7S2A.js";
4
- import "./pidStore-DBjlqzo8.js";
5
- import "./globalPidIndex-yVd3mbsV.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-VayLM5qX.js";
7
-
8
- export { SUPPORTED_CLIS };