wiki-viewer 1.0.0 → 1.0.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 (128) hide show
  1. package/.next/standalone/README.md +328 -99
  2. package/.next/standalone/agents/bootstrap-prompt.md +1 -0
  3. package/.next/standalone/agents/wiki-viewer-skill/SKILL.md +236 -0
  4. package/.next/standalone/bin/wiki-viewer.js +57 -4
  5. package/.next/standalone/docs/agent-collab-plan.md +1615 -0
  6. package/.next/standalone/docs/agent-collab-v2-plan.md +771 -0
  7. package/.next/standalone/package.json +19 -2
  8. package/.next/standalone/pnpm-lock.yaml +1368 -325
  9. package/.next/standalone/pnpm-workspace.yaml +7 -1
  10. package/.next/standalone/src/app/api/agent/activity/route.ts +58 -0
  11. package/.next/standalone/src/app/api/agent/admin/agents/[agentId]/revoke/route.ts +40 -0
  12. package/.next/standalone/src/app/api/agent/admin/agents/route.ts +33 -0
  13. package/.next/standalone/src/app/api/agent/admin/registrations/[regId]/approve/route.ts +83 -0
  14. package/.next/standalone/src/app/api/agent/admin/registrations/[regId]/deny/route.ts +36 -0
  15. package/.next/standalone/src/app/api/agent/admin/registrations/route.ts +27 -0
  16. package/.next/standalone/src/app/api/agent/events/[...path]/route.ts +136 -0
  17. package/.next/standalone/src/app/api/agent/files/[...path]/route.ts +202 -0
  18. package/.next/standalone/src/app/api/agent/internal/span/route.ts +117 -0
  19. package/.next/standalone/src/app/api/agent/register/[regId]/route.ts +50 -0
  20. package/.next/standalone/src/app/api/agent/register/route.ts +110 -0
  21. package/.next/standalone/src/app/api/agent/settings/route.ts +30 -0
  22. package/.next/standalone/src/app/api/agent/settings/token/regenerate/route.ts +20 -0
  23. package/.next/standalone/src/app/api/agent/sidecar/[...path]/route.ts +49 -0
  24. package/.next/standalone/src/app/api/agents/install/route.ts +83 -0
  25. package/.next/standalone/src/app/api/agents/skill/route.ts +20 -0
  26. package/.next/standalone/src/app/api/agents/skill.tar.gz/route.ts +87 -0
  27. package/.next/standalone/src/app/api/auth/[...all]/route.ts +7 -0
  28. package/.next/standalone/src/app/api/owner/init/route.ts +14 -0
  29. package/.next/standalone/src/app/api/system/auth-settings/route.ts +85 -0
  30. package/.next/standalone/src/app/api/system/browse/route.ts +4 -0
  31. package/.next/standalone/src/app/api/system/clear-root/route.ts +8 -1
  32. package/.next/standalone/src/app/api/system/config/route.ts +5 -1
  33. package/.next/standalone/src/app/api/system/pins/route.ts +7 -0
  34. package/.next/standalone/src/app/api/system/reveal/route.ts +7 -0
  35. package/.next/standalone/src/app/api/system/root-status/route.ts +5 -1
  36. package/.next/standalone/src/app/api/system/set-root/route.ts +7 -0
  37. package/.next/standalone/src/app/api/wiki/app/route.ts +15 -0
  38. package/.next/standalone/src/app/api/wiki/content/route.ts +110 -11
  39. package/.next/standalone/src/app/api/wiki/folder/route.ts +7 -0
  40. package/.next/standalone/src/app/api/wiki/move/route.ts +7 -0
  41. package/.next/standalone/src/app/api/wiki/new-file/route.ts +55 -0
  42. package/.next/standalone/src/app/api/wiki/page/route.ts +7 -0
  43. package/.next/standalone/src/app/api/wiki/route.ts +10 -0
  44. package/.next/standalone/src/app/api/wiki/slugs/route.ts +5 -1
  45. package/.next/standalone/src/app/api/wiki/upload/route.ts +7 -0
  46. package/.next/standalone/src/app/api/wiki/watch/route.ts +6 -1
  47. package/.next/standalone/src/app/globals.css +70 -0
  48. package/.next/standalone/src/app/page.tsx +461 -217
  49. package/.next/standalone/src/app/signin/page.tsx +217 -0
  50. package/.next/standalone/src/components/ai-panel/activity-row.tsx +64 -0
  51. package/.next/standalone/src/components/ai-panel/ai-panel.tsx +322 -0
  52. package/.next/standalone/src/components/ai-panel/token-section.tsx +312 -0
  53. package/.next/standalone/src/components/auth-settings-sheet.tsx +209 -0
  54. package/.next/standalone/src/components/editor/bubble-menu.tsx +41 -2
  55. package/.next/standalone/src/components/editor/comment-pip.tsx +56 -0
  56. package/.next/standalone/src/components/editor/comment-thread.tsx +252 -0
  57. package/.next/standalone/src/components/editor/editor.tsx +513 -10
  58. package/.next/standalone/src/components/editor/extensions/proof-span.ts +60 -0
  59. package/.next/standalone/src/components/editor/extensions.ts +2 -0
  60. package/.next/standalone/src/components/editor/proof-span-popover.tsx +161 -0
  61. package/.next/standalone/src/components/editor/suggest-edit-popover.tsx +228 -0
  62. package/.next/standalone/src/components/editor/suggestion-card.tsx +201 -0
  63. package/.next/standalone/src/components/view-width-toggle.tsx +47 -0
  64. package/.next/standalone/src/components/wiki/markdown-preview.tsx +120 -0
  65. package/.next/standalone/src/lib/auth/allowlist.ts +50 -0
  66. package/.next/standalone/src/lib/auth/client.ts +4 -0
  67. package/.next/standalone/src/lib/auth/csrf.ts +68 -0
  68. package/.next/standalone/src/lib/auth/server.ts +164 -0
  69. package/.next/standalone/src/lib/config.ts +4 -0
  70. package/.next/standalone/src/lib/markdown/parse-frontmatter.ts +20 -0
  71. package/.next/standalone/src/lib/markdown/sanitize-schema.ts +106 -0
  72. package/.next/standalone/src/lib/markdown/to-html.ts +46 -8
  73. package/.next/standalone/src/lib/markdown/to-markdown.ts +14 -0
  74. package/.next/standalone/src/lib/proof/activity-shared.ts +31 -0
  75. package/.next/standalone/src/lib/proof/activity.ts +74 -0
  76. package/.next/standalone/src/lib/proof/auth.ts +132 -0
  77. package/.next/standalone/src/lib/proof/block-refs.ts +133 -0
  78. package/.next/standalone/src/lib/proof/blocks.ts +73 -0
  79. package/.next/standalone/src/lib/proof/client-auth.ts +23 -0
  80. package/.next/standalone/src/lib/proof/event-bus.ts +47 -0
  81. package/.next/standalone/src/lib/proof/file-lock.ts +38 -0
  82. package/.next/standalone/src/lib/proof/glob.ts +51 -0
  83. package/.next/standalone/src/lib/proof/idempotency.ts +32 -0
  84. package/.next/standalone/src/lib/proof/mutex.ts +32 -0
  85. package/.next/standalone/src/lib/proof/ops-applier.ts +678 -0
  86. package/.next/standalone/src/lib/proof/owner-auth.ts +2 -0
  87. package/.next/standalone/src/lib/proof/pending.ts +97 -0
  88. package/.next/standalone/src/lib/proof/proof-span.ts +141 -0
  89. package/.next/standalone/src/lib/proof/rate-limit.ts +53 -0
  90. package/.next/standalone/src/lib/proof/register-rate-limit.ts +53 -0
  91. package/.next/standalone/src/lib/proof/registry.ts +190 -0
  92. package/.next/standalone/src/lib/proof/sidecar.ts +66 -0
  93. package/.next/standalone/src/lib/proof/suggest-capture.ts +69 -0
  94. package/.next/standalone/src/lib/proof/types.ts +170 -0
  95. package/.next/standalone/src/lib/proof-config.ts +14 -0
  96. package/.next/standalone/src/middleware.ts +38 -0
  97. package/.next/standalone/src/stores/ai-panel-store.ts +58 -3
  98. package/.next/standalone/src/stores/editor-store.ts +115 -9
  99. package/.next/standalone/src/stores/proof-store.ts +123 -0
  100. package/.next/standalone/src/stores/view-width-store.ts +62 -0
  101. package/.next/standalone/src/tests/proof/activity-aggregator.test.ts +146 -0
  102. package/.next/standalone/src/tests/proof/agent-ownership.test.ts +140 -0
  103. package/.next/standalone/src/tests/proof/agents-install.test.ts +57 -0
  104. package/.next/standalone/src/tests/proof/better-auth.test.ts +68 -0
  105. package/.next/standalone/src/tests/proof/block-refs.test.ts +108 -0
  106. package/.next/standalone/src/tests/proof/blocks.test.ts +92 -0
  107. package/.next/standalone/src/tests/proof/comments-ops.test.ts +177 -0
  108. package/.next/standalone/src/tests/proof/editor-roundtrip.test.ts +85 -0
  109. package/.next/standalone/src/tests/proof/external-edit.test.ts +58 -0
  110. package/.next/standalone/src/tests/proof/file-lock.test.ts +80 -0
  111. package/.next/standalone/src/tests/proof/glob.test.ts +82 -0
  112. package/.next/standalone/src/tests/proof/helpers/auth-session.ts +27 -0
  113. package/.next/standalone/src/tests/proof/markdown-to-html.test.ts +46 -0
  114. package/.next/standalone/src/tests/proof/ops-applier.test.ts +368 -0
  115. package/.next/standalone/src/tests/proof/owner-init.test.ts +2 -0
  116. package/.next/standalone/src/tests/proof/parse-frontmatter.test.ts +60 -0
  117. package/.next/standalone/src/tests/proof/proof-span.test.ts +98 -0
  118. package/.next/standalone/src/tests/proof/rate-limit.test.ts +54 -0
  119. package/.next/standalone/src/tests/proof/registration-flow.test.ts +330 -0
  120. package/.next/standalone/src/tests/proof/registry.test.ts +169 -0
  121. package/.next/standalone/src/tests/proof/routes.test.ts +516 -0
  122. package/.next/standalone/src/tests/proof/sidecar-trim.test.ts +58 -0
  123. package/.next/standalone/src/tests/proof/suggestion-ops.test.ts +280 -0
  124. package/.next/standalone/src/tests/proof/wiki-content-put.test.ts +140 -0
  125. package/.next/standalone/src/tests/proof/wiki-routes-auth.test.ts +208 -0
  126. package/README.md +328 -99
  127. package/bin/wiki-viewer.js +57 -4
  128. package/package.json +19 -2
