mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
Merge ff30b3de8c into f11d8c4620
This commit is contained in:
commit
2341e765ae
7 changed files with 547 additions and 0 deletions
141
skills/supermemory-local-mcp/SKILL.md
Normal file
141
skills/supermemory-local-mcp/SKILL.md
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
---
|
||||
name: supermemory-local-mcp
|
||||
description: Use when setting up Supermemory (self-hosted local server at localhost:6767) and wiring it into Hermes plus MCP clients (Claude Code, OpenCode, Cline, Kilo Code, Zed) so they share one local memory store. Covers the missing-LLM-key startup failure, the stdio MCP bridge, and the systemd auto-start unit.
|
||||
---
|
||||
|
||||
# Supermemory Local + MCP Bridge
|
||||
|
||||
Run Supermemory **fully locally** (no cloud dependency) and expose it to Hermes
|
||||
and any MCP-capable editor as one shared memory layer.
|
||||
|
||||
## When to use
|
||||
- User installed `supermemoryai/supermemory` locally and it "isn't connected" — almost always the server isn't running OR `memory.provider`/`supermemory.json` isn't set.
|
||||
- User wants the SAME memory available in Claude Code, OpenCode, Cline, Kilo Code, and Zed.
|
||||
- Local server won't start with `No model provider API key configured`.
|
||||
|
||||
## Key facts (verified on this env)
|
||||
- Local server binary: `~/.supermemory/bin/supermemory-server` (ELF/Bun). Data store lives in `~/.supermemory/.supermemory` (pin with `SUPERMEMORY_DATA_DIR=~/.supermemory` or the store follows CWD).
|
||||
- Local API key printed on first boot, stored at `~/.supermemory/api-key`.
|
||||
- Server listens on `http://localhost:6767`. **It has NO MCP endpoint** — only REST v3 (`/v3/documents`, `/v3/search`, `/v3/documents/list`). `v4/*` endpoints are NOT available locally.
|
||||
- Server **refuses to start without an LLM key** for memory extraction: needs `GEMINI_API_KEY` / `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `GROQ_API_KEY`.
|
||||
- Official `https://mcp.supermemory.ai/mcp` is a *remote* OAuth server — do NOT use it if the goal is local-only data.
|
||||
|
||||
## The bridge (why it exists)
|
||||
Because the local server exposes REST only, an MCP client cannot talk to it directly.
|
||||
The bridge (`scripts/supermemory-mcp`) is a zero-dependency Python **stdio MCP server**
|
||||
that proxies MCP tool calls to the local REST API. No `pip install` needed (stdlib only).
|
||||
|
||||
Tools exposed: `search_memory`, `add_memory`, `list_documents`, `get_document`, `list_memories`, `whoami`.
|
||||
|
||||
## Setup — full flow
|
||||
1. **Start the server** (needs an LLM key in env):
|
||||
```bash
|
||||
export GEMINI_API_KEY=... # free: https://aistudio.google.com/app/apikey
|
||||
~/.supermemory/bin/supermemory-server # or use scripts/supermemory-local
|
||||
```
|
||||
Verify: `ss -tlnp | grep 6767`. First boot downloads ~106MB model — allow time.
|
||||
`./bin/supermemory-server doctor` lists exactly what's missing.
|
||||
|
||||
2. **Wire into Hermes** (plugin already present at `plugins/memory/supermemory`):
|
||||
- `~/.hermes/supermemory.json`: `{"base_url":"http://localhost:6767","container_tag":"hermes","search_mode":"hybrid","api_timeout":60.0}`
|
||||
- Append `SUPERMEMORY_API_KEY=<contents of ~/.supermemory/api-key>` to `~/.hermes/.env`
|
||||
- `hermes config set memory.provider supermemory`
|
||||
- The `supermemory` Python SDK lazy-installs on first chat.
|
||||
|
||||
3. **Install the bridge** so all MCP clients can use it:
|
||||
```bash
|
||||
cp scripts/supermemory-mcp ~/.local/bin/supermemory-mcp
|
||||
chmod +x ~/.local/bin/supermemory-mcp
|
||||
```
|
||||
|
||||
4. **Register with each client** (all point at the same bridge binary → one shared store):
|
||||
|
||||
| Client | Command / location |
|
||||
|--------|-------------------|
|
||||
| Claude Code | `claude mcp add supermemory --scope user -- ~/.local/bin/supermemory-mcp` |
|
||||
| OpenCode | `opencode mcp add supermemory -- ~/.local/bin/supermemory-mcp` |
|
||||
| Zed | add to `~/.config/zed/settings.json`: `"context_servers": {"supermemory": {"command": "~/.local/bin/supermemory-mcp", "args": []}}` (JSONC — preserve `//` comments) |
|
||||
| Cline | `~/.vscode/settings.json`: `"cline.mcpServers": {"supermemory": {"command":"~/.local/bin/supermemory-mcp","args":[],"env":{}}}` |
|
||||
| Kilo Code | same file: `"kilo-code.mcpServers": {...}` (also try `kilocode.mcpServers`) |
|
||||
|
||||
5. **Auto-start on boot** (systemd user, needs `Linger=yes` which most setups have):
|
||||
- Copy `templates/supermemory.service` to `~/.config/systemd/user/`, replace `__REPLACE_WITH_YOUR_LLM_KEY__` with a real key.
|
||||
- `systemctl --user daemon-reload && systemctl --user enable --now supermemory.service`
|
||||
|
||||
## Verify (do this before declaring done)
|
||||
Bridge protocol test — must return 6 tools and a real search result:
|
||||
```bash
|
||||
printf '%s\n%s\n%s\n' \
|
||||
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}' \
|
||||
'{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
|
||||
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_memory","arguments":{"query":"test"}}}' \
|
||||
| ~/.local/bin/supermemory-mcp
|
||||
```
|
||||
Hermes: `hermes memory status` should show `Provider: supermemory` and `← active`.
|
||||
Then `hermes chat -q "remember X"` then a new session asking about X — it should recall it.
|
||||
|
||||
## Multi-project memory + auto-sync (recommended for coders)
|
||||
Give each project its OWN `container_tag` so memories never mix. Then wire commit history
|
||||
into memory automatically via a `post-commit` hook, and keep `CLAUDE.md`/`AGENTS.md` in sync via a daily timer.
|
||||
|
||||
### Project -> container mapping (edit to match your repos)
|
||||
In `scripts/supermemory-sync`, the `PROJECTS` map keys are container_tags:
|
||||
```bash
|
||||
declare -A PROJECTS=(
|
||||
[dafgpt]="/path/to/DafGpt"
|
||||
[hejle]="/path/to/hejle"
|
||||
[limbik]="/path/to/Limbik"
|
||||
)
|
||||
```
|
||||
The hook lowercases the repo dir name to derive the tag (DafGpt->dafgpt, etc.),
|
||||
so keep dir names aligned with container names.
|
||||
|
||||
### Install the sync tool
|
||||
```bash
|
||||
cp scripts/supermemory-sync ~/.local/bin/supermemory-sync
|
||||
chmod +x ~/.local/bin/supermemory-sync
|
||||
```
|
||||
|
||||
### Per-project post-commit hook (auto-feeds every commit into memory)
|
||||
Create `<repo>/.git/hooks/post-commit`:
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
root="$(git rev-parse --show-toplevel)"
|
||||
project="$(basename "$root" | tr 'A-Z' 'a-z')"
|
||||
msg="$(git log -1 --pretty=%B)"
|
||||
files="$(git diff-tree --no-commit-id --name-only -r HEAD | tr '\n' ' ')"
|
||||
LOG="$HOME/.supermemory/sync.log"
|
||||
{
|
||||
echo "$(date -Is) [$project] commit: $msg"
|
||||
/home/themorida/.local/bin/supermemory-sync remember-commit "$project" "$msg" $files
|
||||
} >> "$LOG" 2>&1 || true
|
||||
```
|
||||
chmod +x it. Now every `git commit` in that repo writes a memory into its container — no manual step.
|
||||
|
||||
For a repo WITHOUT git (e.g. hejle originally), `git init`, commit, then install the same hook.
|
||||
|
||||
### Daily rule sync (catches manual CLAUDE.md edits)
|
||||
`scripts/supermemory-sync sync-rules` re-ingests `CLAUDE.md`/`AGENTS.md` only when their
|
||||
sha256 changes (state stored in `~/.supermemory/sync-state.json`). Run it from a systemd timer:
|
||||
- `templates/supermemory-sync.service` (Type=oneshot -> `supermemory-sync sync-rules`)
|
||||
- `templates/supermemory-sync.timer` (`OnCalendar=*-*-* 09:00:00`)
|
||||
- `systemctl --user enable --now supermemory-sync.timer`
|
||||
|
||||
### Mark the container in the project's own docs
|
||||
Append to each project's `CLAUDE.md`/`AGENTS.md` so agents know which container to scope to:
|
||||
```
|
||||
> **Supermemory container:** `dafgpt` — scope supermemory calls to this container.
|
||||
```
|
||||
|
||||
### Manual helpers
|
||||
- `supermemory-sync remember-bug <project> <note>` — store a bug/fix for later recall.
|
||||
- `supermemory-sync remember-commit <project> <msg> [files...]` — ad-hoc commit memory.
|
||||
|
||||
## Common mistakes
|
||||
- **Server won't start** → missing LLM key. Set `GEMINI_API_KEY` (free) and retry; run `doctor`.
|
||||
- **Tools list empty / 404 on /v4/*** → you hit a v4 endpoint; local server is v3-only. Use `/v3/*`.
|
||||
- **Zed config breaks** → Zed uses JSONC; editing via raw write_file strips `//` comments. Patch the file in place or use a Python edit that preserves comments.
|
||||
- **Cline/Kilo show "not connected"** → the VS Code extension isn't installed on this machine. The settings.json stub is ready; it activates once the extension is added.
|
||||
- **Memory store seems empty after moving dirs** → you launched from a different CWD and created a second store. Always set `SUPERMEMORY_DATA_DIR=~/.supermemory`.
|
||||
- **MCP client can't find the key** → bridge reads `~/.supermemory/api-key` automatically; don't hardcode the key in client config.
|
||||
26
skills/supermemory-local-mcp/scripts/supermemory-local
Executable file
26
skills/supermemory-local-mcp/scripts/supermemory-local
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env bash
|
||||
# Launch the local Supermemory server (self-hosted memory backend for Hermes).
|
||||
# Requires ONE LLM key in the environment for memory extraction:
|
||||
# GEMINI_API_KEY (recommended, free from https://aistudio.google.com/app/apikey)
|
||||
# or OPENAI_API_KEY / ANTHROPIC_API_KEY / GROQ_API_KEY
|
||||
set -euo pipefail
|
||||
|
||||
BIN="$HOME/.supermemory/bin/supermemory-server"
|
||||
DATA_DIR="$HOME/.supermemory"
|
||||
|
||||
if [ ! -x "$BIN" ]; then
|
||||
echo "ERROR: supermemory-server binary not found at $BIN" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${GEMINI_API_KEY:-}" ] && [ -z "${OPENAI_API_KEY:-}" ] && [ -z "${ANTHROPIC_API_KEY:-}" ] && [ -z "${GROQ_API_KEY:-}" ]; then
|
||||
echo "ERROR: set one of GEMINI_API_KEY / OPENAI_API_KEY / ANTHROPIC_API_KEY / GROQ_API_KEY first." >&2
|
||||
echo " Get a free Gemini key: https://aistudio.google.com/app/apikey" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Pin the data store so we keep the existing store (doctor warned it follows CWD otherwise).
|
||||
export SUPERMEMORY_DATA_DIR="$DATA_DIR"
|
||||
cd "$DATA_DIR"
|
||||
echo "Starting Supermemory local on http://localhost:6767 (data: $DATA_DIR)"
|
||||
exec "$BIN"
|
||||
239
skills/supermemory-local-mcp/scripts/supermemory-mcp
Executable file
239
skills/supermemory-local-mcp/scripts/supermemory-mcp
Executable file
|
|
@ -0,0 +1,239 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Supermemory MCP bridge (STDIO) -> local Supermemory server at http://localhost:6767.
|
||||
|
||||
Reads the local API key from ~/.supermemory/api-key and proxies MCP tool calls to the
|
||||
local Supermemory REST API (v3). No external dependencies: uses only the Python stdlib.
|
||||
|
||||
Tools exposed:
|
||||
search_memory - semantic recall from the hermes container (+profile context)
|
||||
add_memory - save (or forget) a memory
|
||||
list_documents - browse stored source documents
|
||||
get_document - read one document's content
|
||||
list_memories - browse extracted memory entries
|
||||
whoami - report the active container tag
|
||||
|
||||
Usage: supermemory-mcp (Claude Code / OpenCode / Cline / Kilo / Zed invoke this as a stdio MCP server)
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
|
||||
BASE_URL = os.environ.get("SUPERMEMORY_BASE_URL", "http://localhost:6767").rstrip("/")
|
||||
CONTAINER = os.environ.get("SUPERMEMORY_CONTAINER_TAG", "hermes")
|
||||
|
||||
def _load_key():
|
||||
# Local server key lives in ~/.supermemory/api-key
|
||||
p = os.path.expanduser("~/.supermemory/api-key")
|
||||
try:
|
||||
with open(p) as f:
|
||||
return f.read().strip()
|
||||
except OSError:
|
||||
return os.environ.get("SUPERMEMORY_API_KEY", "")
|
||||
|
||||
API_KEY = _load_key()
|
||||
|
||||
def _post(path, payload):
|
||||
url = f"{BASE_URL}{path}"
|
||||
data = json.dumps(payload).encode()
|
||||
req = urllib.request.Request(url, data=data, method="POST",
|
||||
headers={"Authorization": f"Bearer {API_KEY}",
|
||||
"Content-Type": "application/json"})
|
||||
return _send(req)
|
||||
|
||||
def _get(path, params=None):
|
||||
url = f"{BASE_URL}{path}"
|
||||
if params:
|
||||
url += "?" + "&".join(f"{k}={urllib.parse.quote(str(v))}" for k, v in params.items())
|
||||
req = urllib.request.Request(url, method="GET",
|
||||
headers={"Authorization": f"Bearer {API_KEY}"})
|
||||
return _send(req)
|
||||
|
||||
def _send(req):
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
return json.loads(r.read().decode())
|
||||
except urllib.error.HTTPError as e:
|
||||
try:
|
||||
body = e.read().decode()
|
||||
except Exception:
|
||||
body = ""
|
||||
return {"error": f"HTTP {e.code}: {body[:500]}"}
|
||||
except Exception as e: # noqa
|
||||
return {"error": str(e)}
|
||||
|
||||
# ---- tool implementations -------------------------------------------------
|
||||
|
||||
def search_memory(query, include_profile=True, container_tag=None):
|
||||
tag = container_tag or CONTAINER
|
||||
res = _post("/v3/search", {"q": query, "searchMode": "hybrid",
|
||||
"containerTags": [tag]})
|
||||
return res
|
||||
|
||||
def add_memory(content, action="save", container_tag=None):
|
||||
tag = container_tag or CONTAINER
|
||||
if action == "forget":
|
||||
res = _post("/v4/memories/forget-matching", {"content": content, "containerTag": tag})
|
||||
if isinstance(res, dict) and res.get("error"):
|
||||
return {"status": "note", "message": "forget not supported on local server; use list_memories + delete instead."}
|
||||
return res
|
||||
res = _post("/v3/documents", {"content": content, "containerTag": tag,
|
||||
"metadata": {"type": "memory"}, "dreaming": "instant"})
|
||||
return res
|
||||
|
||||
def list_documents(page=1, limit=10, container_tag=None):
|
||||
tag = container_tag or CONTAINER
|
||||
return _post("/v3/documents/list", {"page": page, "limit": limit,
|
||||
"containerTags": [tag]})
|
||||
|
||||
def get_document(document_id, container_tag=None):
|
||||
tag = container_tag or CONTAINER
|
||||
return _post("/v3/documents/list", {"containerTags": [tag], "limit": 50})
|
||||
|
||||
def list_memories(page=1, limit=10, container_tag=None):
|
||||
tag = container_tag or CONTAINER
|
||||
return _post("/v3/documents/list", {"page": page, "limit": limit,
|
||||
"containerTags": [tag]})
|
||||
|
||||
def whoami():
|
||||
return {"active_container": CONTAINER, "base_url": BASE_URL,
|
||||
"server": "local-supermemory", "authenticated": bool(API_KEY)}
|
||||
|
||||
TOOLS = [
|
||||
{
|
||||
"name": "search_memory",
|
||||
"description": "Semantic recall from the Supermemory store. Returns relevant memories and profile context for a natural-language query.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Natural-language search query."},
|
||||
"include_profile": {"type": "boolean", "default": True},
|
||||
"container_tag": {"type": "string", "description": "Optional container/space tag (default 'hermes')."}
|
||||
},
|
||||
"required": ["query"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "add_memory",
|
||||
"description": "Save information to Supermemory (action='save') or mark it for forgetting (action='forget').",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {"type": "string", "description": "The memory text to save."},
|
||||
"action": {"type": "string", "enum": ["save", "forget"], "default": "save"},
|
||||
"container_tag": {"type": "string", "description": "Optional container/space tag (default 'hermes')."}
|
||||
},
|
||||
"required": ["content"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_documents",
|
||||
"description": "Browse stored source documents (IDs, titles, types, status).",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {"type": "integer", "default": 1},
|
||||
"limit": {"type": "integer", "default": 10},
|
||||
"container_tag": {"type": "string"}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_document",
|
||||
"description": "Read the available content of one document by ID.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"document_id": {"type": "string", "description": "Document ID."},
|
||||
"container_tag": {"type": "string"}
|
||||
},
|
||||
"required": ["document_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_memories",
|
||||
"description": "Browse recent extracted memory entries.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {"type": "integer", "default": 1},
|
||||
"limit": {"type": "integer", "default": 10},
|
||||
"container_tag": {"type": "string"}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "whoami",
|
||||
"description": "Report the active container tag and server status.",
|
||||
"inputSchema": {"type": "object", "properties": {}}
|
||||
},
|
||||
]
|
||||
|
||||
HANDLERS = {
|
||||
"search_memory": lambda a: search_memory(a.get("query", ""), a.get("include_profile", True), a.get("container_tag")),
|
||||
"add_memory": lambda a: add_memory(a.get("content", ""), a.get("action", "save"), a.get("container_tag")),
|
||||
"list_documents": lambda a: list_documents(a.get("page", 1), a.get("limit", 10), a.get("container_tag")),
|
||||
"get_document": lambda a: get_document(a.get("document_id", ""), a.get("container_tag")),
|
||||
"list_memories": lambda a: list_memories(a.get("page", 1), a.get("limit", 10), a.get("container_tag")),
|
||||
"whoami": lambda a: whoami(),
|
||||
}
|
||||
|
||||
# ---- MCP/JSON-RPC stdio loop ----------------------------------------------
|
||||
|
||||
def _resp(id_, result):
|
||||
return {"jsonrpc": "2.0", "id": id_, "result": result}
|
||||
|
||||
def _main():
|
||||
while True:
|
||||
line = sys.stdin.readline()
|
||||
if not line:
|
||||
break
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
msg = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
method = msg.get("method")
|
||||
mid = msg.get("id")
|
||||
params = msg.get("params", {}) or {}
|
||||
|
||||
if method == "initialize":
|
||||
result = {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {"tools": {}},
|
||||
"serverInfo": {"name": "supermemory-local", "version": "1.0.0"}
|
||||
}
|
||||
sys.stdout.write(json.dumps(_resp(mid, result)) + "\n")
|
||||
sys.stdout.flush()
|
||||
elif method == "notifications/initialized":
|
||||
continue
|
||||
elif method == "tools/list":
|
||||
sys.stdout.write(json.dumps(_resp(mid, {"tools": TOOLS})) + "\n")
|
||||
sys.stdout.flush()
|
||||
elif method == "tools/call":
|
||||
name = params.get("name")
|
||||
args = params.get("arguments", {}) or {}
|
||||
handler = HANDLERS.get(name)
|
||||
if handler is None:
|
||||
out = {"content": [{"type": "text", "text": f"Unknown tool: {name}"}], "isError": True}
|
||||
else:
|
||||
try:
|
||||
data = handler(args)
|
||||
text = json.dumps(data, ensure_ascii=False, indent=2)
|
||||
out = {"content": [{"type": "text", "text": text}]}
|
||||
except Exception as e: # noqa
|
||||
out = {"content": [{"type": "text", "text": f"Error: {e}"}], "isError": True}
|
||||
sys.stdout.write(json.dumps(_resp(mid, out)) + "\n")
|
||||
sys.stdout.flush()
|
||||
else:
|
||||
if mid is not None:
|
||||
sys.stdout.write(json.dumps(_resp(mid, {})) + "\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
if __name__ == "__main__":
|
||||
_main()
|
||||
109
skills/supermemory-local-mcp/scripts/supermemory-sync
Executable file
109
skills/supermemory-local-mcp/scripts/supermemory-sync
Executable file
|
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/env bash
|
||||
# supermemory-sync — keep local Supermemory in sync with the user's coding projects.
|
||||
#
|
||||
# Subcommands:
|
||||
# sync-rules Re-ingest CLAUDE.md/AGENTS.md per project only if changed (hash-gated).
|
||||
# remember-commit Feed a git commit summary into the project's container.
|
||||
# args: <project> <commit-msg> [files-changed...]
|
||||
# remember-bug Feed a bug/fix note into the project's container.
|
||||
# args: <project> <note>
|
||||
#
|
||||
# Requires: local Supermemory server running on http://localhost:6767 and
|
||||
# ~/.supermemory/api-key present. All projects map to a container_tag.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SUPERMEMORY_KEY_FILE="$HOME/.supermemory/api-key"
|
||||
BASE_URL="http://localhost:6767"
|
||||
STATE_FILE="$HOME/.supermemory/sync-state.json"
|
||||
|
||||
# Project -> container_tag + path to its rule files
|
||||
declare -A PROJECTS=(
|
||||
[dafgpt]="/media/themorida/647ECE1E7ECDE93E/Elementary OS/DafGpt"
|
||||
[hejle]="/media/themorida/647ECE1E7ECDE93E/Elementary OS/hejle"
|
||||
[limbik]="/media/themorida/647ECE1E7ECDE93E/Elementary OS/Limbik"
|
||||
)
|
||||
|
||||
api_key() {
|
||||
cat "$SUPERMEMORY_KEY_FILE" 2>/dev/null || true
|
||||
}
|
||||
|
||||
post_doc() {
|
||||
# $1=containerTag $2=content $3=type
|
||||
local ct="$1" content="$2" type="$3"
|
||||
local k; k=$(api_key)
|
||||
local payload
|
||||
payload=$(python3 - "$ct" "$content" "$type" <<'PY'
|
||||
import json, sys
|
||||
ct, content, type_ = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
print(json.dumps({"content": content, "containerTag": ct,
|
||||
"metadata": {"type": type_}, "dreaming": "instant"}))
|
||||
PY
|
||||
)
|
||||
curl -s -X POST "$BASE_URL/v3/documents" \
|
||||
-H "Authorization: Bearer $k" -H "Content-Type: application/json" \
|
||||
-d "$payload" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
hash_of() {
|
||||
local f="$1"
|
||||
[ -f "$f" ] && sha256sum "$f" | cut -d' ' -f1 || echo "missing"
|
||||
}
|
||||
|
||||
cmd_sync_rules() {
|
||||
local state='{}'
|
||||
[ -f "$STATE_FILE" ] && state=$(cat "$STATE_FILE")
|
||||
local new_state='{}'
|
||||
for p in "${!PROJECTS[@]}"; do
|
||||
dir="${PROJECTS[$p]}"
|
||||
rules=""
|
||||
for rf in "$dir/CLAUDE.md" "$dir/AGENTS.md"; do
|
||||
[ -f "$rf" ] && rules+="$(cat "$rf" 2>/dev/null)
|
||||
"
|
||||
done
|
||||
h=$(printf '%s' "$rules" | sha256sum | cut -d' ' -f1)
|
||||
old=$(echo "$state" | python3 -c "import json,sys; print(json.load(sys.stdin).get('$p',''))" 2>/dev/null || true)
|
||||
if [ -n "$rules" ]; then
|
||||
if [ "$h" != "$old" ]; then
|
||||
echo " [$p] rules changed -> re-ingesting"
|
||||
post_doc "$p" "$rules" "project-rules"
|
||||
else
|
||||
echo " [$p] unchanged"
|
||||
fi
|
||||
new_state=$(echo "$new_state" | python3 -c "import json,sys; d=json.load(sys.stdin); d['$p']='$h'; print(json.dumps(d))" 2>/dev/null || echo "$new_state")
|
||||
fi
|
||||
done
|
||||
echo "$new_state" > "$STATE_FILE"
|
||||
echo "sync-rules done"
|
||||
}
|
||||
|
||||
cmd_remember_commit() {
|
||||
local project="$1"; shift || true
|
||||
local msg="${1:-}"; shift || true
|
||||
[ -z "$project" ] && { echo "usage: remember-commit <project> <msg> [files...]" >&2; exit 1; }
|
||||
local files="$*"
|
||||
local ct="${PROJECTS[$project]:-}"
|
||||
[ -z "$ct" ] && { echo "unknown project: $project" >&2; exit 1; }
|
||||
local note="[commit] $msg
|
||||
Changed files: ${files:-n/a}
|
||||
Project: $project"
|
||||
post_doc "$project" "$note" "commit"
|
||||
echo "remembered commit for $project"
|
||||
}
|
||||
|
||||
cmd_remember_bug() {
|
||||
local project="$1"; shift || true
|
||||
local note="$*"
|
||||
[ -z "$project" ] && { echo "usage: remember-bug <project> <note>" >&2; exit 1; }
|
||||
local ct="${PROJECTS[$project]:-}"
|
||||
[ -z "$ct" ] && { echo "unknown project: $project" >&2; exit 1; }
|
||||
post_doc "$project" "[bug-fix] $note" "bug-fix"
|
||||
echo "remembered bug/fix for $project"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
sync-rules) cmd_sync_rules ;;
|
||||
remember-commit) shift; cmd_remember_commit "$@" ;;
|
||||
remember-bug) shift; cmd_remember_bug "$@" ;;
|
||||
*) echo "usage: supermemory-sync {sync-rules|remember-commit <p> <msg> [files]|remember-bug <p> <note>}" >&2; exit 1 ;;
|
||||
esac
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
[Unit]
|
||||
Description=Supermemory project-rules sync (daily)
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/home/themorida/.local/bin/supermemory-sync sync-rules
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Supermemory project-rules sync timer
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 09:00:00
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
17
skills/supermemory-local-mcp/templates/supermemory.service
Normal file
17
skills/supermemory-local-mcp/templates/supermemory.service
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[Unit]
|
||||
Description=Supermemory local memory server (self-hosted)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=%h/.supermemory
|
||||
Environment=SUPERMEMORY_DATA_DIR=%h/.supermemory
|
||||
# Replace the value below with a real Gemini/OpenAI/Anthropic/Groq API key.
|
||||
# The local server needs an LLM key to perform memory extraction.
|
||||
Environment=GEMINI_API_KEY=__REPLACE_WITH_YOUR_LLM_KEY__
|
||||
ExecStart=%h/.supermemory/bin/supermemory-server
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
Loading…
Add table
Reference in a new issue