slimwiki 0.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.
package/README.md ADDED
@@ -0,0 +1,434 @@
1
+ # slimwiki
2
+
3
+ Agent-friendly CLI for [SlimWiki](https://slimwiki.com). Lets a human or an
4
+ AI agent sign in to a SlimWiki account, browse pages, and create new
5
+ ones from Tiptap JSON.
6
+
7
+ The two audiences this README addresses:
8
+
9
+ - **End users + their AI agents** — install the CLI, authenticate it
10
+ once, then either run commands by hand or let an agent drive it. Jump
11
+ to [Install & use](#install--use).
12
+ - **Contributors** — clone the monorepo, run a local stack, edit CLI
13
+ source. Jump to [Development](#development).
14
+
15
+ ---
16
+
17
+ ## Install & use
18
+
19
+ ### 1. Install the CLI
20
+
21
+ Once published:
22
+
23
+ ```sh
24
+ npm i -g slimwiki # or pnpm add -g, bun i -g
25
+ slimwiki --version
26
+ ```
27
+
28
+ Until then, install from source — see [Local install for users](#local-install-for-users).
29
+
30
+ ### 2. Authenticate
31
+
32
+ ```sh
33
+ slimwiki auth login
34
+ ```
35
+
36
+ Opens your browser, you click *Allow*, the CLI saves a 14-day session
37
+ token to:
38
+
39
+ - macOS: `~/Library/Application Support/slimwiki/credentials.json`
40
+ - Linux: `~/.config/slimwiki/credentials.json`
41
+ - Windows: `%APPDATA%\slimwiki\credentials.json`
42
+
43
+ Sanity check:
44
+
45
+ ```sh
46
+ slimwiki auth status
47
+ slimwiki account list
48
+ ```
49
+
50
+ For headless agents (CI, remote machines) skip the browser flow:
51
+
52
+ ```sh
53
+ export SLIMWIKI_TOKEN=<bearer-token>
54
+ export SLIMWIKI_API_BASE=https://slimwiki.com # optional, defaults to prod
55
+ slimwiki auth status
56
+ ```
57
+
58
+ `slimwiki auth logout` revokes the session and wipes the local file.
59
+
60
+ ### 3. Wire up an AI agent
61
+
62
+ The CLI ships two artefacts so agents can learn its contract without
63
+ you re-explaining each session:
64
+
65
+ - **`AGENT.md`** — agent-agnostic contract: command surface, JSON
66
+ output shape, Tiptap-doc schema, failure modes, recipes.
67
+ - **`SKILL.md`** — Claude Code skill stub with frontmatter.
68
+ - **`slimwiki agent-help`** — prints `AGENT.md` to stdout for sandboxed
69
+ agents that can't read files.
70
+
71
+ Pick the row that matches your tool:
72
+
73
+ | Agent | One-time setup |
74
+ |---|---|
75
+ | **Claude Code** | `mkdir -p ~/.claude/skills/slimwiki && ln -sf $(npm root -g)/slimwiki/{SKILL,AGENT}.md ~/.claude/skills/slimwiki/`. Then invoke `/slimwiki` in any session. |
76
+ | **Cursor / Aider / Continue** | Add to project rules (e.g. `.cursorrules`): *"When the user asks to read or write SlimWiki content, use the `slimwiki` CLI. Run `slimwiki agent-help` first to load the contract."* |
77
+ | **Claude API / custom agent** | Pipe `slimwiki agent-help` into the system prompt, or expose a tool that runs CLI subcommands. |
78
+
79
+ Verify it works: open an agent session and ask *"create a SlimWiki page
80
+ titled 'Hello' with one paragraph"*. The agent should run
81
+ `slimwiki page create --title "Hello" --stdin <<...`.
82
+
83
+ ---
84
+
85
+ ## Output
86
+
87
+ By default every command prints **one JSON value** on stdout — agents
88
+ parse this directly. Errors print `{"error":"..."}` on stderr and exit
89
+ non-zero.
90
+
91
+ Pass `--human` for a tabular / formatted view when running the CLI
92
+ interactively.
93
+
94
+ ```sh
95
+ slimwiki account list # JSON
96
+ slimwiki account list --human # table
97
+ ```
98
+
99
+ The CLI deliberately strips internal UUIDs from output — agents
100
+ reference accounts and wikis by `slug`, and pages by `slug` or
101
+ `pageHash`. UUIDs stay inside the CLI for resolving API paths.
102
+
103
+ ## Commands
104
+
105
+ ```
106
+ slimwiki auth login [--api-base <url>]
107
+ slimwiki auth logout
108
+ slimwiki auth status
109
+
110
+ slimwiki account list # → [{slug, name}]
111
+ slimwiki wiki list [--account <slug>] # → [{slug, name}]
112
+
113
+ slimwiki page list [--account <slug>] [--wiki <slug>]
114
+ [--search <query>] [--archived]
115
+ [--limit N] [--offset M]
116
+ # → { rows: [{title, slug, pageHash, updatedAt}], total }
117
+
118
+ slimwiki page get <slug-or-pageHash-or-id>
119
+ [--account <slug>] [--wiki <slug>]
120
+ # → full page row including Tiptap doc body
121
+
122
+ slimwiki page create --title <title>
123
+ (--file <path> | --content <jsonString> | --stdin)
124
+ [--parent <slug-or-pageHash-or-id>]
125
+ [--account <slug>] [--wiki <slug>]
126
+
127
+ slimwiki agent-help # prints AGENT.md to stdout
128
+ ```
129
+
130
+ Global flags (work on every command):
131
+
132
+ - `--human` — opt into formatted output (default: JSON).
133
+ - `--api-base <url>` — override the API base for this invocation. Useful
134
+ for local dev without disturbing cached credentials.
135
+
136
+ Defaults for `--account` / `--wiki`, in order:
137
+
138
+ 1. The flag value.
139
+ 2. `SLIMWIKI_ACCOUNT_SLUG` / `SLIMWIKI_WIKI_SLUG` env vars.
140
+ 3. The slugs cached during `auth login`.
141
+ 4. The user's primary account + that account's default wiki (live
142
+ `/users/me` lookup).
143
+
144
+ If the user belongs to exactly one account, no flags are required.
145
+
146
+ ## Tiptap document contract
147
+
148
+ `page create` accepts a single object of shape
149
+ `{ "type": "doc", "content": [...] }`. The CLI validates locally before
150
+ sending — invalid payloads exit non-zero with the path of the failing
151
+ node so an agent can fix and retry without a server round-trip.
152
+
153
+ Recognised node types — anything else is rejected:
154
+
155
+ | Type | Required attrs | Notes |
156
+ |------------------|---------------------------------|--------------------------------------------|
157
+ | `paragraph` | — | |
158
+ | `heading` | `level: 1 \| 2 \| 3` | No h4+ |
159
+ | `bulletList` | — | Wraps `listItem` nodes |
160
+ | `orderedList` | `start?: number` | Wraps `listItem` nodes |
161
+ | `listItem` | — | |
162
+ | `taskList` | — | Wraps `taskItem` nodes |
163
+ | `taskItem` | `checked?: boolean` | |
164
+ | `blockquote` | — | |
165
+ | `codeBlock` | `language?: string` | Children are `text` only |
166
+ | `horizontalRule` | — | Self-closing |
167
+ | `image` | `src: string`, `alt?`, `title?` | URL must be reachable from the frontend |
168
+ | `video` | `src: string` | |
169
+ | `attachment` | `src?`, `name?` | |
170
+ | `math` | `latex: string` | **Inline** — must live inside a paragraph |
171
+ | `pageMention` | `pageId: string` | |
172
+ | `table` | — | Wraps `tableRow` |
173
+ | `tableRow` | — | Wraps `tableCell` / `tableHeader` |
174
+ | `tableCell` | — | Children = block nodes (usually paragraph) |
175
+ | `tableHeader` | — | Same as tableCell, used in the first row |
176
+
177
+ Inline / leaf:
178
+
179
+ - `text` — `text: string`, optional `marks`.
180
+ - `hardBreak` — line break inside a paragraph.
181
+
182
+ Marks (apply via `text.marks: [{ type, attrs? }]`):
183
+
184
+ - `bold`, `italic`, `underline`, `strike`, `code`, `highlight`
185
+ - `link` (`attrs.href`)
186
+ - `textStyle` (optional `attrs.color`)
187
+
188
+ See `cli/AGENT.md` for the full canonical contract — it's the source of
189
+ truth that agents read.
190
+
191
+ ## Recipes
192
+
193
+ ### Create a page from Tiptap JSON
194
+
195
+ ```sh
196
+ slimwiki page create --title "Apple varieties" --stdin <<'JSON'
197
+ {
198
+ "type": "doc",
199
+ "content": [
200
+ { "type": "heading", "attrs": { "level": 1 },
201
+ "content": [{ "type": "text", "text": "Apple varieties" }] },
202
+ { "type": "paragraph",
203
+ "content": [{ "type": "text", "text": "A few common cultivars." }] },
204
+ { "type": "table", "content": [
205
+ { "type": "tableRow", "content": [
206
+ { "type": "tableHeader", "content": [{ "type": "paragraph",
207
+ "content": [{ "type": "text", "text": "Name" }] }] },
208
+ { "type": "tableHeader", "content": [{ "type": "paragraph",
209
+ "content": [{ "type": "text", "text": "Color" }] }] }
210
+ ]},
211
+ { "type": "tableRow", "content": [
212
+ { "type": "tableCell", "content": [{ "type": "paragraph",
213
+ "content": [{ "type": "text", "text": "Granny Smith" }] }] },
214
+ { "type": "tableCell", "content": [{ "type": "paragraph",
215
+ "content": [{ "type": "text", "text": "Green" }] }] }
216
+ ]}
217
+ ]}
218
+ ]
219
+ }
220
+ JSON
221
+ ```
222
+
223
+ ### Find / count pages
224
+
225
+ ```sh
226
+ slimwiki page list --search SpaceX
227
+ slimwiki page list --search rockets | jq '.total'
228
+ slimwiki page list --search "HR benefits" --limit 3
229
+ ```
230
+
231
+ ### Read a page
232
+
233
+ ```sh
234
+ slimwiki page get quarterly-roadmap-x # by slug
235
+ slimwiki page get 1jydyfwevwro # by pageHash (rename-stable)
236
+ ```
237
+
238
+ ### Create a child page
239
+
240
+ ```sh
241
+ slimwiki page create --title "Q3 Plan" --parent quarterly-roadmap --file q3.json
242
+ ```
243
+
244
+ ### List archived pages
245
+
246
+ ```sh
247
+ slimwiki page list --archived --limit 100
248
+ ```
249
+
250
+ ## Exit codes
251
+
252
+ - `0` — success.
253
+ - `1` — any error (auth, validation, network, API). Stderr carries
254
+ `{"error":"..."}` so agents can branch on it.
255
+
256
+ ---
257
+
258
+ ## Development
259
+
260
+ The CLI is a workspace package inside the SlimWiki monorepo at
261
+ `/cli/`, alongside `/api/` and `/frontend/`.
262
+
263
+ ### Repo layout
264
+
265
+ ```
266
+ cli/
267
+ ├── package.json # bin: slimwiki, deps: commander, env-paths, open, zod
268
+ ├── tsconfig.json # strict TS, NodeNext, ESM
269
+ ├── tsup.config.ts # bundles src/index.ts → dist/index.js (single file)
270
+ ├── AGENT.md # contract for AI agents
271
+ ├── SKILL.md # Claude Code skill stub
272
+ ├── README.md # this file
273
+ └── src/
274
+ ├── index.ts # commander root, registers subcommands
275
+ ├── commands/
276
+ │ ├── auth.ts # login (browser device flow), logout, status
277
+ │ ├── account.ts # account list
278
+ │ ├── wiki.ts # wiki list
279
+ │ ├── page.ts # page list, get, create
280
+ │ └── agent-help.ts # prints AGENT.md
281
+ ├── lib/
282
+ │ ├── api-client.ts # fetch wrapper: bearer auth, JSON parsing, error mapping
283
+ │ ├── config.ts # ~/.config/slimwiki/credentials.json + SLIMWIKI_TOKEN
284
+ │ ├── output.ts # JSON-default vs --human, stderr error path
285
+ │ ├── selectors.ts # resolve --account/--wiki/--parent flags into IDs
286
+ │ └── tiptap-schema.ts # Zod schema for the page-create body
287
+ └── types.ts # public response shapes (no UUIDs)
288
+ ```
289
+
290
+ The CLI talks to the SlimWiki API over HTTPS only. It does **not**
291
+ import anything from `api/` — bringing Drizzle, Better Auth server,
292
+ etc. into a CLI binary would balloon its size and tie its release
293
+ cadence to the API's.
294
+
295
+ ### Local install for users
296
+
297
+ End users who want to try the CLI without waiting for an npm release:
298
+
299
+ ```sh
300
+ git clone git@github.com:slimwiki/slimwiki.git
301
+ cd slimwiki
302
+ pnpm install
303
+ pnpm --filter slimwiki build
304
+
305
+ # Put `slimwiki` on PATH. Either:
306
+ cd cli && npm link # uses your existing Node bin dir
307
+ # or:
308
+ pnpm setup && pnpm link --global # one-time pnpm bin setup
309
+
310
+ slimwiki --version
311
+ ```
312
+
313
+ Note that `pnpm link --global` requires `pnpm setup` to have been run
314
+ once on the machine (it adds `PNPM_HOME` to your shell rc).
315
+
316
+ ### Local dev loop
317
+
318
+ With the API and frontend running locally:
319
+
320
+ ```sh
321
+ # Terminal 1 — API
322
+ cd api && pnpm dev # listens on :3001
323
+
324
+ # Terminal 2 — Frontend
325
+ cd frontend && pnpm dev # listens on :3000
326
+
327
+ # Terminal 3 — CLI
328
+ cd cli
329
+ pnpm dev # tsup --watch, rebuilds dist/ on save
330
+
331
+ # In another terminal, run commands against the local stack:
332
+ node dist/index.js auth login --api-base http://localhost:3001
333
+ node dist/index.js account list --api-base http://localhost:3001
334
+ ```
335
+
336
+ `--api-base` is a global flag, so any command can be redirected at
337
+ localhost without re-running `auth login`. The flag is more convenient
338
+ during development than baking the value into credentials.
339
+
340
+ `pnpm dev` runs tsup in watch mode — every save rebuilds
341
+ `dist/index.js`. If you've run `npm link` once, your global `slimwiki`
342
+ points at the same `dist/`, so subsequent invocations pick up changes
343
+ automatically.
344
+
345
+ ### Typecheck / build / clean
346
+
347
+ ```sh
348
+ pnpm --filter slimwiki typecheck # tsc --noEmit
349
+ pnpm --filter slimwiki build # tsup → dist/index.js
350
+ ```
351
+
352
+ The bundle is a single ESM file with a `#!/usr/bin/env node` shebang.
353
+ There's no separate type-declaration output — the CLI isn't intended to
354
+ be imported as a library.
355
+
356
+ ### How auth works under the hood
357
+
358
+ `slimwiki auth login` runs a **device-code OAuth flow** modelled on
359
+ `gh auth login` and `vercel login`:
360
+
361
+ 1. CLI POSTs `/api/v1/auth/cli/init`. API generates a 32-byte
362
+ `deviceCode` and an 8-character `userCode` (e.g. `WXYZ-1234`),
363
+ inserts them into `cli_auth_codes` with a 10-minute TTL.
364
+ 2. CLI prints the `userCode` to stderr and `open()`s the browser at
365
+ `${client.url}/cli-auth?code=<userCode>`.
366
+ 3. Frontend's `/cli-auth` page (`frontend/src/app/[locale]/cli-auth/`)
367
+ gates on session, displays the code, and POSTs `/api/v1/auth/cli/approve`
368
+ when the user clicks Allow.
369
+ 4. API mints a fresh `ba_session` row tied to the user, attaches its
370
+ `id` to the device-code row.
371
+ 5. CLI polls `/api/v1/auth/cli/poll` every ~2s. While unapproved →
372
+ `{ status: "pending" }`. Once approved → `{ status: "approved",
373
+ token, expiresAt, user, account, wiki }`. CLI persists everything
374
+ to `credentials.json`.
375
+
376
+ The session token is a regular Better Auth `ba_session.token` — the
377
+ CLI sends it as `Authorization: Bearer <token>`, and the API's
378
+ existing `bearer()` plugin handles every subsequent request without
379
+ CLI-specific code paths.
380
+
381
+ Server endpoints live in `api/src/routes/v1/auth.ts` and
382
+ `api/src/services/CliAuthService.ts`; the table schema is in
383
+ `api/src/db/schema/cliAuthCodes.ts` (migration 0011).
384
+
385
+ ### Adding a new command
386
+
387
+ 1. Create `src/commands/<name>.ts`. Export a single
388
+ `register<Name>Commands(program: Command)` function that attaches
389
+ subcommands to the passed-in commander program.
390
+ 2. Reuse `requireAuth({ apiBaseOverride: apiBaseOverride(cmd) })` for
391
+ anything that needs the bearer token.
392
+ 3. Use `writeResult(cmd, value, humanFormatter?)` for the success path
393
+ and `exitWithError(err)` for the failure path. Don't `console.log`
394
+ directly — those helpers handle JSON-vs-human output and exit codes.
395
+ 4. Strip API responses to a narrow public shape before emitting them.
396
+ Defining a new type in `src/types.ts` keeps that contract obvious.
397
+ 5. Register the new file in `src/index.ts`.
398
+ 6. Document the command in this README + `AGENT.md`. The two should
399
+ stay in sync — `AGENT.md` is what agents read first.
400
+
401
+ ### Adding a Tiptap node type
402
+
403
+ The Zod schema in `src/lib/tiptap-schema.ts` is the contract documented
404
+ in `AGENT.md`. To add a new node:
405
+
406
+ 1. Add a discriminated-union branch to the `Node` schema. Use
407
+ `.passthrough()` so unknown attrs survive the round-trip.
408
+ 2. Update the table in `AGENT.md` (and this README) — node name and
409
+ required attrs.
410
+ 3. Verify the same node name and attrs are registered on the frontend
411
+ in `frontend/src/extensions/index.ts` and in the API's hocuspocus
412
+ shadow extensions in
413
+ `api/src/lib/collaboration/extensions/extensions.ts`. All three
414
+ must agree, or pages saved through the CLI won't render in the
415
+ editor.
416
+
417
+ A real-world example of why this matters: the `math` node is named
418
+ `math` (not `mathematics`) with attr `latex` (not `formula`). Getting
419
+ that wrong meant the frontend rejected docs the CLI happily accepted.
420
+
421
+ ### Releasing
422
+
423
+ (Pre-launch — workflow TBD when the package goes public on npm.)
424
+
425
+ Rough plan:
426
+
427
+ 1. Bump `cli/package.json` version.
428
+ 2. `pnpm --filter slimwiki build`.
429
+ 3. `cd cli && npm publish --access public`.
430
+ 4. Tag the repo: `git tag cli-vX.Y.Z && git push --tags`.
431
+
432
+ `AGENT.md` and `SKILL.md` are listed in `package.json#files` so they
433
+ ship with the npm package; `slimwiki agent-help` reads `AGENT.md` from
434
+ the package's installed location at runtime.
package/SKILL.md ADDED
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: slimwiki
3
+ description: Operate the SlimWiki CLI to create, search, and read wiki pages. Use whenever the user asks to add, find, summarise, or update content in their SlimWiki workspace.
4
+ ---
5
+
6
+ # slimwiki
7
+
8
+ You have access to the `slimwiki` CLI. It is the only sanctioned way to
9
+ read or write SlimWiki content — do not call the SlimWiki API directly.
10
+
11
+ **Always read [AGENT.md](./AGENT.md) before issuing your first command in
12
+ a session.** It contains the full command surface, the JSON output
13
+ contract, the Tiptap document schema, and the failure modes you will
14
+ hit. Do not guess node types or flag names.
15
+
16
+ If `AGENT.md` is not on disk (sandboxed environments), run
17
+ `slimwiki agent-help` to print the same content to stdout.