@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,1610 @@
1
+ import { $ as routes, B as COMMAND_NOUNS, G as isModelRefNode, H as commandOf, J as LIVE_PATH, K as routeFields, M as decodeServerFrame, N as encodeFrame, U as commands, V as FLOW_COMMANDS, W as flagNameOf, a as lastAssistantText, it as RedeemPairingResponseSchema, nt as nextViewState, o as viewToText, s as compareVersions, z as AUTO_FIELDS } from "./src-DO9LUlhi.js";
2
+ import { a as lifecycleFlagsOf, c as connectRefusal, i as commandWordOf, n as serverLogOf, o as createHubFileStore, r as writeStoredPort, s as alreadyLinkedRefusal, t as serveInForeground } from "./serve-Cmy0rUWr.js";
3
+ import { a as realLifecycleDeps, c as renderExtras, d as discoverServer, f as CliExit, i as managerOf, l as processEnv, m as usageError, n as linkedMachinesLine, o as withToken, p as failure, r as NOT_RUNNING, s as NO_EXTRAS, u as callRoute } from "./machines-behind-BjGgmSs_.js";
4
+ import { a as cliVersion, r as npmOf, t as PACKAGE_NAME } from "./latest-version-di9hD7cY.js";
5
+ import { a as TemporaryEntryError, i as realContext } from "./manager-B_WHbb1U.js";
6
+ import { t as runServiceCommand } from "./service-command-C74K0saj.js";
7
+ import { createReadStream, existsSync, readFileSync, statSync, watchFile } from "node:fs";
8
+ import { dirname, join } from "node:path";
9
+ import { hostname } from "node:os";
10
+ import { fileURLToPath } from "node:url";
11
+ import { parseArgs } from "node:util";
12
+ import { randomUUID } from "node:crypto";
13
+ import { readFile, readdir } from "node:fs/promises";
14
+ import { Value } from "typebox/value";
15
+ import { createInterface } from "node:readline/promises";
16
+ import QRCode from "qrcode";
17
+ //#region ../protocol/src/live-info.ts
18
+ function docOf(view, kind) {
19
+ return view.docs[kind];
20
+ }
21
+ function isQueuedMessage(item) {
22
+ return item.mode === "steer" || item.mode === "followUp";
23
+ }
24
+ function queuedBlocks(item) {
25
+ const content = item.content;
26
+ if (typeof content === "string") return content;
27
+ const blocks = content?.content;
28
+ if (typeof blocks === "string") return blocks;
29
+ return Array.isArray(blocks) ? blocks : [];
30
+ }
31
+ function queuedText(item) {
32
+ const blocks = queuedBlocks(item);
33
+ if (typeof blocks === "string") return blocks;
34
+ return blocks.map((block) => block.type === "text" ? block.text : "").join("");
35
+ }
36
+ function queuedImages(item) {
37
+ const blocks = queuedBlocks(item);
38
+ if (typeof blocks === "string") return [];
39
+ return blocks.filter((block) => block.type === "image");
40
+ }
41
+ function queuedOf(item) {
42
+ return {
43
+ id: Number(item.id),
44
+ mode: item.mode,
45
+ text: queuedText(item),
46
+ images: queuedImages(item)
47
+ };
48
+ }
49
+ function retryOf(live) {
50
+ const generation = live.generation;
51
+ if (!generation?.retry) return void 0;
52
+ return {
53
+ attempt: generation.attempt,
54
+ ...generation.retry
55
+ };
56
+ }
57
+ function readLive(view) {
58
+ const live = docOf(view, "pi.live") ?? {};
59
+ const inbox = docOf(view, "pi.inbox");
60
+ return {
61
+ busy: live.run !== void 0,
62
+ streaming: live.generation?.message,
63
+ retry: retryOf(live),
64
+ slots: live.tools ?? [],
65
+ compacting: (live.compactions?.length ?? 0) > 0,
66
+ queued: (inbox?.items ?? []).filter(isQueuedMessage).map(queuedOf)
67
+ };
68
+ }
69
+ //#endregion
70
+ //#region src/machines/connect-args.ts
71
+ const USAGE = "Usage: tau connect <link | code --hub <url>> [--name <label>] [--yes]";
72
+ function parseConnectArgs(argv) {
73
+ let target;
74
+ let hub;
75
+ let name;
76
+ let yes = false;
77
+ for (let index = 0; index < argv.length; index++) {
78
+ const token = argv[index];
79
+ const next = argv[index + 1];
80
+ if (token === "--hub" && next !== void 0) hub = argv[++index];
81
+ else if (token.startsWith("--hub=")) hub = token.slice(6);
82
+ else if (token === "--name" && next !== void 0) name = argv[++index];
83
+ else if (token.startsWith("--name=")) name = token.slice(7);
84
+ else if (token === "--yes" || token === "-y") yes = true;
85
+ else if (!token.startsWith("-") && target === void 0) target = token;
86
+ else throw usageError(`Unknown argument "${token}". ${USAGE}`);
87
+ }
88
+ if (target === void 0) throw usageError(USAGE);
89
+ return {
90
+ target,
91
+ ...hub !== void 0 && { hub },
92
+ name: name?.trim() || hostname(),
93
+ yes
94
+ };
95
+ }
96
+ //#endregion
97
+ //#region src/machines/connect-io.ts
98
+ async function ask(question) {
99
+ if (!process.stdin.isTTY) throw usageError("This needs a terminal. Add --yes to skip the question.");
100
+ const lines = createInterface({
101
+ input: process.stdin,
102
+ output: process.stdout
103
+ });
104
+ try {
105
+ return (await lines.question(question)).trim().toLowerCase();
106
+ } finally {
107
+ lines.close();
108
+ }
109
+ }
110
+ const terminalIo = {
111
+ confirm: async (question) => ["y", "yes"].includes(await ask(`${question} [y/N] `)),
112
+ async chooseStart() {
113
+ const answer = await ask("tau is not running here. Start it for this session (s), install it as a service (i), or neither (n)? [s/i/N] ");
114
+ return answer === "s" ? "serve" : answer === "i" ? "service" : "none";
115
+ }
116
+ };
117
+ //#endregion
118
+ //#region src/machines/connect-target.ts
119
+ const LOOPBACK = /* @__PURE__ */ new Set([
120
+ "127.0.0.1",
121
+ "localhost",
122
+ "[::1]"
123
+ ]);
124
+ /** `https://` always; `http://` only for loopback (the same-Mac test with `--hub http://127.0.0.1:<port>`). */
125
+ function normalizeHubUrl(input) {
126
+ let url;
127
+ try {
128
+ url = new URL(input);
129
+ } catch {
130
+ throw usageError(`"${input}" is not a hub URL. Example: https://abc123.opentunnel.xyz`);
131
+ }
132
+ const loopback = LOOPBACK.has(url.hostname);
133
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) throw usageError("The hub URL must start with https:// (http:// only for 127.0.0.1 or localhost).");
134
+ return url.origin;
135
+ }
136
+ /**
137
+ * `tau connect <link>` takes `https://<hostname>/pair#<code>[.<name>]`; `tau connect <short code> --hub <url>`
138
+ * takes the 8-character code. There is no unauthenticated endpoint that returns the hub's name, so it rides in the link.
139
+ */
140
+ function parseConnectTarget(input, hub) {
141
+ if (!input.includes("/")) {
142
+ if (hub === void 0) throw usageError("A short code needs the hub: tau connect <code> --hub <url>");
143
+ return {
144
+ hubUrl: normalizeHubUrl(hub),
145
+ code: input.replace(/\s+/g, "")
146
+ };
147
+ }
148
+ normalizeHubUrl(input);
149
+ const url = new URL(input);
150
+ const fragment = url.hash.replace(/^#/, "");
151
+ if (url.pathname !== "/pair" || fragment === "") throw usageError("That is not a link code. Make one in Settings → Machines on the hub.");
152
+ const dot = fragment.indexOf(".");
153
+ const code = dot < 0 ? fragment : fragment.slice(0, dot);
154
+ const name = dot < 0 ? "" : decodeURIComponent(fragment.slice(dot + 1));
155
+ return {
156
+ hubUrl: url.origin,
157
+ code,
158
+ ...name !== "" && { hubName: name }
159
+ };
160
+ }
161
+ //#endregion
162
+ //#region src/commands/command-flags.ts
163
+ const WAIT_COMMANDS = ["threads start", "threads send"];
164
+ const COMMON_FLAGS = [
165
+ {
166
+ name: "json",
167
+ kind: "value",
168
+ valueKind: "output",
169
+ summary: "Whole request body as JSON (replaces the flags)"
170
+ },
171
+ {
172
+ name: "yes",
173
+ kind: "boolean",
174
+ valueKind: "output",
175
+ summary: "Confirm a destructive command"
176
+ },
177
+ {
178
+ name: "home",
179
+ kind: "value",
180
+ valueKind: "output",
181
+ summary: "Tau data home (default: TAU_HOME or ~/.tau)"
182
+ }
183
+ ];
184
+ const WAIT_FLAGS = [{
185
+ name: "wait",
186
+ kind: "boolean",
187
+ valueKind: "output",
188
+ summary: "Wait until the thread is done, then print the answer"
189
+ }, {
190
+ name: "timeout",
191
+ kind: "value",
192
+ valueKind: "output",
193
+ summary: "Seconds to wait (default 600)"
194
+ }];
195
+ const isLiteralBooleanString = (field) => field.node.anyOf?.every((member) => member.const === "true" || member.const === "false") ?? false;
196
+ function valueKindOf(field) {
197
+ if (isModelRefNode(field.node)) return "model";
198
+ if (isLiteralBooleanString(field)) return "booleanString";
199
+ if (field.boolean) return "boolean";
200
+ const type = field.node.type;
201
+ return type === "number" || type === "integer" ? "number" : "string";
202
+ }
203
+ const isFlagField = (def, field) => !def.positionals.includes(field.path) && def.fixed?.[field.path] === void 0 && (field.primitive || isModelRefNode(field.node));
204
+ function flagOf(field) {
205
+ const valueKind = valueKindOf(field);
206
+ const isBoolean = valueKind === "boolean" || valueKind === "booleanString";
207
+ return {
208
+ name: flagNameOf(field.name),
209
+ kind: isBoolean ? "boolean" : "value",
210
+ valueKind,
211
+ field,
212
+ summary: `${field.required ? "Required. " : ""}${field.source} field ${field.name}`
213
+ };
214
+ }
215
+ function commandFlags(def) {
216
+ const fieldFlags = routeFields(def.route).filter((field) => isFlagField(def, field)).map(flagOf);
217
+ const waitFlags = WAIT_COMMANDS.includes(def.command) ? WAIT_FLAGS : [];
218
+ return [
219
+ ...fieldFlags,
220
+ ...waitFlags,
221
+ ...COMMON_FLAGS
222
+ ];
223
+ }
224
+ const flagSpecOf = (flags) => Object.fromEntries(flags.map((flag) => [flag.name, flag.kind]));
225
+ //#endregion
226
+ //#region src/commands/scan-args.ts
227
+ const NEGATION = /^no-(.+)$/;
228
+ function booleanNameOf(name, spec) {
229
+ if (spec[name] === "boolean") return {
230
+ name,
231
+ value: true
232
+ };
233
+ const negated = NEGATION.exec(name)?.[1];
234
+ if (negated !== void 0 && spec[negated] === "boolean") return {
235
+ name: negated,
236
+ value: false
237
+ };
238
+ }
239
+ /** `--flag value`, `--flag=value`, `--flag` / `--no-flag` for booleans, `--` ends the flags. */
240
+ function scanArgs(argv, spec) {
241
+ const positionals = [];
242
+ const flags = /* @__PURE__ */ new Map();
243
+ for (let index = 0; index < argv.length; index++) {
244
+ const token = argv[index];
245
+ if (token === "--") {
246
+ positionals.push(...argv.slice(index + 1));
247
+ break;
248
+ }
249
+ if (!token.startsWith("--")) {
250
+ positionals.push(token);
251
+ continue;
252
+ }
253
+ const [rawName, inline] = splitInline(token.slice(2));
254
+ const boolean = booleanNameOf(rawName, spec);
255
+ if (boolean && inline === void 0) {
256
+ flags.set(boolean.name, boolean.value);
257
+ continue;
258
+ }
259
+ if (spec[rawName] !== "value") throw usageError(`Unknown flag: --${rawName}`);
260
+ const value = inline ?? argv[++index];
261
+ if (value === void 0) throw usageError(`Flag --${rawName} needs a value`);
262
+ flags.set(rawName, value);
263
+ }
264
+ return {
265
+ positionals,
266
+ flags
267
+ };
268
+ }
269
+ function splitInline(text) {
270
+ const at = text.indexOf("=");
271
+ return at < 0 ? [text, void 0] : [text.slice(0, at), text.slice(at + 1)];
272
+ }
273
+ const NONE = "none";
274
+ function parseModelRef(raw) {
275
+ const slash = raw.indexOf("/");
276
+ if (slash <= 0 || slash === raw.length - 1) throw usageError(`Expected provider/modelId, got: ${raw}`);
277
+ return {
278
+ provider: raw.slice(0, slash),
279
+ modelId: raw.slice(slash + 1)
280
+ };
281
+ }
282
+ function coerce(raw, kind, nullable) {
283
+ if (raw === NONE && nullable) return null;
284
+ if (typeof raw === "boolean") return kind === "booleanString" ? String(raw) : raw;
285
+ if (kind === "model") return parseModelRef(raw);
286
+ if (kind === "number") {
287
+ const value = Number(raw);
288
+ if (Number.isNaN(value)) throw usageError(`Expected a number, got: ${raw}`);
289
+ return value;
290
+ }
291
+ return raw;
292
+ }
293
+ function setPath(target, path, value) {
294
+ target[path.slice(path.indexOf(".") + 1)] = value;
295
+ }
296
+ function fieldAt(def, path) {
297
+ return routeFields(def.route).find((field) => field.path === path);
298
+ }
299
+ function targetOf(sources, path) {
300
+ if (path.startsWith("params.")) return sources.params;
301
+ return path.startsWith("query.") ? sources.query : sources.body;
302
+ }
303
+ function assignPositionals(def, values, sources) {
304
+ if (values.length < def.positionals.length) {
305
+ const missing = def.positionals.slice(values.length).map((path) => `<${path.split(".").pop()}>`);
306
+ throw usageError(`Missing ${missing.join(" ")}. Usage: tau ${def.command} ${usageOf(def)}`);
307
+ }
308
+ if (values.length > def.positionals.length) throw usageError(`Unexpected argument: ${values[def.positionals.length]}`);
309
+ let wholeBody;
310
+ def.positionals.forEach((path, index) => {
311
+ const raw = values[index];
312
+ if (path === "body") {
313
+ wholeBody = parseModelRef(raw);
314
+ return;
315
+ }
316
+ const field = fieldAt(def, path);
317
+ const kind = def.modelRefs?.includes(path) ? "model" : field ? valueKindOf(field) : "string";
318
+ setPath(targetOf(sources, path), path, coerce(raw, kind, field?.nullable ?? false));
319
+ });
320
+ return wholeBody;
321
+ }
322
+ function assignFlags(flags, given, sources) {
323
+ for (const flag of flags) {
324
+ const raw = given.get(flag.name);
325
+ if (raw === void 0 || !flag.field || flag.valueKind === "output") continue;
326
+ setPath(targetOf(sources, flag.field.path), flag.field.path, coerce(raw, flag.valueKind, flag.field.nullable));
327
+ }
328
+ }
329
+ function applyFixed(def, sources) {
330
+ for (const [path, value] of Object.entries(def.fixed ?? {})) setPath(targetOf(sources, path), path, value);
331
+ }
332
+ function defaultRequiredBooleans(def, sources) {
333
+ for (const field of routeFields(def.route)) {
334
+ const target = targetOf(sources, field.path);
335
+ if (field.source === "body" && field.required && field.boolean && target[field.name] === void 0) target[field.name] = false;
336
+ }
337
+ }
338
+ function fillAutoFields(def, body) {
339
+ for (const field of routeFields(def.route)) if (field.source === "body" && AUTO_FIELDS.includes(field.name) && body[field.name] === void 0) body[field.name] = randomUUID();
340
+ }
341
+ /** `tau mcp save docs --json '{…}'`: the body repeats the `name` the positional already gave. */
342
+ function fillFromParams(def, params, body) {
343
+ const bodyNames = routeFields(def.route).filter((field) => field.source === "body").map((field) => field.name);
344
+ for (const [name, value] of Object.entries(params)) if (bodyNames.includes(name) && body[name] === void 0) body[name] = value;
345
+ }
346
+ function parseJsonBody(raw) {
347
+ try {
348
+ const value = JSON.parse(raw);
349
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) return value;
350
+ } catch {}
351
+ throw usageError("--json needs a JSON object");
352
+ }
353
+ function checkSchema(schema, value, label) {
354
+ if (!schema) return;
355
+ const first = Array.from(Value.Errors(schema, value))[0];
356
+ if (!first) return;
357
+ const where = first.instancePath ? `${label}${first.instancePath.replaceAll("/", ".")}` : label;
358
+ throw usageError(`Invalid ${where}: ${first.message}`);
359
+ }
360
+ function validate(def, input) {
361
+ const route = routes[def.route];
362
+ checkSchema(route.params, input.params, "params");
363
+ checkSchema(route.query, input.query, "query");
364
+ checkSchema(route.body, input.body, "body");
365
+ }
366
+ function parseTimeout(raw) {
367
+ if (raw === void 0) return 600;
368
+ const seconds = Number(raw);
369
+ if (typeof raw === "boolean" || !(seconds > 0)) throw usageError("--timeout needs a number of seconds");
370
+ return seconds;
371
+ }
372
+ function bodyOf(def, sources, whole, json) {
373
+ if (!(routes[def.route].body !== void 0)) return void 0;
374
+ if (typeof json === "string") {
375
+ const body = parseJsonBody(json);
376
+ fillAutoFields(def, body);
377
+ fillFromParams(def, sources.params, body);
378
+ return body;
379
+ }
380
+ if (def.json) throw usageError(`tau ${def.command} needs --json '<body>'`);
381
+ if (whole !== void 0) return whole;
382
+ fillAutoFields(def, sources.body);
383
+ return sources.body;
384
+ }
385
+ const stringsOf = (record) => Object.fromEntries(Object.entries(record).map(([key, value]) => [key, String(value)]));
386
+ /** Usage text for the positionals, for example `<id> <text>`. */
387
+ function usageOf(def) {
388
+ return def.positionals.map((path) => `<${path === "body" ? "model" : path.split(".").pop()}>`).join(" ");
389
+ }
390
+ function findCommand(noun, verb) {
391
+ const def = verb === void 0 ? void 0 : commandOf(`${noun} ${verb}`);
392
+ if (def) return def;
393
+ const known = commands.filter((entry) => entry.command.startsWith(`${noun} `)).map((entry) => entry.command.slice(noun.length + 1));
394
+ throw usageError(`Unknown command: ${noun} ${verb ?? ""}. Try: ${known.join(", ")}`);
395
+ }
396
+ /** Argv after the verb, the table and the schemas give the route input. Throws a usage error. */
397
+ function parseCommand(def, argv) {
398
+ const flags = commandFlags(def);
399
+ const { positionals, flags: given } = scanArgs(argv, flagSpecOf(flags));
400
+ const sources = {
401
+ params: {},
402
+ query: {},
403
+ body: {}
404
+ };
405
+ const whole = assignPositionals(def, positionals, sources);
406
+ assignFlags(flags, given, sources);
407
+ applyFixed(def, sources);
408
+ defaultRequiredBooleans(def, sources);
409
+ const input = {
410
+ params: stringsOf(sources.params),
411
+ query: stringsOf(sources.query),
412
+ body: bodyOf(def, sources, whole, given.get("json"))
413
+ };
414
+ validate(def, input);
415
+ return {
416
+ def,
417
+ input,
418
+ yes: given.get("yes") === true,
419
+ wait: given.get("wait") === true,
420
+ timeoutSeconds: parseTimeout(given.get("timeout")),
421
+ home: typeof given.get("home") === "string" ? given.get("home") : void 0
422
+ };
423
+ }
424
+ //#endregion
425
+ //#region src/commands/help.ts
426
+ const THREAD_EXTRAS = [" wait <id> Wait until the thread is done, then print the answer (--timeout <s>)", " read <id> Print the thread as text (--last <n>)"];
427
+ const LIFECYCLE = [
428
+ " start, stop, restart, status, open, logs, service Run and manage the server",
429
+ " serve Run the server in the foreground",
430
+ " update Install the latest tau and restart the server (--check, --yes, --force, --no-restart)",
431
+ " skill Print the tau skill for agents"
432
+ ];
433
+ const UPDATE_FLAGS = [
434
+ "",
435
+ "tau update flags:",
436
+ " --check Print current -> latest; exit 0 current, 10 update available, 1 error",
437
+ " --yes Skip only the \"Install X -> Y and restart?\" question",
438
+ " --force Restart even when threads are running",
439
+ " --no-restart Install and verify, then print `tau restart`",
440
+ " --home <dir>, --port <n> Forwarded to `tau restart`"
441
+ ];
442
+ const pad = (text) => text.padEnd(26);
443
+ function rootHelp() {
444
+ return [
445
+ "tau: chat with a coding agent from the browser, and control it from the shell.",
446
+ "",
447
+ "Usage: tau <command> [args] (no command starts the server and opens the browser)",
448
+ "",
449
+ "Server:",
450
+ ...LIFECYCLE,
451
+ "",
452
+ "API commands (output is JSON):",
453
+ ...COMMAND_NOUNS.map((noun) => ` ${noun}`),
454
+ ...FLOW_COMMANDS.map((word) => ` ${word}`),
455
+ "",
456
+ "Run `tau <noun> --help` for the commands of a noun, `tau <noun> <verb> --help` for one command.",
457
+ ...UPDATE_FLAGS
458
+ ].join("\n");
459
+ }
460
+ function nounHelp(noun) {
461
+ const lines = commands.filter((entry) => entry.command.startsWith(`${noun} `)).map((entry) => ` ${pad(`${entry.command.slice(noun.length + 1)} ${usageOf(entry)}`)}${entry.summary}`);
462
+ const extras = noun === "threads" ? THREAD_EXTRAS : [];
463
+ return [
464
+ `tau ${noun} <command>`,
465
+ "",
466
+ ...lines,
467
+ ...extras
468
+ ].join("\n");
469
+ }
470
+ function commandHelp(command) {
471
+ const def = commandOf(command);
472
+ if (!def) throw usageError(`Unknown command: ${command}`);
473
+ const flags = commandFlags(def).map((flag) => {
474
+ const value = flag.kind === "value" ? " <value>" : "";
475
+ return ` ${pad(`--${flag.name}${value}`)}${flag.summary}`;
476
+ });
477
+ const destructive = def.destructive ? ["", "Destructive: needs --yes."] : [];
478
+ return [
479
+ `tau ${def.command} ${usageOf(def)}`,
480
+ def.summary,
481
+ "",
482
+ "Flags:",
483
+ ...flags,
484
+ ...destructive
485
+ ].join("\n");
486
+ }
487
+ /** Nouns, the commands of a noun, or one command's positionals and flags. */
488
+ function helpText(noun, verb) {
489
+ if (noun === void 0) return rootHelp();
490
+ if (!COMMAND_NOUNS.includes(noun)) throw usageError(`Unknown command: ${noun}`);
491
+ if (verb === void 0) return nounHelp(noun);
492
+ if (noun === "threads" && (verb === "wait" || verb === "read")) return nounHelp(noun);
493
+ return commandHelp(`${noun} ${verb}`);
494
+ }
495
+ /** `tau help [noun [verb]]` and `tau --help`. */
496
+ async function runHelp(argv) {
497
+ const words = argv.filter((word) => !word.startsWith("-"));
498
+ console.log(helpText(words[0], words[1]));
499
+ }
500
+ //#endregion
501
+ //#region src/commands/own-thread.ts
502
+ const GUARDED_COMMANDS = ["threads delete", "threads archive"];
503
+ function lineageOf(all, ownId) {
504
+ const lineage = /* @__PURE__ */ new Set();
505
+ let current = ownId;
506
+ while (current && !lineage.has(current)) {
507
+ lineage.add(current);
508
+ current = all.find((item) => item.id === current)?.parentId;
509
+ }
510
+ return lineage;
511
+ }
512
+ /** An agent must not delete or archive the thread it runs in, or a thread above it. */
513
+ async function assertNotOwnLineage(server, env, command, threadId) {
514
+ const ownId = env.env.TAU_THREAD_ID;
515
+ if (!ownId) return;
516
+ if (lineageOf(await callRoute(server, "listAllConversations", {}, env), ownId).has(threadId)) throw failure(`Refusing \`tau ${command}\`: ${threadId} is your own thread or one of its parents.`);
517
+ }
518
+ //#endregion
519
+ //#region src/commands/live.ts
520
+ const websocketUrlOf = (server) => `${server.url.replace(/^http/, "ws")}${LIVE_PATH}`;
521
+ var NodeLiveClient = class {
522
+ socket;
523
+ views = /* @__PURE__ */ new Map();
524
+ frameListeners = [];
525
+ closeListeners = [];
526
+ pending = [];
527
+ open = false;
528
+ constructor(server, env) {
529
+ this.socket = env.openSocket(websocketUrlOf(server), { authorization: `Bearer ${server.token}` });
530
+ this.socket.onopen = () => {
531
+ this.open = true;
532
+ for (const frame of this.pending.splice(0)) this.socket.send(frame);
533
+ };
534
+ this.socket.onmessage = (event) => this.receive(String(event.data));
535
+ this.socket.onclose = () => {
536
+ for (const listener of this.closeListeners) listener();
537
+ };
538
+ this.socket.onerror = () => void 0;
539
+ }
540
+ receive(raw) {
541
+ const frame = decodeServerFrame(raw);
542
+ if (!frame) return;
543
+ if (frame.type === "ops") try {
544
+ this.views.set(frame.conversationId, nextViewState(this.views.get(frame.conversationId), frame.ops));
545
+ } catch {
546
+ return;
547
+ }
548
+ for (const listener of this.frameListeners) listener();
549
+ }
550
+ subscribe(conversationId) {
551
+ const frame = encodeFrame({
552
+ type: "subscribe",
553
+ conversationId
554
+ });
555
+ if (this.open) this.socket.send(frame);
556
+ else this.pending.push(frame);
557
+ }
558
+ view(conversationId) {
559
+ return this.views.get(conversationId);
560
+ }
561
+ onFrame(listener) {
562
+ this.frameListeners.push(listener);
563
+ }
564
+ onClose(listener) {
565
+ this.closeListeners.push(listener);
566
+ }
567
+ close() {
568
+ this.socket.close();
569
+ }
570
+ };
571
+ /** A Node WebSocket client for `subscribe`, `ops` and `changed` frames, with Bearer auth on upgrade. */
572
+ const connectLive = (server, env) => new NodeLiveClient(server, env);
573
+ //#endregion
574
+ //#region src/commands/read.ts
575
+ const READ_TIMEOUT_MS = 1e4;
576
+ /** The first view frame of a thread, as text. `last` keeps the final N messages. */
577
+ function readThread(server, env, conversationId, last) {
578
+ const live = connectLive(server, env);
579
+ live.subscribe(conversationId);
580
+ return new Promise((resolve, reject) => {
581
+ const cancel = env.after(READ_TIMEOUT_MS, () => end(() => reject(failure("Timed out reading the thread."))));
582
+ const end = (settle) => {
583
+ cancel();
584
+ live.close();
585
+ settle();
586
+ };
587
+ live.onClose(() => end(() => reject(failure("Lost the connection to tau."))));
588
+ live.onFrame(() => {
589
+ const view = live.view(conversationId);
590
+ if (view === void 0) return;
591
+ if (view === null) return end(() => reject(failure(`not_found: Unknown conversation: ${conversationId}`)));
592
+ end(() => resolve(viewToText(view, { last })));
593
+ });
594
+ });
595
+ }
596
+ //#endregion
597
+ //#region src/commands/wait.ts
598
+ const descendantsOf = (all, id) => {
599
+ const children = all.filter((item) => item.parentId === id);
600
+ return [...children, ...children.flatMap((child) => descendantsOf(all, child.id))];
601
+ };
602
+ const runEndedAfter = (summary, endedAfter) => {
603
+ if (endedAfter === void 0) return true;
604
+ if (summary.lastRunEndedAt === null) return false;
605
+ return endedAfter === null || summary.lastRunEndedAt > endedAfter;
606
+ };
607
+ function errorMessageOf(view) {
608
+ for (const entry of [...view.entries].reverse()) {
609
+ const message = entry.model?.[0];
610
+ if (message?.role === "assistant" && message.errorMessage) return message.errorMessage;
611
+ }
612
+ return "the thread ended in error";
613
+ }
614
+ /** The 4-condition rule: idle, a run ended after R0, an empty queue, and no running child. */
615
+ function assessThread(all, conversationId, view, endedAfter) {
616
+ const summary = all.find((item) => item.id === conversationId);
617
+ if (!summary) throw failure(`not_found: Unknown conversation: ${conversationId}`);
618
+ if (summary.status === "error" && runEndedAfter(summary, endedAfter)) return {
619
+ state: "error",
620
+ message: errorMessageOf(view)
621
+ };
622
+ const idle = summary.status === "idle";
623
+ const live = readLive(view);
624
+ const queueEmpty = live.queued.length === 0;
625
+ const viewCaughtUp = !live.busy;
626
+ const childRunning = descendantsOf(all, conversationId).some((child) => child.agentStatus === "running");
627
+ return idle && runEndedAfter(summary, endedAfter) && queueEmpty && viewCaughtUp && !childRunning ? {
628
+ state: "done",
629
+ answer: lastAssistantText(view)
630
+ } : { state: "waiting" };
631
+ }
632
+ const listAll = async (server, env) => await callRoute(server, "listAllConversations", {}, env);
633
+ /** Resolves with the last assistant answer. Exits 1 on an error thread, 3 on timeout. */
634
+ function waitForThread(server, env, request) {
635
+ const live = connectLive(server, env);
636
+ live.subscribe(request.conversationId);
637
+ return new Promise((resolve, reject) => {
638
+ let finished = false;
639
+ let evaluating = false;
640
+ let again = false;
641
+ const cancels = [];
642
+ const finish = (settle) => {
643
+ if (finished) return;
644
+ finished = true;
645
+ for (const cancel of cancels) cancel();
646
+ live.close();
647
+ settle();
648
+ };
649
+ const check = async () => {
650
+ const view = live.view(request.conversationId);
651
+ if (view === void 0) return;
652
+ if (view === null) throw failure(`not_found: Unknown conversation: ${request.conversationId}`);
653
+ const result = assessThread(await listAll(server, env), request.conversationId, view, request.endedAfter);
654
+ if (result.state === "done") finish(() => resolve(result.answer));
655
+ if (result.state === "error") finish(() => reject(failure(result.message)));
656
+ };
657
+ const evaluate = async () => {
658
+ if (finished) return;
659
+ if (evaluating) {
660
+ again = true;
661
+ return;
662
+ }
663
+ evaluating = true;
664
+ try {
665
+ do {
666
+ again = false;
667
+ await check();
668
+ } while (again && !finished);
669
+ } catch (error) {
670
+ finish(() => reject(error));
671
+ }
672
+ evaluating = false;
673
+ };
674
+ const poll = () => {
675
+ cancels.push(env.after(env.pollMs, () => {
676
+ evaluate();
677
+ if (!finished) poll();
678
+ }));
679
+ };
680
+ live.onFrame(() => void evaluate());
681
+ live.onClose(() => finish(() => reject(failure("Lost the connection to tau."))));
682
+ cancels.push(env.after(request.timeoutSeconds * 1e3, () => finish(() => reject(new CliExit(3, `Timed out after ${request.timeoutSeconds}s`)))));
683
+ poll();
684
+ });
685
+ }
686
+ /** `lastRunEndedAt` of a thread now (R0), or `null` when it has never run. */
687
+ async function runEndedAtOf(server, env, conversationId) {
688
+ const found = (await listAll(server, env)).find((item) => item.id === conversationId);
689
+ if (!found) throw failure(`not_found: Unknown conversation: ${conversationId}`);
690
+ return found.lastRunEndedAt;
691
+ }
692
+ //#endregion
693
+ //#region src/commands/thread-extras.ts
694
+ const EXTRA_THREAD_VERBS = ["wait", "read"];
695
+ function positiveNumber(raw, flag) {
696
+ if (raw === void 0) return void 0;
697
+ const value = Number(raw);
698
+ if (typeof raw === "boolean" || !(value > 0)) throw usageError(`--${flag} needs a positive number`);
699
+ return value;
700
+ }
701
+ function threadIdOf(verb, positionals) {
702
+ if (positionals.length !== 1) throw usageError(`Usage: tau threads ${verb} <id>`);
703
+ return positionals[0];
704
+ }
705
+ /** `tau threads wait <id> [--timeout s]` and `tau threads read <id> [--last N]`. They are not routes. */
706
+ async function runThreadExtra(verb, argv, env) {
707
+ const { positionals, flags } = scanArgs(argv, verb === "wait" ? {
708
+ timeout: "value",
709
+ home: "value"
710
+ } : {
711
+ last: "value",
712
+ home: "value"
713
+ });
714
+ const id = threadIdOf(verb, positionals);
715
+ const home = flags.get("home");
716
+ const scoped = typeof home === "string" ? {
717
+ ...env,
718
+ home
719
+ } : env;
720
+ const server = await discoverServer(scoped);
721
+ if (verb === "read") {
722
+ const last = positiveNumber(flags.get("last"), "last");
723
+ env.stdout(await readThread(server, scoped, id, last));
724
+ return;
725
+ }
726
+ const timeoutSeconds = positiveNumber(flags.get("timeout"), "timeout") ?? 600;
727
+ env.stdout(await waitForThread(server, scoped, {
728
+ conversationId: id,
729
+ endedAfter: void 0,
730
+ timeoutSeconds
731
+ }));
732
+ }
733
+ //#endregion
734
+ //#region src/commands/run-command.ts
735
+ const wantsHelp$2 = (argv) => argv.includes("--help") || argv.includes("-h");
736
+ const printJson = (env, result) => env.stdout(JSON.stringify(result === void 0 ? { ok: true } : result, null, 2));
737
+ async function guardDestructive(server, env, parsed) {
738
+ const id = parsed.input.params.id;
739
+ if (id !== void 0 && GUARDED_COMMANDS.includes(parsed.def.command)) await assertNotOwnLineage(server, env, parsed.def.command, id);
740
+ }
741
+ async function runWithWait(server, env, parsed) {
742
+ const isStart = parsed.def.command === "threads start";
743
+ const knownId = parsed.input.params.id;
744
+ const endedAfter = parsed.wait && !isStart && knownId !== void 0 ? await runEndedAtOf(server, env, knownId) : null;
745
+ const result = await callRoute(server, parsed.def.route, parsed.input, env);
746
+ printJson(env, result);
747
+ if (!parsed.wait) return;
748
+ const answer = await waitForThread(server, env, {
749
+ conversationId: knownId ?? result.id,
750
+ endedAfter,
751
+ timeoutSeconds: parsed.timeoutSeconds
752
+ });
753
+ env.stdout(`\n${answer}`);
754
+ }
755
+ /** Runs one API command and throws `CliExit` on any failure. Testable with an injected env. */
756
+ async function executeApiCommand(noun, argv, env) {
757
+ const verb = argv[0]?.startsWith("-") ? void 0 : argv[0];
758
+ if (verb === void 0 || wantsHelp$2(argv)) return env.stdout(helpText(noun, verb));
759
+ if (noun === "threads" && EXTRA_THREAD_VERBS.includes(verb)) return runThreadExtra(verb, argv.slice(1), env);
760
+ const parsed = parseCommand(findCommand(noun, verb), argv.slice(1));
761
+ if (parsed.def.destructive && !parsed.yes) throw usageError(`tau ${parsed.def.command} is destructive. Add --yes to confirm.`);
762
+ const scoped = parsed.home === void 0 ? env : {
763
+ ...env,
764
+ home: parsed.home
765
+ };
766
+ const server = await discoverServer(scoped);
767
+ await guardDestructive(server, scoped, parsed);
768
+ await runWithWait(server, scoped, parsed);
769
+ }
770
+ /** `--home` may come before the verb (`tau --home X threads list`); it is lifted out of argv. */
771
+ function takeHomeFlag(argv) {
772
+ const rest = [];
773
+ let home;
774
+ for (let index = 0; index < argv.length; index++) {
775
+ const token = argv[index];
776
+ if (token === "--") {
777
+ rest.push(...argv.slice(index));
778
+ break;
779
+ }
780
+ if (token === "--home" && argv[index + 1] !== void 0) home = argv[++index];
781
+ else if (token.startsWith("--home=")) home = token.slice(7);
782
+ else rest.push(token);
783
+ }
784
+ return {
785
+ home,
786
+ rest
787
+ };
788
+ }
789
+ /** The `tau <noun> …` entry point. `argv` starts after the noun. */
790
+ async function runApiCommand(noun, argv) {
791
+ const env = processEnv();
792
+ try {
793
+ const { home, rest } = takeHomeFlag(argv);
794
+ await executeApiCommand(noun, rest, home === void 0 ? env : {
795
+ ...env,
796
+ home
797
+ });
798
+ } catch (error) {
799
+ if (!(error instanceof CliExit)) throw error;
800
+ env.stderr(error.message);
801
+ process.exitCode = error.exitCode;
802
+ }
803
+ }
804
+ //#endregion
805
+ //#region src/commands/connect-command.ts
806
+ const REDEEM_PATH = "/api/pair/redeem";
807
+ async function runningServer(env) {
808
+ try {
809
+ return await discoverServer(env);
810
+ } catch {
811
+ return;
812
+ }
813
+ }
814
+ async function linkedMachineCount(server, env) {
815
+ if (server) return (await callRoute(server, "listMachines", {}, env)).length;
816
+ try {
817
+ return (await readdir(join(env.home, "machines"))).length;
818
+ } catch {
819
+ return 0;
820
+ }
821
+ }
822
+ async function redeem(hubUrl, code, deviceName, env) {
823
+ let response;
824
+ try {
825
+ response = await env.fetch(`${hubUrl}${REDEEM_PATH}`, {
826
+ method: "POST",
827
+ headers: {
828
+ "content-type": "application/json",
829
+ origin: hubUrl
830
+ },
831
+ body: JSON.stringify({
832
+ code,
833
+ deviceName
834
+ })
835
+ });
836
+ } catch {
837
+ throw failure(`Could not reach the hub at ${hubUrl}. Is it running and online?`);
838
+ }
839
+ if (!response.ok) throw failure(response.status === 429 ? "Too many tries. Wait a few minutes and make a new code on the hub." : "The hub did not accept that code. It may have expired: make a new one in Settings → Machines.");
840
+ const body = await response.json().catch(() => void 0);
841
+ if (!Value.Check(RedeemPairingResponseSchema, body) || body.secret === void 0) throw failure("The hub answered without a machine secret. Make a new link code (kind: machine).");
842
+ return body.secret;
843
+ }
844
+ async function startAfterLink(choice, env) {
845
+ if (choice === "serve") {
846
+ const { serveInForeground } = await import("./serve-Dmzn5V0l.js");
847
+ env.stdout("Starting tau for this session. Press Ctrl+C to stop.");
848
+ await serveInForeground([
849
+ "--no-open",
850
+ "--home",
851
+ env.home
852
+ ]);
853
+ } else if (choice === "service") {
854
+ const { runServiceCommand } = await import("./service-command-DqcstJ8e.js");
855
+ await runServiceCommand([
856
+ "install",
857
+ "--home",
858
+ env.home
859
+ ]);
860
+ } else env.stdout("Run `tau` (or `tau service install`) to bring this machine online.");
861
+ }
862
+ /**
863
+ * `tau connect <link | code --hub <url>> [--name <label>] [--yes]`: redeem a link code on a hub and keep its
864
+ * machine secret in `hub.json`. A running server picks the file up on its own.
865
+ */
866
+ async function executeConnect(argv, env, io = terminalIo) {
867
+ const args = parseConnectArgs(argv);
868
+ const target = parseConnectTarget(args.target, args.hub);
869
+ const hubFile = createHubFileStore(env.home);
870
+ const refusal = alreadyLinkedRefusal(await hubFile.read() !== void 0);
871
+ if (refusal) throw usageError(refusal);
872
+ const server = await runningServer(env);
873
+ const loop = connectRefusal(await linkedMachineCount(server, env));
874
+ if (loop) throw usageError(loop);
875
+ const hubName = target.hubName ?? new URL(target.hubUrl).host;
876
+ if (!args.yes && !await io.confirm(`Link this machine to ${hubName} (${target.hubUrl})?`)) throw new CliExit(1, "Not linked.");
877
+ const machineSecret = await redeem(target.hubUrl, target.code, args.name, env);
878
+ await hubFile.write({
879
+ hubUrl: target.hubUrl,
880
+ machineSecret,
881
+ name: hubName,
882
+ machineName: args.name
883
+ });
884
+ env.stdout(`Linked to ${hubName} as "${args.name}".`);
885
+ if (server) return env.stdout("The running tau connects within a few seconds.");
886
+ await startAfterLink(args.yes ? "none" : await io.chooseStart(), env);
887
+ }
888
+ /** `tau disconnect`: tells the running server (it closes the link and forgets the hub), then removes `hub.json`. */
889
+ async function executeDisconnect(argv, env) {
890
+ if (argv.length > 0) throw usageError("Usage: tau disconnect");
891
+ const hubFile = createHubFileStore(env.home);
892
+ const file = await hubFile.read();
893
+ const server = await runningServer(env);
894
+ if (!file && !server) return env.stdout("This tau is not linked to a hub.");
895
+ if (server) {
896
+ if ((await callRoute(server, "getHubLink", {}, env)).state !== "none") await callRoute(server, "disconnectHub", {}, env);
897
+ }
898
+ await hubFile.delete();
899
+ if (!file) return env.stdout("This tau is not linked to a hub.");
900
+ env.stdout(`Disconnected from ${file.name}. Remove this machine in the hub's Settings → Machines to revoke it there.`);
901
+ }
902
+ function runner(execute) {
903
+ return async (argv) => {
904
+ const env = processEnv();
905
+ try {
906
+ const { home, rest } = takeHomeFlag(argv);
907
+ await execute(rest, home === void 0 ? env : {
908
+ ...env,
909
+ home
910
+ });
911
+ } catch (error) {
912
+ if (!(error instanceof CliExit)) throw error;
913
+ env.stderr(error.message);
914
+ process.exitCode = error.exitCode;
915
+ }
916
+ };
917
+ }
918
+ const runConnect = runner(executeConnect);
919
+ const runDisconnect = runner(executeDisconnect);
920
+ //#endregion
921
+ //#region src/commands/pair-flow.ts
922
+ const REMOTE_WAIT_MS = 72e4;
923
+ const LINK_POLL_MS = 2e3;
924
+ const REMOTE_POLL_MS = 3e3;
925
+ const PROGRESS_EVERY_MS = 1e4;
926
+ const FULL_POWER_WARNING = "A linked device gets full power over this computer: agents, terminals and your files. Share this only with yourself.";
927
+ function parsePairArgs(argv) {
928
+ let name;
929
+ for (let index = 0; index < argv.length; index++) {
930
+ const token = argv[index];
931
+ if (token === "--name" && argv[index + 1] !== void 0) name = argv[++index];
932
+ else if (token.startsWith("--name=")) name = token.slice(7);
933
+ else throw usageError(`Unknown argument "${token}". Usage: tau pair [--name <label>]`);
934
+ }
935
+ return { name: name?.trim() || hostname() };
936
+ }
937
+ const groupShortCode = (code) => code.match(/.{1,4}/g)?.join(" ") ?? code;
938
+ const remoteOf = (server, env) => callRoute(server, "getRemoteStatus", {}, env);
939
+ function elapsedLine(status, now) {
940
+ const seconds = status.since === null ? 0 : Math.max(0, Math.floor((now - status.since) / 1e3));
941
+ return `Setting up remote access, ${seconds < 60 ? `${seconds} s` : `${Math.floor(seconds / 60)} min`} so far (usually 3-6 minutes)`;
942
+ }
943
+ async function waitUntilOnline(server, env, first) {
944
+ const deadline = env.now() + REMOTE_WAIT_MS;
945
+ let status = first;
946
+ let lastProgress = env.now();
947
+ let lastMessage = null;
948
+ while (status.state !== "online") {
949
+ if (status.state === "error" || status.state === "off") throw new CliExit(1, `Remote access did not start: ${status.message ?? status.state}`);
950
+ if (env.now() >= deadline) throw new CliExit(3, "Remote access is still starting. It keeps going in the server: run `tau pair` again in a few minutes.");
951
+ if (status.message && status.message !== lastMessage) {
952
+ lastMessage = status.message;
953
+ env.stdout(status.message);
954
+ } else if (env.now() - lastProgress >= PROGRESS_EVERY_MS) {
955
+ lastProgress = env.now();
956
+ env.stdout(elapsedLine(status, env.now()));
957
+ }
958
+ await env.sleep(REMOTE_POLL_MS);
959
+ status = await remoteOf(server, env);
960
+ }
961
+ return status;
962
+ }
963
+ /** Turns remote on when needed and waits until the tunnel is online. */
964
+ async function ensureRemoteOnline(server, env) {
965
+ let status = await remoteOf(server, env);
966
+ if (status.state === "online") return;
967
+ if (status.state === "off") {
968
+ env.stdout("Remote access is off. Turning it on: a new URL takes 3-6 minutes, please wait.");
969
+ status = await callRoute(server, "enableRemote", {}, env);
970
+ } else env.stdout("Remote access is starting: this takes a few minutes.");
971
+ await waitUntilOnline(server, env, status);
972
+ }
973
+ async function printPairing(env, pairing, link) {
974
+ const qr = await QRCode.toString(link, {
975
+ type: "terminal",
976
+ small: true
977
+ });
978
+ env.stdout(`\n${qr}`);
979
+ env.stdout(`Open this link on the device:\n ${link}\n`);
980
+ env.stdout(`Or open the same site and type this code: ${groupShortCode(pairing.shortCode)}\n`);
981
+ env.stdout(FULL_POWER_WARNING);
982
+ const minutes = Math.max(1, Math.round((pairing.expiresAt - env.now()) / 6e4));
983
+ env.stdout(`The code works once and expires in ${minutes} minutes. Waiting for the device… (Ctrl+C to stop)`);
984
+ }
985
+ const listDevices = (server, env) => callRoute(server, "listDevices", {}, env);
986
+ async function waitForLinkedDevice(server, env, knownIds, expiresAt) {
987
+ while (env.now() < expiresAt) {
988
+ await env.sleep(LINK_POLL_MS);
989
+ const fresh = (await listDevices(server, env)).find((device) => !knownIds.has(device.id));
990
+ if (fresh) return env.stdout(`Linked: ${fresh.name}`);
991
+ }
992
+ throw new CliExit(3, "The code expired before a device linked. Run `tau pair` again for a new one.");
993
+ }
994
+ async function runPairFlow(argv, env) {
995
+ const { name } = parsePairArgs(argv);
996
+ const server = await discoverServer(env);
997
+ await ensureRemoteOnline(server, env);
998
+ const known = new Set((await listDevices(server, env)).map((device) => device.id));
999
+ const pairing = await callRoute(server, "mintPairing", { query: {
1000
+ kind: "device",
1001
+ name
1002
+ } }, env);
1003
+ if (pairing.link === null) throw new CliExit(1, "Remote access has no URL yet. Try again in a moment.");
1004
+ await printPairing(env, pairing, pairing.link);
1005
+ await waitForLinkedDevice(server, env, known, pairing.expiresAt);
1006
+ }
1007
+ //#endregion
1008
+ //#region src/commands/pair-command.ts
1009
+ const wantsHelp$1 = (argv) => argv.includes("--help") || argv.includes("-h");
1010
+ /** `tau pair new …` and help stay with the generic command table. */
1011
+ const isTableVerb = (argv) => argv[0] === "new" || wantsHelp$1(argv);
1012
+ /** `tau pair [--name <label>]`: link a phone or browser with a link, a short code and a QR. */
1013
+ async function executePair(argv, env) {
1014
+ if (isTableVerb(argv)) return executeApiCommand("pair", argv, env);
1015
+ await runPairFlow(argv, env);
1016
+ }
1017
+ async function runPair(argv) {
1018
+ const env = processEnv();
1019
+ try {
1020
+ const { home, rest } = takeHomeFlag(argv);
1021
+ await executePair(rest, home === void 0 ? env : {
1022
+ ...env,
1023
+ home
1024
+ });
1025
+ } catch (error) {
1026
+ if (!(error instanceof CliExit)) throw error;
1027
+ env.stderr(error.message);
1028
+ process.exitCode = error.exitCode;
1029
+ }
1030
+ }
1031
+ //#endregion
1032
+ //#region src/commands/skill.ts
1033
+ const here = (relative) => fileURLToPath(new URL(relative, import.meta.url));
1034
+ /** Built package (`dist/skills/builtin/tau` next to `dist/bin.js`), then the core source tree. */
1035
+ const SKILL_PATHS = [
1036
+ here("./skills/builtin/tau/SKILL.md"),
1037
+ here("../../core/src/skills/builtin/tau/SKILL.md"),
1038
+ here("../../../core/src/skills/builtin/tau/SKILL.md")
1039
+ ];
1040
+ const MISSING_MESSAGE = "The tau skill is not bundled in this build. Run `tau --help`: output is JSON, and `--wait` replaces polling.";
1041
+ async function firstReadable(paths) {
1042
+ for (const path of paths) try {
1043
+ return await readFile(path, "utf8");
1044
+ } catch {}
1045
+ }
1046
+ async function printSkill(env) {
1047
+ const paths = env.skillPaths.length > 0 ? env.skillPaths : SKILL_PATHS;
1048
+ env.stdout(await firstReadable(paths) ?? MISSING_MESSAGE);
1049
+ }
1050
+ /** `tau skill`: print the bundled SKILL.md. */
1051
+ async function runSkill(_argv) {
1052
+ await printSkill(processEnv());
1053
+ }
1054
+ //#endregion
1055
+ //#region src/lifecycle/open.ts
1056
+ /** Opens the browser on a running server; returns the URL it opened. */
1057
+ async function openRunning(deps, home) {
1058
+ const instance = await deps.waitForInstanceUrl(home);
1059
+ if (!instance?.url) throw new Error(NOT_RUNNING);
1060
+ const url = withToken(instance.url, await deps.readToken(home));
1061
+ try {
1062
+ await deps.openBrowser(url);
1063
+ } catch (error) {
1064
+ deps.out(`Could not open the browser (${error instanceof Error ? error.message : error}). Open ${url}`);
1065
+ }
1066
+ return url;
1067
+ }
1068
+ async function runOpenWith(deps, argv) {
1069
+ const { home } = lifecycleFlagsOf(argv, deps.context);
1070
+ if (!await deps.readInstance(home)) throw new Error(NOT_RUNNING);
1071
+ await openRunning(deps, home);
1072
+ }
1073
+ /** `tau open`. */
1074
+ async function runOpen(argv) {
1075
+ await runOpenWith(await realLifecycleDeps(), argv);
1076
+ }
1077
+ //#endregion
1078
+ //#region src/lifecycle/start.ts
1079
+ /** Starts the service of this home (a detached process when it has none), waits for the URL, prints it. */
1080
+ async function startAndWait(deps, home) {
1081
+ if (!await deps.readInstance(home)) await managerOf(deps, home).start();
1082
+ const instance = await deps.waitForInstanceUrl(home);
1083
+ if (!instance?.url) throw new Error("tau did not start. Run `tau logs` to see why.");
1084
+ return instance.url;
1085
+ }
1086
+ /** A detached server reads `TAU_FAUX`; the service definition never carries `--faux`. */
1087
+ function forwardFauxToServer(deps, argv) {
1088
+ if (argv.includes("--faux")) deps.context.env.TAU_FAUX = "1";
1089
+ }
1090
+ async function runStartWith(deps, argv) {
1091
+ forwardFauxToServer(deps, argv);
1092
+ const flags = lifecycleFlagsOf(argv, deps.context);
1093
+ if (flags.explicitPort !== void 0) writeStoredPort(flags.home, flags.explicitPort);
1094
+ const url = await startAndWait(deps, flags.home);
1095
+ deps.out(`tau is running: ${url}`);
1096
+ if (flags.open) await openRunning(deps, flags.home);
1097
+ }
1098
+ /** `tau start [--port --home --no-open]`. */
1099
+ async function runStart(argv) {
1100
+ await runStartWith(await realLifecycleDeps(), argv);
1101
+ }
1102
+ //#endregion
1103
+ //#region src/lifecycle/bare.ts
1104
+ /** Bare `tau`: install when missing, repair when stale, start when stopped, open the browser. */
1105
+ async function runBareWith(deps, argv, serveInForeground) {
1106
+ const flags = lifecycleFlagsOf(argv, deps.context);
1107
+ if (flags.explicitPort !== void 0) writeStoredPort(flags.home, flags.explicitPort);
1108
+ const manager = managerOf(deps, flags.home);
1109
+ if (!await manager.installed() || await manager.needsRepair()) try {
1110
+ for (const warning of await manager.install()) deps.out(`warning: ${warning}`);
1111
+ } catch (error) {
1112
+ if (!(error instanceof TemporaryEntryError)) throw error;
1113
+ deps.out(`warning: ${error.message} Serving in the foreground instead.`);
1114
+ return serveInForeground(argv);
1115
+ }
1116
+ const url = await startAndWait(deps, flags.home);
1117
+ deps.out(`tau is running: ${url}`);
1118
+ if (flags.open) await openRunning(deps, flags.home);
1119
+ }
1120
+ async function runBare(argv, serveInForeground) {
1121
+ await runBareWith(await realLifecycleDeps(), argv, serveInForeground);
1122
+ }
1123
+ //#endregion
1124
+ //#region src/lifecycle/logs.ts
1125
+ const TAIL_LINES = 200;
1126
+ const lastLinesOf = (text, count) => text.trimEnd().split("\n").slice(-count).join("\n");
1127
+ function follow(file, from, write) {
1128
+ let position = from;
1129
+ watchFile(file, { interval: 500 }, (current) => {
1130
+ if (current.size < position) position = 0;
1131
+ if (current.size === position) return;
1132
+ createReadStream(file, {
1133
+ start: position,
1134
+ end: current.size - 1,
1135
+ encoding: "utf8"
1136
+ }).on("data", (chunk) => write(String(chunk)));
1137
+ position = current.size;
1138
+ });
1139
+ }
1140
+ /** `tau logs [-f]`: the last lines of `<home>/logs/server.log`, then new ones with `-f`. */
1141
+ async function runLogs(argv, write = (text) => process.stdout.write(text)) {
1142
+ const flags = lifecycleFlagsOf(argv, realContext());
1143
+ const file = serverLogOf(flags.home);
1144
+ if (!existsSync(file)) throw new Error(`No log yet: ${file}`);
1145
+ write(`${lastLinesOf(readFileSync(file, "utf8"), TAIL_LINES)}\n`);
1146
+ if (!flags.follow) return;
1147
+ follow(file, statSync(file).size, write);
1148
+ await new Promise(() => void 0);
1149
+ }
1150
+ //#endregion
1151
+ //#region src/lifecycle/restart.ts
1152
+ /** Repairs a stale service, copies PATH again, and restarts. */
1153
+ async function runRestartWith(deps, argv) {
1154
+ const flags = lifecycleFlagsOf(argv, deps.context);
1155
+ if (flags.explicitPort !== void 0) writeStoredPort(flags.home, flags.explicitPort);
1156
+ const manager = managerOf(deps, flags.home);
1157
+ if (await manager.needsRepair()) deps.out("Repairing the service.");
1158
+ await manager.restart();
1159
+ const instance = await deps.waitForInstanceUrl(flags.home);
1160
+ deps.out(instance?.url ? `tau is running: ${instance.url}` : "tau restarted.");
1161
+ }
1162
+ /** `tau restart`. */
1163
+ async function runRestart(argv) {
1164
+ await runRestartWith(await realLifecycleDeps(), argv);
1165
+ }
1166
+ //#endregion
1167
+ //#region src/lifecycle/status.ts
1168
+ async function gatherStatus(deps, home) {
1169
+ const manager = managerOf(deps, home);
1170
+ const [installed, state, instance] = await Promise.all([
1171
+ manager.installed(),
1172
+ manager.state(),
1173
+ deps.readInstance(home)
1174
+ ]);
1175
+ const extras = await deps.extras?.(home, instance) ?? NO_EXTRAS;
1176
+ const recorded = manager.recorded();
1177
+ const nodeMissing = installed && recorded !== void 0 && !deps.fileExists(recorded.node);
1178
+ const cliVersion = deps.version();
1179
+ const managedPid = state.pid;
1180
+ const isService = state.active && (managedPid === void 0 || managedPid === instance?.pid);
1181
+ const warnings = [];
1182
+ if (instance?.version !== void 0 && instance.version !== cliVersion) warnings.push(`Running version ${instance.version} is not ${cliVersion}: run \`tau restart\`.`);
1183
+ if (nodeMissing) warnings.push("node missing: run `tau` to repair the service.");
1184
+ return {
1185
+ installed,
1186
+ running: instance ? isService ? "service" : "foreground" : false,
1187
+ pid: instance?.pid ?? null,
1188
+ url: instance?.url ?? null,
1189
+ version: instance?.version ?? null,
1190
+ cliVersion,
1191
+ home,
1192
+ node: recorded?.node ?? null,
1193
+ nodeMissing,
1194
+ warnings,
1195
+ ...extras
1196
+ };
1197
+ }
1198
+ function renderStatus(report) {
1199
+ return [
1200
+ `tau: ${report.running ? `running (${report.running})` : "not running"}`,
1201
+ `installed: ${report.installed ? "yes" : "no"}`,
1202
+ `pid: ${report.pid ?? "-"}`,
1203
+ `url: ${report.url ?? "-"}`,
1204
+ `version: ${report.version ?? "-"}`,
1205
+ `home: ${report.home}`,
1206
+ `node: ${report.node ?? "-"}${report.nodeMissing ? " (missing)" : ""}`,
1207
+ ...renderExtras(report),
1208
+ ...report.warnings.map((warning) => `warning: ${warning}`)
1209
+ ].join("\n");
1210
+ }
1211
+ /** `tau status [--json]`. */
1212
+ async function runStatusWith(deps, argv) {
1213
+ const flags = lifecycleFlagsOf(argv, deps.context);
1214
+ const report = await gatherStatus(deps, flags.home);
1215
+ deps.out(flags.json ? JSON.stringify(report) : renderStatus(report));
1216
+ }
1217
+ async function runStatus(argv) {
1218
+ await runStatusWith(await realLifecycleDeps(), argv);
1219
+ }
1220
+ //#endregion
1221
+ //#region src/lifecycle/stop.ts
1222
+ async function runStopWith(deps, argv) {
1223
+ const { home } = lifecycleFlagsOf(argv, deps.context);
1224
+ const status = await gatherStatus(deps, home);
1225
+ if (status.running === "foreground") {
1226
+ deps.out(`tau runs in the foreground (pid ${status.pid}). Stop it with Ctrl-C.`);
1227
+ return;
1228
+ }
1229
+ await managerOf(deps, home).stop();
1230
+ deps.out("tau stopped.");
1231
+ }
1232
+ /** `tau stop`: the service stays stopped until `tau start` or the next login. */
1233
+ async function runStop(argv) {
1234
+ await runStopWith(await realLifecycleDeps(), argv);
1235
+ }
1236
+ //#endregion
1237
+ //#region src/update/install-method.ts
1238
+ const NPM_LAYOUT = /\/lib\/node_modules\/@pablopunk\/tau\/dist\/bin\.js$/;
1239
+ const SHIM_MANAGERS = /\/(mise|asdf)\//;
1240
+ const manual = (reason, manualCommand) => ({
1241
+ kind: "manual",
1242
+ reason,
1243
+ manualCommand
1244
+ });
1245
+ function refusalOf(entry, target) {
1246
+ const spec = `${PACKAGE_NAME}@${target}`;
1247
+ if (entry.includes("/.bun/")) return `Installed with bun. Run: bun add -g ${spec}`;
1248
+ if (entry.includes("/.volta/")) return `Installed with volta. Run: volta install ${spec}`;
1249
+ if (/\/_npx\/|\/dlx\/|\/\.bun\/install\/cache\//.test(entry) || entry.includes("/_npx/")) return "Running from a temporary npx/dlx cache: nothing to update. Run `npx @pablopunk/tau@latest` again.";
1250
+ if (/\/packages\/cli\/dist\/bin\.js$/.test(entry)) return "Running from a development checkout: update it with git.";
1251
+ }
1252
+ /** Which package manager owns this tau, from where the running entry file lives. Never guesses. */
1253
+ function installMethodOf(probe) {
1254
+ const { entry, target } = probe;
1255
+ const spec = `${PACKAGE_NAME}@${target}`;
1256
+ if (probe.platform === "win32") return manual("Automatic update is not supported on Windows.", `npm i -g ${spec}`);
1257
+ const refusal = refusalOf(entry, target);
1258
+ if (refusal) return manual(refusal);
1259
+ if (entry.includes("/.pnpm/") || /\/pnpm\/global\//.test(entry)) return {
1260
+ kind: "pnpm",
1261
+ install: {
1262
+ command: "pnpm",
1263
+ args: [
1264
+ "add",
1265
+ "-g",
1266
+ spec
1267
+ ]
1268
+ },
1269
+ manualCommand: `pnpm add -g ${spec}`
1270
+ };
1271
+ if (!NPM_LAYOUT.test(entry)) return manual(`Unknown install layout: ${entry}`, `npm i -g ${spec}`);
1272
+ return {
1273
+ kind: "npm",
1274
+ packageDir: dirname(dirname(entry)),
1275
+ install: {
1276
+ command: npmOf(probe.execPath),
1277
+ args: [
1278
+ "i",
1279
+ "-g",
1280
+ spec
1281
+ ]
1282
+ },
1283
+ manualCommand: `npm i -g ${spec}`,
1284
+ hint: SHIM_MANAGERS.test(entry) ? "If `tau` is missing afterwards, run `mise reshim`." : void 0
1285
+ };
1286
+ }
1287
+ const entryOfPackage = (packageDir) => join(packageDir, "dist", "bin.js");
1288
+ //#endregion
1289
+ //#region src/update/update-text.ts
1290
+ const LAG_NOTE = "A release can take 1-3 minutes to show up in the npm registry.";
1291
+ const LINKED_NOTE = "Linked machines must run the same tau version: run `tau update` on each.";
1292
+ const LINKED_HUB_NOTE = "This tau is linked to a hub: update the hub and every machine to the same version.";
1293
+ const INSIDE_TAU = "You are running inside tau; restarting would kill this command. Run `tau restart` from a normal terminal, or pass `--no-restart`.";
1294
+ const NON_TTY_NEEDS_YES = "Not a terminal. Add --yes to skip the question.";
1295
+ const FOREGROUND_NOTE = "tau runs in the foreground: restart it yourself (Ctrl-C, then `tau`).";
1296
+ const NOT_RUNNING_NOTE = "tau is not running: start it with `tau start`.";
1297
+ const names = (items) => items.map((item) => ` - ${item.title}`);
1298
+ function runningWorkText(work) {
1299
+ const lines = [`Running threads: ${work.threads.length}, subagents: ${work.subagents.length}.`];
1300
+ lines.push(...names(work.threads), ...names(work.subagents));
1301
+ lines.push("A restart resumes or aborts turns on startup, ends terminals and background processes, and the page reconnects in seconds.", "Add --force to restart anyway.");
1302
+ return lines.join("\n");
1303
+ }
1304
+ const resumingText = (work) => work.resuming.length > 0 ? `${work.resuming.length} thread(s) with timers or a goal will resume after the restart.` : void 0;
1305
+ const failedInstallText = (manual, rollback) => `The install failed. Run it yourself: ${manual}\nTo go back: ${rollback}`;
1306
+ const permissionText = (manual) => `Permission denied. Fix the permissions of the npm prefix (or use a node version manager), then run: ${manual}`;
1307
+ const verifyFailedText = (problem, rollback) => `The update did not verify: ${problem}\nNothing was restarted: the old server still runs on old in-memory code and files on disk may be inconsistent. Re-run \`tau update\`, or go back with: ${rollback}`;
1308
+ const skewText = (running, target) => `The server still reports version ${running ?? "none"}, expected ${target}. Check \`tau status\` and \`tau logs\`.`;
1309
+ //#endregion
1310
+ //#region src/update/restart-step.ts
1311
+ async function isInsideServer(deps, home) {
1312
+ if (deps.env.TAU_THREAD_ID) return true;
1313
+ const [instance, ancestors] = [await deps.readInstance(home), await (deps.ancestorPids?.() ?? Promise.resolve([]))];
1314
+ return instance !== void 0 && ancestors.includes(instance.pid);
1315
+ }
1316
+ /** Restarts the managed server with the NEW binary and waits until the lock reports the target version. */
1317
+ async function restartInNewBinary(deps, request) {
1318
+ const running = (await gatherStatus(deps, request.home)).running;
1319
+ if (running === "foreground") return note(deps, FOREGROUND_NOTE);
1320
+ if (running === false) return note(deps, NOT_RUNNING_NOTE);
1321
+ if (await isInsideServer(deps, request.home)) {
1322
+ deps.err(INSIDE_TAU);
1323
+ return 1;
1324
+ }
1325
+ const args = [
1326
+ entryOfPackage(request.packageDir),
1327
+ "restart",
1328
+ ...request.forwarded
1329
+ ];
1330
+ if ((await deps.runCommand(deps.execPath, args, { stdio: "inherit" })).code !== 0) {
1331
+ deps.err("`tau restart` failed. Check `tau status` and `tau logs`.");
1332
+ return 1;
1333
+ }
1334
+ const instance = await deps.pollInstance(request.home, request.target);
1335
+ if (instance?.version !== request.target) {
1336
+ deps.err(skewText(instance?.version, request.target));
1337
+ return 1;
1338
+ }
1339
+ deps.out(`tau ${request.target} is running.`);
1340
+ return 0;
1341
+ }
1342
+ function note(deps, message) {
1343
+ deps.out(`Updated. ${message}`);
1344
+ return 0;
1345
+ }
1346
+ //#endregion
1347
+ //#region src/update/running-work.ts
1348
+ /** What a restart would interrupt. Each thread is counted once; the caller's own lineage is left out. */
1349
+ function runningWorkOf(all, ownId) {
1350
+ const own = ownId ? lineageOf(all, ownId) : /* @__PURE__ */ new Set();
1351
+ const live = all.filter((item) => item.archivedAt === null && !own.has(item.id));
1352
+ const subagents = live.filter((item) => item.parentId !== null && item.agentStatus === "running");
1353
+ return {
1354
+ threads: live.filter((item) => item.parentId === null && item.status === "running"),
1355
+ subagents,
1356
+ resuming: live.filter((item) => item.timers > 0 || item.goal?.state === "active")
1357
+ };
1358
+ }
1359
+ const hasRunningWork = (work) => work.threads.length + work.subagents.length > 0;
1360
+ //#endregion
1361
+ //#region src/update/update-flags.ts
1362
+ function updateFlagsOf(argv) {
1363
+ const { values } = parseArgs({
1364
+ args: [...argv],
1365
+ strict: false,
1366
+ options: {
1367
+ check: { type: "boolean" },
1368
+ yes: {
1369
+ type: "boolean",
1370
+ short: "y"
1371
+ },
1372
+ force: { type: "boolean" },
1373
+ "no-restart": { type: "boolean" },
1374
+ home: { type: "string" },
1375
+ port: { type: "string" }
1376
+ }
1377
+ });
1378
+ const forwarded = [...typeof values.home === "string" ? ["--home", values.home] : [], ...typeof values.port === "string" ? ["--port", values.port] : []];
1379
+ return {
1380
+ check: values.check === true,
1381
+ yes: values.yes === true,
1382
+ force: values.force === true,
1383
+ noRestart: values["no-restart"] === true,
1384
+ forwarded
1385
+ };
1386
+ }
1387
+ //#endregion
1388
+ //#region src/update/verify-install.ts
1389
+ const STATUS_TIMEOUT_MS = 3e4;
1390
+ function reportedVersion(stdout) {
1391
+ try {
1392
+ return JSON.parse(stdout.trim()).cliVersion;
1393
+ } catch {
1394
+ return;
1395
+ }
1396
+ }
1397
+ /** The new files must say the target version and the new binary must load; returns the problem, if any. */
1398
+ async function verifyInstall(deps, packageDir, target, home) {
1399
+ const onDisk = deps.readPackageVersion(packageDir);
1400
+ if (onDisk !== target) return `${packageDir}/package.json says ${onDisk ?? "nothing"}, expected ${target}.`;
1401
+ const args = [
1402
+ entryOfPackage(packageDir),
1403
+ "status",
1404
+ "--home",
1405
+ home,
1406
+ "--json"
1407
+ ];
1408
+ const result = await deps.runCommand(deps.execPath, args, {
1409
+ stdio: "capture",
1410
+ timeoutMs: STATUS_TIMEOUT_MS
1411
+ });
1412
+ if (result.code !== 0) return `The new tau does not start (exit ${result.code}): ${result.stderr.trim().slice(0, 300)}`;
1413
+ const reported = reportedVersion(result.stdout);
1414
+ return reported === target ? void 0 : `The new tau reports version ${reported ?? "unknown"}, expected ${target}.`;
1415
+ }
1416
+ async function planOf(deps, target) {
1417
+ const method = installMethodOf({
1418
+ entry: deps.entry,
1419
+ execPath: deps.execPath,
1420
+ platform: deps.context.platform,
1421
+ target
1422
+ });
1423
+ if (method.kind === "manual") return [method.reason, method.manualCommand ? `Run: ${method.manualCommand}` : ""].filter(Boolean).join("\n");
1424
+ const install = {
1425
+ ...method.install,
1426
+ manual: method.manualCommand
1427
+ };
1428
+ if (method.kind === "npm") return {
1429
+ packageDir: method.packageDir,
1430
+ install,
1431
+ hint: method.hint
1432
+ };
1433
+ const root = await deps.runCommand("pnpm", ["root", "-g"], {
1434
+ stdio: "capture",
1435
+ timeoutMs: 15e3
1436
+ });
1437
+ const dir = root.stdout.trim();
1438
+ if (root.code !== 0 || !dir) return `Could not find the pnpm global directory.\nRun: ${method.manualCommand}`;
1439
+ try {
1440
+ return {
1441
+ packageDir: deps.realpath(`${dir}/@pablopunk/tau`),
1442
+ install
1443
+ };
1444
+ } catch {
1445
+ return `Could not find the pnpm global package.\nRun: ${method.manualCommand}`;
1446
+ }
1447
+ }
1448
+ async function refusalForRunningWork(deps, home) {
1449
+ let all;
1450
+ try {
1451
+ all = await deps.listConversations(home);
1452
+ } catch (error) {
1453
+ return `Could not list the running threads (${error instanceof Error ? error.message : error}). Add --force to restart anyway.`;
1454
+ }
1455
+ const work = runningWorkOf(all, deps.env.TAU_THREAD_ID);
1456
+ const resuming = resumingText(work);
1457
+ if (resuming) deps.out(resuming);
1458
+ return hasRunningWork(work) ? runningWorkText(work) : void 0;
1459
+ }
1460
+ async function installStep(deps, plan, current) {
1461
+ const { command, args, manual } = plan.install;
1462
+ const result = await deps.runCommand(command, args, { stdio: "tee" });
1463
+ if (result.code === 0) return void 0;
1464
+ if (/EACCES|EPERM/.test(result.stderr)) return permissionText(manual);
1465
+ return failedInstallText(manual, manual.replace(/@[^@]+$/, `@${current}`));
1466
+ }
1467
+ async function finish(deps, flags, plan, target, home) {
1468
+ const rollback = plan.install.manual.replace(/@[^@]+$/, `@${deps.version()}`);
1469
+ const problem = await verifyInstall(deps, plan.packageDir, target, home);
1470
+ if (problem) {
1471
+ deps.err(verifyFailedText(problem, rollback));
1472
+ return 1;
1473
+ }
1474
+ if (plan.hint) deps.out(plan.hint);
1475
+ const code = flags.noRestart ? noRestartNote(deps) : await restartInNewBinary(deps, {
1476
+ ...flags,
1477
+ packageDir: plan.packageDir,
1478
+ target,
1479
+ home
1480
+ });
1481
+ if (code === 0) await machinesBehindNote(deps, home, target);
1482
+ if (code === 0) deps.out(deps.hasHubFile(home) ? `${LINKED_NOTE}\n${LINKED_HUB_NOTE}` : LINKED_NOTE);
1483
+ return code;
1484
+ }
1485
+ function noRestartNote(deps) {
1486
+ deps.out("Updated. Run `tau restart` to use the new version.");
1487
+ return 0;
1488
+ }
1489
+ async function machinesBehindNote(deps, home, latest) {
1490
+ try {
1491
+ const line = linkedMachinesLine(await deps.linkedMachines?.(home), latest);
1492
+ if (line) deps.out(line);
1493
+ } catch {}
1494
+ }
1495
+ async function checkOnly(deps, home, current, latest) {
1496
+ deps.out(`${current} -> ${latest}`);
1497
+ await machinesBehindNote(deps, home, latest);
1498
+ return compareVersions(current, latest) < 0 ? 10 : 0;
1499
+ }
1500
+ async function latestOrError(deps) {
1501
+ try {
1502
+ return await deps.latestVersion();
1503
+ } catch (error) {
1504
+ deps.err(error instanceof Error ? error.message : String(error));
1505
+ return;
1506
+ }
1507
+ }
1508
+ /** `tau update`; resolves to the exit code. */
1509
+ async function runUpdateWith(deps, argv) {
1510
+ const flags = updateFlagsOf(argv);
1511
+ const home = lifecycleFlagsOf(argv, deps.context).home;
1512
+ const current = deps.version();
1513
+ const latest = await latestOrError(deps);
1514
+ if (latest === void 0) return 1;
1515
+ if (flags.check) return checkOnly(deps, home, current, latest);
1516
+ if (compareVersions(current, latest) >= 0) {
1517
+ deps.out(`tau ${current} is up to date. ${LAG_NOTE}`);
1518
+ return 0;
1519
+ }
1520
+ const plan = await planOf(deps, latest);
1521
+ if (typeof plan === "string") {
1522
+ deps.err(plan);
1523
+ return 1;
1524
+ }
1525
+ if ((await gatherStatus(deps, home)).running === "service" && !flags.noRestart && !flags.force) {
1526
+ const refusal = await refusalForRunningWork(deps, home);
1527
+ if (refusal) {
1528
+ deps.err(refusal);
1529
+ return 1;
1530
+ }
1531
+ }
1532
+ const question = `Install ${current} -> ${latest}${flags.noRestart ? "" : " and restart"}?`;
1533
+ if (!flags.yes) {
1534
+ if (!deps.isTty) {
1535
+ deps.err(NON_TTY_NEEDS_YES);
1536
+ return 1;
1537
+ }
1538
+ if (!await deps.confirm(question)) {
1539
+ deps.out("Cancelled.");
1540
+ return 0;
1541
+ }
1542
+ }
1543
+ const failed = await installStep(deps, plan, current);
1544
+ if (failed) {
1545
+ deps.err(failed);
1546
+ return 1;
1547
+ }
1548
+ return finish(deps, flags, plan, latest, home);
1549
+ }
1550
+ async function runUpdate(argv) {
1551
+ const { realUpdateDeps } = await import("./real-deps-f96UmLBz.js");
1552
+ process.exitCode = await runUpdateWith(await realUpdateDeps(), argv);
1553
+ }
1554
+ //#endregion
1555
+ //#region src/main.ts
1556
+ const apiCommandHandlers = Object.fromEntries(COMMAND_NOUNS.map((noun) => [noun, (argv) => runApiCommand(noun, argv)]));
1557
+ /** The one dispatch table: command word to handler. */
1558
+ const commandTable = {
1559
+ serve: serveInForeground,
1560
+ start: runStart,
1561
+ stop: runStop,
1562
+ restart: runRestart,
1563
+ status: runStatus,
1564
+ open: runOpen,
1565
+ logs: runLogs,
1566
+ update: runUpdate,
1567
+ service: runServiceCommand,
1568
+ skill: runSkill,
1569
+ help: runHelp,
1570
+ ...apiCommandHandlers,
1571
+ pair: runPair,
1572
+ connect: runConnect,
1573
+ disconnect: runDisconnect
1574
+ };
1575
+ const bareHandler = (argv) => runBare(argv, serveInForeground);
1576
+ const wantsHelp = (argv) => argv.includes("--help") || argv.includes("-h");
1577
+ var UnknownCommandError = class extends Error {
1578
+ constructor(word) {
1579
+ super(`Unknown command: ${word}. Run \`tau --help\`.`);
1580
+ this.name = "UnknownCommandError";
1581
+ }
1582
+ };
1583
+ /** Picks the handler and the argv it receives. */
1584
+ function resolveCommand(argv, table = commandTable, bare = bareHandler) {
1585
+ const { word, rest } = commandWordOf(argv);
1586
+ if (word === void 0) return wantsHelp(argv) ? {
1587
+ handler: table.help ?? bare,
1588
+ argv
1589
+ } : {
1590
+ handler: bare,
1591
+ argv
1592
+ };
1593
+ const handler = table[word];
1594
+ if (!handler) throw new UnknownCommandError(word);
1595
+ return {
1596
+ handler,
1597
+ argv: rest
1598
+ };
1599
+ }
1600
+ const wantsVersion = (argv) => argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v");
1601
+ async function main(argv) {
1602
+ if (wantsVersion(argv)) {
1603
+ console.log(cliVersion());
1604
+ return;
1605
+ }
1606
+ const { handler, argv: rest } = resolveCommand(argv);
1607
+ await handler(rest);
1608
+ }
1609
+ //#endregion
1610
+ export { main };