@xultrax-web/agent-memory-mcp 0.9.0 → 0.10.1
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 +50 -22
- package/dist/index.js +26 -10
- package/dist/tui.js +183 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -64,17 +64,15 @@ If you want vector similarity search, semantic recall, or auto-relation extracti
|
|
|
64
64
|
|
|
65
65
|
## Install
|
|
66
66
|
|
|
67
|
-
###
|
|
67
|
+
### From npm (recommended)
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
|
-
npx -y
|
|
70
|
+
npx -y @xultrax-web/agent-memory-mcp
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
###
|
|
73
|
+
### Listed in the MCP Registry
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
npx -y @xultrax-web/agent-memory-mcp
|
|
77
|
-
```
|
|
75
|
+
`io.github.xultrax-web/agent-memory-mcp` · browse at https://registry.modelcontextprotocol.io
|
|
78
76
|
|
|
79
77
|
### Build locally
|
|
80
78
|
|
|
@@ -103,7 +101,7 @@ Same JSON, slightly different paths per client.
|
|
|
103
101
|
"mcpServers": {
|
|
104
102
|
"agent-memory": {
|
|
105
103
|
"command": "npx",
|
|
106
|
-
"args": ["-y", "
|
|
104
|
+
"args": ["-y", "@xultrax-web/agent-memory-mcp"]
|
|
107
105
|
}
|
|
108
106
|
}
|
|
109
107
|
}
|
|
@@ -117,7 +115,7 @@ Cline → MCP Servers → Add:
|
|
|
117
115
|
{
|
|
118
116
|
"agent-memory": {
|
|
119
117
|
"command": "npx",
|
|
120
|
-
"args": ["-y", "
|
|
118
|
+
"args": ["-y", "@xultrax-web/agent-memory-mcp"]
|
|
121
119
|
}
|
|
122
120
|
}
|
|
123
121
|
```
|
|
@@ -131,7 +129,7 @@ Cline → MCP Servers → Add:
|
|
|
131
129
|
"mcpServers": {
|
|
132
130
|
"agent-memory": {
|
|
133
131
|
"command": "npx",
|
|
134
|
-
"args": ["-y", "
|
|
132
|
+
"args": ["-y", "@xultrax-web/agent-memory-mcp"]
|
|
135
133
|
}
|
|
136
134
|
}
|
|
137
135
|
}
|
|
@@ -140,7 +138,7 @@ Cline → MCP Servers → Add:
|
|
|
140
138
|
> **Windows note:** if `npx` doesn't resolve cleanly, wrap with `cmd /c`:
|
|
141
139
|
>
|
|
142
140
|
> ```json
|
|
143
|
-
> { "command": "cmd", "args": ["/c", "npx", "-y", "
|
|
141
|
+
> { "command": "cmd", "args": ["/c", "npx", "-y", "@xultrax-web/agent-memory-mcp"] }
|
|
144
142
|
> ```
|
|
145
143
|
|
|
146
144
|
### Continue.dev
|
|
@@ -155,7 +153,7 @@ Cline → MCP Servers → Add:
|
|
|
155
153
|
"transport": {
|
|
156
154
|
"type": "stdio",
|
|
157
155
|
"command": "npx",
|
|
158
|
-
"args": ["-y", "
|
|
156
|
+
"args": ["-y", "@xultrax-web/agent-memory-mcp"]
|
|
159
157
|
}
|
|
160
158
|
}
|
|
161
159
|
]
|
|
@@ -272,6 +270,7 @@ agent-memory sync init git@github.com:you/agent-memory.git # multi-machine se
|
|
|
272
270
|
agent-memory sync push # commit + push local changes
|
|
273
271
|
agent-memory sync pull # fast-forward from remote
|
|
274
272
|
agent-memory sync status # local + ahead/behind state
|
|
273
|
+
agent-memory ui # launch the TUI (browse + edit interactively)
|
|
275
274
|
```
|
|
276
275
|
|
|
277
276
|
### Multi-machine memory (git sync)
|
|
@@ -429,25 +428,54 @@ This server is built to be used daily, not to demo well once.
|
|
|
429
428
|
- Per-machine state (`.lock`, `.events.jsonl`, `.trash/`) auto-excluded from sync.
|
|
430
429
|
- Default commit identity injected (`agent-memory@local`) so machines without `git config --global user.email` work without setup.
|
|
431
430
|
|
|
432
|
-
**
|
|
431
|
+
**Shipped in v0.10 · the visual identity (TUI):**
|
|
432
|
+
|
|
433
|
+
- **`agent-memory ui`** — Ink-based terminal UI for browsing, filtering, searching, and editing memories without leaving the terminal.
|
|
434
|
+
- Type-filter quick-keys (0-4 cycle through all/user/feedback/project/reference)
|
|
435
|
+
- Fuzzy live search with `/`
|
|
436
|
+
- `e` opens the highlighted memory in `$EDITOR` (vim/notepad/nano/whatever) — saves back to disk
|
|
437
|
+
- `d` soft-deletes with `y/n` confirmation
|
|
438
|
+
- Detail pane previews the body of the selected memory
|
|
439
|
+
- Color-coded by type, tag chips inline
|
|
440
|
+
|
|
441
|
+
**Landing in v0.11+:**
|
|
433
442
|
|
|
434
|
-
- TUI / web UI for browsing + editing memories in a clean interface
|
|
435
443
|
- Folder support (`.agent-memory/work/`, `.agent-memory/personal/`)
|
|
436
444
|
- Memory packs for shareable curated bundles
|
|
445
|
+
- Web UI for browser-based memory browsing (companion to the TUI)
|
|
446
|
+
- Auto-context loading (LLM gets relevant memories transparently before each prompt)
|
|
437
447
|
|
|
438
448
|
---
|
|
439
449
|
|
|
440
450
|
## Roadmap
|
|
441
451
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
452
|
+
### Released
|
|
453
|
+
|
|
454
|
+
| Version | Highlights |
|
|
455
|
+
| --------- | --------------------------------------------------------------------------------------------------- |
|
|
456
|
+
| v0.1 | Five-tool MVP, file storage, four-client config snippets |
|
|
457
|
+
| v0.2 | MCP Resources, Claude Code import (`agent-memory import-claude-code`), CLI mode, prettier baseline |
|
|
458
|
+
| v0.3 | Atomic writes, file locking, soft delete + `restore_memory`, `doctor` repair, schema versioning |
|
|
459
|
+
| v0.4 | Append-only event log (`.events.jsonl`), `stats` dashboard, `log_events` browser, color output |
|
|
460
|
+
| v0.5 | Fuzzy search via Fuse.js, relevance scoring, body-context snippets, `relevant_memories`, pagination |
|
|
461
|
+
| v0.6 | 25+ Vitest tests, GitHub Actions CI (Node 20/22/24 matrix), `COMPATIBILITY.md` |
|
|
462
|
+
| v0.7 | MCP Prompts (4 starter workflows), `verify_memory`, conflict detection on save |
|
|
463
|
+
| v0.8 | Tags, `[[wiki-links]]`, `find_backlinks`, `find_related` |
|
|
464
|
+
| v0.8.1 | Trusted Publishing live · tokenless OIDC publishes to npm + MCP Registry on git tag |
|
|
465
|
+
| **v0.9** | **`agent-memory sync` · multi-machine memory via git remote (init/push/pull/status/log)** |
|
|
466
|
+
| **v0.10** | **Ink-based TUI · `agent-memory ui` for visual browsing, search, and editing** |
|
|
467
|
+
|
|
468
|
+
### Coming next
|
|
469
|
+
|
|
470
|
+
- Folder support inside the store (`.agent-memory/work/`, `.agent-memory/personal/`) for multi-context separation
|
|
471
|
+
- Auto-context loading — server hook that auto-fires `relevant_memories` before each LLM turn so context flows transparently
|
|
472
|
+
- Memory packs — export/import shareable `.tar.gz` bundles of curated memories
|
|
473
|
+
- Browser companion UI (`agent-memory web`) for non-terminal users
|
|
474
|
+
- TUI polish — file-watching for auto-refresh, inline editing, sync push/pull as keybindings
|
|
475
|
+
|
|
476
|
+
### Beyond
|
|
477
|
+
|
|
478
|
+
Optional local-embeddings sidecar (transformers.js, no API), team mode with diff/merge, browser extension to capture from chatgpt.com / claude.ai → memory, mobile companion.
|
|
451
479
|
|
|
452
480
|
Open an issue if you want one of these before I get to it.
|
|
453
481
|
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,7 @@ import { spawnSync } from "node:child_process";
|
|
|
30
30
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync, } from "node:fs";
|
|
31
31
|
import { homedir } from "node:os";
|
|
32
32
|
import { join, resolve } from "node:path";
|
|
33
|
+
import { fileURLToPath } from "node:url";
|
|
33
34
|
import lockfile from "proper-lockfile";
|
|
34
35
|
// -------------------------------------------------------------
|
|
35
36
|
// Storage location resolution
|
|
@@ -43,7 +44,7 @@ function resolveStorageDir() {
|
|
|
43
44
|
}
|
|
44
45
|
return resolve(process.cwd(), ".agent-memory");
|
|
45
46
|
}
|
|
46
|
-
const MEMORY_DIR = resolveStorageDir();
|
|
47
|
+
export const MEMORY_DIR = resolveStorageDir();
|
|
47
48
|
const INDEX_FILE = join(MEMORY_DIR, "MEMORY.md");
|
|
48
49
|
const TRASH_DIR = join(MEMORY_DIR, ".trash");
|
|
49
50
|
const LOCK_FILE = join(MEMORY_DIR, ".lock");
|
|
@@ -178,10 +179,10 @@ const SLUG_PATTERN = /^[a-z0-9][a-z0-9_-]{0,80}$/;
|
|
|
178
179
|
const TAG_PATTERN = /^[a-z0-9][a-z0-9_-]{0,40}$/;
|
|
179
180
|
// Wiki-links: [[memory-name]] · names follow SLUG_PATTERN rules
|
|
180
181
|
const WIKI_LINK_PATTERN = /\[\[([a-z0-9][a-z0-9_-]{0,80})\]\]/g;
|
|
181
|
-
function memoryFilePath(name) {
|
|
182
|
+
export function memoryFilePath(name) {
|
|
182
183
|
return join(MEMORY_DIR, `${name}.md`);
|
|
183
184
|
}
|
|
184
|
-
function readMemory(name) {
|
|
185
|
+
export function readMemory(name) {
|
|
185
186
|
const fp = memoryFilePath(name);
|
|
186
187
|
if (!existsSync(fp))
|
|
187
188
|
return null;
|
|
@@ -197,7 +198,7 @@ function readMemory(name) {
|
|
|
197
198
|
filePath: fp,
|
|
198
199
|
};
|
|
199
200
|
}
|
|
200
|
-
function listMemoryFiles() {
|
|
201
|
+
export function listMemoryFiles() {
|
|
201
202
|
if (!existsSync(MEMORY_DIR))
|
|
202
203
|
return [];
|
|
203
204
|
return readdirSync(MEMORY_DIR)
|
|
@@ -576,7 +577,7 @@ function toolRelevantMemories(args) {
|
|
|
576
577
|
}
|
|
577
578
|
return sections.join("\n");
|
|
578
579
|
}
|
|
579
|
-
function toolDeleteMemory(args) {
|
|
580
|
+
export function toolDeleteMemory(args) {
|
|
580
581
|
const name = String(args.name ?? "").trim();
|
|
581
582
|
if (!SLUG_PATTERN.test(name))
|
|
582
583
|
throw new Error(`Invalid name "${name}".`);
|
|
@@ -1235,7 +1236,7 @@ function actionColor(action) {
|
|
|
1235
1236
|
// -------------------------------------------------------------
|
|
1236
1237
|
// Server wiring
|
|
1237
1238
|
// -------------------------------------------------------------
|
|
1238
|
-
const server = new Server({ name: "agent-memory", version: "0.
|
|
1239
|
+
const server = new Server({ name: "agent-memory", version: "0.10.1" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
|
|
1239
1240
|
// -------------------------------------------------------------
|
|
1240
1241
|
// Resource URI scheme
|
|
1241
1242
|
// -------------------------------------------------------------
|
|
@@ -1761,6 +1762,7 @@ const CLI_COMMANDS = new Set([
|
|
|
1761
1762
|
"backlinks",
|
|
1762
1763
|
"related",
|
|
1763
1764
|
"sync",
|
|
1765
|
+
"ui",
|
|
1764
1766
|
"import-claude-code",
|
|
1765
1767
|
"help",
|
|
1766
1768
|
"--help",
|
|
@@ -1953,6 +1955,13 @@ async function cliMain(command, rest) {
|
|
|
1953
1955
|
}) + "\n");
|
|
1954
1956
|
return 0;
|
|
1955
1957
|
}
|
|
1958
|
+
case "ui": {
|
|
1959
|
+
// Dynamic import so Ink + React only load when the TUI runs,
|
|
1960
|
+
// keeping cold-start fast for MCP server + every other CLI command.
|
|
1961
|
+
const { runTui } = await import("./tui.js");
|
|
1962
|
+
await runTui();
|
|
1963
|
+
return 0;
|
|
1964
|
+
}
|
|
1956
1965
|
case "import-claude-code": {
|
|
1957
1966
|
return importClaudeCode({
|
|
1958
1967
|
source: flags.source ? String(flags.source) : undefined,
|
|
@@ -2010,6 +2019,8 @@ COMMANDS
|
|
|
2010
2019
|
sync pull Fast-forward pull from remote.
|
|
2011
2020
|
sync status Show local + ahead/behind state.
|
|
2012
2021
|
sync log [--limit N] Recent sync commit history.
|
|
2022
|
+
ui Launch the TUI · browse, filter, search, edit memories
|
|
2023
|
+
in a clean terminal interface (Ink-based).
|
|
2013
2024
|
import-claude-code [--source <path>] [--project <pat>] [--overwrite] [--dry-run]
|
|
2014
2025
|
Walk ~/.claude/projects/*/memory/ and
|
|
2015
2026
|
import each memory into the current store.
|
|
@@ -2132,7 +2143,12 @@ async function main() {
|
|
|
2132
2143
|
await server.connect(transport);
|
|
2133
2144
|
process.stderr.write(`agent-memory-mcp · storage: ${MEMORY_DIR}\n`);
|
|
2134
2145
|
}
|
|
2135
|
-
main().
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2146
|
+
// Only auto-run main() when invoked directly. Importing this file
|
|
2147
|
+
// (e.g. from src/tui.tsx) should not trigger the dispatch.
|
|
2148
|
+
const isEntryPoint = process.argv[1] && process.argv[1] === fileURLToPath(import.meta.url);
|
|
2149
|
+
if (isEntryPoint) {
|
|
2150
|
+
main().catch((err) => {
|
|
2151
|
+
process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
2152
|
+
process.exit(1);
|
|
2153
|
+
});
|
|
2154
|
+
}
|
package/dist/tui.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* agent-memory-mcp · TUI
|
|
4
|
+
*
|
|
5
|
+
* The visual face of the project — Ink-based terminal UI for browsing,
|
|
6
|
+
* filtering, searching, and editing memories.
|
|
7
|
+
*
|
|
8
|
+
* Layout:
|
|
9
|
+
* ┌─ agent-memory ──────────────────────────────────────────┐
|
|
10
|
+
* │ [all] user feedback project reference · N memories │
|
|
11
|
+
* ├──────────────────────────────────────────────────────────┤
|
|
12
|
+
* │ memory list (scrolling) │
|
|
13
|
+
* │ ▶ highlighted name [type] · tags │
|
|
14
|
+
* │ description │
|
|
15
|
+
* ├──────────────────────────────────────────────────────────┤
|
|
16
|
+
* │ detail pane for highlighted memory │
|
|
17
|
+
* ├──────────────────────────────────────────────────────────┤
|
|
18
|
+
* │ key hints footer │
|
|
19
|
+
* └──────────────────────────────────────────────────────────┘
|
|
20
|
+
*
|
|
21
|
+
* Launched via `agent-memory ui`. Dynamic-imported from index.ts so
|
|
22
|
+
* Ink + React only load when the TUI is actually invoked.
|
|
23
|
+
*/
|
|
24
|
+
import { Box, render, Text, useApp, useInput, useStdout } from "ink";
|
|
25
|
+
import TextInput from "ink-text-input";
|
|
26
|
+
import { spawnSync } from "node:child_process";
|
|
27
|
+
import { useEffect, useMemo, useState } from "react";
|
|
28
|
+
import Fuse from "fuse.js";
|
|
29
|
+
import { listMemoryFiles, MEMORY_DIR, memoryFilePath, readMemory, toolDeleteMemory, } from "./index.js";
|
|
30
|
+
const TYPE_FILTERS = ["all", "user", "feedback", "project", "reference"];
|
|
31
|
+
function loadAllMemories() {
|
|
32
|
+
return listMemoryFiles()
|
|
33
|
+
.map((n) => readMemory(n))
|
|
34
|
+
.filter((m) => m !== null)
|
|
35
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
36
|
+
}
|
|
37
|
+
function filterMemories(memories, typeFilter, query) {
|
|
38
|
+
let list = memories;
|
|
39
|
+
if (typeFilter !== "all")
|
|
40
|
+
list = list.filter((m) => m.type === typeFilter);
|
|
41
|
+
if (query.trim()) {
|
|
42
|
+
const fuse = new Fuse(list, {
|
|
43
|
+
includeScore: true,
|
|
44
|
+
threshold: 0.4,
|
|
45
|
+
ignoreLocation: true,
|
|
46
|
+
keys: [
|
|
47
|
+
{ name: "name", weight: 3 },
|
|
48
|
+
{ name: "description", weight: 2 },
|
|
49
|
+
{ name: "body", weight: 1 },
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
return fuse.search(query).map((r) => r.item);
|
|
53
|
+
}
|
|
54
|
+
return list;
|
|
55
|
+
}
|
|
56
|
+
const typeColor = {
|
|
57
|
+
user: "cyan",
|
|
58
|
+
feedback: "yellow",
|
|
59
|
+
project: "green",
|
|
60
|
+
reference: "magenta",
|
|
61
|
+
};
|
|
62
|
+
const App = () => {
|
|
63
|
+
const { exit } = useApp();
|
|
64
|
+
const { stdout } = useStdout();
|
|
65
|
+
const [state, setState] = useState({
|
|
66
|
+
memories: loadAllMemories(),
|
|
67
|
+
selected: 0,
|
|
68
|
+
typeFilter: "all",
|
|
69
|
+
searchMode: false,
|
|
70
|
+
searchQuery: "",
|
|
71
|
+
confirmDelete: null,
|
|
72
|
+
status: null,
|
|
73
|
+
});
|
|
74
|
+
const visible = useMemo(() => filterMemories(state.memories, state.typeFilter, state.searchQuery), [state.memories, state.typeFilter, state.searchQuery]);
|
|
75
|
+
// Keep the selection in range when filters shrink the list
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (state.selected >= visible.length && visible.length > 0) {
|
|
78
|
+
setState((s) => ({ ...s, selected: Math.max(0, visible.length - 1) }));
|
|
79
|
+
}
|
|
80
|
+
else if (visible.length === 0 && state.selected !== 0) {
|
|
81
|
+
setState((s) => ({ ...s, selected: 0 }));
|
|
82
|
+
}
|
|
83
|
+
}, [visible.length, state.selected]);
|
|
84
|
+
const current = visible[state.selected];
|
|
85
|
+
const refresh = () => setState((s) => ({ ...s, memories: loadAllMemories(), status: "refreshed" }));
|
|
86
|
+
useInput((input, key) => {
|
|
87
|
+
if (state.searchMode)
|
|
88
|
+
return; // TextInput component handles search input
|
|
89
|
+
// Delete confirmation flow
|
|
90
|
+
if (state.confirmDelete) {
|
|
91
|
+
if (input === "y" || input === "Y") {
|
|
92
|
+
try {
|
|
93
|
+
toolDeleteMemory({ name: state.confirmDelete });
|
|
94
|
+
setState((s) => ({
|
|
95
|
+
...s,
|
|
96
|
+
memories: loadAllMemories(),
|
|
97
|
+
confirmDelete: null,
|
|
98
|
+
status: `deleted "${state.confirmDelete}" (in .trash/)`,
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
setState((s) => ({
|
|
103
|
+
...s,
|
|
104
|
+
confirmDelete: null,
|
|
105
|
+
status: `delete failed: ${err.message}`,
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else if (input === "n" || input === "N" || key.escape) {
|
|
110
|
+
setState((s) => ({ ...s, confirmDelete: null, status: "delete cancelled" }));
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (input === "q" || (key.ctrl && input === "c")) {
|
|
115
|
+
exit();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (key.upArrow || input === "k") {
|
|
119
|
+
setState((s) => ({ ...s, selected: Math.max(0, s.selected - 1), status: null }));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (key.downArrow || input === "j") {
|
|
123
|
+
setState((s) => ({
|
|
124
|
+
...s,
|
|
125
|
+
selected: Math.min(Math.max(0, visible.length - 1), s.selected + 1),
|
|
126
|
+
status: null,
|
|
127
|
+
}));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// Type filter quick-keys
|
|
131
|
+
const typeKeyMap = {
|
|
132
|
+
"0": "all",
|
|
133
|
+
"1": "user",
|
|
134
|
+
"2": "feedback",
|
|
135
|
+
"3": "project",
|
|
136
|
+
"4": "reference",
|
|
137
|
+
};
|
|
138
|
+
if (typeKeyMap[input]) {
|
|
139
|
+
setState((s) => ({ ...s, typeFilter: typeKeyMap[input], selected: 0, status: null }));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (input === "/") {
|
|
143
|
+
setState((s) => ({ ...s, searchMode: true, status: null }));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (input === "r") {
|
|
147
|
+
refresh();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (input === "e" && current) {
|
|
151
|
+
const editor = process.env.EDITOR || (process.platform === "win32" ? "notepad" : "vi");
|
|
152
|
+
const fp = memoryFilePath(current.name);
|
|
153
|
+
// Suspend Ink rendering, spawn editor, then refresh after exit
|
|
154
|
+
stdout?.write("\x1bc"); // reset terminal to give the editor a clean canvas
|
|
155
|
+
const result = spawnSync(editor, [fp], { stdio: "inherit" });
|
|
156
|
+
setState((s) => ({
|
|
157
|
+
...s,
|
|
158
|
+
memories: loadAllMemories(),
|
|
159
|
+
status: result.status === 0
|
|
160
|
+
? `edited "${current.name}" in ${editor}`
|
|
161
|
+
: `editor exited with code ${result.status}`,
|
|
162
|
+
}));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (input === "d" && current) {
|
|
166
|
+
setState((s) => ({ ...s, confirmDelete: current.name, status: null }));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
const onSearchSubmit = () => setState((s) => ({ ...s, searchMode: false, selected: 0 }));
|
|
171
|
+
const onSearchChange = (value) => setState((s) => ({ ...s, searchQuery: value }));
|
|
172
|
+
return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsxs(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, children: "agent-memory" }), _jsx(Text, { dimColor: true, children: " \u00B7 " }), TYPE_FILTERS.map((t, i) => (_jsxs(Text, { bold: state.typeFilter === t, color: state.typeFilter === t ? "green" : "gray", children: [i > 0 ? " " : "", "[", i, "] ", t] }, t))), _jsxs(Text, { dimColor: true, children: [" · ", visible.length, " of ", state.memories.length, state.searchQuery && ` matching "${state.searchQuery}"`] })] }), state.searchMode && (_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { color: "cyan", children: "/ " }), _jsx(TextInput, { value: state.searchQuery, onChange: onSearchChange, onSubmit: onSearchSubmit, placeholder: "fuzzy search \u00B7 enter to confirm" })] })), _jsxs(Box, { flexDirection: "column", paddingX: 1, children: [visible.length === 0 ? (_jsx(Text, { dimColor: true, children: "(no memories match)" })) : (visible.slice(0, 12).map((m, i) => {
|
|
173
|
+
const isSelected = i === state.selected;
|
|
174
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: isSelected ? "green" : undefined, bold: isSelected, children: [isSelected ? "▶ " : " ", m.name] }), _jsxs(Text, { color: typeColor[m.type] ?? "gray", children: [" [", m.type, "]"] }), m.tags.length > 0 ? (_jsxs(Text, { dimColor: true, children: [" · ", m.tags.join(" · ")] })) : null] }, m.name));
|
|
175
|
+
})), visible.length > 12 && (_jsxs(Text, { dimColor: true, children: [" ... +", visible.length - 12, " more (filter down with /)"] }))] }), current && (_jsxs(Box, { borderStyle: "single", borderColor: "gray", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, children: current.name }), _jsx(Text, { dimColor: true, children: current.description }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [current.body
|
|
176
|
+
.split("\n")
|
|
177
|
+
.slice(0, 10)
|
|
178
|
+
.map((line, i) => (_jsx(Text, { dimColor: line.startsWith("#") ? false : true, children: line || " " }, i))), current.body.split("\n").length > 10 && (_jsx(Text, { dimColor: true, children: "... (truncated \u00B7 press 'e' to open in editor)" }))] })] })), _jsxs(Box, { paddingX: 1, flexDirection: "column", children: [state.confirmDelete ? (_jsxs(Text, { color: "yellow", children: ["Delete \"", state.confirmDelete, "\"? (y/n) \u00B7 soft-delete, recoverable from .trash/"] })) : (_jsx(Text, { dimColor: true, children: "\u2191\u2193/jk navigate \u00B7 0-4 type filter \u00B7 / search \u00B7 e edit \u00B7 d delete \u00B7 r refresh \u00B7 q quit" })), state.status && _jsxs(Text, { color: "cyan", children: ["\u00B7 ", state.status] }), _jsxs(Text, { dimColor: true, children: ["storage: ", MEMORY_DIR] })] })] }));
|
|
179
|
+
};
|
|
180
|
+
export async function runTui() {
|
|
181
|
+
const { waitUntilExit } = render(_jsx(App, {}));
|
|
182
|
+
await waitUntilExit();
|
|
183
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xultrax-web/agent-memory-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"mcpName": "io.github.xultrax-web/agent-memory-mcp",
|
|
5
5
|
"description": "Markdown memory for AI agents. Plain files you can read, edit, grep, and commit. The only MCP memory server that isn't a database.",
|
|
6
6
|
"type": "module",
|
|
@@ -56,11 +56,15 @@
|
|
|
56
56
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
57
57
|
"fuse.js": "^7.3.0",
|
|
58
58
|
"gray-matter": "^4.0.3",
|
|
59
|
-
"
|
|
59
|
+
"ink": "^7.0.3",
|
|
60
|
+
"ink-text-input": "^6.0.0",
|
|
61
|
+
"proper-lockfile": "^4.1.2",
|
|
62
|
+
"react": "^19.2.6"
|
|
60
63
|
},
|
|
61
64
|
"devDependencies": {
|
|
62
65
|
"@types/node": "^22.10.2",
|
|
63
66
|
"@types/proper-lockfile": "^4.1.4",
|
|
67
|
+
"@types/react": "^19.2.15",
|
|
64
68
|
"prettier": "^3.8.3",
|
|
65
69
|
"typescript": "^5.7.2",
|
|
66
70
|
"vitest": "^4.1.7"
|