@openparachute/vault 0.6.5-rc.9 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.parachute/module.json +1 -0
  2. package/core/src/core.test.ts +7 -7
  3. package/core/src/mcp.ts +1 -1
  4. package/core/src/schema.ts +5 -5
  5. package/core/src/seed-packs.test.ts +222 -56
  6. package/core/src/seed-packs.ts +334 -70
  7. package/core/src/tag-hierarchy.ts +1 -1
  8. package/core/src/tag-schemas.ts +2 -2
  9. package/core/src/types.ts +1 -1
  10. package/package.json +1 -1
  11. package/src/admin-spa.ts +2 -1
  12. package/src/auth.ts +1 -1
  13. package/src/cli.ts +317 -53
  14. package/src/export-watch.ts +1 -1
  15. package/src/live-frame-parity.test.ts +201 -0
  16. package/src/module-manifest.ts +8 -0
  17. package/src/onboarding-seed.test.ts +82 -20
  18. package/src/onboarding-seed.ts +2 -2
  19. package/src/routes.ts +3 -3
  20. package/src/routing.test.ts +2 -2
  21. package/src/routing.ts +18 -6
  22. package/src/self-register.test.ts +19 -0
  23. package/src/self-register.ts +6 -1
  24. package/src/server.ts +56 -0
  25. package/src/services-manifest.ts +8 -0
  26. package/src/subscriptions.ts +100 -42
  27. package/src/tag-scope.ts +3 -3
  28. package/src/test-support/live-frame-corpus.ts +72 -0
  29. package/src/token-store.ts +2 -2
  30. package/src/transcription/build.test.ts +86 -5
  31. package/src/transcription/build.ts +87 -7
  32. package/src/transcription/capability.test.ts +25 -0
  33. package/src/transcription/capability.ts +27 -2
  34. package/src/transcription/download.test.ts +101 -0
  35. package/src/transcription/download.ts +71 -0
  36. package/src/transcription/install-python.test.ts +366 -0
  37. package/src/transcription/install-python.ts +471 -0
  38. package/src/transcription/providers/onnx-asr.test.ts +229 -0
  39. package/src/transcription/providers/onnx-asr.ts +239 -0
  40. package/src/transcription/providers/parakeet-mlx.test.ts +290 -0
  41. package/src/transcription/providers/parakeet-mlx.ts +242 -0
  42. package/src/transcription/select.test.ts +166 -1
  43. package/src/transcription/select.ts +234 -1
  44. package/src/transcription/tiers.test.ts +197 -0
  45. package/src/transcription/tiers.ts +184 -0
  46. package/src/vault-create.test.ts +19 -14
  47. package/src/vault.test.ts +1 -1
  48. package/src/ws-server.ts +408 -0
  49. package/src/ws-subscribe.test.ts +474 -0
  50. package/src/ws-subscribe.ts +242 -0
  51. package/src/subscribe.test.ts +0 -609
  52. package/src/subscribe.ts +0 -248
@@ -5,8 +5,9 @@
5
5
  * A pack is a named bundle of `tags` (upserted) + `notes` (created only when
6
6
  * no note already lives at the path). Three packs ship today:
7
7
  *
8
- * - `welcome` — the person-voiced three-note welcome web + the `capture`
9
- * tag the Notes surface expects. Default-seeded on vault creation.
8
+ * - `welcome` — the five-guide welcome ring (Welcome, Capture anything, Tags
9
+ * and the graph, Connect your AI, Yours to keep) + the `capture` / `guide`
10
+ * tags the Notes surface expects. Default-seeded on creation.
10
11
  * - `getting-started` — the AI-facing start-here guide (SKILL.md-style
11
12
  * doctrine addressed to a connected assistant). Default-seeded.
12
13
  * - `surface-starter` — the living starter guide for building a custom
@@ -14,6 +15,12 @@
14
15
  * Surface Starter is out of the default seed) — added on demand via
15
16
  * `parachute-vault add-pack surface-starter` or a console affordance.
16
17
  *
18
+ * Guides are the vault's skill files — the Parachute equivalent of a
19
+ * `SKILL.md`. They're tagged `#guide` (GUIDE_TAG): notes that teach how this
20
+ * vault works and how to work it, for you and your AI alike. Some (the welcome
21
+ * ring) are voiced to the person; some (Getting Started) to the assistant; all
22
+ * are plain markdown either reads — so no per-guide audience field is needed.
23
+ *
17
24
  * This module is the single source of truth for pack content across BOTH