@@ -1,15 +1,13 @@
1
1
  <div align="center">
2
2
  <img src="public/logo.svg" width="80" height="80" alt="wiki-viewer logo" />
3
3
  <h1>wiki-viewer</h1>
4
- <p><strong>Browse, read, and edit your local files from a clean web UI.</strong></p>
4
+ <p><strong>Browse, read, and edit your local files from a clean web UI. Now with multi-user auth and an HTTP API for AI agents.</strong></p>
5
5
  <p>
6
6
  Markdown · PDF · Office docs · Notebooks · Images · Code · and more
7
7
  </p>
8
8
 
9
9
  <p>
10
- <!-- npm badge placeholder — will activate once package is published -->
11
- <!-- <a href="https://www.npmjs.com/package/wiki-viewer"><img src="https://img.shields.io/npm/v/wiki-viewer" alt="npm version" /></a> -->
12
- <img src="https://img.shields.io/badge/npm-not%20yet%20published-lightgrey" alt="npm not yet published" />
10
+ <a href="https://www.npmjs.com/package/wiki-viewer"><img src="https://img.shields.io/npm/v/wiki-viewer" alt="npm version" /></a>
13
11
  <img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen" alt="Node.js ≥18" />
14
12
  <img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license" />
15
13
  </p>
@@ -19,9 +17,15 @@
19
17
 
