agentcache 0.1.3 → 0.2.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.
package/README.md CHANGED
@@ -23,7 +23,7 @@ npm install -g agentcache
23
23
  Done. Start a new session in any IDE. AgentCache is already running.
24
24
 
25
25
  No `init`. No `setup`. No config. No second command. The install itself:
26
- 1. Creates `~/.loop/loop.db` (your knowledge store)
26
+ 1. Creates `~/.agentcache/agentcache.db` (your knowledge store)
27
27
  2. Detects installed IDEs (Claude Code, Cursor, Roo Code, Windsurf, Continue, Codex)
28
28
  3. Registers itself as an MCP server in each
29
29
  4. Sets up Claude Code hooks for automatic transcript recovery
@@ -49,7 +49,8 @@ No `init`. No `setup`. No config. No second command. The install itself:
49
49
  │ └────────────┬────────────┘ │
50
50
  │ │ │
51
51
  │ ┌─────────┴─────────┐ │
52
- │ │ ~/.loop/loop.db │ │
52
+ │ │ ~/.agentcache/ │ │
53
+ │ │ agentcache.db │ │
53
54
  │ │ (SQLite + WAL) │ │
54
55
  │ └───────────────────┘ │
55
56
  └─────────────────────────────────────────────────────────────────┘
@@ -57,8 +58,8 @@ No `init`. No `setup`. No config. No second command. The install itself:
57
58
 
58
59
  ### The Cycle
59
60
 
60
- 1. **Session starts** — agent calls `loop_inject_context` → gets compiled rules, lessons, decisions
61
- 2. **During session** — agent calls `loop_compile_submit` incrementally as it learns things
61
+ 1. **Session starts** — agent calls `agentcache_inject_context` → gets compiled rules, lessons, decisions
62
+ 2. **During session** — agent calls `agentcache_compile_submit` incrementally as it learns things
62
63
  3. **Session ends** — knowledge is already saved. If agent didn't submit (abrupt exit), transcript recovery handles it next session.
63
64
 
64
65
  ### Knowledge Types
@@ -78,14 +79,14 @@ AgentCache exposes 8 tools via the Model Context Protocol:
78
79
 
79
80
  | Tool | Purpose |
80
81
  |------|---------|
81
- | `loop_inject_context` | Load compiled knowledge at session start |
82
- | `loop_compile_submit` | Submit observations incrementally during session |
83
- | `loop_compile_cluster` | Resolve clustering when observations overlap existing knowledge |
84
- | `loop_compile_extract` | Process queued transcripts from previous sessions |
85
- | `loop_enforce` | Check tool calls against enforced policy rules |
86
- | `loop_save_observation` | Save a permanent observation (USER authority, never auto-deprecated) |
87
- | `loop_get_knowledge` | Query the knowledge database |
88
- | `loop_deprecate_knowledge` | Mark knowledge as deprecated when it's no longer valid |
82
+ | `agentcache_inject_context` | Load compiled knowledge at session start |
83
+ | `agentcache_compile_submit` | Submit observations incrementally during session |
84
+ | `agentcache_compile_cluster` | Resolve clustering when observations overlap existing knowledge |
85
+ | `agentcache_compile_extract` | Process queued transcripts from previous sessions |
86
+ | `agentcache_enforce` | Check tool calls against enforced policy rules |
87
+ | `agentcache_save_observation` | Save a permanent observation (USER authority, never auto-deprecated) |
88
+ | `agentcache_get_knowledge` | Query the knowledge database |
89
+ | `agentcache_deprecate_knowledge` | Mark knowledge as deprecated when it's no longer valid |
89
90
 
90
91
  ## CLI Commands
91
92
 
@@ -114,7 +115,7 @@ AgentCache uses MCP (Model Context Protocol) as its only interface. Any IDE that
114
115
 
115
116
  ### Developer-Scoped
116
117
 
117
- One database per developer (`~/.loop/loop.db`), not per project. Rules and lessons learned in one project benefit all your projects. Project-specific decisions stay scoped to their project.
118
+ One database per developer (`~/.agentcache/agentcache.db`), not per project. Rules and lessons learned in one project benefit all your projects. Project-specific decisions stay scoped to their project.
118
119
 
119
120
  ### Resilient to Abrupt Exits
120
121
 
@@ -133,24 +134,32 @@ AgentCache prevents knowledge from growing unbounded:
133
134
 
134
135
  ## Supported IDEs
135
136
 
136
- | IDE | MCP | Transcript Recovery | Hooks |
137
- |-----|-----|--------------------|----|
138
- | Claude Code | Yes | Full (JSONL) | Stop, SessionStart, PreToolUse |
139
- | Cursor | Yes | Incremental only | — |
140
- | Roo Code | Yes | Incremental only | — |
141
- | Windsurf | Yes | Incremental only | — |
142
- | Continue | Yes | Full (JSON) | — |
143
- | Codex | Yes | Incremental only | — |
137
+ | IDE | MCP | Auto-Approve | Transcript Recovery | Hooks |
138
+ |-----|-----|-------------|--------------------|----|
139
+ | Claude Code | Yes | Yes (automatic) | Full (JSONL) | Stop, SessionStart, PreToolUse |
140
+ | Cursor | Yes | **Manual** (see below) | Incremental only | — |
141
+ | Roo Code | Yes | Yes (automatic) | Incremental only | — |
142
+ | Windsurf | Yes | Yes (automatic) | Incremental only | — |
143
+ | Continue | Yes | Yes (automatic) | Full (JSON) | — |
144
+ | Codex | Yes | Yes (automatic) | Incremental only | — |
144
145
 
145
146
  "Incremental only" means if the agent submits observations during the session, they're saved. If the session terminates before any submission, those observations are lost (no transcript access).
146
147
 
148
+ ### Cursor: Enable Auto-Approve
149
+
150
+ Cursor does not support programmatic auto-approve for MCP tools. After installing, you need to manually enable it once:
151
+
152
+ 1. Open Cursor Settings → MCP
153
+ 2. Find **agentcache** in the server list
154
+ 3. Set tool approval to **"Always allow"** (or enable "Yolo mode" in Cursor settings for all tools)
155
+
147
156
  ## Data Storage
148
157
 
149
- All data lives in `~/.loop/loop.db` (SQLite with WAL mode for concurrent access).
158
+ All data lives in `~/.agentcache/agentcache.db` (SQLite with WAL mode for concurrent access).
150
159
 
151
160
  ```
152
- ~/.loop/
153
- └── loop.db # All knowledge, observations, sessions, pending queue
161
+ ~/.agentcache/
162
+ └── agentcache.db # All knowledge, observations, sessions, pending queue
154
163
  ```
