@openparachute/vault 0.4.0 → 0.4.4-rc.11

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 CHANGED
@@ -139,7 +139,7 @@ Password and 2FA secrets live in `~/.parachute/vault/config.yaml` at mode 0600 (
139
139
 
140
140
  Where `{name}` is `default` on a fresh install, or whatever vault you pointed `vault init` at. **First MCP call after `vault init` requires no browser handoff — Claude Code uses the baked-in token and the vault's tools show up in your next session.** This is intentional: for an owner connecting their own machine's vault to their own Claude Code, the token is already there and OAuth would add friction.
141
141
 
142
- To re-point Claude Code at a different vault, change `default_vault` in `~/.parachute/vault/config.yaml` and re-run `parachute-vault init` — which re-mints an API token and re-writes the `~/.claude.json` entry end-to-end. To rotate the token only, edit `~/.claude.json` and replace the `Authorization` header value with a fresh token from `parachute-vault tokens create`. (Running `parachute-vault mcp-install` on its own overwrites the MCP entry *without* an `Authorization` header and is intended for the rare case where you want to drop the token and connect via OAuth instead.)
142
+ To re-point Claude Code at a different vault, change `default_vault` in `~/.parachute/vault/config.yaml` and re-run `parachute-vault init` — which re-mints an API token and re-writes the `~/.claude.json` entry end-to-end. To rotate the token only, run `parachute-vault mcp-install` (defaults to `--mint`, which mints a fresh scope-narrow hub JWT via `~/.parachute/operator.token` and writes it into `~/.claude.json` with an `Authorization: Bearer …` header). See the [cookbook](#install-vault-mcp-into-a-client-config) section below for the full flag surface token paste, scope narrowing, project-level install, multi-vault.
143
143
 
144
144
  ### Claude Desktop (OAuth)
145
145
 
@@ -190,7 +190,13 @@ parachute-vault uninstall --yes --wipe # scripted destructive wipe (prints a
190
190
  parachute-vault create work # create a new vault
191
191
  parachute-vault list # list all vaults (alias: `ls`)
192
192
  parachute-vault remove work --yes # delete a vault (alias: `rm`)
193
- parachute-vault mcp-install # (re)write the ~/.claude.json MCP entry for the default vault
193
+ parachute-vault mcp-install # (re)write the MCP client entry; defaults to --mint (hub-issued JWT) at local scope
194
+ parachute-vault mcp-install --token <t> # paste an existing bearer instead of minting
195
+ parachute-vault mcp-install --legacy-pat # mint a vault-DB pvt_* (self-hosted-without-hub)
196
+ parachute-vault mcp-install --install-scope user # write ~/.claude.json top-level (every project)
197
+ parachute-vault mcp-install --install-scope local # write ~/.claude.json projects[<cwd>] (this directory only — default)
198
+ parachute-vault mcp-install --install-scope project # write ./.mcp.json (checked into the repo)
199
+ parachute-vault mcp-install --vault work # target the "work" vault (keyed as parachute-vault-work)
194
200
 
195
201
  # OAuth — owner password + 2FA
196
202
  parachute-vault set-password # set/change the owner password (OAuth consent page)
@@ -432,6 +438,189 @@ GET /vaults/list public: vault
432
438
  GET /health health check
433
439
  ```
434
440
 
441
+ ## Common queries / cookbook
442
+
443
+ Patterns that fall out of vault's read and write surfaces. All examples assume your vault is reachable at `http://localhost:1940` and your token is in `$VAULT_TOKEN`. MCP-side examples are how the same operation looks via the tool layer (Claude Code, Claude Desktop, Daily). Each recipe answers a question consumers have asked while building against vault — see [vault#285](https://github.com/ParachuteComputer/parachute-vault/issues/285) for the field-input thread these distilled from.
444
+
445
+ ### Fetch every note under a path subtree
446
+
447
+ For an SSG or wiki rendering a section of the vault. `path_prefix` matches against the normalized path string — no `.md`, no trailing slash.
448
+
449
+ ```bash
450
+ curl -H "Authorization: Bearer $VAULT_TOKEN" \
451
+ "http://localhost:1940/vault/default/api/notes?path_prefix=Boulder%20Civics/City%20Council/&include_content=true"
452
+ ```
453
+
454
+ ```jsonc
455
+ // MCP
456
+ { "name": "query-notes", "arguments": { "path_prefix": "Boulder Civics/City Council/", "include_content": true } }
457
+ ```
458
+
459
+ ### Sort by a metadata field
460
+
461
+ Sort by something other than `created_at`. The field must be declared `indexed: true` in some tag schema first — that's what materializes the backing generated column + B-tree index. The "tag authorizes the index" pattern lives in [`core/src/indexed-fields.ts`](./core/src/indexed-fields.ts).
462
+
463
+ ```bash
464
+ curl -H "Authorization: Bearer $VAULT_TOKEN" \
465
+ "http://localhost:1940/vault/default/api/notes?order_by=meeting_date&sort=desc&include_content=true"
466
+ ```
467
+
468
+ Declare the index via `update-tag`:
469
+
470
+ ```jsonc
471
+ { "name": "update-tag", "arguments": {
472
+ "tag": "meeting",
473
+ "fields": { "meeting_date": { "type": "string", "indexed": true } }
474
+ } }
475
+ ```
476
+
477
+ ### Return previews instead of full bodies
478
+
479
+ Listing pages don't need the whole note. By default `GET /notes` returns the lean shape — 120-char whitespace-collapsed `preview`, `byteSize`, and the usual metadata — with no `content`. Single-note `GET /notes/:id` still returns full content by default; pass `include_content=false` to drop it.
480
+
481
+ ```bash
482
+ curl -H "Authorization: Bearer $VAULT_TOKEN" \
483
+ "http://localhost:1940/vault/default/api/notes?path_prefix=Posts/"
484
+ # → [{ id, path, byteSize, preview, tags, metadata, ... }, ...]
485
+ ```
486
+
487
+ Caller-tunable preview length is a future enhancement — file an issue if 120 chars isn't enough.
488
+
489
+ ### Incremental rebuilds: "what changed since X"
490
+
491
+ The SSG / sync pattern. Two equivalent forms — bracket-style is canonical going forward; the flat form is the same shape that ships through the REST/MCP date filter today.
492
+
493
+ ```bash
494
+ # Bracket-style (canonical)
495
+ curl -H "Authorization: Bearer $VAULT_TOKEN" \
496
+ "http://localhost:1940/vault/default/api/notes?meta[updated_at][gte]=2026-04-01T00:00:00Z"
497
+
498
+ # Flat form (DEPRECATED in 0.4.3; planned removal 0.6.0 per vault#288)
499
+ curl -H "Authorization: Bearer $VAULT_TOKEN" \
500
+ "http://localhost:1940/vault/default/api/notes?date_field=updated_at&date_from=2026-04-01T00:00:00Z"
501
+ ```
502
+
503
+ ```jsonc
504
+ // MCP — `date_filter` accepts created_at, updated_at, or any indexed metadata field.
505
+ { "name": "query-notes", "arguments": { "date_filter": { "field": "updated_at", "from": "2026-04-01T00:00:00Z" } } }
506
+ ```
507
+
508
+ Only `gte` and `lt` are accepted on `created_at` / `updated_at` (other operators reject with `INVALID_QUERY` — these bracket forms route to the real-column `dateFilter`, not the full metadata operator set, so the half-open `[from, to)` shape is the only expressible range). The full operator set in the next recipe applies to *metadata* fields only.
509
+
510
+ ### Filter by metadata values (bracket style)
511
+
512
+ Equality on any metadata field works with no setup:
513
+
514
+ ```bash
515
+ curl -H "Authorization: Bearer $VAULT_TOKEN" \
516
+ "http://localhost:1940/vault/default/api/notes?meta[meeting-type]=study-session&include_content=true"
517
+ ```
518
+
519
+ Range, `in`, `not_in`, and `exists` operators require the field to be declared `indexed: true` (same gate as `order_by`):
520
+
521
+ ```bash
522
+ # Range — both bounds AND together on one field
523
+ "…/notes?meta[date][gte]=2026-01-01&meta[date][lt]=2026-04-01"
524
+
525
+ # Membership — `[]` array form OR comma-separated; same result.
526
+ "…/notes?meta[status][in][]=active&meta[status][in][]=exploring"
527
+ "…/notes?meta[status][in]=active,exploring"
528
+
529
+ # Presence check
530
+ "…/notes?meta[deadline][exists]=true"
531
+ ```
532
+
533
+ Supported operators: `eq` (default when no operator given), `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `not_in`, `exists`. Multiple `meta[…]` filters AND together. Bracket-style and shorthand on the same field in one request reject loudly — pick one form.
534
+
535
+ ### Edit one line in a large note (without re-sending the whole body)
536
+
537
+ `content_edit` does surgical find-and-replace. Payload is the changed text, not the whole note. `old_text` must occur exactly once; multiple matches or zero matches reject with a "re-read and retry" error.
538
+
539
+ ```jsonc
540
+ // MCP
541
+ { "name": "update-note", "arguments": {
542
+ "id": "notes/decisions",
543
+ "content_edit": {
544
+ "old_text": "Status: pending",
545
+ "new_text": "Status: shipped (2026-05-10)"
546
+ },
547
+ "if_updated_at": "2026-05-10T12:34:56.789Z"
548
+ } }
549
+ ```
550
+
551
+ ```bash
552
+ # REST equivalent
553
+ curl -X PATCH -H "Authorization: Bearer $VAULT_TOKEN" -H "Content-Type: application/json" \
554
+ -d '{"content_edit":{"old_text":"Status: pending","new_text":"Status: shipped (2026-05-10)"},"if_updated_at":"…"}' \
555
+ "http://localhost:1940/vault/default/api/notes/notes/decisions"
556
+ ```
557
+
558
+ Set `include_content: false` on the request to cut the response cost too — you get back the lean `NoteIndex` shape instead of the full updated note.
559
+
560
+ ### Append to a note (no concurrency ceremony)
561
+
562
+ Atomic at the SQL layer: two concurrent appends both land in some order, never clobber. Append-only and prepend-only updates are exempt from the `if_updated_at` precondition that other mutations require — the SQL-atomic concatenation can't lose data on a stale read, so the precondition would be ceremony for no benefit. Underlying mechanic in [`core/src/notes.ts:295-322`](./core/src/notes.ts).
563
+
564
+ ```jsonc
565
+ { "name": "update-note", "arguments": { "id": "notes/journal/2026-05-10", "append": "\n\nEvening note: …" } }
566
+ ```
567
+
568
+ `prepend` is frontmatter-aware: if the note opens with YAML frontmatter, the prepended text is automatically injected *after* the closing `---` fence so parsers that expect frontmatter at byte 0 still find it. Detection is done in the same SQL UPDATE expression, so atomicity is preserved.
569
+
570
+ ### CI / public access via Tailscale Funnel
571
+
572
+ The shortest path to a public HTTPS URL for a vault you control — useful for SSG rebuilds running on GitHub Actions, Vercel, or any runner that isn't on your tailnet. See [Remote access via Tailscale Funnel](#remote-access-via-tailscale-funnel) below for the full setup.
573
+
574
+ ### Install vault MCP into a client config
575
+
576
+ Bare `parachute-vault mcp-install` from a terminal **walks you through a short contextual conversation** — picks defaults informed by your environment (how many vaults you have, whether the hub is reachable, whether you're in a project directory, whether vault is already installed somewhere), shows the JSON shape it will write before doing anything, and asks before each non-obvious choice. The patterns below are the non-interactive shapes — pass any flag (`--mint`, `--token`, `--scope`, `--install-scope`, `--vault`, `--legacy-pat`) and the walkthrough is skipped.
577
+
578
+ #### Install scopes
579
+
580
+ `--install-scope` accepts three values, matching Claude Code's own `claude mcp add --scope`:
581
+
582
+ | Scope | Where the entry lives | Visibility |
583
+ |---|---|---|
584
+ | `local` *(default)* | `~/.claude.json` under `projects[<absolute-cwd>].mcpServers` | private to your machine, scoped to this directory |
585
+ | `user` | `~/.claude.json` top-level `mcpServers` | every project, every directory on this machine |
586
+ | `project` | `<cwd>/.mcp.json` | checked into the repo, shared with anyone who clones it |
587
+
588
+ The default is **`local`** — Claude Code only loads the entry when launched from the directory you ran the install from. Pick `user` for a global install (every project sees the vault); pick `project` to commit the entry to the repo so collaborators get it on `git pull`.
589
+
590
+ #### Cookbook
591
+
592
+ ```bash
593
+ # 1. Default — mint a scope-narrow hub JWT (vault:<vault>:read) via your
594
+ # operator token; write it into ~/.claude.json under projects[<cwd>]
595
+ # (local scope, this directory only). Requires:
596
+ # - ~/.parachute/operator.token (run `parachute auth rotate-operator` if missing)
597
+ # - PARACHUTE_HUB_ORIGIN set OR an active `parachute expose` session
598
+ parachute-vault mcp-install --mint
599
+
600
+ # 2. Global install — write the entry at top-level ~/.claude.json so
601
+ # Claude Code loads it from every project, every directory.
602
+ parachute-vault mcp-install --install-scope user
603
+
604
+ # 3. Project-level install — write ./.mcp.json (committed to the repo,
605
+ # shared with the team). Pair with --scope vault:write when the
606
+ # project actually mutates the vault.
607
+ parachute-vault mcp-install --install-scope project --scope vault:write
608
+
609
+ # 4. Paste an existing token — useful when you already have a pvt_* in hand
610
+ # or want to re-use a long-lived bearer from another machine. Skips the
611
+ # mint step entirely.
612
+ parachute-vault mcp-install --token pvt_abc123...
613
+
614
+ # 5. Self-hosted-without-hub — mint a vault-DB pvt_* token (the legacy
615
+ # path; preserved so deployments without a hub keep working). Prints a
616
+ # deprecation notice.
617
+ parachute-vault mcp-install --legacy-pat
618
+ ```
619
+
620
+ **Multi-vault.** `--vault <name>` targets a specific vault and writes the entry under `parachute-vault-<name>` so multiple vaults coexist. Without `--vault`, the singular `parachute-vault` slot is used and one install clobbers another — that's intentional for the common single-vault case.
621
+
622
+ **Doctor.** `parachute-vault doctor` checks `~/.claude.json` (both top-level and `projects[<cwd>]`) and `./.mcp.json`, and reports which one holds the entry, plus port-match and reachability of the MCP URL.
623
+
435
624
  ## Data model
436
625
 
437
626
  ```