20
18
  ## What is it?
21
19
 
22
- **wiki-viewer** is a zero-config local file browser you run from your terminal. It starts a small web server and lets you navigate, read, and edit any directory on your machine — right from the browser.
20
+ **wiki-viewer** is a local-or-remote file browser and editor you run from your terminal. It starts a small web server and lets you navigate, read, and edit any directory on your machine.
23
21
 
24
- No cloud. No accounts. No syncing. Your files stay on your machine.
22
+ Originally a zero-config single-user tool, it now supports:
23
+
24
+ - Multi-user sign-in (Google OAuth or email + password) for teams of 3 to 10 sharing a VPS.
25
+ - An HTTP collaboration API that lets AI agents read and edit Markdown files alongside you, with provenance marks, comments, suggestions, and revision-checked mutations.
26
+ - Per-agent registration and scoped tokens. No shared bearer secret.
27
+
28
+ Single-user, no-auth mode still works. Auth turns on automatically once anyone signs up.
25
29
 
26
30
  ---
27
31
 
@@ -34,14 +38,14 @@ No cloud. No accounts. No syncing. Your files stay on your machine.
34
38
  | **File ops** | Upload files, create folders, delete, drag-to-move |
35
39
  | **Wiki links** | `[[page-name]]` links between Markdown files |
36
40
  | **Dark mode** | System-aware, with manual toggle |
