zooid 0.7.4 → 0.9.0

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 CHANGED
@@ -2,6 +2,7 @@
2
2
  import {
3
3
  ApprovalCorrelator,
4
4
  MatrixClient,
5
+ MediaClient,
5
6
  SpawnRegistry,
6
7
  buildAcpRegistry,
7
8
  createMatrixTransport,
@@ -15,17 +16,17 @@ import {
15
16
  renderRegistration,
16
17
  startDaemonSocketServer,
17
18
  startWorkforcePublisher
18
- } from "./chunk-OKGNOROJ.js";
19
+ } from "./chunk-LOILUENL.js";
19
20
 
20
21
  // src/bin.ts
21
- import { resolve as resolve5 } from "path";
22
+ import { resolve as resolve6 } from "path";
22
23
  import { cac } from "cac";
23
24
 
24
25
  // src/commands/dev.ts
25
26
  import chalk from "chalk";
26
27
  import { Listr } from "listr2";
27
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
28
- import { dirname as dirname6, resolve as resolve2 } from "path";
28
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
29
+ import { dirname as dirname6, join as join10, resolve as resolve3 } from "path";
29
30
  import { fileURLToPath } from "url";
30
31
  import { serve as serve2 } from "@hono/node-server";
31
32
 
@@ -189,7 +190,7 @@ MATRIX_HS_TOKEN=${tokens.hsToken}
189
190
  import { readFileSync as readFileSync2 } from "fs";
190
191
  import { mkdir, unlink } from "fs/promises";
191
192
  import { tmpdir } from "os";
192
- import { dirname as dirname2, join as join3 } from "path";
193
+ import { dirname as dirname2, isAbsolute, join as join3, resolve } from "path";
193
194
  import { serve } from "@hono/node-server";
194
195
 
195
196
  // ../transport-http/src/server.ts
