@nogataka/claw-memory 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +16 -0
- package/.claude-plugin/plugin.json +9 -0
- package/.mcp.json +8 -0
- package/README.md +168 -0
- package/dist/cli.js +223 -0
- package/dist/core/config.js +14 -0
- package/dist/core/db.js +92 -0
- package/dist/core/distill.js +146 -0
- package/dist/core/embeddings.js +34 -0
- package/dist/core/excludes.js +16 -0
- package/dist/core/hooks.js +76 -0
- package/dist/core/installer/claude.js +87 -0
- package/dist/core/installer/codex.js +122 -0
- package/dist/core/llm.js +163 -0
- package/dist/core/logger.js +19 -0
- package/dist/core/logsearch/parse.js +0 -0
- package/dist/core/logsearch/paths.js +11 -0
- package/dist/core/logsearch/recent.js +40 -0
- package/dist/core/logsearch/search.js +233 -0
- package/dist/core/memory.js +80 -0
- package/dist/core/paths.js +13 -0
- package/dist/core/private.js +8 -0
- package/dist/core/projects.js +46 -0
- package/dist/core/providers.js +17 -0
- package/dist/core/recall.js +59 -0
- package/dist/core/search.js +45 -0
- package/dist/core/transcript.js +79 -0
- package/dist/core/vector-memory.js +203 -0
- package/dist/core/watermark.js +19 -0
- package/dist/mcp/server.js +259 -0
- package/dist/ui/page.js +182 -0
- package/dist/ui/server.js +109 -0
- package/hooks/claw-hook.sh +21 -0
- package/hooks/hooks.json +37 -0
- package/hooks/run-hook.cmd +25 -0
- package/package.json +65 -0
- package/skills/memory-recall/SKILL.md +39 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// src/ui/server.ts
|
|
2
|
+
//
|
|
3
|
+
// On-demand, read-only memory viewer. Tiny Hono server — started only when the
|
|
4
|
+
// user runs `claw-memory ui`, never a persistent daemon.
|
|
5
|
+
import { Hono } from "hono";
|
|
6
|
+
import { serve } from "@hono/node-server";
|
|
7
|
+
import { streamSSE } from "hono/streaming";
|
|
8
|
+
import { sqlite } from "../core/db.js";
|
|
9
|
+
import { listProjects } from "../core/projects.js";
|
|
10
|
+
import { getRecentSummaries, getPreferences } from "../core/memory.js";
|
|
11
|
+
import { listChunks, getChunkCount } from "../core/vector-memory.js";
|
|
12
|
+
import { searchLogs } from "../core/logsearch/search.js";
|
|
13
|
+
import { PAGE } from "./page.js";
|
|
14
|
+
function countSummaries(projectId) {
|
|
15
|
+
return sqlite
|
|
16
|
+
.prepare("SELECT COUNT(*) c FROM session_summaries WHERE project_id = ?")
|
|
17
|
+
.get(projectId).c;
|
|
18
|
+
}
|
|
19
|
+
function countPreferences(projectId) {
|
|
20
|
+
return sqlite
|
|
21
|
+
.prepare("SELECT COUNT(*) c FROM user_preferences WHERE project_id = ?")
|
|
22
|
+
.get(projectId).c;
|
|
23
|
+
}
|
|
24
|
+
export function buildUiApp() {
|
|
25
|
+
const app = new Hono();
|
|
26
|
+
app.get("/", (c) => c.html(PAGE));
|
|
27
|
+
// Server-Sent Events: push a "change" whenever the DB is modified by any
|
|
28
|
+
// connection (the MCP server runs in a separate process). PRAGMA data_version
|
|
29
|
+
// increments on commits from other connections, so polling it in-process is a
|
|
30
|
+
// cheap, I/O-free change signal — no client-side polling required.
|
|
31
|
+
app.get("/api/events", (c) => {
|
|
32
|
+
return streamSSE(c, async (stream) => {
|
|
33
|
+
const dataVersion = () => sqlite.pragma("data_version", { simple: true });
|
|
34
|
+
let last = dataVersion();
|
|
35
|
+
await stream.writeSSE({ event: "ready", data: String(last) });
|
|
36
|
+
while (!stream.closed && !stream.aborted) {
|
|
37
|
+
await stream.sleep(1500);
|
|
38
|
+
const v = dataVersion();
|
|
39
|
+
if (v !== last) {
|
|
40
|
+
last = v;
|
|
41
|
+
await stream.writeSSE({ event: "change", data: String(v) });
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
// heartbeat keeps proxies/browser from dropping an idle connection
|
|
45
|
+
await stream.writeSSE({ event: "ping", data: "" });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
app.get("/api/stats", (c) => {
|
|
51
|
+
const projects = listProjects();
|
|
52
|
+
const chunks = sqlite.prepare("SELECT COUNT(*) c FROM conversation_chunks").get().c;
|
|
53
|
+
const summaries = sqlite.prepare("SELECT COUNT(*) c FROM session_summaries").get().c;
|
|
54
|
+
return c.json({ projects: projects.length, chunks, summaries });
|
|
55
|
+
});
|
|
56
|
+
app.get("/api/projects", (c) => {
|
|
57
|
+
const out = listProjects().map((p) => ({
|
|
58
|
+
id: p.id,
|
|
59
|
+
name: p.name,
|
|
60
|
+
path: p.path,
|
|
61
|
+
counts: {
|
|
62
|
+
summaries: countSummaries(p.id),
|
|
63
|
+
chunks: getChunkCount(p.id),
|
|
64
|
+
preferences: countPreferences(p.id),
|
|
65
|
+
},
|
|
66
|
+
}));
|
|
67
|
+
return c.json(out);
|
|
68
|
+
});
|
|
69
|
+
app.get("/api/memory", (c) => {
|
|
70
|
+
const projectId = c.req.query("project");
|
|
71
|
+
if (!projectId)
|
|
72
|
+
return c.json({ error: "project required" }, 400);
|
|
73
|
+
return c.json({
|
|
74
|
+
summaries: getRecentSummaries(projectId, 100),
|
|
75
|
+
preferences: getPreferences(projectId),
|
|
76
|
+
chunks: listChunks(projectId, 300),
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
// Raw transcript search (cc-search port) — Claude Code + Codex logs.
|
|
80
|
+
app.get("/api/logs", async (c) => {
|
|
81
|
+
const query = c.req.query("q") ?? "";
|
|
82
|
+
if (!query.trim())
|
|
83
|
+
return c.json({ results: [], total: 0 });
|
|
84
|
+
const sourcesParam = c.req.query("sources");
|
|
85
|
+
const out = await searchLogs({
|
|
86
|
+
query,
|
|
87
|
+
sources: sourcesParam ? sourcesParam.split(",") : undefined,
|
|
88
|
+
projectPath: c.req.query("project") || undefined,
|
|
89
|
+
limit: Number(c.req.query("limit") ?? 30),
|
|
90
|
+
});
|
|
91
|
+
return c.json(out);
|
|
92
|
+
});
|
|
93
|
+
return app;
|
|
94
|
+
}
|
|
95
|
+
export function runUiServer(port, open) {
|
|
96
|
+
const app = buildUiApp();
|
|
97
|
+
serve({ fetch: app.fetch, port }, (info) => {
|
|
98
|
+
const url = `http://localhost:${info.port}`;
|
|
99
|
+
console.error(`[claw-memory] viewer running at ${url}`);
|
|
100
|
+
if (open) {
|
|
101
|
+
const cmd = process.platform === "darwin"
|
|
102
|
+
? "open"
|
|
103
|
+
: process.platform === "win32"
|
|
104
|
+
? "start"
|
|
105
|
+
: "xdg-open";
|
|
106
|
+
import("node:child_process").then(({ spawn }) => spawn(cmd, [url], { stdio: "ignore", detached: true }).unref());
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# claw-memory binary resolver — used by plugin hooks and the plugin MCP server.
|
|
3
|
+
# hooks: claw-hook.sh hook <recall|distill> (hook JSON on stdin)
|
|
4
|
+
# mcp: claw-hook.sh mcp (stdio MCP server)
|
|
5
|
+
# Prefers a globally installed `claw-memory`; falls back to npx. stdin/stdout are
|
|
6
|
+
# inherited so hook input and MCP stdio pass through. Never blocks a session:
|
|
7
|
+
# hook failures are swallowed, but `mcp` must exec directly (no error masking).
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
run() {
|
|
11
|
+
if command -v claw-memory >/dev/null 2>&1; then
|
|
12
|
+
exec claw-memory "$@"
|
|
13
|
+
fi
|
|
14
|
+
exec npx -y @nogataka/claw-memory@latest "$@"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if [ "${1:-}" = "mcp" ]; then
|
|
18
|
+
run "$@"
|
|
19
|
+
else
|
|
20
|
+
run "$@" 2>/dev/null || true
|
|
21
|
+
fi
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": "'${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd' claw-hook.sh hook recall",
|
|
9
|
+
"async": false
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"UserPromptSubmit": [
|
|
15
|
+
{
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "'${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd' claw-hook.sh hook recall",
|
|
20
|
+
"async": false
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"Stop": [
|
|
26
|
+
{
|
|
27
|
+
"hooks": [
|
|
28
|
+
{
|
|
29
|
+
"type": "command",
|
|
30
|
+
"command": "'${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd' claw-hook.sh hook distill",
|
|
31
|
+
"async": true
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
: << 'CMDBLOCK'
|
|
2
|
+
@echo off
|
|
3
|
+
REM Windows: cmd.exe runs this batch section, finds bash and delegates.
|
|
4
|
+
if "%~1"=="" (
|
|
5
|
+
echo run-hook.cmd: missing script name >&2
|
|
6
|
+
exit /b 1
|
|
7
|
+
)
|
|
8
|
+
set "HOOK_DIR=%~dp0"
|
|
9
|
+
if exist "C:\Program Files\Git\bin\bash.exe" (
|
|
10
|
+
"C:\Program Files\Git\bin\bash.exe" "%HOOK_DIR%%~1" %2 %3 %4 %5 %6 %7 %8 %9
|
|
11
|
+
exit /b %ERRORLEVEL%
|
|
12
|
+
)
|
|
13
|
+
where bash >nul 2>&1 && (
|
|
14
|
+
bash "%HOOK_DIR%%~1" %2 %3 %4 %5 %6 %7 %8 %9
|
|
15
|
+
exit /b %ERRORLEVEL%
|
|
16
|
+
)
|
|
17
|
+
echo run-hook.cmd: bash not found >&2
|
|
18
|
+
exit /b 1
|
|
19
|
+
CMDBLOCK
|
|
20
|
+
|
|
21
|
+
# Unix: bash runs this part — delegate to the named hook script with remaining args.
|
|
22
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
23
|
+
SCRIPT_NAME="$1"
|
|
24
|
+
shift
|
|
25
|
+
exec bash "${SCRIPT_DIR}/${SCRIPT_NAME}" "$@"
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nogataka/claw-memory",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Independent, in-process semantic memory MCP server (sqlite-vec + local Xenova e5) with a lightweight web viewer. No daemon, no Python. Installable as a Claude Code plugin and a Codex MCP server.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "nogataka",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/nogataka/claw-memory.git"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"mcp",
|
|
14
|
+
"memory",
|
|
15
|
+
"claude-code",
|
|
16
|
+
"codex",
|
|
17
|
+
"sqlite-vec",
|
|
18
|
+
"embeddings",
|
|
19
|
+
"rag"
|
|
20
|
+
],
|
|
21
|
+
"bin": {
|
|
22
|
+
"claw-memory": "./dist/cli.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"hooks",
|
|
27
|
+
"skills",
|
|
28
|
+
".claude-plugin",
|
|
29
|
+
".mcp.json",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=20.0.0"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc",
|
|
40
|
+
"clean": "rm -rf dist",
|
|
41
|
+
"prebuild": "npm run clean",
|
|
42
|
+
"prepublishOnly": "npm run build",
|
|
43
|
+
"mcp": "node dist/cli.js mcp",
|
|
44
|
+
"ui": "node dist/cli.js ui --open",
|
|
45
|
+
"dev:mcp": "tsx src/cli.ts mcp",
|
|
46
|
+
"dev:ui": "tsx src/cli.ts ui --open"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.44",
|
|
50
|
+
"@hono/node-server": "^1.13.0",
|
|
51
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
52
|
+
"@openai/codex-sdk": "^0.135.0",
|
|
53
|
+
"@xenova/transformers": "^2.17.2",
|
|
54
|
+
"better-sqlite3": "12.8.0",
|
|
55
|
+
"hono": "^4.6.0",
|
|
56
|
+
"sqlite-vec": "^0.1.7-alpha.2",
|
|
57
|
+
"zod": "^3.24.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/better-sqlite3": "^7.6.11",
|
|
61
|
+
"@types/node": "^22.0.0",
|
|
62
|
+
"tsx": "^4.19.0",
|
|
63
|
+
"typescript": "^5.6.0"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: memory-recall
|
|
3
|
+
description: >-
|
|
4
|
+
Recall and search long-term memory across past sessions (claw-memory).
|
|
5
|
+
Use when the user asks about previous sessions, past discussions, prior
|
|
6
|
+
decisions, or to find something discussed before.
|
|
7
|
+
Triggers: "前回の会話", "前のセッション", "過去の会話", "以前話した", "前に決めた",
|
|
8
|
+
"履歴", "recall", "previous session", "what did we discuss", "past decision".
|
|
9
|
+
user_invocable: true
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Memory Recall
|
|
13
|
+
|
|
14
|
+
claw-memory stores long-term memory locally (per project): user preferences,
|
|
15
|
+
session summaries, and embedded conversation chunks, plus raw Claude Code /
|
|
16
|
+
Codex transcripts. Use its MCP tools — do not read the DB directly.
|
|
17
|
+
|
|
18
|
+
## Which tool to use
|
|
19
|
+
|
|
20
|
+
- **`memory_recall(query, cwd?)`** — start here. Returns a ready-to-read block:
|
|
21
|
+
always-apply preferences + recent summaries + semantically similar past
|
|
22
|
+
conversations. Best at the start of a task or when the user references the past.
|
|
23
|
+
- **`memory_search(query, cwd?, type?, concept?, file?, dateFrom?, dateTo?)`** —
|
|
24
|
+
token-efficient index (id + title + date + type) of matching chunks. Filter by
|
|
25
|
+
`type` (discovery|bugfix|feature|decision|change), `concept`, `file`, or date.
|
|
26
|
+
Then fetch only what you need with **`memory_get(ids)`**.
|
|
27
|
+
- **`memory_search_logs(query, sources?, projectPath?, startDate?, endDate?)`** —
|
|
28
|
+
full-text search over RAW transcripts (Claude Code + Codex), including sessions
|
|
29
|
+
that were never distilled. Use when distilled memory has no hit.
|
|
30
|
+
- **`memory_remember(text, cwd?)`** — store a durable note.
|
|
31
|
+
- **`memory_forget(ids)`** — soft-delete chunks surfaced by `memory_search`.
|
|
32
|
+
|
|
33
|
+
## Tips
|
|
34
|
+
|
|
35
|
+
- Pass the user's actual request as `query`; keep it natural language.
|
|
36
|
+
- Prefer `memory_recall` for context, `memory_search`→`memory_get` for digging,
|
|
37
|
+
`memory_search_logs` for "we talked about X somewhere" across raw logs.
|
|
38
|
+
- Treat recalled past conversations as reference-only: don't volunteer them
|
|
39
|
+
unless the user is clearly continuing a prior topic.
|