18
25
  * runtimes: the bun vault (`src/onboarding-seed.ts` default seed + the
19
26
  * `add-pack` CLI verb) and the cloud vault DO (parachute-cloud
@@ -26,23 +33,63 @@
26
33
  * Getting Started / Surface Starter path + content constants.
27
34
  */
28
35
 
29
- import type { Store } from "./types.ts";
36
+ import type { Store, TagFieldSchema } from "./types.ts";
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // Default vault descriptions — the single source of truth
40
+ // ---------------------------------------------------------------------------
41
+
42
+ /**
43
+ * The `vault-info` description a **brand-new** vault ships with. It's the first
44
+ * thing a connected AI reads about the vault, so it doubles as a nudge: orient
45
+ * the person, read the Getting Started brief, do one small real thing, and — once
46
+ * real structure lands — REPLACE this text with a current picture of the vault.
47
+ *
48
+ * Exported as the single source of truth; the runtimes (bun vault default seed +
49
+ * cloud vault DO) consume it in a follow-up PR. Not wired into `applySeedPack`
50
+ * here — a description is vault metadata, not a pack note.
51
+ */
52
+ export const DEFAULT_VAULT_DESCRIPTION =
53
+ 'This is a brand-new personal vault — just its starter guides so far; the person it belongs to is probably new to Parachute. Before helping them set it up, read the "Getting Started" note — it\'s your onboarding brief. Short version: start with a conversation, not a filing system; learn how they want to use this and where their notes live today; do one small real thing first. When the vault has real structure, replace this description (vault-info { description: "..." }) with a current picture of what lives here and how to work it — so every future session, yours or another AI\'s, starts oriented.';
54
+
55
+ /**
56
+ * The `vault-info` description a vault gets when it was **imported/restored** —
57
+ * it arrived with content. Flips the AI's first job from seed-it to learn-it:
58
+ * orient over the existing shape, reflect it back, and only then replace this
59
+ * text with a current picture.
60
+ */
61
+ export const IMPORTED_VAULT_DESCRIPTION =
62
+ 'This vault was imported — it arrived with its own notes, tags, and history. Orient before you write: vault-info { include_stats: true }, list-tags, read a sample, and read "Getting Started" if present. Your first job is to learn the shape that\'s already here and reflect it back to the person — not to propose new structure cold. When you understand it, replace this description (vault-info { description: "..." }) with a current picture.';
30
63
 
31
64
  // ---------------------------------------------------------------------------
32
65
  // Pack shape
33
66
  // ---------------------------------------------------------------------------
34
67
 
35
- /** A tag declaration a pack upserts (identity row, not a schema migration). */
68
+ /**
69
+ * A tag declaration a pack upserts (identity row, not a schema migration).
70
+ *
71
+ * `fields` carries an optional typed-metadata schema — the same
72
+ * `Record<field, { type, enum?, indexed? }>` shape `update-tag` accepts — so a
73
+ * pack can declare a schema-carrying tag. The applier passes it straight
74
+ * through to `upsertTagRecord`.
75
+ */
36
76
  export interface SeedPackTag {
37
77
  name: string;
38
78
  description: string;
39
79
  parent_names?: string[];
80
+ fields?: Record<string, TagFieldSchema>;
40
81
  }
41
82
 
42
- /** A note a pack seeds — created only when no note exists at `path`. */
83
+ /**
84
+ * A note a pack seeds — created only when no note exists at `path`. `tags` +
85
+ * `metadata` are applied at create time (both runtimes' `createNote` accepts
86
+ * them); a guide note carries `tags: ["guide"]`.
87
+ */
43
88
  export interface SeedPackNote {
44
89
  path: string;
45
90
  content: string;
91
+ tags?: string[];
92
+ metadata?: Record<string, unknown>;
46
93
  }
47
94
 
48
95
  /** A named bundle of starter tags + notes. */
@@ -83,58 +130,173 @@ export const NOTES_REQUIRED_TAGS: ReadonlyArray<SeedPackTag> = [
83
130
  },
84
131
  ];
85
132
 
