@everme/claude-code 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,70 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SessionStart hook — surface a recent-context block when a new
4
+ * Claude Code session begins. Helps the user (and Claude) pick up
5
+ * where the previous session left off without manually pasting
6
+ * context.
7
+ *
8
+ * Hook contract:
9
+ * stdin = JSON { cwd, session_id, ... }
10
+ * stdout = JSON { systemMessage, hookSpecificOutput: { ..., additionalContext } }
11
+ */
12
+
13
+ process.on("uncaughtException", () => process.exit(0));
14
+ process.on("unhandledRejection", () => process.exit(0));
15
+
16
+ import { isConfigured } from "./lib/config.js";
17
+ import { getContext } from "./lib/api.js";
18
+ import { redactError, debug } from "./lib/redact.js";
19
+ import { renderProfileBlock, profileItemCount } from "./lib/profile.js";
20
+
21
+ const TOP_K = 6;
22
+
23
+ async function main() {
24
+ if (!isConfigured()) {
25
+ debug("start", "skip: not configured");
26
+ return process.exit(0);
27
+ }
28
+ await readStdinJSON(); // drain stdin (may be empty)
29
+
30
+ let block = "";
31
+ let count = 0;
32
+ try {
33
+ // Empty query — the gateway returns the user's profile snapshot.
34
+ // Shape: { profile: {explicit_info, implicit_traits, ...} }
35
+ const ctx = await getContext("", { topK: TOP_K });
36
+ block = renderProfileBlock(ctx?.profile);
37
+ count = profileItemCount(ctx?.profile);
38
+ } catch (err) {
39
+ debug("start", "context failed:", redactError(err?.message));
40
+ return process.exit(0);
41
+ }
42
+ if (!block || count === 0) {
43
+ debug("start", "no context");
44
+ return process.exit(0);
45
+ }
46
+
47
+ const out = {
48
+ systemMessage: `🧠 EverMe loaded ${count} memory ${count === 1 ? "item" : "items"} from past sessions`,
49
+ hookSpecificOutput: {
50
+ hookEventName: "SessionStart",
51
+ additionalContext: block,
52
+ },
53
+ };
54
+ process.stdout.write(JSON.stringify(out));
55
+ process.exit(0);
56
+ }
57
+
58
+ async function readStdinJSON() {
59
+ const chunks = [];
60
+ for await (const c of process.stdin) chunks.push(c);
61
+ const raw = Buffer.concat(chunks).toString("utf8");
62
+ if (!raw) return {};
63
+ try {
64
+ return JSON.parse(raw);
65
+ } catch {
66
+ return {};
67
+ }
68
+ }
69
+
70
+ main();
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SessionEnd hook — no runtime persistence here.
4
+ *
5
+ * Claude Code runtime memory is written turn-by-turn by the Stop hook through
6
+ * /mem/agent-memory. SessionEnd must not upload a markdown summary to
7
+ * /mem/sources, otherwise long-lived sessions create document sources.
8
+ *
9
+ * Hook contract:
10
+ * stdin = JSON { transcript_path, session_id, cwd, ... }
11
+ * stdout = empty (no UI surface needed)
12
+ */
13
+
14
+ process.on("uncaughtException", () => process.exit(0));
15
+ process.on("unhandledRejection", () => process.exit(0));
16
+
17
+ import { debug } from "./lib/redact.js";
18
+
19
+ async function main() {
20
+ debug("summary", "skip: runtime persistence is handled by Stop via /mem/agent-memory");
21
+ process.exit(0);
22
+ }
23
+
24
+ main();
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Stop hook — fires after Claude finishes responding to a turn.
4
+ * We read the transcript JSONL Claude Code wrote, extract the
5
+ * just-completed raw turn, and POST it through the realtime gateway
6
+ * (/mem/agent-memory). Runtime turns must not create /mem/sources.
7
+ *
8
+ * Hook contract (from Claude Code):
9
+ * stdin = JSON { transcript_path, cwd, session_id, ... }
10
+ * stdout = empty (no need to surface anything to the user)
11
+ *
12
+ * Failure-mode: silent exit 0 — host must NEVER notice memory
13
+ * persistence is broken. The gateway has its own retry loop on the
14
+ * worker side, so a single dropped Stop event isn't catastrophic.
15
+ */
16
+
17
+ process.on("uncaughtException", () => process.exit(0));
18
+ process.on("unhandledRejection", () => process.exit(0));
19
+
20
+ import { isConfigured, getConfig } from "./lib/config.js";
21
+ import {
22
+ saveAgentMemory,
23
+ EvermeError,
24
+ } from "./lib/api.js";
25
+ import { AGENT_MEMORY_ROLES } from "@everme/agent-sdk";
26
+ import { readTranscript, extractAgentMessages } from "./lib/transcript.js";
27
+ import { redactError, debug } from "./lib/redact.js";
28
+
29
+ const MIN_MESSAGES = 1;
30
+
31
+ async function main() {
32
+ if (!isConfigured()) {
33
+ debug("store", "skip: not configured");
34
+ return process.exit(0);
35
+ }
36
+ const cfg = getConfig();
37
+ if (cfg.authMode !== "evt" || !cfg.agentId) {
38
+ debug("store", "skip: realtime agent memory requires EVERME_AGENT_TOKEN + EVERME_AGENT_ID");
39
+ return process.exit(0);
40
+ }
41
+ const data = await readStdinJSON();
42
+ const transcriptPath = data?.transcript_path;
43
+ const sessionId = data?.session_id || "claude-code-session";
44
+ if (!transcriptPath) {
45
+ debug("store", "skip: no transcript_path");
46
+ return process.exit(0);
47
+ }
48
+
49
+ const lines = await readTranscript(transcriptPath);
50
+ const messages = extractAgentMessages(lines);
51
+ // Only persist the LAST user/assistant pair from this Stop event so
52
+ // we don't re-upload the entire history every turn — backend chains
53
+ // versions per documentKey, so each call appends.
54
+ const tail = lastTurn(messages);
55
+ if (tail.length < MIN_MESSAGES) {
56
+ debug("store", `skip: tail < ${MIN_MESSAGES} messages (got ${tail.length})`);
57
+ return process.exit(0);
58
+ }
59
+
60
+ try {
61
+ // Stop is the natural session-end signal for Claude Code; flush
62
+ // so EverOS extracts episodes/profiles instead of letting
63
+ // raw_messages accumulate indefinitely.
64
+ const res = await saveAgentMemory({
65
+ conversationId: sessionId,
66
+ messages: tail,
67
+ flush: true,
68
+ });
69
+ debug("store", `ok agent-memory status=${res?.status || "unknown"} flushed=${!!res?.flushed} messages=${res?.messageCount || tail.length}`);
70
+ } catch (err) {
71
+ if (err instanceof EvermeError) {
72
+ debug("store", `failed type=${err.type}:`, redactError(err.message));
73
+ } else {
74
+ debug("store", "unexpected:", redactError(err?.message || String(err)));
75
+ }
76
+ }
77
+ process.exit(0);
78
+ }
79
+
80
+ /**
81
+ * Take the latest user prompt + assistant reply pair (plus any
82
+ * tool/tool_result events that fall between them). The gateway's
83
+ * realtime write API receives only this delta — uploading the whole
84
+ * history would duplicate memories on every turn.
85
+ */
86
+ function lastTurn(messages) {
87
+ if (messages.length === 0) return [];
88
+ // Walk backwards: take everything from the last `user` event
89
+ // through the end. That captures user → tool... → assistant flow.
90
+ let startIdx = -1;
91
+ for (let i = messages.length - 1; i >= 0; i--) {
92
+ if (messages[i].role === AGENT_MEMORY_ROLES.USER) {
93
+ startIdx = i;
94
+ break;
95
+ }
96
+ }
97
+ if (startIdx === -1) return messages;
98
+ return messages.slice(startIdx);
99
+ }
100
+
101
+ async function readStdinJSON() {
102
+ const chunks = [];
103
+ for await (const c of process.stdin) chunks.push(c);
104
+ const raw = Buffer.concat(chunks).toString("utf8");
105
+ if (!raw) return {};
106
+ try {
107
+ return JSON.parse(raw);
108
+ } catch {
109
+ return {};
110
+ }
111
+ }
112
+
113
+ main();
package/install.sh ADDED
@@ -0,0 +1,112 @@
1
+ #!/bin/bash
2
+ #
3
+ # EverMe plugin installer for Claude Code.
4
+ #
5
+ # What it does:
6
+ # 1. Verifies `claude` CLI exists and Node 18+.
7
+ # 2. Prompts for EVERME_API_KEY (an emk_*) if not already set, and
8
+ # writes it to ~/.claude/everme.env (mode 0600). The plugin's
9
+ # lib/config.js loads this file at hook startup.
10
+ # 3. Tells `claude` to install this plugin from the local directory.
11
+ #
12
+ # What it does NOT do:
13
+ # - Edit the user's shell profile. Earlier revisions appended an
14
+ # `export EVERME_API_KEY=…` line to ~/.zshrc / ~/.bashrc — that
15
+ # stored a secret in a typically world-readable file (mode 0644)
16
+ # and accumulated a duplicate line on every re-install. We now
17
+ # mirror the path `evercli plugin install claude-code` uses: a
18
+ # 0600 file at ~/.claude/everme.env, scoped to the plugin.
19
+ set -e
20
+
21
+ CYAN='\033[0;36m'
22
+ GREEN='\033[0;32m'
23
+ RED='\033[0;31m'
24
+ YELLOW='\033[1;33m'
25
+ NC='\033[0m'
26
+
27
+ PLUGIN_DIR="$(cd "$(dirname "$0")" && pwd)"
28
+ ENV_FILE="$HOME/.claude/everme.env"
29
+
30
+ echo
31
+ echo -e "${CYAN}EverMe — Claude Code plugin installer${NC}"
32
+ echo
33
+
34
+ if ! command -v claude >/dev/null 2>&1; then
35
+ echo -e "${RED}claude CLI not found.${NC}"
36
+ echo "Install Claude Code first: https://claude.ai/code"
37
+ exit 1
38
+ fi
39
+ echo -e "${GREEN}✓${NC} Claude Code CLI detected"
40
+
41
+ if ! command -v node >/dev/null 2>&1; then
42
+ echo -e "${RED}node not found.${NC} Plugin hooks need Node 18+."
43
+ exit 1
44
+ fi
45
+ NODE_MAJOR=$(node -p "process.versions.node.split('.')[0]" 2>/dev/null || echo 0)
46
+ if [ "$NODE_MAJOR" -lt 18 ]; then
47
+ echo -e "${RED}node $NODE_MAJOR is too old.${NC} Need Node 18+."
48
+ exit 1
49
+ fi
50
+ echo -e "${GREEN}✓${NC} Node $(node --version)"
51
+
52
+ # Persist credentials to ~/.claude/everme.env (mode 0600). atomic_write
53
+ # mirrors evercli's writeFileAtomic — body goes via .tmp then rename so
54
+ # the plugin never reads a half-written file.
55
+ atomic_write_env() {
56
+ local body=$1
57
+ mkdir -p "$(dirname "$ENV_FILE")"
58
+ chmod 0700 "$(dirname "$ENV_FILE")" 2>/dev/null || true
59
+ local tmp="${ENV_FILE}.tmp"
60
+ rm -f "$tmp"
61
+ # umask 0077 → 0600 file; -e/-c work with O_EXCL semantics on noclobber.
62
+ (umask 0077 && printf '%s' "$body" > "$tmp")
63
+ mv -f "$tmp" "$ENV_FILE"
64
+ chmod 0600 "$ENV_FILE"
65
+ }
66
+
67
+ # Configure auth.
68
+ if [ -z "${EVERME_API_KEY:-}" ] && [ -z "${EVERME_AGENT_TOKEN:-}" ] && [ ! -f "$ENV_FILE" ]; then
69
+ echo
70
+ echo -e "${YELLOW}EverMe credentials not found.${NC}"
71
+ echo -n "Paste your account API key (starts with emk_, from the EverMe Web UI; input is hidden): "
72
+ # -s suppresses echo so the token never lands in shell scrollback.
73
+ read -rs EMK_INPUT
74
+ echo
75
+ if [ -z "$EMK_INPUT" ]; then
76
+ echo -e "${RED}No key provided. Aborting.${NC}"
77
+ exit 1
78
+ fi
79
+
80
+ BODY="# Managed by ${PLUGIN_DIR##*/}/install.sh — do not edit by hand.
81
+ # Re-run install.sh to refresh, or remove this file to disable the plugin.
82
+ "
83
+ if [ -n "${EVERME_API_BASE:-}" ]; then
84
+ BODY="${BODY}EVERME_API_BASE=${EVERME_API_BASE}
85
+ "
86
+ fi
87
+ BODY="${BODY}EVERME_API_KEY=${EMK_INPUT}
88
+ "
89
+ atomic_write_env "$BODY"
90
+ unset EMK_INPUT BODY
91
+ echo -e "${GREEN}✓${NC} Wrote credentials to $ENV_FILE (mode 0600)"
92
+ elif [ -f "$ENV_FILE" ]; then
93
+ echo -e "${GREEN}✓${NC} Existing $ENV_FILE detected — leaving credentials as-is"
94
+ fi
95
+
96
+ # Optional API base override (default https://everme.evermind.ai).
97
+ if [ -n "${EVERME_API_BASE:-}" ]; then
98
+ echo -e "${GREEN}✓${NC} Using EVERME_API_BASE=$EVERME_API_BASE"
99
+ fi
100
+
101
+ # Install via Claude Code's plugin system.
102
+ echo
103
+ echo -e "${CYAN}Installing plugin via 'claude plugin install'…${NC}"
104
+ claude plugin install "$PLUGIN_DIR"
105
+
106
+ echo
107
+ echo -e "${GREEN}✓ EverMe plugin installed.${NC}"
108
+ echo
109
+ echo "Next steps:"
110
+ echo " 1. Start Claude Code: claude"
111
+ echo " 2. Try '/everme-help' or '/recall <something>' to verify."
112
+ echo " 3. Set EVERME_DEBUG=1 in your environment to see hook traces on stderr."
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@everme/claude-code",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "EverMe native plugin for Claude Code — automatic memory recall via SessionStart/UserPromptSubmit/Stop/SessionEnd hooks, plus /recall slash + bundled MCP server.",
6
+ "license": "Apache-2.0",
7
+ "engines": {
8
+ "node": ">=18.0.0"
9
+ },
10
+ "scripts": {
11
+ "test": "node --test tests/redact.test.js tests/config.test.js tests/transcript.test.js"
12
+ },
13
+ "files": [
14
+ "plugin.json",
15
+ ".claude-plugin/",
16
+ "hooks/",
17
+ "commands/",
18
+ "skills/",
19
+ "install.sh",
20
+ "LICENSE",
21
+ "README.md"
22
+ ],
23
+ "dependencies": {
24
+ "@everme/agent-sdk": "^0.1.0"
25
+ },
26
+ "keywords": ["evermind", "everme", "claude-code", "claude", "anthropic", "memory", "ai", "agent", "mcp"],
27
+ "homepage": "https://everme.ai",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/alwaysday1/everme.git",
31
+ "directory": "plugins/claude-code"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/alwaysday1/everme/issues"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "registry": "https://registry.npmjs.org"
39
+ }
40
+ }
package/plugin.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "everme",
3
+ "version": "0.1.0",
4
+ "description": "EverMe — automatic memory recall for Claude Code. Recalls relevant context from past sessions before each prompt and saves new turns through the EverMe gateway.",
5
+ "author": {
6
+ "name": "EverMind AI",
7
+ "url": "https://everme.evermind.ai"
8
+ },
9
+ "homepage": "https://everme.evermind.ai",
10
+ "keywords": ["memory", "context", "recall", "persistence", "everme"],
11
+ "license": "Apache-2.0"
12
+ }
@@ -0,0 +1,35 @@
1
+ ---
2
+ description: How and when to use EverMe memory tools to bring past-session context into the current Claude Code conversation.
3
+ alwaysInclude: true
4
+ ---
5
+
6
+ # EverMe Memory Tools
7
+
8
+ You have two MCP tools that surface memory persisted by EverMe across past Claude Code sessions:
9
+
10
+ - `everme_search` — semantic + keyword hybrid search over the user's memory store. Returns ranked items with subject, summary, score.
11
+ - `everme_context` — fetch a server-rendered context block (profile + recent episodes) ready to inject into the current turn.
12
+
13
+ The plugin's UserPromptSubmit hook already injects relevant memory automatically before each prompt. You usually do NOT need to call these tools manually — they're for cases the auto-recall missed.
14
+
15
+ ## When to use these tools
16
+
17
+ **Do call** when:
18
+ - The user references something they discussed before ("last time", "remember when", "we decided to use X")
19
+ - The user asks about a project pattern, decision, or convention you have no inline context for
20
+ - You're debugging an error message that may have been seen + resolved before
21
+ - The auto-recall block (`<everme_recall>...</everme_recall>` in your context) is empty or clearly unrelated to the current task
22
+ - The user explicitly asks you to "search my memory" / "recall" / "look up"
23
+
24
+ **Do NOT call** when:
25
+ - The current message is self-contained and you can answer from inline context
26
+ - You already searched in the current turn (don't duplicate)
27
+ - It's a general-knowledge question with no project history component
28
+
29
+ ## Best practices
30
+
31
+ 1. Search with the user's specific terms first; only broaden if zero hits.
32
+ 2. Cite memories by subject so the user can trace them.
33
+ 3. Synthesize, don't copy-paste — quote the relevant lines, not whole memory bodies.
34
+ 4. If recall returns conflicting info ("two prior sessions disagree"), say so and ask the user which is current.
35
+ 5. The user's emk / evt is a credential — never echo it back, even when EverMe-related errors surface.