@karmaniverous/jeeves-server 3.0.0 → 3.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.
package/guides/setup.md CHANGED
@@ -2,70 +2,121 @@
2
2
 
3
3
  ## Prerequisites
4
4
 
5
- - **Node.js** ≥ 18
5
+ - **Node.js** ≥ 20
6
6
  - **Chrome or Chromium** — required for PDF export (Puppeteer uses it headlessly)
7
- - **A domain or IP** where the server will be accessible (for Google OAuth callbacks)
8
7
 
9
8
  ## Installation
10
9
 
11
10
  ```bash
12
- git clone https://github.com/karmaniverous/jeeves-server.git
13
- cd jeeves-server
14
- npm install
11
+ npm install -g @karmaniverous/jeeves-server
15
12
  ```
16
13
 
14
+ The `postinstall` script automatically downloads the PlantUML jar for local diagram rendering.
15
+
17
16
  ## Configuration
18
17
 
19
- Jeeves Server uses a **TypeScript configuration file** validated at startup by a [Zod](https://github.com/colinhacks/zod) schema. The schema at `src/config/schema.ts` is the single source of truth — all types are derived from it.
18
+ Jeeves Server uses [cosmiconfig](https://github.com/cosmiconfig/cosmiconfig) for configuration. Create a config file in any supported format:
19
+
20
+ ```bash
21
+ # JSON (recommended for simplicity)
22
+ jeeves-server.config.json
20
23
 
21
- ### Create your config
24
+ # YAML
25
+ jeeves-server.config.yaml
26
+
27
+ # TypeScript (for type checking during authoring)
28
+ jeeves-server.config.ts
29
+
30
+ # Or any cosmiconfig-supported format
31
+ ```
32
+
33
+ The server searches for config files starting from its working directory and walking up. You can also specify an explicit path:
22
34
 
23
35
  ```bash
24
- cp jeeves.config.template.ts jeeves.config.ts
36
+ jeeves-server start --config /path/to/jeeves-server.config.json
25
37
  ```
26
38
 
27
- Edit `jeeves.config.ts` with your values. This file is **gitignored** — it contains secrets and is never committed.
39
+ ### Config structure (JSON)
40
+
41
+ ```json
42
+ {
43
+ "port": 1934,
44
+ "chromePath": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
45
+ "auth": {
46
+ "modes": ["keys", "google"],
47
+ "google": {
48
+ "clientId": "your-google-client-id",
49
+ "clientSecret": "your-google-client-secret"
50
+ },
51
+ "sessionSecret": "random-session-signing-secret"
52
+ },
53
+ "scopes": {
54
+ "restricted": {
55
+ "allow": ["/d/projects/*"],
56
+ "deny": ["/d/projects/secret/*"]
57
+ }
58
+ },
59
+ "insiders": {
60
+ "alice@example.com": {},
61
+ "contractor@example.com": { "scopes": "restricted" }
62
+ },
63
+ "keys": {
64
+ "_internal": "random-hex-seed-for-pdf-export",
65
+ "_plugin": "random-hex-seed-for-openclaw-plugin",
66
+ "primary": "random-hex-seed-for-api-access",
67
+ "webhook-notion": {
68
+ "key": "random-hex-seed",
69
+ "scopes": ["/event"]
70
+ }
71
+ },
72
+ "events": {},
73
+ "watcherUrl": "http://localhost:1936",
74
+ "runnerUrl": "http://127.0.0.1:1937"
75
+ }
76
+ ```
28
77
 
29
- ### Config structure
78
+ ### Environment variable substitution
30
79
 
31
- ```typescript
32
- import type { JeevesConfig } from './src/config/schema.js';
80
+ String values in the config support `${VAR_NAME}` substitution from `process.env`:
33
81
 
34
- export default {
35
- port: 1934,
36
- chromePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
37
- auth: { ... },
38
- insiders: { ... },
39
- keys: { ... },
40
- events: { ... },
41
- } satisfies JeevesConfig;
82
+ ```json
83
+ {
84
+ "auth": {
85
+ "google": {
86
+ "clientId": "${GOOGLE_CLIENT_ID}",
87
+ "clientSecret": "${GOOGLE_CLIENT_SECRET}"
88
+ }
89
+ }
90
+ }
42
91
  ```
43
92
 
44
- The `satisfies` keyword gives you type checking without losing literal types — your editor will autocomplete and validate as you type.
93
+ ### Validating your config
94
+
95
+ ```bash
96
+ jeeves-server config validate [--config <path>]
97
+ ```
98
+
99
+ This loads and validates the config against the Zod schema, reporting any errors. Use `config show` to see the fully resolved configuration with all derived keys and scope assignments.
45
100
 
46
101
  ### Platform-specific settings
47
102
 
48
103
  **Windows** — drives are auto-discovered; no `roots` config needed:
49
- ```typescript
50
- chromePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
104
+ ```json
105
+ { "chromePath": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" }
51
106
  ```
52
107
 
53
108
  **Linux** — configure filesystem roots for the file browser:
54
- ```typescript
55
- chromePath: '/usr/bin/chromium-browser',
56
- roots: {
57
- home: '/home',
58
- projects: '/opt/projects',
59
- },
60
- mermaidCliPath: '/opt/mermaid-cli', // optional
61
- plantuml: { // optional
62
- jarPath: '/opt/plantuml/plantuml.jar',
63
- javaPath: '/usr/bin/java', // defaults to 'java' on PATH
64
- servers: [], // private servers; community server always appended
65
- },
109
+ ```json
110
+ {
111
+ "chromePath": "/usr/bin/chromium-browser",
112
+ "roots": {
113
+ "home": "/home",
114
+ "projects": "/opt/projects"
115
+ }
116
+ }
66
117
  ```
67
118
 
68
- On Windows, `roots` is ignored. On Linux, if omitted, it defaults to `{ root: '/' }`.
119
+ On Windows, `roots` is ignored. On Linux, if omitted, it defaults to `{ "root": "/" }`.
69
120
 
70
121
  ### Config is immutable at runtime
71
122
 
@@ -77,16 +128,17 @@ Once the server starts, the config is loaded once and never written to. Mutable
77
128
 
78
129
  Jeeves Server supports two authentication methods, configured via `auth.modes`:
79
130
 
80
- ```typescript
81
- auth: {
82
- modes: ['google', 'keys'], // Active modes, in priority order
83
- // ...
131
+ ```json
132
+ {
133
+ "auth": {
134
+ "modes": ["google", "keys"]
135
+ }
84
136
  }
85
137
  ```
86
138
 
87
139
  You can enable one or both. The order matters — modes are checked in the order listed.
88
140
 
89
- ### Google OAuth (`'google'`)
141
+ ### Google OAuth (`"google"`)
90
142
 
91
143
  **Best for:** Teams where insiders log in via browser.
92
144
 
@@ -104,99 +156,106 @@ Users authenticate with their Google account. The server checks their email agai
104
156
  4. Authorized redirect URI: `https://your-domain.com/auth/google/callback`
105
157
  5. Copy the client ID and client secret into your config
106
158
 
107
- **Session secret generation:**
108
- ```bash
109
- node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
110
- ```
111
-
112
- ### Key-Based Auth (`'keys'`)
159
+ ### Key-Based Auth (`"keys"`)
113
160
 
114
161
  **Best for:** Headless access, bot integrations, simple setups without Google.
115
162
 
116
- Users authenticate by appending `?key=<value>` to any URL. The server derives keys from configured seeds using HMAC-SHA256 and checks the provided key against all known derived keys.
163
+ Users authenticate by appending `?key=<value>` to any URL. The server derives keys from configured seeds using HMAC-SHA256.
117
164
 
118
165
  **Requirements when enabled:**
119
166
  - At least one entry in `keys`
120
167
 
121
- **How keys work:** You configure a **seed** (a random secret string). The server derives the actual insider key from it via HMAC. You never put the derived key in the config — only the seed. To get the derived key for use in URLs:
168
+ **How keys work:** You configure a **seed** (a random secret string). The server derives the actual key from it via HMAC. You never put the derived key in the config — only the seed. To get the derived key:
122
169
 
123
170
  ```bash
124
- # Via the API (requires X-API-Key header with any seed)
125
171
  curl -H "X-API-Key: <seed>" https://your-domain.com/insider-key
126
172
  ```
127
173
 
128
174
  ### Both Modes Together
129
175
 
130
- ```typescript
131
- auth: {
132
- modes: ['keys', 'google'], // Keys checked first
133
- }
134
- ```
135
-
136
- When both are active:
137
- - Browser users can log in with Google for a session-based experience
138
- - Bots and scripts can use `?key=` for stateless access
139
- - Both methods work on every endpoint
140
-
141
- ### Choosing a Mode
176
+ When both are active, browser users log in with Google, and bots/scripts use `?key=` for stateless access. Both methods work on every endpoint.
142
177
 
143
178
  | Scenario | Recommended |
144
179
  |----------|-------------|
145
- | Team of humans accessing via browser | `['google']` |
146
- | Bot/script access only | `['keys']` |
147
- | Humans + bots on the same server | `['google', 'keys']` |
148
- | Quick local setup, no Google credentials | `['keys']` |
180
+ | Team of humans accessing via browser | `["google"]` |
181
+ | Bot/script access only | `["keys"]` |
182
+ | Humans + bots on the same server | `["google", "keys"]` |
183
+ | Quick local setup, no Google credentials | `["keys"]` |
149
184
 
150
185
  ---
151
186
 
152
- ## Insiders
153
-
154
- The `insiders` map defines **who** has full browsing access. Each entry is an email address with optional path scopes:
155
-
156
- ```typescript
157
- insiders: {
158
- // Full access
159
- 'alice@example.com': {},
187
+ ## Named Access Scopes
160
188
 
161
- // Restricted to specific paths (allow-only)
162
- 'contractor@example.com': {
163
- scopes: ['/d/projects/client-x/*'],
164
- },
189
+ Define reusable scope policies at the top level, then reference them by name from insiders, keys, or the outsider policy:
165
190
 
166
- // Broad access with cutouts (allow/deny)
167
- 'team-member@example.com': {
168
- scopes: {
169
- allow: ['/d/*'],
170
- deny: ['/d/secrets/*', '/d/.private/*'],
191
+ ```json
192
+ {
193
+ "scopes": {
194
+ "engineering": {
195
+ "allow": ["/d/repos/*", "/d/docs/*"],
196
+ "deny": ["/d/docs/hr/*"]
171
197
  },
198
+ "readonly-projects": {
199
+ "allow": ["/d/projects/*"]
200
+ }
172
201
  },
173
- },
202
+ "insiders": {
203
+ "dev@example.com": { "scopes": "engineering" },
204
+ "contractor@example.com": {
205
+ "scopes": "readonly-projects",
206
+ "deny": ["/d/projects/secret/*"]
207
+ }
208
+ }
209
+ }
174
210
  ```
175
211
 
176
- With Google auth, insiders log in via OAuth and the server checks their email. With key auth, each insider gets a derived URL key.
212
+ Named scopes are **atomic** composition happens at the point of use. An insider or key referencing a named scope can add extra `allow` and `deny` patterns that merge on top of the named scope's rules.
177
213
 
178
- See the [Insiders, Outsiders & Sharing](sharing.md) guide for the full access model.
214
+ Scopes can also be specified inline (without named references) using the same formats as before:
215
+
216
+ ```json
217
+ { "scopes": ["/d/projects/*"] }
218
+ { "scopes": { "allow": ["/d/*"], "deny": ["/d/secrets/*"] } }
219
+ ```
179
220
 
180
221
  ---
181
222
 
182
- ## Keys
223
+ ## Insiders
183
224
 
184
- The `keys` map defines **named API keys** for machine access:
225
+ The `insiders` map defines **who** has full browsing access:
226
+
227
+ ```json
228
+ {
229
+ "insiders": {
230
+ "alice@example.com": {},
231
+ "contractor@example.com": { "scopes": "restricted" },
232
+ "team@example.com": {
233
+ "scopes": { "allow": ["/d/*"], "deny": ["/d/secrets/*"] }
234
+ }
235
+ }
236
+ }
237
+ ```
185
238
 
186
- ```typescript
187
- keys: {
188
- // Unscoped — full access to all paths
189
- primary: 'a-random-64-char-hex-string',
239
+ See the [Insiders, Outsiders & Sharing](sharing.md) guide for the full access model.
190
240
 
191
- // Scoped — restricted to specific paths
192
- 'webhook-notion': {
193
- key: 'another-random-hex-string',
194
- scopes: ['/event'],
195
- },
241
+ ---
196
242
 
197
- // Reserved: internal server operations (Puppeteer export)
198
- _internal: 'yet-another-random-hex-string',
199
- },
243
+ ## Keys
244
+
245
+ The `keys` map defines **named API keys** for machine and human access:
246
+
247
+ ```json
248
+ {
249
+ "keys": {
250
+ "primary": "a-random-64-char-hex-string",
251
+ "webhook-notion": {
252
+ "key": "another-random-hex-string",
253
+ "scopes": ["/event"]
254
+ },
255
+ "_internal": "seed-for-pdf-export",
256
+ "_plugin": "seed-for-openclaw-plugin"
257
+ }
258
+ }
200
259
  ```
201
260
 
202
261
  **Generate seeds with:**
@@ -204,110 +263,81 @@ keys: {
204
263
  node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
205
264
  ```
206
265
 
207
- ### The `_internal` key
266
+ ### Reserved keys
208
267
 
209
- The `_internal` key is reserved for the server's own use — specifically, Puppeteer uses it to authenticate when rendering PDFs and DOCX files. It **must not** have scopes (enforced by the schema).
268
+ | Key | Purpose | Scopes |
269
+ |-----|---------|--------|
270
+ | `_internal` | Puppeteer uses this to authenticate when rendering PDFs/DOCX. **Required for export.** | Must be unscoped |
271
+ | `_plugin` | OpenClaw plugin authentication. See [OpenClaw Integration](../../openclaw/guides/openclaw-integration.md). | Must be unscoped |
210
272
 
211
- If you don't configure `_internal`, PDF/DOCX export will not work.
212
-
213
- ### Key names
214
-
215
- Key names are used for logging and identification. Choose meaningful names: `primary`, `webhook-notion`, `ci-bot`, etc.
273
+ Both reserved keys are enforced unscoped by the Zod schema.
216
274
 
217
275
  ---
218
276
 
219
277
  ## Event Gateway
220
278
 
221
- The event gateway receives webhooks at `POST /event`, validates them against JSON Schema rules, and dispatches matched events to shell commands via a durable JSONL queue.
279
+ See the [Event Gateway](event-gateway.md) guide for full configuration and usage.
222
280
 
223
- ```typescript
224
- events: {
225
- 'notion-page-update': {
226
- // JSON Schema to match against incoming body
227
- schema: {
228
- type: 'object',
229
- properties: { type: { const: 'page.content_updated' } },
230
- required: ['type'],
231
- },
232
- // Command to execute when matched
233
- cmd: 'node /path/to/handler.js',
234
- // Optional: transform body before passing to command
235
- map: {
236
- pageId: { '$': { method: '$.lib._.get', params: ['$.input', 'data.page_id'] } },
237
- },
238
- // Optional: override default timeout
239
- timeoutMs: 60000,
240
- },
241
- },
242
- ```
281
+ ---
243
282
 
244
- Webhook callers authenticate with a scoped key:
245
- ```bash
246
- curl -X POST "https://your-domain.com/event?key=<webhook-key>" \
247
- -H "Content-Type: application/json" \
248
- -d '{"type": "page.content_updated", "data": {"page_id": "abc123"}}'
249
- ```
283
+ ## Optional Integrations
250
284
 
251
- ---
285
+ ### jeeves-watcher (Semantic Search)
252
286
 
253
- ## Building
287
+ When `watcherUrl` is configured, the server proxies semantic search queries to [jeeves-watcher](https://github.com/karmaniverous/jeeves-watcher) and provides a search UI in the header, with filter facets and scope-aware result filtering.
254
288
 
255
- ```bash
256
- # Full build (server TypeScript + React client)
257
- npm run build # Compiles server → dist/
258
- cd client && npx vite build --outDir ../dist/client && cd .. # Builds React SPA → dist/client/
289
+ ```json
290
+ { "watcherUrl": "http://localhost:1936" }
259
291
  ```
260
292
 
261
- > ⚠️ `npm run build` deletes the entire `dist/` directory (including `dist/client/`). Always rebuild the client after the server.
262
-
263
- ---
293
+ ### jeeves-runner (Process Dashboard)
264
294
 
265
- ## Running
295
+ When `runnerUrl` is configured, the server proxies runner API calls for the process dashboard UI.
266
296
 
267
- ```bash
268
- node dist/server.js
297
+ ```json
298
+ { "runnerUrl": "http://127.0.0.1:1937" }
269
299
  ```
270
300
 
271
- ### As a Windows service
301
+ ### PlantUML
272
302
 
273
- ```bash
274
- nssm install JeevesServer "node" "/path/to/dist/server.js"
275
- nssm start JeevesServer
276
- ```
303
+ Mermaid is bundled as a direct dependency — no configuration needed. PlantUML uses a fallback pipeline:
277
304
 
278
- ### Health check
305
+ 1. **Local Java jar** (downloaded automatically via `postinstall`) — fastest, supports `!include`
306
+ 2. **Configured PlantUML servers** — private instances, tried in order
307
+ 3. **Public community server** (`plantuml.com`) — always appended as last resort
279
308
 
280
- ```
281
- GET /health
309
+ ```json
310
+ {
311
+ "plantuml": {
312
+ "jarPath": "/opt/plantuml/plantuml.jar",
313
+ "javaPath": "/usr/bin/java",
314
+ "servers": ["https://internal.plantuml.example.com/plantuml"]
315
+ }
316
+ }
282
317
  ```
283
318
 
284
- Returns `200 OK` with no authentication required.
319
+ If `plantuml` is omitted entirely, only the community server is used.
285
320
 
286
321
  ---
287
322
 
288
- ## File Layout
289
-
290
- ```
291
- jeeves-server/
292
- ├── jeeves.config.ts # Your config (gitignored)
293
- ├── jeeves.config.template.ts # Config template (committed)
294
- ├── state.json # Runtime state (gitignored, auto-managed)
295
- ├── src/ # Server source (TypeScript)
296
- │ ├── config/
297
- │ │ ├── schema.ts # Zod schema (source of truth)
298
- │ │ ├── index.ts # Config loader (jiti for TS)
299
- │ │ └── types.ts # Runtime types
300
- │ ├── auth/ # Google OAuth + key verification
301
- │ ├── routes/ # Fastify route handlers
302
- │ ├── services/ # Export, markdown, event queue
303
- │ └── server.ts # Entry point
304
- ├── client/ # React SPA source
305
- │ └── src/
306
- │ ├── pages/ # FileBrowser, FileViewer, About
307
- │ ├── components/ # Header, dropdowns, viewers
308
- │ └── lib/ # API client, auth, theme
309
- ├── dist/ # Compiled output (gitignored)
310
- │ ├── server.js # Compiled server
311
- │ └── client/ # Built React SPA
312
- └── guides/ # Documentation
313
- ```
323
+ ## Config Reference
324
+
325
+ | Field | Type | Default | Description |
326
+ |-------|------|---------|-------------|
327
+ | `port` | number | `1934` | Server port |
328
+ | `chromePath` | string | *required* | Path to Chrome/Chromium executable |
329
+ | `auth` | object | *required* | Authentication configuration |
330
+ | `scopes` | object | `{}` | Named scope definitions |
331
+ | `insiders` | object | `{}` | Email → insider entry map |
332
+ | `keys` | object | `{}` | Named key entries |
333
+ | `events` | object | `{}` | Event gateway schemas |
334
+ | `eventTimeoutMs` | number | `30000` | Default event command timeout |
335
+ | `eventLogPurgeMs` | number | `2592000000` | Event log retention (default: 30 days) |
336
+ | `maxZipSizeMb` | number | `100` | Max directory size for ZIP export |
337
+ | `roots` | object | — | Linux filesystem roots (ignored on Windows) |
338
+ | `watcherUrl` | string | — | jeeves-watcher API URL for semantic search |
339
+ | `runnerUrl` | string | — | jeeves-runner API URL for process dashboard |
340
+ | `plantuml` | object | — | PlantUML rendering config |
341
+ | `diagramCachePath` | string | `.diagram-cache` | Cached rendered diagram directory |
342
+ | `outsiderPolicy` | scopes | — | Global outsider sharing constraints |
343
+ | `mermaidCliPath` | string | — | **Deprecated.** Mermaid is now bundled. |
package/guides/sharing.md CHANGED
@@ -10,11 +10,11 @@ An insider is an **authenticated user** on the server. Depending on their config
10
10
 
11
11
  Anyone listed in the `insiders` map in your config:
12
12
 
13
- ```typescript
14
- insiders: {
15
- 'alice@example.com': {},
16
- 'bob@example.com': { scopes: ['/d/projects/*'] },
17
- },
13
+ ```json
14
+ {
15
+ "alice@example.com": {},
16
+ "bob@example.com": { "scopes": ["/d/projects/*"] }
17
+ }
18
18
  ```
19
19
 
20
20
  ### How insiders authenticate
@@ -155,8 +155,8 @@ Use rotation when you need to revoke all shared links at once (e.g., a contracto
155
155
 
156
156
  In addition to insider-generated keys, the server supports **named machine keys** for programmatic access:
157
157
 
158
- ```typescript
159
- keys: {
158
+ ```json
159
+ "keys": {
160
160
  primary: 'random-seed-string',
161
161
  'webhook-notion': { key: 'another-seed', scopes: ['/event'] },
162
162
  _internal: 'internal-seed',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/jeeves-server",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "Secure file browser, markdown viewer, and webhook gateway with PDF/DOCX export and expiring share links",
5
5
  "keywords": [
6
6
  "fastify",