@ijfw/install 1.5.3 → 1.5.5
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 +21 -0
- package/README.md +33 -65
- package/dist/hub-index-snippet.json +49 -0
- package/dist/ijfw.js +103 -23
- package/dist/install.js +201 -33
- package/docs/GUIDE.md +2 -2
- package/docs/guide/assets/ferrox-hero.png +0 -0
- package/package.json +16 -5
- package/scripts/hub-extension/aion-extension.json.tmpl +156 -0
- package/scripts/hub-extension/assets/ijfw-logo.svg +4 -0
- package/scripts/hub-extension/install.js.tmpl +46 -0
- package/scripts/hub-extension/uninstall.js.tmpl +42 -0
- package/scripts/pack-hub-extension.js +561 -0
- package/src/install.ps1 +30 -3
package/dist/install.js
CHANGED
|
@@ -73,20 +73,44 @@ function writeAtomic(path3, contents, opts = {}) {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
function backup(path3, ts) {
|
|
76
|
+
const res = backupDetailed(path3, ts);
|
|
77
|
+
return res.ok && res.path ? res.path : null;
|
|
78
|
+
}
|
|
79
|
+
function backupDetailed(path3, ts) {
|
|
80
|
+
let st;
|
|
76
81
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return
|
|
82
|
+
st = statSync(path3);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
if (err && err.code === "ENOENT") return { ok: true, path: null, reason: "absent" };
|
|
85
|
+
return { ok: false, reason: "stat-failed", error: err };
|
|
86
|
+
}
|
|
87
|
+
if (!st.isFile()) {
|
|
88
|
+
return { ok: true, path: null, reason: "not-a-file" };
|
|
81
89
|
}
|
|
82
90
|
const dst = `${path3}.bak.${ts}`;
|
|
83
91
|
try {
|
|
84
92
|
copyFileSync(path3, dst);
|
|
85
93
|
printInfo(`backup: ${basename(path3)}.bak.${ts}`);
|
|
86
|
-
return dst;
|
|
87
|
-
} catch {
|
|
94
|
+
return { ok: true, path: dst };
|
|
95
|
+
} catch (err) {
|
|
96
|
+
return { ok: false, reason: "copy-failed", error: err };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function requireBackup(path3, ts) {
|
|
100
|
+
if (!ts) return null;
|
|
101
|
+
const res = backupDetailed(path3, ts);
|
|
102
|
+
if (res.ok) return res.path;
|
|
103
|
+
if (process.env.IJFW_FORCE_NO_BACKUP === "1") {
|
|
104
|
+
const why2 = res.error && res.error.message ? res.error.message : res.reason;
|
|
105
|
+
printInfo(`backup: forced past failure for ${basename(path3)} (${why2})`);
|
|
88
106
|
return null;
|
|
89
107
|
}
|
|
108
|
+
const why = res.error && res.error.message ? res.error.message : res.reason;
|
|
109
|
+
const err = new Error(
|
|
110
|
+
`Refusing to merge into existing config without backup: ${path3} (${why}). Re-run with IJFW_FORCE_NO_BACKUP=1 to proceed.`
|
|
111
|
+
);
|
|
112
|
+
err.code = "BACKUP_REQUIRED";
|
|
113
|
+
throw err;
|
|
90
114
|
}
|
|
91
115
|
function safeChecksum(path3) {
|
|
92
116
|
try {
|
|
@@ -244,7 +268,7 @@ function readJsonOrEmpty(path3) {
|
|
|
244
268
|
}
|
|
245
269
|
function mergeJson(dst, serverJs, ts) {
|
|
246
270
|
mkdirSync2(dirname3(dst), { recursive: true });
|
|
247
|
-
|
|
271
|
+
requireBackup(dst, ts);
|
|
248
272
|
const doc = readJsonOrEmpty(dst);
|
|
249
273
|
if (!doc.mcpServers || typeof doc.mcpServers !== "object") doc.mcpServers = {};
|
|
250
274
|
const isWin = IS_WIN;
|
|
@@ -272,7 +296,7 @@ function mergeJson(dst, serverJs, ts) {
|
|
|
272
296
|
}
|
|
273
297
|
function mergeToml(dst, serverJs, ts) {
|
|
274
298
|
mkdirSync2(dirname3(dst), { recursive: true });
|
|
275
|
-
|
|
299
|
+
requireBackup(dst, ts);
|
|
276
300
|
let text = "";
|
|
277
301
|
try {
|
|
278
302
|
text = existsSync3(dst) ? readFileSync2(dst, "utf8") : "";
|
|
@@ -560,7 +584,8 @@ import {
|
|
|
560
584
|
statSync as statSync2,
|
|
561
585
|
copyFileSync as copyFileSync2,
|
|
562
586
|
cpSync,
|
|
563
|
-
chmodSync as chmodSync2
|
|
587
|
+
chmodSync as chmodSync2,
|
|
588
|
+
rmSync
|
|
564
589
|
} from "node:fs";
|
|
565
590
|
import { join as join4, dirname as dirname4, isAbsolute } from "node:path";
|
|
566
591
|
import { platform } from "node:os";
|
|
@@ -935,10 +960,31 @@ async function installWayland(ctx) {
|
|
|
935
960
|
const pluginDst = join4(ctx.home, ".wayland", "plugins", "ijfw");
|
|
936
961
|
ensureDir(pluginDst);
|
|
937
962
|
let entries;
|
|
963
|
+
let readdirErr = null;
|
|
938
964
|
try {
|
|
939
965
|
entries = readdirSync(pluginSrc);
|
|
940
|
-
} catch {
|
|
966
|
+
} catch (err) {
|
|
941
967
|
entries = [];
|
|
968
|
+
readdirErr = err;
|
|
969
|
+
}
|
|
970
|
+
if (readdirErr) {
|
|
971
|
+
ctx.log.warn(`Wayland plugin tree readdir failed: ${readdirErr.message || readdirErr}`);
|
|
972
|
+
}
|
|
973
|
+
const srcNames = new Set(entries.filter((n) => n !== "__pycache__"));
|
|
974
|
+
let dstEntries = [];
|
|
975
|
+
try {
|
|
976
|
+
dstEntries = readdirSync(pluginDst);
|
|
977
|
+
} catch {
|
|
978
|
+
}
|
|
979
|
+
for (const name of dstEntries) {
|
|
980
|
+
if (name === "__pycache__") continue;
|
|
981
|
+
if (!srcNames.has(name)) {
|
|
982
|
+
try {
|
|
983
|
+
rmSync(join4(pluginDst, name), { recursive: true, force: true });
|
|
984
|
+
} catch (err) {
|
|
985
|
+
ctx.log.warn(`Wayland plugin: could not remove stale ${name}: ${err.message || err}`);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
942
988
|
}
|
|
943
989
|
for (const name of entries) {
|
|
944
990
|
if (name === "__pycache__") continue;
|
|
@@ -986,10 +1032,31 @@ async function installHermes(ctx) {
|
|
|
986
1032
|
const pluginDst = join4(ctx.home, ".hermes", "plugins", "ijfw");
|
|
987
1033
|
ensureDir(pluginDst);
|
|
988
1034
|
let entries;
|
|
1035
|
+
let readdirErr = null;
|
|
989
1036
|
try {
|
|
990
1037
|
entries = readdirSync(pluginSrc);
|
|
991
|
-
} catch {
|
|
1038
|
+
} catch (err) {
|
|
992
1039
|
entries = [];
|
|
1040
|
+
readdirErr = err;
|
|
1041
|
+
}
|
|
1042
|
+
if (readdirErr) {
|
|
1043
|
+
ctx.log.warn(`Hermes plugin tree readdir failed: ${readdirErr.message || readdirErr}`);
|
|
1044
|
+
}
|
|
1045
|
+
const srcNames = new Set(entries.filter((n) => n !== "__pycache__"));
|
|
1046
|
+
let dstEntries = [];
|
|
1047
|
+
try {
|
|
1048
|
+
dstEntries = readdirSync(pluginDst);
|
|
1049
|
+
} catch {
|
|
1050
|
+
}
|
|
1051
|
+
for (const name of dstEntries) {
|
|
1052
|
+
if (name === "__pycache__") continue;
|
|
1053
|
+
if (!srcNames.has(name)) {
|
|
1054
|
+
try {
|
|
1055
|
+
rmSync(join4(pluginDst, name), { recursive: true, force: true });
|
|
1056
|
+
} catch (err) {
|
|
1057
|
+
ctx.log.warn(`Hermes plugin: could not remove stale ${name}: ${err.message || err}`);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
993
1060
|
}
|
|
994
1061
|
for (const name of entries) {
|
|
995
1062
|
if (name === "__pycache__") continue;
|
|
@@ -1181,18 +1248,27 @@ function installOpenclaw(ctx) {
|
|
|
1181
1248
|
}
|
|
1182
1249
|
const dst = path.join(ctx.home, ".openclaw", "openclaw.json");
|
|
1183
1250
|
const serverJs = ctx.serverJsNative || ctx.serverJs;
|
|
1251
|
+
let cliRegistered = false;
|
|
1184
1252
|
if (commandExists("openclaw")) {
|
|
1185
1253
|
try {
|
|
1186
1254
|
const payload = JSON.stringify({ command: "node", args: [serverJs] });
|
|
1187
|
-
execFileSync("openclaw", ["mcp", "set", "ijfw-memory", payload], {
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1255
|
+
execFileSync("openclaw", ["mcp", "set", "ijfw-memory", payload], {
|
|
1256
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1257
|
+
encoding: "utf8"
|
|
1258
|
+
});
|
|
1259
|
+
cliRegistered = true;
|
|
1260
|
+
} catch (err) {
|
|
1261
|
+
const msg = err && err.stderr ? String(err.stderr).trim() : err && err.message || String(err);
|
|
1262
|
+
printInfo(`openclaw CLI registration failed (${msg}); falling back to file-write merge.`);
|
|
1191
1263
|
}
|
|
1192
1264
|
}
|
|
1193
1265
|
ensureDir2(path.dirname(dst));
|
|
1194
1266
|
openclawMerge(dst, serverJs);
|
|
1195
|
-
|
|
1267
|
+
if (cliRegistered) {
|
|
1268
|
+
printOk(`Registered ijfw-memory via 'openclaw mcp set' AND file-write merge (${dst})`);
|
|
1269
|
+
} else {
|
|
1270
|
+
printOk(`Merged MCP into ${dst} (openclaw mcp.servers schema)`);
|
|
1271
|
+
}
|
|
1196
1272
|
return { status: "ok" };
|
|
1197
1273
|
}
|
|
1198
1274
|
function installAider(ctx) {
|
|
@@ -1294,6 +1370,14 @@ function linkPlugin({ repoRoot, ijfwHome, ts }) {
|
|
|
1294
1370
|
} catch {
|
|
1295
1371
|
}
|
|
1296
1372
|
} else if (st.isDirectory()) {
|
|
1373
|
+
try {
|
|
1374
|
+
fs2.rmSync(pluginDst, { recursive: true, force: true });
|
|
1375
|
+
} catch (err) {
|
|
1376
|
+
process.stderr.write(
|
|
1377
|
+
`[ijfw] linkPlugin: could not clear ${pluginDst} for mirror (${err && err.message ? err.message : err}); falling back to merge.
|
|
1378
|
+
`
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1297
1381
|
fs2.cpSync(pluginSrc, pluginDst, { recursive: true });
|
|
1298
1382
|
printOk(`Plugin tree mirrored to ${pluginDst}`);
|
|
1299
1383
|
return;
|
|
@@ -1369,12 +1453,20 @@ function seedState({ ijfwHome, repoRoot, nodeBin: _nodeBin }) {
|
|
|
1369
1453
|
}
|
|
1370
1454
|
} catch {
|
|
1371
1455
|
}
|
|
1372
|
-
|
|
1456
|
+
const pkgPath = path2.join(repoRoot, "installer", "package.json");
|
|
1457
|
+
let installedVer;
|
|
1373
1458
|
try {
|
|
1374
|
-
const pkgPath = path2.join(repoRoot, "installer", "package.json");
|
|
1375
1459
|
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
|
|
1376
|
-
installedVer = pkg.version
|
|
1377
|
-
} catch {
|
|
1460
|
+
installedVer = pkg.version;
|
|
1461
|
+
} catch (err) {
|
|
1462
|
+
throw new Error(
|
|
1463
|
+
`Preflight: installer/package.json missing or unreadable at ${pkgPath} (${err && err.message ? err.message : err}). Repo tree incomplete; rerun bootstrap from a clean clone.`
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
if (!installedVer || typeof installedVer !== "string") {
|
|
1467
|
+
throw new Error(
|
|
1468
|
+
`Preflight: installer/package.json at ${pkgPath} has no usable "version" field. Rerun bootstrap from a clean clone.`
|
|
1469
|
+
);
|
|
1378
1470
|
}
|
|
1379
1471
|
const nowTs = Math.floor(Date.now() / 1e3);
|
|
1380
1472
|
const state = {
|
|
@@ -1484,7 +1576,14 @@ function patchPluginMcpJson({ ijfwHome, repoRoot, nodeBin, serverJs }) {
|
|
|
1484
1576
|
d.mcpServers["ijfw-memory"].command = nodeBin;
|
|
1485
1577
|
d.mcpServers["ijfw-memory"].args = [serverJs];
|
|
1486
1578
|
const envSep = process.platform === "win32" ? ";" : ":";
|
|
1487
|
-
|
|
1579
|
+
let commonPaths;
|
|
1580
|
+
if (process.platform === "win32") {
|
|
1581
|
+
commonPaths = [nodeDir, "C:\\Windows\\System32"];
|
|
1582
|
+
} else if (process.platform === "darwin") {
|
|
1583
|
+
commonPaths = [nodeDir, "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"];
|
|
1584
|
+
} else {
|
|
1585
|
+
commonPaths = [nodeDir, "/usr/local/bin", "/usr/bin", "/bin"];
|
|
1586
|
+
}
|
|
1488
1587
|
const dedup = [...new Set(commonPaths.filter((x) => x && fs2.existsSync(x)))];
|
|
1489
1588
|
d.mcpServers["ijfw-memory"].env = { PATH: dedup.join(envSep) };
|
|
1490
1589
|
try {
|
|
@@ -1857,7 +1956,7 @@ var init_install_flow = __esm({
|
|
|
1857
1956
|
|
|
1858
1957
|
// src/install.js
|
|
1859
1958
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1860
|
-
import { existsSync as existsSync5, rmSync, mkdirSync as mkdirSync4, realpathSync as realpathSync2, renameSync as renameSync3 } from "node:fs";
|
|
1959
|
+
import { existsSync as existsSync5, rmSync as rmSync2, mkdirSync as mkdirSync4, realpathSync as realpathSync2, renameSync as renameSync3, readdirSync as readdirSync2, cpSync as cpSync2 } from "node:fs";
|
|
1861
1960
|
import { resolve as resolve4, join as join5, dirname as dirname5 } from "node:path";
|
|
1862
1961
|
import { homedir as homedir3, platform as platform2 } from "node:os";
|
|
1863
1962
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
@@ -2017,7 +2116,7 @@ function triggerColdScan(projectRoot, options = {}) {
|
|
|
2017
2116
|
}
|
|
2018
2117
|
|
|
2019
2118
|
// src/install.js
|
|
2020
|
-
var DEFAULT_REPO = "https://
|
|
2119
|
+
var DEFAULT_REPO = "https://github.com/FerroxLabs/ijfw.git";
|
|
2021
2120
|
var DEFAULT_BRANCH = "main";
|
|
2022
2121
|
function parseArgs(argv) {
|
|
2023
2122
|
const out = { yes: false, dir: null, noMarketplace: false, branch: DEFAULT_BRANCH, branchExplicit: false, purge: false };
|
|
@@ -2037,7 +2136,11 @@ function parseArgs(argv) {
|
|
|
2037
2136
|
}
|
|
2038
2137
|
return out;
|
|
2039
2138
|
}
|
|
2139
|
+
function skipNetwork() {
|
|
2140
|
+
return process.env.IJFW_SKIP_NETWORK === "1";
|
|
2141
|
+
}
|
|
2040
2142
|
function latestTagFromGithub() {
|
|
2143
|
+
if (skipNetwork()) return null;
|
|
2041
2144
|
try {
|
|
2042
2145
|
const res = spawnSync2("git", ["ls-remote", "--tags", "--refs", "--sort=-v:refname", DEFAULT_REPO], {
|
|
2043
2146
|
encoding: "utf8",
|
|
@@ -2051,7 +2154,7 @@ function latestTagFromGithub() {
|
|
|
2051
2154
|
return null;
|
|
2052
2155
|
}
|
|
2053
2156
|
}
|
|
2054
|
-
function resolveBranchOrTag({ branch, branchExplicit, _tagLookup } = {}) {
|
|
2157
|
+
function resolveBranchOrTag({ branch, branchExplicit, _tagLookup, _logger } = {}) {
|
|
2055
2158
|
if (branchExplicit) return branch;
|
|
2056
2159
|
const lookup = _tagLookup || latestTagFromGithub;
|
|
2057
2160
|
let tag = null;
|
|
@@ -2060,7 +2163,15 @@ function resolveBranchOrTag({ branch, branchExplicit, _tagLookup } = {}) {
|
|
|
2060
2163
|
} catch {
|
|
2061
2164
|
tag = null;
|
|
2062
2165
|
}
|
|
2063
|
-
|
|
2166
|
+
if (!tag) {
|
|
2167
|
+
const log = _logger || console.warn;
|
|
2168
|
+
const eff = branch || DEFAULT_BRANCH;
|
|
2169
|
+
log(
|
|
2170
|
+
` note: could not resolve latest tag from upstream (network or rate-limit?). Using branch "${eff}" instead. Pin a specific version with --branch vX.Y.Z if needed.`
|
|
2171
|
+
);
|
|
2172
|
+
return eff;
|
|
2173
|
+
}
|
|
2174
|
+
return tag;
|
|
2064
2175
|
}
|
|
2065
2176
|
function printHelp() {
|
|
2066
2177
|
console.log(`ijfw-install -- IJFW installer
|
|
@@ -2128,6 +2239,14 @@ function runCheck(cmd, args, opts) {
|
|
|
2128
2239
|
return { status: r.status, stdout: r.stdout || "", stderr: r.stderr || "", spawnError: r.error?.code, signal: r.signal };
|
|
2129
2240
|
}
|
|
2130
2241
|
function cloneOrPull(dir, branch) {
|
|
2242
|
+
if (skipNetwork()) {
|
|
2243
|
+
if (existsSync5(dir)) {
|
|
2244
|
+
return "skipped-network";
|
|
2245
|
+
}
|
|
2246
|
+
throw new Error(
|
|
2247
|
+
`IJFW_SKIP_NETWORK=1 set but cloneOrPull needs network: target directory ${dir} does not exist. Pre-seed the directory before setting IJFW_SKIP_NETWORK, or unset the env var.`
|
|
2248
|
+
);
|
|
2249
|
+
}
|
|
2131
2250
|
if (!existsSync5(dir)) {
|
|
2132
2251
|
mkdirSync4(dir, { recursive: true });
|
|
2133
2252
|
const r = spawnSync2("git", ["clone", "--depth", "1", "--branch", branch, DEFAULT_REPO, dir], { stdio: "inherit" });
|
|
@@ -2143,7 +2262,11 @@ function cloneOrPull(dir, branch) {
|
|
|
2143
2262
|
if (remoteStatus === 0) {
|
|
2144
2263
|
const STALE_PATTERNS = [
|
|
2145
2264
|
/^https:\/\/github\.com\/seandonahoe\/ijfw(\.git)?\/?$/i,
|
|
2146
|
-
/^https:\/\/github\.com\/therealseandonahoe\/ijfw(\.git)?\/?$/i
|
|
2265
|
+
/^https:\/\/github\.com\/therealseandonahoe\/ijfw(\.git)?\/?$/i,
|
|
2266
|
+
// V155 rebrand: GitLab was the canonical source through v1.5.4;
|
|
2267
|
+
// users who installed from gitlab.com need their origin migrated
|
|
2268
|
+
// forward to FerroxLabs/ijfw on GitHub on next `ijfw-install`.
|
|
2269
|
+
/^https:\/\/gitlab\.com\/therealseandonahoe\/ijfw(\.git)?\/?$/i
|
|
2147
2270
|
];
|
|
2148
2271
|
const currentOrigin = (stdout || "").trim();
|
|
2149
2272
|
if (STALE_PATTERNS.some((re) => re.test(currentOrigin))) {
|
|
@@ -2161,23 +2284,63 @@ function cloneOrPull(dir, branch) {
|
|
|
2161
2284
|
return "updated";
|
|
2162
2285
|
}
|
|
2163
2286
|
}
|
|
2287
|
+
const RESTORE_ALLOWLIST = [
|
|
2288
|
+
"memory",
|
|
2289
|
+
"sessions",
|
|
2290
|
+
"install.log",
|
|
2291
|
+
".session-counter",
|
|
2292
|
+
// v1.5.x additions:
|
|
2293
|
+
"ijfw",
|
|
2294
|
+
// visible brain layer (wiki + facts)
|
|
2295
|
+
"state",
|
|
2296
|
+
// state.json, deploy-failures.jsonl, .dream-state-v2.json
|
|
2297
|
+
"cache",
|
|
2298
|
+
// npm-view-cache and friends
|
|
2299
|
+
"logs",
|
|
2300
|
+
// post-tool-use logs, jsonl observations
|
|
2301
|
+
"run",
|
|
2302
|
+
// runtime lock files / pid markers
|
|
2303
|
+
".ijfw"
|
|
2304
|
+
// internal — recall counter, indexes, layout version
|
|
2305
|
+
];
|
|
2164
2306
|
const backupDir = dir + ".bak." + Date.now();
|
|
2165
2307
|
renameSync3(dir, backupDir);
|
|
2166
2308
|
try {
|
|
2167
2309
|
const r = spawnSync2("git", ["clone", "--depth", "1", "--branch", branch, DEFAULT_REPO, dir], { stdio: "inherit" });
|
|
2168
2310
|
if (r.status !== 0) throw new Error(`IJFW repo fetch did not complete (exit ${r.status}) -- check network access and retry.`);
|
|
2169
|
-
|
|
2311
|
+
let restoredCount = 0;
|
|
2312
|
+
for (const item of RESTORE_ALLOWLIST) {
|
|
2170
2313
|
const src = join5(backupDir, item);
|
|
2171
2314
|
if (existsSync5(src)) {
|
|
2172
2315
|
const dst = join5(dir, item);
|
|
2173
|
-
if (existsSync5(dst))
|
|
2174
|
-
|
|
2316
|
+
if (existsSync5(dst)) rmSync2(dst, { recursive: true, force: true });
|
|
2317
|
+
try {
|
|
2318
|
+
cpSync2(src, dst, { recursive: true, dereference: false });
|
|
2319
|
+
rmSync2(src, { recursive: true, force: true });
|
|
2320
|
+
restoredCount++;
|
|
2321
|
+
} catch (cpErr) {
|
|
2322
|
+
const msg = cpErr && cpErr.message ? cpErr.message : String(cpErr);
|
|
2323
|
+
throw new Error(
|
|
2324
|
+
`IJFW restore: cpSync failed for "${item}" (${msg}). Your data is still intact under: ${backupDir}. Move it back into ${dir} manually after diagnosing the copy failure.`
|
|
2325
|
+
);
|
|
2326
|
+
}
|
|
2175
2327
|
}
|
|
2176
2328
|
}
|
|
2177
|
-
|
|
2329
|
+
let backupResidual = [];
|
|
2330
|
+
try {
|
|
2331
|
+
backupResidual = readdirSync2(backupDir);
|
|
2332
|
+
} catch {
|
|
2333
|
+
}
|
|
2334
|
+
if (backupResidual.length === 0) {
|
|
2335
|
+
rmSync2(backupDir, { recursive: true, force: true });
|
|
2336
|
+
} else {
|
|
2337
|
+
console.warn(
|
|
2338
|
+
` [!] restored ${restoredCount} known dirs; backup retained at ${backupDir} (contains: ${backupResidual.slice(0, 8).join(", ")}${backupResidual.length > 8 ? ", ..." : ""}). Remove manually after verifying.`
|
|
2339
|
+
);
|
|
2340
|
+
}
|
|
2178
2341
|
return "updated";
|
|
2179
2342
|
} catch (err) {
|
|
2180
|
-
if (existsSync5(dir))
|
|
2343
|
+
if (existsSync5(dir)) rmSync2(dir, { recursive: true, force: true });
|
|
2181
2344
|
renameSync3(backupDir, dir);
|
|
2182
2345
|
throw err;
|
|
2183
2346
|
}
|
|
@@ -2208,8 +2371,13 @@ async function main() {
|
|
|
2208
2371
|
const sigint = () => {
|
|
2209
2372
|
if (createdThisRun && existsSync5(target)) {
|
|
2210
2373
|
try {
|
|
2211
|
-
|
|
2212
|
-
} catch {
|
|
2374
|
+
rmSync2(target, { recursive: true, force: true });
|
|
2375
|
+
} catch (err) {
|
|
2376
|
+
const msg = err && err.message ? err.message : String(err);
|
|
2377
|
+
console.warn(
|
|
2378
|
+
`
|
|
2379
|
+
[!] partial install at ${target} could not be cleaned (${msg}) \u2014 run \`rm -rf "${target}"\` (or Remove-Item -Recurse -Force on Windows) before retrying.`
|
|
2380
|
+
);
|
|
2213
2381
|
}
|
|
2214
2382
|
}
|
|
2215
2383
|
process.exit(130);
|
package/docs/GUIDE.md
CHANGED
|
@@ -502,7 +502,7 @@ cat ~/.ijfw/dashboard.port
|
|
|
502
502
|
|
|
503
503
|
### Still stuck
|
|
504
504
|
|
|
505
|
-
Every install writes a log to `~/.ijfw/install.log`. Every session writes observations to `~/.ijfw/observations.jsonl`. Open an issue at [
|
|
505
|
+
Every install writes a log to `~/.ijfw/install.log`. Every session writes observations to `~/.ijfw/observations.jsonl`. Open an issue at [github.com/FerroxLabs/ijfw/issues](https://github.com/FerroxLabs/ijfw/issues) with both files redacted and attached.
|
|
506
506
|
|
|
507
507
|
---
|
|
508
508
|
|
|
@@ -545,7 +545,7 @@ Yes. `.ijfw/team/` is git-committed by default. Decisions, patterns, and stack c
|
|
|
545
545
|
</p>
|
|
546
546
|
|
|
547
547
|
<p align="center">
|
|
548
|
-
<a href="https://
|
|
548
|
+
<a href="https://github.com/FerroxLabs/ijfw">github.com/FerroxLabs/ijfw</a>
|
|
549
549
|
|
|
|
550
550
|
<a href="https://www.npmjs.com/package/@ijfw/install">npm</a>
|
|
551
551
|
|
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ijfw/install",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.5",
|
|
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": {
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"src/install.ps1",
|
|
14
14
|
"docs/GUIDE.md",
|
|
15
15
|
"docs/guide/assets",
|
|
16
|
+
"scripts/pack-hub-extension.js",
|
|
17
|
+
"scripts/hub-extension/**",
|
|
16
18
|
"README.md",
|
|
17
19
|
"CHANGELOG.md",
|
|
18
20
|
"LICENSE"
|
|
@@ -20,9 +22,12 @@
|
|
|
20
22
|
"scripts": {
|
|
21
23
|
"build": "node scripts/build.js",
|
|
22
24
|
"test": "node --test test.js",
|
|
25
|
+
"test:hub-extension": "node --test test/test-pack-hub-extension.js",
|
|
23
26
|
"preflight": "node dist/ijfw.js preflight",
|
|
24
27
|
"pack:check": "npm pack --dry-run",
|
|
25
|
-
"
|
|
28
|
+
"pack:hub-extension": "node scripts/pack-hub-extension.js",
|
|
29
|
+
"prepack": "node -e \"const fs=require('fs'),path=require('path');const d='dist';if(fs.existsSync(d)){for(const f of fs.readdirSync(d)){if(/^ijfw-\\d+\\.\\d+\\.\\d+\\.(zip|sha512)$/.test(f)){fs.rmSync(path.join(d,f))}}}\"",
|
|
30
|
+
"prepublishOnly": "npm run build && npm run preflight && npm run pack:check"
|
|
26
31
|
},
|
|
27
32
|
"devDependencies": {
|
|
28
33
|
"esbuild": "^0.28.0",
|
|
@@ -46,13 +51,19 @@
|
|
|
46
51
|
],
|
|
47
52
|
"license": "MIT",
|
|
48
53
|
"author": "Sean Donahoe",
|
|
49
|
-
"
|
|
54
|
+
"contributors": [
|
|
55
|
+
{
|
|
56
|
+
"name": "Ferrox Labs",
|
|
57
|
+
"url": "https://ferroxlabs.com"
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"homepage": "https://github.com/FerroxLabs/ijfw",
|
|
50
61
|
"bugs": {
|
|
51
|
-
"url": "https://
|
|
62
|
+
"url": "https://github.com/FerroxLabs/ijfw/issues"
|
|
52
63
|
},
|
|
53
64
|
"repository": {
|
|
54
65
|
"type": "git",
|
|
55
|
-
"url": "git+https://
|
|
66
|
+
"url": "git+https://github.com/FerroxLabs/ijfw.git"
|
|
56
67
|
},
|
|
57
68
|
"publishConfig": {
|
|
58
69
|
"access": "public"
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ijfw",
|
|
3
|
+
"displayName": "IJFW — AI Efficiency Layer",
|
|
4
|
+
"version": "{{VERSION}}",
|
|
5
|
+
"description": "One install, every AI coding agent, zero config. Unifies 15 CLIs under a shared MCP memory layer so context follows you across Claude, Codex, Gemini, Cursor, Windsurf, and 10 more.",
|
|
6
|
+
"author": "Sean Donahoe",
|
|
7
|
+
"icon": "assets/ijfw-logo.svg",
|
|
8
|
+
"engines": {
|
|
9
|
+
"wayland": ">=0.6.0"
|
|
10
|
+
},
|
|
11
|
+
"hubs": ["acpAdapters", "mcpServers"],
|
|
12
|
+
"lifecycle": {
|
|
13
|
+
"onInstall": "scripts/install.js",
|
|
14
|
+
"onUninstall": "scripts/uninstall.js"
|
|
15
|
+
},
|
|
16
|
+
"contributes": {
|
|
17
|
+
"acpAdapters": [
|
|
18
|
+
{
|
|
19
|
+
"id": "claude",
|
|
20
|
+
"name": "Claude Code",
|
|
21
|
+
"description": "Anthropic's Claude Code agentic coding CLI with IJFW memory + plugin layer",
|
|
22
|
+
"connectionType": "cli",
|
|
23
|
+
"cliCommand": "claude",
|
|
24
|
+
"defaultCliPath": "npx @anthropic-ai/claude-code",
|
|
25
|
+
"supportsStreaming": true
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "codex",
|
|
29
|
+
"name": "OpenAI Codex CLI",
|
|
30
|
+
"description": "OpenAI Codex CLI with IJFW MCP memory, hooks, skills, and command aliases",
|
|
31
|
+
"connectionType": "cli",
|
|
32
|
+
"cliCommand": "codex",
|
|
33
|
+
"defaultCliPath": "npx @openai/codex",
|
|
34
|
+
"supportsStreaming": true
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "gemini",
|
|
38
|
+
"name": "Gemini CLI",
|
|
39
|
+
"description": "Google Gemini CLI with IJFW MCP memory, extension bundle, and hooks",
|
|
40
|
+
"connectionType": "cli",
|
|
41
|
+
"cliCommand": "gemini",
|
|
42
|
+
"defaultCliPath": "npx @google/gemini-cli",
|
|
43
|
+
"supportsStreaming": true
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "cursor",
|
|
47
|
+
"name": "Cursor",
|
|
48
|
+
"description": "Cursor AI editor with IJFW MCP memory and project rules",
|
|
49
|
+
"connectionType": "cli",
|
|
50
|
+
"cliCommand": "cursor",
|
|
51
|
+
"defaultCliPath": "cursor",
|
|
52
|
+
"supportsStreaming": false
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "windsurf",
|
|
56
|
+
"name": "Windsurf",
|
|
57
|
+
"description": "Windsurf AI editor with IJFW MCP memory and project rules",
|
|
58
|
+
"connectionType": "cli",
|
|
59
|
+
"cliCommand": "windsurf",
|
|
60
|
+
"defaultCliPath": "windsurf",
|
|
61
|
+
"supportsStreaming": false
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "copilot",
|
|
65
|
+
"name": "GitHub Copilot",
|
|
66
|
+
"description": "GitHub Copilot (VS Code) with IJFW MCP memory and copilot-instructions",
|
|
67
|
+
"connectionType": "cli",
|
|
68
|
+
"cliCommand": "code",
|
|
69
|
+
"defaultCliPath": "code",
|
|
70
|
+
"supportsStreaming": false
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"id": "hermes",
|
|
74
|
+
"name": "Hermes CLI",
|
|
75
|
+
"description": "Hermes CLI with IJFW MCP memory, skills, plugin, and tier-2 hook",
|
|
76
|
+
"connectionType": "cli",
|
|
77
|
+
"cliCommand": "hermes",
|
|
78
|
+
"defaultCliPath": "npx @hermes-ai/cli",
|
|
79
|
+
"supportsStreaming": true
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"id": "wayland",
|
|
83
|
+
"name": "Wayland CLI",
|
|
84
|
+
"description": "Wayland CLI with IJFW MCP memory, skills, plugin, and tier-2 hook",
|
|
85
|
+
"connectionType": "cli",
|
|
86
|
+
"cliCommand": "wayland",
|
|
87
|
+
"defaultCliPath": "npx @wayland-ai/cli",
|
|
88
|
+
"supportsStreaming": true
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": "aider",
|
|
92
|
+
"name": "Aider",
|
|
93
|
+
"description": "Aider AI pair programmer with IJFW rules (aider.conf.yml + CONVENTIONS.md)",
|
|
94
|
+
"connectionType": "cli",
|
|
95
|
+
"cliCommand": "aider",
|
|
96
|
+
"defaultCliPath": "npx aider-chat",
|
|
97
|
+
"supportsStreaming": false
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"id": "opencode",
|
|
101
|
+
"name": "OpenCode",
|
|
102
|
+
"description": "OpenCode CLI with IJFW MCP memory (opencode mcp.local schema)",
|
|
103
|
+
"connectionType": "cli",
|
|
104
|
+
"cliCommand": "opencode",
|
|
105
|
+
"defaultCliPath": "npx opencode-ai",
|
|
106
|
+
"supportsStreaming": true
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"id": "qwencode",
|
|
110
|
+
"name": "Qwen Code",
|
|
111
|
+
"description": "Alibaba Qwen Code CLI with IJFW MCP memory",
|
|
112
|
+
"connectionType": "cli",
|
|
113
|
+
"cliCommand": "qwen",
|
|
114
|
+
"defaultCliPath": "npx @qwen-ai/qwen-code",
|
|
115
|
+
"supportsStreaming": true
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"id": "cline",
|
|
119
|
+
"name": "Cline",
|
|
120
|
+
"description": "Cline VS Code extension with IJFW MCP memory (globalStorage schema)",
|
|
121
|
+
"connectionType": "cli",
|
|
122
|
+
"cliCommand": "cline",
|
|
123
|
+
"defaultCliPath": "cline",
|
|
124
|
+
"supportsStreaming": false
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"id": "kimicode",
|
|
128
|
+
"name": "Kimi Code",
|
|
129
|
+
"description": "Moonshot Kimi Code CLI with IJFW MCP memory",
|
|
130
|
+
"connectionType": "cli",
|
|
131
|
+
"cliCommand": "kimi",
|
|
132
|
+
"defaultCliPath": "npx @moonshot-ai/kimi-code",
|
|
133
|
+
"supportsStreaming": true
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"id": "openclaw",
|
|
137
|
+
"name": "OpenClaw",
|
|
138
|
+
"description": "OpenClaw CLI with IJFW MCP memory (mcp.servers schema)",
|
|
139
|
+
"connectionType": "cli",
|
|
140
|
+
"cliCommand": "openclaw",
|
|
141
|
+
"defaultCliPath": "npx @openclaw-ai/cli",
|
|
142
|
+
"supportsStreaming": true
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"id": "antigravity",
|
|
146
|
+
"name": "Antigravity",
|
|
147
|
+
"description": "Antigravity AI IDE (ex-Windsurf team) with IJFW MCP memory (IDE + CLI paths)",
|
|
148
|
+
"connectionType": "cli",
|
|
149
|
+
"cliCommand": "agy",
|
|
150
|
+
"defaultCliPath": "npx @antigravity-ai/cli",
|
|
151
|
+
"supportsStreaming": true
|
|
152
|
+
}
|
|
153
|
+
],
|
|
154
|
+
"mcpServers": ["ijfw-memory"]
|
|
155
|
+
}
|
|
156
|
+
}
|