wiki-viewer 1.4.3 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) 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_client-reference-manifest.js +1 -1
  94. package/.next/standalone/.next/server/app-paths-manifest.json +5 -0
  95. package/.next/standalone/.next/server/chunks/0p91_next_dist_esm_build_templates_app-route_0d2jbo5.js +3 -0
  96. package/.next/standalone/.next/server/chunks/0p91_next_dist_esm_build_templates_app-route_0ytral2.js +3 -0
  97. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0269d2e._.js +1 -1
  98. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05f6x0i._.js +3 -0
  99. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06-dtd0._.js +3 -0
  100. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06sek6_._.js +1 -1
  101. package/.next/standalone/.next/server/chunks/[root-of-the-server]__09s9qkd._.js +1 -1
  102. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d75a5q._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d_da6q._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e7si9p._.js +1 -1
  105. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0hhhvm7._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kk26wc._.js +3 -0
  107. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kur4ro._.js +15 -0
  108. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0p12-6~._.js +1 -1
  109. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qc2~6l._.js +15 -0
  110. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0s~i040._.js +3 -0
  111. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0w47wil._.js +1 -1
  112. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xq124z._.js +3 -0
  113. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l..bxz._.js → [root-of-the-server]__0xz-_0v._.js} +2 -2
  114. package/.next/standalone/.next/server/chunks/[root-of-the-server]__10w2q~o._.js +1 -1
  115. package/.next/standalone/.next/server/chunks/_079xuln._.js +3 -0
  116. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_agent_fs_file_[___path]_route_actions_0gb11t~.js +3 -0
  117. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_agent_fs_ls_[[___path]]_route_actions_0wfg4nl.js +3 -0
  118. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_agent_fs_move_route_actions_0z34mqq.js +3 -0
  119. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_agent_fs_search_route_actions_0ut_ug..js +3 -0
  120. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_wiki_presence_route_actions_0x9d7f4.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/12y~_mermaid_dist_chunks_mermaid_core_diagram-OG6HWLK6_mjs_0t5sus6._.js +1 -1
  122. package/.next/standalone/.next/server/chunks/ssr/12y~_mermaid_dist_chunks_mermaid_core_sankeyDiagram-5OEKKPKP_mjs_11ifrnu._.js +1 -1
  123. package/.next/standalone/.next/server/chunks/ssr/_0pqaawz._.js +2 -2
  124. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0y9k6h~._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0znncy9._.js +1 -1
  126. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_12-ni1n._.js +1 -1
  127. package/.next/standalone/.next/server/functions-config-manifest.json +5 -0
  128. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  129. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  130. package/.next/standalone/.next/server/pages/404.html +1 -1
  131. package/.next/standalone/.next/server/pages/500.html +1 -1
  132. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  133. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  134. package/.next/standalone/.next/static/chunks/{11s9zwr7edrs7.js → 14luv~jq7e6-n.js} +12 -12
  135. package/.next/standalone/README.md +79 -16
  136. package/.next/standalone/agents/bootstrap-prompt.md +12 -1
  137. package/.next/standalone/agents/wiki-viewer-skill/SKILL.md +199 -6
  138. package/.next/standalone/docs/file-vs-collab-authority.md +124 -0
  139. package/.next/standalone/package.json +1 -1
  140. package/.next/standalone/packages/wiki-viewer-mcp/README.md +194 -0
  141. package/.next/standalone/packages/wiki-viewer-mcp/dist/http-client.js +220 -0
  142. package/.next/standalone/packages/wiki-viewer-mcp/dist/index.js +472 -0
  143. package/.next/standalone/packages/wiki-viewer-mcp/dist/register.js +94 -0
  144. package/.next/standalone/packages/wiki-viewer-mcp/dist/state-cache.js +26 -0
  145. package/.next/standalone/packages/wiki-viewer-mcp/package-lock.json +1716 -0
  146. package/.next/standalone/packages/wiki-viewer-mcp/package.json +41 -0
  147. package/.next/standalone/packages/wiki-viewer-mcp/src/http-client.ts +344 -0
  148. package/.next/standalone/packages/wiki-viewer-mcp/src/index.ts +575 -0
  149. package/.next/standalone/packages/wiki-viewer-mcp/src/register.ts +144 -0
  150. package/.next/standalone/packages/wiki-viewer-mcp/src/state-cache.ts +45 -0
  151. package/.next/standalone/packages/wiki-viewer-mcp/tsconfig.json +19 -0
  152. package/.next/standalone/pnpm-lock.yaml +9 -9
  153. package/.next/standalone/src/app/api/agent/files/[...path]/route.ts +11 -1
  154. package/.next/standalone/src/app/api/agent/fs/file/[...path]/route.ts +344 -0
  155. package/.next/standalone/src/app/api/agent/fs/ls/[[...path]]/route.ts +147 -0
  156. package/.next/standalone/src/app/api/agent/fs/move/route.ts +125 -0
  157. package/.next/standalone/src/app/api/agent/fs/search/route.ts +236 -0
  158. package/.next/standalone/src/app/api/agent/register/route.ts +3 -3
  159. package/.next/standalone/src/app/api/agents/install/route.ts +44 -2
  160. package/.next/standalone/src/app/api/wiki/move/route.ts +10 -1
  161. package/.next/standalone/src/app/api/wiki/presence/route.ts +55 -0
  162. package/.next/standalone/src/components/ai-panel/ai-panel.tsx +49 -0
  163. package/.next/standalone/src/components/editor/editor.tsx +38 -0
  164. package/.next/standalone/src/lib/proof/audit.ts +79 -0
  165. package/.next/standalone/src/lib/proof/auth.ts +2 -2
  166. package/.next/standalone/src/lib/proof/collab-state.ts +79 -0
  167. package/.next/standalone/src/lib/proof/lease.ts +104 -0
  168. package/.next/standalone/src/lib/proof/ops-applier.ts +74 -25
  169. package/.next/standalone/src/lib/proof/raw-fs.ts +198 -0
  170. package/.next/standalone/src/lib/proof/registry.ts +1 -1
  171. package/.next/standalone/src/lib/proof/sidecar.ts +38 -1
  172. package/.next/standalone/src/lib/proof/types.ts +13 -1
  173. package/.next/standalone/src/tests/proof/agent-fs.test.ts +824 -0
  174. package/.next/standalone/src/tests/proof/agents-install.test.ts +53 -0
  175. package/.next/standalone/src/tests/proof/collab-state.test.ts +499 -0
  176. package/.next/standalone/src/tests/proof/reconcile-sidecar.test.ts +195 -0
  177. package/.next/standalone/src/tests/proof/sidecar-lifecycle.test.ts +177 -0
  178. package/.next/standalone/tsconfig.tsbuildinfo +1 -1
  179. package/README.md +79 -16
  180. package/package.json +1 -1
  181. package/.next/standalone/.next/server/chunks/[root-of-the-server]__09~~-nd._.js +0 -3
  182. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0w87kiz._.js +0 -3
  183. package/.next/standalone/.next/server/chunks/[root-of-the-server]__12ssbhv._.js +0 -3
  184. package/.next/standalone/.next/server/chunks/node_modules__pnpm_0g0fhfk._.js +0 -3
  185. /package/.next/standalone/.next/static/{kzOuSAQArtvD7uiAJ6ZLh → 19j4-_brJLoulXAMekpM2}/_buildManifest.js +0 -0
  186. /package/.next/standalone/.next/static/{kzOuSAQArtvD7uiAJ6ZLh → 19j4-_brJLoulXAMekpM2}/_clientMiddlewareManifest.js +0 -0
  187. /package/.next/standalone/.next/static/{kzOuSAQArtvD7uiAJ6ZLh → 19j4-_brJLoulXAMekpM2}/_ssgManifest.js +0 -0
