@pablopunk/tau 0.0.0-stage → 0.1.1

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