volute 0.53.0 → 0.54.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.
Files changed (147) hide show
  1. package/dist/auto-upgrade-HEVZKPTF.js +46 -0
  2. package/dist/{backup-WWXVIMYQ.js → backup-QRNX6RK7.js} +11 -11
  3. package/dist/channels-CKCY64TD.js +203 -0
  4. package/dist/{chat-WK2BLM7B.js → chat-ZUGNL5GS.js} +9 -9
  5. package/dist/{chunk-UTKUO2KY.js → chunk-3LHUXY3E.js} +2 -2
  6. package/dist/{chunk-PIZDPY4G.js → chunk-444RR3YF.js} +4 -4
  7. package/dist/{chunk-VDZ4I4ZP.js → chunk-56IT2ZXF.js} +3 -3
  8. package/dist/{chunk-OXWUEU5V.js → chunk-5U5GLZSD.js} +2 -2
  9. package/dist/{chunk-LN7XJ56R.js → chunk-7XKPG66D.js} +9 -9
  10. package/dist/{chunk-R772ET2M.js → chunk-AAPNU7GA.js} +4 -0
  11. package/dist/{chunk-7ARONCZW.js → chunk-ATZF2RNJ.js} +3 -3
  12. package/dist/{chunk-6NLW72HP.js → chunk-CKPAVQNK.js} +1 -1
  13. package/dist/{chunk-RMADJFA7.js → chunk-CLCHOXLR.js} +1 -1
  14. package/dist/{chunk-IQ7VNWH6.js → chunk-HKGYZVWN.js} +1 -1
  15. package/dist/{chunk-HASADR3E.js → chunk-LGXVGIWN.js} +66 -25
  16. package/dist/{chunk-P2TBUKLW.js → chunk-LKUGUQUW.js} +3 -3
  17. package/dist/{chunk-AP2KQMMW.js → chunk-OAYY3WF3.js} +11 -3
  18. package/dist/{chunk-UO4MZQNO.js → chunk-OGPVJ53N.js} +14 -6
  19. package/dist/{chunk-M77B73VV.js → chunk-OIOW3FPT.js} +7 -2
  20. package/dist/chunk-OT5SXF7L.js +904 -0
  21. package/dist/{chunk-EDR47UE2.js → chunk-OYF25B62.js} +1 -1
  22. package/dist/{chunk-TAWRJ24T.js → chunk-PPSN2MLC.js} +2 -2
  23. package/dist/{chunk-5DBGQBQR.js → chunk-RGHF3OK5.js} +21 -10
  24. package/dist/{chunk-RQRO6PC7.js → chunk-VDUCLVZI.js} +305 -31
  25. package/dist/{chunk-23UJGVYP.js → chunk-WHL6PKYP.js} +10 -10
  26. package/dist/{chunk-HTB7KDRU.js → chunk-WZZCA23D.js} +5 -4
  27. package/dist/{chunk-UDZLPR34.js → chunk-YSRYCS3N.js} +13 -8
  28. package/dist/{chunk-5N32EN4O.js → chunk-Z4Q6TQWL.js} +1 -1
  29. package/dist/{chunk-BDHKCLWK.js → chunk-ZNHL5KQJ.js} +1 -1
  30. package/dist/cli.js +19 -19
  31. package/dist/{clock-SH4NQC2F.js → clock-X7V3C7NL.js} +3 -3
  32. package/dist/{cloud-sync-MJVJ33LD.js → cloud-sync-52YNJRMR.js} +11 -11
  33. package/dist/{daemon-restart-BIGU4OKS.js → daemon-restart-QWHN5VMU.js} +4 -4
  34. package/dist/daemon.js +330 -746
  35. package/dist/{db-IPXRJUI7.js → db-I4EH5SLF.js} +3 -1
  36. package/dist/{delivery-manager-FDHCZ7PU.js → delivery-manager-7R2YIQ2W.js} +11 -7
  37. package/dist/{delivery-notices-65TB3CBX.js → delivery-notices-RZQ6GONP.js} +3 -3
  38. package/dist/{delivery-router-RHANNTAC.js → delivery-router-QS4NLJR6.js} +3 -1
  39. package/dist/{down-QSNEMTTX.js → down-UN2EDX2Y.js} +3 -3
  40. package/dist/{echo-text-KU6G4Z7V.js → echo-text-73CYQRL7.js} +14 -14
  41. package/dist/{exec-ABD3DYA2.js → exec-Z34TTUO3.js} +1 -1
  42. package/dist/{extensions-VACUKFA5.js → extensions-QT65JGV6.js} +21 -11
  43. package/dist/{isolation-GVV2IBAU.js → isolation-UQPDO34R.js} +1 -1
  44. package/dist/{message-delivery-TUXM3AAE.js → message-delivery-TRKT4T2P.js} +11 -11
  45. package/dist/migrate-name-placeholder-C7NCNLWF.js +37 -0
  46. package/dist/{mind-IX5AWUST.js → mind-CT6ANXK4.js} +12 -12
  47. package/dist/{mind-contacts-BKUOUJPV.js → mind-contacts-PEXCDRXA.js} +8 -8
  48. package/dist/{mind-list-KNU34SUD.js → mind-list-7KBLPLES.js} +2 -2
  49. package/dist/{mind-service-WHFBUKZE.js → mind-service-7EFIJ3ZA.js} +11 -11
  50. package/dist/{mind-sleep-D3DEYAWG.js → mind-sleep-24HV4RCA.js} +5 -5
  51. package/dist/{mind-status-BPTLQ5Z6.js → mind-status-4YW4PHMO.js} +6 -3
  52. package/dist/{package-BN5KMU54.js → package-F6S5NHY2.js} +4 -4
  53. package/dist/{prompts-HNKGCAEY.js → prompts-JECJIFPQ.js} +1 -1
  54. package/dist/{scheduler-5X5UIQOQ.js → scheduler-73Q4MQ7Y.js} +6 -6
  55. package/dist/{seed-LHZ33UB4.js → seed-DRKAP3XV.js} +1 -1
  56. package/dist/{seed-cmd-U4RVJ3UA.js → seed-cmd-6IMTO3LG.js} +2 -2
  57. package/dist/{seed-create-OQU4KNCB.js → seed-create-VNLDYRFK.js} +1 -1
  58. package/dist/{seed-readiness-WJYYRRCK.js → seed-readiness-H5TTPAQH.js} +2 -2
  59. package/dist/{seed-sprout-SFUYH2RG.js → seed-sprout-PPGYGK4G.js} +8 -8
  60. package/dist/{send-FWZAMDBN.js → send-UXUQKO4N.js} +9 -9
  61. package/dist/{service-XQPMZAGI.js → service-T4P5W6RS.js} +2 -2
  62. package/dist/{service-install-2EWD7KXD.js → service-install-R4SNYUVO.js} +3 -3
  63. package/dist/{setup-RVSGCIF3.js → setup-YUAQMDVZ.js} +4 -4
  64. package/dist/skills/dreaming/SKILL.md +2 -2
  65. package/dist/skills/dreaming/references/INSTALL.md +1 -1
  66. package/dist/skills/tending/SKILL.md +2 -0
  67. package/dist/skills/volute-mind/references/integrations.md +3 -2
  68. package/dist/skills/volute-mind/references/routing.md +18 -5
  69. package/dist/{skills-QRPU6BWY.js → skills-GMQ5UIIW.js} +2 -2
  70. package/dist/{sleep-manager-NWUCRBZB.js → sleep-manager-KHI65FEX.js} +11 -11
  71. package/dist/{spirit-I5ECLGRI.js → spirit-Q5VGFWUI.js} +5 -3
  72. package/dist/{spirit-availability-NIUUYKU2.js → spirit-availability-G5FMWKRH.js} +12 -12
  73. package/dist/{sprout-A2PMEF7Z.js → sprout-PMTRL7QN.js} +1 -1
  74. package/dist/{src-LCSOQY6Z.js → src-3FIW7DLH.js} +241 -87
  75. package/dist/src-H3UCOP6B.js +516 -0
  76. package/dist/{src-HGNE2XG4.js → src-LTTQVN2Z.js} +1 -1
  77. package/dist/{status-EYG4A3OK.js → status-LCKTTIWS.js} +5 -5
  78. package/dist/{system-events-YTOU66QI.js → system-events-Q7IVZ5T4.js} +1 -1
  79. package/dist/{systems-2DIJY4RF.js → systems-HL7SGSW7.js} +6 -6
  80. package/dist/{turn-tracker-NNHS6MAR.js → turn-tracker-HZDPMSCC.js} +2 -2
  81. package/dist/{up-UGBHH743.js → up-XZDSJVMU.js} +3 -3
  82. package/dist/{update-CIWI2JYA.js → update-ILCPVNDD.js} +2 -2
  83. package/dist/{upgrade-LIE46UBB.js → upgrade-XY2J7NWZ.js} +9 -1
  84. package/dist/{variant-cleanup-CZDLIY3F.js → variant-cleanup-QHGGY7ND.js} +6 -6
  85. package/dist/{version-notify-SJVAQLZI.js → version-notify-VNVFX6UP.js} +19 -4
  86. package/dist/web-assets/assets/{index-b7G7ZsVk.js → index-CwtxGwe_.js} +16 -16
  87. package/dist/web-assets/assets/index-DhM1yYRN.css +1 -0
  88. package/dist/web-assets/index.html +2 -2
  89. package/package.json +4 -4
  90. package/packages/extensions/intentions/dist/ui/assets/index-3FneLCKR.css +1 -0
  91. package/packages/extensions/intentions/dist/ui/assets/index-CQz4aceM.js +67 -0
  92. package/packages/extensions/{plan → intentions}/dist/ui/index.html +3 -3
  93. package/packages/extensions/intentions/skills/intention-review/SKILL.md +38 -0
  94. package/packages/extensions/intentions/skills/intentions/SKILL.md +49 -0
  95. package/packages/extensions/intentions/skills/intentions/scripts/intentions-hook.sh +45 -0
  96. package/packages/extensions/pages/skills/commons-gardening/SKILL.md +50 -0
  97. package/packages/extensions/pages/skills/pages/SKILL.md +18 -13
  98. package/templates/_base/.init/.config/prompts.json +2 -2
  99. package/templates/{pi → _base}/.init/.config/routes.json +1 -0
  100. package/templates/_base/.init/memory/dreams/.gitkeep +0 -0
  101. package/templates/_base/home/.config/routes.json +1 -1
  102. package/templates/_base/home/VOLUTE.md +1 -1
  103. package/templates/_base/src/lib/context-breakdown.ts +11 -3
  104. package/templates/_base/src/lib/routing.ts +2 -2
  105. package/templates/_base/src/lib/startup.ts +4 -2
  106. package/templates/claude/.init/CLAUDE.md +4 -0
  107. package/templates/claude/src/agent.ts +28 -12
  108. package/templates/claude/src/lib/message-channel.ts +43 -15
  109. package/templates/claude/src/lib/recover.ts +69 -0
  110. package/templates/claude/src/lib/stream-consumer.ts +23 -5
  111. package/templates/claude/volute-template.json +1 -1
  112. package/templates/codex/.init/AGENTS.md +4 -0
  113. package/templates/codex/volute-template.json +1 -1
  114. package/templates/pi/.init/MINDS.md +4 -0
  115. package/templates/pi/src/lib/mechanics-doc.ts +30 -0
  116. package/templates/pi/src/server.ts +3 -1
  117. package/templates/pi/volute-template.json +1 -1
  118. package/dist/channels-LW5ZLMSQ.js +0 -90
  119. package/dist/skills/plan-coordinator/SKILL.md +0 -60
  120. package/dist/src-UDMJZ6MH.js +0 -426
  121. package/dist/web-assets/assets/index-BHSU9r-4.css +0 -1
  122. package/packages/extensions/plan/dist/ui/assets/index-B8s8wxyt.js +0 -67
  123. package/packages/extensions/plan/dist/ui/assets/index-jIkrt-vI.css +0 -1
  124. package/packages/extensions/plan/skills/plan/SKILL.md +0 -43
  125. package/packages/extensions/plan/skills/plan/scripts/plan-hook.sh +0 -37
  126. package/templates/claude/.init/.config/routes.json +0 -11
  127. package/templates/codex/.init/.config/routes.json +0 -11
  128. package/dist/{accept-I4GVGWA3.js → accept-RSDIVJL6.js} +3 -3
  129. package/dist/{bridge-KU2IKSH3.js → bridge-YK7NTRU5.js} +3 -3
  130. package/dist/{create-C3XZACTV.js → create-M727VUDF.js} +3 -3
  131. package/dist/{env-BJGM6FBA.js → env-7AZFBPWX.js} +7 -7
  132. package/dist/{extension-B2W52WZY.js → extension-MRYJWSMV.js} +3 -3
  133. package/dist/{files-CHPNEQXN.js → files-PCKBQHAY.js} +3 -3
  134. package/dist/{list-R2GDWOEE.js → list-UQGIW7OF.js} +3 -3
  135. package/dist/{login-EZ4RRDQX.js → login-4NUS72Z5.js} +3 -3
  136. package/dist/{login-JDOAK465.js → login-57MMYILL.js} +4 -4
  137. package/dist/{logout-MWSIUOQU.js → logout-BYV433X6.js} +3 -3
  138. package/dist/{logout-K6ZGAJY5.js → logout-YFZAJRPY.js} +3 -3
  139. package/dist/{mind-history-RVF6KJ7K.js → mind-history-RZMMCHMX.js} +9 -9
  140. package/dist/{mind-wake-DCDFFQKX.js → mind-wake-LDGPRA42.js} +3 -3
  141. package/dist/{read-J5RM674N.js → read-RJ5NAW5J.js} +3 -3
  142. package/dist/{register-53LBGSDC.js → register-WNCSOTAU.js} +3 -3
  143. package/dist/{reject-K2A4UK2Y.js → reject-4NFLXKRN.js} +3 -3
  144. package/dist/{restart-R2WFNF5K.js → restart-B4GNMVF2.js} +7 -7
  145. package/dist/{skill-DXLLSUHB.js → skill-QYTBUDNQ.js} +7 -7
  146. package/dist/{start-XDOUAWN3.js → start-BDHJVOVX.js} +6 -6
  147. package/dist/{stop-O62RTEMD.js → stop-45QYSGYS.js} +7 -7
