wiki-viewer 1.4.3 → 1.5.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 (218) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +5 -0
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/routes-manifest.json +34 -0
  6. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  14. package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/api/agent/activity/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js +8 -7
  24. package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route/app-paths-manifest.json +3 -0
  26. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route/build-manifest.json +9 -0
  27. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route/server-reference-manifest.json +4 -0
  28. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js +17 -0
  29. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js.map +5 -0
  30. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js.nft.json +1 -0
  31. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route_client-reference-manifest.js +3 -0
  32. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route/app-paths-manifest.json +3 -0
  33. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route/build-manifest.json +9 -0
  34. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route/server-reference-manifest.json +4 -0
  35. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js +15 -0
  36. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js.map +5 -0
  37. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js.nft.json +1 -0
  38. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route_client-reference-manifest.js +3 -0
  39. package/.next/standalone/.next/server/app/api/agent/fs/move/route/app-paths-manifest.json +3 -0
  40. package/.next/standalone/.next/server/app/api/agent/fs/move/route/build-manifest.json +9 -0
  41. package/.next/standalone/.next/server/app/api/agent/fs/move/route/server-reference-manifest.json +4 -0
  42. package/.next/standalone/.next/server/app/api/agent/fs/move/route.js +15 -0
  43. package/.next/standalone/.next/server/app/api/agent/fs/move/route.js.map +5 -0
  44. package/.next/standalone/.next/server/app/api/agent/fs/move/route.js.nft.json +1 -0
  45. package/.next/standalone/.next/server/app/api/agent/fs/move/route_client-reference-manifest.js +3 -0
  46. package/.next/standalone/.next/server/app/api/agent/fs/search/route/app-paths-manifest.json +3 -0
  47. package/.next/standalone/.next/server/app/api/agent/fs/search/route/build-manifest.json +9 -0
  48. package/.next/standalone/.next/server/app/api/agent/fs/search/route/server-reference-manifest.json +4 -0
  49. package/.next/standalone/.next/server/app/api/agent/fs/search/route.js +15 -0
  50. package/.next/standalone/.next/server/app/api/agent/fs/search/route.js.map +5 -0
  51. package/.next/standalone/.next/server/app/api/agent/fs/search/route.js.nft.json +1 -0
  52. package/.next/standalone/.next/server/app/api/agent/fs/search/route_client-reference-manifest.js +3 -0
  53. package/.next/standalone/.next/server/app/api/agent/internal/span/route.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/api/agent/settings/route.js +1 -1
  55. package/.next/standalone/.next/server/app/api/agent/settings/route.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/api/app-proxy/[...path]/route.js +1 -1
  58. package/.next/standalone/.next/server/app/api/app-proxy/[...path]/route.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/api/assets/[...path]/route.js.nft.json +1 -1
  60. package/.next/standalone/.next/server/app/api/system/browse/route.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/api/system/clear-root/route.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/api/system/reveal/route.js.nft.json +1 -1
  63. package/.next/standalone/.next/server/app/api/system/root-status/route.js.nft.json +1 -1
  64. package/.next/standalone/.next/server/app/api/system/set-root/route.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/api/upload/[...path]/route.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/api/wiki/app/route.js.nft.json +1 -1
  67. package/.next/standalone/.next/server/app/api/wiki/content/route.js +2 -2
  68. package/.next/standalone/.next/server/app/api/wiki/content/route.js.nft.json +1 -1
  69. package/.next/standalone/.next/server/app/api/wiki/file/route.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/api/wiki/folder/route.js.nft.json +1 -1
  71. package/.next/standalone/.next/server/app/api/wiki/move/route.js +3 -3
  72. package/.next/standalone/.next/server/app/api/wiki/move/route.js.nft.json +1 -1
  73. package/.next/standalone/.next/server/app/api/wiki/new-file/route.js.nft.json +1 -1
  74. package/.next/standalone/.next/server/app/api/wiki/page/route.js.nft.json +1 -1
  75. package/.next/standalone/.next/server/app/api/wiki/presence/route/app-paths-manifest.json +3 -0
  76. package/.next/standalone/.next/server/app/api/wiki/presence/route/build-manifest.json +9 -0
  77. package/.next/standalone/.next/server/app/api/wiki/presence/route/server-reference-manifest.json +4 -0
  78. package/.next/standalone/.next/server/app/api/wiki/presence/route.js +14 -0
  79. package/.next/standalone/.next/server/app/api/wiki/presence/route.js.map +5 -0
  80. package/.next/standalone/.next/server/app/api/wiki/presence/route.js.nft.json +1 -0
  81. package/.next/standalone/.next/server/app/api/wiki/presence/route_client-reference-manifest.js +3 -0
  82. package/.next/standalone/.next/server/app/api/wiki/route.js.nft.json +1 -1
  83. package/.next/standalone/.next/server/app/api/wiki/slugs/route.js.nft.json +1 -1
  84. package/.next/standalone/.next/server/app/api/wiki/upload/route.js.nft.json +1 -1
  85. package/.next/standalone/.next/server/app/api/wiki/watch/route.js.nft.json +1 -1
  86. package/.next/standalone/.next/server/app/index.html +1 -1
  87. package/.next/standalone/.next/server/app/index.rsc +2 -2
  88. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  89. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  90. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  91. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  92. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  93. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  94. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  95. package/.next/standalone/.next/server/app-paths-manifest.json +5 -0
  96. package/.next/standalone/.next/server/chunks/0p91_next_dist_esm_build_templates_app-route_0d2jbo5.js +3 -0
  97. package/.next/standalone/.next/server/chunks/0p91_next_dist_esm_build_templates_app-route_0ytral2.js +3 -0
  98. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-ao6a_._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0269d2e._.js +1 -1
  100. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05f6x0i._.js +3 -0
  101. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06-dtd0._.js +3 -0
  102. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06sek6_._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/[root-of-the-server]__09s9qkd._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d75a5q._.js +1 -1
  105. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d_da6q._.js +3 -0
  106. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e7si9p._.js +1 -1
  107. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0hhhvm7._.js +1 -1
  108. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kk26wc._.js +3 -0
  109. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kur4ro._.js +15 -0
  110. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0p12-6~._.js +1 -1
  111. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qc2~6l._.js +15 -0
  112. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0s~i040._.js +3 -0
  113. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0w47wil._.js +1 -1
  114. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xq124z._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l..bxz._.js → [root-of-the-server]__0xz-_0v._.js} +2 -2
  116. package/.next/standalone/.next/server/chunks/[root-of-the-server]__10w2q~o._.js +1 -1
  117. package/.next/standalone/.next/server/chunks/_079xuln._.js +3 -0
  118. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_agent_fs_file_[___path]_route_actions_0gb11t~.js +3 -0
  119. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_agent_fs_ls_[[___path]]_route_actions_0wfg4nl.js +3 -0
  120. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_agent_fs_move_route_actions_0z34mqq.js +3 -0
  121. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_agent_fs_search_route_actions_0ut_ug..js +3 -0
  122. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_wiki_presence_route_actions_0x9d7f4.js +3 -0
  123. package/.next/standalone/.next/server/chunks/ssr/{0~w0_katex_dist_katex_mjs_055boxx._.js → 0~w0_katex_dist_katex_mjs_00650~e._.js} +1 -1
  124. package/.next/standalone/.next/server/chunks/ssr/12y~_mermaid_dist_chunks_mermaid_core_diagram-OG6HWLK6_mjs_0t5sus6._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/12y~_mermaid_dist_chunks_mermaid_core_sankeyDiagram-5OEKKPKP_mjs_11ifrnu._.js +1 -1
  126. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__134_q7l._.js → [root-of-the-server]__12ahxbl._.js} +2 -2
  127. package/.next/standalone/.next/server/chunks/ssr/_0pqaawz._.js +3 -3
  128. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0rc1vh5._.js → node_modules__pnpm_02gx8km._.js} +1 -1
  129. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0t4unpg._.js → node_modules__pnpm_02mew~s._.js} +2 -2
  130. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_12-ni1n._.js → node_modules__pnpm_03ij5.g._.js} +2 -2
  131. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_12~a4c_._.js → node_modules__pnpm_03v21p-._.js} +2 -2
  132. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0f~_mm3._.js → node_modules__pnpm_05qsply._.js} +2 -2
  133. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0alwr5e._.js → node_modules__pnpm_06pgebo._.js} +2 -2
  134. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_02zxe7o._.js → node_modules__pnpm_08ibh0w._.js} +2 -2
  135. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0hr027i._.js → node_modules__pnpm_099cv.b._.js} +1 -1
  136. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0znncy9._.js → node_modules__pnpm_0cwpt9p._.js} +2 -2
  137. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0-pisfi._.js → node_modules__pnpm_0f9fpy5._.js} +1 -1
  138. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0zhbnni._.js → node_modules__pnpm_0f_uzgy._.js} +1 -1
  139. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0-w-d-.._.js → node_modules__pnpm_0fixtms._.js} +1 -1
  140. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_03z0vkh._.js → node_modules__pnpm_0gofu~q._.js} +1 -1
  141. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_10i6lh6._.js → node_modules__pnpm_0iojvye._.js} +2 -2
  142. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0-hfev1._.js → node_modules__pnpm_0li2vzs._.js} +1 -1
  143. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_13kfy_z._.js → node_modules__pnpm_0m5-azh._.js} +1 -1
  144. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_071f6vl._.js → node_modules__pnpm_0n1kfcp._.js} +1 -1
  145. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_07uh2kc._.js → node_modules__pnpm_0rebktf._.js} +1 -1
  146. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0okw1r.._.js → node_modules__pnpm_0tkiej3._.js} +2 -2
  147. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0xuz.8f._.js → node_modules__pnpm_0v_nwsz._.js} +1 -1
  148. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0zb6573._.js → node_modules__pnpm_0wffzo0._.js} +1 -1
  149. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0d7jxsf._.js → node_modules__pnpm_0wouz19._.js} +1 -1
  150. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0egenev._.js → node_modules__pnpm_0xxwmv2._.js} +1 -1
  151. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0j9bq9s._.js → node_modules__pnpm_0zzzsrj._.js} +1 -1
  152. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0irsd.b._.js → node_modules__pnpm_0~.6dgb._.js} +1 -1
  153. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0erwv_m._.js → node_modules__pnpm_0~2~c30._.js} +1 -1
  154. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0y9k6h~._.js → node_modules__pnpm_10413dq._.js} +2 -2
  155. package/.next/standalone/.next/server/functions-config-manifest.json +5 -0
  156. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  157. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  158. package/.next/standalone/.next/server/pages/404.html +1 -1
  159. package/.next/standalone/.next/server/pages/500.html +1 -1
  160. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  161. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  162. package/.next/standalone/.next/static/chunks/{11s9zwr7edrs7.js → 17~r3.8awh3qg.js} +12 -12
  163. package/.next/standalone/README.md +79 -16
  164. package/.next/standalone/agents/bootstrap-prompt.md +12 -1
  165. package/.next/standalone/agents/wiki-viewer-skill/SKILL.md +199 -6
  166. package/.next/standalone/docs/file-vs-collab-authority.md +124 -0
  167. package/.next/standalone/package.json +1 -1
  168. package/.next/standalone/packages/wiki-viewer-mcp/README.md +194 -0
  169. package/.next/standalone/packages/wiki-viewer-mcp/dist/http-client.js +220 -0
  170. package/.next/standalone/packages/wiki-viewer-mcp/dist/index.js +472 -0
  171. package/.next/standalone/packages/wiki-viewer-mcp/dist/register.js +94 -0
  172. package/.next/standalone/packages/wiki-viewer-mcp/dist/state-cache.js +26 -0
  173. package/.next/standalone/packages/wiki-viewer-mcp/package-lock.json +1716 -0
  174. package/.next/standalone/packages/wiki-viewer-mcp/package.json +41 -0
  175. package/.next/standalone/packages/wiki-viewer-mcp/src/http-client.ts +344 -0
  176. package/.next/standalone/packages/wiki-viewer-mcp/src/index.ts +575 -0
  177. package/.next/standalone/packages/wiki-viewer-mcp/src/register.ts +144 -0
  178. package/.next/standalone/packages/wiki-viewer-mcp/src/state-cache.ts +45 -0
  179. package/.next/standalone/packages/wiki-viewer-mcp/tsconfig.json +19 -0
  180. package/.next/standalone/pnpm-lock.yaml +9 -9
  181. package/.next/standalone/src/app/api/agent/files/[...path]/route.ts +11 -1
  182. package/.next/standalone/src/app/api/agent/fs/file/[...path]/route.ts +344 -0
  183. package/.next/standalone/src/app/api/agent/fs/ls/[[...path]]/route.ts +147 -0
  184. package/.next/standalone/src/app/api/agent/fs/move/route.ts +125 -0
  185. package/.next/standalone/src/app/api/agent/fs/search/route.ts +236 -0
  186. package/.next/standalone/src/app/api/agent/register/route.ts +3 -3
  187. package/.next/standalone/src/app/api/agents/install/route.ts +44 -2
  188. package/.next/standalone/src/app/api/system/auth-settings/route.ts +1 -0
  189. package/.next/standalone/src/app/api/wiki/move/route.ts +10 -1
  190. package/.next/standalone/src/app/api/wiki/presence/route.ts +55 -0
  191. package/.next/standalone/src/components/ai-panel/ai-panel.tsx +98 -103
  192. package/.next/standalone/src/components/ai-panel/token-section.tsx +8 -7
  193. package/.next/standalone/src/components/auth-settings-sheet.tsx +81 -22
  194. package/.next/standalone/src/components/editor/editor.tsx +38 -0
  195. package/.next/standalone/src/lib/proof/audit.ts +79 -0
  196. package/.next/standalone/src/lib/proof/auth.ts +2 -2
  197. package/.next/standalone/src/lib/proof/collab-state.ts +79 -0
  198. package/.next/standalone/src/lib/proof/lease.ts +104 -0
  199. package/.next/standalone/src/lib/proof/ops-applier.ts +74 -25
  200. package/.next/standalone/src/lib/proof/raw-fs.ts +198 -0
  201. package/.next/standalone/src/lib/proof/registry.ts +1 -1
  202. package/.next/standalone/src/lib/proof/sidecar.ts +38 -1
  203. package/.next/standalone/src/lib/proof/types.ts +13 -1
  204. package/.next/standalone/src/tests/proof/agent-fs.test.ts +824 -0
  205. package/.next/standalone/src/tests/proof/agents-install.test.ts +53 -0
  206. package/.next/standalone/src/tests/proof/collab-state.test.ts +499 -0
  207. package/.next/standalone/src/tests/proof/reconcile-sidecar.test.ts +195 -0
  208. package/.next/standalone/src/tests/proof/sidecar-lifecycle.test.ts +177 -0
  209. package/.next/standalone/tsconfig.tsbuildinfo +1 -1
  210. package/README.md +79 -16
  211. package/package.json +1 -1
  212. package/.next/standalone/.next/server/chunks/[root-of-the-server]__09~~-nd._.js +0 -3
  213. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0w87kiz._.js +0 -3
  214. package/.next/standalone/.next/server/chunks/[root-of-the-server]__12ssbhv._.js +0 -3
  215. package/.next/standalone/.next/server/chunks/node_modules__pnpm_0g0fhfk._.js +0 -3
  216. /package/.next/standalone/.next/static/{kzOuSAQArtvD7uiAJ6ZLh → 3G_3qm2a9nOX7oi5k0mPV}/_buildManifest.js +0 -0
  217. /package/.next/standalone/.next/static/{kzOuSAQArtvD7uiAJ6ZLh → 3G_3qm2a9nOX7oi5k0mPV}/_clientMiddlewareManifest.js +0 -0
  218. /package/.next/standalone/.next/static/{kzOuSAQArtvD7uiAJ6ZLh → 3G_3qm2a9nOX7oi5k0mPV}/_ssgManifest.js +0 -0
