@ijfw/install 1.3.2 → 1.4.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
@@ -19,11 +19,15 @@ import {
19
19
  chmodSync,
20
20
  mkdirSync as mkdirSync2,
21
21
  realpathSync,
22
- statSync
22
+ statSync,
23
+ lstatSync,
24
+ openSync,
25
+ closeSync,
26
+ constants as fsConstants
23
27
  } from "node:fs";
24
- import { dirname as dirname3, basename, join as join3, normalize, delimiter } from "node:path";
28
+ import { dirname as dirname3, basename, join as join3, normalize, delimiter, resolve as resolve3, sep } from "node:path";
25
29
  import { homedir as homedir2 } from "node:os";
26
- import { createHash } from "node:crypto";
30
+ import { createHash, randomBytes as randomBytes2 } from "node:crypto";
27
31
  function printOk(msg) {
28
32
  process.stdout.write(` [ok] ${msg}
29
33
  `);
@@ -60,7 +64,7 @@ function nativePath(p) {
60
64
  function writeAtomic(path3, contents, opts = {}) {
61
65
  const mode = opts.mode ?? 384;
62
66
  mkdirSync2(dirname3(path3), { recursive: true });
63
- const tmp = `${path3}.tmp.${process.pid}`;
67
+ const tmp = `${path3}.tmp.${process.pid}.${randomBytes2(4).toString("hex")}`;
64
68
  writeFileSync2(tmp, contents, { mode });
65
69
  renameSync2(tmp, path3);
66
70
  try {
@@ -498,10 +502,47 @@ function clineMerge(serverJs, home, ts) {
498
502
  writeAtomic(dst, JSON.stringify(doc, null, 2), { mode: 384 });
499
503
  return dst;
500
504
  }
501
- var IS_WIN;
505
+ function mergeYamlHook(dst, scriptPath, ts) {
506
+ mkdirSync2(dirname3(dst), { recursive: true });
507
+ if (ts) backup(dst, ts);
508
+ let text = "";
509
+ try {
510
+ text = existsSync3(dst) ? readFileSync2(dst, "utf8") : "";
511
+ } catch {
512
+ text = "";
513
+ }
514
+ const BEGIN = "# IJFW-HOOK-BEGIN pre_tool_use";
515
+ const END = "# IJFW-HOOK-END pre_tool_use";
516
+ text = stripSentinelBlock(text, BEGIN, END);
517
+ if (text && !text.endsWith("\n")) text += "\n";
518
+ const escaped = String(scriptPath).replace(/"/g, '\\"');
519
+ let block = `${BEGIN}
520
+ `;
521
+ block += "hooks:\n";
522
+ block += " pre_tool_use:\n";
523
+ block += ` - script: "${escaped}"
524
+ `;
525
+ block += " interpreter: python3\n";
526
+ block += `${END}
527
+ `;
528
+ writeAtomic(dst, text + block, { mode: 384 });
529
+ }
530
+ var IS_WIN, EXTENSION_PLATFORM_SKILL_DIRS;
502
531
  var init_install_helpers = __esm({
503
532
  "src/install-helpers.js"() {
504
533
  IS_WIN = process.platform === "win32";
534
+ EXTENSION_PLATFORM_SKILL_DIRS = Object.freeze([
535
+ { id: "claude", rel: "claude/skills" },
536
+ { id: "codex", rel: "codex/skills" },
537
+ { id: "gemini", rel: "gemini/extensions/ijfw/skills" },
538
+ { id: "cursor", rel: "cursor/skills" },
539
+ { id: "windsurf", rel: "windsurf/skills" },
540
+ { id: "copilot", rel: "copilot/skills" },
541
+ { id: "hermes", rel: "hermes/skills" },
542
+ { id: "wayland", rel: "wayland/skills" },
543
+ { id: "shared", rel: "shared/skills" },
544
+ { id: "universal", rel: "universal/skills" }
545
+ ]);
505
546
  }
506
547
  });
507
548
 
@@ -756,6 +797,12 @@ async function installCodex(ctx) {
756
797
  for (const f of listFiles(hookScriptsDir, ".sh")) {
757
798
  installHook(f.path, join4(hooksBase, f.name), ctx.ts);
758
799
  }
800
+ const codexScriptsSrc = join4(ctx.repoRoot, "codex", ".codex", "scripts");
801
+ const codexScriptsDst = join4(hooksBase, "scripts");
802
+ ensureDir(codexScriptsDst);
803
+ for (const f of listFiles(codexScriptsSrc, ".sh")) {
804
+ installHook(f.path, join4(codexScriptsDst, f.name), ctx.ts);
805
+ }
759
806
  const codexCtx = join4(ctx.home, ".codex", "IJFW.md");
760
807
  copyIfAbsent(join4(ctx.repoRoot, "codex", ".codex", "IJFW.md"), codexCtx);
761
808
  const userSkills = join4(ctx.home, ".codex", "skills");
@@ -836,6 +883,12 @@ async function installGemini(ctx) {
836
883
  for (const f of listFiles(hookScriptsDir, ".sh")) {
837
884
  installHook(f.path, join4(extDst, "hooks", f.name), ctx.ts);
838
885
  }
886
+ const geminiHookScriptsSrc = join4(extSrc, "hooks", "scripts");
887
+ const geminiHookScriptsDst = join4(extDst, "hooks", "scripts");
888
+ ensureDir(geminiHookScriptsDst);
889
+ for (const f of listFiles(geminiHookScriptsSrc, ".sh")) {
890
+ installHook(f.path, join4(geminiHookScriptsDst, f.name), ctx.ts);
891
+ }
839
892
  const skillsSrc = join4(extSrc, "skills");
840
893
  for (const sd of listSubdirs(skillsSrc)) {
841
894
  copyDirIfAbsent(sd.path, join4(extDst, "skills", sd.name));
@@ -898,7 +951,8 @@ async function installWayland(ctx) {
898
951
  }
899
952
  }
900
953
  }
901
- ctx.log.ok("Installed Wayland bundle: MCP + WAYLAND.md + skills + plugin");
954
+ mergeYamlHook(dst, "plugins/ijfw/hooks/pre_tool_use_extension_check.py", ctx.ts);
955
+ ctx.log.ok("Installed Wayland bundle: MCP + WAYLAND.md + skills + plugin + tier-2 hook");
902
956
  return { status: "ok" };
903
957
  }
904
958
  async function installHermes(ctx) {
@@ -949,7 +1003,8 @@ async function installHermes(ctx) {
949
1003
  }
950
1004
  }
951
1005
  mergeYamlPluginsEnabled(dst, "ijfw");
952
- ctx.log.ok("Installed Hermes bundle: MCP + HERMES.md + skills + plugin");
1006
+ mergeYamlHook(dst, "plugins/ijfw/hooks/pre_tool_use_extension_check.py", ctx.ts);
1007
+ ctx.log.ok("Installed Hermes bundle: MCP + HERMES.md + skills + plugin + tier-2 hook");
953
1008
  return { status: "ok" };
954
1009
  }
955
1010
  async function installCursor(ctx) {
@@ -1781,7 +1836,7 @@ var init_install_flow = __esm({
1781
1836
  // src/install.js
1782
1837
  import { spawnSync as spawnSync2 } from "node:child_process";
1783
1838
  import { existsSync as existsSync5, rmSync, mkdirSync as mkdirSync4, realpathSync as realpathSync2, renameSync as renameSync3 } from "node:fs";
1784
- import { resolve as resolve3, join as join5, dirname as dirname5 } from "node:path";
1839
+ import { resolve as resolve4, join as join5, dirname as dirname5 } from "node:path";
1785
1840
  import { homedir as homedir3, platform as platform2 } from "node:os";
1786
1841
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1787
1842
 
@@ -2042,8 +2097,8 @@ function findBash() {
2042
2097
  return null;
2043
2098
  }
2044
2099
  function resolveTarget(opt) {
2045
- if (opt.dir) return resolve3(opt.dir);
2046
- if (process.env.IJFW_HOME) return resolve3(process.env.IJFW_HOME);
2100
+ if (opt.dir) return resolve4(opt.dir);
2101
+ if (process.env.IJFW_HOME) return resolve4(process.env.IJFW_HOME);
2047
2102
  return join5(homedir3(), ".ijfw");
2048
2103
  }
2049
2104
  function runCheck(cmd, args, opts) {
@@ -2107,7 +2162,7 @@ function cloneOrPull(dir, branch) {
2107
2162
  }
2108
2163
  async function runInstallScript(dir) {
2109
2164
  const canonicalDir = join5(homedir3(), ".ijfw");
2110
- const isCustomDir = resolve3(dir) !== canonicalDir;
2165
+ const isCustomDir = resolve4(dir) !== canonicalDir;
2111
2166
  const { runInstall: runInstall2 } = await Promise.resolve().then(() => (init_install_flow(), install_flow_exports));
2112
2167
  await runInstall2({
2113
2168
  targets: void 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ijfw/install",
3
- "version": "1.3.2",
3
+ "version": "1.4.1",
4
4
  "description": "One-command installer for IJFW -- the AI efficiency layer. One install, every AI coding agent, zero config.",
5
5
  "type": "module",
6
6
  "bin": {