@@ -4,44 +4,96 @@ import {
4
4
  } from "./chunk-RG5TOL4O.js";
5
5
  import {
6
6
  getAllSites,
7
+ getMindsWithSites,
7
8
  getPublishedPages,
8
9
  getRecentPages,
9
10
  getSystemPages,
10
11
  initDb,
11
12
  syncPublishedPages,
12
13
  syncSystemPages
13
- } from "./chunk-R772ET2M.js";
14
+ } from "./chunk-AAPNU7GA.js";
14
15
  import "./chunk-K3NQKI34.js";
15
16
 
16
17
  // packages/extensions/pages/src/index.ts
17
- import { resolve as resolve5 } from "path";
18
+ import { resolve as resolve6 } from "path";
18
19
 
19
20
  // packages/extensions/pages/src/commands.ts
20
- import { cpSync, existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2 } from "fs";
21
- import { relative as relative2, resolve as resolve2 } from "path";
21
+ import { cpSync, existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3, rmSync as rmSync2, statSync as statSync2 } from "fs";
22
+ import { relative as relative2, resolve as resolve3 } from "path";
23
+
24
+ // packages/extensions/pages/src/commons.ts
25
+ import { existsSync, readFileSync, writeFileSync } from "fs";
26
+ import { resolve } from "path";
27
+ function commonsReport(repoDir, systemFiles, mindsWithSites) {
28
+ const index = systemFiles.find((f) => f === "index.md" || f === "index.html");
29
+ if (!index) {
30
+ return { hasIndex: false, orphanPages: [...systemFiles], unlinkedMinds: [...mindsWithSites] };
31
+ }
32
+ const contentOf = (f) => {
33
+ try {
34
+ return readFileSync(resolve(repoDir, f), "utf-8");
35
+ } catch {
36
+ return "";
37
+ }
38
+ };
39
+ const reached = /* @__PURE__ */ new Set([index]);
40
+ const queue = [index];
41
+ while (queue.length > 0) {
42
+ const content = contentOf(queue.shift());
43
+ for (const f of systemFiles) {
44
+ if (!reached.has(f) && content.includes(f)) {
45
+ reached.add(f);
46
+ queue.push(f);
47
+ }
48
+ }
49
+ }
50
+ const orphanPages = systemFiles.filter((f) => !reached.has(f));
51
+ const reachableText = [...reached].map(contentOf).join("\n");
52
+ const unlinkedMinds = mindsWithSites.filter(
53
+ (m2) => !reachableText.includes(`../${m2}/`) && !reachableText.includes(`/ext/pages/public/${m2}/`)
54
+ );
55
+ return { hasIndex: true, orphanPages, unlinkedMinds };
56
+ }
57
+ async function maybeSendCommonsCue(ctx, repoDir) {
58
+ const cueFlag = resolve(ctx.dataDir, ".commons-cue-sent");
59
+ const hasIndex = existsSync(resolve(repoDir, "index.md")) || existsSync(resolve(repoDir, "index.html"));
60
+ if (hasIndex || existsSync(cueFlag)) return;
61
+ const spirit = ctx.getSpiritName();
62
+ if (!spirit) return;
63
+ try {
64
+ if (!await ctx.getMindDir(spirit)) return;
65
+ await ctx.recordNotice(
66
+ spirit,
67
+ "The commons \u2014 the shared pages at pages/_system/ that every mind here can edit \u2014 has no index yet. When you have a quiet moment, you might make one: a page that says what this place is, in your own voice, with room in it for the others. The commons-gardening skill describes the craft."
68
+ );
69
+ writeFileSync(cueFlag, (/* @__PURE__ */ new Date()).toISOString());
70
+ } catch (err) {
71
+ console.warn(`[pages] commons cue failed: ${err.message}`);
72
+ }
73
+ }
22
74
 
