@ijfw/install 1.1.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.
@@ -0,0 +1,279 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/install.js
4
+ import { spawnSync } from "node:child_process";
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";
7
+ import { homedir as homedir2, platform } from "node:os";
8
+ import { fileURLToPath } from "node:url";
9
+
10
+ // src/marketplace.js
11
+ import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync } from "node:fs";
12
+ import { dirname, join } from "node:path";
13
+ import { homedir } from "node:os";
14
+ function claudeSettingsPath() {
15
+ return join(homedir(), ".claude", "settings.json");
16
+ }
17
+ function stripJsoncComments(raw) {
18
+ if (raw.charCodeAt(0) === 65279) raw = raw.slice(1);
19
+ let out = "";
20
+ let i = 0;
21
+ const n = raw.length;
22
+ const isLineBreak = (code) => code === 10 || code === 13 || code === 8232 || code === 8233;
23
+ while (i < n) {
24
+ const c = raw[i];
25
+ const c2 = raw[i + 1];
26
+ if (c === '"') {
27
+ out += c;
28
+ i++;
29
+ while (i < n) {
30
+ const k = raw[i];
31
+ out += k;
32
+ if (k === "\\" && i + 1 < n) {
33
+ out += raw[i + 1];
34
+ i += 2;
35
+ continue;
36
+ }
37
+ i++;
38
+ if (k === '"') break;
39
+ }
40
+ continue;
41
+ }
42
+ if (c === "/" && c2 === "/") {
43
+ i += 2;
44
+ while (i < n && !isLineBreak(raw.charCodeAt(i))) i++;
45
+ continue;
46
+ }
47
+ if (c === "/" && c2 === "*") {
48
+ i += 2;
49
+ while (i < n && !(raw[i] === "*" && raw[i + 1] === "/")) i++;
50
+ if (i < n) i += 2;
51
+ continue;
52
+ }
53
+ out += c;
54
+ i++;
55
+ }
56
+ return out;
57
+ }
58
+ function tolerantJsonParse(raw, filepath) {
59
+ try {
60
+ return JSON.parse(raw);
61
+ } catch {
62
+ }
63
+ const stripped = stripJsoncComments(raw).replace(/,(\s*[}\]])/g, "$1");
64
+ try {
65
+ return JSON.parse(stripped);
66
+ } catch (e) {
67
+ const err = new Error(`settings.json at ${filepath} is not valid JSON or recoverable JSONC: ${e.message}`);
68
+ err.code = "IJFW_SETTINGS_UNPARSEABLE";
69
+ throw err;
70
+ }
71
+ }
72
+ function mergeMarketplace(settingsPath = claudeSettingsPath()) {
73
+ let settings = {};
74
+ if (existsSync(settingsPath)) {
75
+ const raw = readFileSync(settingsPath, "utf8");
76
+ settings = tolerantJsonParse(raw, settingsPath);
77
+ } else {
78
+ mkdirSync(dirname(settingsPath), { recursive: true });
79
+ }
80
+ settings.extraKnownMarketplaces = settings.extraKnownMarketplaces || {};
81
+ settings.extraKnownMarketplaces.ijfw = {
82
+ source: { source: "github", repo: "TheRealSeanDonahoe/ijfw" }
83
+ };
84
+ settings.enabledPlugins = settings.enabledPlugins || {};
85
+ if ("ijfw-core@ijfw" in settings.enabledPlugins) {
86
+ delete settings.enabledPlugins["ijfw-core@ijfw"];
87
+ }
88
+ settings.enabledPlugins["ijfw@ijfw"] = true;
89
+ const tmp = settingsPath + ".tmp";
90
+ writeFileSync(tmp, JSON.stringify(settings, null, 2) + "\n");
91
+ renameSync(tmp, settingsPath);
92
+ return settings;
93
+ }
94
+
95
+ // src/install.js
96
+ var DEFAULT_REPO = "https://github.com/TheRealSeanDonahoe/ijfw.git";
97
+ var DEFAULT_BRANCH = "main";
98
+ function parseArgs(argv) {
99
+ const out = { yes: false, dir: null, noMarketplace: false, branch: DEFAULT_BRANCH, branchExplicit: false, purge: false };
100
+ for (let i = 2; i < argv.length; i++) {
101
+ const a = argv[i];
102
+ if (a === "--yes" || a === "-y") out.yes = true;
103
+ else if (a === "--dir") out.dir = argv[++i];
104
+ else if (a === "--no-marketplace") out.noMarketplace = true;
105
+ else if (a === "--branch") {
106
+ out.branch = argv[++i];
107
+ out.branchExplicit = true;
108
+ } else if (a === "--purge") out.purge = true;
109
+ else if (a === "--help" || a === "-h") {
110
+ printHelp();
111
+ process.exit(0);
112
+ }
113
+ }
114
+ return out;
115
+ }
116
+ function latestTagFromGithub() {
117
+ try {
118
+ const res = spawnSync("git", ["ls-remote", "--tags", "--refs", "--sort=-v:refname", DEFAULT_REPO], {
119
+ encoding: "utf8",
120
+ timeout: 1e4
121
+ });
122
+ if (res.status !== 0) return null;
123
+ const first = (res.stdout || "").split("\n")[0] || "";
124
+ const m = first.match(/refs\/tags\/(v[0-9][^\s]*)$/);
125
+ return m ? m[1] : null;
126
+ } catch {
127
+ return null;
128
+ }
129
+ }
130
+ function resolveBranchOrTag({ branch, branchExplicit, _tagLookup } = {}) {
131
+ if (branchExplicit) return branch;
132
+ const lookup = _tagLookup || latestTagFromGithub;
133
+ let tag = null;
134
+ try {
135
+ tag = lookup();
136
+ } catch {
137
+ tag = null;
138
+ }
139
+ return tag || branch || DEFAULT_BRANCH;
140
+ }
141
+ function printHelp() {
142
+ console.log(`ijfw-install -- IJFW installer
143
+ Usage: npx @ijfw/install [--dir <path>] [--branch <name>] [--no-marketplace] [--yes]
144
+ --dir install location (default: $IJFW_HOME or ~/.ijfw)
145
+ --branch git branch or tag (default: latest released tag)
146
+ --no-marketplace skip merging ~/.claude/settings.json
147
+ --yes non-interactive
148
+ `);
149
+ }
150
+ function preflight() {
151
+ const issues = [];
152
+ const [major] = process.versions.node.split(".").map(Number);
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")) {
156
+ 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");
158
+ } else {
159
+ issues.push("IJFW needs bash on PATH -- install bash, then retry.");
160
+ }
161
+ }
162
+ return issues;
163
+ }
164
+ function hasBin(bin) {
165
+ const res = spawnSync(bin, ["--version"], { stdio: "ignore" });
166
+ return res.status === 0 || res.status === null ? res.error ? false : true : false;
167
+ }
168
+ function resolveTarget(opt) {
169
+ if (opt.dir) return resolve(opt.dir);
170
+ if (process.env.IJFW_HOME) return resolve(process.env.IJFW_HOME);
171
+ return join2(homedir2(), ".ijfw");
172
+ }
173
+ function runCheck(cmd, args, opts) {
174
+ const r = spawnSync(cmd, args, { encoding: "utf8", ...opts });
175
+ return { status: r.status, stdout: r.stdout || "" };
176
+ }
177
+ function cloneOrPull(dir, branch) {
178
+ if (!existsSync2(dir)) {
179
+ mkdirSync2(dir, { recursive: true });
180
+ const r = spawnSync("git", ["clone", "--depth", "1", "--branch", branch, DEFAULT_REPO, dir], { stdio: "inherit" });
181
+ if (r.status !== 0) throw new Error(`IJFW repo fetch did not complete (exit ${r.status}) -- check network access and retry.`);
182
+ return "cloned";
183
+ }
184
+ const hasGit = existsSync2(join2(dir, ".git"));
185
+ if (hasGit) {
186
+ const { status: remoteStatus } = runCheck("git", ["-C", dir, "remote", "get-url", "origin"]);
187
+ if (remoteStatus === 0) {
188
+ const fetch = spawnSync("git", ["-C", dir, "fetch", "--depth", "1", "origin", branch], { stdio: "inherit" });
189
+ if (fetch.status !== 0) throw new Error(`IJFW fetch did not complete (exit ${fetch.status}) -- check network access and retry.`);
190
+ const co = spawnSync("git", ["-C", dir, "checkout", "-f", "FETCH_HEAD"], { stdio: "inherit" });
191
+ if (co.status !== 0) throw new Error(`IJFW checkout did not complete (exit ${co.status}) -- run ijfw doctor to check prerequisites.`);
192
+ return "updated";
193
+ }
194
+ }
195
+ const backupDir = dir + ".bak." + Date.now();
196
+ renameSync2(dir, backupDir);
197
+ try {
198
+ const r = spawnSync("git", ["clone", "--depth", "1", "--branch", branch, DEFAULT_REPO, dir], { stdio: "inherit" });
199
+ if (r.status !== 0) throw new Error(`IJFW repo fetch did not complete (exit ${r.status}) -- check network access and retry.`);
200
+ for (const item of ["memory", "sessions", "install.log", ".session-counter"]) {
201
+ const src = join2(backupDir, item);
202
+ if (existsSync2(src)) {
203
+ const dst = join2(dir, item);
204
+ if (existsSync2(dst)) rmSync(dst, { recursive: true, force: true });
205
+ renameSync2(src, dst);
206
+ }
207
+ }
208
+ rmSync(backupDir, { recursive: true, force: true });
209
+ return "updated";
210
+ } catch (err) {
211
+ if (existsSync2(dir)) rmSync(dir, { recursive: true, force: true });
212
+ renameSync2(backupDir, dir);
213
+ throw err;
214
+ }
215
+ }
216
+ function runInstallScript(dir) {
217
+ const script = join2(dir, "scripts", "install.sh");
218
+ 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 });
221
+ 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
+ }
223
+ async function main() {
224
+ const opts = parseArgs(process.argv);
225
+ const issues = preflight();
226
+ if (issues.length) {
227
+ console.error("IJFW needs a couple of things first -- fix these and re-run:");
228
+ for (const i of issues) console.error(" - " + i);
229
+ process.exit(1);
230
+ }
231
+ const target = resolveTarget(opts);
232
+ const createdThisRun = !existsSync2(target);
233
+ const sigint = () => {
234
+ if (createdThisRun && existsSync2(target)) {
235
+ try {
236
+ rmSync(target, { recursive: true, force: true });
237
+ } catch {
238
+ }
239
+ }
240
+ process.exit(130);
241
+ };
242
+ process.on("SIGINT", sigint);
243
+ const ref = resolveBranchOrTag({ branch: opts.branch, branchExplicit: opts.branchExplicit });
244
+ console.log(`IJFW install target: ${target}`);
245
+ console.log(` version: ${ref}`);
246
+ const action = cloneOrPull(target, ref);
247
+ console.log(` repo ${action}`);
248
+ runInstallScript(target);
249
+ console.log(" platform configs applied");
250
+ if (!opts.noMarketplace) {
251
+ const settingsPath = claudeSettingsPath();
252
+ mergeMarketplace(settingsPath);
253
+ console.log(` marketplace registered in ${settingsPath}`);
254
+ }
255
+ console.log("");
256
+ console.log("IJFW now active across 7 platforms -- one memory layer, all your AIs, zero config.");
257
+ console.log(" Run `ijfw demo` to see the Trident in action.");
258
+ console.log(" Run `ijfw doctor` to confirm which auditors are reachable.");
259
+ console.log(" Privacy: everything stays local. See NO_TELEMETRY.md.");
260
+ process.exit(0);
261
+ }
262
+ function isDirectRun() {
263
+ try {
264
+ const entry = process.argv[1] && realpathSync(process.argv[1]);
265
+ const self = fileURLToPath(import.meta.url);
266
+ return entry === self;
267
+ } catch {
268
+ return false;
269
+ }
270
+ }
271
+ if (isDirectRun()) {
272
+ main().catch((e) => {
273
+ console.error(e.message || String(e));
274
+ process.exit(1);
275
+ });
276
+ }
277
+ export {
278
+ resolveBranchOrTag
279
+ };
@@ -0,0 +1,259 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/uninstall.js
4
+ import { existsSync as existsSync2, rmSync, cpSync, mkdtempSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2, readdirSync } from "node:fs";
5
+ import { resolve, join as join2 } from "node:path";
6
+ import { homedir as homedir2, tmpdir } from "node:os";
7
+
8
+ // src/marketplace.js
9
+ import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync } from "node:fs";
10
+ import { dirname, join } from "node:path";
11
+ import { homedir } from "node:os";
12
+ function claudeSettingsPath() {
13
+ return join(homedir(), ".claude", "settings.json");
14
+ }
15
+ function stripJsoncComments(raw) {
16
+ if (raw.charCodeAt(0) === 65279) raw = raw.slice(1);
17
+ let out = "";
18
+ let i = 0;
19
+ const n = raw.length;
20
+ const isLineBreak = (code) => code === 10 || code === 13 || code === 8232 || code === 8233;
21
+ while (i < n) {
22
+ const c = raw[i];
23
+ const c2 = raw[i + 1];
24
+ if (c === '"') {
25
+ out += c;
26
+ i++;
27
+ while (i < n) {
28
+ const k = raw[i];
29
+ out += k;
30
+ if (k === "\\" && i + 1 < n) {
31
+ out += raw[i + 1];
32
+ i += 2;
33
+ continue;
34
+ }
35
+ i++;
36
+ if (k === '"') break;
37
+ }
38
+ continue;
39
+ }
40
+ if (c === "/" && c2 === "/") {
41
+ i += 2;
42
+ while (i < n && !isLineBreak(raw.charCodeAt(i))) i++;
43
+ continue;
44
+ }
45
+ if (c === "/" && c2 === "*") {
46
+ i += 2;
47
+ while (i < n && !(raw[i] === "*" && raw[i + 1] === "/")) i++;
48
+ if (i < n) i += 2;
49
+ continue;
50
+ }
51
+ out += c;
52
+ i++;
53
+ }
54
+ return out;
55
+ }
56
+ function tolerantJsonParse(raw, filepath) {
57
+ try {
58
+ return JSON.parse(raw);
59
+ } catch {
60
+ }
61
+ const stripped = stripJsoncComments(raw).replace(/,(\s*[}\]])/g, "$1");
62
+ try {
63
+ return JSON.parse(stripped);
64
+ } catch (e) {
65
+ const err = new Error(`settings.json at ${filepath} is not valid JSON or recoverable JSONC: ${e.message}`);
66
+ err.code = "IJFW_SETTINGS_UNPARSEABLE";
67
+ throw err;
68
+ }
69
+ }
70
+ function unmergeMarketplace(settingsPath = claudeSettingsPath()) {
71
+ if (!existsSync(settingsPath)) return null;
72
+ const settings = tolerantJsonParse(readFileSync(settingsPath, "utf8"), settingsPath);
73
+ if (settings.extraKnownMarketplaces?.ijfw) delete settings.extraKnownMarketplaces.ijfw;
74
+ if (settings.enabledPlugins) {
75
+ if ("ijfw-core@ijfw" in settings.enabledPlugins) delete settings.enabledPlugins["ijfw-core@ijfw"];
76
+ if ("ijfw@ijfw" in settings.enabledPlugins) delete settings.enabledPlugins["ijfw@ijfw"];
77
+ }
78
+ const tmp = settingsPath + ".tmp";
79
+ writeFileSync(tmp, JSON.stringify(settings, null, 2) + "\n");
80
+ renameSync(tmp, settingsPath);
81
+ return settings;
82
+ }
83
+
84
+ // src/uninstall.js
85
+ function parseArgs(argv) {
86
+ const out = { dir: null, purge: false, noMarketplace: false };
87
+ for (let i = 2; i < argv.length; i++) {
88
+ const a = argv[i];
89
+ if (a === "--dir") out.dir = argv[++i];
90
+ else if (a === "--purge") out.purge = true;
91
+ else if (a === "--no-marketplace") out.noMarketplace = true;
92
+ else if (a === "--help" || a === "-h") {
93
+ printHelp();
94
+ process.exit(0);
95
+ }
96
+ }
97
+ return out;
98
+ }
99
+ function printHelp() {
100
+ console.log(`ijfw-uninstall -- reverse IJFW install
101
+ Usage: ijfw-uninstall [--dir <path>] [--purge] [--no-marketplace]
102
+ --purge also remove memory/ (destructive)
103
+ --no-marketplace skip ~/.claude/settings.json edits
104
+ `);
105
+ }
106
+ var HOME = homedir2();
107
+ var TS = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
108
+ function backupFile(p) {
109
+ if (existsSync2(p)) {
110
+ const bak = p + ".bak." + TS;
111
+ cpSync(p, bak);
112
+ return bak;
113
+ }
114
+ return null;
115
+ }
116
+ function removeTomlSection(p) {
117
+ if (!existsSync2(p)) return false;
118
+ backupFile(p);
119
+ const lines = readFileSync2(p, "utf8").split("\n");
120
+ const out = [];
121
+ let skip = false;
122
+ for (const line of lines) {
123
+ if (/^\[mcp_servers\.ijfw-memory\]\s*$/.test(line)) {
124
+ skip = true;
125
+ continue;
126
+ }
127
+ if (skip && line.startsWith("[") && !line.startsWith("[mcp_servers.ijfw-memory]")) skip = false;
128
+ if (!skip) out.push(line);
129
+ }
130
+ writeFileSync2(p, out.join("\n"));
131
+ return true;
132
+ }
133
+ function removeJsonMcpEntry(p) {
134
+ if (!existsSync2(p)) return false;
135
+ let doc;
136
+ try {
137
+ doc = JSON.parse(readFileSync2(p, "utf8"));
138
+ } catch {
139
+ return false;
140
+ }
141
+ if (!doc || typeof doc !== "object") return false;
142
+ let changed = false;
143
+ if (doc.mcpServers && doc.mcpServers["ijfw-memory"]) {
144
+ backupFile(p);
145
+ delete doc.mcpServers["ijfw-memory"];
146
+ writeFileSync2(p, JSON.stringify(doc, null, 2) + "\n");
147
+ changed = true;
148
+ }
149
+ return changed;
150
+ }
151
+ function removeCodexHooks(p) {
152
+ if (!existsSync2(p)) return false;
153
+ let doc;
154
+ try {
155
+ doc = JSON.parse(readFileSync2(p, "utf8"));
156
+ } catch {
157
+ return false;
158
+ }
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;
163
+ backupFile(p);
164
+ writeFileSync2(p, JSON.stringify(doc, null, 2) + "\n");
165
+ return true;
166
+ }
167
+ function removeIjfwSkills(dir) {
168
+ if (!existsSync2(dir)) return 0;
169
+ let count = 0;
170
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
171
+ if (entry.isDirectory() && entry.name.startsWith("ijfw-")) {
172
+ rmSync(join2(dir, entry.name), { recursive: true, force: true });
173
+ count++;
174
+ }
175
+ }
176
+ return count;
177
+ }
178
+ function cleanPlatforms() {
179
+ const removed = [];
180
+ if (removeTomlSection(join2(HOME, ".codex", "config.toml"))) {
181
+ removed.push("~/.codex/config.toml (removed [mcp_servers.ijfw-memory])");
182
+ }
183
+ if (removeCodexHooks(join2(HOME, ".codex", "hooks.json"))) {
184
+ removed.push("~/.codex/hooks.json (removed IJFW hook entries)");
185
+ }
186
+ const codexSkills = removeIjfwSkills(join2(HOME, ".codex", "skills"));
187
+ if (codexSkills > 0) removed.push(`~/.codex/skills/ijfw-* (removed ${codexSkills} skill dirs)`);
188
+ const codexMd = join2(HOME, ".codex", "IJFW.md");
189
+ if (existsSync2(codexMd)) {
190
+ rmSync(codexMd, { force: true });
191
+ removed.push("~/.codex/IJFW.md");
192
+ }
193
+ if (removeJsonMcpEntry(join2(HOME, ".gemini", "settings.json"))) {
194
+ removed.push("~/.gemini/settings.json (removed ijfw-memory)");
195
+ }
196
+ const geminiExt = join2(HOME, ".gemini", "extensions", "ijfw");
197
+ if (existsSync2(geminiExt)) {
198
+ rmSync(geminiExt, { recursive: true, force: true });
199
+ removed.push("~/.gemini/extensions/ijfw/");
200
+ }
201
+ const cursorMcp = join2(".cursor", "mcp.json");
202
+ if (removeJsonMcpEntry(cursorMcp)) removed.push(".cursor/mcp.json (removed ijfw-memory)");
203
+ if (removeJsonMcpEntry(join2(HOME, ".codeium", "windsurf", "mcp_config.json"))) {
204
+ removed.push("~/.codeium/windsurf/mcp_config.json (removed ijfw-memory)");
205
+ }
206
+ const vscodeMcp = join2(".vscode", "mcp.json");
207
+ if (removeJsonMcpEntry(vscodeMcp)) removed.push(".vscode/mcp.json (removed ijfw-memory)");
208
+ return removed;
209
+ }
210
+ function resolveTarget(opt) {
211
+ if (opt.dir) return resolve(opt.dir);
212
+ if (process.env.IJFW_HOME) return resolve(process.env.IJFW_HOME);
213
+ return join2(homedir2(), ".ijfw");
214
+ }
215
+ async function main() {
216
+ const opts = parseArgs(process.argv);
217
+ const target = resolveTarget(opts);
218
+ console.log("This will remove IJFW configuration. Your memory at ~/.ijfw/memory/ will be preserved. Delete manually if desired.");
219
+ console.log("");
220
+ if (!existsSync2(target)) {
221
+ console.log(`IJFW directory absent (${target}); platform cleanup only.`);
222
+ } else if (opts.purge) {
223
+ rmSync(target, { recursive: true, force: true });
224
+ console.log(` removed ${target} (purged).`);
225
+ } else {
226
+ const memDir = join2(target, "memory");
227
+ let stash = null;
228
+ if (existsSync2(memDir)) {
229
+ stash = mkdtempSync(join2(tmpdir(), "ijfw-memory-"));
230
+ cpSync(memDir, stash, { recursive: true });
231
+ }
232
+ rmSync(target, { recursive: true, force: true });
233
+ if (stash) {
234
+ cpSync(stash, memDir, { recursive: true });
235
+ rmSync(stash, { recursive: true, force: true });
236
+ console.log(` memory/ preserved at ${memDir}`);
237
+ } else {
238
+ console.log(" memory/ was not present; nothing to preserve");
239
+ }
240
+ }
241
+ if (!opts.noMarketplace) {
242
+ const settingsPath = claudeSettingsPath();
243
+ if (existsSync2(settingsPath)) {
244
+ unmergeMarketplace(settingsPath);
245
+ console.log(` marketplace removed from ${settingsPath}`);
246
+ }
247
+ }
248
+ const cleaned = cleanPlatforms();
249
+ if (cleaned.length > 0) {
250
+ console.log(" platform configs cleaned:");
251
+ for (const line of cleaned) console.log(` ${line}`);
252
+ }
253
+ console.log("\nIJFW uninstalled. Thanks for trying it.");
254
+ process.exit(0);
255
+ }
256
+ main().catch((e) => {
257
+ console.error(e.message || String(e));
258
+ process.exit(1);
259
+ });
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@ijfw/install",
3
+ "version": "1.1.0",
4
+ "description": "One-command installer for IJFW -- the AI efficiency layer. One install, every AI coding agent, zero config.",
5
+ "type": "module",
6
+ "bin": {
7
+ "ijfw": "dist/ijfw.js",
8
+ "ijfw-install": "dist/install.js",
9
+ "ijfw-uninstall": "dist/uninstall.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "src/install.ps1",
14
+ "README.md",
15
+ "CHANGELOG.md",
16
+ "LICENSE"
17
+ ],
18
+ "scripts": {
19
+ "build": "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",
20
+ "test": "node --test test.js",
21
+ "preflight": "node dist/ijfw.js preflight",
22
+ "pack:check": "npm pack --dry-run",
23
+ "prepublishOnly": "npm run build && npm run preflight"
24
+ },
25
+ "dependencies": {},
26
+ "devDependencies": {
27
+ "esbuild": "^0.25.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=18"
31
+ },
32
+ "keywords": [
33
+ "ijfw",
34
+ "claude-code",
35
+ "codex",
36
+ "gemini-cli",
37
+ "cursor",
38
+ "windsurf",
39
+ "copilot",
40
+ "mcp",
41
+ "ai-agents",
42
+ "memory",
43
+ "installer"
44
+ ],
45
+ "license": "MIT",
46
+ "author": "Sean Donahoe",
47
+ "homepage": "https://github.com/TheRealSeanDonahoe/ijfw",
48
+ "bugs": {
49
+ "url": "https://github.com/TheRealSeanDonahoe/ijfw/issues"
50
+ },
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/TheRealSeanDonahoe/ijfw.git"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public"
57
+ }
58
+ }