@pablopunk/tau 0.0.0-stage → 0.1.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.
Files changed (64) hide show
  1. package/dist/auth-storage-Dk3CUY3q.js +1814 -0
  2. package/dist/bin.js +23 -0
  3. package/dist/cli-entry-DcDpQ30L.js +13 -0
  4. package/dist/client-C91-b2CL.js +1 -0
  5. package/dist/client-y2WkNuZj.js +7977 -0
  6. package/dist/dist-BFP19NOO.js +432 -0
  7. package/dist/dist-Bh1bQHZA.js +38890 -0
  8. package/dist/dist-Cq73_r65.js +3 -0
  9. package/dist/esm-CMGh_WhC.js +9446 -0
  10. package/dist/execute-BeGXhU1O.js +1757 -0
  11. package/dist/fake-claude-connectors-DSP9UC8d.js +1 -0
  12. package/dist/fake-claude-connectors-sDLeP10T.js +107 -0
  13. package/dist/fake-claude-sdk-CvqW_1iQ.js +571 -0
  14. package/dist/fake-claude-thread-script-2iHHwUZM.js +48 -0
  15. package/dist/last-exit-write-ksHeHGrC.js +9 -0
  16. package/dist/lib-DIX7q_HJ.js +1460 -0
  17. package/dist/main-CI9q691u.js +1490 -0
  18. package/dist/names-cTrEXB6w.js +35 -0
  19. package/dist/node-BpwblPFP.js +56269 -0
  20. package/dist/pdfjs-nTcHjDPT.js +69856 -0
  21. package/dist/photon_rs-DBr7W_rK.js +4252 -0
  22. package/dist/promise.try-CAAOYvMP.js +1426 -0
  23. package/dist/readability-DsDNO63a.js +1670 -0
  24. package/dist/rolldown-runtime-1p2KPIdQ.js +44 -0
  25. package/dist/runtime-i-oEtgAl.js +2721 -0
  26. package/dist/sdk-missing-CHbVbY1G.js +10 -0
  27. package/dist/sdk-port-BGw9ORUR.js +215 -0
  28. package/dist/serve-92lMbcls.js +2958 -0
  29. package/dist/serve-BcVZj5e2.js +1 -0
  30. package/dist/service-command-BVr33vlP.js +1 -0
  31. package/dist/service-command-C7WDeeR2.js +621 -0
  32. package/dist/shell-CQa4e7rx.js +643 -0
  33. package/dist/skills/builtin/tau/SKILL.md +51 -0
  34. package/dist/streamableHttp-BBaRYV9h.js +1 -0
  35. package/dist/streamableHttp-DPAoMlJd.js +1931 -0
  36. package/dist/tool-DBZK2lci.js +14335 -0
  37. package/dist/tool-server-BjP37EJg.js +40 -0
  38. package/dist/turndown.cjs-Bo1xLg2S.js +15620 -0
  39. package/dist/turndown.cjs-jpUF50AR.js +3 -0
  40. package/dist/types-C4M3CRp2.js +7592 -0
  41. package/dist/undici-8cI97lnP.js +27110 -0
  42. package/dist/wait-for-lock-BMk6v3J5.js +1 -0
  43. package/dist/wait-for-lock-D2V3vuye.js +43951 -0
  44. package/dist/watchdog.js +47 -0
  45. package/dist/web/dist/assets/addon-fit-DIOBYJe3.js +1 -0
  46. package/dist/web/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  47. package/dist/web/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  48. package/dist/web/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  49. package/dist/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  50. package/dist/web/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  51. package/dist/web/dist/assets/index--f7AYPm0.js +108 -0
  52. package/dist/web/dist/assets/index-7m0crRNF.css +2 -0
  53. package/dist/web/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  54. package/dist/web/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  55. package/dist/web/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  56. package/dist/web/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  57. package/dist/web/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  58. package/dist/web/dist/assets/wght-DMLdVLkb.css +1 -0
  59. package/dist/web/dist/assets/xterm-BrP-ENHg.css +1 -0
  60. package/dist/web/dist/assets/xterm-CerOhKYV.js +16 -0
  61. package/dist/web/dist/favicon.svg +14 -0
  62. package/dist/web/dist/index.html +25 -0
  63. package/package.json +53 -4
  64. package/README.md +0 -3
