memoryai-mcp 2.2.0 → 2.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.
- package/README.md +362 -304
- package/dist/claude-setup.d.ts +19 -0
- package/dist/claude-setup.js +216 -0
- package/dist/index.js +342 -77
- package/dist/kiro-setup.d.ts +11 -2
- package/dist/kiro-setup.js +143 -60
- package/package.json +46 -45
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* memoryai-claude-setup
|
|
4
|
+
*
|
|
5
|
+
* Wires MemoryAI into Claude Code at the MECHANISM level using HTTP hooks.
|
|
6
|
+
* Claude Code injects a hook's `additionalContext` straight into the model's
|
|
7
|
+
* context — no agent decision required — so memory works the moment it's set up,
|
|
8
|
+
* exactly like the OpenAI proxy. The user runs this once and never thinks about
|
|
9
|
+
* memory again:
|
|
10
|
+
*
|
|
11
|
+
* - SessionStart → POST /v1/hooks/claude/session-start (inject DNA + recent context)
|
|
12
|
+
* - UserPromptSubmit→ POST /v1/hooks/claude/user-prompt (recall before answering)
|
|
13
|
+
* - Stop → POST /v1/hooks/claude/stop (auto-store after each turn)
|
|
14
|
+
*
|
|
15
|
+
* It also registers the MCP server (so the 70+ tools are available for advanced
|
|
16
|
+
* use) and writes a CLAUDE.md note. Existing settings/CLAUDE.md are merged, never
|
|
17
|
+
* clobbered.
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* memoryai-claude-setup
|
|
4
|
+
*
|
|
5
|
+
* Wires MemoryAI into Claude Code at the MECHANISM level using HTTP hooks.
|
|
6
|
+
* Claude Code injects a hook's `additionalContext` straight into the model's
|
|
7
|
+
* context — no agent decision required — so memory works the moment it's set up,
|
|
8
|
+
* exactly like the OpenAI proxy. The user runs this once and never thinks about
|
|
9
|
+
* memory again:
|
|
10
|
+
*
|
|
11
|
+
* - SessionStart → POST /v1/hooks/claude/session-start (inject DNA + recent context)
|
|
12
|
+
* - UserPromptSubmit→ POST /v1/hooks/claude/user-prompt (recall before answering)
|
|
13
|
+
* - Stop → POST /v1/hooks/claude/stop (auto-store after each turn)
|
|
14
|
+
*
|
|
15
|
+
* It also registers the MCP server (so the 70+ tools are available for advanced
|
|
16
|
+
* use) and writes a CLAUDE.md note. Existing settings/CLAUDE.md are merged, never
|
|
17
|
+
* clobbered.
|
|
18
|
+
*/
|
|
19
|
+
import { createInterface } from "node:readline";
|
|
20
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { join, dirname } from "node:path";
|
|
22
|
+
import { homedir } from "node:os";
|
|
23
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
24
|
+
function ask(question, fallback) {
|
|
25
|
+
const suffix = fallback ? ` [${fallback}]` : "";
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
rl.question(`${question}${suffix}: `, (answer) => {
|
|
28
|
+
resolve(answer.trim() || fallback || "");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function readJsonSafe(path) {
|
|
33
|
+
if (!existsSync(path))
|
|
34
|
+
return {};
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(readFileSync(path, "utf-8")) || {};
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
console.error(` warn ${path} is not valid JSON — leaving it untouched and aborting.`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function writeJson(path, data) {
|
|
44
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
45
|
+
writeFileSync(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
46
|
+
}
|
|
47
|
+
/** A single HTTP hook handler bound to a MemoryAI endpoint. */
|
|
48
|
+
function httpHook(endpoint, apiKey, timeout) {
|
|
49
|
+
return {
|
|
50
|
+
type: "http",
|
|
51
|
+
url: endpoint,
|
|
52
|
+
timeout,
|
|
53
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
54
|
+
// Required for Claude Code to interpolate the env-style header value.
|
|
55
|
+
allowedEnvVars: [],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** True if any handler in a hook group already points at a memoryai endpoint. */
|
|
59
|
+
function groupHasMemoryAI(group) {
|
|
60
|
+
const handlers = (group && group.hooks) || [];
|
|
61
|
+
return handlers.some((h) => typeof h?.url === "string" && h.url.includes("/v1/hooks/claude/"));
|
|
62
|
+
}
|
|
63
|
+
function ensureHook(settings, event, handler) {
|
|
64
|
+
settings.hooks = settings.hooks || {};
|
|
65
|
+
settings.hooks[event] = settings.hooks[event] || [];
|
|
66
|
+
// De-dupe: skip if a MemoryAI hook for this event already exists.
|
|
67
|
+
if (settings.hooks[event].some(groupHasMemoryAI))
|
|
68
|
+
return false;
|
|
69
|
+
settings.hooks[event].push({ hooks: [handler] });
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
const CLAUDE_MD = `
|
|
73
|
+
# MemoryAI — Persistent Memory (automatic)
|
|
74
|
+
|
|
75
|
+
MemoryAI is wired into this Claude Code via HTTP hooks, so memory works
|
|
76
|
+
automatically at the mechanism level — you don't have to call tools by hand:
|
|
77
|
+
|
|
78
|
+
- Relevant past context is injected before each prompt (UserPromptSubmit hook).
|
|
79
|
+
- Session-start context (preferences, decisions, recent work) loads on open.
|
|
80
|
+
- Decisions and preferences are stored automatically when each turn ends.
|
|
81
|
+
|
|
82
|
+
The MemoryAI MCP server is also connected for advanced use. You may call
|
|
83
|
+
\`memory_recall\` explicitly when you need deeper history, but for everyday work
|
|
84
|
+
the hooks handle it. Never store secrets or credentials.
|
|
85
|
+
`;
|
|
86
|
+
const MCP_BLOCK = (apiKey, endpoint) => ({
|
|
87
|
+
command: "npx",
|
|
88
|
+
args: ["-y", "memoryai-mcp"],
|
|
89
|
+
env: { HM_API_KEY: apiKey, HM_ENDPOINT: endpoint },
|
|
90
|
+
});
|
|
91
|
+
/**
|
|
92
|
+
* Auto-provision a fresh API key from the public self-service endpoint so the
|
|
93
|
+
* user truly does nothing — no curl, no dashboard. Returns the key string, or
|
|
94
|
+
* null on any failure (caller falls back to asking). The endpoint is public and
|
|
95
|
+
* IP-rate-limited server-side; we accept ToS on the user's behalf since running
|
|
96
|
+
* this installer is an explicit action.
|
|
97
|
+
*/
|
|
98
|
+
async function provisionKey(endpoint, name) {
|
|
99
|
+
const base = endpoint.replace(/\/+$/, "");
|
|
100
|
+
try {
|
|
101
|
+
const resp = await fetch(`${base}/v1/admin/provision`, {
|
|
102
|
+
method: "POST",
|
|
103
|
+
headers: { "Content-Type": "application/json" },
|
|
104
|
+
body: JSON.stringify({ name: name || "claude-code", tos_accepted: true }),
|
|
105
|
+
});
|
|
106
|
+
if (!resp.ok) {
|
|
107
|
+
const txt = await resp.text().catch(() => "");
|
|
108
|
+
console.error(` warn auto-provision failed (HTTP ${resp.status}). ${txt.slice(0, 200)}`);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const data = (await resp.json());
|
|
112
|
+
if (data?.api_key) {
|
|
113
|
+
console.log(` ok provisioned new API key (${String(data.api_key).slice(0, 10)}…, plan=${data.plan || "?"})`);
|
|
114
|
+
return data.api_key;
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
console.error(` warn auto-provision request error: ${e instanceof Error ? e.message : String(e)}`);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function main() {
|
|
124
|
+
console.log(`\nMemoryAI — Claude Code Setup (mechanism-level auto-memory)\n`);
|
|
125
|
+
// Non-interactive fast path: if everything is supplied via env, skip prompts.
|
|
126
|
+
// MEMORYAI_SCOPE = "user" (default) or "project". Enables CI / scripted installs.
|
|
127
|
+
const envKey = process.env.HM_API_KEY || process.env.MEMORYAI_API_KEY || "";
|
|
128
|
+
const envEndpoint = process.env.HM_ENDPOINT || process.env.MEMORYAI_ENDPOINT || "";
|
|
129
|
+
const envScope = (process.env.MEMORYAI_SCOPE || "").toLowerCase();
|
|
130
|
+
const nonInteractive = process.env.MEMORYAI_NONINTERACTIVE === "1" || (Boolean(envKey) && Boolean(envEndpoint));
|
|
131
|
+
let apiKey;
|
|
132
|
+
let endpoint;
|
|
133
|
+
let scopeAns;
|
|
134
|
+
if (nonInteractive) {
|
|
135
|
+
endpoint = envEndpoint || "https://memoryai.dev";
|
|
136
|
+
scopeAns = envScope || "u";
|
|
137
|
+
apiKey = envKey;
|
|
138
|
+
if (!apiKey) {
|
|
139
|
+
console.log(" ... non-interactive, no key — provisioning one");
|
|
140
|
+
const provisioned = await provisionKey(endpoint, "claude-code");
|
|
141
|
+
if (provisioned)
|
|
142
|
+
apiKey = provisioned;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
console.log(" (non-interactive: using environment configuration)");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
endpoint = await ask("Endpoint", envEndpoint || "https://memoryai.dev");
|
|
150
|
+
apiKey = envKey || (await ask("MemoryAI API key (blank = auto-provision a free one)")).trim();
|
|
151
|
+
if (!apiKey) {
|
|
152
|
+
console.log(" ... no key given — provisioning one for you");
|
|
153
|
+
const provisioned = await provisionKey(endpoint, "claude-code");
|
|
154
|
+
if (provisioned)
|
|
155
|
+
apiKey = provisioned;
|
|
156
|
+
}
|
|
157
|
+
scopeAns = (await ask("Apply to (u)ser globally or this (p)roject?", "u")).toLowerCase();
|
|
158
|
+
}
|
|
159
|
+
if (!apiKey) {
|
|
160
|
+
console.error("Error: could not obtain an API key (auto-provision failed). Set HM_API_KEY and re-run.");
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
const settingsPath = scopeAns.startsWith("p")
|
|
164
|
+
? join(process.cwd(), ".claude", "settings.json")
|
|
165
|
+
: join(homedir(), ".claude", "settings.json");
|
|
166
|
+
console.log("");
|
|
167
|
+
const settings = readJsonSafe(settingsPath);
|
|
168
|
+
// 1. MCP server (advanced tools)
|
|
169
|
+
settings.mcpServers = settings.mcpServers || {};
|
|
170
|
+
if (!settings.mcpServers.memoryai) {
|
|
171
|
+
settings.mcpServers.memoryai = MCP_BLOCK(apiKey, endpoint);
|
|
172
|
+
console.log(" add mcpServers.memoryai");
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
console.log(" skip mcpServers.memoryai (already present)");
|
|
176
|
+
}
|
|
177
|
+
// 2. The three lifecycle hooks. UserPromptSubmit gets a tighter timeout
|
|
178
|
+
// because it blocks the prompt until it returns.
|
|
179
|
+
const base = endpoint.replace(/\/+$/, "");
|
|
180
|
+
const added = {
|
|
181
|
+
SessionStart: ensureHook(settings, "SessionStart", httpHook(`${base}/v1/hooks/claude/session-start`, apiKey, 10)),
|
|
182
|
+
UserPromptSubmit: ensureHook(settings, "UserPromptSubmit", httpHook(`${base}/v1/hooks/claude/user-prompt`, apiKey, 10)),
|
|
183
|
+
Stop: ensureHook(settings, "Stop", httpHook(`${base}/v1/hooks/claude/stop`, apiKey, 15)),
|
|
184
|
+
};
|
|
185
|
+
for (const [event, didAdd] of Object.entries(added)) {
|
|
186
|
+
console.log(` ${didAdd ? "add " : "skip "} hooks.${event}${didAdd ? "" : " (already present)"}`);
|
|
187
|
+
}
|
|
188
|
+
writeJson(settingsPath, settings);
|
|
189
|
+
console.log(` write ${settingsPath}`);
|
|
190
|
+
// 3. CLAUDE.md note (append if missing).
|
|
191
|
+
const claudeMdPath = scopeAns.startsWith("p")
|
|
192
|
+
? join(process.cwd(), "CLAUDE.md")
|
|
193
|
+
: join(homedir(), ".claude", "CLAUDE.md");
|
|
194
|
+
const existing = existsSync(claudeMdPath) ? readFileSync(claudeMdPath, "utf-8") : "";
|
|
195
|
+
if (!existing.includes("MemoryAI — Persistent Memory")) {
|
|
196
|
+
writeFileSync(claudeMdPath, existing + (existing ? "\n" : "") + CLAUDE_MD, "utf-8");
|
|
197
|
+
console.log(` ${existing ? "append" : "create"} ${claudeMdPath}`);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
console.log(` skip ${claudeMdPath} (note already present)`);
|
|
201
|
+
}
|
|
202
|
+
console.log(`
|
|
203
|
+
Done. MemoryAI runs automatically in Claude Code — nothing else to do.
|
|
204
|
+
- Context is recalled before each prompt and injected for you.
|
|
205
|
+
- Decisions/preferences are stored when each turn ends.
|
|
206
|
+
|
|
207
|
+
Next steps:
|
|
208
|
+
1. Restart Claude Code (loads the hooks + MCP server).
|
|
209
|
+
2. Just work normally. Memory persists across sessions on its own.
|
|
210
|
+
`);
|
|
211
|
+
rl.close();
|
|
212
|
+
}
|
|
213
|
+
main().catch((err) => {
|
|
214
|
+
console.error(err);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
});
|