@jhizzard/termdeck 0.2.1 → 0.2.3
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/README.md +27 -27
- package/config/config.example.yaml +10 -10
- package/package.json +1 -1
- package/packages/cli/src/index.js +17 -12
- package/packages/cli/src/{init-engram.js → init-mnestra.js} +15 -15
- package/packages/cli/src/init-rumen.js +6 -6
- package/packages/client/public/index.html +8 -8
- package/packages/server/src/config.js +8 -8
- package/packages/server/src/index.js +10 -10
- package/packages/server/src/{engram-bridge → mnestra-bridge}/index.js +18 -18
- package/packages/server/src/rag.js +6 -6
- package/packages/server/src/setup/migrations.js +9 -9
- package/packages/server/src/setup/{engram-migrations/001_engram_tables.sql → mnestra-migrations/001_mnestra_tables.sql} +4 -4
- package/packages/server/src/setup/{engram-migrations/002_engram_search_function.sql → mnestra-migrations/002_mnestra_search_function.sql} +1 -1
- package/packages/server/src/setup/{engram-migrations/003_engram_event_webhook.sql → mnestra-migrations/003_mnestra_event_webhook.sql} +2 -2
- package/packages/server/src/setup/{engram-migrations/004_engram_match_count_cap_and_explain.sql → mnestra-migrations/004_mnestra_match_count_cap_and_explain.sql} +5 -5
- package/packages/server/src/setup/{engram-migrations → mnestra-migrations}/005_v0_1_to_v0_2_upgrade.sql +3 -3
- package/packages/server/src/setup/{engram-migrations → mnestra-migrations}/006_memory_status_rpc.sql +1 -1
- package/packages/server/src/setup/prompts.js +1 -1
- package/packages/server/src/setup/rumen/migrations/001_rumen_tables.sql +1 -1
- package/packages/server/src/setup/supabase-url.js +1 -1
- package/packages/server/src/setup/yaml-io.js +3 -3
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ Enabling Flashback takes **one additional 15-minute setup step** — see Tier 2
|
|
|
26
26
|
|
|
27
27
|
## How Flashback works
|
|
28
28
|
|
|
29
|
-
When a panel's status transitions to `errored`, the server's output analyzer fires an event. The
|
|
29
|
+
When a panel's status transitions to `errored`, the server's output analyzer fires an event. The mnestra bridge takes the session context (type, project, last command, error tail) and queries your Mnestra memory store for the top similar match. If it finds one above the relevance threshold, the result is pushed to the panel's WebSocket as a `proactive_memory` message. The client renders it as a toast anchored to the panel, showing the match's project tag, source type, similarity score, and content snippet. You click the toast to expand into the Memory tab of that panel's drawer.
|
|
30
30
|
|
|
31
31
|
Rate-limited to once per 30 seconds per panel. Needs RAG enabled and credentials configured (Tier 2+). Fires on **any** terminal panel — shell, Claude Code, Python server, anything.
|
|
32
32
|
|
|
@@ -39,8 +39,8 @@ TermDeck is one piece of a three-tier memory stack. Each tier adds capability; e
|
|
|
39
39
|
| Tier | Install time | What you get |
|
|
40
40
|
|---|---|---|
|
|
41
41
|
| **1 — TermDeck alone** | 90 seconds | Full multiplexer, metadata, layouts, themes, tour, session history, command logging. Flashback silent. |
|
|
42
|
-
| **2 — +
|
|
43
|
-
| **3 — + Rumen async learning** | ~30 minutes | Async learning layer runs on a cron, synthesizes insights across projects, writes them back into
|
|
42
|
+
| **2 — + Mnestra memory store** | ~15 minutes | Flashback actively fires. Cross-session recall. "Ask about this terminal" queries real memories. Optional Mnestra-as-MCP for Claude Code / Cursor / Windsurf. |
|
|
43
|
+
| **3 — + Rumen async learning** | ~30 minutes | Async learning layer runs on a cron, synthesizes insights across projects, writes them back into Mnestra. Flashback starts surfacing cross-project patterns, not just direct matches. |
|
|
44
44
|
|
|
45
45
|
### Tier 1 — `npx @jhizzard/termdeck` (you're here)
|
|
46
46
|
|
|
@@ -61,22 +61,22 @@ What's included:
|
|
|
61
61
|
|
|
62
62
|
What's excluded at this tier: **Flashback is silent**, the "Ask about this terminal" input returns nothing, and no memories are surfaced. All other features work.
|
|
63
63
|
|
|
64
|
-
### Tier 2 — Add
|
|
64
|
+
### Tier 2 — Add Mnestra to light up Flashback
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
Mnestra is a separate npm package — `@jhizzard/mnestra@0.2.0` — that ships a Postgres-backed persistent memory store with an MCP server, a webhook server, six search tools, and six SQL migrations. It can be consumed by TermDeck (for Flashback), by Claude Code (as an MCP memory layer), or by any tool that speaks the MCP protocol.
|
|
67
67
|
|
|
68
68
|
To enable Flashback in TermDeck:
|
|
69
69
|
|
|
70
70
|
1. **Provision Postgres with pgvector.** Easiest: create a free Supabase project at supabase.com. Copy the **Project URL** and the **service_role key** from Project Settings → API.
|
|
71
|
-
2. **Apply
|
|
71
|
+
2. **Apply Mnestra's migrations** to the database. Run each in order via the Supabase SQL Editor, or via `psql`:
|
|
72
72
|
```bash
|
|
73
|
-
npm install -g @jhizzard/
|
|
74
|
-
psql "$DATABASE_URL" -f node_modules/@jhizzard/
|
|
75
|
-
psql "$DATABASE_URL" -f node_modules/@jhizzard/
|
|
76
|
-
psql "$DATABASE_URL" -f node_modules/@jhizzard/
|
|
77
|
-
psql "$DATABASE_URL" -f node_modules/@jhizzard/
|
|
78
|
-
psql "$DATABASE_URL" -f node_modules/@jhizzard/
|
|
79
|
-
psql "$DATABASE_URL" -f node_modules/@jhizzard/
|
|
73
|
+
npm install -g @jhizzard/mnestra
|
|
74
|
+
psql "$DATABASE_URL" -f node_modules/@jhizzard/mnestra/migrations/001_mnestra_tables.sql
|
|
75
|
+
psql "$DATABASE_URL" -f node_modules/@jhizzard/mnestra/migrations/002_mnestra_search_function.sql
|
|
76
|
+
psql "$DATABASE_URL" -f node_modules/@jhizzard/mnestra/migrations/003_mnestra_event_webhook.sql
|
|
77
|
+
psql "$DATABASE_URL" -f node_modules/@jhizzard/mnestra/migrations/004_mnestra_match_count_cap_and_explain.sql
|
|
78
|
+
psql "$DATABASE_URL" -f node_modules/@jhizzard/mnestra/migrations/005_v0_1_to_v0_2_upgrade.sql
|
|
79
|
+
psql "$DATABASE_URL" -f node_modules/@jhizzard/mnestra/migrations/006_memory_status_rpc.sql
|
|
80
80
|
```
|
|
81
81
|
3. **Get an OpenAI API key** (text-embedding-3-large).
|
|
82
82
|
4. **Create `~/.termdeck/secrets.env`:**
|
|
@@ -93,18 +93,18 @@ To enable Flashback in TermDeck:
|
|
|
93
93
|
supabaseUrl: ${SUPABASE_URL}
|
|
94
94
|
supabaseKey: ${SUPABASE_SERVICE_ROLE_KEY}
|
|
95
95
|
openaiApiKey: ${OPENAI_API_KEY}
|
|
96
|
-
|
|
96
|
+
mnestraMode: direct
|
|
97
97
|
```
|
|
98
98
|
6. **Restart TermDeck** (`Ctrl+C`, then `npx @jhizzard/termdeck` again).
|
|
99
99
|
|
|
100
100
|
Flashback now fires when panels error. Initially your store is empty, so nothing surfaces. As you use TermDeck day-to-day, the output analyzer captures session events, command history, and error contexts — each one becomes a memory. After a few days of real work, Flashback starts surfacing real hits.
|
|
101
101
|
|
|
102
|
-
### Tier 2 bonus —
|
|
102
|
+
### Tier 2 bonus — Mnestra as a Claude Code MCP server
|
|
103
103
|
|
|
104
|
-
Independently of TermDeck, install
|
|
104
|
+
Independently of TermDeck, install Mnestra as an MCP server so Claude Code (and Cursor / Windsurf / Cline / Continue) have persistent memory across sessions pointing at the same database TermDeck uses:
|
|
105
105
|
|
|
106
106
|
```bash
|
|
107
|
-
npm install -g @jhizzard/
|
|
107
|
+
npm install -g @jhizzard/mnestra
|
|
108
108
|
```
|
|
109
109
|
|
|
110
110
|
Edit `~/.claude/mcp.json`:
|
|
@@ -112,8 +112,8 @@ Edit `~/.claude/mcp.json`:
|
|
|
112
112
|
```json
|
|
113
113
|
{
|
|
114
114
|
"mcpServers": {
|
|
115
|
-
"
|
|
116
|
-
"command": "
|
|
115
|
+
"mnestra": {
|
|
116
|
+
"command": "mnestra",
|
|
117
117
|
"env": {
|
|
118
118
|
"SUPABASE_URL": "https://your-project.supabase.co",
|
|
119
119
|
"SUPABASE_SERVICE_ROLE_KEY": "sb_secret_...",
|
|
@@ -125,11 +125,11 @@ Edit `~/.claude/mcp.json`:
|
|
|
125
125
|
}
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
Restart Claude Code. Six MCP tools appear: `memory_remember`, `memory_recall`, `memory_search`, `memory_forget`, `memory_status`, `memory_summarize_session`. Your AI assistant can now read and write memories that TermDeck's Flashback will later surface automatically. See [github.com/jhizzard/
|
|
128
|
+
Restart Claude Code. Six MCP tools appear: `memory_remember`, `memory_recall`, `memory_search`, `memory_forget`, `memory_status`, `memory_summarize_session`. Your AI assistant can now read and write memories that TermDeck's Flashback will later surface automatically. See [github.com/jhizzard/mnestra](https://github.com/jhizzard/mnestra) for full MCP reference.
|
|
129
129
|
|
|
130
130
|
### Tier 3 — Add Rumen for async learning
|
|
131
131
|
|
|
132
|
-
Rumen is a separate npm package — `@jhizzard/rumen@0.2.0` — that ships as a Supabase Edge Function designed to run on a 15-minute `pg_cron` schedule. It's the async reflection layer over
|
|
132
|
+
Rumen is a separate npm package — `@jhizzard/rumen@0.2.0` — that ships as a Supabase Edge Function designed to run on a 15-minute `pg_cron` schedule. It's the async reflection layer over Mnestra: it reads recent session memories, cross-references them with your entire historical corpus via hybrid search, synthesizes insights via Claude Haiku, and writes the results back into `rumen_insights` (a new table alongside Mnestra's `memory_items`). TermDeck's Flashback and Claude Code's `memory_recall` both automatically benefit because insights flow back into the same database.
|
|
133
133
|
|
|
134
134
|
Rumen v0.2 is the current shipped version. Setup is still manual — Sprint 3 (next) ships a `termdeck init --rumen` one-liner that automates the deploy. For now, follow [github.com/jhizzard/rumen#readme](https://github.com/jhizzard/rumen) to deploy via the Supabase CLI.
|
|
135
135
|
|
|
@@ -162,12 +162,12 @@ Honest limits, stated upfront so the skeptic has nothing to chase:
|
|
|
162
162
|
│ - Express + ws │
|
|
163
163
|
│ - node-pty per session │
|
|
164
164
|
│ - SQLite persistence │
|
|
165
|
-
│ -
|
|
165
|
+
│ - Mnestra bridge (direct/webhook/mcp) │
|
|
166
166
|
└─────────────────┬───────────────────────┘
|
|
167
167
|
│ hybrid search + embeddings
|
|
168
168
|
▼
|
|
169
169
|
┌─────────────────────────────────────────┐
|
|
170
|
-
│
|
|
170
|
+
│ Mnestra │
|
|
171
171
|
│ - Postgres + pgvector │
|
|
172
172
|
│ - MCP server (6 tools) │
|
|
173
173
|
│ - HTTP webhook │
|
|
@@ -183,7 +183,7 @@ Honest limits, stated upfront so the skeptic has nothing to chase:
|
|
|
183
183
|
└─────────────────────────────────────────┘
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
-
All three packages are independent. You can use
|
|
186
|
+
All three packages are independent. You can use Mnestra alone as a Claude Code memory layer. You can run Rumen on any pgvector store with compatible schema. You can use TermDeck with zero memory features. The stack is designed to be progressively adopted.
|
|
187
187
|
|
|
188
188
|
---
|
|
189
189
|
|
|
@@ -201,7 +201,7 @@ For users who want more than `npx` — cloning from source, building a macOS `.a
|
|
|
201
201
|
|
|
202
202
|
## Related packages
|
|
203
203
|
|
|
204
|
-
- **[@jhizzard/
|
|
204
|
+
- **[@jhizzard/mnestra](https://www.npmjs.com/package/@jhizzard/mnestra)** — persistent dev memory MCP server. pgvector + hybrid search + 3-layer progressive disclosure. Works standalone with any MCP client.
|
|
205
205
|
- **[@jhizzard/rumen](https://www.npmjs.com/package/@jhizzard/rumen)** — async learning layer. Extract/Relate/Synthesize loop over any pgvector store. Supabase Edge Function + `pg_cron`.
|
|
206
206
|
|
|
207
207
|
---
|
|
@@ -218,7 +218,7 @@ npm run dev
|
|
|
218
218
|
The server runs at `http://127.0.0.1:3000` with file-watch reload. Workspace layout:
|
|
219
219
|
|
|
220
220
|
- `packages/cli/src/` — the `termdeck` binary launcher
|
|
221
|
-
- `packages/server/src/` — Express + WebSocket + PTY +
|
|
221
|
+
- `packages/server/src/` — Express + WebSocket + PTY + Mnestra bridge + session analyzer
|
|
222
222
|
- `packages/client/public/` — vanilla-JS dashboard (single HTML file, no build step, loads xterm.js from CDN)
|
|
223
223
|
- `config/` — example `config.yaml` and `secrets.env.example`
|
|
224
224
|
- `docs/` — planning documents, launch strategy, install guide, followup items, ship checklist
|
|
@@ -258,7 +258,7 @@ MIT © Joshua Izzard. See [LICENSE](LICENSE).
|
|
|
258
258
|
- GitHub: [github.com/jhizzard/termdeck](https://github.com/jhizzard/termdeck)
|
|
259
259
|
- npm: [@jhizzard/termdeck](https://www.npmjs.com/package/@jhizzard/termdeck)
|
|
260
260
|
- Issues: [github.com/jhizzard/termdeck/issues](https://github.com/jhizzard/termdeck/issues)
|
|
261
|
-
-
|
|
261
|
+
- Mnestra: [github.com/jhizzard/mnestra](https://github.com/jhizzard/mnestra) · [npm](https://www.npmjs.com/package/@jhizzard/mnestra)
|
|
262
262
|
- Rumen: [github.com/jhizzard/rumen](https://github.com/jhizzard/rumen) · [npm](https://www.npmjs.com/package/@jhizzard/rumen)
|
|
263
263
|
|
|
264
264
|
Built because every LLM starts from zero, and every terminal starts from zero, and I got tired of re-debugging the same CORS error.
|
|
@@ -35,20 +35,20 @@ rag:
|
|
|
35
35
|
# developerId: your-username (defaults to OS username)
|
|
36
36
|
syncIntervalMs: 10000
|
|
37
37
|
|
|
38
|
-
#
|
|
38
|
+
# Mnestra bridge — how /api/ai/query talks to @jhizzard/mnestra.
|
|
39
39
|
# direct — server embeds + queries Supabase itself (default, preserves v0.1 behavior)
|
|
40
|
-
# webhook — POST to a running `
|
|
41
|
-
# mcp — spawn the local `
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
#
|
|
40
|
+
# webhook — POST to a running `mnestra serve` HTTP webhook (see mnestraWebhookUrl)
|
|
41
|
+
# mcp — spawn the local `mnestra` binary and talk JSON-RPC over stdio
|
|
42
|
+
mnestraMode: direct
|
|
43
|
+
mnestraWebhookUrl: http://localhost:37778/mnestra
|
|
44
|
+
# mnestraBinary: mnestra # override path to the @jhizzard/mnestra CLI for mcp mode
|
|
45
45
|
|
|
46
46
|
# Supabase table names (created by config/supabase-migration.sql)
|
|
47
47
|
tables:
|
|
48
|
-
session:
|
|
49
|
-
project:
|
|
50
|
-
developer:
|
|
51
|
-
commands:
|
|
48
|
+
session: mnestra_session_memory
|
|
49
|
+
project: mnestra_project_memory
|
|
50
|
+
developer: mnestra_developer_memory
|
|
51
|
+
commands: mnestra_commands
|
|
52
52
|
|
|
53
53
|
# Per-session markdown logs written to ~/.termdeck/sessions/ on session exit.
|
|
54
54
|
# Zero-config: works without Supabase / OpenAI. Optional LLM summary requires
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jhizzard/termdeck",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Browser-based terminal multiplexer with metadata overlays, panel flashback memory recall, and AI-aware session management",
|
|
5
5
|
"bin": {
|
|
6
6
|
"termdeck": "./packages/cli/src/index.js"
|
|
@@ -3,8 +3,13 @@
|
|
|
3
3
|
// TermDeck CLI launcher
|
|
4
4
|
// Usage:
|
|
5
5
|
// termdeck [--port 3000] [--no-open]
|
|
6
|
-
// termdeck init --
|
|
7
|
-
// termdeck init --rumen [flags]
|
|
6
|
+
// termdeck init --mnestra [flags] # Tier 2 memory setup (wired to init-mnestra.js)
|
|
7
|
+
// termdeck init --rumen [flags] # Tier 3 async learning deploy
|
|
8
|
+
//
|
|
9
|
+
// Note (Sprint 3): the `--mnestra` flag name matches the current init-mnestra.js
|
|
10
|
+
// filename. When the main orchestrator completes the Mnestra → Mnestra rename
|
|
11
|
+
// sweep over this repo, both the flag name and the filename should flip to
|
|
12
|
+
// `--mnestra` / `init-mnestra.js` together.
|
|
8
13
|
|
|
9
14
|
const path = require('path');
|
|
10
15
|
const { execSync } = require('child_process');
|
|
@@ -12,10 +17,10 @@ const { execSync } = require('child_process');
|
|
|
12
17
|
// Parse CLI args
|
|
13
18
|
const args = process.argv.slice(2);
|
|
14
19
|
|
|
15
|
-
// Subcommand dispatch — handle `termdeck init --
|
|
16
|
-
// falling through to the default launcher's flag parsing.
|
|
17
|
-
//
|
|
18
|
-
//
|
|
20
|
+
// Subcommand dispatch — handle `termdeck init --mnestra|--rumen` before
|
|
21
|
+
// falling through to the default launcher's flag parsing. The `require` of
|
|
22
|
+
// init-*.js is lazy so users running the normal `termdeck` command never pay
|
|
23
|
+
// the cost of loading pg / supabase helpers at startup.
|
|
19
24
|
if (args[0] === 'init') {
|
|
20
25
|
const mode = args[1];
|
|
21
26
|
const rest = args.slice(2);
|
|
@@ -23,9 +28,9 @@ if (args[0] === 'init') {
|
|
|
23
28
|
const fn = require(modPath);
|
|
24
29
|
return fn(rest).then((code) => process.exit(code || 0));
|
|
25
30
|
};
|
|
26
|
-
if (mode === '--
|
|
27
|
-
run(path.join(__dirname, 'init-
|
|
28
|
-
console.error('[cli] init --
|
|
31
|
+
if (mode === '--mnestra') {
|
|
32
|
+
run(path.join(__dirname, 'init-mnestra.js')).catch((err) => {
|
|
33
|
+
console.error('[cli] init --mnestra failed:', err && err.stack || err);
|
|
29
34
|
process.exit(1);
|
|
30
35
|
});
|
|
31
36
|
return;
|
|
@@ -37,8 +42,8 @@ if (args[0] === 'init') {
|
|
|
37
42
|
});
|
|
38
43
|
return;
|
|
39
44
|
}
|
|
40
|
-
console.error('Usage: termdeck init --
|
|
41
|
-
console.error(' termdeck init --
|
|
45
|
+
console.error('Usage: termdeck init --mnestra | --rumen');
|
|
46
|
+
console.error(' termdeck init --mnestra Configure Tier 2 memory (Supabase + Mnestra)');
|
|
42
47
|
console.error(' termdeck init --rumen Deploy Tier 3 async learning (Rumen)');
|
|
43
48
|
process.exit(1);
|
|
44
49
|
}
|
|
@@ -61,7 +66,7 @@ for (let i = 0; i < args.length; i++) {
|
|
|
61
66
|
termdeck --port 8080 Start on custom port
|
|
62
67
|
termdeck --no-open Don't auto-open browser
|
|
63
68
|
termdeck --session-logs Write per-session markdown logs to ~/.termdeck/sessions/
|
|
64
|
-
termdeck init --
|
|
69
|
+
termdeck init --mnestra Configure Tier 2 memory (Supabase + Mnestra)
|
|
65
70
|
termdeck init --rumen Deploy Tier 3 async learning (Rumen)
|
|
66
71
|
|
|
67
72
|
Keyboard shortcuts (in browser):
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// `termdeck init --
|
|
4
|
-
// layer. Wraps the six manual
|
|
3
|
+
// `termdeck init --mnestra` — interactive wizard for TermDeck's Tier 2 memory
|
|
4
|
+
// layer. Wraps the six manual Mnestra setup steps into one command.
|
|
5
5
|
//
|
|
6
6
|
// Steps:
|
|
7
7
|
// 1. Collect Supabase URL, service_role key, direct DB URL, OpenAI + Anthropic keys
|
|
8
8
|
// 2. Connect via `pg` using the direct URL
|
|
9
|
-
// 3. Apply the six bundled
|
|
9
|
+
// 3. Apply the six bundled Mnestra migrations in order
|
|
10
10
|
// 4. Write ~/.termdeck/secrets.env (merge-aware, preserves existing values)
|
|
11
11
|
// 5. Update ~/.termdeck/config.yaml to enable RAG + point at ${VAR} refs
|
|
12
12
|
// 6. Verify with a memory_status_aggregation() call
|
|
@@ -35,9 +35,9 @@ const {
|
|
|
35
35
|
|
|
36
36
|
const HELP = [
|
|
37
37
|
'',
|
|
38
|
-
'TermDeck
|
|
38
|
+
'TermDeck Mnestra Setup',
|
|
39
39
|
'',
|
|
40
|
-
'Usage: termdeck init --
|
|
40
|
+
'Usage: termdeck init --mnestra [flags]',
|
|
41
41
|
'',
|
|
42
42
|
'Flags:',
|
|
43
43
|
' --help Print this message and exit',
|
|
@@ -48,10 +48,10 @@ const HELP = [
|
|
|
48
48
|
'What this does:',
|
|
49
49
|
' 1. Prompts for Supabase URL, service_role key, direct Postgres connection',
|
|
50
50
|
' string, OpenAI API key, and (optional) Anthropic API key.',
|
|
51
|
-
' 2. Applies the six
|
|
51
|
+
' 2. Applies the six Mnestra schema + RPC migrations via node-postgres.',
|
|
52
52
|
' 3. Writes ~/.termdeck/secrets.env (merge-aware, preserves existing values).',
|
|
53
53
|
' 4. Updates ~/.termdeck/config.yaml to enable RAG and reference ${VAR} keys.',
|
|
54
|
-
' 5. Verifies the
|
|
54
|
+
' 5. Verifies the Mnestra store is reachable via memory_status_aggregation().',
|
|
55
55
|
'',
|
|
56
56
|
'Every secret stays on your machine. Nothing is ever printed once entered.',
|
|
57
57
|
''
|
|
@@ -70,10 +70,10 @@ function parseFlags(argv) {
|
|
|
70
70
|
|
|
71
71
|
function printBanner() {
|
|
72
72
|
process.stdout.write(`
|
|
73
|
-
TermDeck
|
|
73
|
+
TermDeck Mnestra Setup
|
|
74
74
|
─────────────────────
|
|
75
75
|
|
|
76
|
-
This wizard configures TermDeck's Tier 2 memory layer (
|
|
76
|
+
This wizard configures TermDeck's Tier 2 memory layer (Mnestra) by:
|
|
77
77
|
1. Asking for your Supabase URL and service_role key
|
|
78
78
|
2. Asking for a direct Postgres connection string
|
|
79
79
|
3. Applying six SQL migrations to the database
|
|
@@ -166,9 +166,9 @@ async function promptSecretWithValidation(validator) {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
async function applyMigrations(client, dryRun) {
|
|
169
|
-
const files = migrations.
|
|
169
|
+
const files = migrations.listMnestraMigrations();
|
|
170
170
|
if (files.length === 0) {
|
|
171
|
-
throw new Error('No
|
|
171
|
+
throw new Error('No Mnestra migrations found. TermDeck install looks corrupted.');
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
for (const file of files) {
|
|
@@ -257,7 +257,7 @@ function writeLocalConfig(inputs, dryRun) {
|
|
|
257
257
|
|
|
258
258
|
function printNextSteps() {
|
|
259
259
|
process.stdout.write(`
|
|
260
|
-
|
|
260
|
+
Mnestra is configured.
|
|
261
261
|
|
|
262
262
|
Next steps:
|
|
263
263
|
1. Restart TermDeck: termdeck
|
|
@@ -280,7 +280,7 @@ async function main(argv) {
|
|
|
280
280
|
try {
|
|
281
281
|
inputs = await collectInputs({ yes: flags.yes });
|
|
282
282
|
} catch (err) {
|
|
283
|
-
process.stderr.write(`\n[init --
|
|
283
|
+
process.stderr.write(`\n[init --mnestra] ${err.message}\n`);
|
|
284
284
|
return 2;
|
|
285
285
|
}
|
|
286
286
|
|
|
@@ -322,7 +322,7 @@ async function main(argv) {
|
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
324
|
} catch (err) {
|
|
325
|
-
process.stderr.write(`\n[init --
|
|
325
|
+
process.stderr.write(`\n[init --mnestra] ${err.message}\n`);
|
|
326
326
|
return 5;
|
|
327
327
|
} finally {
|
|
328
328
|
try { await client.end(); } catch (_err) { /* ignore */ }
|
|
@@ -336,7 +336,7 @@ if (require.main === module) {
|
|
|
336
336
|
main(process.argv.slice(2))
|
|
337
337
|
.then((code) => process.exit(code || 0))
|
|
338
338
|
.catch((err) => {
|
|
339
|
-
process.stderr.write(`\n[init --
|
|
339
|
+
process.stderr.write(`\n[init --mnestra] unexpected error: ${err && err.stack || err}\n`);
|
|
340
340
|
process.exit(1);
|
|
341
341
|
});
|
|
342
342
|
}
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
// `termdeck init --rumen` — interactive wizard for deploying Rumen as a
|
|
4
4
|
// Supabase Edge Function + pg_cron schedule against the same Supabase
|
|
5
|
-
// project that holds the
|
|
5
|
+
// project that holds the Mnestra store.
|
|
6
6
|
//
|
|
7
7
|
// Requirements checked at runtime:
|
|
8
8
|
// - `supabase` CLI on PATH
|
|
9
9
|
// - `deno` on PATH
|
|
10
10
|
// - `~/.termdeck/secrets.env` with SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY +
|
|
11
|
-
// DATABASE_URL + ANTHROPIC_API_KEY populated (run `termdeck init --
|
|
11
|
+
// DATABASE_URL + ANTHROPIC_API_KEY populated (run `termdeck init --mnestra` first)
|
|
12
12
|
//
|
|
13
13
|
// Steps:
|
|
14
14
|
// 1. Preflight: which supabase, which deno, read secrets.env
|
|
@@ -47,7 +47,7 @@ const HELP = [
|
|
|
47
47
|
' --skip-schedule Deploy the function but do not install the pg_cron schedule',
|
|
48
48
|
'',
|
|
49
49
|
'Requires: Supabase CLI and Deno already installed.',
|
|
50
|
-
'Requires: `termdeck init --
|
|
50
|
+
'Requires: `termdeck init --mnestra` has already run (needs secrets.env).',
|
|
51
51
|
''
|
|
52
52
|
].join('\n');
|
|
53
53
|
|
|
@@ -112,14 +112,14 @@ function preflight() {
|
|
|
112
112
|
}
|
|
113
113
|
ok();
|
|
114
114
|
|
|
115
|
-
step('Reading
|
|
115
|
+
step('Reading Mnestra config from ~/.termdeck/secrets.env...');
|
|
116
116
|
const secrets = dotenv.readSecrets();
|
|
117
117
|
const required = ['SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY', 'DATABASE_URL', 'ANTHROPIC_API_KEY'];
|
|
118
118
|
const missing = required.filter((k) => !secrets[k]);
|
|
119
119
|
if (missing.length > 0) {
|
|
120
120
|
fail(`missing keys: ${missing.join(', ')}`);
|
|
121
121
|
process.stderr.write(
|
|
122
|
-
'\nRun `termdeck init --
|
|
122
|
+
'\nRun `termdeck init --mnestra` first — it writes the keys this wizard needs.\n'
|
|
123
123
|
);
|
|
124
124
|
return null;
|
|
125
125
|
}
|
|
@@ -363,7 +363,7 @@ Next steps:
|
|
|
363
363
|
1. Monitor: psql "$DATABASE_URL" -c "SELECT * FROM rumen_jobs ORDER BY started_at DESC LIMIT 5"
|
|
364
364
|
2. Store the service_role key in Supabase Vault as \`rumen_service_role_key\`
|
|
365
365
|
so the cron call in migrations/002_pg_cron_schedule.sql can authenticate.
|
|
366
|
-
3. Rumen insights flow back into
|
|
366
|
+
3. Rumen insights flow back into Mnestra's memory_items via rumen_insights.
|
|
367
367
|
4. TermDeck's Flashback will surface cross-project patterns automatically.
|
|
368
368
|
`);
|
|
369
369
|
}
|
|
@@ -1901,7 +1901,7 @@
|
|
|
1901
1901
|
|
|
1902
1902
|
toast.innerHTML = `
|
|
1903
1903
|
<button class="t-dismiss" aria-label="Dismiss">×</button>
|
|
1904
|
-
<div class="t-title">
|
|
1904
|
+
<div class="t-title">Mnestra — possible match</div>
|
|
1905
1905
|
<div class="t-body">Found a similar error in <b>${proj}</b>${score ? ` · ${score}` : ''} — click to see.</div>
|
|
1906
1906
|
<div class="t-meta">${snippet}</div>
|
|
1907
1907
|
`;
|
|
@@ -2596,7 +2596,7 @@
|
|
|
2596
2596
|
// Early return if AI queries are not available
|
|
2597
2597
|
if (!state.config.aiQueryAvailable) {
|
|
2598
2598
|
entry.terminal.write(
|
|
2599
|
-
'\r\n\x1b[33m[
|
|
2599
|
+
'\r\n\x1b[33m[mnestra] AI queries are not available.\x1b[0m\r\n' +
|
|
2600
2600
|
'\x1b[33mTo enable, add the following to ~/.termdeck/config.yaml:\x1b[0m\r\n' +
|
|
2601
2601
|
'\x1b[90m rag:\r\n' +
|
|
2602
2602
|
' supabaseUrl: https://your-project.supabase.co\r\n' +
|
|
@@ -2618,7 +2618,7 @@
|
|
|
2618
2618
|
});
|
|
2619
2619
|
|
|
2620
2620
|
if (result.error) {
|
|
2621
|
-
entry.terminal.write(`\r\n\x1b[33m[
|
|
2621
|
+
entry.terminal.write(`\r\n\x1b[33m[mnestra] ${result.error}\x1b[0m\r\n`);
|
|
2622
2622
|
} else if (result.memories && result.memories.length > 0) {
|
|
2623
2623
|
// Cache hits for the Memory tab
|
|
2624
2624
|
if (!entry.memoryHits) entry.memoryHits = [];
|
|
@@ -2651,7 +2651,7 @@
|
|
|
2651
2651
|
return lines;
|
|
2652
2652
|
};
|
|
2653
2653
|
|
|
2654
|
-
entry.terminal.write(`\r\n\x1b[36m━━━
|
|
2654
|
+
entry.terminal.write(`\r\n\x1b[36m━━━ Mnestra: ${result.total} memories found ━━━\x1b[0m\r\n`);
|
|
2655
2655
|
for (const m of result.memories) {
|
|
2656
2656
|
const score = m.similarity ? `${(m.similarity * 100).toFixed(0)}%` : '';
|
|
2657
2657
|
const proj = m.project ? m.project : '';
|
|
@@ -2663,11 +2663,11 @@
|
|
|
2663
2663
|
}
|
|
2664
2664
|
entry.terminal.write(`\r\n\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m\r\n\r\n`);
|
|
2665
2665
|
} else {
|
|
2666
|
-
entry.terminal.write(`\r\n\x1b[33m[
|
|
2666
|
+
entry.terminal.write(`\r\n\x1b[33m[mnestra] No relevant memories found.\x1b[0m\r\n`);
|
|
2667
2667
|
}
|
|
2668
2668
|
} catch (err) {
|
|
2669
2669
|
console.error('[client] AI query failed:', err);
|
|
2670
|
-
entry.terminal.write(`\r\n\x1b[31m[
|
|
2670
|
+
entry.terminal.write(`\r\n\x1b[31m[mnestra] Query failed: ${err.message}\x1b[0m\r\n`);
|
|
2671
2671
|
}
|
|
2672
2672
|
|
|
2673
2673
|
inputEl.value = '';
|
|
@@ -3073,14 +3073,14 @@
|
|
|
3073
3073
|
{
|
|
3074
3074
|
target: '.ctrl-input',
|
|
3075
3075
|
title: 'Ask about this terminal',
|
|
3076
|
-
body: `Type a question here and TermDeck queries your <strong>
|
|
3076
|
+
body: `Type a question here and TermDeck queries your <strong>Mnestra memory store</strong> for relevant context — scoped to the current panel's project. Prefix with <kbd>all:</kbd> to search every project. Results render inline in the terminal with similarity scores.`,
|
|
3077
3077
|
onEnter: async () => { await openFirstPanelDrawer('overview'); },
|
|
3078
3078
|
fallback: '#topbarQuickLaunch',
|
|
3079
3079
|
},
|
|
3080
3080
|
{
|
|
3081
3081
|
target: null,
|
|
3082
3082
|
title: 'Flashback — proactive recall',
|
|
3083
|
-
body: `When a panel errors out, TermDeck <strong>automatically</strong> queries
|
|
3083
|
+
body: `When a panel errors out, TermDeck <strong>automatically</strong> queries Mnestra for similar past errors and surfaces the top match as a toast. You don't have to ask. Rate-limited to one per 30 seconds per panel. Click the toast to open the Memory tab with the full hit expanded.`,
|
|
3084
3084
|
},
|
|
3085
3085
|
{
|
|
3086
3086
|
target: '.prompt-bar',
|
|
@@ -141,13 +141,13 @@ function defaultConfig() {
|
|
|
141
141
|
anthropicApiKey: null,
|
|
142
142
|
developerId: os.userInfo().username,
|
|
143
143
|
syncIntervalMs: 10000,
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
mnestraMode: 'direct',
|
|
145
|
+
mnestraWebhookUrl: 'http://localhost:37778/mnestra',
|
|
146
146
|
tables: {
|
|
147
|
-
session: '
|
|
148
|
-
project: '
|
|
149
|
-
developer: '
|
|
150
|
-
commands: '
|
|
147
|
+
session: 'mnestra_session_memory',
|
|
148
|
+
project: 'mnestra_project_memory',
|
|
149
|
+
developer: 'mnestra_developer_memory',
|
|
150
|
+
commands: 'mnestra_commands'
|
|
151
151
|
}
|
|
152
152
|
},
|
|
153
153
|
sessionLogs: {
|
|
@@ -194,8 +194,8 @@ rag:
|
|
|
194
194
|
openaiApiKey: \${OPENAI_API_KEY}
|
|
195
195
|
anthropicApiKey: \${ANTHROPIC_API_KEY}
|
|
196
196
|
syncIntervalMs: 10000
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
mnestraMode: direct
|
|
198
|
+
mnestraWebhookUrl: http://localhost:37778/mnestra
|
|
199
199
|
|
|
200
200
|
sessionLogs:
|
|
201
201
|
enabled: false
|
|
@@ -17,7 +17,7 @@ try { Database = require('better-sqlite3'); } catch { Database = null; }
|
|
|
17
17
|
const { SessionManager } = require('./session');
|
|
18
18
|
const { initDatabase, logCommand, getSessionHistory, getProjectSessions } = require('./database');
|
|
19
19
|
const { RAGIntegration } = require('./rag');
|
|
20
|
-
const { createBridge } = require('./
|
|
20
|
+
const { createBridge } = require('./mnestra-bridge');
|
|
21
21
|
const { writeSessionLog } = require('./session-logger');
|
|
22
22
|
const { themes, statusColors } = require('./themes');
|
|
23
23
|
const { loadConfig, addProject } = require('./config');
|
|
@@ -54,10 +54,10 @@ function createServer(config) {
|
|
|
54
54
|
// Initialize session manager
|
|
55
55
|
const sessions = new SessionManager(db);
|
|
56
56
|
|
|
57
|
-
// Initialize RAG +
|
|
57
|
+
// Initialize RAG + Mnestra bridge
|
|
58
58
|
const rag = new RAGIntegration(config, db);
|
|
59
|
-
const
|
|
60
|
-
console.log(`[
|
|
59
|
+
const mnestraBridge = createBridge(config);
|
|
60
|
+
console.log(`[mnestra-bridge] mode=${mnestraBridge.mode}`);
|
|
61
61
|
|
|
62
62
|
// Wire RAG to session events
|
|
63
63
|
sessions.on('session:created', (s) => rag.onSessionCreated(s));
|
|
@@ -171,11 +171,11 @@ function createServer(config) {
|
|
|
171
171
|
rag.onStatusChanged(sess, oldStatus, newStatus);
|
|
172
172
|
};
|
|
173
173
|
|
|
174
|
-
// Proactive
|
|
174
|
+
// Proactive Mnestra queries on error — fire-and-forget, respects rag.enabled
|
|
175
175
|
session.onErrorDetected = (sess, ctx) => {
|
|
176
176
|
if (!rag.enabled) return;
|
|
177
177
|
const question = `${sess.meta.type} error ${ctx.lastCommand || ''} ${ctx.tail || ''}`.trim();
|
|
178
|
-
|
|
178
|
+
mnestraBridge.queryMnestra({
|
|
179
179
|
question,
|
|
180
180
|
project: sess.meta.project,
|
|
181
181
|
searchAll: false,
|
|
@@ -196,7 +196,7 @@ function createServer(config) {
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
}).catch((err) => {
|
|
199
|
-
console.warn('[
|
|
199
|
+
console.warn('[mnestra-bridge] proactive query failed:', err.message);
|
|
200
200
|
});
|
|
201
201
|
};
|
|
202
202
|
|
|
@@ -415,7 +415,7 @@ function createServer(config) {
|
|
|
415
415
|
});
|
|
416
416
|
});
|
|
417
417
|
|
|
418
|
-
// POST /api/ai/query - query
|
|
418
|
+
// POST /api/ai/query - query Mnestra memory via the bridge (direct|webhook|mcp)
|
|
419
419
|
app.post('/api/ai/query', async (req, res) => {
|
|
420
420
|
let { question, sessionId, project } = req.body;
|
|
421
421
|
if (!question) return res.status(400).json({ error: 'Missing question' });
|
|
@@ -435,7 +435,7 @@ function createServer(config) {
|
|
|
435
435
|
} : null;
|
|
436
436
|
|
|
437
437
|
try {
|
|
438
|
-
const { memories, total } = await
|
|
438
|
+
const { memories, total } = await mnestraBridge.queryMnestra({
|
|
439
439
|
question,
|
|
440
440
|
project,
|
|
441
441
|
searchAll,
|
|
@@ -455,7 +455,7 @@ function createServer(config) {
|
|
|
455
455
|
total
|
|
456
456
|
});
|
|
457
457
|
} catch (err) {
|
|
458
|
-
console.error('[
|
|
458
|
+
console.error('[mnestra-bridge] query failed:', err.message);
|
|
459
459
|
// Config-shaped errors are 503, everything else 502
|
|
460
460
|
const msg = err.message || 'Query failed';
|
|
461
461
|
const status = /not configured|OPENAI_API_KEY/i.test(msg) ? 503 : 502;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Mnestra bridge — routes TermDeck memory queries through one of three backends:
|
|
2
2
|
// - direct: talk to Supabase + OpenAI from the server (pre-bridge behavior)
|
|
3
|
-
// - webhook: POST to
|
|
4
|
-
// - mcp: spawn the @jhizzard/
|
|
3
|
+
// - webhook: POST to Mnestra's HTTP webhook server (T3.1) at rag.mnestraWebhookUrl
|
|
4
|
+
// - mcp: spawn the @jhizzard/mnestra binary and talk JSON-RPC over stdio
|
|
5
5
|
//
|
|
6
6
|
// All three modes return the same shape:
|
|
7
7
|
// { memories: Array<{ content, source_type, project, similarity, created_at }>, total }
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
const { spawn } = require('child_process');
|
|
12
12
|
|
|
13
13
|
function createBridge(config) {
|
|
14
|
-
const mode = config.rag?.
|
|
14
|
+
const mode = config.rag?.mnestraMode || 'direct';
|
|
15
15
|
const state = { mcpChild: null, mcpQueue: [], mcpNextId: 1, mcpBuffer: '' };
|
|
16
16
|
|
|
17
17
|
async function queryDirect({ question, project, searchAll }) {
|
|
@@ -40,7 +40,7 @@ function createBridge(config) {
|
|
|
40
40
|
});
|
|
41
41
|
if (!embeddingRes.ok) {
|
|
42
42
|
const err = await embeddingRes.text();
|
|
43
|
-
console.error('[
|
|
43
|
+
console.error('[mnestra-bridge:direct] embedding failed:', err);
|
|
44
44
|
throw new Error('Embedding generation failed');
|
|
45
45
|
}
|
|
46
46
|
const embeddingData = await embeddingRes.json();
|
|
@@ -68,7 +68,7 @@ function createBridge(config) {
|
|
|
68
68
|
});
|
|
69
69
|
if (!searchRes.ok) {
|
|
70
70
|
const err = await searchRes.text();
|
|
71
|
-
console.error('[
|
|
71
|
+
console.error('[mnestra-bridge:direct] supabase search failed:', err);
|
|
72
72
|
throw new Error('Memory search failed');
|
|
73
73
|
}
|
|
74
74
|
const rows = await searchRes.json();
|
|
@@ -85,7 +85,7 @@ function createBridge(config) {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
async function queryWebhook({ question, project, searchAll }) {
|
|
88
|
-
const url = config.rag?.
|
|
88
|
+
const url = config.rag?.mnestraWebhookUrl || 'http://localhost:37778/mnestra';
|
|
89
89
|
const res = await fetch(url, {
|
|
90
90
|
method: 'POST',
|
|
91
91
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -98,8 +98,8 @@ function createBridge(config) {
|
|
|
98
98
|
});
|
|
99
99
|
if (!res.ok) {
|
|
100
100
|
const err = await res.text();
|
|
101
|
-
console.error('[
|
|
102
|
-
throw new Error(`
|
|
101
|
+
console.error('[mnestra-bridge:webhook] request failed:', err);
|
|
102
|
+
throw new Error(`Mnestra webhook returned ${res.status}`);
|
|
103
103
|
}
|
|
104
104
|
const data = await res.json();
|
|
105
105
|
const rows = data.memories || [];
|
|
@@ -118,7 +118,7 @@ function createBridge(config) {
|
|
|
118
118
|
function ensureMcpChild() {
|
|
119
119
|
if (state.mcpChild && !state.mcpChild.killed) return state.mcpChild;
|
|
120
120
|
|
|
121
|
-
const bin = config.rag?.
|
|
121
|
+
const bin = config.rag?.mnestraBinary || 'mnestra';
|
|
122
122
|
const child = spawn(bin, ['serve', '--stdio'], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
123
123
|
state.mcpChild = child;
|
|
124
124
|
state.mcpBuffer = '';
|
|
@@ -135,24 +135,24 @@ function createBridge(config) {
|
|
|
135
135
|
const pending = state.mcpQueue.find((p) => p.id === msg.id);
|
|
136
136
|
if (pending) {
|
|
137
137
|
state.mcpQueue = state.mcpQueue.filter((p) => p !== pending);
|
|
138
|
-
if (msg.error) pending.reject(new Error(msg.error.message || '
|
|
138
|
+
if (msg.error) pending.reject(new Error(msg.error.message || 'Mnestra MCP error'));
|
|
139
139
|
else pending.resolve(msg.result);
|
|
140
140
|
}
|
|
141
141
|
} catch (err) {
|
|
142
|
-
console.error('[
|
|
142
|
+
console.error('[mnestra-bridge:mcp] parse error:', err.message, line);
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
});
|
|
146
146
|
|
|
147
147
|
child.stderr.on('data', (chunk) => {
|
|
148
|
-
console.error('[
|
|
148
|
+
console.error('[mnestra-bridge:mcp]', chunk.toString('utf-8').trim());
|
|
149
149
|
});
|
|
150
150
|
|
|
151
151
|
child.on('exit', (code, signal) => {
|
|
152
|
-
console.warn(`[
|
|
152
|
+
console.warn(`[mnestra-bridge:mcp] child exited (code=${code}, signal=${signal}); will respawn on next call`);
|
|
153
153
|
state.mcpChild = null;
|
|
154
154
|
for (const pending of state.mcpQueue) {
|
|
155
|
-
pending.reject(new Error('
|
|
155
|
+
pending.reject(new Error('Mnestra MCP child exited'));
|
|
156
156
|
}
|
|
157
157
|
state.mcpQueue = [];
|
|
158
158
|
});
|
|
@@ -177,7 +177,7 @@ function createBridge(config) {
|
|
|
177
177
|
const pending = state.mcpQueue.find((p) => p.id === id);
|
|
178
178
|
if (pending) {
|
|
179
179
|
state.mcpQueue = state.mcpQueue.filter((p) => p !== pending);
|
|
180
|
-
pending.reject(new Error('
|
|
180
|
+
pending.reject(new Error('Mnestra MCP call timed out'));
|
|
181
181
|
}
|
|
182
182
|
}, 15000);
|
|
183
183
|
});
|
|
@@ -214,7 +214,7 @@ function createBridge(config) {
|
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
async function
|
|
217
|
+
async function queryMnestra({ question, project, searchAll }) {
|
|
218
218
|
switch (mode) {
|
|
219
219
|
case 'webhook':
|
|
220
220
|
return queryWebhook({ question, project, searchAll });
|
|
@@ -226,7 +226,7 @@ function createBridge(config) {
|
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
return { mode,
|
|
229
|
+
return { mode, queryMnestra };
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
module.exports = { createBridge };
|
|
@@ -16,10 +16,10 @@ class RAGIntegration {
|
|
|
16
16
|
|
|
17
17
|
// Table configuration matching Josh's multi-layer schema
|
|
18
18
|
this.tables = {
|
|
19
|
-
sessionMemory: config.rag?.tables?.session || '
|
|
20
|
-
projectMemory: config.rag?.tables?.project || '
|
|
21
|
-
developerMemory: config.rag?.tables?.developer || '
|
|
22
|
-
commandLog: config.rag?.tables?.commands || '
|
|
19
|
+
sessionMemory: config.rag?.tables?.session || 'mnestra_session_memory',
|
|
20
|
+
projectMemory: config.rag?.tables?.project || 'mnestra_project_memory',
|
|
21
|
+
developerMemory: config.rag?.tables?.developer || 'mnestra_developer_memory',
|
|
22
|
+
commandLog: config.rag?.tables?.commands || 'mnestra_commands'
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
if (this.enabled) {
|
|
@@ -120,7 +120,7 @@ class RAGIntegration {
|
|
|
120
120
|
}
|
|
121
121
|
} catch (err) {
|
|
122
122
|
// Will be retried by sync loop
|
|
123
|
-
console.error('[
|
|
123
|
+
console.error('[mnestra] Push failed:', err.message);
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -163,7 +163,7 @@ class RAGIntegration {
|
|
|
163
163
|
markRagEventsSynced(this.db, synced);
|
|
164
164
|
}
|
|
165
165
|
} catch (err) {
|
|
166
|
-
console.error('[
|
|
166
|
+
console.error('[mnestra] Sync cycle error:', err.message);
|
|
167
167
|
}
|
|
168
168
|
}, this.syncInterval);
|
|
169
169
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// Discover the SQL migration files that ship bundled inside the TermDeck
|
|
2
|
-
// package. Both init wizards call this — init-
|
|
2
|
+
// package. Both init wizards call this — init-mnestra for the six Mnestra
|
|
3
3
|
// migrations, init-rumen for the two Rumen migrations.
|
|
4
4
|
//
|
|
5
|
-
// The wizards intentionally do NOT fall back to a sibling `../../
|
|
5
|
+
// The wizards intentionally do NOT fall back to a sibling `../../mnestra`
|
|
6
6
|
// working copy. Resolution order:
|
|
7
7
|
//
|
|
8
|
-
// 1. Files bundled at `packages/server/src/setup/
|
|
8
|
+
// 1. Files bundled at `packages/server/src/setup/mnestra-migrations/*.sql`
|
|
9
9
|
// (this directory is covered by the root package.json `files` glob).
|
|
10
|
-
// 2. Files at `node_modules/@jhizzard/
|
|
10
|
+
// 2. Files at `node_modules/@jhizzard/mnestra/migrations/*.sql` if that
|
|
11
11
|
// package is installed alongside TermDeck (future-proof path — shipping
|
|
12
|
-
// `@jhizzard/
|
|
12
|
+
// `@jhizzard/mnestra` as an optional peer would let us drop the bundled
|
|
13
13
|
// copy).
|
|
14
14
|
|
|
15
15
|
const fs = require('fs');
|
|
@@ -44,10 +44,10 @@ function tryNodeModules(packageName, migrationSubdir = 'migrations') {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function
|
|
48
|
-
const fromNm = tryNodeModules('@jhizzard/
|
|
47
|
+
function listMnestraMigrations() {
|
|
48
|
+
const fromNm = tryNodeModules('@jhizzard/mnestra');
|
|
49
49
|
if (fromNm.length > 0) return fromNm;
|
|
50
|
-
return listBundled('
|
|
50
|
+
return listBundled('mnestra-migrations');
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function listRumenMigrations() {
|
|
@@ -73,7 +73,7 @@ function readFile(filepath) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
module.exports = {
|
|
76
|
-
|
|
76
|
+
listMnestraMigrations,
|
|
77
77
|
listRumenMigrations,
|
|
78
78
|
rumenFunctionDir,
|
|
79
79
|
readFile
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
--
|
|
1
|
+
-- Mnestra v0.1 — core tables
|
|
2
2
|
--
|
|
3
3
|
-- Run against a Postgres 15+ database that has pgvector installed
|
|
4
4
|
-- (Supabase already ships with it). Apply in order:
|
|
5
|
-
--
|
|
6
|
-
--
|
|
7
|
-
--
|
|
5
|
+
-- 001_mnestra_tables.sql
|
|
6
|
+
-- 002_mnestra_search_function.sql
|
|
7
|
+
-- 003_mnestra_event_webhook.sql
|
|
8
8
|
|
|
9
9
|
create extension if not exists "vector";
|
|
10
10
|
create extension if not exists "pg_trgm";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
--
|
|
1
|
+
-- Mnestra v0.1 — real-time event webhook (Fix 6)
|
|
2
2
|
--
|
|
3
3
|
-- Fix 6 from RAG-MEMORY-IMPROVEMENTS-AND-TERMDECK-STRATEGY.md is a
|
|
4
4
|
-- real-time event intake path so TermDeck (or any other client) can
|
|
5
5
|
-- POST terminal events — "server started on :8080", "tests failing",
|
|
6
6
|
-- "error detected" — and have them land in memory immediately.
|
|
7
7
|
--
|
|
8
|
-
-- That intake is implemented as an HTTP endpoint inside the
|
|
8
|
+
-- That intake is implemented as an HTTP endpoint inside the Mnestra MCP
|
|
9
9
|
-- server process, not as a SQL trigger. This file exists as a placeholder
|
|
10
10
|
-- so the migration history is explicit and future database-side changes
|
|
11
11
|
-- (e.g. an events queue table for async ingestion) have a home.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
--
|
|
1
|
+
-- Mnestra v0.2 — match_count cap + EXPLAIN variant
|
|
2
2
|
--
|
|
3
3
|
-- Two changes to the search surface:
|
|
4
4
|
--
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
-- Default cap: 200. The original function was unbounded, which risks
|
|
7
7
|
-- runaway queries at scale (10k+ rows pulled per call).
|
|
8
8
|
--
|
|
9
|
-
-- Override per-database: ALTER DATABASE your_db SET
|
|
10
|
-
-- Override per-session: SET
|
|
9
|
+
-- Override per-database: ALTER DATABASE your_db SET mnestra.max_match_count = 500;
|
|
10
|
+
-- Override per-session: SET mnestra.max_match_count = 500;
|
|
11
11
|
-- Leave unset: cap defaults to 200.
|
|
12
12
|
--
|
|
13
13
|
-- 2. A new function `memory_hybrid_search_explain` that returns
|
|
14
14
|
-- EXPLAIN (ANALYZE, BUFFERS) output for an equivalent call. Used by
|
|
15
|
-
-- `
|
|
15
|
+
-- `mnestra diagnose` to troubleshoot slow recall queries.
|
|
16
16
|
--
|
|
17
17
|
-- Rerun-safe: CREATE OR REPLACE on both.
|
|
18
18
|
|
|
@@ -140,7 +140,7 @@ from scored s
|
|
|
140
140
|
order by s.score desc
|
|
141
141
|
limit least(
|
|
142
142
|
greatest(match_count, 1),
|
|
143
|
-
coalesce(nullif(current_setting('
|
|
143
|
+
coalesce(nullif(current_setting('mnestra.max_match_count', true), '')::int, 200)
|
|
144
144
|
);
|
|
145
145
|
$$;
|
|
146
146
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
--
|
|
1
|
+
-- Mnestra v0.2 — minimal additive delta for stores provisioned against the
|
|
2
2
|
-- original rag-system schema.
|
|
3
3
|
--
|
|
4
4
|
-- This migration is idempotent and non-destructive:
|
|
5
|
-
-- • adds only the single column (`archived`) that
|
|
5
|
+
-- • adds only the single column (`archived`) that Mnestra v0.2 reads/writes
|
|
6
6
|
-- but which is absent from the original rag-system `memory_items` table;
|
|
7
7
|
-- • re-creates the two partial indexes under v2 names so they cannot
|
|
8
8
|
-- collide with any pre-existing same-named index that uses a different
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
-- other SQL function — those remain on their production versions.
|
|
12
12
|
--
|
|
13
13
|
-- Apply once, in the Supabase SQL editor, against any existing store that
|
|
14
|
-
-- pre-dates
|
|
14
|
+
-- pre-dates Mnestra v0.2's `archived` soft-delete column.
|
|
15
15
|
|
|
16
16
|
alter table memory_items
|
|
17
17
|
add column if not exists archived boolean not null default false;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// hangs. The event-driven path works for both TTY and piped input, which lets
|
|
9
9
|
// us drive the wizard non-interactively in tests:
|
|
10
10
|
//
|
|
11
|
-
// printf 'a\nb\nc\n' | termdeck init --
|
|
11
|
+
// printf 'a\nb\nc\n' | termdeck init --mnestra --dry-run
|
|
12
12
|
//
|
|
13
13
|
// Secret prompts still mute stdout echo when TTY is attached; on non-TTY
|
|
14
14
|
// stdin they fall back to visible input so piped test runs work.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
-- Rumen v0.1 schema
|
|
2
2
|
-- Non-destructive: creates three new tables under the rumen_ namespace.
|
|
3
|
-
-- Does NOT modify or reference
|
|
3
|
+
-- Does NOT modify or reference Mnestra's existing memory_items / memory_sessions tables.
|
|
4
4
|
--
|
|
5
5
|
-- Apply with:
|
|
6
6
|
-- psql "$DIRECT_URL" -f migrations/001_rumen_tables.sql
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Parse + validate Supabase URLs and derive what we can from them without the
|
|
2
2
|
// database password. Useful for both init wizards:
|
|
3
3
|
//
|
|
4
|
-
// - init-
|
|
4
|
+
// - init-mnestra needs the project ref to show in status output and also
|
|
5
5
|
// needs a full DATABASE_URL to apply migrations; since the DB password
|
|
6
6
|
// cannot be derived from the project URL alone, the wizard prompts for
|
|
7
7
|
// the direct connection string separately.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Targeted writer for ~/.termdeck/config.yaml that the `init --
|
|
1
|
+
// Targeted writer for ~/.termdeck/config.yaml that the `init --mnestra` wizard
|
|
2
2
|
// uses to flip `rag.enabled: true` and point secret fields at `${VAR}` refs
|
|
3
3
|
// instead of inline values.
|
|
4
4
|
//
|
|
@@ -56,8 +56,8 @@ function updateRagConfig(updates) {
|
|
|
56
56
|
openaiApiKey: updates.openaiApiKey || rag.openaiApiKey || '${OPENAI_API_KEY}',
|
|
57
57
|
anthropicApiKey: updates.anthropicApiKey || rag.anthropicApiKey || '${ANTHROPIC_API_KEY}',
|
|
58
58
|
syncIntervalMs: rag.syncIntervalMs != null ? rag.syncIntervalMs : 10000,
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
mnestraMode: rag.mnestraMode || 'direct',
|
|
60
|
+
mnestraWebhookUrl: rag.mnestraWebhookUrl || 'http://localhost:37778/mnestra'
|
|
61
61
|
};
|
|
62
62
|
|
|
63
63
|
// Preserve any fields we didn't explicitly handle (e.g. tables, developerId).
|