tono 0.3.1 → 0.3.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 CHANGED
@@ -85,19 +85,27 @@ which tono # → ~/.local/share/pnpm/tono (or your pnpm bin path)
85
85
  To upgrade later:
86
86
 
87
87
  ```bash
88
- pnpm add -g tono@latest
89
- tono gateway restart # pick up the new binary in the background daemon
88
+ tono upgrade # one-shot: install the latest version + restart any loaded daemons
90
89
  ```
91
90
 
92
- If you have remote workers, upgrade each worker machine the same way and
93
- restart the worker daemon:
91
+ `tono upgrade` auto-detects the package manager that installed the binary
92
+ (pnpm / npm / yarn / bun by inspecting the install path), runs the
93
+ equivalent of `<pm> add -g tono@latest`, and restarts whichever LaunchAgents
94
+ are currently loaded (`com.tono.gateway`, `com.tono.worker`). Pass
95
+ `--check` to see current vs latest without installing, `--no-restart` to
96
+ skip the daemon restart, or `--pm <name>` to force a specific package
97
+ manager.
98
+
99
+ You can still upgrade by hand if you prefer:
94
100
 
95
101
  ```bash
96
- # on each worker machine
97
102
  pnpm add -g tono@latest
98
- tono worker restart
103
+ tono gateway restart # pick up the new binary in the background daemon
99
104
  ```
100
105
 
106
+ If you have remote workers, run `tono upgrade` on each worker machine too
107
+ (or upgrade by hand and `tono worker restart`).
108
+
101
109
  DB migrations (gateway-side) run automatically on the next `tono start` /
102
110
  `tono gateway restart`. Tono backs up `~/.tono/tono.db` to
103
111
  `~/.tono/tono.db.bak.v<N>` before any version-stepped migration runs, so an
