@ijfw/install 1.3.1 → 1.4.0

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 {
@@ -385,7 +389,7 @@ function mergeYamlPluginsEnabled(dst, pluginName, ts) {
385
389
  if (inPluginsBlock) {
386
390
  if (/^\S/.test(line) && line.trim() !== "") {
387
391
  inPluginsBlock = false;
388
- } else if (/^\s+enabled:\s*(\[\s*\])?\s*$/.test(line) || /^\s+enabled:\s*\[.*\]\s*$/.test(line)) {
392
+ } else if (isIndentedEnabledLine(line)) {
389
393
  enabledLineIdx = i;
390
394
  } else if (enabledLineIdx >= 0 && itemRe.test(line)) {
391
395
  alreadyListed = true;
@@ -423,6 +427,11 @@ function mergeYamlPluginsEnabled(dst, pluginName, ts) {
423
427
  outText += "# IJFW-PLUGINS-END\n";
424
428
  writeAtomic(dst, outText, { mode: 384 });
425
429
  }
430
+ function isIndentedEnabledLine(line) {
431
+ if (!line || !/\s/.test(line[0])) return false;
432
+ const trimmed = line.trim();
433
+ return trimmed === "enabled:" || trimmed === "enabled: []" || trimmed.startsWith("enabled: [") && trimmed.endsWith("]");
434
+ }
426
435
  function opencodeMerge(dst, serverJs, ts) {
427
436
  mkdirSync2(dirname3(dst), { recursive: true });
428
437
  if (ts) backup(dst, ts);
@@ -493,10 +502,22 @@ function clineMerge(serverJs, home, ts) {
493
502
  writeAtomic(dst, JSON.stringify(doc, null, 2), { mode: 384 });
494
503
  return dst;
495
504
  }
496
- var IS_WIN;
505
+ var IS_WIN, EXTENSION_PLATFORM_SKILL_DIRS;
497
506
  var init_install_helpers = __esm({
498
507
  "src/install-helpers.js"() {
499
508
  IS_WIN = process.platform === "win32";
509
+ EXTENSION_PLATFORM_SKILL_DIRS = Object.freeze([
510
+ { id: "claude", rel: "claude/skills" },
511
+ { id: "codex", rel: "codex/skills" },
512
+ { id: "gemini", rel: "gemini/extensions/ijfw/skills" },
513
+ { id: "cursor", rel: "cursor/skills" },
514
+ { id: "windsurf", rel: "windsurf/skills" },
515
+ { id: "copilot", rel: "copilot/skills" },
516
+ { id: "hermes", rel: "hermes/skills" },
517
+ { id: "wayland", rel: "wayland/skills" },
518
+ { id: "shared", rel: "shared/skills" },
519
+ { id: "universal", rel: "universal/skills" }
520
+ ]);
500
521
  }
501
522
  });
502
523
 
@@ -759,6 +780,12 @@ async function installCodex(ctx) {
759
780
  for (const sd of listSubdirs(repoSkills)) {
760
781
  copyDirIfAbsent(sd.path, join4(userSkills, sd.name));
761
782
  }
783
+ const userCommands = join4(ctx.home, ".codex", "commands");
784
+ ensureDir(userCommands);
785
+ const repoCommands = join4(ctx.repoRoot, "codex", "commands");
786
+ for (const f of listFiles(repoCommands, ".md")) {
787
+ copyIfAbsent(f.path, join4(userCommands, f.name));
788
+ }
762
789
  const cwd = ctx.cwd || process.cwd();
763
790
  if (existsSync4(join4(cwd, ".codex", "config.toml")) || existsSync4(join4(cwd, ".ijfw"))) {
764
791
  const projSkills = join4(cwd, ".codex", "skills");
@@ -766,8 +793,13 @@ async function installCodex(ctx) {
766
793
  for (const sd of listSubdirs(repoSkills)) {
767
794
  copyDirIfAbsent(sd.path, join4(projSkills, sd.name));
768
795
  }
796
+ const projCommands = join4(cwd, ".codex", "commands");
797
+ ensureDir(projCommands);
798
+ for (const f of listFiles(repoCommands, ".md")) {
799
+ copyIfAbsent(f.path, join4(projCommands, f.name));
800
+ }
769
801
  }
770
- ctx.log.ok("Installed Codex bundle: MCP + hooks + 19 skills + context");
802
+ ctx.log.ok("Installed Codex bundle: MCP + hooks + 19 skills + 22 command aliases + context");
771
803
  return { status: "ok" };
772
804
  }
773
805
  async function installGemini(ctx) {
@@ -1765,7 +1797,7 @@ var init_install_flow = __esm({
1765
1797
  // src/install.js
1766
1798
  import { spawnSync as spawnSync2 } from "node:child_process";
1767
1799
  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";
1800
+ import { resolve as resolve4, join as join5, dirname as dirname5 } from "node:path";
1769
1801
  import { homedir as homedir3, platform as platform2 } from "node:os";
1770
1802
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1771
1803
 
@@ -2026,8 +2058,8 @@ function findBash() {
2026
2058
  return null;
2027
2059
  }
2028
2060
  function resolveTarget(opt) {
2029
- if (opt.dir) return resolve3(opt.dir);
2030
- if (process.env.IJFW_HOME) return resolve3(process.env.IJFW_HOME);
2061
+ if (opt.dir) return resolve4(opt.dir);
2062
+ if (process.env.IJFW_HOME) return resolve4(process.env.IJFW_HOME);
2031
2063
  return join5(homedir3(), ".ijfw");
2032
2064
  }
2033
2065
  function runCheck(cmd, args, opts) {
@@ -2091,7 +2123,7 @@ function cloneOrPull(dir, branch) {
2091
2123
  }
2092
2124
  async function runInstallScript(dir) {
2093
2125
  const canonicalDir = join5(homedir3(), ".ijfw");
2094
- const isCustomDir = resolve3(dir) !== canonicalDir;
2126
+ const isCustomDir = resolve4(dir) !== canonicalDir;
2095
2127
  const { runInstall: runInstall2 } = await Promise.resolve().then(() => (init_install_flow(), install_flow_exports));
2096
2128
  await runInstall2({
2097
2129
  targets: void 0,
package/dist/uninstall.js CHANGED
@@ -241,9 +241,11 @@ import os; os.replace(p + ".tmp", p)
241
241
  return true;
242
242
  }
243
243
  const stripped = raw.replace(
244
+ // eslint-disable-next-line security/detect-unsafe-regex -- raw is a small local YAML config file; pattern is line-anchored to the IJFW-owned block.
244
245
  /^ ijfw-memory:\n(?: .*\n)*(?:# IJFW-MCP-END ijfw-memory\n)?/m,
245
246
  ""
246
247
  ).replace(
248
+ // eslint-disable-next-line security/detect-unsafe-regex -- raw is a small local YAML config file; pattern is bounded by exact IJFW sentinel markers.
247
249
  /# IJFW-MCP-BEGIN ijfw-memory\n(?:.*\n)*?# IJFW-MCP-END ijfw-memory\n/,
248
250
  ""
249
251
  );
@@ -263,6 +265,42 @@ function removeIjfwSkills(dir) {
263
265
  }
264
266
  return count;
265
267
  }
268
+ var CODEX_COMMAND_FILES = [
269
+ "compress.md",
270
+ "consolidate.md",
271
+ "cross-audit.md",
272
+ "cross-critique.md",
273
+ "cross-research.md",
274
+ "doctor.md",
275
+ "handoff.md",
276
+ "ijfw-audit.md",
277
+ "ijfw-execute.md",
278
+ "ijfw-help.md",
279
+ "ijfw-plan.md",
280
+ "ijfw-ship.md",
281
+ "ijfw-verify.md",
282
+ "ijfw.md",
283
+ "memory-audit.md",
284
+ "memory-consent.md",
285
+ "memory-why.md",
286
+ "metrics.md",
287
+ "mode.md",
288
+ "status.md",
289
+ "team.md",
290
+ "workflow.md"
291
+ ];
292
+ function removeCodexCommands(dir) {
293
+ if (!existsSync2(dir)) return 0;
294
+ let count = 0;
295
+ for (const name of CODEX_COMMAND_FILES) {
296
+ const path = join2(dir, name);
297
+ if (existsSync2(path)) {
298
+ rmSync(path, { force: true });
299
+ count++;
300
+ }
301
+ }
302
+ return count;
303
+ }
266
304
  function cleanPlatforms() {
267
305
  const removed = [];
268
306
  if (removeTomlSection(join2(HOME, ".codex", "config.toml"))) {
@@ -273,6 +311,8 @@ function cleanPlatforms() {
273
311
  }
274
312
  const codexSkills = removeIjfwSkills(join2(HOME, ".codex", "skills"));
275
313
  if (codexSkills > 0) removed.push(`~/.codex/skills/ijfw-* (removed ${codexSkills} skill dirs)`);
314
+ const codexCommands = removeCodexCommands(join2(HOME, ".codex", "commands"));
315
+ if (codexCommands > 0) removed.push(`~/.codex/commands (removed ${codexCommands} IJFW command aliases)`);
276
316
  const codexMd = join2(HOME, ".codex", "IJFW.md");
277
317
  if (existsSync2(codexMd)) {
278
318
  rmSync(codexMd, { force: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ijfw/install",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
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": {