zooid 0.8.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/README.md +5 -5
- package/dist/bin.js +195 -156
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-R6EDLH23.js → chunk-PDSJJWQW.js} +1928 -990
- package/dist/chunk-PDSJJWQW.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +10 -10
- package/src/bin.ts +1 -1
- 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 +9 -9
- 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 +62 -15
- package/src/daemon/sync-cursors.test.ts +38 -0
- package/src/daemon/sync-cursors.ts +31 -0
- package/src/web/fetch.integration.test.ts +4 -4
- package/src/web/fetch.test.ts +4 -4
- package/src/web/fetch.ts +2 -2
- package/src/web/pin.test.ts +2 -2
- package/src/web/pin.ts +2 -2
- package/src/web/resolve.test.ts +5 -5
- package/src/web/resolve.ts +5 -5
- package/src/web/test-helpers.ts +1 -1
- package/dist/chunk-R6EDLH23.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,
|
|
@@ -559,6 +605,9 @@ async function startDaemon(opts = {}) {
|
|
|
559
605
|
binding.agentWorkspacePath = isContainerRuntime ? "/workspace" : workspaceDir;
|
|
560
606
|
bindings.push(binding);
|
|
561
607
|
}
|
|
608
|
+
const serverName = matrix.transport.user_namespace.split(":").slice(1).join(":").replace(/\\?\)?$/, "") || new URL(matrix.transport.homeserver).hostname;
|
|
609
|
+
const asUserId = `@${matrix.transport.sender_localpart}:${serverName}`;
|
|
610
|
+
const nameByUserId = new Map(bindings.map((b) => [b.userId, b.name]));
|
|
562
611
|
const transport = createMatrixTransport({
|
|
563
612
|
agents: registry,
|
|
564
613
|
approvals,
|
|
@@ -566,13 +615,25 @@ async function startDaemon(opts = {}) {
|
|
|
566
615
|
bindings,
|
|
567
616
|
hsToken: matrix.transport.hs_token,
|
|
568
617
|
adminUserId: opts.adminUserId,
|
|
569
|
-
|
|
618
|
+
botUserId: asUserId,
|
|
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
|
+
}
|
|
570
629
|
});
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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
|
+
}
|
|
576
637
|
const spaceLocalpart = matrix.transport.space ?? "dev";
|
|
577
638
|
const adminUserIds = opts.adminUserId ? [opts.adminUserId] : [];
|
|
578
639
|
let spaceRoomId;
|
|
@@ -591,6 +652,11 @@ async function startDaemon(opts = {}) {
|
|
|
591
652
|
console.warn("[matrix] workforce space provisioning failed:", err);
|
|
592
653
|
}
|
|
593
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
|
+
}
|
|
594
660
|
if (spaceRoomId) {
|
|
595
661
|
try {
|
|
596
662
|
const generalRoomId = await ensureDefaultChannel({
|
|
@@ -611,7 +677,7 @@ async function startDaemon(opts = {}) {
|
|
|
611
677
|
asUserId,
|
|
612
678
|
getAgents: () => bindings
|
|
613
679
|
});
|
|
614
|
-
console.log(`[matrix] published
|
|
680
|
+
console.log(`[matrix] published dev.zooid.workforce (${bindings.length} agents)`);
|
|
615
681
|
void publisher;
|
|
616
682
|
} catch (err) {
|
|
617
683
|
console.warn("[matrix] workforce roster publication failed:", err);
|
|
@@ -629,6 +695,10 @@ async function startDaemon(opts = {}) {
|
|
|
629
695
|
const stop = async () => {
|
|
630
696
|
if (stopped) return whenStopped;
|
|
631
697
|
stopped = true;
|
|
698
|
+
try {
|
|
699
|
+
if (syncLoops) for (const loop of syncLoops) loop.stop();
|
|
700
|
+
} catch {
|
|
701
|
+
}
|
|
632
702
|
try {
|
|
633
703
|
if (server) await closeAsync(server);
|
|
634
704
|
} catch {
|
|
@@ -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
|
-
var PKG = "@zooid/
|
|
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,42 +915,42 @@ 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(
|
|
855
|
-
`No @zooid/
|
|
925
|
+
`No @zooid/web version pin (zooid.webVersion) in the cli package.json and no monorepo sibling build found.
|
|
856
926
|
Set ${ENV_OVERRIDE} to a built dist, or run from the monorepo.`
|
|
857
927
|
);
|
|
858
928
|
}
|
|
859
|
-
opts.onProgress?.(`Fetching @zooid/
|
|
929
|
+
opts.onProgress?.(`Fetching @zooid/web ${opts.version}\u2026`);
|
|
860
930
|
const fetchBundleFn = opts.fetchBundle ?? fetchWebBundle;
|
|
861
931
|
return fetchBundleFn({ version: opts.version, cacheDir: opts.cacheDir });
|
|
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(
|
|
875
|
-
return pkg.zooid?.
|
|
944
|
+
const pkg = JSON.parse(readFileSync4(join8(cliRoot, "package.json"), "utf8"));
|
|
945
|
+
return pkg.zooid?.webVersion;
|
|
876
946
|
} catch {
|
|
877
947
|
return void 0;
|
|
878
948
|
}
|
|
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
|
}
|
|
@@ -1210,7 +1280,7 @@ var CLI_ROOT = (() => {
|
|
|
1210
1280
|
const pkgPath = resolve3(dir, "package.json");
|
|
1211
1281
|
if (existsSync5(pkgPath)) {
|
|
1212
1282
|
try {
|
|
1213
|
-
const pkg = JSON.parse(
|
|
1283
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
1214
1284
|
if (pkg.name === "zooid" || pkg.name === "@zooid/cli") return dir;
|
|
1215
1285
|
} catch {
|
|
1216
1286
|
}
|
|
@@ -1233,7 +1303,7 @@ async function runDev(flags) {
|
|
|
1233
1303
|
const tokens = ensureTokens(paths.envPath);
|
|
1234
1304
|
process.env.MATRIX_AS_TOKEN = tokens.asToken;
|
|
1235
1305
|
process.env.MATRIX_HS_TOKEN = tokens.hsToken;
|
|
1236
|
-
const rawYaml =
|
|
1306
|
+
const rawYaml = readFileSync6(found.path, "utf8");
|
|
1237
1307
|
const preview = loadZooidConfig(rawYaml, { configDir: dirname6(found.path) });
|
|
1238
1308
|
const matrix = findMatrixTransport(preview);
|
|
1239
1309
|
if (!matrix) {
|
|
@@ -1327,14 +1397,14 @@ async function runDev(flags) {
|
|
|
1327
1397
|
},
|
|
1328
1398
|
...flags.watchWeb ? [
|
|
1329
1399
|
{
|
|
1330
|
-
title: "Start @zooid/
|
|
1400
|
+
title: "Start @zooid/web watcher (vite build --watch)",
|
|
1331
1401
|
task: async () => {
|
|
1332
1402
|
const pkgDir = typeof flags.watchWeb === "string" ? resolve3(flags.watchWeb) : webSourcePackage(CLI_ROOT);
|
|
1333
1403
|
if (!pkgDir) {
|
|
1334
|
-
const defaultPath = dirname6(dirname6(dirname6(CLI_ROOT))) + "/
|
|
1404
|
+
const defaultPath = dirname6(dirname6(dirname6(CLI_ROOT))) + "/zooid-clients/packages/web";
|
|
1335
1405
|
throw new Error(
|
|
1336
|
-
`--watch-web: @zooid/
|
|
1337
|
-
Pass an explicit path: --watch-web=/path/to/
|
|
1406
|
+
`--watch-web: @zooid/web not found at ${defaultPath}.
|
|
1407
|
+
Pass an explicit path: --watch-web=/path/to/zooid-clients/packages/web`
|
|
1338
1408
|
);
|
|
1339
1409
|
}
|
|
1340
1410
|
ctx.webWatch = await startWebWatch({ webPackageDir: pkgDir });
|
|
@@ -1342,11 +1412,11 @@ Pass an explicit path: --watch-web=/path/to/zoon/packages/web`
|
|
|
1342
1412
|
}
|
|
1343
1413
|
] : [],
|
|
1344
1414
|
{
|
|
1345
|
-
title: `Serve @zooid/
|
|
1415
|
+
title: `Serve @zooid/web on http://localhost:${flags.uiPort}`,
|
|
1346
1416
|
task: async (_, t) => {
|
|
1347
1417
|
const webRoot = ctx.webWatch?.distPath ?? await ensureWebRoot({
|
|
1348
1418
|
cliRoot: CLI_ROOT,
|
|
1349
|
-
cacheDir:
|
|
1419
|
+
cacheDir: join12(layout.dataRoot, "web"),
|
|
1350
1420
|
version: readZoonWebPin(CLI_ROOT),
|
|
1351
1421
|
onProgress: (msg) => {
|
|
1352
1422
|
t.output = msg;
|
|
@@ -1397,10 +1467,10 @@ Pass an explicit path: --watch-web=/path/to/zoon/packages/web`
|
|
|
1397
1467
|
[
|
|
1398
1468
|
"",
|
|
1399
1469
|
chalk.bold("Tuwunel is up.") + ` ${homeserver}`,
|
|
1400
|
-
chalk.bold("UI: ") + ` http://localhost:${flags.uiPort}`,
|
|
1470
|
+
chalk.bold("UI: ") + ` http://localhost:${ctx.uiServer?.address()?.port ?? flags.uiPort}`,
|
|
1401
1471
|
` ${chalk.cyan("admin user:")} ${flags.adminUser} / ${flags.adminPassword}`,
|
|
1402
1472
|
` ${chalk.cyan("data dir:")} ${layout.dataRoot}`,
|
|
1403
|
-
...ctx.webWatch ? [` ${chalk.cyan("web watcher:")} live (vite build --watch on @zooid/
|
|
1473
|
+
...ctx.webWatch ? [` ${chalk.cyan("web watcher:")} live (vite build --watch on @zooid/web)`] : [],
|
|
1404
1474
|
"",
|
|
1405
1475
|
chalk.dim("Press Ctrl-C to stop."),
|
|
1406
1476
|
""
|
|
@@ -1424,15 +1494,21 @@ function loadEnvFiles(cwd) {
|
|
|
1424
1494
|
}
|
|
1425
1495
|
|
|
1426
1496
|
// src/commands/init.ts
|
|
1427
|
-
import { existsSync as existsSync7, mkdirSync as
|
|
1428
|
-
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";
|
|
1429
1499
|
|
|
1430
1500
|
// src/commands/init/generators.ts
|
|
1431
1501
|
function generateZooidYaml(opts) {
|
|
1432
|
-
const acpBlock = opts.preset === "opencode" ? ` acp: { preset:
|
|
1502
|
+
const acpBlock = opts.preset === "opencode" || !opts.model ? ` acp: { preset: ${opts.preset} }` : ` acp:
|
|
1433
1503
|
preset: ${opts.preset}
|
|
1434
1504
|
model: ${opts.model}`;
|
|
1435
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.
|
|
1436
1512
|
|
|
1437
1513
|
transports:
|
|
1438
1514
|
matrix:
|
|
@@ -1489,23 +1565,20 @@ function generateOpencodeJson(opts) {
|
|
|
1489
1565
|
return JSON.stringify(
|
|
1490
1566
|
{
|
|
1491
1567
|
$schema: "https://opencode.ai/config.json",
|
|
1492
|
-
model: "TODO/your-model",
|
|
1493
1568
|
provider: { TODO: { options: {} } }
|
|
1494
1569
|
},
|
|
1495
1570
|
null,
|
|
1496
1571
|
2
|
|
1497
1572
|
) + "\n";
|
|
1498
1573
|
}
|
|
1499
|
-
const cfg = {
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
provider: {
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
}
|
|
1506
|
-
},
|
|
1507
|
-
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
|
+
}
|
|
1508
1580
|
};
|
|
1581
|
+
cfg.permission = { webfetch: "allow" };
|
|
1509
1582
|
return JSON.stringify(cfg, null, 2) + "\n";
|
|
1510
1583
|
}
|
|
1511
1584
|
function generateEnv(opts) {
|
|
@@ -1525,7 +1598,6 @@ provider-specific options and per-tool permissions.
|
|
|
1525
1598
|
var SIMPLE_PRESETS = [
|
|
1526
1599
|
{
|
|
1527
1600
|
preset: "claude",
|
|
1528
|
-
models: ["claude-sonnet-4-6", "claude-opus-4-7", "claude-haiku-4-5"],
|
|
1529
1601
|
subscriptionLabel: "Claude subscription (Pro / Max / Team / Enterprise)",
|
|
1530
1602
|
apiKeyEnvVar: "ANTHROPIC_API_KEY",
|
|
1531
1603
|
apiKeyPromptLabel: "Anthropic",
|
|
@@ -1533,7 +1605,6 @@ var SIMPLE_PRESETS = [
|
|
|
1533
1605
|
},
|
|
1534
1606
|
{
|
|
1535
1607
|
preset: "codex",
|
|
1536
|
-
models: ["gpt-5.5"],
|
|
1537
1608
|
subscriptionLabel: "ChatGPT subscription (Plus / Pro / Team)",
|
|
1538
1609
|
apiKeyEnvVar: "OPENAI_API_KEY",
|
|
1539
1610
|
apiKeyPromptLabel: "OpenAI",
|
|
@@ -1545,28 +1616,24 @@ var OPENCODE_PROVIDERS = [
|
|
|
1545
1616
|
id: "opencode-go",
|
|
1546
1617
|
label: "opencode-go",
|
|
1547
1618
|
description: "$10/mo subscription \u2014 Kimi, GLM, MiniMax",
|
|
1548
|
-
models: ["kimi-k2.6", "kimi-k2.5", "glm-5.1", "glm-5", "minimax-m2.7"],
|
|
1549
1619
|
apiKeyEnvVar: "OPENCODE_API_KEY"
|
|
1550
1620
|
},
|
|
1551
1621
|
{
|
|
1552
1622
|
id: "opencode",
|
|
1553
1623
|
label: "opencode (Zen)",
|
|
1554
1624
|
description: "free tier \u2014 curated models",
|
|
1555
|
-
models: ["kimi-k2.5-free", "glm-4.7-free", "minimax-m2.1-free"],
|
|
1556
1625
|
apiKeyEnvVar: "OPENCODE_API_KEY"
|
|
1557
1626
|
},
|
|
1558
1627
|
{
|
|
1559
1628
|
id: "anthropic",
|
|
1560
1629
|
label: "Anthropic",
|
|
1561
1630
|
description: "Claude via direct Anthropic API",
|
|
1562
|
-
models: ["claude-sonnet-4-6", "claude-opus-4-7", "claude-haiku-4-5"],
|
|
1563
1631
|
apiKeyEnvVar: "ANTHROPIC_API_KEY"
|
|
1564
1632
|
},
|
|
1565
1633
|
{
|
|
1566
1634
|
id: "openrouter",
|
|
1567
1635
|
label: "OpenRouter",
|
|
1568
1636
|
description: "many providers via OpenRouter",
|
|
1569
|
-
models: ["anthropic/claude-sonnet-4-6", "zhipuai/glm-5", "moonshot/kimi-k2.6"],
|
|
1570
1637
|
apiKeyEnvVar: "OPENROUTER_API_KEY"
|
|
1571
1638
|
}
|
|
1572
1639
|
];
|
|
@@ -1580,11 +1647,11 @@ function findOpencodeProvider(id) {
|
|
|
1580
1647
|
// src/commands/init/sniff.ts
|
|
1581
1648
|
import { existsSync as existsSync6 } from "fs";
|
|
1582
1649
|
import { homedir } from "os";
|
|
1583
|
-
import { join as
|
|
1650
|
+
import { join as join13 } from "path";
|
|
1584
1651
|
function sniffCredentials(preset, home = homedir()) {
|
|
1585
1652
|
const meta = findSimplePreset(preset);
|
|
1586
1653
|
if (!meta) return { found: false };
|
|
1587
|
-
const full =
|
|
1654
|
+
const full = join13(home, meta.credentialDir);
|
|
1588
1655
|
if (existsSync6(full)) return { found: true, path: full };
|
|
1589
1656
|
return { found: false };
|
|
1590
1657
|
}
|
|
@@ -1603,7 +1670,7 @@ var IGNORED_PREEXISTING = /* @__PURE__ */ new Set([
|
|
|
1603
1670
|
]);
|
|
1604
1671
|
async function runInit(opts) {
|
|
1605
1672
|
const dir = resolve4(opts.dir);
|
|
1606
|
-
|
|
1673
|
+
mkdirSync5(dir, { recursive: true });
|
|
1607
1674
|
if (!opts.force) {
|
|
1608
1675
|
const blocking = readdirSync2(dir).filter((n) => !IGNORED_PREEXISTING.has(n));
|
|
1609
1676
|
if (blocking.length > 0) {
|
|
@@ -1614,7 +1681,6 @@ async function runInit(opts) {
|
|
|
1614
1681
|
}
|
|
1615
1682
|
const writes = [];
|
|
1616
1683
|
if (opts.preset === "claude" || opts.preset === "codex") {
|
|
1617
|
-
if (!opts.model) throw new Error(`--model is required for preset ${opts.preset}`);
|
|
1618
1684
|
if (!opts.auth) throw new Error("--auth (subscription|api-key) is required for claude/codex");
|
|
1619
1685
|
const meta = findSimplePreset(opts.preset);
|
|
1620
1686
|
writes.push({
|
|
@@ -1644,7 +1710,6 @@ async function runInit(opts) {
|
|
|
1644
1710
|
const isCustom = opts.provider === "custom";
|
|
1645
1711
|
const providerMeta = isCustom ? void 0 : findOpencodeProvider(opts.provider);
|
|
1646
1712
|
if (!isCustom && !providerMeta) throw new Error(`unknown opencode provider: ${opts.provider}`);
|
|
1647
|
-
if (!isCustom && !opts.model) throw new Error("--model is required (paired with --provider)");
|
|
1648
1713
|
if (!isCustom && !opts.apiKey) throw new Error("--api-key is required for opencode");
|
|
1649
1714
|
writes.push({ path: "zooid.yaml", content: generateZooidYaml({ preset: "opencode" }) });
|
|
1650
1715
|
writes.push({ path: "agents/zooid-assistant/AGENTS.md", content: generateAgentsMd() });
|
|
@@ -1672,14 +1737,14 @@ async function runInit(opts) {
|
|
|
1672
1737
|
}
|
|
1673
1738
|
writes.push({ path: ".gitignore", content: generateGitignore() });
|
|
1674
1739
|
for (const w of writes) {
|
|
1675
|
-
const full =
|
|
1740
|
+
const full = join14(dir, w.path);
|
|
1676
1741
|
const exists = existsSync7(full);
|
|
1677
1742
|
if (exists && !opts.overwrite) {
|
|
1678
1743
|
console.warn(`\u26A0 ${w.path} exists; left as-is (use --force --overwrite to replace)`);
|
|
1679
1744
|
continue;
|
|
1680
1745
|
}
|
|
1681
|
-
|
|
1682
|
-
|
|
1746
|
+
mkdirSync5(dirname7(full), { recursive: true });
|
|
1747
|
+
writeFileSync5(full, w.content);
|
|
1683
1748
|
console.log(`\u2713 Created ${w.path}`);
|
|
1684
1749
|
}
|
|
1685
1750
|
if ((opts.preset === "claude" || opts.preset === "codex") && opts.auth === "subscription") {
|
|
@@ -1715,19 +1780,7 @@ async function resolveOptions(flags) {
|
|
|
1715
1780
|
() => "--preset is required (claude | codex | opencode)"
|
|
1716
1781
|
);
|
|
1717
1782
|
if (preset === "opencode") {
|
|
1718
|
-
const provider = flags.provider ??
|
|
1719
|
-
() => select({
|
|
1720
|
-
message: "Which provider?",
|
|
1721
|
-
choices: [
|
|
1722
|
-
...OPENCODE_PROVIDERS.map((p) => ({
|
|
1723
|
-
name: `${p.label} (${p.description})`,
|
|
1724
|
-
value: p.id
|
|
1725
|
-
})),
|
|
1726
|
-
{ name: "custom (write your own opencode.json block)", value: "custom" }
|
|
1727
|
-
]
|
|
1728
|
-
}),
|
|
1729
|
-
() => "--provider is required for opencode"
|
|
1730
|
-
);
|
|
1783
|
+
const provider = flags.provider ?? "opencode-go";
|
|
1731
1784
|
if (provider === "custom") {
|
|
1732
1785
|
return {
|
|
1733
1786
|
dir: flags.dir,
|
|
@@ -1739,13 +1792,6 @@ async function resolveOptions(flags) {
|
|
|
1739
1792
|
}
|
|
1740
1793
|
const meta2 = findOpencodeProvider(provider);
|
|
1741
1794
|
if (!meta2) throw new Error(`unknown opencode provider: ${provider}`);
|
|
1742
|
-
const model2 = flags.model ?? await ask(
|
|
1743
|
-
() => select({
|
|
1744
|
-
message: "Which model?",
|
|
1745
|
-
choices: meta2.models.map((m) => ({ name: m, value: m }))
|
|
1746
|
-
}),
|
|
1747
|
-
() => "--model is required"
|
|
1748
|
-
);
|
|
1749
1795
|
const apiKey2 = flags.apiKey ?? await ask(
|
|
1750
1796
|
() => password({ message: `${meta2.label} API key:` }),
|
|
1751
1797
|
() => "--api-key is required"
|
|
@@ -1754,7 +1800,7 @@ async function resolveOptions(flags) {
|
|
|
1754
1800
|
dir: flags.dir,
|
|
1755
1801
|
preset,
|
|
1756
1802
|
provider,
|
|
1757
|
-
model:
|
|
1803
|
+
model: flags.model,
|
|
1758
1804
|
apiKey: apiKey2,
|
|
1759
1805
|
force: flags.force,
|
|
1760
1806
|
overwrite: flags.overwrite
|
|
@@ -1772,13 +1818,6 @@ async function resolveOptions(flags) {
|
|
|
1772
1818
|
}),
|
|
1773
1819
|
() => "--auth is required (subscription | api-key)"
|
|
1774
1820
|
);
|
|
1775
|
-
const model = flags.model ?? await ask(
|
|
1776
|
-
() => select({
|
|
1777
|
-
message: "Which model?",
|
|
1778
|
-
choices: meta.models.map((m) => ({ name: m, value: m }))
|
|
1779
|
-
}),
|
|
1780
|
-
() => "--model is required"
|
|
1781
|
-
);
|
|
1782
1821
|
const apiKey = auth === "api-key" ? flags.apiKey ?? await ask(
|
|
1783
1822
|
() => password({ message: `${meta.apiKeyPromptLabel} API key:` }),
|
|
1784
1823
|
() => "--api-key is required when --auth=api-key"
|
|
@@ -1787,7 +1826,7 @@ async function resolveOptions(flags) {
|
|
|
1787
1826
|
dir: flags.dir,
|
|
1788
1827
|
preset,
|
|
1789
1828
|
auth,
|
|
1790
|
-
model,
|
|
1829
|
+
model: flags.model,
|
|
1791
1830
|
apiKey,
|
|
1792
1831
|
force: flags.force,
|
|
1793
1832
|
overwrite: flags.overwrite
|
|
@@ -1797,7 +1836,7 @@ async function resolveOptions(flags) {
|
|
|
1797
1836
|
// src/commands/logs.ts
|
|
1798
1837
|
import { readFile, readdir as readdir2, readlink } from "fs/promises";
|
|
1799
1838
|
import { existsSync as existsSync8 } from "fs";
|
|
1800
|
-
import { join as
|
|
1839
|
+
import { join as join15 } from "path";
|
|
1801
1840
|
var KNOWN_SOURCES = ["tuwunel", "daemon", "dev"];
|
|
1802
1841
|
async function runLogs(flags) {
|
|
1803
1842
|
const writer = flags.writer ?? ((s) => process.stdout.write(s));
|
|
@@ -1816,7 +1855,7 @@ async function runLogs(flags) {
|
|
|
1816
1855
|
writer("no logs yet\n");
|
|
1817
1856
|
return;
|
|
1818
1857
|
}
|
|
1819
|
-
const dayDir =
|
|
1858
|
+
const dayDir = join15(flags.dataDir, "logs", day);
|
|
1820
1859
|
if (!existsSync8(dayDir)) {
|
|
1821
1860
|
writer(`no logs for ${day}
|
|
1822
1861
|
`);
|
|
@@ -1845,7 +1884,7 @@ async function runLogs(flags) {
|
|
|
1845
1884
|
writer(await readFile(path, "utf8"));
|
|
1846
1885
|
}
|
|
1847
1886
|
async function resolveTodaySlug(dataDir) {
|
|
1848
|
-
const link =
|
|
1887
|
+
const link = join15(dataDir, "logs", "today");
|
|
1849
1888
|
try {
|
|
1850
1889
|
return await readlink(link);
|
|
1851
1890
|
} catch {
|
|
@@ -1854,18 +1893,18 @@ async function resolveTodaySlug(dataDir) {
|
|
|
1854
1893
|
}
|
|
1855
1894
|
function resolveSourcePath(dayDir, source) {
|
|
1856
1895
|
if (source.startsWith("agent-")) {
|
|
1857
|
-
if (source.endsWith(".acp")) return
|
|
1858
|
-
return
|
|
1896
|
+
if (source.endsWith(".acp")) return join15(dayDir, `${source.slice(0, -4)}.acp.jsonl`);
|
|
1897
|
+
return join15(dayDir, `${source}.log`);
|
|
1859
1898
|
}
|
|
1860
1899
|
if (KNOWN_SOURCES.includes(source))
|
|
1861
|
-
return
|
|
1862
|
-
return
|
|
1900
|
+
return join15(dayDir, `${source}.log`);
|
|
1901
|
+
return join15(dayDir, source);
|
|
1863
1902
|
}
|
|
1864
1903
|
async function dumpByTurn(dayDir, turnId, writer) {
|
|
1865
1904
|
const entries = await readdir2(dayDir);
|
|
1866
1905
|
const taps = entries.filter((e) => e.endsWith(".acp.jsonl")).sort();
|
|
1867
1906
|
for (const f of taps) {
|
|
1868
|
-
const text = await readFile(
|
|
1907
|
+
const text = await readFile(join15(dayDir, f), "utf8");
|
|
1869
1908
|
for (const line of text.split("\n")) {
|
|
1870
1909
|
if (!line) continue;
|
|
1871
1910
|
try {
|
|
@@ -1899,7 +1938,7 @@ async function runStart(flags) {
|
|
|
1899
1938
|
}
|
|
1900
1939
|
|
|
1901
1940
|
// src/commands/status.ts
|
|
1902
|
-
import { readFileSync as
|
|
1941
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
1903
1942
|
import { dirname as dirname8 } from "path";
|
|
1904
1943
|
import chalk2 from "chalk";
|
|
1905
1944
|
async function probe(url, timeoutMs = 2e3) {
|
|
@@ -1924,7 +1963,7 @@ async function collectStatus(opts) {
|
|
|
1924
1963
|
agents: []
|
|
1925
1964
|
};
|
|
1926
1965
|
}
|
|
1927
|
-
const cfg = loadZooidConfig(
|
|
1966
|
+
const cfg = loadZooidConfig(readFileSync7(found.path, "utf8"), {
|
|
1928
1967
|
configDir: dirname8(found.path)
|
|
1929
1968
|
});
|
|
1930
1969
|
const matrixEntry = Object.entries(cfg.transports).find(
|
|
@@ -1955,7 +1994,7 @@ async function runStatus(flags) {
|
|
|
1955
1994
|
if (port === void 0) {
|
|
1956
1995
|
const found = findConfigFile(cwd);
|
|
1957
1996
|
if (found) {
|
|
1958
|
-
const cfg = loadZooidConfig(
|
|
1997
|
+
const cfg = loadZooidConfig(readFileSync7(found.path, "utf8"), {
|
|
1959
1998
|
configDir: dirname8(found.path)
|
|
1960
1999
|
});
|
|
1961
2000
|
const matrix = findMatrixTransport(cfg);
|
|
@@ -1992,7 +2031,7 @@ cli.command("start", "Run the daemon (production entry-point)").option("--data <
|
|
|
1992
2031
|
});
|
|
1993
2032
|
cli.command("dev", "Tuwunel + daemon + UI for local development").option("--data <dir>", "Persistent data root dir", { default: "./data" }).option("--engine <docker|podman>", "Container engine", { default: "docker" }).option("--ui-port <n>", "UI HTTP port", { default: 5173 }).option("--admin-user <name>", "Admin username", { default: "admin" }).option("--admin-password <pw>", "Admin password", { default: "admin" }).option(
|
|
1994
2033
|
"--watch-web [path]",
|
|
1995
|
-
"Run vite build --watch on @zooid/
|
|
2034
|
+
"Run vite build --watch on @zooid/web. Path defaults to sibling ../zooid-clients/packages/web."
|
|
1996
2035
|
).action(async (flags) => {
|
|
1997
2036
|
await runDev({
|
|
1998
2037
|
dataDir: flags.data,
|