133
+ /**
134
+ * The `guide` tag — the vault's skill-file tag. Guides are the Parachute
135
+ * equivalent of a `SKILL.md`: notes that teach how this vault works and how to
136
+ * work it, for you and your AI alike. Plain markdown, so either reads them.
137
+ *
138
+ * Declared by every pack that ships guide notes (welcome / getting-started /
139
+ * surface-starter). The upserts converge on one row, so each pack stays
140
+ * self-sufficient — applying any one of them alone still lands the tag.
141
+ */
142
+ export const GUIDE_TAG: SeedPackTag = {
143
+ name: "guide",
144
+ description:
145
+ "Guides — the vault's skill files. Notes that teach how this vault works and how to work it, for you and your AI alike. Plain markdown, so either reads them.",
146
+ };
147
+
86
148
  export const WELCOME_PATH = "Welcome to your vault 🪂";
87
- export const TRY_LINKING_PATH = "Try linking notes";
149
+ export const CAPTURE_ANYTHING_PATH = "Capture anything";
150
+ export const TAGS_GRAPH_PATH = "Tags and the graph";
88
151
  export const CONNECT_AI_PATH = "Connect your AI";
152
+ export const YOURS_TO_KEEP_PATH = "Yours to keep";
89
153
 
90
154
  /**
91
- * Build the `welcome` pack: a three-note welcome web (welcome try-linking →
92
- * back, connect-AI welcome) so the graph view shows a connected structure
93
- * from minute one, plus the `capture` tag above. The notes are ordinary
94
- * notes no special flags, deletable like anything else.
155
+ * Build the `welcome` pack: the five-guide welcome ring Welcome, Capture
156
+ * anything, Tags and the graph, Connect your AI, Yours to keep — plus the
157
+ * `capture` / `guide` tags. The guides are ordinary notes tagged `#guide`
158
+ * (the vault's skill-file tag). They form a
159
+ * small linked web (Welcome → all four; the rest chain 2→3→4→5→1; Connect
160
+ * your AI also links the AI-facing [[Getting Started]]) so the graph view
161
+ * shows a connected structure from minute one. Deletable like anything else.
95
162
  *
96
- * Content is ported EXACTLY from parachute-cloud workers/vault/src/welcome.ts
97
- * (person-voiced, addressed to the everyday user). `consoleOrigin` is the
98
- * origin of the operator's console (the cloud console, or a hub portal) —
99
- * when known, the Connect-your-AI note names it; when omitted (the bun vault
100
- * seeds at create time, often pre-expose, and must never bake in a loopback
101
- * origin) the line stays generic.
163
+ * These five bodies are the ratified guides (already live at
164
+ * parachute.computer/guides). `consoleOrigin` is the origin of the operator's
165
+ * console (the cloud console, or a hub portal) — when known, the Connect-your-AI
166
+ * note names it; when omitted (the bun vault seeds at create time, often
167
+ * pre-expose, and must never bake in a loopback origin) the line stays generic.
102
168
  */