@@ -0,0 +1,194 @@
1
+ # wiki-viewer-mcp
2
+
3
+ MCP filesystem adapter for [wiki-viewer](https://github.com/anh-chu/wiki-viewer).
4
+
5
+ Maps standard MCP filesystem tools onto the wiki-viewer agent HTTP API so Claude Code, Cursor, and Codex get native-feeling file tools against a **remote** wiki-viewer instance.
6
+
7
+ ## First-time setup (register)
8
+
9
+ Before connecting an agent, register it with your wiki-viewer instance.
10
+ The human operator approves the request in the wiki-viewer AI Panel.
11
+
12
+ **Using the built-in CLI (recommended):**
13
+
14
+ ```bash
15
+ # Once published to npm:
16
+ npx wiki-viewer-mcp register \
17
+ --url https://notes.example.com \
18
+ --id ai:myagent \
19
+ --name "My Agent" \
20
+ --scope-paths "**/*" \
21
+ --ops read,mutate,delete \
22
+ --timeout 300
23
+
24
+ # Or from a local build (after cd packages/wiki-viewer-mcp && npm run build):
25
+ node ./dist/index.js register \
26
+ --url http://localhost:3000 \
27
+ --id ai:myagent \
28
+ --name "My Agent"
29
+ ```
30
+
31
+ The CLI will:
32
+
33
+ 1. POST the registration to wiki-viewer and print the registration ID.
34
+ 2. Print `⏳ Waiting for approval. Open the wiki-viewer AI Panel and approve agent "ai:myagent".`
35
+ 3. Poll every 3 s until the human approves (or timeout).
36
+ 4. On approval, print your `WIKI_VIEWER_TOKEN` and `WIKI_VIEWER_AGENT_ID`, then print a ready-to-paste `mcp.json` snippet.
37
+
38
+ ```
39
+ ✅ Approved!
40
+
41
+ Agent ID : ai:myagent
42
+ Token : wv_tok_…
43
+
44
+ Paste this into your mcp.json:
45
+
46
+ {
47
+ "servers": {
48
+ "wiki-viewer": {
49
+ "command": "npx",
50
+ "args": ["wiki-viewer-mcp"],
51
+ "env": {
52
+ "WIKI_VIEWER_URL": "https://notes.example.com",
53
+ "WIKI_VIEWER_TOKEN": "wv_tok_…",
54
+ "WIKI_VIEWER_AGENT_ID": "ai:myagent"
55
+ }
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ **CLI options:**
62
+
63
+ | Flag | Required | Default | Description |
64
+ | --------------- | -------- | ------------- | --------------------------------------------------------- |
65
+ | `--url` | yes | — | Base URL of wiki-viewer, e.g. `https://notes.example.com` |
66
+ | `--id` | yes | — | Agent ID, must match `ai:[a-z][a-z0-9-]{0,30}` |
67
+ | `--name` | yes | — | Human-readable display name |
68
+ | `--scope-paths` | no | `**/*` | Comma-separated path globs the agent can access |
69
+ | `--ops` | no | `read,mutate` | Comma-separated: `read`, `mutate`, `delete` |
70
+ | `--timeout` | no | `300` | Seconds to wait for approval before giving up |
71
+
72
+ ---
73
+
74
+ ## Installation
75
+
76
+ ```bash
77
+ # Once published to npm:
78
+ npx wiki-viewer-mcp
79
+ # or install globally
80
+ npm i -g wiki-viewer-mcp
81
+
82
+ # From a local build:
83
+ cd packages/wiki-viewer-mcp
84
+ npm run build
85
+ node dist/index.js # start MCP server
86
+ ```
87
+
88
+ ## Configuration
89
+
90
+ Set three environment variables before starting the MCP server
91
+ (the `register` command prints them for you after approval):
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 |
98
+
99
+ ## Usage in Claude Code / Cursor / Codex
100
+
101
+ Add to your `mcp.json` (or equivalent):
102
+
103
+ ```json
104
+ {
105
+ "servers": {
106
+ "wiki-viewer": {
107
+ "command": "npx",
108
+ "args": ["wiki-viewer-mcp"],
109
+ "env": {
110
+ "WIKI_VIEWER_URL": "https://notes.example.com",
111
+ "WIKI_VIEWER_TOKEN": "<your-token>",
112
+ "WIKI_VIEWER_AGENT_ID": "<your-agent-id>"
113
+ }
114
+ }
115
+ }
116
+ }
117
+ ```
118
+
119
+ ## Available tools
120
+
121
+ | Tool | Maps to | Description |
122
+ | ---------------- | ---------------------------------- | ----------------------------------------------- |
123
+ | `read_file` | `GET /api/agent/fs/file/<path>` | Read file bytes; captures sha256 + collab state |
124
+ | `write_file` | `PUT /api/agent/fs/file/<path>` | Atomic whole-file write with If-Match |
125
+ | `edit_file` | read → str-replace → PUT | Client-side exact-string replacement |
126
+ | `list_directory` | `GET /api/agent/fs/ls/<path>` | Scope-filtered directory listing |
127
+ | `search` | `POST /api/agent/fs/search` | Server-side grep or glob |
128
+ | `move_file` | `POST /api/agent/fs/move` | Rename/move; sidecar handled by server |
129
+ | `delete_file` | `DELETE /api/agent/fs/file/<path>` | Delete; requires `delete` scope |
130
+
131
+ ## Working mode vs Collaborating mode (important)
132
+
133
+ wiki-viewer has two tiers for `.md` files:
134
+
135
+ - **Tier 1 (raw fs)** — fast, all file types, light audit. Use for code, config, non-prose, or whole-file rewrites.
136
+ - **Tier 2 (collab)** — block-ops + proof-spans, review/accept/revert by humans. Use for prose that a human is co-editing.
137
+
138
+ The shim enforces this automatically in three layers:
139
+
140
+ ### Layer 1 — discoverable (headers)
141
+
142
+ Every `read_file` response includes:
143
+
144
+ ```
145
+ X-Collab-State: active | tracked | untracked | not-markdown
146
+ X-Collab-Revision: <n>
147
+ X-Collab-Snapshot: /api/agent/files/<path>.md
148
+ ```
149
+
150
+ | State | Meaning | You should |
151
+ | -------------- | ------------------------------------------------------------ | -------------------------------------- |
152
+ | `active` | Human has the doc open OR there are pending review artifacts | **Use Tier-2 block-ops** |
153
+ | `tracked` | Sidecar exists, no active review | Prefer Tier-2 for prose/semantic edits |
154
+ | `untracked` | Plain `.md`, no collaboration history | Raw ok |
155
+ | `not-markdown` | Not a `.md` file | Raw only |
156
+
157
+ ### Layer 2 — client-side guard
158
+
159
+ If the shim's last-known `X-Collab-State` for a `.md` is `active`, `write_file` and `edit_file` are **blocked** before any HTTP request is made. The tool returns a clear error with the Tier-2 snapshot URL.
160
+
161
+ ### Layer 3 — server-side enforcement (409)
162
+
163
+ Even if the cache is stale, the server re-checks collab state **atomically inside the write mutex**. A raw `PUT` to an `active` `.md` is rejected with `409 COLLAB_ACTIVE` and the Tier-2 snapshot URL. The shim surfaces this as a clear error message.
164
+
165
+ ### Rule
166
+
167
+ > Before editing a `.md`, run `read_file` and check `X-Collab-State`.
168
+ > If `active`, use wiki-viewer block-ops (Tier 2).
169
+ > For everything else, use raw fs tools.
170
+
171
+ ## If-Match concurrency
172
+
173
+ The shim caches the sha256 from every `read_file` response (ETag header). `write_file`, `edit_file`, and `delete_file` automatically send this as `If-Match`. If the file changed since your read, you get a `412` error with instructions to re-read.
174
+
175
+ `edit_file` always reads fresh before writing, so it's naturally atomic per-op.
176
+
177
+ Use `force: true` to bypass `If-Match` (audited by the server).
178
+
179
+ ## Scope
180
+
181
+ Your token's `scope.paths` (set during registration) governs what paths you can touch. Paths outside scope, and internal paths (`.proof/`, `.locks/`, the app db), are rejected by the server. Scope supports `**`, `*`, `?` glob patterns.
182
+
183
+ The `delete` op is a separate scope permission from `mutate` — an agent can be granted "edit but never delete."
184
+
185
+ ## Development
186
+
187
+ ```bash
188
+ cd packages/wiki-viewer-mcp
189
+ pnpm install
190
+ pnpm test # runs unit tests with tsx --test
191
+ pnpm build # compiles to dist/
192
+ ```
193
+
194
+ Tests use a mock fetch — no real wiki-viewer instance needed.
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Typed HTTP client over the wiki-viewer agent filesystem API.
3
+ * Contract: docs/agent-fs-plan.md §2.
4
+ *
5
+ * All methods throw WikiViewerError on non-2xx unless noted.
6
+ * The caller (index.ts) catches 409/412 and surfaces them cleanly to the agent.
7
+ */
8
+ import * as cache from "./state-cache.js";
9
+ // ─── Errors ──────────────────────────────────────────────────────────────────
10
+ export class WikiViewerError extends Error {
11
+ status;
12
+ code;
13
+ body;
14
+ constructor(status, code, message, body) {
15
+ super(message);
16
+ this.status = status;
17
+ this.code = code;
18
+ this.body = body;
19
+ this.name = "WikiViewerError";
20
+ }
21
+ }
22
+ export class IfMatchError extends WikiViewerError {
23
+ constructor(path, body) {
24
+ super(412, "IF_MATCH_MISMATCH", `If-Match mismatch for ${path} — re-read before writing`, body);
25
+ this.name = "IfMatchError";
26
+ }
27
+ }
28
+ export class CollabActiveError extends WikiViewerError {
29
+ snapshotUrl;
30
+ constructor(path, snapshotUrl, body) {
31
+ super(409, "COLLAB_ACTIVE", `${path} is being actively collaborated on — use Tier-2 block-ops`, body);
32
+ this.snapshotUrl = snapshotUrl;
33
+ this.name = "CollabActiveError";
34
+ }
35
+ }
36
+ // ─── Client ──────────────────────────────────────────────────────────────────
37
+ export class WikiViewerClient {
38
+ baseUrl;
39
+ token;
40
+ agentId;
41
+ _fetch;
42
+ constructor(config) {
43
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
44
+ this.token = config.token;
45
+ this.agentId = config.agentId;
46
+ this._fetch = config.fetch ?? globalThis.fetch;
47
+ }
48
+ // ── Helpers ────────────────────────────────────────────────────────────────
49
+ headers(extra = {}) {
50
+ return {
51
+ Authorization: `Bearer ${this.token}`,
52
+ "X-Agent-Id": this.agentId,
53
+ ...extra,
54
+ };
55
+ }
56
+ url(path, params) {
57
+ const u = new URL(`${this.baseUrl}${path}`);
58
+ if (params) {
59
+ for (const [k, v] of Object.entries(params)) {
60
+ u.searchParams.set(k, v);
61
+ }
62
+ }
63
+ return u.toString();
64
+ }
65
+ async assertOk(res, path) {
66
+ if (res.ok)
67
+ return;
68
+ let body;
69
+ try {
70
+ body = await res.json();
71
+ }
72
+ catch {
73
+ body = await res.text().catch(() => undefined);
74
+ }
75
+ if (res.status === 412)
76
+ throw new IfMatchError(path, body);
77
+ if (res.status === 409) {
78
+ const snapshotUrl = body?.snapshotUrl ?? null;
79
+ throw new CollabActiveError(path, snapshotUrl, body);
80
+ }
81
+ throw new WikiViewerError(res.status, `HTTP_${res.status}`, `wiki-viewer ${res.status} for ${path}: ${res.statusText}`, body);
82
+ }
83
+ parseCollabHeaders(res) {
84
+ const raw = (res.headers.get("X-Collab-State") ?? "not-markdown");
85
+ const validStates = ["active", "tracked", "untracked", "not-markdown"];
86
+ const collabState = validStates.includes(raw) ? raw : "not-markdown";
87
+ const revRaw = res.headers.get("X-Collab-Revision");
88
+ const collabRevision = revRaw !== null ? parseInt(revRaw, 10) : null;
89
+ const collabSnapshot = res.headers.get("X-Collab-Snapshot") ?? null;
90
+ return { collabState, collabRevision, collabSnapshot };
91
+ }
92
+ // ── read_file ──────────────────────────────────────────────────────────────
93
+ async readFile(path, range) {
94
+ const extraHeaders = {};
95
+ if (range)
96
+ extraHeaders["Range"] = range;
97
+ const res = await this._fetch(this.url(`/api/agent/fs/file/${encodeFilePath(path)}`), { headers: this.headers(extraHeaders) });
98
+ await this.assertOk(res, path);
99
+ const sha256 = stripQuotes(res.headers.get("ETag") ?? "");
100
+ const size = parseInt(res.headers.get("X-File-Size") ?? "0", 10);
101
+ const mtime = res.headers.get("X-File-Mtime") ?? "";
102
+ const contentType = res.headers.get("Content-Type") ?? "application/octet-stream";
103
+ const collab = this.parseCollabHeaders(res);
104
+ const bodyBuf = new Uint8Array(await res.arrayBuffer());
105
+ const isText = contentType.startsWith("text/") ||
106
+ contentType.includes("json") ||
107
+ contentType.includes("xml") ||
108
+ contentType.includes("javascript") ||
109
+ contentType.includes("typescript");
110
+ const text = isText ? new TextDecoder().decode(bodyBuf) : null;
111
+ const state = {
112
+ sha256,
113
+ collabState: collab.collabState,
114
+ collabRevision: collab.collabRevision,
115
+ collabSnapshot: collab.collabSnapshot,
116
+ fetchedAt: Date.now(),
117
+ };
118
+ cache.set(path, state);
119
+ return { body: bodyBuf, text, sha256, size, mtime, contentType, ...collab };
120
+ }
121
+ // ── write_file ─────────────────────────────────────────────────────────────
122
+ async writeFile(path, content, opts = {}) {
123
+ const extraHeaders = {};
124
+ // Auto-fill If-Match from the last-read sha (cache) on overwrite, unless the
125
+ // caller explicitly supplied one or is forcing. A create (no cached sha) omits it.
126
+ const ifMatch = opts.ifMatch ?? (opts.force ? undefined : cache.get(path)?.sha256);
127
+ if (ifMatch)
128
+ extraHeaders["If-Match"] = ifMatch;
129
+ if (opts.ifCollabMatch !== undefined) {
130
+ extraHeaders["If-Collab-Match"] = String(opts.ifCollabMatch);
131
+ }
132
+ const params = {};
133
+ if (opts.mkdirs)
134
+ params["mkdirs"] = "true";
135
+ if (opts.force)
136
+ params["force"] = "true";
137
+ const body = typeof content === "string"
138
+ ? new TextEncoder().encode(content)
139
+ : content;
140
+ const res = await this._fetch(this.url(`/api/agent/fs/file/${encodeFilePath(path)}`, params), {
141
+ method: "PUT",
142
+ headers: this.headers({ "Content-Type": "application/octet-stream", ...extraHeaders }),
143
+ body,
144
+ });
145
+ await this.assertOk(res, path);
146
+ const result = await res.json();
147
+ // Update cache after successful write
148
+ cache.set(path, {
149
+ sha256: result.sha256,
150
+ collabState: "not-markdown",
151
+ collabRevision: null,
152
+ collabSnapshot: null,
153
+ fetchedAt: Date.now(),
154
+ });
155
+ return result;
156
+ }
157
+ // ── delete_file ────────────────────────────────────────────────────────────
158
+ async deleteFile(path, ifMatch, opts = {}) {
159
+ const params = {};
160
+ if (opts.recursive)
161
+ params["recursive"] = "true";
162
+ if (opts.force)
163
+ params["force"] = "true";
164
+ const res = await this._fetch(this.url(`/api/agent/fs/file/${encodeFilePath(path)}`, params), {
165
+ method: "DELETE",
166
+ headers: this.headers({ "If-Match": ifMatch }),
167
+ });
168
+ await this.assertOk(res, path);
169
+ cache.del(path);
170
+ }
171
+ // ── list_directory ─────────────────────────────────────────────────────────
172
+ async listDirectory(path, opts = {}) {
173
+ const params = {};
174
+ if (opts.recursive)
175
+ params["recursive"] = "true";
176
+ if (opts.depth !== undefined)
177
+ params["depth"] = String(opts.depth);
178
+ if (opts.limit !== undefined)
179
+ params["limit"] = String(opts.limit);
180
+ const res = await this._fetch(this.url(`/api/agent/fs/ls/${encodeFilePath(path)}`, params), { headers: this.headers() });
181
+ await this.assertOk(res, path);
182
+ return res.json();
183
+ }
184
+ // ── search ────────────────────────────────────────────────────────────────
185
+ async search(body) {
186
+ const res = await this._fetch(this.url("/api/agent/fs/search"), {
187
+ method: "POST",
188
+ headers: this.headers({ "Content-Type": "application/json" }),
189
+ body: JSON.stringify(body),
190
+ });
191
+ await this.assertOk(res, "search");
192
+ return res.json();
193
+ }
194
+ // ── move_file ─────────────────────────────────────────────────────────────
195
+ async moveFile(from, to, ifMatch) {
196
+ const body = { from, to };
197
+ if (ifMatch)
198
+ body["ifMatch"] = ifMatch;
199
+ const res = await this._fetch(this.url("/api/agent/fs/move"), {
200
+ method: "POST",
201
+ headers: this.headers({ "Content-Type": "application/json" }),
202
+ body: JSON.stringify(body),
203
+ });
204
+ await this.assertOk(res, from);
205
+ cache.rename(from, to);
206
+ }
207
+ }
208
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
209
+ /** Encode path segments but preserve slashes */
210
+ function encodeFilePath(path) {
211
+ return path
212
+ .replace(/^\/+/, "")
213
+ .split("/")
214
+ .map((seg) => encodeURIComponent(seg))
215
+ .join("/");
216
+ }
217
+ function stripQuotes(etag) {
218
+ return etag.replace(/^"/, "").replace(/"$/, "");
219
+ }
220
+ //# sourceMappingURL=http-client.js.map