@ijfw/install 1.5.6 → 1.6.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/ijfw.js CHANGED
@@ -389,7 +389,7 @@ __export(psscriptanalyzer_exports, {
389
389
  severity: () => severity4
390
390
  });
391
391
  import { spawnSync as spawnSync4 } from "node:child_process";
392
- import { readFileSync, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
392
+ import { readFileSync, readdirSync as readdirSync2, statSync as statSync2, existsSync } from "node:fs";
393
393
  import { join as join3 } from "node:path";
394
394
  function findPs1Files(dir, acc = []) {
395
395
  let entries;
@@ -539,11 +539,13 @@ async function run4(ctx) {
539
539
  if (moduleCheck.status !== 0) {
540
540
  return runFallback(files, t0, "PSScriptAnalyzer module unavailable");
541
541
  }
542
+ const psSettings = join3(ctx.repoRoot, "PSScriptAnalyzerSettings.psd1");
543
+ const invoke = existsSync(psSettings) ? `Invoke-ScriptAnalyzer -Path $f -Settings '${psSettings.replace(/'/g, "''")}' -ErrorAction SilentlyContinue` : "Invoke-ScriptAnalyzer -Path $f -Severity Warning -ErrorAction SilentlyContinue";
542
544
  const script = `
543
545
  $files = @(${files.map((f) => `'${f.replace(/'/g, "''")}'`).join(",")})
544
546
  $found = $false
545
547
  foreach ($f in $files) {
546
- $results = Invoke-ScriptAnalyzer -Path $f -Severity Warning -ErrorAction SilentlyContinue
548
+ $results = ${invoke}
547
549
  if ($results) {
548
550
  $found = $true
549
551
  foreach ($r in $results) {
@@ -861,14 +863,14 @@ var init_gate_result_schema = __esm({
861
863
  });
862
864
 
863
865
  // ../mcp-server/src/scan-resume.js
864
- import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync, unlinkSync, copyFileSync } from "fs";
866
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync, unlinkSync, copyFileSync } from "fs";
865
867
  import { join as join4 } from "path";
866
868
  function statePath(projectRoot) {
867
869
  return join4(String(projectRoot), ".ijfw", STATE_FILE);
868
870
  }
869
871
  function loadScanState(projectRoot) {
870
872
  const path = statePath(projectRoot);
871
- if (!existsSync(path)) return null;
873
+ if (!existsSync2(path)) return null;
872
874
  try {
873
875
  const raw = readFileSync2(path, "utf8");
874
876
  const parsed = JSON.parse(raw);
@@ -879,7 +881,7 @@ function loadScanState(projectRoot) {
879
881
  }
880
882
  function writeScanState(projectRoot, state) {
881
883
  const dir = join4(String(projectRoot), ".ijfw");
882
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
884
+ if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
883
885
  const finalPath = statePath(projectRoot);
884
886
  const tmpPath = `${finalPath}.tmp.${process.pid}.${Date.now()}`;
885
887
  const safe = {
@@ -925,7 +927,7 @@ function isPidAlive(pid) {
925
927
  }
926
928
  }
927
929
  function reclaimIfStale(lp) {
928
- if (!existsSync(lp)) return;
930
+ if (!existsSync2(lp)) return;
929
931
  let raw;
930
932
  try {
931
933
  raw = readFileSync2(lp, "utf8");
@@ -944,7 +946,7 @@ function reclaimIfStale(lp) {
944
946
  }
945
947
  function acquireScanLock(projectRoot) {
946
948
  const dir = join4(String(projectRoot), ".ijfw");
947
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
949
+ if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
948
950
  const lp = lockPath(projectRoot);
949
951
  reclaimIfStale(lp);
950
952
  const payload = String(process.pid) + "\n" + String(Date.now()) + "\n";
@@ -980,7 +982,7 @@ function shouldResume(state) {
980
982
  }
981
983
  function clearScanState(projectRoot) {
982
984
  const path = statePath(projectRoot);
983
- if (existsSync(path)) {
985
+ if (existsSync2(path)) {
984
986
  try {
985
987
  unlinkSync(path);
986
988
  } catch {
@@ -1002,7 +1004,7 @@ var init_scan_resume = __esm({
1002
1004
  import {
1003
1005
  readFileSync as readFileSync3,
1004
1006
  writeFileSync as writeFileSync3,
1005
- existsSync as existsSync2,
1007
+ existsSync as existsSync3,
1006
1008
  readdirSync as readdirSync3,
1007
1009
  statSync as statSync3,
1008
1010
  renameSync as renameSync2,
@@ -1154,7 +1156,7 @@ function finalize({ primary, secondary, score, signals, scanIncomplete, fallback
1154
1156
  return out;
1155
1157
  }
1156
1158
  function readFrontmatterType(path) {
1157
- if (!existsSync2(path)) return null;
1159
+ if (!existsSync3(path)) return null;
1158
1160
  let src;
1159
1161
  try {
1160
1162
  src = readFileSync3(path, "utf8");
@@ -1406,7 +1408,7 @@ function fileTreeHash(paths) {
1406
1408
  function branchHash(root) {
1407
1409
  try {
1408
1410
  const dotGit = join5(root, ".git");
1409
- if (!existsSync2(dotGit)) return "";
1411
+ if (!existsSync3(dotGit)) return "";
1410
1412
  let headPath = null;
1411
1413
  let st;
1412
1414
  try {
@@ -1426,7 +1428,7 @@ function branchHash(root) {
1426
1428
  } else {
1427
1429
  return "";
1428
1430
  }
1429
- if (!headPath || !existsSync2(headPath)) return "";
1431
+ if (!headPath || !existsSync3(headPath)) return "";
1430
1432
  const head = readFileSync3(headPath, "utf8").trim();
1431
1433
  const m2 = head.match(/^ref:\s*(.+)$/);
1432
1434
  const branch = m2 ? m2[1] : head;
@@ -1440,7 +1442,7 @@ function isC9AvailableSync() {
1440
1442
  try {
1441
1443
  const here = fileURLToPath(import.meta.url);
1442
1444
  const fts5Path = join5(dirname(here), "compute", "fts5.js");
1443
- _c9AvailableCache = existsSync2(fts5Path);
1445
+ _c9AvailableCache = existsSync3(fts5Path);
1444
1446
  } catch {
1445
1447
  _c9AvailableCache = false;
1446
1448
  }
@@ -1775,30 +1777,32 @@ async function run7(ctx) {
1775
1777
  timeout: 6e4
1776
1778
  }
1777
1779
  );
1778
- const output = (res.stdout || "") + (res.stderr || "");
1779
- const report = parseAuditReport(output);
1780
+ const report = parseAuditReport(res.stdout || "");
1780
1781
  const highCritical = highCriticalCount(report);
1781
- return { dir, status: res.status, output, report, highCritical };
1782
+ return { dir, status: res.status, output: (res.stdout || "") + (res.stderr || ""), report, highCritical };
1782
1783
  });
1783
1784
  const durationMs = Date.now() - t0;
1784
- const failed = runs.filter((r) => !r.report || r.highCritical > 0);
1785
- const status = failed.length === 0 ? "PASS" : "FAIL";
1786
- const message = status === "PASS" ? "audit-ci: no high/critical vulnerabilities in installer or mcp-server" : "audit-ci: high or critical vulnerabilities found";
1785
+ const realVulns = runs.filter((r) => r.report && r.highCritical > 0);
1786
+ const unparseable = runs.filter((r) => !r.report);
1787
+ const status = realVulns.length > 0 ? "FAIL" : unparseable.length > 0 ? "WARN" : "PASS";
1788
+ const message = status === "FAIL" ? "audit-ci: high or critical vulnerabilities found" : status === "WARN" ? "audit-ci: audit could not be completed for some package(s); no high/critical in the rest" : "audit-ci: no high/critical vulnerabilities in installer or mcp-server";
1787
1789
  let details;
1788
1790
  if (status === "PASS") {
1789
1791
  details = runs.map((r) => `${r.dir}: pass`);
1790
- } else {
1792
+ } else if (status === "FAIL") {
1791
1793
  const lines = [];
1792
- for (const r of failed) {
1793
- if (!r.report) {
1794
- lines.push(`${r.dir}: audit report unavailable`);
1795
- lines.push(...r.output.split("\n").filter(Boolean).slice(0, 10));
1796
- continue;
1797
- }
1794
+ for (const r of realVulns) {
1798
1795
  lines.push(`${r.dir}: ${r.highCritical} high/critical advisory item(s)`);
1799
1796
  lines.push(...vulnerableNames(r.report).slice(0, 10));
1800
1797
  }
1801
1798
  details = lines.slice(0, 20);
1799
+ } else {
1800
+ const lines = [];
1801
+ for (const r of unparseable) {
1802
+ lines.push(`${r.dir}: audit report unavailable (non-blocking)`);
1803
+ lines.push(...r.output.split("\n").filter(Boolean).slice(0, 6));
1804
+ }
1805
+ details = lines.slice(0, 20);
1802
1806
  }
1803
1807
  try {
1804
1808
  const block = await emitGateResult(
@@ -1952,7 +1956,7 @@ __export(pack_smoke_exports, {
1952
1956
  severity: () => severity10
1953
1957
  });
1954
1958
  import { spawnSync as spawnSync10 } from "node:child_process";
1955
- import { mkdtempSync as mkdtempSync2, rmSync as rmSync2, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readdirSync as readdirSync4, existsSync as existsSync3 } from "node:fs";
1959
+ import { mkdtempSync as mkdtempSync2, rmSync as rmSync2, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readdirSync as readdirSync4, existsSync as existsSync4 } from "node:fs";
1956
1960
  import { join as join9, resolve } from "node:path";
1957
1961
  import { tmpdir as tmpdir2 } from "node:os";
1958
1962
  async function run10(ctx) {
@@ -2034,7 +2038,7 @@ async function run10(ctx) {
2034
2038
  ];
2035
2039
  let binPath = null;
2036
2040
  for (const c2 of binCandidates) {
2037
- if (existsSync3(c2)) {
2041
+ if (existsSync4(c2)) {
2038
2042
  binPath = c2;
2039
2043
  break;
2040
2044
  }
@@ -2110,7 +2114,7 @@ __export(upgrade_smoke_exports, {
2110
2114
  severity: () => severity11
2111
2115
  });
2112
2116
  import { spawnSync as spawnSync11 } from "node:child_process";
2113
- import { mkdtempSync as mkdtempSync3, rmSync as rmSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5, readFileSync as readFileSync4, existsSync as existsSync4, cpSync } from "node:fs";
2117
+ import { mkdtempSync as mkdtempSync3, rmSync as rmSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5, readFileSync as readFileSync4, existsSync as existsSync5, cpSync } from "node:fs";
2114
2118
  import { join as join10, resolve as resolve2 } from "node:path";
2115
2119
  import { tmpdir as tmpdir3 } from "node:os";
2116
2120
  async function run11(ctx) {
@@ -2185,7 +2189,7 @@ async function run11(ctx) {
2185
2189
  ];
2186
2190
  let installerBin = null;
2187
2191
  for (const c2 of binCandidates) {
2188
- if (existsSync4(c2)) {
2192
+ if (existsSync5(c2)) {
2189
2193
  installerBin = c2;
2190
2194
  break;
2191
2195
  }
@@ -2203,12 +2207,12 @@ async function run11(ctx) {
2203
2207
  mkdirSync4(targetIjfwHome, { recursive: true });
2204
2208
  for (const sub of ["claude", "mcp-server"]) {
2205
2209
  const src = join10(ctx.repoRoot, sub);
2206
- if (existsSync4(src)) {
2210
+ if (existsSync5(src)) {
2207
2211
  cpSync(src, join10(targetIjfwHome, sub), { recursive: true });
2208
2212
  }
2209
2213
  }
2210
2214
  const installerPkgSrc = join10(ctx.repoRoot, "installer", "package.json");
2211
- if (existsSync4(installerPkgSrc)) {
2215
+ if (existsSync5(installerPkgSrc)) {
2212
2216
  mkdirSync4(join10(targetIjfwHome, "installer"), { recursive: true });
2213
2217
  cpSync(installerPkgSrc, join10(targetIjfwHome, "installer", "package.json"));
2214
2218
  }
@@ -2252,7 +2256,7 @@ async function run11(ctx) {
2252
2256
  };
2253
2257
  }
2254
2258
  const settingsPath = join10(claudeDir, "settings.json");
2255
- if (!existsSync4(settingsPath)) {
2259
+ if (!existsSync5(settingsPath)) {
2256
2260
  return {
2257
2261
  name: "upgrade-smoke",
2258
2262
  status: "FAIL",
@@ -2289,7 +2293,7 @@ async function run11(ctx) {
2289
2293
  };
2290
2294
  }
2291
2295
  const marketplaceSrc = join10(installerDir, "src", "marketplace.js");
2292
- if (existsSync4(marketplaceSrc)) {
2296
+ if (existsSync5(marketplaceSrc)) {
2293
2297
  const src = readFileSync4(marketplaceSrc, "utf8");
2294
2298
  const registersCorrectKey = src.includes("'ijfw@ijfw'") || src.includes('"ijfw@ijfw"');
2295
2299
  const registersWrongKey = /enabledPlugins\[['"]ijfw-core@ijfw['"]\]\s*=\s*true/.test(src);
@@ -2345,7 +2349,7 @@ var preflight_exports = {};
2345
2349
  __export(preflight_exports, {
2346
2350
  runPreflightCommand: () => runPreflightCommand
2347
2351
  });
2348
- import { readFileSync as readFileSync5, existsSync as existsSync5 } from "node:fs";
2352
+ import { readFileSync as readFileSync5, existsSync as existsSync6 } from "node:fs";
2349
2353
  import { join as join11, dirname as dirname3, resolve as resolve3 } from "node:path";
2350
2354
  import { fileURLToPath as fileURLToPath2 } from "node:url";
2351
2355
  function printHelp() {
@@ -2387,7 +2391,7 @@ function loadVersions(repoRoot2) {
2387
2391
  join11(repoRoot2, ".ijfw", "preflight-versions.json")
2388
2392
  ];
2389
2393
  for (const f of candidates) {
2390
- if (existsSync5(f)) {
2394
+ if (existsSync6(f)) {
2391
2395
  try {
2392
2396
  return JSON.parse(readFileSync5(f, "utf8"));
2393
2397
  } catch {
@@ -2448,7 +2452,7 @@ async function runPreflightCommand(argv, repoRoot2) {
2448
2452
  function defaultRepoRoot() {
2449
2453
  let dir = __dirname;
2450
2454
  for (let i = 0; i < 8; i++) {
2451
- if (existsSync5(join11(dir, "package.json")) && existsSync5(join11(dir, "mcp-server"))) return dir;
2455
+ if (existsSync6(join11(dir, "package.json")) && existsSync6(join11(dir, "mcp-server"))) return dir;
2452
2456
  const next = resolve3(dir, "..");
2453
2457
  if (next === dir) break;
2454
2458
  dir = next;
@@ -3728,7 +3732,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
3728
3732
  // src/ijfw.js
3729
3733
  import { dirname as dirname4, join as join12, resolve as resolve4, basename as basename2 } from "node:path";
3730
3734
  import { fileURLToPath as fileURLToPath3 } from "node:url";
3731
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, copyFileSync as copyFileSync3, readdirSync as readdirSync5, rmSync as rmSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
3735
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, copyFileSync as copyFileSync3, readdirSync as readdirSync5, rmSync as rmSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
3732
3736
  import { homedir, platform } from "node:os";
3733
3737
  import { spawnSync as spawnSync12 } from "node:child_process";
3734
3738
 
@@ -3765,6 +3769,16 @@ var COMMAND_REGISTRY = Object.freeze([
3765
3769
  status: "active",
3766
3770
  helpGroup: "GET STARTED"
3767
3771
  },
3772
+ {
3773
+ name: "init",
3774
+ tier: "primary",
3775
+ owner: "orchestrator",
3776
+ description: "Approve the current folder for codebase indexing",
3777
+ aliases: [],
3778
+ since: "1.6.1",
3779
+ status: "active",
3780
+ helpGroup: "GET STARTED"
3781
+ },
3768
3782
  {
3769
3783
  name: "update",
3770
3784
  tier: "primary",
@@ -3929,6 +3943,26 @@ var COMMAND_REGISTRY = Object.freeze([
3929
3943
  since: "1.4.0",
3930
3944
  status: "active"
3931
3945
  },
3946
+ {
3947
+ name: "personalize",
3948
+ tier: "coordination",
3949
+ owner: "orchestrator",
3950
+ description: "Control the profile-bus learning feature (on|off|status|forget)",
3951
+ aliases: [],
3952
+ since: "1.6.0",
3953
+ status: "active",
3954
+ notes: 'Opt-in injection control. `on`/`off` toggle whether your learned style is added to prompts; `status` shows flags + what was inferred; `forget` deletes inferences. Capture is always local; injection defaults to "ask".'
3955
+ },
3956
+ {
3957
+ name: "checkpoint",
3958
+ tier: "coordination",
3959
+ owner: "orchestrator",
3960
+ description: "Snapshot swarm/memory state (alias for `memory checkpoint`)",
3961
+ aliases: [],
3962
+ since: "1.6.0",
3963
+ status: "active",
3964
+ notes: "Thin top-level alias forwarding to `ijfw memory checkpoint <label>`. Added so the documented bare verb routes instead of returning Unknown."
3965
+ },
3932
3966
  {
3933
3967
  name: "design",
3934
3968
  tier: "coordination",
@@ -4002,6 +4036,16 @@ var COMMAND_REGISTRY = Object.freeze([
4002
4036
  since: "1.5.0",
4003
4037
  status: "active"
4004
4038
  },
4039
+ {
4040
+ name: "worktree",
4041
+ tier: "plumbing",
4042
+ owner: "orchestrator",
4043
+ description: "Manage swarm task worktrees (alias for `swarm worktree`)",
4044
+ aliases: [],
4045
+ since: "1.6.0",
4046
+ status: "active",
4047
+ notes: "Thin top-level alias forwarding to `ijfw swarm worktree \u2026`. Added so the documented bare verb routes instead of returning Unknown."
4048
+ },
4005
4049
  {
4006
4050
  name: "--purge-receipts",
4007
4051
  tier: "plumbing",
@@ -4194,10 +4238,52 @@ function commandsByTier() {
4194
4238
 
4195
4239
  // src/ijfw.js
4196
4240
  var __dirname2 = dirname4(fileURLToPath3(import.meta.url));
4241
+ var LAUNCHER_VERB_REDIRECTS = {
4242
+ on: "Did you mean: ijfw install \xB7 or ijfw personalize on",
4243
+ marketplace: "Not available in this release \u2014 install via `ijfw install` or your agent's native plugin marketplace UI.",
4244
+ cluster: "Multi-machine cluster mode is a design milestone, not shipped in this release.",
4245
+ "wave-status": "Did you mean: ijfw swarm status",
4246
+ "worktree-drain": "Did you mean: ijfw worktree cleanup <task-id>"
4247
+ };
4248
+ function launcherEditDistance(a, b2) {
4249
+ a = String(a);
4250
+ b2 = String(b2);
4251
+ const m2 = a.length, n = b2.length;
4252
+ if (!m2) return n;
4253
+ if (!n) return m2;
4254
+ let prev = Array.from({ length: n + 1 }, (_2, j2) => j2);
4255
+ const cur = Array.from({ length: n + 1 });
4256
+ for (let i = 1; i <= m2; i++) {
4257
+ cur[0] = i;
4258
+ for (let j2 = 1; j2 <= n; j2++) {
4259
+ const cost = a[i - 1] === b2[j2 - 1] ? 0 : 1;
4260
+ cur[j2] = Math.min(prev[j2] + 1, cur[j2 - 1] + 1, prev[j2 - 1] + cost);
4261
+ }
4262
+ prev = cur.slice();
4263
+ }
4264
+ return prev[n];
4265
+ }
4266
+ function launcherSuggest(raw) {
4267
+ const q2 = String(raw || "").toLowerCase();
4268
+ if (Object.prototype.hasOwnProperty.call(LAUNCHER_VERB_REDIRECTS, q2)) {
4269
+ return LAUNCHER_VERB_REDIRECTS[q2];
4270
+ }
4271
+ const candidates = [...ALL_COMMAND_NAMES, ...ORCHESTRATOR_COMMAND_NAMES, ...INSTALLER_DIRECT_COMMAND_NAMES].filter((c2) => c2 && !c2.startsWith("--"));
4272
+ let best = null, bestD = Infinity;
4273
+ for (const c2 of candidates) {
4274
+ const d = launcherEditDistance(q2, c2.toLowerCase());
4275
+ if (d < bestD) {
4276
+ bestD = d;
4277
+ best = c2;
4278
+ }
4279
+ }
4280
+ const tol = Math.max(2, Math.floor(0.4 * Math.max(q2.length, (best || "").length)));
4281
+ return best && bestD <= tol ? `Did you mean: ijfw ${best}` : null;
4282
+ }
4197
4283
  function repoRoot() {
4198
4284
  let dir = __dirname2;
4199
4285
  for (let i = 0; i < 6; i++) {
4200
- if (existsSync6(join12(dir, "package.json")) && existsSync6(join12(dir, ".git"))) return dir;
4286
+ if (existsSync7(join12(dir, "package.json")) && existsSync7(join12(dir, ".git"))) return dir;
4201
4287
  dir = resolve4(dir, "..");
4202
4288
  }
4203
4289
  return process.cwd();
@@ -4206,7 +4292,7 @@ function findInternalAsset(...rel) {
4206
4292
  const root = repoRoot();
4207
4293
  const ijfwHome = join12(homedir(), ".ijfw");
4208
4294
  const candidates = [join12(root, ...rel), join12(ijfwHome, ...rel)];
4209
- return candidates.find((p) => existsSync6(p)) || null;
4295
+ return candidates.find((p) => existsSync7(p)) || null;
4210
4296
  }
4211
4297
  function readDashboardPort() {
4212
4298
  const portFile = join12(homedir(), ".ijfw", "dashboard.port");
@@ -4266,7 +4352,7 @@ function findCli() {
4266
4352
  join12(repoRoot(), "mcp-server", "src", "cross-orchestrator-cli.js"),
4267
4353
  join12(homedir(), ".ijfw", "mcp-server", "src", "cross-orchestrator-cli.js")
4268
4354
  ];
4269
- return candidates.find((p) => existsSync6(p)) || null;
4355
+ return candidates.find((p) => existsSync7(p)) || null;
4270
4356
  }
4271
4357
  function delegateToCli(argTail) {
4272
4358
  const cli = findCli();
@@ -4425,7 +4511,7 @@ async function main() {
4425
4511
  console.error("Design companion accepts standalone .html files.");
4426
4512
  process.exit(1);
4427
4513
  }
4428
- if (!existsSync6(abs)) {
4514
+ if (!existsSync7(abs)) {
4429
4515
  console.error(`File not found: ${abs}`);
4430
4516
  process.exit(1);
4431
4517
  }
@@ -4453,7 +4539,7 @@ async function main() {
4453
4539
  resolve4(__dirname2, "..", "docs", "GUIDE.md"),
4454
4540
  join12(homedir(), ".ijfw", "docs", "GUIDE.md")
4455
4541
  ];
4456
- const guidePath = candidates.find((p) => existsSync6(p));
4542
+ const guidePath = candidates.find((p) => existsSync7(p));
4457
4543
  if (!guidePath) {
4458
4544
  console.error("[ijfw] Guide not found. Run `ijfw install` to fetch the full guide, or visit https://github.com/FerroxLabs/ijfw/blob/main/docs/GUIDE.md");
4459
4545
  process.exit(1);
@@ -4463,7 +4549,7 @@ async function main() {
4463
4549
  const assetsSrc = join12(dirname4(guidePath), "guide", "assets");
4464
4550
  const outDir = join12(homedir(), ".ijfw", "guide");
4465
4551
  mkdirSync5(join12(outDir, "assets"), { recursive: true });
4466
- if (existsSync6(assetsSrc)) {
4552
+ if (existsSync7(assetsSrc)) {
4467
4553
  for (const f of readdirSync5(assetsSrc)) {
4468
4554
  copyFileSync3(join12(assetsSrc, f), join12(outDir, "assets", f));
4469
4555
  }
@@ -4516,8 +4602,10 @@ async function main() {
4516
4602
  break;
4517
4603
  }
4518
4604
  default: {
4519
- console.error(`Unknown subcommand: ${sub}`);
4520
- printHelp2();
4605
+ console.error(`Unknown command: ${sub}`);
4606
+ const hint = launcherSuggest(sub);
4607
+ if (hint) console.error(hint);
4608
+ console.error("Run `ijfw --help` for the user-facing command list, or `ijfw commands` for the full surface.");
4521
4609
  process.exit(1);
4522
4610
  }
4523
4611
  }