103
169
  export function welcomePack(opts: { consoleOrigin?: string } = {}): SeedPack {
104
170
  const consoleLine = opts.consoleOrigin
105
171
  ? `Grab the connection URL from your console at ${opts.consoleOrigin}.`
106
172
  : `Grab the connection URL from your console.`;
173
+ const guideNote = { tags: ["guide"] };
107
174
  return {
108
175
  name: "welcome",
109
176
  description:
110
- "A small linked welcome web (three notes) + the capture tag the Notes surface uses. Seeded by default on new vaults.",
111
- tags: NOTES_REQUIRED_TAGS,
177
+ "The five-guide welcome ring (Welcome, Capture anything, Tags and the graph, Connect your AI, Yours to keep) the vault's #guide skill files — plus the capture/guide tags. Seeded by default on new vaults.",
178
+ // capture (Notes surface) + guide (the skill-file tag). Upserts are idempotent.
179
+ tags: [...NOTES_REQUIRED_TAGS, GUIDE_TAG],
112
180
  // `[[wikilinks]]` resolve by note path — pending links auto-resolve when
113
181
  // the target is created, so order only affects how briefly a link sits
114
- // unresolved during the seed.
182
+ // unresolved during the seed. Every [[target]] here resolves to a note the
183
+ // default seed writes (the four siblings + [[Getting Started]] from the
184
+ // getting-started pack) — no dangling links on a fresh vault.
115
185
  notes: [
116
186
  {
117
187
  path: WELCOME_PATH,
188
+ tags: ["guide"],
118
189
  content: `# ${WELCOME_PATH}
119
190
 
120
191
  This vault is yours.
121
- Write anything.
122
- Notes can link to each other, like this: [[${TRY_LINKING_PATH}]].
192
+
193
+ Write anything — ideas, people, plans, wifi passwords. Notes can link to each
194
+ other, like this: [[${CAPTURE_ANYTHING_PATH}]]. Follow that link and keep going — these
195
+ guides form a small web you can wander:
196
+
197
+ - [[${CAPTURE_ANYTHING_PATH}]] — get thoughts in and link them together
198
+ - [[${TAGS_GRAPH_PATH}]] — structure that grows out of what you write
199
+ - [[${CONNECT_AI_PATH}]] — any AI can read and write your vault
200
+ - [[${YOURS_TO_KEEP_PATH}]] — export everything, anytime
201
+
202
+ They're ordinary notes, tagged #guide. Edit them, add to them, or delete the
203
+ lot — the vault is yours, remember.
123
204
  `,
124
205
  },
125
206
  {
126
- path: TRY_LINKING_PATH,
127
- content: `# ${TRY_LINKING_PATH}
207
+ path: CAPTURE_ANYTHING_PATH,
208
+ ...guideNote,
209
+ content: `# ${CAPTURE_ANYTHING_PATH}
210
+
211
+ The one habit that makes a vault work: when a thought strikes, write it down.
212
+ Don't sort it. Don't file it. Let it land.
213
+
214
+ A note can be two words or two pages. The blog idea on a trail, the follow-up
215
+ from a meeting, the dream at 2am — capture first. Organizing can happen later,
216
+ or never; that's what tags and your AI are for.
217
+
218
+ To connect two notes, wrap a note's name in double square brackets:
219
+ [[${WELCOME_PATH}]] is a link back to where you started. If the note
220
+ doesn't exist yet, the link waits patiently and connects the moment it does.
128
221
 
129
- Wrap a note's name in double square brackets to make a wikilink, like this one back to [[${WELCOME_PATH}]].
222
+ That's the whole trick. Storage is easy connection is everything. A linked
223
+ note is a thought your future self, and your AI, can find again.
224
+
225
+ Next: [[${TAGS_GRAPH_PATH}]].
226
+ `,
227
+ },
228
+ {
229
+ path: TAGS_GRAPH_PATH,
230
+ ...guideNote,
231
+ content: `# ${TAGS_GRAPH_PATH}
232
+
233
+ Notes and links come first. Tags come later, when you notice a pattern.
234
+
235
+ A tag answers "what kind of thing is this?" — #person, #recipe, #idea. Tag a
236
+ few notes the same way and a view lights up: all your people, all your recipes,
237
+ everything half-finished. Your vault starts with just two:
238
+
239
+ - #capture — the Notes app puts it on whatever you write or speak in directly,
240
+ marking your own raw words.
241
+ - #guide — these notes.
242
+
243
+ Underneath, everything is one graph: every note a node, every link an edge.
244
+ Open the graph view and watch it grow; ask a connected AI "what's near this
245
+ note?" and it walks the edges for you.
246
+
247
+ The structure isn't a filing system you design on day one. It grows out of what
248
+ you actually write — so don't over-organize an empty vault. Write, link, and
249
+ let the shape appear.
250
+
251
+ Next: [[${CONNECT_AI_PATH}]].
130
252
  `,
131
253
  },
132
254
  {
133
255
  path: CONNECT_AI_PATH,
256
+ ...guideNote,
134
257
  content: `# ${CONNECT_AI_PATH}
135
258
 
136
- Your vault speaks MCP. ${consoleLine}
137
- Start from [[${WELCOME_PATH}]].
259
+ Your vault speaks MCP — an open standard — so any AI can read and write it:
260
+ Claude, ChatGPT, Claude Code, Cursor, or an agent you build. ${consoleLine}
261
+
262
+ Once you're connected, try:
263
+
264
+ - "Read the Getting Started note and help me set this vault up."
265
+ - "What did I want to write about?"
266
+ - "Tag my untagged notes."
267
+
268
+ Your AI sees what you see — the same notes, links, and tags — and what it
269
+ writes lands here, as notes you can read, edit, or delete. There's even a note
270
+ in this vault written for it: [[${GETTING_STARTED_PATH}]], the vault-design brief your
271
+ AI reads so you don't have to.
272
+
273
+ One memory, shared with every AI you choose to connect. That's the point.
274
+
275
+ Next: [[${YOURS_TO_KEEP_PATH}]].
276
+ `,
277
+ },
278
+ {
279
+ path: YOURS_TO_KEEP_PATH,
280
+ ...guideNote,
281
+ content: `# ${YOURS_TO_KEEP_PATH}
282
+
283
+ Everything here — every note, tag, and link — exports as plain markdown files
284
+ with the structure intact. On Parachute Cloud, it's the Export button in your
285
+ console. Self-hosting, it's one command: \`parachute-vault export <dir>\`.
286
+
287
+ What you get is a folder any app can open, and another Parachute can import
288
+ losslessly. Move from our cloud to your own machine, or the other way — same
289
+ software, same format, nothing held hostage between the doors.
290
+
291
+ We build it this way on purpose. A connection you can't leave isn't a
292
+ relationship. You can always leave — which is exactly why you can trust
293
+ staying.
294
+
295
+ Export isn't a feature we grudgingly support; it's the moral center of the
296
+ design. We test it constantly, because a promise you don't test is just a
297
+ hope.
298
+
299
+ Back to [[${WELCOME_PATH}]].
138
300
  `,
139
301
  },
140
302
  ],
@@ -156,24 +318,98 @@ export const SURFACE_STARTER_PATH = "Surface Starter";
156
318
  /**
157
319
  * Body of the seeded `Getting Started` note.
158
320
  *
159
- * Voice: addressed to the connected AI ("you"), practical, SKILL.md-style.
160
- * Covers (a) what a Parachute vault is, (b) tags-vs-paths-vs-schemas design,
161
- * (c) importing existing notes, (d) that it's an adaptable starting point.
162
- * Mentions the Surface Starter PACK by name (no wikilink the note isn't
163
- * seeded by default, and a dangling `[[Surface Starter]]` would sit unresolved
164
- * on every fresh vault).
321
+ * Voice: addressed to the connected AI ("you"), warm and practical, SKILL.md-style.
322
+ * ONE note, two labeled parts:
323
+ * - Part 1 Onboarding the person: lead with a real conversation, leverage
324
+ * prior context, offer to survey their content, do one small real thing
325
+ * first, the data-in menu (import UI / MCP bridge / paste-and-file — NOT the
326
+ * CLI, NOT Obsidian-first), the arrived-with-content branch, and closing the
327
+ * loop by updating the vault description (see DEFAULT_VAULT_DESCRIPTION).
328
+ * - Part 2 — Vault mechanics: the preserved reference — what a vault is + the
329
+ * tool list, tags-vs-paths-vs-schemas, write gotchas, the custom-surface
330
+ * pointer, and "Adapt this note."
331
+ *
332
+ * Deliberately points AIs at the import UI + MCP bridge, never at a
333
+ * `parachute-vault import` CLI (that verb doesn't exist on cloud — the old copy
334
+ * shipped that live bug to every hosted user). Mentions the Surface Starter PACK
335
+ * by name (no wikilink — the note isn't seeded by default, and a dangling
336
+ * `[[Surface Starter]]` would sit unresolved on every fresh vault).
165
337
  */
166
338
  export const GETTING_STARTED_CONTENT = `# Getting Started
167
339
 
168
- This is the **start-here guide** for this Parachute vault — think of it like a
169
- \`SKILL.md\`: practical instructions for setting up and growing the vault. Read it
170
- when you're **getting the vault started** or orienting yourself to it you don't
171
- need to re-read it every session. It's a **starting point, not a script**, and
172
- it's adaptable: edit it (see "Adapt this note") as the vault takes shape.
173
-
174
- When the operator says something like *"help me set up my parachute,"* this is
175
- your brief: design their structure with them, import what they already have, and
176
- shape the vault around how they actually think and work.
340
+ This is the **start-here guide** for this Parachute vault — instructions for a
341
+ connected AI (you), like a \`SKILL.md\`. Read it when the vault is new, when
342
+ you're orienting yourself, or when the person says *"help me set up my
343
+ parachute."* You don't need to re-read it every session. It has two parts:
344
+ **how to onboard the person** (start there if they're new) and **vault
345
+ mechanics** (reference for operating the vault). It's a starting point, not a
346
+ script and it's editable (see "Adapt this note" at the end).
347
+
348
+ ---
349
+
350
+ ## Part 1 — Onboarding the person
351
+
352
+ ### Start with a conversation, not a filing system
353
+
354
+ If this vault is fresh, this is probably the person's first real session with
355
+ it. Don't start building — orient them, briefly, in your own words: this vault
356
+ is **their** memory, one place every AI they choose to connect can read and
357
+ write, and they can export all of it anytime as plain markdown. Then have a
358
+ real conversation:
359
+
360
+ - **How do they imagine using this?** A journal, a project brain, a people
361
+ directory, meeting notes, a commonplace book — or they don't know yet
362
+ (that's fine; capture-first works).
363
+ - **Where do their notes and data live now?** Another app, a pile of markdown,
364
+ another Parachute — or nowhere yet.
365
+ - **What's alive for them right now?** A project, a decision, a trip, a draft.
366
+ The first note should be about something real.
367
+
368
+ **Use what you already know.** If you have memory or context about this person
369
+ from working with them, bring it — propose the two or three kinds of
370
+ information *they specifically* would want here ("you mention a lot of book
371
+ ideas — want a place for those?"). Propose, confirm, then write; never
372
+ bulk-create structure they didn't agree to. You can also **offer to survey**
373
+ what they already have — "want me to look through what you've brought in and
374
+ suggest a few ways to organize it?" — then read a sample and propose, don't
375
+ impose.
376
+
377
+ ### Do one small real thing first
378
+
379
+ The failure mode of every notes tool is over-organizing an empty vault. So:
380
+ **one small real thing this session.** Capture a thought they actually have,
381
+ file one document, or bring in one small batch — then stop and show them what
382
+ happened. Structure grows from real notes (that's Part 2's design vocabulary).
383
+ Setup is a relationship over many sessions, not an install step.
384
+
385
+ Five short guides ship in this vault for the person to read (tagged \`#guide\`):
386
+ [[Welcome to your vault 🪂]], [[Capture anything]], [[Tags and the graph]],
387
+ [[Connect your AI]], and [[Yours to keep]]. Point them there for anything you'd
388
+ otherwise lecture about — they're the return-point between sessions. If a
389
+ session ends mid-setup, leave a breadcrumb (update this note or the vault
390
+ description) so the next one picks up where you left off.
391
+
392
+ ### If this vault already has content
393
+
394
+ Some vaults arrive full — an import, a restore, months of use. Then your first
395
+ job is to **learn it, not seed it**: \`vault-info { include_stats: true }\`,
396
+ \`list-tags\`, read a sample (\`query-notes { search: "..." }\`), then reflect the
397
+ shape back to the person and ask what's missing or wrong. Propose structure
398
+ only once you can describe what's already there.
399
+
400
+ ### Close the loop: describe the vault
401
+
402
+ When the first real structure lands, update the vault description —
403
+ \`vault-info { description: "..." }\` — so every future session (yours or another
404
+ AI's) starts oriented: what this vault is for, its main tags, its conventions.
405
+ The default description says "brand-new vault"; once that stops being true,
406
+ replace it. Keep it a few sentences (the projection carries the schema detail,
407
+ and this note carries the richer conventions — see "Adapt this note"). Bringing
408
+ their existing data in is covered under "Bringing existing notes in" below.
409
+
410
+ ---
411
+
412
+ ## Part 2 — Vault mechanics
177
413
 
178
414
  ## What a Parachute vault is
179
415
 
@@ -267,33 +503,40 @@ A few behaviors worth knowing before you write at scale:
267
503
 
268
504
  (Full design guide, with copy-paste examples: https://parachute.computer/scripting/)
269
505
 
270
- ## Importing existing notes
506
+ ## Bringing existing notes in
507
+
508
+ If the person already keeps notes somewhere, help them bring what matters —
509
+ start with what's alive for them, not the whole archive. What works today:
271
510
 
272
- If the operator already keeps notes (Obsidian, Markdown, etc.), bring them in
273
- rather than starting cold:
511
+ - **Connect the source over MCP (the flexible path).** If their AI client can
512
+ add other MCP servers — a Google Drive server, a Notion server, a filesystem
513
+ server — connect it alongside this vault in the same session. Then read from
514
+ there and \`create-note\` here: a selective, conversational migration, no
515
+ export file needed.
516
+ - **Paste and file.** They paste anything — a doc, a list, an export — and you
517
+ \`create-note\` it (batch mode for many at once).
518
+ - **Import a Parachute export.** Moving between Parachute vaults (cloud ⇄
519
+ self-host) is lossless — on Parachute Cloud, the console's Import button —
520
+ ids, tags, links, schemas, and attachments round-trip.
274
521
 
275
- - **Obsidian / a Markdown folder:** \`parachute-vault import <path>\` preserves
276
- frontmatter, tags, \`[[wikilinks]]\`, and file paths.
277
- - **A portable Parachute export** (a dir with \`.parachute/vault.yaml\`): the same
278
- \`import\` command auto-detects it and does a lossless round-trip (ids, typed
279
- links, tag schemas, attachments).
280
- - **Ad hoc / pasted content:** just \`create-note\` it. Then help the operator tag
281
- and schematize: read a sample of imported notes, propose a small tag
282
- vocabulary, and apply it.
522
+ Bringing a whole Obsidian or Markdown library in is getting easier, and a
523
+ flexible import is on the way; for now the connect-over-MCP and paste paths
524
+ handle it without anyone touching a terminal. Whatever they bring, offer to
525
+ survey it and suggest a first structure — read a sample, propose a small tag
526
+ vocabulary, confirm, then apply. Don't impose structure silently.
283
527
 
284
- After an import, orient yourself: \`vault-info\` for the new schema picture,
285
- \`list-tags\` to see what vocabulary arrived, \`query-notes { search: "..." }\` to
286
- spot-check. Then propose structure — don't impose it silently.
528
+ After bringing content in, orient: \`vault-info\` for the schema picture,
529
+ \`list-tags\` for the vocabulary that arrived, \`query-notes { search: "..." }\`
530
+ to spot-check.
287
531
 
288
532
  ## Later: a custom surface
289
533
 
290
534
  Building a custom UI over the vault (a dashboard, a notes app) is usually **not**
291
- the starting point — get the notes and structure right first. If and when the
292
- operator wants one, add the **Surface Starter** guide to this vault — it's a
293
- seed pack that isn't installed by default. Run
294
- \`parachute-vault add-pack surface-starter\` (or use the console's add-pack
295
- affordance) to seed it; it covers building a surface with
296
- \`@openparachute/surface-client\` + \`@openparachute/surface-render\`.
535
+ the starting point — get the notes and structure right first. When the person
536
+ wants one, add the **Surface Starter** guide to this vault — an optional pack
537
+ (not installed by default) that covers building a surface with
538
+ \`@openparachute/surface-client\` + \`@openparachute/surface-render\`. On Parachute
539
+ Cloud it's the console's add-pack affordance.
297
540
 
298
541
  ## Adapt this note
299
542
 
@@ -313,8 +556,16 @@ export const GETTING_STARTED_PACK: SeedPack = {
313
556
  name: "getting-started",
314
557
  description:
315
558
  "The AI-facing start-here guide: vault design vocabulary (tags/paths/schemas), imports, write gotchas. Seeded by default on new vaults.",
316
- tags: [],
317
- notes: [{ path: GETTING_STARTED_PATH, content: GETTING_STARTED_CONTENT }],
559
+ // Declares GUIDE_TAG too — each pack is self-sufficient; the upsert converges
560
+ // with the welcome pack's. This guide is written FOR the AI.
561
+ tags: [GUIDE_TAG],
562
+ notes: [
563
+ {
564
+ path: GETTING_STARTED_PATH,
565
+ tags: ["guide"],
566
+ content: GETTING_STARTED_CONTENT,
567
+ },
568
+ ],
318
569
  };
319
570
 
320
571
  // ---------------------------------------------------------------------------
@@ -450,8 +701,16 @@ export const SURFACE_STARTER_PACK: SeedPack = {
450
701
  name: "surface-starter",
451
702
  description:
452
703
  "A living starter guide for building a custom surface (UI) over this vault with the published surface packages. Opt-in — not seeded by default.",
453
- tags: [],
454
- notes: [{ path: SURFACE_STARTER_PATH, content: SURFACE_STARTER_CONTENT }],
704
+ // A guide too (written for the AI) — declares GUIDE_TAG so the opt-in
705
+ // add-pack path lands the tag even if this pack is applied on its own.
706
+ tags: [GUIDE_TAG],
707
+ notes: [
708
+ {
709
+ path: SURFACE_STARTER_PATH,
710
+ tags: ["guide"],
711
+ content: SURFACE_STARTER_CONTENT,
712
+ },
713
+ ],
455
714
  };
456
715
 
457
716
  // ---------------------------------------------------------------------------
@@ -541,18 +800,23 @@ export async function applySeedPack(
541
800
  await store.upsertTagRecord(decl.name, {
542
801
  description: decl.description,
543
802
  ...(decl.parent_names ? { parent_names: decl.parent_names } : {}),
803
+ ...(decl.fields ? { fields: decl.fields } : {}),
544
804
  });
545
805
  result.tags.push(decl.name);
546
806
  }
547
807
 
548
- for (const { path, content } of pack.notes) {
549
- const existing = await store.getNoteByPath(path);
808
+ for (const note of pack.notes) {
809
+ const existing = await store.getNoteByPath(note.path);
550
810
  if (existing) {
551
- result.skippedNotes.push(path);
811
+ result.skippedNotes.push(note.path);
552
812
  continue;
553
813
  }
554
- await store.createNote(content, { path });
555
- result.seededNotes.push(path);
814
+ await store.createNote(note.content, {
815
+ path: note.path,
816
+ tags: note.tags,
817
+ metadata: note.metadata,
818
+ });
819
+ result.seededNotes.push(note.path);
556
820
  }
557
821
 
558
822
  return result;
@@ -9,7 +9,7 @@
9
9
  * History: pre-v14 vaults stored hierarchy in notes-as-config at
10
10
  * `_tags/<name>`. The v14 migration (see core/src/schema.ts:migrateToV14)
11
11
  * lifts those parent declarations onto the tags row and the resolver here
12
- * was swapped accordingly. See parachute-patterns/patterns/tag-data-model.md.
12
+ * was swapped accordingly. See docs/contracts/tag-data-model.md.
13
13
  *
14
14
  * Resolution model:
15
15
  * - Lazy: built on first access, cached on the store.
@@ -4,7 +4,7 @@
4
4
  * Each tag row carries: human-readable description, indexed metadata field
5
5
  * declarations (`fields`), typed-link relationship declarations
6
6
  * (`relationships`), and the hierarchy parent list (`parent_names`).
7
- * See parachute-patterns/patterns/tag-data-model.md.
7
+ * See docs/contracts/tag-data-model.md.
8
8
  *
9
9
  * This module retains the historical `tag-schemas` filename and exports
10
10
  * (`TagSchema`, `listTagSchemas`, `getTagSchema`, `upsertTagSchema`,
@@ -51,7 +51,7 @@ export interface TagFieldSchema {
51
51
  * `{ target_tag, cardinality }` declaration — but `relationships` is now an
52
52
  * opaque vocabulary map (see `TagRelationshipMap` / `validateRelationships`),
53
53
  * so this is one valid value shape among many, not a required one.
54
- * See patterns/tag-data-model.md §Relationships.
54
+ * See docs/contracts/tag-data-model.md §Relationships.
55
55
  */
56
56
  export type TagRelCardinality = "one" | "optional" | "many" | "many-required";
57
57
 
package/core/src/types.ts CHANGED
@@ -406,7 +406,7 @@ export interface Store {
406
406
 
407
407
  // Tag records — full v14 identity row (description + fields + typed
408
408
  // relationships + parent_names + timestamps). See
409
- // parachute-patterns/patterns/tag-data-model.md.
409
+ // docs/contracts/tag-data-model.md.
410
410
  listTagRecords(): Promise<TagRecord[]>;
411
411
  getTagRecord(tag: string): Promise<TagRecord | null>;
412
412
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openparachute/vault",
3
- "version": "0.6.5-rc.9",
3
+ "version": "0.6.5",
4
4
  "description": "Agent-native knowledge graph. Notes, tags, links over MCP.",
5
5
  "module": "src/cli.ts",
6
6
  "type": "module",
package/src/admin-spa.ts CHANGED
@@ -8,7 +8,8 @@
8
8
  *
9
9
  * Per-vault mount (vault#252): the SPA lives under `/vault/<name>/admin/*`
10
10
  * rather than the origin-rooted `/admin/*` it shipped with. The hub only
11
- * proxies `/vault/<name>/*` paths (per parachute-patterns/module-protocol),
11
+ * proxies `/vault/<name>/*` paths (per the module protocol
12
+ * https://github.com/ParachuteComputer/parachute-hub/blob/main/docs/contracts/module-protocol.md),
12
13
  * so an origin-rooted SPA is unreachable through the hub. Asset URLs are
13
14
  * relative (Vite `base: "./"`), so the same bundle works at any mount
14
15
  * point — no rebuild per vault.
package/src/auth.ts CHANGED
@@ -123,7 +123,7 @@ export interface AuthResult {
123
123
  * Tag-allowlist (root tag names) for tag-scoped tokens. NULL = unscoped
124
124
  * (current full-vault behavior). Hub-issued JWTs and legacy YAML keys
125
125
  * always have null — tag scoping is a per-token vault-DB attribute, not
126
- * an OAuth-claim concern. See patterns/tag-scoped-tokens.md.
126
+ * an OAuth-claim concern. See docs/contracts/tag-scoped-tokens.md.
127
127
  */
128
128
  scoped_tags: string[] | null;
129
129
  /**