wiki-viewer 1.0.0 → 1.1.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 (128) hide show
  1. package/.next/standalone/README.md +365 -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 +431 -33
  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 +365 -99
  127. package/bin/wiki-viewer.js +431 -33
  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`).
61
+
62
+ To switch the served directory later, click **Change** at the bottom of the sidebar and pick a new folder. No restart needed.
57
63
 
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`.
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.
59
65
 
60
- ### Options
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,19 @@ 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
80
+
81
+ Commands:
82
+ service install [dir] [options] Install as a user service (persists across reboot)
83
+ service uninstall Remove the user service
84
+ service status Show service status
85
+ service logs Tail service logs
86
+ service restart Restart the service
87
+ update Update to the latest version and restart the service
72
88
  ```
73
89
 
74
- **Examples:**
90
+ Examples:
75
91
 
76
92
  ```bash
77
93
  # Custom port
@@ -84,118 +100,352 @@ npx wiki-viewer ~/notes -H 0.0.0.0
84
100
  npx wiki-viewer ~/notes --https -p 8443
85
101
  ```
86
102
 
103
+ ### Run as a service (reboot persistence)
104
+
105
+ Install wiki-viewer as a user service so it starts at boot and restarts on failure. Linux uses `systemd --user`, macOS uses a launchd LaunchAgent. No root needed.
106
+
107
+ ```bash
108
+ # Install with the run config you want (dir, host, port, https)
109
+ wiki-viewer service install ~/notes -H 0.0.0.0 -p 3003 --https
110
+
111
+ # Manage it
112
+ wiki-viewer service status
113
+ wiki-viewer service logs
114
+ wiki-viewer service restart
115
+ wiki-viewer service uninstall
116
+ ```
117
+
118
+ The run config is saved to `~/.wiki-viewer/config.json`. Edit that file and run `wiki-viewer service restart` to change settings without reinstalling. To change just the served directory at runtime, you can also click **Change** in the sidebar.
119
+
120
+ On Linux, install enables lingering (`loginctl enable-linger`) so the service runs without an active login session and survives reboot. If that step needs privileges, the installer prints the command to run manually.
121
+
122
+ > Ad-hoc runs like `wiki-viewer ~/docs` ignore the saved config. Only the service (and `wiki-viewer service run`) reads `config.json`.
123
+
124
+ ### Update
125
+
126
+ ```bash
127
+ wiki-viewer update
128
+ ```
129
+
130
+ Updates the global install to the latest version (npm/pnpm/yarn auto-detected) and restarts the service if one is installed.
131
+
87
132
  ---
88
133
 
89
- ## 🔧 Dev setup
134
+ ## Auth and multi-user mode
90
135
 
91
- Follow these steps to run wiki-viewer from source for local development or contribution.
136
+ 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
137
 
93
- ### Prerequisites
138
+ Sign-in providers:
94
139
 
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
- ```
140
+ - **Google OAuth.** Enabled when both `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` are set.
141
+ - **Email + password.** Always available. No verification email is sent by default.
100
142
 
101
- ### 1 · Clone the repo
143
+ ### Email allowlist
144
+
145
+ By default any email can sign up. Lock it down before exposing the server.
146
+
147
+ 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.
148
+
149
+ **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.
150
+
151
+ **From the environment.** Set either or both before starting the server:
102
152
 
103
153
  ```bash
104
- git clone https://github.com/anh-chu/wiki-viewer.git
105
- cd wiki-viewer
154
+ # Allow specific accounts
155
+ export AUTH_ALLOWED_EMAILS="alice@team.com,bob@team.com"
156
+
157
+ # Or whole domains (csv)
158
+ export AUTH_ALLOWED_DOMAIN="team.com,partner.org"
106
159
  ```
107
160
 
108
- ### 2 · Install dependencies
161
+ **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.
162
+
163
+ ### Production guard
164
+
165
+ 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
166
 
110
167
  ```bash
111
- pnpm install
168
+ export BETTER_AUTH_URL="https://wiki.team.com"
112
169
  ```
113
170
 
