@ijfw/memory-server 1.6.0 → 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/package.json +1 -1
- package/src/cross-orchestrator-cli.js +46 -0
- package/src/dashboard-server.js +16 -0
- package/src/model-refresh.js +4 -2
- package/src/server.js +11 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ijfw/memory-server",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "Cross-platform persistent memory server for IJFW. 14 MCP tools (memory + admin/update + brain). Works with 15 platforms: 14 via MCP (Claude Code, Codex, Gemini CLI, Cursor, Windsurf, Copilot, Hermes, Wayland, OpenCode, QwenCode, Cline, KimiCode, OpenClaw, Antigravity) plus Aider via the rules-only tier.",
|
|
5
5
|
"author": "Sean Donahoe",
|
|
6
6
|
"contributors": [
|
|
@@ -319,6 +319,10 @@ function parseArgsInner(args) {
|
|
|
319
319
|
return { cmd: 'doctor' };
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
+
if (args[0] === 'init') {
|
|
323
|
+
return { cmd: 'init', force: args.includes('--force') };
|
|
324
|
+
}
|
|
325
|
+
|
|
322
326
|
if (args[0] === 'update') {
|
|
323
327
|
const opts = { cmd: 'update' };
|
|
324
328
|
for (let i = 1; i < args.length; i++) {
|
|
@@ -2865,6 +2869,8 @@ if (isMainModule) {
|
|
|
2865
2869
|
cmdImport(parsed).catch(err => { console.error(err.message); process.exit(1); });
|
|
2866
2870
|
} else if (parsed.cmd === 'doctor') {
|
|
2867
2871
|
cmdDoctor(parsed);
|
|
2872
|
+
} else if (parsed.cmd === 'init') {
|
|
2873
|
+
cmdInit(parsed);
|
|
2868
2874
|
} else if (parsed.cmd === 'update') {
|
|
2869
2875
|
cmdUpdate(parsed);
|
|
2870
2876
|
} else if (parsed.cmd === 'version') {
|
|
@@ -2954,6 +2960,46 @@ function findCliAsset(...rel) {
|
|
|
2954
2960
|
].filter(Boolean);
|
|
2955
2961
|
return candidates.find(p => existsSync(p)) || null;
|
|
2956
2962
|
}
|
|
2963
|
+
// `ijfw init` -- explicitly bless the current folder for codebase indexing.
|
|
2964
|
+
// The indexer (scripts/build-codebase-index.sh) refuses any folder that has no
|
|
2965
|
+
// project marker (issue #16). For a plain working folder with no .git/package.json
|
|
2966
|
+
// etc, this drops a .ijfw/project marker so the indexer will index it. It will
|
|
2967
|
+
// NOT bless the home directory or filesystem root -- that is the whole point of
|
|
2968
|
+
// the guard.
|
|
2969
|
+
function cmdInit(parsed = {}) {
|
|
2970
|
+
const cwd = process.cwd();
|
|
2971
|
+
let phys;
|
|
2972
|
+
try { phys = realpathSync(cwd); } catch { phys = resolve(cwd); }
|
|
2973
|
+
let homePhys;
|
|
2974
|
+
try { homePhys = realpathSync(homedir()); } catch { homePhys = homedir(); }
|
|
2975
|
+
if (phys === '/' || phys === homePhys) {
|
|
2976
|
+
console.error('ijfw init: refusing to bless your home directory or the filesystem root for indexing.');
|
|
2977
|
+
console.error('Run `ijfw init` from inside an actual project folder.');
|
|
2978
|
+
process.exit(1);
|
|
2979
|
+
}
|
|
2980
|
+
const marker = join(cwd, '.ijfw', 'project');
|
|
2981
|
+
try {
|
|
2982
|
+
mkdirSync(dirname(marker), { recursive: true });
|
|
2983
|
+
if (existsSync(marker) && !parsed.force) {
|
|
2984
|
+
console.log(`This folder is already initialised for IJFW indexing (${marker}).`);
|
|
2985
|
+
process.exit(0);
|
|
2986
|
+
}
|
|
2987
|
+
const stamp = new Date().toISOString();
|
|
2988
|
+
writeFileSync(
|
|
2989
|
+
marker,
|
|
2990
|
+
`# IJFW project marker\n` +
|
|
2991
|
+
`# Created by \`ijfw init\`. This folder is approved for codebase indexing.\n` +
|
|
2992
|
+
`# Safe to commit. Delete this file to stop IJFW indexing this folder.\n` +
|
|
2993
|
+
`created_at: ${stamp}\n`,
|
|
2994
|
+
{ mode: 0o644 }
|
|
2995
|
+
);
|
|
2996
|
+
console.log('IJFW initialised. This folder is now approved for codebase indexing.');
|
|
2997
|
+
console.log(`Marker: ${marker}`);
|
|
2998
|
+
} catch (err) {
|
|
2999
|
+
console.error(`ijfw init: could not write marker -- ${err.message}`);
|
|
3000
|
+
process.exit(1);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
2957
3003
|
function cmdInstall() {
|
|
2958
3004
|
const script = findCliAsset('scripts', 'install.sh');
|
|
2959
3005
|
if (!script) {
|
package/src/dashboard-server.js
CHANGED
|
@@ -174,10 +174,26 @@ function requireLocalhost(req, res) {
|
|
|
174
174
|
return false;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
// CSRF guard: reject cross-origin browser requests to the data API. Browsers
|
|
178
|
+
// stamp Sec-Fetch-Site; the dashboard's own page is 'same-origin', direct tools
|
|
179
|
+
// (curl, address bar) send 'none'/nothing. Only same-machine cross-origin pages
|
|
180
|
+
// hit 'cross-site'/'same-site' -- block those on /api.
|
|
181
|
+
function rejectCrossSiteApi(req, res, path) {
|
|
182
|
+
if (!path.startsWith('/api')) return false;
|
|
183
|
+
const sfs = req.headers['sec-fetch-site'];
|
|
184
|
+
if (sfs === 'cross-site' || sfs === 'same-site') {
|
|
185
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
186
|
+
res.end('{"error":"cross-origin request rejected"}');
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
177
192
|
// ---------- simple router ----------
|
|
178
193
|
function route(req, res, routes) {
|
|
179
194
|
const url = new URL(req.url, 'http://localhost');
|
|
180
195
|
const path = url.pathname;
|
|
196
|
+
if (rejectCrossSiteApi(req, res, path)) return;
|
|
181
197
|
for (const [pattern, handler] of routes) {
|
|
182
198
|
if (typeof pattern === 'string' ? path === pattern : pattern.test(path)) {
|
|
183
199
|
handler(req, res, url);
|
package/src/model-refresh.js
CHANGED
|
@@ -232,9 +232,11 @@ async function probeGoogle(env, fetchImpl) {
|
|
|
232
232
|
if (!key) return null;
|
|
233
233
|
const { signal, cancel } = makeAbortable();
|
|
234
234
|
try {
|
|
235
|
+
// Pass the key as a header, not a URL query param, so it never lands in
|
|
236
|
+
// proxy / CDN / firewall access logs (privacy audit finding).
|
|
235
237
|
const r = await fetchImpl(
|
|
236
|
-
|
|
237
|
-
{ signal },
|
|
238
|
+
'https://generativelanguage.googleapis.com/v1beta/models',
|
|
239
|
+
{ signal, headers: { 'x-goog-api-key': key } },
|
|
238
240
|
);
|
|
239
241
|
if (!r.ok) return null;
|
|
240
242
|
const json = await r.json();
|
package/src/server.js
CHANGED
|
@@ -2117,7 +2117,7 @@ function handleMessage(msg) {
|
|
|
2117
2117
|
try {
|
|
2118
2118
|
const fs = await import('node:fs');
|
|
2119
2119
|
const path = await import('node:path');
|
|
2120
|
-
const home = process.env.IJFW_HOME || path.join(process.env.HOME ||
|
|
2120
|
+
const home = process.env.IJFW_HOME || path.join(process.env.HOME || homedir(), '.ijfw');
|
|
2121
2121
|
const s = JSON.parse(fs.readFileSync(path.join(home, 'settings.json'), 'utf8'));
|
|
2122
2122
|
injectOn = s && s.profile && s.profile.inject === 'on';
|
|
2123
2123
|
} catch { injectOn = false; }
|
|
@@ -2424,9 +2424,16 @@ function handleMessage(msg) {
|
|
|
2424
2424
|
case 'ijfw_metrics':
|
|
2425
2425
|
result = handleMetrics(args || {});
|
|
2426
2426
|
break;
|
|
2427
|
-
case 'ijfw_cross_project_search':
|
|
2428
|
-
|
|
2427
|
+
case 'ijfw_cross_project_search': {
|
|
2428
|
+
// Privacy opt-out: a dedicated off-switch + the IJFW_MINIMAL master
|
|
2429
|
+
// switch disable cross-project memory surfacing entirely.
|
|
2430
|
+
const xpOff = /^(1|true|yes|on)$/i.test(process.env.IJFW_NO_CROSS_PROJECT || '')
|
|
2431
|
+
|| process.env.IJFW_MINIMAL === '1';
|
|
2432
|
+
result = xpOff
|
|
2433
|
+
? { text: 'Cross-project search is disabled (IJFW_NO_CROSS_PROJECT / IJFW_MINIMAL).' }
|
|
2434
|
+
: handleCrossProjectSearch(args || {});
|
|
2429
2435
|
break;
|
|
2436
|
+
}
|
|
2430
2437
|
case 'ijfw_prompt_check': {
|
|
2431
2438
|
const pc = checkPrompt((args && args.prompt) || '');
|
|
2432
2439
|
const text = pc.vague
|
|
@@ -2550,7 +2557,7 @@ function handleMessage(msg) {
|
|
|
2550
2557
|
try {
|
|
2551
2558
|
const fs = await import('node:fs');
|
|
2552
2559
|
const path = await import('node:path');
|
|
2553
|
-
const home = process.env.IJFW_HOME || path.join(process.env.HOME ||
|
|
2560
|
+
const home = process.env.IJFW_HOME || path.join(process.env.HOME || homedir(), '.ijfw');
|
|
2554
2561
|
const s = JSON.parse(fs.readFileSync(path.join(home, 'settings.json'), 'utf8'));
|
|
2555
2562
|
injectOn = s && s.profile && s.profile.inject === 'on';
|
|
2556
2563
|
} catch { injectOn = false; }
|