memoryai-claude 0.1.1 → 0.1.2

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 MemoryAI
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MemoryAI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,69 +1,69 @@
1
- # MemoryAI for Claude Code
2
-
3
- Your AI keeps forgetting you. MemoryAI gives Claude Code a real long-term
4
- memory: one that follows you across every model.
5
-
6
- ```text
7
- You move models. You move IDEs. Your memory stays.
8
- ```
9
-
10
- ## Install (one command)
11
-
12
- ```bash
13
- npx memoryai-claude install
14
- ```
15
-
16
- That's it. After it finishes, restart Claude Code once and just work. Past
17
- decisions, preferences, and recent project context come back at the start of
18
- each prompt; important moments save automatically when each turn ends.
19
-
20
- ## Get a key
21
-
22
- Pick whichever is easier:
23
-
24
- - Skip the prompt and a free key is created for you on install
25
- - Or grab one upfront at https://memoryai.dev/connect and paste it
26
-
27
- ## Health check
28
-
29
- ```bash
30
- npx memoryai-claude doctor
31
- ```
32
-
33
- Confirms the three hooks are wired and that each endpoint actually responds.
34
-
35
- ## Status
36
-
37
- ```bash
38
- npx memoryai-claude status
39
- ```
40
-
41
- Tells you how many hooks are wired in user-scope and project-scope settings.
42
-
43
- ## Uninstall
44
-
45
- ```bash
46
- npx memoryai-claude uninstall
47
- ```
48
-
49
- Removes the hooks from `settings.json`. Foreign hooks stay untouched. Memory
50
- on the server is preserved unless you delete it from the dashboard.
51
-
52
- ## What gets touched
53
-
54
- - `~/.claude/settings.json` (or `./.claude/settings.json` with `--project`)
55
- - `~/.claude/CLAUDE.md` (or `./CLAUDE.md` with `--project`) — a short note
56
- is appended so the agent knows memory is wired.
57
-
58
- The original file is backed up before each write.
59
-
60
- ## Privacy
61
-
62
- - API keys live in your local `settings.json`. They never leave your machine
63
- except to talk to the endpoint you configured.
64
- - No telemetry from this CLI itself.
65
- - Add `MEMORYAI_NONINTERACTIVE=1` to skip prompts during scripted installs.
66
-
67
- ## License
68
-
69
- MIT.
1
+ # MemoryAI for Claude Code
2
+
3
+ Your AI keeps forgetting you. MemoryAI gives Claude Code a real long-term
4
+ memory: one that follows you across every model.
5
+
6
+ ```text
7
+ You move models. You move IDEs. Your memory stays.
8
+ ```
9
+
10
+ ## Install (one command)
11
+
12
+ ```bash
13
+ npx memoryai-claude install
14
+ ```
15
+
16
+ That's it. After it finishes, restart Claude Code once and just work. Past
17
+ decisions, preferences, and recent project context come back at the start of
18
+ each prompt; important moments save automatically when each turn ends.
19
+
20
+ ## Get a key
21
+
22
+ Pick whichever is easier:
23
+
24
+ - Skip the prompt and a free key is created for you on install
25
+ - Or grab one upfront at https://memoryai.dev/connect and paste it
26
+
27
+ ## Health check
28
+
29
+ ```bash
30
+ npx memoryai-claude doctor
31
+ ```
32
+
33
+ Confirms the three hooks are wired and that each endpoint actually responds.
34
+
35
+ ## Status
36
+
37
+ ```bash
38
+ npx memoryai-claude status
39
+ ```
40
+
41
+ Tells you how many hooks are wired in user-scope and project-scope settings.
42
+
43
+ ## Uninstall
44
+
45
+ ```bash
46
+ npx memoryai-claude uninstall
47
+ ```
48
+
49
+ Removes the hooks from `settings.json`. Foreign hooks stay untouched. Memory
50
+ on the server is preserved unless you delete it from the dashboard.
51
+
52
+ ## What gets touched
53
+
54
+ - `~/.claude/settings.json` (or `./.claude/settings.json` with `--project`)
55
+ - `~/.claude/CLAUDE.md` (or `./CLAUDE.md` with `--project`) — a short note
56
+ is appended so the agent knows memory is wired.
57
+
58
+ The original file is backed up before each write.
59
+
60
+ ## Privacy
61
+
62
+ - API keys live in your local `settings.json`. They never leave your machine
63
+ except to talk to the endpoint you configured.
64
+ - No telemetry from this CLI itself.
65
+ - Add `MEMORYAI_NONINTERACTIVE=1` to skip prompts during scripted installs.
66
+
67
+ ## License
68
+
69
+ MIT.
package/dist/cli.js CHANGED
@@ -1,15 +1,255 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var a=require("node:fs"),m=require("node:path"),X=require("node:os");var F=require("node:readline/promises"),S=require("node:process");async function $(e,o){let t=(0,F.createInterface)({input:S.stdin,output:S.stdout});try{let n=o?` [${o}]`:"";return(await t.question(`${e}${n}: `)).trim()||o||""}finally{t.close()}}async function R(e,o=!0){return(await $(`${e} (${o?"Y/n":"y/N"})`,o?"y":"n")).toLowerCase().startsWith("y")}function k(){return process.env.MEMORYAI_NONINTERACTIVE==="1"||process.env.CI==="true"||!process.stdin.isTTY}var l=require("node:fs"),g=require("node:path"),x=require("node:os");function v(e){return e==="project"?(0,g.join)(process.cwd(),".claude","settings.json"):(0,g.join)((0,x.homedir)(),".claude","settings.json")}function N(e){return e==="project"?(0,g.join)(process.cwd(),"CLAUDE.md"):(0,g.join)((0,x.homedir)(),".claude","CLAUDE.md")}function w(e){if(!(0,l.existsSync)(e))return{};try{return JSON.parse((0,l.readFileSync)(e,"utf-8"))||{}}catch{throw new Error(`${e} is not valid JSON. Fix it manually before re-running.`)}}function I(e,o){(0,l.mkdirSync)((0,g.dirname)(e),{recursive:!0}),(0,l.writeFileSync)(e,JSON.stringify(o,null,2)+`
3
- `,"utf-8")}function M(e){if(!(0,l.existsSync)(e))return null;let o=new Date().toISOString().replace(/[:.]/g,"-"),t=`${e}.bak-${o}`;return(0,l.copyFileSync)(e,t),t}var oe="/v1/hooks/claude/",te="memoryai/session-start-runner";function H(e,o,t){return{type:"http",url:e,timeout:t,headers:{Authorization:`Bearer ${o}`},allowedEnvVars:[]}}function K(e,o,t){return{type:"command",command:e,args:[o],timeout:t}}function D(e){if(!e)return!1;if(typeof e.url=="string"&&e.url.includes(oe))return!0;if(typeof e.command=="string"){let o=Array.isArray(e.args)?e.args.join(" "):"",t=`${e.command} ${o}`;if(t.includes(te)||t.includes("session-start-runner"))return!0}return!1}function J(e){return(e&&e.hooks||[]).some(D)}function A(e,o,t){return e.hooks=e.hooks||{},e.hooks[o]=e.hooks[o]||[],e.hooks[o].some(J)?!1:(e.hooks[o].push({hooks:[t]}),!0)}function L(e){if(!e.hooks)return 0;let o=0;for(let t of Object.keys(e.hooks)){let n=(e.hooks[t]||[]).length;e.hooks[t]=(e.hooks[t]||[]).filter(r=>!J(r)),o+=n-e.hooks[t].length,e.hooks[t].length===0&&delete e.hooks[t]}return Object.keys(e.hooks).length===0&&delete e.hooks,o}function O(e){let o={SessionStart:{present:!1},UserPromptSubmit:{present:!1},Stop:{present:!1}};for(let t of Object.keys(o)){let n=e?.hooks?.[t]||[];for(let r of n){let s=(r.hooks||[]).find(D);if(s){o[t]={present:!0,url:s.url,command:s.command?`${s.command} ${(s.args||[]).join(" ")}`.trim():void 0,timeout:s.timeout};break}}}return o}var p=require("node:fs"),P="<!-- memoryai:auto-note -->",Y=`
4
- ${P}
2
+ "use strict";
3
+
4
+ // src/cli.ts
5
+ var import_node_fs3 = require("node:fs");
6
+ var import_node_path2 = require("node:path");
7
+ var import_node_os2 = require("node:os");
8
+
9
+ // src/prompts.ts
10
+ var import_promises = require("node:readline/promises");
11
+ var import_node_process = require("node:process");
12
+ async function ask(question, fallback) {
13
+ const rl = (0, import_promises.createInterface)({ input: import_node_process.stdin, output: import_node_process.stdout });
14
+ try {
15
+ const suffix = fallback ? ` [${fallback}]` : "";
16
+ const answer = (await rl.question(`${question}${suffix}: `)).trim();
17
+ return answer || fallback || "";
18
+ } finally {
19
+ rl.close();
20
+ }
21
+ }
22
+ async function askYesNo(question, defYes = true) {
23
+ const ans = (await ask(`${question} (${defYes ? "Y/n" : "y/N"})`, defYes ? "y" : "n")).toLowerCase();
24
+ return ans.startsWith("y");
25
+ }
26
+ function isNonInteractive() {
27
+ return process.env.MEMORYAI_NONINTERACTIVE === "1" || process.env.CI === "true" || !process.stdin.isTTY;
28
+ }
29
+
30
+ // src/settings.ts
31
+ var import_node_fs = require("node:fs");
32
+ var import_node_path = require("node:path");
33
+ var import_node_os = require("node:os");
34
+ function settingsPath(scope) {
35
+ return scope === "project" ? (0, import_node_path.join)(process.cwd(), ".claude", "settings.json") : (0, import_node_path.join)((0, import_node_os.homedir)(), ".claude", "settings.json");
36
+ }
37
+ function claudeMdPath(scope) {
38
+ return scope === "project" ? (0, import_node_path.join)(process.cwd(), "CLAUDE.md") : (0, import_node_path.join)((0, import_node_os.homedir)(), ".claude", "CLAUDE.md");
39
+ }
40
+ function readJsonSafe(path) {
41
+ if (!(0, import_node_fs.existsSync)(path)) return {};
42
+ try {
43
+ return JSON.parse((0, import_node_fs.readFileSync)(path, "utf-8")) || {};
44
+ } catch {
45
+ throw new Error(`${path} is not valid JSON. Fix it manually before re-running.`);
46
+ }
47
+ }
48
+ function writeJson(path, data) {
49
+ (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
50
+ (0, import_node_fs.writeFileSync)(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
51
+ }
52
+ function backup(path) {
53
+ if (!(0, import_node_fs.existsSync)(path)) return null;
54
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
55
+ const dest = `${path}.bak-${stamp}`;
56
+ (0, import_node_fs.copyFileSync)(path, dest);
57
+ return dest;
58
+ }
59
+
60
+ // src/hooks.ts
61
+ var MEMORYAI_PATH_MARKER = "/v1/hooks/claude/";
62
+ var MEMORYAI_COMMAND_MARKER = "memoryai/session-start-runner";
63
+ function httpHook(endpoint, apiKey, timeout) {
64
+ return {
65
+ type: "http",
66
+ url: endpoint,
67
+ timeout,
68
+ headers: { Authorization: `Bearer ${apiKey}` },
69
+ allowedEnvVars: []
70
+ };
71
+ }
72
+ function commandHook(nodeBinary, runnerPath, timeout) {
73
+ return {
74
+ type: "command",
75
+ command: nodeBinary,
76
+ args: [runnerPath],
77
+ timeout
78
+ };
79
+ }
80
+ function isMemoryAiHandler(h) {
81
+ if (!h) return false;
82
+ if (typeof h.url === "string" && h.url.includes(MEMORYAI_PATH_MARKER)) return true;
83
+ if (typeof h.command === "string") {
84
+ const args = Array.isArray(h.args) ? h.args.join(" ") : "";
85
+ const blob = `${h.command} ${args}`;
86
+ if (blob.includes(MEMORYAI_COMMAND_MARKER) || blob.includes("session-start-runner")) return true;
87
+ }
88
+ return false;
89
+ }
90
+ function groupHasMemoryAi(group) {
91
+ const handlers = group && group.hooks || [];
92
+ return handlers.some(isMemoryAiHandler);
93
+ }
94
+ function ensureHook(settings, event, handler) {
95
+ settings.hooks = settings.hooks || {};
96
+ settings.hooks[event] = settings.hooks[event] || [];
97
+ if (settings.hooks[event].some(groupHasMemoryAi)) return false;
98
+ settings.hooks[event].push({ hooks: [handler] });
99
+ return true;
100
+ }
101
+ function removeMemoryAiHooks(settings) {
102
+ if (!settings.hooks) return 0;
103
+ let removed = 0;
104
+ for (const event of Object.keys(settings.hooks)) {
105
+ const before = (settings.hooks[event] || []).length;
106
+ settings.hooks[event] = (settings.hooks[event] || []).filter((g) => !groupHasMemoryAi(g));
107
+ removed += before - settings.hooks[event].length;
108
+ if (settings.hooks[event].length === 0) delete settings.hooks[event];
109
+ }
110
+ if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
111
+ return removed;
112
+ }
113
+ function describeHooks(settings) {
114
+ const out = {
115
+ SessionStart: { present: false },
116
+ UserPromptSubmit: { present: false },
117
+ Stop: { present: false }
118
+ };
119
+ for (const event of Object.keys(out)) {
120
+ const groups = settings?.hooks?.[event] || [];
121
+ for (const g of groups) {
122
+ const h = (g.hooks || []).find(isMemoryAiHandler);
123
+ if (h) {
124
+ out[event] = {
125
+ present: true,
126
+ url: h.url,
127
+ command: h.command ? `${h.command} ${(h.args || []).join(" ")}`.trim() : void 0,
128
+ timeout: h.timeout
129
+ };
130
+ break;
131
+ }
132
+ }
133
+ }
134
+ return out;
135
+ }
136
+
137
+ // src/notes.ts
138
+ var import_node_fs2 = require("node:fs");
139
+ var MARKER = "<!-- memoryai:auto-note -->";
140
+ var NOTE = `
141
+ ${MARKER}
5
142
  ## MemoryAI
