@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.
- package/dist/auth-storage-Dk3CUY3q.js +1814 -0
- package/dist/bin.js +23 -0
- package/dist/cli-entry-DcDpQ30L.js +13 -0
- package/dist/client-C91-b2CL.js +1 -0
- package/dist/client-y2WkNuZj.js +7977 -0
- package/dist/dist-BFP19NOO.js +432 -0
- package/dist/dist-Bh1bQHZA.js +38890 -0
- package/dist/dist-Cq73_r65.js +3 -0
- package/dist/esm-CMGh_WhC.js +9446 -0
- package/dist/execute-BeGXhU1O.js +1757 -0
- package/dist/fake-claude-connectors-DSP9UC8d.js +1 -0
- package/dist/fake-claude-connectors-sDLeP10T.js +107 -0
- package/dist/fake-claude-sdk-CvqW_1iQ.js +571 -0
- package/dist/fake-claude-thread-script-2iHHwUZM.js +48 -0
- package/dist/last-exit-write-ksHeHGrC.js +9 -0
- package/dist/latest-version-di9hD7cY.js +106 -0
- package/dist/lib-DIX7q_HJ.js +1460 -0
- package/dist/machines-behind-BjGgmSs_.js +257 -0
- package/dist/main-D-Tg9b5Y.js +1610 -0
- package/dist/manager-B_WHbb1U.js +589 -0
- package/dist/names-cTrEXB6w.js +35 -0
- package/dist/node-BpwblPFP.js +56269 -0
- package/dist/pdfjs-nTcHjDPT.js +69856 -0
- package/dist/photon_rs-DBr7W_rK.js +4252 -0
- package/dist/promise.try-CAAOYvMP.js +1426 -0
- package/dist/readability-DsDNO63a.js +1670 -0
- package/dist/real-deps-f96UmLBz.js +89 -0
- package/dist/rolldown-runtime-1p2KPIdQ.js +44 -0
- package/dist/runtime-i-oEtgAl.js +2721 -0
- package/dist/sdk-missing-CHbVbY1G.js +10 -0
- package/dist/sdk-port-BGw9ORUR.js +215 -0
- package/dist/serve-Cmy0rUWr.js +2932 -0
- package/dist/serve-Dmzn5V0l.js +1 -0
- package/dist/service-command-C74K0saj.js +34 -0
- package/dist/service-command-DqcstJ8e.js +1 -0
- package/dist/shell-CQa4e7rx.js +643 -0
- package/dist/skills/builtin/tau/SKILL.md +51 -0
- package/dist/src-DO9LUlhi.js +44088 -0
- package/dist/streamableHttp-BBaRYV9h.js +1 -0
- package/dist/streamableHttp-DPAoMlJd.js +1931 -0
- package/dist/tool-DBZK2lci.js +14335 -0
- package/dist/tool-server-BjP37EJg.js +40 -0
- package/dist/turndown.cjs-Bo1xLg2S.js +15620 -0
- package/dist/turndown.cjs-jpUF50AR.js +3 -0
- package/dist/types-C4M3CRp2.js +7592 -0
- package/dist/undici-8cI97lnP.js +27110 -0
- package/dist/wait-for-lock-B1Qx8GWg.js +1 -0
- package/dist/wait-for-lock-CrSl2mi1.js +25 -0
- package/dist/watchdog.js +47 -0
- package/dist/web/dist/assets/addon-fit-DIOBYJe3.js +1 -0
- package/dist/web/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dist/web/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dist/web/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dist/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dist/web/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dist/web/dist/assets/index-BktLKZHR.js +108 -0
- package/dist/web/dist/assets/index-D_Aa3Oou.css +2 -0
- package/dist/web/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/web/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/web/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/web/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/web/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/web/dist/assets/wght-DMLdVLkb.css +1 -0
- package/dist/web/dist/assets/xterm-BrP-ENHg.css +1 -0
- package/dist/web/dist/assets/xterm-CerOhKYV.js +16 -0
- package/dist/web/dist/favicon.svg +14 -0
- package/dist/web/dist/index.html +25 -0
- package/package.json +53 -4
- 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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
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 };
|