155
164
 
156
165
  No data leaves your machine. No network calls. No telemetry. No accounts.
@@ -185,8 +194,8 @@ Projects are identified by a hash of their full filesystem path, not just the fo
185
194
  ## Contributing
186
195
 
187
196
  ```bash
188
- git clone https://github.com/raghav-a21ai/loop-eng
189
- cd loop-eng
197
+ git clone https://github.com/raghav-a21ai/agentcache
198
+ cd agentcache
190
199
  npm install
191
200
  npm run build
192
201
  npm test
@@ -0,0 +1,282 @@
1
+ // src/utils/ide-detector.ts
2
+ import { existsSync } from "fs";
3
+ import { join } from "path";
4
+ import { homedir } from "os";
5
+ function getRooConfigPath() {
6
+ const home = homedir();
7
+ if (process.platform === "darwin") {
8
+ return join(home, "Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json");
9
+ }
10
+ if (process.platform === "win32") {
11
+ return join(process.env.APPDATA || join(home, "AppData/Roaming"), "Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json");
12
+ }
13
+ return join(home, ".config/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json");
14
+ }
15
+ function getWindsurfConfigPath() {
16
+ const home = homedir();
17
+ return join(home, ".codeium", "windsurf", "mcp_config.json");
18
+ }
19
+ function getContinueConfigPath() {
20
+ const home = homedir();
21
+ return join(home, ".continue", "mcpServers", "agentcache.json");
22
+ }
23
+ function getCodexConfigPath() {
24
+ const home = homedir();
25
+ return join(home, ".codex", "config.toml");
26
+ }
27
+ function detectInstalledIdes() {
28
+ const home = homedir();
29
+ return [
30
+ {
31
+ name: "Claude Code",
32
+ detected: existsSync(join(home, ".claude")),
33
+ mcpConfigPath: join(home, ".claude.json"),
34
+ mcpConfigFormat: "claude-settings"
35
+ },
36
+ {
37
+ name: "Cursor",
38
+ detected: existsSync(join(home, ".cursor")),
39
+ mcpConfigPath: join(home, ".cursor", "mcp.json"),
40
+ mcpConfigFormat: "mcp-json"
41
+ },
42
+ {
43
+ name: "Roo Code",
44
+ detected: existsSync(getRooConfigPath()),
45
+ mcpConfigPath: getRooConfigPath(),
46
+ mcpConfigFormat: "mcp-json"
47
+ },
48
+ {
49
+ name: "Windsurf",
50
+ detected: existsSync(join(home, ".codeium", "windsurf")) || existsSync(join(home, ".windsurf")),
51
+ mcpConfigPath: getWindsurfConfigPath(),
52
+ mcpConfigFormat: "mcp-json"
53
+ },
54
+ {
55
+ name: "Continue",
56
+ detected: existsSync(join(home, ".continue")),
57
+ mcpConfigPath: getContinueConfigPath(),
58
+ mcpConfigFormat: "continue-dir"
59
+ },
60
+ {
61
+ name: "Codex",
62
+ detected: existsSync(join(home, ".codex")),
63
+ mcpConfigPath: getCodexConfigPath(),
64
+ mcpConfigFormat: "codex-toml"
65
+ }
66
+ ];
67
+ }
68
+
69
+ // src/utils/ide-registrar.ts
70
+ import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync, appendFileSync } from "fs";
71
+ import { join as join2, dirname } from "path";
72
+ import { homedir as homedir2 } from "os";
73
+ import { execSync } from "child_process";
74
+ function findNodeBinary() {
75
+ try {
76
+ return execSync("which node", { encoding: "utf-8" }).trim();
77
+ } catch {
78
+ return "node";
79
+ }
80
+ }
81
+ function findAgentcacheScript() {
82
+ try {
83
+ const binPath = execSync("which agentcache", { encoding: "utf-8" }).trim();
84
+ return binPath;
85
+ } catch {
86
+ return join2(dirname(dirname(__dirname)), "dist", "cli.js");
87
+ }
88
+ }
89
+ function isVscodeExtensionIde(ide) {
90
+ return ide.name === "Roo Code" || ide.name === "Continue";
91
+ }
92
+ var ALL_TOOLS = [
93
+ "agentcache_inject_context",
94
+ "agentcache_compile_submit",
95
+ "agentcache_compile_cluster",
96
+ "agentcache_compile_extract",
97
+ "agentcache_enforce",
98
+ "agentcache_save_observation",
99
+ "agentcache_get_knowledge",
100
+ "agentcache_deprecate_knowledge"
101
+ ];
102
+ function registerMcpServer(ide) {
103
+ if (!ide.detected) return false;
104
+ if (ide.mcpConfigFormat === "claude-settings") {
105
+ return registerClaudeCode();
106
+ }
107
+ if (ide.mcpConfigFormat === "mcp-json") {
108
+ return registerMcpJson(ide);
109
+ }
110
+ if (ide.mcpConfigFormat === "continue-dir") {
111
+ return registerContinue(ide);
112
+ }
113
+ if (ide.mcpConfigFormat === "codex-toml") {
114
+ return registerCodex(ide);
115
+ }
116
+ return false;
117
+ }
118
+ function registerClaudeCode() {
119
+ const claudeJsonPath = join2(homedir2(), ".claude.json");
120
+ let config = {};
121
+ if (existsSync2(claudeJsonPath)) {
122
+ try {
123
+ config = JSON.parse(readFileSync(claudeJsonPath, "utf-8"));
124
+ } catch {
125
+ config = {};
126
+ }
127
+ }
128
+ if (!config.mcpServers) config.mcpServers = {};
129
+ let serverRegistered = false;
130
+ if (!config.mcpServers.agentcache) {
131
+ config.mcpServers.agentcache = {
132
+ type: "stdio",
133
+ command: "agentcache",
134
+ args: ["serve"],
135
+ env: {}
136
+ };
137
+ writeFileSync(claudeJsonPath, JSON.stringify(config, null, 2));
138
+ serverRegistered = true;
139
+ }
140
+ const settingsPath = join2(homedir2(), ".claude", "settings.json");
141
+ if (existsSync2(join2(homedir2(), ".claude"))) {
142
+ let settings = {};
143
+ if (existsSync2(settingsPath)) {
144
+ try {
145
+ settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
146
+ } catch {
147
+ settings = {};
148
+ }
149
+ }
150
+ if (!settings.permissions) settings.permissions = {};
151
+ if (!settings.permissions.allow) settings.permissions.allow = [];
152
+ const allowList = settings.permissions.allow;
153
+ const mcpPerms = ALL_TOOLS.map((t) => `mcp__agentcache__${t}`);
154
+ let permsUpdated = false;
155
+ for (const perm of mcpPerms) {
156
+ if (!allowList.includes(perm)) {
157
+ allowList.push(perm);
158
+ permsUpdated = true;
159
+ }
160
+ }
161
+ if (permsUpdated) {
162
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
163
+ }
164
+ }
165
+ return serverRegistered;
166
+ }
167
+ function registerMcpJson(ide) {
168
+ let config = {};
169
+ if (existsSync2(ide.mcpConfigPath)) {
170
+ try {
171
+ config = JSON.parse(readFileSync(ide.mcpConfigPath, "utf-8"));
172
+ } catch {
173
+ config = {};
174
+ }
175
+ }
176
+ if (!config.mcpServers) config.mcpServers = {};
177
+ const existing = config.mcpServers.agentcache;
178
+ if (isVscodeExtensionIde(ide)) {
179
+ const nodeBin = findNodeBinary();
180
+ const script = findAgentcacheScript();
181
+ config.mcpServers.agentcache = {
182
+ command: nodeBin,
183
+ args: [script, "serve"],
184
+ alwaysAllow: ALL_TOOLS,
185
+ disabled: false
186
+ };
187
+ } else if (!existing) {
188
+ const agentcacheBin = findAgentcacheScript();
189
+ config.mcpServers.agentcache = {
190
+ command: agentcacheBin,
191
+ args: ["serve"]
192
+ };
193
+ } else {
194
+ return false;
195
+ }
196
+ mkdirSync(dirname(ide.mcpConfigPath), { recursive: true });
197
+ writeFileSync(ide.mcpConfigPath, JSON.stringify(config, null, 2));
198
+ return !existing;
199
+ }
200
+ function registerContinue(ide) {
201
+ const configPath = ide.mcpConfigPath;
202
+ if (existsSync2(configPath)) {
203
+ try {
204
+ const existing = JSON.parse(readFileSync(configPath, "utf-8"));
205
+ if (existing.mcpServers?.agentcache) return false;
206
+ } catch {
207
+ }
208
+ }
209
+ const nodeBin = findNodeBinary();
210
+ const script = findAgentcacheScript();
211
+ const config = {
212
+ mcpServers: {
213
+ agentcache: {
214
+ command: nodeBin,
215
+ args: [script, "serve"]
216
+ }
217
+ }
218
+ };
219
+ mkdirSync(dirname(configPath), { recursive: true });
220
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
221
+ return true;
222
+ }
223
+ function registerCodex(ide) {
224
+ const configPath = ide.mcpConfigPath;
225
+ if (existsSync2(configPath)) {
226
+ const content = readFileSync(configPath, "utf-8");
227
+ if (content.includes("[mcp_servers.agentcache]")) return false;
228
+ }
229
+ const agentcacheBin = findAgentcacheScript();
230
+ const tomlBlock = `
231
+ [mcp_servers.agentcache]
232
+ command = "${agentcacheBin}"
233
+ args = ["serve"]
234
+ default_tools_approval_mode = "auto"
235
+ `;
236
+ mkdirSync(dirname(configPath), { recursive: true });
237
+ if (existsSync2(configPath)) {
238
+ appendFileSync(configPath, tomlBlock);
239
+ } else {
240
+ writeFileSync(configPath, tomlBlock.trimStart());
241
+ }
242
+ return true;
243
+ }
244
+ function registerClaudeHooks() {
245
+ const settingsPath = join2(homedir2(), ".claude", "settings.json");
246
+ if (!existsSync2(join2(homedir2(), ".claude"))) return false;
247
+ let settings = {};
248
+ if (existsSync2(settingsPath)) {
249
+ try {
250
+ settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
251
+ } catch {
252
+ settings = {};
253
+ }
254
+ }
255
+ if (!settings.hooks) settings.hooks = {};
256
+ const hooks = settings.hooks;
257
+ const agentcacheHooks = {
258
+ Stop: [{ matcher: "", hooks: [{ type: "command", command: "agentcache compile-session" }] }],
259
+ SessionStart: [{ matcher: "", hooks: [{ type: "command", command: "agentcache discover" }] }],
260
+ PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: "agentcache enforce" }] }]
261
+ };
262
+ let registered = false;
263
+ for (const [event, hookConfig] of Object.entries(agentcacheHooks)) {
264
+ if (!hooks[event]) hooks[event] = [];
265
+ const existing = hooks[event];
266
+ const hasAgentcache = existing.some((h) => h.hooks?.some((hh) => hh.command?.includes("agentcache")));
267
+ if (!hasAgentcache) {
268
+ hooks[event].push(...hookConfig);
269
+ registered = true;
270
+ }
271
+ }
272
+ if (registered) {
273
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
274
+ }
275
+ return registered;
276
+ }
277
+
278
+ export {
279
+ detectInstalledIdes,
280
+ registerMcpServer,
281
+ registerClaudeHooks
282
+ };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getClaudeTranscriptsDir,
3
3
  getContinueSessionsDir
4
- } from "./chunk-VPEEZXLK.js";
4
+ } from "./chunk-WHP4Z32Z.js";
5
5
  import {
6
6
  __export
7
7
  } from "./chunk-MLKGABMK.js";
@@ -1,8 +1,8 @@
1
1
  // src/policy/engine.ts
2
2
  var HARDCODED_BLOCKS = [
3
- { pattern: /git\s+push\s+--force\s+(origin\s+)?(main|master)/i, reason: "Force-push to main/master is blocked by Loop policy" },
4
- { pattern: /rm\s+-rf\s+[\/~]/i, reason: "Destructive rm -rf on root or home is blocked by Loop policy" },
5
- { pattern: />\s*(.*\.(env|pem|key))/i, reason: "Writing to sensitive files (.env, .pem, .key) is blocked by Loop policy" }
3
+ { pattern: /git\s+push\s+--force\s+(origin\s+)?(main|master)/i, reason: "Force-push to main/master is blocked by AgentCache policy" },
4
+ { pattern: /rm\s+-rf\s+[\/~]/i, reason: "Destructive rm -rf on root or home is blocked by AgentCache policy" },
5
+ { pattern: />\s*(.*\.(env|pem|key))/i, reason: "Writing to sensitive files (.env, .pem, .key) is blocked by AgentCache policy" }
6
6
  ];
7
7
  function evaluatePolicy(input, enforcedRules) {
8
8
  const command = extractCommand(input.tool_name, input.tool_input);
@@ -1,6 +1,6 @@
1
1
  // src/utils/paths.ts
2
- import { existsSync } from "fs";
3
- import { join } from "path";
2
+ import { existsSync, renameSync, mkdirSync } from "fs";
3
+ import { join, dirname } from "path";
4
4
  import { homedir } from "os";
5
5
  import { createHash } from "crypto";
6
6
 
@@ -26,15 +26,23 @@ function getGitRoot(cwd) {
26
26
  }
27
27
 
28
28
  // src/utils/paths.ts
29
- function getGlobalLoopDir() {
30
- return join(homedir(), ".loop");
29
+ function getDataDir() {
30
+ return join(homedir(), ".agentcache");
31
31
  }
32
32
  function getDbPath() {
33
- return join(getGlobalLoopDir(), "loop.db");
33
+ return join(getDataDir(), "agentcache.db");
34
34
  }
35
- function isLoopInitialized() {
35
+ function isInitialized() {
36
36
  return existsSync(getDbPath());
37
37
  }
38
+ function migrateFromLegacy() {
39
+ const legacyDb = join(homedir(), ".loop", "loop.db");
40
+ const newDb = getDbPath();
41
+ if (existsSync(legacyDb) && !existsSync(newDb)) {
42
+ mkdirSync(dirname(newDb), { recursive: true });
43
+ renameSync(legacyDb, newDb);
44
+ }
45
+ }
38
46
  function findProjectRoot(cwd) {
39
47
  const dir = cwd || process.cwd();
40
48
  const gitRoot = getGitRoot(dir);
@@ -57,9 +65,10 @@ function getContinueSessionsDir() {
57
65
 
58
66
  export {
59
67
  getGitContext,
60
- getGlobalLoopDir,
68
+ getDataDir,
61
69
  getDbPath,
62
- isLoopInitialized,
70
+ isInitialized,
71
+ migrateFromLegacy,
63
72
  findProjectRoot,
64
73
  getProjectId,
65
74
  getProjectDisplayName,
package/dist/cli.js CHANGED
@@ -4,16 +4,16 @@
4
4
  import { Command } from "commander";
5
5
  var program = new Command();
6
6
  program.name("agentcache").description("Engineering Knowledge Compiler \u2014 universal, zero-config").version("0.3.0");
7
- program.command("setup").description("Detect IDEs and register Loop (runs automatically on install)").action(async () => {
8
- const { runSetup } = await import("./setup-C6PI6YD7.js");
7
+ program.command("setup").description("Detect IDEs and register AgentCache (runs automatically on install)").action(async () => {
8
+ const { runSetup } = await import("./setup-YCFTG2KT.js");
9
9
  await runSetup();
10
10
  });
11
- program.command("serve").description("Start Loop MCP server (spawned by IDEs automatically)").action(async () => {
11
+ program.command("serve").description("Start AgentCache MCP server (spawned by IDEs automatically)").action(async () => {
12
12
  const { startMcpServer } = await import("./mcp.js");
13
13
  await startMcpServer();
14
14
  });
15
15
  program.command("compile-session").description("Stop hook: queue transcript for compilation").action(async () => {
16
- const { handleStop } = await import("./stop-DRL3LXFQ.js");
16
+ const { handleStop } = await import("./stop-6MKD743B.js");
17
17
  let payload;
18
18
  try {
19
19
  let data = "";
@@ -28,11 +28,11 @@ program.command("compile-session").description("Stop hook: queue transcript for
28
28
  await handleStop(payload);
29
29
  });
30
30
  program.command("discover").description("SessionStart hook: discover uncompiled transcripts").action(async () => {
31
- const { handleSessionStart } = await import("./session-start-ILEPFZZC.js");
31
+ const { handleSessionStart } = await import("./session-start-BIY7CBXU.js");
32
32
  await handleSessionStart();
33
33
  });
34
34
  program.command("enforce").description("PreToolUse hook: policy enforcement").action(async () => {
35
- const { handlePreToolUse } = await import("./pre-tool-use-2P5P6JWE.js");
35
+ const { handlePreToolUse } = await import("./pre-tool-use-TPCPTJXS.js");
36
36
  let data = "";
37
37
  for await (const chunk of process.stdin) {
38
38
  data += chunk;
@@ -45,10 +45,10 @@ program.command("enforce").description("PreToolUse hook: policy enforcement").ac
45
45
  process.stdout.write("{}");
46
46
  }
47
47
  });
48
- program.command("status").description("Show Loop knowledge stats").action(async () => {
49
- const { getDbPath, isLoopInitialized, findProjectRoot, getProjectId, getProjectDisplayName } = await import("./paths-TWJ7GAJY.js");
50
- if (!isLoopInitialized()) {
51
- console.log("Loop not initialized. Run: agentcache setup");
48
+ program.command("status").description("Show AgentCache knowledge stats").action(async () => {
49
+ const { getDbPath, isInitialized, findProjectRoot, getProjectId, getProjectDisplayName } = await import("./paths-LEZQCRKI.js");
50
+ if (!isInitialized()) {
51
+ console.log("AgentCache not initialized. Run: agentcache setup");
52
52
  return;
53
53
  }
54
54
  const { SqliteKnowledgeRepository } = await import("./sqlite-5V565IV3.js");
@@ -65,7 +65,7 @@ program.command("status").description("Show Loop knowledge stats").action(async
65
65
  const projectItems = items.filter((i) => i.scope === "project");
66
66
  const pending = repo.getPendingCount();
67
67
  repo.close();
68
- console.log(`Loop \u2014 ${displayName} (${project})`);
68
+ console.log(`AgentCache \u2014 ${displayName} (${project})`);
69
69
  console.log(` ${items.length} items (${globalItems.length} global, ${projectItems.length} project)`);
70
70
  console.log(` ${rules.length} rules | ${lessons.length} lessons | ${decisions.length} decisions | ${context.length} context`);
71
71
  if (pending > 0) console.log(` ${pending} sessions pending compilation`);
package/dist/mcp.js CHANGED
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  evaluatePolicy
3
- } from "./chunk-PYGRUQNL.js";
3
+ } from "./chunk-T7BJPANN.js";
4
4
  import {
5
5
  parseTranscript
6
- } from "./chunk-QGG25FWV.js";
6
+ } from "./chunk-OSFK44XC.js";
7
7
  import {
8
8
  findProjectRoot,
9
9
  getDbPath,
10
10
  getGitContext,
11
11
  getProjectId,
12
- isLoopInitialized
13
- } from "./chunk-VPEEZXLK.js";
12
+ isInitialized
13
+ } from "./chunk-WHP4Z32Z.js";
14
14
  import {
15
15
  SqliteKnowledgeRepository
16
16
  } from "./chunk-MMSMDJ4O.js";
@@ -528,7 +528,7 @@ function saveCompileRun(repo, sessionId, project, observationsProcessed, autoRei
528
528
  }
529
529
  function formatDiagnostics(extracted, autoReinforced, created, reinforced, superseded, deprecated, ignored, project, sessionId) {
530
530
  return [
531
- `Loop Compiler v${COMPILER_VERSION}`,
531
+ `AgentCache Compiler v${COMPILER_VERSION}`,
532
532
  `Project: ${project} | Session: ${sessionId}`,
533
533
  ` ${extracted} observations processed`,
534
534
  autoReinforced > 0 ? ` ${autoReinforced} auto-reinforced (no LLM needed)` : "",
@@ -570,7 +570,7 @@ async function startMcpServer() {
570
570
  { name: "agentcache", version: "0.1.0" },
571
571
  {
572
572
  capabilities: { tools: {} },
573
- instructions: "AgentCache is your knowledge cache. At the START of every session, call loop_inject_context to load compiled rules, lessons, decisions, and context. Submit observations INCREMENTALLY via loop_compile_submit as you learn them \u2014 do not wait until session end."
573
+ instructions: "AgentCache is your knowledge cache. At the START of every session, call agentcache_inject_context to load compiled rules, lessons, decisions, and context. Submit observations INCREMENTALLY via agentcache_compile_submit as you learn them \u2014 do not wait until session end."
574
574
  }
575
575
  );
576
576
  server.oninitialized = async () => {
@@ -582,7 +582,7 @@ async function startMcpServer() {
582
582
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
583
583
  tools: [
584
584
  {
585
- name: "loop_inject_context",
585
+ name: "agentcache_inject_context",
586
586
  description: "Get compiled engineering knowledge for this project. Returns global rules/lessons (apply everywhere) + project-specific decisions/context. Call this at the START of every session.",
587
587
  inputSchema: {
588
588
  type: "object",
@@ -593,7 +593,7 @@ async function startMcpServer() {
593
593
  }
594
594
  },
595
595
  {
596
- name: "loop_compile_submit",
596
+ name: "agentcache_compile_submit",
597
597
  description: "Submit observations extracted from your session. Call this INCREMENTALLY \u2014 each time you learn a rule, lesson, decision, or context item. Do NOT batch until end of session; sessions can terminate without warning.",
598
598
  inputSchema: {
599
599
  type: "object",
@@ -619,12 +619,12 @@ async function startMcpServer() {
619
619
  }
620
620
  },
621
621
  {
622
- name: "loop_compile_cluster",
623
- description: "Submit clustering decisions when loop_compile_submit returns needs_clustering. Determines whether observations create new knowledge or relate to existing items.",
622
+ name: "agentcache_compile_cluster",
623
+ description: "Submit clustering decisions when agentcache_compile_submit returns needs_clustering. Determines whether observations create new knowledge or relate to existing items.",
624
624
  inputSchema: {
625
625
  type: "object",
626
626
  properties: {
627
- sessionId: { type: "string", description: "Session ID from loop_compile_submit response" },
627
+ sessionId: { type: "string", description: "Session ID from agentcache_compile_submit response" },
628
628
  clusters: {
629
629
  type: "array",
630
630
  items: {
@@ -644,8 +644,8 @@ async function startMcpServer() {
644
644
  }
645
645
  },
646
646
  {
647
- name: "loop_compile_extract",
648
- description: "For PREVIOUS sessions stored as transcript files. Reads a queued transcript and returns an extraction prompt for you to process. After processing, call loop_compile_submit with the results.",
647
+ name: "agentcache_compile_extract",
648
+ description: "For PREVIOUS sessions stored as transcript files. Reads a queued transcript and returns an extraction prompt for you to process. After processing, call agentcache_compile_submit with the results.",
649
649
  inputSchema: {
650
650
  type: "object",
651
651
  properties: {
@@ -655,8 +655,8 @@ async function startMcpServer() {
655
655
  }
656
656
  },
657
657
  {
658
- name: "loop_enforce",
659
- description: "Check if a tool call is allowed by Loop's policy rules. Call this BEFORE executing risky operations (file deletions, force pushes, etc). Returns allow or block with reason.",
658
+ name: "agentcache_enforce",
659
+ description: "Check if a tool call is allowed by AgentCache policy rules. Call this BEFORE executing risky operations (file deletions, force pushes, etc). Returns allow or block with reason.",
660
660
  inputSchema: {
661
661
  type: "object",
662
662
  properties: {
@@ -668,7 +668,7 @@ async function startMcpServer() {
668
668
  }
669
669
  },
670
670
  {
671
- name: "loop_save_observation",
671
+ name: "agentcache_save_observation",
672
672
  description: "Save a single observation immediately with USER authority (never overwritten by compiler). Use for important rules or decisions that should persist permanently.",
673
673
  inputSchema: {
674
674
  type: "object",
@@ -683,8 +683,8 @@ async function startMcpServer() {
683
683
  }
684
684
  },
685
685
  {
686
- name: "loop_get_knowledge",
687
- description: "Query knowledge items from Loop's compiled database.",
686
+ name: "agentcache_get_knowledge",
687
+ description: "Query knowledge items from AgentCache knowledge database.",
688
688
  inputSchema: {
689
689
  type: "object",
690
690
  properties: {
@@ -697,7 +697,7 @@ async function startMcpServer() {
697
697
  }
698
698
  },
699
699
  {
700
- name: "loop_deprecate_knowledge",
700
+ name: "agentcache_deprecate_knowledge",
701
701
  description: "Mark a knowledge item as deprecated. Use when a rule, lesson, or decision is no longer valid. Works on both auto-compiled and user-saved items.",
702
702
  inputSchema: {
703
703
  type: "object",
@@ -711,15 +711,15 @@ async function startMcpServer() {
711
711
  ]
712
712
  }));
713
713
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
714
- if (!isLoopInitialized()) {
715
- return { content: [{ type: "text", text: "Loop not initialized. Run: agentcache setup" }], isError: true };
714
+ if (!isInitialized()) {
715
+ return { content: [{ type: "text", text: "AgentCache not initialized. Run: agentcache setup" }], isError: true };
716
716
  }
717
717
  const repo = new SqliteKnowledgeRepository(getDbPath());
718
718
  const projectRoot = getResolvedProjectRoot();
719
719
  const detectedProject = getResolvedProjectId();
720
720
  try {
721
721
  switch (request.params.name) {
722
- case "loop_inject_context": {
722
+ case "agentcache_inject_context": {
723
723
  const args = request.params.arguments || {};
724
724
  const project = args.project || detectedProject;
725
725
  const items = repo.getKnowledgeForContext(project);
@@ -740,17 +740,17 @@ async function startMcpServer() {
740
740
  if (context.length) {
741
741
  output += "# Context\n" + context.map((c) => `- ${c.content}`).join("\n") + "\n\n";
742
742
  }
743
- if (!output) output = "No compiled knowledge yet. This will populate as you use Loop across sessions.\n";
743
+ if (!output) output = "No compiled knowledge yet. This will populate as you use AgentCache across sessions.\n";
744
744
  const pendingCount = repo.getPendingCount();
745
745
  if (pendingCount > 0) {
746
- output = `<!-- ${pendingCount} previous session(s) pending compilation. Call loop_compile_extract to process. -->
746
+ output = `<!-- ${pendingCount} previous session(s) pending compilation. Call agentcache_compile_extract to process. -->
747
747
 
748
748
  ` + output;