6
143
 
7
144
  Memory works automatically here. Past decisions, preferences, and recent project
8
145
  context are recalled before each prompt and saved when each turn ends. Nothing
9
146
  to call by hand \u2014 just work normally.
10
- `;function z(e){let o=(0,p.existsSync)(e)?(0,p.readFileSync)(e,"utf-8"):"";if(o.includes(P))return"skipped";let t=o?`${o.replace(/\s*$/,"")}
11
- ${Y}`:Y;return(0,p.writeFileSync)(e,t,"utf-8"),o?"appended":"created"}function B(e){if(!(0,p.existsSync)(e))return!1;let o=(0,p.readFileSync)(e,"utf-8");if(!o.includes(P))return!1;let t=o.replace(new RegExp(`\\n*${P}[\\s\\S]*$`,"m"),"").replace(/\s*$/,`
12
- `);return(0,p.writeFileSync)(e,t,"utf-8"),!0}async function G(e,o){let t=e.replace(/\/+$/,"");try{return(await fetch(`${t}/v1/stats`,{method:"GET",headers:{Authorization:`Bearer ${o}`}})).ok}catch{return!1}}async function V(e,o){let t=e.replace(/\/+$/,"");try{let n=await fetch(`${t}/v1/admin/provision`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:o||"claude-code",tos_accepted:!0})});if(!n.ok)return null;let r=await n.json();return r&&typeof r.api_key=="string"?{api_key:r.api_key,plan:r.plan}:null}catch{return null}}async function q(e,o){let t=e.replace(/\/+$/,""),n={},r=[{event:"SessionStart",path:"/v1/ide/guard/bootstrap",body:{task:"",limit:1}},{event:"UserPromptSubmit",path:"/v1/hooks/claude/user-prompt",body:{prompt:"health check from memoryai-claude doctor"}},{event:"Stop",path:"/v1/hooks/claude/stop",body:{last_assistant_message:""}}];for(let{event:s,path:c,body:f}of r)try{let u=await fetch(`${t}${c}`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify(f)});n[s]=u.ok}catch{n[s]=!1}return n}var Z="0.1.1",W="https://memoryai.dev",j=(0,m.join)((0,X.homedir)(),".memoryai"),E=(0,m.join)(j,"claude.json"),_=(0,m.join)(j,"session-start-runner.js");function ne(e){let o={command:e[0]||"help"};for(let t=1;t<e.length;t++){let n=e[t];n==="--user"?o.scope="user":n==="--project"?o.scope="project":n==="--endpoint"?o.endpoint=e[++t]:n==="--key"?o.key=e[++t]:(n==="--yes"||n==="-y")&&(o.yes=!0)}return o}function h(){console.log(`MemoryAI for Claude Code v${Z}`),console.log("One brain. Every AI you use. Forever."),console.log("")}function Q(){h(),console.log(`Usage:
147
+ `;
148
+ function ensureNote(path) {
149
+ const existing = (0, import_node_fs2.existsSync)(path) ? (0, import_node_fs2.readFileSync)(path, "utf-8") : "";
150
+ if (existing.includes(MARKER)) return "skipped";
151
+ const next = existing ? `${existing.replace(/\s*$/, "")}
152
+ ${NOTE}` : NOTE;
153
+ (0, import_node_fs2.writeFileSync)(path, next, "utf-8");
154
+ return existing ? "appended" : "created";
155
+ }
156
+ function removeNote(path) {
157
+ if (!(0, import_node_fs2.existsSync)(path)) return false;
158
+ const existing = (0, import_node_fs2.readFileSync)(path, "utf-8");
159
+ if (!existing.includes(MARKER)) return false;
160
+ const cleaned = existing.replace(new RegExp(`\\n*${MARKER}[\\s\\S]*$`, "m"), "").replace(/\s*$/, "\n");
161
+ (0, import_node_fs2.writeFileSync)(path, cleaned, "utf-8");
162
+ return true;
163
+ }
164
+
165
+ // src/api.ts
166
+ async function healthcheck(endpoint, apiKey) {
167
+ const base = endpoint.replace(/\/+$/, "");
168
+ try {
169
+ const resp = await fetch(`${base}/v1/stats`, {
170
+ method: "GET",
171
+ headers: { Authorization: `Bearer ${apiKey}` }
172
+ });
173
+ return resp.ok;
174
+ } catch {
175
+ return false;
176
+ }
177
+ }
178
+ async function provisionKey(endpoint, name) {
179
+ const base = endpoint.replace(/\/+$/, "");
180
+ try {
181
+ const resp = await fetch(`${base}/v1/admin/provision`, {
182
+ method: "POST",
183
+ headers: { "Content-Type": "application/json" },
184
+ body: JSON.stringify({ name: name || "claude-code", tos_accepted: true })
185
+ });
186
+ if (!resp.ok) {
187
+ return null;
188
+ }
189
+ const data = await resp.json();
190
+ if (data && typeof data.api_key === "string") {
191
+ return { api_key: data.api_key, plan: data.plan };
192
+ }
193
+ return null;
194
+ } catch {
195
+ return null;
196
+ }
197
+ }
198
+ async function pingHooks(endpoint, apiKey) {
199
+ const base = endpoint.replace(/\/+$/, "");
200
+ const results = {};
201
+ const events = [
202
+ // SessionStart hook now uses /v1/ide/guard/bootstrap (called by the
203
+ // local Node runner), not /v1/hooks/claude/session-start. The runner
204
+ // wakes Claude Code's session by printing the bootstrap context block.
205
+ { event: "SessionStart", path: "/v1/ide/guard/bootstrap", body: { task: "", limit: 1 } },
206
+ { event: "UserPromptSubmit", path: "/v1/hooks/claude/user-prompt", body: { prompt: "health check from memoryai-claude doctor" } },
207
+ { event: "Stop", path: "/v1/hooks/claude/stop", body: { last_assistant_message: "" } }
208
+ ];
209
+ for (const { event, path, body } of events) {
210
+ try {
211
+ const resp = await fetch(`${base}${path}`, {
212
+ method: "POST",
213
+ headers: {
214
+ "Content-Type": "application/json",
215
+ Authorization: `Bearer ${apiKey}`
216
+ },
217
+ body: JSON.stringify(body)
218
+ });
219
+ results[event] = resp.ok;
220
+ } catch {
221
+ results[event] = false;
222
+ }
223
+ }
224
+ return results;
225
+ }
226
+
227
+ // src/cli.ts
228
+ var VERSION = "0.1.1";
229
+ var DEFAULT_ENDPOINT = "https://memoryai.dev";
230
+ var RUNTIME_DIR = (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".memoryai");
231
+ var RUNTIME_CONFIG = (0, import_node_path2.join)(RUNTIME_DIR, "claude.json");
232
+ var RUNTIME_RUNNER = (0, import_node_path2.join)(RUNTIME_DIR, "session-start-runner.js");
233
+ function parseArgs(argv) {
234
+ const out = { command: argv[0] || "help" };
235
+ for (let i = 1; i < argv.length; i++) {
236
+ const a = argv[i];
237
+ if (a === "--user") out.scope = "user";
238
+ else if (a === "--project") out.scope = "project";
239
+ else if (a === "--endpoint") out.endpoint = argv[++i];
240
+ else if (a === "--key") out.key = argv[++i];
241
+ else if (a === "--yes" || a === "-y") out.yes = true;
242
+ }
243
+ return out;
244
+ }
245
+ function printHeader() {
246
+ console.log(`MemoryAI for Claude Code v${VERSION}`);
247
+ console.log(`One brain. Every AI you use. Forever.`);
248
+ console.log("");
249
+ }
250
+ function printHelp() {
251
+ printHeader();
252
+ console.log(`Usage:
13
253
  memoryai-claude install [--user|--project] [--endpoint URL] [--key KEY] [--yes]
