wiki-viewer 1.7.2 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (424) hide show
  1. package/.next/standalone/.github/workflows/publish.yml +1 -1
  2. package/.next/standalone/.next/BUILD_ID +1 -1
  3. package/.next/standalone/.next/app-path-routes-manifest.json +6 -0
  4. package/.next/standalone/.next/build-manifest.json +3 -3
  5. package/.next/standalone/.next/prerender-manifest.json +3 -3
  6. package/.next/standalone/.next/routes-manifest.json +40 -0
  7. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  15. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
  17. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  18. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  20. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/api/agent/activity/route.js +6 -6
  24. package/.next/standalone/.next/server/app/api/agent/activity/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/agent/admin/agents/[agentId]/revoke/route.js +6 -6
  26. package/.next/standalone/.next/server/app/api/agent/admin/agents/[agentId]/revoke/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/agent/admin/agents/route.js +6 -6
  28. package/.next/standalone/.next/server/app/api/agent/admin/agents/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/approve/route.js +7 -7
  30. package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/approve/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/deny/route.js +5 -5
  32. package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/deny/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/agent/admin/registrations/route.js +5 -5
  34. package/.next/standalone/.next/server/app/api/agent/admin/registrations/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js +8 -7
  36. package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js +9 -8
  38. package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js +8 -7
  40. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js +7 -7
  42. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/api/agent/fs/move/route.js +6 -5
  44. package/.next/standalone/.next/server/app/api/agent/fs/move/route.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/api/agent/fs/search/route.js +7 -6
  46. package/.next/standalone/.next/server/app/api/agent/fs/search/route.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/api/agent/internal/span/route.js +6 -5
  48. package/.next/standalone/.next/server/app/api/agent/internal/span/route.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/api/agent/settings/route.js +7 -7
  50. package/.next/standalone/.next/server/app/api/agent/settings/route.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js +8 -7
  52. package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/api/app-proxy/[...path]/route.js +2 -2
  54. package/.next/standalone/.next/server/app/api/app-proxy/[...path]/route.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/api/assets/[...path]/route.js +8 -2
  56. package/.next/standalone/.next/server/app/api/assets/[...path]/route.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/api/auth/[...all]/route.js +5 -5
  58. package/.next/standalone/.next/server/app/api/auth/[...all]/route.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/api/system/admins/route/app-paths-manifest.json +3 -0
  60. package/.next/standalone/.next/server/app/api/system/admins/route/build-manifest.json +9 -0
  61. package/.next/standalone/.next/server/app/api/system/admins/route/server-reference-manifest.json +4 -0
  62. package/.next/standalone/.next/server/app/api/system/admins/route.js +14 -0
  63. package/.next/standalone/.next/server/app/api/system/admins/route.js.map +5 -0
  64. package/.next/standalone/.next/server/app/api/system/admins/route.js.nft.json +1 -0
  65. package/.next/standalone/.next/server/app/api/system/admins/route_client-reference-manifest.js +3 -0
  66. package/.next/standalone/.next/server/app/api/system/auth-config/route.js +6 -6
  67. package/.next/standalone/.next/server/app/api/system/auth-config/route.js.nft.json +1 -1
  68. package/.next/standalone/.next/server/app/api/system/auth-settings/route.js +4 -4
  69. package/.next/standalone/.next/server/app/api/system/auth-settings/route.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/api/system/browse/route.js +4 -4
  71. package/.next/standalone/.next/server/app/api/system/browse/route.js.nft.json +1 -1
  72. package/.next/standalone/.next/server/app/api/system/clear-root/route.js +5 -5
  73. package/.next/standalone/.next/server/app/api/system/clear-root/route.js.nft.json +1 -1
  74. package/.next/standalone/.next/server/app/api/system/config/route.js +6 -6
  75. package/.next/standalone/.next/server/app/api/system/config/route.js.nft.json +1 -1
  76. package/.next/standalone/.next/server/app/api/system/pins/route.js +4 -4
  77. package/.next/standalone/.next/server/app/api/system/pins/route.js.nft.json +1 -1
  78. package/.next/standalone/.next/server/app/api/system/reveal/route.js +6 -6
  79. package/.next/standalone/.next/server/app/api/system/reveal/route.js.nft.json +1 -1
  80. package/.next/standalone/.next/server/app/api/system/root-status/route.js +5 -5
  81. package/.next/standalone/.next/server/app/api/system/root-status/route.js.nft.json +1 -1
  82. package/.next/standalone/.next/server/app/api/system/set-root/route.js +6 -6
  83. package/.next/standalone/.next/server/app/api/system/set-root/route.js.nft.json +1 -1
  84. package/.next/standalone/.next/server/app/api/system/users/route/app-paths-manifest.json +3 -0
  85. package/.next/standalone/.next/server/app/api/system/users/route/build-manifest.json +9 -0
  86. package/.next/standalone/.next/server/app/api/system/users/route/server-reference-manifest.json +4 -0
  87. package/.next/standalone/.next/server/app/api/system/users/route.js +14 -0
  88. package/.next/standalone/.next/server/app/api/system/users/route.js.map +5 -0
  89. package/.next/standalone/.next/server/app/api/system/users/route.js.nft.json +1 -0
  90. package/.next/standalone/.next/server/app/api/system/users/route_client-reference-manifest.js +3 -0
  91. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route/app-paths-manifest.json +3 -0
  92. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route/build-manifest.json +9 -0
  93. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route/server-reference-manifest.json +4 -0
  94. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route.js +14 -0
  95. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route.js.map +5 -0
  96. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route.js.nft.json +1 -0
  97. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route_client-reference-manifest.js +3 -0
  98. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route/app-paths-manifest.json +3 -0
  99. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route/build-manifest.json +9 -0
  100. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route/server-reference-manifest.json +4 -0
  101. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route.js +14 -0
  102. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route.js.map +5 -0
  103. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route.js.nft.json +1 -0
  104. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route_client-reference-manifest.js +3 -0
  105. package/.next/standalone/.next/server/app/api/system/workspaces/route/app-paths-manifest.json +3 -0
  106. package/.next/standalone/.next/server/app/api/system/workspaces/route/build-manifest.json +9 -0
  107. package/.next/standalone/.next/server/app/api/system/workspaces/route/server-reference-manifest.json +4 -0
  108. package/.next/standalone/.next/server/app/api/system/workspaces/route.js +14 -0
  109. package/.next/standalone/.next/server/app/api/system/workspaces/route.js.map +5 -0
  110. package/.next/standalone/.next/server/app/api/system/workspaces/route.js.nft.json +1 -0
  111. package/.next/standalone/.next/server/app/api/system/workspaces/route_client-reference-manifest.js +3 -0
  112. package/.next/standalone/.next/server/app/api/upload/[...path]/route.js +8 -2
  113. package/.next/standalone/.next/server/app/api/upload/[...path]/route.js.nft.json +1 -1
  114. package/.next/standalone/.next/server/app/api/wiki/app/route.js +5 -5
  115. package/.next/standalone/.next/server/app/api/wiki/app/route.js.nft.json +1 -1
  116. package/.next/standalone/.next/server/app/api/wiki/content/route.js +6 -6
  117. package/.next/standalone/.next/server/app/api/wiki/content/route.js.nft.json +1 -1
  118. package/.next/standalone/.next/server/app/api/wiki/download/route.js +7 -7
  119. package/.next/standalone/.next/server/app/api/wiki/download/route.js.nft.json +1 -1
  120. package/.next/standalone/.next/server/app/api/wiki/file/route.js +8 -2
  121. package/.next/standalone/.next/server/app/api/wiki/file/route.js.nft.json +1 -1
  122. package/.next/standalone/.next/server/app/api/wiki/folder/route.js +6 -6
  123. package/.next/standalone/.next/server/app/api/wiki/folder/route.js.nft.json +1 -1
  124. package/.next/standalone/.next/server/app/api/wiki/move/route.js +6 -6
  125. package/.next/standalone/.next/server/app/api/wiki/move/route.js.nft.json +1 -1
  126. package/.next/standalone/.next/server/app/api/wiki/new-file/route.js +7 -7
  127. package/.next/standalone/.next/server/app/api/wiki/new-file/route.js.nft.json +1 -1
  128. package/.next/standalone/.next/server/app/api/wiki/page/route.js +7 -7
  129. package/.next/standalone/.next/server/app/api/wiki/page/route.js.nft.json +1 -1
  130. package/.next/standalone/.next/server/app/api/wiki/presence/route.js +7 -7
  131. package/.next/standalone/.next/server/app/api/wiki/presence/route.js.nft.json +1 -1
  132. package/.next/standalone/.next/server/app/api/wiki/route.js +6 -6
  133. package/.next/standalone/.next/server/app/api/wiki/route.js.nft.json +1 -1
  134. package/.next/standalone/.next/server/app/api/wiki/search/route/app-paths-manifest.json +3 -0
  135. package/.next/standalone/.next/server/app/api/wiki/search/route/build-manifest.json +9 -0
  136. package/.next/standalone/.next/server/app/api/wiki/search/route/server-reference-manifest.json +4 -0
  137. package/.next/standalone/.next/server/app/api/wiki/search/route.js +15 -0
  138. package/.next/standalone/.next/server/app/api/wiki/search/route.js.map +5 -0
  139. package/.next/standalone/.next/server/app/api/wiki/search/route.js.nft.json +1 -0
  140. package/.next/standalone/.next/server/app/api/wiki/search/route_client-reference-manifest.js +3 -0
  141. package/.next/standalone/.next/server/app/api/wiki/slugs/route.js +6 -6
  142. package/.next/standalone/.next/server/app/api/wiki/slugs/route.js.nft.json +1 -1
  143. package/.next/standalone/.next/server/app/api/wiki/upload/route.js +7 -7
  144. package/.next/standalone/.next/server/app/api/wiki/upload/route.js.nft.json +1 -1
  145. package/.next/standalone/.next/server/app/api/wiki/watch/route.js +7 -9
  146. package/.next/standalone/.next/server/app/api/wiki/watch/route.js.nft.json +1 -1
  147. package/.next/standalone/.next/server/app/index.html +1 -1
  148. package/.next/standalone/.next/server/app/index.rsc +3 -3
  149. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  150. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
  151. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  152. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
  153. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  154. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  155. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  156. package/.next/standalone/.next/server/app/signin/page.js +1 -1
  157. package/.next/standalone/.next/server/app/signin/page.js.nft.json +1 -1
  158. package/.next/standalone/.next/server/app/signin/page_client-reference-manifest.js +1 -1
  159. package/.next/standalone/.next/server/app-paths-manifest.json +6 -0
  160. package/.next/standalone/.next/server/chunks/0p91_next_dist_esm_build_templates_app-route_0d2jbo5.js +1 -1
  161. package/.next/standalone/.next/server/chunks/0p91_next_dist_esm_build_templates_app-route_0ytral2.js +1 -1
  162. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-244bv._.js +16 -0
  163. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0xz-_0v._.js → [root-of-the-server]__0-inabx._.js} +2 -2
  164. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-ao6a_._.js → [root-of-the-server]__0.-9jt0._.js} +1 -1
  165. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0~-r-8f._.js → [root-of-the-server]__00c1-dr._.js} +1 -1
  166. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00lep-u._.js +3 -0
  167. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00mtevo._.js +3 -0
  168. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00qu86p._.js +3 -0
  169. package/.next/standalone/.next/server/chunks/[root-of-the-server]__012753j._.js +3 -0
  170. package/.next/standalone/.next/server/chunks/[root-of-the-server]__01hp8ax._.js +3 -0
  171. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0269d2e._.js +1 -1
  172. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0jhr.ex._.js → [root-of-the-server]__02n6z97._.js} +1 -1
  173. package/.next/standalone/.next/server/chunks/[root-of-the-server]__036mhjp._.js +5 -0
  174. package/.next/standalone/.next/server/chunks/[root-of-the-server]__03p0ob1._.js +16 -0
  175. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05t~v6l._.js +3 -0
  176. package/.next/standalone/.next/server/chunks/[root-of-the-server]__068ni_k._.js +3 -0
  177. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__082nhm6._.js → [root-of-the-server]__07vphbd._.js} +1 -1
  178. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0pg6dn-._.js → [root-of-the-server]__08lkuwa._.js} +1 -1
  179. package/.next/standalone/.next/server/chunks/[root-of-the-server]__09cqzem._.js +3 -0
  180. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0p_ox-k._.js → [root-of-the-server]__09k4q9e._.js} +1 -1
  181. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0_601._.js +3 -0
  182. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ae3dk6._.js +3 -0
  183. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0s28.4-._.js → [root-of-the-server]__0af-x45._.js} +1 -1
  184. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cqu-wm._.js +3 -0
  185. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d75a5q._.js +1 -1
  186. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d7x3e1._.js +3 -0
  187. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d7zxm_._.js +3 -0
  188. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e7vs98._.js +3 -0
  189. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0fxzg4v._.js +3 -0
  190. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0gda_iu._.js +3 -0
  191. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0hu~q20._.js +3 -0
  192. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jk1uf4._.js +3 -0
  193. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l-8clq._.js +3 -0
  194. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0lhka0x._.js +3 -0
  195. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0kdn1le._.js → [root-of-the-server]__0na11h0._.js} +2 -2
  196. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0nd.b_c._.js +3 -0
  197. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0otb8ra._.js +3 -0
  198. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0p0-p__._.js +3 -0
  199. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0piofm3._.js +3 -0
  200. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rrf-jr._.js +3 -0
  201. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0s6_lzw._.js +3 -0
  202. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0sh13v5._.js +3 -0
  203. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ta~flu._.js +3 -0
  204. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0v4im4p._.js +3 -0
  205. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0qn8e~s._.js → [root-of-the-server]__0v_kyp-._.js} +1 -1
  206. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vatqe-._.js +3 -0
  207. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vdb2nr._.js +3 -0
  208. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wdh63w._.js +3 -0
  209. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0z3wg9x._.js +12 -0
  210. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0mzo03s._.js → [root-of-the-server]__0~.-3yk._.js} +1 -1
  211. package/.next/standalone/.next/server/chunks/[root-of-the-server]__123._fe._.js +3 -0
  212. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__12800b1._.js → [root-of-the-server]__12ee_s6._.js} +1 -1
  213. package/.next/standalone/.next/server/chunks/[root-of-the-server]__12onytt._.js +3 -0
  214. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__04udrya._.js → [root-of-the-server]__12q8ch8._.js} +2 -2
  215. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1377.79._.js +3 -0
  216. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1378-ox._.js +3 -0
  217. package/.next/standalone/.next/server/chunks/[root-of-the-server]__13aid91._.js +3 -0
  218. package/.next/standalone/.next/server/chunks/[root-of-the-server]__13hp4tw._.js +12 -0
  219. package/.next/standalone/.next/server/chunks/_079xuln._.js +1 -1
  220. package/.next/standalone/.next/server/chunks/_0dr1r1g._.js +452 -0
  221. package/.next/standalone/.next/server/chunks/_101exke._.js +452 -0
  222. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_system_admins_route_actions_0dsm7cx.js +3 -0
  223. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_system_users_route_actions_0whj5cy.js +3 -0
  224. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_system_workspaces_[id]_open_route_actions_1087xu7.js +3 -0
  225. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_system_workspaces_[id]_route_actions_0~h7-8s.js +3 -0
  226. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_system_workspaces_route_actions_0mkalc3.js +3 -0
  227. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_wiki_search_route_actions_0inxu93.js +3 -0
  228. package/.next/standalone/.next/server/chunks/node_modules__pnpm_0aj5g-w._.js +1 -1
  229. package/.next/standalone/.next/server/chunks/src_lib_search_0d-_j3u._.js +46 -0
  230. package/.next/standalone/.next/server/chunks/src_lib_search_0ymmp0c._.js +46 -0
  231. package/.next/standalone/.next/server/chunks/ssr/{0~w0_katex_dist_katex_mjs_0b8ra51._.js → 0~w0_katex_dist_katex_mjs_04mhawr._.js} +1 -1
  232. package/.next/standalone/.next/server/chunks/ssr/12y~_mermaid_dist_chunks_mermaid_core_erDiagram-TEJ5UH35_mjs_0v6rnqy._.js +1 -1
  233. package/.next/standalone/.next/server/chunks/ssr/12y~_mermaid_dist_mermaid_core_mjs_0hj_n-~._.js +1 -1
  234. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0btcd4o._.js → [root-of-the-server]__02twzph._.js} +2 -2
  235. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ytvlzs._.js → [root-of-the-server]__09~x2v~._.js} +2 -2
  236. package/.next/standalone/.next/server/chunks/ssr/_0pqaawz._.js +18 -18
  237. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0c71i8a._.js → node_modules__pnpm_0.96tmo._.js} +1 -1
  238. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0lm8e2c._.js → node_modules__pnpm_011vy_q._.js} +2 -2
  239. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0ezm2us._.js → node_modules__pnpm_01o_5qt._.js} +1 -1
  240. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0j3mvgg._.js → node_modules__pnpm_04n0h6.._.js} +1 -1
  241. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0~ln0by._.js → node_modules__pnpm_093dp2q._.js} +1 -1
  242. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_11e~j49._.js → node_modules__pnpm_09ej62_._.js} +1 -1
  243. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0de8xt-._.js → node_modules__pnpm_09ljbdz._.js} +2 -2
  244. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0-x6zex._.js → node_modules__pnpm_09~_ob2._.js} +1 -1
  245. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0yq5z.h._.js → node_modules__pnpm_0a01ng7._.js} +2 -2
  246. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0kwc-vs._.js → node_modules__pnpm_0b8qx0g._.js} +1 -1
  247. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0hrbz84._.js → node_modules__pnpm_0bbc---._.js} +1 -1
  248. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0hobcbs._.js → node_modules__pnpm_0c.309.._.js} +1 -1
  249. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0rw91-f._.js → node_modules__pnpm_0c8aunj._.js} +1 -1
  250. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0o.c1z8._.js → node_modules__pnpm_0hk6xys._.js} +2 -2
  251. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0dboa_e._.js → node_modules__pnpm_0hlzy8f._.js} +1 -1
  252. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0j53hti._.js → node_modules__pnpm_0nw5ou2._.js} +1 -1
  253. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0xrmzh1._.js → node_modules__pnpm_0o.dn8o._.js} +1 -1
  254. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0cxwhfd._.js → node_modules__pnpm_0s0ur~d._.js} +1 -1
  255. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_07rxv2x._.js → node_modules__pnpm_0shlang._.js} +1 -1
  256. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0i.pw_m._.js → node_modules__pnpm_0sq3c2r._.js} +1 -1
  257. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0ajzar9._.js → node_modules__pnpm_0t9jb7d._.js} +1 -1
  258. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_012nt.s._.js → node_modules__pnpm_0wmx9kq._.js} +2 -2
  259. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0z~h2ow._.js → node_modules__pnpm_0xfx0sf._.js} +1 -1
  260. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0cbsnwf._.js → node_modules__pnpm_0yiz.-t._.js} +1 -1
  261. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0bum3-3._.js → node_modules__pnpm_11sut1u._.js} +1 -1
  262. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0mdo4i3._.js → node_modules__pnpm_124u4o2._.js} +2 -2
  263. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0il8j.j._.js → node_modules__pnpm_13tajgx._.js} +2 -2
  264. package/.next/standalone/.next/server/chunks/ssr/src_app_signin_00yk0vm._.js +2 -2
  265. package/.next/standalone/.next/server/edge/chunks/{08yl_next_dist_esm_build_templates_edge-wrapper_0o9h3w_.js → 08yl_next_dist_esm_build_templates_edge-wrapper_0xbpts6.js} +2 -2
  266. package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__0.pmgt2._.js +3 -0
  267. package/.next/standalone/.next/server/functions-config-manifest.json +6 -0
  268. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  269. package/.next/standalone/.next/server/middleware-manifest.json +8 -8
  270. package/.next/standalone/.next/server/pages/404.html +1 -1
  271. package/.next/standalone/.next/server/pages/500.html +1 -1
  272. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  273. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  274. package/.next/standalone/.next/static/chunks/0c_~zevf2n0r4.css +2 -0
  275. package/.next/standalone/.next/static/chunks/0l4842ez-0_66.js +129 -0
  276. package/.next/standalone/README.md +46 -2
  277. package/.next/standalone/agents/bootstrap-prompt.md +3 -1
  278. package/.next/standalone/agents/wiki-viewer-skill/SKILL.md +38 -11
  279. package/.next/standalone/docs/workspacing-plan.md +364 -0
  280. package/.next/standalone/package.json +2 -2
  281. package/.next/standalone/packages/wiki-viewer-mcp/README.md +6 -5
  282. package/.next/standalone/packages/wiki-viewer-mcp/dist/http-client.js +4 -0
  283. package/.next/standalone/packages/wiki-viewer-mcp/dist/index.js +4 -0
  284. package/.next/standalone/packages/wiki-viewer-mcp/src/http-client.ts +6 -0
  285. package/.next/standalone/packages/wiki-viewer-mcp/src/index.ts +5 -0
  286. package/.next/standalone/src/app/api/agent/activity/route.ts +9 -8
  287. package/.next/standalone/src/app/api/agent/events/[...path]/route.ts +20 -8
  288. package/.next/standalone/src/app/api/agent/files/[...path]/route.ts +16 -9
  289. package/.next/standalone/src/app/api/agent/fs/file/[...path]/route.ts +28 -18
  290. package/.next/standalone/src/app/api/agent/fs/ls/[[...path]]/route.ts +12 -7
  291. package/.next/standalone/src/app/api/agent/fs/move/route.ts +16 -11
  292. package/.next/standalone/src/app/api/agent/fs/search/route.ts +49 -13
  293. package/.next/standalone/src/app/api/agent/internal/span/route.ts +9 -5
  294. package/.next/standalone/src/app/api/agent/register/route.ts +9 -1
  295. package/.next/standalone/src/app/api/agent/settings/route.ts +7 -3
  296. package/.next/standalone/src/app/api/agent/sidecar/[...path]/route.ts +8 -4
  297. package/.next/standalone/src/app/api/agents/install/route.ts +20 -2
  298. package/.next/standalone/src/app/api/assets/[...path]/route.ts +9 -4
  299. package/.next/standalone/src/app/api/system/admins/route.ts +103 -0
  300. package/.next/standalone/src/app/api/system/reveal/route.ts +7 -6
  301. package/.next/standalone/src/app/api/system/set-root/route.ts +24 -1
  302. package/.next/standalone/src/app/api/system/users/route.ts +120 -0
  303. package/.next/standalone/src/app/api/system/workspaces/[id]/open/route.ts +34 -0
  304. package/.next/standalone/src/app/api/system/workspaces/[id]/route.ts +74 -0
  305. package/.next/standalone/src/app/api/system/workspaces/route.ts +67 -0
  306. package/.next/standalone/src/app/api/upload/[...path]/route.ts +10 -5
  307. package/.next/standalone/src/app/api/wiki/app/route.ts +10 -9
  308. package/.next/standalone/src/app/api/wiki/content/route.ts +14 -14
  309. package/.next/standalone/src/app/api/wiki/download/route.ts +7 -6
  310. package/.next/standalone/src/app/api/wiki/file/route.ts +7 -2
  311. package/.next/standalone/src/app/api/wiki/folder/route.ts +6 -5
  312. package/.next/standalone/src/app/api/wiki/move/route.ts +7 -7
  313. package/.next/standalone/src/app/api/wiki/new-file/route.ts +7 -6
  314. package/.next/standalone/src/app/api/wiki/page/route.ts +6 -5
  315. package/.next/standalone/src/app/api/wiki/presence/route.ts +8 -7
  316. package/.next/standalone/src/app/api/wiki/route.ts +13 -11
  317. package/.next/standalone/src/app/api/wiki/search/route.ts +44 -0
  318. package/.next/standalone/src/app/api/wiki/slugs/route.ts +8 -8
  319. package/.next/standalone/src/app/api/wiki/upload/route.ts +6 -5
  320. package/.next/standalone/src/app/api/wiki/watch/route.ts +16 -40
  321. package/.next/standalone/src/app/globals.css +484 -484
  322. package/.next/standalone/src/app/page.tsx +163 -49
  323. package/.next/standalone/src/components/auth-settings-sheet.tsx +171 -2
  324. package/.next/standalone/src/components/dir-picker.tsx +8 -6
  325. package/.next/standalone/src/components/editor/comment-thread.tsx +2 -1
  326. package/.next/standalone/src/components/editor/csv-viewer.tsx +3 -2
  327. package/.next/standalone/src/components/editor/editor-toolbar.tsx +2 -2
  328. package/.next/standalone/src/components/editor/editor.tsx +6 -5
  329. package/.next/standalone/src/components/editor/file-fallback-viewer.tsx +3 -2
  330. package/.next/standalone/src/components/editor/folder-index.tsx +2 -1
  331. package/.next/standalone/src/components/editor/image-viewer.tsx +2 -1
  332. package/.next/standalone/src/components/editor/media-popover.tsx +2 -1
  333. package/.next/standalone/src/components/editor/media-viewer.tsx +2 -1
  334. package/.next/standalone/src/components/editor/mermaid-viewer.tsx +3 -2
  335. package/.next/standalone/src/components/editor/node-app-viewer.tsx +4 -3
  336. package/.next/standalone/src/components/editor/notebook-viewer.tsx +3 -2
  337. package/.next/standalone/src/components/editor/office/docx-viewer.tsx +2 -1
  338. package/.next/standalone/src/components/editor/office/office-chrome.tsx +3 -2
  339. package/.next/standalone/src/components/editor/office/pptx-viewer.tsx +2 -1
  340. package/.next/standalone/src/components/editor/office/xlsx-viewer.tsx +2 -1
  341. package/.next/standalone/src/components/editor/pdf-viewer.tsx +2 -1
  342. package/.next/standalone/src/components/editor/proof-span-popover.tsx +2 -1
  343. package/.next/standalone/src/components/editor/source-viewer.tsx +3 -2
  344. package/.next/standalone/src/components/editor/suggest-edit-popover.tsx +2 -1
  345. package/.next/standalone/src/components/editor/suggestion-card.tsx +2 -1
  346. package/.next/standalone/src/components/editor/website-viewer.tsx +2 -1
  347. package/.next/standalone/src/components/editor/wiki-link-create-dialog.tsx +2 -1
  348. package/.next/standalone/src/components/search/search-command-dialog.tsx +109 -0
  349. package/.next/standalone/src/components/search/sidebar-search-box.tsx +121 -0
  350. package/.next/standalone/src/components/search/snippet-text.tsx +48 -0
  351. package/.next/standalone/src/components/ui/command.tsx +3 -2
  352. package/.next/standalone/src/lib/auth/admin.ts +112 -0
  353. package/.next/standalone/src/lib/auth/server.ts +13 -2
  354. package/.next/standalone/src/lib/config.ts +56 -4
  355. package/.next/standalone/src/lib/proof/audit.ts +19 -10
  356. package/.next/standalone/src/lib/proof/auth.ts +11 -0
  357. package/.next/standalone/src/lib/proof/collab-state.ts +4 -2
  358. package/.next/standalone/src/lib/proof/file-lock.ts +5 -0
  359. package/.next/standalone/src/lib/proof/idempotency.ts +5 -0
  360. package/.next/standalone/src/lib/proof/lease.ts +40 -27
  361. package/.next/standalone/src/lib/proof/mutex.ts +4 -0
  362. package/.next/standalone/src/lib/proof/raw-fs.ts +2 -3
  363. package/.next/standalone/src/lib/proof/registry.ts +5 -0
  364. package/.next/standalone/src/lib/search/indexable-exts.ts +25 -0
  365. package/.next/standalone/src/lib/search/indexer.ts +567 -0
  366. package/.next/standalone/src/lib/search/sanitize.ts +35 -0
  367. package/.next/standalone/src/lib/search/search-db.ts +57 -0
  368. package/.next/standalone/src/lib/search/watcher-pool.ts +79 -0
  369. package/.next/standalone/src/lib/workspace-client.ts +82 -0
  370. package/.next/standalone/src/lib/workspace-context.ts +142 -0
  371. package/.next/standalone/src/lib/workspaces.ts +196 -0
  372. package/.next/standalone/src/stores/ai-panel-store.ts +3 -2
  373. package/.next/standalone/src/stores/editor-store.ts +4 -3
  374. package/.next/standalone/src/stores/proof-store.ts +4 -3
  375. package/.next/standalone/src/stores/search-store.ts +86 -0
  376. package/.next/standalone/src/stores/wiki-slugs-store.ts +2 -1
  377. package/.next/standalone/src/tests/proof/admin-create-user.test.ts +43 -0
  378. package/.next/standalone/src/tests/proof/agent-workspace-scope.test.ts +134 -0
  379. package/.next/standalone/src/tests/proof/collab-state.test.ts +43 -41
  380. package/.next/standalone/src/tests/proof/preload.ts +21 -0
  381. package/.next/standalone/src/tests/proof/search-index.test.ts +339 -0
  382. package/.next/standalone/src/tests/proof/workspace-isolation.test.ts +87 -0
  383. package/.next/standalone/src/tests/proof/workspaces-api.test.ts +227 -0
  384. package/.next/standalone/tsconfig.tsbuildinfo +1 -1
  385. package/README.md +46 -2
  386. package/package.json +2 -2
  387. package/.next/standalone/.next/server/chunks/0p91_next_dist_esm_build_templates_app-route_0o~h4zq.js +0 -5
  388. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0.hb9q2._.js +0 -3
  389. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0.uux34._.js +0 -3
  390. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0092hkp._.js +0 -3
  391. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05f6x0i._.js +0 -3
  392. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05idwm9._.js +0 -3
  393. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06-dtd0._.js +0 -3
  394. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06sek6_._.js +0 -3
  395. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07fpqks._.js +0 -3
  396. package/.next/standalone/.next/server/chunks/[root-of-the-server]__094q5bj._.js +0 -3
  397. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0awzyon._.js +0 -3
  398. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d_da6q._.js +0 -3
  399. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0hhhvm7._.js +0 -3
  400. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kk26wc._.js +0 -3
  401. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0km8suj._.js +0 -3
  402. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kur4ro._.js +0 -15
  403. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0mkwoul._.js +0 -12
  404. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qc2~6l._.js +0 -15
  405. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qyd-o8._.js +0 -3
  406. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rkve~v._.js +0 -3
  407. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rzo40z._.js +0 -3
  408. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0sjocwy._.js +0 -3
  409. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ssj9~d._.js +0 -3
  410. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0s~i040._.js +0 -3
  411. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t220d-._.js +0 -3
  412. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t5mg.n._.js +0 -3
  413. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0trsqf4._.js +0 -3
  414. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xq124z._.js +0 -3
  415. package/.next/standalone/.next/server/chunks/[root-of-the-server]__106uw_c._.js +0 -3
  416. package/.next/standalone/.next/server/chunks/[root-of-the-server]__10w2q~o._.js +0 -3
  417. package/.next/standalone/.next/server/chunks/[root-of-the-server]__13w-0iy._.js +0 -3
  418. package/.next/standalone/.next/server/chunks/src_lib_auth_server_ts_00f4e0h._.js +0 -452
  419. package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__0ug2rvl._.js +0 -3
  420. package/.next/standalone/.next/static/chunks/0vwkqw_rofm-c.js +0 -129
  421. package/.next/standalone/.next/static/chunks/0zay6349~v_wc.css +0 -2
  422. /package/.next/standalone/.next/static/{0XvNCOSEq7cbARTLtAolL → 17VWCQ9_AhwJBT4yjkvWG}/_buildManifest.js +0 -0
  423. /package/.next/standalone/.next/static/{0XvNCOSEq7cbARTLtAolL → 17VWCQ9_AhwJBT4yjkvWG}/_clientMiddlewareManifest.js +0 -0
  424. /package/.next/standalone/.next/static/{0XvNCOSEq7cbARTLtAolL → 17VWCQ9_AhwJBT4yjkvWG}/_ssgManifest.js +0 -0
