@everme/claude-code 0.1.0 → 0.3.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.
@@ -10,7 +10,7 @@
10
10
  "name": "everme",
11
11
  "source": "./",
12
12
  "description": "Automatic memory recall for Claude Code through the EverMe gateway. Saves and recalls per-session context using your EverMe account credentials.",
13
- "version": "0.1.0",
13
+ "version": "0.3.1",
14
14
  "homepage": "https://everme.evermind.ai",
15
15
  "license": "Apache-2.0"
16
16
  }
@@ -1,12 +1,18 @@
1
1
  {
2
2
  "name": "everme",
3
- "version": "0.1.0",
3
+ "version": "0.3.1",
4
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
5
  "author": {
6
6
  "name": "EverMind AI",
7
7
  "url": "https://everme.evermind.ai"
8
8
  },
9
9
  "homepage": "https://everme.evermind.ai",
10
- "keywords": ["memory", "context", "recall", "persistence", "everme"],
10
+ "keywords": [
11
+ "memory",
12
+ "context",
13
+ "recall",
14
+ "persistence",
15
+ "everme"
16
+ ],
11
17
  "license": "Apache-2.0"
12
18
  }
package/README.md CHANGED
@@ -43,7 +43,7 @@ The installer:
43
43
 
44
44
  ```bash
45
45
  export EVERME_API_KEY="emk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # from EverMe Web UI
46
- export EVERME_API_BASE="http://localhost:8080" # optional — defaults to everme.evermind.ai
46
+ export EVERME_API_BASE="http://localhost:8080" # optional — defaults to api.everme.evermind.ai
47
47
  claude plugin install /path/to/everme/plugins/claude-code
48
48
  ```
49
49
 
@@ -54,7 +54,7 @@ claude plugin install /path/to/everme/plugins/claude-code
54
54
  | `EVERME_API_KEY` | Account-level emk. Supports recall-only mode. |
55
55
  | `EVERME_AGENT_TOKEN` | Per-machine evt — written by `evercli plugin install claude-code`. Required for realtime writes and wins over emk when both are set. |
56
56
  | `EVERME_AGENT_ID` | Required with `EVERME_AGENT_TOKEN` for realtime writes; also pins recall to a specific cloud agent. |
57
- | `EVERME_API_BASE` | Gateway host. Defaults to `https://everme.evermind.ai`. Set to `http://localhost:8080` for local dev. |
57
+ | `EVERME_API_BASE` | Gateway host. Defaults to `https://api.everme.evermind.ai`. Set to `http://localhost:8080` for local dev. |
58
58
  | `EVERME_DEBUG` | `1` to print hook traces to stderr (with token redaction). |
59
59
 
60
60
  ## Verifying the install
@@ -76,9 +76,9 @@ In a new Claude Code session:
76
76
  ## Files
77
77
 
