zooid 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +182 -142
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-LOILUENL.js → chunk-PDSJJWQW.js} +1827 -967
- package/dist/chunk-PDSJJWQW.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +9 -9
- package/src/bootstrap/configs.ts +19 -6
- package/src/bootstrap/derive.ts +3 -2
- package/src/bootstrap/identity-bootstrap.integration.test.ts +53 -0
- package/src/bootstrap/registration-url.test.ts +14 -0
- package/src/bootstrap/registration-url.ts +7 -0
- package/src/commands/dev.ts +1 -1
- package/src/commands/init/generators.test.ts +30 -12
- package/src/commands/init/generators.ts +23 -12
- package/src/commands/init/prompts.ts +9 -34
- package/src/commands/init/registry.test.ts +2 -2
- package/src/commands/init/registry.ts +9 -8
- package/src/commands/init/scaffold-roundtrip.test.ts +38 -0
- package/src/commands/init.test.ts +17 -12
- package/src/commands/init.ts +2 -4
- package/src/daemon/pull-wiring.test.ts +14 -0
- package/src/daemon/pull-wiring.ts +4 -0
- package/src/daemon/start-daemon-pull.integration.test.ts +95 -0
- package/src/daemon/start-daemon.ts +53 -6
- package/src/daemon/sync-cursors.test.ts +38 -0
- package/src/daemon/sync-cursors.ts +31 -0
- package/src/web/resolve.test.ts +2 -2
- package/src/web/resolve.ts +2 -2
- package/dist/chunk-LOILUENL.js.map +0 -1
package/dist/bin.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
renderRegistration,
|
|
17
17
|
startDaemonSocketServer,
|
|
18
18
|
startWorkforcePublisher
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-PDSJJWQW.js";
|
|
20
20
|
|
|
21
21
|
// src/bin.ts
|
|
22
22
|
import { resolve as resolve6 } from "path";
|
|
@@ -25,8 +25,8 @@ import { cac } from "cac";
|
|
|
25
25
|
// src/commands/dev.ts
|
|
26
26
|
import chalk from "chalk";
|
|
27
27
|
import { Listr } from "listr2";
|
|
28
|
-
import { existsSync as existsSync5, readFileSync as
|
|
29
|
-
import { dirname as dirname6, join as
|
|
28
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
29
|
+
import { dirname as dirname6, join as join12, resolve as resolve3 } from "path";
|
|
30
30
|
import { fileURLToPath } from "url";
|
|
31
31
|
import { serve as serve2 } from "@hono/node-server";
|
|
32
32
|
|
|
@@ -57,7 +57,19 @@ async function ensureAdminUser(opts) {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
// src/bootstrap/configs.ts
|
|
60
|
+
import { join } from "path";
|
|
60
61
|
import { mkdirSync, writeFileSync } from "fs";
|
|
62
|
+
|
|
63
|
+
// src/bootstrap/registration-url.ts
|
|
64
|
+
function deriveRegistrationUrl(opts) {
|
|
65
|
+
if (opts.port != null && opts.advertise_url != null)
|
|
66
|
+
throw new Error("registration url: 'port' and 'advertise_url' are mutually exclusive");
|
|
67
|
+
if (opts.advertise_url != null) return opts.advertise_url;
|
|
68
|
+
if (opts.port != null) return `http://host.docker.internal:${opts.port}`;
|
|
69
|
+
throw new Error("registration url: need port or advertise_url");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/bootstrap/configs.ts
|
|
61
73
|
var TUWUNEL_INTERNAL_PORT = 8448;
|
|
62
74
|
function renderTuwunelToml(opts) {
|
|
63
75
|
return [
|
|
@@ -80,9 +92,13 @@ function writeBootstrapConfigs(opts) {
|
|
|
80
92
|
mkdirSync(paths.mediaDir, { recursive: true });
|
|
81
93
|
mkdirSync(paths.registrationsDir, { recursive: true });
|
|
82
94
|
writeFileSync(paths.tuwunelTomlPath, renderTuwunelToml({ serverName }));
|
|
95
|
+
const id = opts.workstation ?? "zooid";
|
|
96
|
+
const url = opts.workstation ? deriveRegistrationUrl({ port: opts.port ?? 9099, advertise_url: opts.advertiseUrl }) : `http://host.docker.internal:9099`;
|
|
97
|
+
const exclusive = !!opts.workstation;
|
|
98
|
+
const registrationPath = join(paths.registrationsDir, `${id}.yaml`);
|
|
83
99
|
const yaml = renderRegistration({
|
|
84
|
-
id
|
|
85
|
-
url
|
|
100
|
+
id,
|
|
101
|
+
url,
|
|
86
102
|
homeserver: `http://localhost:${TUWUNEL_INTERNAL_PORT}`,
|
|
87
103
|
asToken,
|
|
88
104
|
hsToken,
|
|
@@ -91,28 +107,26 @@ function writeBootstrapConfigs(opts) {
|
|
|
91
107
|
// BotPool.bootstrap creates `#alias:<server>` rooms when missing — the
|
|
92
108
|
// AS needs an aliases namespace to legally claim them.
|
|
93
109
|
aliasNamespace: `#.*:${serverName}`,
|
|
94
|
-
|
|
95
|
-
// admin human, so the AS cannot claim the namespace exclusively.
|
|
96
|
-
exclusive: false
|
|
110
|
+
exclusive
|
|
97
111
|
});
|
|
98
|
-
writeFileSync(
|
|
112
|
+
writeFileSync(registrationPath, yaml);
|
|
99
113
|
}
|
|
100
114
|
|
|
101
115
|
// src/bootstrap/data-layout.ts
|
|
102
|
-
import { join } from "path";
|
|
116
|
+
import { join as join2 } from "path";
|
|
103
117
|
function resolveDataLayout(dataRoot) {
|
|
104
|
-
const agentsDir =
|
|
118
|
+
const agentsDir = join2(dataRoot, "agents");
|
|
105
119
|
return {
|
|
106
120
|
dataRoot,
|
|
107
|
-
matrixDir:
|
|
108
|
-
logsDir:
|
|
121
|
+
matrixDir: join2(dataRoot, "matrix"),
|
|
122
|
+
logsDir: join2(dataRoot, "logs"),
|
|
109
123
|
agentsDir,
|
|
110
|
-
agentDir: (id) =>
|
|
124
|
+
agentDir: (id) => join2(agentsDir, id)
|
|
111
125
|
};
|
|
112
126
|
}
|
|
113
127
|
|
|
114
128
|
// src/bootstrap/derive.ts
|
|
115
|
-
var NAMESPACE_RE =
|
|
129
|
+
var NAMESPACE_RE = /^@(?:\.\*|[a-z0-9-]+\\\.\.\*):([A-Za-z0-9.-]+)$/;
|
|
116
130
|
function deriveHomeserverShape(matrix, agentUserIds) {
|
|
117
131
|
const url = new URL(matrix.homeserver);
|
|
118
132
|
const host = url.hostname;
|
|
@@ -120,7 +134,7 @@ function deriveHomeserverShape(matrix, agentUserIds) {
|
|
|
120
134
|
const m = NAMESPACE_RE.exec(matrix.user_namespace);
|
|
121
135
|
if (!m) {
|
|
122
136
|
throw new Error(
|
|
123
|
-
`user_namespace ${matrix.user_namespace}: expected
|
|
137
|
+
`user_namespace ${matrix.user_namespace}: expected '@.*:<server_name>' or '@<workstation>\\..*:<server_name>'`
|
|
124
138
|
);
|
|
125
139
|
}
|
|
126
140
|
const serverName = m[1];
|
|
@@ -135,19 +149,19 @@ function deriveHomeserverShape(matrix, agentUserIds) {
|
|
|
135
149
|
}
|
|
136
150
|
|
|
137
151
|
// src/bootstrap/paths.ts
|
|
138
|
-
import { join as
|
|
152
|
+
import { join as join3 } from "path";
|
|
139
153
|
function resolvePaths(dataDir) {
|
|
140
|
-
const configDir =
|
|
141
|
-
const registrationsDir =
|
|
154
|
+
const configDir = join3(dataDir, "config");
|
|
155
|
+
const registrationsDir = join3(configDir, "registrations");
|
|
142
156
|
return {
|
|
143
157
|
dataDir,
|
|
144
|
-
dbDir:
|
|
145
|
-
mediaDir:
|
|
158
|
+
dbDir: join3(dataDir, "db"),
|
|
159
|
+
mediaDir: join3(dataDir, "media"),
|
|
146
160
|
configDir,
|
|
147
161
|
registrationsDir,
|
|
148
|
-
tuwunelTomlPath:
|
|
149
|
-
appserviceYamlPath:
|
|
150
|
-
envPath:
|
|
162
|
+
tuwunelTomlPath: join3(configDir, "tuwunel.toml"),
|
|
163
|
+
appserviceYamlPath: join3(registrationsDir, "zooid.yaml"),
|
|
164
|
+
envPath: join3(configDir, ".env")
|
|
151
165
|
};
|
|
152
166
|
}
|
|
153
167
|
|
|
@@ -187,10 +201,10 @@ MATRIX_HS_TOKEN=${tokens.hsToken}
|
|
|
187
201
|
}
|
|
188
202
|
|
|
189
203
|
// src/daemon/start-daemon.ts
|
|
190
|
-
import { readFileSync as
|
|
204
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
191
205
|
import { mkdir, unlink } from "fs/promises";
|
|
192
206
|
import { tmpdir } from "os";
|
|
193
|
-
import { dirname as dirname2, isAbsolute, join as
|
|
207
|
+
import { dirname as dirname2, isAbsolute, join as join5, resolve } from "path";
|
|
194
208
|
import { serve } from "@hono/node-server";
|
|
195
209
|
|
|
196
210
|
// ../transport-http/src/server.ts
|
|
@@ -465,6 +479,31 @@ var defaultExec = async (cmd, args) => {
|
|
|
465
479
|
}
|
|
466
480
|
};
|
|
467
481
|
|
|
482
|
+
// src/daemon/sync-cursors.ts
|
|
483
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
484
|
+
import { join as join4 } from "path";
|
|
485
|
+
function makeSyncCursorStore(agentsDir) {
|
|
486
|
+
const fileFor = (agentName) => join4(agentsDir, agentName, "sync-since");
|
|
487
|
+
return {
|
|
488
|
+
loadSince(agentName) {
|
|
489
|
+
try {
|
|
490
|
+
return readFileSync2(fileFor(agentName), "utf8").trim() || null;
|
|
491
|
+
} catch {
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
saveSince(agentName, since) {
|
|
496
|
+
mkdirSync3(join4(agentsDir, agentName), { recursive: true });
|
|
497
|
+
writeFileSync3(fileFor(agentName), since, "utf8");
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// src/daemon/pull-wiring.ts
|
|
503
|
+
function shouldBindHttpListener(mode) {
|
|
504
|
+
return mode !== "client";
|
|
505
|
+
}
|
|
506
|
+
|
|
468
507
|
// src/daemon/start-daemon.ts
|
|
469
508
|
function listenAsync(server) {
|
|
470
509
|
return new Promise((resolve7) => {
|
|
@@ -486,10 +525,10 @@ async function startDaemon(opts = {}) {
|
|
|
486
525
|
const found = opts.configPath ? { path: opts.configPath } : findConfigFile(cwd);
|
|
487
526
|
if (!found) throw new Error("zooid.yaml is required");
|
|
488
527
|
const configDir = dirname2(found.path);
|
|
489
|
-
const base = loadZooidConfig(
|
|
528
|
+
const base = loadZooidConfig(readFileSync3(found.path, "utf8"), { configDir });
|
|
490
529
|
const config = mergeCliFlags(base, opts.cliFlags ?? {});
|
|
491
530
|
const approvals = new ApprovalCorrelator();
|
|
492
|
-
const daemonSockPath = opts.agentsDir ?
|
|
531
|
+
const daemonSockPath = opts.agentsDir ? join5(opts.agentsDir, "..", "run", "context.sock") : join5(tmpdir(), `zooid-context-${process.pid}.sock`);
|
|
493
532
|
await mkdir(dirname2(daemonSockPath), { recursive: true }).catch(() => {
|
|
494
533
|
});
|
|
495
534
|
const contextSpawnRegistry = new SpawnRegistry();
|
|
@@ -527,6 +566,7 @@ async function startDaemon(opts = {}) {
|
|
|
527
566
|
`[context] socket=${daemonSockPath} status=${contextSocket ? "listening" : "disabled"} agents={${agentNames.map((n) => `${n}:${registry.hasContextSpawn(n) ? "yes" : "no"}`).join(", ")}}`
|
|
528
567
|
);
|
|
529
568
|
let server = null;
|
|
569
|
+
let syncLoops;
|
|
530
570
|
let stopped = false;
|
|
531
571
|
let resolveStopped;
|
|
532
572
|
const whenStopped = new Promise((r) => {
|
|
@@ -535,9 +575,15 @@ async function startDaemon(opts = {}) {
|
|
|
535
575
|
const matrix = findMatrixTransport(config);
|
|
536
576
|
let port;
|
|
537
577
|
if (matrix) {
|
|
578
|
+
const mode = matrix.transport.mode ?? "appservice";
|
|
579
|
+
if (mode === "client" && !opts.agentsDir) {
|
|
580
|
+
throw new Error("matrix client (pull) mode requires a data dir for since-cursor persistence");
|
|
581
|
+
}
|
|
582
|
+
const cursors = opts.agentsDir ? makeSyncCursorStore(opts.agentsDir) : void 0;
|
|
538
583
|
const client = new MatrixClient({
|
|
539
584
|
homeserver: matrix.transport.homeserver,
|
|
540
|
-
asToken: matrix.transport.as_token
|
|
585
|
+
asToken: matrix.transport.as_token,
|
|
586
|
+
...opts.fetch ? { fetch: opts.fetch } : {}
|
|
541
587
|
});
|
|
542
588
|
const mediaClient = new MediaClient({
|
|
543
589
|
homeserver: matrix.transport.homeserver,
|
|
@@ -561,6 +607,7 @@ async function startDaemon(opts = {}) {
|
|
|
561
607
|
}
|
|
562
608
|
const serverName = matrix.transport.user_namespace.split(":").slice(1).join(":").replace(/\\?\)?$/, "") || new URL(matrix.transport.homeserver).hostname;
|
|
563
609
|
const asUserId = `@${matrix.transport.sender_localpart}:${serverName}`;
|
|
610
|
+
const nameByUserId = new Map(bindings.map((b) => [b.userId, b.name]));
|
|
564
611
|
const transport = createMatrixTransport({
|
|
565
612
|
agents: registry,
|
|
566
613
|
approvals,
|
|
@@ -569,11 +616,24 @@ async function startDaemon(opts = {}) {
|
|
|
569
616
|
hsToken: matrix.transport.hs_token,
|
|
570
617
|
adminUserId: opts.adminUserId,
|
|
571
618
|
botUserId: asUserId,
|
|
572
|
-
media: mediaClient
|
|
619
|
+
media: mediaClient,
|
|
620
|
+
mode,
|
|
621
|
+
loadSince: (uid) => {
|
|
622
|
+
const name = nameByUserId.get(uid);
|
|
623
|
+
return name && cursors ? cursors.loadSince(name) : null;
|
|
624
|
+
},
|
|
625
|
+
saveSince: (uid, since) => {
|
|
626
|
+
const name = nameByUserId.get(uid);
|
|
627
|
+
if (name && cursors) cursors.saveSince(name, since);
|
|
628
|
+
}
|
|
573
629
|
});
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
630
|
+
if (shouldBindHttpListener(mode)) {
|
|
631
|
+
const requestedPort = matrix.transport.port ?? 9e3;
|
|
632
|
+
server = serve({ fetch: transport.app.fetch, port: requestedPort, hostname: "0.0.0.0" });
|
|
633
|
+
port = await listenAsync(server);
|
|
634
|
+
} else {
|
|
635
|
+
port = 0;
|
|
636
|
+
}
|
|
577
637
|
const spaceLocalpart = matrix.transport.space ?? "dev";
|
|
578
638
|
const adminUserIds = opts.adminUserId ? [opts.adminUserId] : [];
|
|
579
639
|
let spaceRoomId;
|
|
@@ -592,6 +652,11 @@ async function startDaemon(opts = {}) {
|
|
|
592
652
|
console.warn("[matrix] workforce space provisioning failed:", err);
|
|
593
653
|
}
|
|
594
654
|
await transport.bootstrap({ spaceRoomId, asUserId, adminUserIds });
|
|
655
|
+
syncLoops = transport.syncLoops;
|
|
656
|
+
if (syncLoops?.length) {
|
|
657
|
+
for (const loop of syncLoops) void loop.run();
|
|
658
|
+
console.log(`[matrix] pull mode: ${syncLoops.length} sync loop(s) running`);
|
|
659
|
+
}
|
|
595
660
|
if (spaceRoomId) {
|
|
596
661
|
try {
|
|
597
662
|
const generalRoomId = await ensureDefaultChannel({
|
|
@@ -630,6 +695,10 @@ async function startDaemon(opts = {}) {
|
|
|
630
695
|
const stop = async () => {
|
|
631
696
|
if (stopped) return whenStopped;
|
|
632
697
|
stopped = true;
|
|
698
|
+
try {
|
|
699
|
+
if (syncLoops) for (const loop of syncLoops) loop.stop();
|
|
700
|
+
} catch {
|
|
701
|
+
}
|
|
633
702
|
try {
|
|
634
703
|
if (server) await closeAsync(server);
|
|
635
704
|
} catch {
|
|
@@ -690,6 +759,7 @@ var TuwunelService = class {
|
|
|
690
759
|
constructor(opts) {
|
|
691
760
|
this.opts = opts;
|
|
692
761
|
}
|
|
762
|
+
opts;
|
|
693
763
|
child = null;
|
|
694
764
|
start() {
|
|
695
765
|
this.child = spawn(this.opts.engine, buildRunArgs(this.opts), {
|
|
@@ -773,18 +843,18 @@ function execEngine(engine, args) {
|
|
|
773
843
|
|
|
774
844
|
// src/web/resolve.ts
|
|
775
845
|
import { existsSync as existsSync3 } from "fs";
|
|
776
|
-
import { dirname as dirname3, join as
|
|
846
|
+
import { dirname as dirname3, join as join7, resolve as resolve2 } from "path";
|
|
777
847
|
|
|
778
848
|
// src/web/fetch.ts
|
|
779
|
-
import { mkdirSync as
|
|
780
|
-
import { join as
|
|
849
|
+
import { mkdirSync as mkdirSync4, renameSync, rmSync, existsSync as existsSync2, readdirSync, writeFileSync as writeFileSync4 } from "fs";
|
|
850
|
+
import { join as join6 } from "path";
|
|
781
851
|
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
782
852
|
import * as tar from "tar";
|
|
783
853
|
var PKG = "@zooid/web";
|
|
784
854
|
var DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
785
855
|
async function fetchWebBundle(opts) {
|
|
786
|
-
const target =
|
|
787
|
-
if (existsSync2(
|
|
856
|
+
const target = join6(opts.cacheDir, opts.version);
|
|
857
|
+
if (existsSync2(join6(target, "index.html"))) return target;
|
|
788
858
|
if (existsSync2(target)) rmSync(target, { recursive: true, force: true });
|
|
789
859
|
const f = opts.fetch ?? globalThis.fetch;
|
|
790
860
|
const registry = (opts.registryUrl ?? DEFAULT_REGISTRY).replace(/\/$/, "");
|
|
@@ -812,11 +882,11 @@ async function fetchWebBundle(opts) {
|
|
|
812
882
|
Cause: ${err instanceof Error ? err.message : String(err)}`
|
|
813
883
|
);
|
|
814
884
|
}
|
|
815
|
-
const tmp =
|
|
816
|
-
|
|
817
|
-
const tgzPath =
|
|
885
|
+
const tmp = join6(opts.cacheDir, `.tmp-${randomUUID2().slice(0, 8)}`);
|
|
886
|
+
mkdirSync4(tmp, { recursive: true });
|
|
887
|
+
const tgzPath = join6(tmp, ".bundle.tgz");
|
|
818
888
|
try {
|
|
819
|
-
|
|
889
|
+
writeFileSync4(tgzPath, tgz);
|
|
820
890
|
await tar.extract({
|
|
821
891
|
file: tgzPath,
|
|
822
892
|
cwd: tmp,
|
|
@@ -825,7 +895,7 @@ async function fetchWebBundle(opts) {
|
|
|
825
895
|
filter: (p) => p === "package/dist" || p.startsWith("package/dist/")
|
|
826
896
|
});
|
|
827
897
|
rmSync(tgzPath);
|
|
828
|
-
if (!existsSync2(
|
|
898
|
+
if (!existsSync2(join6(tmp, "index.html"))) {
|
|
829
899
|
throw new Error(`${PKG}@${opts.version} tarball has no dist/index.html`);
|
|
830
900
|
}
|
|
831
901
|
renameSync(tmp, target);
|
|
@@ -835,7 +905,7 @@ async function fetchWebBundle(opts) {
|
|
|
835
905
|
}
|
|
836
906
|
for (const entry of readdirSync(opts.cacheDir)) {
|
|
837
907
|
if (entry !== opts.version) {
|
|
838
|
-
rmSync(
|
|
908
|
+
rmSync(join6(opts.cacheDir, entry), { recursive: true, force: true });
|
|
839
909
|
}
|
|
840
910
|
}
|
|
841
911
|
return target;
|
|
@@ -845,10 +915,10 @@ async function fetchWebBundle(opts) {
|
|
|
845
915
|
var ENV_OVERRIDE = "ZOOID_DEV_WEB_ROOT_OVERRIDE";
|
|
846
916
|
async function ensureWebRoot(opts) {
|
|
847
917
|
const override = process.env[ENV_OVERRIDE];
|
|
848
|
-
if (override && existsSync3(
|
|
918
|
+
if (override && existsSync3(join7(override, "index.html"))) return resolve2(override);
|
|
849
919
|
const fromSource = webSourcePackage(opts.cliRoot);
|
|
850
|
-
if (fromSource && existsSync3(
|
|
851
|
-
return
|
|
920
|
+
if (fromSource && existsSync3(join7(fromSource, "dist", "index.html"))) {
|
|
921
|
+
return join7(fromSource, "dist");
|
|
852
922
|
}
|
|
853
923
|
if (!opts.version) {
|
|
854
924
|
throw new Error(
|
|
@@ -862,16 +932,16 @@ Set ${ENV_OVERRIDE} to a built dist, or run from the monorepo.`
|
|
|
862
932
|
}
|
|
863
933
|
function webSourcePackage(cliRoot) {
|
|
864
934
|
const workspaceRoot = dirname3(dirname3(dirname3(cliRoot)));
|
|
865
|
-
const candidate =
|
|
866
|
-
return existsSync3(
|
|
935
|
+
const candidate = join7(workspaceRoot, "zooid-clients", "packages", "web");
|
|
936
|
+
return existsSync3(join7(candidate, "package.json")) ? candidate : null;
|
|
867
937
|
}
|
|
868
938
|
|
|
869
939
|
// src/web/pin.ts
|
|
870
|
-
import { readFileSync as
|
|
871
|
-
import { join as
|
|
940
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
941
|
+
import { join as join8 } from "path";
|
|
872
942
|
function readZoonWebPin(cliRoot) {
|
|
873
943
|
try {
|
|
874
|
-
const pkg = JSON.parse(
|
|
944
|
+
const pkg = JSON.parse(readFileSync4(join8(cliRoot, "package.json"), "utf8"));
|
|
875
945
|
return pkg.zooid?.webVersion;
|
|
876
946
|
} catch {
|
|
877
947
|
return void 0;
|
|
@@ -879,8 +949,8 @@ function readZoonWebPin(cliRoot) {
|
|
|
879
949
|
}
|
|
880
950
|
|
|
881
951
|
// src/web/static.ts
|
|
882
|
-
import { readFileSync as
|
|
883
|
-
import { extname, join as
|
|
952
|
+
import { readFileSync as readFileSync5, statSync } from "fs";
|
|
953
|
+
import { extname, join as join9, normalize } from "path";
|
|
884
954
|
import { Hono as Hono2 } from "hono";
|
|
885
955
|
var MIME = {
|
|
886
956
|
".html": "text/html; charset=utf-8",
|
|
@@ -906,19 +976,19 @@ function webStatic(opts) {
|
|
|
906
976
|
const requested = decodeURIComponent(url.pathname);
|
|
907
977
|
const wantFile = requested === "/" ? "/index.html" : requested;
|
|
908
978
|
const safe = normalize(wantFile).replace(/^(\.\.[/\\])+/g, "");
|
|
909
|
-
const filePath =
|
|
979
|
+
const filePath = join9(opts.webRoot, safe);
|
|
910
980
|
if (!filePath.startsWith(opts.webRoot)) return c.notFound();
|
|
911
981
|
try {
|
|
912
982
|
const stat = statSync(filePath);
|
|
913
983
|
if (stat.isFile()) {
|
|
914
|
-
const body =
|
|
984
|
+
const body = readFileSync5(filePath);
|
|
915
985
|
const ct = MIME[extname(filePath)] ?? "application/octet-stream";
|
|
916
986
|
return c.body(body, 200, { "content-type": ct });
|
|
917
987
|
}
|
|
918
988
|
} catch {
|
|
919
989
|
}
|
|
920
990
|
if (isAssetPath(requested)) return c.notFound();
|
|
921
|
-
const indexBytes =
|
|
991
|
+
const indexBytes = readFileSync5(join9(opts.webRoot, "index.html"));
|
|
922
992
|
return c.body(indexBytes, 200, {
|
|
923
993
|
"content-type": MIME[".html"]
|
|
924
994
|
});
|
|
@@ -929,10 +999,10 @@ function webStatic(opts) {
|
|
|
929
999
|
// src/web/watch.ts
|
|
930
1000
|
import { spawn as spawn2 } from "child_process";
|
|
931
1001
|
import { existsSync as existsSync4, statSync as statSync2 } from "fs";
|
|
932
|
-
import { join as
|
|
1002
|
+
import { join as join10 } from "path";
|
|
933
1003
|
async function startWebWatch(opts) {
|
|
934
|
-
const distPath =
|
|
935
|
-
const indexPath =
|
|
1004
|
+
const distPath = join10(opts.webPackageDir, "dist");
|
|
1005
|
+
const indexPath = join10(distPath, "index.html");
|
|
936
1006
|
const timeoutMs = opts.firstBuildTimeoutMs ?? 6e4;
|
|
937
1007
|
const spawnTime = Date.now();
|
|
938
1008
|
const child = spawn2(
|
|
@@ -1006,7 +1076,7 @@ async function stopChild(child) {
|
|
|
1006
1076
|
|
|
1007
1077
|
// src/observability/paths.ts
|
|
1008
1078
|
import { mkdir as mkdir2, readdir, rm, symlink, unlink as unlink2 } from "fs/promises";
|
|
1009
|
-
import { join as
|
|
1079
|
+
import { join as join11 } from "path";
|
|
1010
1080
|
function localDateSlug(d) {
|
|
1011
1081
|
const y = d.getFullYear();
|
|
1012
1082
|
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
@@ -1016,19 +1086,19 @@ function localDateSlug(d) {
|
|
|
1016
1086
|
var DAY_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
1017
1087
|
function resolveLogPaths({ dataDir, now }) {
|
|
1018
1088
|
const slug = localDateSlug(now ?? /* @__PURE__ */ new Date());
|
|
1019
|
-
const logsDir =
|
|
1020
|
-
const dayDir =
|
|
1089
|
+
const logsDir = join11(dataDir, "logs");
|
|
1090
|
+
const dayDir = join11(logsDir, slug);
|
|
1021
1091
|
return {
|
|
1022
1092
|
dataDir,
|
|
1023
1093
|
logsDir,
|
|
1024
1094
|
dayDir,
|
|
1025
1095
|
daySlug: slug,
|
|
1026
|
-
todayLink:
|
|
1027
|
-
tuwunelLog:
|
|
1028
|
-
daemonLog:
|
|
1029
|
-
devLog:
|
|
1030
|
-
agentLog: (n) =>
|
|
1031
|
-
agentTap: (n) =>
|
|
1096
|
+
todayLink: join11(logsDir, "today"),
|
|
1097
|
+
tuwunelLog: join11(dayDir, "tuwunel.log"),
|
|
1098
|
+
daemonLog: join11(dayDir, "daemon.log"),
|
|
1099
|
+
devLog: join11(dayDir, "dev.log"),
|
|
1100
|
+
agentLog: (n) => join11(dayDir, `agent-${n}.log`),
|
|
1101
|
+
agentTap: (n) => join11(dayDir, `agent-${n}.acp.jsonl`)
|
|
1032
1102
|
};
|
|
1033
1103
|
}
|
|
1034
1104
|
async function ensureDayFolder(p) {
|
|
@@ -1041,7 +1111,7 @@ async function ensureDayFolder(p) {
|
|
|
1041
1111
|
}
|
|
1042
1112
|
async function pruneOldDays(opts) {
|
|
1043
1113
|
if (opts.retainDays <= 0) return [];
|
|
1044
|
-
const logsDir =
|
|
1114
|
+
const logsDir = join11(opts.dataDir, "logs");
|
|
1045
1115
|
let entries;
|
|
1046
1116
|
try {
|
|
1047
1117
|
entries = await readdir(logsDir);
|
|
@@ -1060,7 +1130,7 @@ async function pruneOldDays(opts) {
|
|
|
1060
1130
|
const [y, m, d] = name.split("-").map(Number);
|
|
1061
1131
|
const t = new Date(y, m - 1, d).getTime();
|
|
1062
1132
|
if (t < cutoff) {
|
|
1063
|
-
await rm(
|
|
1133
|
+
await rm(join11(logsDir, name), { recursive: true, force: true });
|
|
1064
1134
|
removed.push(name);
|
|
1065
1135
|
}
|
|
1066
1136
|
}
|
|
@@ -1081,6 +1151,7 @@ var JsonlSink = class {
|
|
|
1081
1151
|
this.stream = createWriteStream(this.path, { flags: "a" });
|
|
1082
1152
|
})();
|
|
1083
1153
|
}
|
|
1154
|
+
path;
|
|
1084
1155
|
stream = null;
|
|
1085
1156
|
maxStringLen;
|
|
1086
1157
|
readyPromise;
|
|
@@ -1209,7 +1280,7 @@ var CLI_ROOT = (() => {
|
|
|
1209
1280
|
const pkgPath = resolve3(dir, "package.json");
|
|
1210
1281
|
if (existsSync5(pkgPath)) {
|
|
1211
1282
|
try {
|
|
1212
|
-
const pkg = JSON.parse(
|
|
1283
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
1213
1284
|
if (pkg.name === "zooid" || pkg.name === "@zooid/cli") return dir;
|
|
1214
1285
|
} catch {
|
|
1215
1286
|
}
|
|
@@ -1232,7 +1303,7 @@ async function runDev(flags) {
|
|
|
1232
1303
|
const tokens = ensureTokens(paths.envPath);
|
|
1233
1304
|
process.env.MATRIX_AS_TOKEN = tokens.asToken;
|
|
1234
1305
|
process.env.MATRIX_HS_TOKEN = tokens.hsToken;
|
|
1235
|
-
const rawYaml =
|
|
1306
|
+
const rawYaml = readFileSync6(found.path, "utf8");
|
|
1236
1307
|
const preview = loadZooidConfig(rawYaml, { configDir: dirname6(found.path) });
|
|
1237
1308
|
const matrix = findMatrixTransport(preview);
|
|
1238
1309
|
if (!matrix) {
|
|
@@ -1345,7 +1416,7 @@ Pass an explicit path: --watch-web=/path/to/zooid-clients/packages/web`
|
|
|
1345
1416
|
task: async (_, t) => {
|
|
1346
1417
|
const webRoot = ctx.webWatch?.distPath ?? await ensureWebRoot({
|
|
1347
1418
|
cliRoot: CLI_ROOT,
|
|
1348
|
-
cacheDir:
|
|
1419
|
+
cacheDir: join12(layout.dataRoot, "web"),
|
|
1349
1420
|
version: readZoonWebPin(CLI_ROOT),
|
|
1350
1421
|
onProgress: (msg) => {
|
|
1351
1422
|
t.output = msg;
|
|
@@ -1396,7 +1467,7 @@ Pass an explicit path: --watch-web=/path/to/zooid-clients/packages/web`
|
|
|
1396
1467
|
[
|
|
1397
1468
|
"",
|
|
1398
1469
|
chalk.bold("Tuwunel is up.") + ` ${homeserver}`,
|
|
1399
|
-
chalk.bold("UI: ") + ` http://localhost:${flags.uiPort}`,
|
|
1470
|
+
chalk.bold("UI: ") + ` http://localhost:${ctx.uiServer?.address()?.port ?? flags.uiPort}`,
|
|
1400
1471
|
` ${chalk.cyan("admin user:")} ${flags.adminUser} / ${flags.adminPassword}`,
|
|
1401
1472
|
` ${chalk.cyan("data dir:")} ${layout.dataRoot}`,
|
|
1402
1473
|
...ctx.webWatch ? [` ${chalk.cyan("web watcher:")} live (vite build --watch on @zooid/web)`] : [],
|
|
@@ -1423,15 +1494,21 @@ function loadEnvFiles(cwd) {
|
|
|
1423
1494
|
}
|
|
1424
1495
|
|
|
1425
1496
|
// src/commands/init.ts
|
|
1426
|
-
import { existsSync as existsSync7, mkdirSync as
|
|
1427
|
-
import { dirname as dirname7, join as
|
|
1497
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readdirSync as readdirSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
1498
|
+
import { dirname as dirname7, join as join14, resolve as resolve4 } from "path";
|
|
1428
1499
|
|
|
1429
1500
|
// src/commands/init/generators.ts
|
|
1430
1501
|
function generateZooidYaml(opts) {
|
|
1431
|
-
const acpBlock = opts.preset === "opencode" ? ` acp: { preset:
|
|
1502
|
+
const acpBlock = opts.preset === "opencode" || !opts.model ? ` acp: { preset: ${opts.preset} }` : ` acp:
|
|
1432
1503
|
preset: ${opts.preset}
|
|
1433
1504
|
model: ${opts.model}`;
|
|
1434
1505
|
return `runtime: local
|
|
1506
|
+
workstation: dev
|
|
1507
|
+
|
|
1508
|
+
# This daemon connects in push mode: the homeserver reaches the listener on
|
|
1509
|
+
# \`port\` below. To run it against a homeserver it can't open a port to \u2014
|
|
1510
|
+
# e.g. on your laptop, behind NAT \u2014 switch to pull mode: set \`mode: client\`
|
|
1511
|
+
# under transports.matrix and drop \`port\`. Agents are reachable either way.
|
|
1435
1512
|
|
|
1436
1513
|
transports:
|
|
1437
1514
|
matrix:
|
|
@@ -1488,23 +1565,20 @@ function generateOpencodeJson(opts) {
|
|
|
1488
1565
|
return JSON.stringify(
|
|
1489
1566
|
{
|
|
1490
1567
|
$schema: "https://opencode.ai/config.json",
|
|
1491
|
-
model: "TODO/your-model",
|
|
1492
1568
|
provider: { TODO: { options: {} } }
|
|
1493
1569
|
},
|
|
1494
1570
|
null,
|
|
1495
1571
|
2
|
|
1496
1572
|
) + "\n";
|
|
1497
1573
|
}
|
|
1498
|
-
const cfg = {
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
provider: {
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
}
|
|
1505
|
-
},
|
|
1506
|
-
permission: { webfetch: "allow" }
|
|
1574
|
+
const cfg = { $schema: "https://opencode.ai/config.json" };
|
|
1575
|
+
if (opts.model) cfg.model = `${opts.provider}/${opts.model}`;
|
|
1576
|
+
cfg.provider = {
|
|
1577
|
+
[opts.provider]: {
|
|
1578
|
+
options: { apiKey: `{env:${opts.apiKeyEnvVar}}` }
|
|
1579
|
+
}
|
|
1507
1580
|
};
|
|
1581
|
+
cfg.permission = { webfetch: "allow" };
|
|
1508
1582
|
return JSON.stringify(cfg, null, 2) + "\n";
|
|
1509
1583
|
}
|
|
1510
1584
|
function generateEnv(opts) {
|
|
@@ -1524,7 +1598,6 @@ provider-specific options and per-tool permissions.
|
|
|
1524
1598
|
var SIMPLE_PRESETS = [
|
|
1525
1599
|
{
|
|
1526
1600
|
preset: "claude",
|
|
1527
|
-
models: ["claude-sonnet-4-6", "claude-opus-4-7", "claude-haiku-4-5"],
|
|
1528
1601
|
subscriptionLabel: "Claude subscription (Pro / Max / Team / Enterprise)",
|
|
1529
1602
|
apiKeyEnvVar: "ANTHROPIC_API_KEY",
|
|
1530
1603
|
apiKeyPromptLabel: "Anthropic",
|
|
@@ -1532,7 +1605,6 @@ var SIMPLE_PRESETS = [
|
|
|
1532
1605
|
},
|
|
1533
1606
|
{
|
|
1534
1607
|
preset: "codex",
|
|
1535
|
-
models: ["gpt-5.5"],
|
|
1536
1608
|
subscriptionLabel: "ChatGPT subscription (Plus / Pro / Team)",
|
|
1537
1609
|
apiKeyEnvVar: "OPENAI_API_KEY",
|
|
1538
1610
|
apiKeyPromptLabel: "OpenAI",
|
|
@@ -1544,28 +1616,24 @@ var OPENCODE_PROVIDERS = [
|
|
|
1544
1616
|
id: "opencode-go",
|
|
1545
1617
|
label: "opencode-go",
|
|
1546
1618
|
description: "$10/mo subscription \u2014 Kimi, GLM, MiniMax",
|
|
1547
|
-
models: ["kimi-k2.6", "kimi-k2.5", "glm-5.1", "glm-5", "minimax-m2.7"],
|
|
1548
1619
|
apiKeyEnvVar: "OPENCODE_API_KEY"
|
|
1549
1620
|
},
|
|
1550
1621
|
{
|
|
1551
1622
|
id: "opencode",
|
|
1552
1623
|
label: "opencode (Zen)",
|
|
1553
1624
|
description: "free tier \u2014 curated models",
|
|
1554
|
-
models: ["kimi-k2.5-free", "glm-4.7-free", "minimax-m2.1-free"],
|
|
1555
1625
|
apiKeyEnvVar: "OPENCODE_API_KEY"
|
|
1556
1626
|
},
|
|
1557
1627
|
{
|
|
1558
1628
|
id: "anthropic",
|
|
1559
1629
|
label: "Anthropic",
|
|
1560
1630
|
description: "Claude via direct Anthropic API",
|
|
1561
|
-
models: ["claude-sonnet-4-6", "claude-opus-4-7", "claude-haiku-4-5"],
|
|
1562
1631
|
apiKeyEnvVar: "ANTHROPIC_API_KEY"
|
|
1563
1632
|
},
|
|
1564
1633
|
{
|
|
1565
1634
|
id: "openrouter",
|
|
1566
1635
|
label: "OpenRouter",
|
|
1567
1636
|
description: "many providers via OpenRouter",
|
|
1568
|
-
models: ["anthropic/claude-sonnet-4-6", "zhipuai/glm-5", "moonshot/kimi-k2.6"],
|
|
1569
1637
|
apiKeyEnvVar: "OPENROUTER_API_KEY"
|
|
1570
1638
|
}
|
|
1571
1639
|
];
|
|
@@ -1579,11 +1647,11 @@ function findOpencodeProvider(id) {
|
|
|
1579
1647
|
// src/commands/init/sniff.ts
|
|
1580
1648
|
import { existsSync as existsSync6 } from "fs";
|
|
1581
1649
|
import { homedir } from "os";
|
|
1582
|
-
import { join as
|
|
1650
|
+
import { join as join13 } from "path";
|
|
1583
1651
|
function sniffCredentials(preset, home = homedir()) {
|
|
1584
1652
|
const meta = findSimplePreset(preset);
|
|
1585
1653
|
if (!meta) return { found: false };
|
|
1586
|
-
const full =
|
|
1654
|
+
const full = join13(home, meta.credentialDir);
|
|
1587
1655
|
if (existsSync6(full)) return { found: true, path: full };
|
|
1588
1656
|
return { found: false };
|
|
1589
1657
|
}
|
|
@@ -1602,7 +1670,7 @@ var IGNORED_PREEXISTING = /* @__PURE__ */ new Set([
|
|
|
1602
1670
|
]);
|
|
1603
1671
|
async function runInit(opts) {
|
|
1604
1672
|
const dir = resolve4(opts.dir);
|
|
1605
|
-
|
|
1673
|
+
mkdirSync5(dir, { recursive: true });
|
|
1606
1674
|
if (!opts.force) {
|
|
1607
1675
|
const blocking = readdirSync2(dir).filter((n) => !IGNORED_PREEXISTING.has(n));
|
|
1608
1676
|
if (blocking.length > 0) {
|
|
@@ -1613,7 +1681,6 @@ async function runInit(opts) {
|
|
|
1613
1681
|
}
|
|
1614
1682
|
const writes = [];
|
|
1615
1683
|
if (opts.preset === "claude" || opts.preset === "codex") {
|
|
1616
|
-
if (!opts.model) throw new Error(`--model is required for preset ${opts.preset}`);
|
|
1617
1684
|
if (!opts.auth) throw new Error("--auth (subscription|api-key) is required for claude/codex");
|
|
1618
1685
|
const meta = findSimplePreset(opts.preset);
|
|
1619
1686
|
writes.push({
|
|
@@ -1643,7 +1710,6 @@ async function runInit(opts) {
|
|
|
1643
1710
|
const isCustom = opts.provider === "custom";
|
|
1644
1711
|
const providerMeta = isCustom ? void 0 : findOpencodeProvider(opts.provider);
|
|
1645
1712
|
if (!isCustom && !providerMeta) throw new Error(`unknown opencode provider: ${opts.provider}`);
|
|
1646
|
-
if (!isCustom && !opts.model) throw new Error("--model is required (paired with --provider)");
|
|
1647
1713
|
if (!isCustom && !opts.apiKey) throw new Error("--api-key is required for opencode");
|
|
1648
1714
|
writes.push({ path: "zooid.yaml", content: generateZooidYaml({ preset: "opencode" }) });
|
|
1649
1715
|
writes.push({ path: "agents/zooid-assistant/AGENTS.md", content: generateAgentsMd() });
|
|
@@ -1671,14 +1737,14 @@ async function runInit(opts) {
|
|
|
1671
1737
|
}
|
|
1672
1738
|
writes.push({ path: ".gitignore", content: generateGitignore() });
|
|
1673
1739
|
for (const w of writes) {
|
|
1674
|
-
const full =
|
|
1740
|
+
const full = join14(dir, w.path);
|
|
1675
1741
|
const exists = existsSync7(full);
|
|
1676
1742
|
if (exists && !opts.overwrite) {
|
|
1677
1743
|
console.warn(`\u26A0 ${w.path} exists; left as-is (use --force --overwrite to replace)`);
|
|
1678
1744
|
continue;
|
|
1679
1745
|
}
|
|
1680
|
-
|
|
1681
|
-
|
|
1746
|
+
mkdirSync5(dirname7(full), { recursive: true });
|
|
1747
|
+
writeFileSync5(full, w.content);
|
|
1682
1748
|
console.log(`\u2713 Created ${w.path}`);
|
|
1683
1749
|
}
|
|
1684
1750
|
if ((opts.preset === "claude" || opts.preset === "codex") && opts.auth === "subscription") {
|
|
@@ -1714,19 +1780,7 @@ async function resolveOptions(flags) {
|
|
|
1714
1780
|
() => "--preset is required (claude | codex | opencode)"
|
|
1715
1781
|
);
|
|
1716
1782
|
if (preset === "opencode") {
|
|
1717
|
-
const provider = flags.provider ??
|
|
1718
|
-
() => select({
|
|
1719
|
-
message: "Which provider?",
|
|
1720
|
-
choices: [
|
|
1721
|
-
...OPENCODE_PROVIDERS.map((p) => ({
|
|
1722
|
-
name: `${p.label} (${p.description})`,
|
|
1723
|
-
value: p.id
|
|
1724
|
-
})),
|
|
1725
|
-
{ name: "custom (write your own opencode.json block)", value: "custom" }
|
|
1726
|
-
]
|
|
1727
|
-
}),
|
|
1728
|
-
() => "--provider is required for opencode"
|
|
1729
|
-
);
|
|
1783
|
+
const provider = flags.provider ?? "opencode-go";
|
|
1730
1784
|
if (provider === "custom") {
|
|
1731
1785
|
return {
|
|
1732
1786
|
dir: flags.dir,
|
|
@@ -1738,13 +1792,6 @@ async function resolveOptions(flags) {
|
|
|
1738
1792
|
}
|
|
1739
1793
|
const meta2 = findOpencodeProvider(provider);
|
|
1740
1794
|
if (!meta2) throw new Error(`unknown opencode provider: ${provider}`);
|
|
1741
|
-
const model2 = flags.model ?? await ask(
|
|
1742
|
-
() => select({
|
|
1743
|
-
message: "Which model?",
|
|
1744
|
-
choices: meta2.models.map((m) => ({ name: m, value: m }))
|
|
1745
|
-
}),
|
|
1746
|
-
() => "--model is required"
|
|
1747
|
-
);
|
|
1748
1795
|
const apiKey2 = flags.apiKey ?? await ask(
|
|
1749
1796
|
() => password({ message: `${meta2.label} API key:` }),
|
|
1750
1797
|
() => "--api-key is required"
|
|
@@ -1753,7 +1800,7 @@ async function resolveOptions(flags) {
|
|
|
1753
1800
|
dir: flags.dir,
|
|
1754
1801
|
preset,
|
|
1755
1802
|
provider,
|
|
1756
|
-
model:
|
|
1803
|
+
model: flags.model,
|
|
1757
1804
|
apiKey: apiKey2,
|
|
1758
1805
|
force: flags.force,
|
|
1759
1806
|
overwrite: flags.overwrite
|
|
@@ -1771,13 +1818,6 @@ async function resolveOptions(flags) {
|
|
|
1771
1818
|
}),
|
|
1772
1819
|
() => "--auth is required (subscription | api-key)"
|
|
1773
1820
|
);
|
|
1774
|
-
const model = flags.model ?? await ask(
|
|
1775
|
-
() => select({
|
|
1776
|
-
message: "Which model?",
|
|
1777
|
-
choices: meta.models.map((m) => ({ name: m, value: m }))
|
|
1778
|
-
}),
|
|
1779
|
-
() => "--model is required"
|
|
1780
|
-
);
|
|
1781
1821
|
const apiKey = auth === "api-key" ? flags.apiKey ?? await ask(
|
|
1782
1822
|
() => password({ message: `${meta.apiKeyPromptLabel} API key:` }),
|
|
1783
1823
|
() => "--api-key is required when --auth=api-key"
|
|
@@ -1786,7 +1826,7 @@ async function resolveOptions(flags) {
|
|
|
1786
1826
|
dir: flags.dir,
|
|
1787
1827
|
preset,
|
|
1788
1828
|
auth,
|
|
1789
|
-
model,
|
|
1829
|
+
model: flags.model,
|
|
1790
1830
|
apiKey,
|
|
1791
1831
|
force: flags.force,
|
|
1792
1832
|
overwrite: flags.overwrite
|
|
@@ -1796,7 +1836,7 @@ async function resolveOptions(flags) {
|
|
|
1796
1836
|
// src/commands/logs.ts
|
|
1797
1837
|
import { readFile, readdir as readdir2, readlink } from "fs/promises";
|
|
1798
1838
|
import { existsSync as existsSync8 } from "fs";
|
|
1799
|
-
import { join as
|
|
1839
|
+
import { join as join15 } from "path";
|
|
1800
1840
|
var KNOWN_SOURCES = ["tuwunel", "daemon", "dev"];
|
|
1801
1841
|
async function runLogs(flags) {
|
|
1802
1842
|
const writer = flags.writer ?? ((s) => process.stdout.write(s));
|
|
@@ -1815,7 +1855,7 @@ async function runLogs(flags) {
|
|
|
1815
1855
|
writer("no logs yet\n");
|
|
1816
1856
|
return;
|
|
1817
1857
|
}
|
|
1818
|
-
const dayDir =
|
|
1858
|
+
const dayDir = join15(flags.dataDir, "logs", day);
|
|
1819
1859
|
if (!existsSync8(dayDir)) {
|
|
1820
1860
|
writer(`no logs for ${day}
|
|
1821
1861
|
`);
|
|
@@ -1844,7 +1884,7 @@ async function runLogs(flags) {
|
|
|
1844
1884
|
writer(await readFile(path, "utf8"));
|
|
1845
1885
|
}
|
|
1846
1886
|
async function resolveTodaySlug(dataDir) {
|
|
1847
|
-
const link =
|
|
1887
|
+
const link = join15(dataDir, "logs", "today");
|
|
1848
1888
|
try {
|
|
1849
1889
|
return await readlink(link);
|
|
1850
1890
|
} catch {
|
|
@@ -1853,18 +1893,18 @@ async function resolveTodaySlug(dataDir) {
|
|
|
1853
1893
|
}
|
|
1854
1894
|
function resolveSourcePath(dayDir, source) {
|
|
1855
1895
|
if (source.startsWith("agent-")) {
|
|
1856
|
-
if (source.endsWith(".acp")) return
|
|
1857
|
-
return
|
|
1896
|
+
if (source.endsWith(".acp")) return join15(dayDir, `${source.slice(0, -4)}.acp.jsonl`);
|
|
1897
|
+
return join15(dayDir, `${source}.log`);
|
|
1858
1898
|
}
|
|
1859
1899
|
if (KNOWN_SOURCES.includes(source))
|
|
1860
|
-
return
|
|
1861
|
-
return
|
|
1900
|
+
return join15(dayDir, `${source}.log`);
|
|
1901
|
+
return join15(dayDir, source);
|
|
1862
1902
|
}
|
|
1863
1903
|
async function dumpByTurn(dayDir, turnId, writer) {
|
|
1864
1904
|
const entries = await readdir2(dayDir);
|
|
1865
1905
|
const taps = entries.filter((e) => e.endsWith(".acp.jsonl")).sort();
|
|
1866
1906
|
for (const f of taps) {
|
|
1867
|
-
const text = await readFile(
|
|
1907
|
+
const text = await readFile(join15(dayDir, f), "utf8");
|
|
1868
1908
|
for (const line of text.split("\n")) {
|
|
1869
1909
|
if (!line) continue;
|
|
1870
1910
|
try {
|
|
@@ -1898,7 +1938,7 @@ async function runStart(flags) {
|
|
|
1898
1938
|
}
|
|
1899
1939
|
|
|
1900
1940
|
// src/commands/status.ts
|
|
1901
|
-
import { readFileSync as
|
|
1941
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
1902
1942
|
import { dirname as dirname8 } from "path";
|
|
1903
1943
|
import chalk2 from "chalk";
|
|
1904
1944
|
async function probe(url, timeoutMs = 2e3) {
|
|
@@ -1923,7 +1963,7 @@ async function collectStatus(opts) {
|
|
|
1923
1963
|
agents: []
|
|
1924
1964
|
};
|
|
1925
1965
|
}
|
|
1926
|
-
const cfg = loadZooidConfig(
|
|
1966
|
+
const cfg = loadZooidConfig(readFileSync7(found.path, "utf8"), {
|
|
1927
1967
|
configDir: dirname8(found.path)
|
|
1928
1968
|
});
|
|
1929
1969
|
const matrixEntry = Object.entries(cfg.transports).find(
|
|
@@ -1954,7 +1994,7 @@ async function runStatus(flags) {
|
|
|
1954
1994
|
if (port === void 0) {
|
|
1955
1995
|
const found = findConfigFile(cwd);
|
|
1956
1996
|
if (found) {
|
|
1957
|
-
const cfg = loadZooidConfig(
|
|
1997
|
+
const cfg = loadZooidConfig(readFileSync7(found.path, "utf8"), {
|
|
1958
1998
|
configDir: dirname8(found.path)
|
|
1959
1999
|
});
|
|
1960
2000
|
const matrix = findMatrixTransport(cfg);
|