@todos-dev/cli 0.1.3 → 0.1.4
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/index.js +207 -143
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -1367,6 +1367,18 @@ function clearConfig() {
|
|
|
1367
1367
|
}
|
|
1368
1368
|
}
|
|
1369
1369
|
|
|
1370
|
+
// src/lib/log.ts
|
|
1371
|
+
var installed = false;
|
|
1372
|
+
function installTimestampedLogging() {
|
|
1373
|
+
if (installed) return;
|
|
1374
|
+
installed = true;
|
|
1375
|
+
const stamp = () => `[${(/* @__PURE__ */ new Date()).toISOString()}]`;
|
|
1376
|
+
for (const method of ["log", "info", "warn", "error", "debug"]) {
|
|
1377
|
+
const orig = console[method].bind(console);
|
|
1378
|
+
console[method] = (...args2) => orig(stamp(), ...args2);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1370
1382
|
// src/lib/daemon.ts
|
|
1371
1383
|
var LOG_CAP = 10 * 1024 * 1024;
|
|
1372
1384
|
var EXIT_TERMINAL = 78;
|
|
@@ -1431,6 +1443,7 @@ function spawnSupervisor(userArgs, logFd) {
|
|
|
1431
1443
|
var MODE_FLAGS = /* @__PURE__ */ new Set(["--supervisor", "--foreground", "-f"]);
|
|
1432
1444
|
var userFlagsOnly = (args2) => args2.filter((a) => !MODE_FLAGS.has(a));
|
|
1433
1445
|
async function runSupervisor(args2) {
|
|
1446
|
+
installTimestampedLogging();
|
|
1434
1447
|
const userArgs = userFlagsOnly(args2);
|
|
1435
1448
|
const MAX_BACKOFF = 3e4;
|
|
1436
1449
|
const STABLE_MS = 6e4;
|
|
@@ -14173,14 +14186,14 @@ var safeDecodeAsync2 = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);
|
|
|
14173
14186
|
var _installedGroups = /* @__PURE__ */ new WeakMap();
|
|
14174
14187
|
function _installLazyMethods(inst, group, methods) {
|
|
14175
14188
|
const proto = Object.getPrototypeOf(inst);
|
|
14176
|
-
let
|
|
14177
|
-
if (!
|
|
14178
|
-
|
|
14179
|
-
_installedGroups.set(proto,
|
|
14189
|
+
let installed2 = _installedGroups.get(proto);
|
|
14190
|
+
if (!installed2) {
|
|
14191
|
+
installed2 = /* @__PURE__ */ new Set();
|
|
14192
|
+
_installedGroups.set(proto, installed2);
|
|
14180
14193
|
}
|
|
14181
|
-
if (
|
|
14194
|
+
if (installed2.has(group))
|
|
14182
14195
|
return;
|
|
14183
|
-
|
|
14196
|
+
installed2.add(group);
|
|
14184
14197
|
for (const key in methods) {
|
|
14185
14198
|
const fn = methods[key];
|
|
14186
14199
|
Object.defineProperty(proto, key, {
|
|
@@ -16183,6 +16196,7 @@ var PollClient = class {
|
|
|
16183
16196
|
runningTasks = /* @__PURE__ */ new Map();
|
|
16184
16197
|
presenceTimer = null;
|
|
16185
16198
|
gcTimer = null;
|
|
16199
|
+
pokeTimer = null;
|
|
16186
16200
|
lastSentTunnelKey;
|
|
16187
16201
|
revoked = false;
|
|
16188
16202
|
maxConcurrentTasks = null;
|
|
@@ -16223,6 +16237,16 @@ var PollClient = class {
|
|
|
16223
16237
|
tunnel?.setNodeChangeHandler(() => void this.reportPresence());
|
|
16224
16238
|
void this.reportPresence();
|
|
16225
16239
|
}
|
|
16240
|
+
// Out-of-cycle beat for mirror-health transitions, so the web flips within ~a second
|
|
16241
|
+
// instead of waiting out the ≤20s presence tail. Debounced: a burst of transitions (daemon
|
|
16242
|
+
// restart syncing every mount at once) collapses into one POST carrying the final state.
|
|
16243
|
+
pokePresence() {
|
|
16244
|
+
if (this.pokeTimer) return;
|
|
16245
|
+
this.pokeTimer = setTimeout(() => {
|
|
16246
|
+
this.pokeTimer = null;
|
|
16247
|
+
void this.reportPresence();
|
|
16248
|
+
}, 250);
|
|
16249
|
+
}
|
|
16226
16250
|
maybeStartClaim() {
|
|
16227
16251
|
if (this.running && this.canClaim && !this.loopActive) {
|
|
16228
16252
|
this.loopActive = true;
|
|
@@ -16257,6 +16281,10 @@ var PollClient = class {
|
|
|
16257
16281
|
clearInterval(this.gcTimer);
|
|
16258
16282
|
this.gcTimer = null;
|
|
16259
16283
|
}
|
|
16284
|
+
if (this.pokeTimer) {
|
|
16285
|
+
clearTimeout(this.pokeTimer);
|
|
16286
|
+
this.pokeTimer = null;
|
|
16287
|
+
}
|
|
16260
16288
|
}
|
|
16261
16289
|
// The machine was removed from its team (or its token revoked): every /api/machine/* call now
|
|
16262
16290
|
// 401/403s. Fail fast instead of spinning forever — stop and exit so the operator notices.
|
|
@@ -16413,10 +16441,12 @@ function sleep(ms, signal) {
|
|
|
16413
16441
|
}
|
|
16414
16442
|
|
|
16415
16443
|
// src/SyncManager.ts
|
|
16416
|
-
var import_node_child_process2 = require("node:child_process");
|
|
16417
16444
|
var import_node_os4 = require("node:os");
|
|
16418
16445
|
var import_node_path6 = require("node:path");
|
|
16419
16446
|
|
|
16447
|
+
// src/lib/sidecar.ts
|
|
16448
|
+
var import_node_child_process2 = require("node:child_process");
|
|
16449
|
+
|
|
16420
16450
|
// src/lib/bin.ts
|
|
16421
16451
|
var import_node_fs5 = require("node:fs");
|
|
16422
16452
|
var import_node_path5 = require("node:path");
|
|
@@ -16482,31 +16512,24 @@ function createNdjsonParser(onEvent) {
|
|
|
16482
16512
|
};
|
|
16483
16513
|
}
|
|
16484
16514
|
|
|
16485
|
-
// src/
|
|
16515
|
+
// src/lib/sidecar.ts
|
|
16486
16516
|
var RESTART_MIN_MS = 1e3;
|
|
16487
16517
|
var RESTART_MAX_MS = 6e4;
|
|
16488
|
-
|
|
16489
|
-
|
|
16490
|
-
|
|
16491
|
-
|
|
16492
|
-
|
|
16493
|
-
|
|
16494
|
-
// by mountId
|
|
16495
|
-
constructor(serverUrl, token, machineId) {
|
|
16496
|
-
this.serverUrl = serverUrl;
|
|
16497
|
-
this.token = token;
|
|
16498
|
-
this.machineId = machineId;
|
|
16518
|
+
var Sidecar = class {
|
|
16519
|
+
constructor(args2, label, onEvent, onExit) {
|
|
16520
|
+
this.args = args2;
|
|
16521
|
+
this.label = label;
|
|
16522
|
+
this.onExit = onExit;
|
|
16523
|
+
this.parse = createNdjsonParser(onEvent);
|
|
16499
16524
|
}
|
|
16500
16525
|
proc = null;
|
|
16501
16526
|
stopped = false;
|
|
16502
16527
|
restartTimer = null;
|
|
16503
16528
|
restartDelayMs = RESTART_MIN_MS;
|
|
16504
|
-
|
|
16505
|
-
health = /* @__PURE__ */ new Map();
|
|
16529
|
+
parse;
|
|
16506
16530
|
start() {
|
|
16507
16531
|
this.stopped = false;
|
|
16508
|
-
|
|
16509
|
-
this.spawnSidecar();
|
|
16532
|
+
this.spawn();
|
|
16510
16533
|
}
|
|
16511
16534
|
stop() {
|
|
16512
16535
|
this.stopped = true;
|
|
@@ -16517,6 +16540,88 @@ var SyncManager = class {
|
|
|
16517
16540
|
this.proc?.kill();
|
|
16518
16541
|
this.proc = null;
|
|
16519
16542
|
}
|
|
16543
|
+
// Owner saw the sidecar's healthy signal — future crashes retry quickly again.
|
|
16544
|
+
resetBackoff() {
|
|
16545
|
+
this.restartDelayMs = RESTART_MIN_MS;
|
|
16546
|
+
}
|
|
16547
|
+
// Whether control messages can be sent at all (process up with a writable stdin).
|
|
16548
|
+
alive() {
|
|
16549
|
+
return Boolean(this.proc && !this.proc.killed && this.proc.stdin && !this.proc.stdin.destroyed);
|
|
16550
|
+
}
|
|
16551
|
+
// Write one NDJSON control message to the sidecar's stdin; false when it can't be delivered
|
|
16552
|
+
// (the exit/respawn path is the recovery, so callers just skip).
|
|
16553
|
+
write(msg) {
|
|
16554
|
+
const stdin = this.proc?.stdin;
|
|
16555
|
+
if (!stdin || stdin.destroyed) return false;
|
|
16556
|
+
try {
|
|
16557
|
+
stdin.write(JSON.stringify(msg) + "\n");
|
|
16558
|
+
return true;
|
|
16559
|
+
} catch {
|
|
16560
|
+
return false;
|
|
16561
|
+
}
|
|
16562
|
+
}
|
|
16563
|
+
spawn() {
|
|
16564
|
+
try {
|
|
16565
|
+
this.proc = (0, import_node_child_process2.spawn)(TUNNEL_BIN, this.args, { stdio: ["pipe", "pipe", "inherit"] });
|
|
16566
|
+
} catch {
|
|
16567
|
+
console.warn(`[${this.label}] tds-tunnel not available \u2014 disabled`);
|
|
16568
|
+
return;
|
|
16569
|
+
}
|
|
16570
|
+
this.proc.on("error", () => {
|
|
16571
|
+
console.warn(`[${this.label}] sidecar failed to start \u2014 disabled`);
|
|
16572
|
+
this.proc = null;
|
|
16573
|
+
});
|
|
16574
|
+
this.proc.on("exit", () => {
|
|
16575
|
+
this.proc = null;
|
|
16576
|
+
this.onExit?.();
|
|
16577
|
+
this.scheduleRestart();
|
|
16578
|
+
});
|
|
16579
|
+
this.proc.stdout?.on("data", (d) => this.parse(String(d)));
|
|
16580
|
+
this.proc.stdin?.on("error", () => {
|
|
16581
|
+
});
|
|
16582
|
+
}
|
|
16583
|
+
scheduleRestart() {
|
|
16584
|
+
if (this.stopped || this.restartTimer) return;
|
|
16585
|
+
const delay2 = this.restartDelayMs;
|
|
16586
|
+
this.restartDelayMs = Math.min(this.restartDelayMs * 2, RESTART_MAX_MS);
|
|
16587
|
+
console.warn(`[${this.label}] sidecar exited \u2014 restarting in ${Math.round(delay2 / 1e3)}s`);
|
|
16588
|
+
this.restartTimer = setTimeout(() => {
|
|
16589
|
+
this.restartTimer = null;
|
|
16590
|
+
this.spawn();
|
|
16591
|
+
}, delay2);
|
|
16592
|
+
}
|
|
16593
|
+
};
|
|
16594
|
+
|
|
16595
|
+
// src/SyncManager.ts
|
|
16596
|
+
function expandHome(p) {
|
|
16597
|
+
if (p === "~") return (0, import_node_os4.homedir)();
|
|
16598
|
+
if (p.startsWith("~/") || p.startsWith("~\\")) return (0, import_node_path6.join)((0, import_node_os4.homedir)(), p.slice(2));
|
|
16599
|
+
return p;
|
|
16600
|
+
}
|
|
16601
|
+
var WORKTREE_REBUILD_PATIENCE_MS = 18e4;
|
|
16602
|
+
var SyncManager = class {
|
|
16603
|
+
constructor(serverUrl, token, machineId) {
|
|
16604
|
+
this.machineId = machineId;
|
|
16605
|
+
this.sidecar = new Sidecar(
|
|
16606
|
+
["mirror", "--server", serverUrl, "--token", token, "--machine", machineId],
|
|
16607
|
+
"runner",
|
|
16608
|
+
(e) => this.onEvent(e)
|
|
16609
|
+
);
|
|
16610
|
+
}
|
|
16611
|
+
sidecar;
|
|
16612
|
+
desired = [];
|
|
16613
|
+
health = /* @__PURE__ */ new Map();
|
|
16614
|
+
// by mountId
|
|
16615
|
+
// Fired on coarse health transitions (first cycle, error set/cleared) so the presence beat
|
|
16616
|
+
// carrying the change goes out immediately instead of on the next 20s tick.
|
|
16617
|
+
onHealthChanged;
|
|
16618
|
+
start() {
|
|
16619
|
+
console.log(`[machine] runner: managing worktree mirrors (machineId=${this.machineId})`);
|
|
16620
|
+
this.sidecar.start();
|
|
16621
|
+
}
|
|
16622
|
+
stop() {
|
|
16623
|
+
this.sidecar.stop();
|
|
16624
|
+
}
|
|
16520
16625
|
// Desired mounts, straight off the presence response. Declarative: resolve dests, remember the
|
|
16521
16626
|
// set (a respawned daemon gets it replayed on its 'up' event), and forward it.
|
|
16522
16627
|
setMounts(resolved) {
|
|
@@ -16545,64 +16650,36 @@ var SyncManager = class {
|
|
|
16545
16650
|
};
|
|
16546
16651
|
});
|
|
16547
16652
|
}
|
|
16548
|
-
|
|
16549
|
-
try {
|
|
16550
|
-
this.proc = (0, import_node_child_process2.spawn)(
|
|
16551
|
-
TUNNEL_BIN,
|
|
16552
|
-
["mirror", "--server", this.serverUrl, "--token", this.token, "--machine", this.machineId],
|
|
16553
|
-
// stdin is piped: the desired mount set goes down as NDJSON control messages.
|
|
16554
|
-
{ stdio: ["pipe", "pipe", "inherit"] }
|
|
16555
|
-
);
|
|
16556
|
-
} catch {
|
|
16557
|
-
console.warn("[machine] runner: tds-tunnel not available \u2014 mirroring disabled");
|
|
16558
|
-
return;
|
|
16559
|
-
}
|
|
16560
|
-
this.proc.on("error", () => {
|
|
16561
|
-
console.warn("[machine] runner: mirror daemon failed to start \u2014 mirroring disabled");
|
|
16562
|
-
this.proc = null;
|
|
16563
|
-
});
|
|
16564
|
-
this.proc.on("exit", () => {
|
|
16565
|
-
this.proc = null;
|
|
16566
|
-
this.scheduleRestart();
|
|
16567
|
-
});
|
|
16568
|
-
this.proc.stdout?.on("data", (d) => this.onStdout(String(d)));
|
|
16569
|
-
this.proc.stdin?.on("error", () => {
|
|
16570
|
-
});
|
|
16571
|
-
}
|
|
16572
|
-
scheduleRestart() {
|
|
16573
|
-
if (this.stopped || this.restartTimer) return;
|
|
16574
|
-
const delay2 = this.restartDelayMs;
|
|
16575
|
-
this.restartDelayMs = Math.min(this.restartDelayMs * 2, RESTART_MAX_MS);
|
|
16576
|
-
console.warn(`[machine] runner: mirror daemon exited \u2014 restarting in ${Math.round(delay2 / 1e3)}s`);
|
|
16577
|
-
this.restartTimer = setTimeout(() => {
|
|
16578
|
-
this.restartTimer = null;
|
|
16579
|
-
this.spawnSidecar();
|
|
16580
|
-
}, delay2);
|
|
16581
|
-
}
|
|
16582
|
-
onStdout = createNdjsonParser((e) => {
|
|
16653
|
+
onEvent(e) {
|
|
16583
16654
|
if (e.event === "up") {
|
|
16584
|
-
this.
|
|
16655
|
+
this.sidecar.resetBackoff();
|
|
16585
16656
|
this.sendMounts();
|
|
16586
16657
|
} else if (e.event === "synced" && e.mount) {
|
|
16658
|
+
const prev = this.health.get(e.mount);
|
|
16587
16659
|
this.health.set(e.mount, { syncedAt: Date.now(), lastError: null });
|
|
16660
|
+
if (!prev?.syncedAt || prev.lastError) this.onHealthChanged?.();
|
|
16588
16661
|
if (e.changed || e.deleted) console.log(`[machine] runner: mount ${e.mount} \u2193 ${e.changed ?? 0} changed, ${e.deleted ?? 0} deleted`);
|
|
16662
|
+
} else if (e.event === "worktree-missing" && e.mount) {
|
|
16663
|
+
const prev = this.health.get(e.mount);
|
|
16664
|
+
const missingSince = prev?.missingSince ?? Date.now();
|
|
16665
|
+
const lastError = Date.now() - missingSince > WORKTREE_REBUILD_PATIENCE_MS ? e.message ?? "worktree missing" : null;
|
|
16666
|
+
this.health.set(e.mount, { syncedAt: 0, lastError, missingSince });
|
|
16667
|
+
if (!!prev?.syncedAt || (prev?.lastError ?? null) !== lastError) this.onHealthChanged?.();
|
|
16668
|
+
console.log(`[machine] runner: mount ${e.mount}: worktree missing \u2014 rebuild requested`);
|
|
16589
16669
|
} else if (e.event === "error") {
|
|
16590
16670
|
if (e.mount) {
|
|
16591
|
-
const
|
|
16592
|
-
|
|
16593
|
-
|
|
16671
|
+
const prev = this.health.get(e.mount);
|
|
16672
|
+
const message = e.message ?? "error";
|
|
16673
|
+
this.health.set(e.mount, { syncedAt: prev?.syncedAt ?? 0, lastError: message });
|
|
16674
|
+
if (prev?.lastError !== message) this.onHealthChanged?.();
|
|
16675
|
+
console.warn(`[machine] runner: mount ${e.mount}: ${message}`);
|
|
16594
16676
|
} else {
|
|
16595
16677
|
console.warn(`[machine] runner: ${e.message ?? "error"}`);
|
|
16596
16678
|
}
|
|
16597
16679
|
}
|
|
16598
|
-
}
|
|
16680
|
+
}
|
|
16599
16681
|
sendMounts() {
|
|
16600
|
-
|
|
16601
|
-
if (!stdin || stdin.destroyed) return;
|
|
16602
|
-
try {
|
|
16603
|
-
stdin.write(JSON.stringify({ cmd: "mounts", mounts: this.desired }) + "\n");
|
|
16604
|
-
} catch {
|
|
16605
|
-
}
|
|
16682
|
+
this.sidecar.write({ cmd: "mounts", mounts: this.desired });
|
|
16606
16683
|
}
|
|
16607
16684
|
};
|
|
16608
16685
|
|
|
@@ -16714,26 +16791,30 @@ async function syncModelsToServer(serverUrl, token, runtime) {
|
|
|
16714
16791
|
}
|
|
16715
16792
|
|
|
16716
16793
|
// src/lib/tunnel.ts
|
|
16717
|
-
var import_node_child_process3 = require("node:child_process");
|
|
16718
16794
|
var SHELL_BUFFER_CAP = 256 * 1024;
|
|
16719
|
-
var RESTART_MIN_MS2 = 1e3;
|
|
16720
|
-
var RESTART_MAX_MS2 = 6e4;
|
|
16721
16795
|
var EMPTY_BUFFER = Buffer.alloc(0);
|
|
16722
16796
|
var TunnelServer = class _TunnelServer {
|
|
16723
|
-
constructor(serverUrl, token) {
|
|
16724
|
-
this.serverUrl = serverUrl;
|
|
16725
|
-
this.token = token;
|
|
16726
|
-
}
|
|
16727
|
-
proc = null;
|
|
16728
16797
|
node = null;
|
|
16729
16798
|
onChange = null;
|
|
16730
|
-
|
|
16731
|
-
|
|
16732
|
-
|
|
16799
|
+
// Process lifecycle (spawn / respawn-with-backoff / stdin control) is the shared Sidecar's job;
|
|
16800
|
+
// this class keeps only the domain state that dies with the process: node + shells.
|
|
16801
|
+
sidecar;
|
|
16733
16802
|
// Reverse shells, keyed by (build, machine) — a build mirrored onto several runners has one shell
|
|
16734
16803
|
// per machine. Output is buffered even with no live subscriber so output a backgrounded command
|
|
16735
16804
|
// emits between tool calls survives until the next drain.
|
|
16736
16805
|
shells = /* @__PURE__ */ new Map();
|
|
16806
|
+
constructor(serverUrl, token) {
|
|
16807
|
+
this.sidecar = new Sidecar(
|
|
16808
|
+
["serve", "--server", serverUrl, "--token", token],
|
|
16809
|
+
"tunnel",
|
|
16810
|
+
(evt) => this.onEvent(evt),
|
|
16811
|
+
() => {
|
|
16812
|
+
this.node = null;
|
|
16813
|
+
this.closeAllShells();
|
|
16814
|
+
this.onChange?.();
|
|
16815
|
+
}
|
|
16816
|
+
);
|
|
16817
|
+
}
|
|
16737
16818
|
// Set the single handler fired whenever the tunnel node appears/changes/clears, so presence can
|
|
16738
16819
|
// be pushed immediately instead of waiting for the next heartbeat tick. Fires now if already ready.
|
|
16739
16820
|
setNodeChangeHandler(cb) {
|
|
@@ -16741,56 +16822,16 @@ var TunnelServer = class _TunnelServer {
|
|
|
16741
16822
|
if (this.node) cb();
|
|
16742
16823
|
}
|
|
16743
16824
|
start() {
|
|
16744
|
-
this.
|
|
16745
|
-
this.spawnSidecar();
|
|
16746
|
-
}
|
|
16747
|
-
spawnSidecar() {
|
|
16748
|
-
try {
|
|
16749
|
-
this.proc = (0, import_node_child_process3.spawn)(
|
|
16750
|
-
TUNNEL_BIN,
|
|
16751
|
-
["serve", "--server", this.serverUrl, "--token", this.token],
|
|
16752
|
-
// stdin is piped (not ignored) so we can write reverse-shell control messages to the sidecar.
|
|
16753
|
-
{ stdio: ["pipe", "pipe", "inherit"] }
|
|
16754
|
-
);
|
|
16755
|
-
} catch {
|
|
16756
|
-
console.warn("[tunnel] sidecar not available \u2014 runner mirroring disabled");
|
|
16757
|
-
return;
|
|
16758
|
-
}
|
|
16759
|
-
this.proc.on("error", () => {
|
|
16760
|
-
console.warn("[tunnel] sidecar failed to start \u2014 runner mirroring disabled");
|
|
16761
|
-
this.proc = null;
|
|
16762
|
-
});
|
|
16763
|
-
this.proc.on("exit", () => {
|
|
16764
|
-
this.proc = null;
|
|
16765
|
-
this.node = null;
|
|
16766
|
-
this.closeAllShells();
|
|
16767
|
-
this.onChange?.();
|
|
16768
|
-
this.scheduleRestart();
|
|
16769
|
-
});
|
|
16770
|
-
this.proc.stdout?.on("data", (d) => this.onStdout(String(d)));
|
|
16771
|
-
this.proc.stdin?.on("error", () => {
|
|
16772
|
-
});
|
|
16773
|
-
}
|
|
16774
|
-
scheduleRestart() {
|
|
16775
|
-
if (this.stopped || this.restartTimer) return;
|
|
16776
|
-
const delay2 = this.restartDelayMs;
|
|
16777
|
-
this.restartDelayMs = Math.min(this.restartDelayMs * 2, RESTART_MAX_MS2);
|
|
16778
|
-
console.warn(`[tunnel] sidecar exited \u2014 restarting in ${Math.round(delay2 / 1e3)}s`);
|
|
16779
|
-
this.restartTimer = setTimeout(() => {
|
|
16780
|
-
this.restartTimer = null;
|
|
16781
|
-
this.spawnSidecar();
|
|
16782
|
-
}, delay2);
|
|
16825
|
+
this.sidecar.start();
|
|
16783
16826
|
}
|
|
16784
16827
|
// Reverse-shell map key: a build mirrored onto several runners has one shell per machine.
|
|
16785
16828
|
static shellKey(build, machine) {
|
|
16786
16829
|
return `${build}\0${machine}`;
|
|
16787
16830
|
}
|
|
16788
|
-
|
|
16789
|
-
// shell-opened/shell-output/shell-closed carry reverse-shell traffic for a (build, machine).
|
|
16790
|
-
onStdout = createNdjsonParser((evt) => {
|
|
16831
|
+
onEvent(evt) {
|
|
16791
16832
|
if (evt.event === "ready" && evt.tailnetAddr && evt.sshHostKey) {
|
|
16792
16833
|
this.node = { tailnetAddr: evt.tailnetAddr, sshHostKey: evt.sshHostKey };
|
|
16793
|
-
this.
|
|
16834
|
+
this.sidecar.resetBackoff();
|
|
16794
16835
|
console.log(`[tunnel] ready (tailnet ${evt.tailnetAddr})`);
|
|
16795
16836
|
this.onChange?.();
|
|
16796
16837
|
} else if ((evt.event === "shell-opened" || evt.event === "shell-output" || evt.event === "shell-closed") && evt.build) {
|
|
@@ -16814,27 +16855,17 @@ var TunnelServer = class _TunnelServer {
|
|
|
16814
16855
|
} else if (evt.event === "error") {
|
|
16815
16856
|
console.warn(`[tunnel] ${evt.message ?? "error"}`);
|
|
16816
16857
|
}
|
|
16817
|
-
}
|
|
16858
|
+
}
|
|
16818
16859
|
// --- Reverse shell (agent → user's machine via the sync client) -----------------------------
|
|
16819
16860
|
// Whether reverse-shell control can be sent at all (sidecar up with a writable stdin). The tool
|
|
16820
16861
|
// checks this to fail fast instead of writing into the void.
|
|
16821
16862
|
shellAvailable() {
|
|
16822
|
-
return
|
|
16823
|
-
}
|
|
16824
|
-
writeControl(msg) {
|
|
16825
|
-
const stdin = this.proc?.stdin;
|
|
16826
|
-
if (!stdin || stdin.destroyed) return false;
|
|
16827
|
-
try {
|
|
16828
|
-
stdin.write(JSON.stringify(msg) + "\n");
|
|
16829
|
-
return true;
|
|
16830
|
-
} catch {
|
|
16831
|
-
return false;
|
|
16832
|
-
}
|
|
16863
|
+
return this.sidecar.alive();
|
|
16833
16864
|
}
|
|
16834
16865
|
// Send keystrokes/command text to a (build, machine) shell. data is UTF-8 text (may include control
|
|
16835
16866
|
// bytes like \x03 for Ctrl-C); it's base64-framed on the wire.
|
|
16836
16867
|
shellSendInput(build, machine, data) {
|
|
16837
|
-
return this.
|
|
16868
|
+
return this.sidecar.write({ cmd: "shell-input", build, machine, data: Buffer.from(data, "utf8").toString("base64") });
|
|
16838
16869
|
}
|
|
16839
16870
|
// Whether the client's shell channel is currently registered sidecar-side. undefined = no event
|
|
16840
16871
|
// seen yet for this key (a client may still be connected — input discovers it); false = it was
|
|
@@ -16891,13 +16922,7 @@ var TunnelServer = class _TunnelServer {
|
|
|
16891
16922
|
return this.node;
|
|
16892
16923
|
}
|
|
16893
16924
|
stop() {
|
|
16894
|
-
this.
|
|
16895
|
-
if (this.restartTimer) {
|
|
16896
|
-
clearTimeout(this.restartTimer);
|
|
16897
|
-
this.restartTimer = null;
|
|
16898
|
-
}
|
|
16899
|
-
this.proc?.kill();
|
|
16900
|
-
this.proc = null;
|
|
16925
|
+
this.sidecar.stop();
|
|
16901
16926
|
this.node = null;
|
|
16902
16927
|
this.closeAllShells();
|
|
16903
16928
|
}
|
|
@@ -16957,6 +16982,7 @@ async function startCommand(args2) {
|
|
|
16957
16982
|
const { flags, bools } = parseFlags(args2, { f: "foreground" });
|
|
16958
16983
|
if (bools.supervisor) return runSupervisor(args2);
|
|
16959
16984
|
if (bools.foreground) {
|
|
16985
|
+
installTimestampedLogging();
|
|
16960
16986
|
const { config: config2, serverUrl } = await ensureEnrolled(flags);
|
|
16961
16987
|
return runWorker(config2, serverUrl);
|
|
16962
16988
|
}
|
|
@@ -17089,6 +17115,7 @@ async function runWorker(config2, serverUrl) {
|
|
|
17089
17115
|
syncManager.start();
|
|
17090
17116
|
syncManager.setMounts(lastMounts);
|
|
17091
17117
|
client.getMountSync = () => syncManager?.status() ?? null;
|
|
17118
|
+
syncManager.onHealthChanged = () => client.pokePresence();
|
|
17092
17119
|
};
|
|
17093
17120
|
const disableRunner = () => {
|
|
17094
17121
|
if (!syncManager) return;
|
|
@@ -17788,6 +17815,32 @@ async function logoutCommand() {
|
|
|
17788
17815
|
console.log("[tds] Run 'tds start' to enroll again.");
|
|
17789
17816
|
}
|
|
17790
17817
|
|
|
17818
|
+
// src/lib/version.ts
|
|
17819
|
+
var import_fs6 = require("fs");
|
|
17820
|
+
var import_path2 = require("path");
|
|
17821
|
+
function getVersion() {
|
|
17822
|
+
let dir = __dirname;
|
|
17823
|
+
for (let i = 0; i < 6; i++) {
|
|
17824
|
+
const p = (0, import_path2.join)(dir, "package.json");
|
|
17825
|
+
if ((0, import_fs6.existsSync)(p)) {
|
|
17826
|
+
try {
|
|
17827
|
+
const pkg = JSON.parse((0, import_fs6.readFileSync)(p, "utf-8"));
|
|
17828
|
+
if (pkg.version) return pkg.version;
|
|
17829
|
+
} catch {
|
|
17830
|
+
}
|
|
17831
|
+
}
|
|
17832
|
+
const parent = (0, import_path2.dirname)(dir);
|
|
17833
|
+
if (parent === dir) break;
|
|
17834
|
+
dir = parent;
|
|
17835
|
+
}
|
|
17836
|
+
return "unknown";
|
|
17837
|
+
}
|
|
17838
|
+
|
|
17839
|
+
// src/commands/version.ts
|
|
17840
|
+
async function versionCommand() {
|
|
17841
|
+
console.log(`tds ${getVersion()}`);
|
|
17842
|
+
}
|
|
17843
|
+
|
|
17791
17844
|
// src/index.ts
|
|
17792
17845
|
var COMMANDS = {
|
|
17793
17846
|
start: {
|
|
@@ -17857,6 +17910,13 @@ Stop the background daemon (if running), then clear ~/.tds/machine.json so the n
|
|
|
17857
17910
|
help: `Usage: tds status
|
|
17858
17911
|
|
|
17859
17912
|
Show this machine's registration and current status.`
|
|
17913
|
+
},
|
|
17914
|
+
version: {
|
|
17915
|
+
run: () => versionCommand(),
|
|
17916
|
+
summary: "Show the tds CLI version",
|
|
17917
|
+
help: `Usage: tds version
|
|
17918
|
+
|
|
17919
|
+
Print the installed tds CLI version.`
|
|
17860
17920
|
},
|
|
17861
17921
|
provider: {
|
|
17862
17922
|
run: providerCommand,
|
|
@@ -17875,6 +17935,10 @@ Subcommands:
|
|
|
17875
17935
|
var args = process.argv.slice(2);
|
|
17876
17936
|
var cmd = args[0];
|
|
17877
17937
|
var rest = args.slice(1);
|
|
17938
|
+
if (cmd === "--version" || cmd === "-v") {
|
|
17939
|
+
console.log(`tds ${getVersion()}`);
|
|
17940
|
+
process.exit(0);
|
|
17941
|
+
}
|
|
17878
17942
|
if (!cmd || cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
17879
17943
|
const topic = cmd === "help" ? rest[0] : void 0;
|
|
17880
17944
|
if (topic && COMMANDS[topic]) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@todos-dev/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"bin": {
|
|
5
5
|
"tds": "dist/index.js"
|
|
6
6
|
},
|
|
@@ -27,12 +27,12 @@
|
|
|
27
27
|
"@tds/types": "0.1.0"
|
|
28
28
|
},
|
|
29
29
|
"optionalDependencies": {
|
|
30
|
-
"@todos-dev/cli-darwin-arm64": "0.1.
|
|
31
|
-
"@todos-dev/cli-darwin-x64": "0.1.
|
|
32
|
-
"@todos-dev/cli-linux-x64": "0.1.
|
|
33
|
-
"@todos-dev/cli-linux-arm64": "0.1.
|
|
34
|
-
"@todos-dev/cli-win32-x64": "0.1.
|
|
35
|
-
"@todos-dev/cli-win32-arm64": "0.1.
|
|
30
|
+
"@todos-dev/cli-darwin-arm64": "0.1.4",
|
|
31
|
+
"@todos-dev/cli-darwin-x64": "0.1.4",
|
|
32
|
+
"@todos-dev/cli-linux-x64": "0.1.4",
|
|
33
|
+
"@todos-dev/cli-linux-arm64": "0.1.4",
|
|
34
|
+
"@todos-dev/cli-win32-x64": "0.1.4",
|
|
35
|
+
"@todos-dev/cli-win32-arm64": "0.1.4"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"dev": "tsx watch src/index.ts",
|