agentcache 0.4.2 → 0.5.0-beta.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.
Files changed (38) hide show
  1. package/README.md +282 -151
  2. package/dist/{chunk-T4COG3XD.js → chunk-R5I6WWSD.js} +31 -14
  3. package/dist/chunk-RXGW4Q3G.js +109 -0
  4. package/dist/chunk-XRJ6QW6N.js +92 -0
  5. package/dist/chunk-YKG6CDGT.js +1818 -0
  6. package/dist/chunk-YY7QXBG5.js +6610 -0
  7. package/dist/cli.js +2535 -292
  8. package/dist/device-id-RV7RO5RB.js +7 -0
  9. package/dist/ide-detector-ETGAVVXO.js +8 -0
  10. package/dist/mcp.d.ts +734 -2
  11. package/dist/mcp.js +1126 -446
  12. package/dist/{paths-5LZRKNYY.js → paths-NTZ2357O.js} +3 -2
  13. package/dist/postinstall.js +1 -65
  14. package/dist/setup-7JJPW3VG.js +48 -0
  15. package/docs/compatibility.md +152 -0
  16. package/docs/demo-script.md +121 -0
  17. package/docs/launch-copy.md +125 -0
  18. package/docs/privacy.md +173 -0
  19. package/docs/troubleshooting.md +209 -0
  20. package/package.json +32 -14
  21. package/dist/3-canonicalizer-HIN2F7SZ.js +0 -11
  22. package/dist/chunk-5UO7NJPQ.js +0 -71
  23. package/dist/chunk-CUBZRYS5.js +0 -580
  24. package/dist/chunk-GGAATZKM.js +0 -120
  25. package/dist/chunk-JUDLOBOC.js +0 -77
  26. package/dist/chunk-KFQGP6VL.js +0 -33
  27. package/dist/chunk-PSASDZQE.js +0 -490
  28. package/dist/chunk-SLRKWMSE.js +0 -202
  29. package/dist/chunk-T7BJPANN.js +0 -45
  30. package/dist/chunk-WTXSZBQE.js +0 -388
  31. package/dist/compile-all-PTWTZVP5.js +0 -495
  32. package/dist/ide-detector-5TRCR4F5.js +0 -7
  33. package/dist/pre-tool-use-A4AJHZOJ.js +0 -30
  34. package/dist/session-start-DGMGEAJU.js +0 -78
  35. package/dist/setup-CVG35TUZ.js +0 -51
  36. package/dist/sqlite-NM2BVHUY.js +0 -7
  37. package/dist/stop-WGGRX6TQ.js +0 -38
  38. package/dist/transcript-JWSGSDSF.js +0 -24