@@ -466,18 +467,18 @@ var defaultExec = async (cmd, args) => {
466
467
 
467
468
  // src/daemon/start-daemon.ts
468
469
  function listenAsync(server) {
469
- return new Promise((resolve6) => {
470
+ return new Promise((resolve7) => {
470
471
  const check = () => {
471
472
  const addr = server.address();
472
- if (addr && typeof addr === "object") resolve6(addr.port);
473
+ if (addr && typeof addr === "object") resolve7(addr.port);
473
474
  else setImmediate(check);
474
475
  };
475
476
  check();
476
477
  });
477
478
  }
478
479
  function closeAsync(server) {
479
- return new Promise((resolve6) => {
480
- server.close(() => resolve6());
480
+ return new Promise((resolve7) => {
481
+ server.close(() => resolve7());
481
482
  });
482
483
  }
483
484
  async function startDaemon(opts = {}) {
@@ -538,6 +539,11 @@ async function startDaemon(opts = {}) {
538
539
  homeserver: matrix.transport.homeserver,
539
540
  asToken: matrix.transport.as_token
540
541
  });
542
+ const mediaClient = new MediaClient({
543
+ homeserver: matrix.transport.homeserver,
544
+ asToken: matrix.transport.as_token
545
+ });
546
+ const isContainerRuntime = config.runtime !== "local";
541
547
  const bindings = [];
542
548
  for (const a of Object.values(config.agents)) {
543
549
  if (a.matrix?.transport !== matrix.name) continue;
@@ -548,21 +554,26 @@ async function startDaemon(opts = {}) {
548
554
  trigger: a.matrix.trigger
549
555
  };
550
556
  if (a.matrix.display_name !== void 0) binding.displayName = a.matrix.display_name;
557
+ const workspaceDir = isAbsolute(a.workdir) ? a.workdir : resolve(configDir, a.workdir);
558
+ binding.workspaceDir = workspaceDir;
559
+ binding.agentWorkspacePath = isContainerRuntime ? "/workspace" : workspaceDir;
551
560
  bindings.push(binding);
552
561
  }
562
+ const serverName = matrix.transport.user_namespace.split(":").slice(1).join(":").replace(/\\?\)?$/, "") || new URL(matrix.transport.homeserver).hostname;
563
+ const asUserId = `@${matrix.transport.sender_localpart}:${serverName}`;
553
564
  const transport = createMatrixTransport({
554
565
  agents: registry,
555
566
  approvals,
556
567
  client,
557
568
  bindings,
558
569
  hsToken: matrix.transport.hs_token,
559
- adminUserId: opts.adminUserId
570
+ adminUserId: opts.adminUserId,
571
+ botUserId: asUserId,
572
+ media: mediaClient
560
573
  });
561
574
  const requestedPort = matrix.transport.port ?? 9e3;
562
575
  server = serve({ fetch: transport.app.fetch, port: requestedPort, hostname: "0.0.0.0" });
563
576
  port = await listenAsync(server);
564
- const serverName = matrix.transport.user_namespace.split(":").slice(1).join(":").replace(/\\?\)?$/, "") || new URL(matrix.transport.homeserver).hostname;
565
- const asUserId = `@${matrix.transport.sender_localpart}:${serverName}`;
566
577
  const spaceLocalpart = matrix.transport.space ?? "dev";
567
578
  const adminUserIds = opts.adminUserId ? [opts.adminUserId] : [];
568
579
  let spaceRoomId;
@@ -573,7 +584,8 @@ async function startDaemon(opts = {}) {
573
584
  serverName,
574
585
  spaceLocalpart,
575
586
  preset: "public_chat",
576
- admins: adminUserIds
587
+ admins: adminUserIds,
588
+ joinRule: opts.publicWorkforceSpace ? "public" : "invite"
577
589
  });
578
590
  console.log(`[matrix] ensured workforce space #${spaceLocalpart}:${serverName} \u2192 ${spaceRoomId}`);
579
591
  } catch (err) {
@@ -600,7 +612,7 @@ async function startDaemon(opts = {}) {
600
612
  asUserId,
601
613
  getAgents: () => bindings
602
614
  });
603
- console.log(`[matrix] published eco.zoon.workforce (${bindings.length} agents)`);
615
+ console.log(`[matrix] published dev.zooid.workforce (${bindings.length} agents)`);
604
616
  void publisher;
605
617
  } catch (err) {
606
618
  console.warn("[matrix] workforce roster publication failed:", err);
@@ -688,8 +700,8 @@ var TuwunelService = class {
688
700
  async stop() {
689
701
  if (this.child && this.child.exitCode === null) {
690
702
  this.child.kill("SIGTERM");
691
- await new Promise((resolve6) => {
692
- this.child.on("exit", () => resolve6());
703
+ await new Promise((resolve7) => {
704
+ this.child.on("exit", () => resolve7());
693
705
  });
694
706
  }
695
707
  this.child = null;
@@ -706,7 +718,7 @@ var TuwunelService = class {
706
718
  `Tuwunel container exited before serving HTTP (exit=${state.exitCode}${state.error ? `, error=${state.error}` : ""}). Check the engine logs (\`${this.opts.engine} logs ${this.opts.name}\`).`
707
719
  );
708
720
  }
709
- await new Promise((resolve6) => setTimeout(resolve6, 500));
721
+ await new Promise((resolve7) => setTimeout(resolve7, 500));
710
722
  }
711
723
  while (Date.now() < deadline) {
712
724
  try {
@@ -714,7 +726,7 @@ var TuwunelService = class {
714
726
  if (r.ok) return;
715
727
  } catch {
716
728
  }
717
- await new Promise((resolve6) => setTimeout(resolve6, 500));
729
+ await new Promise((resolve7) => setTimeout(resolve7, 500));
718
730
  }
719
731
  const finalState = await this.inspectState().catch(() => null);
720
732
  const detail = finalState ? ` (container status=${finalState.status}${finalState.exitCode !== void 0 ? `, exit=${finalState.exitCode}` : ""})` : "";
@@ -747,12 +759,12 @@ var TuwunelService = class {
747
759
  }
748
760
  };
749
761
  function execEngine(engine, args) {
750
- return new Promise((resolve6, reject) => {
762
+ return new Promise((resolve7, reject) => {
751
763
  const child = spawn(engine, args, { stdio: "pipe" });
752
764
  let stderr = "";
753
765
  child.stderr.on("data", (b) => stderr += String(b));
754
766
  child.on("exit", (code) => {
755
- if (code === 0) resolve6();
767
+ if (code === 0) resolve7();
756
768
  else reject(new Error(`${engine} ${args.join(" ")} failed: ${stderr.trim()}`));
757
769
  });
758
770
  child.on("error", reject);
@@ -760,34 +772,115 @@ function execEngine(engine, args) {
760
772
  }
761
773
 
762
774
  // src/web/resolve.ts
763
- import { existsSync as existsSync2 } from "fs";
764
- import { dirname as dirname3, join as join4, resolve } from "path";
775
+ import { existsSync as existsSync3 } from "fs";
776
+ import { dirname as dirname3, join as join5, resolve as resolve2 } from "path";
777
+
778
+ // src/web/fetch.ts
779
+ import { mkdirSync as mkdirSync3, renameSync, rmSync, existsSync as existsSync2, readdirSync, writeFileSync as writeFileSync3 } from "fs";
780
+ import { join as join4 } from "path";
781
+ import { createHash, randomUUID as randomUUID2 } from "crypto";
782
+ import * as tar from "tar";
783
+ var PKG = "@zooid/web";
784
+ var DEFAULT_REGISTRY = "https://registry.npmjs.org";
785
+ async function fetchWebBundle(opts) {
786
+ const target = join4(opts.cacheDir, opts.version);
787
+ if (existsSync2(join4(target, "index.html"))) return target;
788
+ if (existsSync2(target)) rmSync(target, { recursive: true, force: true });
789
+ const f = opts.fetch ?? globalThis.fetch;
790
+ const registry = (opts.registryUrl ?? DEFAULT_REGISTRY).replace(/\/$/, "");
791
+ let tgz;
792
+ try {
793
+ const metaRes = await f(`${registry}/${PKG}`);
794
+ if (!metaRes.ok) throw new Error(`registry metadata: HTTP ${metaRes.status}`);
795
+ const meta = await metaRes.json();
796
+ const dist = meta.versions?.[opts.version]?.dist;
797
+ if (!dist?.tarball || !dist.integrity) {
798
+ throw new Error(`${PKG}@${opts.version} not found on the registry`);
799
+ }
800
+ const tgzRes = await f(dist.tarball);
801
+ if (!tgzRes.ok) throw new Error(`tarball: HTTP ${tgzRes.status}`);
802
+ tgz = Buffer.from(await tgzRes.arrayBuffer());
803
+ const sha = "sha512-" + createHash("sha512").update(tgz).digest("base64");
804
+ if (sha !== dist.integrity) {
805
+ throw new Error(`integrity mismatch for ${PKG}@${opts.version}: got ${sha}`);
806
+ }
807
+ } catch (err) {
808
+ throw new Error(
809
+ `Failed to fetch ${PKG}@${opts.version} from ${registry}.
810
+ Cache: ${opts.cacheDir}
811
+ Offline? Point ZOOID_DEV_WEB_ROOT_OVERRIDE at a built dist as a manual escape hatch.
812
+ Cause: ${err instanceof Error ? err.message : String(err)}`
813
+ );
814
+ }
815
+ const tmp = join4(opts.cacheDir, `.tmp-${randomUUID2().slice(0, 8)}`);
816
+ mkdirSync3(tmp, { recursive: true });
817
+ const tgzPath = join4(tmp, ".bundle.tgz");
818
+ try {
819
+ writeFileSync3(tgzPath, tgz);
820
+ await tar.extract({
821
+ file: tgzPath,
822
+ cwd: tmp,
823
+ strip: 2,
824
+ // package/dist/<file> → <file>
825
+ filter: (p) => p === "package/dist" || p.startsWith("package/dist/")
826
+ });
827
+ rmSync(tgzPath);
828
+ if (!existsSync2(join4(tmp, "index.html"))) {
829
+ throw new Error(`${PKG}@${opts.version} tarball has no dist/index.html`);
830
+ }
831
+ renameSync(tmp, target);
832
+ } catch (err) {
833
+ rmSync(tmp, { recursive: true, force: true });
834
+ throw err;
835
+ }
836
+ for (const entry of readdirSync(opts.cacheDir)) {
837
+ if (entry !== opts.version) {
838
+ rmSync(join4(opts.cacheDir, entry), { recursive: true, force: true });
839
+ }
840
+ }
841
+ return target;
842
+ }
843
+
844
+ // src/web/resolve.ts
765
845
  var ENV_OVERRIDE = "ZOOID_DEV_WEB_ROOT_OVERRIDE";
766
- function resolveWebRoot(cliRoot) {
846
+ async function ensureWebRoot(opts) {
767
847
  const override = process.env[ENV_OVERRIDE];
768
- if (override && existsSync2(join4(override, "index.html"))) return resolve(override);
769
- const published = join4(cliRoot, "dist", "web");
770
- if (existsSync2(join4(published, "index.html"))) return published;
771
- const fromSource = webSourcePackage(cliRoot);
772
- if (fromSource && existsSync2(join4(fromSource, "dist", "index.html"))) {
773
- return join4(fromSource, "dist");
848
+ if (override && existsSync3(join5(override, "index.html"))) return resolve2(override);
849
+ const fromSource = webSourcePackage(opts.cliRoot);
850
+ if (fromSource && existsSync3(join5(fromSource, "dist", "index.html"))) {
851
+ return join5(fromSource, "dist");
774
852
  }
775
- throw new Error(
776
- `@zoon/web build not found.
777
- Tried: ${published}
778
- Tried: ${fromSource ?? "(no monorepo source)"}
779
- Run \`pnpm -C zoon/packages/web build\` (or set ${ENV_OVERRIDE} to a built dist).`
780
- );
853
+ if (!opts.version) {
854
+ throw new Error(
855
+ `No @zooid/web version pin (zooid.webVersion) in the cli package.json and no monorepo sibling build found.
856
+ Set ${ENV_OVERRIDE} to a built dist, or run from the monorepo.`
857
+ );
858
+ }
859
+ opts.onProgress?.(`Fetching @zooid/web ${opts.version}\u2026`);
860
+ const fetchBundleFn = opts.fetchBundle ?? fetchWebBundle;
861
+ return fetchBundleFn({ version: opts.version, cacheDir: opts.cacheDir });
781
862
  }
782
863
  function webSourcePackage(cliRoot) {
783
864
  const workspaceRoot = dirname3(dirname3(dirname3(cliRoot)));
784
- const candidate = join4(workspaceRoot, "zoon", "packages", "web");
785
- return existsSync2(join4(candidate, "package.json")) ? candidate : null;
865
+ const candidate = join5(workspaceRoot, "zoon", "packages", "web");
866
+ return existsSync3(join5(candidate, "package.json")) ? candidate : null;
867
+ }
868
+
869
+ // src/web/pin.ts
870
+ import { readFileSync as readFileSync3 } from "fs";
871
+ import { join as join6 } from "path";
872
+ function readZoonWebPin(cliRoot) {
873
+ try {
874
+ const pkg = JSON.parse(readFileSync3(join6(cliRoot, "package.json"), "utf8"));
875
+ return pkg.zooid?.webVersion;
876
+ } catch {
877
+ return void 0;
878
+ }
786
879
  }
787
880
 
788
881
  // src/web/static.ts
789
- import { readFileSync as readFileSync3, statSync } from "fs";
790
- import { extname, join as join5, normalize } from "path";
882
+ import { readFileSync as readFileSync4, statSync } from "fs";
883
+ import { extname, join as join7, normalize } from "path";
791
884
  import { Hono as Hono2 } from "hono";
792
885
  var MIME = {
793
886
  ".html": "text/html; charset=utf-8",
@@ -813,19 +906,19 @@ function webStatic(opts) {
813
906
  const requested = decodeURIComponent(url.pathname);
814
907
  const wantFile = requested === "/" ? "/index.html" : requested;
815
908
  const safe = normalize(wantFile).replace(/^(\.\.[/\\])+/g, "");
816
- const filePath = join5(opts.webRoot, safe);
909
+ const filePath = join7(opts.webRoot, safe);
817
910
  if (!filePath.startsWith(opts.webRoot)) return c.notFound();
818
911
  try {
819
912
  const stat = statSync(filePath);
820
913
  if (stat.isFile()) {
821
- const body = readFileSync3(filePath);
914
+ const body = readFileSync4(filePath);
822
915
  const ct = MIME[extname(filePath)] ?? "application/octet-stream";
823
916
  return c.body(body, 200, { "content-type": ct });
824
917
  }
825
918
  } catch {
826
919
  }
827
920
  if (isAssetPath(requested)) return c.notFound();
828
- const indexBytes = readFileSync3(join5(opts.webRoot, "index.html"));
921
+ const indexBytes = readFileSync4(join7(opts.webRoot, "index.html"));
829
922
  return c.body(indexBytes, 200, {
830
923
  "content-type": MIME[".html"]
831
924
  });
@@ -835,11 +928,11 @@ function webStatic(opts) {
835
928
 
836
929
  // src/web/watch.ts
837
930
  import { spawn as spawn2 } from "child_process";
838
- import { existsSync as existsSync3, statSync as statSync2 } from "fs";
839
- import { join as join6 } from "path";
931
+ import { existsSync as existsSync4, statSync as statSync2 } from "fs";
932
+ import { join as join8 } from "path";
840
933
  async function startWebWatch(opts) {
841
- const distPath = join6(opts.webPackageDir, "dist");
842
- const indexPath = join6(distPath, "index.html");
934
+ const distPath = join8(opts.webPackageDir, "dist");
935
+ const indexPath = join8(distPath, "index.html");
843
936
  const timeoutMs = opts.firstBuildTimeoutMs ?? 6e4;
844
937
  const spawnTime = Date.now();
845
938
  const child = spawn2(
@@ -873,7 +966,7 @@ async function startWebWatch(opts) {
873
966
  attachStdio();
874
967
  throw new Error(`vite build --watch exited before first build (code ${child.exitCode})`);
875
968
  }
876
- if (existsSync3(indexPath)) {
969
+ if (existsSync4(indexPath)) {
877
970
  const m = statSync2(indexPath).mtimeMs;
878
971
  if (m >= spawnTime - 500) return makeHandle();
879
972
  }
@@ -893,12 +986,12 @@ async function startWebWatch(opts) {
893
986
  async function stopChild(child) {
894
987
  if (child.exitCode !== null) return;
895
988
  child.kill("SIGTERM");
896
- await new Promise((resolve6) => {
989
+ await new Promise((resolve7) => {
897
990
  let done = false;
898
991
  const finish = () => {
899
992
  if (done) return;
900
993
  done = true;
901
- resolve6();
994
+ resolve7();
902
995
  };
903
996
  child.once("exit", finish);
904
997
  setTimeout(() => {
@@ -913,7 +1006,7 @@ async function stopChild(child) {
913
1006
 
914
1007
  // src/observability/paths.ts
915
1008
  import { mkdir as mkdir2, readdir, rm, symlink, unlink as unlink2 } from "fs/promises";
916
- import { join as join7 } from "path";
1009
+ import { join as join9 } from "path";
917
1010
  function localDateSlug(d) {
918
1011
  const y = d.getFullYear();
919
1012
  const m = String(d.getMonth() + 1).padStart(2, "0");
@@ -923,19 +1016,19 @@ function localDateSlug(d) {
923
1016
  var DAY_RE = /^\d{4}-\d{2}-\d{2}$/;
924
1017
  function resolveLogPaths({ dataDir, now }) {
925
1018
  const slug = localDateSlug(now ?? /* @__PURE__ */ new Date());
926
- const logsDir = join7(dataDir, "logs");
927
- const dayDir = join7(logsDir, slug);
1019
+ const logsDir = join9(dataDir, "logs");
1020
+ const dayDir = join9(logsDir, slug);
928
1021
  return {
929
1022
  dataDir,
930
1023
  logsDir,
931
1024
  dayDir,
932
1025
  daySlug: slug,
933
- todayLink: join7(logsDir, "today"),
934
- tuwunelLog: join7(dayDir, "tuwunel.log"),
935
- daemonLog: join7(dayDir, "daemon.log"),
936
- devLog: join7(dayDir, "dev.log"),
937
- agentLog: (n) => join7(dayDir, `agent-${n}.log`),
938
- agentTap: (n) => join7(dayDir, `agent-${n}.acp.jsonl`)
1026
+ todayLink: join9(logsDir, "today"),
1027
+ tuwunelLog: join9(dayDir, "tuwunel.log"),
1028
+ daemonLog: join9(dayDir, "daemon.log"),
1029
+ devLog: join9(dayDir, "dev.log"),
1030
+ agentLog: (n) => join9(dayDir, `agent-${n}.log`),
1031
+ agentTap: (n) => join9(dayDir, `agent-${n}.acp.jsonl`)
939
1032
  };
940
1033
  }
941
1034
  async function ensureDayFolder(p) {
@@ -948,7 +1041,7 @@ async function ensureDayFolder(p) {
948
1041
  }
949
1042
  async function pruneOldDays(opts) {
950
1043
  if (opts.retainDays <= 0) return [];
951
- const logsDir = join7(opts.dataDir, "logs");
1044
+ const logsDir = join9(opts.dataDir, "logs");
952
1045
  let entries;
953
1046
  try {
954
1047
  entries = await readdir(logsDir);
@@ -967,7 +1060,7 @@ async function pruneOldDays(opts) {
967
1060
  const [y, m, d] = name.split("-").map(Number);
968
1061
  const t = new Date(y, m - 1, d).getTime();
969
1062
  if (t < cutoff) {
970
- await rm(join7(logsDir, name), { recursive: true, force: true });
1063
+ await rm(join9(logsDir, name), { recursive: true, force: true });
971
1064
  removed.push(name);
972
1065
  }
973
1066
  }
@@ -995,14 +1088,14 @@ var JsonlSink = class {
995
1088
  await this.readyPromise;
996
1089
  const capped = capStrings(obj, this.maxStringLen);
997
1090
  const line = JSON.stringify(capped) + "\n";
998
- return new Promise((resolve6, reject) => {
999
- this.stream.write(line, (err) => err ? reject(err) : resolve6());
1091
+ return new Promise((resolve7, reject) => {
1092
+ this.stream.write(line, (err) => err ? reject(err) : resolve7());
1000
1093
  });
1001
1094
  }
1002
1095
  async close() {
1003
1096
  await this.readyPromise;
1004
- return new Promise((resolve6) => {
1005
- this.stream.end(() => resolve6());
1097
+ return new Promise((resolve7) => {
1098
+ this.stream.end(() => resolve7());
1006
1099
  });
1007
1100
  }
1008
1101
  };
@@ -1077,8 +1170,8 @@ function captureChildToFile(child, path) {
1077
1170
  const stream = createWriteStream2(path, { flags: "a" });
1078
1171
  if (child.stdout) child.stdout.on("data", (b) => stream.write(b));
1079
1172
  if (child.stderr) child.stderr.on("data", (b) => stream.write(b));
1080
- await new Promise((resolve6) => {
1081
- child.on("exit", () => stream.end(() => resolve6()));
1173
+ await new Promise((resolve7) => {
1174
+ child.on("exit", () => stream.end(() => resolve7()));
1082
1175
  });
1083
1176
  })();
1084
1177
  }
@@ -1113,10 +1206,10 @@ function buildShutdown(layers) {
1113
1206
  var CLI_ROOT = (() => {
1114
1207
  let dir = dirname6(fileURLToPath(import.meta.url));
1115
1208
  for (let i = 0; i < 8; i++) {
1116
- const pkgPath = resolve2(dir, "package.json");
1117
- if (existsSync4(pkgPath)) {
1209
+ const pkgPath = resolve3(dir, "package.json");
1210
+ if (existsSync5(pkgPath)) {
1118
1211
  try {
1119
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
1212
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
1120
1213
  if (pkg.name === "zooid" || pkg.name === "@zooid/cli") return dir;
1121
1214
  } catch {
1122
1215
  }
@@ -1132,14 +1225,14 @@ async function runDev(flags) {
1132
1225
  const found = findConfigFile(cwd);
1133
1226
  if (!found) throw new Error(`zooid.yaml not found in ${cwd}`);
1134
1227
  loadEnvFiles(cwd);
1135
- const dataRoot = resolve2(cwd, flags.dataDir);
1228
+ const dataRoot = resolve3(cwd, flags.dataDir);
1136
1229
  const layout = resolveDataLayout(dataRoot);
1137
1230
  const paths = resolvePaths(layout.matrixDir);
1138
1231
  const logPaths = resolveLogPaths({ dataDir: layout.dataRoot });
1139
1232
  const tokens = ensureTokens(paths.envPath);
1140
1233
  process.env.MATRIX_AS_TOKEN = tokens.asToken;
1141
1234
  process.env.MATRIX_HS_TOKEN = tokens.hsToken;
1142
- const rawYaml = readFileSync4(found.path, "utf8");
1235
+ const rawYaml = readFileSync5(found.path, "utf8");
1143
1236
  const preview = loadZooidConfig(rawYaml, { configDir: dirname6(found.path) });
1144
1237
  const matrix = findMatrixTransport(preview);
1145
1238
  if (!matrix) {
@@ -1220,6 +1313,10 @@ async function runDev(flags) {
1220
1313
  installSignalHandlers: false,
1221
1314
  adminUserId: `@${flags.adminUser}:${shape.serverName}`,
1222
1315
  agentsDir: layout.agentsDir,
1316
+ // Local-only homeserver: make the workforce space publicly joinable
1317
+ // so a self-service-registered dev account lands in #<space> from
1318
+ // the web client without needing an invite.
1319
+ publicWorkforceSpace: true,
1223
1320
  onTap: (agentName, event) => captures[agentName]?.onTap(event),
1224
1321
  prepullLog: (line) => {
1225
1322
  t.output = line.replace(/^\[zooid\]\s+/, "").trim();
@@ -1229,14 +1326,14 @@ async function runDev(flags) {
1229
1326
  },
1230
1327
  ...flags.watchWeb ? [
1231
1328
  {
1232
- title: "Start @zoon/web watcher (vite build --watch)",
1329
+ title: "Start @zooid/web watcher (vite build --watch)",
1233
1330
  task: async () => {
1234
- const pkgDir = typeof flags.watchWeb === "string" ? resolve2(flags.watchWeb) : webSourcePackage(CLI_ROOT);
1331
+ const pkgDir = typeof flags.watchWeb === "string" ? resolve3(flags.watchWeb) : webSourcePackage(CLI_ROOT);
1235
1332
  if (!pkgDir) {
1236
- const defaultPath = dirname6(dirname6(dirname6(CLI_ROOT))) + "/zoon/packages/web";
1333
+ const defaultPath = dirname6(dirname6(dirname6(CLI_ROOT))) + "/zooid-clients/packages/web";
1237
1334
  throw new Error(
1238
- `--watch-web: @zoon/web not found at ${defaultPath}.
1239
- Pass an explicit path: --watch-web=/path/to/zoon/packages/web`
1335
+ `--watch-web: @zooid/web not found at ${defaultPath}.
1336
+ Pass an explicit path: --watch-web=/path/to/zooid-clients/packages/web`
1240
1337
  );
1241
1338
  }
1242
1339
  ctx.webWatch = await startWebWatch({ webPackageDir: pkgDir });
@@ -1244,9 +1341,16 @@ Pass an explicit path: --watch-web=/path/to/zoon/packages/web`
1244
1341
  }
1245
1342
  ] : [],
1246
1343
  {
1247
- title: `Serve @zoon/web on http://localhost:${flags.uiPort}`,
1248
- task: () => {
1249
- const webRoot = ctx.webWatch?.distPath ?? resolveWebRoot(CLI_ROOT);
1344
+ title: `Serve @zooid/web on http://localhost:${flags.uiPort}`,
1345
+ task: async (_, t) => {
1346
+ const webRoot = ctx.webWatch?.distPath ?? await ensureWebRoot({
1347
+ cliRoot: CLI_ROOT,
1348
+ cacheDir: join10(layout.dataRoot, "web"),
1349
+ version: readZoonWebPin(CLI_ROOT),
1350
+ onProgress: (msg) => {
1351
+ t.output = msg;
1352
+ }
1353
+ });
1250
1354
  const app = webStatic({ webRoot, homeserverUrl: homeserver });
1251
1355
  ctx.uiServer = serve2({ fetch: app.fetch, port: flags.uiPort });
1252
1356
  }
@@ -1295,7 +1399,7 @@ Pass an explicit path: --watch-web=/path/to/zoon/packages/web`
1295
1399
  chalk.bold("UI: ") + ` http://localhost:${flags.uiPort}`,
1296
1400
  ` ${chalk.cyan("admin user:")} ${flags.adminUser} / ${flags.adminPassword}`,
1297
1401
  ` ${chalk.cyan("data dir:")} ${layout.dataRoot}`,
1298
- ...ctx.webWatch ? [` ${chalk.cyan("web watcher:")} live (vite build --watch on @zoon/web)`] : [],
1402
+ ...ctx.webWatch ? [` ${chalk.cyan("web watcher:")} live (vite build --watch on @zooid/web)`] : [],
1299
1403
  "",
1300
1404
  chalk.dim("Press Ctrl-C to stop."),
1301
1405
  ""
@@ -1308,8 +1412,8 @@ Pass an explicit path: --watch-web=/path/to/zoon/packages/web`
1308
1412
  }
1309
1413
  function loadEnvFiles(cwd) {
1310
1414
  for (const name of [".env.local", ".env"]) {
1311
- const path = resolve2(cwd, name);
1312
- if (!existsSync4(path)) continue;
1415
+ const path = resolve3(cwd, name);
1416
+ if (!existsSync5(path)) continue;
1313
1417
  try {
1314
1418
  process.loadEnvFile(path);
1315
1419
  } catch (err) {
@@ -1319,8 +1423,8 @@ function loadEnvFiles(cwd) {
1319
1423
  }
1320
1424
 
1321
1425
  // src/commands/init.ts
1322
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readdirSync, writeFileSync as writeFileSync3 } from "fs";
1323
- import { dirname as dirname7, join as join9, resolve as resolve3 } from "path";
1426
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync2, writeFileSync as writeFileSync4 } from "fs";
1427
+ import { dirname as dirname7, join as join12, resolve as resolve4 } from "path";
1324
1428
 
1325
1429
  // src/commands/init/generators.ts
1326
1430
  function generateZooidYaml(opts) {
@@ -1473,14 +1577,14 @@ function findOpencodeProvider(id) {
1473
1577
  }
1474
1578
 
1475
1579
  // src/commands/init/sniff.ts
1476
- import { existsSync as existsSync5 } from "fs";
1580
+ import { existsSync as existsSync6 } from "fs";
1477
1581
  import { homedir } from "os";
1478
- import { join as join8 } from "path";
1582
+ import { join as join11 } from "path";
1479
1583
  function sniffCredentials(preset, home = homedir()) {
1480
1584
  const meta = findSimplePreset(preset);
1481
1585
  if (!meta) return { found: false };
1482
- const full = join8(home, meta.credentialDir);
1483
- if (existsSync5(full)) return { found: true, path: full };
1586
+ const full = join11(home, meta.credentialDir);
1587
+ if (existsSync6(full)) return { found: true, path: full };
1484
1588
  return { found: false };
1485
1589
  }
1486
1590
 
@@ -1497,10 +1601,10 @@ var IGNORED_PREEXISTING = /* @__PURE__ */ new Set([
1497
1601
  "yarn.lock"
1498
1602
  ]);
1499
1603
  async function runInit(opts) {
1500
- const dir = resolve3(opts.dir);
1501
- mkdirSync3(dir, { recursive: true });
1604
+ const dir = resolve4(opts.dir);
1605
+ mkdirSync4(dir, { recursive: true });
1502
1606
  if (!opts.force) {
1503
- const blocking = readdirSync(dir).filter((n) => !IGNORED_PREEXISTING.has(n));
1607
+ const blocking = readdirSync2(dir).filter((n) => !IGNORED_PREEXISTING.has(n));
1504
1608
  if (blocking.length > 0) {
1505
1609
  throw new Error(
1506
1610
  `${dir} is non-empty (use --force to allow scaffolding into it). Conflicting entries: ${blocking.join(", ")}`
@@ -1567,14 +1671,14 @@ async function runInit(opts) {
1567
1671
  }
1568
1672
  writes.push({ path: ".gitignore", content: generateGitignore() });
1569
1673
  for (const w of writes) {
1570
- const full = join9(dir, w.path);
1571
- const exists = existsSync6(full);
1674
+ const full = join12(dir, w.path);
1675
+ const exists = existsSync7(full);
1572
1676
  if (exists && !opts.overwrite) {
1573
1677
  console.warn(`\u26A0 ${w.path} exists; left as-is (use --force --overwrite to replace)`);
1574
1678
  continue;
1575
1679
  }
1576
- mkdirSync3(dirname7(full), { recursive: true });
1577
- writeFileSync3(full, w.content);
1680
+ mkdirSync4(dirname7(full), { recursive: true });
1681
+ writeFileSync4(full, w.content);
1578
1682
  console.log(`\u2713 Created ${w.path}`);
1579
1683
  }
1580
1684
  if ((opts.preset === "claude" || opts.preset === "codex") && opts.auth === "subscription") {
@@ -1691,8 +1795,8 @@ async function resolveOptions(flags) {
1691
1795
 
1692
1796
  // src/commands/logs.ts
1693
1797
  import { readFile, readdir as readdir2, readlink } from "fs/promises";
1694
- import { existsSync as existsSync7 } from "fs";
1695
- import { join as join10 } from "path";
1798
+ import { existsSync as existsSync8 } from "fs";
1799
+ import { join as join13 } from "path";
1696
1800
  var KNOWN_SOURCES = ["tuwunel", "daemon", "dev"];
1697
1801
  async function runLogs(flags) {
1698
1802
  const writer = flags.writer ?? ((s) => process.stdout.write(s));
@@ -1711,8 +1815,8 @@ async function runLogs(flags) {
1711
1815
  writer("no logs yet\n");
1712
1816
  return;
1713
1817
  }
1714
- const dayDir = join10(flags.dataDir, "logs", day);
1715
- if (!existsSync7(dayDir)) {
1818
+ const dayDir = join13(flags.dataDir, "logs", day);
1819
+ if (!existsSync8(dayDir)) {
1716
1820
  writer(`no logs for ${day}
1717
1821
  `);
1718
1822
  return;
@@ -1732,7 +1836,7 @@ async function runLogs(flags) {
1732
1836
  return;
1733
1837
  }
1734
1838
  const path = resolveSourcePath(dayDir, flags.source);
1735
- if (!existsSync7(path)) {
1839
+ if (!existsSync8(path)) {
1736
1840
  writer(`no such source: ${flags.source}
1737
1841
  `);
1738
1842
  return;
@@ -1740,7 +1844,7 @@ async function runLogs(flags) {
1740
1844
  writer(await readFile(path, "utf8"));
1741
1845
  }
1742
1846
  async function resolveTodaySlug(dataDir) {
1743
- const link = join10(dataDir, "logs", "today");
1847
+ const link = join13(dataDir, "logs", "today");
1744
1848
  try {
1745
1849
  return await readlink(link);
1746
1850
  } catch {
@@ -1749,18 +1853,18 @@ async function resolveTodaySlug(dataDir) {
1749
1853
  }
1750
1854
  function resolveSourcePath(dayDir, source) {
1751
1855
  if (source.startsWith("agent-")) {
1752
- if (source.endsWith(".acp")) return join10(dayDir, `${source.slice(0, -4)}.acp.jsonl`);
1753
- return join10(dayDir, `${source}.log`);
1856
+ if (source.endsWith(".acp")) return join13(dayDir, `${source.slice(0, -4)}.acp.jsonl`);
1857
+ return join13(dayDir, `${source}.log`);
1754
1858
  }
1755
1859
  if (KNOWN_SOURCES.includes(source))
1756
- return join10(dayDir, `${source}.log`);
1757
- return join10(dayDir, source);
1860
+ return join13(dayDir, `${source}.log`);
1861
+ return join13(dayDir, source);
1758
1862
  }
1759
1863
  async function dumpByTurn(dayDir, turnId, writer) {
1760
1864
  const entries = await readdir2(dayDir);
1761
1865
  const taps = entries.filter((e) => e.endsWith(".acp.jsonl")).sort();
1762
1866
  for (const f of taps) {
1763
- const text = await readFile(join10(dayDir, f), "utf8");
1867
+ const text = await readFile(join13(dayDir, f), "utf8");
1764
1868
  for (const line of text.split("\n")) {
1765
1869
  if (!line) continue;
1766
1870
  try {
@@ -1774,14 +1878,14 @@ async function dumpByTurn(dayDir, turnId, writer) {
1774
1878
 
1775
1879
  // src/commands/start.ts
1776
1880
  import { randomBytes as randomBytes2 } from "crypto";
1777
- import { resolve as resolve4 } from "path";
1881
+ import { resolve as resolve5 } from "path";
1778
1882
  async function runStart(flags) {
1779
1883
  if (flags.printToken) {
1780
1884
  process.stdout.write(`${randomBytes2(32).toString("hex")}
1781
1885
  `);
1782
1886
  return;
1783
1887
  }
1784
- const dataRoot = resolve4(process.cwd(), flags.dataDir ?? "./data");
1888
+ const dataRoot = resolve5(process.cwd(), flags.dataDir ?? "./data");
1785
1889
  const layout = resolveDataLayout(dataRoot);
1786
1890
  const handle = await startDaemon({
1787
1891
  cliFlags: flags,
@@ -1794,7 +1898,7 @@ async function runStart(flags) {
1794
1898
  }
1795
1899
 
1796
1900
  // src/commands/status.ts
1797
- import { readFileSync as readFileSync5 } from "fs";
1901
+ import { readFileSync as readFileSync6 } from "fs";
1798
1902
  import { dirname as dirname8 } from "path";
1799
1903
  import chalk2 from "chalk";
1800
1904
  async function probe(url, timeoutMs = 2e3) {
@@ -1819,7 +1923,7 @@ async function collectStatus(opts) {
1819
1923
  agents: []
1820
1924
  };
1821
1925
  }
1822
- const cfg = loadZooidConfig(readFileSync5(found.path, "utf8"), {
1926
+ const cfg = loadZooidConfig(readFileSync6(found.path, "utf8"), {
1823
1927
  configDir: dirname8(found.path)
1824
1928
  });
1825
1929
  const matrixEntry = Object.entries(cfg.transports).find(
@@ -1850,7 +1954,7 @@ async function runStatus(flags) {
1850
1954
  if (port === void 0) {
1851
1955
  const found = findConfigFile(cwd);
1852
1956
  if (found) {
1853
- const cfg = loadZooidConfig(readFileSync5(found.path, "utf8"), {
1957
+ const cfg = loadZooidConfig(readFileSync6(found.path, "utf8"), {
1854
1958
  configDir: dirname8(found.path)
1855
1959
  });
1856
1960
  const matrix = findMatrixTransport(cfg);
@@ -1887,7 +1991,7 @@ cli.command("start", "Run the daemon (production entry-point)").option("--data <
1887
1991
  });
1888
1992
  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(
1889
1993
  "--watch-web [path]",
1890
- "Run vite build --watch on @zoon/web. Path defaults to sibling ../zoon/packages/web."
1994
+ "Run vite build --watch on @zooid/web. Path defaults to sibling ../zooid-clients/packages/web."
1891
1995
  ).action(async (flags) => {
1892
1996
  await runDev({
1893
1997
  dataDir: flags.data,
@@ -1904,14 +2008,14 @@ cli.command(
1904
2008
  ).option("--data <dir>", "Persistent data root dir", { default: "./data" }).option("--day <YYYY-MM-DD>", "Day partition (defaults to today)").option("--turn <id>", "Filter ACP taps to a single turn id").option("-f, --follow", "Tail the file (not yet implemented)").option("--keep <n>", "For `logs prune`: days to retain", { default: 14 }).action(async (source, flags) => {
1905
2009
  if (source === "prune") {
1906
2010
  await runLogs({
1907
- dataDir: resolve5(process.cwd(), flags.data),
2011
+ dataDir: resolve6(process.cwd(), flags.data),
1908
2012
  subcommand: "prune",
1909
2013
  keep: Number(flags.keep)
1910
2014
  });
1911
2015
  return;
1912
2016
  }
1913
2017
  await runLogs({
1914
- dataDir: resolve5(process.cwd(), flags.data),
2018
+ dataDir: resolve6(process.cwd(), flags.data),
1915
2019
  source,
1916
2020
  day: flags.day,
1917
2021
  turn: flags.turn,