@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.
@@ -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
@@ -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.