@openparachute/vault 0.2.3 → 0.3.0-rc.1
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/.claude/settings.local.json +8 -0
- package/CHANGELOG.md +70 -0
- package/CLAUDE.md +17 -7
- package/README.md +169 -136
- package/core/src/core.test.ts +603 -19
- package/core/src/indexed-fields.test.ts +285 -0
- package/core/src/indexed-fields.ts +238 -0
- package/core/src/mcp.ts +127 -6
- package/core/src/notes.ts +157 -11
- package/core/src/query-operators.ts +174 -0
- package/core/src/schema.ts +69 -2
- package/core/src/store.ts +92 -0
- package/core/src/tag-schemas.ts +5 -0
- package/core/src/types.ts +29 -1
- package/docs/HTTP_API.md +105 -1
- package/package/package.json +32 -0
- package/package.json +2 -2
- package/src/auth.test.ts +83 -114
- package/src/auth.ts +68 -6
- package/src/backup-launchd.ts +1 -1
- package/src/backup.test.ts +1 -1
- package/src/backup.ts +18 -17
- package/src/cli.ts +179 -121
- package/src/config-triggers.test.ts +49 -0
- package/src/config.test.ts +317 -2
- package/src/config.ts +420 -40
- package/src/context.test.ts +136 -0
- package/src/context.ts +115 -0
- package/src/daemon.ts +17 -16
- package/src/doctor.test.ts +9 -7
- package/src/launchd.test.ts +1 -1
- package/src/launchd.ts +6 -6
- package/src/mcp-http.ts +75 -21
- package/src/mcp-install.test.ts +125 -0
- package/src/mcp-install.ts +60 -0
- package/src/mcp-tools.ts +34 -96
- package/src/module-config.ts +109 -0
- package/src/oauth.test.ts +345 -57
- package/src/oauth.ts +155 -35
- package/src/published.test.ts +2 -2
- package/src/routes.ts +209 -33
- package/src/routing.test.ts +817 -300
- package/src/routing.ts +204 -202
- package/src/scopes.test.ts +136 -0
- package/src/scopes.ts +105 -0
- package/src/scribe-env.test.ts +49 -0
- package/src/scribe-env.ts +33 -0
- package/src/server.ts +57 -5
- package/src/services-manifest.test.ts +140 -0
- package/src/services-manifest.ts +99 -0
- package/src/systemd.ts +3 -3
- package/src/token-store.ts +42 -9
- package/src/transcription-worker.test.ts +583 -0
- package/src/transcription-worker.ts +346 -0
- package/src/triggers.test.ts +191 -1
- package/src/triggers.ts +17 -2
- package/src/vault.test.ts +693 -77
- package/src/version.test.ts +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Requires [Bun](https://bun.sh) (`curl -fsSL https://bun.sh/install | bash`) and
|
|
|
11
11
|
```bash
|
|
12
12
|
# Install globally (registers the `parachute` CLI)
|
|
13
13
|
bun add -g @openparachute/vault
|
|
14
|
-
parachute
|
|
14
|
+
parachute-vault init
|
|
15
15
|
|
|
16
16
|
# Or clone and run directly
|
|
17
17
|
git clone https://github.com/ParachuteComputer/parachute-vault
|
|
@@ -44,27 +44,34 @@ A mental model for "where is my data?" and "what can I poke at?" after the one-c
|
|
|
44
44
|
### On disk — `~/.parachute/`
|
|
45
45
|
|
|
46
46
|
```
|
|
47
|
-
~/.parachute/
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
47
|
+
~/.parachute/ # ecosystem root — shared with sibling services
|
|
48
|
+
services.json # CLI-owned manifest (used by the dispatcher for
|
|
49
|
+
# discovery + routing across Parachute services)
|
|
50
|
+
well-known/ # CLI-owned (.well-known serving)
|
|
51
|
+
vault/ # everything vault owns
|
|
52
|
+
config.yaml # global config — port, default_vault, owner password
|
|
53
|
+
# hash, TOTP secret, backup-codes hashes, backup
|
|
54
|
+
# schedule. 0600.
|
|
55
|
+
.env # runtime env vars (PORT=1940 by default; any webhook
|
|
56
|
+
# API keys you add later). Sourced by the daemon wrapper.
|
|
57
|
+
vault.log # stdout of the running daemon (tail via `parachute-vault logs`)
|
|
58
|
+
vault.err # stderr of the running daemon
|
|
59
|
+
server-path # text file: absolute path to the repo's src/server.ts —
|
|
60
|
+
# how the daemon wrapper finds the source after you move it
|
|
61
|
+
start.sh # the wrapper launchd/systemd execs. Knows the absolute
|
|
62
|
+
# path to `bun` so a later PATH change doesn't break the daemon
|
|
63
|
+
assets/ # legacy top-level uploads dir (attachments now land per-vault)
|
|
64
|
+
vaults/ # one subdirectory per vault
|
|
65
|
+
default/
|
|
66
|
+
vault.db # the SQLite database — notes, tags, links, attachments,
|
|
67
|
+
# per-vault tokens, OAuth clients + codes, tag schemas
|
|
68
|
+
vault.yaml # per-vault config — description (sent as MCP session
|
|
69
|
+
# instruction), published_tag override, legacy api_keys
|
|
70
|
+
assets/ # per-vault uploaded attachments (audio, images)
|
|
66
71
|
```
|
|
67
72
|
|
|
73
|
+
`~/.parachute/` itself is the ecosystem root shared across sibling services — `services.json` and `well-known/` live at the root and are managed by the top-level CLI. Everything vault owns is scoped under `~/.parachute/vault/`. Pre-0.3 installs kept vault state directly at the root; any legacy paths still there are auto-migrated into `vault/` on first post-upgrade run (see CHANGELOG).
|
|
74
|
+
|
|
68
75
|
`config.yaml` is the one file written at 0600 because it holds the bcrypt owner-password hash and the plaintext TOTP secret. `.env` is written with your umask default (typically 0644); if you add webhook API keys there, tighten the mode yourself. SQLite DBs follow your umask.
|
|
69
76
|
|
|
70
77
|
### Registered externally
|
|
@@ -73,19 +80,19 @@ A mental model for "where is my data?" and "what can I poke at?" after the one-c
|
|
|
73
80
|
- **Linux + systemd**: a user service named `parachute-vault.service` (managed via `systemctl --user`).
|
|
74
81
|
- **Neither of the above**: `vault init` prints a reminder to start the server yourself (`bun src/server.ts` or Docker). No service registration.
|
|
75
82
|
|
|
76
|
-
The daemon binds `0.0.0.0:1940` (or whatever you set in `PORT`) and serves REST, MCP, and OAuth routes. `parachute
|
|
83
|
+
The daemon binds `0.0.0.0:1940` (or whatever you set in `PORT`) and serves REST, MCP, and OAuth routes. `parachute-vault status` is the fast check; `parachute-vault url` prints just the URL for use in scripts.
|
|
77
84
|
|
|
78
85
|
### `~/.claude.json`
|
|
79
86
|
|
|
80
|
-
`vault init` adds one entry — `mcpServers["parachute-vault"]` — pointing at `http://127.0.0.1:<port>/
|
|
87
|
+
`vault init` adds one entry — `mcpServers["parachute-vault"]` — pointing at `http://127.0.0.1:<port>/vault/<default-vault>/mcp` with a baked-in `Authorization: Bearer pvt_...` header. Next Claude Code session picks it up; there's no further wiring. See [Connecting a client](#connecting-a-client) for rotating that token or pointing it elsewhere.
|
|
81
88
|
|
|
82
89
|
### Your API token
|
|
83
90
|
|
|
84
|
-
The `pvt_...` token printed at init is the one baked into `~/.claude.json`. It's not stored anywhere retrievable — save it if you need it for `curl`, cron, or any other script. Lost it? Just mint a new one: `parachute
|
|
91
|
+
The `pvt_...` token printed at init is the one baked into `~/.claude.json`. It's not stored anywhere retrievable — save it if you need it for `curl`, cron, or any other script. Lost it? Just mint a new one: `parachute-vault tokens create`. Tokens are SHA-256 hashed at rest in each vault's `vault.db`.
|
|
85
92
|
|
|
86
93
|
### Owner password prompt
|
|
87
94
|
|
|
88
|
-
Init pauses for one interactive prompt: "Set an owner password for OAuth consent?" The password is what the consent page asks for when Claude Desktop / Parachute Daily / any browser-OAuth client connects. You can skip it and set it later with `parachute
|
|
95
|
+
Init pauses for one interactive prompt: "Set an owner password for OAuth consent?" The password is what the consent page asks for when Claude Desktop / Parachute Daily / any browser-OAuth client connects. You can skip it and set it later with `parachute-vault set-password`; without it, the consent page falls back to pasting a vault token. See [Connecting a client → Owner password](#owner-password-needed-for-oauth).
|
|
89
96
|
|
|
90
97
|
## Connecting a client
|
|
91
98
|
|
|
@@ -94,7 +101,7 @@ Two ways to authenticate — pick based on the client, not the deployment:
|
|
|
94
101
|
| Path | When to use | User action |
|
|
95
102
|
|---|---|---|
|
|
96
103
|
| **OAuth 2.1 + PKCE (browser flow)** | Claude Desktop, Parachute Daily, any third-party MCP client set up interactively | Click "Add integration", enter server URL, a browser opens to the vault's consent page, you enter the owner password, done — no token ever touches your clipboard |
|
|
97
|
-
| **Bearer token** | Claude Code (auto-wired by `vault init`), CLI scripts, cron jobs, any non-interactive caller | `curl -H "Authorization: Bearer pvt_..."` — the token is printed once at `vault init` (save it) or minted on demand with `parachute
|
|
104
|
+
| **Bearer token** | Claude Code (auto-wired by `vault init`), CLI scripts, cron jobs, any non-interactive caller | `curl -H "Authorization: Bearer pvt_..."` — the token is printed once at `vault init` (save it) or minted on demand with `parachute-vault tokens create` |
|
|
98
105
|
|
|
99
106
|
Both paths end up with the same kind of token in the vault's DB — a `pvt_` string, scoped to one vault and one permission level (`full` or `read`). OAuth just moves the "how does the client get that token" step from "human copy-pastes it" to "browser-based handshake with the owner's consent."
|
|
100
107
|
|
|
@@ -103,12 +110,12 @@ Both paths end up with the same kind of token in the vault's DB — a `pvt_` str
|
|
|
103
110
|
`vault init` prompts you to set an owner password (minimum 12 characters). This is what the OAuth consent page asks for when a client requests access. If you skip the prompt, OAuth still works but the consent page falls back to asking for a vault token instead — functional but clunky. Set it later with:
|
|
104
111
|
|
|
105
112
|
```bash
|
|
106
|
-
parachute
|
|
107
|
-
parachute
|
|
108
|
-
parachute
|
|
113
|
+
parachute-vault set-password # set / change
|
|
114
|
+
parachute-vault set-password --clear # remove (reverts to token fallback)
|
|
115
|
+
parachute-vault 2fa enroll # optional: add TOTP 2FA on top
|
|
109
116
|
```
|
|
110
117
|
|
|
111
|
-
Password and 2FA secrets live in `~/.parachute/config.yaml` at mode 0600 (bcrypt hash + base32 TOTP secret).
|
|
118
|
+
Password and 2FA secrets live in `~/.parachute/vault/config.yaml` at mode 0600 (bcrypt hash + base32 TOTP secret).
|
|
112
119
|
|
|
113
120
|
### Claude Code
|
|
114
121
|
|
|
@@ -119,7 +126,7 @@ Password and 2FA secrets live in `~/.parachute/config.yaml` at mode 0600 (bcrypt
|
|
|
119
126
|
"mcpServers": {
|
|
120
127
|
"parachute-vault": {
|
|
121
128
|
"type": "http",
|
|
122
|
-
"url": "http://127.0.0.1:1940/
|
|
129
|
+
"url": "http://127.0.0.1:1940/vault/{name}/mcp",
|
|
123
130
|
"headers": { "Authorization": "Bearer pvt_..." }
|
|
124
131
|
}
|
|
125
132
|
}
|
|
@@ -128,19 +135,19 @@ Password and 2FA secrets live in `~/.parachute/config.yaml` at mode 0600 (bcrypt
|
|
|
128
135
|
|
|
129
136
|
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.
|
|
130
137
|
|
|
131
|
-
To re-point Claude Code at a different vault, change `default_vault` in `~/.parachute/config.yaml` and re-run `parachute
|
|
138
|
+
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.)
|
|
132
139
|
|
|
133
140
|
### Claude Desktop (OAuth)
|
|
134
141
|
|
|
135
142
|
For Claude Desktop — or any install where the server is on a different machine from the client — use the browser-based OAuth flow:
|
|
136
143
|
|
|
137
144
|
1. Claude Desktop → Settings → Integrations → Add MCP server.
|
|
138
|
-
2. Enter the URL: `https://vault.yourdomain.com/
|
|
139
|
-
3. An OAuth-capable MCP client discovers the vault's authorization server at
|
|
145
|
+
2. Enter the URL: `https://vault.yourdomain.com/vault/{name}/mcp` (replace `{name}` with your vault name — e.g. `default`). **Do not paste a bearer token** — leave the auth field empty.
|
|
146
|
+
3. An OAuth-capable MCP client discovers the vault's authorization server at `/vault/{name}/.well-known/oauth-authorization-server`, registers itself via Dynamic Client Registration (RFC 7591), and opens your browser to the vault's consent page.
|
|
140
147
|
4. Enter your owner password (plus TOTP code / backup code if 2FA is enabled), pick a scope (`full` or `read`), click Authorize.
|
|
141
148
|
5. Browser redirects back. The connection is live. The client now holds a `pvt_` token scoped to this vault.
|
|
142
149
|
|
|
143
|
-
If you'd rather skip OAuth — e.g. you're scripting the setup — Claude Desktop also accepts a bearer token via the integration's auth header field. Use a token from `parachute
|
|
150
|
+
If you'd rather skip OAuth — e.g. you're scripting the setup — Claude Desktop also accepts a bearer token via the integration's auth header field. Use a token from `parachute-vault tokens create` (or the one from `vault init` if you still have it). This is the "manual bearer" fallback; OAuth is the recommended path.
|
|
144
151
|
|
|
145
152
|
### Parachute Daily (mobile)
|
|
146
153
|
|
|
@@ -151,75 +158,73 @@ Daily uses the same OAuth flow. On first launch: enter the server URL, pick the
|
|
|
151
158
|
One server, many vaults. Each vault is its own SQLite DB with its own MCP endpoint, its own OAuth, and its own tokens.
|
|
152
159
|
|
|
153
160
|
```bash
|
|
154
|
-
parachute
|
|
155
|
-
parachute
|
|
156
|
-
parachute
|
|
161
|
+
parachute-vault create work # new vault named "work"
|
|
162
|
+
parachute-vault list # show all vaults on this server
|
|
163
|
+
parachute-vault remove work --yes
|
|
157
164
|
```
|
|
158
165
|
|
|
159
|
-
**The default vault is managed for you.** `vault init` creates `default` on first install and records it as `default_vault` in `~/.parachute/config.yaml`. `vault create <name>` promotes the newly-created vault to default when no default exists or when the configured default points at a missing vault. `vault remove <name>` promotes the sole survivor when you delete the default and one vault remains; if multiple remain after removing the default, it clears the setting and tells you to edit `config.yaml` yourself. There is no `vault set-default` subcommand — to point the server at a different existing vault, edit the `default_vault:` line in `~/.parachute/config.yaml` and `parachute
|
|
160
|
-
|
|
161
|
-
**Single-vault rule.** When the server has exactly one vault, the unscoped `/oauth/*` and `/mcp` paths transparently resolve to it — regardless of its name. A lone vault named `journal` works at `https://vault.example.com/mcp` with no vault-in-URL needed.
|
|
166
|
+
**The default vault is managed for you.** `vault init` creates `default` on first install and records it as `default_vault` in `~/.parachute/config.yaml`. `vault create <name>` promotes the newly-created vault to default when no default exists or when the configured default points at a missing vault. `vault remove <name>` promotes the sole survivor when you delete the default and one vault remains; if multiple remain after removing the default, it clears the setting and tells you to edit `config.yaml` yourself. There is no `vault set-default` subcommand — to point the server at a different existing vault, edit the `default_vault:` line in `~/.parachute/config.yaml` and `parachute-vault restart`.
|
|
162
167
|
|
|
163
|
-
**
|
|
168
|
+
**URL shape.** Every vault-touching route lives under `/vault/{name}/...`: `/vault/{name}/mcp`, `/vault/{name}/oauth/authorize`, `/vault/{name}/api/notes`, `/vault/{name}/view/{id}`. There is no unscoped fallback — pick the vault in the URL even if you only have one. OAuth tokens are scoped to the vault in their issuing URL; cross-vault substitution is rejected at the OAuth layer (an auth code minted for one vault cannot be redeemed at another vault's token endpoint).
|
|
164
169
|
|
|
165
|
-
**Listing vaults from a client.** The authenticated `GET /vaults` endpoint returns full vault metadata. The public `GET /vaults/list` endpoint returns names only, no metadata, no auth required — this is what Parachute Daily's vault picker calls before the user authenticates. Operators who want to hide the vault list from unauthenticated callers can set `discovery: disabled` in `~/.parachute/config.yaml` to make `/vaults/list` return 404.
|
|
170
|
+
**Listing vaults from a client.** The authenticated `GET /vaults` endpoint returns full vault metadata. The public `GET /vaults/list` endpoint returns names only, no metadata, no auth required — this is what Parachute Daily's vault picker calls before the user authenticates. Operators who want to hide the vault list from unauthenticated callers can set `discovery: disabled` in `~/.parachute/vault/config.yaml` to make `/vaults/list` return 404.
|
|
166
171
|
|
|
167
172
|
## CLI
|
|
168
173
|
|
|
169
174
|
```bash
|
|
170
175
|
# Setup
|
|
171
|
-
parachute
|
|
172
|
-
parachute
|
|
173
|
-
parachute
|
|
174
|
-
parachute
|
|
176
|
+
parachute-vault init # one-command setup (idempotent — safe to re-run)
|
|
177
|
+
parachute-vault status # check what's running
|
|
178
|
+
parachute-vault doctor # diagnose install/config issues (see Troubleshooting)
|
|
179
|
+
parachute-vault url # print the local server URL (for scripts)
|
|
175
180
|
parachute --version # print the installed version (aliases: -v, version)
|
|
176
|
-
parachute
|
|
177
|
-
parachute
|
|
178
|
-
parachute
|
|
181
|
+
parachute-vault uninstall # remove daemon + MCP entry; keeps user data
|
|
182
|
+
parachute-vault uninstall --wipe # ...and also remove vaults, .env, config.yaml, logs
|
|
183
|
+
parachute-vault uninstall --yes --wipe # scripted destructive wipe (prints an audit line)
|
|
179
184
|
|
|
180
185
|
# Vaults
|
|
181
|
-
parachute
|
|
182
|
-
parachute
|
|
183
|
-
parachute
|
|
184
|
-
parachute
|
|
186
|
+
parachute-vault create work # create a new vault
|
|
187
|
+
parachute-vault list # list all vaults (alias: `ls`)
|
|
188
|
+
parachute-vault remove work --yes # delete a vault (alias: `rm`)
|
|
189
|
+
parachute-vault mcp-install # (re)write the ~/.claude.json MCP entry for the default vault
|
|
185
190
|
|
|
186
191
|
# OAuth — owner password + 2FA
|
|
187
|
-
parachute
|
|
188
|
-
parachute
|
|
189
|
-
parachute
|
|
190
|
-
parachute
|
|
191
|
-
parachute
|
|
192
|
-
parachute
|
|
192
|
+
parachute-vault set-password # set/change the owner password (OAuth consent page)
|
|
193
|
+
parachute-vault set-password --clear # remove the owner password (falls back to vault-token auth)
|
|
194
|
+
parachute-vault 2fa status # show 2FA state + remaining backup codes
|
|
195
|
+
parachute-vault 2fa enroll # enroll TOTP (shows QR + prints one-time backup codes)
|
|
196
|
+
parachute-vault 2fa disable # disable 2FA (requires password or TOTP/backup code)
|
|
197
|
+
parachute-vault 2fa backup-codes # regenerate backup codes (invalidates the old set)
|
|
193
198
|
|
|
194
199
|
# Tokens
|
|
195
|
-
parachute
|
|
196
|
-
parachute
|
|
197
|
-
parachute
|
|
198
|
-
parachute
|
|
199
|
-
parachute
|
|
200
|
-
parachute
|
|
201
|
-
parachute
|
|
200
|
+
parachute-vault tokens # list all tokens across all vaults
|
|
201
|
+
parachute-vault tokens create # full-access token in the default vault
|
|
202
|
+
parachute-vault tokens create --vault work # ...in a specific vault
|
|
203
|
+
parachute-vault tokens create --read # read-only token
|
|
204
|
+
parachute-vault tokens create --expires 30d # expiring token (N{h|d|w|m|y})
|
|
205
|
+
parachute-vault tokens create --label mobile # labeled token
|
|
206
|
+
parachute-vault tokens revoke <token-id> # revoke (default vault; add --vault to target)
|
|
202
207
|
|
|
203
208
|
# Obsidian
|
|
204
|
-
parachute
|
|
205
|
-
parachute
|
|
206
|
-
parachute
|
|
207
|
-
parachute
|
|
209
|
+
parachute-vault import ~/Obsidian/MyVault # import into default vault
|
|
210
|
+
parachute-vault import ~/Obsidian/Work --vault work # import into a specific vault
|
|
211
|
+
parachute-vault import <path> --dry-run # preview without importing
|
|
212
|
+
parachute-vault export ./output --vault work # export a specific vault
|
|
208
213
|
|
|
209
214
|
# Config
|
|
210
|
-
parachute
|
|
211
|
-
parachute
|
|
212
|
-
parachute
|
|
213
|
-
parachute
|
|
215
|
+
parachute-vault config # show current configuration
|
|
216
|
+
parachute-vault config set KEY value # set an env var (e.g. PORT=1940)
|
|
217
|
+
parachute-vault config unset KEY # remove an env var
|
|
218
|
+
parachute-vault restart # apply config changes (bounces the daemon)
|
|
214
219
|
|
|
215
220
|
# Server
|
|
216
|
-
parachute
|
|
217
|
-
parachute
|
|
221
|
+
parachute-vault serve # run the server in the foreground (no daemon)
|
|
222
|
+
parachute-vault logs # stream vault.log + vault.err (tail -f)
|
|
218
223
|
|
|
219
224
|
# Backup
|
|
220
|
-
parachute
|
|
221
|
-
parachute
|
|
222
|
-
parachute
|
|
225
|
+
parachute-vault backup # one-shot backup to configured destinations
|
|
226
|
+
parachute-vault backup --schedule daily # hourly | daily | weekly | manual (macOS launchd)
|
|
227
|
+
parachute-vault backup status # schedule, last run, destinations, next run
|
|
223
228
|
```
|
|
224
229
|
|
|
225
230
|
## MCP tools (9)
|
|
@@ -234,7 +239,7 @@ parachute vault backup status # schedule, last run, destination
|
|
|
234
239
|
Each vault teaches AI agents how to use it:
|
|
235
240
|
|
|
236
241
|
```yaml
|
|
237
|
-
# ~/.parachute/vaults/default/vault.yaml
|
|
242
|
+
# ~/.parachute/vault/vaults/default/vault.yaml
|
|
238
243
|
name: default
|
|
239
244
|
description: |
|
|
240
245
|
Personal knowledge vault. Tags in use: daily, voice, reader, project.
|
|
@@ -252,9 +257,9 @@ Sent as the MCP server instruction at session start.
|
|
|
252
257
|
### Obsidian import/export
|
|
253
258
|
|
|
254
259
|
```bash
|
|
255
|
-
parachute
|
|
256
|
-
parachute
|
|
257
|
-
parachute
|
|
260
|
+
parachute-vault create research
|
|
261
|
+
parachute-vault import ~/Obsidian/Research --vault research
|
|
262
|
+
parachute-vault export ./output --vault research
|
|
258
263
|
```
|
|
259
264
|
|
|
260
265
|
Imports: frontmatter → metadata, `#tags` → tags table, `[[wikilinks]]` → links table, file paths → note.path. Skips duplicates.
|
|
@@ -265,7 +270,7 @@ Note paths work like Obsidian file paths (`Projects/Parachute/README`). Normaliz
|
|
|
265
270
|
|
|
266
271
|
### Webhook triggers
|
|
267
272
|
|
|
268
|
-
Declarative config-driven webhooks that fire when a note mutation matches a predicate. Configured in `~/.parachute/config.yaml`:
|
|
273
|
+
Declarative config-driven webhooks that fire when a note mutation matches a predicate. Configured in `~/.parachute/vault/config.yaml`:
|
|
269
274
|
|
|
270
275
|
```yaml
|
|
271
276
|
triggers:
|
|
@@ -319,18 +324,39 @@ triggers:
|
|
|
319
324
|
|
|
320
325
|
Webhook servers (scribe, narrate) are stateless — they don't need vault's API key.
|
|
321
326
|
|
|
327
|
+
### On-upload transcription
|
|
328
|
+
|
|
329
|
+
The trigger system above handles the "tag a note and let a webhook fill in content later" shape. Clients that already know at upload time that an audio file should be transcribed can take a shorter path: `POST /api/notes/{id}/attachments` with `{transcribe: true}` in the body. The vault stamps the attachment with `transcribe_status: "pending"` and the note with `transcribe_stub: true`, then a background worker drains the queue FIFO.
|
|
330
|
+
|
|
331
|
+
Enable the worker by pointing the server at a Whisper-compatible endpoint:
|
|
332
|
+
|
|
333
|
+
```
|
|
334
|
+
SCRIBE_URL=http://localhost:3200
|
|
335
|
+
SCRIBE_TOKEN=optional-bearer-token
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
The worker POSTs audio as multipart to `${SCRIBE_URL}/v1/audio/transcriptions` and expects `{ text: string }` back. On success it replaces the `_Transcript pending._` placeholder in the note body (or the whole body if the placeholder is absent), clears `transcribe_stub`, and records `transcript` + `transcribe_done_at` on the attachment. If the user edited the note and cleared `transcribe_stub` before the transcript landed, the note is left alone — but the transcript is still stored on the attachment. Failures retry with exponential backoff up to three attempts before flipping `transcribe_status` to `"failed"`.
|
|
339
|
+
|
|
340
|
+
Per-vault `audio_retention` controls what happens to the audio file on disk once the worker reaches a terminal state. It's readable and mutable at runtime via `GET` / `PATCH /api/vault` (under `config.audio_retention`), or by hand-editing `vault.yaml`.
|
|
341
|
+
|
|
342
|
+
- `keep` (default) — leave the file on disk indefinitely.
|
|
343
|
+
- `until_transcribed` — unlink on successful transcription; on failure the file is kept so you can retry or re-upload.
|
|
344
|
+
- `never` — unlink on any terminal state, **including failure**. Users who opt in accept that losing a bad transcription also loses the source audio. The file stays during mid-queue retries so in-flight attempts still have something to send.
|
|
345
|
+
|
|
346
|
+
In every mode the attachment row (and any stored transcript) is preserved; only the file on disk is affected.
|
|
347
|
+
|
|
322
348
|
### Backing up your vault
|
|
323
349
|
|
|
324
|
-
Your vault is just SQLite DBs + a handful of YAML files under `~/.parachute/`. `parachute
|
|
350
|
+
Your vault is just SQLite DBs + a handful of YAML files under `~/.parachute/vault/`. `parachute-vault backup` snapshots everything into a single timestamped tarball, for a one-shot or a scheduled run.
|
|
325
351
|
|
|
326
352
|
```bash
|
|
327
|
-
parachute
|
|
328
|
-
parachute
|
|
329
|
-
parachute
|
|
330
|
-
parachute
|
|
353
|
+
parachute-vault backup # one-shot — snapshot + ship to destinations
|
|
354
|
+
parachute-vault backup --schedule daily # register a launchd agent (macOS)
|
|
355
|
+
parachute-vault backup --schedule manual # stop scheduled backups
|
|
356
|
+
parachute-vault backup status # schedule, last run, destinations, next run
|
|
331
357
|
```
|
|
332
358
|
|
|
333
|
-
Configure destinations in `~/.parachute/config.yaml`:
|
|
359
|
+
Configure destinations in `~/.parachute/vault/config.yaml`:
|
|
334
360
|
|
|
335
361
|
```yaml
|
|
336
362
|
backup:
|
|
@@ -358,11 +384,11 @@ A snapshot that qualifies for multiple tiers is kept once. Set any tier to `0` t
|
|
|
358
384
|
|
|
359
385
|
**What's in a snapshot**: atomic `VACUUM INTO` copies of every `vaults/<name>/vault.db`, your `config.yaml`, and each vault's `vault.yaml`, bundled as `parachute-backup-<timestamp>.tar.gz`. Safe under concurrent reads/writes — no need to stop the daemon.
|
|
360
386
|
|
|
361
|
-
**Restore**: extract the tarball into a fresh `~/.parachute/` and run `parachute
|
|
387
|
+
**Restore**: extract the tarball into a fresh `~/.parachute/vault/` and run `parachute-vault init` to re-register the daemon. The DBs and configs drop in place; you don't need any special restore command (for now — a dedicated `vault restore` is coming soon).
|
|
362
388
|
|
|
363
389
|
Destination kinds shipping in this release: `local` (any filesystem path — including iCloud Drive, a mounted external disk, or an rsync/Syncthing-backed folder). `s3`, `rsync`, and `cloud` destinations are planned but not yet implemented.
|
|
364
390
|
|
|
365
|
-
On Linux, scheduled runs via systemd timers are a follow-up; for now `parachute
|
|
391
|
+
On Linux, scheduled runs via systemd timers are a follow-up; for now `parachute-vault backup` works on Linux but you'll need to wire the cron yourself.
|
|
366
392
|
|
|
367
393
|
### View endpoint
|
|
368
394
|
|
|
@@ -373,29 +399,35 @@ Serve notes as clean HTML pages at `/view/:noteId`:
|
|
|
373
399
|
- **Custom tag**: set `published_tag` in vault.yaml to use a different tag name (default: `publish`).
|
|
374
400
|
|
|
375
401
|
```yaml
|
|
376
|
-
# ~/.parachute/vaults/default/vault.yaml
|
|
402
|
+
# ~/.parachute/vault/vaults/default/vault.yaml
|
|
377
403
|
published_tag: public
|
|
378
404
|
```
|
|
379
405
|
|
|
380
406
|
## REST API
|
|
381
407
|
|
|
408
|
+
Every vault-touching path is scoped under `/vault/{name}/` — substitute your vault name (e.g. `default`) for `{name}`:
|
|
409
|
+
|
|
382
410
|
```
|
|
383
|
-
GET/POST /api/notes
|
|
384
|
-
GET/PATCH/DEL /api/notes/:idOrPath
|
|
385
|
-
GET/POST /api/notes/:id/attachments
|
|
386
|
-
GET /api/tags
|
|
387
|
-
GET/PUT/DEL /api/tags/:name
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
GET
|
|
392
|
-
POST /
|
|
393
|
-
GET /
|
|
394
|
-
|
|
411
|
+
GET/POST /vault/{name}/api/notes query or create notes
|
|
412
|
+
GET/PATCH/DEL /vault/{name}/api/notes/:idOrPath read, update, delete a single note
|
|
413
|
+
GET/POST /vault/{name}/api/notes/:id/attachments list or add attachments
|
|
414
|
+
GET /vault/{name}/api/tags list tags (?include_schema=true for schemas)
|
|
415
|
+
GET/PUT/DEL /vault/{name}/api/tags/:name get, update, or delete a tag
|
|
416
|
+
POST /vault/{name}/api/tags/:name/rename atomic rename (409 if target_exists)
|
|
417
|
+
POST /vault/{name}/api/tags/merge atomic multi-source merge into a target tag
|
|
418
|
+
GET /vault/{name}/api/find-path?source=...&target=... shortest path between two notes
|
|
419
|
+
GET/PATCH /vault/{name}/api/vault vault info (get or update description)
|
|
420
|
+
POST /vault/{name}/api/storage/upload upload file (audio/image)
|
|
421
|
+
GET /vault/{name}/api/storage/:path download file
|
|
422
|
+
POST /vault/{name}/mcp MCP endpoint (per-vault session)
|
|
423
|
+
* /vault/{name}/oauth/{register,authorize,token} OAuth 2.1 + PKCE flow
|
|
424
|
+
* /vault/{name}/.well-known/oauth-* RFC 8414 / RFC 9728 discovery
|
|
425
|
+
GET /vault/{name}/view/:idOrPath render note as HTML (public or auth)
|
|
426
|
+
GET /vaults authenticated: full metadata
|
|
427
|
+
GET /vaults/list public: vault names only (unless discovery: disabled)
|
|
428
|
+
GET /health health check
|
|
395
429
|
```
|
|
396
430
|
|
|
397
|
-
Per-vault routes at `/vaults/{name}/api/...`, `/vaults/{name}/mcp`, and `/vaults/{name}/view/:idOrPath`.
|
|
398
|
-
|
|
399
431
|
## Data model
|
|
400
432
|
|
|
401
433
|
```
|
|
@@ -412,24 +444,24 @@ Metadata is a JSON column. Vaults start blank — no predefined tags or schema.
|
|
|
412
444
|
|
|
413
445
|
**All API and MCP requests require a valid API key.** No exceptions — localhost gets no special treatment.
|
|
414
446
|
|
|
415
|
-
For wiring up an AI client (Claude Code, Claude Desktop, Parachute Daily), see [Connecting a client](#connecting-a-client) above. This section covers token-level details: how to pass a key, how to manage tokens, and which endpoints are public by design (`/health`, published notes at `/view/:id`).
|
|
447
|
+
For wiring up an AI client (Claude Code, Claude Desktop, Parachute Daily), see [Connecting a client](#connecting-a-client) above. This section covers token-level details: how to pass a key, how to manage tokens, and which endpoints are public by design (`/health`, `/vaults/list`, published notes at `/vault/{name}/view/:id`).
|
|
416
448
|
|
|
417
449
|
### Passing the key
|
|
418
450
|
|
|
419
451
|
Tokens come in two shapes. Both work interchangeably at every authenticated endpoint:
|
|
420
452
|
|
|
421
|
-
- `pvt_...` — per-vault scoped tokens (the modern format; what `vault init` mints, what OAuth issues, what `parachute
|
|
453
|
+
- `pvt_...` — per-vault scoped tokens (the modern format; what `vault init` mints, what OAuth issues, what `parachute-vault tokens create` produces)
|
|
422
454
|
- `pvk_...` — legacy global API keys from `config.yaml` (still honored for existing deployments)
|
|
423
455
|
|
|
424
456
|
```bash
|
|
425
457
|
# Header (preferred)
|
|
426
|
-
curl -H "Authorization: Bearer pvt_..." http://localhost:1940/api/notes
|
|
458
|
+
curl -H "Authorization: Bearer pvt_..." http://localhost:1940/vault/default/api/notes
|
|
427
459
|
|
|
428
460
|
# Alternative header
|
|
429
|
-
curl -H "X-API-Key: pvt_..." http://localhost:1940/api/notes
|
|
461
|
+
curl -H "X-API-Key: pvt_..." http://localhost:1940/vault/default/api/notes
|
|
430
462
|
|
|
431
463
|
# Query param (for /view endpoint only — convenient for browsers)
|
|
432
|
-
curl http://localhost:1940/view/noteId?key=pvt_...
|
|
464
|
+
curl http://localhost:1940/vault/default/view/noteId?key=pvt_...
|
|
433
465
|
```
|
|
434
466
|
|
|
435
467
|
### Token management
|
|
@@ -442,12 +474,12 @@ Per-vault tokens with two permission levels:
|
|
|
442
474
|
| `read` | Query, list, find-path, vault-info only |
|
|
443
475
|
|
|
444
476
|
```bash
|
|
445
|
-
parachute
|
|
446
|
-
parachute
|
|
447
|
-
parachute
|
|
448
|
-
parachute
|
|
449
|
-
parachute
|
|
450
|
-
parachute
|
|
477
|
+
parachute-vault tokens # list all tokens
|
|
478
|
+
parachute-vault tokens create --vault work # full-access token
|
|
479
|
+
parachute-vault tokens create --vault work --read # read-only
|
|
480
|
+
parachute-vault tokens create --vault work --expires 30d # with expiry
|
|
481
|
+
parachute-vault tokens create --vault work --label phone # labeled token
|
|
482
|
+
parachute-vault tokens revoke <token-id> --vault work # revoke
|
|
451
483
|
```
|
|
452
484
|
|
|
453
485
|
Tokens are shown once at creation — save them immediately. SHA-256 hashed at rest.
|
|
@@ -456,9 +488,10 @@ Legacy API keys (`pvk_...`) from config.yaml still work at runtime but the `vaul
|
|
|
456
488
|
|
|
457
489
|
### Public endpoints
|
|
458
490
|
|
|
459
|
-
|
|
491
|
+
Three endpoints work without auth:
|
|
460
492
|
- `GET /health` — returns `{ status: "ok" }` (no sensitive data)
|
|
461
|
-
- `GET /
|
|
493
|
+
- `GET /vaults/list` — vault names only (set `discovery: disabled` in `config.yaml` to hide)
|
|
494
|
+
- `GET /vault/{name}/view/:noteId` — serves published notes only (returns 404 for unpublished)
|
|
462
495
|
|
|
463
496
|
## Network security
|
|
464
497
|
|
|
@@ -479,7 +512,7 @@ For remote access, always use a TLS-terminating proxy:
|
|
|
479
512
|
|
|
480
513
|
## Troubleshooting
|
|
481
514
|
|
|
482
|
-
### `parachute
|
|
515
|
+
### `parachute-vault doctor` is your first stop
|
|
483
516
|
|
|
484
517
|
`doctor` inspects the install and prints one line per check with a status (`✓` pass, `!` warn, `✗` fail) and, when relevant, a suggested fix. It exits 1 on any `fail` and 0 otherwise. Run it any time something feels off.
|
|
485
518
|
|
|
@@ -487,26 +520,26 @@ The checks, in the order they're emitted:
|
|
|
487
520
|
|
|
488
521
|
| Check | What it verifies | Typical fix when failing |
|
|
489
522
|
|---|---|---|
|
|
490
|
-
| server-path pointer | `~/.parachute/server-path` exists, is non-empty, and points at a `src/server.ts` that actually exists. This is where the stale-path failure after a repo move shows up first. | `parachute
|
|
491
|
-
| wrapper script | `~/.parachute/start.sh` exists. Without it, launchd / systemd has nothing to exec. | `parachute
|
|
492
|
-
| launchd agent (macOS) / systemd service (Linux) | The daemon is registered and loaded/active. On Linux without systemd, the check is silently skipped. | `parachute
|
|
523
|
+
| server-path pointer | `~/.parachute/vault/server-path` exists, is non-empty, and points at a `src/server.ts` that actually exists. This is where the stale-path failure after a repo move shows up first. | `parachute-vault init` from the current repo location. |
|
|
524
|
+
| wrapper script | `~/.parachute/vault/start.sh` exists. Without it, launchd / systemd has nothing to exec. | `parachute-vault init`. |
|
|
525
|
+
| launchd agent (macOS) / systemd service (Linux) | The daemon is registered and loaded/active. On Linux without systemd, the check is silently skipped. | `parachute-vault restart` or re-run `vault init`. |
|
|
493
526
|
| bun on PATH | `bun` is resolvable via your shell's PATH. Not required once the daemon is installed (`start.sh` embeds an absolute bun path at init time) but missing bun is the #1 first-time-user failure. | `curl -fsSL https://bun.sh/install \| bash` and restart the shell. |
|
|
494
|
-
| MCP entry in `~/.claude.json` | An entry is present. When it is, two follow-ups: the URL's port matches the running vault's port, and the MCP URL is reachable over HTTP (any response — even 401 — counts as reachable). | `parachute
|
|
495
|
-
| port `1940` availability | Probes via `lsof` / `ss` and classifies: free, held by our daemon (pass), held by a foreign process (warn), or unknown (tool unavailable → check silently omitted). | Stop the conflicting process, or set a different `PORT` in `~/.parachute/.env` and re-run `vault init`. |
|
|
496
|
-
| backup agent (macOS, only when `backup.schedule != manual`) | The scheduled-backup launchd agent is loaded. | `parachute
|
|
497
|
-
| backup destinations (only when `backup.schedule != manual`) | At least one destination is configured; each configured destination is writable. | Edit `~/.parachute/config.yaml` under `backup.destinations`, or fix the path's permissions. |
|
|
527
|
+
| MCP entry in `~/.claude.json` | An entry is present. When it is, two follow-ups: the URL's port matches the running vault's port, and the MCP URL is reachable over HTTP (any response — even 401 — counts as reachable). | `parachute-vault mcp-install` to rewrite the entry, or `parachute-vault restart` if the daemon is down. |
|
|
528
|
+
| port `1940` availability | Probes via `lsof` / `ss` and classifies: free, held by our daemon (pass), held by a foreign process (warn), or unknown (tool unavailable → check silently omitted). | Stop the conflicting process, or set a different `PORT` in `~/.parachute/vault/.env` and re-run `vault init`. |
|
|
529
|
+
| backup agent (macOS, only when `backup.schedule != manual`) | The scheduled-backup launchd agent is loaded. | `parachute-vault backup --schedule <hourly\|daily\|weekly>` to reinstall the agent. |
|
|
530
|
+
| backup destinations (only when `backup.schedule != manual`) | At least one destination is configured; each configured destination is writable. | Edit `~/.parachute/vault/config.yaml` under `backup.destinations`, or fix the path's permissions. |
|
|
498
531
|
|
|
499
532
|
### Common failure modes
|
|
500
533
|
|
|
501
|
-
- **Daemon won't start after a port change.** `~/.parachute/.env` has the new `PORT=...` but the daemon is still trying to bind the old one, or something else already holds the new port. `parachute
|
|
502
|
-
- **MCP entry is stale after moving the repo.** launchd/systemd keeps pointing at the old path. `doctor` flags this as a failed `server.ts at pointer target` check; `parachute
|
|
503
|
-
- **Claude Code shows no vault tools.** Check in order: (1) is the daemon up (`parachute
|
|
504
|
-
- **Claude Desktop / Daily won't connect via OAuth.** If the owner-password prompt was skipped at `vault init`, the consent page falls back to requiring a vault token in place of the password (functional but clunky). Set one now with `parachute
|
|
505
|
-
- **Scheduled backups aren't running.** On macOS: `doctor` flags `backup agent: not loaded` when `schedule` isn't `manual` but the launchd agent is missing — rerun `parachute
|
|
534
|
+
- **Daemon won't start after a port change.** `~/.parachute/vault/.env` has the new `PORT=...` but the daemon is still trying to bind the old one, or something else already holds the new port. `parachute-vault doctor` surfaces both conditions. Fix the holder (or pick a different port) and `parachute-vault restart`.
|
|
535
|
+
- **MCP entry is stale after moving the repo.** launchd/systemd keeps pointing at the old path. `doctor` flags this as a failed `server.ts at pointer target` check; `parachute-vault init` from the new location rewrites the pointer, wrapper, and daemon registration.
|
|
536
|
+
- **Claude Code shows no vault tools.** Check in order: (1) is the daemon up (`parachute-vault status`)? (2) does `~/.claude.json` have a `parachute-vault` entry with both `url` and a valid `Authorization` header? (3) does the URL's vault name match an existing vault? `parachute-vault doctor` catches the first two. A missing or stale `Authorization` header after a bare `vault mcp-install` is the usual culprit for #2 — see the Claude Code section of [Connecting a client](#connecting-a-client) for how to rewrite it.
|
|
537
|
+
- **Claude Desktop / Daily won't connect via OAuth.** If the owner-password prompt was skipped at `vault init`, the consent page falls back to requiring a vault token in place of the password (functional but clunky). Set one now with `parachute-vault set-password`. If 2FA is enrolled, have your authenticator app ready before starting the flow; lost TOTP access recovers via the backup codes printed at enrollment.
|
|
538
|
+
- **Scheduled backups aren't running.** On macOS: `doctor` flags `backup agent: not loaded` when `schedule` isn't `manual` but the launchd agent is missing — rerun `parachute-vault backup --schedule <freq>` to reinstall it. On Linux: systemd-timer support for backup isn't shipped yet, so `--schedule daily` silently skips the scheduler. Run `parachute-vault backup` from cron (or similar) until that lands.
|
|
506
539
|
|
|
507
540
|
### Getting help
|
|
508
541
|
|
|
509
|
-
If `doctor` is all-green but something still isn't working, capture the output alongside `parachute
|
|
542
|
+
If `doctor` is all-green but something still isn't working, capture the output alongside `parachute-vault status` and open an issue at <https://github.com/ParachuteComputer/parachute-vault/issues>. Redact tokens from any logs before attaching.
|
|
510
543
|
|
|
511
544
|
## Deployment
|
|
512
545
|
|
|
@@ -545,7 +578,7 @@ sudo cloudflared service install
|
|
|
545
578
|
sudo systemctl start cloudflared
|
|
546
579
|
```
|
|
547
580
|
|
|
548
|
-
Then point any client at `https://vault.yourdomain.com/
|
|
581
|
+
Then point any client at `https://vault.yourdomain.com/vault/{name}/mcp`. See [Connecting a client → Claude Desktop (OAuth)](#claude-desktop-oauth) — the flow is identical to the local case once the URL is remote; the browser-based OAuth handshake makes the connection without pasting a bearer token.
|
|
549
582
|
|
|
550
583
|
### Remote access via Tailscale Funnel
|
|
551
584
|
|
|
@@ -572,7 +605,7 @@ tailscale funnel reset
|
|
|
572
605
|
The resulting URL is `https://<your-device>.<your-tailnet>.ts.net/` — `tailscale funnel status` prints it verbatim. You can also use ports `8443` or `10000` via `--https=<port>`; no other public ports are available to Funnel.
|
|
573
606
|
|
|
574
607
|
Point any MCP client at the Tailscale URL:
|
|
575
|
-
- Claude Desktop → Settings → Integrations → Add MCP → `https://<your-device>.<your-tailnet>.ts.net/
|
|
608
|
+
- Claude Desktop → Settings → Integrations → Add MCP → `https://<your-device>.<your-tailnet>.ts.net/vault/{name}/mcp` (leave the Authorization field empty; the OAuth flow will handle it — see [Connecting a client](#connecting-a-client)).
|
|
576
609
|
- Parachute Daily → enter the base URL `https://<your-device>.<your-tailnet>.ts.net`, pick the vault, tap Connect.
|
|
577
610
|
|
|
578
611
|
**Cloudflare vs Tailscale, at a glance.** Pick Cloudflare when you want a custom domain, bandwidth headroom for heavier traffic, or to share the vault with people who aren't on your tailnet. Pick Tailscale when you're already running it, you're fine with a `*.ts.net` URL, and you want the setup to fit in two commands.
|