37
- | **HTTPS** | Required for remote access self-signed cert (OpenSSL) or trusted cert (mkcert); runs as HTTPS proxy in front of the internal HTTP server |
41
+ | **Auth** | Google OAuth and email + password via [Better Auth](https://better-auth.com). Email allowlist. SQLite-backed sessions. |
42
+ | **AI agents** | Per-agent HTTP API. Trust On First Use registration. Comments, suggestions, inline provenance marks (`<proof-span>`). Block-level revision check. Idempotency keys. Per-IP rate limiting. |
43
+ | **HTTPS** | Required for remote access. Self-signed cert (OpenSSL), trusted local cert (mkcert), or your own TLS in front of plain HTTP. |
38
44
 
39
45
  ---
40
46
 
41
47
  ## Quick start
42
48
 
43
- > **Note:** The npm package is not yet published. Use the [dev setup](#-dev-setup) below to run locally from source. The `npx` commands below will work once the package is on npm.
44
-
45
49
  ```bash
46
50
  # Point it at a directory
47
51
  npx wiki-viewer ~/notes
@@ -53,11 +57,15 @@ npx wiki-viewer
53
57
  npx wiki-viewer ~/notes --https
54
58
  ```
55
59
 
56
- Then open **http://localhost:3000** (or **https://localhost:3000** with `--https`).
60
+ Open **http://localhost:3000** (or **https://localhost:3000** with `--https`).
57
61
 
58
- > ⚠️ **Running on a remote host?** The app must be accessed over **HTTPS** not plain HTTP or several features will silently break (PDF viewer, service workers, and anything that requires a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts)). Use `--https` or put it behind a reverse proxy that handles TLS. Plain HTTP only works correctly on `localhost`.
62
+ To switch the served directory later, click **Change** at the bottom of the sidebar and pick a new folder. No restart needed.
59
63
 
60
- ### Options
64
+ 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.
65
+
66
+ > ⚠️ **Running on a remote host?** The app must be accessed over **HTTPS**. Browsers gate several APIs (service workers, PDF.js, secure-context features) behind HTTPS. Plain HTTP only works on `localhost`.
67
+
68
+ ### CLI options
61
69
 
62
70
  ```
63
71
  wiki-viewer [directory] [options]
@@ -67,11 +75,11 @@ wiki-viewer [directory] [options]
67
75
  Options:
68
76
  -p, --port <port> Port to listen on (default: 3000)
69
77
  -H, --host <host> Host to bind to (default: localhost)
70
- --https Enable HTTPS (self-signed cert, required for service workers on remote)
78
+ --https Enable HTTPS (self-signed cert, required on remote)
71
79
  -h, --help Show this help message
72
80
  ```
73
81
 
74
- **Examples:**
82
+ Examples:
75
83
 
76
84
  ```bash
77
85
  # Custom port
@@ -86,116 +94,321 @@ npx wiki-viewer ~/notes --https -p 8443
86
94
 
87
95
  ---
88
96
 
89
- ## 🔧 Dev setup
97
+ ## Auth and multi-user mode
90
98
 
91
- Follow these steps to run wiki-viewer from source for local development or contribution.
99
+ 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.
92
100
 
93
- ### Prerequisites
101
+ Sign-in providers:
94
102
 
95
- - **Node.js** 18 [nodejs.org](https://nodejs.org)
96
- - **pnpm** [pnpm.io/installation](https://pnpm.io/installation)
97
- ```bash
98
- npm install -g pnpm
99
- ```
103
+ - **Google OAuth.** Enabled when both `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` are set.
104
+ - **Email + password.** Always available. No verification email is sent by default.
100
105
 
101
- ### 1 · Clone the repo
106
+ ### Email allowlist
107
+
108
+ By default any email can sign up. Lock it down before exposing the server.
109
+
110
+ There are two ways to set the allowlist. The settings sheet is the easy path; env vars are the fallback for headless or scripted deploys.
111
+
112
+ **From the UI (recommended).** Click the gear icon in the sidebar toolbar to open Signup allowlist. Enter allowed emails and domains, one per line or comma-separated, then save. Changes are stored in `~/.wiki-viewer/config.json` and apply on the next signup with no restart. Clearing both lists reverts to the environment variables below.
113
+
114
+ **From the environment.** Set either or both before starting the server:
102
115
 
103
116
  ```bash
104
- git clone https://github.com/anh-chu/wiki-viewer.git
105
- cd wiki-viewer
117
+ # Allow specific accounts
118
+ export AUTH_ALLOWED_EMAILS="alice@team.com,bob@team.com"
119
+
120
+ # Or whole domains (csv)
121
+ export AUTH_ALLOWED_DOMAIN="team.com,partner.org"
106
122
  ```
107
123
 
108
- ### 2 · Install dependencies
124
+ **Precedence.** If the UI allowlist in `config.json` is non-empty, it wins and the env vars are ignored. If it is empty, the env vars are used. If neither is set, any email can sign up. When both an email and a domain list apply, either match grants access.
125
+
126
+ ### Production guard
127
+
128
+ In production, the server refuses to boot unless `BETTER_AUTH_URL` is set to an `https://` origin. This prevents accidentally serving auth cookies over plain HTTP.
109
129
 
110
130
  ```bash
111
- pnpm install
131
+ export BETTER_AUTH_URL="https://wiki.team.com"
112
132
  ```
113
133
 
114
- ### 3 · Start the dev server
134
+ For development or local smoke tests, set `WIKI_ALLOW_INSECURE=1` to bypass the guard.
135
+
136
+ ### CSRF and trusted origins
137
+
138
+ All state-changing routes (`POST` / `PUT` / `DELETE` / `PATCH`) under `/api/wiki/*` and `/api/system/*` check the request's `Origin` header against an allowlist. Cross-origin requests with a session cookie are rejected with `403 FORBIDDEN`.
139
+
140
+ By default the allowlist includes `localhost` and `127.0.0.1`. Add hostnames you actually browse from:
115
141
 
116
142
  ```bash
117
- # Serve a directory
118
- ROOT_DIR=~/notes pnpm dev
143
+ export WIKI_OWNER_HOSTS="wiki.team.com,office.lan"
144
+ ```
145
+
146
+ Do not place wiki-viewer behind a reverse proxy that rewrites `Host`. The Origin check assumes the browser-visible hostname matches what wiki-viewer sees. Bind to loopback and front with a TLS-terminating proxy that preserves the original `Host` (nginx `proxy_set_header Host $host;` or Caddy default).
119
147
 
120
- # No directory — pick one in the browser
121
- pnpm dev
148
+ ### Rate limits
122
149
 
123
- # HTTPS dev mode (uses Next.js experimental HTTPS)
124
- ROOT_DIR=~/notes pnpm dev:https
150
+ | Endpoint | Default |
151
+ | ----------------- | ------------------------- |
152
+ | `/sign-in/email` | 20 requests / 60 seconds |
153
+ | `/sign-up/email` | 10 requests / 60 seconds |
154
+ | Other auth routes | 100 requests / 60 seconds |
155
+
156
+ Rate limiting is disabled in development (`NODE_ENV !== "production"`).
157
+
158
+ ### Recovering a locked-out account
159
+
160
+ The rate limiter stores counts in memory. Restart the server to clear it.
161
+
162
+ To delete and re-create a user from scratch:
163
+
164
+ ```bash
165
+ sqlite3 ~/.wiki-viewer/auth.db "DELETE FROM user WHERE email='you@team.com';"
166
+ sqlite3 ~/.wiki-viewer/auth.db "DELETE FROM account WHERE userId NOT IN (SELECT id FROM user);"
167
+ sqlite3 ~/.wiki-viewer/auth.db "DELETE FROM session WHERE userId NOT IN (SELECT id FROM user);"
125
168
  ```
126
169
 
127
- Open **http://localhost:3000**.
170
+ Then sign up again.
171
+
172
+ ### Editor save conflicts
173
+
174
+ The Markdown editor now sends a `baseRevision` with every save. If another user (or an agent) modified the file in the meantime, the server returns `409 STALE_REVISION` and the editor silently reloads the new content. Your in-progress edits in that tab are lost. Reload before making large changes if you know someone else is also editing.
175
+
176
+ ---
177
+
178
+ ## Working with AI agents
128
179
 
129
- The dev server supports **hot reload** changes to source files are reflected instantly.
180
+ wiki-viewer exposes an HTTP collaboration protocol so agents (Claude, Cursor, ChatGPT desktop, custom scripts) can read and edit Markdown files. Every AI-authored insert is wrapped in an inline `<proof-span>` mark so the human reviewer can see, accept, or revert each change.
130
181
 
131
- ### Available scripts
182
+ The protocol is intentionally API-compatible in spirit with [Proof SDK](https://github.com/EveryInc/proof-sdk).
132
183
 
133
- | Command | Description |
134
- | ---------------- | ---------------------------------------- |
135
- | `pnpm dev` | Start Next.js development server |
136
- | `pnpm dev:https` | Start dev server with experimental HTTPS |
137
- | `pnpm build` | Build production bundle |
138
- | `pnpm start` | Start production server (after build) |
139
- | `pnpm wiki` | Run the CLI entry point (after build) |
184
+ ### Trust On First Use registration
140
185
 
141
- ### Environment variables
186
+ Each agent gets its own bearer token tied to a stable identity. No shared secret.
142
187
 
143
- | Variable | Description | Default |
144
- | ---------- | ------------------------ | --------------------- |
145
- | `ROOT_DIR` | Directory to serve | `~/wiki-viewer-files` |
146
- | `PORT` | Port to listen on | `3000` |
147
- | `HOSTNAME` | Host / interface to bind | `localhost` |
188
+ 1. Agent calls `POST /api/agent/register` with `id`, `displayName`, and requested `scope`.
189
+ 2. Server returns `registrationId` and `pollUrl`. The id itself is the pickup secret.
190
+ 3. Owner opens the AI Panel, sees the pending request, clicks **Approve**.
191
+ 4. Agent polls and receives a one-shot `token`. Pickup deletes the token from the server.
192
+ 5. Agent sends `Authorization: Bearer <token>` and `X-Agent-Id: <id>` on every later request.
193
+
194
+ The registry lives in `~/.wiki-viewer/agents.json`. Only SHA-256 hashes of tokens are stored.
195
+
196
+ ### Distribute the agent skill
197
+
198
+ The running server exposes itself as an installable [Agent Skill](https://github.com/anthropics/skills):
199
+
200
+ ```bash
201
+ # Claude Code, Codex, Cursor, OpenCode
202
+ npx skills add anh-chu/wiki-viewer/agents/wiki-viewer-skill
203
+ ```
204
+
205
+ For any chat agent, paste the bootstrap prompt from `<your-server>/agents/install` (also visible in the AI Panel). The agent fetches `/api/agents/install` and learns the full op vocabulary at runtime.
206
+
207
+ ### Op vocabulary
208
+
209
+ Block-level edits (revision-checked, idempotent):
210
+
211
+ ```json
212
+ { "type": "block.replace", "ref": "b7f2c1", "markdown": "New content." }
213
+ { "type": "block.insertAfter", "ref": "b7f2c1", "markdown": "..." }
214
+ { "type": "block.insertBefore", "ref": "b7f2c1", "markdown": "..." }
215
+ { "type": "block.delete", "ref": "b7f2c1" }
216
+ { "type": "block.append", "markdown": "..." }
217
+ { "type": "block.prepend", "markdown": "..." }
218
+ ```
219
+
220
+ Comments and suggestions:
221
+
222
+ ```json
223
+ { "type": "comment.add", "ref": "b7f2c1", "text": "Why end of June?" }
224
+ { "type": "comment.reply", "commentId": "c4a1", "text": "API freeze." }
225
+ { "type": "comment.resolve", "commentId": "c4a1" }
226
+ { "type": "suggestion.add", "ref": "b7f2c1", "kind": "replace", "markdown": "...",
227
+ "basis": "described", "basisDetail": "user asked" }
228
+ { "type": "suggestion.accept", "suggestionId": "s3b2" }
229
+ ```
230
+
231
+ See [`docs/agent-collab-plan.md`](docs/agent-collab-plan.md) for the full spec: snapshot shape, event log, suggestion lifecycle, provenance attribute rules, and edge cases.
232
+
233
+ ### Key routes
234
+
235
+ Anonymous:
236
+
237
+ | Method | Path | Description |
238
+ | ------ | ----------------------------- | ----------------------------------------------- |
239
+ | `POST` | `/api/agent/register` | Request registration. Returns `registrationId`. |
240
+ | `GET` | `/api/agent/register/<regId>` | Poll status. Returns token once approved. |
241
+ | `GET` | `/api/agents/install` | Discovery JSON for agents. |
242
+ | `GET` | `/api/agents/skill` | Raw SKILL.md. |
243
+ | `GET` | `/api/agents/skill.tar.gz` | Skill as gzip tarball. |
244
+
245
+ Owner-only (session cookie):
246
+
247
+ | Method | Path | Description |
248
+ | ------ | ------------------------------------------------ | --------------------------- |
249
+ | `GET` | `/api/agent/admin/registrations` | List pending registrations. |
250
+ | `POST` | `/api/agent/admin/registrations/<regId>/approve` | Approve, mint token. |
251
+ | `POST` | `/api/agent/admin/registrations/<regId>/deny` | Deny. |
252
+ | `GET` | `/api/agent/admin/agents` | List your approved agents. |
253
+ | `POST` | `/api/agent/admin/agents/<id>/revoke` | Revoke an agent. |
254
+
255
+ Agent routes (bearer + `X-Agent-Id`, scope-checked):
256
+
257
+ | Method | Path | Required scope |
258
+ | ------ | ---------------------------------------- | --------------------- |
259
+ | `GET` | `/api/agent/files/<path.md>` | `read` + path match |
260
+ | `POST` | `/api/agent/files/<path.md>` | `mutate` + path match |
261
+ | `GET` | `/api/agent/events/<path.md>?after=<id>` | `read` + path match |
262
+ | `POST` | `/api/agent/events/<path.md>` | `read` + path match |
263
+ | `GET` | `/api/agent/sidecar/<path.md>` | `read` + path match |
264
+ | `GET` | `/api/agent/settings` | `read` |
265
+ | `GET` | `/api/agent/activity` | `read` |
266
+
267
+ ### Full curl trace
268
+
269
+ ```bash
270
+ # 1. Agent registers
271
+ curl -s -X POST https://wiki.team.com/api/agent/register \
272
+ -H "Content-Type: application/json" \
273
+ -d '{"id":"ai:claude","displayName":"Claude",
274
+ "scope":{"paths":["**/*"],"ops":["read","mutate"]}}'
275
+ # -> { "registrationId":"reg_abc","pollUrl":"/api/agent/register/reg_abc","status":"pending" }
276
+
277
+ # 2. Owner approves in the AI Panel.
278
+
279
+ # 3. Agent picks up the token (one shot, 410 on replay)
280
+ TOKEN=$(curl -s https://wiki.team.com/api/agent/register/reg_abc | jq -r .token)
281
+
282
+ # 4. Read a file
283
+ curl -s \
284
+ -H "Authorization: Bearer $TOKEN" \
285
+ -H "X-Agent-Id: ai:claude" \
286
+ https://wiki.team.com/api/agent/files/notes.md | jq
287
+
288
+ # 5. Mutate. `by` must equal `X-Agent-Id`. Idempotency-Key is required.
289
+ curl -s -X POST \
290
+ -H "Authorization: Bearer $TOKEN" \
291
+ -H "X-Agent-Id: ai:claude" \
292
+ -H "Content-Type: application/json" \
293
+ -H "Idempotency-Key: req-$(cat /proc/sys/kernel/random/uuid)" \
294
+ -d '{"baseRevision":7,"by":"ai:claude",
295
+ "ops":[{"type":"block.append","markdown":"From your agent."}]}' \
296
+ https://wiki.team.com/api/agent/files/notes.md
297
+
298
+ # 6. Poll events to see human comments, suggestion accepts, external edits
299
+ curl -s -H "Authorization: Bearer $TOKEN" -H "X-Agent-Id: ai:claude" \
300
+ "https://wiki.team.com/api/agent/events/notes.md?after=0" | jq
301
+ ```
302
+
303
+ Response codes to handle:
304
+
305
+ - `401 UNAUTHORIZED` — bad token or `X-Agent-Id`.
306
+ - `403 FORBIDDEN` — out of scope or `by` mismatches identity.
307
+ - `409 STALE_REVISION` — refetch and retry. Response includes a fresh snapshot.
308
+ - `409 BLOCK_NOT_FOUND` — the ref no longer exists.
309
+ - `409 IDEMPOTENCY_KEY_REUSED` — same key, different body.
310
+ - `429 RATE_LIMITED` — honor `Retry-After`. Default 60 ops/minute per agent.
311
+
312
+ ### Agent rate limit override
313
+
314
+ ```bash
315
+ export AGENT_RATE_LIMIT=120
316
+ ```
317
+
318
+ Note: `AGENT_BEARER_TOKEN` (legacy single-secret mode) does nothing now. The server logs a one-time warning if it is set. Remove it.
148
319
 
149
320
  ---
150
321
 
151
- ## 🚀 Local deployment (self-hosted)
322
+ ## Dev setup
152
323
 
153
- Want to run wiki-viewer as a persistent server on your machine or a home server? Here's how.
324
+ ### Prerequisites
154
325
 
155
- ### Option A — Build and run directly
326
+ - **Node.js** 18
327
+ - **pnpm** — `npm install -g pnpm`
328
+
329
+ ### Run from source
156
330
 
157
331
  ```bash
158
- # 1. Clone & install
159
332
  git clone https://github.com/anh-chu/wiki-viewer.git
160
333
  cd wiki-viewer
161
334
  pnpm install
335
+ ROOT_DIR=~/notes pnpm dev
336
+ ```
337
+
338
+ The dev server supports hot reload.
339
+
340
+ ### Scripts
341
+
342
+ | Command | Description |
343
+ | ---------------- | -------------------------------------- |
344
+ | `pnpm dev` | Next.js development server |
345
+ | `pnpm dev:https` | Dev server with experimental HTTPS |
346
+ | `pnpm build` | Production build |
347
+ | `pnpm start` | Production server (after `build`) |
348
+ | `pnpm wiki` | CLI entry point (after `build`) |
349
+ | `pnpm test` | Run the proof + auth test suite (180+) |
350
+
351
+ ### All environment variables
352
+
353
+ | Variable | Description | Default |
354
+ | ---------------------- | --------------------------------------------------------------------------------- | --------------------- |
355
+ | `ROOT_DIR` | Directory to serve | `~/wiki-viewer-files` |
356
+ | `PORT` | Port to listen on | `3000` |
357
+ | `HOSTNAME` | Host / interface to bind | `localhost` |
358
+ | `BETTER_AUTH_URL` | Public origin (required in production, must be `https://`) | unset |
359
+ | `BETTER_AUTH_SECRET` | Override for the auto-generated session signing secret | file-stored |
360
+ | `GOOGLE_CLIENT_ID` | Enable Google OAuth button | unset |
361
+ | `GOOGLE_CLIENT_SECRET` | Enable Google OAuth button | unset |
362
+ | `AUTH_ALLOWED_EMAILS` | csv: only these emails can sign up (overridden by UI allowlist if set) | unset (open) |
363
+ | `AUTH_ALLOWED_DOMAIN` | csv: only emails on these domains can sign up (overridden by UI allowlist if set) | unset (open) |
364
+ | `WIKI_OWNER_HOSTS` | csv: extra hostnames trusted for CSRF Origin check | `localhost,127.0.0.1` |
365
+ | `WIKI_ALLOW_INSECURE` | Set to `1` to bypass the prod-https guard (dev / CI only) | unset |
366
+ | `AGENT_RATE_LIMIT` | Max mutation ops per minute per agent identity | `60` |
367
+
368
+ ---
369
+
370
+ ## Self-hosted deployment
371
+
372
+ ### Option A — Build and run directly
162
373
 
163
- # 2. Build the production bundle
374
+ ```bash
375
+ git clone https://github.com/anh-chu/wiki-viewer.git
376
+ cd wiki-viewer
377
+ pnpm install
164
378
  pnpm build
165
379
 
166
- # 3. Copy static assets into the standalone output (required step)
380
+ # Static assets into the standalone output
167
381
  cp -r .next/static .next/standalone/.next/static
168
382
  cp -r public .next/standalone/public
169
383
 
170
- # 4. Start the server
171
- ROOT_DIR=/path/to/your/files node .next/standalone/server.js
384
+ # Start
385
+ ROOT_DIR=/srv/notes BETTER_AUTH_URL=https://wiki.team.com \
386
+ AUTH_ALLOWED_DOMAIN=team.com \
387
+ node .next/standalone/server.js
172
388
  ```
173
389
 
174
- Or use the CLI wrapper (which handles the above automatically):
390
+ Or use the CLI wrapper, which handles the static-asset copy:
175
391
 
176
392
  ```bash
177
- node bin/wiki-viewer.js /path/to/your/files
393
+ node bin/wiki-viewer.js /srv/notes
178
394
  ```
179
395
 
180
- ### Option B — Run with PM2 (auto-restart on crash / reboot)
396
+ ### Option B — PM2
181
397
 
182
398
  ```bash
183
399
  npm install -g pm2
184
400
 
185
- # Start and name the process
186
401
  pm2 start bin/wiki-viewer.js \
187
402
  --name wiki-viewer \
188
- --node-args="" \
189
- -- /path/to/your/files --port 3000
403
+ -- /srv/notes --port 3000
190
404
 
191
- # Save so it auto-starts on reboot
192
405
  pm2 save
193
- pm2 startup # follow the printed instructions
406
+ pm2 startup # follow printed instructions
194
407
  ```
195
408
 
196
- ### Option C — systemd service (Linux)
409
+ ### Option C — systemd
197
410
 
198
- Create `/etc/systemd/system/wiki-viewer.service`:
411
+ `/etc/systemd/system/wiki-viewer.service`:
199
412
 
200
413
  ```ini
201
414
  [Unit]
@@ -206,44 +419,44 @@ After=network.target
206
419
  Type=simple
207
420
  User=YOUR_USER
208
421
  WorkingDirectory=/home/YOUR_USER/wiki-viewer
209
- ExecStart=/usr/bin/node bin/wiki-viewer.js /path/to/your/files --port 3000 --host 0.0.0.0
422
+ ExecStart=/usr/bin/node bin/wiki-viewer.js /srv/notes --port 3000 --host 0.0.0.0
210
423
  Restart=on-failure
211
424
  Environment=NODE_ENV=production
425
+ Environment=BETTER_AUTH_URL=https://wiki.team.com
426
+ Environment=AUTH_ALLOWED_DOMAIN=team.com
427
+ Environment=WIKI_OWNER_HOSTS=wiki.team.com
212
428
 
213
429
  [Install]
214
430
  WantedBy=multi-user.target
215
431
  ```
216
432
 
217
- Then enable and start it:
218
-
219
433
  ```bash
220
434
  sudo systemctl daemon-reload
221
435
  sudo systemctl enable wiki-viewer
222
436
  sudo systemctl start wiki-viewer
223
- sudo systemctl status wiki-viewer
437
+ sudo journalctl -u wiki-viewer -f
224
438
  ```
225
439
 
226
- ### HTTPS on a remote server
440
+ ### HTTPS
227
441
 
228
- > ⚠️ **HTTPS is required when wiki-viewer is not on `localhost`.** Browsers restrict several APIs (service workers, PDF.js, and other [secure-context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) features) to HTTPS-only origins. Accessing wiki-viewer over plain HTTP on a remote host will silently break parts of the UI.
442
+ `--https` runs an HTTPS reverse proxy in front of the internal HTTP server. mkcert is used when available, otherwise OpenSSL self-signed.
229
443
 
230
- **How it works under the hood:** `--https` does not make the Next.js server itself speak TLS. Instead it:
444
+ For real deployments, run wiki-viewer on plain HTTP behind nginx or Caddy with a Let's Encrypt cert. Configure the proxy to:
231
445
 
232
- 1. Starts the Next.js standalone server on a random internal HTTP port (`127.0.0.1:XXXXX`)
233
- 2. Starts an HTTPS reverse proxy on the user-facing port that forwards all traffic to the internal server
446
+ - Terminate TLS.
447
+ - Preserve the original `Host` header (`proxy_set_header Host $host;` in nginx; Caddy does this by default).
448
+ - Forward all paths.
234
449
 
235
- So the chain is always: **browser → HTTPS proxy → internal HTTP server**. This is why `--https` is the correct flag and not a plain HTTP URL even in "HTTPS mode".
236
-
237
- **Certificate generation** (automatic, stored at `~/.wiki-viewer/certs/`):
238
-
239
- - If **mkcert** is installed → locally-trusted cert (no browser warning)
240
- - Otherwise → OpenSSL self-signed cert (browser warns once — click through)
241
-
242
- ```bash
243
- node bin/wiki-viewer.js /path/to/your/files --https --port 443 --host 0.0.0.0
244
- ```
450
+ ### Production deployment checklist
245
451
 
246
- > **Tip for production:** Use a real reverse proxy (nginx / Caddy) with a proper TLS certificate instead of `--https`. Configure it to proxy to wiki-viewer's plain HTTP port, and wiki-viewer will work correctly behind it — no `--https` flag needed.
452
+ - [ ] `BETTER_AUTH_URL=https://your-domain` exported in the service environment.
453
+ - [ ] `AUTH_ALLOWED_DOMAIN` or `AUTH_ALLOWED_EMAILS` set before opening the server to anyone.
454
+ - [ ] `WIKI_OWNER_HOSTS` includes every hostname your users browse from.
455
+ - [ ] TLS handled by your reverse proxy or `--https`. Plain HTTP rejects in prod.
456
+ - [ ] `~/.wiki-viewer/` on local disk only. SQLite WAL is not safe on NFS or shared between replicas.
457
+ - [ ] Single host. If you cluster, you also need a shared lock service. Out of scope today.
458
+ - [ ] OAuth redirect URI registered with Google: `https://your-domain/api/auth/callback/google`.
459
+ - [ ] If you ran an older version: legacy agents in `agents.json` without `ownerUserId` are visible to and revocable by any signed-in user. Either edit the file to add `"ownerUserId": "<your user id>"` to each, or revoke and re-register them.
247
460
 
248
461
  ---
249
462
 
@@ -251,16 +464,32 @@ node bin/wiki-viewer.js /path/to/your/files --https --port 443 --host 0.0.0.0
251
464
 
252
465
  ```
253
466
  wiki-viewer/
467
+ ├── agents/ Installable Agent Skill + bootstrap prompt
468
+ │ ├── wiki-viewer-skill/ SKILL.md and assets
469
+ │ └── bootstrap-prompt.md One-paragraph prompt for any chat agent
254
470
  ├── bin/
255
- │ └── wiki-viewer.js CLI entry point
471
+ │ └── wiki-viewer.js CLI entry point
472
+ ├── docs/
473
+ │ ├── agent-collab-plan.md Full v1 spec for the agent HTTP protocol
474
+ │ └── agent-collab-v2-plan.md Reference for a future Yjs / multi-tenant pivot
256
475
  ├── src/
257
- │ ├── app/ Next.js app router pages & API routes
258
- │ ├── components/ React components (viewer, editor, sidebar…)
259
- │ ├── lib/ File system helpers, parsers, utilities
260
- │ ├── stores/ Zustand state stores
261
- └── types/ Shared TypeScript types
476
+ │ ├── app/
477
+ ├── api/agent/ Agent HTTP API
478
+ ├── api/agents/ Public install endpoints
479
+ ├── api/auth/ Better Auth handler
480
+ │ ├── api/wiki/ File browser API (session-gated)
481
+ │ │ ├── api/system/ System config API (session-gated)
482
+ │ │ └── signin/ Sign-in page
483
+ │ ├── components/
484
+ │ │ ├── editor/ TipTap editor + proof-span + comment-pip + suggestion-card
485
+ │ │ └── ai-panel/ Right-side AI panel (agents, activity, install)
486
+ │ ├── lib/
487
+ │ │ ├── auth/ Better Auth server + client + allowlist + CSRF
488
+ │ │ └── proof/ Agent protocol core (ops-applier, registry, file-lock)
489
+ │ ├── stores/ Zustand state
490
+ │ ├── tests/proof/ Node test runner suite (180+ tests)
491
+ │ └── middleware.ts Cookie-presence gate for UI routes
262
492
  ├── public/
263
- │ └── logo.svg
264
493
  ├── next.config.ts
265
494
  └── package.json
266
495
  ```
@@ -269,10 +498,10 @@ wiki-viewer/
269
498
 
270
499
  ## Contributing
271
500
 
272
- 1. Fork the repo and create a branch: `git checkout -b my-feature`
273
- 2. Make your changes
274
- 3. Test with `ROOT_DIR=~/notes pnpm dev`
275
- 4. Open a pull request
501
+ 1. Fork and branch: `git checkout -b my-feature`
502
+ 2. `pnpm install && pnpm dev`
503
+ 3. Run tests: `pnpm test`
504
+ 4. Open a PR.
276
505
 
277
506
  Bug reports and feature requests welcome via [GitHub Issues](https://github.com/anh-chu/wiki-viewer/issues).
278
507
 
@@ -0,0 +1 @@
1
+ You are connecting to a running wiki-viewer instance at $WIKI_URL (e.g. `http://localhost:3000`). Fetch `$WIKI_URL/api/agents/install`and follow the instructions there to register, wait for human approval, then read and edit markdown files via its HTTP API. Set`basis`and`basisDetail`on every content op so the human can see where your changes came from. Prefer`suggestion.add` over direct block ops unless told otherwise.