@@ -67,7 +67,7 @@ The wizard walks you through every option and writes `~/.wiki-viewer/config.json
67
67
 
68
68
  Open **http://localhost:3000** (or **https://localhost:3000** with `--https`).
69
69
 
70
- To switch the served directory later, click **Change** at the bottom of the sidebar and pick a new folder. No restart needed.
70
+ To switch between directories later, use the **workspace switcher** at the bottom of the sidebar. Each _workspace_ is a registered root directory; you can run several at once with no state bleed (open different workspaces in different browser tabs). See [Workspaces](#workspaces) below. No restart needed.
71
71
 
72
72
  On first run with no users in the database, the app works in single-user mode and any visitor on `localhost` can sign up. Set `AUTH_ALLOWED_DOMAIN` or `AUTH_ALLOWED_EMAILS` before exposing the server to anyone else.
73
73
 
@@ -123,7 +123,7 @@ wiki-viewer service restart
123
123
  wiki-viewer service uninstall
124
124
  ```
125
125
 
126
- The run config is saved to `~/.wiki-viewer/config.json`. Edit that file and run `wiki-viewer service restart` to change settings without reinstalling. To change just the served directory at runtime, you can also click **Change** in the sidebar.
126
+ The run config is saved to `~/.wiki-viewer/config.json`. Edit that file and run `wiki-viewer service restart` to change settings without reinstalling. To change the served directory at runtime, use the workspace switcher in the sidebar (see [Workspaces](#workspaces)).
127
127
 
128
128
  On Linux, install enables lingering (`loginctl enable-linger`) so the service runs without an active login session and survives reboot. If that step needs privileges, the installer prints the command to run manually.
129
129
 
@@ -174,6 +174,49 @@ Updates the global install to the latest version (npm/pnpm/yarn auto-detected) a
174
174
 
175
175
  ---
176
176
 
177
+ ## Workspaces
178
+
179
+ A **workspace** is a registered root directory. One running wiki-viewer can
180
+ serve many workspaces at once, fully isolated from each other:
181
+
182
+ - Two browser tabs can each open a different workspace (via a `?ws=<id>` URL
183
+ param) and edit files concurrently. Edit leases, write locks, idempotency
184
+ keys, collab sidecars, and the audit log are all namespaced per workspace —
185
+ a `notes.md` in workspace A never collides with a `notes.md` in workspace B.
186
+ - AI agents target a workspace with an `X-Workspace: <id>` header (or `?ws=`).
187
+ An agent's grant may be pinned to one workspace; requests that resolve to a
188
+ different workspace are rejected `403 FORBIDDEN`. Agents registered without a
189
+ workspace id keep working across all workspaces (wildcard).
190
+
191
+ ### Switching and creating
192
+
193
+ The sidebar footer shows the active workspace. Click it to switch between the
194
+ workspaces you can access, or (admins only) **Add workspace…** to register a
195
+ new root directory. Switching navigates to `?ws=<id>` and resets the view.
196
+
197
+ The legacy single-directory flow is unchanged: launching with `ROOT_DIR` or
198
+ `wiki-viewer <dir>` auto-registers that directory as the first workspace, and a
199
+ single-workspace install behaves exactly as before (no `?ws=` needed).
200
+
201
+ ### Admins and access control
202
+
203
+ Workspace management is **admin-gated** (organizational access control, not
204
+ OS-level permissions):
205
+
206
+ - The **first user to sign up** becomes an admin automatically. Admins can
207
+ promote/demote other users from the settings sheet (gear icon → **Admins**).
208
+ Seed or override admins headlessly with `WIKI_ADMIN_EMAILS` (csv).
209
+ - Only admins can create/delete workspaces and edit a workspace's allowed-user
210
+ list (`allowedUserIds`). An empty allow-list means any signed-in user may
211
+ open the workspace (the default). Non-admins with access get full file
212
+ read/write on the workspaces they can see.
213
+ - Admin state lives in `~/.wiki-viewer/config.json` (`adminUserIds`), workspace
214
+ records under `workspaces[]`.
215
+
216
+ > Note: removing a workspace only unregisters it — the directory on disk is
217
+ > never touched. The last admin cannot be demoted unless `WIKI_ADMIN_EMAILS`
218
+ > provides a fallback.
219
+
177
220
  ## Auth and multi-user mode
178
221
 
179
222
  wiki-viewer uses [Better Auth](https://better-auth.com) with SQLite. State lives at `~/.wiki-viewer/auth.db` (WAL mode) and `~/.wiki-viewer/auth.secret` (chmod 0600). Both are created on first start. Set `BETTER_AUTH_SECRET` in the environment to override the file-stored secret.
@@ -528,6 +571,7 @@ The dev server supports hot reload.
528
571
  | `AUTH_ALLOWED_DOMAIN` | csv: only emails on these domains can sign up (overridden by UI allowlist if set) | unset (open) |
529
572
  | `WIKI_OWNER_HOSTS` | csv: extra hostnames trusted for CSRF Origin check | `localhost,127.0.0.1` |
530
573
  | `WIKI_ALLOW_INSECURE` | Set to `1` to bypass the prod-https guard (dev / CI only) | unset |
574
+ | `WIKI_ADMIN_EMAILS` | csv: emails treated as admins (seed/override; otherwise first signup is admin) | unset |
531
575
  | `AGENT_RATE_LIMIT` | Max mutation ops per minute per agent identity | `60` |
532
576
 
533
577
  ---
@@ -7,6 +7,8 @@ You are connecting to a running wiki-viewer instance at $WIKI_URL (e.g. `http://
7
7
 
8
8
  **Mode rule:** Every file read returns `X-Collab-State`. If `active` → use Tier-2 block-ops so the human can review. Otherwise → use Tier-1 raw fs. A raw write to an `active` .md is rejected 409 `COLLAB_ACTIVE` with the Tier-2 URL.
9
9
 
10
+ **Workspaces:** this instance may serve several root directories. Send `X-Workspace: <workspaceId>` (or `?ws=<id>`) on every request to target one; omit it only on a single-workspace instance (the server falls back to the default). Ask the human for the workspace id or read it from the `?ws=` param of the URL they shared. A token may be pinned to one workspace — a mismatched workspace returns 403.
11
+
10
12
  Set `basis` and `basisDetail` on every Tier-2 content op so the human can see where your changes came from. Prefer `suggestion.add` over direct block ops unless told otherwise.
11
13
 
12
- MCP-capable agents: `npx wiki-viewer-mcp` (set `WIKI_VIEWER_URL`, `WIKI_VIEWER_TOKEN`, `WIKI_VIEWER_AGENT_ID`) gives native **Tier-1 file tools** (read/write/edit/list/search/move/delete) and refuses to overwrite an `active` doc. It has **no Tier-2 collab tools** — to co-write a doc (suggestions/comments), call the Tier-2 HTTP endpoints directly. MCP = fast filework; Tier-2 HTTP = reviewable collaboration.
14
+ MCP-capable agents: `npx wiki-viewer-mcp` (set `WIKI_VIEWER_URL`, `WIKI_VIEWER_TOKEN`, `WIKI_VIEWER_AGENT_ID`, and optionally `WIKI_VIEWER_WORKSPACE` to target a workspace) gives native **Tier-1 file tools** (read/write/edit/list/search/move/delete) and refuses to overwrite an `active` doc. It has **no Tier-2 collab tools** — to co-write a doc (suggestions/comments), call the Tier-2 HTTP endpoints directly. MCP = fast filework; Tier-2 HTTP = reviewable collaboration.
@@ -58,7 +58,10 @@ Content-Type: application/json
58
58
  - `id` must match `^ai:[a-z][a-z0-9-]{0,30}$`. Pick something stable per agent identity (e.g. `ai:claude`, `ai:cursor`, `ai:my-script`).
59
59
  - `displayName` 1-80 chars. What the human sees in the approval UI.
60
60
  - `scope.paths` is a glob list. Use `**/*` for full repo access, or restrict to a subtree like `notes/**`.
61
- - `scope.ops` ⊆ `["read", "mutate"]`.
61
+ - `scope.ops` ⊆ `["read", "mutate"]` (add `"delete"` if you need to remove files).
62
+ - `scope.workspaceId` (optional): pin this grant to a single workspace id. Omit
63
+ to request a wildcard grant usable in any workspace (the human can still narrow
64
+ it at approval time).
62
65
 
63
66
  Response:
64
67
 
@@ -90,10 +93,34 @@ Every subsequent request:
90
93
  ```
91
94
  Authorization: Bearer <token>
92
95
  X-Agent-Id: ai:<your-name>
96
+ X-Workspace: <workspaceId> # optional but recommended (see Workspaces)
93
97
  ```
94
98
 
95
99
  The `X-Agent-Id` must match the id the token was issued for. Spoofing rejected with `401`.
96
100
 
101
+ ## Workspaces
102
+
103
+ A wiki-viewer instance can serve **multiple root directories ("workspaces")**
104
+ at once. Every file path you use is relative to **one** workspace's root.
105
+
106
+ - **Targeting a workspace:** send an `X-Workspace: <workspaceId>` header on every
107
+ request (or append `?ws=<workspaceId>` to the URL). If you omit it, the server
108
+ resolves to a default (the most-recently-opened workspace), which is correct
109
+ for a single-workspace instance but ambiguous when several exist — so prefer
110
+ to be explicit.
111
+ - **Discovering workspace ids:** `GET /api/agent/settings` returns the resolved
112
+ `root` (the active workspace's directory) for whatever `X-Workspace` you send.
113
+ The human picks/creates workspaces in the UI; ask them for the id, or read it
114
+ from the `?ws=` param in a wiki-viewer URL they pasted.
115
+ - **Scoped grants:** your token may be pinned to one workspace at approval time.
116
+ A request that resolves to a different workspace is rejected `403 FORBIDDEN`.
117
+ A token with no workspace pin works against any workspace (wildcard).
118
+ - **Isolation:** paths in workspace A never touch workspace B, even if they share
119
+ the same relative path (`notes.md` in A ≠ `notes.md` in B).
120
+
121
+ Whenever the human is working in a specific workspace, capture its id once and
122
+ send `X-Workspace: <id>` on all subsequent calls.
123
+
97
124
  ## Reading
98
125
 
99
126
  ```
@@ -214,16 +241,16 @@ Acks are advisory; events are never deleted.
214
241
 
215
242
  ## Error codes
216
243
 
217
- | Status | Code | Meaning |
218
- | ------ | ---------------------- | -------------------------------------------------- |
219
- | 401 | UNAUTHORIZED | bad/missing token or X-Agent-Id |
220
- | 403 | FORBIDDEN | scope mismatch, or `by` doesn't match `X-Agent-Id` |
221
- | 404 | FILE_NOT_FOUND | path doesn't exist under server root |
222
- | 409 | STALE_REVISION | `baseRevision` wrong, retry with included snapshot |
223
- | 409 | BLOCK_NOT_FOUND | ref no longer exists, refetch snapshot |
224
- | 409 | IDEMPOTENCY_KEY_REUSED | same key, different body |
225
- | 422 | INVALID_MARKDOWN | op's markdown failed to parse |
226
- | 429 | RATE_LIMITED | bucket exhausted, honor `Retry-After` header |
244
+ | Status | Code | Meaning |
245
+ | ------ | ---------------------- | --------------------------------------------------------------------------------------- |
246
+ | 401 | UNAUTHORIZED | bad/missing token or X-Agent-Id |
247
+ | 403 | FORBIDDEN | scope/path mismatch, workspace not covered by grant, or `by` doesn't match `X-Agent-Id` |
248
+ | 404 | FILE_NOT_FOUND | path doesn't exist under server root |
249
+ | 409 | STALE_REVISION | `baseRevision` wrong, retry with included snapshot |
250
+ | 409 | BLOCK_NOT_FOUND | ref no longer exists, refetch snapshot |
251
+ | 409 | IDEMPOTENCY_KEY_REUSED | same key, different body |
252
+ | 422 | INVALID_MARKDOWN | op's markdown failed to parse |
253
+ | 429 | RATE_LIMITED | bucket exhausted, honor `Retry-After` header |
227
254
 
228
255
  ---
229
256
 
@@ -0,0 +1,364 @@
1
+ # Workspacing plan
2
+
3
+ > **Status: IMPLEMENTED.** Phases A–E complete. 294 tests pass, tsc clean.
4
+ > Key files: `src/lib/workspaces.ts`, `src/lib/workspace-context.ts`,
5
+ > `src/lib/auth/admin.ts`, `src/lib/workspace-client.ts`, workspace + admin
6
+ > APIs under `src/app/api/system/`, agent scope via `scope.workspaceId`.
7
+ > The legacy `src/lib/root-dir.ts` global is retained as a fallback source
8
+ > (synthetic default workspace) for `ROOT_DIR`/CLI/tests — not yet deleted.
9
+
10
+ ## Goal
11
+
12
+ Let one running wiki-viewer serve multiple root directories ("workspaces")
13
+ concurrently, with zero state bleed between them. A user can have two tabs open
14
+ on two different workspaces at once. AI agents target a specific workspace.
15
+
16
+ Decisions (locked):
17
+
18
+ - **Selection**: URL/query carries `ws=<wsId>` for browser routes; agents send
19
+ an `X-Workspace: <wsId>` header. No process-global "current root".
20
+ - **Agents are workspace-scoped**: every agent request resolves a workspace and
21
+ is denied if it has no grant for it.
22
+ - **Isolation type**: organizational, not OS-level ACL. We add an explicit
23
+ per-workspace access list (which users / which agents may use a workspace).
24
+ We do NOT add per-user filesystem path restrictions on the directory browser.
25
+
26
+ ## Why the current code bleeds
27
+
28
+ `src/lib/root-dir.ts` keeps a single `globalThis.__wikiRootDir`. Every route
29
+ calls `getRootDir()` / `safeRootPath()` against that one value, so:
30
+
31
+ - "Change" (`/api/system/clear-root`) wipes the root for every user/tab.
32
+ - Only one root can be active per process. No concurrency at all.
33
+
34
+ In-memory stores keyed by **relPath only** (collide across roots once more than
35
+ one root is live):
36
+
37
+ - `src/lib/proof/lease.ts` — `leases` + `generations` maps keyed by relPath.
38
+ - `src/lib/proof/idempotency.ts` — single LRU keyed by idempotency key.
39
+ - `src/lib/proof/mutex.ts` — in-process lock map keyed by relPath / mdPath.
40
+ - `src/lib/proof/file-lock.ts` — cross-process sentinel hashed from lockKey
41
+ (relPath). Two workspaces with `notes.md` share one lock.
42
+
43
+ Config is single-root shaped:
44
+
45
+ - `src/lib/config.ts` — one `lastOpenedPath`, one flat `pinnedPaths`.
46
+
47
+ Already correctly scoped (these take `rootDir` as a param, keep as-is):
48
+
49
+ - `src/lib/proof/sidecar.ts`, `activity.ts` — read/write under
50
+ `<rootDir>/.proof/`. Naturally isolated once `rootDir` is per-workspace.
51
+
52
+ Global, needs a `workspaceId` column / field:
53
+
54
+ - `src/lib/proof/audit.ts` — `agent_fs_audit` table mixes paths from all roots.
55
+ - `src/lib/proof/registry.ts` — `agents.json`; agent scope is path globs with
56
+ no workspace dimension.
57
+
58
+ ## Target architecture
59
+
60
+ ### 0. Admin role (new prerequisite)
61
+
62
+ No admin concept exists today: every signed-in user is equal, and the settings
63
+ routes just `requireUser`. Workspace access management is admin-gated, so we
64
+ introduce a minimal global admin role.
65
+
66
+ New `src/lib/auth/admin.ts`:
67
+
68
+ ```ts
69
+ isAdmin(userId, email): Promise<boolean>
70
+ listAdmins(): Promise<string[]> // user ids
71
+ addAdmin(userId): Promise<void>
72
+ removeAdmin(userId): Promise<void> // refuse to remove the last admin
73
+ requireAdmin(req): Promise<{ ok: true; user } | { ok: false; status; code }>
74
+ ```
75
+
76
+ Resolution:
77
+
78
+ - `adminUserIds: string[]` stored in `config.json`.
79
+ - `WIKI_ADMIN_EMAILS` (csv) env: any matching signed-in user is treated as
80
+ admin regardless of config (seed/override, headless-friendly).
81
+ - **Bootstrap**: if `adminUserIds` is empty AND no `WIKI_ADMIN_EMAILS`, the
82
+ first user to sign up is written into `adminUserIds`. Hook the better-auth
83
+ signup path, or do it lazily on the first authenticated request when the
84
+ admin set is empty (behind the config write mutex). Log once.
85
+ - `requireAdmin` returns 403 `ADMIN_REQUIRED` for non-admins.
86
+
87
+ Admin management API: `GET/POST/DELETE /api/system/admins` (admin-gated; list
88
+ users, promote, demote). `DELETE` refuses to remove the last admin (lockout
89
+ guard). A small Admins section in the settings sheet lists users (from the
90
+ better-auth `user` table) with promote/demote toggles.
91
+
92
+ Non-admin users keep full file access to workspaces they are granted; admin is
93
+ only about creating workspaces and editing access lists (and promoting admins).
94
+
95
+ ### 1. Workspace registry (replaces single rootDir)
96
+
97
+ New `src/lib/workspaces.ts`. Persisted in `~/.wiki-viewer/config.json` under a
98
+ new `workspaces` array. Each entry:
99
+
100
+ ```ts
101
+ interface Workspace {
102
+ id: string; // "ws_" + short random; stable, URL-safe
103
+ name: string; // display label, defaults to basename(rootDir)
104
+ rootDir: string; // absolute, resolved
105
+ createdAt: string;
106
+ lastOpenedAt?: string;
107
+ pinnedPaths?: string[]; // moved here from the flat top-level list
108
+ createdBy?: string; // user id of the admin who created it
109
+ // access management (organizational, admin-edited only):
110
+ allowedUserIds?: string[]; // empty/undefined = any signed-in user
111
+ // agent grants live in the agent registry, keyed by workspaceId
112
+ }
113
+ ```
114
+
115
+ API surface (pure functions, no globalThis root):
116
+
117
+ ```ts
118
+ listWorkspaces(): Promise<Workspace[]>
119
+ getWorkspace(id): Promise<Workspace | null>
120
+ createWorkspace({ rootDir, name? }): Promise<Workspace>
121
+ renameWorkspace(id, name): Promise<void>
122
+ removeWorkspace(id): Promise<void> // does NOT touch the directory
123
+ setWorkspaceAccess(id, userIds): Promise<void> // admin-only, edits allowedUserIds
124
+ userCanAccess(ws, userId): boolean // admins always pass; else allowedUserIds check
125
+ resolveWorkspaceRoot(id): Promise<string|null>
126
+ safeWorkspacePath(rootDir, rel): string|null // the old safeRootPath logic
127
+ ```
128
+
129
+ `safeRootPath` logic moves here as `safeWorkspacePath(rootDir, rel)` (takes an
130
+ explicit root instead of reading the global). Keep `root-dir.ts` as a thin
131
+ deprecated shim during migration if helpful, then delete.
132
+
133
+ ### 2. Request-scoped workspace resolution
134
+
135
+ New `src/lib/workspace-context.ts`:
136
+
137
+ ```ts
138
+ // Browser/session routes
139
+ async function resolveWorkspaceForUser(
140
+ req,
141
+ ): Promise<
142
+ | { ok: true; ws: Workspace; rootDir: string }
143
+ | { ok: false; status: number; code: string }
144
+ >;
145
+ ```
146
+
147
+ Resolution order for browser routes:
148
+
149
+ 1. `ws` query param (preferred) or `x-workspace` header.
150
+ 2. If absent, fall back to the user's `lastOpenedAt` workspace (back-compat,
151
+ single-workspace users never notice).
152
+ 3. 400 `WORKSPACE_REQUIRED` if none and more than one workspace exists.
153
+
154
+ Then enforce `userCanAccess(ws, user.id)` → 403 `WORKSPACE_FORBIDDEN`.
155
+
156
+ For agent routes, `checkAuth` (in `src/lib/proof/auth.ts`) additionally reads
157
+ `X-Workspace`, looks up the workspace, and checks the agent's grant for it
158
+ (see §5). Returns `{ ok, agent, workspace, rootDir }`.
159
+
160
+ ### 3. Namespacing the in-memory stores
161
+
162
+ Every store keyed by relPath becomes keyed by `wsId + "\0" + relPath` (the
163
+ workspace id is a stable prefix, NUL separator avoids collisions).
164
+
165
+ - `lease.ts`: `setLease`, `hasActiveLease`, `clearLease`, `leaseGeneration`
166
+ gain a `wsId` first arg → internal key `${wsId}\0${relPath}`.
167
+ `computeCollabState(rootDir, relPath)` already has rootDir; add `wsId` (or
168
+ derive a key from rootDir — but explicit wsId is cleaner). Update
169
+ `collab-state.ts` and `presence/route.ts` callers.
170
+ - `mutex.ts` / `file-lock.ts`: callers pass a key already; change every call
171
+ site to prefix with `wsId` (e.g. `withFileMutex(`${wsId}\0${relPath}`, ...)`).
172
+ Cross-process sentinel hash then differs per workspace automatically.
173
+ - `idempotency.ts`: prefix the key with `wsId` at the call site
174
+ (`events/[...path]/route.ts`, `files/[...path]/route.ts`).
175
+ - `registry.ts` `lastSeenWriteAt` throttle map is keyed by agentId — fine, no
176
+ change.
177
+
178
+ ### 4. Threading rootDir through routes (29 files)
179
+
180
+ Pattern, every session route:
181
+
182
+ ```ts
183
+ const ctx = await resolveWorkspaceForUser(request);
184
+ if (!ctx.ok)
185
+ return NextResponse.json({ error: ctx.code }, { status: ctx.status });
186
+ const { ws, rootDir } = ctx;
187
+ const filePath = safeWorkspacePath(rootDir, rel);
188
+ ```
189
+
190
+ Replace all `getRootDir()` → `ctx.rootDir`, `safeRootPath(rel)` →
191
+ `safeWorkspacePath(rootDir, rel)`, `isRootDirSet()` → existence of ctx.
192
+
193
+ Route inventory (all under `src/app/api`):
194
+
195
+ - `wiki/route.ts`, `wiki/content/route.ts`, `wiki/page/route.ts`,
196
+ `wiki/slugs/route.ts`, `wiki/file/route.ts`, `wiki/new-file/route.ts`,
197
+ `wiki/folder/route.ts`, `wiki/move/route.ts`, `wiki/upload/route.ts`,
198
+ `wiki/download/route.ts`, `wiki/app/route.ts`, `wiki/watch/route.ts`,
199
+ `wiki/presence/route.ts`
200
+ - `assets/[...path]/route.ts`, `upload/[...path]/route.ts`
201
+ - `system/reveal/route.ts`
202
+ - agent: `fs/file`, `fs/ls`, `fs/move`, `fs/search`, `files`, `events`,
203
+ `sidecar`, `activity`, `settings`, `internal/span`
204
+
205
+ `watch/route.ts` (SSE / chokidar): watcher must watch `ws.rootDir`. Each EventSource
206
+ connection is per-workspace; the client opens `/api/wiki/watch?ws=<id>`.
207
+
208
+ ### 5. Workspace-scoped agents
209
+
210
+ Agent grants gain a workspace dimension. In `registry.ts`:
211
+
212
+ ```ts
213
+ interface AgentScope {
214
+ workspaceId: string; // NEW — the workspace this grant applies to
215
+ paths: string[];
216
+ ops: Array<"read" | "mutate" | "delete">;
217
+ }
218
+ ```
219
+
220
+ (An agent id may appear once per workspace, or scope becomes an array of
221
+ per-workspace grants. Simpler: one registry row per (agentId, workspaceId).
222
+ Recommend: keep `Agent.id` unique, add `workspaceId` to the record; an agent
223
+ that needs two workspaces registers twice.)
224
+
225
+ - `register` route: request body includes target `workspaceId`. Approval UI in
226
+ the AI panel shows which workspace the request is for.
227
+ - `checkAuth`: resolve `X-Workspace`, then `enforceScope` also checks
228
+ `agent.scope.workspaceId === ws.id`. Mismatch → 403.
229
+ - Browser session users (`user:<id>`) are synthesized in `checkAuth` with full
230
+ scope; gate them with `userCanAccess(ws, userId)` instead.
231
+ - `audit.ts`: add `workspace_id` column to `agent_fs_audit`
232
+ (`ALTER TABLE ... ADD COLUMN`; tolerate existing DBs). Write it on every row.
233
+
234
+ ### 6. Config migration
235
+
236
+ On first read of `config.json` with the new code:
237
+
238
+ - If `workspaces` is absent but `lastOpenedPath` exists, synthesize one
239
+ workspace `{ id: ws_*, rootDir: lastOpenedPath, name: basename, pinnedPaths:
240
+ config.pinnedPaths }` and mark it lastOpened. Keep old fields for one release
241
+ for rollback safety; new writes use `workspaces`.
242
+ - `agents.json`: existing agents have no `workspaceId`. On load, backfill them
243
+ to the migrated default workspace id (and log once, mirroring the existing
244
+ legacy `ownerUserId` handling in the README checklist).
245
+
246
+ ### 7. API: workspace management routes
247
+
248
+ New under `src/app/api/system/workspaces`:
249
+
250
+ - `GET /api/system/workspaces` — list workspaces visible to the user
251
+ (filtered by `userCanAccess`).
252
+ - `POST /api/system/workspaces` — create `{ rootDir, name? }` (validates dir
253
+ exists, isDirectory). Replaces `set-root` semantics.
254
+ - `PATCH /api/system/workspaces/<id>` — rename / pin-unpin allowed for any
255
+ user with access; **editing `allowedUserIds` is admin-only** (enforced field
256
+ by field in the handler).
257
+ - `DELETE /api/system/workspaces/<id>` — **admin-only**. Unregister (directory
258
+ untouched).
259
+
260
+ Workspace create (`POST`) is **admin-only** and sets `createdBy`. `GET` is open
261
+ to any signed-in user but filtered: admins see all, others see only workspaces
262
+ they can access. Admins management: `GET/POST/DELETE /api/system/admins`.
263
+
264
+ Deprecate / re-point: `set-root`, `clear-root`, `root-status`, `pins`. Keep
265
+ thin back-compat shims that operate on "the user's current/last workspace" so
266
+ nothing 404s mid-migration, then remove in a follow-up.
267
+
268
+ ### 8. Client (browser)
269
+
270
+ - `src/app/page.tsx`: hold `activeWorkspaceId` in state. Read from URL `?ws=`
271
+ first, else last opened. Put `ws` into every fetch (`/api/wiki?ws=...`,
272
+ watch URL, presence body). Cleanest: a small `wsFetch(path, init)` helper /
273
+ thin client store (`workspace-store.ts`) that injects `ws` automatically so
274
+ we are not editing dozens of `fetch` calls by hand.
275
+ - Sidebar footer "Change" → opens a workspace switcher (list of workspaces the
276
+ user may access). "Add workspace" (reuses `DirPicker`) is shown **only to
277
+ admins**. Switching navigates to `?ws=<id>` (and `path` resets). Two tabs
278
+ with different `?ws=` are fully independent.
279
+ - `DirPicker` (`src/components/dir-picker.tsx`): admin-only path. "Select" now
280
+ POSTs `/api/system/workspaces` (create) instead of `set-root`, then routes
281
+ to `?ws=<newId>`. Pins move under the chosen workspace.
282
+ - Settings sheet, admins only: an **Admins** section (promote/demote users)
283
+ and a per-workspace **Access** editor (`allowedUserIds`). The client reads an
284
+ `isAdmin` flag (from `/api/system/admins` or root-status) to gate this UI.
285
+ - `wiki-slugs-store`, `editor-store`, `tree-store`: any cached state that is
286
+ keyed by path must also be reset on workspace switch (cheap: remount the
287
+ page subtree on `ws` change via a React `key={activeWorkspaceId}`).
288
+
289
+ ### 9. CLI / startup (`bin/wiki-viewer.js`, `ROOT_DIR`)
290
+
291
+ - `ROOT_DIR` (and `wiki-viewer ~/dir`) still works: on boot, if no workspaces
292
+ exist, auto-create one from `ROOT_DIR` and mark it default/lastOpened. So the
293
+ zero-config single-dir path is unchanged for existing users.
294
+ - README "switch the served directory" copy updates to mention multiple
295
+ concurrent workspaces.
296
+
297
+ ## Isolation guarantees (the "no bleed" contract)
298
+
299
+ | Surface | Before | After |
300
+ | -------------------- | ----------------- | ------------------------------- |
301
+ | Root dir | one global | per request, from `ws` |
302
+ | Sidecars / activity | `<root>/.proof/` | unchanged (already per-root) |
303
+ | Edit leases | key=relPath | key=`wsId\0relPath` |
304
+ | In-proc + file locks | key=relPath | key=`wsId\0relPath` |
305
+ | Idempotency LRU | key=idemKey | key=`wsId\0idemKey` |
306
+ | Audit DB | path only | `+ workspace_id` column |
307
+ | Agents | global path globs | `+ workspaceId` grant, enforced |
308
+ | Access control | any signed-in | admin-set `allowedUserIds`/ws |
309
+
310
+ Two workspaces sharing the same relative path (`notes.md`) can be edited
311
+ simultaneously without lock contention, lease confusion, or idempotency
312
+ cross-talk.
313
+
314
+ ## Testing
315
+
316
+ Extend `src/tests/proof/*`:
317
+
318
+ - Two-workspace lease test: lease on `(wsA, x.md)` does not make
319
+ `(wsB, x.md)` active.
320
+ - Mutex/lock test: concurrent writes to same relPath in two workspaces do not
321
+ serialize against each other (or at least do not corrupt).
322
+ - Idempotency: same key in two workspaces are independent.
323
+ - Scope test: agent granted `wsA` is 403 on `X-Workspace: wsB`.
324
+ - Access test: user not in `allowedUserIds` is 403; admin always passes.
325
+ - Admin test: non-admin POST /workspaces is 403; last-admin DELETE refused;
326
+ first-signup bootstrap promotes one admin.
327
+ - Migration test: old config.json (lastOpenedPath + pinnedPaths) yields one
328
+ workspace; old agents.json backfills workspaceId.
329
+ - Update existing tests: `setRootDir(tmpRoot)` calls in test setup become
330
+ "create a workspace + resolve it" (helper).
331
+
332
+ ## Rollout / sequencing
333
+
334
+ 0. Admin role: `auth/admin.ts`, `/api/system/admins`, bootstrap, settings UI
335
+ section. Independent, shippable alone.
336
+ 1. `workspaces.ts` + `workspace-context.ts` + config migration (no route
337
+ changes yet; default workspace mirrors old behavior).
338
+ 2. Namespacing stores (lease, mutex, file-lock, idempotency) with `wsId` arg,
339
+ defaulting to the single default workspace so nothing breaks.
340
+ 3. Thread `ctx.rootDir` through all 29 routes; delete `getRootDir` global.
341
+ 4. Agent workspace scoping + audit column.
342
+ 5. Workspace management API + client switcher + DirPicker repoint.
343
+ 6. Tests, README, CLI copy.
344
+
345
+ Steps 1-2 are non-breaking and shippable alone. Step 3 is the big mechanical
346
+ diff (good candidate for a `worker` pass with explicit outcomes per route).
347
+
348
+ ## Open questions / risks
349
+
350
+ - **`pnpm-workspace.yaml` naming clash**: this repo is itself a pnpm workspace.
351
+ Use "workspace" in UI/API, but consider an internal code name like `space` or
352
+ keep `workspace` and just be careful in docs. Low risk, naming only.
353
+ - **SSE watcher fan-out**: one chokidar watcher per (connection, workspace).
354
+ Many open tabs on the same workspace = many watchers. Acceptable now;
355
+ optimize later with a shared per-workspace watcher + ref count if needed.
356
+ - **Admin bootstrap timing**: writing the first user into `adminUserIds` must
357
+ happen exactly once. Better-auth has no built-in "first user" hook, so
358
+ either tap the signup callback or do it lazily on the first authenticated
359
+ request when the admin set is empty (race-safe behind the config write
360
+ mutex). Decide during step 0.
361
+ - **`allowedUserIds` default**: empty = any signed-in user (matches today's
362
+ behavior). Admins tighten per workspace via the Access editor.
363
+ - Removing a workspace leaves `.proof/` and `.locks/` sentinels on disk; fine
364
+ (directory untouched by design), but document it.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wiki-viewer",
3
- "version": "1.7.2",
3
+ "version": "1.8.1",
4
4
  "description": "Local file viewer and wiki — view markdown, HTML, PDFs, notebooks, office docs, and more from any directory",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  "wiki": "node bin/wiki-viewer.js",
20
20
  "postbuild": "node bin/postbuild.js",
21
21
  "prepublishOnly": "npm run build",
22
- "test": "tsx --test src/tests/proof/*.test.ts"
22
+ "test": "tsx --import ./src/tests/proof/preload.ts --test src/tests/proof/*.test.ts"
23
23
  },
24
24
  "engines": {
25
25
  "node": ">=18.0.0"
@@ -90,11 +90,12 @@ node dist/index.js # start MCP server
90
90
  Set three environment variables before starting the MCP server
91
91
  (the `register` command prints them for you after approval):
92
92
 
93
- | Var | Description |
94
- | ---------------------- | ------------------------------------------------------------------------ |
95
- | `WIKI_VIEWER_URL` | Base URL of your wiki-viewer instance, e.g. `https://notes.example.com` |
96
- | `WIKI_VIEWER_TOKEN` | Bearer token obtained via `wiki-viewer-mcp register` |
97
- | `WIKI_VIEWER_AGENT_ID` | Your agent ID (e.g. `ai:myagent`), sent as `X-Agent-Id` on every request |
93
+ | Var | Description |
94
+ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
95
+ | `WIKI_VIEWER_URL` | Base URL of your wiki-viewer instance, e.g. `https://notes.example.com` |
96
+ | `WIKI_VIEWER_TOKEN` | Bearer token obtained via `wiki-viewer-mcp register` |
97
+ | `WIKI_VIEWER_AGENT_ID` | Your agent ID (e.g. `ai:myagent`), sent as `X-Agent-Id` on every request |
98
+ | `WIKI_VIEWER_WORKSPACE` | _Optional._ Workspace id, sent as `X-Workspace` on every request. Targets one root directory when the instance serves several. Omit for single-workspace instances (the server uses its default). |
98
99
 
99
100
  ## Usage in Claude Code / Cursor / Codex
100
101
 
@@ -56,11 +56,13 @@ export class WikiViewerClient {
56
56
  baseUrl;
57
57
  token;
58
58
  agentId;
59
+ workspace;
59
60
  _fetch;
60
61
  constructor(config) {
61
62
  this.baseUrl = config.baseUrl.replace(/\/$/, "");
62
63
  this.token = config.token;
63
64
  this.agentId = config.agentId;
65
+ this.workspace = config.workspace;
64
66
  this._fetch = config.fetch ?? globalThis.fetch;
65
67
  }
66
68
  // ── Helpers ────────────────────────────────────────────────────────────────
@@ -68,6 +70,8 @@ export class WikiViewerClient {
68
70
  return {
69
71
  Authorization: `Bearer ${this.token}`,
70
72
  "X-Agent-Id": this.agentId,
73
+ // Target a specific workspace when configured. Omitted = server default.
74
+ ...(this.workspace ? { "X-Workspace": this.workspace } : {}),
71
75
  ...extra,
72
76
  };
73
77
  }
@@ -9,6 +9,7 @@
9
9
  * WIKI_VIEWER_URL Base URL of wiki-viewer instance (required)
10
10
  * WIKI_VIEWER_TOKEN Bearer token from TOFU registration (required)
11
11
  * WIKI_VIEWER_AGENT_ID X-Agent-Id header value (required)
12
+ * WIKI_VIEWER_WORKSPACE Workspace id for X-Workspace header (optional; targets one workspace)
12
13
  *
13
14
  * Mode-awareness:
14
15
  * Before any raw write to a .md file, the shim checks the cached
@@ -62,6 +63,7 @@ export function createClient(overrides) {
62
63
  baseUrl: overrides?.baseUrl ?? requireEnv("WIKI_VIEWER_URL"),
63
64
  token: overrides?.token ?? requireEnv("WIKI_VIEWER_TOKEN"),
64
65
  agentId: overrides?.agentId ?? requireEnv("WIKI_VIEWER_AGENT_ID"),
66
+ workspace: overrides?.workspace ?? process.env.WIKI_VIEWER_WORKSPACE,
65
67
  fetch: overrides?.fetch,
66
68
  });
67
69
  }
@@ -424,6 +426,7 @@ async function runRegister() {
424
426
  "scope-paths": { type: "string", default: "**/*" },
425
427
  ops: { type: "string", default: "read,mutate" },
426
428
  timeout: { type: "string", default: "300" },
429
+ workspace: { type: "string" },
427
430
  },
428
431
  allowPositionals: true,
429
432
  });
@@ -489,6 +492,7 @@ async function runRegister() {
489
492
  WIKI_VIEWER_URL: values.url,
490
493
  WIKI_VIEWER_TOKEN: result.token,
491
494
  WIKI_VIEWER_AGENT_ID: result.agentId,
495
+ ...(values.workspace ? { WIKI_VIEWER_WORKSPACE: values.workspace } : {}),
492
496
  },
493
497
  },