114
- ### 3 · Start the dev server
171
+ For development or local smoke tests, set `WIKI_ALLOW_INSECURE=1` to bypass the guard.
172
+
173
+ ### CSRF and trusted origins
174
+
175
+ 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`.
176
+
177
+ By default the allowlist includes `localhost` and `127.0.0.1`. Add hostnames you actually browse from:
115
178
 
116
179
  ```bash
117
- # Serve a directory
118
- ROOT_DIR=~/notes pnpm dev
180
+ export WIKI_OWNER_HOSTS="wiki.team.com,office.lan"
181
+ ```
182
+
183
+ 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).
184
+
185
+ ### Rate limits
186
+
187
+ | Endpoint | Default |
188
+ | ----------------- | ------------------------- |
189
+ | `/sign-in/email` | 20 requests / 60 seconds |
190
+ | `/sign-up/email` | 10 requests / 60 seconds |
191
+ | Other auth routes | 100 requests / 60 seconds |
192
+
193
+ Rate limiting is disabled in development (`NODE_ENV !== "production"`).
119
194
 
120
- # No directory pick one in the browser
121
- pnpm dev
195
+ ### Recovering a locked-out account
122
196
 
123
- # HTTPS dev mode (uses Next.js experimental HTTPS)
124
- ROOT_DIR=~/notes pnpm dev:https
197
+ The rate limiter stores counts in memory. Restart the server to clear it.
198
+
199
+ To delete and re-create a user from scratch:
200
+
201
+ ```bash
202
+ sqlite3 ~/.wiki-viewer/auth.db "DELETE FROM user WHERE email='you@team.com';"
203
+ sqlite3 ~/.wiki-viewer/auth.db "DELETE FROM account WHERE userId NOT IN (SELECT id FROM user);"
204
+ sqlite3 ~/.wiki-viewer/auth.db "DELETE FROM session WHERE userId NOT IN (SELECT id FROM user);"
125
205
  ```
126
206
 
127
- Open **http://localhost:3000**.
207
+ Then sign up again.
208
+
209
+ ### Editor save conflicts
210
+
211
+ 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.
212
+
213
+ ---
214
+
215
+ ## Working with AI agents
216
+
217
+ 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.
128
218
 
129
- The dev server supports **hot reload** changes to source files are reflected instantly.
219
+ The protocol is intentionally API-compatible in spirit with [Proof SDK](https://github.com/EveryInc/proof-sdk).
130
220
 
131
- ### Available scripts
221
+ ### Trust On First Use registration
132
222
 
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) |
223
+ Each agent gets its own bearer token tied to a stable identity. No shared secret.
140
224
 
141
- ### Environment variables
225
+ 1. Agent calls `POST /api/agent/register` with `id`, `displayName`, and requested `scope`.
226
+ 2. Server returns `registrationId` and `pollUrl`. The id itself is the pickup secret.
227
+ 3. Owner opens the AI Panel, sees the pending request, clicks **Approve**.
228
+ 4. Agent polls and receives a one-shot `token`. Pickup deletes the token from the server.
229
+ 5. Agent sends `Authorization: Bearer <token>` and `X-Agent-Id: <id>` on every later request.
142
230
 
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` |
231
+ The registry lives in `~/.wiki-viewer/agents.json`. Only SHA-256 hashes of tokens are stored.
232
+
233
+ ### Distribute the agent skill
234
+
235
+ The running server exposes itself as an installable [Agent Skill](https://github.com/anthropics/skills):
236
+
237
+ ```bash
238
+ # Claude Code, Codex, Cursor, OpenCode
239
+ npx skills add anh-chu/wiki-viewer/agents/wiki-viewer-skill
240
+ ```
241
+
242
+ 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.
243
+
244
+ ### Op vocabulary
245
+
246
+ Block-level edits (revision-checked, idempotent):
247
+
248
+ ```json
249
+ { "type": "block.replace", "ref": "b7f2c1", "markdown": "New content." }
250
+ { "type": "block.insertAfter", "ref": "b7f2c1", "markdown": "..." }
251
+ { "type": "block.insertBefore", "ref": "b7f2c1", "markdown": "..." }
252
+ { "type": "block.delete", "ref": "b7f2c1" }
253
+ { "type": "block.append", "markdown": "..." }
254
+ { "type": "block.prepend", "markdown": "..." }
255
+ ```
256
+
257
+ Comments and suggestions:
258
+
259
+ ```json
260
+ { "type": "comment.add", "ref": "b7f2c1", "text": "Why end of June?" }
261
+ { "type": "comment.reply", "commentId": "c4a1", "text": "API freeze." }
262
+ { "type": "comment.resolve", "commentId": "c4a1" }
263
+ { "type": "suggestion.add", "ref": "b7f2c1", "kind": "replace", "markdown": "...",
264
+ "basis": "described", "basisDetail": "user asked" }
265
+ { "type": "suggestion.accept", "suggestionId": "s3b2" }
266
+ ```
267
+
268
+ 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.
269
+
270
+ ### Key routes
271
+
272
+ Anonymous:
273
+
274
+ | Method | Path | Description |
275
+ | ------ | ----------------------------- | ----------------------------------------------- |
276
+ | `POST` | `/api/agent/register` | Request registration. Returns `registrationId`. |
277
+ | `GET` | `/api/agent/register/<regId>` | Poll status. Returns token once approved. |
278
+ | `GET` | `/api/agents/install` | Discovery JSON for agents. |
279
+ | `GET` | `/api/agents/skill` | Raw SKILL.md. |
280
+ | `GET` | `/api/agents/skill.tar.gz` | Skill as gzip tarball. |
281
+
282
+ Owner-only (session cookie):
283
+
284
+ | Method | Path | Description |
285
+ | ------ | ------------------------------------------------ | --------------------------- |
286
+ | `GET` | `/api/agent/admin/registrations` | List pending registrations. |
287
+ | `POST` | `/api/agent/admin/registrations/<regId>/approve` | Approve, mint token. |
288
+ | `POST` | `/api/agent/admin/registrations/<regId>/deny` | Deny. |
289
+ | `GET` | `/api/agent/admin/agents` | List your approved agents. |
290
+ | `POST` | `/api/agent/admin/agents/<id>/revoke` | Revoke an agent. |
291
+
292
+ Agent routes (bearer + `X-Agent-Id`, scope-checked):
293
+
294
+ | Method | Path | Required scope |
295
+ | ------ | ---------------------------------------- | --------------------- |
296
+ | `GET` | `/api/agent/files/<path.md>` | `read` + path match |
297
+ | `POST` | `/api/agent/files/<path.md>` | `mutate` + path match |
298
+ | `GET` | `/api/agent/events/<path.md>?after=<id>` | `read` + path match |
299
+ | `POST` | `/api/agent/events/<path.md>` | `read` + path match |
300
+ | `GET` | `/api/agent/sidecar/<path.md>` | `read` + path match |
301
+ | `GET` | `/api/agent/settings` | `read` |
302
+ | `GET` | `/api/agent/activity` | `read` |
303
+
304
+ ### Full curl trace
305
+
306
+ ```bash
307
+ # 1. Agent registers
308
+ curl -s -X POST https://wiki.team.com/api/agent/register \
309
+ -H "Content-Type: application/json" \
310
+ -d '{"id":"ai:claude","displayName":"Claude",
311
+ "scope":{"paths":["**/*"],"ops":["read","mutate"]}}'
312
+ # -> { "registrationId":"reg_abc","pollUrl":"/api/agent/register/reg_abc","status":"pending" }
313
+
314
+ # 2. Owner approves in the AI Panel.
315
+
316
+ # 3. Agent picks up the token (one shot, 410 on replay)
317
+ TOKEN=$(curl -s https://wiki.team.com/api/agent/register/reg_abc | jq -r .token)
318
+
319
+ # 4. Read a file
320
+ curl -s \
321
+ -H "Authorization: Bearer $TOKEN" \
322
+ -H "X-Agent-Id: ai:claude" \
323
+ https://wiki.team.com/api/agent/files/notes.md | jq
324
+
325
+ # 5. Mutate. `by` must equal `X-Agent-Id`. Idempotency-Key is required.
326
+ curl -s -X POST \
327
+ -H "Authorization: Bearer $TOKEN" \
328
+ -H "X-Agent-Id: ai:claude" \
329
+ -H "Content-Type: application/json" \
330
+ -H "Idempotency-Key: req-$(cat /proc/sys/kernel/random/uuid)" \
331
+ -d '{"baseRevision":7,"by":"ai:claude",
332
+ "ops":[{"type":"block.append","markdown":"From your agent."}]}' \
333
+ https://wiki.team.com/api/agent/files/notes.md
334
+
335
+ # 6. Poll events to see human comments, suggestion accepts, external edits
336
+ curl -s -H "Authorization: Bearer $TOKEN" -H "X-Agent-Id: ai:claude" \
337
+ "https://wiki.team.com/api/agent/events/notes.md?after=0" | jq
338
+ ```
339
+
340
+ Response codes to handle:
341
+
342
+ - `401 UNAUTHORIZED` — bad token or `X-Agent-Id`.
343
+ - `403 FORBIDDEN` — out of scope or `by` mismatches identity.
344
+ - `409 STALE_REVISION` — refetch and retry. Response includes a fresh snapshot.
345
+ - `409 BLOCK_NOT_FOUND` — the ref no longer exists.
346
+ - `409 IDEMPOTENCY_KEY_REUSED` — same key, different body.
347
+ - `429 RATE_LIMITED` — honor `Retry-After`. Default 60 ops/minute per agent.
348
+
349
+ ### Agent rate limit override
350
+
351
+ ```bash
352
+ export AGENT_RATE_LIMIT=120
353
+ ```
354
+
355
+ 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
356
 
149
357
  ---
150
358
 
151
- ## 🚀 Local deployment (self-hosted)
359
+ ## Dev setup
152
360
 
153
- Want to run wiki-viewer as a persistent server on your machine or a home server? Here's how.
361
+ ### Prerequisites
154
362
 
155
- ### Option A — Build and run directly
363
+ - **Node.js** 18
364
+ - **pnpm** — `npm install -g pnpm`
365
+
366
+ ### Run from source
156
367
 
157
368
  ```bash
158
- # 1. Clone & install
159
369
  git clone https://github.com/anh-chu/wiki-viewer.git
160
370
  cd wiki-viewer
161
371
  pnpm install
372
+ ROOT_DIR=~/notes pnpm dev
373
+ ```
374
+
375
+ The dev server supports hot reload.
376
+
377
+ ### Scripts
378
+
379
+ | Command | Description |
380
+ | ---------------- | -------------------------------------- |
381
+ | `pnpm dev` | Next.js development server |
382
+ | `pnpm dev:https` | Dev server with experimental HTTPS |
383
+ | `pnpm build` | Production build |
384
+ | `pnpm start` | Production server (after `build`) |
385
+ | `pnpm wiki` | CLI entry point (after `build`) |
386
+ | `pnpm test` | Run the proof + auth test suite (180+) |
387
+
388
+ ### All environment variables
389
+
390
+ | Variable | Description | Default |
391
+ | ---------------------- | --------------------------------------------------------------------------------- | --------------------- |
392
+ | `ROOT_DIR` | Directory to serve | `~/wiki-viewer-files` |
393
+ | `PORT` | Port to listen on | `3000` |
394
+ | `HOSTNAME` | Host / interface to bind | `localhost` |
395
+ | `BETTER_AUTH_URL` | Public origin (required in production, must be `https://`) | unset |
396
+ | `BETTER_AUTH_SECRET` | Override for the auto-generated session signing secret | file-stored |
397
+ | `GOOGLE_CLIENT_ID` | Enable Google OAuth button | unset |
398
+ | `GOOGLE_CLIENT_SECRET` | Enable Google OAuth button | unset |
399
+ | `AUTH_ALLOWED_EMAILS` | csv: only these emails can sign up (overridden by UI allowlist if set) | unset (open) |
400
+ | `AUTH_ALLOWED_DOMAIN` | csv: only emails on these domains can sign up (overridden by UI allowlist if set) | unset (open) |
401
+ | `WIKI_OWNER_HOSTS` | csv: extra hostnames trusted for CSRF Origin check | `localhost,127.0.0.1` |
402
+ | `WIKI_ALLOW_INSECURE` | Set to `1` to bypass the prod-https guard (dev / CI only) | unset |
403
+ | `AGENT_RATE_LIMIT` | Max mutation ops per minute per agent identity | `60` |
404
+
405
+ ---
406
+
407
+ ## Self-hosted deployment
162
408
 
163
- # 2. Build the production bundle
409
+ ### Option A — Build and run directly
410
+
411
+ ```bash
412
+ git clone https://github.com/anh-chu/wiki-viewer.git
413
+ cd wiki-viewer
414
+ pnpm install
164
415
  pnpm build
165
416
 
166
- # 3. Copy static assets into the standalone output (required step)
417
+ # Static assets into the standalone output
167
418
  cp -r .next/static .next/standalone/.next/static
168
419
  cp -r public .next/standalone/public
169
420
 
170
- # 4. Start the server
171
- ROOT_DIR=/path/to/your/files node .next/standalone/server.js
421
+ # Start
422
+ ROOT_DIR=/srv/notes BETTER_AUTH_URL=https://wiki.team.com \
423
+ AUTH_ALLOWED_DOMAIN=team.com \
424
+ node .next/standalone/server.js
172
425
  ```
173
426
 
174
- Or use the CLI wrapper (which handles the above automatically):
427
+ Or use the CLI wrapper, which handles the static-asset copy:
175
428
 
176
429
  ```bash
177
- node bin/wiki-viewer.js /path/to/your/files
430
+ node bin/wiki-viewer.js /srv/notes
178
431
  ```
179
432
 
180
- ### Option B — Run with PM2 (auto-restart on crash / reboot)
433
+ ### Option B — PM2
181
434
 
182
435
  ```bash
183
436
  npm install -g pm2
184
437
 
185
- # Start and name the process
186
438
  pm2 start bin/wiki-viewer.js \
187
439
  --name wiki-viewer \
188
- --node-args="" \
189
- -- /path/to/your/files --port 3000
440
+ -- /srv/notes --port 3000
190
441
 
191
- # Save so it auto-starts on reboot
192
442
  pm2 save
193
- pm2 startup # follow the printed instructions
443
+ pm2 startup # follow printed instructions
194
444
  ```
195
445
 
196
- ### Option C — systemd service (Linux)
446
+ ### Option C — systemd
197
447
 
198
- Create `/etc/systemd/system/wiki-viewer.service`:
448
+ `/etc/systemd/system/wiki-viewer.service`:
199
449
 
200
450
  ```ini
201
451
  [Unit]
@@ -206,44 +456,44 @@ After=network.target
206
456
  Type=simple
207
457
  User=YOUR_USER
208
458
  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
459
+ ExecStart=/usr/bin/node bin/wiki-viewer.js /srv/notes --port 3000 --host 0.0.0.0
210
460
  Restart=on-failure
211
461
  Environment=NODE_ENV=production
462
+ Environment=BETTER_AUTH_URL=https://wiki.team.com
463
+ Environment=AUTH_ALLOWED_DOMAIN=team.com
464
+ Environment=WIKI_OWNER_HOSTS=wiki.team.com
212
465
 
213
466
  [Install]
214
467
  WantedBy=multi-user.target
215
468
  ```
216
469
 
217
- Then enable and start it:
218
-
219
470
  ```bash
220
471
  sudo systemctl daemon-reload
221
472
  sudo systemctl enable wiki-viewer
222
473
  sudo systemctl start wiki-viewer
223
- sudo systemctl status wiki-viewer
474
+ sudo journalctl -u wiki-viewer -f
224
475
  ```
225
476
 
226
- ### HTTPS on a remote server
227
-
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.
229
-
230
- **How it works under the hood:** `--https` does not make the Next.js server itself speak TLS. Instead it:
477
+ ### HTTPS
231
478
 
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
479
+ `--https` runs an HTTPS reverse proxy in front of the internal HTTP server. mkcert is used when available, otherwise OpenSSL self-signed.
234
480
 
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".
481
+ For real deployments, run wiki-viewer on plain HTTP behind nginx or Caddy with a Let's Encrypt cert. Configure the proxy to:
236
482
 
237
- **Certificate generation** (automatic, stored at `~/.wiki-viewer/certs/`):
483
+ - Terminate TLS.
484
+ - Preserve the original `Host` header (`proxy_set_header Host $host;` in nginx; Caddy does this by default).
485
+ - Forward all paths.
238
486
 
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
- ```
487
+ ### Production deployment checklist
245
488
 
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.
489
+ - [ ] `BETTER_AUTH_URL=https://your-domain` exported in the service environment.
490
+ - [ ] `AUTH_ALLOWED_DOMAIN` or `AUTH_ALLOWED_EMAILS` set before opening the server to anyone.
491
+ - [ ] `WIKI_OWNER_HOSTS` includes every hostname your users browse from.
492
+ - [ ] TLS handled by your reverse proxy or `--https`. Plain HTTP rejects in prod.
493
+ - [ ] `~/.wiki-viewer/` on local disk only. SQLite WAL is not safe on NFS or shared between replicas.
494
+ - [ ] Single host. If you cluster, you also need a shared lock service. Out of scope today.
495
+ - [ ] OAuth redirect URI registered with Google: `https://your-domain/api/auth/callback/google`.
496
+ - [ ] 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
497
 
248
498
  ---
249
499
 
@@ -251,16 +501,32 @@ node bin/wiki-viewer.js /path/to/your/files --https --port 443 --host 0.0.0.0
251
501
 
252
502
  ```
253
503
  wiki-viewer/
504
+ ├── agents/ Installable Agent Skill + bootstrap prompt
505
+ │ ├── wiki-viewer-skill/ SKILL.md and assets
506
+ │ └── bootstrap-prompt.md One-paragraph prompt for any chat agent
254
507
  ├── bin/
255
- │ └── wiki-viewer.js CLI entry point
508
+ │ └── wiki-viewer.js CLI entry point
509
+ ├── docs/
510
+ │ ├── agent-collab-plan.md Full v1 spec for the agent HTTP protocol
511
+ │ └── agent-collab-v2-plan.md Reference for a future Yjs / multi-tenant pivot
256
512
  ├── 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
513
+ │ ├── app/
514
+ ├── api/agent/ Agent HTTP API
515
+ ├── api/agents/ Public install endpoints
516
+ ├── api/auth/ Better Auth handler
517
+ │ ├── api/wiki/ File browser API (session-gated)
518
+ │ │ ├── api/system/ System config API (session-gated)
519
+ │ │ └── signin/ Sign-in page
520
+ │ ├── components/
521
+ │ │ ├── editor/ TipTap editor + proof-span + comment-pip + suggestion-card
522
+ │ │ └── ai-panel/ Right-side AI panel (agents, activity, install)
523
+ │ ├── lib/
524
+ │ │ ├── auth/ Better Auth server + client + allowlist + CSRF
525
+ │ │ └── proof/ Agent protocol core (ops-applier, registry, file-lock)
526
+ │ ├── stores/ Zustand state
527
+ │ ├── tests/proof/ Node test runner suite (180+ tests)
528
+ │ └── middleware.ts Cookie-presence gate for UI routes
262
529
  ├── public/
263
- │ └── logo.svg
264
530
  ├── next.config.ts
265
531
  └── package.json
266
532
  ```
@@ -269,10 +535,10 @@ wiki-viewer/
269
535
 
270
536
  ## Contributing
271
537
 
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
538
+ 1. Fork and branch: `git checkout -b my-feature`
539
+ 2. `pnpm install && pnpm dev`
540
+ 3. Run tests: `pnpm test`
541
+ 4. Open a PR.
276
542
 
277
543
  Bug reports and feature requests welcome via [GitHub Issues](https://github.com/anh-chu/wiki-viewer/issues).
278
544
 
@@ -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.