@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.
- package/.parachute/module.json +15 -0
- package/README.md +133 -0
- package/core/src/core.test.ts +2990 -92
- package/core/src/links.ts +1 -1
- package/core/src/mcp.ts +413 -68
- package/core/src/notes.ts +693 -42
- package/core/src/obsidian.ts +3 -3
- package/core/src/paths.ts +1 -1
- package/core/src/query-operators.ts +23 -7
- package/core/src/schema-defaults.ts +331 -0
- package/core/src/schema.ts +467 -11
- package/core/src/store.ts +262 -8
- package/core/src/tag-hierarchy.ts +171 -0
- package/core/src/tag-schemas.ts +242 -42
- package/core/src/types.ts +96 -7
- package/core/src/vault-projection.ts +309 -0
- package/core/src/wikilinks.ts +3 -3
- package/package.json +13 -3
- package/src/admin-spa.test.ts +161 -0
- package/src/admin-spa.ts +161 -0
- package/src/auth-hub-jwt.test.ts +360 -0
- package/src/auth-status.ts +84 -0
- package/src/auth.test.ts +135 -23
- package/src/auth.ts +173 -15
- package/src/backup.ts +4 -7
- package/src/cli.ts +322 -57
- package/src/config.test.ts +44 -0
- package/src/config.ts +68 -40
- package/src/hub-jwt.test.ts +307 -0
- package/src/hub-jwt.ts +88 -0
- package/src/init.test.ts +216 -0
- package/src/mcp-http.ts +33 -29
- package/src/mcp-install.ts +1 -1
- package/src/mcp-tools.ts +318 -19
- package/src/module-config.ts +1 -1
- package/src/oauth.test.ts +345 -0
- package/src/oauth.ts +85 -14
- package/src/owner-auth.ts +57 -1
- package/src/prompt.ts +6 -5
- package/src/routes.ts +796 -61
- package/src/routing.test.ts +466 -1
- package/src/routing.ts +106 -24
- package/src/scopes.test.ts +66 -8
- package/src/scopes.ts +163 -37
- package/src/server.ts +24 -2
- package/src/services-manifest.test.ts +20 -0
- package/src/services-manifest.ts +9 -2
- package/src/stop-signal.test.ts +85 -0
- package/src/storage.test.ts +92 -0
- package/src/tag-scope.ts +118 -0
- package/src/token-store.test.ts +47 -0
- package/src/token-store.ts +128 -13
- package/src/tokens-routes.test.ts +727 -0
- package/src/tokens-routes.ts +392 -0
- package/src/transcription-worker.test.ts +5 -0
- package/src/triggers.ts +1 -1
- package/src/two-factor.ts +2 -2
- package/src/vault-create.test.ts +193 -0
- package/src/vault-name.test.ts +123 -0
- package/src/vault-name.ts +80 -0
- package/src/vault.test.ts +1626 -183
- package/tsconfig.json +8 -1
- package/.claude/settings.local.json +0 -8
- package/.dockerignore +0 -8
- package/.env.example +0 -9
- package/CHANGELOG.md +0 -175
- package/CLAUDE.md +0 -125
- package/Caddyfile +0 -3
- package/Dockerfile +0 -22
- package/bun.lock +0 -219
- package/bunfig.toml +0 -2
- package/deploy/parachute-vault.service +0 -20
- package/docker-compose.yml +0 -50
- package/docs/HTTP_API.md +0 -434
- package/docs/auth-model.md +0 -340
- package/fly.toml +0 -24
- package/package/package.json +0 -32
- package/railway.json +0 -14
- package/scripts/migrate-audio-to-opus.test.ts +0 -237
- package/scripts/migrate-audio-to-opus.ts +0 -499
package/tsconfig.json
CHANGED
|
@@ -25,5 +25,12 @@
|
|
|
25
25
|
"noUnusedLocals": false,
|
|
26
26
|
"noUnusedParameters": false,
|
|
27
27
|
"noPropertyAccessFromIndexSignature": false
|
|
28
|
-
}
|
|
28
|
+
},
|
|
29
|
+
"exclude": [
|
|
30
|
+
"node_modules",
|
|
31
|
+
"**/*.test.ts",
|
|
32
|
+
"**/*.spec.ts",
|
|
33
|
+
"web/ui",
|
|
34
|
+
"scripts/migrate-audio-to-opus.ts"
|
|
35
|
+
]
|
|
29
36
|
}
|
package/.dockerignore
DELETED
package/.env.example
DELETED
package/CHANGELOG.md
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to Parachute Vault are documented here.
|
|
4
|
-
|
|
5
|
-
This project loosely follows [Keep a Changelog](https://keepachangelog.com) and [Semantic Versioning](https://semver.org).
|
|
6
|
-
|
|
7
|
-
## [Unreleased]
|
|
8
|
-
|
|
9
|
-
### Added
|
|
10
|
-
|
|
11
|
-
- **Vault is now the scribe context provider: triggers + worker can enrich transcription POSTs with vault notes.** Two surfaces, one shape. (1) Every trigger's `action` gains an `include_context` list — `[{tag, exclude_tag?, include_metadata?}]` — whose predicates pre-fetch matching notes at fire time. `send: "attachment"` attaches them as a multipart `context` JSON part (`{entries: [{name, ...metadata}]}`); `send: "json"` inlines the same payload under a top-level `context` field. `send: "content"` (TTS-out) ignores context. (2) The dedicated transcription worker gains the same surface via a per-vault `transcription.context` section in `vault.yaml`; the worker attaches the resulting `context` multipart part to each scribe POST. In both paths the `name` is the note path's basename (stem) and only whitelisted `include_metadata` keys are surfaced — unrelated metadata (including secrets a vault might carry) never leaks. Fetch failures are isolated per-predicate and logged, so a single bad tag can't block a whole fire or tick. Existing configs without `include_context` / `transcription.context` see no behavior change. (Scribe will drop its own vault client in a follow-up — vault is now the single reader.)
|
|
12
|
-
- **`SCRIBE_AUTH_TOKEN` env var — canonical name for the scribe bearer token.** Matches the CLI's install-time auto-wire. `SCRIBE_TOKEN` is retained as a deprecated alias for one release: when only the legacy name is set, the worker reads it and logs a one-time boot warning. Both unset means no `Authorization` header is sent (back-compat with loopback-trust deployments). When a webhook trigger points at the same host as `SCRIBE_URL` while the dedicated worker is enabled, vault now logs a soft-deprecation warning at boot — the trigger's `missing_metadata` guard keeps it idempotent, but running both against the same scribe endpoint is noise and the worker is the preferred path.
|
|
13
|
-
- **`PARACHUTE_HUB_ORIGIN` env var — vault can advertise a hub as the OAuth issuer.** When set (e.g. `https://hub.example`) *and* the incoming request arrives via the hub origin (matched against `X-Forwarded-Host` / request `Host`), vault's authorization-server metadata publishes `issuer = $HUB` and rewrites the `authorization_endpoint`, `token_endpoint`, and `registration_endpoint` to `${HUB}/oauth/{authorize,token,register}`; protected-resource metadata lists the hub as the authorization server; and the token response includes `iss = $HUB`. When the same vault is reached via a non-matching origin (typically direct loopback, `http://127.0.0.1:<port>/...`), the discovery document describes *that* origin instead — `issuer = <origin>/vault/<name>` and vault-rooted endpoints — so RFC 8414 §2 issuer/origin consistency holds on both views concurrently. The CLI is responsible for routing `${HUB}/oauth/*` to the vault's internal `/vault/<name>/oauth/*` endpoints at the reverse-proxy layer. Phase 0 of the hub-as-OAuth-issuer design; future phases will introduce per-service scope enforcement. When the env is unset, behavior is unchanged — vault advertises itself as the issuer for every request.
|
|
14
|
-
- **Token response includes a `services` catalog.** `POST /vault/<name>/oauth/token` now includes a `services` object alongside `access_token`, sourced from `~/.parachute/services.json` (the CLI-owned manifest). Each entry's `paths[0]` is rewritten into an absolute URL rooted at the origin the client used to reach vault — hub origin for tokens minted via the hub reverse proxy, vault request origin otherwise — so clients get externally-reachable URLs for the same origin they're already talking to, not internal paths or a mismatched host. Shape: `{vault: {url: "...", version: "0.3.0"}, notes: {url: "...", version: "0.1.0"}}`. Additive field — older clients that don't expect it ignore the key. Unreadable manifest logs a warning and returns an empty catalog rather than failing the token exchange.
|
|
15
|
-
- **`parachute-vault mcp-install` picks the URL matching vault's advertised issuer.** The URL written into `~/.claude.json` previously always pointed at loopback (`http://127.0.0.1:<port>/vault/<name>/mcp`). Now it prefers, in order: `PARACHUTE_HUB_ORIGIN` env (hub-rooted URL), then `~/.parachute/expose-state.json`'s `canonicalFqdn` when an active tailnet/public exposure is configured (`https://<fqdn>/vault/<name>/mcp`), then the loopback fallback. This is the visible behavior Aaron hit: with a hub exposure live, strict MCP clients (Claude Code) were hitting a loopback URL whose discovery issuer pointed at the hub — the command now writes a URL that matches the discovery issuer for that origin. Logs which URL was chosen and why.
|
|
16
|
-
- **`scopes_supported` publishes the final vault-scoped shape.** Discovery metadata now advertises `["vault:read", "vault:write", "full", "read"]` — the new names alongside the legacy ones for back-compat with 0.2.x clients. Vault does not yet *enforce* per-scope distinctions (all tokens continue to grant full-or-read access); this just publishes the shape so hub/CLI tooling can plan for the Phase 2 enforcement cutover.
|
|
17
|
-
- **Scope enforcement at the HTTP and MCP boundary (`vault:read`, `vault:write`, `vault:admin`).** Tokens now carry an OAuth-standard whitespace-separated `scopes` string on the row (schema v12), and every request is gated by the scope required for its target. HTTP: `GET/HEAD/OPTIONS /vault/<name>/api/*` requires `vault:read`; `POST/PATCH/PUT/DELETE` requires `vault:write`; `GET /vault/<name>/.parachute/config` requires `vault:admin` (flipping from public — hub keeps working loopback via the admin-scoped token minted at setup). Inheritance is `admin ⊇ write ⊇ read`, so a single higher-scoped token still works everywhere below it. MCP: tools are partitioned (`query-notes`, `list-tags`, `find-path`, `vault-info` → `vault:read`; `create-note`, `update-note`, `delete-note`, `update-tag`, `delete-tag` → `vault:write`) — read-only tokens only see the read tools in `tools/list`, and a direct `tools/call` of a mutation tool returns an error naming the missing scope rather than silently succeeding. Forbidden responses carry `{error_type: "insufficient_scope", required_scope, granted_scopes}` so agents can diagnose without tracing. OAuth token responses now emit the OAuth-standard `scope` string (`"vault:read vault:write vault:admin"` for full-access consent) instead of the legacy `"full"`/`"read"` shorthand; the consent page radio buttons are unchanged. `parachute-vault tokens create --read` is now enforcement-real: the token it mints is rejected on writes with 403. Back-compat: pre-v12 rows (NULL `scopes` column) continue to work for one release by falling back to `legacyPermissionToScopes(permission)`; on first use they log a one-time deprecation warning, and the shim will be removed after the next release. `vault:<name>:<verb>` is accepted as a synonym for `vault:<verb>` today (Phase 2+ per-vault narrowing is reserved but not yet enforced). Additive migration — no data rewrite, no client churn on the hot path.
|
|
18
|
-
- **`kind: "api"` on `/vault/<name>/.parachute/info`.** The service-info card now includes a `kind` field so the hub can render API services (no browser UI, JSON at root) differently from frontend services (launch-in-tab). Additive to the locked card shape; versioned hub renderers that don't know about `kind` will keep treating the card as a default tile.
|
|
19
|
-
- **`GET /vault/<name>/.parachute/config/schema` + `/.parachute/config` — module configuration endpoints.** Phase 2 of the module architecture: every module exposes a JSON Schema (draft-07) describing its configurable settings, and a paired endpoint returning the current effective values. Hub reads both and renders a configuration form without any hub-side knowledge of vault's settings. The schema describes `audio_retention` (enum, per-vault), `scribe_url` (uri, env-backed until Phase 3), `scribe_token` (`writeOnly` — never returned by GET), and `port` (read-only informational, from global config). The config endpoint returns effective values with `writeOnly` fields stripped; the token never appears in any response. Both endpoints are public during Phase 0–2 while the hub is loopback-only; the `vault:admin` scope will gate `/config` once scope enforcement lands in Phase 3. `PUT /.parachute/config` is explicitly Phase 3 — non-GET methods return 405 so clients that already speak the full contract discover the gap.
|
|
20
|
-
|
|
21
|
-
### Fixed
|
|
22
|
-
|
|
23
|
-
- **OAuth discovery issuer is now origin-aware, not globally hub-rooted (RFC 8414 §2).** The Phase 0 seam previously published `issuer = $PARACHUTE_HUB_ORIGIN` for every discovery request — so a client fetching `/.well-known/oauth-authorization-server` via `http://127.0.0.1:<port>/...` would get back `issuer: https://hub.example`, an origin mismatch that strict RFC 8414 clients (including Claude Code's MCP OAuth SDK) reject. The same vault now concurrently exposes two self-consistent issuer views: tokens minted and discovery served over the hub reverse-proxy origin return `issuer = $HUB`; requests that arrive directly on any other origin (loopback in practice) return `issuer = <origin>/vault/<name>` with vault-rooted endpoints. Match is against the incoming request's base URL (honoring `X-Forwarded-Host` / `X-Forwarded-Proto`). The token response's `iss` claim follows the same resolution, so tokens minted on one origin validate against that origin's discovery doc. Companion to the `mcp-install` URL picker above — together they close the RFC 8414 violation without per-origin configuration.
|
|
24
|
-
- **Optimistic concurrency made safe-by-default and legible end-to-end.** Four bundled fixes so an agent client can never accidentally clobber a concurrent write and always has the token + structured error needed to recover. (1) `update-note` now **requires** `if_updated_at` (or an explicit `force: true` override) — previous behavior allowed unconditional writes when the field was omitted, which is exactly the footgun the field exists to prevent. MCP returns a JSON-RPC `InvalidParams` error with `data: {error_type: "precondition_required", note_id, path}`; REST returns 428 Precondition Required with the same body shape. `force: true` is the documented escape hatch for bulk migrations and scripted writes where concurrency is known-safe. (2) Conflict errors now carry a structured shape — `{error_type: "conflict", current_updated_at, your_updated_at, path, note_id}` — surfaced in the MCP error `data` field and the REST 409 body. Clients can branch on `error_type` and immediately re-arm a retry with `current_updated_at` rather than parsing the human-readable message. REST 409 is additive: new fields sit alongside the previous `{error: "conflict", expected_updated_at}` shape so existing lens clients keep working. (3) `query-notes` single-note fetches (by `id` or `path`) now return `updatedAt` — previously only `createdAt` came back, which left the caller with no concurrency token to arm a subsequent `update-note`. (4) `create-note` returns `updatedAt` on fresh notes (same value as `createdAt`, matching the insert-time invariant from PR #70), so a client that creates and immediately updates can thread the token through without a second fetch. Existing in-band `isError: true` tool results remain the fallback for unstructured errors.
|
|
25
|
-
- **OAuth discovery served at RFC 8414 §3.1 / RFC 9728 §3 path-insertion URLs for the `/vault/<name>/` URL shape.** For a resource at `/vault/<name>/mcp`, the spec-mandated metadata URLs are `/.well-known/oauth-authorization-server/vault/<name>[/mcp]` and `/.well-known/oauth-protected-resource/vault/<name>[/mcp]` — path-insertion, with `.well-known` above the issuer path — not the path-append `/vault/<name>/.well-known/<type>` shape that the 0.3 URL migration shipped alone. Strict clients including Claude Code's MCP OAuth SDK probe only the insertion form; without these routes they 404 on discovery and can't complete the authorization handshake. PR #124 originally implemented this for the pre-migration `/vaults/<name>/` URL shape; the `/vaults/` → `/vault/` rename in the breaking URL migration dropped the insertion routes and this restores them. Both URL shapes now return deep-equal JSON via the shared handlers, so mixed-toolchain clients can't observe drift. Unknown-vault requests on the insertion form return 404 rather than phantom metadata.
|
|
26
|
-
|
|
27
|
-
### Changed
|
|
28
|
-
|
|
29
|
-
- **Filesystem hygiene inside `~/.parachute/vault/`: `vaults/` → `data/`, and logs moved into `logs/`.** Two internal moves with the same target-wins, idempotent, auto-migrating shape as the 0.3 ecosystem-root move. Per-vault SQLite state now lives at `~/.parachute/vault/data/<name>/` (was `vaults/<name>/`) — matches the Postgres/Redis convention and avoids the doubled "vault/vaults" path. Daemon logs now live at `~/.parachute/vault/logs/vault.log` and `~/.parachute/vault/logs/vault.err` (were flat in `~/.parachute/vault/`) — matches the `~/.parachute/<svc>/logs/<svc>.log` convention the CLI uses for every sibling service. On first post-upgrade run the vault auto-migrates `vault/vaults/` → `vault/data/` and `vault/vault.log`/`vault.err` → `vault/logs/`, logging each move to stderr. Target-wins on conflict: if both `vault/data/` and `vault/vaults/` exist (or both log locations), the new one is kept and the legacy copy is left in place with a warning. No user action required — any `parachute-vault` invocation triggers the migration. Note: vault does not use `~/.parachute/tokens.db` (no code references it), so it is not part of this move; the CLI will archive that file separately.
|
|
30
|
-
|
|
31
|
-
### Added
|
|
32
|
-
|
|
33
|
-
- **`GET /vault/<name>/.parachute/info` + `/.parachute/icon.svg` for the CLI hub page.** Two public (no auth), CORS-`*` endpoints so the ecosystem-root hub rendered by the CLI can aggregate service cards. `info` returns a locked card shape — `name`, `displayName`, `tagline`, `version` (from `package.json`), `iconUrl` — and `icon.svg` returns a small placeholder monogram inline. Zero PII, read-only. Non-GET methods return 405.
|
|
34
|
-
|
|
35
|
-
### Changed
|
|
36
|
-
|
|
37
|
-
- **Vault state moved from `~/.parachute/` into `~/.parachute/vault/`.** The ecosystem root (`~/.parachute/`) now hosts multiple sibling services — `services.json` and `well-known/` stay at the root (CLI-owned), and everything vault owns (`.env`, `config.yaml`, `vault.log`, `vault.err`, `start.sh`, `server-path`, `vaults/`, `assets/`, `backup-last.json`, top-level `*.db` snapshots) has moved under `~/.parachute/vault/`. `PARACHUTE_HOME` still points at the ecosystem root; the vault subdir is derived as `${PARACHUTE_HOME}/vault`. On first post-upgrade run, any legacy paths still at the root are auto-migrated into `vault/` — the CLI logs each moved path to stderr and the migration is idempotent (double-runs are a no-op). If a legacy path and its new counterpart both exist, the new one wins and the legacy copy is left in place with a warning so users can inspect before removing. The launchd plist + systemd unit both point `WorkingDirectory` at the new `vault/` subdir, and the generated `start.sh` wrapper now sources `~/.parachute/vault/.env`. No user action is required — running any `parachute-vault` command (including `doctor` and `url`) triggers the migration.
|
|
38
|
-
|
|
39
|
-
### Added
|
|
40
|
-
|
|
41
|
-
- **`query-notes` gains operator objects on `metadata` fields and top-level `order_by`.** Metadata values can now be operator objects — `{metadata: {priority: {gte: 3, lt: 10}, status: {in: ["open", "in_progress"]}}}` — instead of only exact-match scalars. Supported operators: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `not_in`, `exists` (boolean). Multiple ops on one field compose as AND. `order_by: "<field>"` sorts results by a metadata field, using the existing `sort` param (`asc` / `desc`) for direction and appending `created_at` as a stable tiebreaker. Both paths require the field to be declared `indexed: true` in some tag schema (via `update-tag`) — operator queries and `order_by` route through the `meta_<field>` generated column + B-tree index shipped in the previous release, so they stay O(log n) instead of O(notes). Errors are loud: unknown operator → `UNKNOWN_OPERATOR`; non-indexed field → `FIELD_NOT_INDEXED`; type-mismatched operator value (`in` expecting array, `exists` expecting boolean) → `INVALID_OPERATOR_VALUE`; REST returns 400 with `{error, code}`. `ne` preserves "unset AND differs" semantics via `(col IS NULL OR col <> ?)` so rows missing the field aren't silently excluded. Empty `in: []` contradicts; empty `not_in: []` is a no-op — both avoid SQLite's `IN ()` syntax error. Existing primitive-value metadata filters (`{metadata: {status: "open"}}`) still JSON-exact-match and work on un-indexed fields; the shape of the value — scalar vs. object — picks the path. REST exposes `order_by` via the `order_by` query param on `GET /vault/<name>/api/notes`.
|
|
42
|
-
- **`query-notes` gains `has_tags` and `has_links` presence filters.** Two new booleans on the `query-notes` MCP tool and the `GET /vault/<name>/api/notes` REST endpoint: `has_tags` (true = tagged-only, false = untagged-only) and `has_links` (true = notes with any inbound or outbound link, false = orphans in either direction). Composable with each other and with existing filters; `has_tags: false, has_links: false` returns the true loners. When `tag` is already set, `has_tags` is ignored — the tag filter is strictly narrower and wins. Implemented as correlated `EXISTS` / `NOT EXISTS` subqueries against `note_tags` and `links`, which lets SQLite use the existing indexes and stay O(rows) rather than O(rows × tags).
|
|
43
|
-
|
|
44
|
-
### Changed
|
|
45
|
-
|
|
46
|
-
- **Breaking: every vault-touching route moved to `/vault/<name>/...`; unscoped routes removed.** There is one URL shape for every client, same layout whether you have one vault or ten. The API lives at `/vault/<name>/api/...`, MCP at `/vault/<name>/mcp`, OAuth at `/vault/<name>/oauth/{register,authorize,token}`, discovery at `/vault/<name>/.well-known/oauth-*`, published notes at `/vault/<name>/view/:id`. The old unscoped `/api`, `/mcp`, `/oauth/*`, `/view/*` paths — and the previous `/vaults/<name>/...` prefix — are gone; requests to them return 404. Cross-vault endpoints (`GET /vaults`, `GET /vaults/list`, `GET /health`) are unchanged. The unified MCP endpoint that fanned tool calls across vaults via a `vault` param has been dropped — each MCP session now pins to one vault by the URL and the `list-vaults` tool is no longer exposed. A new `WWW-Authenticate: Bearer resource_metadata="..."` header decorates every MCP 401 so OAuth-capable clients can discover the right authorization server directly from the challenge (RFC 9728).
|
|
47
|
-
|
|
48
|
-
#### Upgrading from 0.2.x
|
|
49
|
-
|
|
50
|
-
- **Claude Code**: run `parachute-vault mcp-install` (or re-run `parachute-vault init`) to rewrite `~/.claude.json` with the new `/vault/<name>/mcp` URL. Existing `pvt_` tokens are kept; no re-auth needed.
|
|
51
|
-
- **Claude Desktop / Parachute Daily / any OAuth client**: remove the integration and add it back pointing at `https://<your-host>/vault/<name>/mcp`. The OAuth handshake will re-run and mint a fresh per-vault token. Pasted bearer-token integrations need only the URL updated.
|
|
52
|
-
- **curl / scripts**: rewrite hardcoded URLs. Old `/api/notes` → `/vault/default/api/notes`; old `/vaults/work/api/...` → `/vault/work/api/...`; old unscoped `/mcp` → `/vault/default/mcp`. Tokens keep working.
|
|
53
|
-
- **Published-note permalinks**: `/view/<id>` and `/vaults/<name>/view/<id>` now 404. Update to `/vault/<name>/view/<id>`.
|
|
54
|
-
|
|
55
|
-
### Fixed
|
|
56
|
-
|
|
57
|
-
- **Fresh notes now have `updated_at = created_at` instead of `NULL`.** Clients that fall back to `createdAt` when computing an optimistic-concurrency token (the common `updatedAt ?? createdAt` pattern, used by the Lens editor) were being rejected with a `409 CONFLICT` on the very first edit of a just-created note, because the stored `updated_at IS NULL` never matched the sent timestamp. The insert path now writes both columns at once; a one-time idempotent migration backfills `updated_at = created_at` for any existing rows with `NULL`. Rows that already had a real `updated_at` are untouched. Hook-style writes with `skipUpdatedAt` continue to preserve the column, so `updated_at > created_at` still means "user-touched since creation."
|
|
58
|
-
|
|
59
|
-
### Changed
|
|
60
|
-
|
|
61
|
-
- **CLI renamed: `parachute` → `parachute-vault`.** The published `@openparachute/vault` package now exposes its binary as `parachute-vault`, freeing the `parachute` name for the forthcoming `@openparachute/cli` dispatcher that will front this service alongside sibling Parachute Computer services. Direct invocations become `parachute-vault init`, `parachute-vault status`, etc. Users installing the upcoming dispatcher can keep typing `parachute vault <cmd>` — the dispatcher forwards to `parachute-vault <cmd>` transparently. The CLI's own arg-parser still accepts a leading `vault` prefix (`parachute-vault vault init` works), so existing launchd / systemd wrappers that hardcode the full form continue to work across the upgrade.
|
|
62
|
-
|
|
63
|
-
### Added
|
|
64
|
-
|
|
65
|
-
- **`update-tag` field specs gain `indexed: boolean`; declared-indexed fields get a generated column + B-tree index on `notes`.** When any tag schema declares a field with `indexed: true`, vault adds a VIRTUAL generated column `meta_<field>` computed from `json_extract(notes.metadata, '$."<field>"')` and indexes it. The tag authorizes the index; the index is universal across all notes, not partitioned by tag — so once `#project` declares `status: indexed`, any note with `status` in its metadata is indexed regardless of tags. `type` and `indexed` are global — all declarers must agree; mismatches at `update-tag` throw a loud error naming the conflicting tag. `description` and `enum` remain per-tag. A new `indexed_fields` table (`field`, `sqlite_type`, `declarer_tags` JSON) is the single source of truth; the column + index drop when the last declarer releases the flag or is removed via `delete-tag`. Type map: `string`→TEXT, `integer`/`boolean`→INTEGER. Field names are restricted to `[A-Za-z_][A-Za-z0-9_]{0,62}` for SQL-identifier safety. Indexes are rebuilt idempotently from `indexed_fields` on every vault init. The query surface — operator objects on `metadata` and `order_by` — lands separately; this release just puts the indexes in place.
|
|
66
|
-
- **`parachute-vault init` registers the service in `~/.parachute/services.json`.** An `upsertService` call writes `{name: "parachute-vault", port, paths: ["/vault/<default_vault>"], health: "/health", version}` into the shared manifest that the `@openparachute/cli` dispatcher consumes for discovery, health probes, and routing. `paths[0]` is the canonical mount point — the CLI uses it to build the `.well-known/parachute.json` URL and for `parachute expose`. When no default vault is set (multi-vault, no fallback), `paths` falls back to `["/"]` and the operator is expected to fix the config. The write is upsert-by-name and preserves entries from other services (notes, scribe, channel) that share the file. Malformed-manifest errors are logged and init proceeds — the manifest is advisory, not a blocker.
|
|
67
|
-
- **Atomic tag rename + merge endpoints.** `POST /api/tags/{name}/rename` with `{new_name}` rewrites the tag across `tags`, `note_tags`, and the schema row in a single transaction; `POST /api/tags/merge` with `{sources, target}` retags every note carrying any source tag onto the target (creating it if missing), preserves the target's schema, and drops the sources. Rename returns `409 {error: "target_exists"}` when `new_name` is already a tag, pointing clients at the merge endpoint instead of the previous N+1 client-side PATCH stopgap.
|
|
68
|
-
- **Server-side transcription on attachment upload.** `POST /api/notes/{id}/attachments` now accepts `{transcribe: true}`. The attachment is stamped with `transcribe_status: "pending"` and the note with `transcribe_stub: true`. A background worker (enabled by setting `SCRIBE_URL` / optional `SCRIBE_TOKEN` in the server environment) drains the queue FIFO, POSTs the audio to `${SCRIBE_URL}/v1/audio/transcriptions`, and on success replaces the `_Transcript pending._` placeholder (or the whole body, if absent) with the transcript. If the user cleared the stub marker before the transcript arrived, the note is left alone — but the transcript is still recorded on the attachment. Retries use exponential backoff up to three attempts before flipping to `transcribe_status: "failed"`. The queue is the `attachments` table, so a restart resumes pending work. Per-vault `audio_retention: "until_transcribed"` (in `vault.yaml`) unlinks the audio file after success while keeping the attachment row (and transcript) addressable; `"keep"` (default) preserves the file.
|
|
69
|
-
- **Audio retention API: `GET` + `PATCH /api/vault` expose `config.audio_retention`.** The previously file-only setting is now mutable at runtime without hand-editing `vault.yaml`. `GET` reports the active mode (defaulting to `"keep"` for vaults created before the setting existed); `PATCH {config: {audio_retention: ...}}` sets it and validates against the allowed set `"keep"` / `"until_transcribed"` / `"never"`. The new `"never"` mode unlinks audio on *any* terminal state — including failure — for users who want to guarantee no audio persists after processing, trading off the ability to retry a failed transcription. The file is still kept during mid-queue retries so in-flight attempts have something to send. Invalid modes return `400 {error: "invalid_audio_retention"}`.
|
|
70
|
-
|
|
71
|
-
## [0.2.4] — 2026-04-18
|
|
72
|
-
|
|
73
|
-
### Added
|
|
74
|
-
|
|
75
|
-
- `link_count` surfaced in the vault stats response (REST + MCP `vault-info`), matching the existing note and tag counts.
|
|
76
|
-
|
|
77
|
-
## [0.2.3] — 2026-04-17
|
|
78
|
-
|
|
79
|
-
### Fixed
|
|
80
|
-
|
|
81
|
-
- **OAuth discovery endpoints now served at RFC-compliant path-insertion URLs (`/.well-known/oauth-authorization-server/{path}`) in addition to the existing path-append form.** Restores Claude Code's MCP OAuth SDK compatibility, which follows RFC 8414 §3.1 and RFC 9728 §3 strictly and probes only the path-insertion shape. Before 0.2.3, the SDK's AS-metadata fetch 404'd, leaving it without a `registration_endpoint` and cascading into a 404 on the `/register` fallback. Both scoped forms now work: `/.well-known/oauth-authorization-server/vaults/<name>` and the longer `/.well-known/oauth-authorization-server/vaults/<name>/mcp`; same shapes on `/.well-known/oauth-protected-resource/...`. Path-append routes (`/vaults/<name>/.well-known/<type>`) are unchanged so lax clients keep working.
|
|
82
|
-
|
|
83
|
-
## [0.2.2] — 2026-04-17
|
|
84
|
-
|
|
85
|
-
### Fixed
|
|
86
|
-
|
|
87
|
-
- **`start.sh` daemon wrapper no longer crashes on user shell profiles that reference unbound variables.** The generated wrapper ran `source ~/.zprofile` and `source ~/.zshrc` under `set -u`, so a zsh plugin framework or any conditional profile setup that touched an unset variable would abort the wrapper with exit 1. The `2>/dev/null` redirect swallowed the error, launchd saw repeated exit 1s, and the daemon silently refused to start with an empty `vault.err`. The wrapper now brackets the profile-source lines with `set +u` / `set -u` so -u is only active for code the wrapper owns. Run `parachute vault init` once on 0.2.2 to rewrite `~/.parachute/start.sh` — the rewrite is idempotent.
|
|
88
|
-
|
|
89
|
-
### Added
|
|
90
|
-
|
|
91
|
-
- **`parachute --version` / `parachute -v` / `parachute version`** print the installed package version to stdout. Works at the root and with the `vault` prefix (`parachute vault --version`, etc.). Reads from the installed `package.json` at module load, not a hardcoded string.
|
|
92
|
-
|
|
93
|
-
## [0.2.1] — 2026-04-17
|
|
94
|
-
|
|
95
|
-
### Fixed
|
|
96
|
-
|
|
97
|
-
- OAuth discovery now works against Claude Code's MCP SDK (and any other strict RFC 9728 client): 401 responses from the MCP endpoint carry a `WWW-Authenticate: Bearer resource_metadata="…"` header pointing at the scoped or unscoped protected-resource metadata document, matching the URL the client actually hit. Previously, clients with no pointer fell back to probing the root `/.well-known/oauth-protected-resource`, got `resource: <base>/mcp`, and rejected any connection to `/vaults/<name>/mcp` as a resource mismatch.
|
|
98
|
-
|
|
99
|
-
## [0.2.0] — 2026-04-17
|
|
100
|
-
|
|
101
|
-
First tagged public release. Ships the auth, backup, and onboarding surface the project needs for first-wave users.
|
|
102
|
-
|
|
103
|
-
### Authentication
|
|
104
|
-
|
|
105
|
-
- **OAuth 2.1 + PKCE** with Dynamic Client Registration (RFC 7591). Claude Desktop, Parachute Daily, and any OAuth-capable MCP client can connect with no manual token paste — user clicks "Add integration", browser opens to the vault's consent page, done.
|
|
106
|
-
- **Owner password** (bcrypt-hashed, min 12 characters) for the OAuth consent page. Prompt fires at `vault init`; manage later with `parachute vault set-password` / `--clear`.
|
|
107
|
-
- **TOTP 2FA with single-use backup codes**. `parachute vault 2fa enroll` prints a QR and one-time backup codes; `status` / `disable` / `backup-codes` subcommands for lifecycle.
|
|
108
|
-
- **Per-vault OAuth scope** — discovery at `/vaults/{name}/.well-known/oauth-authorization-server` returns vault-scoped endpoints. Tokens minted there authenticate only against that vault.
|
|
109
|
-
- **Cross-vault substitution blocked**: an OAuth code issued for one vault cannot be redeemed at another vault's token endpoint (schema-enforced via a `vault_name` column on `oauth_codes`).
|
|
110
|
-
- **Honest token response**: `/oauth/token` returns `{ access_token, token_type, scope, vault }` so the client knows which vault it just connected to.
|
|
111
|
-
- **Two permission tiers**: `full` (CRUD + delete + token management) and `read` (query / list / find-path / vault-info). Tokens default to `full`; pass `--read` to `tokens create` for read-only.
|
|
112
|
-
- **Token CLI**: `parachute vault tokens` (list), `tokens create [--vault] [--read] [--expires <N{h|d|w|m|y}>] [--label]`, `tokens revoke <id> [--vault]`. Tokens are SHA-256 hashed at rest.
|
|
113
|
-
- **Query-param auth for `/view`**: `?key=pvt_...` works alongside `Authorization: Bearer` and `X-API-Key` headers, convenient for browsers.
|
|
114
|
-
|
|
115
|
-
### Backup
|
|
116
|
-
|
|
117
|
-
- **`parachute vault backup`** — one-shot snapshot: atomic `VACUUM INTO` of every vault's `vault.db`, plus `config.yaml` and each vault's `vault.yaml`, bundled as a timestamped `.tar.gz`. Safe under concurrent reads/writes.
|
|
118
|
-
- **Scheduled runs** via `parachute vault backup --schedule hourly|daily|weekly|manual` (macOS launchd). Linux systemd-timer support is a follow-up; wire cron yourself for now.
|
|
119
|
-
- **`backup status`** shows schedule, last run, destinations, next run, and per-destination tier breakdown.
|
|
120
|
-
- **Tiered (grandfather-father-son) retention**. Default: `daily: 7 / weekly: 4 / monthly: 12 / yearly: null` (unbounded). Set any tier to `0` to disable. Local-timezone bucketing.
|
|
121
|
-
- **Pluggable destinations**. `local` (any filesystem path — iCloud Drive, external disk, rsync/Syncthing folder) ships in 0.2.0. `s3`, `rsync`, and `cloud` destinations designed but not yet implemented.
|
|
122
|
-
- **`vault uninstall` tears down the backup agent too** on macOS, so scheduled backups don't keep firing on a removed install.
|
|
123
|
-
|
|
124
|
-
### Reliability
|
|
125
|
-
|
|
126
|
-
- **`parachute vault doctor`** — diagnostic suite covering server-path pointer, wrapper script, launchd agent (macOS) / systemd service (Linux), bun-on-PATH, MCP entry in `~/.claude.json` (presence + URL port match + reachability), port-collision (free / ours / foreign via `lsof` or `ss`), and — when scheduled backups are configured — backup agent + per-destination writability. Exits 1 on any `fail`.
|
|
127
|
-
- **`vault status`** is healthcheck-aware and reports live daemon state, not just service registration.
|
|
128
|
-
- **`vault restart`** blocks until `/health` returns 200, with a sensible budget and progress indicator.
|
|
129
|
-
- **Path-resilient `start.sh`** — the wrapper launchd/systemd executes embeds an absolute `bun` path + points at `~/.parachute/server-path`, which resolves to the current repo location. Move the repo, re-run `vault init`, and the daemon follows you.
|
|
130
|
-
- **Idempotent `vault init`** — safe to re-run after a folder move or config edit; refreshes the pointer, wrapper, and service registration without touching user data.
|
|
131
|
-
- **Graceful shutdown**: in-flight webhook triggers get a 5 s drain window before the daemon exits on SIGTERM/SIGINT.
|
|
132
|
-
|
|
133
|
-
### Multi-vault
|
|
134
|
-
|
|
135
|
-
- **Public `GET /vaults/list`** — unauthenticated discovery endpoint returning only vault names (no descriptions, timestamps, counts, or keys). Lets a client populate a vault picker before OAuth. Operators who want to hide vault existence can set `discovery: disabled` in `~/.parachute/config.yaml` to make the endpoint return 404.
|
|
136
|
-
- **Single-vault auto-default** — when the server has exactly one vault, the unscoped `/mcp`, `/api/*`, and `/oauth/*` paths transparently resolve to it regardless of its name. A lone vault named `journal` works at `/mcp` with no vault-in-URL needed.
|
|
137
|
-
- **Vault-management CLI**: `parachute vault create <name>`, `list` (alias `ls`), `remove <name> --yes` (alias `rm`).
|
|
138
|
-
- **Automatic `default_vault` management** — `vault create` promotes a new vault to default when none is set or the configured default points at a missing vault. `vault remove` promotes the sole survivor when you delete the default and one vault remains.
|
|
139
|
-
|
|
140
|
-
### Install / uninstall
|
|
141
|
-
|
|
142
|
-
- **`vault uninstall`** — removes the daemon registration, the `start.sh` wrapper, the `~/.parachute/server-path` pointer, and the `parachute-vault` entry in `~/.claude.json`. On macOS, tears down both the main vault agent and the backup agent. Preserves all user data.
|
|
143
|
-
- **`vault uninstall --wipe`** — additionally removes `vaults/`, `.env`, `config.yaml`, `vault.log`, and `vault.err` after a second interactive confirm (default NO).
|
|
144
|
-
- **`vault uninstall --yes --wipe`** — scripted destructive path. Skips both confirms and prints an ISO-timestamped audit line to stdout naming the target paths.
|
|
145
|
-
- **`vault url`** prints the local server URL in a script-friendly form.
|
|
146
|
-
|
|
147
|
-
### API / primitives
|
|
148
|
-
|
|
149
|
-
- **Optimistic concurrency on `update-note`** via an `if_updated_at` parameter. When supplied and it doesn't match the note's current `updated_at`, the update is rejected (MCP: `ConflictError`; HTTP: 409). Batch updates fail fast on the first conflict.
|
|
150
|
-
- **Link expansion on `query-notes`** — new `expand_links` / `expand_depth` (0–3) / `expand_mode` (`"full"` | `"summary"`) parameters inline `[[wikilink]]` targets directly into the returned content. Works on the MCP tool and the HTTP routes (single-note, search, and structured-list).
|
|
151
|
-
- **9 composable MCP tools** (was 30): `query-notes`, `create-note`, `update-note`, `delete-note`, `list-tags`, `update-tag`, `delete-tag`, `find-path`, `vault-info`. Every note parameter accepts either an ID or a path.
|
|
152
|
-
- **Webhook triggers** — declarative config-driven webhooks fire on note mutations matching tag / metadata predicates. Three send modes: `json` (general), `attachment` (Whisper-compatible transcription), `content` (OpenAI-compatible TTS).
|
|
153
|
-
|
|
154
|
-
### Documentation
|
|
155
|
-
|
|
156
|
-
- Entirely overhauled onboarding path: OAuth walkthrough, doctor + troubleshooting, first-run narrative (what `vault init` does on disk), multi-vault subsection, Tailscale Funnel walkthrough, prerequisites block.
|
|
157
|
-
- Honest token-shape documentation (`pvt_` is modern; `pvk_` is legacy and still accepted).
|
|
158
|
-
- README tells the truth about what `vault init` writes to `~/.claude.json` — a vault-scoped URL with a baked-in `pvt_` bearer, not OAuth.
|
|
159
|
-
|
|
160
|
-
### Removed
|
|
161
|
-
|
|
162
|
-
- **Semantic / vector search** — the embeddings path (`sqlite-vec`, `semantic-search` tool, embedding-provider setup wizard, `/api/ingest` endpoint). Full-text search via `query-notes` `search=` remains.
|
|
163
|
-
- **`parachute vault keys` subcommand** — superseded by `parachute vault tokens`. Legacy `pvk_...` keys in `config.yaml` are still honored at runtime.
|
|
164
|
-
|
|
165
|
-
### For contributors
|
|
166
|
-
|
|
167
|
-
- **Async `Store` interface**, renamed to `BunSqliteStore`. Paves the way for Durable Object SQLite and R2 blob backends (in flight).
|
|
168
|
-
- **`src/routing.ts`** extracted from `src/server.ts` so the request dispatcher is unit-testable without spinning up `Bun.serve()`.
|
|
169
|
-
- **`core/src/test-preload.ts`** isolates `PARACHUTE_HOME` for tests so `bun test` never touches a user's real `~/.parachute/`.
|
|
170
|
-
- Test suite at release cut: **538 passing / 0 failing / 3 skipped** across 22 files (541 tests total).
|
|
171
|
-
|
|
172
|
-
[0.2.3]: https://github.com/ParachuteComputer/parachute-vault/releases/tag/v0.2.3
|
|
173
|
-
[0.2.2]: https://github.com/ParachuteComputer/parachute-vault/releases/tag/v0.2.2
|
|
174
|
-
[0.2.1]: https://github.com/ParachuteComputer/parachute-vault/releases/tag/v0.2.1
|
|
175
|
-
[0.2.0]: https://github.com/ParachuteComputer/parachute-vault/releases/tag/v0.2.0
|
package/CLAUDE.md
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
# Parachute Vault
|
|
2
|
-
|
|
3
|
-
Agent-native knowledge graph. Notes, tags, links over MCP. Self-hosted, one command setup.
|
|
4
|
-
|
|
5
|
-
## Architecture
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
parachute-vault init → ~/.parachute/vault/ (config, .env, daemon, MCP)
|
|
9
|
-
parachute-vault create → new vault (SQLite DB + vault.yaml + pvt_ token)
|
|
10
|
-
parachute-vault config → manage env vars (PORT, etc.)
|
|
11
|
-
parachute-vault tokens → list / create / revoke per-vault tokens
|
|
12
|
-
|
|
13
|
-
CLI → Bun server (port 1940) → multiple vaults (each its own SQLite DB)
|
|
14
|
-
↑
|
|
15
|
-
Any AI → MCP (stdio or HTTP) ─────────┘
|
|
16
|
-
Phone → REST API ──────────────────┘
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Packages
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
core/ — TypeScript library: schema, store, MCP tools, wikilinks, paths (bun:sqlite)
|
|
23
|
-
src/ — Bun CLI + server + MCP + webhook triggers
|
|
24
|
-
deploy/ — systemd unit, Dockerfile, docker-compose, fly.toml, railway.json
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Data Model
|
|
28
|
-
|
|
29
|
-
Five core tables per vault. Vaults start blank — no predefined tags or schema. Clients create the tags they need.
|
|
30
|
-
|
|
31
|
-
```sql
|
|
32
|
-
notes (id, content, path, metadata, created_at, updated_at)
|
|
33
|
-
tags (name)
|
|
34
|
-
note_tags (note_id, tag_name)
|
|
35
|
-
attachments (id, note_id, path, mime_type, metadata, created_at)
|
|
36
|
-
links (source_id, target_id, relationship, metadata, created_at)
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Additional tables:
|
|
40
|
-
- `tag_schemas` — tag description + metadata field definitions (JSON)
|
|
41
|
-
- `unresolved_wikilinks` — pending wikilink resolution
|
|
42
|
-
- `schema_version` — migration tracking
|
|
43
|
-
|
|
44
|
-
Metadata is a JSON column on notes, links, and attachments. Queryable via `json_extract()`.
|
|
45
|
-
|
|
46
|
-
Path is unique (when set), normalized (no .md, no trailing slashes), and used for wikilink resolution.
|
|
47
|
-
|
|
48
|
-
### MCP Tools (9)
|
|
49
|
-
|
|
50
|
-
Notes: `query-notes` (single by ID/path, filter, search, graph neighborhood), `create-note` (single or batch), `update-note` (single or batch — content, tags, links, metadata merge), `delete-note`
|
|
51
|
-
|
|
52
|
-
Tags: `list-tags` (with optional schema detail), `update-tag` (upsert schema), `delete-tag`
|
|
53
|
-
|
|
54
|
-
Graph: `find-path` (BFS shortest path)
|
|
55
|
-
|
|
56
|
-
Vault: `vault-info` (get/update description + stats)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
## Bun-native
|
|
60
|
-
|
|
61
|
-
Use Bun for everything. No Node.js.
|
|
62
|
-
|
|
63
|
-
- `Bun.serve()` for HTTP server
|
|
64
|
-
- `bun:sqlite` for SQLite
|
|
65
|
-
- `Bun.$` for shell commands
|
|
66
|
-
- `bun test` for tests
|
|
67
|
-
|
|
68
|
-
## Key design decisions
|
|
69
|
-
|
|
70
|
-
- **Bare primitives**: Vault has no opinions about tags or conventions. It's the engine, not the schema. Clients (parachute-daily, etc.) bring their own tag schema.
|
|
71
|
-
- **Multi-vault**: One server hosts many vaults. Each vault = own SQLite DB + config + API keys.
|
|
72
|
-
- **Per-vault MCP descriptions**: vault.yaml is sent as MCP server instruction at session start. The vault teaches the AI how to use it.
|
|
73
|
-
- **Wikilink auto-linking**: `[[wikilinks]]` in note content are automatically parsed and maintained as links. Unresolved links auto-resolve when target notes are created.
|
|
74
|
-
- **Path normalization**: Paths are normalized on write (strip .md, collapse slashes, trim). UNIQUE constraint enforced. Rename cascading updates wikilinks in other notes.
|
|
75
|
-
- **Obsidian interop**: Import/export preserves frontmatter, tags, wikilinks, and file paths.
|
|
76
|
-
- **Unified config**: All env vars in `~/.parachute/vault/.env` (or `$PARACHUTE_HOME/vault/.env` in Docker).
|
|
77
|
-
- **Docker-friendly**: `PARACHUTE_HOME` env var overrides the ecosystem root; vault state lands at `$PARACHUTE_HOME/vault/`. Server auto-creates default vault on first run.
|
|
78
|
-
|
|
79
|
-
## Config
|
|
80
|
-
|
|
81
|
-
All configuration in `~/.parachute/vault/.env`:
|
|
82
|
-
|
|
83
|
-
```
|
|
84
|
-
PORT=1940
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
## Naming
|
|
88
|
-
|
|
89
|
-
- Domain: `parachute.computer`
|
|
90
|
-
- Package ID: `computer.parachute.vault`
|
|
91
|
-
- npm scope: `@openparachute/`
|
|
92
|
-
- Launchd label: `computer.parachute.vault`
|
|
93
|
-
|
|
94
|
-
## Running
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
bun src/cli.ts vault init # setup everything
|
|
98
|
-
bun src/cli.ts vault status # check status
|
|
99
|
-
bun src/cli.ts vault config # view/edit config
|
|
100
|
-
bun src/cli.ts vault import <path> # import Obsidian vault
|
|
101
|
-
bun src/cli.ts vault export <path> # export as Obsidian markdown
|
|
102
|
-
bun test src/ # run server tests
|
|
103
|
-
bun test core/src/ # run core tests
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## Deployment
|
|
107
|
-
|
|
108
|
-
Self-hosted:
|
|
109
|
-
- **Mac**: `bun install && vault init` (launchd daemon, localhost)
|
|
110
|
-
- **VPS**: `docker compose up -d` (Hetzner, DigitalOcean, etc.)
|
|
111
|
-
- **Remote access**: Cloudflare Tunnel for HTTPS (`cloudflared tunnel --url http://localhost:1940`)
|
|
112
|
-
|
|
113
|
-
Hosted (future, issue #5):
|
|
114
|
-
- Cloudflare Workers + D1 + R2
|
|
115
|
-
- Requires async Store interface refactor
|
|
116
|
-
|
|
117
|
-
## Post-merge hygiene
|
|
118
|
-
|
|
119
|
-
When a PR is merged, locally:
|
|
120
|
-
|
|
121
|
-
```
|
|
122
|
-
git checkout main && git pull
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
Aaron runs vault via `bun link` in development — the linked install follows whatever branch is checked out. Leaving the repo on a feature branch after merge means Aaron's `parachute start vault` is running stale feature-branch code, not the merged main. Caught 2026-04-21 when several stewards (including vault) left their local repo on a feature branch after merge.
|
package/Caddyfile
DELETED
package/Dockerfile
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
FROM oven/bun:1.2-alpine AS base
|
|
2
|
-
WORKDIR /app
|
|
3
|
-
|
|
4
|
-
# Install deps
|
|
5
|
-
COPY package.json bun.lock ./
|
|
6
|
-
RUN bun install --frozen-lockfile --production
|
|
7
|
-
|
|
8
|
-
# Copy source
|
|
9
|
-
COPY core core
|
|
10
|
-
COPY src src
|
|
11
|
-
COPY bunfig.toml ./
|
|
12
|
-
|
|
13
|
-
# Data directory — mount a volume here for persistence
|
|
14
|
-
ENV PARACHUTE_HOME=/data
|
|
15
|
-
RUN mkdir -p /data
|
|
16
|
-
|
|
17
|
-
EXPOSE 1940
|
|
18
|
-
|
|
19
|
-
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
|
|
20
|
-
CMD wget -qO- http://localhost:1940/health || exit 1
|
|
21
|
-
|
|
22
|
-
CMD ["bun", "src/server.ts"]
|