@pablopunk/tau 0.1.0 → 0.1.2
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/bin.js +1 -1
- package/dist/latest-version-di9hD7cY.js +106 -0
- package/dist/machines-behind-BjGgmSs_.js +257 -0
- package/dist/{main-CI9q691u.js → main-R5EDx89w.js} +349 -229
- package/dist/{service-command-C7WDeeR2.js → manager-B_WHbb1U.js} +1 -33
- package/dist/real-deps-f96UmLBz.js +89 -0
- package/dist/serve-CuMzEud7.js +1 -0
- package/dist/{serve-92lMbcls.js → serve-Uc4Nin5f.js} +25 -51
- package/dist/service-command-C74K0saj.js +34 -0
- package/dist/service-command-DqcstJ8e.js +1 -0
- package/dist/{wait-for-lock-D2V3vuye.js → src-DO9LUlhi.js} +183 -46
- package/dist/wait-for-lock-B1Qx8GWg.js +1 -0
- package/dist/wait-for-lock-CrSl2mi1.js +25 -0
- package/dist/web/dist/assets/{index-7m0crRNF.css → index-D_Aa3Oou.css} +1 -1
- package/dist/web/dist/assets/index-vDvuOoR3.js +110 -0
- package/dist/web/dist/index.html +2 -2
- package/package.json +1 -1
- package/dist/serve-BcVZj5e2.js +0 -1
- package/dist/service-command-BVr33vlP.js +0 -1
- package/dist/wait-for-lock-BMk6v3J5.js +0 -1
- package/dist/web/dist/assets/index--f7AYPm0.js +0 -108
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { $ as routes, B as COMMAND_NOUNS, G as isModelRefNode, H as commandOf, J as LIVE_PATH, K as routeFields,
|
|
2
|
-
import { a as lifecycleFlagsOf, c as
|
|
3
|
-
import { a as
|
|
4
|
-
import { t as
|
|
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-Uc4Nin5f.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";
|
|
5
7
|
import { createReadStream, existsSync, readFileSync, statSync, watchFile } from "node:fs";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
import {
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { hostname } from "node:os";
|
|
8
10
|
import { fileURLToPath } from "node:url";
|
|
9
|
-
import {
|
|
11
|
+
import { parseArgs } from "node:util";
|
|
10
12
|
import { randomUUID } from "node:crypto";
|
|
11
13
|
import { readFile, readdir } from "node:fs/promises";
|
|
12
|
-
import { execFile } from "node:child_process";
|
|
13
14
|
import { Value } from "typebox/value";
|
|
14
15
|
import { createInterface } from "node:readline/promises";
|
|
15
16
|
import QRCode from "qrcode";
|
|
@@ -65,18 +66,6 @@ function readLive(view) {
|
|
|
65
66
|
queued: (inbox?.items ?? []).filter(isQueuedMessage).map(queuedOf)
|
|
66
67
|
};
|
|
67
68
|
}
|
|
68
|
-
/** A failure that ends the command with a message on stderr and an exit code. */
|
|
69
|
-
var CliExit = class extends Error {
|
|
70
|
-
exitCode;
|
|
71
|
-
constructor(exitCode, message) {
|
|
72
|
-
super(message);
|
|
73
|
-
this.name = "CliExit";
|
|
74
|
-
this.exitCode = exitCode;
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
const usageError = (message) => new CliExit(2, message);
|
|
78
|
-
const failure = (message) => new CliExit(1, message);
|
|
79
|
-
const NOT_RUNNING_MESSAGE = "tau is not running. Run `tau` to start it.";
|
|
80
69
|
//#endregion
|
|
81
70
|
//#region src/machines/connect-args.ts
|
|
82
71
|
const USAGE = "Usage: tau connect <link | code --hub <url>> [--name <label>] [--yes]";
|
|
@@ -170,94 +159,6 @@ function parseConnectTarget(input, hub) {
|
|
|
170
159
|
};
|
|
171
160
|
}
|
|
172
161
|
//#endregion
|
|
173
|
-
//#region src/commands/client.ts
|
|
174
|
-
const URL_POLL_MS = 100;
|
|
175
|
-
async function waitForLockUrl(home, env) {
|
|
176
|
-
const deadline = env.now() + env.startupWaitMs;
|
|
177
|
-
for (;;) {
|
|
178
|
-
const info = await readRunningInstance(home);
|
|
179
|
-
if (!info) throw failure(NOT_RUNNING_MESSAGE);
|
|
180
|
-
if (info.url) return info.url;
|
|
181
|
-
if (env.now() >= deadline) throw failure("tau is starting but has not published its URL yet.");
|
|
182
|
-
await env.sleep(URL_POLL_MS);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
async function readToken(home) {
|
|
186
|
-
try {
|
|
187
|
-
return (await readFile(join(home, "token"), "utf8")).trim();
|
|
188
|
-
} catch {
|
|
189
|
-
throw failure(NOT_RUNNING_MESSAGE);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
/** The lock gives the URL and `<home>/token` the secret. Retries while a live server has no URL yet. */
|
|
193
|
-
async function discoverServer(env) {
|
|
194
|
-
return {
|
|
195
|
-
url: await waitForLockUrl(env.home, env),
|
|
196
|
-
token: await readToken(env.home)
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
const queryString = (query) => {
|
|
200
|
-
const entries = Object.entries(query ?? {});
|
|
201
|
-
return entries.length === 0 ? "" : `?${new URLSearchParams(entries).toString()}`;
|
|
202
|
-
};
|
|
203
|
-
async function errorMessageOf$1(response) {
|
|
204
|
-
const text = await response.text();
|
|
205
|
-
try {
|
|
206
|
-
const parsed = JSON.parse(text);
|
|
207
|
-
if (isErrorBody(parsed)) return `${parsed.error.code}: ${parsed.error.message}`;
|
|
208
|
-
} catch {}
|
|
209
|
-
return `HTTP ${response.status}${text ? `: ${text.slice(0, 200)}` : ""}`;
|
|
210
|
-
}
|
|
211
|
-
async function send(server, name, request, env) {
|
|
212
|
-
const route = routes[name];
|
|
213
|
-
const target = `${server.url}${fillPath(route.path, request.params)}${queryString(request.query)}`;
|
|
214
|
-
const hasBody = request.body !== void 0;
|
|
215
|
-
try {
|
|
216
|
-
return await env.fetch(target, {
|
|
217
|
-
method: route.method,
|
|
218
|
-
headers: {
|
|
219
|
-
authorization: `Bearer ${server.token}`,
|
|
220
|
-
...hasBody ? { "content-type": "application/json" } : {}
|
|
221
|
-
},
|
|
222
|
-
body: hasBody ? JSON.stringify(request.body) : void 0
|
|
223
|
-
});
|
|
224
|
-
} catch {
|
|
225
|
-
throw failure(NOT_RUNNING_MESSAGE);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
/** Calls one route. Returns the parsed JSON, or `undefined` for an empty answer. An `AppError` becomes a failure. */
|
|
229
|
-
async function callRoute(server, name, request, env) {
|
|
230
|
-
const response = await send(server, name, request, env);
|
|
231
|
-
if (!response.ok) throw failure(await errorMessageOf$1(response));
|
|
232
|
-
const text = await response.text();
|
|
233
|
-
return text ? JSON.parse(text) : void 0;
|
|
234
|
-
}
|
|
235
|
-
//#endregion
|
|
236
|
-
//#region src/commands/command-env.ts
|
|
237
|
-
const defaultOpenSocket = (url, headers) => {
|
|
238
|
-
return new WebSocket(url, { headers });
|
|
239
|
-
};
|
|
240
|
-
const homeOf = (env) => env.TAU_HOME ?? join(homedir(), ".tau");
|
|
241
|
-
function processEnv() {
|
|
242
|
-
return {
|
|
243
|
-
home: homeOf(process.env),
|
|
244
|
-
env: process.env,
|
|
245
|
-
stdout: (text) => void process.stdout.write(`${text}\n`),
|
|
246
|
-
stderr: (text) => void process.stderr.write(`${text}\n`),
|
|
247
|
-
fetch: (input, init) => fetch(input, init),
|
|
248
|
-
openSocket: defaultOpenSocket,
|
|
249
|
-
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
250
|
-
after: (ms, fn) => {
|
|
251
|
-
const timer = setTimeout(fn, ms);
|
|
252
|
-
return () => clearTimeout(timer);
|
|
253
|
-
},
|
|
254
|
-
now: Date.now,
|
|
255
|
-
startupWaitMs: 5e3,
|
|
256
|
-
pollMs: 2e3,
|
|
257
|
-
skillPaths: []
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
//#endregion
|
|
261
162
|
//#region src/commands/command-flags.ts
|
|
262
163
|
const WAIT_COMMANDS = ["threads start", "threads send"];
|
|
263
164
|
const COMMON_FLAGS = [
|
|
@@ -526,8 +427,18 @@ const THREAD_EXTRAS = [" wait <id> Wait until the thread is done, then pr
|
|
|
526
427
|
const LIFECYCLE = [
|
|
527
428
|
" start, stop, restart, status, open, logs, service Run and manage the server",
|
|
528
429
|
" serve Run the server in the foreground",
|
|
430
|
+
" update Install the latest tau and restart the server (--check, --yes, --force, --no-restart)",
|
|
529
431
|
" skill Print the tau skill for agents"
|
|
530
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
|
+
];
|
|
531
442
|
const pad = (text) => text.padEnd(26);
|
|
532
443
|
function rootHelp() {
|
|
533
444
|
return [
|
|
@@ -542,7 +453,8 @@ function rootHelp() {
|
|
|
542
453
|
...COMMAND_NOUNS.map((noun) => ` ${noun}`),
|
|
543
454
|
...FLOW_COMMANDS.map((word) => ` ${word}`),
|
|
544
455
|
"",
|
|
545
|
-
"Run `tau <noun> --help` for the commands of a noun, `tau <noun> <verb> --help` for one command."
|
|
456
|
+
"Run `tau <noun> --help` for the commands of a noun, `tau <noun> <verb> --help` for one command.",
|
|
457
|
+
...UPDATE_FLAGS
|
|
546
458
|
].join("\n");
|
|
547
459
|
}
|
|
548
460
|
function nounHelp(noun) {
|
|
@@ -931,7 +843,7 @@ async function redeem(hubUrl, code, deviceName, env) {
|
|
|
931
843
|
}
|
|
932
844
|
async function startAfterLink(choice, env) {
|
|
933
845
|
if (choice === "serve") {
|
|
934
|
-
const { serveInForeground } = await import("./serve-
|
|
846
|
+
const { serveInForeground } = await import("./serve-CuMzEud7.js");
|
|
935
847
|
env.stdout("Starting tau for this session. Press Ctrl+C to stop.");
|
|
936
848
|
await serveInForeground([
|
|
937
849
|
"--no-open",
|
|
@@ -939,7 +851,7 @@ async function startAfterLink(choice, env) {
|
|
|
939
851
|
env.home
|
|
940
852
|
]);
|
|
941
853
|
} else if (choice === "service") {
|
|
942
|
-
const { runServiceCommand } = await import("./service-command-
|
|
854
|
+
const { runServiceCommand } = await import("./service-command-DqcstJ8e.js");
|
|
943
855
|
await runServiceCommand([
|
|
944
856
|
"install",
|
|
945
857
|
"--home",
|
|
@@ -1010,7 +922,7 @@ const runDisconnect = runner(executeDisconnect);
|
|
|
1010
922
|
const REMOTE_WAIT_MS = 72e4;
|
|
1011
923
|
const LINK_POLL_MS = 2e3;
|
|
1012
924
|
const REMOTE_POLL_MS = 3e3;
|
|
1013
|
-
const PROGRESS_EVERY_MS =
|
|
925
|
+
const PROGRESS_EVERY_MS = 1e4;
|
|
1014
926
|
const FULL_POWER_WARNING = "A linked device gets full power over this computer: agents, terminals and your files. Share this only with yourself.";
|
|
1015
927
|
function parsePairArgs(argv) {
|
|
1016
928
|
let name;
|
|
@@ -1024,6 +936,10 @@ function parsePairArgs(argv) {
|
|
|
1024
936
|
}
|
|
1025
937
|
const groupShortCode = (code) => code.match(/.{1,4}/g)?.join(" ") ?? code;
|
|
1026
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
|
+
}
|
|
1027
943
|
async function waitUntilOnline(server, env, first) {
|
|
1028
944
|
const deadline = env.now() + REMOTE_WAIT_MS;
|
|
1029
945
|
let status = first;
|
|
@@ -1037,7 +953,7 @@ async function waitUntilOnline(server, env, first) {
|
|
|
1037
953
|
env.stdout(status.message);
|
|
1038
954
|
} else if (env.now() - lastProgress >= PROGRESS_EVERY_MS) {
|
|
1039
955
|
lastProgress = env.now();
|
|
1040
|
-
env.stdout(
|
|
956
|
+
env.stdout(elapsedLine(status, env.now()));
|
|
1041
957
|
}
|
|
1042
958
|
await env.sleep(REMOTE_POLL_MS);
|
|
1043
959
|
status = await remoteOf(server, env);
|
|
@@ -1049,7 +965,7 @@ async function ensureRemoteOnline(server, env) {
|
|
|
1049
965
|
let status = await remoteOf(server, env);
|
|
1050
966
|
if (status.state === "online") return;
|
|
1051
967
|
if (status.state === "off") {
|
|
1052
|
-
env.stdout("Remote access is off. Turning it on:
|
|
968
|
+
env.stdout("Remote access is off. Turning it on: a new URL takes 3-6 minutes, please wait.");
|
|
1053
969
|
status = await callRoute(server, "enableRemote", {}, env);
|
|
1054
970
|
} else env.stdout("Remote access is starting: this takes a few minutes.");
|
|
1055
971
|
await waitUntilOnline(server, env, status);
|
|
@@ -1136,121 +1052,6 @@ async function runSkill(_argv) {
|
|
|
1136
1052
|
await printSkill(processEnv());
|
|
1137
1053
|
}
|
|
1138
1054
|
//#endregion
|
|
1139
|
-
//#region src/lifecycle/status-extras.ts
|
|
1140
|
-
const NO_EXTRAS = {
|
|
1141
|
-
uptimeSeconds: null,
|
|
1142
|
-
lastExit: null,
|
|
1143
|
-
tunnel: null
|
|
1144
|
-
};
|
|
1145
|
-
const execFileAsync = promisify(execFile);
|
|
1146
|
-
/** `[[dd-]hh:]mm:ss` as printed by `ps -o etime=`. */
|
|
1147
|
-
function parseElapsed(text) {
|
|
1148
|
-
const match = /^(?:(?:(\d+)-)?(\d+):)?(\d+):(\d+)$/.exec(text.trim());
|
|
1149
|
-
if (!match) return null;
|
|
1150
|
-
const [, days, hours, minutes, seconds] = match.map((part) => Number(part ?? 0));
|
|
1151
|
-
return ((days ?? 0) * 24 + (hours ?? 0)) * 3600 + (minutes ?? 0) * 60 + (seconds ?? 0);
|
|
1152
|
-
}
|
|
1153
|
-
async function uptimeOf(pid) {
|
|
1154
|
-
try {
|
|
1155
|
-
const { stdout } = await execFileAsync("ps", [
|
|
1156
|
-
"-o",
|
|
1157
|
-
"etime=",
|
|
1158
|
-
"-p",
|
|
1159
|
-
String(pid)
|
|
1160
|
-
]);
|
|
1161
|
-
return parseElapsed(stdout);
|
|
1162
|
-
} catch {
|
|
1163
|
-
return null;
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
/** Missing or corrupt files give `null`. */
|
|
1167
|
-
async function readLastExit(home) {
|
|
1168
|
-
try {
|
|
1169
|
-
const value = JSON.parse(await readFile(join(home, "last-exit.json"), "utf8"));
|
|
1170
|
-
if (typeof value.reason !== "string" || typeof value.at !== "number") return null;
|
|
1171
|
-
return {
|
|
1172
|
-
reason: value.reason,
|
|
1173
|
-
at: value.at,
|
|
1174
|
-
...value.detail ? { detail: String(value.detail) } : {}
|
|
1175
|
-
};
|
|
1176
|
-
} catch {
|
|
1177
|
-
return null;
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
async function readTunnel(home, instance) {
|
|
1181
|
-
if (!instance?.url) return null;
|
|
1182
|
-
try {
|
|
1183
|
-
const token = (await readFile(join(home, "token"), "utf8")).trim();
|
|
1184
|
-
const response = await fetch(`${instance.url}/api/remote`, {
|
|
1185
|
-
headers: { authorization: `Bearer ${token}` },
|
|
1186
|
-
signal: AbortSignal.timeout(2e3)
|
|
1187
|
-
});
|
|
1188
|
-
if (!response.ok) return null;
|
|
1189
|
-
const { state, url, message } = await response.json();
|
|
1190
|
-
return {
|
|
1191
|
-
state,
|
|
1192
|
-
url,
|
|
1193
|
-
message
|
|
1194
|
-
};
|
|
1195
|
-
} catch {
|
|
1196
|
-
return null;
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
async function realExtras(home, instance) {
|
|
1200
|
-
const [uptimeSeconds, lastExit, tunnel] = await Promise.all([
|
|
1201
|
-
instance ? uptimeOf(instance.pid) : null,
|
|
1202
|
-
readLastExit(home),
|
|
1203
|
-
readTunnel(home, instance)
|
|
1204
|
-
]);
|
|
1205
|
-
return {
|
|
1206
|
-
uptimeSeconds,
|
|
1207
|
-
lastExit,
|
|
1208
|
-
tunnel
|
|
1209
|
-
};
|
|
1210
|
-
}
|
|
1211
|
-
function formatDuration(totalSeconds) {
|
|
1212
|
-
const days = Math.floor(totalSeconds / 86400);
|
|
1213
|
-
const hours = Math.floor(totalSeconds % 86400 / 3600);
|
|
1214
|
-
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
1215
|
-
if (days > 0) return `${days}d ${hours}h`;
|
|
1216
|
-
if (hours > 0) return `${hours}h ${minutes}m`;
|
|
1217
|
-
return minutes > 0 ? `${minutes}m ${totalSeconds % 60}s` : `${totalSeconds}s`;
|
|
1218
|
-
}
|
|
1219
|
-
function renderExtras(extras) {
|
|
1220
|
-
const { uptimeSeconds, lastExit, tunnel } = extras;
|
|
1221
|
-
const exit = lastExit ? `${lastExit.reason}${lastExit.detail ? ` (${lastExit.detail})` : ""} at ${new Date(lastExit.at).toISOString()}` : "-";
|
|
1222
|
-
const tunnelText = tunnel ? `${tunnel.state}${tunnel.url ? ` ${tunnel.url}` : ""}` : "-";
|
|
1223
|
-
return [
|
|
1224
|
-
`uptime: ${uptimeSeconds === null ? "-" : formatDuration(uptimeSeconds)}`,
|
|
1225
|
-
`last exit: ${exit}`,
|
|
1226
|
-
`tunnel: ${tunnelText}`
|
|
1227
|
-
];
|
|
1228
|
-
}
|
|
1229
|
-
//#endregion
|
|
1230
|
-
//#region src/lifecycle/deps.ts
|
|
1231
|
-
const withToken = (origin, token) => `${origin}/?token=${encodeURIComponent(token)}`;
|
|
1232
|
-
const managerOf = (deps, home) => createServiceManager(deps.context, deps.cli(), home);
|
|
1233
|
-
const NOT_RUNNING = "tau is not running. Run `tau` to start it.";
|
|
1234
|
-
async function realLifecycleDeps() {
|
|
1235
|
-
const { waitForUrl } = await import("./wait-for-lock-BMk6v3J5.js");
|
|
1236
|
-
const { existsSync } = await import("node:fs");
|
|
1237
|
-
return {
|
|
1238
|
-
context: realContext(),
|
|
1239
|
-
cli: () => cliEntry(),
|
|
1240
|
-
version: cliVersion,
|
|
1241
|
-
readInstance: readRunningInstance,
|
|
1242
|
-
waitForInstanceUrl: (home) => waitForUrl(home),
|
|
1243
|
-
readToken: readOrCreateToken,
|
|
1244
|
-
openBrowser: async (url) => {
|
|
1245
|
-
const { default: open } = await import("open");
|
|
1246
|
-
await open(url);
|
|
1247
|
-
},
|
|
1248
|
-
out: (message) => console.log(message),
|
|
1249
|
-
fileExists: existsSync,
|
|
1250
|
-
extras: realExtras
|
|
1251
|
-
};
|
|
1252
|
-
}
|
|
1253
|
-
//#endregion
|
|
1254
1055
|
//#region src/lifecycle/open.ts
|
|
1255
1056
|
/** Opens the browser on a running server; returns the URL it opened. */
|
|
1256
1057
|
async function openRunning(deps, home) {
|
|
@@ -1433,6 +1234,324 @@ async function runStop(argv) {
|
|
|
1433
1234
|
await runStopWith(await realLifecycleDeps(), argv);
|
|
1434
1235
|
}
|
|
1435
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
|
|
1436
1555
|
//#region src/main.ts
|
|
1437
1556
|
const apiCommandHandlers = Object.fromEntries(COMMAND_NOUNS.map((noun) => [noun, (argv) => runApiCommand(noun, argv)]));
|
|
1438
1557
|
/** The one dispatch table: command word to handler. */
|
|
@@ -1444,6 +1563,7 @@ const commandTable = {
|
|
|
1444
1563
|
status: runStatus,
|
|
1445
1564
|
open: runOpen,
|
|
1446
1565
|
logs: runLogs,
|
|
1566
|
+
update: runUpdate,
|
|
1447
1567
|
service: runServiceCommand,
|
|
1448
1568
|
skill: runSkill,
|
|
1449
1569
|
help: runHelp,
|