78
78
  ```
79
- plugin.json plugin metadata
80
- .claude-plugin/.mcp.json MCP server registration
79
+ .claude-plugin/plugin.json plugin metadata
81
80
  .claude-plugin/marketplace.json marketplace listing
81
+ .mcp.json MCP server registration
82
82
  hooks/hooks.json SessionStart / UserPromptSubmit / Stop / SessionEnd wiring
83
83
  hooks/scripts/inject-memories.js UserPromptSubmit handler
84
84
  hooks/scripts/store-memories.js Stop handler
@@ -11,7 +11,7 @@ EverMe for Claude Code
11
11
  ─────────────────────
12
12
  Auth: EVERME_API_KEY (account emk_*) — recall-only mode
13
13
  EVERME_AGENT_TOKEN + EVERME_AGENT_ID — required for realtime writes
14
- Gateway: EVERME_API_BASE — defaults to https://everme.evermind.ai
14
+ Gateway: EVERME_API_BASE — defaults to https://api.everme.evermind.ai
15
15
 
16
16
  Hooks:
17
17
  SessionStart → loads recent context from past sessions
@@ -48,6 +48,7 @@ async function main() {
48
48
  // where there is no prompt, but per-turn inject goes through search.
49
49
  let block = "";
50
50
  let count = 0;
51
+ let degraded = null;
51
52
  try {
52
53
  const res = await searchMemories(prompt, { topK: TOP_K });
53
54
  // EverOS frequently returns score=null on episodic hits (decoded as
@@ -77,11 +78,22 @@ async function main() {
77
78
  const inner = buildMemoryPrompt(bundle, { wrapInCodeBlock: false });
78
79
  if (inner) block = `<everme_recall>\n${inner}\n</everme_recall>`;
79
80
  } catch (err) {
80
- debug("inject", "search failed:", redactError(err?.message));
81
+ const reason = redactError(err?.message || String(err));
82
+ degraded = reason;
83
+ debug("inject", "search failed:", reason);
81
84
  }
82
85
 
83
86
  if (!block || count === 0) {
84
- debug("inject", "no memories above threshold");
87
+ if (degraded) {
88
+ // Visible single-line WARN — the hook is by design non-blocking,
89
+ // but a fully silent failure means the user thinks EverMe is
90
+ // running while every recall is dropped. Surface it once per
91
+ // hook invocation so persistent issues (expired token, backend
92
+ // unreachable) bubble up without forcing EVERME_DEBUG=1.
93
+ process.stderr.write(`EverMe inject hook degraded: ${degraded}\n`);
94
+ } else {
95
+ debug("inject", "no memories above threshold");
96
+ }
85
97
  return process.exit(0);
86
98
  }
87
99
 
@@ -14,7 +14,7 @@
14
14
  * users may legitimately want to override these from a shell or from
15
15
  * Claude Code's mcp .env block, and evercli does not own them.
16
16
  *
17
- * Compiled defaults (everme.evermind.ai, no token) sit at the bottom.
17
+ * Compiled defaults (api.everme.evermind.ai, no token) sit at the bottom.
18
18
  *
19
19
  * Auth modes (mutually exclusive, both wire-compatible):
20
20
  * evt — set EVERME_AGENT_TOKEN (per-machine token from evercli)
@@ -36,7 +36,23 @@ const RETRY_DELAY_MS = 100;
36
36
  export async function readTranscript(path) {
37
37
  if (!path || !existsSync(path)) return [];
38
38
  for (let i = 0; i < READ_RETRIES; i++) {
39
- const raw = await readFile(path, "utf8");
39
+ // readFile errors must NOT escape this function. The Stop hook
40
+ // installs a process-wide unhandledRejection handler that exits 0
41
+ // silently — an ENOENT here (the transcript file was rotated /
42
+ // removed mid-read) used to fall into that catch-all and the
43
+ // entire Stop write was silently dropped. Treating ENOENT as
44
+ // "transient, retry" and other fs errors as "give up, return []"
45
+ // surfaces the right signal: zero lines means no Stop work to do.
46
+ let raw;
47
+ try {
48
+ raw = await readFile(path, "utf8");
49
+ } catch (err) {
50
+ if (err?.code === "ENOENT" && i < READ_RETRIES - 1) {
51
+ await sleep(RETRY_DELAY_MS);
52
+ continue;
53
+ }
54
+ return [];
55
+ }
40
56
  const lines = raw.trim().split("\n").filter(Boolean);
41
57
  if (lines.length === 0) {
42
58
  await sleep(RETRY_DELAY_MS);
@@ -127,27 +143,80 @@ export function extractAgentMessages(lines) {
127
143
  continue;
128
144
  }
129
145
  const timestamp = normalizeTimestamp(ev.timestamp);
130
- if (ev.role === AGENT_MEMORY_ROLES.USER) {
131
- const content = textFromContent(ev.content);
132
- if (content) messages.push({ role: AGENT_MEMORY_ROLES.USER, timestamp, content });
146
+ // Real Claude Code transcript schema is nested: each line is
147
+ // { "type":"user"|"assistant", "message":{role, content}, ... }
148
+ // with content.string OR content[]={text|thinking|tool_use|tool_result}.
149
+ // Tool results live INSIDE a user-role envelope (with content[].type=tool_result
150
+ // carrying tool_use_id). The legacy flat-shape fixture (top-level
151
+ // ev.role + ev.content) is accepted as a fallback so existing tests
152
+ // and any external producers keep working.
153
+ const inner = ev.message && typeof ev.message === "object" ? ev.message : null;
154
+ const role = inner?.role ?? ev.role;
155
+ const rawContent = inner?.content ?? ev.content;
156
+
157
+ if (role === AGENT_MEMORY_ROLES.USER) {
158
+ // Split the user envelope: tool_result blocks become role=tool
159
+ // messages (with their own toolCallId), free text becomes a
160
+ // role=user message. A single CC user event can therefore emit
161
+ // multiple EverMe messages.
162
+ const toolResults = extractToolResults(rawContent, timestamp);
163
+ messages.push(...toolResults);
164
+ const text = textFromContent(rawContent);
165
+ if (text) messages.push({ role: AGENT_MEMORY_ROLES.USER, timestamp, content: text });
133
166
  continue;
134
167
  }
135
- if (ev.role === AGENT_MEMORY_ROLES.ASSISTANT) {
136
- const msg = agentAssistantMessage(ev.content, timestamp);
168
+ if (role === AGENT_MEMORY_ROLES.ASSISTANT) {
169
+ const msg = agentAssistantMessage(rawContent, timestamp);
137
170
  if (msg) messages.push(msg);
138
171
  continue;
139
172
  }
140
- if (ev.role === AGENT_MEMORY_ROLES.TOOL || ev.type === "tool_result") {
173
+ // Legacy flat tool-role fallback: { role:"tool", content, toolCallId }
174
+ if (role === AGENT_MEMORY_ROLES.TOOL || ev.type === "tool_result") {
141
175
  const toolCallId = ev.toolCallId || ev.tool_call_id || ev.tool_use_id;
142
176
  if (!toolCallId) continue;
143
177
  const content =
144
- typeof ev.content === "string" ? ev.content : safeJsonStringify(ev.content);
178
+ typeof rawContent === "string" ? rawContent : safeJsonStringify(rawContent);
145
179
  messages.push({ role: AGENT_MEMORY_ROLES.TOOL, timestamp, toolCallId, content });
146
180
  }
147
181
  }
148
182
  return messages;
149
183
  }
150
184
 
185
+ // extractToolResults pulls every `tool_result` block out of a CC user-
186
+ // envelope content array and converts each into an EverMe role=tool
187
+ // message. CC encodes tool_result as a content block inside a user
188
+ // envelope (not a separate top-level event), so without this step the
189
+ // entire tool round-trip is lost.
190
+ function extractToolResults(content, timestamp) {
191
+ if (!Array.isArray(content)) return [];
192
+ const out = [];
193
+ for (const b of content) {
194
+ if (!b || typeof b !== "object") continue;
195
+ if (b.type !== "tool_result") continue;
196
+ const toolCallId = b.tool_use_id || b.toolCallId || b.tool_call_id;
197
+ if (!toolCallId) continue;
198
+ let text;
199
+ if (typeof b.content === "string") {
200
+ text = b.content;
201
+ } else if (Array.isArray(b.content)) {
202
+ // tool_result.content can itself be a list of typed blocks (e.g.
203
+ // [{type:"text", text:...}]) — flatten the text-bearing ones.
204
+ text = b.content
205
+ .map((c) => {
206
+ if (typeof c === "string") return c;
207
+ if (c?.type === "text" && typeof c.text === "string") return c.text;
208
+ return "";
209
+ })
210
+ .filter(Boolean)
211
+ .join("\n");
212
+ } else {
213
+ text = safeJsonStringify(b.content);
214
+ }
215
+ out.push({ role: AGENT_MEMORY_ROLES.TOOL, timestamp, toolCallId, content: text });
216
+ }
217
+ return out;
218
+ }
219
+
151
220
  function agentAssistantMessage(content, timestamp) {
152
221
  if (typeof content === "string") {
153
222
  return content ? { role: AGENT_MEMORY_ROLES.ASSISTANT, timestamp, content } : null;
@@ -17,10 +17,25 @@
17
17
  */
18
18
 
19
19
  import { createInterface } from "readline";
20
+ import { createRequire } from "node:module";
20
21
  import { searchMemories, getContext, EvermeError } from "./lib/api.js";
21
22
  import { isConfigured } from "./lib/config.js";
22
23
  import { redactError, debug } from "./lib/redact.js";
23
24
 
25
+ // Derive serverInfo.version from package.json so the value tracks the
26
+ // plugin release rather than rotting as a hard-coded literal. We sit at
27
+ // hooks/scripts/mcp-server.js, package.json is three levels up.
28
+ const { version: PKG_VERSION } = createRequire(import.meta.url)("../../../package.json");
29
+
30
+ // Protocol versions this hand-rolled server knows about. Clients that
31
+ // announce one of these in `initialize` get their version echoed back
32
+ // (per MCP spec the server SHOULD agree to the client's version when
33
+ // supported); anything else falls back to the newest we support so the
34
+ // session can still establish. Prior code hard-coded "2024-11-05" while
35
+ // Claude Code already negotiates "2025-03-26".
36
+ const SUPPORTED_PROTOCOL_VERSIONS = new Set(["2024-11-05", "2025-03-26"]);
37
+ const LATEST_PROTOCOL_VERSION = "2025-03-26";
38
+
24
39
  const TOOLS = [
25
40
  {
26
41
  name: "everme_search",
@@ -50,11 +65,17 @@ const TOOLS = [
50
65
  ];
51
66
 
52
67
  const handlers = {
53
- initialize: () => ({
54
- protocolVersion: "2024-11-05",
55
- capabilities: { tools: { listChanged: false } },
56
- serverInfo: { name: "everme", version: "0.1.0" },
57
- }),
68
+ initialize: (params) => {
69
+ const requested = typeof params?.protocolVersion === "string" ? params.protocolVersion : "";
70
+ const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.has(requested)
71
+ ? requested
72
+ : LATEST_PROTOCOL_VERSION;
73
+ return {
74
+ protocolVersion,
75
+ capabilities: { tools: { listChanged: false } },
76
+ serverInfo: { name: "everme", version: PKG_VERSION },
77
+ };
78
+ },
58
79
  "tools/list": () => ({ tools: TOOLS }),
59
80
  "tools/call": async (params) => {
60
81
  const name = params?.name;
@@ -68,11 +68,20 @@ async function main() {
68
68
  });
69
69
  debug("store", `ok agent-memory status=${res?.status || "unknown"} flushed=${!!res?.flushed} messages=${res?.messageCount || tail.length}`);
70
70
  } catch (err) {
71
+ // Visible single-line WARN — Stop hook is non-blocking by design,
72
+ // but silently dropping every turn means a misconfigured machine
73
+ // looks healthy while EverMe never receives a write. Surface
74
+ // it once per Stop so persistent issues (401, network, schema
75
+ // drift) bubble up without forcing EVERME_DEBUG=1.
76
+ let reason;
71
77
  if (err instanceof EvermeError) {
78
+ reason = `${err.type}: ${redactError(err.message)}`;
72
79
  debug("store", `failed type=${err.type}:`, redactError(err.message));
73
80
  } else {
74
- debug("store", "unexpected:", redactError(err?.message || String(err)));
81
+ reason = redactError(err?.message || String(err));
82
+ debug("store", "unexpected:", reason);
75
83
  }
84
+ process.stderr.write(`EverMe store hook degraded: ${reason}\n`);
76
85
  }
77
86
  process.exit(0);
78
87
  }
package/install.sh CHANGED
@@ -93,7 +93,7 @@ elif [ -f "$ENV_FILE" ]; then
93
93
  echo -e "${GREEN}✓${NC} Existing $ENV_FILE detected — leaving credentials as-is"
94
94
  fi
95
95
 
96
- # Optional API base override (default https://everme.evermind.ai).
96
+ # Optional API base override (default https://api.everme.evermind.ai).
97
97
  if [ -n "${EVERME_API_BASE:-}" ]; then
98
98
  echo -e "${GREEN}✓${NC} Using EVERME_API_BASE=$EVERME_API_BASE"
99
99
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@everme/claude-code",
3
- "version": "0.1.0",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "description": "EverMe native plugin for Claude Code — automatic memory recall via SessionStart/UserPromptSubmit/Stop/SessionEnd hooks, plus /recall slash + bundled MCP server.",
6
6
  "license": "Apache-2.0",
@@ -8,11 +8,11 @@
8
8
  "node": ">=18.0.0"
9
9
  },
10
10
  "scripts": {
11
- "test": "node --test tests/redact.test.js tests/config.test.js tests/transcript.test.js"
11
+ "test": "node --test tests/redact.test.js tests/config.test.js tests/transcript.test.js tests/hooks.test.js"
12
12
  },
13
13
  "files": [
14
- "plugin.json",
15
14
  ".claude-plugin/",
15
+ ".mcp.json",
16
16
  "hooks/",
17
17
  "commands/",
18
18
  "skills/",
@@ -21,17 +21,27 @@
21
21
  "README.md"
22
22
  ],
23
23
  "dependencies": {
24
- "@everme/agent-sdk": "^0.1.0"
24
+ "@everme/agent-sdk": "^0.3.1"
25
25
  },
26
- "keywords": ["evermind", "everme", "claude-code", "claude", "anthropic", "memory", "ai", "agent", "mcp"],
27
- "homepage": "https://everme.ai",
26
+ "keywords": [
27
+ "evermind",
28
+ "everme",
29
+ "claude-code",
30
+ "claude",
31
+ "anthropic",
32
+ "memory",
33
+ "ai",
34
+ "agent",
35
+ "mcp"
36
+ ],
37
+ "homepage": "https://everme.evermind.ai",
28
38
  "repository": {
29
39
  "type": "git",
30
- "url": "git+https://github.com/alwaysday1/everme.git",
40
+ "url": "git+https://github.com/EverMind-AI/EverMe-CLI.git",
31
41
  "directory": "plugins/claude-code"
32
42
  },
33
43
  "bugs": {
34
- "url": "https://github.com/alwaysday1/everme/issues"
44
+ "url": "https://github.com/EverMind-AI/EverMe-CLI/issues"
35
45
  },
36
46
  "publishConfig": {
37
47
  "access": "public",
@@ -1,54 +0,0 @@
1
- /**
2
- * Stable per-machine source key for documentKey derivation.
3
- *
4
- * The hooks pass this into `buildDocumentKey(sourceKey, logicalPath)`
5
- * to anchor a version chain. In evt mode the agentId (`agt_…`) is
6
- * unique per machine + user + platform, so it works as the source key
7
- * unmodified. In emk mode the user has no agentId, so the previous
8
- * code fell back to the literal `"agt_claude_code"` — which means
9
- * every user on every machine sharing the same EverMe account would
10
- * write into the SAME version chain, overwriting each other's runtime
11
- * docs.
12
- *
13
- * This module computes a stable replacement: `claude-code:<host>:<user>`,
14
- * SHA256-truncated to a 24-hex prefix. Same machine + user → same
15
- * key across processes and reboots; two machines under one account →
16
- * two distinct chains.
17
- */
18
-
19
- import { createHash } from "node:crypto";
20
- import { hostname, userInfo } from "node:os";
21
-
22
- let cached = null;
23
-
24
- /**
25
- * Returns the source key the runtime hooks should pass into
26
- * buildDocumentKey. Honours `cfg.agentId` (evt mode) when present —
27
- * agentId is already the canonical per-machine fingerprint that
28
- * evercli writes — and falls back to a hashed host+user string when
29
- * the user is on emk auth.
30
- */
31
- export function getSourceKey(cfg) {
32
- if (cfg?.agentId) return cfg.agentId;
33
- if (cached) return cached;
34
- let host = "unknown-host";
35
- let user = "unknown-user";
36
- try {
37
- host = hostname() || host;
38
- } catch {
39
- /* fall through */
40
- }
41
- try {
42
- user = userInfo().username || user;
43
- } catch {
44
- /* fall through */
45
- }
46
- const sum = createHash("sha256").update(`claude-code:${host}:${user}`).digest("hex");
47
- cached = "agt_emk_" + sum.slice(0, 24);
48
- return cached;
49
- }
50
-
51
- /** Test seam — wipes the per-process cache. */
52
- export function _reset() {
53
- cached = null;
54
- }
File without changes