@@ -1,12 +1,11 @@
1
1
  "use client";
2
2
 
3
3
  import { useEffect, useCallback, useRef, useState } from "react";
4
- import { X, Copy, Check, Bot, Wifi, Download, ExternalLink } from "lucide-react";
4
+ import { X, Copy, Check, Bot, Wifi, ExternalLink } from "lucide-react";
5
5
  import { Button } from "@/components/ui/button";
6
6
  import { useAIPanelStore } from "@/stores/ai-panel-store";
7
7
  import { TokenSection } from "./token-section";
8
8
  import { ActivityRow } from "./activity-row";
9
- import { authClient } from "@/lib/auth/client";
10
9
  const SKILL_CLI = "npx skills add anh-chu/wiki-viewer/agents/wiki-viewer-skill";
11
10
 
12
11
  function useCopyCurl(currentPath: string | null) {
@@ -55,7 +54,6 @@ export function AIPanel({ currentPath }: { currentPath?: string | null }) {
55
54
  const close = useAIPanelStore((s) => s.close);
56
55
  const activity = useAIPanelStore((s) => s.activity);
57
56
  const connections = useAIPanelStore((s) => s.connections);
58
- const rateLimit = useAIPanelStore((s) => s.rateLimit);
59
57
  const loadActivity = useAIPanelStore((s) => s.loadActivity);
60
58
  const { copied, copy } = useCopyCurl(currentPath ?? null);
61
59
  const [bootstrapPrompt, setBootstrapPrompt] = useState<string>("");
@@ -68,8 +66,36 @@ export function AIPanel({ currentPath }: { currentPath?: string | null }) {
68
66
  () => bootstrapPrompt.replace(/\$WIKI_URL/g, origin),
69
67
  [bootstrapPrompt, origin]
70
68
  );
69
+ const getMcpRegister = useCallback(
70
+ () =>
71
+ `npx wiki-viewer-mcp register --url ${origin} --id ai:myagent --name "My Agent"`,
72
+ [origin]
73
+ );
74
+ const getMcpJson = useCallback(
75
+ () =>
76
+ JSON.stringify(
77
+ {
78
+ servers: {
79
+ "wiki-viewer": {
80
+ command: "npx",
81
+ args: ["wiki-viewer-mcp"],
82
+ env: {
83
+ WIKI_VIEWER_URL: origin,
84
+ WIKI_VIEWER_TOKEN: "<token from register>",
85
+ WIKI_VIEWER_AGENT_ID: "ai:myagent",
86
+ },
87
+ },
88
+ },
89
+ },
90
+ null,
91
+ 2
92
+ ),
93
+ [origin]
94
+ );
71
95
  const skillCli = useCopyButton(getSkillCli);
72
96
  const bootstrapCopy = useCopyButton(getBootstrapPrompt);
97
+ const mcpRegister = useCopyButton(getMcpRegister);
98
+ const mcpJson = useCopyButton(getMcpJson);
73
99
 
74
100
  // Keyboard: Esc closes
75
101
  useEffect(() => {
@@ -144,74 +170,84 @@ export function AIPanel({ currentPath }: { currentPath?: string | null }) {
144
170
 
145
171
  {/* Scrollable body */}
146
172
  <div className="flex-1 overflow-y-auto space-y-5 px-4 py-4">
147
- <SignedInUser />
173
+ {/* What this does — one line */}
174
+ <section className="rounded-md border border-border bg-muted/30 p-3">
175
+ <p className="text-sm font-medium text-foreground">
176
+ Work with this wiki using AI
177
+ </p>
178
+ <p className="mt-1 text-xs leading-relaxed text-muted-foreground">
179
+ Connect Claude, Cursor, ChatGPT, and more. Pick by what you want —
180
+ each option gives a copy-paste setup. You approve every assistant
181
+ in <span className="font-medium text-foreground/80">Agents</span> below before it gets access.
182
+ </p>
183
+ </section>
148
184
 
149
- {/* Bridge endpoint */}
150
- <section className="space-y-2">
151
- <h3 className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
152
- Bridge Endpoint
153
- </h3>
154
- <div className="rounded-md border border-border bg-muted/40 p-3 space-y-2">
185
+ {/* Choose by goal */}
186
+ <section className="space-y-3">
187
+ {/* Goal 1: collaborate (one-off instructions + permanent skill) */}
188
+ <div className="rounded-md border border-border bg-muted/40 p-3 space-y-1.5">
189
+ <p className="text-xs font-semibold text-foreground">
190
+ Co-write docs with you
191
+ </p>
192
+ <p className="text-[10px] text-muted-foreground/70">
193
+ Edits arrive as suggestions with comments you accept or revert.
194
+ No MCP needed.
195
+ </p>
196
+ <Button size="sm" variant="outline" className="h-7 w-full text-xs gap-1.5" onClick={() => void bootstrapCopy.copy()} disabled={!bootstrapPrompt}>
197
+ {bootstrapCopy.copied
198
+ ? <><Check className="h-3.5 w-3.5 text-green-500" /> Copied!</>
199
+ : <><Copy className="h-3.5 w-3.5" /> Paste into any chatbot</>}
200
+ </Button>
201
+ <p className="text-[10px] text-muted-foreground/50 pt-1">
202
+ Or install once so it sticks{" "}
203
+ <span className="text-muted-foreground/40">· Claude, OpenCode</span>:
204
+ </p>
155
205
  <div className="flex items-center gap-2">
156
- <code className="flex-1 text-xs font-mono text-foreground/80 truncate">
157
- {origin}
206
+ <code className="flex-1 text-[10px] font-mono text-foreground/80 truncate bg-muted rounded px-1 py-0.5">
207
+ {SKILL_CLI}
158
208
  </code>
159
- <Button
160
- size="sm"
161
- variant="ghost"
162
- className="h-6 w-6 p-0 shrink-0"
163
- title="Copy curl example"
164
- onClick={() => void copy()}
165
- >
166
- {copied ? (
167
- <Check className="h-3.5 w-3.5 text-green-500" />
168
- ) : (
169
- <Copy className="h-3.5 w-3.5" />
170
- )}
209
+ <Button size="sm" variant="ghost" className="h-6 w-6 p-0 shrink-0" title="Copy this command" onClick={() => void skillCli.copy()}>
210
+ {skillCli.copied ? <Check className="h-3.5 w-3.5 text-green-500" /> : <Copy className="h-3.5 w-3.5" />}
171
211
  </Button>
172
212
  </div>
173
- <p className="text-[10px] text-muted-foreground/70">
174
- Copy icon copies a registration-flow <code className="bg-muted px-0.5 rounded">curl</code> trace for the current file.
175
- </p>
176
213
  </div>
177
- </section>
178
214
 
179
- {/* Install for AI agents */}
180
- <section className="space-y-2">
181
- <h3 className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
182
- Install for AI Agents
183
- </h3>
184
- <div className="rounded-md border border-border bg-muted/40 p-3 space-y-3">
185
- <div className="space-y-1">
186
- <p className="text-[10px] text-muted-foreground/70">Agent Skills standard:</p>
187
- <div className="flex items-center gap-2">
188
- <code className="flex-1 text-[10px] font-mono text-foreground/80 truncate bg-muted rounded px-1 py-0.5">
189
- {SKILL_CLI}
190
- </code>
191
- <Button size="sm" variant="ghost" className="h-6 w-6 p-0 shrink-0" title="Copy CLI command" onClick={() => void skillCli.copy()}>
192
- {skillCli.copied ? <Check className="h-3.5 w-3.5 text-green-500" /> : <Copy className="h-3.5 w-3.5" />}
193
- </Button>
194
- </div>
195
- </div>
196
- <div className="space-y-1">
197
- <p className="text-[10px] text-muted-foreground/70">Any agent (paste into chat):</p>
198
- <Button size="sm" variant="outline" className="h-7 w-full text-xs gap-1.5" onClick={() => void bootstrapCopy.copy()} disabled={!bootstrapPrompt}>
199
- {bootstrapCopy.copied
200
- ? <><Check className="h-3.5 w-3.5 text-green-500" /> Copied!</>
201
- : <><Copy className="h-3.5 w-3.5" /> Copy bootstrap prompt</>}
215
+ {/* Goal 2: raw filework */}
216
+ <div className="rounded-md border border-border bg-muted/40 p-3 space-y-1.5">
217
+ <p className="text-xs font-semibold text-foreground">
218
+ Edit files directly{" "}
219
+ <span className="font-normal text-muted-foreground/60">· Claude Code, Cursor, Codex</span>
220
+ </p>
221
+ <p className="text-[10px] text-muted-foreground/70">
222
+ Native read / search / edit tools over MCP. Run to request access:
223
+ </p>
224
+ <div className="flex items-center gap-2">
225
+ <code className="flex-1 text-[10px] font-mono text-foreground/80 truncate bg-muted rounded px-1 py-0.5">
226
+ {getMcpRegister()}
227
+ </code>
228
+ <Button size="sm" variant="ghost" className="h-6 w-6 p-0 shrink-0" title="Copy this command" onClick={() => void mcpRegister.copy()}>
229
+ {mcpRegister.copied ? <Check className="h-3.5 w-3.5 text-green-500" /> : <Copy className="h-3.5 w-3.5" />}
202
230
  </Button>
203
231
  </div>
204
- <div className="flex flex-wrap gap-x-3 gap-y-1 pt-1">
205
- <a href="/api/agents/skill.tar.gz" download className="inline-flex items-center gap-1 text-[10px] text-primary hover:underline">
206
- <Download className="h-3 w-3" /> skill (.tar.gz)
207
- </a>
208
- <a href="/api/agents/skill" target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 text-[10px] text-primary hover:underline">
209
- <ExternalLink className="h-3 w-3" /> skill markdown
210
- </a>
211
- <a href="/api/agents/install" target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 text-[10px] text-primary hover:underline">
212
- <ExternalLink className="h-3 w-3" /> install JSON
213
- </a>
214
- </div>
232
+ <p className="text-[10px] text-muted-foreground/50">Approve in <span className="font-medium text-foreground/70">Agents</span>, then add to your tool:</p>
233
+ <Button size="sm" variant="outline" className="h-7 w-full text-xs gap-1.5" onClick={() => void mcpJson.copy()}>
234
+ {mcpJson.copied
235
+ ? <><Check className="h-3.5 w-3.5 text-green-500" /> Copied!</>
236
+ : <><Copy className="h-3.5 w-3.5" /> Copy config (mcp.json)</>}
237
+ </Button>
238
+ </div>
239
+
240
+ <div className="flex flex-wrap items-center gap-x-3 gap-y-1">
241
+ <span className="text-[10px] text-muted-foreground/50">For developers:</span>
242
+ <button type="button" onClick={() => void copy()} className="inline-flex items-center gap-1 text-[10px] text-primary hover:underline">
243
+ {copied ? <Check className="h-3 w-3 text-green-500" /> : <Copy className="h-3 w-3" />} curl example
244
+ </button>
245
+ <a href="/api/agents/skill" target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 text-[10px] text-primary hover:underline">
246
+ <ExternalLink className="h-3 w-3" /> skill
247
+ </a>
248
+ <a href="/api/agents/install" target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 text-[10px] text-primary hover:underline">
249
+ <ExternalLink className="h-3 w-3" /> install JSON
250
+ </a>
215
251
  </div>
216
252
  </section>
217
253
 
@@ -263,24 +299,6 @@ export function AIPanel({ currentPath }: { currentPath?: string | null }) {
263
299
  </div>
264
300
  </section>
265
301
 
266
- {/* Settings */}
267
- <section className="space-y-2">
268
- <h3 className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
269
- Settings
270
- </h3>
271
- <div className="rounded-md border border-border bg-muted/40 p-3 space-y-1.5">
272
- <div className="flex items-center justify-between text-xs">
273
- <span className="text-muted-foreground">Rate limit</span>
274
- <span className="font-mono">
275
- {rateLimit !== null ? `${rateLimit} ops/min` : "--"}
276
- </span>
277
- </div>
278
- <p className="text-[10px] text-muted-foreground/60">
279
- Override with <code className="bg-muted px-0.5 rounded">AGENT_RATE_LIMIT</code> env var.
280
- </p>
281
- </div>
282
- </section>
283
-
284
302
  {/* Docs link */}
285
303
  <section>
286
304
  <a
@@ -297,26 +315,3 @@ export function AIPanel({ currentPath }: { currentPath?: string | null }) {
297
315
  </>
298
316
  );
299
317
  }
300
-
301
- function SignedInUser() {
302
- const { data: session } = authClient.useSession();
303
- if (!session?.user) return null;
304
- return (
305
- <div className="flex items-center justify-between rounded-md border border-border bg-muted/40 p-3">
306
- <div className="text-sm min-w-0">
307
- <div className="font-medium truncate">{session.user.name}</div>
308
- <div className="text-xs text-muted-foreground truncate">{session.user.email}</div>
309
- </div>
310
- <button
311
- type="button"
312
- onClick={async () => {
313
- await authClient.signOut();
314
- window.location.href = "/signin";
315
- }}
316
- className="ml-2 shrink-0 text-xs px-2 py-1 rounded border border-border hover:bg-accent"
317
- >
318
- Sign out
319
- </button>
320
- </div>
321
- );
322
- }
@@ -251,7 +251,7 @@ export function TokenSection() {
251
251
  <section className="space-y-2">
252
252
  <div className="flex items-center justify-between">
253
253
  <h3 className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
254
- Agents
254
+ Agents · approve &amp; manage access
255
255
  </h3>
256
256
  <Button
257
257
  size="sm"
@@ -269,7 +269,7 @@ export function TokenSection() {
269
269
  <div className="rounded-md border border-amber-500/40 bg-amber-50/10 p-2">
270
270
  <p className="text-[10px] font-semibold uppercase tracking-widest text-amber-600/80 mb-1 flex items-center gap-1">
271
271
  <Clock className="h-3 w-3" />
272
- Pending ({pending.length})
272
+ Waiting for your approval ({pending.length})
273
273
  </p>
274
274
  <ul>
275
275
  {pending.map((reg) => (
@@ -288,9 +288,10 @@ export function TokenSection() {
288
288
  <div className="rounded-md border border-border bg-muted/40 p-2">
289
289
  {agents.length === 0 ? (
290
290
  <p className="text-xs text-muted-foreground/60 py-1">
291
- No registered agents.{" "}
291
+ No assistants connected yet.{" "}
292
292
  <span className="text-muted-foreground/40">
293
- Agents register via POST /api/agent/register.
293
+ Connect one above; once it requests access it’ll appear here
294
+ for you to approve.
294
295
  </span>
295
296
  </p>
296
297
  ) : (
@@ -303,9 +304,9 @@ export function TokenSection() {
303
304
  </div>
304
305
 
305
306
  <p className="text-[10px] text-muted-foreground/60 leading-relaxed">
306
- Agents authenticate via bearer token + X-Agent-Id header. Use{" "}
307
- <code className="bg-muted px-0.5 rounded">POST /api/agent/register</code> to start the
308
- TOFU flow.
307
+ Each assistant gets its own access that you can revoke anytime. New
308
+ requests appear above for your approval before they can read or edit
309
+ anything.
309
310
  </p>
310
311
  </section>
311
312
  );
@@ -1,24 +1,19 @@
1
1
  "use client";
2
2
 
3
3
  import { useCallback, useEffect, useState } from "react";
4
- import { AlertCircle, Loader2 } from "lucide-react";
4
+ import { AlertCircle, Loader2, X } from "lucide-react";
5
5
 
6
6
  import { Button } from "@/components/ui/button";
7
- import {
8
- Sheet,
9
- SheetContent,
10
- SheetDescription,
11
- SheetFooter,
12
- SheetHeader,
13
- SheetTitle,
14
- } from "@/components/ui/sheet";
7
+ import { Sheet, SheetContent, SheetClose } from "@/components/ui/sheet";
15
8
  import { showError } from "@/lib/toast";
9
+ import { authClient } from "@/lib/auth/client";
16
10
 
17
11
  interface AuthSettings {
18
12
  allowedEmails: string[];
19
13
  allowedDomains: string[];
20
14
  source: "config" | "env";
21
15
  envFallbackActive: boolean;
16
+ rateLimit?: number;
22
17
  }
23
18
 
24
19
  function listToText(list: string[]): string {
@@ -45,6 +40,8 @@ export function AuthSettingsSheet({
45
40
  const [source, setSource] = useState<"config" | "env">("env");
46
41
  const [emailsText, setEmailsText] = useState("");
47
42
  const [domainsText, setDomainsText] = useState("");
43
+ const [rateLimit, setRateLimit] = useState<number | null>(null);
44
+ const { data: session } = authClient.useSession();
48
45
 
49
46
  const load = useCallback(async () => {
50
47
  setLoading(true);
@@ -56,6 +53,7 @@ export function AuthSettingsSheet({
56
53
  setEmailsText(listToText(data.allowedEmails));
57
54
  setDomainsText(listToText(data.allowedDomains));
58
55
  setSource(data.source);
56
+ setRateLimit(typeof data.rateLimit === "number" ? data.rateLimit : null);
59
57
  } catch (e) {
60
58
  setError(e instanceof Error ? e.message : "Failed to load");
61
59
  } finally {
@@ -101,22 +99,63 @@ export function AuthSettingsSheet({
101
99
 
102
100
  return (
103
101
  <Sheet open={open} onOpenChange={onOpenChange}>
104
- <SheetContent side="right" className="w-full sm:max-w-md flex flex-col">
105
- <SheetHeader>
106
- <SheetTitle>Signup allowlist</SheetTitle>
107
- <SheetDescription>
108
- Control who can create an account. Leave both lists empty to allow
109
- any email. Saving here overrides the AUTH_ALLOWED_EMAILS and
110
- AUTH_ALLOWED_DOMAIN environment variables.
111
- </SheetDescription>
112
- </SheetHeader>
102
+ <SheetContent
103
+ side="right"
104
+ className="w-80 sm:max-w-md flex flex-col border-l border-border p-0"
105
+ >
106
+ {/* Header matches AI panel chrome */}
107
+ <div className="flex items-center justify-between border-b border-border px-4 py-3 shrink-0">
108
+ <span className="text-sm font-semibold">Settings</span>
109
+ <SheetClose className="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent focus:outline-none focus:ring-2 focus:ring-ring">
110
+ <X className="h-3.5 w-3.5" />
111
+ <span className="sr-only">Close</span>
112
+ </SheetClose>
113
+ </div>
113
114
 
114
115
  {loading ? (
115
116
  <div className="flex flex-1 items-center justify-center">
116
117
  <Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
117
118
  </div>
118
119
  ) : (
119
- <div className="flex-1 space-y-5 overflow-auto py-4">
120
+ <div className="flex-1 space-y-5 overflow-y-auto px-4 py-4">
121
+ {/* Account */}
122
+ {session?.user && (
123
+ <section className="space-y-2">
124
+ <h3 className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
125
+ Account
126
+ </h3>
127
+ <div className="flex items-center justify-between rounded-md border border-border bg-muted/40 p-3">
128
+ <div className="text-sm min-w-0">
129
+ <div className="font-medium truncate">{session.user.name}</div>
130
+ <div className="text-xs text-muted-foreground truncate">{session.user.email}</div>
131
+ </div>
132
+ <button
133
+ type="button"
134
+ onClick={async () => {
135
+ await authClient.signOut();
136
+ window.location.href = "/signin";
137
+ }}
138
+ className="ml-2 shrink-0 text-xs px-2 py-1 rounded border border-border hover:bg-accent"
139
+ >
140
+ Sign out
141
+ </button>
142
+ </div>
143
+ </section>
144
+ )}
145
+
146
+ {/* Signup allowlist */}
147
+ <section className="space-y-2">
148
+ <h3 className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
149
+ Signup allowlist
150
+ </h3>
151
+ <p className="text-xs leading-relaxed text-muted-foreground">
152
+ Control who can create an account. Leave both lists empty to
153
+ allow any email. Saving here overrides the{" "}
154
+ <code className="bg-muted px-0.5 rounded">AUTH_ALLOWED_EMAILS</code>{" "}
155
+ and{" "}
156
+ <code className="bg-muted px-0.5 rounded">AUTH_ALLOWED_DOMAIN</code>{" "}
157
+ environment variables.
158
+ </p>
120
159
  {source === "env" && (
121
160
  <div className="flex items-start gap-2 rounded-md bg-muted px-3 py-2 text-xs text-muted-foreground">
122
161
  <AlertCircle className="h-3.5 w-3.5 shrink-0 mt-0.5" />
@@ -187,22 +226,42 @@ export function AuthSettingsSheet({
187
226
  {error}
188
227
  </div>
189
228
  )}
229
+ </section>
230
+
231
+ {/* Agent rate limit */}
232
+ <section className="space-y-2">
233
+ <h3 className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
234
+ Agent rate limit
235
+ </h3>
236
+ <div className="rounded-md border border-border bg-muted/40 p-3 space-y-1.5">
237
+ <div className="flex items-center justify-between text-xs">
238
+ <span className="text-muted-foreground">Per agent</span>
239
+ <span className="font-mono">
240
+ {rateLimit !== null ? `${rateLimit} ops/min` : "--"}
241
+ </span>
242
+ </div>
243
+ <p className="text-[10px] text-muted-foreground/60">
244
+ Override with <code className="bg-muted px-0.5 rounded">AGENT_RATE_LIMIT</code> env var.
245
+ </p>
246
+ </div>
247
+ </section>
190
248
  </div>
191
249
  )}
192
250
 
193
- <SheetFooter>
251
+ <div className="flex items-center justify-end gap-2 border-t border-border px-4 py-3 shrink-0">
194
252
  <Button
253
+ size="sm"
195
254
  variant="ghost"
196
255
  onClick={() => onOpenChange(false)}
197
256
  disabled={saving}
198
257
  >
199
258
  Cancel
200
259
  </Button>
201
- <Button onClick={handleSave} disabled={saving || loading} className="gap-1.5">
260
+ <Button size="sm" onClick={handleSave} disabled={saving || loading} className="gap-1.5">
202
261
  {saving && <Loader2 className="h-3.5 w-3.5 animate-spin" />}
203
262
  Save
204
263
  </Button>
205
- </SheetFooter>
264
+ </div>
206
265
  </SheetContent>
207
266
  </Sheet>
208
267
  );
@@ -184,6 +184,44 @@ export function KBEditor() {
184
184
  void useProofStore.getState().loadSidecar(currentPath);
185
185
  }, [currentPath]);
186
186
 
187
+ // Human edit-lease heartbeat: tell the server a human has this markdown doc
188
+ // open so computeCollabState() reports "active" even before the first
189
+ // suggestion/comment. This is what makes raw-fs writes from agents defer to
190
+ // the collaborative (Tier-2) path while a human is editing. Only markdown
191
+ // files participate in the collab-state machine.
192
+ useEffect(() => {
193
+ if (typeof window === "undefined") return;
194
+ if (!currentPath) return;
195
+ if (!/\.(md|markdown)$/i.test(currentPath)) return;
196
+
197
+ const path = currentPath;
198
+ const ping = (action: "open" | "heartbeat" | "close") => {
199
+ // keepalive lets the "close" beacon survive page unload.
200
+ void fetch("/api/wiki/presence", {
201
+ method: "POST",
202
+ headers: { "Content-Type": "application/json" },
203
+ body: JSON.stringify({ path, action }),
204
+ keepalive: action === "close",
205
+ }).catch(() => {
206
+ /* presence is best-effort; ignore failures */
207
+ });
208
+ };
209
+
210
+ ping("open");
211
+ // Refresh well within the 90s server lease TTL.
212
+ const id = setInterval(() => ping("heartbeat"), 30_000);
213
+ const onHidden = () => {
214
+ if (document.visibilityState === "hidden") ping("heartbeat");
215
+ };
216
+ document.addEventListener("visibilitychange", onHidden);
217
+
218
+ return () => {
219
+ clearInterval(id);
220
+ document.removeEventListener("visibilitychange", onHidden);
221
+ ping("close");
222
+ };
223
+ }, [currentPath]);
224
+
187
225
  // Subscribe to chokidar SSE: when current file changes on disk, reload sidecar.
188
226
  useEffect(() => {
189
227
  if (typeof window === "undefined") return;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Minimal durable audit log for raw-fs mutations (Tier 1).
3
+ *
4
+ * Uses the same ~/.wiki-viewer/auth.db already opened by better-auth.
5
+ * Table: agent_fs_audit
6
+ *
7
+ * For .md files the sidecar event covers provenance; this covers ALL types.
8
+ */
9
+ import Database from "better-sqlite3";
10
+ import path from "node:path";
11
+ import os from "node:os";
12
+ import { mkdirSync } from "node:fs";
13
+
14
+ function dataDir(): string {
15
+ return path.join(process.env.HOME ?? os.homedir(), ".wiki-viewer");
16
+ }
17
+
18
+ // Lazily initialised so tests can set HOME before first import.
19
+ let _db: InstanceType<typeof Database> | null = null;
20
+
21
+ function getDb(): InstanceType<typeof Database> {
22
+ if (_db) return _db;
23
+ const dir = dataDir();
24
+ mkdirSync(dir, { recursive: true });
25
+ const dbPath = path.join(dir, "auth.db");
26
+ _db = new Database(dbPath);
27
+ _db.pragma("journal_mode = WAL");
28
+ _db.exec(`
29
+ CREATE TABLE IF NOT EXISTS agent_fs_audit (
30
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
31
+ agent_id TEXT NOT NULL,
32
+ op TEXT NOT NULL,
33
+ path TEXT NOT NULL,
34
+ old_sha TEXT,
35
+ new_sha TEXT,
36
+ forced INTEGER NOT NULL DEFAULT 0,
37
+ at TEXT NOT NULL
38
+ )
39
+ `);
40
+ return _db;
41
+ }
42
+
43
+ export interface AuditRow {
44
+ agentId: string;
45
+ op: string;
46
+ path: string;
47
+ oldSha?: string;
48
+ newSha?: string;
49
+ forced?: boolean;
50
+ }
51
+
52
+ /**
53
+ * Write one audit row synchronously (SQLite sync is fine here — it's a WAL journal append).
54
+ * Fire-and-forget: never throws; logs to stderr on error.
55
+ */
56
+ export function writeAuditRow(row: AuditRow): void {
57
+ try {
58
+ const db = getDb();
59
+ db.prepare(
60
+ `INSERT INTO agent_fs_audit (agent_id, op, path, old_sha, new_sha, forced, at)
61
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
62
+ ).run(
63
+ row.agentId,
64
+ row.op,
65
+ row.path,
66
+ row.oldSha ?? null,
67
+ row.newSha ?? null,
68
+ row.forced ? 1 : 0,
69
+ new Date().toISOString(),
70
+ );
71
+ } catch (e) {
72
+ console.error("[agent-fs audit] write failed:", e);
73
+ }
74
+ }
75
+
76
+ /** Reset cached DB handle (used by tests to pick up new HOME). */
77
+ export function _resetAuditDb(): void {
78
+ _db = null;
79
+ }
@@ -34,7 +34,7 @@ export async function checkAuth(req: Request): Promise<CheckAuthResult> {
34
34
  id: `user:${u.id}`,
35
35
  displayName: u.name,
36
36
  tokenHash: "",
37
- scope: { paths: ["**/*"], ops: ["read", "mutate"] },
37
+ scope: { paths: ["**/*"], ops: ["read", "mutate", "delete"] },
38
38
  createdAt: "",
39
39
  lastSeen: "",
40
40
  },
@@ -71,7 +71,7 @@ export async function checkAuth(req: Request): Promise<CheckAuthResult> {
71
71
 
72
72
  export interface ScopeParams {
73
73
  filePath?: string;
74
- op: "read" | "mutate";
74
+ op: "read" | "mutate" | "delete";
75
75
  }
76
76
 
77
77
  export function enforceScope(