@openparachute/vault 0.1.0
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 +31 -0
- package/.dockerignore +8 -0
- package/.env.example +9 -0
- package/.playwright-mcp/console-2026-04-14T04-17-25-395Z.log +2 -0
- package/.playwright-mcp/console-2026-04-14T04-18-11-767Z.log +1 -0
- package/.playwright-mcp/console-2026-04-14T04-19-07-733Z.log +2 -0
- package/.playwright-mcp/console-2026-04-14T04-20-45-440Z.log +2 -0
- package/.playwright-mcp/page-2026-04-14T04-17-25-536Z.yml +1 -0
- package/.playwright-mcp/page-2026-04-14T04-18-11-816Z.yml +1 -0
- package/.playwright-mcp/page-2026-04-14T04-18-31-674Z.yml +211 -0
- package/.playwright-mcp/page-2026-04-14T04-19-07-795Z.yml +59 -0
- package/.playwright-mcp/page-2026-04-14T04-19-36-239Z.yml +232 -0
- package/.playwright-mcp/page-2026-04-14T04-19-58-327Z.yml +182 -0
- package/.playwright-mcp/page-2026-04-14T04-20-10-517Z.yml +91 -0
- package/.playwright-mcp/page-2026-04-14T04-20-14-796Z.yml +70 -0
- package/.playwright-mcp/page-2026-04-14T04-20-45-509Z.yml +59 -0
- package/CLAUDE.md +115 -0
- package/Caddyfile +3 -0
- package/Dockerfile +22 -0
- package/LICENSE +661 -0
- package/README.md +356 -0
- package/bun.lock +219 -0
- package/bunfig.toml +2 -0
- package/core/package.json +7 -0
- package/core/src/core.test.ts +940 -0
- package/core/src/hooks.test.ts +361 -0
- package/core/src/hooks.ts +234 -0
- package/core/src/links.ts +352 -0
- package/core/src/mcp.ts +672 -0
- package/core/src/notes.ts +520 -0
- package/core/src/obsidian.test.ts +380 -0
- package/core/src/obsidian.ts +322 -0
- package/core/src/paths.test.ts +197 -0
- package/core/src/paths.ts +53 -0
- package/core/src/schema.ts +331 -0
- package/core/src/store.ts +303 -0
- package/core/src/tag-schemas.ts +104 -0
- package/core/src/test-preload.ts +8 -0
- package/core/src/types.ts +140 -0
- package/core/src/wikilinks.test.ts +277 -0
- package/core/src/wikilinks.ts +402 -0
- package/deploy/parachute-vault.service +20 -0
- package/docker-compose.yml +50 -0
- package/docs/HTTP_API.md +328 -0
- package/fly.toml +24 -0
- package/package.json +32 -0
- package/railway.json +14 -0
- package/religions-abrahamic-filter.png +0 -0
- package/religions-buddhism-v2.png +0 -0
- package/religions-buddhism.png +0 -0
- package/religions-final.png +0 -0
- package/religions-v1.png +0 -0
- package/religions-v2.png +0 -0
- package/religions-zen.png +0 -0
- package/scripts/migrate-audio-to-opus.test.ts +237 -0
- package/scripts/migrate-audio-to-opus.ts +499 -0
- package/src/auth.ts +170 -0
- package/src/cli.ts +1131 -0
- package/src/config-triggers.test.ts +83 -0
- package/src/config.test.ts +125 -0
- package/src/config.ts +716 -0
- package/src/db.ts +14 -0
- package/src/launchd.ts +109 -0
- package/src/mcp-http.ts +113 -0
- package/src/mcp-tools.ts +155 -0
- package/src/oauth.test.ts +1242 -0
- package/src/oauth.ts +729 -0
- package/src/owner-auth.ts +159 -0
- package/src/prompt.ts +141 -0
- package/src/published.test.ts +214 -0
- package/src/qrcode-terminal.d.ts +9 -0
- package/src/routes.ts +822 -0
- package/src/server.ts +450 -0
- package/src/systemd.ts +84 -0
- package/src/token-store.test.ts +174 -0
- package/src/token-store.ts +241 -0
- package/src/triggers.test.ts +397 -0
- package/src/triggers.ts +412 -0
- package/src/two-factor.test.ts +246 -0
- package/src/two-factor.ts +222 -0
- package/src/vault-store.ts +47 -0
- package/src/vault.test.ts +1309 -0
- package/tsconfig.json +29 -0
- package/web/README.md +73 -0
- package/web/bun.lock +827 -0
- package/web/eslint.config.js +23 -0
- package/web/index.html +15 -0
- package/web/package.json +36 -0
- package/web/public/favicon.svg +1 -0
- package/web/public/icons.svg +24 -0
- package/web/src/App.tsx +149 -0
- package/web/src/Graph.tsx +200 -0
- package/web/src/NoteView.tsx +155 -0
- package/web/src/Sidebar.tsx +186 -0
- package/web/src/api.ts +21 -0
- package/web/src/index.css +50 -0
- package/web/src/main.tsx +10 -0
- package/web/src/types.ts +37 -0
- package/web/src/utils.ts +107 -0
- package/web/tsconfig.app.json +25 -0
- package/web/tsconfig.json +7 -0
- package/web/tsconfig.node.json +24 -0
- package/web/vite.config.ts +15 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from "bun:test";
|
|
2
|
+
import { writeFileSync, mkdirSync, rmSync, existsSync, readFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
|
|
6
|
+
// We test the parsing by writing a config file and reading it back
|
|
7
|
+
// using the real readGlobalConfig/writeGlobalConfig functions.
|
|
8
|
+
// To avoid touching the real config, we override PARACHUTE_HOME.
|
|
9
|
+
|
|
10
|
+
const testDir = join(tmpdir(), `vault-trigger-test-${Date.now()}`);
|
|
11
|
+
|
|
12
|
+
// Must set env before importing config module
|
|
13
|
+
process.env.PARACHUTE_HOME = testDir;
|
|
14
|
+
|
|
15
|
+
// Dynamic import after env is set
|
|
16
|
+
const { readGlobalConfig, writeGlobalConfig } = await import("./config.ts");
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
if (existsSync(testDir)) {
|
|
20
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("trigger config round-trip", () => {
|
|
25
|
+
it("writes and reads triggers", () => {
|
|
26
|
+
mkdirSync(testDir, { recursive: true });
|
|
27
|
+
writeGlobalConfig({
|
|
28
|
+
port: 1940,
|
|
29
|
+
triggers: [
|
|
30
|
+
{
|
|
31
|
+
name: "tts-reader",
|
|
32
|
+
events: ["created", "updated"],
|
|
33
|
+
when: {
|
|
34
|
+
tags: ["reader"],
|
|
35
|
+
has_content: true,
|
|
36
|
+
missing_metadata: ["audio_rendered_at", "audio_pending_at"],
|
|
37
|
+
},
|
|
38
|
+
action: {
|
|
39
|
+
webhook: "http://localhost:8080/tts",
|
|
40
|
+
timeout: 30000,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "transcribe-capture",
|
|
45
|
+
when: {
|
|
46
|
+
tags: ["capture"],
|
|
47
|
+
has_content: false,
|
|
48
|
+
},
|
|
49
|
+
action: {
|
|
50
|
+
webhook: "http://localhost:8080/transcribe",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const config = readGlobalConfig();
|
|
57
|
+
expect(config.triggers).toBeDefined();
|
|
58
|
+
expect(config.triggers!.length).toBe(2);
|
|
59
|
+
|
|
60
|
+
const tts = config.triggers![0];
|
|
61
|
+
expect(tts.name).toBe("tts-reader");
|
|
62
|
+
expect(tts.events).toEqual(["created", "updated"]);
|
|
63
|
+
expect(tts.when.tags).toEqual(["reader"]);
|
|
64
|
+
expect(tts.when.has_content).toBe(true);
|
|
65
|
+
expect(tts.when.missing_metadata).toEqual(["audio_rendered_at", "audio_pending_at"]);
|
|
66
|
+
expect(tts.action.webhook).toBe("http://localhost:8080/tts");
|
|
67
|
+
expect(tts.action.timeout).toBe(30000);
|
|
68
|
+
|
|
69
|
+
const transcribe = config.triggers![1];
|
|
70
|
+
expect(transcribe.name).toBe("transcribe-capture");
|
|
71
|
+
expect(transcribe.when.tags).toEqual(["capture"]);
|
|
72
|
+
expect(transcribe.when.has_content).toBe(false);
|
|
73
|
+
expect(transcribe.action.webhook).toBe("http://localhost:8080/transcribe");
|
|
74
|
+
expect(transcribe.action.timeout).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("handles config with no triggers", () => {
|
|
78
|
+
mkdirSync(testDir, { recursive: true });
|
|
79
|
+
writeGlobalConfig({ port: 1940 });
|
|
80
|
+
const config = readGlobalConfig();
|
|
81
|
+
expect(config.triggers).toBeUndefined();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
writeVaultConfig,
|
|
4
|
+
readVaultConfig,
|
|
5
|
+
generateApiKey,
|
|
6
|
+
hashKey,
|
|
7
|
+
verifyKey,
|
|
8
|
+
} from "./config.ts";
|
|
9
|
+
import type { VaultConfig } from "./config.ts";
|
|
10
|
+
|
|
11
|
+
describe("config", () => {
|
|
12
|
+
test("generates and verifies API keys", () => {
|
|
13
|
+
const { fullKey, keyId } = generateApiKey();
|
|
14
|
+
expect(fullKey).toMatch(/^pvk_/);
|
|
15
|
+
expect(keyId).toMatch(/^k_/);
|
|
16
|
+
|
|
17
|
+
const hash = hashKey(fullKey);
|
|
18
|
+
expect(hash).toMatch(/^sha256:/);
|
|
19
|
+
expect(verifyKey(fullKey, hash)).toBe(true);
|
|
20
|
+
expect(verifyKey("wrong_key_here_1234567890123456", hash)).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("round-trips vault config", () => {
|
|
24
|
+
const config: VaultConfig = {
|
|
25
|
+
name: "testvault",
|
|
26
|
+
description: "A test vault for testing",
|
|
27
|
+
api_keys: [
|
|
28
|
+
{
|
|
29
|
+
id: "k_abc123",
|
|
30
|
+
label: "default",
|
|
31
|
+
scope: "write",
|
|
32
|
+
key_hash: "sha256:fakehash",
|
|
33
|
+
created_at: "2026-01-01T00:00:00.000Z",
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
created_at: "2026-01-01T00:00:00.000Z",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
writeVaultConfig(config);
|
|
40
|
+
|
|
41
|
+
const loaded = readVaultConfig("testvault");
|
|
42
|
+
expect(loaded).not.toBeNull();
|
|
43
|
+
expect(loaded!.name).toBe("testvault");
|
|
44
|
+
expect(loaded!.description).toBe("A test vault for testing");
|
|
45
|
+
expect(loaded!.api_keys.length).toBe(1);
|
|
46
|
+
expect(loaded!.api_keys[0].id).toBe("k_abc123");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("round-trips tag_schemas in vault config", () => {
|
|
50
|
+
const config: VaultConfig = {
|
|
51
|
+
name: "testvault",
|
|
52
|
+
api_keys: [
|
|
53
|
+
{
|
|
54
|
+
id: "k_abc123",
|
|
55
|
+
label: "default",
|
|
56
|
+
scope: "write",
|
|
57
|
+
key_hash: "sha256:fakehash",
|
|
58
|
+
created_at: "2026-01-01T00:00:00.000Z",
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
created_at: "2026-01-01T00:00:00.000Z",
|
|
62
|
+
tag_schemas: {
|
|
63
|
+
person: {
|
|
64
|
+
description: "A person in Aaron's life",
|
|
65
|
+
fields: {
|
|
66
|
+
first_appeared: {
|
|
67
|
+
type: "string",
|
|
68
|
+
description: "When this person first appeared",
|
|
69
|
+
},
|
|
70
|
+
relationship: {
|
|
71
|
+
type: "string",
|
|
72
|
+
description: "How they relate to Aaron",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
project: {
|
|
77
|
+
description: "A project",
|
|
78
|
+
fields: {
|
|
79
|
+
status: {
|
|
80
|
+
type: "string",
|
|
81
|
+
enum: ["active", "completed", "abandoned"],
|
|
82
|
+
description: "Current project status",
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
"summary/monthly": {
|
|
87
|
+
description: "Monthly summary note",
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
writeVaultConfig(config);
|
|
93
|
+
|
|
94
|
+
const loaded = readVaultConfig("testvault");
|
|
95
|
+
expect(loaded).not.toBeNull();
|
|
96
|
+
expect(loaded!.tag_schemas).toBeDefined();
|
|
97
|
+
|
|
98
|
+
const person = loaded!.tag_schemas!.person;
|
|
99
|
+
expect(person.description).toBe("A person in Aaron's life");
|
|
100
|
+
expect(person.fields!.first_appeared.type).toBe("string");
|
|
101
|
+
expect(person.fields!.first_appeared.description).toBe("When this person first appeared");
|
|
102
|
+
expect(person.fields!.relationship.type).toBe("string");
|
|
103
|
+
|
|
104
|
+
const project = loaded!.tag_schemas!.project;
|
|
105
|
+
expect(project.fields!.status.enum).toEqual(["active", "completed", "abandoned"]);
|
|
106
|
+
|
|
107
|
+
const monthly = loaded!.tag_schemas!["summary/monthly"];
|
|
108
|
+
expect(monthly.description).toBe("Monthly summary note");
|
|
109
|
+
expect(monthly.fields).toBeUndefined();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("vault config without tag_schemas loads cleanly", () => {
|
|
113
|
+
const config: VaultConfig = {
|
|
114
|
+
name: "testvault",
|
|
115
|
+
api_keys: [],
|
|
116
|
+
created_at: "2026-01-01T00:00:00.000Z",
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
writeVaultConfig(config);
|
|
120
|
+
|
|
121
|
+
const loaded = readVaultConfig("testvault");
|
|
122
|
+
expect(loaded).not.toBeNull();
|
|
123
|
+
expect(loaded!.tag_schemas).toBeUndefined();
|
|
124
|
+
});
|
|
125
|
+
});
|