@@ -0,0 +1,1818 @@
1
+ import {
2
+ resolveGooseConfigPath
3
+ } from "./chunk-RXGW4Q3G.js";
4
+
5
+ // src/utils/ide-registrar.ts
6
+ import {
7
+ closeSync as closeSync2,
8
+ constants as fsConstants2,
9
+ fstatSync as fstatSync2,
10
+ lstatSync as lstatSync2,
11
+ openSync as openSync2,
12
+ readSync as readSync2,
13
+ readlinkSync,
14
+ realpathSync,
15
+ statSync
16
+ } from "fs";
17
+ import { dirname as dirname2, isAbsolute, join as join2, resolve as resolve2 } from "path";
18
+ import { homedir } from "os";
19
+ import { TextDecoder } from "util";
20
+ import { parse as parseToml } from "smol-toml";
21
+ import { isMap, isScalar, isSeq, parseDocument } from "yaml";
22
+
23
+ // src/utils/atomic-file.ts
24
+ import {
25
+ accessSync,
26
+ closeSync,
27
+ constants as fsConstants,
28
+ fchmodSync,
29
+ fstatSync,
30
+ fsyncSync,
31
+ linkSync,
32
+ lstatSync,
33
+ mkdirSync,
34
+ openSync,
35
+ readSync,
36
+ renameSync,
37
+ unlinkSync,
38
+ writeFileSync
39
+ } from "fs";
40
+ import { randomUUID } from "crypto";
41
+ import { basename, dirname, join, resolve } from "path";
42
+ function missingPath(error) {
43
+ return error?.code === "ENOENT";
44
+ }
45
+ function sameFileIdentity(left, right) {
46
+ return left.dev === right.dev && left.ino === right.ino;
47
+ }
48
+ function removeIfPresent(path) {
49
+ try {
50
+ unlinkSync(path);
51
+ } catch (error) {
52
+ if (!missingPath(error)) throw error;
53
+ }
54
+ }
55
+ function safeDirectory(path) {
56
+ try {
57
+ const metadata = lstatSync(path);
58
+ if (metadata.isSymbolicLink() || !metadata.isDirectory()) return void 0;
59
+ return { path, identity: { dev: metadata.dev, ino: metadata.ino } };
60
+ } catch {
61
+ return void 0;
62
+ }
63
+ }
64
+ function currentUserControlsDirectory(path, metadata) {
65
+ const root = dirname(path) === path;
66
+ if (root) return false;
67
+ const uid = process.getuid?.();
68
+ if (uid !== void 0 && (uid === 0 || metadata.uid === uid)) return true;
69
+ try {
70
+ accessSync(path, fsConstants.W_OK);
71
+ return true;
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+ function inspectSafeDirectoryChain(path, createMissing) {
77
+ const pending = [];
78
+ const chain = [];
79
+ let cursor = resolve(path);
80
+ let protectedDepth = 0;
81
+ while (true) {
82
+ let metadata;
83
+ try {
84
+ metadata = lstatSync(cursor);
85
+ } catch (error) {
86
+ if (!missingPath(error)) return void 0;
87
+ pending.push(cursor);
88
+ const ancestor2 = dirname(cursor);
89
+ if (ancestor2 === cursor) return void 0;
90
+ cursor = ancestor2;
91
+ continue;
92
+ }
93
+ if (metadata.isSymbolicLink() || !metadata.isDirectory()) return void 0;
94
+ chain.push({ path: cursor, identity: { dev: metadata.dev, ino: metadata.ino } });
95
+ protectedDepth = currentUserControlsDirectory(cursor, metadata) ? 0 : protectedDepth + 1;
96
+ const ancestor = dirname(cursor);
97
+ if (ancestor === cursor || protectedDepth >= 2) break;
98
+ cursor = ancestor;
99
+ }
100
+ if (!createMissing) return chain;
101
+ for (const missingDirectory of pending.reverse()) {
102
+ if (!directoryChainMatches(chain)) return void 0;
103
+ try {
104
+ mkdirSync(missingDirectory, { mode: 448 });
105
+ } catch (error) {
106
+ if (error?.code !== "EEXIST") return void 0;
107
+ }
108
+ const created = safeDirectory(missingDirectory);
109
+ if (!created || !directoryChainMatches(chain)) return void 0;
110
+ chain.push(created);
111
+ }
112
+ return chain;
113
+ }
114
+ function atomicWritePathIsSafe(filePath) {
115
+ return inspectSafeDirectoryChain(dirname(resolve(filePath)), false) !== void 0;
116
+ }
117
+ function directoryChainMatches(chain) {
118
+ return chain.every(({ path, identity }) => {
119
+ const current = safeDirectory(path);
120
+ return !!current && sameFileIdentity(current.identity, identity);
121
+ });
122
+ }
123
+ function atomicWriteExpectationMatches(filePath, expected) {
124
+ if (!expected.existed) {
125
+ try {
126
+ lstatSync(filePath);
127
+ return false;
128
+ } catch (error) {
129
+ return missingPath(error);
130
+ }
131
+ }
132
+ const openFlags = fsConstants.O_RDONLY | (typeof fsConstants.O_NONBLOCK === "number" ? fsConstants.O_NONBLOCK : 0) | (typeof fsConstants.O_NOFOLLOW === "number" ? fsConstants.O_NOFOLLOW : 0);
133
+ let descriptor;
134
+ try {
135
+ descriptor = openSync(filePath, openFlags);
136
+ const opened = fstatSync(descriptor);
137
+ const pathAtOpen = lstatSync(filePath);
138
+ if (!opened.isFile() || opened.size !== expected.contents.length || !sameFileIdentity(opened, expected.identity) || pathAtOpen.isSymbolicLink() || !pathAtOpen.isFile() || !sameFileIdentity(pathAtOpen, expected.identity)) {
139
+ return false;
140
+ }
141
+ const actual = Buffer.alloc(expected.contents.length);
142
+ let length = 0;
143
+ while (length < actual.length) {
144
+ const read = readSync(descriptor, actual, length, actual.length - length, length);
145
+ if (read === 0) break;
146
+ length += read;
147
+ }
148
+ const completed = fstatSync(descriptor);
149
+ const finalPath = lstatSync(filePath);
150
+ return length === expected.contents.length && actual.equals(expected.contents) && completed.isFile() && completed.size === expected.contents.length && sameFileIdentity(completed, expected.identity) && finalPath.isFile() && !finalPath.isSymbolicLink() && sameFileIdentity(finalPath, expected.identity);
151
+ } catch {
152
+ return false;
153
+ } finally {
154
+ if (descriptor !== void 0) closeSync(descriptor);
155
+ }
156
+ }
157
+ function syncDirectory(path) {
158
+ let fd;
159
+ try {
160
+ fd = openSync(path, "r");
161
+ fsyncSync(fd);
162
+ } catch {
163
+ } finally {
164
+ if (fd !== void 0) closeSync(fd);
165
+ }
166
+ }
167
+ function writeRestrictiveFile(path, contents) {
168
+ const fd = openSync(path, "wx", 384);
169
+ let failure;
170
+ try {
171
+ fchmodSync(fd, 384);
172
+ writeFileSync(fd, contents);
173
+ fsyncSync(fd);
174
+ } catch (error) {
175
+ failure = error;
176
+ } finally {
177
+ try {
178
+ closeSync(fd);
179
+ } catch (error) {
180
+ failure ??= error;
181
+ }
182
+ }
183
+ if (failure !== void 0) {
184
+ removeIfPresent(path);
185
+ throw failure;
186
+ }
187
+ }
188
+ function atomicOperationUnsupported(error) {
189
+ const code = error?.code;
190
+ return code === "ENOTSUP" || code === "EOPNOTSUPP" || code === "ENOSYS" || code === "EXDEV" || code === "EPERM" || code === "EACCES" || code === "EROFS";
191
+ }
192
+ function replacementConflict(error) {
193
+ const code = error?.code;
194
+ return code === "EEXIST" || code === "EISDIR" || code === "ENOTDIR" || code === "ENOTEMPTY" || code === "ENOENT";
195
+ }
196
+ function publishNoReplace(sourcePath, targetPath) {
197
+ try {
198
+ linkSync(sourcePath, targetPath);
199
+ return "published";
200
+ } catch (error) {
201
+ if (error?.code === "EEXIST") return "conflict";
202
+ if (atomicOperationUnsupported(error)) return "unsupported";
203
+ throw error;
204
+ }
205
+ }
206
+ function atomicWriteFileSync(filePath, contents, expected) {
207
+ const targetPath = resolve(filePath);
208
+ const next = Buffer.isBuffer(contents) ? contents : Buffer.from(contents, "utf8");
209
+ const parent = dirname(targetPath);
210
+ const directoryChain = inspectSafeDirectoryChain(parent, true);
211
+ if (!directoryChain) return { changed: false, conflict: true };
212
+ if (!atomicWriteExpectationMatches(targetPath, expected)) {
213
+ return { changed: false, conflict: true };
214
+ }
215
+ if (expected.existed && expected.contents.equals(next)) return { changed: false };
216
+ let backupPath;
217
+ const temporaryPath = join(
218
+ parent,
219
+ `.${basename(targetPath)}.agentcache-${process.pid}-${randomUUID()}.tmp`
220
+ );
221
+ try {
222
+ if (!directoryChainMatches(directoryChain)) {
223
+ return { changed: false, conflict: true };
224
+ }
225
+ writeRestrictiveFile(temporaryPath, next);
226
+ if (!directoryChainMatches(directoryChain) || !atomicWriteExpectationMatches(targetPath, expected)) {
227
+ removeIfPresent(temporaryPath);
228
+ return { changed: false, conflict: true };
229
+ }
230
+ if (expected.existed) {
231
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
232
+ backupPath = `${targetPath}.agentcache-backup-${timestamp}-${randomUUID()}`;
233
+ writeRestrictiveFile(backupPath, expected.contents);
234
+ syncDirectory(parent);
235
+ if (!directoryChainMatches(directoryChain) || !atomicWriteExpectationMatches(targetPath, expected)) {
236
+ removeIfPresent(temporaryPath);
237
+ removeIfPresent(backupPath);
238
+ return { changed: false, conflict: true };
239
+ }
240
+ }
241
+ if (!directoryChainMatches(directoryChain) || !atomicWriteExpectationMatches(targetPath, expected)) {
242
+ removeIfPresent(temporaryPath);
243
+ if (backupPath) removeIfPresent(backupPath);
244
+ return { changed: false, conflict: true };
245
+ }
246
+ if (expected.existed) {
247
+ try {
248
+ renameSync(temporaryPath, targetPath);
249
+ } catch (error) {
250
+ removeIfPresent(temporaryPath);
251
+ if (backupPath) removeIfPresent(backupPath);
252
+ if (!atomicWriteExpectationMatches(targetPath, expected)) {
253
+ return { changed: false, conflict: true };
254
+ }
255
+ if (atomicOperationUnsupported(error)) {
256
+ return { changed: false, unsupported: true };
257
+ }
258
+ if (replacementConflict(error)) {
259
+ return { changed: false, conflict: true };
260
+ }
261
+ throw error;
262
+ }
263
+ } else {
264
+ const publication = publishNoReplace(temporaryPath, targetPath);
265
+ if (publication !== "published") {
266
+ removeIfPresent(temporaryPath);
267
+ return publication === "unsupported" ? { changed: false, unsupported: true } : { changed: false, conflict: true };
268
+ }
269
+ removeIfPresent(temporaryPath);
270
+ }
271
+ syncDirectory(parent);
272
+ } catch (error) {
273
+ removeIfPresent(temporaryPath);
274
+ if (backupPath) removeIfPresent(backupPath);
275
+ if (atomicOperationUnsupported(error)) {
276
+ return { changed: false, unsupported: true };
277
+ }
278
+ throw error;
279
+ }
280
+ return { changed: true, backupPath };
281
+ }
282
+ function atomicRemoveFileSync(filePath, expected) {
283
+ const targetPath = resolve(filePath);
284
+ const parent = dirname(targetPath);
285
+ const directoryChain = inspectSafeDirectoryChain(parent, false);
286
+ if (!directoryChain || !directoryChainMatches(directoryChain) || !atomicWriteExpectationMatches(targetPath, expected)) {
287
+ return { changed: false, conflict: true };
288
+ }
289
+ try {
290
+ if (!directoryChainMatches(directoryChain) || !atomicWriteExpectationMatches(targetPath, expected)) {
291
+ return { changed: false, conflict: true };
292
+ }
293
+ unlinkSync(targetPath);
294
+ syncDirectory(parent);
295
+ return { changed: true };
296
+ } catch (error) {
297
+ if (atomicOperationUnsupported(error)) return { changed: false, unsupported: true };
298
+ if (replacementConflict(error)) return { changed: false, conflict: true };
299
+ throw error;
300
+ }
301
+ }
302
+
303
+ // src/utils/ide-registrar.ts
304
+ var CODEX_MARKER_START = "# >>> AgentCache managed MCP server >>>";
305
+ var CODEX_MARKER_END = "# <<< AgentCache managed MCP server <<<";
306
+ var MAX_REGISTRATION_CONFIG_BYTES = 1024 * 1024;
307
+ var FATAL_UTF8_DECODER = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false });
308
+ var JSON_NUMBER_TOKEN = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
309
+ var registrationCliEntrypoint;
310
+ function configureMcpRegistrationEntrypoint(entrypoint) {
311
+ if (!isAbsolute(entrypoint)) {
312
+ throw new Error("AgentCache registration CLI entrypoint must be absolute");
313
+ }
314
+ const resolved = realpathSync(entrypoint);
315
+ if (!statSync(resolved).isFile()) {
316
+ throw new Error("AgentCache registration CLI entrypoint must be a file");
317
+ }
318
+ registrationCliEntrypoint = resolved;
319
+ }
320
+ function isVscodeExtensionIde(ide) {
321
+ return ide.adapterId === "roo-code" || ide.adapterId === "continue";
322
+ }
323
+ function sameFileIdentity2(left, right) {
324
+ return left.dev === right.dev && left.ino === right.ino;
325
+ }
326
+ function missingPath2(error) {
327
+ return error?.code === "ENOENT";
328
+ }
329
+ function pathIsStillMissing(path, openError) {
330
+ if (!missingPath2(openError)) return false;
331
+ try {
332
+ lstatSync2(path);
333
+ return false;
334
+ } catch (error) {
335
+ return missingPath2(error);
336
+ }
337
+ }
338
+ function readBoundedRegistrationFile(path, allowMissing = false) {
339
+ if (!atomicWritePathIsSafe(path)) return void 0;
340
+ const openFlags = fsConstants2.O_RDONLY | (typeof fsConstants2.O_NONBLOCK === "number" ? fsConstants2.O_NONBLOCK : 0) | (typeof fsConstants2.O_NOFOLLOW === "number" ? fsConstants2.O_NOFOLLOW : 0);
341
+ let descriptor;
342
+ try {
343
+ descriptor = openSync2(path, openFlags);
344
+ const opened = fstatSync2(descriptor);
345
+ if (!opened.isFile() || opened.size > MAX_REGISTRATION_CONFIG_BYTES) return void 0;
346
+ const pathAtOpen = lstatSync2(path);
347
+ if (pathAtOpen.isSymbolicLink() || !pathAtOpen.isFile() || !sameFileIdentity2(opened, pathAtOpen)) {
348
+ return void 0;
349
+ }
350
+ const bytes = Buffer.alloc(MAX_REGISTRATION_CONFIG_BYTES + 1);
351
+ let length = 0;
352
+ while (length < bytes.length) {
353
+ const read = readSync2(descriptor, bytes, length, bytes.length - length, length);
354
+ if (read === 0) break;
355
+ length += read;
356
+ }
357
+ if (length > MAX_REGISTRATION_CONFIG_BYTES) return void 0;
358
+ const completed = fstatSync2(descriptor);
359
+ const finalPath = lstatSync2(path);
360
+ if (!completed.isFile() || completed.size !== opened.size || completed.mtimeMs !== opened.mtimeMs || completed.ctimeMs !== opened.ctimeMs || !sameFileIdentity2(opened, completed) || finalPath.isSymbolicLink() || !finalPath.isFile() || !sameFileIdentity2(opened, finalPath)) {
361
+ return void 0;
362
+ }
363
+ const contents = Buffer.from(bytes.subarray(0, length));
364
+ const content = FATAL_UTF8_DECODER.decode(contents);
365
+ return {
366
+ content,
367
+ expectation: {
368
+ existed: true,
369
+ contents,
370
+ identity: { dev: opened.dev, ino: opened.ino }
371
+ }
372
+ };
373
+ } catch (error) {
374
+ if (allowMissing && pathIsStillMissing(path, error)) {
375
+ return { content: "", expectation: { existed: false } };
376
+ }
377
+ return void 0;
378
+ } finally {
379
+ if (descriptor !== void 0) {
380
+ try {
381
+ closeSync2(descriptor);
382
+ } catch {
383
+ }
384
+ }
385
+ }
386
+ }
387
+ function parseJsonFile(path) {
388
+ const file = readBoundedRegistrationFile(path, true);
389
+ if (!file) return void 0;
390
+ if (!file.expectation.existed) return { config: {}, file };
391
+ if (hasLossyJsonNumber(file.content) || jsonHasDuplicateKeys(file.content) !== false) {
392
+ return void 0;
393
+ }
394
+ try {
395
+ const parsed = JSON.parse(file.content);
396
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
397
+ return { config: parsed, file };
398
+ } catch {
399
+ return void 0;
400
+ }
401
+ }
402
+ function normalizeJsonNumber(token) {
403
+ const match = token.match(
404
+ /^(-?)(0|[1-9]\d*)(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/
405
+ );
406
+ if (!match) return void 0;
407
+ const fraction = match[3] ?? "";
408
+ let digits = `${match[2]}${fraction}`.replace(/^0+/, "");
409
+ if (digits.length === 0) return { negative: false, digits: "0", exponent: 0 };
410
+ const explicitExponent = Number(match[4] ?? "0");
411
+ if (!Number.isSafeInteger(explicitExponent)) return void 0;
412
+ let exponent = explicitExponent - fraction.length;
413
+ while (digits.endsWith("0")) {
414
+ digits = digits.slice(0, -1);
415
+ exponent += 1;
416
+ }
417
+ return { negative: match[1] === "-", digits, exponent };
418
+ }
419
+ function sameNormalizedJsonNumber(left, right) {
420
+ const normalizedLeft = normalizeJsonNumber(left);
421
+ const normalizedRight = normalizeJsonNumber(right);
422
+ return !!normalizedLeft && !!normalizedRight && normalizedLeft.negative === normalizedRight.negative && normalizedLeft.digits === normalizedRight.digits && normalizedLeft.exponent === normalizedRight.exponent;
423
+ }
424
+ function jsonNumberTokenAt(content, index) {
425
+ JSON_NUMBER_TOKEN.lastIndex = index;
426
+ return JSON_NUMBER_TOKEN.exec(content)?.[0];
427
+ }
428
+ function hasLossyJsonNumber(content) {
429
+ let inString = false;
430
+ let escaped = false;
431
+ for (let index = 0; index < content.length; index += 1) {
432
+ const character = content[index];
433
+ if (inString) {
434
+ if (escaped) escaped = false;
435
+ else if (character === "\\") escaped = true;
436
+ else if (character === '"') inString = false;
437
+ continue;
438
+ }
439
+ if (character === '"') {
440
+ inString = true;
441
+ continue;
442
+ }
443
+ if (character !== "-" && (character < "0" || character > "9")) continue;
444
+ const token = jsonNumberTokenAt(content, index);
445
+ if (!token) continue;
446
+ const value = Number(token);
447
+ if (!Number.isFinite(value) || Object.is(value, -0) || !sameNormalizedJsonNumber(token, value.toString())) {
448
+ return true;
449
+ }
450
+ index += token.length - 1;
451
+ }
452
+ return false;
453
+ }
454
+ function jsonHasDuplicateKeys(content) {
455
+ let index = 0;
456
+ let duplicate = false;
457
+ const skipWhitespace = () => {
458
+ while (content[index] === " " || content[index] === " " || content[index] === "\r" || content[index] === "\n") {
459
+ index += 1;
460
+ }
461
+ };
462
+ const parseStringToken = () => {
463
+ if (content[index] !== '"') return void 0;
464
+ const start = index;
465
+ index += 1;
466
+ while (index < content.length) {
467
+ const character = content[index];
468
+ if (character === '"') {
469
+ index += 1;
470
+ try {
471
+ const decoded = JSON.parse(content.slice(start, index));
472
+ return typeof decoded === "string" ? decoded : void 0;
473
+ } catch {
474
+ return void 0;
475
+ }
476
+ }
477
+ if (character.charCodeAt(0) < 32) return void 0;
478
+ if (character === "\\") {
479
+ index += 1;
480
+ const escape = content[index];
481
+ if (escape === "u") {
482
+ if (!/^[0-9a-fA-F]{4}$/.test(content.slice(index + 1, index + 5))) {
483
+ return void 0;
484
+ }
485
+ index += 5;
486
+ continue;
487
+ }
488
+ if (!escape || !'"\\/bfnrt'.includes(escape)) return void 0;
489
+ }
490
+ index += 1;
491
+ }
492
+ return void 0;
493
+ };
494
+ const parseNumberToken = () => {
495
+ const token = jsonNumberTokenAt(content, index);
496
+ if (!token) return false;
497
+ index += token.length;
498
+ return true;
499
+ };
500
+ const parseValue = () => {
501
+ skipWhitespace();
502
+ if (content[index] === "{") return parseObject();
503
+ if (content[index] === "[") return parseArray();
504
+ if (content[index] === '"') return parseStringToken() !== void 0;
505
+ for (const literal of ["true", "false", "null"]) {
506
+ if (content.startsWith(literal, index)) {
507
+ index += literal.length;
508
+ return true;
509
+ }
510
+ }
511
+ return parseNumberToken();
512
+ };
513
+ const parseObject = () => {
514
+ index += 1;
515
+ skipWhitespace();
516
+ if (content[index] === "}") {
517
+ index += 1;
518
+ return true;
519
+ }
520
+ const keys = /* @__PURE__ */ new Set();
521
+ while (index < content.length) {
522
+ skipWhitespace();
523
+ const key = parseStringToken();
524
+ if (key === void 0) return false;
525
+ if (keys.has(key)) duplicate = true;
526
+ keys.add(key);
527
+ skipWhitespace();
528
+ if (content[index] !== ":") return false;
529
+ index += 1;
530
+ if (!parseValue()) return false;
531
+ skipWhitespace();
532
+ if (content[index] === "}") {
533
+ index += 1;
534
+ return true;
535
+ }
536
+ if (content[index] !== ",") return false;
537
+ index += 1;
538
+ }
539
+ return false;
540
+ };
541
+ const parseArray = () => {
542
+ index += 1;
543
+ skipWhitespace();
544
+ if (content[index] === "]") {
545
+ index += 1;
546
+ return true;
547
+ }
548
+ while (index < content.length) {
549
+ if (!parseValue()) return false;
550
+ skipWhitespace();
551
+ if (content[index] === "]") {
552
+ index += 1;
553
+ return true;
554
+ }
555
+ if (content[index] !== ",") return false;
556
+ index += 1;
557
+ }
558
+ return false;
559
+ };
560
+ try {
561
+ if (!parseValue()) return void 0;
562
+ skipWhitespace();
563
+ return index === content.length ? duplicate : void 0;
564
+ } catch {
565
+ return void 0;
566
+ }
567
+ }
568
+ function parseJsonFileForInspection(path) {
569
+ const file = readBoundedRegistrationFile(path);
570
+ if (!file) return void 0;
571
+ if (hasLossyJsonNumber(file.content) || jsonHasDuplicateKeys(file.content) !== false) {
572
+ return void 0;
573
+ }
574
+ try {
575
+ const parsed = JSON.parse(file.content);
576
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
577
+ return parsed;
578
+ } catch {
579
+ return void 0;
580
+ }
581
+ }
582
+ function writeJson(path, value, expectation) {
583
+ return atomicWriteFileSync(path, jsonFileContents(value), expectation);
584
+ }
585
+ function jsonFileContents(value) {
586
+ return `${JSON.stringify(value, null, 2)}
587
+ `;
588
+ }
589
+ function registrationResult(changed) {
590
+ return { status: changed ? "registered" : "unchanged" };
591
+ }
592
+ function skipped(reason) {
593
+ return { status: "skipped", reason };
594
+ }
595
+ function registrationWriteResult(result, conflictReason, unsupportedReason = "safe atomic config publication unsupported") {
596
+ if (result.conflict) return skipped(conflictReason);
597
+ if (result.unsupported) return skipped(unsupportedReason);
598
+ return registrationResult(result.changed);
599
+ }
600
+ function isValidToml(content) {
601
+ try {
602
+ parseToml(content);
603
+ return true;
604
+ } catch {
605
+ return false;
606
+ }
607
+ }
608
+ function validServerMap(value) {
609
+ return value === void 0 || !!value && typeof value === "object" && !Array.isArray(value);
610
+ }
611
+ function isJsonObject(value) {
612
+ return !!value && typeof value === "object" && !Array.isArray(value);
613
+ }
614
+ function argsEqual(value, expected) {
615
+ return Array.isArray(value) && value.length === expected.length && value.every((item, index) => item === expected[index]);
616
+ }
617
+ function serverCoreMatches(value, expected) {
618
+ return isJsonObject(value) && value.command === expected.command && argsEqual(value.args, expected.args);
619
+ }
620
+ function executableBasename(value) {
621
+ if (typeof value !== "string") return void 0;
622
+ return value.trim().split(/[/\\]/).pop()?.toLowerCase();
623
+ }
624
+ function isAgentCacheExecutable(value) {
625
+ return ["agentcache", "agentcache.cmd", "agentcache.exe"].includes(
626
+ executableBasename(value) ?? ""
627
+ );
628
+ }
629
+ function isPortableAbsolutePath(value) {
630
+ return isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || /^\\\\/.test(value);
631
+ }
632
+ function isRecognizedAgentCacheScript(value) {
633
+ if (typeof value !== "string" || !isPortableAbsolutePath(value)) return false;
634
+ if (isAgentCacheExecutable(value)) return true;
635
+ const segments = value.toLowerCase().split(/[/\\]+/).filter(Boolean);
636
+ return segments.length >= 3 && segments.at(-3) === "agentcache" && segments.at(-2) === "dist" && segments.at(-1) === "cli.js";
637
+ }
638
+ function isAgentCacheNodeWrapper(value, ide) {
639
+ if (!isJsonObject(value) || typeof value.command !== "string" || !isPortableAbsolutePath(value.command) || !["node", "node.exe"].includes(executableBasename(value.command) ?? "")) {
640
+ return false;
641
+ }
642
+ if (!Array.isArray(value.args) || value.args.length < 2) return false;
643
+ const script = value.args[0];
644
+ if (!isRecognizedAgentCacheScript(script)) return false;
645
+ return argsEqual(value.args.slice(1), ["serve"]) || argsEqual(value.args.slice(1), boundServeArgs(ide));
646
+ }
647
+ function registrationOwnership(config, ide, desired) {
648
+ if (!config.mcpServers || !Object.hasOwn(config.mcpServers, "agentcache")) {
649
+ return "absent";
650
+ }
651
+ const existing = config.mcpServers.agentcache;
652
+ if (serverCoreMatches(existing, desired)) return "owned";
653
+ if (serverCoreMatches(existing, { command: "agentcache", args: ["serve"] })) {
654
+ return "owned";
655
+ }
656
+ if (isJsonObject(existing) && isAgentCacheExecutable(existing.command) && (argsEqual(existing.args, ["serve"]) || argsEqual(existing.args, boundServeArgs(ide)))) {
657
+ return "owned";
658
+ }
659
+ if (isAgentCacheNodeWrapper(existing, ide)) {
660
+ return "owned";
661
+ }
662
+ return "ambiguous";
663
+ }
664
+ function legacyLoopRegistrationOwnership(config) {
665
+ const servers = config.mcpServers;
666
+ if (!servers || !isJsonObject(servers) || !Object.hasOwn(servers, "loop")) {
667
+ return "absent";
668
+ }
669
+ const existing = servers.loop;
670
+ if (isJsonObject(existing) && ["loop-eng", "loop-eng.cmd", "loop-eng.exe"].includes(
671
+ executableBasename(existing.command) ?? ""
672
+ ) && argsEqual(existing.args, ["serve"])) {
673
+ return "owned";
674
+ }
675
+ return "ambiguous";
676
+ }
677
+ function legacyClaudeSettingsRegistrationOwnership(settings, key) {
678
+ const servers = settings.mcpServers;
679
+ if (!servers || !isJsonObject(servers) || !Object.hasOwn(servers, key)) {
680
+ return "absent";
681
+ }
682
+ const existing = servers[key];
683
+ if (!isJsonObject(existing) || !argsEqual(existing.args, ["serve"])) {
684
+ return "ambiguous";
685
+ }
686
+ if (key === "loop") {
687
+ return ["loop-eng", "loop-eng.cmd", "loop-eng.exe"].includes(
688
+ executableBasename(existing.command) ?? ""
689
+ ) ? "owned" : "ambiguous";
690
+ }
691
+ return isAgentCacheExecutable(existing.command) ? "owned" : "ambiguous";
692
+ }
693
+ var RELEASED_LEGACY_HOOKS = Object.freeze({
694
+ Stop: "compile-session",
695
+ SessionStart: "discover",
696
+ PreToolUse: "enforce"
697
+ });
698
+ function releasedLegacyHookSubcommand(command) {
699
+ if (typeof command !== "string") return void 0;
700
+ const value = command.trim();
701
+ const parts = value.split(/[ \t]+/);
702
+ if (parts.length !== 2) return void 0;
703
+ const executable = executableBasename(parts[0]);
704
+ const subcommand = parts[1];
705
+ if (![
706
+ "agentcache",
707
+ "agentcache.cmd",
708
+ "agentcache.exe",
709
+ "loop-eng",
710
+ "loop-eng.cmd",
711
+ "loop-eng.exe"
712
+ ].includes(executable ?? "")) return void 0;
713
+ return ["compile-session", "discover", "enforce"].includes(subcommand) ? subcommand : void 0;
714
+ }
715
+ function isReleasedLegacyHookEntry(event, entry) {
716
+ const expectedSubcommand = RELEASED_LEGACY_HOOKS[event];
717
+ if (!expectedSubcommand || !isJsonObject(entry)) return false;
718
+ if (Object.keys(entry).sort().join("\0") !== ["hooks", "matcher"].join("\0")) return false;
719
+ if (entry.matcher !== "" || !Array.isArray(entry.hooks) || entry.hooks.length !== 1) return false;
720
+ const hook = entry.hooks[0];
721
+ if (!isJsonObject(hook)) return false;
722
+ if (Object.keys(hook).sort().join("\0") !== ["command", "type"].join("\0")) return false;
723
+ return hook.type === "command" && releasedLegacyHookSubcommand(hook.command) === expectedSubcommand;
724
+ }
725
+ function legacyClaudeHookOwnership(settings) {
726
+ if (!isJsonObject(settings)) return "ambiguous";
727
+ const hooks = settings.hooks;
728
+ if (hooks === void 0) return "absent";
729
+ if (!isJsonObject(hooks)) return "ambiguous";
730
+ let owned = false;
731
+ for (const [event, entries] of Object.entries(hooks)) {
732
+ if (!Array.isArray(entries)) return "ambiguous";
733
+ for (const entry of entries) {
734
+ if (!isJsonObject(entry)) return "ambiguous";
735
+ if (entry.hooks === void 0) continue;
736
+ if (!Array.isArray(entry.hooks)) return "ambiguous";
737
+ for (const hook of entry.hooks) {
738
+ if (!isJsonObject(hook)) return "ambiguous";
739
+ }
740
+ if (isReleasedLegacyHookEntry(event, entry)) {
741
+ owned = true;
742
+ continue;
743
+ }
744
+ if (entry.hooks.some((hook) => releasedLegacyHookSubcommand(hook.command) !== void 0)) {
745
+ return "ambiguous";
746
+ }
747
+ }
748
+ }
749
+ return owned ? "owned" : "absent";
750
+ }
751
+ function inspectLegacyClaudeHookPresence(settings) {
752
+ const ownership = legacyClaudeHookOwnership(settings);
753
+ return ownership === "owned" ? "present" : ownership === "absent" ? "absent" : "unknown";
754
+ }
755
+ function removeAgentCacheHooks(settings) {
756
+ if (!settings.hooks || typeof settings.hooks !== "object" || Array.isArray(settings.hooks)) {
757
+ return false;
758
+ }
759
+ if (legacyClaudeHookOwnership(settings) === "ambiguous") return false;
760
+ let changed = false;
761
+ for (const [event, entriesValue] of Object.entries(settings.hooks)) {
762
+ if (!Array.isArray(entriesValue)) continue;
763
+ const entries = entriesValue.filter((entry) => {
764
+ if (!isReleasedLegacyHookEntry(event, entry)) return true;
765
+ changed = true;
766
+ return false;
767
+ });
768
+ if (entries.length > 0) settings.hooks[event] = entries;
769
+ else delete settings.hooks[event];
770
+ }
771
+ if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
772
+ return changed;
773
+ }
774
+ function removeAgentCachePermissions(settings) {
775
+ const permissions = settings.permissions;
776
+ if (!permissions || typeof permissions !== "object" || Array.isArray(permissions)) return false;
777
+ if (!Array.isArray(permissions.allow)) return false;
778
+ const filtered = permissions.allow.filter(
779
+ (permission) => typeof permission !== "string" || !permission.startsWith("mcp__agentcache__")
780
+ );
781
+ if (filtered.length === permissions.allow.length) return false;
782
+ permissions.allow = filtered;
783
+ return true;
784
+ }
785
+ function removeLegacyClaudeMcpServers(settings) {
786
+ const servers = settings.mcpServers;
787
+ if (!isJsonObject(servers)) return false;
788
+ let changed = false;
789
+ for (const key of ["loop", "agentcache"]) {
790
+ if (legacyClaudeSettingsRegistrationOwnership(settings, key) !== "owned") continue;
791
+ delete servers[key];
792
+ changed = true;
793
+ }
794
+ if (Object.keys(servers).length === 0) delete settings.mcpServers;
795
+ return changed;
796
+ }
797
+ function hasLegacyClaudeSettings(settings) {
798
+ const permissions = settings.permissions;
799
+ const permissionsPresent = permissions !== void 0 && (!isJsonObject(permissions) || permissions.allow !== void 0 && (!Array.isArray(permissions.allow) || permissions.allow.some(
800
+ (permission) => typeof permission === "string" && permission.startsWith("mcp__agentcache__")
801
+ )));
802
+ const hooksPresent = legacyClaudeHookOwnership(settings) !== "absent";
803
+ const servers = settings.mcpServers;
804
+ const registrationsPresent = servers !== void 0 && (!isJsonObject(servers) || ["loop", "agentcache"].some(
805
+ (key) => legacyClaudeSettingsRegistrationOwnership(settings, key) !== "absent"
806
+ ));
807
+ return permissionsPresent || hooksPresent || registrationsPresent;
808
+ }
809
+ function restorePublishedFile(path, publishedContents, original) {
810
+ const published = readBoundedRegistrationFile(path);
811
+ if (!published?.expectation.existed || !published.expectation.contents.equals(publishedContents)) {
812
+ return false;
813
+ }
814
+ const restore = original.existed ? atomicWriteFileSync(path, original.contents, published.expectation) : atomicRemoveFileSync(path, published.expectation);
815
+ return !restore.conflict && !restore.unsupported && restore.changed;
816
+ }
817
+ function claudeSettingsPath(ide) {
818
+ const home = ide ? dirname2(ide.mcpConfigPath) : homedir();
819
+ return join2(home, ".claude", "settings.json");
820
+ }
821
+ function registerMcpServer(ide, options = {}) {
822
+ const obsolete = cleanOneObsoleteJsonRegistration(ide);
823
+ if (obsolete === "unknown") {
824
+ return skipped("Ambiguous obsolete AgentCache registration preserved");
825
+ }
826
+ if (obsolete === "cleaned") {
827
+ return skipped("Obsolete AgentCache registration cleaned; run registration again");
828
+ }
829
+ const mayInitializeGoose = options.allowUndetectedGoose === true && ide.adapterId === "goose" && ide.mcpConfigFormat === "goose-yaml";
830
+ if (!ide.detected && !mayInitializeGoose) return skipped("IDE not detected");
831
+ if (!ide.detected && ide.mcpConfigPath !== resolveGooseConfigPath()) {
832
+ return skipped("unsafe Goose config path preserved");
833
+ }
834
+ if (ide.mcpConfigFormat === "claude-settings") return registerClaudeCode(ide);
835
+ if (ide.mcpConfigFormat === "mcp-json") return registerMcpJson(ide);
836
+ if (ide.mcpConfigFormat === "continue-dir") return registerContinue(ide);
837
+ if (ide.mcpConfigFormat === "codex-toml") return registerCodex(ide);
838
+ if (ide.mcpConfigFormat === "goose-yaml") return registerGoose(ide);
839
+ return skipped("unsupported configuration format");
840
+ }
841
+ function unregisterMcpServer(ide) {
842
+ const obsolete = cleanOneObsoleteJsonRegistration(ide);
843
+ if (obsolete === "unknown") return false;
844
+ if (obsolete === "cleaned") return true;
845
+ if (ide.mcpConfigFormat === "claude-settings") return unregisterClaudeCode(ide);
846
+ if (ide.mcpConfigFormat === "mcp-json" || ide.mcpConfigFormat === "continue-dir") {
847
+ return unregisterJson(ide);
848
+ }
849
+ if (ide.mcpConfigFormat === "codex-toml") return unregisterCodex(ide);
850
+ if (ide.mcpConfigFormat === "goose-yaml") return unregisterGoose(ide);
851
+ return false;
852
+ }
853
+ function isMcpServerRegistered(ide) {
854
+ if (ide.mcpConfigFormat === "claude-settings") {
855
+ const desired = claudeServerConfig(ide);
856
+ return desired ? isJsonMcpServerRegistered(ide, desired) : false;
857
+ }
858
+ if (ide.mcpConfigFormat === "mcp-json" || ide.mcpConfigFormat === "continue-dir") {
859
+ const desired = serverConfig(ide);
860
+ return desired ? isJsonMcpServerRegistered(ide, desired) : false;
861
+ }
862
+ if (ide.mcpConfigFormat === "codex-toml") return isCodexRegistered(ide);
863
+ if (ide.mcpConfigFormat === "goose-yaml") return isGooseRegistered(ide);
864
+ return false;
865
+ }
866
+ function inspectMcpServerPresence(ide) {
867
+ const obsoletePresence = inspectObsoleteJsonRegistrationPresence(ide);
868
+ let currentPresence;
869
+ if (ide.mcpConfigFormat === "claude-settings") {
870
+ const desired = claudeServerConfig(ide);
871
+ currentPresence = combineRegistrationPresence(
872
+ desired ? inspectJsonMcpServerPresence(ide, desired) : "unknown",
873
+ inspectClaudeSettingsPresence(claudeSettingsPath(ide))
874
+ );
875
+ } else if (ide.mcpConfigFormat === "mcp-json" || ide.mcpConfigFormat === "continue-dir") {
876
+ const desired = serverConfig(ide);
877
+ currentPresence = desired ? inspectJsonMcpServerPresence(ide, desired) : "unknown";
878
+ } else if (ide.mcpConfigFormat === "codex-toml") {
879
+ currentPresence = inspectCodexPresence(ide);
880
+ } else if (ide.mcpConfigFormat === "goose-yaml") {
881
+ currentPresence = inspectGoosePresence(ide);
882
+ } else {
883
+ currentPresence = "unknown";
884
+ }
885
+ return combineRegistrationPresence(currentPresence, obsoletePresence);
886
+ }
887
+ function combineRegistrationPresence(...values) {
888
+ if (values.includes("present")) return "present";
889
+ return values.every((value) => value === "absent") ? "absent" : "unknown";
890
+ }
891
+ function obsoleteJsonRegistrationPaths(ide) {
892
+ const paths = ide.legacyMcpConfigPaths ?? [];
893
+ if (!Array.isArray(paths) || paths.length > 8) return void 0;
894
+ const unique = /* @__PURE__ */ new Set();
895
+ for (const path of paths) {
896
+ if (typeof path !== "string" || !isAbsolute(path) || path === ide.mcpConfigPath || unique.has(path)) {
897
+ return void 0;
898
+ }
899
+ unique.add(path);
900
+ }
901
+ return [...unique];
902
+ }
903
+ function obsoleteJsonIde(ide, path) {
904
+ return {
905
+ adapterId: ide.adapterId,
906
+ name: ide.name,
907
+ detected: ide.detected,
908
+ mcpConfigPath: path,
909
+ mcpConfigFormat: "mcp-json"
910
+ };
911
+ }
912
+ function inspectObsoleteJsonRegistrationPresence(ide) {
913
+ const paths = obsoleteJsonRegistrationPaths(ide);
914
+ if (!paths) return "unknown";
915
+ return combineRegistrationPresence(...paths.map(
916
+ (path) => inspectJsonMcpServerPresence(
917
+ obsoleteJsonIde(ide, path),
918
+ { command: "agentcache", args: boundServeArgs(ide) }
919
+ )
920
+ ));
921
+ }
922
+ function cleanOneObsoleteJsonRegistration(ide) {
923
+ const paths = obsoleteJsonRegistrationPaths(ide);
924
+ if (!paths) return "unknown";
925
+ const candidates = [];
926
+ for (const path of paths) {
927
+ const file = parseJsonFile(path);
928
+ if (!file || !validServerMap(file.config.mcpServers)) return "unknown";
929
+ if (!file.file.expectation.existed || file.config.mcpServers === void 0) continue;
930
+ const legacyIde = obsoleteJsonIde(ide, path);
931
+ const current = registrationOwnership(
932
+ file.config,
933
+ legacyIde,
934
+ { command: "agentcache", args: boundServeArgs(ide) }
935
+ );
936
+ const legacy = legacyLoopRegistrationOwnership(file.config);
937
+ if (current === "ambiguous" || legacy === "ambiguous") return "unknown";
938
+ candidates.push({ path, file, current, legacy });
939
+ }
940
+ const candidate = candidates.find(
941
+ ({ current, legacy }) => current === "owned" || legacy === "owned"
942
+ );
943
+ if (!candidate) return "absent";
944
+ const servers = candidate.file.config.mcpServers;
945
+ if (candidate.current === "owned") delete servers.agentcache;
946
+ if (candidate.legacy === "owned") delete servers.loop;
947
+ const result = writeJson(
948
+ candidate.path,
949
+ candidate.file.config,
950
+ candidate.file.file.expectation
951
+ );
952
+ return !result.conflict && !result.unsupported && result.changed ? "cleaned" : "unknown";
953
+ }
954
+ function inspectClaudeSettingsPresence(path) {
955
+ const file = readBoundedRegistrationFile(path, true);
956
+ if (!file) return "unknown";
957
+ if (!file.expectation.existed) return "absent";
958
+ let settings;
959
+ try {
960
+ settings = JSON.parse(file.content);
961
+ } catch {
962
+ return "unknown";
963
+ }
964
+ const duplicateKeys = jsonHasDuplicateKeys(file.content);
965
+ if (duplicateKeys === void 0 || !isJsonObject(settings)) return "unknown";
966
+ let ambiguous = duplicateKeys;
967
+ if (settings.mcpServers !== void 0 && !isJsonObject(settings.mcpServers)) {
968
+ return "unknown";
969
+ }
970
+ for (const key of ["loop", "agentcache"]) {
971
+ const ownership = legacyClaudeSettingsRegistrationOwnership(settings, key);
972
+ if (ownership === "owned") return "present";
973
+ if (ownership === "ambiguous") ambiguous = true;
974
+ }
975
+ const permissions = settings.permissions;
976
+ if (permissions !== void 0) {
977
+ if (!isJsonObject(permissions)) return "unknown";
978
+ const allow = permissions.allow;
979
+ if (allow !== void 0) {
980
+ if (!Array.isArray(allow)) return "unknown";
981
+ if (allow.some(
982
+ (permission) => typeof permission === "string" && permission.startsWith("mcp__agentcache__")
983
+ )) {
984
+ return "present";
985
+ }
986
+ }
987
+ }
988
+ const hookOwnership = legacyClaudeHookOwnership(settings);
989
+ if (hookOwnership === "owned") return "present";
990
+ if (hookOwnership === "ambiguous") ambiguous = true;
991
+ return ambiguous ? "unknown" : "absent";
992
+ }
993
+ function registerClaudeCode(ide) {
994
+ const configFile = parseJsonFile(ide.mcpConfigPath);
995
+ const settingsPath = claudeSettingsPath(ide);
996
+ const settingsFile = parseJsonFile(settingsPath);
997
+ if (!configFile || !settingsFile || !validServerMap(configFile.config.mcpServers)) {
998
+ return skipped("malformed or unsupported JSON config preserved");
999
+ }
1000
+ const config = configFile.config;
1001
+ const settings = settingsFile.config;
1002
+ const desired = claudeServerConfig(ide);
1003
+ if (!desired) return skipped("trusted AgentCache CLI entrypoint unavailable");
1004
+ if (registrationOwnership(config, ide, desired) === "ambiguous") {
1005
+ return skipped("ambiguous AgentCache registration preserved");
1006
+ }
1007
+ if (legacyClaudeHookOwnership(settings) === "ambiguous") {
1008
+ return skipped("ambiguous Claude legacy hook preserved");
1009
+ }
1010
+ if (legacyLoopRegistrationOwnership(config) === "owned" && isJsonObject(config.mcpServers)) {
1011
+ delete config.mcpServers.loop;
1012
+ }
1013
+ config.mcpServers ??= {};
1014
+ config.mcpServers.agentcache = desired;
1015
+ const permissionsChanged = removeAgentCachePermissions(settings);
1016
+ const hooksChanged = removeAgentCacheHooks(settings);
1017
+ const registrationsChanged = removeLegacyClaudeMcpServers(settings);
1018
+ const settingsChanged = permissionsChanged || hooksChanged || registrationsChanged;
1019
+ if (settingsChanged) {
1020
+ const settingsWrite = writeJson(settingsPath, settings, settingsFile.file.expectation);
1021
+ if (settingsWrite.conflict) return skipped("Claude settings changed during registration");
1022
+ if (settingsWrite.unsupported) {
1023
+ return skipped("safe atomic Claude settings publication unsupported");
1024
+ }
1025
+ return skipped("Claude legacy settings cleaned; run registration again");
1026
+ }
1027
+ const nextConfig = jsonFileContents(config);
1028
+ const configWrite = atomicWriteFileSync(
1029
+ ide.mcpConfigPath,
1030
+ nextConfig,
1031
+ configFile.file.expectation
1032
+ );
1033
+ if (configWrite.conflict) return skipped("Claude config changed during registration");
1034
+ if (configWrite.unsupported) {
1035
+ return skipped("safe atomic Claude config publication unsupported");
1036
+ }
1037
+ const currentSettings = parseJsonFile(settingsPath);
1038
+ if (!currentSettings || hasLegacyClaudeSettings(currentSettings.config)) {
1039
+ if (!configWrite.changed) {
1040
+ return skipped("Claude settings became unsafe; existing registration preserved");
1041
+ }
1042
+ const restored = restorePublishedFile(
1043
+ ide.mcpConfigPath,
1044
+ Buffer.from(nextConfig, "utf8"),
1045
+ configFile.file.expectation
1046
+ );
1047
+ return restored ? skipped("Claude settings became unsafe; registration rolled back") : skipped("Claude settings became unsafe; registration rollback could not be completed");
1048
+ }
1049
+ return registrationResult(configWrite.changed);
1050
+ }
1051
+ function unregisterClaudeCode(ide) {
1052
+ const configFile = parseJsonFile(ide.mcpConfigPath);
1053
+ const settingsPath = claudeSettingsPath(ide);
1054
+ const settingsFile = parseJsonFile(settingsPath);
1055
+ if (!configFile || !settingsFile || !validServerMap(configFile.config.mcpServers)) return false;
1056
+ const config = configFile.config;
1057
+ const settings = settingsFile.config;
1058
+ const desired = claudeServerConfig(ide);
1059
+ if (!desired) return false;
1060
+ const ownership = registrationOwnership(config, ide, desired);
1061
+ if (ownership === "ambiguous") return false;
1062
+ if (legacyClaudeHookOwnership(settings) === "ambiguous") return false;
1063
+ const legacyOwnership = legacyLoopRegistrationOwnership(config);
1064
+ const permissionsChanged = removeAgentCachePermissions(settings);
1065
+ const hooksChanged = removeAgentCacheHooks(settings);
1066
+ const registrationsChanged = removeLegacyClaudeMcpServers(settings);
1067
+ const settingsChanged = permissionsChanged || hooksChanged || registrationsChanged;
1068
+ if (settingsChanged) {
1069
+ const settingsWrite = writeJson(settingsPath, settings, settingsFile.file.expectation);
1070
+ return !settingsWrite.conflict && !settingsWrite.unsupported && settingsWrite.changed;
1071
+ }
1072
+ let configChanged = false;
1073
+ if (ownership === "owned") {
1074
+ delete config.mcpServers.agentcache;
1075
+ configChanged = true;
1076
+ }
1077
+ if (legacyOwnership === "owned") {
1078
+ delete config.mcpServers.loop;
1079
+ configChanged = true;
1080
+ }
1081
+ if (!configChanged) return false;
1082
+ const result = writeJson(ide.mcpConfigPath, config, configFile.file.expectation);
1083
+ return !result.conflict && !result.unsupported && result.changed;
1084
+ }
1085
+ function boundServeArgs(ide) {
1086
+ return ["serve", "--adapter", ide.adapterId];
1087
+ }
1088
+ function pinnedServeCommand(ide) {
1089
+ if (!registrationCliEntrypoint) return void 0;
1090
+ return {
1091
+ command: process.execPath,
1092
+ args: [registrationCliEntrypoint, ...boundServeArgs(ide)]
1093
+ };
1094
+ }
1095
+ function claudeServerConfig(ide) {
1096
+ const pinned = pinnedServeCommand(ide);
1097
+ if (!pinned) return void 0;
1098
+ return {
1099
+ type: "stdio",
1100
+ ...pinned,
1101
+ env: {}
1102
+ };
1103
+ }
1104
+ function serverConfig(ide) {
1105
+ const pinned = pinnedServeCommand(ide);
1106
+ if (!pinned) return void 0;
1107
+ return isVscodeExtensionIde(ide) ? { ...pinned, disabled: false } : pinned;
1108
+ }
1109
+ function isJsonMcpServerRegistered(ide, desired) {
1110
+ const config = parseJsonFileForInspection(ide.mcpConfigPath);
1111
+ if (!config || !validServerMap(config.mcpServers) || !config.mcpServers) return false;
1112
+ const existing = config.mcpServers.agentcache;
1113
+ if (!isJsonObject(existing) || existing.disabled === true) return false;
1114
+ if (isVscodeExtensionIde(ide)) {
1115
+ return existing.disabled === false && serverCoreMatches(existing, desired);
1116
+ }
1117
+ return serverCoreMatches(existing, desired);
1118
+ }
1119
+ function inspectJsonMcpServerPresence(ide, desired) {
1120
+ const file = readBoundedRegistrationFile(ide.mcpConfigPath, true);
1121
+ if (!file) return "unknown";
1122
+ if (!file.expectation.existed) return "absent";
1123
+ let config;
1124
+ try {
1125
+ config = JSON.parse(file.content);
1126
+ } catch {
1127
+ return "unknown";
1128
+ }
1129
+ const duplicateKeys = jsonHasDuplicateKeys(file.content);
1130
+ if (duplicateKeys === void 0) return "unknown";
1131
+ if (!isJsonObject(config)) return "unknown";
1132
+ if (config.mcpServers === void 0) return duplicateKeys ? "unknown" : "absent";
1133
+ if (!isJsonObject(config.mcpServers)) return "unknown";
1134
+ const current = registrationOwnership(config, ide, desired);
1135
+ const legacy = legacyLoopRegistrationOwnership(config);
1136
+ if (current === "owned" || legacy === "owned") return "present";
1137
+ if (current === "ambiguous" || legacy === "ambiguous" || duplicateKeys) return "unknown";
1138
+ return "absent";
1139
+ }
1140
+ function registerMcpJson(ide) {
1141
+ const configFile = parseJsonFile(ide.mcpConfigPath);
1142
+ if (!configFile || !validServerMap(configFile.config.mcpServers)) {
1143
+ return skipped("malformed or unsupported JSON config preserved");
1144
+ }
1145
+ const config = configFile.config;
1146
+ const desired = serverConfig(ide);
1147
+ if (!desired) return skipped("trusted AgentCache CLI entrypoint unavailable");
1148
+ if (registrationOwnership(config, ide, desired) === "ambiguous") {
1149
+ return skipped("ambiguous AgentCache registration preserved");
1150
+ }
1151
+ const legacyOwnership = legacyLoopRegistrationOwnership(config);
1152
+ if (legacyOwnership === "owned" && isJsonObject(config.mcpServers)) {
1153
+ delete config.mcpServers.loop;
1154
+ }
1155
+ config.mcpServers ??= {};
1156
+ config.mcpServers.agentcache = desired;
1157
+ return registrationWriteResult(
1158
+ writeJson(ide.mcpConfigPath, config, configFile.file.expectation),
1159
+ "JSON config changed during registration"
1160
+ );
1161
+ }
1162
+ function registerContinue(ide) {
1163
+ return registerMcpJson(ide);
1164
+ }
1165
+ function unregisterJson(ide) {
1166
+ const configFile = parseJsonFile(ide.mcpConfigPath);
1167
+ if (!configFile || !configFile.file.expectation.existed || !validServerMap(configFile.config.mcpServers) || !configFile.config.mcpServers) {
1168
+ return false;
1169
+ }
1170
+ const config = configFile.config;
1171
+ const desired = serverConfig(ide);
1172
+ if (!desired) return false;
1173
+ const currentOwnership = registrationOwnership(config, ide, desired);
1174
+ const legacyOwnership = legacyLoopRegistrationOwnership(config);
1175
+ let changed = false;
1176
+ if (currentOwnership === "owned") {
1177
+ delete config.mcpServers.agentcache;
1178
+ changed = true;
1179
+ }
1180
+ if (legacyOwnership === "owned") {
1181
+ delete config.mcpServers.loop;
1182
+ changed = true;
1183
+ }
1184
+ if (!changed) return false;
1185
+ const result = writeJson(ide.mcpConfigPath, config, configFile.file.expectation);
1186
+ return !result.conflict && result.changed;
1187
+ }
1188
+ var GOOSE_EXTENSION_KEY = "agentcache";
1189
+ function resolveGooseConfigTarget(logicalPath) {
1190
+ if (!isAbsolute(logicalPath) || !atomicWritePathIsSafe(logicalPath)) return void 0;
1191
+ let metadata;
1192
+ try {
1193
+ metadata = lstatSync2(logicalPath);
1194
+ } catch (error) {
1195
+ return missingPath2(error) ? { logicalPath, writePath: logicalPath, existed: false } : void 0;
1196
+ }
1197
+ if (!metadata.isSymbolicLink()) {
1198
+ return metadata.isFile() ? { logicalPath, writePath: logicalPath, existed: true } : void 0;
1199
+ }
1200
+ try {
1201
+ const linkValue = readlinkSync(logicalPath);
1202
+ const writePath = resolve2(dirname2(logicalPath), linkValue);
1203
+ const targetMetadata = lstatSync2(writePath);
1204
+ if (targetMetadata.isSymbolicLink() || !targetMetadata.isFile()) return void 0;
1205
+ return { logicalPath, writePath, existed: true, linkValue };
1206
+ } catch {
1207
+ return void 0;
1208
+ }
1209
+ }
1210
+ function gooseTargetStillBound(target, expectation) {
1211
+ try {
1212
+ if (target.linkValue !== void 0) {
1213
+ const linkMetadata = lstatSync2(target.logicalPath);
1214
+ if (!linkMetadata.isSymbolicLink()) return false;
1215
+ if (readlinkSync(target.logicalPath) !== target.linkValue) return false;
1216
+ } else if (expectation.existed) {
1217
+ const metadata = lstatSync2(target.logicalPath);
1218
+ if (metadata.isSymbolicLink() || !metadata.isFile()) return false;
1219
+ } else {
1220
+ try {
1221
+ lstatSync2(target.logicalPath);
1222
+ return false;
1223
+ } catch (error) {
1224
+ return missingPath2(error);
1225
+ }
1226
+ }
1227
+ return atomicWriteExpectationMatches(target.writePath, expectation);
1228
+ } catch {
1229
+ return false;
1230
+ }
1231
+ }
1232
+ function currentPublishedGooseFile(target, contents) {
1233
+ const published = readBoundedRegistrationFile(target.writePath);
1234
+ return published?.expectation.existed && published.expectation.contents.equals(contents) ? published : void 0;
1235
+ }
1236
+ function restoreGooseTarget(target, published, original) {
1237
+ if (!published?.expectation.existed) return false;
1238
+ const restore = original.existed ? atomicWriteFileSync(target.writePath, original.contents, published.expectation) : atomicRemoveFileSync(target.writePath, published.expectation);
1239
+ return !restore.conflict && !restore.unsupported && restore.changed;
1240
+ }
1241
+ function gooseServerConfig(ide) {
1242
+ const pinned = pinnedServeCommand(ide);
1243
+ if (!pinned) return void 0;
1244
+ return {
1245
+ enabled: true,
1246
+ name: GOOSE_EXTENSION_KEY,
1247
+ description: "AgentCache MCP server",
1248
+ type: "stdio",
1249
+ cmd: pinned.command,
1250
+ args: pinned.args
1251
+ };
1252
+ }
1253
+ function gooseCoreMatches(value, expected) {
1254
+ return isJsonObject(value) && value.name === GOOSE_EXTENSION_KEY && value.type === "stdio" && value.cmd === expected.cmd && argsEqual(value.args, expected.args);
1255
+ }
1256
+ function gooseOwnership(value, ide, desired) {
1257
+ if (value === void 0) return "absent";
1258
+ if (desired && gooseCoreMatches(value, desired)) return "owned";
1259
+ if (gooseCoreMatches(value, { cmd: "agentcache", args: boundServeArgs(ide) })) return "owned";
1260
+ if (gooseCoreMatches(value, { cmd: "agentcache", args: ["serve"] })) return "owned";
1261
+ if (isJsonObject(value) && value.name === GOOSE_EXTENSION_KEY && value.type === "stdio" && isAgentCacheNodeWrapper({ command: value.cmd, args: value.args }, ide)) {
1262
+ return "owned";
1263
+ }
1264
+ return "ambiguous";
1265
+ }
1266
+ function parseGooseDocument(content) {
1267
+ const document = parseDocument(content, {
1268
+ prettyErrors: false,
1269
+ strict: true,
1270
+ uniqueKeys: true
1271
+ });
1272
+ if (document.errors.length > 0 || document.warnings.length > 0) return void 0;
1273
+ if (document.contents !== null && !isMap(document.contents)) return void 0;
1274
+ return document;
1275
+ }
1276
+ function gooseExtensions(document) {
1277
+ if (document.contents === null) return void 0;
1278
+ const extensions = document.get("extensions", true);
1279
+ if (extensions === void 0) return void 0;
1280
+ return isMap(extensions) ? extensions : null;
1281
+ }
1282
+ function createGooseMap(document, value = {}) {
1283
+ const node = document.createNode(value);
1284
+ return isMap(node) ? node : void 0;
1285
+ }
1286
+ function registerGoose(ide) {
1287
+ if (!isAbsolute(ide.mcpConfigPath)) {
1288
+ return skipped("unsafe Goose config path preserved");
1289
+ }
1290
+ const target = resolveGooseConfigTarget(ide.mcpConfigPath);
1291
+ if (!target) return skipped("unsupported Goose config symlink preserved");
1292
+ const file = readBoundedRegistrationFile(target.writePath, true);
1293
+ if (!file || file.expectation.existed !== target.existed || !gooseTargetStillBound(target, file.expectation)) {
1294
+ return skipped("malformed or unsupported Goose YAML config preserved");
1295
+ }
1296
+ const original = file.content;
1297
+ const document = parseGooseDocument(original);
1298
+ if (!document) {
1299
+ return skipped("malformed or unsupported Goose YAML config preserved");
1300
+ }
1301
+ let extensions = gooseExtensions(document);
1302
+ if (extensions === null) {
1303
+ return skipped("malformed or unsupported Goose YAML config preserved");
1304
+ }
1305
+ const existingNode = extensions?.get(GOOSE_EXTENSION_KEY, true);
1306
+ let existing;
1307
+ try {
1308
+ existing = existingNode?.toJSON();
1309
+ } catch {
1310
+ return skipped("malformed or unsupported Goose YAML config preserved");
1311
+ }
1312
+ const desired = gooseServerConfig(ide);
1313
+ if (!desired) return skipped("trusted AgentCache CLI entrypoint unavailable");
1314
+ const ownership = gooseOwnership(existing, ide, desired);
1315
+ if (ownership === "ambiguous" || existingNode !== void 0 && !isMap(existingNode)) {
1316
+ return skipped("ambiguous AgentCache registration preserved");
1317
+ }
1318
+ if (ownership === "owned" && Object.entries(desired).every(([key, value]) => {
1319
+ const existingValue = existing[key];
1320
+ return Array.isArray(value) ? argsEqual(existingValue, value) : existingValue === value;
1321
+ })) {
1322
+ return gooseTargetStillBound(target, file.expectation) ? { status: "unchanged" } : skipped("Goose config changed during registration");
1323
+ }
1324
+ if (!extensions) {
1325
+ if (document.contents === null) {
1326
+ const root = createGooseMap(document);
1327
+ if (!root) return skipped("malformed or unsupported Goose YAML config preserved");
1328
+ document.contents = root;
1329
+ }
1330
+ const extensionsNode = createGooseMap(document);
1331
+ if (!extensionsNode || !isMap(document.contents)) {
1332
+ return skipped("malformed or unsupported Goose YAML config preserved");
1333
+ }
1334
+ document.contents.set("extensions", extensionsNode);
1335
+ extensions = extensionsNode;
1336
+ }
1337
+ if (!extensions || !isMap(extensions)) {
1338
+ return skipped("malformed or unsupported Goose YAML config preserved");
1339
+ }
1340
+ if (existingNode && isMap(existingNode)) {
1341
+ for (const [key, value] of Object.entries(desired)) {
1342
+ const existingValue = existing[key];
1343
+ const matches = Array.isArray(value) ? argsEqual(existingValue, value) : existingValue === value;
1344
+ if (!matches) {
1345
+ const currentNode = existingNode.get(key, true);
1346
+ if (isScalar(currentNode) && !Array.isArray(value)) currentNode.value = value;
1347
+ else {
1348
+ const replacement = document.createNode(value);
1349
+ if (isSeq(currentNode) && isSeq(replacement)) {
1350
+ replacement.comment = currentNode.comment;
1351
+ replacement.commentBefore = currentNode.commentBefore;
1352
+ replacement.spaceBefore = currentNode.spaceBefore;
1353
+ for (const replacementItem of replacement.items) {
1354
+ if (!isScalar(replacementItem)) continue;
1355
+ const previousItem = currentNode.items.find(
1356
+ (item) => isScalar(item) && item.value === replacementItem.value
1357
+ );
1358
+ if (!isScalar(previousItem)) continue;
1359
+ replacementItem.comment = previousItem.comment;
1360
+ replacementItem.commentBefore = previousItem.commentBefore;
1361
+ replacementItem.spaceBefore = previousItem.spaceBefore;
1362
+ }
1363
+ }
1364
+ existingNode.set(key, replacement);
1365
+ }
1366
+ }
1367
+ }
1368
+ } else {
1369
+ const desiredNode = createGooseMap(document, desired);
1370
+ if (!desiredNode) {
1371
+ return skipped("malformed or unsupported Goose YAML config preserved");
1372
+ }
1373
+ extensions.set(GOOSE_EXTENSION_KEY, desiredNode);
1374
+ }
1375
+ if (!gooseTargetStillBound(target, file.expectation)) {
1376
+ return skipped("Goose config changed during registration");
1377
+ }
1378
+ const next = String(document);
1379
+ const nextBytes = Buffer.from(next, "utf8");
1380
+ const write = atomicWriteFileSync(target.writePath, next, file.expectation);
1381
+ const result = registrationWriteResult(
1382
+ write,
1383
+ "Goose config changed during registration",
1384
+ "safe atomic Goose config publication unsupported"
1385
+ );
1386
+ if (!write.changed) return result;
1387
+ const published = currentPublishedGooseFile(target, nextBytes);
1388
+ if (published && gooseTargetStillBound(target, published.expectation)) return result;
1389
+ const restored = restoreGooseTarget(target, published, file.expectation);
1390
+ return restored ? skipped("Goose config target changed during registration; original target restored") : skipped("Goose config changed after registration; rollback could not be completed");
1391
+ }
1392
+ function unregisterGoose(ide) {
1393
+ const target = resolveGooseConfigTarget(ide.mcpConfigPath);
1394
+ if (!target?.existed) return false;
1395
+ const file = readBoundedRegistrationFile(target.writePath);
1396
+ if (!file || !gooseTargetStillBound(target, file.expectation)) return false;
1397
+ const original = file.content;
1398
+ const document = parseGooseDocument(original);
1399
+ if (!document) return false;
1400
+ const extensions = gooseExtensions(document);
1401
+ if (!extensions || !isMap(extensions)) return false;
1402
+ const existingNode = extensions.get(GOOSE_EXTENSION_KEY, true);
1403
+ if (!existingNode || !isMap(existingNode)) return false;
1404
+ let existing;
1405
+ try {
1406
+ existing = existingNode.toJSON();
1407
+ } catch {
1408
+ return false;
1409
+ }
1410
+ if (gooseOwnership(existing, ide, gooseServerConfig(ide)) !== "owned") return false;
1411
+ extensions.delete(GOOSE_EXTENSION_KEY);
1412
+ if (!gooseTargetStillBound(target, file.expectation)) return false;
1413
+ const next = String(document);
1414
+ const nextBytes = Buffer.from(next, "utf8");
1415
+ const result = atomicWriteFileSync(target.writePath, next, file.expectation);
1416
+ if (result.conflict || result.unsupported || !result.changed) return false;
1417
+ const published = currentPublishedGooseFile(target, nextBytes);
1418
+ if (published && gooseTargetStillBound(target, published.expectation)) return true;
1419
+ restoreGooseTarget(target, published, file.expectation);
1420
+ return false;
1421
+ }
1422
+ function isGooseRegistered(ide) {
1423
+ const target = resolveGooseConfigTarget(ide.mcpConfigPath);
1424
+ if (!target?.existed) return false;
1425
+ try {
1426
+ const file = readBoundedRegistrationFile(target.writePath);
1427
+ if (!file || !gooseTargetStillBound(target, file.expectation)) return false;
1428
+ const document = parseGooseDocument(file.content);
1429
+ if (!document) return false;
1430
+ const extensions = gooseExtensions(document);
1431
+ if (!extensions || !isMap(extensions)) return false;
1432
+ const entry = extensions.get(GOOSE_EXTENSION_KEY, true);
1433
+ if (!entry || !isMap(entry)) return false;
1434
+ const value = entry.toJSON();
1435
+ const desired = gooseServerConfig(ide);
1436
+ return !!desired && isJsonObject(value) && value.enabled === true && gooseCoreMatches(value, desired);
1437
+ } catch {
1438
+ return false;
1439
+ }
1440
+ }
1441
+ function inspectGoosePresence(ide) {
1442
+ const target = resolveGooseConfigTarget(ide.mcpConfigPath);
1443
+ if (!target) return "unknown";
1444
+ if (!target.existed) return "absent";
1445
+ try {
1446
+ const file = readBoundedRegistrationFile(target.writePath);
1447
+ if (!file || !gooseTargetStillBound(target, file.expectation)) return "unknown";
1448
+ const document = parseGooseDocument(file.content);
1449
+ if (!document) return "unknown";
1450
+ const extensions = gooseExtensions(document);
1451
+ if (extensions === void 0) return "absent";
1452
+ if (!isMap(extensions)) return "unknown";
1453
+ const entry = extensions.get(GOOSE_EXTENSION_KEY, true);
1454
+ if (entry === void 0) return "absent";
1455
+ if (!isMap(entry)) return "unknown";
1456
+ return gooseOwnership(entry.toJSON(), ide, gooseServerConfig(ide)) === "owned" ? "present" : "unknown";
1457
+ } catch {
1458
+ return "unknown";
1459
+ }
1460
+ }
1461
+ function formatCodexBlock(command, args) {
1462
+ return [
1463
+ CODEX_MARKER_START,
1464
+ "[mcp_servers.agentcache]",
1465
+ `command = ${JSON.stringify(command)}`,
1466
+ `args = [${args.map((value) => JSON.stringify(value)).join(", ")}]`,
1467
+ CODEX_MARKER_END,
1468
+ ""
1469
+ ].join("\n");
1470
+ }
1471
+ function codexBlock(ide) {
1472
+ const pinned = pinnedServeCommand(ide);
1473
+ return pinned ? formatCodexBlock(pinned.command, pinned.args) : void 0;
1474
+ }
1475
+ function maskTomlStrings(content) {
1476
+ let state = "normal";
1477
+ let masked = "";
1478
+ for (let index = 0; index < content.length; index += 1) {
1479
+ const char = content[index];
1480
+ const triple = content.slice(index, index + 3);
1481
+ if (state === "comment") {
1482
+ masked += char;
1483
+ if (char === "\n") state = "normal";
1484
+ continue;
1485
+ }
1486
+ if (state === "basic") {
1487
+ if (char === "\n" || char === "\r") return void 0;
1488
+ masked += " ";
1489
+ if (char === "\\") {
1490
+ if (index + 1 >= content.length || content[index + 1] === "\n") return void 0;
1491
+ masked += " ";
1492
+ index += 1;
1493
+ } else if (char === '"') {
1494
+ state = "normal";
1495
+ }
1496
+ continue;
1497
+ }
1498
+ if (state === "literal") {
1499
+ if (char === "\n" || char === "\r") return void 0;
1500
+ masked += " ";
1501
+ if (char === "'") state = "normal";
1502
+ continue;
1503
+ }
1504
+ if (state === "multi-basic") {
1505
+ if (triple === '"""') {
1506
+ masked += " ";
1507
+ index += 2;
1508
+ state = "normal";
1509
+ } else {
1510
+ masked += char === "\n" ? "\n" : " ";
1511
+ if (char === "\\" && index + 1 < content.length) {
1512
+ masked += content[index + 1] === "\n" ? "\n" : " ";
1513
+ index += 1;
1514
+ }
1515
+ }
1516
+ continue;
1517
+ }
1518
+ if (state === "multi-literal") {
1519
+ if (triple === "'''") {
1520
+ masked += " ";
1521
+ index += 2;
1522
+ state = "normal";
1523
+ } else {
1524
+ masked += char === "\n" ? "\n" : " ";
1525
+ }
1526
+ continue;
1527
+ }
1528
+ if (triple === '"""') {
1529
+ masked += " ";
1530
+ index += 2;
1531
+ state = "multi-basic";
1532
+ } else if (triple === "'''") {
1533
+ masked += " ";
1534
+ index += 2;
1535
+ state = "multi-literal";
1536
+ } else if (char === '"') {
1537
+ masked += " ";
1538
+ state = "basic";
1539
+ } else if (char === "'") {
1540
+ masked += " ";
1541
+ state = "literal";
1542
+ } else if (char === "#") {
1543
+ masked += char;
1544
+ state = "comment";
1545
+ } else {
1546
+ masked += char;
1547
+ }
1548
+ }
1549
+ return state === "normal" || state === "comment" ? masked : void 0;
1550
+ }
1551
+ var TOML_HEADER_SENTINEL = "__agentcache_header_sentinel_4f20c6d1__";
1552
+ function tomlHeaderSentinelPaths(value) {
1553
+ if (!value || typeof value !== "object") return [];
1554
+ const paths = [];
1555
+ for (const [key, child] of Object.entries(value)) {
1556
+ if (key === TOML_HEADER_SENTINEL && child === true) {
1557
+ paths.push([]);
1558
+ continue;
1559
+ }
1560
+ const candidates = Array.isArray(child) ? child : [child];
1561
+ for (const candidate of candidates) {
1562
+ for (const nestedPath of tomlHeaderSentinelPaths(candidate)) {
1563
+ paths.push([key, ...nestedPath]);
1564
+ }
1565
+ }
1566
+ }
1567
+ return paths;
1568
+ }
1569
+ function parseTomlHeader(source) {
1570
+ try {
1571
+ const parsed = parseToml(`${source}
1572
+ ${TOML_HEADER_SENTINEL} = true
1573
+ `);
1574
+ const paths = tomlHeaderSentinelPaths(parsed);
1575
+ if (paths.length !== 1 || paths[0].length === 0) return void 0;
1576
+ return { path: paths[0], array: source.startsWith("[[") };
1577
+ } catch {
1578
+ return void 0;
1579
+ }
1580
+ }
1581
+ function isOwnedCodexHeader(header) {
1582
+ return header.path.length >= 2 && header.path[0] === "mcp_servers" && header.path[1] === "agentcache";
1583
+ }
1584
+ function isExactCodexHeader(header) {
1585
+ return !header.array && header.path.length === 2 && isOwnedCodexHeader(header);
1586
+ }
1587
+ function scanToml(content) {
1588
+ const masked = maskTomlStrings(content);
1589
+ if (masked === void 0) return void 0;
1590
+ const lines = [];
1591
+ const headers = [];
1592
+ const markerStarts = [];
1593
+ const markerEnds = [];
1594
+ let squareDepth = 0;
1595
+ let inlineTableDepth = 0;
1596
+ let offset = 0;
1597
+ while (offset < content.length) {
1598
+ const newline = content.indexOf("\n", offset);
1599
+ const end = newline < 0 ? content.length : newline + 1;
1600
+ const originalLine = content.slice(offset, end).replace(/\r?\n$/, "");
1601
+ const maskedLine = masked.slice(offset, end).replace(/\r?\n$/, "");
1602
+ const trimmed = maskedLine.trim();
1603
+ const marker = trimmed === CODEX_MARKER_START ? "start" : trimmed === CODEX_MARKER_END ? "end" : void 0;
1604
+ const commentAt = maskedLine.indexOf("#");
1605
+ const code = (commentAt < 0 ? maskedLine : maskedLine.slice(0, commentAt)).trim();
1606
+ const line = { start: offset, end, code, marker };
1607
+ lines.push(line);
1608
+ if (marker === "start") markerStarts.push(line);
1609
+ if (marker === "end") markerEnds.push(line);
1610
+ if (code.startsWith("[") && squareDepth === 0 && inlineTableDepth === 0) {
1611
+ const source = (commentAt < 0 ? originalLine : originalLine.slice(0, commentAt)).trim();
1612
+ const header = parseTomlHeader(source);
1613
+ if (!header) return void 0;
1614
+ headers.push({ ...header, start: offset, bodyStart: end, end: content.length });
1615
+ } else if (code) {
1616
+ const startsAtTopLevel = squareDepth === 0 && inlineTableDepth === 0;
1617
+ if (startsAtTopLevel && !code.includes("=")) return void 0;
1618
+ for (const char of code) {
1619
+ if (char === "[") squareDepth += 1;
1620
+ else if (char === "]") squareDepth -= 1;
1621
+ else if (char === "{") inlineTableDepth += 1;
1622
+ else if (char === "}") inlineTableDepth -= 1;
1623
+ if (squareDepth < 0 || inlineTableDepth < 0) return void 0;
1624
+ }
1625
+ }
1626
+ offset = end;
1627
+ }
1628
+ if (squareDepth !== 0 || inlineTableDepth !== 0) return void 0;
1629
+ if (markerStarts.length > 1 || markerEnds.length > 1) return void 0;
1630
+ if (markerStarts.length === 1 !== (markerEnds.length === 1)) return void 0;
1631
+ if (markerStarts[0] && markerEnds[0] && markerStarts[0].start >= markerEnds[0].start) {
1632
+ return void 0;
1633
+ }
1634
+ for (let index = 0; index < headers.length; index += 1) {
1635
+ headers[index].end = headers[index + 1]?.start ?? content.length;
1636
+ }
1637
+ return {
1638
+ lines,
1639
+ headers,
1640
+ markerStart: markerStarts[0],
1641
+ markerEnd: markerEnds[0]
1642
+ };
1643
+ }
1644
+ function managedCodexRange(content, scan, ide, desiredBlock) {
1645
+ if (!scan.markerStart && !scan.markerEnd) return void 0;
1646
+ if (!scan.markerStart || !scan.markerEnd) return null;
1647
+ const range = { start: scan.markerStart.start, end: scan.markerEnd.end };
1648
+ const managedContent = content.slice(range.start, range.end);
1649
+ const current = managedContent === desiredBlock;
1650
+ const ownedHeaders = scan.headers.filter(isOwnedCodexHeader);
1651
+ const headersInside = scan.headers.filter(
1652
+ (header) => header.start > scan.markerStart.start && header.start < scan.markerEnd.start
1653
+ );
1654
+ if (ownedHeaders.length !== 1 || headersInside.length !== 1 || headersInside[0] !== ownedHeaders[0] || !isExactCodexHeader(ownedHeaders[0]) || ownedHeaders[0].start < range.start || ownedHeaders[0].start >= range.end) {
1655
+ return null;
1656
+ }
1657
+ const leadingTableContent = scan.lines.some(
1658
+ (line) => line.start >= scan.markerStart.end && line.start < ownedHeaders[0].start && line.code.length > 0
1659
+ );
1660
+ if (leadingTableContent) return null;
1661
+ const trailingTableContent = scan.lines.some(
1662
+ (line) => line.start >= scan.markerEnd.end && line.start < ownedHeaders[0].end && line.code.length > 0
1663
+ );
1664
+ if (trailingTableContent) return null;
1665
+ if (!legacyCodexRange(content, scan, ide, true)) return null;
1666
+ return { ...range, current };
1667
+ }
1668
+ function legacyCodexRange(content, scan, ide, allowManagedNodeWrapper = false) {
1669
+ const ownedHeaders = scan.headers.filter(isOwnedCodexHeader);
1670
+ if (ownedHeaders.length === 0) return void 0;
1671
+ if (ownedHeaders.length !== 1 || !isExactCodexHeader(ownedHeaders[0])) return null;
1672
+ const header = ownedHeaders[0];
1673
+ const bodyLines = scan.lines.filter(
1674
+ (line) => line.start >= header.bodyStart && line.start < header.end && line.code.length > 0
1675
+ );
1676
+ let hasCommand = false;
1677
+ let hasArgs = false;
1678
+ let hasLegacyApproval = false;
1679
+ let semanticServer;
1680
+ try {
1681
+ const parsed = parseToml(content);
1682
+ const table = parsed.mcp_servers?.agentcache;
1683
+ if (!isJsonObject(table)) return null;
1684
+ semanticServer = { command: table.command, args: table.args };
1685
+ } catch {
1686
+ return null;
1687
+ }
1688
+ for (const line of bodyLines) {
1689
+ const source = content.slice(line.start, line.end).trim();
1690
+ if (/^command\s*=/.test(source)) {
1691
+ if (hasCommand) return null;
1692
+ hasCommand = true;
1693
+ } else if (/^args\s*=/.test(source)) {
1694
+ if (hasArgs) return null;
1695
+ hasArgs = true;
1696
+ } else if (/^default_tools_approval_mode\s*=\s*"auto"\s*(?:#.*)?$/.test(source)) {
1697
+ hasLegacyApproval = true;
1698
+ } else return null;
1699
+ }
1700
+ const desired = pinnedServeCommand(ide);
1701
+ const current = !!desired && serverCoreMatches(semanticServer, desired);
1702
+ const owned = current || isAgentCacheExecutable(semanticServer.command) && (argsEqual(semanticServer.args, ["serve"]) || argsEqual(semanticServer.args, boundServeArgs(ide))) || allowManagedNodeWrapper && isAgentCacheNodeWrapper(semanticServer, ide);
1703
+ return hasCommand && hasArgs && owned ? {
1704
+ start: header.start,
1705
+ end: header.end,
1706
+ current: current && !hasLegacyApproval
1707
+ } : null;
1708
+ }
1709
+ function removeCodexRange(content, range) {
1710
+ let start = range.start;
1711
+ if (range.end === content.length && content.slice(0, start).endsWith("\n\n")) start -= 2;
1712
+ return content.slice(0, start) + content.slice(range.end);
1713
+ }
1714
+ function registerCodex(ide) {
1715
+ const desiredBlock = codexBlock(ide);
1716
+ if (!desiredBlock) return skipped("trusted AgentCache CLI entrypoint unavailable");
1717
+ const file = readBoundedRegistrationFile(ide.mcpConfigPath, true);
1718
+ if (!file) return skipped("malformed or ambiguous TOML config preserved");
1719
+ const original = file.content;
1720
+ if (!isValidToml(original)) {
1721
+ return skipped("malformed or ambiguous TOML config preserved");
1722
+ }
1723
+ const scan = scanToml(original);
1724
+ if (!scan) return skipped("malformed or ambiguous TOML config preserved");
1725
+ const managed = managedCodexRange(original, scan, ide, desiredBlock);
1726
+ if (managed === null) return skipped("malformed or ambiguous TOML config preserved");
1727
+ if (managed?.current) {
1728
+ return atomicWriteExpectationMatches(ide.mcpConfigPath, file.expectation) ? { status: "unchanged" } : skipped("Codex config changed during registration");
1729
+ }
1730
+ const legacy = managed ?? legacyCodexRange(original, scan, ide);
1731
+ if (legacy === null) return skipped("malformed or ambiguous TOML config preserved");
1732
+ const next = legacy ? original.slice(0, legacy.start) + desiredBlock + original.slice(legacy.end) : `${original}${original.length > 0 ? "\n\n" : ""}${desiredBlock}`;
1733
+ if (!isValidToml(next)) {
1734
+ return skipped("generated TOML would be invalid; existing config preserved");
1735
+ }
1736
+ return registrationWriteResult(
1737
+ atomicWriteFileSync(ide.mcpConfigPath, next, file.expectation),
1738
+ "Codex config changed during registration"
1739
+ );
1740
+ }
1741
+ function unregisterCodex(ide) {
1742
+ const desiredBlock = codexBlock(ide);
1743
+ if (!desiredBlock) return false;
1744
+ const file = readBoundedRegistrationFile(ide.mcpConfigPath);
1745
+ if (!file) return false;
1746
+ const original = file.content;
1747
+ if (!isValidToml(original)) return false;
1748
+ const scan = scanToml(original);
1749
+ if (!scan) return false;
1750
+ const managed = managedCodexRange(original, scan, ide, desiredBlock);
1751
+ if (managed === null) return false;
1752
+ const legacy = managed ? void 0 : legacyCodexRange(original, scan, ide);
1753
+ if (legacy === null) return false;
1754
+ const range = managed ?? legacy;
1755
+ if (!range) return false;
1756
+ const next = removeCodexRange(original, range);
1757
+ if (!isValidToml(next)) return false;
1758
+ const result = atomicWriteFileSync(ide.mcpConfigPath, next, file.expectation);
1759
+ return !result.conflict && result.changed;
1760
+ }
1761
+ function isCodexRegistered(ide) {
1762
+ try {
1763
+ const desiredBlock = codexBlock(ide);
1764
+ if (!desiredBlock) return false;
1765
+ const file = readBoundedRegistrationFile(ide.mcpConfigPath);
1766
+ if (!file) return false;
1767
+ const original = file.content;
1768
+ if (!isValidToml(original)) return false;
1769
+ const scan = scanToml(original);
1770
+ if (!scan) return false;
1771
+ const managed = managedCodexRange(original, scan, ide, desiredBlock);
1772
+ if (managed === null) return false;
1773
+ if (managed) return managed.current;
1774
+ const recognized = legacyCodexRange(original, scan, ide);
1775
+ return recognized?.current === true;
1776
+ } catch {
1777
+ return false;
1778
+ }
1779
+ }
1780
+ function inspectCodexPresence(ide) {
1781
+ try {
1782
+ const desiredBlock = codexBlock(ide);
1783
+ if (!desiredBlock) return "unknown";
1784
+ const file = readBoundedRegistrationFile(ide.mcpConfigPath, true);
1785
+ if (!file) return "unknown";
1786
+ if (!file.expectation.existed) return "absent";
1787
+ const original = file.content;
1788
+ if (!isValidToml(original)) return "unknown";
1789
+ const scan = scanToml(original);
1790
+ if (!scan) return "unknown";
1791
+ const managed = managedCodexRange(original, scan, ide, desiredBlock);
1792
+ if (managed === null) return "unknown";
1793
+ if (managed) return "present";
1794
+ const legacy = legacyCodexRange(original, scan, ide);
1795
+ if (legacy === null) return "unknown";
1796
+ return legacy ? "present" : "absent";
1797
+ } catch {
1798
+ return "unknown";
1799
+ }
1800
+ }
1801
+ function registerClaudeHooks(settingsPath = claudeSettingsPath()) {
1802
+ const settingsFile = parseJsonFile(settingsPath);
1803
+ if (!settingsFile || !settingsFile.file.expectation.existed || legacyClaudeHookOwnership(settingsFile.config) === "ambiguous" || !removeAgentCacheHooks(settingsFile.config)) {
1804
+ return false;
1805
+ }
1806
+ const result = writeJson(settingsPath, settingsFile.config, settingsFile.file.expectation);
1807
+ return !result.conflict && result.changed;
1808
+ }
1809
+
1810
+ export {
1811
+ configureMcpRegistrationEntrypoint,
1812
+ inspectLegacyClaudeHookPresence,
1813
+ registerMcpServer,
1814
+ unregisterMcpServer,
1815
+ isMcpServerRegistered,
1816
+ inspectMcpServerPresence,
1817
+ registerClaudeHooks
1818
+ };