@ijfw/install 1.2.10 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/install.js CHANGED
@@ -1,28 +1,1790 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/install-helpers.js
13
+ import {
14
+ existsSync as existsSync3,
15
+ readFileSync as readFileSync2,
16
+ writeFileSync as writeFileSync2,
17
+ renameSync as renameSync2,
18
+ copyFileSync,
19
+ chmodSync,
20
+ mkdirSync as mkdirSync2,
21
+ realpathSync,
22
+ statSync
23
+ } from "node:fs";
24
+ import { dirname as dirname3, basename, join as join3, normalize, delimiter } from "node:path";
25
+ import { homedir as homedir2 } from "node:os";
26
+ import { createHash } from "node:crypto";
27
+ function printOk(msg) {
28
+ process.stdout.write(` [ok] ${msg}
29
+ `);
30
+ }
31
+ function printNote(msg) {
32
+ process.stdout.write(` [--] ${msg}
33
+ `);
34
+ }
35
+ function printInfo(msg) {
36
+ process.stdout.write(` -- ${msg}
37
+ `);
38
+ }
39
+ function printWarn(msg) {
40
+ process.stdout.write(` [!] ${msg}
41
+ `);
42
+ }
43
+ function printSection(name) {
44
+ process.stdout.write(`
45
+ [${name}]
46
+ `);
47
+ }
48
+ function homeReal() {
49
+ const h = homedir2();
50
+ try {
51
+ return realpathSync(h);
52
+ } catch {
53
+ return h;
54
+ }
55
+ }
56
+ function nativePath(p) {
57
+ if (p == null) return p;
58
+ return normalize(String(p));
59
+ }
60
+ function writeAtomic(path3, contents, opts = {}) {
61
+ const mode = opts.mode ?? 384;
62
+ mkdirSync2(dirname3(path3), { recursive: true });
63
+ const tmp = `${path3}.tmp.${process.pid}`;
64
+ writeFileSync2(tmp, contents, { mode });
65
+ renameSync2(tmp, path3);
66
+ try {
67
+ chmodSync(path3, mode);
68
+ } catch {
69
+ }
70
+ }
71
+ function backup(path3, ts) {
72
+ try {
73
+ const st = statSync(path3);
74
+ if (!st.isFile()) return null;
75
+ } catch {
76
+ return null;
77
+ }
78
+ const dst = `${path3}.bak.${ts}`;
79
+ try {
80
+ copyFileSync(path3, dst);
81
+ printInfo(`backup: ${basename(path3)}.bak.${ts}`);
82
+ return dst;
83
+ } catch {
84
+ return null;
85
+ }
86
+ }
87
+ function safeChecksum(path3) {
88
+ try {
89
+ return createHash("sha1").update(readFileSync2(path3)).digest("hex");
90
+ } catch {
91
+ return "";
92
+ }
93
+ }
94
+ function installHook(src, dst, ts) {
95
+ if (!existsSync3(src)) return;
96
+ if (existsSync3(dst)) {
97
+ const srcSum = safeChecksum(src);
98
+ const dstSum = safeChecksum(dst);
99
+ if (!srcSum || !dstSum) {
100
+ try {
101
+ copyFileSync(dst, `${dst}.bak.${ts}`);
102
+ } catch {
103
+ }
104
+ printNote(`Updated ${basename(dst)} (no checksum util on host -- precautionary backup)`);
105
+ } else if (srcSum === dstSum) {
106
+ return;
107
+ } else {
108
+ try {
109
+ copyFileSync(dst, `${dst}.bak.${ts}`);
110
+ } catch {
111
+ }
112
+ printNote(`Updated ${basename(dst)} (your custom version backed up to ${basename(dst)}.bak.${ts})`);
113
+ }
114
+ }
115
+ mkdirSync2(dirname3(dst), { recursive: true });
116
+ copyFileSync(src, dst);
117
+ if (!IS_WIN) {
118
+ try {
119
+ chmodSync(dst, 493);
120
+ } catch {
121
+ }
122
+ }
123
+ }
124
+ function which(bin) {
125
+ if (!bin) return null;
126
+ const path3 = process.env.PATH || "";
127
+ const parts = path3.split(delimiter).filter(Boolean);
128
+ const exts = IS_WIN ? (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";").map((e) => e.toLowerCase()) : [""];
129
+ for (const dir of parts) {
130
+ for (const ext of exts) {
131
+ const candidate = join3(dir, bin + ext);
132
+ try {
133
+ if (existsSync3(candidate)) return candidate;
134
+ } catch {
135
+ }
136
+ }
137
+ }
138
+ return null;
139
+ }
140
+ function hasBin(bin) {
141
+ return which(bin) != null;
142
+ }
143
+ function isLive(targetId, home) {
144
+ const H = home || homeReal();
145
+ const APPDATA = process.env.APPDATA || "";
146
+ const appdataOr = (rel) => APPDATA ? join3(APPDATA, rel) : null;
147
+ switch (targetId) {
148
+ case "claude":
149
+ return hasBin("claude") || existsSync3(join3(H, ".claude"));
150
+ case "codex":
151
+ return hasBin("codex") || existsSync3(join3(H, ".codex"));
152
+ case "gemini":
153
+ return hasBin("gemini") || existsSync3(join3(H, ".gemini"));
154
+ case "cursor":
155
+ return hasBin("cursor");
156
+ case "windsurf":
157
+ return hasBin("windsurf") || existsSync3(join3(H, ".codeium", "windsurf"));
158
+ case "copilot": {
159
+ if (hasBin("code")) return true;
160
+ const candidates = [
161
+ join3(H, ".vscode"),
162
+ join3(H, ".config", "Code"),
163
+ join3(H, "Library", "Application Support", "Code"),
164
+ appdataOr("Code")
165
+ ].filter(Boolean);
166
+ return candidates.some((p) => existsSync3(p));
167
+ }
168
+ case "hermes":
169
+ return hasBin("hermes") || existsSync3(join3(H, ".hermes"));
170
+ case "wayland":
171
+ return hasBin("wayland") || existsSync3(join3(H, ".wayland"));
172
+ case "opencode":
173
+ return hasBin("opencode") || existsSync3(join3(H, ".config", "opencode"));
174
+ case "qwen":
175
+ return hasBin("qwen") || existsSync3(join3(H, ".qwen"));
176
+ case "cline": {
177
+ const ext = "saoudrizwan.claude-dev";
178
+ const candidates = [
179
+ // macOS
180
+ join3(H, "Library", "Application Support", "Code", "User", "globalStorage", ext),
181
+ join3(H, "Library", "Application Support", "Code - Insiders", "User", "globalStorage", ext),
182
+ join3(H, "Library", "Application Support", "VSCodium", "User", "globalStorage", ext),
183
+ // Linux
184
+ join3(H, ".config", "Code", "User", "globalStorage", ext),
185
+ join3(H, ".config", "VSCodium", "User", "globalStorage", ext),
186
+ join3(H, ".var", "app", "com.visualstudio.code", "config", "Code", "User", "globalStorage", ext),
187
+ join3(H, "snap", "code", "current", ".config", "Code", "User", "globalStorage", ext),
188
+ // Windows
189
+ appdataOr(join3("Code", "User", "globalStorage", ext)),
190
+ appdataOr(join3("Code - Insiders", "User", "globalStorage", ext)),
191
+ appdataOr(join3("VSCodium", "User", "globalStorage", ext)),
192
+ // Catch-all extensions dir
193
+ join3(H, ".vscode", "extensions")
194
+ ].filter(Boolean);
195
+ return candidates.some((p) => existsSync3(p));
196
+ }
197
+ case "kimi":
198
+ return hasBin("kimi") || existsSync3(join3(H, ".kimi"));
199
+ case "openclaw":
200
+ return hasBin("openclaw") || existsSync3(join3(H, ".openclaw"));
201
+ case "aider":
202
+ return hasBin("aider") || existsSync3(join3(H, ".aider.conf.yml"));
203
+ default:
204
+ return false;
205
+ }
206
+ }
207
+ function prettyName(targetId) {
208
+ const map = {
209
+ claude: "Claude Code",
210
+ codex: "Codex",
211
+ gemini: "Gemini",
212
+ cursor: "Cursor",
213
+ windsurf: "Windsurf",
214
+ copilot: "Copilot",
215
+ hermes: "Hermes",
216
+ wayland: "Wayland",
217
+ opencode: "OpenCode",
218
+ qwen: "Qwen Code",
219
+ cline: "Cline",
220
+ kimi: "Kimi Code",
221
+ openclaw: "OpenClaw",
222
+ aider: "Aider"
223
+ };
224
+ return map[targetId] || String(targetId);
225
+ }
226
+ function readJsonOrEmpty(path3) {
227
+ if (!existsSync3(path3)) return {};
228
+ try {
229
+ const raw = readFileSync2(path3, "utf8") || "{}";
230
+ const parsed = JSON.parse(raw);
231
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
232
+ return parsed;
233
+ } catch {
234
+ return {};
235
+ }
236
+ }
237
+ function mergeJson(dst, serverJs, ts) {
238
+ mkdirSync2(dirname3(dst), { recursive: true });
239
+ if (ts) backup(dst, ts);
240
+ const doc = readJsonOrEmpty(dst);
241
+ if (!doc.mcpServers || typeof doc.mcpServers !== "object") doc.mcpServers = {};
242
+ const isWin = IS_WIN;
243
+ const nodeDir = dirname3(process.execPath);
244
+ const home = homedir2();
245
+ const candidatePaths = isWin ? [] : [
246
+ nodeDir,
247
+ "/opt/homebrew/bin",
248
+ "/usr/local/bin",
249
+ `${home}/.nvm/versions/node/${process.version}/bin`,
250
+ "/usr/bin",
251
+ "/bin"
252
+ ];
253
+ const envPath = candidatePaths.filter((d) => {
254
+ try {
255
+ return typeof d === "string" && d.length > 0 && existsSync3(d);
256
+ } catch {
257
+ return false;
258
+ }
259
+ }).join(":");
260
+ const entry = { command: "node", args: [serverJs] };
261
+ if (envPath) entry.env = { PATH: envPath };
262
+ doc.mcpServers["ijfw-memory"] = entry;
263
+ writeAtomic(dst, JSON.stringify(doc, null, 2) + "\n", { mode: 384 });
264
+ }
265
+ function mergeToml(dst, serverJs, ts) {
266
+ mkdirSync2(dirname3(dst), { recursive: true });
267
+ if (ts) backup(dst, ts);
268
+ let text = "";
269
+ try {
270
+ text = existsSync3(dst) ? readFileSync2(dst, "utf8") : "";
271
+ } catch {
272
+ text = "";
273
+ }
274
+ text = stripTomlSection(text, "mcp_servers.ijfw-memory");
275
+ if (/^\[features\]/m.test(text)) {
276
+ if (!/^codex_hooks\s*=/m.test(text)) {
277
+ text = text.replace(/^(\[features\][^\n]*\n)/m, "$1codex_hooks = true\n");
278
+ }
279
+ } else {
280
+ text = text.replace(/\n+$/, "") + "\n\n[features]\ncodex_hooks = true\n";
281
+ }
282
+ if (!/^suppress_unstable_features_warning\s*=/m.test(text)) {
283
+ if (/^\[/m.test(text)) {
284
+ text = text.replace(/^(\[)/m, "suppress_unstable_features_warning = true\n\n$1");
285
+ } else {
286
+ text = text.replace(/\n+$/, "") + "\nsuppress_unstable_features_warning = true\n";
287
+ }
288
+ }
289
+ const escaped = String(serverJs).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
290
+ let block = "";
291
+ if (text && !text.endsWith("\n")) block += "\n";
292
+ block += "\n[mcp_servers.ijfw-memory]\n";
293
+ block += 'command = "node"\n';
294
+ block += `args = ["${escaped}"]
295
+ `;
296
+ block += "enabled = true\n";
297
+ block += "startup_timeout_sec = 10\n";
298
+ block += "tool_timeout_sec = 30\n";
299
+ writeAtomic(dst, text + block, { mode: 384 });
300
+ }
301
+ function stripTomlSection(text, sectionName) {
302
+ const safe = sectionName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
303
+ const lines = text.split("\n");
304
+ const out = [];
305
+ let skip = false;
306
+ const headerRe = new RegExp(`^\\[${safe}\\][\\s]*$`);
307
+ for (const line of lines) {
308
+ if (headerRe.test(line)) {
309
+ skip = true;
310
+ continue;
311
+ }
312
+ if (skip && line.startsWith("[") && !headerRe.test(line)) skip = false;
313
+ if (skip) continue;
314
+ out.push(line);
315
+ }
316
+ return out.join("\n");
317
+ }
318
+ function mergeYamlMcp(dst, serverJs, ts) {
319
+ mkdirSync2(dirname3(dst), { recursive: true });
320
+ if (ts) backup(dst, ts);
321
+ let text = "";
322
+ try {
323
+ text = existsSync3(dst) ? readFileSync2(dst, "utf8") : "";
324
+ } catch {
325
+ text = "";
326
+ }
327
+ text = stripSentinelBlock(text, "# IJFW-MCP-BEGIN ijfw-memory", "# IJFW-MCP-END ijfw-memory");
328
+ if (!/^mcp_servers:/m.test(text)) {
329
+ if (text && !text.endsWith("\n")) text += "\n";
330
+ text += "\nmcp_servers:\n";
331
+ }
332
+ const escaped = String(serverJs).replace(/"/g, '\\"');
333
+ let block = "";
334
+ if (!text.endsWith("\n")) block += "\n";
335
+ block += "# IJFW-MCP-BEGIN ijfw-memory\n";
336
+ block += " ijfw-memory:\n";
337
+ block += ' command: "node"\n';
338
+ block += ` args: ["${escaped}"]
339
+ `;
340
+ block += " enabled: true\n";
341
+ block += "# IJFW-MCP-END ijfw-memory\n";
342
+ writeAtomic(dst, text + block, { mode: 384 });
343
+ }
344
+ function stripSentinelBlock(text, beginMark, endMark) {
345
+ const lines = text.split("\n");
346
+ const out = [];
347
+ let skip = false;
348
+ for (const line of lines) {
349
+ if (line === beginMark) {
350
+ skip = true;
351
+ continue;
352
+ }
353
+ if (line === endMark) {
354
+ skip = false;
355
+ continue;
356
+ }
357
+ if (skip) continue;
358
+ out.push(line);
359
+ }
360
+ return out.join("\n");
361
+ }
362
+ function mergeYamlPluginsEnabled(dst, pluginName, ts) {
363
+ mkdirSync2(dirname3(dst), { recursive: true });
364
+ if (ts) backup(dst, ts);
365
+ let text = "";
366
+ try {
367
+ text = existsSync3(dst) ? readFileSync2(dst, "utf8") : "";
368
+ } catch {
369
+ text = "";
370
+ }
371
+ text = stripSentinelBlock(text, "# IJFW-PLUGINS-BEGIN", "# IJFW-PLUGINS-END");
372
+ const lines = text.split("\n");
373
+ let pluginsLineIdx = -1;
374
+ let enabledLineIdx = -1;
375
+ let inPluginsBlock = false;
376
+ let alreadyListed = false;
377
+ const itemRe = new RegExp(`^\\s+-\\s+${pluginName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*$`);
378
+ for (let i = 0; i < lines.length; i++) {
379
+ const line = lines[i];
380
+ if (/^plugins:\s*$/.test(line)) {
381
+ pluginsLineIdx = i;
382
+ inPluginsBlock = true;
383
+ continue;
384
+ }
385
+ if (inPluginsBlock) {
386
+ if (/^\S/.test(line) && line.trim() !== "") {
387
+ inPluginsBlock = false;
388
+ } else if (/^\s+enabled:\s*(\[\s*\])?\s*$/.test(line) || /^\s+enabled:\s*\[.*\]\s*$/.test(line)) {
389
+ enabledLineIdx = i;
390
+ } else if (enabledLineIdx >= 0 && itemRe.test(line)) {
391
+ alreadyListed = true;
392
+ }
393
+ }
394
+ }
395
+ if (pluginsLineIdx < 0) {
396
+ if (text && !text.endsWith("\n")) text += "\n";
397
+ text += "\nplugins:\n";
398
+ const newLines = text.split("\n");
399
+ pluginsLineIdx = newLines.length - 2;
400
+ enabledLineIdx = -1;
401
+ }
402
+ let cur = text.split("\n");
403
+ if (enabledLineIdx < 0) {
404
+ cur.splice(pluginsLineIdx + 1, 0, " enabled: []");
405
+ enabledLineIdx = pluginsLineIdx + 1;
406
+ }
407
+ if (!alreadyListed) {
408
+ const enabledLine = cur[enabledLineIdx];
409
+ if (/^\s+enabled:\s*\[\s*\]\s*$/.test(enabledLine)) {
410
+ cur[enabledLineIdx] = " enabled:";
411
+ cur.splice(enabledLineIdx + 1, 0, ` - ${pluginName}`);
412
+ } else if (/^\s+enabled:\s*\[.+\]\s*$/.test(enabledLine)) {
413
+ cur[enabledLineIdx] = enabledLine.replace(/\]\s*$/, `, ${pluginName}]`);
414
+ } else {
415
+ cur.splice(enabledLineIdx + 1, 0, ` - ${pluginName}`);
416
+ }
417
+ }
418
+ let outText = cur.join("\n");
419
+ if (outText && !outText.endsWith("\n")) outText += "\n";
420
+ outText += "# IJFW-PLUGINS-BEGIN\n";
421
+ outText += `# plugin ${pluginName} registered by IJFW installer
422
+ `;
423
+ outText += "# IJFW-PLUGINS-END\n";
424
+ writeAtomic(dst, outText, { mode: 384 });
425
+ }
426
+ function opencodeMerge(dst, serverJs, ts) {
427
+ mkdirSync2(dirname3(dst), { recursive: true });
428
+ if (ts) backup(dst, ts);
429
+ const doc = readJsonOrEmpty(dst);
430
+ if (!doc.mcp || typeof doc.mcp !== "object") doc.mcp = {};
431
+ doc.mcp["ijfw-memory"] = { type: "local", command: ["node", serverJs] };
432
+ writeAtomic(dst, JSON.stringify(doc, null, 2), { mode: 384 });
433
+ }
434
+ function openclawMerge(dst, serverJs, ts) {
435
+ mkdirSync2(dirname3(dst), { recursive: true });
436
+ if (ts) backup(dst, ts);
437
+ const doc = readJsonOrEmpty(dst);
438
+ if (!doc.mcp || typeof doc.mcp !== "object") doc.mcp = {};
439
+ if (!doc.mcp.servers || typeof doc.mcp.servers !== "object") doc.mcp.servers = {};
440
+ doc.mcp.servers["ijfw-memory"] = { command: "node", args: [serverJs] };
441
+ writeAtomic(dst, JSON.stringify(doc, null, 2), { mode: 384 });
442
+ }
443
+ function clineMerge(serverJs, home, ts) {
444
+ const H = home || homeReal();
445
+ const APPDATA = process.env.APPDATA || join3(H, "AppData", "Roaming");
446
+ const ext = "saoudrizwan.claude-dev";
447
+ let candidates;
448
+ let osDefault;
449
+ if (process.platform === "darwin") {
450
+ candidates = [
451
+ join3(H, "Library", "Application Support", "Code", "User"),
452
+ join3(H, "Library", "Application Support", "Code - Insiders", "User"),
453
+ join3(H, "Library", "Application Support", "VSCodium", "User")
454
+ ];
455
+ osDefault = join3(H, "Library", "Application Support", "Code", "User");
456
+ } else if (IS_WIN) {
457
+ candidates = [
458
+ join3(APPDATA, "Code", "User"),
459
+ join3(APPDATA, "Code - Insiders", "User"),
460
+ join3(APPDATA, "VSCodium", "User")
461
+ ];
462
+ osDefault = join3(APPDATA, "Code", "User");
463
+ } else {
464
+ candidates = [
465
+ join3(H, ".config", "Code", "User"),
466
+ join3(H, ".config", "VSCodium", "User"),
467
+ join3(H, ".var", "app", "com.visualstudio.code", "config", "Code", "User"),
468
+ join3(H, "snap", "code", "current", ".config", "Code", "User")
469
+ ];
470
+ osDefault = join3(H, ".config", "Code", "User");
471
+ }
472
+ let userDir = "";
473
+ for (const c of candidates) {
474
+ if (existsSync3(join3(c, "globalStorage", ext))) {
475
+ userDir = c;
476
+ break;
477
+ }
478
+ }
479
+ if (!userDir) userDir = osDefault;
480
+ const dst = join3(userDir, "globalStorage", ext, "settings", "cline_mcp_settings.json");
481
+ mkdirSync2(dirname3(dst), { recursive: true });
482
+ if (ts) backup(dst, ts);
483
+ const doc = readJsonOrEmpty(dst);
484
+ if (!doc.mcpServers || typeof doc.mcpServers !== "object") doc.mcpServers = {};
485
+ doc.mcpServers["ijfw-memory"] = {
486
+ type: "stdio",
487
+ command: "node",
488
+ args: [serverJs],
489
+ disabled: false,
490
+ autoApprove: [],
491
+ timeout: 60
492
+ };
493
+ writeAtomic(dst, JSON.stringify(doc, null, 2), { mode: 384 });
494
+ return dst;
495
+ }
496
+ var IS_WIN;
497
+ var init_install_helpers = __esm({
498
+ "src/install-helpers.js"() {
499
+ IS_WIN = process.platform === "win32";
500
+ }
501
+ });
502
+
503
+ // src/install-targets-1-7.js
504
+ import {
505
+ existsSync as existsSync4,
506
+ mkdirSync as mkdirSync3,
507
+ readFileSync as readFileSync3,
508
+ writeFileSync as writeFileSync3,
509
+ readdirSync,
510
+ statSync as statSync2,
511
+ copyFileSync as copyFileSync2,
512
+ cpSync,
513
+ chmodSync as chmodSync2
514
+ } from "node:fs";
515
+ import { join as join4, dirname as dirname4, isAbsolute } from "node:path";
516
+ import { platform } from "node:os";
517
+ function ensureDir(p) {
518
+ try {
519
+ mkdirSync3(p, { recursive: true });
520
+ } catch {
521
+ }
522
+ }
523
+ function copyIfAbsent(src, dst) {
524
+ if (!existsSync4(src)) return false;
525
+ if (existsSync4(dst)) return false;
526
+ ensureDir(dirname4(dst));
527
+ try {
528
+ copyFileSync2(src, dst);
529
+ return true;
530
+ } catch {
531
+ return false;
532
+ }
533
+ }
534
+ function copyDirIfAbsent(src, dst) {
535
+ if (!existsSync4(src)) return false;
536
+ if (existsSync4(dst)) return false;
537
+ ensureDir(dirname4(dst));
538
+ try {
539
+ cpSync(src, dst, { recursive: true });
540
+ return true;
541
+ } catch {
542
+ return false;
543
+ }
544
+ }
545
+ function listSubdirs(parent) {
546
+ if (!existsSync4(parent)) return [];
547
+ let names;
548
+ try {
549
+ names = readdirSync(parent);
550
+ } catch {
551
+ return [];
552
+ }
553
+ const out = [];
554
+ for (const name of names) {
555
+ if (name === "__pycache__") continue;
556
+ const path3 = join4(parent, name);
557
+ try {
558
+ if (statSync2(path3).isDirectory()) out.push({ name, path: path3 });
559
+ } catch {
560
+ }
561
+ }
562
+ return out;
563
+ }
564
+ function listFiles(parent, extFilter) {
565
+ if (!existsSync4(parent)) return [];
566
+ let names;
567
+ try {
568
+ names = readdirSync(parent);
569
+ } catch {
570
+ return [];
571
+ }
572
+ const out = [];
573
+ for (const name of names) {
574
+ if (extFilter && !name.endsWith(extFilter)) continue;
575
+ const path3 = join4(parent, name);
576
+ try {
577
+ if (statSync2(path3).isFile()) out.push({ name, path: path3 });
578
+ } catch {
579
+ }
580
+ }
581
+ return out;
582
+ }
583
+ function customDirNoop(ctx, targetId, displayName, reason) {
584
+ ctx.log.info(reason);
585
+ ctx.log.ok(`${displayName}: real platform config left untouched.`);
586
+ return { status: "noop" };
587
+ }
588
+ async function installClaude(ctx) {
589
+ if (ctx.ijfwCustomDir) {
590
+ return customDirNoop(
591
+ ctx,
592
+ "claude",
593
+ "Claude Code",
594
+ "Custom-dir install -- skipping ~/.claude/settings.json merge."
595
+ );
596
+ }
597
+ const claudePluginPath = join4(ctx.home, ".ijfw", "claude");
598
+ const claudeSettings = join4(ctx.home, ".claude", "settings.json");
599
+ const claudeMarketplaces = join4(
600
+ ctx.home,
601
+ ".claude",
602
+ "plugins",
603
+ "known_marketplaces.json"
604
+ );
605
+ ensureDir(join4(ctx.home, ".claude", "plugins"));
606
+ backup(claudeSettings, ctx.ts);
607
+ let settings = {};
608
+ if (existsSync4(claudeSettings)) {
609
+ try {
610
+ settings = JSON.parse(readFileSync3(claudeSettings, "utf8") || "{}");
611
+ } catch {
612
+ settings = {};
613
+ }
614
+ }
615
+ if (!settings || typeof settings !== "object") settings = {};
616
+ settings.enabledPlugins = settings.enabledPlugins || {};
617
+ settings.enabledPlugins["ijfw@ijfw"] = true;
618
+ settings.extraKnownMarketplaces = settings.extraKnownMarketplaces || {};
619
+ settings.extraKnownMarketplaces["ijfw"] = {
620
+ source: { source: "directory", path: claudePluginPath }
621
+ };
622
+ const now = (/* @__PURE__ */ new Date()).toISOString();
623
+ let mp = {};
624
+ if (existsSync4(claudeMarketplaces)) {
625
+ try {
626
+ mp = JSON.parse(readFileSync3(claudeMarketplaces, "utf8") || "{}");
627
+ } catch {
628
+ mp = {};
629
+ }
630
+ }
631
+ if (!mp || typeof mp !== "object") mp = {};
632
+ mp["ijfw"] = {
633
+ source: { source: "directory", path: claudePluginPath },
634
+ installLocation: claudePluginPath,
635
+ lastUpdated: now
636
+ };
637
+ writeAtomic(claudeMarketplaces, JSON.stringify(mp, null, 2) + "\n");
638
+ const existing = settings.mcpServers && settings.mcpServers["ijfw-memory"];
639
+ if (existing && existing.command) {
640
+ const cmd = existing.command;
641
+ if (isAbsolute(cmd) && !existsSync4(cmd)) {
642
+ delete settings.mcpServers["ijfw-memory"];
643
+ }
644
+ }
645
+ settings.mcpServers = settings.mcpServers || {};
646
+ settings.mcpServers["ijfw-memory"] = {
647
+ command: "node",
648
+ args: [ctx.serverJsNative],
649
+ env: {}
650
+ };
651
+ writeAtomic(claudeSettings, JSON.stringify(settings, null, 2) + "\n");
652
+ const launcherPath = join4(ctx.repoRoot, "mcp-server", "bin", "ijfw-memory");
653
+ if (platform() !== "win32" && existsSync4(launcherPath)) {
654
+ try {
655
+ chmodSync2(launcherPath, 493);
656
+ } catch {
657
+ }
658
+ }
659
+ ctx.log.ok("Claude Code ready.");
660
+ ctx.log.note(`.claudeignore template at ${ctx.repoRoot}/claude/.claudeignore`);
661
+ ctx.log.note(" Copy to your project root for instant context savings.");
662
+ return { status: "ok", restart: false };
663
+ }
664
+ async function installCodex(ctx) {
665
+ if (ctx.ijfwCustomDir) {
666
+ return customDirNoop(
667
+ ctx,
668
+ "codex",
669
+ "Codex",
670
+ "Custom-dir install -- skipping ~/.codex/ merges."
671
+ );
672
+ }
673
+ const configToml = join4(ctx.home, ".codex", "config.toml");
674
+ ensureDir(dirname4(configToml));
675
+ mergeToml(configToml, ctx.serverJsNative);
676
+ const hooksDst = join4(ctx.home, ".codex", "hooks.json");
677
+ const hooksSrc = join4(ctx.repoRoot, "codex", ".codex", "hooks.json");
678
+ const hooksBase = join4(ctx.home, ".codex", "hooks");
679
+ ensureDir(hooksBase);
680
+ if (existsSync4(hooksSrc)) {
681
+ let doc = {};
682
+ let rawForBackup = null;
683
+ if (existsSync4(hooksDst)) {
684
+ rawForBackup = readFileSync3(hooksDst, "utf8");
685
+ try {
686
+ doc = JSON.parse(rawForBackup || "{}");
687
+ } catch {
688
+ doc = {};
689
+ }
690
+ }
691
+ const isLegacyShape = Array.isArray(doc) || doc && typeof doc === "object" && doc.hooks && (Array.isArray(doc.hooks) || typeof doc.hooks !== "object");
692
+ if (isLegacyShape && rawForBackup) {
693
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
694
+ const bk = `${hooksDst}.legacy.bak.${ts}`;
695
+ try {
696
+ writeFileSync3(bk, rawForBackup);
697
+ ctx.log.note(`preserved legacy hooks.json at ${bk}`);
698
+ } catch {
699
+ }
700
+ }
701
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) doc = {};
702
+ if (!doc.hooks || typeof doc.hooks !== "object" || Array.isArray(doc.hooks)) {
703
+ doc.hooks = {};
704
+ }
705
+ const VALID_EVENTS = [
706
+ "SessionStart",
707
+ "UserPromptSubmit",
708
+ "PreToolUse",
709
+ "PostToolUse",
710
+ "Stop",
711
+ "PermissionRequest"
712
+ ];
713
+ for (const ev of VALID_EVENTS) {
714
+ if (!Array.isArray(doc.hooks[ev])) doc.hooks[ev] = [];
715
+ doc.hooks[ev] = doc.hooks[ev].filter((g) => {
716
+ if (!g || !Array.isArray(g.hooks)) return true;
717
+ return !g.hooks.some((h) => h && h._ijfw);
718
+ });
719
+ }
720
+ const shellQuote = (p) => {
721
+ if (/^[A-Za-z0-9_./@-]+$/.test(p)) return p;
722
+ return "'" + p.replace(/'/g, "'\\''") + "'";
723
+ };
724
+ let ijfw = {};
725
+ try {
726
+ ijfw = JSON.parse(readFileSync3(hooksSrc, "utf8"));
727
+ } catch {
728
+ ijfw = {};
729
+ }
730
+ const srcHooks = ijfw && ijfw.hooks ? ijfw.hooks : {};
731
+ for (const [ev, groups] of Object.entries(srcHooks)) {
732
+ if (!VALID_EVENTS.includes(ev)) continue;
733
+ if (!Array.isArray(groups)) continue;
734
+ for (const g of groups) {
735
+ if (!g || !Array.isArray(g.hooks)) continue;
736
+ const rewritten = g.hooks.map((h) => {
737
+ if (!h || h.type !== "command" || !h.command) return h;
738
+ const rel = String(h.command).replace(/^hooks\//, "");
739
+ const cmd = shellQuote(`${hooksBase}/${rel}`);
740
+ return { ...h, command: cmd };
741
+ });
742
+ doc.hooks[ev].push({
743
+ ...g.matcher ? { matcher: g.matcher } : {},
744
+ hooks: rewritten
745
+ });
746
+ }
747
+ }
748
+ writeAtomic(hooksDst, JSON.stringify(doc, null, 2) + "\n");
749
+ }
750
+ const hookScriptsDir = join4(ctx.repoRoot, "codex", ".codex", "hooks");
751
+ for (const f of listFiles(hookScriptsDir, ".sh")) {
752
+ installHook(f.path, join4(hooksBase, f.name), ctx.ts);
753
+ }
754
+ const codexCtx = join4(ctx.home, ".codex", "IJFW.md");
755
+ copyIfAbsent(join4(ctx.repoRoot, "codex", ".codex", "IJFW.md"), codexCtx);
756
+ const userSkills = join4(ctx.home, ".codex", "skills");
757
+ ensureDir(userSkills);
758
+ const repoSkills = join4(ctx.repoRoot, "codex", "skills");
759
+ for (const sd of listSubdirs(repoSkills)) {
760
+ copyDirIfAbsent(sd.path, join4(userSkills, sd.name));
761
+ }
762
+ const cwd = ctx.cwd || process.cwd();
763
+ if (existsSync4(join4(cwd, ".codex", "config.toml")) || existsSync4(join4(cwd, ".ijfw"))) {
764
+ const projSkills = join4(cwd, ".codex", "skills");
765
+ ensureDir(projSkills);
766
+ for (const sd of listSubdirs(repoSkills)) {
767
+ copyDirIfAbsent(sd.path, join4(projSkills, sd.name));
768
+ }
769
+ }
770
+ ctx.log.ok("Installed Codex bundle: MCP + hooks + 19 skills + context");
771
+ return { status: "ok" };
772
+ }
773
+ async function installGemini(ctx) {
774
+ if (ctx.ijfwCustomDir) {
775
+ return customDirNoop(
776
+ ctx,
777
+ "gemini",
778
+ "Gemini",
779
+ "Custom-dir install -- skipping ~/.gemini/ merges."
780
+ );
781
+ }
782
+ const dst = join4(ctx.home, ".gemini", "settings.json");
783
+ ensureDir(dirname4(dst));
784
+ mergeJson(dst, ctx.serverJsNative);
785
+ const extDst = join4(ctx.home, ".gemini", "extensions", "ijfw");
786
+ const extSrc = join4(ctx.repoRoot, "gemini", "extensions", "ijfw");
787
+ for (const sub of ["hooks", "skills", "commands", "agents", "policies"]) {
788
+ ensureDir(join4(extDst, sub));
789
+ }
790
+ for (const rel of [
791
+ "gemini-extension.json",
792
+ "IJFW.md",
793
+ "hooks/hooks.json",
794
+ "policies/ijfw.toml"
795
+ ]) {
796
+ const dstFile = join4(extDst, rel);
797
+ if (!existsSync4(dstFile)) {
798
+ ensureDir(dirname4(dstFile));
799
+ const srcFile = join4(extSrc, rel);
800
+ try {
801
+ if (existsSync4(srcFile)) copyFileSync2(srcFile, dstFile);
802
+ } catch {
803
+ }
804
+ }
805
+ }
806
+ const hooksJson = join4(extDst, "hooks", "hooks.json");
807
+ if (existsSync4(hooksJson)) {
808
+ let raw = "";
809
+ try {
810
+ raw = readFileSync3(hooksJson, "utf8");
811
+ } catch {
812
+ raw = "";
813
+ }
814
+ if (raw.includes("{{extensionPath}}")) {
815
+ const replaced = raw.split("{{extensionPath}}").join(extDst);
816
+ writeAtomic(hooksJson, replaced);
817
+ }
818
+ }
819
+ const hookScriptsDir = join4(extSrc, "hooks");
820
+ for (const f of listFiles(hookScriptsDir, ".sh")) {
821
+ installHook(f.path, join4(extDst, "hooks", f.name), ctx.ts);
822
+ }
823
+ const skillsSrc = join4(extSrc, "skills");
824
+ for (const sd of listSubdirs(skillsSrc)) {
825
+ copyDirIfAbsent(sd.path, join4(extDst, "skills", sd.name));
826
+ }
827
+ const cmdSrc = join4(extSrc, "commands");
828
+ for (const f of listFiles(cmdSrc, ".toml")) {
829
+ copyIfAbsent(f.path, join4(extDst, "commands", f.name));
830
+ }
831
+ const agentSrc = join4(extSrc, "agents");
832
+ for (const f of listFiles(agentSrc, ".md")) {
833
+ copyIfAbsent(f.path, join4(extDst, "agents", f.name));
834
+ }
835
+ ctx.log.ok("Installed Gemini bundle: MCP + extension + 19 skills + 11 hooks + policy");
836
+ return { status: "ok" };
837
+ }
838
+ async function installWayland(ctx) {
839
+ if (ctx.ijfwCustomDir) {
840
+ return customDirNoop(
841
+ ctx,
842
+ "wayland",
843
+ "Wayland",
844
+ "Custom-dir install -- skipping ~/.wayland/ merges."
845
+ );
846
+ }
847
+ const dst = join4(ctx.home, ".wayland", "config.yaml");
848
+ ensureDir(dirname4(dst));
849
+ mergeYamlMcp(dst, ctx.serverJsNative);
850
+ ensureDir(join4(ctx.home, ".wayland"));
851
+ copyIfAbsent(
852
+ join4(ctx.repoRoot, "wayland", "WAYLAND.md"),
853
+ join4(ctx.home, ".wayland", "WAYLAND.md")
854
+ );
855
+ ensureDir(join4(ctx.home, ".wayland", "skills"));
856
+ const sharedSkills = join4(ctx.repoRoot, "shared", "skills");
857
+ for (const sd of listSubdirs(sharedSkills)) {
858
+ copyDirIfAbsent(sd.path, join4(ctx.home, ".wayland", "skills", sd.name));
859
+ }
860
+ const pluginSrc = join4(ctx.repoRoot, "wayland", "plugins", "ijfw");
861
+ if (existsSync4(pluginSrc)) {
862
+ const pluginDst = join4(ctx.home, ".wayland", "plugins", "ijfw");
863
+ ensureDir(pluginDst);
864
+ let entries;
865
+ try {
866
+ entries = readdirSync(pluginSrc);
867
+ } catch {
868
+ entries = [];
869
+ }
870
+ for (const name of entries) {
871
+ if (name === "__pycache__") continue;
872
+ const src = join4(pluginSrc, name);
873
+ const dstEntry = join4(pluginDst, name);
874
+ try {
875
+ const st = statSync2(src);
876
+ if (st.isDirectory()) {
877
+ cpSync(src, dstEntry, { recursive: true, force: true });
878
+ } else if (st.isFile()) {
879
+ copyFileSync2(src, dstEntry);
880
+ }
881
+ } catch {
882
+ }
883
+ }
884
+ }
885
+ ctx.log.ok("Installed Wayland bundle: MCP + WAYLAND.md + skills + plugin");
886
+ return { status: "ok" };
887
+ }
888
+ async function installHermes(ctx) {
889
+ if (ctx.ijfwCustomDir) {
890
+ return customDirNoop(
891
+ ctx,
892
+ "hermes",
893
+ "Hermes",
894
+ "Custom-dir install -- skipping ~/.hermes/ merges."
895
+ );
896
+ }
897
+ const dst = join4(ctx.home, ".hermes", "config.yaml");
898
+ ensureDir(dirname4(dst));
899
+ mergeYamlMcp(dst, ctx.serverJsNative);
900
+ ensureDir(join4(ctx.home, ".hermes"));
901
+ copyIfAbsent(
902
+ join4(ctx.repoRoot, "hermes", "HERMES.md"),
903
+ join4(ctx.home, ".hermes", "HERMES.md")
904
+ );
905
+ ensureDir(join4(ctx.home, ".hermes", "skills"));
906
+ const sharedSkills = join4(ctx.repoRoot, "shared", "skills");
907
+ for (const sd of listSubdirs(sharedSkills)) {
908
+ copyDirIfAbsent(sd.path, join4(ctx.home, ".hermes", "skills", sd.name));
909
+ }
910
+ const pluginSrc = join4(ctx.repoRoot, "hermes", "plugins", "ijfw");
911
+ if (existsSync4(pluginSrc)) {
912
+ const pluginDst = join4(ctx.home, ".hermes", "plugins", "ijfw");
913
+ ensureDir(pluginDst);
914
+ let entries;
915
+ try {
916
+ entries = readdirSync(pluginSrc);
917
+ } catch {
918
+ entries = [];
919
+ }
920
+ for (const name of entries) {
921
+ if (name === "__pycache__") continue;
922
+ const src = join4(pluginSrc, name);
923
+ const dstEntry = join4(pluginDst, name);
924
+ try {
925
+ const st = statSync2(src);
926
+ if (st.isDirectory()) {
927
+ cpSync(src, dstEntry, { recursive: true, force: true });
928
+ } else if (st.isFile()) {
929
+ copyFileSync2(src, dstEntry);
930
+ }
931
+ } catch {
932
+ }
933
+ }
934
+ }
935
+ mergeYamlPluginsEnabled(dst, "ijfw");
936
+ ctx.log.ok("Installed Hermes bundle: MCP + HERMES.md + skills + plugin");
937
+ return { status: "ok" };
938
+ }
939
+ async function installCursor(ctx) {
940
+ if (ctx.isIjfwSource) {
941
+ ctx.log.info("IJFW source tree detected -- skipping Cursor project writes (would litter source).");
942
+ ctx.log.ok("Cursor: source tree left untouched.");
943
+ return { status: "noop" };
944
+ }
945
+ if (ctx.ijfwCustomDir) {
946
+ return customDirNoop(
947
+ ctx,
948
+ "cursor",
949
+ "Cursor",
950
+ "Custom-dir install -- skipping Cursor project writes."
951
+ );
952
+ }
953
+ const cwd = ctx.cwd || process.cwd();
954
+ const dst = join4(cwd, ".cursor", "mcp.json");
955
+ ensureDir(dirname4(dst));
956
+ mergeJson(dst, ctx.serverJsNative);
957
+ const rulesDir = join4(cwd, ".cursor", "rules");
958
+ ensureDir(rulesDir);
959
+ const ruleSrc = join4(ctx.repoRoot, "cursor", ".cursor", "rules", "ijfw.mdc");
960
+ if (existsSync4(ruleSrc)) {
961
+ try {
962
+ copyFileSync2(ruleSrc, join4(rulesDir, "ijfw.mdc"));
963
+ } catch {
964
+ }
965
+ }
966
+ ctx.log.ok("Merged MCP + installed rule to project ./.cursor/");
967
+ return { status: "ok" };
968
+ }
969
+ async function installWindsurf(ctx) {
970
+ if (ctx.ijfwCustomDir || ctx.isIjfwSource) {
971
+ return customDirNoop(
972
+ ctx,
973
+ "windsurf",
974
+ "Windsurf",
975
+ "Skipping Windsurf platform writes (custom-dir or IJFW source tree)."
976
+ );
977
+ }
978
+ const dst = join4(ctx.home, ".codeium", "windsurf", "mcp_config.json");
979
+ ensureDir(dirname4(dst));
980
+ mergeJson(dst, ctx.serverJsNative);
981
+ const cwd = ctx.cwd || process.cwd();
982
+ const projectRules = join4(cwd, ".windsurfrules");
983
+ const repoRules = join4(ctx.repoRoot, "windsurf", ".windsurfrules");
984
+ let installedRules = false;
985
+ if (!existsSync4(projectRules) && existsSync4(repoRules)) {
986
+ try {
987
+ copyFileSync2(repoRules, projectRules);
988
+ installedRules = true;
989
+ } catch {
990
+ }
991
+ }
992
+ if (installedRules) {
993
+ ctx.log.ok("Merged MCP + installed .windsurfrules");
994
+ } else {
995
+ ctx.log.ok(`Merged MCP into ${dst}`);
996
+ }
997
+ return { status: "ok" };
998
+ }
999
+ var init_install_targets_1_7 = __esm({
1000
+ "src/install-targets-1-7.js"() {
1001
+ init_install_helpers();
1002
+ }
1003
+ });
1004
+
1005
+ // src/install-targets-8-14.js
1006
+ import fs from "node:fs";
1007
+ import path from "node:path";
1008
+ import { execFileSync, spawnSync } from "node:child_process";
1009
+ function ensureDir2(dir) {
1010
+ try {
1011
+ fs.mkdirSync(dir, { recursive: true });
1012
+ } catch {
1013
+ }
1014
+ }
1015
+ function copyIfMissing(src, dst) {
1016
+ try {
1017
+ if (fs.existsSync(dst)) return false;
1018
+ if (!fs.existsSync(src)) return false;
1019
+ ensureDir2(path.dirname(dst));
1020
+ fs.copyFileSync(src, dst);
1021
+ return true;
1022
+ } catch {
1023
+ return false;
1024
+ }
1025
+ }
1026
+ function commandExists(name) {
1027
+ const probeCmd = process.platform === "win32" ? "where" : "which";
1028
+ const r = spawnSync(probeCmd, [name], { stdio: "ignore" });
1029
+ return r.status === 0;
1030
+ }
1031
+ function installCopilot(ctx) {
1032
+ if (ctx.ijfwCustomDir) {
1033
+ printInfo("Custom-dir install -- skipping Copilot project writes.");
1034
+ printOk("Copilot: real platform config left untouched.");
1035
+ return { status: "noop" };
1036
+ }
1037
+ if (ctx.isIjfwSource) {
1038
+ printInfo("IJFW source tree detected -- skipping Copilot project writes (would litter source).");
1039
+ printOk("Copilot: source tree left untouched.");
1040
+ return { status: "noop" };
1041
+ }
1042
+ const dst = path.join(process.cwd(), ".vscode", "mcp.json");
1043
+ ensureDir2(path.dirname(dst));
1044
+ mergeJson(dst, ctx.serverJsNative || ctx.serverJs);
1045
+ const rulesDst = path.join(process.cwd(), ".github", "copilot-instructions.md");
1046
+ const rulesSrc = path.join(ctx.repoRoot, "copilot", "copilot-instructions.md");
1047
+ const wroteRules = copyIfMissing(rulesSrc, rulesDst);
1048
+ if (wroteRules) {
1049
+ printOk("Merged MCP + installed .github/copilot-instructions.md");
1050
+ } else {
1051
+ printOk("Merged MCP into project ./.vscode/mcp.json");
1052
+ }
1053
+ return { status: "ok" };
1054
+ }
1055
+ function installOpencode(ctx) {
1056
+ if (ctx.ijfwCustomDir) {
1057
+ printInfo("Custom-dir install -- skipping ~/.config/opencode/ merge.");
1058
+ printOk("OpenCode: real platform config left untouched.");
1059
+ return { status: "noop" };
1060
+ }
1061
+ const dst = path.join(ctx.home, ".config", "opencode", "opencode.json");
1062
+ ensureDir2(path.dirname(dst));
1063
+ opencodeMerge(dst, ctx.serverJsNative || ctx.serverJs);
1064
+ printOk(`Merged MCP into ${dst} (opencode mcp.local schema)`);
1065
+ return { status: "ok" };
1066
+ }
1067
+ function installQwen(ctx) {
1068
+ if (ctx.ijfwCustomDir) {
1069
+ printInfo("Custom-dir install -- skipping ~/.qwen/ merge.");
1070
+ printOk("Qwen Code: real platform config left untouched.");
1071
+ return { status: "noop" };
1072
+ }
1073
+ const dst = path.join(ctx.home, ".qwen", "settings.json");
1074
+ ensureDir2(path.dirname(dst));
1075
+ mergeJson(dst, ctx.serverJsNative || ctx.serverJs);
1076
+ printOk(`Merged MCP into ${dst}`);
1077
+ return { status: "ok" };
1078
+ }
1079
+ function installCline(ctx) {
1080
+ if (ctx.ijfwCustomDir) {
1081
+ printInfo("Custom-dir install -- skipping Cline merges.");
1082
+ printOk("Cline: real platform config left untouched.");
1083
+ return { status: "noop" };
1084
+ }
1085
+ const dst = clineMerge(ctx.serverJsNative || ctx.serverJs, ctx.home);
1086
+ printOk(`Merged MCP into ${dst} (cline globalStorage schema)`);
1087
+ return { status: "ok" };
1088
+ }
1089
+ function installKimi(ctx) {
1090
+ if (ctx.ijfwCustomDir) {
1091
+ printInfo("Custom-dir install -- skipping ~/.kimi/ merge.");
1092
+ printOk("Kimi Code: real platform config left untouched.");
1093
+ return { status: "noop" };
1094
+ }
1095
+ const dst = path.join(ctx.home, ".kimi", "mcp.json");
1096
+ ensureDir2(path.dirname(dst));
1097
+ mergeJson(dst, ctx.serverJsNative || ctx.serverJs);
1098
+ printOk(`Merged MCP into ${dst}`);
1099
+ return { status: "ok" };
1100
+ }
1101
+ function installOpenclaw(ctx) {
1102
+ if (ctx.ijfwCustomDir) {
1103
+ printInfo("Custom-dir install -- skipping OpenClaw merges.");
1104
+ printOk("OpenClaw: real platform config left untouched.");
1105
+ return { status: "noop" };
1106
+ }
1107
+ const dst = path.join(ctx.home, ".openclaw", "openclaw.json");
1108
+ const serverJs = ctx.serverJsNative || ctx.serverJs;
1109
+ if (commandExists("openclaw")) {
1110
+ try {
1111
+ const payload = JSON.stringify({ command: "node", args: [serverJs] });
1112
+ execFileSync("openclaw", ["mcp", "set", "ijfw-memory", payload], { stdio: "ignore" });
1113
+ printOk(`Registered ijfw-memory via 'openclaw mcp set' (${dst})`);
1114
+ return { status: "ok" };
1115
+ } catch {
1116
+ }
1117
+ }
1118
+ ensureDir2(path.dirname(dst));
1119
+ openclawMerge(dst, serverJs);
1120
+ printOk(`Merged MCP into ${dst} (openclaw mcp.servers schema)`);
1121
+ return { status: "ok" };
1122
+ }
1123
+ function installAider(ctx) {
1124
+ if (ctx.ijfwCustomDir) {
1125
+ printInfo("Custom-dir install -- skipping Aider merges.");
1126
+ printOk("Aider: real platform config left untouched.");
1127
+ return { status: "noop" };
1128
+ }
1129
+ const confSrc = path.join(ctx.repoRoot, "aider", "aider.conf.yml");
1130
+ const confDst = path.join(ctx.home, ".aider.conf.yml");
1131
+ copyIfMissing(confSrc, confDst);
1132
+ const convSrc = path.join(ctx.repoRoot, "aider", "CONVENTIONS.md");
1133
+ const convDst = path.join(ctx.home, "CONVENTIONS.md");
1134
+ copyIfMissing(convSrc, convDst);
1135
+ printOk("Aider: rules-only install (~/.aider.conf.yml + ~/CONVENTIONS.md). No MCP -- Aider lacks a native MCP client.");
1136
+ return { status: "ok" };
1137
+ }
1138
+ var init_install_targets_8_14 = __esm({
1139
+ "src/install-targets-8-14.js"() {
1140
+ init_install_helpers();
1141
+ }
1142
+ });
1143
+
1144
+ // src/install-flow.js
1145
+ var install_flow_exports = {};
1146
+ __export(install_flow_exports, {
1147
+ CANONICAL_ORDER: () => CANONICAL_ORDER,
1148
+ default: () => install_flow_default,
1149
+ runInstall: () => runInstall
1150
+ });
1151
+ import fs2 from "node:fs";
1152
+ import path2 from "node:path";
1153
+ function timestamp() {
1154
+ const d = /* @__PURE__ */ new Date();
1155
+ const p = (n, w = 2) => String(n).padStart(w, "0");
1156
+ return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
1157
+ }
1158
+ function isWindows() {
1159
+ return process.platform === "win32";
1160
+ }
1161
+ function realpathOrSelf(p) {
1162
+ try {
1163
+ return fs2.realpathSync(p);
1164
+ } catch {
1165
+ return p;
1166
+ }
1167
+ }
1168
+ function preflight({ repoRoot, serverJs }) {
1169
+ const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
1170
+ if (!Number.isFinite(nodeMajor) || nodeMajor < 18) {
1171
+ throw new Error(
1172
+ `Preflight: Node.js ${process.versions.node} is too old (need 18+).`
1173
+ );
1174
+ }
1175
+ if (!repoRoot || typeof repoRoot !== "string") {
1176
+ throw new Error("Preflight: repoRoot is required and must be a string.");
1177
+ }
1178
+ if (!fs2.existsSync(repoRoot)) {
1179
+ throw new Error(`Preflight: repoRoot does not exist at ${repoRoot}`);
1180
+ }
1181
+ if (!fs2.existsSync(serverJs)) {
1182
+ throw new Error(
1183
+ `Preflight: MCP server.js missing at ${serverJs} -- re-clone the IJFW source tree.`
1184
+ );
1185
+ }
1186
+ }
1187
+ function linkPlugin({ repoRoot, ijfwHome, ts }) {
1188
+ const pluginSrc = path2.join(repoRoot, "claude");
1189
+ const pluginDst = path2.join(ijfwHome, "claude");
1190
+ const pluginSrcReal = realpathOrSelf(pluginSrc);
1191
+ const pluginDstReal = realpathOrSelf(pluginDst);
1192
+ if (pluginSrcReal === pluginDstReal && fs2.existsSync(pluginDst)) {
1193
+ printOk("Plugin source already at canonical path -- symlink not needed.");
1194
+ return;
1195
+ }
1196
+ fs2.mkdirSync(ijfwHome, { recursive: true });
1197
+ if (isWindows()) {
1198
+ if (fs2.existsSync(pluginDst)) {
1199
+ const st = fs2.lstatSync(pluginDst);
1200
+ if (st.isSymbolicLink()) {
1201
+ try {
1202
+ fs2.unlinkSync(pluginDst);
1203
+ } catch {
1204
+ }
1205
+ } else if (st.isDirectory()) {
1206
+ fs2.cpSync(pluginSrc, pluginDst, { recursive: true });
1207
+ printOk(`Plugin tree mirrored to ${pluginDst}`);
1208
+ return;
1209
+ } else {
1210
+ try {
1211
+ fs2.rmSync(pluginDst, { recursive: true, force: true });
1212
+ } catch {
1213
+ }
1214
+ }
1215
+ }
1216
+ fs2.cpSync(pluginSrc, pluginDst, { recursive: true });
1217
+ printOk(`Plugin tree copied to ${pluginDst}`);
1218
+ return;
1219
+ }
1220
+ let dstStat = null;
1221
+ try {
1222
+ dstStat = fs2.lstatSync(pluginDst);
1223
+ } catch {
1224
+ dstStat = null;
1225
+ }
1226
+ if (dstStat && dstStat.isSymbolicLink()) {
1227
+ let cur = "";
1228
+ try {
1229
+ cur = fs2.readlinkSync(pluginDst);
1230
+ } catch {
1231
+ cur = "";
1232
+ }
1233
+ if (cur !== pluginSrc) {
1234
+ try {
1235
+ fs2.unlinkSync(pluginDst);
1236
+ } catch {
1237
+ }
1238
+ fs2.symlinkSync(pluginSrc, pluginDst, "dir");
1239
+ printOk(`Plugin link retargeted: ${pluginDst} -> ${pluginSrc}`);
1240
+ } else {
1241
+ printOk(`Plugin link already correct at ${pluginDst}`);
1242
+ }
1243
+ } else if (dstStat) {
1244
+ const backup2 = `${pluginDst}.backup.${ts}`;
1245
+ fs2.renameSync(pluginDst, backup2);
1246
+ fs2.symlinkSync(pluginSrc, pluginDst, "dir");
1247
+ printOk(`Plugin link created (existing dir preserved at ${backup2}).`);
1248
+ } else {
1249
+ fs2.symlinkSync(pluginSrc, pluginDst, "dir");
1250
+ printOk(`Plugin link created: ${pluginDst} -> ${pluginSrc}`);
1251
+ }
1252
+ const manifest = path2.join(pluginDst, ".claude-plugin", "plugin.json");
1253
+ if (!fs2.existsSync(manifest)) {
1254
+ printWarn(
1255
+ `Plugin at ${pluginDst} is missing .claude-plugin/plugin.json -- install may be incomplete.`
1256
+ );
1257
+ }
1258
+ }
1259
+ function seedState({ ijfwHome, repoRoot, nodeBin: _nodeBin }) {
1260
+ const cacheDir = path2.join(ijfwHome, "cache");
1261
+ const runDir = path2.join(ijfwHome, "run");
1262
+ const logsDir = path2.join(ijfwHome, "logs");
1263
+ fs2.mkdirSync(cacheDir, { recursive: true });
1264
+ fs2.mkdirSync(runDir, { recursive: true });
1265
+ fs2.mkdirSync(logsDir, { recursive: true });
1266
+ try {
1267
+ fs2.chmodSync(ijfwHome, 448);
1268
+ } catch {
1269
+ }
1270
+ let installMethod = "manual";
1271
+ if (fs2.existsSync(path2.join(repoRoot, ".git"))) {
1272
+ installMethod = "git-clone";
1273
+ }
1274
+ try {
1275
+ const npmGlobalRoot = process.env.npm_config_prefix ? path2.join(process.env.npm_config_prefix, "lib", "node_modules") : null;
1276
+ if (npmGlobalRoot && repoRoot.startsWith(npmGlobalRoot) && repoRoot !== npmGlobalRoot) {
1277
+ installMethod = "npm-global";
1278
+ }
1279
+ } catch {
1280
+ }
1281
+ let installedVer = "0.0.0";
1282
+ try {
1283
+ const pkgPath = path2.join(repoRoot, "installer", "package.json");
1284
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
1285
+ installedVer = pkg.version || "0.0.0";
1286
+ } catch {
1287
+ }
1288
+ const nowTs = Math.floor(Date.now() / 1e3);
1289
+ const state = {
1290
+ schema_version: 1,
1291
+ install_method: installMethod,
1292
+ installed_version: installedVer,
1293
+ last_applied_version: installedVer,
1294
+ last_good_shasum: null,
1295
+ settings_reseeded_at: null,
1296
+ installed_at: nowTs
1297
+ };
1298
+ writeAtomic(
1299
+ path2.join(ijfwHome, "state.json"),
1300
+ JSON.stringify(state, null, 2) + "\n",
1301
+ { mode: 384 }
1302
+ );
1303
+ const settingsPath = path2.join(ijfwHome, "settings.json");
1304
+ if (!fs2.existsSync(settingsPath)) {
1305
+ const seedPath = path2.join(
1306
+ repoRoot,
1307
+ "installer",
1308
+ "src",
1309
+ "settings-seed.json"
1310
+ );
1311
+ if (fs2.existsSync(seedPath)) {
1312
+ const seed = fs2.readFileSync(seedPath, "utf8");
1313
+ writeAtomic(settingsPath, seed, { mode: 384 });
1314
+ }
1315
+ }
1316
+ writeAtomic(
1317
+ path2.join(ijfwHome, "install-method"),
1318
+ `${installMethod}
1319
+ `,
1320
+ { mode: 384 }
1321
+ );
1322
+ printOk(`State seeded (${installMethod}, v${installedVer})`);
1323
+ return { installMethod, installedVer };
1324
+ }
1325
+ function detectStatusline({ home, ijfwHome }) {
1326
+ const claudeSettingsPath2 = path2.join(home, ".claude", "settings.json");
1327
+ if (!fs2.existsSync(claudeSettingsPath2)) {
1328
+ return;
1329
+ }
1330
+ let claudeSettings;
1331
+ try {
1332
+ claudeSettings = JSON.parse(fs2.readFileSync(claudeSettingsPath2, "utf8"));
1333
+ } catch {
1334
+ return;
1335
+ }
1336
+ const existingCmd = claudeSettings && claudeSettings.statusLine && claudeSettings.statusLine.command ? String(claudeSettings.statusLine.command) : "";
1337
+ if (!existingCmd) {
1338
+ printOk(
1339
+ "statusLine off by default. Run 'ijfw statusline --install' to enable."
1340
+ );
1341
+ return;
1342
+ }
1343
+ const allowed = existingCmd.includes("/.claude/") || existingCmd.includes("/.gsd/") || existingCmd.includes("/.ijfw/claude/") || existingCmd.includes("/.cursor/");
1344
+ if (!allowed) {
1345
+ printWarn(
1346
+ `Existing statusLine at ${existingCmd} -- not composing for security.`
1347
+ );
1348
+ printNote(
1349
+ "Run 'ijfw statusline --install' to replace, or '--compose' if trusted."
1350
+ );
1351
+ return;
1352
+ }
1353
+ const settingsPath = path2.join(ijfwHome, "settings.json");
1354
+ let s = {};
1355
+ try {
1356
+ s = JSON.parse(fs2.readFileSync(settingsPath, "utf8"));
1357
+ } catch {
1358
+ s = {};
1359
+ }
1360
+ if (!s.schema_version) s.schema_version = 1;
1361
+ if (!s.statusline) s.statusline = {};
1362
+ s.statusline.composed_command = existingCmd;
1363
+ s.statusline.mode = "compose";
1364
+ s.statusline.enabled = "auto";
1365
+ try {
1366
+ writeAtomic(settingsPath, JSON.stringify(s, null, 2) + "\n", {
1367
+ mode: 384
1368
+ });
1369
+ printOk(
1370
+ "Composed alongside existing statusLine. Run 'ijfw statusline --disable' to opt out."
1371
+ );
1372
+ } catch (err) {
1373
+ printWarn(`Statusline compose write failed: ${err.message}`);
1374
+ }
1375
+ }
1376
+ function patchPluginMcpJson({ ijfwHome, repoRoot, nodeBin, serverJs }) {
1377
+ const pluginDst = path2.join(ijfwHome, "claude");
1378
+ const mcpJsonPath = path2.join(pluginDst, ".mcp.json");
1379
+ if (!fs2.existsSync(mcpJsonPath)) return;
1380
+ const pluginDstReal = realpathOrSelf(pluginDst);
1381
+ const repoRootReal = realpathOrSelf(repoRoot);
1382
+ if (fs2.existsSync(path2.join(repoRoot, ".git")) && pluginDstReal.startsWith(repoRootReal) && pluginDstReal !== repoRootReal) {
1383
+ return;
1384
+ }
1385
+ let d;
1386
+ try {
1387
+ d = JSON.parse(fs2.readFileSync(mcpJsonPath, "utf8"));
1388
+ } catch {
1389
+ return;
1390
+ }
1391
+ if (!d || !d.mcpServers || !d.mcpServers["ijfw-memory"]) return;
1392
+ const nodeDir = path2.dirname(nodeBin);
1393
+ d.mcpServers["ijfw-memory"].command = nodeBin;
1394
+ d.mcpServers["ijfw-memory"].args = [serverJs];
1395
+ const envSep = process.platform === "win32" ? ";" : ":";
1396
+ const commonPaths = process.platform === "win32" ? [nodeDir, "C:\\Windows\\System32"] : [nodeDir, "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"];
1397
+ const dedup = [...new Set(commonPaths.filter((x) => x && fs2.existsSync(x)))];
1398
+ d.mcpServers["ijfw-memory"].env = { PATH: dedup.join(envSep) };
1399
+ try {
1400
+ writeAtomic(mcpJsonPath, JSON.stringify(d, null, 2) + "\n");
1401
+ } catch (err) {
1402
+ printWarn(`Plugin .mcp.json patch failed: ${err.message}`);
1403
+ }
1404
+ }
1405
+ function nukePluginCache({ home }) {
1406
+ const cacheDir = path2.join(home, ".claude", "plugins", "cache", "ijfw");
1407
+ if (fs2.existsSync(cacheDir)) {
1408
+ try {
1409
+ fs2.rmSync(cacheDir, { recursive: true, force: true });
1410
+ } catch {
1411
+ }
1412
+ }
1413
+ }
1414
+ function linkMcpSibling({ repoRoot, ijfwHome, ts }) {
1415
+ const mcpSrc = path2.join(repoRoot, "mcp-server");
1416
+ const mcpDst = path2.join(ijfwHome, "mcp-server");
1417
+ const mcpSrcReal = realpathOrSelf(mcpSrc);
1418
+ const mcpDstReal = realpathOrSelf(mcpDst);
1419
+ if (mcpSrcReal === mcpDstReal && fs2.existsSync(mcpDst)) {
1420
+ printOk("MCP source already at canonical path -- symlink not needed.");
1421
+ return;
1422
+ }
1423
+ fs2.mkdirSync(ijfwHome, { recursive: true });
1424
+ if (isWindows()) {
1425
+ if (fs2.existsSync(mcpDst)) {
1426
+ const st = fs2.lstatSync(mcpDst);
1427
+ if (st.isSymbolicLink()) {
1428
+ try {
1429
+ fs2.unlinkSync(mcpDst);
1430
+ } catch {
1431
+ }
1432
+ } else if (st.isDirectory()) {
1433
+ fs2.cpSync(mcpSrc, mcpDst, { recursive: true });
1434
+ return;
1435
+ } else {
1436
+ try {
1437
+ fs2.rmSync(mcpDst, { recursive: true, force: true });
1438
+ } catch {
1439
+ }
1440
+ }
1441
+ }
1442
+ fs2.cpSync(mcpSrc, mcpDst, { recursive: true });
1443
+ return;
1444
+ }
1445
+ let dstStat = null;
1446
+ try {
1447
+ dstStat = fs2.lstatSync(mcpDst);
1448
+ } catch {
1449
+ dstStat = null;
1450
+ }
1451
+ if (dstStat && dstStat.isSymbolicLink()) {
1452
+ let cur = "";
1453
+ try {
1454
+ cur = fs2.readlinkSync(mcpDst);
1455
+ } catch {
1456
+ cur = "";
1457
+ }
1458
+ if (cur !== mcpSrc) {
1459
+ try {
1460
+ fs2.unlinkSync(mcpDst);
1461
+ } catch {
1462
+ }
1463
+ fs2.symlinkSync(mcpSrc, mcpDst, "dir");
1464
+ }
1465
+ } else if (dstStat) {
1466
+ const backup2 = `${mcpDst}.backup.${ts}`;
1467
+ fs2.renameSync(mcpDst, backup2);
1468
+ fs2.symlinkSync(mcpSrc, mcpDst, "dir");
1469
+ } else {
1470
+ fs2.symlinkSync(mcpSrc, mcpDst, "dir");
1471
+ }
1472
+ if (!fs2.existsSync(path2.join(mcpDst, "src", "server.js"))) {
1473
+ printWarn(
1474
+ `MCP server at ${mcpDst} is missing src/server.js -- install may be incomplete.`
1475
+ );
1476
+ }
1477
+ }
1478
+ function pruneBackups({ home }) {
1479
+ const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
1480
+ const cwd = process.cwd();
1481
+ const dirs = [
1482
+ path2.join(home, ".codex"),
1483
+ path2.join(home, ".gemini"),
1484
+ path2.join(home, ".codeium", "windsurf"),
1485
+ path2.join(home, ".hermes"),
1486
+ path2.join(home, ".wayland"),
1487
+ path2.join(cwd, ".vscode"),
1488
+ path2.join(cwd, ".cursor")
1489
+ ];
1490
+ const cutoff = Date.now() - THIRTY_DAYS_MS;
1491
+ for (const d of dirs) {
1492
+ if (!fs2.existsSync(d)) continue;
1493
+ walkAndPrune(d, 2, cutoff);
1494
+ }
1495
+ }
1496
+ function walkAndPrune(dir, depth, cutoff) {
1497
+ if (depth < 0) return;
1498
+ let entries = [];
1499
+ try {
1500
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
1501
+ } catch {
1502
+ return;
1503
+ }
1504
+ for (const entry of entries) {
1505
+ const full = path2.join(dir, entry.name);
1506
+ if (entry.isDirectory()) {
1507
+ walkAndPrune(full, depth - 1, cutoff);
1508
+ continue;
1509
+ }
1510
+ if (!entry.isFile()) continue;
1511
+ if (!entry.name.includes(".bak.")) continue;
1512
+ let st;
1513
+ try {
1514
+ st = fs2.statSync(full);
1515
+ } catch {
1516
+ continue;
1517
+ }
1518
+ if (st.mtimeMs < cutoff) {
1519
+ try {
1520
+ fs2.unlinkSync(full);
1521
+ } catch {
1522
+ }
1523
+ }
1524
+ }
1525
+ }
1526
+ function printSummary({ live, standby, claudeNeedsRestart, repoRoot }) {
1527
+ const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
1528
+ const c = useColor ? {
1529
+ reset: "\x1B[0m",
1530
+ bold: "\x1B[1m",
1531
+ dim: "\x1B[2m",
1532
+ cyan: "\x1B[36m",
1533
+ green: "\x1B[32m",
1534
+ yellow: "\x1B[33m"
1535
+ } : { reset: "", bold: "", dim: "", cyan: "", green: "", yellow: "" };
1536
+ const nativeRepo = nativePath(repoRoot);
1537
+ process.stdout.write("\n");
1538
+ process.stdout.write(
1539
+ ` ${c.bold}${c.cyan}+----------------------------------------+${c.reset}
1540
+ `
1541
+ );
1542
+ process.stdout.write(
1543
+ ` ${c.bold}${c.cyan}|${c.reset} ${c.bold}${c.cyan}|${c.reset}
1544
+ `
1545
+ );
1546
+ process.stdout.write(
1547
+ ` ${c.bold}${c.cyan}|${c.reset} ${c.bold}${c.cyan}IJFW${c.reset} ${c.dim}It just f*cking works.${c.reset} ${c.bold}${c.cyan}|${c.reset}
1548
+ `
1549
+ );
1550
+ process.stdout.write(
1551
+ ` ${c.bold}${c.cyan}|${c.reset} ${c.bold}${c.cyan}|${c.reset}
1552
+ `
1553
+ );
1554
+ process.stdout.write(
1555
+ ` ${c.bold}${c.cyan}+----------------------------------------+${c.reset}
1556
+ `
1557
+ );
1558
+ process.stdout.write("\n");
1559
+ process.stdout.write(
1560
+ ` ${c.dim}Installed at${c.reset} ${nativeRepo}
1561
+
1562
+ `
1563
+ );
1564
+ if (live.length) {
1565
+ process.stdout.write(
1566
+ ` ${c.bold}${c.green}==> LIVE NOW (${live.length})${c.reset}
1567
+ `
1568
+ );
1569
+ for (const p of live) {
1570
+ process.stdout.write(` ${c.green}o${c.reset} ${p}
1571
+ `);
1572
+ }
1573
+ process.stdout.write("\n");
1574
+ }
1575
+ if (standby.length) {
1576
+ process.stdout.write(
1577
+ ` ${c.bold}${c.yellow}==> STANDING BY (${standby.length})${c.reset} ${c.dim}auto-activate on install${c.reset}
1578
+ `
1579
+ );
1580
+ for (const p of standby) {
1581
+ process.stdout.write(` ${c.yellow}o${c.reset} ${p}
1582
+ `);
1583
+ }
1584
+ process.stdout.write("\n");
1585
+ }
1586
+ if (live.length === 0 && standby.length === 0) {
1587
+ process.stdout.write(
1588
+ ` ${c.yellow}Ready to configure${c.reset} -- pass a platform name to get started: ${c.bold}ijfw install claude${c.reset}
1589
+
1590
+ `
1591
+ );
1592
+ }
1593
+ if (claudeNeedsRestart) {
1594
+ process.stdout.write(
1595
+ ` ${c.bold}${c.yellow}==> RESTART REQUIRED${c.reset} Claude Code is running -- ${c.bold}restart your sessions now to activate IJFW.${c.reset}
1596
+
1597
+ `
1598
+ );
1599
+ }
1600
+ }
1601
+ function maybeInstallPostCommitHook({ noninteractive }) {
1602
+ if (process.env.CI || noninteractive) return;
1603
+ if (!fs2.existsSync(".git")) return;
1604
+ printNote(
1605
+ "Tip: background Trident critique on every commit -- pass --post-commit-hook to enable."
1606
+ );
1607
+ }
1608
+ async function runInstall({
1609
+ targets,
1610
+ ijfwHome,
1611
+ ijfwCustomDir,
1612
+ repoRoot,
1613
+ noninteractive
1614
+ } = {}) {
1615
+ const home = homeReal();
1616
+ const resolvedIjfwHome = ijfwHome && typeof ijfwHome === "string" && ijfwHome.length > 0 ? path2.resolve(ijfwHome) : path2.join(home, ".ijfw");
1617
+ const customDir = !!ijfwCustomDir;
1618
+ const resolvedRepoRoot = repoRoot && typeof repoRoot === "string" ? path2.resolve(repoRoot) : process.cwd();
1619
+ let targetList = Array.isArray(targets) && targets.length > 0 ? targets.filter((t) => CANONICAL_ORDER.includes(t)) : CANONICAL_ORDER.slice();
1620
+ targetList = CANONICAL_ORDER.filter((t) => targetList.includes(t));
1621
+ const serverJs = path2.join(resolvedRepoRoot, "mcp-server", "src", "server.js");
1622
+ const serverJsNative = nativePath(serverJs);
1623
+ const nodeBin = process.execPath;
1624
+ const ts = timestamp();
1625
+ preflight({ repoRoot: resolvedRepoRoot, serverJs });
1626
+ if (customDir) {
1627
+ printOk(
1628
+ "Custom-dir install -- skipping ~/.ijfw/ sibling links (canonical-dir feature)."
1629
+ );
1630
+ } else {
1631
+ linkPlugin({ repoRoot: resolvedRepoRoot, ijfwHome: resolvedIjfwHome, ts });
1632
+ }
1633
+ seedState({
1634
+ ijfwHome: resolvedIjfwHome,
1635
+ repoRoot: resolvedRepoRoot,
1636
+ nodeBin
1637
+ });
1638
+ if (!customDir) {
1639
+ detectStatusline({ home, ijfwHome: resolvedIjfwHome });
1640
+ }
1641
+ if (!customDir) {
1642
+ patchPluginMcpJson({
1643
+ ijfwHome: resolvedIjfwHome,
1644
+ repoRoot: resolvedRepoRoot,
1645
+ nodeBin,
1646
+ serverJs
1647
+ });
1648
+ }
1649
+ if (!customDir) {
1650
+ nukePluginCache({ home });
1651
+ }
1652
+ if (!customDir) {
1653
+ linkMcpSibling({
1654
+ repoRoot: resolvedRepoRoot,
1655
+ ijfwHome: resolvedIjfwHome,
1656
+ ts
1657
+ });
1658
+ }
1659
+ pruneBackups({ home });
1660
+ const live = [];
1661
+ const standby = [];
1662
+ const failed = [];
1663
+ let claudeNeedsRestart = false;
1664
+ for (const target of targetList) {
1665
+ const fn = TARGET_FNS[target];
1666
+ if (typeof fn !== "function") {
1667
+ printWarn(`No installer registered for target '${target}' -- skipped.`);
1668
+ continue;
1669
+ }
1670
+ printSection(`[${prettyName(target)}]`);
1671
+ const ctx = {
1672
+ home,
1673
+ homeReal: home,
1674
+ ijfwHome: resolvedIjfwHome,
1675
+ ijfwCustomDir: customDir,
1676
+ repoRoot: resolvedRepoRoot,
1677
+ serverJs,
1678
+ serverJsNative,
1679
+ nodeBin,
1680
+ ts,
1681
+ log: {
1682
+ ok: printOk,
1683
+ note: printNote,
1684
+ info: printInfo,
1685
+ warn: printWarn
1686
+ }
1687
+ };
1688
+ let outcome;
1689
+ try {
1690
+ outcome = await fn(ctx);
1691
+ } catch (err) {
1692
+ failed.push(prettyName(target));
1693
+ printWarn(
1694
+ `${prettyName(target)}: install failed -- ${err && err.message ? err.message : String(err)}`
1695
+ );
1696
+ continue;
1697
+ }
1698
+ if (outcome && outcome.restart === true && target === "claude") {
1699
+ claudeNeedsRestart = true;
1700
+ }
1701
+ let liveNow = false;
1702
+ try {
1703
+ liveNow = !!isLive(target, home);
1704
+ } catch {
1705
+ liveNow = false;
1706
+ }
1707
+ const display = prettyName(target);
1708
+ if (liveNow) {
1709
+ live.push(display);
1710
+ } else {
1711
+ standby.push(display);
1712
+ }
1713
+ }
1714
+ printSummary({
1715
+ live,
1716
+ standby,
1717
+ claudeNeedsRestart,
1718
+ repoRoot: resolvedRepoRoot
1719
+ });
1720
+ maybeInstallPostCommitHook({ noninteractive: !!noninteractive });
1721
+ return { live, standby, failed, claudeNeedsRestart };
1722
+ }
1723
+ var CANONICAL_ORDER, TARGET_FNS, install_flow_default;
1724
+ var init_install_flow = __esm({
1725
+ "src/install-flow.js"() {
1726
+ init_install_helpers();
1727
+ init_install_targets_1_7();
1728
+ init_install_targets_8_14();
1729
+ CANONICAL_ORDER = [
1730
+ "claude",
1731
+ "codex",
1732
+ "gemini",
1733
+ "wayland",
1734
+ "hermes",
1735
+ "cursor",
1736
+ "windsurf",
1737
+ "copilot",
1738
+ "opencode",
1739
+ "qwen",
1740
+ "cline",
1741
+ "kimi",
1742
+ "openclaw",
1743
+ "aider"
1744
+ ];
1745
+ TARGET_FNS = {
1746
+ claude: installClaude,
1747
+ codex: installCodex,
1748
+ gemini: installGemini,
1749
+ wayland: installWayland,
1750
+ hermes: installHermes,
1751
+ cursor: installCursor,
1752
+ windsurf: installWindsurf,
1753
+ copilot: installCopilot,
1754
+ opencode: installOpencode,
1755
+ qwen: installQwen,
1756
+ cline: installCline,
1757
+ kimi: installKimi,
1758
+ openclaw: installOpenclaw,
1759
+ aider: installAider
1760
+ };
1761
+ install_flow_default = { runInstall, CANONICAL_ORDER };
1762
+ }
1763
+ });
2
1764
 
3
1765
  // src/install.js
4
- import { spawnSync } from "node:child_process";
5
- import { existsSync as existsSync2, rmSync, mkdirSync as mkdirSync2, realpathSync, renameSync as renameSync2 } from "node:fs";
6
- import { resolve as resolve2, join as join2, dirname as dirname2 } from "node:path";
7
- import { homedir as homedir2, platform } from "node:os";
8
- import { fileURLToPath } from "node:url";
1766
+ import { spawnSync as spawnSync2 } from "node:child_process";
1767
+ import { existsSync as existsSync5, rmSync, mkdirSync as mkdirSync4, realpathSync as realpathSync2, renameSync as renameSync3 } from "node:fs";
1768
+ import { resolve as resolve3, join as join5, dirname as dirname5 } from "node:path";
1769
+ import { homedir as homedir3, platform as platform2 } from "node:os";
1770
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
9
1771
 
10
1772
  // src/marketplace.js
11
1773
  import { readFileSync, writeFileSync, renameSync, unlinkSync, existsSync, mkdirSync } from "node:fs";
12
1774
  import { randomBytes } from "node:crypto";
13
1775
  import { dirname, join, resolve } from "node:path";
14
1776
  import { homedir } from "node:os";
15
- function atomicWriteJson(path, data) {
16
- const tmp = `${path}.tmp.${process.pid}.${randomBytes(4).toString("hex")}`;
1777
+ function atomicWriteJson(path3, data) {
1778
+ const tmp = `${path3}.tmp.${process.pid}.${randomBytes(4).toString("hex")}`;
17
1779
  writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n");
18
1780
  try {
19
- renameSync(tmp, path);
1781
+ renameSync(tmp, path3);
20
1782
  } catch (err) {
21
1783
  try {
22
1784
  unlinkSync(tmp);
23
1785
  } catch {
24
1786
  }
25
- throw new Error(`atomic write failed for ${path}: ${err.message}`);
1787
+ throw new Error(`atomic write failed for ${path3}: ${err.message}`);
26
1788
  }
27
1789
  }
28
1790
  function claudeSettingsPath() {
@@ -110,6 +1872,57 @@ function mergeMarketplace(settingsPath = claudeSettingsPath(), options = {}) {
110
1872
  return settings;
111
1873
  }
112
1874
 
1875
+ // src/post-install/cold-scan.js
1876
+ import { spawn } from "node:child_process";
1877
+ import { existsSync as existsSync2 } from "node:fs";
1878
+ import { join as join2, dirname as dirname2, resolve as resolve2 } from "node:path";
1879
+ import { fileURLToPath } from "node:url";
1880
+ var __filename = fileURLToPath(import.meta.url);
1881
+ var __dirname = dirname2(__filename);
1882
+ var RUNNER_REL_FROM_HERE = "../../../mcp-server/src/cold-scan-runner.mjs";
1883
+ function triggerColdScan(projectRoot, options = {}) {
1884
+ const root = String(projectRoot || process.cwd());
1885
+ if (!existsSync2(root)) {
1886
+ return { spawned: false, reason: "project root not present" };
1887
+ }
1888
+ const ijfwHome = options.ijfwHome || process.env.IJFW_HOME || "";
1889
+ const candidates = [
1890
+ resolve2(__dirname, RUNNER_REL_FROM_HERE),
1891
+ // repo root from installer/src/post-install/
1892
+ resolve2(__dirname, "../../mcp-server/src/cold-scan-runner.mjs"),
1893
+ // legacy guess (kept for compat)
1894
+ ijfwHome ? join2(ijfwHome, "mcp-server/src/cold-scan-runner.mjs") : "",
1895
+ // explicit IJFW_HOME
1896
+ resolve2(projectRoot, "mcp-server/src/cold-scan-runner.mjs")
1897
+ // projectRoot itself is an IJFW checkout
1898
+ ].filter(Boolean);
1899
+ let runnerPath = null;
1900
+ for (const c of candidates) {
1901
+ if (c && existsSync2(c)) {
1902
+ runnerPath = c;
1903
+ break;
1904
+ }
1905
+ }
1906
+ if (!runnerPath) {
1907
+ return { spawned: false, reason: "cold-scan runner not present" };
1908
+ }
1909
+ const nodeBin = options.nodeBin || process.execPath;
1910
+ const args = [runnerPath, "--project-root", root];
1911
+ if (options.c9Available === false) args.push("--no-c9");
1912
+ if (options.maxFiles) args.push("--max-files", String(options.maxFiles));
1913
+ try {
1914
+ const child = spawn(nodeBin, args, {
1915
+ detached: true,
1916
+ stdio: "ignore",
1917
+ env: { ...process.env, IJFW_COLD_SCAN: "1" }
1918
+ });
1919
+ child.unref();
1920
+ return { spawned: true, pid: child.pid };
1921
+ } catch (err) {
1922
+ return { spawned: false, reason: String(err && err.message ? err.message : err) };
1923
+ }
1924
+ }
1925
+
113
1926
  // src/install.js
114
1927
  var DEFAULT_REPO = "https://gitlab.com/therealseandonahoe/ijfw.git";
115
1928
  var DEFAULT_BRANCH = "main";
@@ -133,7 +1946,7 @@ function parseArgs(argv) {
133
1946
  }
134
1947
  function latestTagFromGithub() {
135
1948
  try {
136
- const res = spawnSync("git", ["ls-remote", "--tags", "--refs", "--sort=-v:refname", DEFAULT_REPO], {
1949
+ const res = spawnSync2("git", ["ls-remote", "--tags", "--refs", "--sort=-v:refname", DEFAULT_REPO], {
137
1950
  encoding: "utf8",
138
1951
  timeout: 1e4
139
1952
  });
@@ -165,12 +1978,12 @@ Usage: npx @ijfw/install [--dir <path>] [--branch <name>] [--no-marketplace] [--
165
1978
  --yes non-interactive
166
1979
  `);
167
1980
  }
168
- function preflight() {
1981
+ function preflight2() {
169
1982
  const issues = [];
170
1983
  const [major] = process.versions.node.split(".").map(Number);
171
1984
  if (major < 18) issues.push(`IJFW needs Node >=18 -- current: ${process.versions.node}. Upgrade Node, then retry.`);
172
- if (!hasBin("git")) {
173
- if (platform() === "win32") {
1985
+ if (!hasBin2("git")) {
1986
+ if (platform2() === "win32") {
174
1987
  issues.push(
175
1988
  "IJFW needs Git for Windows (it bundles git + bash). One command:\n winget install --id Git.Git -e --source winget --accept-source-agreements --accept-package-agreements\n Then close this PowerShell window, open a fresh one, and rerun:\n npx -p @ijfw/install ijfw-install"
176
1989
  );
@@ -178,38 +1991,29 @@ function preflight() {
178
1991
  issues.push("IJFW needs git on PATH -- install git (https://git-scm.com), then retry.");
179
1992
  }
180
1993
  }
181
- if (!findBash()) {
182
- if (platform() === "win32") {
183
- issues.push(
184
- "IJFW could not locate bash.exe. Git for Windows installs it at\n C:\\Program Files\\Git\\bin\\bash.exe\n If you installed Git elsewhere, add its bin\\ to PATH and rerun.\n Missing Git entirely? winget install --id Git.Git -e --source winget"
185
- );
186
- } else {
187
- issues.push("IJFW needs bash on PATH -- install bash, then retry.");
188
- }
189
- }
190
1994
  return issues;
191
1995
  }
192
- function hasBin(bin) {
193
- const res = spawnSync(bin, ["--version"], { stdio: "ignore", timeout: 3e3 });
1996
+ function hasBin2(bin) {
1997
+ const res = spawnSync2(bin, ["--version"], { stdio: "ignore", timeout: 3e3 });
194
1998
  if (res.error && res.error.code === "ENOENT") return false;
195
1999
  if (res.status === 0) return true;
196
2000
  return res.error == null;
197
2001
  }
198
2002
  function findBash() {
199
- if (hasBin("bash") && platform() !== "win32") return "bash";
200
- if (platform() !== "win32") return hasBin("bash") ? "bash" : null;
201
- const whereGit = spawnSync("where", ["git"], { encoding: "utf8" });
2003
+ if (hasBin2("bash") && platform2() !== "win32") return "bash";
2004
+ if (platform2() !== "win32") return hasBin2("bash") ? "bash" : null;
2005
+ const whereGit = spawnSync2("where", ["git"], { encoding: "utf8" });
202
2006
  if (whereGit.status === 0) {
203
2007
  const gitPath = (whereGit.stdout || "").split(/\r?\n/)[0].trim();
204
- if (gitPath && existsSync2(gitPath)) {
205
- const gitDir = dirname2(gitPath);
206
- const gitRoot = dirname2(gitDir);
2008
+ if (gitPath && existsSync5(gitPath)) {
2009
+ const gitDir = dirname5(gitPath);
2010
+ const gitRoot = dirname5(gitDir);
207
2011
  const candidates = [
208
- join2(gitDir, "bash.exe"),
209
- join2(gitRoot, "bin", "bash.exe"),
210
- join2(gitRoot, "usr", "bin", "bash.exe")
2012
+ join5(gitDir, "bash.exe"),
2013
+ join5(gitRoot, "bin", "bash.exe"),
2014
+ join5(gitRoot, "usr", "bin", "bash.exe")
211
2015
  ];
212
- for (const c of candidates) if (existsSync2(c)) return c;
2016
+ for (const c of candidates) if (existsSync5(c)) return c;
213
2017
  }
214
2018
  }
215
2019
  for (const c of [
@@ -217,106 +2021,99 @@ function findBash() {
217
2021
  "C:\\Program Files\\Git\\usr\\bin\\bash.exe",
218
2022
  "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
219
2023
  "C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe"
220
- ]) if (existsSync2(c)) return c;
221
- if (hasBin("bash")) return "bash";
2024
+ ]) if (existsSync5(c)) return c;
2025
+ if (hasBin2("bash")) return "bash";
222
2026
  return null;
223
2027
  }
224
2028
  function resolveTarget(opt) {
225
- if (opt.dir) return resolve2(opt.dir);
226
- if (process.env.IJFW_HOME) return resolve2(process.env.IJFW_HOME);
227
- return join2(homedir2(), ".ijfw");
2029
+ if (opt.dir) return resolve3(opt.dir);
2030
+ if (process.env.IJFW_HOME) return resolve3(process.env.IJFW_HOME);
2031
+ return join5(homedir3(), ".ijfw");
228
2032
  }
229
2033
  function runCheck(cmd, args, opts) {
230
- const r = spawnSync(cmd, args, { encoding: "utf8", ...opts });
2034
+ const r = spawnSync2(cmd, args, { encoding: "utf8", ...opts });
231
2035
  return { status: r.status, stdout: r.stdout || "", stderr: r.stderr || "", spawnError: r.error?.code, signal: r.signal };
232
2036
  }
233
2037
  function cloneOrPull(dir, branch) {
234
- if (!existsSync2(dir)) {
235
- mkdirSync2(dir, { recursive: true });
236
- const r = spawnSync("git", ["clone", "--depth", "1", "--branch", branch, DEFAULT_REPO, dir], { stdio: "inherit" });
2038
+ if (!existsSync5(dir)) {
2039
+ mkdirSync4(dir, { recursive: true });
2040
+ const r = spawnSync2("git", ["clone", "--depth", "1", "--branch", branch, DEFAULT_REPO, dir], { stdio: "inherit" });
237
2041
  if (r.status !== 0) throw new Error(`IJFW repo fetch did not complete (exit ${r.status}) -- check network access and retry.`);
238
2042
  return "cloned";
239
2043
  }
240
- const hasGit = existsSync2(join2(dir, ".git"));
2044
+ const hasGit = existsSync5(join5(dir, ".git"));
241
2045
  if (hasGit) {
242
2046
  const { status: remoteStatus, stdout, stderr: remoteStderr, spawnError: remoteSpawnError, signal: remoteSignal } = runCheck("git", ["-C", dir, "remote", "get-url", "origin"]);
243
2047
  if (remoteSpawnError) console.warn(` git spawn error (${remoteSpawnError}) -- check git is on PATH`);
244
2048
  else if (remoteSignal) console.warn(` git exited on signal ${remoteSignal}`);
245
2049
  else if (remoteStatus !== 0 && remoteStderr) console.warn(` git remote get-url: ${remoteStderr.slice(0, 120).trim()}`);
246
2050
  if (remoteStatus === 0) {
247
- const STALE_ORIGINS = [
248
- "https://github.com/seandonahoe/ijfw.git",
249
- "https://github.com/seandonahoe/ijfw",
250
- "https://github.com/seandonahoe/ijfw/",
251
- "https://github.com/seandonahoe/ijfw.git/"
2051
+ const STALE_PATTERNS = [
2052
+ /^https:\/\/github\.com\/seandonahoe\/ijfw(\.git)?\/?$/i,
2053
+ /^https:\/\/github\.com\/therealseandonahoe\/ijfw(\.git)?\/?$/i
252
2054
  ];
253
2055
  const currentOrigin = (stdout || "").trim();
254
- if (STALE_ORIGINS.includes(currentOrigin)) {
255
- const setUrl = spawnSync("git", ["-C", dir, "remote", "set-url", "origin", DEFAULT_REPO], { stdio: "inherit" });
2056
+ if (STALE_PATTERNS.some((re) => re.test(currentOrigin))) {
2057
+ const setUrl = spawnSync2("git", ["-C", dir, "remote", "set-url", "origin", DEFAULT_REPO], { stdio: "inherit" });
256
2058
  if (setUrl.status !== 0) {
257
2059
  console.warn(` [!] origin migration failed -- could not repoint ${currentOrigin} to ${DEFAULT_REPO}`);
258
2060
  } else {
259
2061
  console.log(` origin migration: ${currentOrigin} -> ${DEFAULT_REPO}`);
260
2062
  }
261
2063
  }
262
- const fetch = spawnSync("git", ["-C", dir, "fetch", "--depth", "1", "origin", branch], { stdio: "inherit" });
2064
+ const fetch = spawnSync2("git", ["-C", dir, "fetch", "--depth", "1", "origin", branch], { stdio: "inherit" });
263
2065
  if (fetch.status !== 0) throw new Error(`IJFW fetch did not complete (exit ${fetch.status}) -- check network access and retry.`);
264
- const co = spawnSync("git", ["-C", dir, "checkout", "-f", "FETCH_HEAD"], { stdio: "inherit" });
2066
+ const co = spawnSync2("git", ["-C", dir, "checkout", "-f", "FETCH_HEAD"], { stdio: "inherit" });
265
2067
  if (co.status !== 0) throw new Error(`IJFW checkout did not complete (exit ${co.status}) -- run ijfw doctor to check prerequisites.`);
266
2068
  return "updated";
267
2069
  }
268
2070
  }
269
2071
  const backupDir = dir + ".bak." + Date.now();
270
- renameSync2(dir, backupDir);
2072
+ renameSync3(dir, backupDir);
271
2073
  try {
272
- const r = spawnSync("git", ["clone", "--depth", "1", "--branch", branch, DEFAULT_REPO, dir], { stdio: "inherit" });
2074
+ const r = spawnSync2("git", ["clone", "--depth", "1", "--branch", branch, DEFAULT_REPO, dir], { stdio: "inherit" });
273
2075
  if (r.status !== 0) throw new Error(`IJFW repo fetch did not complete (exit ${r.status}) -- check network access and retry.`);
274
2076
  for (const item of ["memory", "sessions", "install.log", ".session-counter"]) {
275
- const src = join2(backupDir, item);
276
- if (existsSync2(src)) {
277
- const dst = join2(dir, item);
278
- if (existsSync2(dst)) rmSync(dst, { recursive: true, force: true });
279
- renameSync2(src, dst);
2077
+ const src = join5(backupDir, item);
2078
+ if (existsSync5(src)) {
2079
+ const dst = join5(dir, item);
2080
+ if (existsSync5(dst)) rmSync(dst, { recursive: true, force: true });
2081
+ renameSync3(src, dst);
280
2082
  }
281
2083
  }
282
2084
  rmSync(backupDir, { recursive: true, force: true });
283
2085
  return "updated";
284
2086
  } catch (err) {
285
- if (existsSync2(dir)) rmSync(dir, { recursive: true, force: true });
286
- renameSync2(backupDir, dir);
2087
+ if (existsSync5(dir)) rmSync(dir, { recursive: true, force: true });
2088
+ renameSync3(backupDir, dir);
287
2089
  throw err;
288
2090
  }
289
2091
  }
290
- function runInstallScript(dir) {
291
- const script = join2(dir, "scripts", "install.sh");
292
- if (!existsSync2(script)) throw new Error(`IJFW install script not found at ${script} -- re-run the installer to restore it.`);
293
- const canonicalDir = join2(homedir2(), ".ijfw");
294
- const isCustomDir = resolve2(dir) !== canonicalDir ? "1" : "0";
295
- const env = {
296
- ...process.env,
297
- IJFW_NONINTERACTIVE: process.env.CI ? "1" : process.env.IJFW_NONINTERACTIVE ?? "",
298
- IJFW_HOME: dir,
299
- IJFW_CUSTOM_DIR: isCustomDir
300
- };
301
- const bashExe = findBash();
302
- if (!bashExe) {
303
- throw new Error("IJFW could not locate bash (preflight should have caught this -- file an issue).");
304
- }
305
- const r = spawnSync(bashExe, ["scripts/install.sh"], { cwd: dir, stdio: "inherit", env });
306
- if (r.status !== 0) throw new Error(`IJFW platform config step did not complete (exit ${r.status}) -- run ijfw doctor to see what to fix.`);
2092
+ async function runInstallScript(dir) {
2093
+ const canonicalDir = join5(homedir3(), ".ijfw");
2094
+ const isCustomDir = resolve3(dir) !== canonicalDir;
2095
+ const { runInstall: runInstall2 } = await Promise.resolve().then(() => (init_install_flow(), install_flow_exports));
2096
+ await runInstall2({
2097
+ targets: void 0,
2098
+ // undefined = canonical 14
2099
+ ijfwHome: dir,
2100
+ ijfwCustomDir: isCustomDir,
2101
+ repoRoot: dir,
2102
+ noninteractive: !!process.env.CI || process.env.IJFW_NONINTERACTIVE === "1"
2103
+ });
307
2104
  }
308
2105
  async function main() {
309
2106
  const opts = parseArgs(process.argv);
310
- const issues = preflight();
2107
+ const issues = preflight2();
311
2108
  if (issues.length) {
312
2109
  console.error("IJFW needs a couple of things first -- fix these and re-run:");
313
2110
  for (const i of issues) console.error(" - " + i);
314
2111
  process.exit(1);
315
2112
  }
316
2113
  const target = resolveTarget(opts);
317
- const createdThisRun = !existsSync2(target);
2114
+ const createdThisRun = !existsSync5(target);
318
2115
  const sigint = () => {
319
- if (createdThisRun && existsSync2(target)) {
2116
+ if (createdThisRun && existsSync5(target)) {
320
2117
  try {
321
2118
  rmSync(target, { recursive: true, force: true });
322
2119
  } catch {
@@ -330,15 +2127,20 @@ async function main() {
330
2127
  console.log(` version: ${ref}`);
331
2128
  const action = cloneOrPull(target, ref);
332
2129
  console.log(` repo ${action}`);
333
- runInstallScript(target);
2130
+ await runInstallScript(target);
334
2131
  console.log(" platform configs applied");
335
2132
  if (!opts.noMarketplace) {
336
2133
  const settingsPath = claudeSettingsPath();
337
2134
  mergeMarketplace(settingsPath, { rootDir: target });
338
2135
  console.log(` marketplace registered in ${settingsPath}`);
339
2136
  }
2137
+ try {
2138
+ const coldScanRoot = process.env.IJFW_PROJECT_DIR || process.cwd();
2139
+ triggerColdScan(coldScanRoot, { ijfwHome: target });
2140
+ } catch {
2141
+ }
340
2142
  console.log("");
341
- console.log("IJFW now active across 13 platforms -- one memory layer, all your AIs, zero config.");
2143
+ console.log("IJFW now active across 14 platforms -- one memory layer, all your models, zero config.");
342
2144
  console.log(" Run `ijfw demo` to see the Trident in action.");
343
2145
  console.log(" Run `ijfw doctor` to confirm which auditors are reachable.");
344
2146
  console.log(" Privacy: everything stays local. See NO_TELEMETRY.md.");
@@ -346,8 +2148,8 @@ async function main() {
346
2148
  }
347
2149
  function isDirectRun() {
348
2150
  try {
349
- const entry = process.argv[1] && realpathSync(process.argv[1]);
350
- const self = fileURLToPath(import.meta.url);
2151
+ const entry = process.argv[1] && realpathSync2(process.argv[1]);
2152
+ const self = fileURLToPath2(import.meta.url);
351
2153
  return entry === self;
352
2154
  } catch {
353
2155
  return false;