@openparachute/vault 0.3.3 → 0.4.3

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 (80) hide show
  1. package/.parachute/module.json +15 -0
  2. package/README.md +133 -0
  3. package/core/src/core.test.ts +2990 -92
  4. package/core/src/links.ts +1 -1
  5. package/core/src/mcp.ts +413 -68
  6. package/core/src/notes.ts +693 -42
  7. package/core/src/obsidian.ts +3 -3
  8. package/core/src/paths.ts +1 -1
  9. package/core/src/query-operators.ts +23 -7
  10. package/core/src/schema-defaults.ts +331 -0
  11. package/core/src/schema.ts +467 -11
  12. package/core/src/store.ts +262 -8
  13. package/core/src/tag-hierarchy.ts +171 -0
  14. package/core/src/tag-schemas.ts +242 -42
  15. package/core/src/types.ts +96 -7
  16. package/core/src/vault-projection.ts +309 -0
  17. package/core/src/wikilinks.ts +3 -3
  18. package/package.json +13 -3
  19. package/src/admin-spa.test.ts +161 -0
  20. package/src/admin-spa.ts +161 -0
  21. package/src/auth-hub-jwt.test.ts +360 -0
  22. package/src/auth-status.ts +84 -0
  23. package/src/auth.test.ts +135 -23
  24. package/src/auth.ts +173 -15
  25. package/src/backup.ts +4 -7
  26. package/src/cli.ts +322 -57
  27. package/src/config.test.ts +44 -0
  28. package/src/config.ts +68 -40
  29. package/src/hub-jwt.test.ts +307 -0
  30. package/src/hub-jwt.ts +88 -0
  31. package/src/init.test.ts +216 -0
  32. package/src/mcp-http.ts +33 -29
  33. package/src/mcp-install.ts +1 -1
  34. package/src/mcp-tools.ts +318 -19
  35. package/src/module-config.ts +1 -1
  36. package/src/oauth.test.ts +345 -0
  37. package/src/oauth.ts +85 -14
  38. package/src/owner-auth.ts +57 -1
  39. package/src/prompt.ts +6 -5
  40. package/src/routes.ts +796 -61
  41. package/src/routing.test.ts +466 -1
  42. package/src/routing.ts +106 -24
  43. package/src/scopes.test.ts +66 -8
  44. package/src/scopes.ts +163 -37
  45. package/src/server.ts +24 -2
  46. package/src/services-manifest.test.ts +20 -0
  47. package/src/services-manifest.ts +9 -2
  48. package/src/stop-signal.test.ts +85 -0
  49. package/src/storage.test.ts +92 -0
  50. package/src/tag-scope.ts +118 -0
  51. package/src/token-store.test.ts +47 -0
  52. package/src/token-store.ts +128 -13
  53. package/src/tokens-routes.test.ts +727 -0
  54. package/src/tokens-routes.ts +392 -0
  55. package/src/transcription-worker.test.ts +5 -0
  56. package/src/triggers.ts +1 -1
  57. package/src/two-factor.ts +2 -2
  58. package/src/vault-create.test.ts +193 -0
  59. package/src/vault-name.test.ts +123 -0
  60. package/src/vault-name.ts +80 -0
  61. package/src/vault.test.ts +1626 -183
  62. package/tsconfig.json +8 -1
  63. package/.claude/settings.local.json +0 -8
  64. package/.dockerignore +0 -8
  65. package/.env.example +0 -9
  66. package/CHANGELOG.md +0 -175
  67. package/CLAUDE.md +0 -125
  68. package/Caddyfile +0 -3
  69. package/Dockerfile +0 -22
  70. package/bun.lock +0 -219
  71. package/bunfig.toml +0 -2
  72. package/deploy/parachute-vault.service +0 -20
  73. package/docker-compose.yml +0 -50
  74. package/docs/HTTP_API.md +0 -434
  75. package/docs/auth-model.md +0 -340
  76. package/fly.toml +0 -24
  77. package/package/package.json +0 -32
  78. package/railway.json +0 -14
  79. package/scripts/migrate-audio-to-opus.test.ts +0 -237
  80. package/scripts/migrate-audio-to-opus.ts +0 -499
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "vault",
3
+ "manifestName": "parachute-vault",
4
+ "displayName": "Vault",
5
+ "tagline": "Your owner-authenticated MCP knowledge store.",
6
+ "kind": "api",
7
+ "port": 1940,
8
+ "paths": ["/vault/default"],
9
+ "health": "/vault/default/health",
10
+ "managementUrl": "/admin/",
11
+ "startCmd": ["parachute-vault", "serve"],
12
+ "scopes": {
13
+ "defines": ["vault:read", "vault:write", "vault:admin"]
14
+ }
15
+ }
package/README.md CHANGED
@@ -432,6 +432,139 @@ GET /vaults/list public: vault
432
432
  GET /health health check
