@openparachute/vault 0.6.4 → 0.6.5-rc.10
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/core/src/__fixtures__/golden-vault.ts +125 -0
- package/core/src/__fixtures__/portable-export-golden.json +10 -0
- package/core/src/do-param-cap.test.ts +161 -0
- package/core/src/links.ts +8 -13
- package/core/src/mcp.ts +306 -314
- package/core/src/notes.ts +54 -62
- package/core/src/onboarding.ts +14 -296
- package/core/src/portable-md-batching.test.ts +67 -0
- package/core/src/portable-md-golden.test.ts +55 -0
- package/core/src/portable-md.ts +579 -435
- package/core/src/schema.ts +21 -43
- package/core/src/seed-packs.test.ts +191 -0
- package/core/src/seed-packs.ts +559 -0
- package/core/src/sql-in.test.ts +58 -0
- package/core/src/sql-in.ts +76 -0
- package/core/src/store.ts +14 -9
- package/core/src/transcription/provider.ts +141 -0
- package/core/src/txn.test.ts +229 -0
- package/core/src/txn.ts +105 -0
- package/core/src/types.ts +9 -0
- package/core/src/vault-projection.ts +1 -1
- package/core/src/wikilinks.ts +10 -4
- package/package.json +1 -1
- package/src/add-pack.test.ts +142 -0
- package/src/cli.ts +778 -7
- package/src/onboarding-seed.test.ts +126 -40
- package/src/onboarding-seed.ts +41 -46
- package/src/routes.ts +25 -7
- package/src/server.ts +108 -31
- package/src/transcription/build.test.ts +224 -0
- package/src/transcription/build.ts +252 -0
- package/src/transcription/capability.test.ts +118 -0
- package/src/transcription/capability.ts +96 -0
- package/src/transcription/install-python.test.ts +366 -0
- package/src/transcription/install-python.ts +471 -0
- package/src/transcription/install.test.ts +167 -0
- package/src/transcription/install.ts +296 -0
- package/src/transcription/providers/onnx-asr.test.ts +229 -0
- package/src/transcription/providers/onnx-asr.ts +239 -0
- package/src/transcription/providers/parakeet-mlx.test.ts +290 -0
- package/src/transcription/providers/parakeet-mlx.ts +242 -0
- package/src/transcription/providers/scribe-http.test.ts +195 -0
- package/src/transcription/providers/scribe-http.ts +144 -0
- package/src/transcription/providers/transcribe-cpp.test.ts +314 -0
- package/src/transcription/providers/transcribe-cpp.ts +293 -0
- package/src/transcription/select.test.ts +259 -0
- package/src/transcription/select.ts +334 -0
- package/src/transcription/tiers.test.ts +197 -0
- package/src/transcription/tiers.ts +184 -0
- package/src/transcription-worker.test.ts +44 -0
- package/src/transcription-worker.ts +57 -122
- package/src/vault-create.test.ts +38 -10
- package/src/vault.test.ts +48 -0
package/core/src/schema.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
2
|
import { normalizePath } from "./paths.js";
|
|
3
3
|
import { rebuildIndexes } from "./indexed-fields.js";
|
|
4
|
+
import { transaction } from "./txn.js";
|
|
4
5
|
|
|
5
6
|
export const SCHEMA_VERSION = 23;
|
|
6
7
|
|
|
@@ -680,8 +681,7 @@ function migrateToV13(db: Database): void {
|
|
|
680
681
|
function migrateToV14(db: Database): void {
|
|
681
682
|
if (!hasTable(db, "tags")) return;
|
|
682
683
|
|
|
683
|
-
db
|
|
684
|
-
try {
|
|
684
|
+
const { copiedSchemas, copiedHierarchy } = transaction(db, () => {
|
|
685
685
|
// 1. ALTER TABLE — additive, idempotent.
|
|
686
686
|
const cols: [string, string][] = [
|
|
687
687
|
["description", "TEXT"],
|
|
@@ -762,16 +762,13 @@ function migrateToV14(db: Database): void {
|
|
|
762
762
|
db.exec("DROP TABLE tag_schemas");
|
|
763
763
|
}
|
|
764
764
|
|
|
765
|
-
|
|
765
|
+
return { copiedSchemas, copiedHierarchy };
|
|
766
|
+
});
|
|
766
767
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
}
|
|
772
|
-
} catch (err) {
|
|
773
|
-
db.exec("ROLLBACK");
|
|
774
|
-
throw err;
|
|
768
|
+
if (copiedSchemas > 0 || copiedHierarchy > 0) {
|
|
769
|
+
console.log(
|
|
770
|
+
`[vault] migrated to schema v14: copied ${copiedSchemas} tag_schemas + ${copiedHierarchy} _tags/* hierarchies onto tags rows`,
|
|
771
|
+
);
|
|
775
772
|
}
|
|
776
773
|
}
|
|
777
774
|
|
|
@@ -806,8 +803,7 @@ function migrateToV15(db: Database): void {
|
|
|
806
803
|
).get()) !== null;
|
|
807
804
|
if (hasSchemas || hasMappings) return;
|
|
808
805
|
|
|
809
|
-
db
|
|
810
|
-
try {
|
|
806
|
+
const { copiedSchemas, copiedMappings } = transaction(db, () => {
|
|
811
807
|
const now = new Date().toISOString();
|
|
812
808
|
let copiedSchemas = 0;
|
|
813
809
|
let copiedMappings = 0;
|
|
@@ -884,16 +880,13 @@ function migrateToV15(db: Database): void {
|
|
|
884
880
|
}
|
|
885
881
|
}
|
|
886
882
|
|
|
887
|
-
|
|
883
|
+
return { copiedSchemas, copiedMappings };
|
|
884
|
+
});
|
|
888
885
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
}
|
|
894
|
-
} catch (err) {
|
|
895
|
-
db.exec("ROLLBACK");
|
|
896
|
-
throw err;
|
|
886
|
+
if (copiedSchemas > 0 || copiedMappings > 0) {
|
|
887
|
+
console.log(
|
|
888
|
+
`[vault] migrated to schema v15: copied ${copiedSchemas} _schemas/* + ${copiedMappings} _schema_defaults mappings into note_schemas/schema_mappings`,
|
|
889
|
+
);
|
|
897
890
|
}
|
|
898
891
|
}
|
|
899
892
|
|
|
@@ -922,15 +915,10 @@ function migrateToV16(db: Database): void {
|
|
|
922
915
|
// ALTER block so a fresh vault — where the column exists but the index
|
|
923
916
|
// doesn't — still gets it.
|
|
924
917
|
if (!hasColumn(db, "tokens", "vault_name")) {
|
|
925
|
-
db
|
|
926
|
-
try {
|
|
918
|
+
transaction(db, () => {
|
|
927
919
|
db.exec("ALTER TABLE tokens ADD COLUMN vault_name TEXT");
|
|
928
920
|
db.exec("CREATE INDEX IF NOT EXISTS idx_tokens_vault_name ON tokens(vault_name)");
|
|
929
|
-
|
|
930
|
-
} catch (err) {
|
|
931
|
-
db.exec("ROLLBACK");
|
|
932
|
-
throw err;
|
|
933
|
-
}
|
|
921
|
+
});
|
|
934
922
|
return;
|
|
935
923
|
}
|
|
936
924
|
|
|
@@ -980,8 +968,7 @@ function migrateToV17(db: Database): void {
|
|
|
980
968
|
).all() as { schema_name: string; match_kind: string; match_value: string }[];
|
|
981
969
|
}
|
|
982
970
|
|
|
983
|
-
db
|
|
984
|
-
try {
|
|
971
|
+
transaction(db, () => {
|
|
985
972
|
// Drop the index first — the index references the table; SQLite would
|
|
986
973
|
// tear it down on DROP TABLE but the explicit DROP keeps the order
|
|
987
974
|
// obvious if a future migration reads from sqlite_master mid-flight.
|
|
@@ -993,11 +980,7 @@ function migrateToV17(db: Database): void {
|
|
|
993
980
|
if (hasNoteSchemas) {
|
|
994
981
|
db.exec("DROP TABLE note_schemas");
|
|
995
982
|
}
|
|
996
|
-
|
|
997
|
-
} catch (err) {
|
|
998
|
-
db.exec("ROLLBACK");
|
|
999
|
-
throw err;
|
|
1000
|
-
}
|
|
983
|
+
});
|
|
1001
984
|
|
|
1002
985
|
if (droppedSchemas.length > 0 || droppedMappings.length > 0) {
|
|
1003
986
|
const schemaNames = droppedSchemas.map((s) => s.name).join(", ");
|
|
@@ -1052,8 +1035,7 @@ function migrateToV18(db: Database): void {
|
|
|
1052
1035
|
const hasNewUnique = indexes.some((r) => r.name === "idx_notes_path_ext_unique");
|
|
1053
1036
|
if (!needsColumn && hasNewUnique && !hasOldUnique) return;
|
|
1054
1037
|
|
|
1055
|
-
db
|
|
1056
|
-
try {
|
|
1038
|
+
transaction(db, () => {
|
|
1057
1039
|
if (needsColumn) {
|
|
1058
1040
|
db.exec("ALTER TABLE notes ADD COLUMN extension TEXT NOT NULL DEFAULT 'md'");
|
|
1059
1041
|
}
|
|
@@ -1065,11 +1047,7 @@ function migrateToV18(db: Database): void {
|
|
|
1065
1047
|
"CREATE UNIQUE INDEX idx_notes_path_ext_unique ON notes(path, extension) WHERE path IS NOT NULL",
|
|
1066
1048
|
);
|
|
1067
1049
|
}
|
|
1068
|
-
|
|
1069
|
-
} catch (err) {
|
|
1070
|
-
db.exec("ROLLBACK");
|
|
1071
|
-
throw err;
|
|
1072
|
-
}
|
|
1050
|
+
});
|
|
1073
1051
|
}
|
|
1074
1052
|
|
|
1075
1053
|
/**
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content + registry pins for the named seed packs (core/src/seed-packs.ts).
|
|
3
|
+
*
|
|
4
|
+
* Pure content tests — no DB. The applier's behavior (idempotence, seeding)
|
|
5
|
+
* is exercised store-level in src/onboarding-seed.test.ts and CLI-level in
|
|
6
|
+
* src/add-pack.test.ts.
|
|
7
|
+
*
|
|
8
|
+
* The load-bearing pin: the `welcome` pack's tags must stay byte-equal to
|
|
9
|
+
* notes-ui's NOTES_REQUIRED_SCHEMA — the Notes PWA's connect-time audit
|
|
10
|
+
* compares `description` verbatim, and its schema banner only clears when the
|
|
11
|
+
* tags genuinely carry the semantics Notes declares. ONE tag since 2026-07-03:
|
|
12
|
+
* `#capture` only — entry method (text|voice) is note `metadata.source`
|
|
13
|
+
* provenance, not taxonomy (sibling notes-ui PR carries the client side).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { describe, test, expect } from "bun:test";
|
|
17
|
+
import {
|
|
18
|
+
applySeedPack,
|
|
19
|
+
CONNECT_AI_PATH,
|
|
20
|
+
GETTING_STARTED_CONTENT,
|
|
21
|
+
GETTING_STARTED_PACK,
|
|
22
|
+
GETTING_STARTED_PATH,
|
|
23
|
+
getSeedPack,
|
|
24
|
+
listSeedPacks,
|
|
25
|
+
NOTES_REQUIRED_TAGS,
|
|
26
|
+
SEED_PACK_NAMES,
|
|
27
|
+
SURFACE_STARTER_CONTENT,
|
|
28
|
+
SURFACE_STARTER_PACK,
|
|
29
|
+
SURFACE_STARTER_PATH,
|
|
30
|
+
TRY_LINKING_PATH,
|
|
31
|
+
WELCOME_PATH,
|
|
32
|
+
welcomePack,
|
|
33
|
+
} from "./seed-packs.ts";
|
|
34
|
+
import * as onboardingShim from "./onboarding.ts";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* LITERAL copy of `NOTES_REQUIRED_SCHEMA.tags` from
|
|
38
|
+
* parachute-surface/packages/notes-ui/src/lib/vault/schema.ts — deliberately
|
|
39
|
+
* NOT imported (different repo; and the whole point is an independent copy
|
|
40
|
+
* that breaks this test when either side drifts). If this test fails, change
|
|
41
|
+
* notes-ui and the pack in lockstep, never one side alone.
|
|
42
|
+
*/
|
|
43
|
+
const NOTES_UI_REQUIRED_SCHEMA_TAGS = [
|
|
44
|
+
{
|
|
45
|
+
name: "capture",
|
|
46
|
+
description: "Notes captured directly by the user (text or voice).",
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
describe("welcome pack — notes-ui schema parity", () => {
|
|
51
|
+
test("welcome tags are byte-equal to notes-ui's NOTES_REQUIRED_SCHEMA", () => {
|
|
52
|
+
expect(NOTES_REQUIRED_TAGS).toEqual(NOTES_UI_REQUIRED_SCHEMA_TAGS);
|
|
53
|
+
expect(welcomePack().tags).toEqual(NOTES_UI_REQUIRED_SCHEMA_TAGS);
|
|
54
|
+
// Field-level byte pins (toEqual ignores key order; these don't).
|
|
55
|
+
for (const [i, expected] of NOTES_UI_REQUIRED_SCHEMA_TAGS.entries()) {
|
|
56
|
+
expect(NOTES_REQUIRED_TAGS[i]!.name).toBe(expected.name);
|
|
57
|
+
expect(NOTES_REQUIRED_TAGS[i]!.description).toBe(expected.description);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("exactly ONE capture tag — no subtype tags, no parent_names (2026-07-03)", () => {
|
|
62
|
+
// Entry method is metadata.source (text|voice), not taxonomy. The old
|
|
63
|
+
// capture/text + capture/voice subtypes must not creep back into the seed
|
|
64
|
+
// (existing vaults carrying them stay valid — tags are user data).
|
|
65
|
+
expect(NOTES_REQUIRED_TAGS).toHaveLength(1);
|
|
66
|
+
expect(NOTES_REQUIRED_TAGS[0]!.name).toBe("capture");
|
|
67
|
+
expect(NOTES_REQUIRED_TAGS[0]!.parent_names).toBeUndefined();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("welcome pack — person-voiced welcome web", () => {
|
|
72
|
+
test("three notes forming a linked web (welcome ⇄ try-linking, connect-AI → welcome)", () => {
|
|
73
|
+
const pack = welcomePack();
|
|
74
|
+
expect(pack.name).toBe("welcome");
|
|
75
|
+
expect(pack.notes.map((n) => n.path)).toEqual([
|
|
76
|
+
WELCOME_PATH,
|
|
77
|
+
TRY_LINKING_PATH,
|
|
78
|
+
CONNECT_AI_PATH,
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
const [welcome, tryLinking, connectAi] = pack.notes;
|
|
82
|
+
expect(welcome!.content).toContain(`[[${TRY_LINKING_PATH}]]`);
|
|
83
|
+
expect(tryLinking!.content).toContain(`[[${WELCOME_PATH}]]`);
|
|
84
|
+
expect(connectAi!.content).toContain(`[[${WELCOME_PATH}]]`);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("content with consoleOrigin is byte-equal to the cloud's welcome copy", () => {
|
|
88
|
+
// Pinned against parachute-cloud workers/vault/src/welcome.ts
|
|
89
|
+
// `welcomeNotes(consoleOrigin)` — the sibling cloud PR swaps that module
|
|
90
|
+
// for this pack, so the ported content must not drift.
|
|
91
|
+
const origin = "https://cloud.parachute.computer";
|
|
92
|
+
const pack = welcomePack({ consoleOrigin: origin });
|
|
93
|
+
|
|
94
|
+
expect(pack.notes[0]!.content).toBe(`# ${WELCOME_PATH}
|
|
95
|
+
|
|
96
|
+
This vault is yours.
|
|
97
|
+
Write anything.
|
|
98
|
+
Notes can link to each other, like this: [[${TRY_LINKING_PATH}]].
|
|
99
|
+
`);
|
|
100
|
+
expect(pack.notes[1]!.content).toBe(`# ${TRY_LINKING_PATH}
|
|
101
|
+
|
|
102
|
+
Wrap a note's name in double square brackets to make a wikilink, like this one back to [[${WELCOME_PATH}]].
|
|
103
|
+
`);
|
|
104
|
+
expect(pack.notes[2]!.content).toBe(`# ${CONNECT_AI_PATH}
|
|
105
|
+
|
|
106
|
+
Your vault speaks MCP. Grab the connection URL from your console at ${origin}.
|
|
107
|
+
Start from [[${WELCOME_PATH}]].
|
|
108
|
+
`);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("without consoleOrigin the Connect-AI line stays generic (no baked origin)", () => {
|
|
112
|
+
const connectAi = welcomePack().notes[2]!;
|
|
113
|
+
expect(connectAi.content).toContain(
|
|
114
|
+
"Your vault speaks MCP. Grab the connection URL from your console.",
|
|
115
|
+
);
|
|
116
|
+
expect(connectAi.content).not.toContain("undefined");
|
|
117
|
+
expect(connectAi.content).not.toContain("at .");
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("getting-started pack", () => {
|
|
122
|
+
test("carries the doctrine note at the canonical path", () => {
|
|
123
|
+
expect(GETTING_STARTED_PACK.name).toBe("getting-started");
|
|
124
|
+
expect(GETTING_STARTED_PACK.tags).toEqual([]);
|
|
125
|
+
expect(GETTING_STARTED_PACK.notes).toEqual([
|
|
126
|
+
{ path: GETTING_STARTED_PATH, content: GETTING_STARTED_CONTENT },
|
|
127
|
+
]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("no dangling [[Surface Starter]] wikilink — points at the add-pack flow instead", () => {
|
|
131
|
+
// Surface Starter is out of the default seed (ratified 2026-07-02), so the
|
|
132
|
+
// default-seeded guide must not carry a wikilink to a note that doesn't
|
|
133
|
+
// exist. It names the pack + the way to add it.
|
|
134
|
+
expect(GETTING_STARTED_CONTENT).not.toContain("[[Surface Starter]]");
|
|
135
|
+
expect(GETTING_STARTED_CONTENT).toContain("Surface Starter");
|
|
136
|
+
expect(GETTING_STARTED_CONTENT).toContain("add-pack surface-starter");
|
|
137
|
+
// The surface packages are still named for discoverability.
|
|
138
|
+
expect(GETTING_STARTED_CONTENT).toContain("@openparachute/surface-client");
|
|
139
|
+
expect(GETTING_STARTED_CONTENT).toContain("@openparachute/surface-render");
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("surface-starter pack", () => {
|
|
144
|
+
test("carries the surface guide at the canonical path (opt-in, not default)", () => {
|
|
145
|
+
expect(SURFACE_STARTER_PACK.name).toBe("surface-starter");
|
|
146
|
+
expect(SURFACE_STARTER_PACK.tags).toEqual([]);
|
|
147
|
+
expect(SURFACE_STARTER_PACK.notes).toEqual([
|
|
148
|
+
{ path: SURFACE_STARTER_PATH, content: SURFACE_STARTER_CONTENT },
|
|
149
|
+
]);
|
|
150
|
+
expect(SURFACE_STARTER_PACK.description).toContain("not seeded by default");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("links back to Getting Started (default-seeded, so the wikilink resolves)", () => {
|
|
154
|
+
expect(SURFACE_STARTER_CONTENT).toContain("[[Getting Started]]");
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("pack registry", () => {
|
|
159
|
+
test("lists exactly the three packs, in order", () => {
|
|
160
|
+
expect([...SEED_PACK_NAMES]).toEqual([
|
|
161
|
+
"welcome",
|
|
162
|
+
"getting-started",
|
|
163
|
+
"surface-starter",
|
|
164
|
+
]);
|
|
165
|
+
expect(listSeedPacks().map((p) => p.name)).toEqual([...SEED_PACK_NAMES]);
|
|
166
|
+
for (const p of listSeedPacks()) {
|
|
167
|
+
expect(p.description.length).toBeGreaterThan(0);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("getSeedPack resolves each name and returns null for unknown", () => {
|
|
172
|
+
for (const name of SEED_PACK_NAMES) {
|
|
173
|
+
expect(getSeedPack(name)?.name).toBe(name);
|
|
174
|
+
}
|
|
175
|
+
expect(getSeedPack("nope")).toBeNull();
|
|
176
|
+
expect(getSeedPack("")).toBeNull();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("applySeedPack is exported for both runtimes", () => {
|
|
180
|
+
expect(typeof applySeedPack).toBe("function");
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe("onboarding.ts back-compat shim", () => {
|
|
185
|
+
test("re-exports the canonical paths + contents from seed-packs", () => {
|
|
186
|
+
expect(onboardingShim.GETTING_STARTED_PATH).toBe(GETTING_STARTED_PATH);
|
|
187
|
+
expect(onboardingShim.GETTING_STARTED_CONTENT).toBe(GETTING_STARTED_CONTENT);
|
|
188
|
+
expect(onboardingShim.SURFACE_STARTER_PATH).toBe(SURFACE_STARTER_PATH);
|
|
189
|
+
expect(onboardingShim.SURFACE_STARTER_CONTENT).toBe(SURFACE_STARTER_CONTENT);
|
|
190
|
+
});
|
|
191
|
+
});
|