@@ -0,0 +1 @@
1
+ export { t as serveInForeground } from "./serve-92lMbcls.js";
@@ -0,0 +1 @@
1
+ export { t as runServiceCommand } from "./service-command-C7WDeeR2.js";
@@ -0,0 +1,621 @@
1
+ import { t as cliEntry } from "./cli-entry-DcDpQ30L.js";
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { delimiter, dirname, join, resolve } from "node:path";
4
+ import { homedir, release, userInfo } from "node:os";
5
+ import { parseArgs } from "node:util";
6
+ import { createHash } from "node:crypto";
7
+ import { spawn, spawnSync } from "node:child_process";
8
+ //#region src/service/entry-guard.ts
9
+ const TEMPORARY_RUNNER_FOLDERS = [
10
+ /[\\/]_npx[\\/]/,
11
+ /[\\/]dlx-[^\\/]+[\\/]/,
12
+ /[\\/]pnpm[\\/]dlx[\\/]/,
13
+ /[\\/]\.bun[\\/]install[\\/]cache[\\/]/
14
+ ];
15
+ const isTemporaryRunnerEntry = (entry) => TEMPORARY_RUNNER_FOLDERS.some((folder) => folder.test(entry));
16
+ var TemporaryEntryError = class extends Error {
17
+ constructor(entry) {
18
+ super(`tau runs from a temporary cache (${entry}). Install tau globally first: npm i -g @pablopunk/tau.`);
19
+ this.name = "TemporaryEntryError";
20
+ }
21
+ };
22
+ function refuseTemporaryEntry(entry) {
23
+ if (isTemporaryRunnerEntry(entry)) throw new TemporaryEntryError(entry);
24
+ }
25
+ //#endregion
26
+ //#region src/service/runner.ts
27
+ const realRunner = {
28
+ async run(command, args) {
29
+ const result = spawnSync(command, [...args], { encoding: "utf8" });
30
+ return {
31
+ code: result.status ?? 1,
32
+ stdout: result.stdout ?? "",
33
+ stderr: result.stderr ?? result.error?.message ?? ""
34
+ };
35
+ },
36
+ async spawnDetached(command, args, options = {}) {
37
+ const child = spawn(command, [...args], {
38
+ detached: true,
39
+ stdio: "ignore",
40
+ windowsHide: true,
41
+ env: options.env ?? process.env
42
+ });
43
+ child.unref();
44
+ return child.pid;
45
+ }
46
+ };
47
+ //#endregion
48
+ //#region src/service/context.ts
49
+ function isAlive(pid) {
50
+ try {
51
+ process.kill(pid, 0);
52
+ return true;
53
+ } catch (error) {
54
+ return error.code === "EPERM";
55
+ }
56
+ }
57
+ const windowsBuildOf = () => process.platform === "win32" ? Number(release().split(".")[2]) : void 0;
58
+ function realContext(env = process.env) {
59
+ return {
60
+ platform: process.platform,
61
+ uid: process.getuid?.() ?? 0,
62
+ username: userInfo().username,
63
+ osHome: homedir(),
64
+ defaultHome: join(homedir(), ".tau"),
65
+ xdgRuntimeDir: env.XDG_RUNTIME_DIR,
66
+ windowsBuild: windowsBuildOf(),
67
+ env,
68
+ runner: realRunner,
69
+ isAlive,
70
+ kill: (pid) => process.kill(pid, "SIGTERM")
71
+ };
72
+ }
73
+ //#endregion
74
+ //#region src/service/names.ts
75
+ const hashOf = (home) => createHash("sha256").update(resolve(home)).digest("hex").slice(0, 8);
76
+ /** The default home keeps the plain names; any other home gets its own, so dev homes never replace the user's service. */
77
+ function serviceNames(home, defaultHome) {
78
+ if (resolve(home) === resolve(defaultHome)) return {
79
+ launchdLabel: "dev.tau.server",
80
+ systemdUnit: "tau.service",
81
+ taskName: "tau"
82
+ };
83
+ const hash = hashOf(home);
84
+ return {
85
+ launchdLabel: `dev.tau.server.${hash}`,
86
+ systemdUnit: `tau-${hash}.service`,
87
+ taskName: `tau-${hash}`
88
+ };
89
+ }
90
+ //#endregion
91
+ //#region src/service/path-capture.ts
92
+ const miseShimsOf = (osHome) => join(osHome, ".local", "share", "mise", "shims");
93
+ /** The user's PATH with the mise shims folder first, so a removed tool version does not break agents. */
94
+ function captureServicePath(pathValue, osHome, exists = existsSync, separator = delimiter) {
95
+ const shims = miseShimsOf(osHome);
96
+ const entries = pathValue.split(separator).filter((entry) => entry !== "" && entry !== shims);
97
+ return (exists(shims) ? [shims, ...entries] : entries).join(separator);
98
+ }
99
+ //#endregion
100
+ //#region src/service/spec.ts
101
+ const serviceArgs = (home, isDefaultHome) => [
102
+ "serve",
103
+ "--service",
104
+ "--no-open",
105
+ ...isDefaultHome ? [] : ["--home", home]
106
+ ];
107
+ function buildSpec(context, cli, home) {
108
+ const names = serviceNames(home, context.defaultHome);
109
+ return {
110
+ names,
111
+ home,
112
+ node: cli.node,
113
+ entry: cli.entry,
114
+ path: captureServicePath(context.env.PATH ?? "", context.osHome),
115
+ args: serviceArgs(home, names.launchdLabel === "dev.tau.server")
116
+ };
117
+ }
118
+ const recordFileOf = (home) => join(home, "service.json");
119
+ const pidFileOf = (home) => join(home, "service.pid");
120
+ const wrapperOf = (home) => join(home, "bin", "tau-service.cmd");
121
+ //#endregion
122
+ //#region src/service/detached.ts
123
+ function readPid(file) {
124
+ try {
125
+ const pid = Number(readFileSync(file, "utf8").trim());
126
+ return Number.isInteger(pid) && pid > 0 ? pid : void 0;
127
+ } catch {
128
+ return;
129
+ }
130
+ }
131
+ /** A plain background process: used when the OS has no usable service manager, or for a home with no service. */
132
+ function createDetachedBackend(context) {
133
+ const livePid = (spec) => {
134
+ const pid = readPid(pidFileOf(spec.home));
135
+ return pid !== void 0 && context.isAlive(pid) ? pid : void 0;
136
+ };
137
+ async function start(spec) {
138
+ if (livePid(spec) !== void 0) return;
139
+ const pid = await context.runner.spawnDetached(spec.node, [spec.entry, ...spec.args], { env: {
140
+ ...context.env,
141
+ PATH: spec.path
142
+ } });
143
+ if (pid === void 0) return;
144
+ mkdirSync(dirname(pidFileOf(spec.home)), { recursive: true });
145
+ writeFileSync(pidFileOf(spec.home), String(pid));
146
+ }
147
+ async function stop(spec) {
148
+ const pid = livePid(spec);
149
+ if (pid !== void 0) context.kill(pid);
150
+ rmSync(pidFileOf(spec.home), { force: true });
151
+ }
152
+ return {
153
+ kind: "detached",
154
+ files: () => [],
155
+ isInstalled: async () => false,
156
+ async install(spec) {
157
+ await start(spec);
158
+ return { warnings: [] };
159
+ },
160
+ uninstall: stop,
161
+ reload: async (spec) => {
162
+ await stop(spec);
163
+ await start(spec);
164
+ },
165
+ start,
166
+ stop,
167
+ async restart(spec) {
168
+ await stop(spec);
169
+ await start(spec);
170
+ },
171
+ async state(spec) {
172
+ const pid = livePid(spec);
173
+ return {
174
+ active: pid !== void 0,
175
+ pid
176
+ };
177
+ },
178
+ list: async () => []
179
+ };
180
+ }
181
+ //#endregion
182
+ //#region src/service/backend.ts
183
+ var ServiceCommandError = class extends Error {
184
+ constructor(command, stderr) {
185
+ super(`${command} failed: ${stderr.trim() || "no output"}`);
186
+ this.name = "ServiceCommandError";
187
+ }
188
+ };
189
+ //#endregion
190
+ //#region src/service/render.ts
191
+ const xmlEscape = (text) => text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
192
+ function renderPlist(spec) {
193
+ const programArguments = [
194
+ spec.node,
195
+ spec.entry,
196
+ ...spec.args
197
+ ].map((argument) => ` <string>${xmlEscape(argument)}</string>`).join("\n");
198
+ return `<?xml version="1.0" encoding="UTF-8"?>
199
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
200
+ <plist version="1.0">
201
+ <dict>
202
+ <key>Label</key>
203
+ <string>${xmlEscape(spec.names.launchdLabel)}</string>
204
+ <key>ProgramArguments</key>
205
+ <array>
206
+ ${programArguments}
207
+ </array>
208
+ <key>RunAtLoad</key>
209
+ <true/>
210
+ <key>KeepAlive</key>
211
+ <true/>
212
+ <key>EnvironmentVariables</key>
213
+ <dict>
214
+ <key>PATH</key>
215
+ <string>${xmlEscape(spec.path)}</string>
216
+ </dict>
217
+ </dict>
218
+ </plist>
219
+ `;
220
+ }
221
+ const systemdQuote = (text) => `"${text.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("%", "%%")}"`;
222
+ function renderSystemdUnit(spec) {
223
+ return `[Unit]
224
+ Description=tau server
225
+
226
+ [Service]
227
+ ExecStart=${[
228
+ spec.node,
229
+ spec.entry,
230
+ ...spec.args
231
+ ].map(systemdQuote).join(" ")}
232
+ Restart=always
233
+ RestartSec=5
234
+ Environment=${systemdQuote(`PATH=${spec.path}`)}
235
+
236
+ [Install]
237
+ WantedBy=default.target
238
+ `;
239
+ }
240
+ function renderTaskXml(wrapperPath) {
241
+ return `<?xml version="1.0" encoding="UTF-16"?>
242
+ <Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
243
+ <Triggers>
244
+ <LogonTrigger>
245
+ <Enabled>true</Enabled>
246
+ </LogonTrigger>
247
+ </Triggers>
248
+ <Principals>
249
+ <Principal id="Author">
250
+ <LogonType>InteractiveToken</LogonType>
251
+ <RunLevel>LeastPrivilege</RunLevel>
252
+ </Principal>
253
+ </Principals>
254
+ <Settings>
255
+ <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
256
+ <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
257
+ <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
258
+ <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
259
+ <RestartOnFailure>
260
+ <Interval>PT1M</Interval>
261
+ <Count>999</Count>
262
+ </RestartOnFailure>
263
+ <StartWhenAvailable>true</StartWhenAvailable>
264
+ <AllowStartOnDemand>true</AllowStartOnDemand>
265
+ <Hidden>true</Hidden>
266
+ <Enabled>true</Enabled>
267
+ </Settings>
268
+ <Actions Context="Author">
269
+ <Exec>
270
+ <Command>conhost.exe</Command>
271
+ <Arguments>--headless "${xmlEscape(wrapperPath)}"</Arguments>
272
+ </Exec>
273
+ </Actions>
274
+ </Task>
275
+ `;
276
+ }
277
+ const cmdQuote = (text) => `"${text.replaceAll("%", "%%")}"`;
278
+ function renderCmdWrapper(spec) {
279
+ const command = [
280
+ spec.node,
281
+ spec.entry,
282
+ ...spec.args
283
+ ].map(cmdQuote).join(" ");
284
+ return `@echo off\r
285
+ set "PATH=${spec.path.replaceAll("%", "%%")}"\r
286
+ ${command}\r
287
+ exit /b %ERRORLEVEL%\r
288
+ `;
289
+ }
290
+ //#endregion
291
+ //#region src/service/launchd.ts
292
+ const plistDirOf = (context) => join(context.osHome, "Library", "LaunchAgents");
293
+ const plistPathOf = (context, spec) => join(plistDirOf(context), `${spec.names.launchdLabel}.plist`);
294
+ const PID_LINE = /^\s*pid = (\d+)/m;
295
+ function createLaunchdBackend(context) {
296
+ const launchctl = (...args) => context.runner.run("launchctl", args);
297
+ const domains = [`gui/${context.uid}`, `user/${context.uid}`];
298
+ async function liveDomain() {
299
+ for (const domain of domains) if ((await launchctl("print", domain)).code === 0) return domain;
300
+ }
301
+ const targetOf = async (spec) => `${await liveDomain() ?? domains[0]}/${spec.names.launchdLabel}`;
302
+ async function bootstrap(spec) {
303
+ for (const domain of domains) if ((await launchctl("bootstrap", domain, plistPathOf(context, spec))).code === 0) return true;
304
+ return false;
305
+ }
306
+ return {
307
+ kind: "launchd",
308
+ files: (spec) => [{
309
+ path: plistPathOf(context, spec),
310
+ content: renderPlist(spec)
311
+ }],
312
+ isInstalled: async (spec) => existsSync(plistPathOf(context, spec)),
313
+ async install(spec) {
314
+ await launchctl("bootout", await targetOf(spec));
315
+ if (await bootstrap(spec)) return { warnings: [] };
316
+ return {
317
+ warnings: ["launchd refused the service (no GUI session?). tau runs detached and will not survive a reboot."],
318
+ fellBack: true
319
+ };
320
+ },
321
+ async reload(spec) {
322
+ await launchctl("bootout", await targetOf(spec));
323
+ if (!await bootstrap(spec)) throw new ServiceCommandError("launchctl bootstrap", "the job could not be loaded");
324
+ },
325
+ async uninstall(spec) {
326
+ await launchctl("bootout", await targetOf(spec));
327
+ },
328
+ async start(spec) {
329
+ if (await bootstrap(spec)) return;
330
+ throw new ServiceCommandError("launchctl bootstrap", "the job could not be loaded");
331
+ },
332
+ async stop(spec) {
333
+ await launchctl("bootout", await targetOf(spec));
334
+ },
335
+ async restart(spec) {
336
+ const result = await launchctl("kickstart", "-k", await targetOf(spec));
337
+ if (result.code !== 0) throw new ServiceCommandError("launchctl kickstart", result.stderr);
338
+ },
339
+ async state(spec) {
340
+ const result = await launchctl("print", await targetOf(spec));
341
+ if (result.code !== 0) return { active: false };
342
+ const pid = PID_LINE.exec(result.stdout)?.[1];
343
+ return {
344
+ active: pid !== void 0,
345
+ pid: pid === void 0 ? void 0 : Number(pid)
346
+ };
347
+ },
348
+ async list() {
349
+ const dir = plistDirOf(context);
350
+ if (!existsSync(dir)) return [];
351
+ return readdirSync(dir).filter((file) => /^dev\.tau\.server(\.[0-9a-f]+)?\.plist$/.test(file)).map((file) => file.replace(/\.plist$/, ""));
352
+ }
353
+ };
354
+ }
355
+ //#endregion
356
+ //#region src/service/schtasks.ts
357
+ const CONHOST_HEADLESS_BUILD = 17763;
358
+ const hasHeadlessConhost = (context) => (context.windowsBuild ?? 0) >= CONHOST_HEADLESS_BUILD;
359
+ const utf16WithBom = (text) => Buffer.from(`\ufeff${text}`, "utf16le");
360
+ const taskXmlPathOf = (spec) => join(spec.home, "bin", `${spec.names.taskName}.xml`);
361
+ function createSchtasksBackend(context) {
362
+ const schtasks = (...args) => context.runner.run("schtasks", args);
363
+ const must = async (...args) => {
364
+ const result = await schtasks(...args);
365
+ if (result.code !== 0) throw new ServiceCommandError(`schtasks ${args[0]}`, result.stderr);
366
+ };
367
+ const queryOf = (spec) => schtasks("/query", "/tn", spec.names.taskName, "/fo", "csv", "/nh");
368
+ return {
369
+ kind: "schtasks",
370
+ files: (spec) => [{
371
+ path: wrapperOf(spec.home),
372
+ content: renderCmdWrapper(spec)
373
+ }, {
374
+ path: taskXmlPathOf(spec),
375
+ content: utf16WithBom(renderTaskXml(wrapperOf(spec.home)))
376
+ }],
377
+ isInstalled: async (spec) => (await queryOf(spec)).code === 0,
378
+ async install(spec) {
379
+ await must("/create", "/tn", spec.names.taskName, "/xml", taskXmlPathOf(spec), "/f");
380
+ await must("/run", "/tn", spec.names.taskName);
381
+ return { warnings: [] };
382
+ },
383
+ async uninstall(spec) {
384
+ await schtasks("/end", "/tn", spec.names.taskName);
385
+ await schtasks("/delete", "/tn", spec.names.taskName, "/f");
386
+ },
387
+ async reload(spec) {
388
+ await must("/create", "/tn", spec.names.taskName, "/xml", taskXmlPathOf(spec), "/f");
389
+ await schtasks("/end", "/tn", spec.names.taskName);
390
+ await must("/run", "/tn", spec.names.taskName);
391
+ },
392
+ start: (spec) => must("/run", "/tn", spec.names.taskName),
393
+ stop: (spec) => must("/end", "/tn", spec.names.taskName),
394
+ async restart(spec) {
395
+ await schtasks("/end", "/tn", spec.names.taskName);
396
+ await must("/run", "/tn", spec.names.taskName);
397
+ },
398
+ async state(spec) {
399
+ const result = await queryOf(spec);
400
+ return { active: result.code === 0 && /Running/i.test(result.stdout) };
401
+ },
402
+ async list() {
403
+ return (await schtasks("/query", "/fo", "csv", "/nh")).stdout.split(/\r?\n/).map((line) => /^"\\?(tau(?:-[0-9a-f]+)?)"/.exec(line)?.[1]).filter((name) => name !== void 0);
404
+ }
405
+ };
406
+ }
407
+ //#endregion
408
+ //#region src/service/systemd.ts
409
+ const unitDirOf = (context) => join(context.osHome, ".config", "systemd", "user");
410
+ const unitPathOf = (context, spec) => join(unitDirOf(context), spec.names.systemdUnit);
411
+ const hasUserSystemd = (context) => Boolean(context.xdgRuntimeDir);
412
+ function createSystemdBackend(context) {
413
+ const systemctl = (...args) => context.runner.run("systemctl", ["--user", ...args]);
414
+ const must = async (...args) => {
415
+ const result = await systemctl(...args);
416
+ if (result.code !== 0) throw new ServiceCommandError(`systemctl --user ${args[0]}`, result.stderr);
417
+ };
418
+ async function enableLinger() {
419
+ return (await context.runner.run("loginctl", ["enable-linger", context.username])).code === 0 ? [] : ["Could not enable linger: tau will not start after a reboot until you log in."];
420
+ }
421
+ return {
422
+ kind: "systemd",
423
+ files: (spec) => [{
424
+ path: unitPathOf(context, spec),
425
+ content: renderSystemdUnit(spec)
426
+ }],
427
+ isInstalled: async (spec) => existsSync(unitPathOf(context, spec)),
428
+ async install(spec) {
429
+ await must("daemon-reload");
430
+ await must("enable", "--now", spec.names.systemdUnit);
431
+ return { warnings: await enableLinger() };
432
+ },
433
+ async reload(spec) {
434
+ await must("daemon-reload");
435
+ await must("restart", spec.names.systemdUnit);
436
+ },
437
+ async uninstall(spec) {
438
+ await systemctl("disable", "--now", spec.names.systemdUnit);
439
+ },
440
+ start: (spec) => must("start", spec.names.systemdUnit),
441
+ stop: (spec) => must("stop", spec.names.systemdUnit),
442
+ restart: (spec) => must("restart", spec.names.systemdUnit),
443
+ async state(spec) {
444
+ const result = await systemctl("show", spec.names.systemdUnit, "-p", "MainPID", "-p", "ActiveState");
445
+ const active = /ActiveState=active/.test(result.stdout);
446
+ const pid = Number(/MainPID=(\d+)/.exec(result.stdout)?.[1] ?? 0);
447
+ return {
448
+ active,
449
+ pid: pid > 0 ? pid : void 0
450
+ };
451
+ },
452
+ async list() {
453
+ const dir = unitDirOf(context);
454
+ if (!existsSync(dir)) return [];
455
+ return readdirSync(dir).filter((file) => /^tau(-[0-9a-f]+)?\.service$/.test(file));
456
+ }
457
+ };
458
+ }
459
+ //#endregion
460
+ //#region src/service/pick-backend.ts
461
+ /** The OS service manager for this machine, or undefined when it cannot host one. */
462
+ function nativeBackendOf(context) {
463
+ if (context.platform === "darwin") return createLaunchdBackend(context);
464
+ if (context.platform === "linux") return hasUserSystemd(context) ? createSystemdBackend(context) : void 0;
465
+ if (context.platform === "win32") return hasHeadlessConhost(context) ? createSchtasksBackend(context) : void 0;
466
+ }
467
+ const FALLBACK_WARNING = "No usable service manager here. tau runs detached and will not survive a reboot.";
468
+ const detachedBackendOf = createDetachedBackend;
469
+ //#endregion
470
+ //#region src/service/provider-keys.ts
471
+ const PROVIDER_KEY_VARIABLES = [
472
+ "ANTHROPIC_API_KEY",
473
+ "OPENAI_API_KEY",
474
+ "GEMINI_API_KEY",
475
+ "GOOGLE_API_KEY",
476
+ "OPENROUTER_API_KEY",
477
+ "XAI_API_KEY",
478
+ "GROQ_API_KEY",
479
+ "MISTRAL_API_KEY"
480
+ ];
481
+ /** Provider keys that exist only as shell variables: the service cannot see them. */
482
+ const shellOnlyProviderKeys = (env) => PROVIDER_KEY_VARIABLES.filter((name) => Boolean(env[name]));
483
+ const providerKeyNotice = (names) => names.length === 0 ? void 0 : `Not copied to the service: ${names.join(", ")}. Save these in Settings → Providers.`;
484
+ //#endregion
485
+ //#region src/service/manager.ts
486
+ function writeFiles(files) {
487
+ let changed = false;
488
+ for (const file of files) {
489
+ const current = existsSync(file.path) ? readFileSync(file.path) : void 0;
490
+ const next = Buffer.from(file.content);
491
+ if (current?.equals(next)) continue;
492
+ mkdirSync(dirname(file.path), { recursive: true });
493
+ writeFileSync(file.path, next);
494
+ changed = true;
495
+ }
496
+ return changed;
497
+ }
498
+ function readRecord(home) {
499
+ try {
500
+ const value = JSON.parse(readFileSync(recordFileOf(home), "utf8"));
501
+ return typeof value.node === "string" && typeof value.entry === "string" ? {
502
+ node: value.node,
503
+ entry: value.entry
504
+ } : void 0;
505
+ } catch {
506
+ return;
507
+ }
508
+ }
509
+ function createServiceManager(context, cli, home) {
510
+ const spec = buildSpec(context, cli, home);
511
+ const native = nativeBackendOf(context);
512
+ const detached = detachedBackendOf(context);
513
+ const installed = async () => native ? native.isInstalled(spec) : false;
514
+ const activeBackend = async () => await installed() && native ? native : detached;
515
+ function record() {
516
+ mkdirSync(home, { recursive: true });
517
+ writeFileSync(recordFileOf(home), JSON.stringify({
518
+ node: spec.node,
519
+ entry: spec.entry
520
+ }));
521
+ }
522
+ async function installNatively(backend) {
523
+ writeFiles(backend.files(spec));
524
+ const outcome = await backend.install(spec);
525
+ if (!outcome.fellBack) return [...outcome.warnings];
526
+ for (const file of backend.files(spec)) rmSync(file.path, { force: true });
527
+ await detached.install(spec);
528
+ return [...outcome.warnings];
529
+ }
530
+ return {
531
+ spec,
532
+ installed,
533
+ recorded: () => readRecord(home),
534
+ async needsRepair() {
535
+ if (!await installed()) return false;
536
+ const recorded = readRecord(home);
537
+ return recorded?.node !== spec.node || recorded?.entry !== spec.entry;
538
+ },
539
+ async install() {
540
+ refuseTemporaryEntry(spec.entry);
541
+ record();
542
+ const warnings = [providerKeyNotice(shellOnlyProviderKeys(context.env))];
543
+ if (!native) {
544
+ await detached.install(spec);
545
+ return [FALLBACK_WARNING, ...warnings].filter((line) => line !== void 0);
546
+ }
547
+ return [...await installNatively(native), ...warnings].filter((line) => line !== void 0);
548
+ },
549
+ async uninstall() {
550
+ await detached.stop(spec);
551
+ if (native) {
552
+ await native.uninstall(spec);
553
+ for (const file of native.files(spec)) rmSync(file.path, { force: true });
554
+ }
555
+ rmSync(recordFileOf(home), { force: true });
556
+ },
557
+ async start() {
558
+ await (await activeBackend()).start(spec);
559
+ },
560
+ async stop() {
561
+ await (await activeBackend()).stop(spec);
562
+ },
563
+ async restart() {
564
+ if (!await installed() || !native) return (await activeBackend()).restart(spec);
565
+ refuseTemporaryEntry(spec.entry);
566
+ const changed = writeFiles(native.files(spec));
567
+ record();
568
+ await (changed ? native.reload(spec) : native.restart(spec));
569
+ },
570
+ async state() {
571
+ const backend = await activeBackend();
572
+ return {
573
+ ...await backend.state(spec),
574
+ kind: backend.kind
575
+ };
576
+ }
577
+ };
578
+ }
579
+ /** Every tau service of this user, dev homes included. */
580
+ async function listServices(context) {
581
+ return await nativeBackendOf(context)?.list() ?? [];
582
+ }
583
+ /** Removes the service of one home without needing the current CLI paths. */
584
+ async function uninstallHome(context, home) {
585
+ await createServiceManager(context, {
586
+ node: "",
587
+ entry: ""
588
+ }, home).uninstall();
589
+ }
590
+ //#endregion
591
+ //#region src/service/service-command.ts
592
+ const USAGE = "Usage: tau service install | uninstall [--home <path>] | list";
593
+ /** `tau service install | uninstall [--home <path>] | list`. `argv` starts after the word `service`. */
594
+ async function runServiceCommandWith(deps, argv) {
595
+ const { positionals, values } = parseArgs({
596
+ args: [...argv],
597
+ allowPositionals: true,
598
+ options: { home: { type: "string" } }
599
+ });
600
+ const home = values.home ?? deps.context.env.TAU_HOME ?? deps.context.defaultHome;
601
+ const [action] = positionals;
602
+ if (action === "install") {
603
+ const warnings = await createServiceManager(deps.context, cliEntry(), home).install();
604
+ for (const warning of warnings) deps.out(`warning: ${warning}`);
605
+ deps.out("tau service installed.");
606
+ } else if (action === "uninstall") {
607
+ await uninstallHome(deps.context, home);
608
+ deps.out("tau service removed.");
609
+ } else if (action === "list") {
610
+ const names = await listServices(deps.context);
611
+ deps.out(names.length === 0 ? "No tau services." : names.join("\n"));
612
+ } else throw new Error(USAGE);
613
+ }
614
+ async function runServiceCommand(argv) {
615
+ await runServiceCommandWith({
616
+ context: realContext(),
617
+ out: (message) => console.log(message)
618
+ }, argv);
619
+ }
620
+ //#endregion
621
+ export { TemporaryEntryError as a, realContext as i, runServiceCommandWith as n, createServiceManager as r, runServiceCommand as t };