@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 +191 -2
- package/core/src/core.test.ts +1295 -526
- package/core/src/mcp.ts +129 -428
- package/core/src/notes.ts +405 -32
- package/core/src/obsidian.ts +55 -177
- package/core/src/portable-md.test.ts +1001 -0
- package/core/src/portable-md.ts +1409 -0
- package/core/src/schema-defaults.ts +233 -171
- package/core/src/schema.ts +104 -32
- package/core/src/store.ts +103 -78
- package/core/src/tag-hierarchy.ts +36 -2
- package/core/src/types.ts +52 -42
- package/core/src/vault-projection.ts +309 -0
- package/package.json +2 -2
- package/src/auth-hub-jwt.test.ts +142 -13
- package/src/auth.ts +29 -0
- package/src/cli.ts +699 -141
- package/src/doctor.test.ts +7 -6
- package/src/hub-jwt.test.ts +16 -5
- package/src/hub-jwt.ts +9 -0
- package/src/mcp-http.ts +4 -2
- package/src/mcp-install-interactive.test.ts +883 -0
- package/src/mcp-install-interactive.ts +412 -0
- package/src/mcp-install.test.ts +957 -5
- package/src/mcp-install.ts +580 -13
- package/src/mcp-tools.ts +101 -90
- package/src/routes.ts +330 -207
- package/src/routing.test.ts +12 -12
- package/src/routing.ts +0 -2
- package/src/tokens-routes.test.ts +11 -4
- package/src/vault.test.ts +1052 -333
- package/core/src/note-schemas.ts +0 -232
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,
|
|
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
|
|
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
|
```
|