@openparachute/vault 0.1.0 → 0.2.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.
Files changed (87) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/CLAUDE.md +2 -2
  3. package/README.md +289 -44
  4. package/core/src/core.test.ts +802 -346
  5. package/core/src/expand.ts +140 -0
  6. package/core/src/hooks.test.ts +27 -27
  7. package/core/src/hooks.ts +1 -1
  8. package/core/src/mcp.ts +102 -39
  9. package/core/src/notes.ts +82 -4
  10. package/core/src/obsidian.test.ts +11 -11
  11. package/core/src/paths.test.ts +46 -46
  12. package/core/src/schema.ts +18 -2
  13. package/core/src/store.ts +51 -51
  14. package/core/src/types.ts +29 -29
  15. package/core/src/wikilinks.test.ts +61 -61
  16. package/docs/HTTP_API.md +4 -2
  17. package/package.json +1 -1
  18. package/src/auth.test.ts +319 -0
  19. package/src/backup-launchd.test.ts +90 -0
  20. package/src/backup-launchd.ts +169 -0
  21. package/src/backup.test.ts +715 -0
  22. package/src/backup.ts +699 -0
  23. package/src/cli.ts +923 -31
  24. package/src/config.test.ts +173 -0
  25. package/src/config.ts +345 -15
  26. package/src/daemon.ts +136 -0
  27. package/src/doctor.test.ts +356 -0
  28. package/src/health.test.ts +201 -0
  29. package/src/health.ts +115 -0
  30. package/src/launchd.test.ts +91 -0
  31. package/src/launchd.ts +37 -40
  32. package/src/mcp-http.ts +1 -1
  33. package/src/mcp-tools.ts +7 -9
  34. package/src/oauth.test.ts +289 -8
  35. package/src/oauth.ts +57 -12
  36. package/src/published.test.ts +21 -21
  37. package/src/routes.ts +152 -70
  38. package/src/routing.test.ts +347 -0
  39. package/src/routing.ts +365 -0
  40. package/src/server.ts +7 -278
  41. package/src/systemd.test.ts +15 -0
  42. package/src/systemd.ts +18 -11
  43. package/src/triggers.test.ts +7 -7
  44. package/src/triggers.ts +6 -6
  45. package/src/vault-store.ts +20 -3
  46. package/src/vault.test.ts +356 -262
  47. package/.claude/settings.local.json +0 -31
  48. package/.playwright-mcp/console-2026-04-14T04-17-25-395Z.log +0 -2
  49. package/.playwright-mcp/console-2026-04-14T04-18-11-767Z.log +0 -1
  50. package/.playwright-mcp/console-2026-04-14T04-19-07-733Z.log +0 -2
  51. package/.playwright-mcp/console-2026-04-14T04-20-45-440Z.log +0 -2
  52. package/.playwright-mcp/page-2026-04-14T04-17-25-536Z.yml +0 -1
  53. package/.playwright-mcp/page-2026-04-14T04-18-11-816Z.yml +0 -1
  54. package/.playwright-mcp/page-2026-04-14T04-18-31-674Z.yml +0 -211
  55. package/.playwright-mcp/page-2026-04-14T04-19-07-795Z.yml +0 -59
  56. package/.playwright-mcp/page-2026-04-14T04-19-36-239Z.yml +0 -232
  57. package/.playwright-mcp/page-2026-04-14T04-19-58-327Z.yml +0 -182
  58. package/.playwright-mcp/page-2026-04-14T04-20-10-517Z.yml +0 -91
  59. package/.playwright-mcp/page-2026-04-14T04-20-14-796Z.yml +0 -70
  60. package/.playwright-mcp/page-2026-04-14T04-20-45-509Z.yml +0 -59
  61. package/religions-abrahamic-filter.png +0 -0
  62. package/religions-buddhism-v2.png +0 -0
  63. package/religions-buddhism.png +0 -0
  64. package/religions-final.png +0 -0
  65. package/religions-v1.png +0 -0
  66. package/religions-v2.png +0 -0
  67. package/religions-zen.png +0 -0
  68. package/web/README.md +0 -73
  69. package/web/bun.lock +0 -827
  70. package/web/eslint.config.js +0 -23
  71. package/web/index.html +0 -15
  72. package/web/package.json +0 -36
  73. package/web/public/favicon.svg +0 -1
  74. package/web/public/icons.svg +0 -24
  75. package/web/src/App.tsx +0 -149
  76. package/web/src/Graph.tsx +0 -200
  77. package/web/src/NoteView.tsx +0 -155
  78. package/web/src/Sidebar.tsx +0 -186
  79. package/web/src/api.ts +0 -21
  80. package/web/src/index.css +0 -50
  81. package/web/src/main.tsx +0 -10
  82. package/web/src/types.ts +0 -37
  83. package/web/src/utils.ts +0 -107
  84. package/web/tsconfig.app.json +0 -25
  85. package/web/tsconfig.json +0 -7
  86. package/web/tsconfig.node.json +0 -24
  87. package/web/vite.config.ts +0 -15
