@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 +134 -46
- package/dist/install.js +296 -79
- package/dist/uninstall.js +543 -52
- package/docs/GUIDE.md +20 -1
- package/docs/guide/assets/ferrox-hero.png +0 -0
- package/package.json +5 -2
- package/src/install.ps1 +2 -2
- package/templates/aider/CONVENTIONS.md +54 -0
- package/templates/aider/aider.conf.yml +23 -0
- package/templates/pi/AGENTS.md +55 -0
- package/dist/hub-index-snippet.json +0 -50
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 =
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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 || !
|
|
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 =
|
|
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
|
|
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
|
|
1785
|
-
const
|
|
1786
|
-
const
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
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 (
|
|
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
|
|
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 (
|
|
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 (
|
|
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
|
|
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 (
|
|
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) =>
|
|
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) =>
|
|
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 (!
|
|
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) =>
|
|
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 (
|
|
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
|
|
4520
|
-
|
|
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
|
}
|