@ijfw/install 1.4.0 → 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/README.md CHANGED
@@ -49,6 +49,36 @@ iwr https://gitlab.com/therealseandonahoe/ijfw/-/raw/main/installer/src/install.
49
49
  .\install.ps1 -Dir $env:USERPROFILE\.ijfw
50
50
  ```
51
51
 
52
+ ## Extension CLI
53
+
54
+ IJFW ships a full extension system for installing and sandboxing third-party skills.
55
+
56
+ ```bash
57
+ # Publisher key management
58
+ ijfw extension keygen <author> # Generate an Ed25519 publisher keypair
59
+ ijfw extension trust <keyId> <publicKey> # Add a publisher to your trusted store
60
+ ijfw extension trust-registry [<url>] # Pull + apply the hosted publisher registry
61
+ ijfw extension untrust <keyId> # Remove a publisher from your trusted store
62
+ ijfw extension trusted # List all trusted publishers
63
+
64
+ # Extension lifecycle
65
+ ijfw extension add <source> [flags] # Install an extension (npm name, local path, or https git URL)
66
+ --allow-unsigned # Accept extensions with no signature
67
+ --accept-untrusted # Accept extensions signed by an untrusted publisher (prompts on TTY)
68
+ --activate # Auto-activate after install
69
+ ijfw extension activate <name> # Activate an installed extension (enforces declared permissions)
70
+ ijfw extension deactivate # Deactivate the current extension
71
+
72
+ # Admin / registry maintainer (rare)
73
+ ijfw extension rotate-keys <oldKeyId> <newKeyId> # Produce a signed rotation token
74
+ ijfw extension keygen-meta <author> # Generate the registry meta-keypair
75
+ ijfw extension sign-registry <path> # Sign a registry JSON file in place
76
+ ijfw extension verify-registry <path> # Verify a registry JSON signature
77
+ ijfw extension registry-status # Show registry cache age + signature status
78
+ ```
79
+
80
+ The rotation flow and registry maintainer docs live in `docs/REGISTRY-MAINTAINER.md`.
81
+
52
82
  ## Build (contributors)
53
83
 
54
84
  ```bash
package/dist/install.js CHANGED
@@ -502,6 +502,31 @@ function clineMerge(serverJs, home, ts) {
502
502
  writeAtomic(dst, JSON.stringify(doc, null, 2), { mode: 384 });
503
503
  return dst;
504
504
  }
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
+ }
505
530
  var IS_WIN, EXTENSION_PLATFORM_SKILL_DIRS;
506
531
  var init_install_helpers = __esm({
507
532
  "src/install-helpers.js"() {
@@ -772,6 +797,12 @@ async function installCodex(ctx) {
772
797
  for (const f of listFiles(hookScriptsDir, ".sh")) {
773
798
  installHook(f.path, join4(hooksBase, f.name), ctx.ts);
774
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
+ }
775
806
  const codexCtx = join4(ctx.home, ".codex", "IJFW.md");
776
807
  copyIfAbsent(join4(ctx.repoRoot, "codex", ".codex", "IJFW.md"), codexCtx);
777
808
  const userSkills = join4(ctx.home, ".codex", "skills");
@@ -852,6 +883,12 @@ async function installGemini(ctx) {
852
883
  for (const f of listFiles(hookScriptsDir, ".sh")) {
853
884
  installHook(f.path, join4(extDst, "hooks", f.name), ctx.ts);
854
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
+ }
855
892
  const skillsSrc = join4(extSrc, "skills");
856
893
  for (const sd of listSubdirs(skillsSrc)) {
857
894
  copyDirIfAbsent(sd.path, join4(extDst, "skills", sd.name));
@@ -914,7 +951,8 @@ async function installWayland(ctx) {
914
951
  }
915
952
  }
916
953
  }
917
- 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");
918
956
  return { status: "ok" };
919
957
  }
920
958
  async function installHermes(ctx) {
@@ -965,7 +1003,8 @@ async function installHermes(ctx) {
965
1003
  }
966
1004
  }
967
1005
  mergeYamlPluginsEnabled(dst, "ijfw");
968
- 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");
969
1008
  return { status: "ok" };
970
1009
  }
971
1010
  async function installCursor(ctx) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ijfw/install",
3
- "version": "1.4.0",
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": {