433
433
  ```
434
434
 
435
+ ## Common queries / cookbook
436
+
437
+ 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.
438
+
439
+ ### Fetch every note under a path subtree
440
+
441
+ For an SSG or wiki rendering a section of the vault. `path_prefix` matches against the normalized path string — no `.md`, no trailing slash.
442
+
443
+ ```bash
444
+ curl -H "Authorization: Bearer $VAULT_TOKEN" \
445
+ "http://localhost:1940/vault/default/api/notes?path_prefix=Boulder%20Civics/City%20Council/&include_content=true"
446
+ ```
447
+
448
+ ```jsonc
449
+ // MCP
450
+ { "name": "query-notes", "arguments": { "path_prefix": "Boulder Civics/City Council/", "include_content": true } }
451
+ ```
452
+
453
+ ### Sort by a metadata field
454
+
455
+ 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).
456
+
457
+ ```bash
458
+ curl -H "Authorization: Bearer $VAULT_TOKEN" \
459
+ "http://localhost:1940/vault/default/api/notes?order_by=meeting_date&sort=desc&include_content=true"
460
+ ```
461
+
462
+ Declare the index via `update-tag`:
463
+
464
+ ```jsonc
465
+ { "name": "update-tag", "arguments": {
466
+ "tag": "meeting",
467
+ "fields": { "meeting_date": { "type": "string", "indexed": true } }
468
+ } }
469
+ ```
470
+
471
+ ### Return previews instead of full bodies
472
+
473
+ 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.
474
+
475
+ ```bash
476
+ curl -H "Authorization: Bearer $VAULT_TOKEN" \
477
+ "http://localhost:1940/vault/default/api/notes?path_prefix=Posts/"
478
+ # → [{ id, path, byteSize, preview, tags, metadata, ... }, ...]
479
+ ```
480
+
481
+ Caller-tunable preview length is a future enhancement — file an issue if 120 chars isn't enough.
482
+
483
+ ### Incremental rebuilds: "what changed since X"
484
+
485
+ 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.
486
+
487
+ ```bash
488
+ # Bracket-style (canonical)
489
+ curl -H "Authorization: Bearer $VAULT_TOKEN" \
490
+ "http://localhost:1940/vault/default/api/notes?meta[updated_at][gte]=2026-04-01T00:00:00Z"
491
+
492
+ # Flat form (DEPRECATED in 0.4.3; planned removal 0.6.0 per vault#288)
493
+ curl -H "Authorization: Bearer $VAULT_TOKEN" \
494
+ "http://localhost:1940/vault/default/api/notes?date_field=updated_at&date_from=2026-04-01T00:00:00Z"
495
+ ```
496
+
497
+ ```jsonc
498
+ // MCP — `date_filter` accepts created_at, updated_at, or any indexed metadata field.
499
+ { "name": "query-notes", "arguments": { "date_filter": { "field": "updated_at", "from": "2026-04-01T00:00:00Z" } } }
500
+ ```
501
+
502
+ 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.
503
+
504
+ ### Filter by metadata values (bracket style)
505
+
506
+ Equality on any metadata field works with no setup:
507
+
508
+ ```bash
509
+ curl -H "Authorization: Bearer $VAULT_TOKEN" \
510
+ "http://localhost:1940/vault/default/api/notes?meta[meeting-type]=study-session&include_content=true"
511
+ ```
512
+
513
+ Range, `in`, `not_in`, and `exists` operators require the field to be declared `indexed: true` (same gate as `order_by`):
514
+
515
+ ```bash
516
+ # Range — both bounds AND together on one field
517
+ "…/notes?meta[date][gte]=2026-01-01&meta[date][lt]=2026-04-01"
518
+
519
+ # Membership — `[]` array form OR comma-separated; same result.
520
+ "…/notes?meta[status][in][]=active&meta[status][in][]=exploring"
521
+ "…/notes?meta[status][in]=active,exploring"
522
+
523
+ # Presence check
524
+ "…/notes?meta[deadline][exists]=true"
525
+ ```
526
+
527
+ 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.
528
+
529
+ ### Edit one line in a large note (without re-sending the whole body)
530
+
531
+ `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.
532
+
533
+ ```jsonc
534
+ // MCP
535
+ { "name": "update-note", "arguments": {
536
+ "id": "notes/decisions",
537
+ "content_edit": {
538
+ "old_text": "Status: pending",
539
+ "new_text": "Status: shipped (2026-05-10)"
540
+ },
541
+ "if_updated_at": "2026-05-10T12:34:56.789Z"
542
+ } }
543
+ ```
544
+
545
+ ```bash
546
+ # REST equivalent
547
+ curl -X PATCH -H "Authorization: Bearer $VAULT_TOKEN" -H "Content-Type: application/json" \
548
+ -d '{"content_edit":{"old_text":"Status: pending","new_text":"Status: shipped (2026-05-10)"},"if_updated_at":"…"}' \
549
+ "http://localhost:1940/vault/default/api/notes/notes/decisions"
550
+ ```
551
+
552
+ 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.
553
+
554
+ ### Append to a note (no concurrency ceremony)
555
+
556
+ 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).
557
+
558
+ ```jsonc
559
+ { "name": "update-note", "arguments": { "id": "notes/journal/2026-05-10", "append": "\n\nEvening note: …" } }
560
+ ```
561
+
562
+ `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.
563
+
564
+ ### CI / public access via Tailscale Funnel
565
+
566
+ 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.
567
+
435
568
  ## Data model
436
569
 
437
570
  ```