23
75
  // packages/extensions/pages/src/shared-pages.ts
24
76
  import { execFile as execFileCb } from "child_process";
25
77
  import { createHash } from "crypto";
26
78
  import {
27
79
  chmodSync,
28
- existsSync,
80
+ existsSync as existsSync2,
29
81
  mkdirSync,
30
82
  readdirSync,
31
- readFileSync,
83
+ readFileSync as readFileSync2,
32
84
  rmSync,
33
85
  statSync,
34
- writeFileSync
86
+ writeFileSync as writeFileSync2
35
87
  } from "fs";
36
- import { relative, resolve } from "path";
88
+ import { relative, resolve as resolve2 } from "path";
37
89
  function isolationFrom(ctx) {
38
90
  return { isIsolationEnabled: ctx.isIsolationEnabled, getMindUser: ctx.getMindUser };
39
91
  }
40
92
  function execAsync(cmd, args) {
41
- return new Promise((resolve6, reject) => {
93
+ return new Promise((resolve7, reject) => {
42
94
  execFileCb(cmd, args, (err) => {
43
95
  if (err) reject(err);
44
- else resolve6();
96
+ else resolve7();
45
97
  });
46
98
  });
47
99
  }
@@ -51,7 +103,7 @@ function gitExec(args, opts, isolation) {
51
103
  if (isolation?.isIsolationEnabled()) prefix.push("-c", "safe.directory=*");
52
104
  if (args[0] === "commit") prefix.push(...IDENTITY_ARGS);
53
105
  const fullArgs = prefix.length ? [...prefix, ...args] : args;
54
- return new Promise((resolve6, reject) => {
106
+ return new Promise((resolve7, reject) => {
55
107
  execFileCb("git", fullArgs, { cwd: opts.cwd }, (err, stdout, stderr) => {
56
108
  if (err) {
57
109
  const e = err;
@@ -59,16 +111,16 @@ function gitExec(args, opts, isolation) {
59
111
  e.stdout = stdout;
60
112
  reject(err);
61
113
  } else {
62
- resolve6(stdout);
114
+ resolve7(stdout);
63
115
  }
64
116
  });
65
117
  });
66
118
  }
67
119
  function readWorktreeGitDir(worktreePath2) {
68
- const dotGit = resolve(worktreePath2, ".git");
69
- if (!existsSync(dotGit)) return null;
120
+ const dotGit = resolve2(worktreePath2, ".git");
121
+ if (!existsSync2(dotGit)) return null;
70
122
  try {
71
- const content = readFileSync(dotGit, "utf-8").trim();
123
+ const content = readFileSync2(dotGit, "utf-8").trim();
72
124
  const match = content.match(/^gitdir:\s*(.+)$/);
73
125
  return match ? match[1] : null;
74
126
  } catch (err) {
@@ -77,13 +129,13 @@ function readWorktreeGitDir(worktreePath2) {
77
129
  }
78
130
  }
79
131
  function pagesRepoDir(dataDir) {
80
- return resolve(dataDir, "repo");
132
+ return resolve2(dataDir, "repo");
81
133
  }
82
134
  function worktreePath(mindDir) {
83
- return resolve(mindDir, "home", "pages", "_system");
135
+ return resolve2(mindDir, "home", "pages", "_system");
84
136
  }
85
137
  function pagesIsolationChownPaths(mindDir, wtGitDir) {
86
- const paths = [resolve(mindDir, "home", "pages")];
138
+ const paths = [resolve2(mindDir, "home", "pages")];
87
139
  if (wtGitDir) paths.push(wtGitDir);
88
140
  return paths;
89
141
  }
@@ -98,16 +150,16 @@ async function isRepoValid(dir, isolation) {
98
150
  async function ensurePagesRepo(dataDir, isolation) {
99
151
  const dir = pagesRepoDir(dataDir);
100
152
  mkdirSync(dir, { recursive: true });
101
- if (existsSync(resolve(dir, ".git"))) {
153
+ if (existsSync2(resolve2(dir, ".git"))) {
102
154
  if (await isRepoValid(dir, isolation)) return;
103
155
  console.warn("[pages] repo invalid or incomplete, re-initializing");
104
- rmSync(resolve(dir, ".git"), { recursive: true, force: true });
156
+ rmSync(resolve2(dir, ".git"), { recursive: true, force: true });
105
157
  }
106
158
  const isIso = isolation?.isIsolationEnabled() ?? false;
107
159
  const initArgs = isIso ? ["init", "--shared=group"] : ["init"];
108
160
  await gitExec(initArgs, { cwd: dir }, isolation);
109
161
  await gitExec(["checkout", "-b", "main"], { cwd: dir }, isolation);
110
- writeFileSync(resolve(dir, ".gitkeep"), "");
162
+ writeFileSync2(resolve2(dir, ".gitkeep"), "");
111
163
  await gitExec(["add", "-A"], { cwd: dir }, isolation);
112
164
  await gitExec(["commit", "-m", "init pages repo"], { cwd: dir }, isolation);
113
165
  if (isIso) {
@@ -126,8 +178,15 @@ async function addPagesWorktree(mindName, mindDir, dataDir, isolation) {
126
178
  return;
127
179
  }
128
180
  const wt = worktreePath(mindDir);
129
- if (existsSync(wt)) return;
130
- mkdirSync(resolve(mindDir, "home", "pages"), { recursive: true });
181
+ if (existsSync2(wt)) {
182
+ if (!existsSync2(resolve2(wt, ".git"))) {
183
+ console.warn(
184
+ `[pages] ${wt} exists but is not a worktree \u2014 shared publishing will fail for ${mindName}. Move it aside and restart the mind to provision one.`
185
+ );
186
+ }
187
+ return;
188
+ }
189
+ mkdirSync(resolve2(mindDir, "home", "pages"), { recursive: true });
131
190
  let branchExists = false;
132
191
  try {
133
192
  await gitExec(["rev-parse", "--verify", mindName], { cwd: dir }, isolation);
@@ -266,13 +325,27 @@ async function pagesPullAndMerge(mindName, mindDir, dataDir, message, isolation)
266
325
  { cwd: dir },
267
326
  isolation
268
327
  );
328
+ let changedFiles = [];
329
+ const priorAuthors = {};
330
+ try {
331
+ changedFiles = (await gitExec(["diff", "--name-only", "HEAD^", "HEAD"], { cwd: dir }, isolation)).trim().split("\n").filter(Boolean);
332
+ for (const file of changedFiles) {
333
+ const names = (await gitExec(["log", "--format=%an", "HEAD^", "--", file], { cwd: dir }, isolation)).trim().split("\n").filter(Boolean);
334
+ const unique = [...new Set(names)].filter((n) => n !== mindName && n !== "volute");
335
+ if (unique.length > 0) priorAuthors[file] = unique;
336
+ }
337
+ } catch (err) {
338
+ console.warn(`[pages] contributor lookup failed: ${err.message}`);
339
+ }
269
340
  try {
270
341
  await gitExec(["reset", "--hard", "main"], { cwd: wt }, isolation);
271
342
  } catch (err) {
272
343
  console.error(`[pages] branch reset failed for ${mindName}`, err);
273
344
  return {
274
345
  ok: true,
275
- message: "Published to main, but branch reset failed \u2014 run 'volute pages pull' to sync"
346
+ message: "Published to main, but branch reset failed \u2014 run 'volute pages pull' to sync",
347
+ changedFiles,
348
+ priorAuthors
276
349
  };
277
350
  }
278
351
  if (isolation?.isIsolationEnabled()) {
@@ -281,10 +354,10 @@ async function pagesPullAndMerge(mindName, mindDir, dataDir, message, isolation)
281
354
  } catch {
282
355
  }
283
356
  }
284
- return { ok: true };
357
+ return { ok: true, changedFiles, priorAuthors };
285
358
  });
286
359
  }
287
- function collectHtmlFiles(dir) {
360
+ function collectPageFiles(dir) {
288
361
  const files = [];
289
362
  function walk(d2) {
290
363
  let items;
@@ -296,10 +369,10 @@ function collectHtmlFiles(dir) {
296
369
  }
297
370
  for (const item of items) {
298
371
  if (item.startsWith(".")) continue;
299
- const full = resolve(d2, item);
372
+ const full = resolve2(d2, item);
300
373
  try {
301
374
  const s = statSync(full);
302
- if (s.isFile() && item.endsWith(".html")) {
375
+ if (s.isFile() && (item.endsWith(".html") || item.endsWith(".md"))) {
303
376
  files.push(relative(dir, full));
304
377
  } else if (s.isDirectory()) {
305
378
  walk(full);
@@ -316,9 +389,12 @@ function collectHtmlFiles(dir) {
316
389
  function hashFiles(baseDir, files) {
317
390
  return files.map((file) => ({
318
391
  file,
319
- hash: createHash("sha256").update(readFileSync(resolve(baseDir, file))).digest("hex")
392
+ hash: createHash("sha256").update(readFileSync2(resolve2(baseDir, file))).digest("hex")
320
393
  }));
321
394
  }
395
+ function isPageFile(f) {
396
+ return f.endsWith(".html") || f.endsWith(".md");
397
+ }
322
398
  async function pagesStatus(mindDir, isolation) {
323
399
  const wt = worktreePath(mindDir);
324
400
  const errors = [];
@@ -346,14 +422,14 @@ async function pagesStatus(mindDir, isolation) {
346
422
  if (match) uncommittedFiles.add(match[1]);
347
423
  }
348
424
  }
349
- const mainSet = new Set(mainFiles.filter((f) => f.endsWith(".html")));
350
- const allHtml = /* @__PURE__ */ new Set([
351
- ...mainFiles.filter((f) => f.endsWith(".html")),
352
- ...branchFiles.filter((f) => f.endsWith(".html")),
353
- ...[...uncommittedFiles].filter((f) => f.endsWith(".html"))
425
+ const mainSet = new Set(mainFiles.filter(isPageFile));
426
+ const allPageFiles = /* @__PURE__ */ new Set([
427
+ ...mainFiles.filter(isPageFile),
428
+ ...branchFiles.filter(isPageFile),
429
+ ...[...uncommittedFiles].filter(isPageFile)
354
430
  ]);
355
- if (allHtml.size === 0) return "No shared pages found.";
356
- const lines = [...allHtml].sort().map((file) => {
431
+ if (allPageFiles.size === 0) return "No shared pages found.";
432
+ const lines = [...allPageFiles].sort().map((file) => {
357
433
  const onMain = mainSet.has(file);
358
434
  const isUncommitted = uncommittedFiles.has(file);
359
435
  let status;
@@ -388,7 +464,7 @@ function createCommands() {
388
464
  const mindName = ctx.mindName;
389
465
  if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
390
466
  if (flags.shared) {
391
- const mindDir2 = ctx.getMindDir(mindName);
467
+ const mindDir2 = await ctx.getMindDir(mindName);
392
468
  if (!mindDir2) return { error: `Mind not found: ${mindName}` };
393
469
  const message = args.message?.trim();
394
470
  if (!message)
@@ -408,29 +484,65 @@ function createCommands() {
408
484
  }
409
485
  let syncWarning = "";
410
486
  if (result.ok && ctx.db) {
411
- const repoDir = resolve2(ctx.dataDir, "repo");
487
+ const repoDir = resolve3(ctx.dataDir, "repo");
412
488
  try {
413
- syncSystemPages(ctx.db, hashFiles(repoDir, collectHtmlFiles(repoDir)), mindName);
489
+ syncSystemPages(ctx.db, hashFiles(repoDir, collectPageFiles(repoDir)), mindName);
414
490
  } catch (err) {
415
491
  console.error("[pages] failed to sync system pages to DB:", err);
416
492
  syncWarning = "\nWarning: failed to sync pages to dashboard \u2014 they may not appear in the UI until the next daemon restart.";
417
493
  }
418
494
  }
495
+ const files = result.changedFiles ?? [];
496
+ if (files.length > 0) {
497
+ const fileList = files.join(", ");
498
+ const pageFile = files.find(isPageFile);
499
+ ctx.publishActivity({
500
+ type: "page_published",
501
+ mind: mindName,
502
+ summary: `${mindName} tended the commons: ${fileList}`,
503
+ metadata: {
504
+ shared: true,
505
+ files,
506
+ message,
507
+ ...pageFile ? { iframeUrl: `/ext/pages/public/_system/${pageFile}` } : {}
508
+ }
509
+ });
510
+ try {
511
+ await ctx.announceToSystem(
512
+ `${mindName} tended the commons: ${fileList} \u2014 "${message}"`
513
+ );
514
+ const authorFiles = /* @__PURE__ */ new Map();
515
+ for (const [file, authors] of Object.entries(result.priorAuthors ?? {})) {
516
+ for (const a of authors)
517
+ authorFiles.set(a, [...authorFiles.get(a) ?? [], file]);
518
+ }
519
+ for (const [author, theirs] of authorFiles) {
520
+ await ctx.recordNotice(
521
+ author,
522
+ `${mindName} built on ${theirs.join(", ")} in the commons \u2014 ${theirs.length > 1 ? "pages" : "a page"} you helped write ("${message}"). \`volute pages log\` shows the trail.`
523
+ );
524
+ }
525
+ } catch (err) {
526
+ console.warn(
527
+ `[pages] shared publish notification failed: ${err.message}`
528
+ );
529
+ }
530
+ }
419
531
  return { output: (result.message || "Published shared pages.") + syncWarning };
420
532
  } catch (err) {
421
533
  return { error: `Shared publish failed: ${err.message}` };
422
534
  }
423
535
  }
424
536
  const remote = flags.remote;
425
- const mindDir = ctx.getMindDir(mindName);
537
+ const mindDir = await ctx.getMindDir(mindName);
426
538
  if (!mindDir) return { error: `Mind not found: ${mindName}` };
427
- const sourceDir = resolve2(mindDir, "home", "pages");
428
- if (!existsSync2(sourceDir)) return { error: "No pages directory found (home/pages/)" };
539
+ const sourceDir = resolve3(mindDir, "home", "pages");
540
+ if (!existsSync3(sourceDir)) return { error: "No pages directory found (home/pages/)" };
429
541
  const db = ctx.db;
430
542
  if (!db) return { error: "Database not available" };
431
- const snapshotDir = resolve2(ctx.dataDir, "sites", mindName);
543
+ const snapshotDir = resolve3(ctx.dataDir, "sites", mindName);
432
544
  try {
433
- if (existsSync2(snapshotDir)) rmSync2(snapshotDir, { recursive: true });
545
+ if (existsSync3(snapshotDir)) rmSync2(snapshotDir, { recursive: true });
434
546
  cpSync(sourceDir, snapshotDir, {
435
547
  recursive: true,
436
548
  filter: (src) => !src.endsWith("/_system") && !src.includes("/_system/")
@@ -480,8 +592,8 @@ function createCommands() {
480
592
  const allFiles = collectFiles(snapshotDir, snapshotDir);
481
593
  const files = {};
482
594
  for (const f of allFiles) {
483
- const fp = resolve2(snapshotDir, f);
484
- files[f] = readFileSync2(fp).toString("base64");
595
+ const fp = resolve3(snapshotDir, f);
596
+ files[f] = readFileSync3(fp).toString("base64");
485
597
  }
486
598
  try {
487
599
  const res = await fetch(`${config.apiUrl}/api/pages/publish/${mindName}`, {
@@ -521,7 +633,7 @@ Warning: remote publish failed: ${err.message}`;
521
633
  const allFlag = flags.all;
522
634
  const port = process.env.VOLUTE_DAEMON_PORT || "1618";
523
635
  if (allFlag) {
524
- const { getAllSites: getAllSites2, getSystemPages: getSystemPages2 } = await import("./db-IPXRJUI7.js");
636
+ const { getAllSites: getAllSites2, getSystemPages: getSystemPages2 } = await import("./db-I4EH5SLF.js");
525
637
  const sites = getAllSites2(db);
526
638
  const system = getSystemPages2(db);
527
639
  const lines2 = [];
@@ -543,7 +655,7 @@ Warning: remote publish failed: ${err.message}`;
543
655
  const mindName = ctx.mindName;
544
656
  if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
545
657
  if (flags.shared) {
546
- const mindDir2 = ctx.getMindDir(mindName);
658
+ const mindDir2 = await ctx.getMindDir(mindName);
547
659
  if (!mindDir2) return { error: `Mind not found: ${mindName}` };
548
660
  try {
549
661
  const status = await pagesStatus(mindDir2, isolationFrom(ctx));
@@ -552,11 +664,11 @@ Warning: remote publish failed: ${err.message}`;
552
664
  return { error: `Failed to check shared status: ${err.message}` };
553
665
  }
554
666
  }
555
- const mindDir = ctx.getMindDir(mindName);
667
+ const mindDir = await ctx.getMindDir(mindName);
556
668
  if (!mindDir) return { error: `Mind not found: ${mindName}` };
557
- const sourceDir = resolve2(mindDir, "home", "pages");
669
+ const sourceDir = resolve3(mindDir, "home", "pages");
558
670
  const published = new Set(getPublishedPages(db, mindName).map((p) => p.file));
559
- const draftFiles = existsSync2(sourceDir) ? collectFiles(sourceDir, sourceDir, [".html", ".md"]) : [];
671
+ const draftFiles = existsSync3(sourceDir) ? collectFiles(sourceDir, sourceDir, [".html", ".md"]) : [];
560
672
  const allFiles = /* @__PURE__ */ new Set([...published, ...draftFiles]);
561
673
  if (allFiles.size === 0) return { output: "No pages found." };
562
674
  const lines = [...allFiles].sort().map((file) => {
@@ -573,7 +685,7 @@ Warning: remote publish failed: ${err.message}`;
573
685
  handler: async (_parsed, ctx) => {
574
686
  const mindName = ctx.mindName;
575
687
  if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
576
- const mindDir = ctx.getMindDir(mindName);
688
+ const mindDir = await ctx.getMindDir(mindName);
577
689
  if (!mindDir) return { error: `Mind not found: ${mindName}` };
578
690
  try {
579
691
  const result = await pagesPull(mindName, mindDir, isolationFrom(ctx));
@@ -594,7 +706,7 @@ Warning: remote publish failed: ${err.message}`;
594
706
  handler: async ({ flags }, ctx) => {
595
707
  const mindName = ctx.mindName;
596
708
  if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
597
- const mindDir = ctx.getMindDir(mindName);
709
+ const mindDir = await ctx.getMindDir(mindName);
598
710
  if (!mindDir) return { error: `Mind not found: ${mindName}` };
599
711
  const limit = flags.limit ?? 20;
600
712
  try {
@@ -604,6 +716,29 @@ Warning: remote publish failed: ${err.message}`;
604
716
  return { error: `Failed to read shared log: ${err.message}` };
605
717
  }
606
718
  }
719
+ },
720
+ commons: {
721
+ description: "Curation report for the commons: index, orphaned pages, unrepresented minds",
722
+ handler: async (_parsed, ctx) => {
723
+ const db = ctx.db;
724
+ if (!db) return { error: "Database not available" };
725
+ const repoDir = resolve3(ctx.dataDir, "repo");
726
+ const files = collectPageFiles(repoDir);
727
+ const report = commonsReport(repoDir, files, getMindsWithSites(db));
728
+ const lines = [];
729
+ if (!report.hasIndex) {
730
+ lines.push("The commons has no index page yet (index.md or index.html).");
731
+ } else {
732
+ lines.push(`Index: ok (${files.length} pages in the commons)`);
733
+ }
734
+ lines.push(
735
+ report.orphanPages.length > 0 ? `Orphaned pages (not reachable from the index): ${report.orphanPages.join(", ")}` : "Orphaned pages: none"
736
+ );
737
+ lines.push(
738
+ report.unlinkedMinds.length > 0 ? `Minds with sites not linked from the commons: ${report.unlinkedMinds.join(", ")}` : "Resident sites: all linked"
739
+ );
740
+ return { output: lines.join("\n") };
741
+ }
607
742
  }
608
743
  };
609
744
  }
@@ -618,7 +753,7 @@ function collectFiles(dir, baseDir, ext) {
618
753
  }
619
754
  for (const item of items) {
620
755
  if (item.startsWith(".")) continue;
621
- const fullPath = resolve2(dir, item);
756
+ const fullPath = resolve3(dir, item);
622
757
  try {
623
758
  const s = statSync2(fullPath);
624
759
  const matchesExt = !ext || (Array.isArray(ext) ? ext.some((e) => item.endsWith(e)) : item.endsWith(ext));
@@ -636,7 +771,7 @@ function collectFiles(dir, baseDir, ext) {
636
771
 
637
772
  // packages/extensions/pages/src/routes.ts
638
773
  import { readFile, stat } from "fs/promises";
639
- import { extname, resolve as resolve4 } from "path";
774
+ import { extname, resolve as resolve5 } from "path";
640
775
  import { Hono } from "hono";
641
776
 
642
777
  // packages/extensions/pages/src/cache.ts
@@ -670,8 +805,8 @@ function getRecentPagesList(db, opts) {
670
805
  }
671
806
 
672
807
  // packages/extensions/pages/src/markdown.ts
673
- import { existsSync as existsSync3 } from "fs";
674
- import { dirname, relative as relative3, resolve as resolve3 } from "path";
808
+ import { existsSync as existsSync4 } from "fs";
809
+ import { dirname, relative as relative3, resolve as resolve4 } from "path";
675
810
  import DOMPurify from "isomorphic-dompurify";
676
811
 
677
812
  // node_modules/marked/lib/marked.esm.js
@@ -1974,15 +2109,15 @@ function parseFrontmatter(raw) {
1974
2109
  }
1975
2110
  function resolveStylesheet(mdFilePath, pagesRoot, frontmatterStyle) {
1976
2111
  if (frontmatterStyle) {
1977
- const abs = resolve3(pagesRoot, frontmatterStyle);
1978
- if (abs.startsWith(pagesRoot + "/") && existsSync3(abs)) return frontmatterStyle;
2112
+ const abs = resolve4(pagesRoot, frontmatterStyle);
2113
+ if (abs.startsWith(pagesRoot + "/") && existsSync4(abs)) return frontmatterStyle;
1979
2114
  }
1980
2115
  const dir = dirname(mdFilePath);
1981
- const localCss = resolve3(dir, "style.css");
1982
- if (existsSync3(localCss)) return relative3(pagesRoot, localCss);
2116
+ const localCss = resolve4(dir, "style.css");
2117
+ if (existsSync4(localCss)) return relative3(pagesRoot, localCss);
1983
2118
  if (dir !== pagesRoot) {
1984
- const rootCss = resolve3(pagesRoot, "style.css");
1985
- if (existsSync3(rootCss)) return "style.css";
2119
+ const rootCss = resolve4(pagesRoot, "style.css");
2120
+ if (existsSync4(rootCss)) return "style.css";
1986
2121
  }
1987
2122
  return null;
1988
2123
  }
@@ -2035,17 +2170,20 @@ function createRoutes(ctx) {
2035
2170
  const limit = rawLimit ? parseInt(rawLimit, 10) || 8 : 8;
2036
2171
  const recentPages = getRecentPagesList(ctx.db, { mind: mind || void 0, limit });
2037
2172
  return c.json(
2038
- recentPages.map((p) => ({
2039
- id: `page-${p.mind}-${p.file}`,
2040
- title: `${p.mind}/${p.file}`,
2041
- url: p.url ?? `/minds/${p.mind}/pages/${p.file}`,
2042
- date: p.modified,
2043
- author: p.mind,
2044
- bodyHtml: `<p>Page updated</p>`,
2045
- iframeUrl: `/ext/pages/public/${p.mind}/${p.file}`,
2046
- icon: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6"/><path d="M2 8h12M8 2c-2 2-2 10 0 12M8 2c2 2 2 10 0 12"/></svg>',
2047
- color: "purple"
2048
- }))
2173
+ recentPages.map((p) => {
2174
+ const isCommons = p.mind === "_system";
2175
+ return {
2176
+ id: `page-${p.mind}-${p.file}`,
2177
+ title: isCommons ? `commons \u2014 ${p.file}` : `${p.mind}/${p.file}`,
2178
+ url: p.url ?? `/minds/${p.mind}/pages/${p.file}`,
2179
+ date: p.modified,
2180
+ author: isCommons ? p.author ?? "commons" : p.mind,
2181
+ bodyHtml: isCommons ? `<p>Commons page tended${p.author ? ` by ${p.author}` : ""}</p>` : `<p>Page updated</p>`,
2182
+ iframeUrl: `/ext/pages/public/${p.mind}/${p.file}`,
2183
+ icon: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6"/><path d="M2 8h12M8 2c-2 2-2 10 0 12M8 2c2 2 2 10 0 12"/></svg>',
2184
+ color: "purple"
2185
+ };
2186
+ })
2049
2187
  );
2050
2188
  }).put("/publish/:name", ctx.requireSelf("name"), async (c) => {
2051
2189
  const name = c.req.param("name");
@@ -2099,15 +2237,15 @@ function createPublicRoutes(ctx) {
2099
2237
  let pagesRoot;
2100
2238
  let blockDotfiles = false;
2101
2239
  if (name === "_system") {
2102
- pagesRoot = resolve4(ctx.dataDir, "repo");
2240
+ pagesRoot = resolve5(ctx.dataDir, "repo");
2103
2241
  blockDotfiles = true;
2104
2242
  } else {
2105
- pagesRoot = resolve4(ctx.dataDir, "sites", name);
2243
+ pagesRoot = resolve5(ctx.dataDir, "sites", name);
2106
2244
  }
2107
2245
  const prefix = `/public/${name}`;
2108
2246
  const idx = c.req.path.indexOf(prefix);
2109
2247
  const wildcard = idx >= 0 ? c.req.path.slice(idx + prefix.length) : "/";
2110
- const requestedPath = resolve4(pagesRoot, wildcard.slice(1));
2248
+ const requestedPath = resolve5(pagesRoot, wildcard.slice(1));
2111
2249
  if (requestedPath !== pagesRoot && !requestedPath.startsWith(`${pagesRoot}/`))
2112
2250
  return c.text("Forbidden", 403);
2113
2251
  if (blockDotfiles) {
@@ -2118,12 +2256,12 @@ function createPublicRoutes(ctx) {
2118
2256
  let fileToServe = requestedPath;
2119
2257
  let fileStat = await stat(requestedPath).catch(() => null);
2120
2258
  if (fileStat?.isDirectory()) {
2121
- const indexPath = resolve4(requestedPath, "index.html");
2259
+ const indexPath = resolve5(requestedPath, "index.html");
2122
2260
  fileStat = await stat(indexPath).catch(() => null);
2123
2261
  if (fileStat?.isFile()) {
2124
2262
  fileToServe = indexPath;
2125
2263
  } else {
2126
- const mdIndexPath = resolve4(requestedPath, "index.md");
2264
+ const mdIndexPath = resolve5(requestedPath, "index.md");
2127
2265
  fileStat = await stat(mdIndexPath).catch(() => null);
2128
2266
  if (fileStat?.isFile()) {
2129
2267
  fileToServe = mdIndexPath;
@@ -2162,21 +2300,22 @@ function createPublicRoutes(ctx) {
2162
2300
  }
2163
2301
 
2164
2302
  // packages/extensions/pages/src/index.ts
2165
- var assetsDir = resolve5(import.meta.dirname, "../dist/ui");
2166
- var skillsDir = resolve5(import.meta.dirname, "../skills");
2303
+ var assetsDir = resolve6(import.meta.dirname, "../dist/ui");
2304
+ var skillsDir = resolve6(import.meta.dirname, "../skills");
2167
2305
  var repoReady = null;
2168
2306
  var src_default = createExtension({
2169
2307
  id: "pages",
2170
2308
  name: "Pages",
2171
2309
  version: "0.1.0",
2172
2310
  description: "Publish and serve web pages from mind directories",
2173
- mindDoc: "Publish web pages others can visit \u2014 finished creative work, essays, experiments, anything you want to give a lasting home on the web.",
2311
+ mindDoc: "Publish web pages others can visit \u2014 essays, experiments, works in progress, anything you want to give a lasting home on the web. A page doesn't have to be finished to be worth publishing, and you can revise it any time. And the commons: shared pages at pages/_system/ that every mind here tends together. Your changes are announced, pages remember their authors, and your entry on the residents page is yours to write.",
2174
2312
  initDb,
2175
2313
  routes: (ctx) => createRoutes(ctx),
2176
2314
  publicRoutes: (ctx) => createPublicRoutes(ctx),
2177
2315
  commands: createCommands(),
2178
2316
  skillsDir,
2179
2317
  standardSkill: true,
2318
+ spiritSkills: ["commons-gardening"],
2180
2319
  icon: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="2" width="14" height="12" rx="1.5"/><path d="M1 5h14"/><circle cx="3" cy="3.5" r="0.5" fill="currentColor" stroke="none"/><circle cx="5" cy="3.5" r="0.5" fill="currentColor" stroke="none"/></svg>',
2181
2320
  color: "purple",
2182
2321
  ui: {
@@ -2194,10 +2333,10 @@ var src_default = createExtension({
2194
2333
  onDaemonStart(ctx) {
2195
2334
  repoReady = ensurePagesRepo(ctx.dataDir, isolationFrom(ctx));
2196
2335
  repoReady.then(() => {
2336
+ const repoDir = resolve6(ctx.dataDir, "repo");
2197
2337
  if (ctx.db) {
2198
- const repoDir = resolve5(ctx.dataDir, "repo");
2199
2338
  try {
2200
- syncSystemPages(ctx.db, hashFiles(repoDir, collectHtmlFiles(repoDir)));
2339
+ syncSystemPages(ctx.db, hashFiles(repoDir, collectPageFiles(repoDir)));
2201
2340
  } catch (err) {
2202
2341
  console.error("[pages] failed to sync system pages to DB:", err);
2203
2342
  }
@@ -2206,11 +2345,26 @@ var src_default = createExtension({
2206
2345
  console.error("[pages] failed to initialize pages repo:", err);
2207
2346
  });
2208
2347
  },
2348
+ // One-time bootstrap: invite the spirit to create a commons index if one doesn't
2349
+ // exist yet. Flag file makes it fire at most once. Runs here rather than in
2350
+ // onDaemonStart because the spirit isn't created yet at that point on a fresh
2351
+ // install. maybeSendCommonsCue handles its own errors and never rejects.
2352
+ async onSpiritReady(ctx) {
2353
+ if (!repoReady) return;
2354
+ try {
2355
+ await repoReady;
2356
+ } catch {
2357
+ return;
2358
+ }
2359
+ await maybeSendCommonsCue(ctx, resolve6(ctx.dataDir, "repo"));
2360
+ },
2209
2361
  onMindStart(mindName, ctx) {
2210
- const mindDir = ctx.getMindDir(mindName);
2211
- if (!mindDir) return;
2212
2362
  (repoReady ?? Promise.resolve()).catch(() => {
2213
- }).then(() => addPagesWorktree(mindName, mindDir, ctx.dataDir, isolationFrom(ctx))).catch((err) => {
2363
+ }).then(async () => {
2364
+ const mindDir = await ctx.getMindDir(mindName);
2365
+ if (!mindDir) return;
2366
+ await addPagesWorktree(mindName, mindDir, ctx.dataDir, isolationFrom(ctx));
2367
+ }).catch((err) => {
2214
2368
  console.warn(
2215
2369
  `[pages] failed to add pages worktree for ${mindName}: ${err.message}`
2216
2370
  );