@tekmidian/pai 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.
Files changed (66) hide show
  1. package/ARCHITECTURE.md +567 -0
  2. package/FEATURE.md +108 -0
  3. package/LICENSE +21 -0
  4. package/README.md +101 -0
  5. package/dist/auto-route-D7W6RE06.mjs +86 -0
  6. package/dist/auto-route-D7W6RE06.mjs.map +1 -0
  7. package/dist/cli/index.d.mts +1 -0
  8. package/dist/cli/index.mjs +5927 -0
  9. package/dist/cli/index.mjs.map +1 -0
  10. package/dist/config-DBh1bYM2.mjs +151 -0
  11. package/dist/config-DBh1bYM2.mjs.map +1 -0
  12. package/dist/daemon/index.d.mts +1 -0
  13. package/dist/daemon/index.mjs +56 -0
  14. package/dist/daemon/index.mjs.map +1 -0
  15. package/dist/daemon-mcp/index.d.mts +1 -0
  16. package/dist/daemon-mcp/index.mjs +185 -0
  17. package/dist/daemon-mcp/index.mjs.map +1 -0
  18. package/dist/daemon-v5O897D4.mjs +773 -0
  19. package/dist/daemon-v5O897D4.mjs.map +1 -0
  20. package/dist/db-4lSqLFb8.mjs +199 -0
  21. package/dist/db-4lSqLFb8.mjs.map +1 -0
  22. package/dist/db-BcDxXVBu.mjs +110 -0
  23. package/dist/db-BcDxXVBu.mjs.map +1 -0
  24. package/dist/detect-BHqYcjJ1.mjs +86 -0
  25. package/dist/detect-BHqYcjJ1.mjs.map +1 -0
  26. package/dist/detector-DKA83aTZ.mjs +74 -0
  27. package/dist/detector-DKA83aTZ.mjs.map +1 -0
  28. package/dist/embeddings-mfqv-jFu.mjs +91 -0
  29. package/dist/embeddings-mfqv-jFu.mjs.map +1 -0
  30. package/dist/factory-BDAiKtYR.mjs +42 -0
  31. package/dist/factory-BDAiKtYR.mjs.map +1 -0
  32. package/dist/index.d.mts +307 -0
  33. package/dist/index.d.mts.map +1 -0
  34. package/dist/index.mjs +11 -0
  35. package/dist/indexer-B20bPHL-.mjs +677 -0
  36. package/dist/indexer-B20bPHL-.mjs.map +1 -0
  37. package/dist/indexer-backend-BXaocO5r.mjs +360 -0
  38. package/dist/indexer-backend-BXaocO5r.mjs.map +1 -0
  39. package/dist/ipc-client-DPy7s3iu.mjs +156 -0
  40. package/dist/ipc-client-DPy7s3iu.mjs.map +1 -0
  41. package/dist/mcp/index.d.mts +1 -0
  42. package/dist/mcp/index.mjs +373 -0
  43. package/dist/mcp/index.mjs.map +1 -0
  44. package/dist/migrate-Bwj7qPaE.mjs +241 -0
  45. package/dist/migrate-Bwj7qPaE.mjs.map +1 -0
  46. package/dist/pai-marker-DX_mFLum.mjs +186 -0
  47. package/dist/pai-marker-DX_mFLum.mjs.map +1 -0
  48. package/dist/postgres-Ccvpc6fC.mjs +335 -0
  49. package/dist/postgres-Ccvpc6fC.mjs.map +1 -0
  50. package/dist/rolldown-runtime-95iHPtFO.mjs +18 -0
  51. package/dist/schemas-DjdwzIQ8.mjs +3405 -0
  52. package/dist/schemas-DjdwzIQ8.mjs.map +1 -0
  53. package/dist/search-PjftDxxs.mjs +282 -0
  54. package/dist/search-PjftDxxs.mjs.map +1 -0
  55. package/dist/sqlite-CHUrNtbI.mjs +90 -0
  56. package/dist/sqlite-CHUrNtbI.mjs.map +1 -0
  57. package/dist/tools-CLK4080-.mjs +805 -0
  58. package/dist/tools-CLK4080-.mjs.map +1 -0
  59. package/dist/utils-DEWdIFQ0.mjs +160 -0
  60. package/dist/utils-DEWdIFQ0.mjs.map +1 -0
  61. package/package.json +72 -0
  62. package/templates/README.md +181 -0
  63. package/templates/agent-prefs.example.md +362 -0
  64. package/templates/claude-md.template.md +733 -0
  65. package/templates/pai-project.template.md +13 -0
  66. package/templates/voices.example.json +251 -0
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # PAI Knowledge OS
2
+
3
+ Claude Code has a memory problem. Every new session starts cold — no idea what you built yesterday, what decisions you made, or where you left off. You re-explain everything, every time. PAI fixes this.
4
+
5
+ Install PAI and Claude remembers. Ask it what you were working on. Ask it to find that conversation about the database schema. Ask it to pick up exactly where the last session ended. It knows.
6
+
7
+ ---
8
+
9
+ ## What You Can Ask Claude
10
+
11
+ ### Searching Your Memory
12
+
13
+ - "Search your memory for authentication" — finds past sessions about auth, even with different words
14
+ - "What do you know about the Whazaa project?" — retrieves full project context instantly
15
+ - "Find where we discussed the database migration" — semantic search finds it even if you phrase it differently
16
+ - "Search your memory for that Chrome browser issue" — keyword and meaning-based search combined
17
+
18
+ ### Managing Projects
19
+
20
+ - "Show me all my projects" — lists everything PAI tracks with stats
21
+ - "Which project am I in?" — auto-detects from your current directory
22
+ - "What's the status of the PAI project?" — full project details, sessions, last activity
23
+ - "How many sessions does Whazaa have?" — project-level session history
24
+
25
+ ### Navigating Sessions
26
+
27
+ - "List my recent sessions" — shows what you've been working on across all projects
28
+ - "What did we do in session 42?" — retrieves any specific session by number
29
+ - "What were we working on last week?" — Claude knows, without you re-explaining
30
+ - "Clean up my session notes" — auto-names unnamed sessions and organizes by date
31
+
32
+ ### Keeping Things Safe
33
+
34
+ - "Back up everything" — creates a timestamped backup of all your data
35
+ - "How's the system doing?" — checks daemon health, index stats, embedding coverage
36
+
37
+ ### Obsidian Integration
38
+
39
+ - "Sync my Obsidian vault" — updates your linked vault with the latest notes
40
+ - "Open my notes in Obsidian" — launches Obsidian with your full knowledge graph
41
+
42
+ ---
43
+
44
+ ## Quick Start
45
+
46
+ Tell Claude Code:
47
+
48
+ > Clone https://github.com/mnott/PAI and set it up for me
49
+
50
+ Claude finds the setup skill, checks your system, runs the interactive wizard, and configures itself. You answer a few questions — simple mode or full mode, where your projects live, whether you use Obsidian — and Claude does the rest.
51
+
52
+ ---
53
+
54
+ ## Storage Options
55
+
56
+ PAI offers two modes, and the setup wizard asks which you prefer.
57
+
58
+ **Simple mode (SQLite)** — Zero dependencies beyond Bun. Keyword search only. Great for trying it out or for systems without Docker.
59
+
60
+ **Full mode (PostgreSQL + pgvector)** — Adds semantic search and vector embeddings. Finds things by meaning, not just exact words. "How does the reconnection logic work?" finds the right session even if it never used those exact words. Requires Docker.
61
+
62
+ ---
63
+
64
+ ## Prerequisites
65
+
66
+ - [Bun](https://bun.sh) — `curl -fsSL https://bun.sh/install | bash`
67
+ - [Docker](https://docs.docker.com/get-docker/) — only for full mode
68
+ - [Claude Code](https://claude.ai/code)
69
+ - macOS or Linux
70
+
71
+ ---
72
+
73
+ ## How It Works
74
+
75
+ A background service runs quietly alongside your work. Every five minutes it indexes your Claude Code projects and session notes — chunking them, hashing them for change detection, and storing them in a local database. When you ask Claude something about past work, it searches this index by keyword, by meaning, or both, and surfaces the relevant context in seconds.
76
+
77
+ Everything runs locally. No cloud. No API keys for the core system.
78
+
79
+ For the technical deep-dive — architecture, database schema, CLI reference, and development setup — see [ARCHITECTURE.md](ARCHITECTURE.md).
80
+
81
+ ---
82
+
83
+ ## Companion Projects
84
+
85
+ PAI works great alongside these tools (also by the same author):
86
+
87
+ - **[Whazaa](https://github.com/mnott/Whazaa)** — WhatsApp bridge for Claude Code (voice notes, screenshots, session routing)
88
+ - **[Coogle](https://github.com/mnott/Coogle)** — Google Workspace MCP daemon (Gmail, Calendar, Drive multiplexing)
89
+ - **[DEVONthink MCP](https://github.com/mnott/devonthink-mcp)** — DEVONthink integration for document search and archival
90
+
91
+ ---
92
+
93
+ ## Acknowledgments
94
+
95
+ PAI Knowledge OS is inspired by [Daniel Miessler](https://github.com/danielmiessler)'s concept of Personal AI Infrastructure and his [Fabric](https://github.com/danielmiessler/fabric) project — a Python CLI for augmenting human capabilities with reusable AI prompt patterns. Fabric is excellent and solves a different problem; PAI takes the same philosophy in a different direction: persistent memory, session continuity, and deep Claude Code integration. See [FEATURE.md](FEATURE.md) for a detailed comparison.
96
+
97
+ ---
98
+
99
+ ## License
100
+
101
+ MIT
@@ -0,0 +1,86 @@
1
+ import { r as readPaiMarker } from "./pai-marker-DX_mFLum.mjs";
2
+ import { t as detectProject } from "./detect-BHqYcjJ1.mjs";
3
+ import { existsSync } from "node:fs";
4
+ import { dirname, resolve } from "node:path";
5
+
6
+ //#region src/session/auto-route.ts
7
+ /**
8
+ * Determine which project a session should be routed to.
9
+ *
10
+ * @param registryDb Open PAI registry database
11
+ * @param federation Memory storage backend (needed only for topic fallback)
12
+ * @param cwd Working directory to detect from (defaults to process.cwd())
13
+ * @param context Optional conversation text for topic-based fallback
14
+ * @returns Best project match, or null if nothing matched
15
+ */
16
+ async function autoRoute(registryDb, federation, cwd, context) {
17
+ const target = resolve(cwd ?? process.cwd());
18
+ const pathMatch = detectProject(registryDb, target);
19
+ if (pathMatch) return {
20
+ slug: pathMatch.slug,
21
+ display_name: pathMatch.display_name,
22
+ root_path: pathMatch.root_path,
23
+ method: "path",
24
+ confidence: 1
25
+ };
26
+ const markerResult = findMarkerUpward(registryDb, target);
27
+ if (markerResult) return markerResult;
28
+ if (context && context.trim().length > 0) {
29
+ const { detectTopicShift } = await import("./detector-DKA83aTZ.mjs").then((n) => n.n);
30
+ const topicResult = await detectTopicShift(registryDb, federation, {
31
+ context,
32
+ threshold: .5
33
+ });
34
+ if (topicResult.suggestedProject && topicResult.confidence > 0) {
35
+ const projectRow = registryDb.prepare("SELECT slug, display_name, root_path FROM projects WHERE slug = ? AND status != 'archived'").get(topicResult.suggestedProject);
36
+ if (projectRow) return {
37
+ slug: projectRow.slug,
38
+ display_name: projectRow.display_name,
39
+ root_path: projectRow.root_path,
40
+ method: "topic",
41
+ confidence: topicResult.confidence
42
+ };
43
+ }
44
+ }
45
+ return null;
46
+ }
47
+ /**
48
+ * Walk up the directory tree from `startDir`, checking each level for a
49
+ * `Notes/PAI.md` file. If found, read the slug and look up the project.
50
+ *
51
+ * Stops at the filesystem root or after 20 levels (safety guard).
52
+ */
53
+ function findMarkerUpward(registryDb, startDir) {
54
+ let current = startDir;
55
+ let depth = 0;
56
+ while (depth < 20) {
57
+ if (existsSync(`${current}/Notes/PAI.md`)) {
58
+ const marker = readPaiMarker(current);
59
+ if (marker && marker.status !== "archived") {
60
+ const projectRow = registryDb.prepare("SELECT slug, display_name, root_path FROM projects WHERE slug = ? AND status != 'archived'").get(marker.slug);
61
+ if (projectRow) return {
62
+ slug: projectRow.slug,
63
+ display_name: projectRow.display_name,
64
+ root_path: projectRow.root_path,
65
+ method: "marker",
66
+ confidence: 1
67
+ };
68
+ }
69
+ }
70
+ const parent = dirname(current);
71
+ if (parent === current) break;
72
+ current = parent;
73
+ depth++;
74
+ }
75
+ return null;
76
+ }
77
+ /**
78
+ * Format an AutoRouteResult as JSON for machine consumption.
79
+ */
80
+ function formatAutoRouteJson(result) {
81
+ return JSON.stringify(result, null, 2);
82
+ }
83
+
84
+ //#endregion
85
+ export { autoRoute, formatAutoRouteJson };
86
+ //# sourceMappingURL=auto-route-D7W6RE06.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-route-D7W6RE06.mjs","names":[],"sources":["../src/session/auto-route.ts"],"sourcesContent":["/**\n * Auto-route: automatic project routing suggestion on session start.\n *\n * Given a working directory (and optional conversation context), determine\n * which registered project the session belongs to.\n *\n * Strategy (in priority order):\n * 1. Path match — exact or parent-directory match in the project registry\n * 2. Marker walk — walk up from cwd looking for Notes/PAI.md, resolve slug\n * 3. Topic match — BM25 keyword search against memory (requires context text)\n *\n * The function is stateless and works with direct DB access (no daemon\n * required), making it fast and safe to call during session startup.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport type { StorageBackend } from \"../storage/interface.js\";\nimport { resolve, dirname } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { readPaiMarker } from \"../registry/pai-marker.js\";\nimport { detectProject } from \"../cli/commands/detect.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type AutoRouteMethod = \"path\" | \"marker\" | \"topic\";\n\nexport interface AutoRouteResult {\n /** Project slug */\n slug: string;\n /** Human-readable project name */\n display_name: string;\n /** Absolute path to the project root */\n root_path: string;\n /** How the project was detected */\n method: AutoRouteMethod;\n /** Confidence [0,1]: 1.0 for path/marker matches, BM25 fraction for topic */\n confidence: number;\n}\n\n// ---------------------------------------------------------------------------\n// Core function\n// ---------------------------------------------------------------------------\n\n/**\n * Determine which project a session should be routed to.\n *\n * @param registryDb Open PAI registry database\n * @param federation Memory storage backend (needed only for topic fallback)\n * @param cwd Working directory to detect from (defaults to process.cwd())\n * @param context Optional conversation text for topic-based fallback\n * @returns Best project match, or null if nothing matched\n */\nexport async function autoRoute(\n registryDb: Database,\n federation: Database | StorageBackend,\n cwd?: string,\n context?: string\n): Promise<AutoRouteResult | null> {\n const target = resolve(cwd ?? process.cwd());\n\n // -------------------------------------------------------------------------\n // Strategy 1: Path match via registry\n // -------------------------------------------------------------------------\n\n const pathMatch = detectProject(registryDb, target);\n\n if (pathMatch) {\n return {\n slug: pathMatch.slug,\n display_name: pathMatch.display_name,\n root_path: pathMatch.root_path,\n method: \"path\",\n confidence: 1.0,\n };\n }\n\n // -------------------------------------------------------------------------\n // Strategy 2: PAI.md marker file walk\n //\n // Walk up from cwd, checking <dir>/Notes/PAI.md at each level.\n // Once found, resolve the slug against the registry to get full project info.\n // -------------------------------------------------------------------------\n\n const markerResult = findMarkerUpward(registryDb, target);\n if (markerResult) {\n return markerResult;\n }\n\n // -------------------------------------------------------------------------\n // Strategy 3: Topic detection (requires context text)\n // -------------------------------------------------------------------------\n\n if (context && context.trim().length > 0) {\n // Lazy import to avoid bundler pulling in daemon/index.mjs at module load time\n const { detectTopicShift } = await import(\"../topics/detector.js\");\n const topicResult = await detectTopicShift(registryDb, federation, {\n context,\n threshold: 0.5, // Lower threshold for initial routing (vs shift detection)\n });\n\n if (topicResult.suggestedProject && topicResult.confidence > 0) {\n // Look up the full project info from the registry\n const projectRow = registryDb\n .prepare(\n \"SELECT slug, display_name, root_path FROM projects WHERE slug = ? AND status != 'archived'\"\n )\n .get(topicResult.suggestedProject) as\n | { slug: string; display_name: string; root_path: string }\n | undefined;\n\n if (projectRow) {\n return {\n slug: projectRow.slug,\n display_name: projectRow.display_name,\n root_path: projectRow.root_path,\n method: \"topic\",\n confidence: topicResult.confidence,\n };\n }\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Marker walk helper\n// ---------------------------------------------------------------------------\n\n/**\n * Walk up the directory tree from `startDir`, checking each level for a\n * `Notes/PAI.md` file. If found, read the slug and look up the project.\n *\n * Stops at the filesystem root or after 20 levels (safety guard).\n */\nfunction findMarkerUpward(\n registryDb: Database,\n startDir: string\n): AutoRouteResult | null {\n let current = startDir;\n let depth = 0;\n\n while (depth < 20) {\n const markerPath = `${current}/Notes/PAI.md`;\n\n if (existsSync(markerPath)) {\n const marker = readPaiMarker(current);\n\n if (marker && marker.status !== \"archived\") {\n // Resolve slug to full project info in the registry\n const projectRow = registryDb\n .prepare(\n \"SELECT slug, display_name, root_path FROM projects WHERE slug = ? AND status != 'archived'\"\n )\n .get(marker.slug) as\n | { slug: string; display_name: string; root_path: string }\n | undefined;\n\n if (projectRow) {\n return {\n slug: projectRow.slug,\n display_name: projectRow.display_name,\n root_path: projectRow.root_path,\n method: \"marker\",\n confidence: 1.0,\n };\n }\n }\n }\n\n const parent = dirname(current);\n if (parent === current) break; // Reached filesystem root\n current = parent;\n depth++;\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Format helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Format an AutoRouteResult as a human-readable string for CLI output.\n */\nexport function formatAutoRoute(result: AutoRouteResult): string {\n const lines: string[] = [\n `slug: ${result.slug}`,\n `display_name: ${result.display_name}`,\n `root_path: ${result.root_path}`,\n `method: ${result.method}`,\n `confidence: ${(result.confidence * 100).toFixed(0)}%`,\n ];\n return lines.join(\"\\n\");\n}\n\n/**\n * Format an AutoRouteResult as JSON for machine consumption.\n */\nexport function formatAutoRouteJson(result: AutoRouteResult): string {\n return JSON.stringify(result, null, 2);\n}\n"],"mappings":";;;;;;;;;;;;;;;AAsDA,eAAsB,UACpB,YACA,YACA,KACA,SACiC;CACjC,MAAM,SAAS,QAAQ,OAAO,QAAQ,KAAK,CAAC;CAM5C,MAAM,YAAY,cAAc,YAAY,OAAO;AAEnD,KAAI,UACF,QAAO;EACL,MAAM,UAAU;EAChB,cAAc,UAAU;EACxB,WAAW,UAAU;EACrB,QAAQ;EACR,YAAY;EACb;CAUH,MAAM,eAAe,iBAAiB,YAAY,OAAO;AACzD,KAAI,aACF,QAAO;AAOT,KAAI,WAAW,QAAQ,MAAM,CAAC,SAAS,GAAG;EAExC,MAAM,EAAE,qBAAqB,MAAM,OAAO;EAC1C,MAAM,cAAc,MAAM,iBAAiB,YAAY,YAAY;GACjE;GACA,WAAW;GACZ,CAAC;AAEF,MAAI,YAAY,oBAAoB,YAAY,aAAa,GAAG;GAE9D,MAAM,aAAa,WAChB,QACC,6FACD,CACA,IAAI,YAAY,iBAAiB;AAIpC,OAAI,WACF,QAAO;IACL,MAAM,WAAW;IACjB,cAAc,WAAW;IACzB,WAAW,WAAW;IACtB,QAAQ;IACR,YAAY,YAAY;IACzB;;;AAKP,QAAO;;;;;;;;AAaT,SAAS,iBACP,YACA,UACwB;CACxB,IAAI,UAAU;CACd,IAAI,QAAQ;AAEZ,QAAO,QAAQ,IAAI;AAGjB,MAAI,WAFe,GAAG,QAAQ,eAEJ,EAAE;GAC1B,MAAM,SAAS,cAAc,QAAQ;AAErC,OAAI,UAAU,OAAO,WAAW,YAAY;IAE1C,MAAM,aAAa,WAChB,QACC,6FACD,CACA,IAAI,OAAO,KAAK;AAInB,QAAI,WACF,QAAO;KACL,MAAM,WAAW;KACjB,cAAc,WAAW;KACzB,WAAW,WAAW;KACtB,QAAQ;KACR,YAAY;KACb;;;EAKP,MAAM,SAAS,QAAQ,QAAQ;AAC/B,MAAI,WAAW,QAAS;AACxB,YAAU;AACV;;AAGF,QAAO;;;;;AAwBT,SAAgB,oBAAoB,QAAiC;AACnE,QAAO,KAAK,UAAU,QAAQ,MAAM,EAAE"}
@@ -0,0 +1 @@
1
+ export { };