14
254
  memoryai-claude doctor
15
255
  memoryai-claude status
@@ -23,6 +263,287 @@ nothing to call by hand.
23
263
 
24
264
  Get a free key at https://memoryai.dev (or leave it blank during install and a
25
265
  key will be created for you).
26
- `)}async function ee(e){return e.scope?e.scope:k()?"user":(await $("Apply to (u)ser globally or this (p)roject?","u")).toLowerCase().startsWith("p")?"project":"user"}async function re(e){if(e.endpoint)return e.endpoint;let o=process.env.HM_ENDPOINT||process.env.MEMORYAI_ENDPOINT;return o||(k()?W:await $("Endpoint",W))}async function se(e,o){if(e.key)return e.key;let t=process.env.HM_API_KEY||process.env.MEMORYAI_API_KEY;if(t)return t;let n="";if(k()||(n=(await $("API key (leave blank to auto-create a free one")).trim()),!n){process.stdout.write(" ... creating a free key for you ...");let r=await V(o,"claude-code");if(r)return console.log(` ok (plan=${r.plan||"free"})`),r.api_key;throw console.log(" failed"),new Error("Could not create a key automatically. Get one from https://memoryai.dev/connect and rerun with --key.")}return n}function ie(e){try{let o=new URL(e);return`${o.origin}${o.pathname.replace("/v1/hooks/claude/","/v1/hooks/claude/")}`}catch{return e}}async function ae(e){h();let o=await ee(e),t=await re(e),n=await se(e,t);if(process.stdout.write(" ... verifying key ..."),await G(t,n))console.log(" ok");else if(console.log(" failed"),!e.yes&&!k()&&!await R("Server did not accept the key (or is offline). Continue anyway?",!1))throw new Error("Aborted by user.");let s=v(o),c=N(o),f=w(s),u=M(s);u&&console.log(` back ${u}`),(0,a.mkdirSync)(j,{recursive:!0}),(0,a.writeFileSync)(E,JSON.stringify({endpoint:t,apiKey:n},null,2)+`
27
- `,"utf-8");try{require("node:fs").chmodSync(E,384)}catch{}let y=ce();if(!y)throw new Error("session-start-runner.js not found alongside the CLI. If running from source, run `npm run build` first.");(0,a.copyFileSync)(y,_),console.log(` setup ${_}`);let i=process.execPath,d=t.replace(/\/+$/,""),b={SessionStart:A(f,"SessionStart",K(i,_,12)),UserPromptSubmit:A(f,"UserPromptSubmit",H(`${d}/v1/hooks/claude/user-prompt`,n,10)),Stop:A(f,"Stop",H(`${d}/v1/hooks/claude/stop`,n,15))};for(let[T,U]of Object.entries(b))console.log(` ${U?"add ":"skip "} hook ${T}${U?"":" (already present)"}`);I(s,f),console.log(` write ${s}`);let C=z(c);console.log(C==="created"?` create ${c}`:C==="appended"?` append ${c}`:` skip ${c} (note already present)`),console.log(""),console.log("Installed. Restart Claude Code once, then just work."),console.log(" - Past context returns at the start of each prompt."),console.log(" - Important moments save when each turn ends."),console.log(" - Run `memoryai-claude doctor` any time to verify health.")}function ce(){let e=[(0,m.join)((0,m.dirname)(process.argv[1]||""),"session-start-runner.js"),(0,m.join)(__dirname,"session-start-runner.js")];for(let o of e)if(o&&(0,a.existsSync)(o))return o;return null}async function le(){h(),console.log("Diagnostics:");let e=v("user"),o=v("project"),t=!1;for(let[n,r]of[["user",e],["project",o]]){if(!(0,a.existsSync)(r)){console.log(` -- ${n}: ${r} (not present)`);continue}let s=w(r),c=O(s);if(Object.values(c).filter(i=>i.present).length===0){console.log(` -- ${n}: ${r} (no MemoryAI hooks)`);continue}t=!0,console.log(` ok ${n}: ${r}`);for(let[i,d]of Object.entries(c)){let b=d.url?` ${ie(d.url)}`:d.command?` command: ${d.command}`:"";console.log(` ${d.present?"present":"MISSING"} ${i}${b}`)}let u="",y="";if((0,a.existsSync)(E))try{let i=JSON.parse((0,a.readFileSync)(E,"utf-8"));i&&typeof i.endpoint=="string"&&(u=i.endpoint),i&&typeof i.apiKey=="string"&&(y=i.apiKey)}catch{}if(!u||!y){let i=ue(s);i?.url&&(u=new URL(i.url).origin,y=(i.headers?.Authorization||"").replace(/^Bearer\s+/i,""))}if(u&&y){process.stdout.write(" ... ping endpoints ...");let i=await q(u,y);console.log("");for(let[d,b]of Object.entries(i))console.log(` ${b?"ok":"FAIL"} ${d}`)}}t||(console.log(""),console.log("No MemoryAI hooks found in either user or project settings."),console.log("Run `memoryai-claude install` to wire them up."))}function ue(e){let o=e?.hooks||{};for(let t of Object.keys(o))for(let n of o[t]||[])for(let r of n.hooks||[])if(typeof r?.url=="string"&&r.url.includes("/v1/hooks/claude/"))return r;return null}async function de(){let e=(0,m.join)(j,"runner.log");if(!(0,a.existsSync)(e)){h(),console.log(`No SessionStart hook activity yet (${e} does not exist).`),console.log("This means Claude Code has not fired SessionStart since install."),console.log("Open a fresh terminal and run `claude` to trigger it.");return}let n=(0,a.readFileSync)(e,"utf-8").trim().split(/\r?\n/).slice(-20).join(`
28
- `);h(),console.log(`SessionStart runner log (${e}):`),console.log(""),console.log(n)}async function pe(){h();for(let e of["user","project"]){let o=v(e);if(!(0,a.existsSync)(o)){console.log(` ${e}: not present`);continue}let t=w(o),n=O(t),r=Object.values(n).filter(s=>s.present).length;console.log(` ${e}: ${r}/3 hooks wired (${o})`)}}async function me(e){h();let o=await ee(e),t=v(o),n=N(o);if(!(0,a.existsSync)(t)){console.log(` Nothing to do \u2014 ${t} does not exist.`);return}if(!e.yes&&!k()&&!await R(`Remove MemoryAI hooks from ${t}?`,!0))return;let r=w(t),s=M(t);s&&console.log(` back ${s}`);let c=L(r);I(t,r),console.log(` removed ${c} hook${c===1?"":"s"} from ${t}`),B(n)&&console.log(` cleaned ${n}`),console.log(""),console.log("Uninstalled. Restart Claude Code once. Memory still lives on the server until you delete it.")}async function fe(){let e=ne(process.argv.slice(2));try{switch(e.command){case"install":await ae(e);break;case"doctor":await le();break;case"status":await pe();break;case"logs":await de();break;case"uninstall":await me(e);break;case"-v":case"--version":console.log(Z);break;case"help":case"--help":case"-h":case void 0:case"":Q();break;default:console.error(`Unknown command: ${e.command}`),Q(),process.exit(2)}}catch(o){console.error(""),console.error(`Error: ${o.message}`),process.exit(1)}}fe();
266
+ `);
267
+ }
268
+ async function resolveScope(args) {
269
+ if (args.scope) return args.scope;
270
+ if (isNonInteractive()) return "user";
271
+ const ans = (await ask("Apply to (u)ser globally or this (p)roject?", "u")).toLowerCase();
272
+ return ans.startsWith("p") ? "project" : "user";
273
+ }
274
+ async function resolveEndpoint(args) {
275
+ if (args.endpoint) return args.endpoint;
276
+ const envEndpoint = process.env.HM_ENDPOINT || process.env.MEMORYAI_ENDPOINT;
277
+ if (envEndpoint) return envEndpoint;
278
+ if (isNonInteractive()) return DEFAULT_ENDPOINT;
279
+ return await ask("Endpoint", DEFAULT_ENDPOINT);
280
+ }
281
+ async function resolveKey(args, endpoint) {
282
+ if (args.key) return args.key;
283
+ const envKey = process.env.HM_API_KEY || process.env.MEMORYAI_API_KEY;
284
+ if (envKey) return envKey;
285
+ let key = "";
286
+ if (!isNonInteractive()) {
287
+ key = (await ask("API key (leave blank to auto-create a free one")).trim();
288
+ }
289
+ if (!key) {
290
+ process.stdout.write(" ... creating a free key for you ...");
291
+ const provisioned = await provisionKey(endpoint, "claude-code");
292
+ if (provisioned) {
293
+ console.log(` ok (plan=${provisioned.plan || "free"})`);
294
+ return provisioned.api_key;
295
+ }
296
+ console.log(" failed");
297
+ throw new Error(
298
+ "Could not create a key automatically. Get one from https://memoryai.dev/connect and rerun with --key."
299
+ );
300
+ }
301
+ return key;
302
+ }
303
+ function relativizeUrl(url) {
304
+ try {
305
+ const u = new URL(url);
306
+ return `${u.origin}${u.pathname.replace("/v1/hooks/claude/", "/v1/hooks/claude/")}`;
307
+ } catch {
308
+ return url;
309
+ }
310
+ }
311
+ async function cmdInstall(args) {
312
+ printHeader();
313
+ const scope = await resolveScope(args);
314
+ const endpoint = await resolveEndpoint(args);
315
+ const apiKey = await resolveKey(args, endpoint);
316
+ process.stdout.write(" ... verifying key ...");
317
+ const ok = await healthcheck(endpoint, apiKey);
318
+ if (!ok) {
319
+ console.log(" failed");
320
+ if (!args.yes && !isNonInteractive()) {
321
+ const proceed = await askYesNo("Server did not accept the key (or is offline). Continue anyway?", false);
322
+ if (!proceed) throw new Error("Aborted by user.");
323
+ }
324
+ } else {
325
+ console.log(" ok");
326
+ }
327
+ const sPath = settingsPath(scope);
328
+ const cMdPath = claudeMdPath(scope);
329
+ let settings = readJsonSafe(sPath);
330
+ const backupPath = backup(sPath);
331
+ if (backupPath) console.log(` back ${backupPath}`);
332
+ (0, import_node_fs3.mkdirSync)(RUNTIME_DIR, { recursive: true });
333
+ (0, import_node_fs3.writeFileSync)(
334
+ RUNTIME_CONFIG,
335
+ JSON.stringify({ endpoint, apiKey }, null, 2) + "\n",
336
+ "utf-8"
337
+ );
338
+ try {
339
+ require("node:fs").chmodSync(RUNTIME_CONFIG, 384);
340
+ } catch {
341
+ }
342
+ const runnerSrc = locateRunner();
343
+ if (!runnerSrc) {
344
+ throw new Error(
345
+ "session-start-runner.js not found alongside the CLI. If running from source, run `npm run build` first."
346
+ );
347
+ }
348
+ (0, import_node_fs3.copyFileSync)(runnerSrc, RUNTIME_RUNNER);
349
+ console.log(` setup ${RUNTIME_RUNNER}`);
350
+ const nodeBin = process.execPath;
351
+ const base = endpoint.replace(/\/+$/, "");
352
+ const added = {
353
+ SessionStart: ensureHook(settings, "SessionStart", commandHook(nodeBin, RUNTIME_RUNNER, 12)),
354
+ UserPromptSubmit: ensureHook(settings, "UserPromptSubmit", httpHook(`${base}/v1/hooks/claude/user-prompt`, apiKey, 10)),
355
+ Stop: ensureHook(settings, "Stop", httpHook(`${base}/v1/hooks/claude/stop`, apiKey, 15))
356
+ };
357
+ for (const [event, didAdd] of Object.entries(added)) {
358
+ console.log(` ${didAdd ? "add " : "skip "} hook ${event}${didAdd ? "" : " (already present)"}`);
359
+ }
360
+ writeJson(sPath, settings);
361
+ console.log(` write ${sPath}`);
362
+ const noteResult = ensureNote(cMdPath);
363
+ if (noteResult === "created") console.log(` create ${cMdPath}`);
364
+ else if (noteResult === "appended") console.log(` append ${cMdPath}`);
365
+ else console.log(` skip ${cMdPath} (note already present)`);
366
+ console.log("");
367
+ console.log("Installed. Restart Claude Code once, then just work.");
368
+ console.log(" - Past context returns at the start of each prompt.");
369
+ console.log(" - Important moments save when each turn ends.");
370
+ console.log(" - Run `memoryai-claude doctor` any time to verify health.");
371
+ }
372
+ function locateRunner() {
373
+ const candidates = [
374
+ (0, import_node_path2.join)((0, import_node_path2.dirname)(process.argv[1] || ""), "session-start-runner.js"),
375
+ (0, import_node_path2.join)(__dirname, "session-start-runner.js")
376
+ ];
377
+ for (const c of candidates) {
378
+ if (c && (0, import_node_fs3.existsSync)(c)) return c;
379
+ }
380
+ return null;
381
+ }
382
+ async function cmdDoctor() {
383
+ printHeader();
384
+ console.log("Diagnostics:");
385
+ const userPath = settingsPath("user");
386
+ const projectPath = settingsPath("project");
387
+ let foundAny = false;
388
+ for (const [scope, path] of [
389
+ ["user", userPath],
390
+ ["project", projectPath]
391
+ ]) {
392
+ if (!(0, import_node_fs3.existsSync)(path)) {
393
+ console.log(` -- ${scope}: ${path} (not present)`);
394
+ continue;
395
+ }
396
+ const settings = readJsonSafe(path);
397
+ const desc = describeHooks(settings);
398
+ const present = Object.values(desc).filter((d) => d.present).length;
399
+ if (present === 0) {
400
+ console.log(` -- ${scope}: ${path} (no MemoryAI hooks)`);
401
+ continue;
402
+ }
403
+ foundAny = true;
404
+ console.log(` ok ${scope}: ${path}`);
405
+ for (const [event, info] of Object.entries(desc)) {
406
+ const detail = info.url ? ` ${relativizeUrl(info.url)}` : info.command ? ` command: ${info.command}` : "";
407
+ console.log(` ${info.present ? "present" : "MISSING"} ${event}${detail}`);
408
+ }
409
+ let endpoint = "";
410
+ let apiKey = "";
411
+ if ((0, import_node_fs3.existsSync)(RUNTIME_CONFIG)) {
412
+ try {
413
+ const cfg = JSON.parse((0, import_node_fs3.readFileSync)(RUNTIME_CONFIG, "utf-8"));
414
+ if (cfg && typeof cfg.endpoint === "string") endpoint = cfg.endpoint;
415
+ if (cfg && typeof cfg.apiKey === "string") apiKey = cfg.apiKey;
416
+ } catch {
417
+ }
418
+ }
419
+ if (!endpoint || !apiKey) {
420
+ const handler = findFirstHttpHandler(settings);
421
+ if (handler?.url) {
422
+ endpoint = new URL(handler.url).origin;
423
+ apiKey = (handler.headers?.Authorization || "").replace(/^Bearer\s+/i, "");
424
+ }
425
+ }
426
+ if (endpoint && apiKey) {
427
+ process.stdout.write(` ... ping endpoints ...`);
428
+ const pings = await pingHooks(endpoint, apiKey);
429
+ console.log("");
430
+ for (const [event, ok] of Object.entries(pings)) {
431
+ console.log(` ${ok ? "ok" : "FAIL"} ${event}`);
432
+ }
433
+ }
434
+ }
435
+ if (!foundAny) {
436
+ console.log("");
437
+ console.log("No MemoryAI hooks found in either user or project settings.");
438
+ console.log("Run `memoryai-claude install` to wire them up.");
439
+ }
440
+ }
441
+ function findFirstHttpHandler(settings) {
442
+ const events = settings?.hooks || {};
443
+ for (const event of Object.keys(events)) {
444
+ for (const group of events[event] || []) {
445
+ for (const h of group.hooks || []) {
446
+ if (typeof h?.url === "string" && h.url.includes("/v1/hooks/claude/")) return h;
447
+ }
448
+ }
449
+ }
450
+ return null;
451
+ }
452
+ async function cmdLogs() {
453
+ const logPath = (0, import_node_path2.join)(RUNTIME_DIR, "runner.log");
454
+ if (!(0, import_node_fs3.existsSync)(logPath)) {
455
+ printHeader();
456
+ console.log(`No SessionStart hook activity yet (${logPath} does not exist).`);
457
+ console.log("This means Claude Code has not fired SessionStart since install.");
458
+ console.log("Open a fresh terminal and run `claude` to trigger it.");
459
+ return;
460
+ }
461
+ const text = (0, import_node_fs3.readFileSync)(logPath, "utf-8");
462
+ const lines = text.trim().split(/\r?\n/);
463
+ const tail = lines.slice(-20).join("\n");
464
+ printHeader();
465
+ console.log(`SessionStart runner log (${logPath}):`);
466
+ console.log("");
467
+ console.log(tail);
468
+ }
469
+ async function cmdStatus() {
470
+ printHeader();
471
+ for (const scope of ["user", "project"]) {
472
+ const path = settingsPath(scope);
473
+ if (!(0, import_node_fs3.existsSync)(path)) {
474
+ console.log(` ${scope}: not present`);
475
+ continue;
476
+ }
477
+ const settings = readJsonSafe(path);
478
+ const desc = describeHooks(settings);
479
+ const wired = Object.values(desc).filter((d) => d.present).length;
480
+ console.log(` ${scope}: ${wired}/3 hooks wired (${path})`);
481
+ }
482
+ }
483
+ async function cmdUninstall(args) {
484
+ printHeader();
485
+ const scope = await resolveScope(args);
486
+ const sPath = settingsPath(scope);
487
+ const cMdPath = claudeMdPath(scope);
488
+ if (!(0, import_node_fs3.existsSync)(sPath)) {
489
+ console.log(` Nothing to do \u2014 ${sPath} does not exist.`);
490
+ return;
491
+ }
492
+ if (!args.yes && !isNonInteractive()) {
493
+ const ok = await askYesNo(`Remove MemoryAI hooks from ${sPath}?`, true);
494
+ if (!ok) return;
495
+ }
496
+ const settings = readJsonSafe(sPath);
497
+ const backupPath = backup(sPath);
498
+ if (backupPath) console.log(` back ${backupPath}`);
499
+ const removed = removeMemoryAiHooks(settings);
500
+ writeJson(sPath, settings);
501
+ console.log(` removed ${removed} hook${removed === 1 ? "" : "s"} from ${sPath}`);
502
+ if (removeNote(cMdPath)) {
503
+ console.log(` cleaned ${cMdPath}`);
504
+ }
505
+ console.log("");
506
+ console.log("Uninstalled. Restart Claude Code once. Memory still lives on the server until you delete it.");
507
+ }
508
+ async function main() {
509
+ const args = parseArgs(process.argv.slice(2));
510
+ try {
511
+ switch (args.command) {
512
+ case "install":
513
+ await cmdInstall(args);
514
+ break;
515
+ case "doctor":
516
+ await cmdDoctor();
517
+ break;
518
+ case "status":
519
+ await cmdStatus();
520
+ break;
521
+ case "logs":
522
+ await cmdLogs();
523
+ break;
524
+ case "uninstall":
525
+ await cmdUninstall(args);
526
+ break;
527
+ case "-v":
528
+ case "--version":
529
+ console.log(VERSION);
530
+ break;
531
+ case "help":
532
+ case "--help":
533
+ case "-h":
534
+ case void 0:
535
+ case "":
536
+ printHelp();
537
+ break;
538
+ default:
539
+ console.error(`Unknown command: ${args.command}`);
540
+ printHelp();
541
+ process.exit(2);
542
+ }
543
+ } catch (e) {
544
+ console.error("");
545
+ console.error(`Error: ${e.message}`);
546
+ process.exit(1);
547
+ }
548
+ }
549
+ main();
@@ -1,3 +1,112 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var o=require("node:fs"),s=require("node:path"),p=require("node:os"),d=process.env.MEMORYAI_CLAUDE_CONFIG||(0,s.join)((0,p.homedir)(),".memoryai","claude.json"),f=(0,s.join)((0,p.homedir)(),".memoryai","runner.log");function n(t){try{(0,o.mkdirSync)((0,s.dirname)(f),{recursive:!0}),(0,o.appendFileSync)(f,`${new Date().toISOString()} ${t}
3
- `,"utf-8")}catch{}}function l(){if(!(0,o.existsSync)(d))return n("config-missing"),null;try{let t=JSON.parse((0,o.readFileSync)(d,"utf-8"));return t&&typeof t.endpoint=="string"&&typeof t.apiKey=="string"?t:(n("config-malformed"),null)}catch(t){return n(`config-parse-error: ${t.message}`),null}}async function y(){return new Promise(t=>{let e="";if(process.stdin.isTTY){t({});return}process.stdin.setEncoding("utf-8"),process.stdin.on("data",r=>{e+=r}),process.stdin.on("end",()=>{try{t(e?JSON.parse(e):{})}catch{t({})}}),setTimeout(()=>t(e?S(e):{}),800)})}function S(t){try{return JSON.parse(t)}catch{return{}}}function h(t){let r={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:t.slice(0,9500)}};process.stdout.write(JSON.stringify(r))}async function k(){let e=(await y())?.source||"unknown";n(`fired source=${e}`);let r=l();r||process.exit(0);try{let a=r.endpoint.replace(/\/+$/,""),u=new AbortController,g=setTimeout(()=>u.abort(),1e4),c=await fetch(`${a}/v1/ide/guard/bootstrap`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${r.apiKey}`},body:JSON.stringify({task:"",limit:14}),signal:u.signal});clearTimeout(g),c.ok||(n(`bootstrap-http-${c.status}`),process.exit(0));let i=await c.json(),m=i&&typeof i.context_block=="string"?i.context_block.trim():"";m||(n("bootstrap-empty"),process.exit(0)),h(m),n(`bootstrap-ok memories=${i.memories_restored??"?"} tokens=${i.tokens_used??"?"}`)}catch(a){n(`exception: ${a.message}`)}process.exit(0)}k();
2
+ "use strict";
3
+
4
+ // src/session-start-runner.ts
5
+ var import_node_fs = require("node:fs");
6
+ var import_node_path = require("node:path");
7
+ var import_node_os = require("node:os");
8
+ var CONFIG_PATH = process.env.MEMORYAI_CLAUDE_CONFIG || (0, import_node_path.join)((0, import_node_os.homedir)(), ".memoryai", "claude.json");
9
+ var LOG_PATH = (0, import_node_path.join)((0, import_node_os.homedir)(), ".memoryai", "runner.log");
10
+ function log(line) {
11
+ try {
12
+ (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(LOG_PATH), { recursive: true });
13
+ (0, import_node_fs.appendFileSync)(LOG_PATH, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
14
+ `, "utf-8");
15
+ } catch {
16
+ }
17
+ }
18
+ function loadConfig() {
19
+ if (!(0, import_node_fs.existsSync)(CONFIG_PATH)) {
20
+ log("config-missing");
21
+ return null;
22
+ }
23
+ try {
24
+ const raw = JSON.parse((0, import_node_fs.readFileSync)(CONFIG_PATH, "utf-8"));
25
+ if (raw && typeof raw.endpoint === "string" && typeof raw.apiKey === "string") {
26
+ return raw;
27
+ }
28
+ log("config-malformed");
29
+ return null;
30
+ } catch (e) {
31
+ log(`config-parse-error: ${e.message}`);
32
+ return null;
33
+ }
34
+ }
35
+ async function readStdinJson() {
36
+ return new Promise((resolve) => {
37
+ let data = "";
38
+ if (process.stdin.isTTY) {
39
+ resolve({});
40
+ return;
41
+ }
42
+ process.stdin.setEncoding("utf-8");
43
+ process.stdin.on("data", (chunk) => {
44
+ data += chunk;
45
+ });
46
+ process.stdin.on("end", () => {
47
+ try {
48
+ resolve(data ? JSON.parse(data) : {});
49
+ } catch {
50
+ resolve({});
51
+ }
52
+ });
53
+ setTimeout(() => resolve(data ? safeParse(data) : {}), 800);
54
+ });
55
+ }
56
+ function safeParse(s) {
57
+ try {
58
+ return JSON.parse(s);
59
+ } catch {
60
+ return {};
61
+ }
62
+ }
63
+ function emitContext(block) {
64
+ const trimmed = block.slice(0, 9500);
65
+ const out = {
66
+ hookSpecificOutput: {
67
+ hookEventName: "SessionStart",
68
+ additionalContext: trimmed
69
+ }
70
+ };
71
+ process.stdout.write(JSON.stringify(out));
72
+ }
73
+ async function main() {
74
+ const input = await readStdinJson();
75
+ const source = input?.source || "unknown";
76
+ log(`fired source=${source}`);
77
+ const cfg = loadConfig();
78
+ if (!cfg) {
79
+ process.exit(0);
80
+ }
81
+ try {
82
+ const base = cfg.endpoint.replace(/\/+$/, "");
83
+ const ctrl = new AbortController();
84
+ const timer = setTimeout(() => ctrl.abort(), 1e4);
85
+ const resp = await fetch(`${base}/v1/ide/guard/bootstrap`, {
86
+ method: "POST",
87
+ headers: {
88
+ "Content-Type": "application/json",
89
+ Authorization: `Bearer ${cfg.apiKey}`
90
+ },
91
+ body: JSON.stringify({ task: "", limit: 14 }),
92
+ signal: ctrl.signal
93
+ });
94
+ clearTimeout(timer);
95
+ if (!resp.ok) {
96
+ log(`bootstrap-http-${resp.status}`);
97
+ process.exit(0);
98
+ }
99
+ const data = await resp.json();
100
+ const block = data && typeof data.context_block === "string" ? data.context_block.trim() : "";
101
+ if (!block) {
102
+ log("bootstrap-empty");
103
+ process.exit(0);
104
+ }
105
+ emitContext(block);
106
+ log(`bootstrap-ok memories=${data.memories_restored ?? "?"} tokens=${data.tokens_used ?? "?"}`);
107
+ } catch (e) {
108
+ log(`exception: ${e.message}`);
109
+ }
110
+ process.exit(0);
111
+ }
112
+ main();
package/package.json CHANGED
@@ -1,44 +1,44 @@
1
- {
2
- "name": "memoryai-claude",
3
- "version": "0.1.1",
4
- "description": "Your AI keeps forgetting you. MemoryAI gives Claude Code a real long-term memory — one that follows you across every model.",
5
- "license": "MIT",
6
- "homepage": "https://memoryai.dev",
7
- "repository": {
8
- "type": "git",
9
- "url": "https://github.com/memoryai-dev/memoryai-claude"
10
- },
11
- "keywords": [
12
- "claude-code",
13
- "memory",
14
- "context",
15
- "llm",
16
- "ai"
17
- ],
18
- "engines": {
19
- "node": ">=18"
20
- },
21
- "bin": {
22
- "memoryai-claude": "dist/cli.js"
23
- },
24
- "files": [
25
- "dist/",
26
- "README.md",
27
- "LICENSE"
28
- ],
29
- "scripts": {
30
- "vscode:prepublish": "npm run package",
31
- "build:cli": "esbuild ./src/cli.ts --bundle --outfile=dist/cli.js --platform=node --target=node18 --format=cjs --banner:js=\"#!/usr/bin/env node\"",
32
- "build:runner": "esbuild ./src/session-start-runner.ts --bundle --outfile=dist/session-start-runner.js --platform=node --target=node18 --format=cjs --banner:js=\"#!/usr/bin/env node\"",
33
- "build": "npm run build:cli && npm run build:runner",
34
- "package:cli": "npm run build:cli -- --minify",
35
- "package:runner": "npm run build:runner -- --minify",
36
- "package": "npm run package:cli && npm run package:runner",
37
- "lint": "tsc --noEmit"
38
- },
39
- "devDependencies": {
40
- "@types/node": "^22.0.0",
41
- "esbuild": "^0.25.0",
42
- "typescript": "^5.5.0"
43
- }
44
- }
1
+ {
2
+ "name": "memoryai-claude",
3
+ "version": "0.1.2",
4
+ "description": "Your AI keeps forgetting you. MemoryAI gives Claude Code a real long-term memory — one that follows you across every model.",
5
+ "license": "MIT",
6
+ "homepage": "https://memoryai.dev",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/memoryai-dev/memoryai-claude"
10
+ },
11
+ "keywords": [
12
+ "claude-code",
13
+ "memory",
14
+ "context",
15
+ "llm",
16
+ "ai"
17
+ ],
18
+ "engines": {
19
+ "node": ">=18"
20
+ },
21
+ "bin": {
22
+ "memoryai-claude": "dist/cli.js"
23
+ },
24
+ "files": [
25
+ "dist/",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "scripts": {
30
+ "vscode:prepublish": "npm run package",
31
+ "build:cli": "esbuild ./src/cli.ts --bundle --outfile=dist/cli.js --platform=node --target=node18 --format=cjs --banner:js=\"#!/usr/bin/env node\"",
32
+ "build:runner": "esbuild ./src/session-start-runner.ts --bundle --outfile=dist/session-start-runner.js --platform=node --target=node18 --format=cjs --banner:js=\"#!/usr/bin/env node\"",
33
+ "build": "npm run build:cli && npm run build:runner",
34
+ "package:cli": "npm run build:cli -- --minify",
35
+ "package:runner": "npm run build:runner -- --minify",
36
+ "package": "npm run package:cli && npm run package:runner",
37
+ "lint": "tsc --noEmit"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^22.0.0",
41
+ "esbuild": "^0.25.12",
42
+ "typescript": "^5.5.0"
43
+ }
44
+ }