@ijfw/install 1.6.0 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ijfw.js +196 -178
- package/dist/install.js +493 -120
- package/dist/uninstall.js +392 -111
- package/package.json +3 -2
- package/src/install.ps1 +1 -1
- package/templates/pi/AGENTS.md +55 -0
package/dist/uninstall.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/uninstall.js
|
|
4
|
-
import { existsSync as
|
|
5
|
-
import { resolve as resolve2, join as
|
|
4
|
+
import { existsSync as existsSync3, rmSync, cpSync, mkdtempSync, readFileSync as readFileSync3, writeFileSync as writeFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync2, readdirSync as readdirSync2, realpathSync, statSync, chmodSync } from "node:fs";
|
|
5
|
+
import { resolve as resolve2, join as join3, dirname as dirname2, basename } from "node:path";
|
|
6
6
|
import { homedir as homedir2, tmpdir } from "node:os";
|
|
7
7
|
import { spawnSync } from "node:child_process";
|
|
8
8
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -95,6 +95,29 @@ function unmergeMarketplace(settingsPath = claudeSettingsPath()) {
|
|
|
95
95
|
return settings;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// src/install-ledger.js
|
|
99
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, readdirSync } from "node:fs";
|
|
100
|
+
import { join as join2 } from "node:path";
|
|
101
|
+
function ledgerPath(ijfwHome) {
|
|
102
|
+
return join2(ijfwHome, "install-ledger.json");
|
|
103
|
+
}
|
|
104
|
+
function readLedger(ijfwHome) {
|
|
105
|
+
try {
|
|
106
|
+
const raw = readFileSync2(ledgerPath(ijfwHome), "utf8");
|
|
107
|
+
const doc = JSON.parse(raw);
|
|
108
|
+
if (doc && Array.isArray(doc.createdDirs)) return doc;
|
|
109
|
+
} catch {
|
|
110
|
+
}
|
|
111
|
+
return { version: 1, createdDirs: [] };
|
|
112
|
+
}
|
|
113
|
+
function isEmptyDir(p) {
|
|
114
|
+
try {
|
|
115
|
+
return existsSync2(p) && readdirSync(p).length === 0;
|
|
116
|
+
} catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
98
121
|
// src/uninstall.js
|
|
99
122
|
var __filename = fileURLToPath(import.meta.url);
|
|
100
123
|
var __dirname = dirname2(__filename);
|
|
@@ -103,9 +126,9 @@ function resolveAiderTemplate(name, repoRoot) {
|
|
|
103
126
|
const root = repoRoot || REPO_ROOT;
|
|
104
127
|
const candidates = [
|
|
105
128
|
// (a) git clone: top-level aider/ under the (injected) repo root.
|
|
106
|
-
|
|
129
|
+
join3(root, "aider", name),
|
|
107
130
|
// (a') repo root with staged templates under installer/.
|
|
108
|
-
|
|
131
|
+
join3(root, "installer", "templates", "aider", name),
|
|
109
132
|
// (b) tarball/dist fallback: templates staged next to the package root.
|
|
110
133
|
// dist/uninstall.js -> __dirname=<pkg>/dist -> <pkg>/templates/aider/<name>
|
|
111
134
|
// src/uninstall.js -> __dirname=<pkg>/src -> <pkg>/templates/aider/<name>
|
|
@@ -114,17 +137,26 @@ function resolveAiderTemplate(name, repoRoot) {
|
|
|
114
137
|
];
|
|
115
138
|
for (const c of candidates) {
|
|
116
139
|
try {
|
|
117
|
-
if (
|
|
140
|
+
if (existsSync3(c)) return c;
|
|
118
141
|
} catch {
|
|
119
142
|
}
|
|
120
143
|
}
|
|
121
144
|
return "";
|
|
122
145
|
}
|
|
123
146
|
function writeAtomic(target, content) {
|
|
147
|
+
let mode = 384;
|
|
148
|
+
try {
|
|
149
|
+
mode = statSync(target).mode & 511;
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
124
152
|
const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
|
|
125
|
-
|
|
153
|
+
writeFileSync3(tmp, content, { mode });
|
|
126
154
|
try {
|
|
127
155
|
renameSync2(tmp, target);
|
|
156
|
+
try {
|
|
157
|
+
chmodSync(target, mode);
|
|
158
|
+
} catch {
|
|
159
|
+
}
|
|
128
160
|
} catch (err) {
|
|
129
161
|
try {
|
|
130
162
|
unlinkSync2(tmp);
|
|
@@ -178,7 +210,7 @@ function confirm(question) {
|
|
|
178
210
|
var HOME = homedir2();
|
|
179
211
|
var TS = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
|
|
180
212
|
function backupFile(p) {
|
|
181
|
-
if (
|
|
213
|
+
if (existsSync3(p)) {
|
|
182
214
|
const bak = p + ".bak." + TS;
|
|
183
215
|
cpSync(p, bak);
|
|
184
216
|
return bak;
|
|
@@ -212,10 +244,10 @@ function hasIjfwMarker(text) {
|
|
|
212
244
|
}
|
|
213
245
|
function stripMarkerFile(p, opts = {}) {
|
|
214
246
|
try {
|
|
215
|
-
if (!
|
|
247
|
+
if (!existsSync3(p)) return null;
|
|
216
248
|
let text;
|
|
217
249
|
try {
|
|
218
|
-
text =
|
|
250
|
+
text = readFileSync3(p, "utf8");
|
|
219
251
|
} catch {
|
|
220
252
|
return null;
|
|
221
253
|
}
|
|
@@ -229,35 +261,46 @@ function stripMarkerFile(p, opts = {}) {
|
|
|
229
261
|
}
|
|
230
262
|
writeAtomic(p, stripped);
|
|
231
263
|
return `${opts.label || p} (stripped IJFW marker regions, user content preserved)`;
|
|
232
|
-
} catch {
|
|
233
|
-
return
|
|
264
|
+
} catch (err) {
|
|
265
|
+
return `${opts.label || p} (KEPT -- IJFW region strip FAILED: ${err && err.message ? err.message : err}; remove the IJFW marker regions manually)`;
|
|
234
266
|
}
|
|
235
267
|
}
|
|
236
268
|
function removeTomlSection(p) {
|
|
237
|
-
if (!
|
|
238
|
-
|
|
239
|
-
const lines = readFileSync2(p, "utf8").split("\n");
|
|
269
|
+
if (!existsSync3(p)) return false;
|
|
270
|
+
const lines = readFileSync3(p, "utf8").split("\n");
|
|
240
271
|
const out = [];
|
|
241
272
|
let skip = false;
|
|
273
|
+
let changed = false;
|
|
242
274
|
for (const line of lines) {
|
|
243
275
|
if (/^\[mcp_servers\.ijfw-memory\]\s*$/.test(line)) {
|
|
244
276
|
skip = true;
|
|
277
|
+
changed = true;
|
|
245
278
|
continue;
|
|
246
279
|
}
|
|
247
280
|
if (skip && line.startsWith("[") && !line.startsWith("[mcp_servers.ijfw-memory]")) skip = false;
|
|
248
281
|
if (!skip) out.push(line);
|
|
249
282
|
}
|
|
250
|
-
|
|
283
|
+
if (!changed) return false;
|
|
284
|
+
backupFile(p);
|
|
285
|
+
let text = out.join("\n");
|
|
286
|
+
if (!text.endsWith("\n")) text += "\n";
|
|
287
|
+
writeAtomic(p, text);
|
|
251
288
|
return true;
|
|
252
289
|
}
|
|
253
290
|
function removeJsonMcpEntry(p) {
|
|
254
|
-
if (!
|
|
255
|
-
let
|
|
291
|
+
if (!existsSync3(p)) return false;
|
|
292
|
+
let raw;
|
|
256
293
|
try {
|
|
257
|
-
|
|
294
|
+
raw = readFileSync3(p, "utf8");
|
|
258
295
|
} catch {
|
|
259
296
|
return false;
|
|
260
297
|
}
|
|
298
|
+
let doc;
|
|
299
|
+
try {
|
|
300
|
+
doc = JSON.parse(raw);
|
|
301
|
+
} catch {
|
|
302
|
+
return /\bijfw-memory\b/.test(raw) ? "parse-failed" : false;
|
|
303
|
+
}
|
|
261
304
|
if (!doc || typeof doc !== "object") return false;
|
|
262
305
|
let changed = false;
|
|
263
306
|
if (doc.mcpServers && doc.mcpServers["ijfw-memory"]) {
|
|
@@ -270,10 +313,10 @@ function removeJsonMcpEntry(p) {
|
|
|
270
313
|
}
|
|
271
314
|
function removeNestedMcpEntry(p, keyPath) {
|
|
272
315
|
try {
|
|
273
|
-
if (!
|
|
316
|
+
if (!existsSync3(p)) return false;
|
|
274
317
|
let doc;
|
|
275
318
|
try {
|
|
276
|
-
doc = JSON.parse(
|
|
319
|
+
doc = JSON.parse(readFileSync3(p, "utf8"));
|
|
277
320
|
} catch {
|
|
278
321
|
return false;
|
|
279
322
|
}
|
|
@@ -294,43 +337,43 @@ function removeNestedMcpEntry(p, keyPath) {
|
|
|
294
337
|
}
|
|
295
338
|
function resolveClineSettingsPath(home) {
|
|
296
339
|
const H = home;
|
|
297
|
-
const APPDATA = process.env.APPDATA ||
|
|
340
|
+
const APPDATA = process.env.APPDATA || join3(H, "AppData", "Roaming");
|
|
298
341
|
const ext = "saoudrizwan.claude-dev";
|
|
299
342
|
let userDirs;
|
|
300
343
|
if (process.platform === "darwin") {
|
|
301
344
|
userDirs = [
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
345
|
+
join3(H, "Library", "Application Support", "Code", "User"),
|
|
346
|
+
join3(H, "Library", "Application Support", "Code - Insiders", "User"),
|
|
347
|
+
join3(H, "Library", "Application Support", "VSCodium", "User")
|
|
305
348
|
];
|
|
306
349
|
} else if (process.platform === "win32") {
|
|
307
350
|
userDirs = [
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
351
|
+
join3(APPDATA, "Code", "User"),
|
|
352
|
+
join3(APPDATA, "Code - Insiders", "User"),
|
|
353
|
+
join3(APPDATA, "VSCodium", "User")
|
|
311
354
|
];
|
|
312
355
|
} else {
|
|
313
356
|
userDirs = [
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
357
|
+
join3(H, ".config", "Code", "User"),
|
|
358
|
+
join3(H, ".config", "VSCodium", "User"),
|
|
359
|
+
join3(H, ".var", "app", "com.visualstudio.code", "config", "Code", "User"),
|
|
360
|
+
join3(H, "snap", "code", "current", ".config", "Code", "User")
|
|
318
361
|
];
|
|
319
362
|
}
|
|
320
363
|
for (const d of userDirs) {
|
|
321
|
-
const settings =
|
|
322
|
-
if (
|
|
364
|
+
const settings = join3(d, "globalStorage", ext, "settings", "cline_mcp_settings.json");
|
|
365
|
+
if (existsSync3(settings)) return settings;
|
|
323
366
|
}
|
|
324
367
|
return null;
|
|
325
368
|
}
|
|
326
369
|
function removeAiderFileIfPristine(installedPath, templatePath) {
|
|
327
370
|
try {
|
|
328
|
-
if (!
|
|
329
|
-
if (!
|
|
371
|
+
if (!existsSync3(installedPath)) return "absent";
|
|
372
|
+
if (!existsSync3(templatePath)) return "kept-modified";
|
|
330
373
|
let a, b;
|
|
331
374
|
try {
|
|
332
|
-
a =
|
|
333
|
-
b =
|
|
375
|
+
a = readFileSync3(installedPath);
|
|
376
|
+
b = readFileSync3(templatePath);
|
|
334
377
|
} catch {
|
|
335
378
|
return "kept-modified";
|
|
336
379
|
}
|
|
@@ -345,10 +388,10 @@ function removeAiderFileIfPristine(installedPath, templatePath) {
|
|
|
345
388
|
}
|
|
346
389
|
}
|
|
347
390
|
function removeCodexHooks(p) {
|
|
348
|
-
if (!
|
|
391
|
+
if (!existsSync3(p)) return false;
|
|
349
392
|
let doc;
|
|
350
393
|
try {
|
|
351
|
-
doc = JSON.parse(
|
|
394
|
+
doc = JSON.parse(readFileSync3(p, "utf8"));
|
|
352
395
|
} catch {
|
|
353
396
|
return false;
|
|
354
397
|
}
|
|
@@ -389,9 +432,10 @@ function removeCodexHooks(p) {
|
|
|
389
432
|
return false;
|
|
390
433
|
}
|
|
391
434
|
function removeYamlMcpEntry(p) {
|
|
392
|
-
if (!
|
|
393
|
-
const raw =
|
|
435
|
+
if (!existsSync3(p)) return false;
|
|
436
|
+
const raw = readFileSync3(p, "utf8");
|
|
394
437
|
if (!/\bijfw-memory\b/.test(raw)) return false;
|
|
438
|
+
const bak = backupFile(p);
|
|
395
439
|
const py = spawnSync("python3", ["-c", `
|
|
396
440
|
import sys, yaml
|
|
397
441
|
p = sys.argv[1]
|
|
@@ -404,12 +448,11 @@ del srv["ijfw-memory"]
|
|
|
404
448
|
if not srv: del doc["mcp_servers"]
|
|
405
449
|
with open(p + ".tmp", "w") as f:
|
|
406
450
|
yaml.safe_dump(doc, f, sort_keys=False, default_flow_style=False)
|
|
407
|
-
import os
|
|
451
|
+
import os, stat
|
|
452
|
+
os.chmod(p + ".tmp", stat.S_IMODE(os.stat(p).st_mode))
|
|
453
|
+
os.replace(p + ".tmp", p)
|
|
408
454
|
`, p], { encoding: "utf8" });
|
|
409
|
-
if (py.status === 0)
|
|
410
|
-
backupFile(p);
|
|
411
|
-
return true;
|
|
412
|
-
}
|
|
455
|
+
if (py.status === 0) return true;
|
|
413
456
|
const stripped = raw.replace(
|
|
414
457
|
// eslint-disable-next-line security/detect-unsafe-regex -- raw is a small local YAML config file; pattern is line-anchored to the IJFW-owned block.
|
|
415
458
|
/^ ijfw-memory:\n(?: .*\n)*(?:# IJFW-MCP-END ijfw-memory\n)?/m,
|
|
@@ -419,17 +462,93 @@ import os; os.replace(p + ".tmp", p)
|
|
|
419
462
|
/# IJFW-MCP-BEGIN ijfw-memory\n(?:.*\n)*?# IJFW-MCP-END ijfw-memory\n/,
|
|
420
463
|
""
|
|
421
464
|
);
|
|
422
|
-
if (stripped === raw)
|
|
423
|
-
|
|
465
|
+
if (stripped === raw) {
|
|
466
|
+
if (bak) {
|
|
467
|
+
try {
|
|
468
|
+
rmSync(bak, { force: true });
|
|
469
|
+
} catch {
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
424
474
|
writeAtomic(p, stripped);
|
|
425
475
|
return true;
|
|
426
476
|
}
|
|
477
|
+
function resolveShippedTemplate(rel, repoRoot) {
|
|
478
|
+
const root = repoRoot || REPO_ROOT;
|
|
479
|
+
const candidates = [
|
|
480
|
+
join3(root, rel),
|
|
481
|
+
join3(root, "installer", "templates", rel),
|
|
482
|
+
resolve2(__dirname, "..", "templates", rel)
|
|
483
|
+
];
|
|
484
|
+
for (const c of candidates) {
|
|
485
|
+
try {
|
|
486
|
+
if (existsSync3(c)) return c;
|
|
487
|
+
} catch {
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return "";
|
|
491
|
+
}
|
|
492
|
+
function removeHermesIjfwWiring(p) {
|
|
493
|
+
if (!existsSync3(p)) return false;
|
|
494
|
+
const raw = readFileSync3(p, "utf8");
|
|
495
|
+
if (!/\bijfw\b/i.test(raw)) return false;
|
|
496
|
+
const bak = backupFile(p);
|
|
497
|
+
const py = spawnSync("python3", ["-c", `
|
|
498
|
+
import sys, yaml
|
|
499
|
+
p = sys.argv[1]
|
|
500
|
+
with open(p) as f: raw = f.read()
|
|
501
|
+
doc = yaml.safe_load(raw) if raw.strip() else {}
|
|
502
|
+
if not isinstance(doc, dict): sys.exit(2)
|
|
503
|
+
changed = False
|
|
504
|
+
srv = doc.get('mcp_servers')
|
|
505
|
+
if isinstance(srv, dict) and 'ijfw-memory' in srv:
|
|
506
|
+
del srv['ijfw-memory']; changed = True
|
|
507
|
+
if not srv: del doc['mcp_servers']
|
|
508
|
+
pl = doc.get('plugins')
|
|
509
|
+
if isinstance(pl, dict) and isinstance(pl.get('enabled'), list) and 'ijfw' in pl['enabled']:
|
|
510
|
+
pl['enabled'] = [x for x in pl['enabled'] if x != 'ijfw']; changed = True
|
|
511
|
+
if not pl['enabled']: del pl['enabled']
|
|
512
|
+
if not pl: del doc['plugins']
|
|
513
|
+
hk = doc.get('hooks')
|
|
514
|
+
if isinstance(hk, dict):
|
|
515
|
+
for ev in list(hk.keys()):
|
|
516
|
+
items = hk[ev]
|
|
517
|
+
if isinstance(items, list):
|
|
518
|
+
new = [it for it in items if not (isinstance(it, dict) and 'ijfw' in str(it.get('script','')))]
|
|
519
|
+
if len(new) != len(items):
|
|
520
|
+
changed = True
|
|
521
|
+
if new: hk[ev] = new
|
|
522
|
+
else: del hk[ev]
|
|
523
|
+
if isinstance(doc.get('hooks'), dict) and not doc['hooks']: del doc['hooks']
|
|
524
|
+
if not changed: sys.exit(3)
|
|
525
|
+
with open(p + '.tmp', 'w') as f:
|
|
526
|
+
if doc: yaml.safe_dump(doc, f, sort_keys=False, default_flow_style=False)
|
|
527
|
+
else: f.write('')
|
|
528
|
+
import os, stat
|
|
529
|
+
os.chmod(p + '.tmp', stat.S_IMODE(os.stat(p).st_mode))
|
|
530
|
+
os.replace(p + '.tmp', p)
|
|
531
|
+
`, p], { encoding: "utf8" });
|
|
532
|
+
if (py.status === 0) return true;
|
|
533
|
+
const out = raw.replace(/# IJFW-MCP-BEGIN ijfw-memory\n[\s\S]*?# IJFW-MCP-END ijfw-memory\n/g, "").replace(/# IJFW-PLUGINS-BEGIN\n[\s\S]*?# IJFW-PLUGINS-END\n/g, "").replace(/# IJFW-HOOK-BEGIN pre_tool_use\n[\s\S]*?# IJFW-HOOK-END pre_tool_use\n/g, "").replace(/^[ \t]*-[ \t]+ijfw[ \t]*\n/gm, "").replace(/^[ \t]*-[ \t]+script:[ \t]*["']?plugins\/ijfw\/[^\n]*\n/gm, "");
|
|
534
|
+
if (out === raw) {
|
|
535
|
+
if (bak) {
|
|
536
|
+
try {
|
|
537
|
+
rmSync(bak, { force: true });
|
|
538
|
+
} catch {
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
writeAtomic(p, out);
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
427
546
|
function removeIjfwSkills(dir) {
|
|
428
|
-
if (!
|
|
547
|
+
if (!existsSync3(dir)) return 0;
|
|
429
548
|
let count = 0;
|
|
430
|
-
for (const entry of
|
|
549
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
431
550
|
if (entry.isDirectory() && entry.name.startsWith("ijfw-")) {
|
|
432
|
-
rmSync(
|
|
551
|
+
rmSync(join3(dir, entry.name), { recursive: true, force: true });
|
|
433
552
|
count++;
|
|
434
553
|
}
|
|
435
554
|
}
|
|
@@ -460,109 +579,199 @@ var CODEX_COMMAND_FILES = [
|
|
|
460
579
|
"workflow.md"
|
|
461
580
|
];
|
|
462
581
|
function removeCodexCommands(dir) {
|
|
463
|
-
if (!
|
|
582
|
+
if (!existsSync3(dir)) return 0;
|
|
464
583
|
let count = 0;
|
|
465
584
|
for (const name of CODEX_COMMAND_FILES) {
|
|
466
|
-
const path =
|
|
467
|
-
if (
|
|
585
|
+
const path = join3(dir, name);
|
|
586
|
+
if (existsSync3(path)) {
|
|
468
587
|
rmSync(path, { force: true });
|
|
469
588
|
count++;
|
|
470
589
|
}
|
|
471
590
|
}
|
|
472
591
|
return count;
|
|
473
592
|
}
|
|
593
|
+
function removeCodexHookFiles(hooksDir) {
|
|
594
|
+
if (!existsSync3(hooksDir)) return 0;
|
|
595
|
+
let count = 0;
|
|
596
|
+
const scriptsDir = join3(hooksDir, "scripts");
|
|
597
|
+
if (existsSync3(scriptsDir)) {
|
|
598
|
+
rmSync(scriptsDir, { recursive: true, force: true });
|
|
599
|
+
count++;
|
|
600
|
+
}
|
|
601
|
+
let entries = [];
|
|
602
|
+
try {
|
|
603
|
+
entries = readdirSync2(hooksDir, { withFileTypes: true });
|
|
604
|
+
} catch {
|
|
605
|
+
return count;
|
|
606
|
+
}
|
|
607
|
+
for (const e of entries) {
|
|
608
|
+
if (!e.isFile() || !e.name.endsWith(".sh")) continue;
|
|
609
|
+
const p = join3(hooksDir, e.name);
|
|
610
|
+
let body = "";
|
|
611
|
+
try {
|
|
612
|
+
body = readFileSync3(p, "utf8");
|
|
613
|
+
} catch {
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
const header = body.split("\n", 4).slice(0, 4);
|
|
617
|
+
if (header.some((l) => /^#\s*IJFW\b/.test(l))) {
|
|
618
|
+
backupFile(p);
|
|
619
|
+
rmSync(p, { force: true });
|
|
620
|
+
count++;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return count;
|
|
624
|
+
}
|
|
625
|
+
function removeKnownMarketplacesEntry(p) {
|
|
626
|
+
if (!existsSync3(p)) return false;
|
|
627
|
+
let doc;
|
|
628
|
+
try {
|
|
629
|
+
doc = JSON.parse(readFileSync3(p, "utf8"));
|
|
630
|
+
} catch {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
if (!doc || typeof doc !== "object") return false;
|
|
634
|
+
let changed = false;
|
|
635
|
+
if (doc.ijfw) {
|
|
636
|
+
delete doc.ijfw;
|
|
637
|
+
changed = true;
|
|
638
|
+
}
|
|
639
|
+
if (doc.extraKnownMarketplaces && typeof doc.extraKnownMarketplaces === "object" && doc.extraKnownMarketplaces.ijfw) {
|
|
640
|
+
delete doc.extraKnownMarketplaces.ijfw;
|
|
641
|
+
if (Object.keys(doc.extraKnownMarketplaces).length === 0) delete doc.extraKnownMarketplaces;
|
|
642
|
+
changed = true;
|
|
643
|
+
}
|
|
644
|
+
if (!changed) return false;
|
|
645
|
+
backupFile(p);
|
|
646
|
+
writeAtomic(p, JSON.stringify(doc, null, 2) + "\n");
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
474
649
|
function cleanPlatforms(opts = {}) {
|
|
475
650
|
const home = opts.home || HOME;
|
|
476
651
|
const cwd = opts.cwd || process.cwd();
|
|
477
652
|
const repoRoot = opts.repoRoot || REPO_ROOT;
|
|
478
653
|
const removed = [];
|
|
479
|
-
|
|
654
|
+
const rmJsonEntry = (p, label) => {
|
|
655
|
+
const r = removeJsonMcpEntry(p);
|
|
656
|
+
if (r === "parse-failed") {
|
|
657
|
+
removed.push(`${label} (KEPT -- file is not valid JSON but still references ijfw-memory; remove the entry manually)`);
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
return r === true;
|
|
661
|
+
};
|
|
662
|
+
if (rmJsonEntry(join3(home, ".claude", "settings.json"), "~/.claude/settings.json")) {
|
|
663
|
+
removed.push("~/.claude/settings.json (removed ijfw-memory mcp entry)");
|
|
664
|
+
}
|
|
665
|
+
if (removeKnownMarketplacesEntry(join3(home, ".claude", "plugins", "known_marketplaces.json"))) {
|
|
666
|
+
removed.push("~/.claude/plugins/known_marketplaces.json (removed ijfw entry)");
|
|
667
|
+
}
|
|
668
|
+
if (removeTomlSection(join3(home, ".codex", "config.toml"))) {
|
|
480
669
|
removed.push("~/.codex/config.toml (removed [mcp_servers.ijfw-memory])");
|
|
481
670
|
}
|
|
482
|
-
if (removeCodexHooks(
|
|
671
|
+
if (removeCodexHooks(join3(home, ".codex", "hooks.json"))) {
|
|
483
672
|
removed.push("~/.codex/hooks.json (removed IJFW hook entries)");
|
|
484
673
|
}
|
|
485
|
-
const codexSkills = removeIjfwSkills(
|
|
674
|
+
const codexSkills = removeIjfwSkills(join3(home, ".codex", "skills"));
|
|
486
675
|
if (codexSkills > 0) removed.push(`~/.codex/skills/ijfw-* (removed ${codexSkills} skill dirs)`);
|
|
487
|
-
const codexCommands = removeCodexCommands(
|
|
676
|
+
const codexCommands = removeCodexCommands(join3(home, ".codex", "commands"));
|
|
488
677
|
if (codexCommands > 0) removed.push(`~/.codex/commands (removed ${codexCommands} IJFW command aliases)`);
|
|
489
|
-
const codexMd =
|
|
490
|
-
if (
|
|
678
|
+
const codexMd = join3(home, ".codex", "IJFW.md");
|
|
679
|
+
if (existsSync3(codexMd)) {
|
|
491
680
|
rmSync(codexMd, { force: true });
|
|
492
681
|
removed.push("~/.codex/IJFW.md");
|
|
493
682
|
}
|
|
494
|
-
|
|
683
|
+
const codexHookFiles = removeCodexHookFiles(join3(home, ".codex", "hooks"));
|
|
684
|
+
if (codexHookFiles > 0) removed.push(`~/.codex/hooks/ (removed ${codexHookFiles} IJFW hook scripts)`);
|
|
685
|
+
if (rmJsonEntry(join3(home, ".gemini", "settings.json"), "~/.gemini/settings.json")) {
|
|
495
686
|
removed.push("~/.gemini/settings.json (removed ijfw-memory)");
|
|
496
687
|
}
|
|
497
|
-
const geminiExt =
|
|
498
|
-
if (
|
|
688
|
+
const geminiExt = join3(home, ".gemini", "extensions", "ijfw");
|
|
689
|
+
if (existsSync3(geminiExt)) {
|
|
499
690
|
rmSync(geminiExt, { recursive: true, force: true });
|
|
500
691
|
removed.push("~/.gemini/extensions/ijfw/");
|
|
501
692
|
}
|
|
502
|
-
const cursorMcp =
|
|
503
|
-
if (
|
|
504
|
-
if (
|
|
693
|
+
const cursorMcp = join3(cwd, ".cursor", "mcp.json");
|
|
694
|
+
if (rmJsonEntry(cursorMcp, ".cursor/mcp.json")) removed.push(".cursor/mcp.json (removed ijfw-memory)");
|
|
695
|
+
if (rmJsonEntry(join3(home, ".codeium", "windsurf", "mcp_config.json"), "~/.codeium/windsurf/mcp_config.json")) {
|
|
505
696
|
removed.push("~/.codeium/windsurf/mcp_config.json (removed ijfw-memory)");
|
|
506
697
|
}
|
|
507
|
-
const vscodeMcp =
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
698
|
+
const vscodeMcp = join3(cwd, ".vscode", "mcp.json");
|
|
699
|
+
const vscodeLegacy = rmJsonEntry(vscodeMcp, ".vscode/mcp.json");
|
|
700
|
+
const vscodeServers = removeNestedMcpEntry(vscodeMcp, ["servers"]);
|
|
701
|
+
if (vscodeLegacy || vscodeServers) removed.push(".vscode/mcp.json (removed ijfw-memory)");
|
|
702
|
+
if (removeHermesIjfwWiring(join3(home, ".hermes", "config.yaml"))) {
|
|
703
|
+
removed.push("~/.hermes/config.yaml (removed ijfw-memory + plugin + hook wiring)");
|
|
511
704
|
}
|
|
512
|
-
const hermesSkills = removeIjfwSkills(
|
|
705
|
+
const hermesSkills = removeIjfwSkills(join3(home, ".hermes", "skills"));
|
|
513
706
|
if (hermesSkills > 0) removed.push(`~/.hermes/skills/ijfw-* (removed ${hermesSkills} skill dirs)`);
|
|
514
|
-
const hermesMd =
|
|
515
|
-
if (
|
|
707
|
+
const hermesMd = join3(home, ".hermes", "HERMES.md");
|
|
708
|
+
if (existsSync3(hermesMd)) {
|
|
516
709
|
rmSync(hermesMd, { force: true });
|
|
517
710
|
removed.push("~/.hermes/HERMES.md");
|
|
518
711
|
}
|
|
519
|
-
const
|
|
520
|
-
if (
|
|
712
|
+
const hermesPlugin = join3(home, ".hermes", "plugins", "ijfw");
|
|
713
|
+
if (existsSync3(hermesPlugin)) {
|
|
714
|
+
rmSync(hermesPlugin, { recursive: true, force: true });
|
|
715
|
+
removed.push("~/.hermes/plugins/ijfw/ (removed plugin tree)");
|
|
716
|
+
}
|
|
717
|
+
const waylandPluginDir = join3(home, ".wayland", "plugins", "ijfw");
|
|
718
|
+
if (existsSync3(waylandPluginDir)) {
|
|
521
719
|
rmSync(waylandPluginDir, { recursive: true, force: true });
|
|
522
720
|
removed.push("~/.wayland/plugins/ijfw/ (removed plugin.toml + hooks + MCP)");
|
|
523
721
|
}
|
|
524
|
-
if (removeYamlMcpEntry(
|
|
722
|
+
if (removeYamlMcpEntry(join3(home, ".wayland", "config.yaml"))) {
|
|
525
723
|
removed.push("~/.wayland/config.yaml (removed legacy ijfw-memory)");
|
|
526
724
|
}
|
|
527
|
-
const waylandSkills = removeIjfwSkills(
|
|
725
|
+
const waylandSkills = removeIjfwSkills(join3(home, ".wayland", "skills"));
|
|
528
726
|
if (waylandSkills > 0) removed.push(`~/.wayland/skills/ijfw-* (removed ${waylandSkills} skill dirs)`);
|
|
529
|
-
const waylandMd =
|
|
530
|
-
if (
|
|
727
|
+
const waylandMd = join3(home, ".wayland", "WAYLAND.md");
|
|
728
|
+
if (existsSync3(waylandMd)) {
|
|
531
729
|
rmSync(waylandMd, { force: true });
|
|
532
730
|
removed.push("~/.wayland/WAYLAND.md");
|
|
533
731
|
}
|
|
534
|
-
if (
|
|
732
|
+
if (rmJsonEntry(join3(home, ".qwen", "settings.json"), "~/.qwen/settings.json")) {
|
|
535
733
|
removed.push("~/.qwen/settings.json (removed ijfw-memory)");
|
|
536
734
|
}
|
|
537
|
-
if (
|
|
735
|
+
if (rmJsonEntry(join3(home, ".kimi", "mcp.json"), "~/.kimi/mcp.json")) {
|
|
538
736
|
removed.push("~/.kimi/mcp.json (removed ijfw-memory)");
|
|
539
737
|
}
|
|
540
|
-
if (
|
|
738
|
+
if (rmJsonEntry(join3(home, ".gemini", "antigravity", "mcp_config.json"), "~/.gemini/antigravity/mcp_config.json")) {
|
|
541
739
|
removed.push("~/.gemini/antigravity/mcp_config.json (removed ijfw-memory)");
|
|
542
740
|
}
|
|
543
|
-
if (
|
|
741
|
+
if (rmJsonEntry(join3(home, ".gemini", "config", "mcp_config.json"), "~/.gemini/config/mcp_config.json")) {
|
|
544
742
|
removed.push("~/.gemini/config/mcp_config.json (removed ijfw-memory)");
|
|
545
743
|
}
|
|
546
|
-
if (removeNestedMcpEntry(
|
|
744
|
+
if (removeNestedMcpEntry(join3(home, ".config", "opencode", "opencode.json"), ["mcp"])) {
|
|
547
745
|
removed.push("~/.config/opencode/opencode.json (removed mcp.ijfw-memory)");
|
|
548
746
|
}
|
|
549
|
-
if (removeNestedMcpEntry(
|
|
747
|
+
if (removeNestedMcpEntry(join3(home, ".openclaw", "openclaw.json"), ["mcp", "servers"])) {
|
|
550
748
|
removed.push("~/.openclaw/openclaw.json (removed mcp.servers.ijfw-memory)");
|
|
551
749
|
}
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
750
|
+
const piPath = join3(home, ".pi", "agent", "AGENTS.md");
|
|
751
|
+
const piStatus = stripMarkerFile(piPath, { label: "~/.pi/agent/AGENTS.md" });
|
|
752
|
+
if (piStatus) {
|
|
753
|
+
removed.push(piStatus);
|
|
754
|
+
} else {
|
|
755
|
+
const piPristine = removeAiderFileIfPristine(
|
|
756
|
+
piPath,
|
|
757
|
+
resolveShippedTemplate(join3("pi", "AGENTS.md"), repoRoot)
|
|
758
|
+
);
|
|
759
|
+
if (piPristine === "removed") {
|
|
760
|
+
removed.push("~/.pi/agent/AGENTS.md (removed -- matched shipped template)");
|
|
761
|
+
} else if (piPristine === "kept-modified") {
|
|
762
|
+
removed.push("~/.pi/agent/AGENTS.md (KEPT -- differs from shipped template; remove manually if it is IJFW-only)");
|
|
763
|
+
}
|
|
764
|
+
}
|
|
556
765
|
const clineSettings = resolveClineSettingsPath(home);
|
|
557
766
|
if (clineSettings) {
|
|
558
|
-
if (
|
|
767
|
+
if (rmJsonEntry(clineSettings, clineSettings)) {
|
|
559
768
|
removed.push(`${clineSettings} (removed ijfw-memory)`);
|
|
560
769
|
}
|
|
561
770
|
} else {
|
|
562
771
|
removed.push("Cline: no globalStorage found -- if you use Cline, remove the ijfw-memory MCP entry manually.");
|
|
563
772
|
}
|
|
564
773
|
const confResult = removeAiderFileIfPristine(
|
|
565
|
-
|
|
774
|
+
join3(home, ".aider.conf.yml"),
|
|
566
775
|
resolveAiderTemplate("aider.conf.yml", repoRoot)
|
|
567
776
|
);
|
|
568
777
|
if (confResult === "removed") {
|
|
@@ -571,7 +780,7 @@ function cleanPlatforms(opts = {}) {
|
|
|
571
780
|
removed.push("~/.aider.conf.yml (KEPT -- differs from shipped template; remove manually if it is IJFW-only)");
|
|
572
781
|
}
|
|
573
782
|
const convResult = removeAiderFileIfPristine(
|
|
574
|
-
|
|
783
|
+
join3(home, "CONVENTIONS.md"),
|
|
575
784
|
resolveAiderTemplate("CONVENTIONS.md", repoRoot)
|
|
576
785
|
);
|
|
577
786
|
if (convResult === "removed") {
|
|
@@ -583,8 +792,8 @@ function cleanPlatforms(opts = {}) {
|
|
|
583
792
|
}
|
|
584
793
|
function parseRegistryPaths(registryPath) {
|
|
585
794
|
try {
|
|
586
|
-
if (!
|
|
587
|
-
const raw =
|
|
795
|
+
if (!existsSync3(registryPath)) return [];
|
|
796
|
+
const raw = readFileSync3(registryPath, "utf8");
|
|
588
797
|
const paths = [];
|
|
589
798
|
for (const line of raw.split("\n")) {
|
|
590
799
|
const trimmed = line.trim();
|
|
@@ -602,37 +811,37 @@ function parseRegistryPaths(registryPath) {
|
|
|
602
811
|
function stripRegisteredProjectBlocks(opts = {}) {
|
|
603
812
|
const home = opts.home || HOME;
|
|
604
813
|
const cwd = opts.cwd || process.cwd();
|
|
605
|
-
const registryPath = opts.registryPath ||
|
|
814
|
+
const registryPath = opts.registryPath || join3(home, ".ijfw", "registry.md");
|
|
606
815
|
const results = [];
|
|
607
816
|
for (const projPath of parseRegistryPaths(registryPath)) {
|
|
608
817
|
let dirExists = false;
|
|
609
818
|
try {
|
|
610
|
-
dirExists =
|
|
819
|
+
dirExists = existsSync3(projPath);
|
|
611
820
|
} catch {
|
|
612
821
|
dirExists = false;
|
|
613
822
|
}
|
|
614
823
|
if (!dirExists) continue;
|
|
615
824
|
for (const name of ["CLAUDE.md", "AGENTS.md"]) {
|
|
616
|
-
const filePath =
|
|
617
|
-
const status = stripMarkerFile(filePath, { label:
|
|
825
|
+
const filePath = join3(projPath, name);
|
|
826
|
+
const status = stripMarkerFile(filePath, { label: join3(projPath, name) });
|
|
618
827
|
if (status) results.push(status);
|
|
619
828
|
}
|
|
620
829
|
}
|
|
621
830
|
try {
|
|
622
|
-
const cursorRule =
|
|
623
|
-
if (
|
|
831
|
+
const cursorRule = join3(cwd, ".cursor", "rules", "ijfw.mdc");
|
|
832
|
+
if (existsSync3(cursorRule)) {
|
|
624
833
|
backupFile(cursorRule);
|
|
625
834
|
rmSync(cursorRule, { force: true });
|
|
626
835
|
results.push(".cursor/rules/ijfw.mdc (removed -- wholly IJFW-authored)");
|
|
627
836
|
}
|
|
628
837
|
} catch {
|
|
629
838
|
}
|
|
630
|
-
const windsurfStatus = stripMarkerFile(
|
|
839
|
+
const windsurfStatus = stripMarkerFile(join3(cwd, ".windsurfrules"), {
|
|
631
840
|
label: ".windsurfrules",
|
|
632
841
|
deleteIfEmpty: true
|
|
633
842
|
});
|
|
634
843
|
if (windsurfStatus) results.push(windsurfStatus);
|
|
635
|
-
const copilotStatus = stripMarkerFile(
|
|
844
|
+
const copilotStatus = stripMarkerFile(join3(cwd, ".github", "copilot-instructions.md"), {
|
|
636
845
|
label: ".github/copilot-instructions.md",
|
|
637
846
|
deleteIfEmpty: true
|
|
638
847
|
});
|
|
@@ -642,17 +851,77 @@ function stripRegisteredProjectBlocks(opts = {}) {
|
|
|
642
851
|
function resolveTarget(opt) {
|
|
643
852
|
if (opt.dir) return resolve2(opt.dir);
|
|
644
853
|
if (process.env.IJFW_HOME) return resolve2(process.env.IJFW_HOME);
|
|
645
|
-
return
|
|
854
|
+
return join3(homedir2(), ".ijfw");
|
|
855
|
+
}
|
|
856
|
+
function assertSafePurgeTarget(target) {
|
|
857
|
+
let real = target;
|
|
858
|
+
try {
|
|
859
|
+
real = realpathSync(target);
|
|
860
|
+
} catch {
|
|
861
|
+
}
|
|
862
|
+
let home = homedir2();
|
|
863
|
+
try {
|
|
864
|
+
home = realpathSync(home);
|
|
865
|
+
} catch {
|
|
866
|
+
}
|
|
867
|
+
const isFsRoot = (p) => p === "/" || /^[A-Za-z]:[\\/]?$/.test(p);
|
|
868
|
+
if (!real || isFsRoot(real) || real === home) {
|
|
869
|
+
throw new Error(`refusing to delete '${target}': it resolves to the home or filesystem root.`);
|
|
870
|
+
}
|
|
871
|
+
const segs = real.split(/[\\/]+/).filter((s) => s && !/^[A-Za-z]:$/.test(s));
|
|
872
|
+
if (segs.length < 2) {
|
|
873
|
+
throw new Error(`refusing to delete shallow path '${real}'.`);
|
|
874
|
+
}
|
|
875
|
+
const hasIjfwState = () => {
|
|
876
|
+
try {
|
|
877
|
+
const doc = JSON.parse(readFileSync3(join3(real, "state.json"), "utf8"));
|
|
878
|
+
return !!doc && typeof doc === "object" && ("install_method" in doc || "installed_version" in doc);
|
|
879
|
+
} catch {
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
const looksIjfw = basename(real) === ".ijfw" || existsSync3(join3(real, "install-method")) || existsSync3(join3(real, "install-ledger.json")) || hasIjfwState();
|
|
884
|
+
if (!looksIjfw) {
|
|
885
|
+
throw new Error(`refusing to delete '${target}': it does not look like an IJFW install (no .ijfw basename / install-method / install-ledger.json / IJFW state.json). Aborting.`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
function removeCreatedDirs(home, createdDirs) {
|
|
889
|
+
const removed = [];
|
|
890
|
+
for (const rel of createdDirs || []) {
|
|
891
|
+
const abs = join3(home, rel);
|
|
892
|
+
if (isEmptyDir(abs)) {
|
|
893
|
+
try {
|
|
894
|
+
rmSync(abs, { recursive: false, force: true });
|
|
895
|
+
removed.push(`~/${rel} (IJFW-created, now empty)`);
|
|
896
|
+
} catch {
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return removed;
|
|
646
901
|
}
|
|
647
902
|
async function main() {
|
|
648
903
|
const opts = parseArgs(process.argv);
|
|
649
904
|
const target = resolveTarget(opts);
|
|
905
|
+
const realOrSelf = (p) => {
|
|
906
|
+
try {
|
|
907
|
+
return realpathSync(p);
|
|
908
|
+
} catch {
|
|
909
|
+
return p;
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
const isCanonical = realOrSelf(target) === realOrSelf(join3(HOME, ".ijfw"));
|
|
913
|
+
const ledgerCreatedDirs = existsSync3(target) ? readLedger(target).createdDirs : [];
|
|
650
914
|
console.log("This will remove IJFW configuration. Your memory at ~/.ijfw/memory/ will be preserved. Delete manually if desired.");
|
|
651
915
|
if (opts.purge) {
|
|
652
916
|
console.log("WARNING: --purge will also DELETE ~/.ijfw/memory/ (project memory cannot be recovered).");
|
|
653
917
|
}
|
|
654
918
|
console.log("");
|
|
655
|
-
if (!opts.yes
|
|
919
|
+
if (!opts.yes) {
|
|
920
|
+
if (!process.stdin.isTTY) {
|
|
921
|
+
console.error("Refusing to proceed: stdin is not a TTY, so the confirmation prompt cannot be answered.");
|
|
922
|
+
console.error("Non-interactive uninstall requires an explicit --yes (or -y). Nothing was changed.");
|
|
923
|
+
process.exit(1);
|
|
924
|
+
}
|
|
656
925
|
const ok = await confirm("Proceed with IJFW uninstall? [y/N] ");
|
|
657
926
|
if (!ok) {
|
|
658
927
|
console.log("Uninstall cancelled. Nothing was changed.");
|
|
@@ -660,16 +929,18 @@ async function main() {
|
|
|
660
929
|
}
|
|
661
930
|
console.log("");
|
|
662
931
|
}
|
|
663
|
-
if (!
|
|
932
|
+
if (!existsSync3(target)) {
|
|
664
933
|
console.log(`IJFW directory absent (${target}); platform cleanup only.`);
|
|
665
934
|
} else if (opts.purge) {
|
|
935
|
+
assertSafePurgeTarget(target);
|
|
666
936
|
rmSync(target, { recursive: true, force: true });
|
|
667
937
|
console.log(` removed ${target} (purged).`);
|
|
668
938
|
} else {
|
|
669
|
-
|
|
939
|
+
assertSafePurgeTarget(target);
|
|
940
|
+
const memDir = join3(target, "memory");
|
|
670
941
|
let stash = null;
|
|
671
|
-
if (
|
|
672
|
-
stash = mkdtempSync(
|
|
942
|
+
if (existsSync3(memDir)) {
|
|
943
|
+
stash = mkdtempSync(join3(tmpdir(), "ijfw-memory-"));
|
|
673
944
|
cpSync(memDir, stash, { recursive: true });
|
|
674
945
|
}
|
|
675
946
|
rmSync(target, { recursive: true, force: true });
|
|
@@ -681,11 +952,9 @@ async function main() {
|
|
|
681
952
|
console.log(" memory/ was not present; nothing to preserve");
|
|
682
953
|
}
|
|
683
954
|
}
|
|
684
|
-
const canonicalDir = join2(HOME, ".ijfw");
|
|
685
|
-
const isCanonical = target === canonicalDir;
|
|
686
955
|
if (isCanonical && !opts.noMarketplace) {
|
|
687
956
|
const settingsPath = claudeSettingsPath();
|
|
688
|
-
if (
|
|
957
|
+
if (existsSync3(settingsPath)) {
|
|
689
958
|
unmergeMarketplace(settingsPath);
|
|
690
959
|
console.log(` marketplace removed from ${settingsPath}`);
|
|
691
960
|
}
|
|
@@ -701,6 +970,13 @@ async function main() {
|
|
|
701
970
|
console.log(" project blocks cleaned:");
|
|
702
971
|
for (const line of projectCleaned) console.log(` ${line}`);
|
|
703
972
|
}
|
|
973
|
+
if (opts.purge) {
|
|
974
|
+
const dirsRemoved = removeCreatedDirs(HOME, ledgerCreatedDirs);
|
|
975
|
+
if (dirsRemoved.length > 0) {
|
|
976
|
+
console.log(" IJFW-created dirs removed:");
|
|
977
|
+
for (const line of dirsRemoved) console.log(` ${line}`);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
704
980
|
} else {
|
|
705
981
|
console.log(` custom-dir uninstall (${target}) -- platform configs in your real home left untouched.`);
|
|
706
982
|
}
|
|
@@ -715,10 +991,15 @@ if (isDirectRun) {
|
|
|
715
991
|
});
|
|
716
992
|
}
|
|
717
993
|
export {
|
|
994
|
+
assertSafePurgeTarget,
|
|
718
995
|
cleanPlatforms,
|
|
719
996
|
parseRegistryPaths,
|
|
720
997
|
removeAiderFileIfPristine,
|
|
998
|
+
removeCodexHookFiles,
|
|
999
|
+
removeJsonMcpEntry,
|
|
721
1000
|
removeNestedMcpEntry,
|
|
1001
|
+
removeTomlSection,
|
|
1002
|
+
removeYamlMcpEntry,
|
|
722
1003
|
resolveAiderTemplate,
|
|
723
1004
|
resolveClineSettingsPath,
|
|
724
1005
|
stripIjfwRegions,
|