749
749
  }
750
- output += "\n---\nIMPORTANT: Submit observations incrementally as they happen during this session.\nWhen you learn something (rule, lesson, decision, context), call loop_compile_submit immediately.\nDo NOT wait until the end \u2014 sessions can terminate without warning.\n";
750
+ output += "\n---\nIMPORTANT: Submit observations incrementally as they happen during this session.\nWhen you learn something (rule, lesson, decision, context), call agentcache_compile_submit immediately.\nDo NOT wait until the end \u2014 sessions can terminate without warning.\n";
751
751
  return { content: [{ type: "text", text: output.trim() }] };
752
752
  }
753
- case "loop_compile_submit": {
753
+ case "agentcache_compile_submit": {
754
754
  const args = request.params.arguments;
755
755
  const project = args.project || detectedProject;
756
756
  const sessionId = `sess_${randomUUID4().slice(0, 8)}`;
@@ -768,14 +768,14 @@ async function startMcpServer() {
768
768
  content: [{ type: "text", text: JSON.stringify({ status: "needs_clustering", sessionId: result.sessionId, clusteringContext: result.clusteringPrompt }) }]
769
769
  };
770
770
  }
771
- case "loop_compile_cluster": {
771
+ case "agentcache_compile_cluster": {
772
772
  const args = request.params.arguments;
773
773
  const project = args.project || detectedProject;
774
774
  const responseText = JSON.stringify({ clusters: args.clusters });
775
775
  const result = processClustering(repo, responseText, args.sessionId, project, projectRoot);
776
776
  return { content: [{ type: "text", text: JSON.stringify({ status: "complete", diagnostics: result.diagnostics }) }] };
777
777
  }
778
- case "loop_compile_extract": {
778
+ case "agentcache_compile_extract": {
779
779
  const args = request.params.arguments || {};
780
780
  const entry = repo.popPendingTranscript();
781
781
  if (!entry) {
@@ -793,14 +793,14 @@ async function startMcpServer() {
793
793
  const state = startCompile(events, sessionId, project, entry.projectRoot || projectRoot, repo, entry.transcriptPath);
794
794
  return { content: [{ type: "text", text: JSON.stringify({ sessionId: state.sessionId, prompt: state.prompt }) }] };
795
795
  }
796
- case "loop_enforce": {
796
+ case "agentcache_enforce": {
797
797
  const args = request.params.arguments;
798
798
  const project = args.project || detectedProject;
799
799
  const input = { tool_name: args.tool_name, tool_input: args.tool_input || {} };
800
800
  const result = evaluatePolicy(input, repo.getEnforcedRules(project));
801
801
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
802
802
  }
803
- case "loop_save_observation": {
803
+ case "agentcache_save_observation": {
804
804
  const args = request.params.arguments;
805
805
  const project = args.project || detectedProject;
806
806
  const scope = args.scope || defaultScope(args.type);
@@ -849,7 +849,7 @@ async function startMcpServer() {
849
849
  });
850
850
  return { content: [{ type: "text", text: JSON.stringify({ saved: true, scope }) }] };
851
851
  }
852
- case "loop_get_knowledge": {
852
+ case "agentcache_get_knowledge": {
853
853
  const args = request.params.arguments || {};
854
854
  const project = args.project || detectedProject;
855
855
  const items = repo.getKnowledgeItems(project, {
@@ -860,7 +860,7 @@ async function startMcpServer() {
860
860
  const summary = filtered.map((i) => `[${i.id}] [${i.scope}/${i.confidence}] (${i.type}) ${i.content}`).join("\n");
861
861
  return { content: [{ type: "text", text: summary || "No knowledge items found." }] };
862
862
  }
863
- case "loop_deprecate_knowledge": {
863
+ case "agentcache_deprecate_knowledge": {
864
864
  const args = request.params.arguments;
865
865
  const item = repo.getKnowledgeItem(args.id);
866
866
  if (!item) {
@@ -2,20 +2,22 @@ import {
2
2
  findProjectRoot,
3
3
  getClaudeTranscriptsDir,
4
4
  getContinueSessionsDir,
5
+ getDataDir,
5
6
  getDbPath,
6
- getGlobalLoopDir,
7
7
  getProjectDisplayName,
8
8
  getProjectId,
9
- isLoopInitialized
10
- } from "./chunk-VPEEZXLK.js";
9
+ isInitialized,
10
+ migrateFromLegacy
11
+ } from "./chunk-WHP4Z32Z.js";
11
12
  import "./chunk-MLKGABMK.js";
12
13
  export {
13
14
  findProjectRoot,
14
15
  getClaudeTranscriptsDir,
15
16
  getContinueSessionsDir,
17
+ getDataDir,
16
18
  getDbPath,
17
- getGlobalLoopDir,
18
19
  getProjectDisplayName,
19
20
  getProjectId,
20
- isLoopInitialized
21
+ isInitialized,
22
+ migrateFromLegacy
21
23
  };
@@ -2,11 +2,12 @@ import {
2
2
  detectInstalledIdes,
3
3
  registerClaudeHooks,
4
4
  registerMcpServer
5
- } from "./chunk-7FIT3LNA.js";
5
+ } from "./chunk-LDQPTAZ7.js";
6
6
  import {
7
+ getDataDir,
7
8
  getDbPath,
8
- getGlobalLoopDir
9
- } from "./chunk-VPEEZXLK.js";
9
+ migrateFromLegacy
10
+ } from "./chunk-WHP4Z32Z.js";
10
11
  import {
11
12
  SqliteKnowledgeRepository
12
13
  } from "./chunk-MMSMDJ4O.js";
@@ -18,7 +19,8 @@ if (process.env.CI) {
18
19
  process.exit(0);
19
20
  }
20
21
  try {
21
- mkdirSync(getGlobalLoopDir(), { recursive: true });
22
+ migrateFromLegacy();
23
+ mkdirSync(getDataDir(), { recursive: true });
22
24
  const repo = new SqliteKnowledgeRepository(getDbPath());
23
25
  repo.close();
24
26
  const ides = detectInstalledIdes().filter((i) => i.detected);
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  evaluatePolicy
3
- } from "./chunk-PYGRUQNL.js";
3
+ } from "./chunk-T7BJPANN.js";
4
4
  import {
5
5
  findProjectRoot,
6
6
  getDbPath,
7
7
  getProjectId,
8
- isLoopInitialized
9
- } from "./chunk-VPEEZXLK.js";
8
+ isInitialized
9
+ } from "./chunk-WHP4Z32Z.js";
10
10
  import {
11
11
  SqliteKnowledgeRepository
12
12
  } from "./chunk-MMSMDJ4O.js";
@@ -14,7 +14,7 @@ import "./chunk-MLKGABMK.js";
14
14
 
15
15
  // src/hooks/pre-tool-use.ts
16
16
  function handlePreToolUse(input) {
17
- if (!isLoopInitialized()) return {};
17
+ if (!isInitialized()) return {};
18
18
  const projectRoot = findProjectRoot();
19
19
  const repo = new SqliteKnowledgeRepository(getDbPath());
20
20
  try {
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  findAllClaudeTranscripts,
3
3
  findAllContinueTranscripts
4
- } from "./chunk-QGG25FWV.js";
4
+ } from "./chunk-OSFK44XC.js";
5
5
  import {
6
6
  getDbPath,
7
7
  getProjectId,
8
- isLoopInitialized
9
- } from "./chunk-VPEEZXLK.js";
8
+ isInitialized
9
+ } from "./chunk-WHP4Z32Z.js";
10
10
  import {
11
11
  SqliteKnowledgeRepository
12
12
  } from "./chunk-MMSMDJ4O.js";
@@ -25,7 +25,7 @@ function inferProjectRootFromTranscriptPath(path) {
25
25
  return dir;
26
26
  }
27
27
  async function handleSessionStart() {
28
- if (!isLoopInitialized()) return;
28
+ if (!isInitialized()) return;
29
29
  const repo = new SqliteKnowledgeRepository(getDbPath());
30
30
  const compiledPaths = new Set(repo.getAllCompiledTranscriptPaths());
31
31
  const allTranscripts = [
@@ -2,11 +2,12 @@ import {
2
2
  detectInstalledIdes,
3
3
  registerClaudeHooks,
4
4
  registerMcpServer
5
- } from "./chunk-7FIT3LNA.js";
5
+ } from "./chunk-LDQPTAZ7.js";
6
6
  import {
7
+ getDataDir,
7
8
  getDbPath,
8
- getGlobalLoopDir
9
- } from "./chunk-VPEEZXLK.js";
9
+ migrateFromLegacy
10
+ } from "./chunk-WHP4Z32Z.js";
10
11
  import {
11
12
  SqliteKnowledgeRepository
12
13
  } from "./chunk-MMSMDJ4O.js";
@@ -15,13 +16,14 @@ import "./chunk-MLKGABMK.js";
15
16
  // src/setup.ts
16
17
  import { mkdirSync } from "fs";
17
18
  async function runSetup() {
18
- mkdirSync(getGlobalLoopDir(), { recursive: true });
19
+ migrateFromLegacy();
20
+ mkdirSync(getDataDir(), { recursive: true });
19
21
  const repo = new SqliteKnowledgeRepository(getDbPath());
20
22
  repo.close();
21
23
  const ides = detectInstalledIdes();
22
24
  const detected = ides.filter((i) => i.detected);
23
25
  console.log(`
24
- Loop setup complete.`);
26
+ AgentCache setup complete.`);
25
27
  console.log(` Central DB: ${getDbPath()}
26
28
  `);
27
29
  if (detected.length === 0) {
@@ -40,7 +42,7 @@ Loop setup complete.`);
40
42
  Claude Code hooks: registered (Stop, SessionStart, PreToolUse)`);
41
43
  }
42
44
  console.log(`
43
- Done. Loop compiles knowledge across all sessions and IDEs.`);
45
+ Done. AgentCache compiles knowledge across all sessions and IDEs.`);
44
46
  }
45
47
  export {
46
48
  runSetup
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  findLatestTranscript
3
- } from "./chunk-QGG25FWV.js";
3
+ } from "./chunk-OSFK44XC.js";
4
4
  import {
5
5
  findProjectRoot,
6
6
  getDbPath,
7
7
  getProjectId,
8
- isLoopInitialized
9
- } from "./chunk-VPEEZXLK.js";
8
+ isInitialized
9
+ } from "./chunk-WHP4Z32Z.js";
10
10
  import {
11
11
  SqliteKnowledgeRepository
12
12
  } from "./chunk-MMSMDJ4O.js";
@@ -15,7 +15,7 @@ import "./chunk-MLKGABMK.js";
15
15
  // src/hooks/stop.ts
16
16
  import { randomUUID } from "crypto";
17
17
  async function handleStop(payload) {
18
- if (!isLoopInitialized()) return;
18
+ if (!isInitialized()) return;
19
19
  const transcriptPath = payload?.transcript_path || findLatestTranscript();
20
20
  if (!transcriptPath) return;
21
21
  const repo = new SqliteKnowledgeRepository(getDbPath());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentcache",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Knowledge cache for AI agents — learns how you work, remembers across sessions, works everywhere",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,207 +0,0 @@
1
- // src/utils/ide-detector.ts
2
- import { existsSync } from "fs";
3
- import { join } from "path";
4
- import { homedir } from "os";
5
- function getRooConfigPath() {
6
- const home = homedir();
7
- if (process.platform === "darwin") {
8
- return join(home, "Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json");
9
- }
10
- if (process.platform === "win32") {
11
- return join(process.env.APPDATA || join(home, "AppData/Roaming"), "Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json");
12
- }
13
- return join(home, ".config/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json");
14
- }
15
- function rooDetected() {
16
- return existsSync(getRooConfigPath());
17
- }
18
- function detectInstalledIdes() {
19
- const home = homedir();
20
- return [
21
- {
22
- name: "Claude Code",
23
- detected: existsSync(join(home, ".claude")),
24
- mcpConfigPath: join(home, ".claude.json"),
25
- mcpConfigFormat: "claude-settings"
26
- },
27
- {
28
- name: "Cursor",
29
- detected: existsSync(join(home, ".cursor")),
30
- mcpConfigPath: join(home, ".cursor", "mcp.json"),
31
- mcpConfigFormat: "mcp-json"
32
- },
33
- {
34
- name: "Roo Code",
35
- detected: rooDetected(),
36
- mcpConfigPath: getRooConfigPath(),
37
- mcpConfigFormat: "mcp-json"
38
- },
39
- {
40
- name: "Windsurf",
41
- detected: existsSync(join(home, ".windsurf")),
42
- mcpConfigPath: join(home, ".windsurf", "mcp.json"),
43
- mcpConfigFormat: "mcp-json"
44
- },
45
- {
46
- name: "Continue",
47
- detected: existsSync(join(home, ".continue")),
48
- mcpConfigPath: join(home, ".continue", "mcp.json"),
49
- mcpConfigFormat: "mcp-json"
50
- },
51
- {
52
- name: "Codex",
53
- detected: existsSync(join(home, ".codex")),
54
- mcpConfigPath: join(home, ".codex", "mcp.json"),
55
- mcpConfigFormat: "mcp-json"
56
- }
57
- ];
58
- }
59
-
60
- // src/utils/ide-registrar.ts
61
- import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
62
- import { join as join2, dirname } from "path";
63
- import { homedir as homedir2 } from "os";
64
- import { execSync } from "child_process";
65
- function findNodeBinary() {
66
- try {
67
- return execSync("which node", { encoding: "utf-8" }).trim();
68
- } catch {
69
- return "node";
70
- }
71
- }
72
- function findAgentcacheScript() {
73
- try {
74
- const binPath = execSync("which agentcache", { encoding: "utf-8" }).trim();
75
- return binPath;
76
- } catch {
77
- return join2(dirname(dirname(__dirname)), "dist", "cli.js");
78
- }
79
- }
80
- function isVscodeExtensionIde(ide) {
81
- return ide.name === "Roo Code";
82
- }
83
- var ALL_TOOLS = [
84
- "loop_inject_context",
85
- "loop_compile_submit",
86
- "loop_compile_cluster",
87
- "loop_compile_extract",
88
- "loop_enforce",
89
- "loop_save_observation",
90
- "loop_get_knowledge",
91
- "loop_deprecate_knowledge"
92
- ];
93
- function registerMcpServer(ide) {
94
- if (!ide.detected) return false;
95
- if (ide.mcpConfigFormat === "claude-settings") {
96
- const claudeJsonPath = join2(homedir2(), ".claude.json");
97
- let config = {};
98
- if (existsSync2(claudeJsonPath)) {
99
- try {
100
- config = JSON.parse(readFileSync(claudeJsonPath, "utf-8"));
101
- } catch {
102
- config = {};
103
- }
104
- }
105
- if (!config.mcpServers) config.mcpServers = {};
106
- if (config.mcpServers.agentcache) return false;
107
- config.mcpServers.agentcache = {
108
- type: "stdio",
109
- command: "agentcache",
110
- args: ["serve"],
111
- env: {}
112
- };
113
- writeFileSync(claudeJsonPath, JSON.stringify(config, null, 2));
114
- const settingsPath = join2(homedir2(), ".claude", "settings.json");
115
- if (existsSync2(join2(homedir2(), ".claude"))) {
116
- let settings = {};
117
- if (existsSync2(settingsPath)) {
118
- try {
119
- settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
120
- } catch {
121
- settings = {};
122
- }
123
- }
124
- if (!settings.permissions) settings.permissions = {};
125
- if (!settings.permissions.allow) settings.permissions.allow = [];
126
- const allowList = settings.permissions.allow;
127
- const mcpPerms = ALL_TOOLS.map((t) => `mcp__agentcache__${t}`);
128
- for (const perm of mcpPerms) {
129
- if (!allowList.includes(perm)) {
130
- allowList.push(perm);
131
- }
132
- }
133
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
134
- }
135
- return true;
136
- }
137
- if (ide.mcpConfigFormat === "mcp-json") {
138
- let config = {};
139
- if (existsSync2(ide.mcpConfigPath)) {
140
- try {
141
- config = JSON.parse(readFileSync(ide.mcpConfigPath, "utf-8"));
142
- } catch {
143
- config = {};
144
- }
145
- }
146
- if (!config.mcpServers) config.mcpServers = {};
147
- if (config.mcpServers.agentcache) return false;
148
- if (isVscodeExtensionIde(ide)) {
149
- const nodeBin = findNodeBinary();
150
- const script = findAgentcacheScript();
151
- config.mcpServers.agentcache = {
152
- command: nodeBin,
153
- args: [script, "serve"],
154
- alwaysAllow: ALL_TOOLS,
155
- disabled: false
156
- };
157
- } else {
158
- config.mcpServers.agentcache = {
159
- command: "agentcache",
160
- args: ["serve"]
161
- };
162
- }
163
- mkdirSync(dirname(ide.mcpConfigPath), { recursive: true });
164
- writeFileSync(ide.mcpConfigPath, JSON.stringify(config, null, 2));
165
- return true;
166
- }
167
- return false;
168
- }
169
- function registerClaudeHooks() {
170
- const settingsPath = join2(homedir2(), ".claude", "settings.json");
171
- if (!existsSync2(join2(homedir2(), ".claude"))) return false;
172
- let settings = {};
173
- if (existsSync2(settingsPath)) {
174
- try {
175
- settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
176
- } catch {
177
- settings = {};
178
- }
179
- }
180
- if (!settings.hooks) settings.hooks = {};
181
- const hooks = settings.hooks;
182
- const loopHooks = {
183
- Stop: [{ matcher: "", hooks: [{ type: "command", command: "agentcache compile-session" }] }],
184
- SessionStart: [{ matcher: "", hooks: [{ type: "command", command: "agentcache discover" }] }],
185
- PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: "agentcache enforce" }] }]
186
- };
187
- let registered = false;
188
- for (const [event, hookConfig] of Object.entries(loopHooks)) {
189
- if (!hooks[event]) hooks[event] = [];
190
- const existing = hooks[event];
191
- const hasLoop = existing.some((h) => h.hooks?.some((hh) => hh.command?.includes("agentcache")));
192
- if (!hasLoop) {
193
- hooks[event].push(...hookConfig);
194
- registered = true;
195
- }
196
- }
197
- if (registered) {
198
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
199
- }
200
- return registered;
201
- }
202
-
203
- export {
204
- detectInstalledIdes,
205
- registerMcpServer,
206
- registerClaudeHooks
207
- };