@@ -0,0 +1,288 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join, sep } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { VERSION } from "../../shared/version.js";
7
+ import { GATEWAY_LABEL, WORKER_LABEL } from "../launchd.js";
8
+ function tonoHomeDir() {
9
+ return process.env.TONO_HOME ?? join(homedir(), ".tono");
10
+ }
11
+ function installRecordPath() {
12
+ return join(tonoHomeDir(), "install.json");
13
+ }
14
+ function readInstallRecord() {
15
+ try {
16
+ return JSON.parse(readFileSync(installRecordPath(), "utf8"));
17
+ }
18
+ catch {
19
+ return {};
20
+ }
21
+ }
22
+ function writeInstallRecord(rec) {
23
+ try {
24
+ mkdirSync(tonoHomeDir(), { recursive: true });
25
+ writeFileSync(installRecordPath(), JSON.stringify(rec, null, 2) + "\n");
26
+ }
27
+ catch {
28
+ // Best-effort. If the home dir isn't writable we still want the upgrade
29
+ // itself to proceed.
30
+ }
31
+ }
32
+ const HELP = `tono upgrade
33
+
34
+ Self-upgrade tono to the latest version on npm. Detects the package manager
35
+ that installed this binary (pnpm / npm / yarn / bun) and runs the equivalent
36
+ of \`<pm> add -g tono@latest\`. After the new version is installed, restarts
37
+ the gateway and worker LaunchAgents (if loaded) so the daemons pick up the
38
+ new code.
39
+
40
+ The chosen package manager is persisted to ~/.tono/install.json after a
41
+ successful upgrade, so subsequent runs don't need to re-detect (and don't
42
+ get fooled if your PATH changes). Override with --pm to update the record.
43
+
44
+ Usage:
45
+ tono upgrade [--check] [--no-restart] [--pm <pnpm|npm|yarn|bun>]
46
+
47
+ Options:
48
+ --check Print current vs latest, don't install anything.
49
+ --no-restart Skip restarting com.tono.gateway / com.tono.worker after upgrade.
50
+ --pm <name> Force a specific package manager. Updates ~/.tono/install.json.
51
+ `;
52
+ function parseArgs(argv) {
53
+ const out = { check: false, restart: true, help: false };
54
+ for (let i = 0; i < argv.length; i++) {
55
+ const a = argv[i];
56
+ if (a === "--help" || a === "-h") {
57
+ out.help = true;
58
+ continue;
59
+ }
60
+ if (a === "--check") {
61
+ out.check = true;
62
+ continue;
63
+ }
64
+ if (a === "--no-restart") {
65
+ out.restart = false;
66
+ continue;
67
+ }
68
+ if (a === "--pm") {
69
+ const v = argv[++i];
70
+ if (!v || !["pnpm", "npm", "yarn", "bun"].includes(v)) {
71
+ throw new Error(`bad --pm value: ${v ?? "(missing)"}`);
72
+ }
73
+ out.forcePm = v;
74
+ continue;
75
+ }
76
+ throw new Error(`unknown argument: ${a}`);
77
+ }
78
+ return out;
79
+ }
80
+ function exec(cmd, args, opts = {}) {
81
+ const r = spawnSync(cmd, args, { encoding: "utf8", stdio: opts.stdio === "inherit" ? "inherit" : "pipe" });
82
+ return { code: r.status ?? -1, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
83
+ }
84
+ function commandOnPath(cmd) {
85
+ const r = spawnSync("command", ["-v", cmd], { encoding: "utf8", shell: "/bin/sh" });
86
+ return r.status === 0 && (r.stdout ?? "").trim().length > 0;
87
+ }
88
+ /**
89
+ * Detect the package manager that installed this `tono` by walking up the
90
+ * realpath of the entry script and looking for hints in the directory chain.
91
+ *
92
+ * Heuristics (in order):
93
+ * - `/Library/pnpm/`, `/.pnpm/`, `pnpm-store/` → pnpm
94
+ * - `/.bun/` → bun
95
+ * - `/yarn/global/`, `/.yarn/` → yarn
96
+ * - any `/node_modules/` ancestor → npm (default fallback)
97
+ */
98
+ function detectInstaller() {
99
+ const here = dirname(fileURLToPath(import.meta.url));
100
+ let p;
101
+ try {
102
+ p = realpathSync(here);
103
+ }
104
+ catch {
105
+ return { pm: null, reason: `couldn't resolve realpath of ${here}` };
106
+ }
107
+ // Bail if running from a source checkout (./src/cli/...). Detect by absence
108
+ // of a `node_modules/tono` ancestor or the presence of a top-level src dir.
109
+ if (p.includes(`${sep}src${sep}cli${sep}`)) {
110
+ return { pm: null, reason: "running from source checkout — install globally first, or use --pm to force a package manager" };
111
+ }
112
+ while (p.length > 1) {
113
+ const lower = p.toLowerCase();
114
+ if (lower.includes(`${sep}.pnpm${sep}`) || lower.includes(`${sep}pnpm${sep}`) || lower.includes(`${sep}library${sep}pnpm${sep}`)) {
115
+ return { pm: "pnpm", reason: `path contains pnpm: ${p}` };
116
+ }
117
+ if (lower.includes(`${sep}.bun${sep}`)) {
118
+ return { pm: "bun", reason: `path contains .bun: ${p}` };
119
+ }
120
+ if (lower.includes(`${sep}.yarn${sep}`) || lower.includes(`${sep}yarn${sep}global${sep}`)) {
121
+ return { pm: "yarn", reason: `path contains yarn: ${p}` };
122
+ }
123
+ const next = dirname(p);
124
+ if (next === p)
125
+ break;
126
+ p = next;
127
+ }
128
+ return { pm: "npm", reason: "no pm hints in path; defaulting to npm" };
129
+ }
130
+ function pmAddCmd(pm) {
131
+ switch (pm) {
132
+ case "pnpm":
133
+ return ["pnpm", "add", "-g", "tono@latest"];
134
+ case "yarn":
135
+ return ["yarn", "global", "add", "tono@latest"];
136
+ case "bun":
137
+ return ["bun", "add", "-g", "tono@latest"];
138
+ case "npm":
139
+ default:
140
+ return ["npm", "install", "-g", "tono@latest"];
141
+ }
142
+ }
143
+ async function fetchLatest() {
144
+ try {
145
+ const res = await fetch("https://registry.npmjs.org/tono/latest", {
146
+ signal: AbortSignal.timeout(8000),
147
+ headers: { accept: "application/json" },
148
+ });
149
+ if (!res.ok)
150
+ return null;
151
+ const j = (await res.json());
152
+ return j.version ?? null;
153
+ }
154
+ catch {
155
+ return null;
156
+ }
157
+ }
158
+ function entryPath() {
159
+ // dist/cli/commands/upgrade.js → ../index.js
160
+ const here = dirname(fileURLToPath(import.meta.url));
161
+ return `${dirname(here)}/index.js`;
162
+ }
163
+ function isLoaded(label) {
164
+ const r = spawnSync("launchctl", ["list", label], { encoding: "utf8" });
165
+ return r.status === 0;
166
+ }
167
+ function restartLaunchAgent(label, subcommand) {
168
+ // We invoke the freshly-installed `tono` binary via process.execPath +
169
+ // entry, NOT the live one we're running from — `pnpm add -g` may have
170
+ // replaced the global symlink. process.execPath is node; entryPath() is the
171
+ // dist/cli/index.js path, which on most package managers is replaced
172
+ // atomically (npm/pnpm/yarn) so the new code is now at the same path.
173
+ const entry = entryPath();
174
+ if (!existsSync(entry)) {
175
+ return { ok: false, output: `entry path missing: ${entry}` };
176
+ }
177
+ const r = spawnSync(process.execPath, [entry, subcommand, "restart"], {
178
+ encoding: "utf8",
179
+ });
180
+ void label;
181
+ return {
182
+ ok: r.status === 0,
183
+ output: ((r.stdout ?? "") + (r.stderr ?? "")).trim() || `${subcommand} restart`,
184
+ };
185
+ }
186
+ export async function runUpgrade(argv) {
187
+ let parsed;
188
+ try {
189
+ parsed = parseArgs(argv);
190
+ }
191
+ catch (err) {
192
+ console.error(err.message);
193
+ console.error("");
194
+ console.error(HELP);
195
+ process.exit(2);
196
+ }
197
+ if (parsed.help) {
198
+ console.log(HELP);
199
+ return;
200
+ }
201
+ process.stdout.write(`tono ${VERSION} (current)\n`);
202
+ const latest = await fetchLatest();
203
+ if (latest) {
204
+ process.stdout.write(`tono ${latest} (latest on npm)\n`);
205
+ }
206
+ else {
207
+ process.stdout.write(`tono ?.?.? (couldn't reach https://registry.npmjs.org)\n`);
208
+ }
209
+ if (parsed.check)
210
+ return;
211
+ if (latest && latest === VERSION) {
212
+ process.stdout.write(`already at the latest version; nothing to do\n`);
213
+ process.stdout.write(`(re-run with \`tono upgrade --no-restart\` to skip the daemon restart, or use the package manager directly to force-reinstall)\n`);
214
+ return;
215
+ }
216
+ // Resolution order for the package manager:
217
+ // 1. --pm flag (always wins; persisted as the new sticky choice)
218
+ // 2. ~/.tono/install.json packageManager (sticky after first upgrade)
219
+ // 3. realpath-based detection (works for properly-installed globals)
220
+ // 4. npm fallback
221
+ const record = readInstallRecord();
222
+ let pm;
223
+ let pmSource;
224
+ if (parsed.forcePm) {
225
+ pm = parsed.forcePm;
226
+ pmSource = "forced via --pm";
227
+ }
228
+ else if (record.packageManager) {
229
+ pm = record.packageManager;
230
+ pmSource = `from ${installRecordPath()}`;
231
+ }
232
+ else {
233
+ const det = detectInstaller();
234
+ if (!det.pm) {
235
+ console.error(`error: ${det.reason}`);
236
+ console.error(`run with --pm <pnpm|npm|yarn|bun> to force a choice`);
237
+ process.exit(1);
238
+ }
239
+ pm = det.pm;
240
+ pmSource = "auto-detected";
241
+ }
242
+ process.stdout.write(`installer: ${pm} (${pmSource})\n`);
243
+ if (!commandOnPath(pm)) {
244
+ console.error(`error: \`${pm}\` is not on PATH. Install ${pm} first, or pass --pm <other>.`);
245
+ process.exit(1);
246
+ }
247
+ const cmd = pmAddCmd(pm);
248
+ process.stdout.write(`running: ${cmd.join(" ")}\n`);
249
+ const r = exec(cmd[0], cmd.slice(1), { stdio: "inherit" });
250
+ if (r.code !== 0) {
251
+ console.error(`upgrade failed (${pm} exited ${r.code})`);
252
+ process.exit(r.code === -1 ? 1 : r.code);
253
+ }
254
+ // Persist the chosen pm so future `tono upgrade` runs use the same one
255
+ // without re-detecting (and without being fooled by PATH changes). Always
256
+ // overwrite — the most recent successful upgrade wins.
257
+ writeInstallRecord({
258
+ packageManager: pm,
259
+ lastSeenVersion: latest ?? VERSION,
260
+ recordedAt: record.recordedAt ?? new Date().toISOString(),
261
+ });
262
+ if (!parsed.restart) {
263
+ process.stdout.write(`upgrade complete. Skipping daemon restart (--no-restart). Restart manually:\n`);
264
+ process.stdout.write(` tono gateway restart\n`);
265
+ process.stdout.write(` tono worker restart\n`);
266
+ return;
267
+ }
268
+ // Restart whichever daemons are currently loaded. Either, both, or neither
269
+ // can apply on a given machine.
270
+ const gatewayLoaded = isLoaded(GATEWAY_LABEL);
271
+ const workerLoaded = isLoaded(WORKER_LABEL);
272
+ if (!gatewayLoaded && !workerLoaded) {
273
+ process.stdout.write(`upgrade complete. No tono daemon currently loaded; nothing to restart.\n`);
274
+ return;
275
+ }
276
+ if (gatewayLoaded) {
277
+ process.stdout.write(`restarting gateway…\n`);
278
+ const res = restartLaunchAgent(GATEWAY_LABEL, "gateway");
279
+ process.stdout.write(` ${res.output}\n`);
280
+ }
281
+ if (workerLoaded) {
282
+ process.stdout.write(`restarting worker…\n`);
283
+ const res = restartLaunchAgent(WORKER_LABEL, "worker");
284
+ process.stdout.write(` ${res.output}\n`);
285
+ }
286
+ process.stdout.write(`done.\n`);
287
+ }
288
+ //# sourceMappingURL=upgrade.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../../src/cli/commands/upgrade.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC3F,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE5D,SAAS,WAAW;IAClB,OAAO,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,iBAAiB;IACxB,OAAO,IAAI,CAAC,WAAW,EAAE,EAAE,cAAc,CAAC,CAAC;AAC7C,CAAC;AAWD,SAAS,iBAAiB;IACxB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,iBAAiB,EAAE,EAAE,MAAM,CAAC,CAAkB,CAAC;IAChF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAkB;IAC5C,IAAI,CAAC;QACH,SAAS,CAAC,WAAW,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,aAAa,CAAC,iBAAiB,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;QACxE,qBAAqB;IACvB,CAAC;AACH,CAAC;AAED,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;CAmBZ,CAAC;AAWF,SAAS,SAAS,CAAC,IAAc;IAC/B,MAAM,GAAG,GAAS,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAC/D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACnB,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;YAAC,SAAS;QAAC,CAAC;QAChE,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YAAC,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC;YAAC,SAAS;QAAC,CAAC;QACpD,IAAI,CAAC,KAAK,cAAc,EAAE,CAAC;YAAC,GAAG,CAAC,OAAO,GAAG,KAAK,CAAC;YAAC,SAAS;QAAC,CAAC;QAC5D,IAAI,CAAC,KAAK,MAAM,EAAE,CAAC;YACjB,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACpB,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;gBACtD,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,IAAI,WAAW,EAAE,CAAC,CAAC;YACzD,CAAC;YACD,GAAG,CAAC,OAAO,GAAG,CAAO,CAAC;YACtB,SAAS;QACX,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,IAAI,CAAC,GAAW,EAAE,IAAc,EAAE,OAAuC,EAAE;IAClF,MAAM,CAAC,GAAG,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3G,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;AAClF,CAAC;AAED,SAAS,aAAa,CAAC,GAAW;IAChC,MAAM,CAAC,GAAG,SAAS,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IACpF,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;AAC9D,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,eAAe;IACtB,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,IAAI,CAAS,CAAC;IACd,IAAI,CAAC;QACH,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,gCAAgC,IAAI,EAAE,EAAE,CAAC;IACtE,CAAC;IAED,4EAA4E;IAC5E,4EAA4E;IAC5E,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,GAAG,MAAM,GAAG,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC;QAC3C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,+FAA+F,EAAE,CAAC;IAC/H,CAAC;IAED,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;QAC9B,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,GAAG,QAAQ,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,GAAG,OAAO,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,GAAG,UAAU,GAAG,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC;YACjI,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,uBAAuB,CAAC,EAAE,EAAE,CAAC;QAC5D,CAAC;QACD,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,GAAG,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC;YACvC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,CAAC,EAAE,EAAE,CAAC;QAC3D,CAAC;QACD,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,GAAG,QAAQ,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,GAAG,OAAO,GAAG,SAAS,GAAG,EAAE,CAAC,EAAE,CAAC;YAC1F,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,uBAAuB,CAAC,EAAE,EAAE,CAAC;QAC5D,CAAC;QACD,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACxB,IAAI,IAAI,KAAK,CAAC;YAAE,MAAM;QACtB,CAAC,GAAG,IAAI,CAAC;IACX,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,wCAAwC,EAAE,CAAC;AACzE,CAAC;AAED,SAAS,QAAQ,CAAC,EAAM;IACtB,QAAQ,EAAE,EAAE,CAAC;QACX,KAAK,MAAM;YACT,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;QAC9C,KAAK,MAAM;YACT,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC;QAClD,KAAK,KAAK;YACR,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;QAC7C,KAAK,KAAK,CAAC;QACX;YACE,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;IACnD,CAAC;AACH,CAAC;AAED,KAAK,UAAU,WAAW;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,wCAAwC,EAAE;YAChE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC;YACjC,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;SACxC,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QACzB,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAyB,CAAC;QACrD,OAAO,CAAC,CAAC,OAAO,IAAI,IAAI,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,SAAS;IAChB,6CAA6C;IAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC;AACrC,CAAC;AAED,SAAS,QAAQ,CAAC,KAAa;IAC7B,MAAM,CAAC,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IACxE,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAa,EAAE,UAAkB;IAC3D,uEAAuE;IACvE,sEAAsE;IACtE,4EAA4E;IAC5E,qEAAqE;IACrE,sEAAsE;IACtE,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;IAC1B,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,KAAK,EAAE,EAAE,CAAC;IAC/D,CAAC;IACD,MAAM,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE;QACpE,QAAQ,EAAE,MAAM;KACjB,CAAC,CAAC;IACH,KAAK,KAAK,CAAC;IACX,OAAO;QACL,EAAE,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC;QAClB,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,UAAU;KAChF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAc;IAC7C,IAAI,MAAY,CAAC;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAE,GAAa,CAAC,OAAO,CAAC,CAAC;QACtC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,OAAO;IACT,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,OAAO,cAAc,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,MAAM,WAAW,EAAE,CAAC;IACnC,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,MAAM,oBAAoB,CAAC,CAAC;IAC3D,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0DAA0D,CAAC,CAAC;IACnF,CAAC;IAED,IAAI,MAAM,CAAC,KAAK;QAAE,OAAO;IAEzB,IAAI,MAAM,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;QACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACvE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kIAAkI,CAAC,CAAC;QACzJ,OAAO;IACT,CAAC;IAED,4CAA4C;IAC5C,mEAAmE;IACnE,wEAAwE;IACxE,uEAAuE;IACvE,oBAAoB;IACpB,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAC;IACnC,IAAI,EAAM,CAAC;IACX,IAAI,QAAgB,CAAC;IACrB,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC;QACpB,QAAQ,GAAG,iBAAiB,CAAC;IAC/B,CAAC;SAAM,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;QACjC,EAAE,GAAG,MAAM,CAAC,cAAc,CAAC;QAC3B,QAAQ,GAAG,QAAQ,iBAAiB,EAAE,EAAE,CAAC;IAC3C,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;QAC9B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YACtC,OAAO,CAAC,KAAK,CAAC,qDAAqD,CAAC,CAAC;YACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC;QACZ,QAAQ,GAAG,eAAe,CAAC;IAC7B,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,QAAQ,KAAK,CAAC,CAAC;IAEzD,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,KAAK,CAAC,YAAY,EAAE,8BAA8B,EAAE,+BAA+B,CAAC,CAAC;QAC7F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAE,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAC5D,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACjB,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,WAAW,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;QACzD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC;IAED,uEAAuE;IACvE,0EAA0E;IAC1E,uDAAuD;IACvD,kBAAkB,CAAC;QACjB,cAAc,EAAE,EAAE;QAClB,eAAe,EAAE,MAAM,IAAI,OAAO;QAClC,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KAC1D,CAAC,CAAC;IAEH,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,+EAA+E,CAAC,CAAC;QACtG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QACjD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAChD,OAAO;IACT,CAAC;IAED,2EAA2E;IAC3E,gCAAgC;IAChC,MAAM,aAAa,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC;IAC9C,MAAM,YAAY,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC5C,IAAI,CAAC,aAAa,IAAI,CAAC,YAAY,EAAE,CAAC;QACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0EAA0E,CAAC,CAAC;QACjG,OAAO;IACT,CAAC;IAED,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;QAC9C,MAAM,GAAG,GAAG,kBAAkB,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QACzD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;IAC5C,CAAC;IACD,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAC7C,MAAM,GAAG,GAAG,kBAAkB,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;QACvD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;IAC5C,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC"}
package/dist/cli/index.js CHANGED
@@ -5,6 +5,7 @@ import { runConfigure } from "./commands/configure.js";
5
5
  import { runStart } from "./commands/start.js";
6
6
  import { runGateway } from "./commands/gateway.js";
7
7
  import { runWorker } from "./commands/worker.js";
8
+ import { runUpgrade } from "./commands/upgrade.js";
8
9
  import { runOpen } from "./commands/open.js";
9
10
  import { runDoctor } from "./commands/doctor.js";
10
11
  import { VERSION } from "../shared/version.js";
@@ -35,6 +36,7 @@ Commands:
35
36
  worker uninstall unload and remove the worker LaunchAgent
36
37
  worker logs [out|err]
37
38
  tail the background worker's log file
39
+ upgrade upgrade tono to the latest version on npm and restart any loaded daemons
38
40
  open open the web UI in the default browser
39
41
  config path print the config file path
40
42
  config validate validate the config against the schema
@@ -71,6 +73,9 @@ async function main(argv) {
71
73
  case "worker":
72
74
  await runWorker(rest);
73
75
  return;
76
+ case "upgrade":
77
+ await runUpgrade(rest);
78
+ return;
74
79
  case "gateway":
75
80
  runGateway(rest);
76
81
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE/C,MAAM,IAAI,GAAG,QAAQ,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiC3B,CAAC;AAEF,KAAK,UAAU,IAAI,CAAC,IAAc;IAChC,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAE5B,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,SAAS,CAAC;QACf,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ,CAAC;QACd,KAAK,IAAI;YACP,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAClB,OAAO;QACT,KAAK,SAAS,CAAC;QACf,KAAK,WAAW,CAAC;QACjB,KAAK,IAAI;YACP,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACrB,OAAO;QACT,KAAK,MAAM;YACT,MAAM,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YACnD,OAAO;QACT,KAAK,WAAW;YACd,MAAM,YAAY,EAAE,CAAC;YACrB,OAAO;QACT,KAAK,QAAQ;YACX,MAAM,SAAS,EAAE,CAAC;YAClB,OAAO;QACT,KAAK,OAAO;YACV,MAAM,QAAQ,EAAE,CAAC;YACjB,OAAO;QACT,KAAK,QAAQ;YACX,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;YACtB,OAAO;QACT,KAAK,SAAS;YACZ,UAAU,CAAC,IAAI,CAAC,CAAC;YACjB,OAAO;QACT,KAAK,QAAQ;YACX,OAAO,CAAC,KAAK,CAAC,0DAA0D,CAAC,CAAC;YAC1E,UAAU,CAAC,IAAI,CAAC,CAAC;YACjB,OAAO;QACT,KAAK,MAAM;YACT,OAAO,EAAE,CAAC;YACV,OAAO;QACT,KAAK,QAAQ;YACX,SAAS,CAAC,IAAI,CAAC,CAAC;YAChB,OAAO;QACT;YACE,OAAO,CAAC,KAAK,CAAC,oBAAoB,GAAG,EAAE,CAAC,CAAC;YACzC,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;YAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACxC,OAAO,CAAC,KAAK,CAAE,GAAa,CAAC,OAAO,CAAC,CAAC;IACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE/C,MAAM,IAAI,GAAG,QAAQ,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkC3B,CAAC;AAEF,KAAK,UAAU,IAAI,CAAC,IAAc;IAChC,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAE5B,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,SAAS,CAAC;QACf,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ,CAAC;QACd,KAAK,IAAI;YACP,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAClB,OAAO;QACT,KAAK,SAAS,CAAC;QACf,KAAK,WAAW,CAAC;QACjB,KAAK,IAAI;YACP,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACrB,OAAO;QACT,KAAK,MAAM;YACT,MAAM,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YACnD,OAAO;QACT,KAAK,WAAW;YACd,MAAM,YAAY,EAAE,CAAC;YACrB,OAAO;QACT,KAAK,QAAQ;YACX,MAAM,SAAS,EAAE,CAAC;YAClB,OAAO;QACT,KAAK,OAAO;YACV,MAAM,QAAQ,EAAE,CAAC;YACjB,OAAO;QACT,KAAK,QAAQ;YACX,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;YACtB,OAAO;QACT,KAAK,SAAS;YACZ,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;YACvB,OAAO;QACT,KAAK,SAAS;YACZ,UAAU,CAAC,IAAI,CAAC,CAAC;YACjB,OAAO;QACT,KAAK,QAAQ;YACX,OAAO,CAAC,KAAK,CAAC,0DAA0D,CAAC,CAAC;YAC1E,UAAU,CAAC,IAAI,CAAC,CAAC;YACjB,OAAO;QACT,KAAK,MAAM;YACT,OAAO,EAAE,CAAC;YACV,OAAO;QACT,KAAK,QAAQ;YACX,SAAS,CAAC,IAAI,CAAC,CAAC;YAChB,OAAO;QACT;YACE,OAAO,CAAC,KAAK,CAAC,oBAAoB,GAAG,EAAE,CAAC,CAAC;YACzC,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;YAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACxC,OAAO,CAAC,KAAK,CAAE,GAAa,CAAC,OAAO,CAAC,CAAC;IACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -1,6 +1,6 @@
1
- import { existsSync, readdirSync, statSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync, } from "node:fs";
2
2
  import { homedir } from "node:os";
3
- import { join } from "node:path";
3
+ import { dirname, join } from "node:path";
4
4
  import { injectPromptIntoArgs, makeAgentEnv, renderPrompt, } from "./types.js";
5
5
  /**
6
6
  * Claude Code stores transcripts at ~/.claude/projects/<encoded-cwd>/<uuid>.jsonl
@@ -70,11 +70,62 @@ async function captureSessionId({ cwd, timeoutMs = 30_000, }) {
70
70
  function findExistingSession({ cwd }) {
71
71
  return newestSessionId(projectDirFor(cwd));
72
72
  }
73
+ /**
74
+ * Pre-seed `~/.claude.json` so Claude Code skips its "do you trust this
75
+ * folder?" prompt when it spawns inside a fresh worktree. Tono creates the
76
+ * worktree itself a moment before, so it knows the path is safe.
77
+ *
78
+ * Concurrency: read-modify-rename. Two simultaneous spawns racing on the
79
+ * same `~/.claude.json` could lose one update; the worst case is one of the
80
+ * worktrees prompts on first run. The pattern (mkdir-tmp, write, rename) is
81
+ * the standard "atomic-ish" approach on POSIX.
82
+ */
83
+ function claudeJsonPath() {
84
+ return join(homedir(), ".claude.json");
85
+ }
86
+ function preSpawn({ cwd }) {
87
+ const path = claudeJsonPath();
88
+ let parsed = {};
89
+ if (existsSync(path)) {
90
+ try {
91
+ parsed = JSON.parse(readFileSync(path, "utf8"));
92
+ }
93
+ catch {
94
+ // Existing file is unparseable — refuse to clobber.
95
+ return;
96
+ }
97
+ }
98
+ else {
99
+ mkdirSync(dirname(path), { recursive: true });
100
+ }
101
+ if (!parsed.projects || typeof parsed.projects !== "object")
102
+ parsed.projects = {};
103
+ const existing = parsed.projects[cwd] ?? {};
104
+ if (existing.hasTrustDialogAccepted === true)
105
+ return; // already trusted
106
+ // Spread `existing` first so prior fields are preserved, then force the
107
+ // trust flag on. Defaults below cover the case where there's no prior
108
+ // entry at all.
109
+ parsed.projects[cwd] = {
110
+ allowedTools: [],
111
+ mcpContextUris: [],
112
+ mcpServers: {},
113
+ approvedMcprcServers: [],
114
+ rejectedMcprcServers: [],
115
+ hasCompletedProjectOnboarding: true,
116
+ ...existing,
117
+ hasTrustDialogAccepted: true,
118
+ };
119
+ const tmp = `${path}.tono-${process.pid}-${Date.now()}.tmp`;
120
+ writeFileSync(tmp, JSON.stringify(parsed, null, 2));
121
+ renameSync(tmp, path);
122
+ }
73
123
  export const claudeCodeAdapter = {
74
124
  type: "claude-code",
75
125
  buildFreshSpec: freshSpec,
76
126
  buildResumeSpec: resumeSpec,
77
127
  captureSessionId,
78
128
  findExistingSession,
129
+ preSpawn,
79
130
  };
80
131
  //# sourceMappingURL=claude-code.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../../src/server/agents/claude-code.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../../src/server/agents/claude-code.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/server/agents/types.ts"],"names":[],"mappings":"AAoEA,MAAM,WAAW,GAAG,oFAAoF,CAAC;AAEzG,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,GAAgB;IAC7D,OAAO,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,EAAE,EAAE,GAAsB,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAC/F,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAgB,EAAE,IAAc;IAC3D,OAAO;QACL,GAAG,OAAO,CAAC,GAAG;QACd,cAAc,EAAE,IAAI;QACpB,eAAe,EAAE,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;QACxC,cAAc,EAAE,GAAG,CAAC,QAAQ;QAC5B,gBAAgB,EAAE,GAAG,CAAC,MAAM;QAC5B,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtD,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAc,EAAE,MAAc;IACjE,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACzB,IAAI,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,QAAQ,GAAG,IAAI,CAAC;YAChB,OAAO,CAAC,CAAC,UAAU,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC,CAAC,CAAC;IACH,IAAI,CAAC,QAAQ;QAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAChC,OAAO,GAAG,CAAC;AACb,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,kBAAkB,CAAC,MAAsB,EAAE,IAAe;IACxE,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,GAAG,CAAC,CAAC;IAClE,OAAO,KAAK,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/server/agents/types.ts"],"names":[],"mappings":"AA4EA,MAAM,WAAW,GAAG,oFAAoF,CAAC;AAEzG,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,GAAgB;IAC7D,OAAO,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,EAAE,EAAE,GAAsB,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAC/F,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAgB,EAAE,IAAc;IAC3D,OAAO;QACL,GAAG,OAAO,CAAC,GAAG;QACd,cAAc,EAAE,IAAI;QACpB,eAAe,EAAE,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;QACxC,cAAc,EAAE,GAAG,CAAC,QAAQ;QAC5B,gBAAgB,EAAE,GAAG,CAAC,MAAM;QAC5B,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtD,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAc,EAAE,MAAc;IACjE,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACzB,IAAI,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,QAAQ,GAAG,IAAI,CAAC;YAChB,OAAO,CAAC,CAAC,UAAU,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC,CAAC,CAAC;IACH,IAAI,CAAC,QAAQ;QAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAChC,OAAO,GAAG,CAAC;AACb,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,kBAAkB,CAAC,MAAsB,EAAE,IAAe;IACxE,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,GAAG,CAAC,CAAC;IAClE,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -95,13 +95,34 @@ export function buildApp(deps) {
95
95
  return c.json({ error: `agent must be one of: ${Object.keys(cfg.agents).join(", ")}` }, 400);
96
96
  }
97
97
  const existing = deps.q.taskByIssue(repoSlug, issueNumber, kind);
98
- if (existing && (existing.status === "queued" || existing.status === "running")) {
98
+ if (existing && (existing.status === "queued" || existing.status === "running" || existing.status === "assigning")) {
99
99
  return c.json({ error: `task #${existing.id} is already ${existing.status} for this ${kind === "review" ? "PR" : "issue"}`, task: existing }, 409);
100
100
  }
101
- // Manual trigger only supports implement for now; reviews come through the poller.
102
- if (kind !== "implement") {
103
- return c.json({ error: "manual review tasks aren't supported yet — apply the review label on the PR instead" }, 400);
101
+ if (kind === "review") {
102
+ let pr;
103
+ try {
104
+ pr = await gh.prDetails(repoSlug, issueNumber);
105
+ }
106
+ catch (err) {
107
+ return c.json({ error: err.message }, 502);
108
+ }
109
+ // Review tasks store the PR head ref name as `branch` (informational —
110
+ // the worktree checks out a detached HEAD at `refs/tono/pr-N`), and the
111
+ // PR URL is set at insert time so the agent prompt can reference it.
112
+ const task = deps.q.insertTask({
113
+ kind: "review",
114
+ repoSlug,
115
+ issueNumber: pr.number,
116
+ issueTitle: pr.title,
117
+ issueBody: pr.body ?? "",
118
+ branch: pr.headRefName,
119
+ agent: agent,
120
+ prUrl: pr.url,
121
+ });
122
+ deps.bus.emit("task:queued", { task });
123
+ return c.json({ task });
104
124
  }
125
+ // implement
105
126
  let issue;
106
127
  try {
107
128
  issue = await gh.issueView(repoSlug, issueNumber);
@@ -122,6 +143,29 @@ export function buildApp(deps) {
122
143
  deps.bus.emit("task:queued", { task });
123
144
  return c.json({ task });
124
145
  });
146
+ // List open issues + PRs for a configured repo, used by the Start-session
147
+ // picker. Two parallel `gh` calls; either may fail independently — we
148
+ // surface partial results rather than 502'ing the whole thing.
149
+ app.get("/api/repos/:slug{.+}/items", async (c) => {
150
+ const slug = c.req.param("slug");
151
+ const cfg = cfgRef();
152
+ if (!cfg.repos.find((r) => r.slug === slug)) {
153
+ return c.json({ error: `repo ${slug} is not configured` }, 404);
154
+ }
155
+ const [issuesRes, prsRes] = await Promise.allSettled([gh.openIssues(slug), gh.openPrs(slug)]);
156
+ const issues = issuesRes.status === "fulfilled"
157
+ ? issuesRes.value.map((i) => ({ number: i.number, title: i.title, url: i.url, updatedAt: i.updatedAt, labels: i.labels.map((l) => l.name) }))
158
+ : [];
159
+ const prs = prsRes.status === "fulfilled"
160
+ ? prsRes.value.map((p) => ({ number: p.number, title: p.title, url: p.url, updatedAt: p.updatedAt, headRefName: p.headRefName, labels: p.labels.map((l) => l.name) }))
161
+ : [];
162
+ return c.json({
163
+ issues,
164
+ prs,
165
+ issuesError: issuesRes.status === "rejected" ? issuesRes.reason.message : null,
166
+ prsError: prsRes.status === "rejected" ? prsRes.reason.message : null,
167
+ });
168
+ });
125
169
  app.get("/api/tasks/:id", (c) => {
126
170
  const id = Number(c.req.param("id"));
127
171
  if (!Number.isFinite(id))
@@ -129,7 +173,13 @@ export function buildApp(deps) {
129
173
  const task = deps.q.getTask(id);
130
174
  if (!task)
131
175
  return c.json({ error: "not found" }, 404);
132
- return c.json({ task: { ...task, canResume: !!task.agentSessionId } });
176
+ return c.json({
177
+ task: {
178
+ ...task,
179
+ canResume: !!task.agentSessionId,
180
+ workerHostname: resolveWorkerHostname(deps, task.assignedWorkerId),
181
+ },
182
+ });
133
183
  });
134
184
  app.get("/api/sessions", (c) => {
135
185
  // All live sessions (shells + agent tasks) are tracked in the registry
@@ -301,7 +351,31 @@ export function buildApp(deps) {
301
351
  }
302
352
  const cfg = cfgRef();
303
353
  const repo = cfg.repos.find((r) => r.slug === task.repoSlug);
304
- if (repo) {
354
+ // Preferred path: route to the worker that holds the worktree on disk.
355
+ // Falls back to gateway-local removeWorktree when the worker is offline
356
+ // OR when the task pre-dates worker tracking (assignedWorkerId is null).
357
+ // The fallback is harmless on remote-worker tasks: removeWorktree silently
358
+ // no-ops when the path doesn't exist on the gateway's filesystem.
359
+ if (task.assignedWorkerId && deps.registry.get(task.assignedWorkerId)) {
360
+ const ack = deps.registry.awaitAck("cleanup", id, 10_000);
361
+ const sent = deps.registry.sendControl(task.assignedWorkerId, {
362
+ type: "task.cleanup",
363
+ taskId: id,
364
+ kind: task.kind,
365
+ repoSlug: task.repoSlug,
366
+ issueNumber: task.issueNumber,
367
+ });
368
+ if (!sent) {
369
+ return c.json({ error: "couldn't reach the owning worker" }, 503);
370
+ }
371
+ const result = await ack;
372
+ if (!result.ok) {
373
+ return c.json({ error: result.error ?? "worker reported cleanup failed" }, 502);
374
+ }
375
+ }
376
+ else if (repo) {
377
+ // No live worker — best-effort local cleanup. Skips silently when the
378
+ // path lives on a different filesystem.
305
379
  try {
306
380
  if (task.kind === "review") {
307
381
  await removeReviewWorktree({
@@ -378,6 +452,20 @@ export function buildApp(deps) {
378
452
  }
379
453
  return app;
380
454
  }
455
+ /**
456
+ * Resolve a worker id to a friendly hostname. Prefers the live registry
457
+ * (most up-to-date), falls back to the persisted workers table for tasks
458
+ * whose worker has since disconnected. Returns null when nothing's known.
459
+ */
460
+ function resolveWorkerHostname(deps, workerId) {
461
+ if (!workerId)
462
+ return null;
463
+ const live = deps.registry.get(workerId);
464
+ if (live)
465
+ return live.hostname;
466
+ const row = deps.q.getWorker(workerId);
467
+ return row?.hostname ?? null;
468
+ }
381
469
  const MIME = {
382
470
  ".html": "text/html; charset=utf-8",
383
471
  ".js": "application/javascript; charset=utf-8",