494
498
  },
@@ -15,6 +15,8 @@ export interface ClientConfig {
15
15
  baseUrl: string; // e.g. "https://notes.example.com"
16
16
  token: string; // Bearer token from TOFU registration
17
17
  agentId: string; // X-Agent-Id header
18
+ /** Target workspace id (X-Workspace header). Optional: omit for single-workspace instances. */
19
+ workspace?: string;
18
20
  /** Override fetch implementation (for testing) */
19
21
  fetch?: typeof fetch;
20
22
  }
@@ -116,12 +118,14 @@ export class WikiViewerClient {
116
118
  private readonly baseUrl: string;
117
119
  private readonly token: string;
118
120
  private readonly agentId: string;
121
+ private readonly workspace?: string;
119
122
  private readonly _fetch: typeof fetch;
120
123
 
121
124
  constructor(config: ClientConfig) {
122
125
  this.baseUrl = config.baseUrl.replace(/\/$/, "");
123
126
  this.token = config.token;
124
127
  this.agentId = config.agentId;
128
+ this.workspace = config.workspace;
125
129
  this._fetch = config.fetch ?? globalThis.fetch;
126
130
  }
127
131
 
@@ -131,6 +135,8 @@ export class WikiViewerClient {
131
135
  return {
132
136
  Authorization: `Bearer ${this.token}`,
133
137
  "X-Agent-Id": this.agentId,
138
+ // Target a specific workspace when configured. Omitted = server default.
139
+ ...(this.workspace ? { "X-Workspace": this.workspace } : {}),
134
140
  ...extra,
135
141
  };
136
142
  }