@ijfw/install 1.1.1 → 1.1.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,86 @@
1
1
  # Changelog -- @ijfw/install
2
2
 
3
+ ## [1.1.3] -- 2026-04-21
4
+
5
+ Windows "it just works" fix + Windows CI gate.
6
+
7
+ ### Windows npx path finally works
8
+
9
+ - **`install.js` now resolves `bash.exe` via `git.exe`'s install root** instead of requiring `bash` on PATH. Git for Windows installs `bash.exe` at `C:\Program Files\Git\bin\bash.exe` but by default does **not** add that dir to PATH, so the previous `hasBin('bash')` preflight always failed -- even on a perfectly-functional Git for Windows install. New `findBash()` helper mirrors `install.ps1`'s `Resolve-GitBash`: walks `where git` output to find the sibling bash.exe, falls back to `Program Files\Git\{bin,usr/bin}\bash.exe`, and only errors if nothing lands.
10
+ - `runInstallScript` now calls the resolved `bash.exe` path directly instead of bare `"bash"`, so the child process spawns cleanly on Windows without PATH manipulation.
11
+ - Error message when git is missing on Windows now leads with a single `winget install --id Git.Git` command, drops the PS1 `irm | iex` fallback (Windows Defender heuristically blocks that pattern), and tells the user to reopen PowerShell before rerunning so the PATH refresh picks up.
12
+ - Error message when git is present but bash.exe cannot be located points at the expected path and gives a one-line remediation.
13
+
14
+ ### Windows CI gate (new)
15
+
16
+ - `.github/workflows/windows-smoke.yml`. Runs on every push and PR on `windows-latest`. Checks: install.js loads, `findBash()` returns a live path, installer clones + runs `scripts/install.sh` against a scratch IJFW_HOME with `IJFW_CUSTOM_DIR=1`, writes the expected files. Catches the 1.1.2 regression at source -- a Windows publish that would have shipped a broken `npx` entry point now fails CI before it reaches main.
17
+
18
+ ### Internal
19
+
20
+ - `mcp-server/package.json` bumped 1.1.2 -> 1.1.3 to stay in lockstep with the installer.
21
+
22
+ ### Notes for upgraders
23
+
24
+ - No behavioral change for macOS or Linux users. `findBash()` returns `"bash"` on non-Windows hosts where PATH is reliable.
25
+ - No config-schema changes. `~/.codex/hooks.json` stays on the 1.1.2 nested map schema.
26
+
27
+ ## [1.1.2] -- 2026-04-21
28
+
29
+ Reach + bug fixes. Two new platforms (Hermes + Wayland), deep installer repairs uncovered by live-platform testing, cross-platform sync of new behavioral rules. Ships with a full end-to-end smoke harness at `scripts/e2e-smoke.sh` that now has to pass before any future release.
30
+
31
+ ### New platforms
32
+
33
+ - **Hermes** (`hermes/`): MCP registration in `~/.hermes/config.yaml`, `HERMES.md` context file, 19 IJFW skills dropped into `~/.hermes/skills/ijfw-*` in agentskills.io format. Python CLI (`hermes` command).
34
+ - **Wayland** (`wayland/`): same shape. MCP registration in `~/.wayland/config.yaml`, `WAYLAND.md` context file, skills bundle in `~/.wayland/skills/ijfw-*`. Python CLI (`wayland` command).
35
+ - `scripts/install.sh` gains a `merge_yaml_mcp` helper (prefers python3+PyYAML for parser-safe merge; sentinel-anchored fallback if PyYAML isn't available).
36
+ - Default target list expands to 8: `claude codex gemini cursor windsurf copilot hermes wayland`. `is_live` and `pretty_name` updated to match.
37
+
38
+ ### Installer repairs (high -- announcement blockers)
39
+
40
+ - **Bug A: platform-config writes now respect `IJFW_CUSTOM_DIR`.** The 1.1.2 prep pass only guarded sibling links and bin wiring. The `~/.claude/settings.json`, `~/.codex/`, `~/.gemini/`, `~/.codeium/windsurf/` merges were still running during scratch installs and clobbering real user configs with scratch paths. Every platform case block now short-circuits early when `IJFW_CUSTOM_DIR=1`, prints a single "real platform config left untouched" line, and still classifies the platform as live/standby for the summary banner.
41
+ - **Bug B: Codex `hooks.json` schema migrated to the current nested format.** Codex CLI 0.120+ rejects both the legacy `{"hooks":[...]}` object-wrapper and the bare-array shape this release started with. Authoritative schema (per `codex-rs/hooks/src/engine/config.rs`): `{"hooks": {EventName: [MatcherGroup]}}` where each MatcherGroup is `{matcher?, hooks: [{type: "command", command, timeout?, ...}]}`. Installer writer now emits this shape, absorbs either legacy shape on read, drops the non-existent `AfterAgent` event, renames `script` to `command`, and adds the `"type": "command"` discriminator.
42
+ - **Bug C: `suppress_unstable_features_warning = true` is now written to `~/.codex/config.toml`.** Stops the "under-development features enabled: codex_hooks" banner on every Codex startup.
43
+ - **Self-loop guards now canonicalize `$HOME`.** On macOS `/var/folders` is a symlink to `/private/var/folders`; the `cd -P` used for `REPO_ROOT` resolved that, but `$HOME` did not, so the `PLUGIN_SRC == PLUGIN_DST` and `MCP_SRC == MCP_DST` comparisons missed the equal case and created recursive self-symlinks ("too many levels of symbolic links" on next access). Installer now computes `HOME_REAL="$(cd -P "$HOME" && pwd)"` once and uses it for all self-loop comparisons.
44
+ - **`C_RED` variable declared.** Previously only initialized on interactive TTYs; a failing post-install gate in a non-TTY context (CI, harness, `npx` capture) would crash the installer with `C_RED: unbound variable` under `set -u`. Declared in both branches of the color-init block.
45
+
46
+ ### Behavioral additions (synced across Claude / Codex / Gemini / Cursor / Windsurf / Copilot / Hermes / Wayland / universal where relevant)
47
+
48
+ - `ijfw-core` and platform rules-files: explicit banned openers ("Great question", "You're absolutely right", "Excellent idea", "I'd be happy to") and a sharpened two-strikes session-reset rule that asks the user to start a fresh session with a tighter prompt rather than burning context on a third failed attempt.
49
+ - `ijfw-debug`: new Step 6 templating the two-strikes reset with a memory-store call so lessons inherit forward without context noise.
50
+ - `ijfw-verify`: opens with "Plausibility is not correctness." Every claim must trace to a command output, test pass, or manual verification.
51
+ - `ijfw-workflow` Quick FRAME: five concrete goal-rewrite examples ("Add validation" -> "Write tests for invalid inputs..."). Vague asks must surface the gap rather than silently proceed.
52
+ - `ijfw-memory-audit`: pruning question added ("Would removing this rule cause the agent to make a mistake?") so memory stays sharp instead of bloated.
53
+ - `ijfw-critique`: refactor reframe ("Knowing everything I know now, what would the elegant solution look like?") for breaking frame on non-trivial decisions.
54
+
55
+ ### End-to-end smoke harness
56
+
57
+ - New `scripts/e2e-smoke.sh`. Two modes, both must pass:
58
+ 1. **Scratch-guard check** -- runs installer with `IJFW_CUSTOM_DIR=1` pointed at a throwaway dir, verifies zero drift across 10 real-home config paths (hashes before and after). Catches any future Bug A regression.
59
+ 2. **Canonical isolated-HOME install** -- runs installer with `HOME=$(mktemp -d)`, parses every platform's written config against its expected schema (Codex nested hooks, Gemini JSON, YAML for Hermes/Wayland, etc.), completes the MCP `initialize + tools/list` handshake, and fails loudly on any mismatch.
60
+ - 13 gates total. Harness must be green before any future `npm publish`.
61
+
62
+ ### Uninstaller
63
+
64
+ - `installer/src/uninstall.js:removeCodexHooks` now handles all three hook-file shapes we have ever shipped (bare array, legacy `{hooks:[...]}` object-wrapper, current nested-map). Uninstall works regardless of which version the user last installed.
65
+ - New `removeYamlMcpEntry` helper (python3+PyYAML preferred, regex fallback). Cleans `~/.hermes/config.yaml` and `~/.wayland/config.yaml`, removes skill dirs and context files for both new platforms.
66
+ - `cleanPlatforms()` comment updated: "all 8 platforms".
67
+
68
+ ### Installer scope-leak fixes (carried from 1.1.2 prep)
69
+
70
+ - `scripts/install.sh` now respects `IJFW_CUSTOM_DIR` from `install.js`. Custom-dir installs (`--dir <scratch>`) skip user-home mutations: no sibling links into `~/.ijfw/`, no bin symlinks into `~/.local/bin/`, no `.mcp.json` patching of the real plugin, no `~/.claude/plugins/cache/ijfw` invalidation. Default canonical install behavior unchanged.
71
+ - Self-loop guard: when `PLUGIN_SRC == PLUGIN_DST` (install dir is the canonical home and source happens to live there), the symlink step is skipped instead of creating a recursive `~/.ijfw/claude -> ~/.ijfw/claude` loop.
72
+ - `installer/src/uninstall.js`: `uninstall --dir <scratch>` now leaves `~/.codex/`, `~/.gemini/`, `~/.codeium/windsurf/` configs and skill dirs alone. Only canonical uninstalls (`~/.ijfw`) clean platform configs.
73
+
74
+ ### Dynamic version strings
75
+
76
+ - `mcp-server/src/server.js` and `mcp-server/src/dashboard-server.js` now read version from `mcp-server/package.json` at module load instead of hardcoding. MCP `serverInfo` and `/api/health` always match the shipped version.
77
+
78
+ ### Internal
79
+
80
+ - `mcp-server/package.json` bumped from 1.1.0 to 1.1.2 (was lagging two minor cycles).
81
+ - `.gitattributes`: added LF normalization rules (carried from 1.1.1).
82
+ - Banner on successful install now says "8 platforms" instead of "7".
83
+
3
84
  ## [1.1.1] -- 2026-04-19
4
85
 
5
86
  Docs and discoverability.
package/dist/install.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/install.js
4
4
  import { spawnSync } from "node:child_process";
5
5
  import { existsSync as existsSync2, rmSync, mkdirSync as mkdirSync2, realpathSync, renameSync as renameSync2 } from "node:fs";
6
- import { resolve, join as join2 } from "node:path";
6
+ import { resolve, join as join2, dirname as dirname2 } from "node:path";
7
7
  import { homedir as homedir2, platform } from "node:os";
8
8
  import { fileURLToPath } from "node:url";
9
9
 
@@ -151,10 +151,20 @@ function preflight() {
151
151
  const issues = [];
152
152
  const [major] = process.versions.node.split(".").map(Number);
153
153
  if (major < 18) issues.push(`IJFW needs Node >=18 -- current: ${process.versions.node}. Upgrade Node, then retry.`);
154
- if (!hasBin("git")) issues.push("IJFW needs git on PATH -- install git (https://git-scm.com), then retry.");
155
- if (!hasBin("bash")) {
154
+ if (!hasBin("git")) {
156
155
  if (platform() === "win32") {
157
- issues.push("IJFW on Windows needs Git Bash. Install Git for Windows (https://git-scm.com/download/win), or run the PowerShell installer: irm https://raw.githubusercontent.com/TheRealSeanDonahoe/ijfw/main/installer/src/install.ps1 | iex");
156
+ issues.push(
157
+ "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"
158
+ );
159
+ } else {
160
+ issues.push("IJFW needs git on PATH -- install git (https://git-scm.com), then retry.");
161
+ }
162
+ }
163
+ if (!findBash()) {
164
+ if (platform() === "win32") {
165
+ issues.push(
166
+ "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"
167
+ );
158
168
  } else {
159
169
  issues.push("IJFW needs bash on PATH -- install bash, then retry.");
160
170
  }
@@ -165,6 +175,32 @@ function hasBin(bin) {
165
175
  const res = spawnSync(bin, ["--version"], { stdio: "ignore" });
166
176
  return res.status === 0 || res.status === null ? res.error ? false : true : false;
167
177
  }
178
+ function findBash() {
179
+ if (hasBin("bash") && platform() !== "win32") return "bash";
180
+ if (platform() !== "win32") return hasBin("bash") ? "bash" : null;
181
+ const whereGit = spawnSync("where", ["git"], { encoding: "utf8" });
182
+ if (whereGit.status === 0) {
183
+ const gitPath = (whereGit.stdout || "").split(/\r?\n/)[0].trim();
184
+ if (gitPath && existsSync2(gitPath)) {
185
+ const gitDir = dirname2(gitPath);
186
+ const gitRoot = dirname2(gitDir);
187
+ const candidates = [
188
+ join2(gitDir, "bash.exe"),
189
+ join2(gitRoot, "bin", "bash.exe"),
190
+ join2(gitRoot, "usr", "bin", "bash.exe")
191
+ ];
192
+ for (const c of candidates) if (existsSync2(c)) return c;
193
+ }
194
+ }
195
+ for (const c of [
196
+ "C:\\Program Files\\Git\\bin\\bash.exe",
197
+ "C:\\Program Files\\Git\\usr\\bin\\bash.exe",
198
+ "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
199
+ "C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe"
200
+ ]) if (existsSync2(c)) return c;
201
+ if (hasBin("bash")) return "bash";
202
+ return null;
203
+ }
168
204
  function resolveTarget(opt) {
169
205
  if (opt.dir) return resolve(opt.dir);
170
206
  if (process.env.IJFW_HOME) return resolve(process.env.IJFW_HOME);
@@ -216,8 +252,19 @@ function cloneOrPull(dir, branch) {
216
252
  function runInstallScript(dir) {
217
253
  const script = join2(dir, "scripts", "install.sh");
218
254
  if (!existsSync2(script)) throw new Error(`IJFW install script not found at ${script} -- re-run the installer to restore it.`);
219
- const env = { ...process.env, IJFW_NONINTERACTIVE: process.env.CI ? "1" : process.env.IJFW_NONINTERACTIVE ?? "" };
220
- const r = spawnSync("bash", ["scripts/install.sh"], { cwd: dir, stdio: "inherit", env });
255
+ const canonicalDir = join2(homedir2(), ".ijfw");
256
+ const isCustomDir = resolve(dir) !== canonicalDir ? "1" : "0";
257
+ const env = {
258
+ ...process.env,
259
+ IJFW_NONINTERACTIVE: process.env.CI ? "1" : process.env.IJFW_NONINTERACTIVE ?? "",
260
+ IJFW_HOME: dir,
261
+ IJFW_CUSTOM_DIR: isCustomDir
262
+ };
263
+ const bashExe = findBash();
264
+ if (!bashExe) {
265
+ throw new Error("IJFW could not locate bash (preflight should have caught this -- file an issue).");
266
+ }
267
+ const r = spawnSync(bashExe, ["scripts/install.sh"], { cwd: dir, stdio: "inherit", env });
221
268
  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.`);
222
269
  }
223
270
  async function main() {
@@ -253,7 +300,7 @@ async function main() {
253
300
  console.log(` marketplace registered in ${settingsPath}`);
254
301
  }
255
302
  console.log("");
256
- console.log("IJFW now active across 7 platforms -- one memory layer, all your AIs, zero config.");
303
+ console.log("IJFW now active across 8 platforms -- one memory layer, all your AIs, zero config.");
257
304
  console.log(" Run `ijfw demo` to see the Trident in action.");
258
305
  console.log(" Run `ijfw doctor` to confirm which auditors are reachable.");
259
306
  console.log(" Privacy: everything stays local. See NO_TELEMETRY.md.");
@@ -275,5 +322,6 @@ if (isDirectRun()) {
275
322
  });
276
323
  }
277
324
  export {
325
+ findBash,
278
326
  resolveBranchOrTag
279
327
  };
package/dist/uninstall.js CHANGED
@@ -4,6 +4,7 @@
4
4
  import { existsSync as existsSync2, rmSync, cpSync, mkdtempSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2, readdirSync } from "node:fs";
5
5
  import { resolve, join as join2 } from "node:path";
6
6
  import { homedir as homedir2, tmpdir } from "node:os";
7
+ import { spawnSync } from "node:child_process";
7
8
 
8
9
  // src/marketplace.js
9
10
  import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync } from "node:fs";
@@ -156,12 +157,74 @@ function removeCodexHooks(p) {
156
157
  } catch {
157
158
  return false;
158
159
  }
159
- if (!Array.isArray(doc.hooks)) return false;
160
- const before = doc.hooks.length;
161
- doc.hooks = doc.hooks.filter((h) => !h._ijfw);
162
- if (doc.hooks.length === before) return false;
160
+ if (Array.isArray(doc)) {
161
+ const before = doc.length;
162
+ const after = doc.filter((h) => !(h && h._ijfw));
163
+ if (after.length === before) return false;
164
+ backupFile(p);
165
+ writeFileSync2(p, JSON.stringify(after, null, 2) + "\n");
166
+ return true;
167
+ }
168
+ if (!doc || typeof doc !== "object" || !doc.hooks) return false;
169
+ if (doc.hooks && typeof doc.hooks === "object" && !Array.isArray(doc.hooks)) {
170
+ let changed = false;
171
+ for (const ev of Object.keys(doc.hooks)) {
172
+ const groups = doc.hooks[ev];
173
+ if (!Array.isArray(groups)) continue;
174
+ const before = groups.length;
175
+ doc.hooks[ev] = groups.filter((g) => {
176
+ if (!g || !Array.isArray(g.hooks)) return true;
177
+ return !g.hooks.some((h) => h && h._ijfw);
178
+ });
179
+ if (doc.hooks[ev].length !== before) changed = true;
180
+ }
181
+ if (!changed) return false;
182
+ backupFile(p);
183
+ writeFileSync2(p, JSON.stringify(doc, null, 2) + "\n");
184
+ return true;
185
+ }
186
+ if (Array.isArray(doc.hooks)) {
187
+ const before = doc.hooks.length;
188
+ doc.hooks = doc.hooks.filter((h) => !(h && h._ijfw));
189
+ if (doc.hooks.length === before) return false;
190
+ backupFile(p);
191
+ writeFileSync2(p, JSON.stringify(doc, null, 2) + "\n");
192
+ return true;
193
+ }
194
+ return false;
195
+ }
196
+ function removeYamlMcpEntry(p) {
197
+ if (!existsSync2(p)) return false;
198
+ const raw = readFileSync2(p, "utf8");
199
+ if (!/\bijfw-memory\b/.test(raw)) return false;
200
+ const py = spawnSync("python3", ["-c", `
201
+ import sys, yaml
202
+ p = sys.argv[1]
203
+ with open(p) as f: raw = f.read()
204
+ doc = yaml.safe_load(raw) if raw.strip() else {}
205
+ if not isinstance(doc, dict): sys.exit(2)
206
+ srv = doc.get("mcp_servers")
207
+ if not isinstance(srv, dict) or "ijfw-memory" not in srv: sys.exit(3)
208
+ del srv["ijfw-memory"]
209
+ if not srv: del doc["mcp_servers"]
210
+ with open(p + ".tmp", "w") as f:
211
+ yaml.safe_dump(doc, f, sort_keys=False, default_flow_style=False)
212
+ import os; os.replace(p + ".tmp", p)
213
+ `, p], { encoding: "utf8" });
214
+ if (py.status === 0) {
215
+ backupFile(p);
216
+ return true;
217
+ }
218
+ const stripped = raw.replace(
219
+ /^ ijfw-memory:\n(?: .*\n)*(?:# IJFW-MCP-END ijfw-memory\n)?/m,
220
+ ""
221
+ ).replace(
222
+ /# IJFW-MCP-BEGIN ijfw-memory\n(?:.*\n)*?# IJFW-MCP-END ijfw-memory\n/,
223
+ ""
224
+ );
225
+ if (stripped === raw) return false;
163
226
  backupFile(p);
164
- writeFileSync2(p, JSON.stringify(doc, null, 2) + "\n");
227
+ writeFileSync2(p, stripped);
165
228
  return true;
166
229
  }
167
230
  function removeIjfwSkills(dir) {
@@ -205,6 +268,26 @@ function cleanPlatforms() {
205
268
  }
206
269
  const vscodeMcp = join2(".vscode", "mcp.json");
207
270
  if (removeJsonMcpEntry(vscodeMcp)) removed.push(".vscode/mcp.json (removed ijfw-memory)");
271
+ if (removeYamlMcpEntry(join2(HOME, ".hermes", "config.yaml"))) {
272
+ removed.push("~/.hermes/config.yaml (removed ijfw-memory)");
273
+ }
274
+ const hermesSkills = removeIjfwSkills(join2(HOME, ".hermes", "skills"));
275
+ if (hermesSkills > 0) removed.push(`~/.hermes/skills/ijfw-* (removed ${hermesSkills} skill dirs)`);
276
+ const hermesMd = join2(HOME, ".hermes", "HERMES.md");
277
+ if (existsSync2(hermesMd)) {
278
+ rmSync(hermesMd, { force: true });
279
+ removed.push("~/.hermes/HERMES.md");
280
+ }
281
+ if (removeYamlMcpEntry(join2(HOME, ".wayland", "config.yaml"))) {
282
+ removed.push("~/.wayland/config.yaml (removed ijfw-memory)");
283
+ }
284
+ const waylandSkills = removeIjfwSkills(join2(HOME, ".wayland", "skills"));
285
+ if (waylandSkills > 0) removed.push(`~/.wayland/skills/ijfw-* (removed ${waylandSkills} skill dirs)`);
286
+ const waylandMd = join2(HOME, ".wayland", "WAYLAND.md");
287
+ if (existsSync2(waylandMd)) {
288
+ rmSync(waylandMd, { force: true });
289
+ removed.push("~/.wayland/WAYLAND.md");
290
+ }
208
291
  return removed;
209
292
  }
210
293
  function resolveTarget(opt) {
@@ -238,17 +321,23 @@ async function main() {
238
321
  console.log(" memory/ was not present; nothing to preserve");
239
322
  }
240
323
  }
241
- if (!opts.noMarketplace) {
324
+ const canonicalDir = join2(HOME, ".ijfw");
325
+ const isCanonical = target === canonicalDir;
326
+ if (isCanonical && !opts.noMarketplace) {
242
327
  const settingsPath = claudeSettingsPath();
243
328
  if (existsSync2(settingsPath)) {
244
329
  unmergeMarketplace(settingsPath);
245
330
  console.log(` marketplace removed from ${settingsPath}`);
246
331
  }
247
332
  }
248
- const cleaned = cleanPlatforms();
249
- if (cleaned.length > 0) {
250
- console.log(" platform configs cleaned:");
251
- for (const line of cleaned) console.log(` ${line}`);
333
+ if (isCanonical) {
334
+ const cleaned = cleanPlatforms();
335
+ if (cleaned.length > 0) {
336
+ console.log(" platform configs cleaned:");
337
+ for (const line of cleaned) console.log(` ${line}`);
338
+ }
339
+ } else {
340
+ console.log(` custom-dir uninstall (${target}) -- platform configs in your real home left untouched.`);
252
341
  }
253
342
  console.log("\nIJFW uninstalled. Thanks for trying it.");
254
343
  process.exit(0);
package/docs/GUIDE.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://github.com/TheRealSeanDonahoe/ijfw/releases/download/v1.1.0/ijfw-hero.png" alt="IJFW" width="100%"/>
2
+ <img src="https://github.com/TheRealSeanDonahoe/ijfw/releases/download/v1.1.1/ijfw-hero.png" alt="IJFW" width="100%"/>
3
3
  </p>
4
4
 
5
5
  # The IJFW Guide
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ijfw/install",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
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": {
@@ -18,7 +18,7 @@
18
18
  "LICENSE"
19
19
  ],
20
20
  "scripts": {
21
- "build": "rm -rf docs && mkdir -p docs/guide && cp ../docs/GUIDE.md docs/GUIDE.md && cp -r ../docs/guide/assets docs/guide/assets && esbuild src/install.js src/uninstall.js src/ijfw.js --bundle --platform=node --target=node18 --outdir=dist --format=esm --banner:js='#!/usr/bin/env node' && chmod +x dist/install.js dist/uninstall.js dist/ijfw.js",
21
+ "build": "node scripts/build.js",
22
22
  "test": "node --test test.js",
23
23
  "preflight": "node dist/ijfw.js preflight",
24
24
  "pack:check": "npm pack --dry-run",