@@ -1,186 +0,0 @@
1
- import type { GraphNode } from "./types";
2
- import { FAMILY_COLORS, TYPE_COLORS } from "./utils";
3
-
4
- interface Props {
5
- nodes: GraphNode[];
6
- activeFilter: string | null;
7
- onFilterChange: (tag: string | null) => void;
8
- searchQuery: string;
9
- onSearchChange: (q: string) => void;
10
- searchResults: GraphNode[];
11
- onSelect: (id: string) => void;
12
- selectedId: string | null;
13
- }
14
-
15
- const FAMILIES = [
16
- { tag: "family/abrahamic", label: "Abrahamic", color: FAMILY_COLORS.abrahamic },
17
- { tag: "family/dharmic", label: "Dharmic", color: FAMILY_COLORS.dharmic },
18
- { tag: "family/east-asian", label: "East Asian", color: FAMILY_COLORS["east-asian"] },
19
- { tag: "family/iranian", label: "Iranian", color: FAMILY_COLORS.iranian },
20
- { tag: "family/indigenous", label: "Indigenous", color: FAMILY_COLORS.indigenous },
21
- { tag: "family/nrm", label: "New Religious Movements", color: FAMILY_COLORS.nrm },
22
- ];
23
-
24
- const TYPES = [
25
- { tag: "type/tradition", label: "Traditions", color: "#ffffff" },
26
- { tag: "type/concept", label: "Concepts", color: TYPE_COLORS.concept },
27
- { tag: "type/figure", label: "Figures", color: TYPE_COLORS.figure },
28
- { tag: "type/text", label: "Texts", color: TYPE_COLORS.text },
29
- { tag: "type/practice", label: "Practices", color: TYPE_COLORS.practice },
30
- { tag: "type/school", label: "Schools", color: TYPE_COLORS.school },
31
- { tag: "type/theme", label: "Themes", color: TYPE_COLORS.theme },
32
- { tag: "type/moc", label: "Maps of Content", color: TYPE_COLORS.moc },
33
- ];
34
-
35
- function FilterButton({
36
- label,
37
- color,
38
- active,
39
- count,
40
- onClick,
41
- }: {
42
- label: string;
43
- color: string;
44
- active: boolean;
45
- count: number;
46
- onClick: () => void;
47
- }) {
48
- return (
49
- <button
50
- onClick={onClick}
51
- className={`flex items-center gap-2 w-full text-left text-sm py-1 px-2 rounded transition-colors cursor-pointer ${
52
- active
53
- ? "bg-surface-2 text-white"
54
- : "text-text-muted hover:text-text hover:bg-surface-2/50"
55
- }`}
56
- >
57
- <span
58
- className="w-2 h-2 rounded-full flex-shrink-0"
59
- style={{ background: color }}
60
- />
61
- <span className="flex-1 truncate">{label}</span>
62
- <span className="text-xs opacity-50">{count}</span>
63
- </button>
64
- );
65
- }
66
-
67
- export default function Sidebar({
68
- nodes,
69
- activeFilter,
70
- onFilterChange,
71
- searchQuery,
72
- onSearchChange,
73
- searchResults,
74
- onSelect,
75
- selectedId,
76
- }: Props) {
77
- const tagCounts = new Map<string, number>();
78
- for (const n of nodes) {
79
- for (const t of n.tags) {
80
- tagCounts.set(t, (tagCounts.get(t) ?? 0) + 1);
81
- }
82
- }
83
-
84
- return (
85
- <div className="w-[240px] min-w-[200px] h-screen bg-surface border-r border-border flex flex-col overflow-hidden">
86
- {/* Header */}
87
- <div className="p-4 pb-3 border-b border-border">
88
- <h1 className="text-sm font-semibold text-white tracking-tight">
89
- Religions of the World
90
- </h1>
91
- <p className="text-xs text-text-muted mt-0.5">Knowledge Graph</p>
92
- </div>
93
-
94
- {/* Search */}
95
- <div className="px-3 pt-3 pb-2">
96
- <input
97
- type="text"
98
- value={searchQuery}
99
- onChange={(e) => onSearchChange(e.target.value)}
100
- placeholder="Search notes..."
101
- className="w-full bg-surface-2 border border-border rounded px-2.5 py-1.5 text-sm text-text placeholder:text-text-muted/50 outline-none focus:border-accent/50 transition-colors"
102
- />
103
- </div>
104
-
105
- {/* Search results */}
106
- {searchQuery.trim() && (
107
- <div className="px-2 pb-2 border-b border-border max-h-[200px] overflow-y-auto">
108
- {searchResults.length === 0 ? (
109
- <div className="text-xs text-text-muted px-2 py-1">No results</div>
110
- ) : (
111
- searchResults.map((n) => (
112
- <button
113
- key={n.id}
114
- onClick={() => onSelect(n.id)}
115
- className={`w-full text-left text-sm py-1 px-2 rounded truncate cursor-pointer transition-colors ${
116
- n.id === selectedId
117
- ? "bg-surface-2 text-white"
118
- : "text-text-muted hover:text-text hover:bg-surface-2/50"
119
- }`}
120
- >
121
- {n.label}
122
- <span className="text-xs opacity-40 ml-1">{n.type}</span>
123
- </button>
124
- ))
125
- )}
126
- </div>
127
- )}
128
-
129
- {/* Filters */}
130
- <div className="flex-1 overflow-y-auto px-2 py-3 space-y-4">
131
- {/* Clear filter */}
132
- {activeFilter && (
133
- <button
134
- onClick={() => onFilterChange(null)}
135
- className="flex items-center gap-1.5 text-xs text-accent px-2 py-1 hover:underline cursor-pointer"
136
- >
137
- &times; Clear filter
138
- </button>
139
- )}
140
-
141
- {/* By Family */}
142
- <div>
143
- <div className="text-[10px] font-semibold text-text-muted uppercase tracking-widest px-2 mb-1">
144
- By Family
145
- </div>
146
- {FAMILIES.map((f) => (
147
- <FilterButton
148
- key={f.tag}
149
- label={f.label}
150
- color={f.color}
151
- active={activeFilter === f.tag}
152
- count={tagCounts.get(f.tag) ?? 0}
153
- onClick={() =>
154
- onFilterChange(activeFilter === f.tag ? null : f.tag)
155
- }
156
- />
157
- ))}
158
- </div>
159
-
160
- {/* By Type */}
161
- <div>
162
- <div className="text-[10px] font-semibold text-text-muted uppercase tracking-widest px-2 mb-1">
163
- By Type
164
- </div>
165
- {TYPES.map((t) => (
166
- <FilterButton
167
- key={t.tag}
168
- label={t.label}
169
- color={t.color}
170
- active={activeFilter === t.tag}
171
- count={tagCounts.get(t.tag) ?? 0}
172
- onClick={() =>
173
- onFilterChange(activeFilter === t.tag ? null : t.tag)
174
- }
175
- />
176
- ))}
177
- </div>
178
- </div>
179
-
180
- {/* Footer */}
181
- <div className="p-3 border-t border-border text-[10px] text-text-muted/50 text-center">
182
- Powered by Parachute Vault
183
- </div>
184
- </div>
185
- );
186
- }
package/web/src/api.ts DELETED
@@ -1,21 +0,0 @@
1
- import type { Note, Tag } from "./types";
2
-
3
- const BASE = "/api";
4
-
5
- export async function fetchAllNotes(): Promise<Note[]> {
6
- const res = await fetch(`${BASE}/notes?include_content=true&limit=500`);
7
- if (!res.ok) throw new Error(`Failed to fetch notes: ${res.status}`);
8
- return res.json();
9
- }
10
-
11
- export async function fetchTags(): Promise<Tag[]> {
12
- const res = await fetch(`${BASE}/tags`);
13
- if (!res.ok) throw new Error(`Failed to fetch tags: ${res.status}`);
14
- return res.json();
15
- }
16
-
17
- export async function searchNotes(q: string): Promise<Note[]> {
18
- const res = await fetch(`${BASE}/search?q=${encodeURIComponent(q)}`);
19
- if (!res.ok) throw new Error(`Failed to search: ${res.status}`);
20
- return res.json();
21
- }
package/web/src/index.css DELETED
@@ -1,50 +0,0 @@
1
- @import "tailwindcss";
2
-
3
- @theme {
4
- --color-bg: #0a0a0f;
5
- --color-surface: #111118;
6
- --color-surface-2: #1a1a24;
7
- --color-border: #2a2a3a;
8
- --color-text: #d4d4e0;
9
- --color-text-muted: #7878a0;
10
- --color-accent: #7c8aff;
11
-
12
- --color-abrahamic: #5b8def;
13
- --color-dharmic: #e8a44a;
14
- --color-east-asian: #4ecdc4;
15
- --color-iranian: #c45bef;
16
- --color-indigenous: #5aaf5a;
17
- --color-nrm: #ef5b8d;
18
- --color-default: #6a6a8a;
19
-
20
- --font-serif: "Newsreader", Georgia, serif;
21
- --font-sans: "Inter", system-ui, sans-serif;
22
- }
23
-
24
- html {
25
- background: var(--color-bg);
26
- color: var(--color-text);
27
- font-family: var(--font-sans);
28
- }
29
-
30
- body {
31
- margin: 0;
32
- overflow: hidden;
33
- }
34
-
35
- /* Markdown rendering */
36
- .note-content h1 { font: 700 1.75rem/1.2 var(--font-serif); margin-bottom: 0.75rem; color: white; }
37
- .note-content h2 { font: 600 1.2rem/1.3 var(--font-sans); margin: 1.75rem 0 0.5rem; color: #bbbbd0; border-bottom: 1px solid var(--color-border); padding-bottom: 0.35rem; }
38
- .note-content h3 { font: 600 1.05rem/1.3 var(--font-sans); margin: 1.25rem 0 0.4rem; color: #a0a0b8; }
39
- .note-content p { margin: 0.5rem 0; line-height: 1.7; font-family: var(--font-serif); font-size: 1.02rem; }
40
- .note-content ul, .note-content ol { margin: 0.5rem 0; padding-left: 1.25rem; }
41
- .note-content li { margin: 0.2rem 0; line-height: 1.6; font-family: var(--font-serif); font-size: 1.02rem; }
42
- .note-content strong { color: white; font-weight: 600; }
43
- .note-content em { color: #b0b0c8; }
44
- .note-content blockquote { border-left: 3px solid var(--color-accent); padding-left: 1rem; margin: 1rem 0; color: var(--color-text-muted); }
45
- .note-content code { background: var(--color-surface-2); padding: 0.12rem 0.35rem; border-radius: 3px; font-size: 0.88em; }
46
-
47
- /* Scrollbar */
48
- ::-webkit-scrollbar { width: 5px; }
49
- ::-webkit-scrollbar-track { background: transparent; }
50
- ::-webkit-scrollbar-thumb { background: var(--color-border); border-radius: 3px; }
package/web/src/main.tsx DELETED
@@ -1,10 +0,0 @@
1
- import { StrictMode } from "react";
2
- import { createRoot } from "react-dom/client";
3
- import "./index.css";
4
- import App from "./App.tsx";
5
-
6
- createRoot(document.getElementById("root")!).render(
7
- <StrictMode>
8
- <App />
9
- </StrictMode>,
10
- );
package/web/src/types.ts DELETED
@@ -1,37 +0,0 @@
1
- export interface NoteIndex {
2
- id: string;
3
- path?: string;
4
- tags: string[];
5
- createdAt: string;
6
- updatedAt: string;
7
- }
8
-
9
- export interface Note extends NoteIndex {
10
- content: string;
11
- metadata?: Record<string, unknown>;
12
- }
13
-
14
- export interface GraphNode {
15
- id: string;
16
- path: string;
17
- label: string;
18
- type: string; // tradition, concept, figure, text, practice, school, theme, moc
19
- family: string; // abrahamic, dharmic, east-asian, iranian, indigenous, nrm, ""
20
- tags: string[];
21
- x?: number;
22
- y?: number;
23
- fx?: number | null;
24
- fy?: number | null;
25
- vx?: number;
26
- vy?: number;
27
- }
28
-
29
- export interface GraphEdge {
30
- source: string | GraphNode;
31
- target: string | GraphNode;
32
- }
33
-
34
- export interface Tag {
35
- name: string;
36
- count: number;
37
- }
package/web/src/utils.ts DELETED
@@ -1,107 +0,0 @@
1
- import type { Note, GraphNode, GraphEdge } from "./types";
2
-
3
- // Extract [[wikilinks]] from note content and resolve to note IDs
4
- const WIKILINK_RE = /\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g;
5
-
6
- export function extractWikilinks(content: string): string[] {
7
- const links: string[] = [];
8
- let m: RegExpExecArray | null;
9
- while ((m = WIKILINK_RE.exec(content)) !== null) {
10
- links.push(m[1]);
11
- }
12
- return links;
13
- }
14
-
15
- export function buildGraph(notes: Note[]): { nodes: GraphNode[]; edges: GraphEdge[] } {
16
- const pathToId = new Map<string, string>();
17
- for (const n of notes) {
18
- if (n.path) pathToId.set(n.path, n.id);
19
- }
20
-
21
- const nodes: GraphNode[] = notes.map((n) => ({
22
- id: n.id,
23
- path: n.path ?? n.id,
24
- label: labelFromPath(n.path ?? n.id),
25
- type: getType(n.tags),
26
- family: getFamily(n.tags),
27
- tags: n.tags,
28
- }));
29
-
30
- const nodeIds = new Set(nodes.map((n) => n.id));
31
- const edgeSet = new Set<string>();
32
- const edges: GraphEdge[] = [];
33
-
34
- for (const n of notes) {
35
- const targets = extractWikilinks(n.content);
36
- for (const target of targets) {
37
- const targetId = pathToId.get(target);
38
- if (targetId && targetId !== n.id && nodeIds.has(targetId)) {
39
- const key = `${n.id}->${targetId}`;
40
- if (!edgeSet.has(key)) {
41
- edgeSet.add(key);
42
- edges.push({ source: n.id, target: targetId });
43
- }
44
- }
45
- }
46
- }
47
-
48
- return { nodes, edges };
49
- }
50
-
51
- function labelFromPath(path: string): string {
52
- const parts = path.split("/");
53
- return parts[parts.length - 1];
54
- }
55
-
56
- function getType(tags: string[]): string {
57
- for (const t of tags) {
58
- if (t.startsWith("type/")) return t.slice(5);
59
- }
60
- return "other";
61
- }
62
-
63
- function getFamily(tags: string[]): string {
64
- for (const t of tags) {
65
- if (t.startsWith("family/")) return t.slice(7);
66
- }
67
- return "";
68
- }
69
-
70
- export const FAMILY_COLORS: Record<string, string> = {
71
- abrahamic: "#5b8def",
72
- dharmic: "#e8a44a",
73
- "east-asian": "#4ecdc4",
74
- iranian: "#c45bef",
75
- indigenous: "#5aaf5a",
76
- nrm: "#ef5b8d",
77
- };
78
-
79
- export const TYPE_COLORS: Record<string, string> = {
80
- tradition: "#ffffff",
81
- concept: "#8888a0",
82
- figure: "#e8a44a",
83
- text: "#5b8def",
84
- practice: "#4ecdc4",
85
- school: "#c45bef",
86
- theme: "#ef5b8d",
87
- moc: "#ffffff",
88
- };
89
-
90
- export function nodeColor(node: GraphNode): string {
91
- // For traditions/mocs, use family color
92
- if (node.type === "tradition" || node.type === "moc") {
93
- return FAMILY_COLORS[node.family] ?? "#6a6a8a";
94
- }
95
- // For others, try family first, then type
96
- if (node.family && FAMILY_COLORS[node.family]) {
97
- return FAMILY_COLORS[node.family];
98
- }
99
- return TYPE_COLORS[node.type] ?? "#6a6a8a";
100
- }
101
-
102
- export function nodeRadius(node: GraphNode): number {
103
- if (node.type === "moc") return 8;
104
- if (node.type === "tradition") return 6;
105
- if (node.type === "theme") return 5;
106
- return 3.5;
107
- }
@@ -1,25 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
- "target": "es2023",
5
- "lib": ["ES2023", "DOM", "DOM.Iterable"],
6
- "module": "esnext",
7
- "types": ["vite/client"],
8
- "skipLibCheck": true,
9
-
10
- /* Bundler mode */
11
- "moduleResolution": "bundler",
12
- "allowImportingTsExtensions": true,
13
- "verbatimModuleSyntax": true,
14
- "moduleDetection": "force",
15
- "noEmit": true,
16
- "jsx": "react-jsx",
17
-
18
- /* Linting */
19
- "noUnusedLocals": true,
20
- "noUnusedParameters": true,
21
- "erasableSyntaxOnly": true,
22
- "noFallthroughCasesInSwitch": true
23
- },
24
- "include": ["src"]
25
- }
package/web/tsconfig.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "files": [],
3
- "references": [
4
- { "path": "./tsconfig.app.json" },
5
- { "path": "./tsconfig.node.json" }
6
- ]
7
- }
@@ -1,24 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
- "target": "es2023",
5
- "lib": ["ES2023"],
6
- "module": "esnext",
7
- "types": ["node"],
8
- "skipLibCheck": true,
9
-
10
- /* Bundler mode */
11
- "moduleResolution": "bundler",
12
- "allowImportingTsExtensions": true,
13
- "verbatimModuleSyntax": true,
14
- "moduleDetection": "force",
15
- "noEmit": true,
16
-
17
- /* Linting */
18
- "noUnusedLocals": true,
19
- "noUnusedParameters": true,
20
- "erasableSyntaxOnly": true,
21
- "noFallthroughCasesInSwitch": true
22
- },
23
- "include": ["vite.config.ts"]
24
- }
@@ -1,15 +0,0 @@
1
- import { defineConfig } from "vite";
2
- import react from "@vitejs/plugin-react";
3
- import tailwindcss from "@tailwindcss/vite";
4
-
5
- export default defineConfig({
6
- plugins: [react(), tailwindcss()],
7
- server: {
8
- proxy: {
9
- "/api": {
10
- target: "http://localhost:1940/vaults/religions",
11
- changeOrigin: true,
12
- },
13
- },
14
- },
15
- });