zooid 0.7.4 → 0.8.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-R6EDLH23.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,6 +554,9 @@ 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
  }
553
562
  const transport = createMatrixTransport({
@@ -556,7 +565,8 @@ async function startDaemon(opts = {}) {
556
565
  client,
557
566
  bindings,
558
567
  hsToken: matrix.transport.hs_token,
559
- adminUserId: opts.adminUserId
568
+ adminUserId: opts.adminUserId,
569
+ media: mediaClient
560
570
  });
561
571
  const requestedPort = matrix.transport.port ?? 9e3;
562
572
  server = serve({ fetch: transport.app.fetch, port: requestedPort, hostname: "0.0.0.0" });
@@ -573,7 +583,8 @@ async function startDaemon(opts = {}) {
573
583
  serverName,
574
584
  spaceLocalpart,
575
585
  preset: "public_chat",
576
- admins: adminUserIds
586
+ admins: adminUserIds,
587
+ joinRule: opts.publicWorkforceSpace ? "public" : "invite"
577
588
  });
578
589
  console.log(`[matrix] ensured workforce space #${spaceLocalpart}:${serverName} \u2192 ${spaceRoomId}`);
579
590
  } catch (err) {
@@ -678,6 +689,7 @@ var TuwunelService = class {
678
689
  constructor(opts) {
679
690
  this.opts = opts;
680
691
  }
692
+ opts;
681
693
  child = null;
682
694
  start() {
683
695
  this.child = spawn(this.opts.engine, buildRunArgs(this.opts), {
@@ -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/zoon-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/zoon-web version pin (zooid.zoonWebVersion) 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/zoon-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?.zoonWebVersion;
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
  }
@@ -988,6 +1081,7 @@ var JsonlSink = class {
988
1081
  this.stream = createWriteStream(this.path, { flags: "a" });
989
1082
  })();
990
1083
  }
1084
+ path;
991
1085
  stream = null;
992
1086
  maxStringLen;
993
1087
  readyPromise;
@@ -995,14 +1089,14 @@ var JsonlSink = class {
995
1089
  await this.readyPromise;
996
1090
  const capped = capStrings(obj, this.maxStringLen);
997
1091
  const line = JSON.stringify(capped) + "\n";
998
- return new Promise((resolve6, reject) => {
999
- this.stream.write(line, (err) => err ? reject(err) : resolve6());
1092
+ return new Promise((resolve7, reject) => {
1093
+ this.stream.write(line, (err) => err ? reject(err) : resolve7());
1000
1094
  });
1001
1095
  }
1002
1096
  async close() {
1003
1097
  await this.readyPromise;
1004
- return new Promise((resolve6) => {
1005
- this.stream.end(() => resolve6());
1098
+ return new Promise((resolve7) => {
1099
+ this.stream.end(() => resolve7());
1006
1100
  });
1007
1101
  }
1008
1102
  };
@@ -1077,8 +1171,8 @@ function captureChildToFile(child, path) {
1077
1171
  const stream = createWriteStream2(path, { flags: "a" });
1078
1172
  if (child.stdout) child.stdout.on("data", (b) => stream.write(b));
1079
1173
  if (child.stderr) child.stderr.on("data", (b) => stream.write(b));
1080
- await new Promise((resolve6) => {
1081
- child.on("exit", () => stream.end(() => resolve6()));
1174
+ await new Promise((resolve7) => {
1175
+ child.on("exit", () => stream.end(() => resolve7()));
1082
1176
  });
1083
1177
  })();
1084
1178
  }
@@ -1113,10 +1207,10 @@ function buildShutdown(layers) {
1113
1207
  var CLI_ROOT = (() => {
1114
1208
  let dir = dirname6(fileURLToPath(import.meta.url));
1115
1209
  for (let i = 0; i < 8; i++) {
1116
- const pkgPath = resolve2(dir, "package.json");
1117
- if (existsSync4(pkgPath)) {
1210
+ const pkgPath = resolve3(dir, "package.json");
1211
+ if (existsSync5(pkgPath)) {
1118
1212
  try {
1119
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
1213
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
1120
1214
  if (pkg.name === "zooid" || pkg.name === "@zooid/cli") return dir;
1121
1215
  } catch {
1122
1216
  }
@@ -1132,14 +1226,14 @@ async function runDev(flags) {
1132
1226
  const found = findConfigFile(cwd);
1133
1227
  if (!found) throw new Error(`zooid.yaml not found in ${cwd}`);
1134
1228
  loadEnvFiles(cwd);
1135
- const dataRoot = resolve2(cwd, flags.dataDir);
1229
+ const dataRoot = resolve3(cwd, flags.dataDir);
1136
1230
  const layout = resolveDataLayout(dataRoot);
1137
1231
  const paths = resolvePaths(layout.matrixDir);
1138
1232
  const logPaths = resolveLogPaths({ dataDir: layout.dataRoot });
1139
1233
  const tokens = ensureTokens(paths.envPath);
1140
1234
  process.env.MATRIX_AS_TOKEN = tokens.asToken;
1141
1235
  process.env.MATRIX_HS_TOKEN = tokens.hsToken;
1142
- const rawYaml = readFileSync4(found.path, "utf8");
1236
+ const rawYaml = readFileSync5(found.path, "utf8");
1143
1237
  const preview = loadZooidConfig(rawYaml, { configDir: dirname6(found.path) });
1144
1238
  const matrix = findMatrixTransport(preview);
1145
1239
  if (!matrix) {
@@ -1220,6 +1314,10 @@ async function runDev(flags) {
1220
1314
  installSignalHandlers: false,
1221
1315
  adminUserId: `@${flags.adminUser}:${shape.serverName}`,
1222
1316
  agentsDir: layout.agentsDir,
1317
+ // Local-only homeserver: make the workforce space publicly joinable
1318
+ // so a self-service-registered dev account lands in #<space> from
1319
+ // the web client without needing an invite.
1320
+ publicWorkforceSpace: true,
1223
1321
  onTap: (agentName, event) => captures[agentName]?.onTap(event),
1224
1322
  prepullLog: (line) => {
1225
1323
  t.output = line.replace(/^\[zooid\]\s+/, "").trim();
@@ -1229,13 +1327,13 @@ async function runDev(flags) {
1229
1327
  },
1230
1328
  ...flags.watchWeb ? [
1231
1329
  {
1232
- title: "Start @zoon/web watcher (vite build --watch)",
1330
+ title: "Start @zooid/zoon-web watcher (vite build --watch)",
1233
1331
  task: async () => {
1234
- const pkgDir = typeof flags.watchWeb === "string" ? resolve2(flags.watchWeb) : webSourcePackage(CLI_ROOT);
1332
+ const pkgDir = typeof flags.watchWeb === "string" ? resolve3(flags.watchWeb) : webSourcePackage(CLI_ROOT);
1235
1333
  if (!pkgDir) {
1236
1334
  const defaultPath = dirname6(dirname6(dirname6(CLI_ROOT))) + "/zoon/packages/web";
1237
1335
  throw new Error(
1238
- `--watch-web: @zoon/web not found at ${defaultPath}.
1336
+ `--watch-web: @zooid/zoon-web not found at ${defaultPath}.
1239
1337
  Pass an explicit path: --watch-web=/path/to/zoon/packages/web`
1240
1338
  );
1241
1339
  }
@@ -1244,9 +1342,16 @@ Pass an explicit path: --watch-web=/path/to/zoon/packages/web`
1244
1342
  }
1245
1343
  ] : [],
1246
1344
  {
1247
- title: `Serve @zoon/web on http://localhost:${flags.uiPort}`,
1248
- task: () => {
1249
- const webRoot = ctx.webWatch?.distPath ?? resolveWebRoot(CLI_ROOT);
1345
+ title: `Serve @zooid/zoon-web on http://localhost:${flags.uiPort}`,
1346
+ task: async (_, t) => {
1347
+ const webRoot = ctx.webWatch?.distPath ?? await ensureWebRoot({
1348
+ cliRoot: CLI_ROOT,
1349
+ cacheDir: join10(layout.dataRoot, "web"),
1350
+ version: readZoonWebPin(CLI_ROOT),
1351
+ onProgress: (msg) => {
1352
+ t.output = msg;
1353
+ }
1354
+ });
1250
1355
  const app = webStatic({ webRoot, homeserverUrl: homeserver });
1251
1356
  ctx.uiServer = serve2({ fetch: app.fetch, port: flags.uiPort });
1252
1357
  }
@@ -1295,7 +1400,7 @@ Pass an explicit path: --watch-web=/path/to/zoon/packages/web`
1295
1400
  chalk.bold("UI: ") + ` http://localhost:${flags.uiPort}`,
1296
1401
  ` ${chalk.cyan("admin user:")} ${flags.adminUser} / ${flags.adminPassword}`,
1297
1402
  ` ${chalk.cyan("data dir:")} ${layout.dataRoot}`,
1298
- ...ctx.webWatch ? [` ${chalk.cyan("web watcher:")} live (vite build --watch on @zoon/web)`] : [],
1403
+ ...ctx.webWatch ? [` ${chalk.cyan("web watcher:")} live (vite build --watch on @zooid/zoon-web)`] : [],
1299
1404
  "",
1300
1405
  chalk.dim("Press Ctrl-C to stop."),
1301
1406
  ""
@@ -1308,8 +1413,8 @@ Pass an explicit path: --watch-web=/path/to/zoon/packages/web`
1308
1413
  }
1309
1414
  function loadEnvFiles(cwd) {
1310
1415
  for (const name of [".env.local", ".env"]) {
1311
- const path = resolve2(cwd, name);
1312
- if (!existsSync4(path)) continue;
1416
+ const path = resolve3(cwd, name);
1417
+ if (!existsSync5(path)) continue;
1313
1418
  try {
1314
1419
  process.loadEnvFile(path);
1315
1420
  } catch (err) {
@@ -1319,8 +1424,8 @@ function loadEnvFiles(cwd) {
1319
1424
  }
1320
1425
 
1321
1426
  // 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";
1427
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync2, writeFileSync as writeFileSync4 } from "fs";
1428
+ import { dirname as dirname7, join as join12, resolve as resolve4 } from "path";
1324
1429
 
1325
1430
  // src/commands/init/generators.ts
1326
1431
  function generateZooidYaml(opts) {
@@ -1473,14 +1578,14 @@ function findOpencodeProvider(id) {
1473
1578
  }
1474
1579
 
1475
1580
  // src/commands/init/sniff.ts
1476
- import { existsSync as existsSync5 } from "fs";
1581
+ import { existsSync as existsSync6 } from "fs";
1477
1582
  import { homedir } from "os";
1478
- import { join as join8 } from "path";
1583
+ import { join as join11 } from "path";
1479
1584
  function sniffCredentials(preset, home = homedir()) {
1480
1585
  const meta = findSimplePreset(preset);
1481
1586
  if (!meta) return { found: false };
1482
- const full = join8(home, meta.credentialDir);
1483
- if (existsSync5(full)) return { found: true, path: full };
1587
+ const full = join11(home, meta.credentialDir);
1588
+ if (existsSync6(full)) return { found: true, path: full };
1484
1589
  return { found: false };
1485
1590
  }
1486
1591
 
@@ -1497,10 +1602,10 @@ var IGNORED_PREEXISTING = /* @__PURE__ */ new Set([
1497
1602
  "yarn.lock"
1498
1603
  ]);
1499
1604
  async function runInit(opts) {
1500
- const dir = resolve3(opts.dir);
1501
- mkdirSync3(dir, { recursive: true });
1605
+ const dir = resolve4(opts.dir);
1606
+ mkdirSync4(dir, { recursive: true });
1502
1607
  if (!opts.force) {
1503
- const blocking = readdirSync(dir).filter((n) => !IGNORED_PREEXISTING.has(n));
1608
+ const blocking = readdirSync2(dir).filter((n) => !IGNORED_PREEXISTING.has(n));
1504
1609
  if (blocking.length > 0) {
1505
1610
  throw new Error(
1506
1611
  `${dir} is non-empty (use --force to allow scaffolding into it). Conflicting entries: ${blocking.join(", ")}`
@@ -1567,14 +1672,14 @@ async function runInit(opts) {
1567
1672
  }
1568
1673
  writes.push({ path: ".gitignore", content: generateGitignore() });
1569
1674
  for (const w of writes) {
1570
- const full = join9(dir, w.path);
1571
- const exists = existsSync6(full);
1675
+ const full = join12(dir, w.path);
1676
+ const exists = existsSync7(full);
1572
1677
  if (exists && !opts.overwrite) {
1573
1678
  console.warn(`\u26A0 ${w.path} exists; left as-is (use --force --overwrite to replace)`);
1574
1679
  continue;
1575
1680
  }
1576
- mkdirSync3(dirname7(full), { recursive: true });
1577
- writeFileSync3(full, w.content);
1681
+ mkdirSync4(dirname7(full), { recursive: true });
1682
+ writeFileSync4(full, w.content);
1578
1683
  console.log(`\u2713 Created ${w.path}`);
1579
1684
  }
1580
1685
  if ((opts.preset === "claude" || opts.preset === "codex") && opts.auth === "subscription") {
@@ -1691,8 +1796,8 @@ async function resolveOptions(flags) {
1691
1796
 
1692
1797
  // src/commands/logs.ts
1693
1798
  import { readFile, readdir as readdir2, readlink } from "fs/promises";
1694
- import { existsSync as existsSync7 } from "fs";
1695
- import { join as join10 } from "path";
1799
+ import { existsSync as existsSync8 } from "fs";
1800
+ import { join as join13 } from "path";
1696
1801
  var KNOWN_SOURCES = ["tuwunel", "daemon", "dev"];
1697
1802
  async function runLogs(flags) {
1698
1803
  const writer = flags.writer ?? ((s) => process.stdout.write(s));
@@ -1711,8 +1816,8 @@ async function runLogs(flags) {
1711
1816
  writer("no logs yet\n");
1712
1817
  return;
1713
1818
  }
1714
- const dayDir = join10(flags.dataDir, "logs", day);
1715
- if (!existsSync7(dayDir)) {
1819
+ const dayDir = join13(flags.dataDir, "logs", day);
1820
+ if (!existsSync8(dayDir)) {
1716
1821
  writer(`no logs for ${day}
1717
1822
  `);
1718
1823
  return;
@@ -1732,7 +1837,7 @@ async function runLogs(flags) {
1732
1837
  return;
1733
1838
  }
1734
1839
  const path = resolveSourcePath(dayDir, flags.source);
1735
- if (!existsSync7(path)) {
1840
+ if (!existsSync8(path)) {
1736
1841
  writer(`no such source: ${flags.source}
1737
1842
  `);
1738
1843
  return;
@@ -1740,7 +1845,7 @@ async function runLogs(flags) {
1740
1845
  writer(await readFile(path, "utf8"));
1741
1846
  }
1742
1847
  async function resolveTodaySlug(dataDir) {
1743
- const link = join10(dataDir, "logs", "today");
1848
+ const link = join13(dataDir, "logs", "today");
1744
1849
  try {
1745
1850
  return await readlink(link);
1746
1851
  } catch {
@@ -1749,18 +1854,18 @@ async function resolveTodaySlug(dataDir) {
1749
1854
  }
1750
1855
  function resolveSourcePath(dayDir, source) {
1751
1856
  if (source.startsWith("agent-")) {
1752
- if (source.endsWith(".acp")) return join10(dayDir, `${source.slice(0, -4)}.acp.jsonl`);
1753
- return join10(dayDir, `${source}.log`);
1857
+ if (source.endsWith(".acp")) return join13(dayDir, `${source.slice(0, -4)}.acp.jsonl`);
1858
+ return join13(dayDir, `${source}.log`);
1754
1859
  }
1755
1860
  if (KNOWN_SOURCES.includes(source))
1756
- return join10(dayDir, `${source}.log`);
1757
- return join10(dayDir, source);
1861
+ return join13(dayDir, `${source}.log`);
1862
+ return join13(dayDir, source);
1758
1863
  }
1759
1864
  async function dumpByTurn(dayDir, turnId, writer) {
1760
1865
  const entries = await readdir2(dayDir);
1761
1866
  const taps = entries.filter((e) => e.endsWith(".acp.jsonl")).sort();
1762
1867
  for (const f of taps) {
1763
- const text = await readFile(join10(dayDir, f), "utf8");
1868
+ const text = await readFile(join13(dayDir, f), "utf8");
1764
1869
  for (const line of text.split("\n")) {
1765
1870
  if (!line) continue;
1766
1871
  try {
@@ -1774,14 +1879,14 @@ async function dumpByTurn(dayDir, turnId, writer) {
1774
1879
 
1775
1880
  // src/commands/start.ts
1776
1881
  import { randomBytes as randomBytes2 } from "crypto";
1777
- import { resolve as resolve4 } from "path";
1882
+ import { resolve as resolve5 } from "path";
1778
1883
  async function runStart(flags) {
1779
1884
  if (flags.printToken) {
1780
1885
  process.stdout.write(`${randomBytes2(32).toString("hex")}
1781
1886
  `);
1782
1887
  return;
1783
1888
  }
1784
- const dataRoot = resolve4(process.cwd(), flags.dataDir ?? "./data");
1889
+ const dataRoot = resolve5(process.cwd(), flags.dataDir ?? "./data");
1785
1890
  const layout = resolveDataLayout(dataRoot);
1786
1891
  const handle = await startDaemon({
1787
1892
  cliFlags: flags,
@@ -1794,7 +1899,7 @@ async function runStart(flags) {
1794
1899
  }
1795
1900
 
1796
1901
  // src/commands/status.ts
1797
- import { readFileSync as readFileSync5 } from "fs";
1902
+ import { readFileSync as readFileSync6 } from "fs";
1798
1903
  import { dirname as dirname8 } from "path";
1799
1904
  import chalk2 from "chalk";
1800
1905
  async function probe(url, timeoutMs = 2e3) {
@@ -1819,7 +1924,7 @@ async function collectStatus(opts) {
1819
1924
  agents: []
1820
1925
  };
1821
1926
  }
1822
- const cfg = loadZooidConfig(readFileSync5(found.path, "utf8"), {
1927
+ const cfg = loadZooidConfig(readFileSync6(found.path, "utf8"), {
1823
1928
  configDir: dirname8(found.path)
1824
1929
  });
1825
1930
  const matrixEntry = Object.entries(cfg.transports).find(
@@ -1850,7 +1955,7 @@ async function runStatus(flags) {
1850
1955
  if (port === void 0) {
1851
1956
  const found = findConfigFile(cwd);
1852
1957
  if (found) {
1853
- const cfg = loadZooidConfig(readFileSync5(found.path, "utf8"), {
1958
+ const cfg = loadZooidConfig(readFileSync6(found.path, "utf8"), {
1854
1959
  configDir: dirname8(found.path)
1855
1960
  });
1856
1961
  const matrix = findMatrixTransport(cfg);
@@ -1887,7 +1992,7 @@ cli.command("start", "Run the daemon (production entry-point)").option("--data <
1887
1992
  });
1888
1993
  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
1994
  "--watch-web [path]",
1890
- "Run vite build --watch on @zoon/web. Path defaults to sibling ../zoon/packages/web."
1995
+ "Run vite build --watch on @zooid/zoon-web. Path defaults to sibling ../zoon/packages/web."
1891
1996
  ).action(async (flags) => {
1892
1997
  await runDev({
1893
1998
  dataDir: flags.data,
@@ -1904,14 +2009,14 @@ cli.command(
1904
2009
  ).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
2010
  if (source === "prune") {
1906
2011
  await runLogs({
1907
- dataDir: resolve5(process.cwd(), flags.data),
2012
+ dataDir: resolve6(process.cwd(), flags.data),
1908
2013
  subcommand: "prune",
1909
2014
  keep: Number(flags.keep)
1910
2015
  });
1911
2016
  return;
1912
2017
  }
1913
2018
  await runLogs({
1914
- dataDir: resolve5(process.cwd(), flags.data),
2019
+ dataDir: resolve6(process.cwd(), flags.data),
1915
2020
  source,
1916
2021
  day: flags.day,
1917
2022
  turn: flags.turn,