cursor-guard 4.9.9 → 4.9.15
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/README.md +697 -697
- package/README.zh-CN.md +696 -696
- package/ROADMAP.md +1775 -1720
- package/SKILL.md +631 -629
- package/docs/RELEASE.md +197 -196
- package/docs/SNAPSHOT-BOOKMARK.md +47 -0
- package/package.json +70 -69
- package/references/dashboard/public/app.js +2079 -1832
- package/references/dashboard/public/style.css +1660 -1573
- package/references/dashboard/server.js +197 -4
- package/references/lib/core/backups.js +509 -492
- package/references/lib/core/core.test.js +1761 -1616
- package/references/lib/core/snapshot.js +441 -369
- package/references/mcp/mcp.test.js +381 -362
- package/references/mcp/server.js +404 -347
- package/references/vscode-extension/dist/{cursor-guard-ide-4.9.9.vsix → cursor-guard-ide-4.9.15.vsix} +0 -0
- package/references/vscode-extension/dist/dashboard/public/app.js +2079 -1832
- package/references/vscode-extension/dist/dashboard/public/style.css +1660 -1573
- package/references/vscode-extension/dist/dashboard/server.js +197 -4
- package/references/vscode-extension/dist/extension.js +780 -704
- package/references/vscode-extension/dist/guard-version.json +1 -1
- package/references/vscode-extension/dist/lib/auto-setup.js +201 -192
- package/references/vscode-extension/dist/lib/core/backups.js +509 -492
- package/references/vscode-extension/dist/lib/core/snapshot.js +441 -369
- package/references/vscode-extension/dist/lib/poller.js +161 -21
- package/references/vscode-extension/dist/lib/sidebar-webview.js +22 -0
- package/references/vscode-extension/dist/mcp/server.js +152 -35
- package/references/vscode-extension/dist/package.json +7 -1
- package/references/vscode-extension/dist/skill/ROADMAP.md +1775 -1720
- package/references/vscode-extension/dist/skill/SKILL.md +631 -629
- package/references/vscode-extension/extension.js +780 -704
- package/references/vscode-extension/lib/auto-setup.js +201 -192
- package/references/vscode-extension/lib/poller.js +161 -21
- package/references/vscode-extension/lib/sidebar-webview.js +22 -0
- package/references/vscode-extension/package.json +146 -140
|
@@ -14,6 +14,7 @@ function coreDeps() {
|
|
|
14
14
|
runDiagnostics: require('../lib/core/doctor').runDiagnostics,
|
|
15
15
|
listBackups: require('../lib/core/backups').listBackups,
|
|
16
16
|
getBackupFiles: require('../lib/core/backups').getBackupFiles,
|
|
17
|
+
clearAlert: require('../lib/core/anomaly').clearAlert,
|
|
17
18
|
};
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -196,7 +197,21 @@ function handleApi(pathname, query, registry, res, req) {
|
|
|
196
197
|
return json(res, { error: `Project directory not accessible: ${project.pathLabel}` }, 500);
|
|
197
198
|
}
|
|
198
199
|
|
|
199
|
-
const { getDashboard, runDiagnostics, listBackups, getBackupFiles } = coreDeps();
|
|
200
|
+
const { getDashboard, runDiagnostics, listBackups, getBackupFiles, clearAlert } = coreDeps();
|
|
201
|
+
|
|
202
|
+
if (pathname === '/api/dismiss-alert') {
|
|
203
|
+
if (req.method !== 'POST') {
|
|
204
|
+
res.writeHead(405);
|
|
205
|
+
return res.end('Method Not Allowed');
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
clearAlert(pp);
|
|
209
|
+
if (_instance?.hub && id) broadcastGuardChanged(_instance.hub, id);
|
|
210
|
+
return json(res, { ok: true });
|
|
211
|
+
} catch (e) {
|
|
212
|
+
return json(res, { ok: false, error: e.message }, 500);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
200
215
|
|
|
201
216
|
if (pathname === '/api/page-data') {
|
|
202
217
|
const scope = query.get('scope');
|
|
@@ -245,6 +260,164 @@ function handleApi(pathname, query, registry, res, req) {
|
|
|
245
260
|
return notFound(res);
|
|
246
261
|
}
|
|
247
262
|
|
|
263
|
+
/* ── Live push (SSE) + fs.watch on Guard refs / backups ─────── */
|
|
264
|
+
|
|
265
|
+
function broadcastGuardChanged(hub, projectId) {
|
|
266
|
+
if (!hub?.sseClients?.size) return;
|
|
267
|
+
const line = `data: ${JSON.stringify({ type: 'guard-changed', projectId })}\n\n`;
|
|
268
|
+
for (const c of hub.sseClients) {
|
|
269
|
+
try {
|
|
270
|
+
if (c.projectId && c.projectId !== projectId) continue;
|
|
271
|
+
c.res.write(line);
|
|
272
|
+
} catch {
|
|
273
|
+
hub.sseClients.delete(c);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function teardownGuardWatchersOnly(hub) {
|
|
279
|
+
if (hub._reattachTimer) {
|
|
280
|
+
clearTimeout(hub._reattachTimer);
|
|
281
|
+
hub._reattachTimer = null;
|
|
282
|
+
}
|
|
283
|
+
if (hub.guardDebounce) {
|
|
284
|
+
for (const t of hub.guardDebounce.values()) clearTimeout(t);
|
|
285
|
+
hub.guardDebounce.clear();
|
|
286
|
+
}
|
|
287
|
+
for (const w of hub.guardWatchers) {
|
|
288
|
+
try { w.close(); } catch { /* ignore */ }
|
|
289
|
+
}
|
|
290
|
+
hub.guardWatchers = [];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function tryWatchDirRecursive(dir, cb) {
|
|
294
|
+
try {
|
|
295
|
+
return fs.watch(dir, { recursive: true }, cb);
|
|
296
|
+
} catch {
|
|
297
|
+
try {
|
|
298
|
+
return fs.watch(dir, cb);
|
|
299
|
+
} catch {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* When Git refs under refs/guard/, shadow backup dir, or alert/lock change, notify SSE clients.
|
|
307
|
+
*/
|
|
308
|
+
function attachGuardWatchers(hub) {
|
|
309
|
+
const { isGitRepo, gitDir: getGitDir } = require('../lib/utils');
|
|
310
|
+
teardownGuardWatchersOnly(hub);
|
|
311
|
+
if (!hub.guardDebounce) hub.guardDebounce = new Map();
|
|
312
|
+
|
|
313
|
+
function schedule(projectId) {
|
|
314
|
+
const prev = hub.guardDebounce.get(projectId);
|
|
315
|
+
if (prev) clearTimeout(prev);
|
|
316
|
+
hub.guardDebounce.set(projectId, setTimeout(() => {
|
|
317
|
+
hub.guardDebounce.delete(projectId);
|
|
318
|
+
broadcastGuardChanged(hub, projectId);
|
|
319
|
+
}, 400));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
for (const proj of hub.registry.values()) {
|
|
323
|
+
const pp = proj._path;
|
|
324
|
+
const pid = proj.id;
|
|
325
|
+
|
|
326
|
+
if (isGitRepo(pp)) {
|
|
327
|
+
const gDir = getGitDir(pp);
|
|
328
|
+
if (gDir && fs.existsSync(gDir)) {
|
|
329
|
+
try {
|
|
330
|
+
const w = fs.watch(gDir, (ev, fname) => {
|
|
331
|
+
if (fname === 'cursor-guard-alert.json' || fname === 'cursor-guard.lock') schedule(pid);
|
|
332
|
+
});
|
|
333
|
+
hub.guardWatchers.push(w);
|
|
334
|
+
} catch { /* ignore */ }
|
|
335
|
+
|
|
336
|
+
const refsDir = path.join(gDir, 'refs');
|
|
337
|
+
if (fs.existsSync(refsDir)) {
|
|
338
|
+
try {
|
|
339
|
+
const w = fs.watch(refsDir, (ev, fname) => {
|
|
340
|
+
if (!fname) return;
|
|
341
|
+
if (fname === 'guard' || fname.startsWith('guard')) {
|
|
342
|
+
schedule(pid);
|
|
343
|
+
if (!hub._reattachTimer) {
|
|
344
|
+
hub._reattachTimer = setTimeout(() => {
|
|
345
|
+
hub._reattachTimer = null;
|
|
346
|
+
attachGuardWatchers(hub);
|
|
347
|
+
}, 600);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
hub.guardWatchers.push(w);
|
|
352
|
+
} catch { /* ignore */ }
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const guardDir = path.join(gDir, 'refs', 'guard');
|
|
356
|
+
if (fs.existsSync(guardDir)) {
|
|
357
|
+
const w = tryWatchDirRecursive(guardDir, () => schedule(pid));
|
|
358
|
+
if (w) hub.guardWatchers.push(w);
|
|
359
|
+
const preRestoreDir = path.join(guardDir, 'pre-restore');
|
|
360
|
+
if (fs.existsSync(preRestoreDir)) {
|
|
361
|
+
const w2 = tryWatchDirRecursive(preRestoreDir, () => schedule(pid));
|
|
362
|
+
if (w2) hub.guardWatchers.push(w2);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const backupDir = path.join(pp, '.cursor-guard-backup');
|
|
369
|
+
if (fs.existsSync(backupDir)) {
|
|
370
|
+
const w = tryWatchDirRecursive(backupDir, () => schedule(pid));
|
|
371
|
+
if (w) hub.guardWatchers.push(w);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function teardownLivePush(hub) {
|
|
377
|
+
teardownGuardWatchersOnly(hub);
|
|
378
|
+
for (const c of hub.sseClients) {
|
|
379
|
+
try { c.res.end(); } catch { /* ignore */ }
|
|
380
|
+
}
|
|
381
|
+
hub.sseClients.clear();
|
|
382
|
+
if (hub.ssePingTimer) {
|
|
383
|
+
clearInterval(hub.ssePingTimer);
|
|
384
|
+
hub.ssePingTimer = null;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function openSseStream(query, hub, res, req) {
|
|
389
|
+
const subscribeId = query.get('id') || null;
|
|
390
|
+
res.writeHead(200, {
|
|
391
|
+
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
392
|
+
'Cache-Control': 'no-store, no-cache',
|
|
393
|
+
'Connection': 'keep-alive',
|
|
394
|
+
'X-Accel-Buffering': 'no',
|
|
395
|
+
'Access-Control-Allow-Origin': '*',
|
|
396
|
+
});
|
|
397
|
+
res.write(': connected\n\n');
|
|
398
|
+
const client = { res, projectId: subscribeId };
|
|
399
|
+
hub.sseClients.add(client);
|
|
400
|
+
req.on('close', () => {
|
|
401
|
+
hub.sseClients.delete(client);
|
|
402
|
+
if (hub.sseClients.size === 0 && hub.ssePingTimer) {
|
|
403
|
+
clearInterval(hub.ssePingTimer);
|
|
404
|
+
hub.ssePingTimer = null;
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
if (!hub.ssePingTimer) {
|
|
408
|
+
hub.ssePingTimer = setInterval(() => {
|
|
409
|
+
if (hub.sseClients.size === 0) return;
|
|
410
|
+
for (const c of hub.sseClients) {
|
|
411
|
+
try {
|
|
412
|
+
c.res.write(': ping\n\n');
|
|
413
|
+
} catch {
|
|
414
|
+
hub.sseClients.delete(c);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}, 25000);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
248
421
|
/* ── Server (singleton) ─────────────────────────────────────── */
|
|
249
422
|
|
|
250
423
|
let _instance = null;
|
|
@@ -280,8 +453,11 @@ function _mergeProjects(registry, paths) {
|
|
|
280
453
|
function startDashboardServer(paths, opts = {}) {
|
|
281
454
|
if (_instance) {
|
|
282
455
|
const added = _mergeProjects(_instance.registry, paths);
|
|
283
|
-
if (added > 0
|
|
284
|
-
|
|
456
|
+
if (added > 0) {
|
|
457
|
+
attachGuardWatchers(_instance.hub);
|
|
458
|
+
if (!opts.silent) {
|
|
459
|
+
console.log(` [dashboard] Hot-added ${added} project(s) — total: ${_instance.registry.size} on port ${_instance.port}`);
|
|
460
|
+
}
|
|
285
461
|
}
|
|
286
462
|
return Promise.resolve(_instance);
|
|
287
463
|
}
|
|
@@ -290,6 +466,14 @@ function startDashboardServer(paths, opts = {}) {
|
|
|
290
466
|
const silent = opts.silent || false;
|
|
291
467
|
const registry = buildRegistry(paths);
|
|
292
468
|
const token = crypto.randomBytes(16).toString('hex');
|
|
469
|
+
const hub = {
|
|
470
|
+
registry,
|
|
471
|
+
token,
|
|
472
|
+
sseClients: new Set(),
|
|
473
|
+
guardWatchers: [],
|
|
474
|
+
guardDebounce: new Map(),
|
|
475
|
+
ssePingTimer: null,
|
|
476
|
+
};
|
|
293
477
|
|
|
294
478
|
return new Promise((resolve, reject) => {
|
|
295
479
|
let currentPort = port;
|
|
@@ -326,6 +510,13 @@ function startDashboardServer(paths, opts = {}) {
|
|
|
326
510
|
res.writeHead(403);
|
|
327
511
|
return res.end('Forbidden: invalid token');
|
|
328
512
|
}
|
|
513
|
+
if (parsed.pathname === '/api/events') {
|
|
514
|
+
if (req.method !== 'GET') {
|
|
515
|
+
res.writeHead(405);
|
|
516
|
+
return res.end('Method Not Allowed');
|
|
517
|
+
}
|
|
518
|
+
return openSseStream(parsed.searchParams, hub, res, req);
|
|
519
|
+
}
|
|
329
520
|
handleApi(parsed.pathname, parsed.searchParams, registry, res, req);
|
|
330
521
|
} else {
|
|
331
522
|
if (req.method !== 'GET') { res.writeHead(405); return res.end('Method Not Allowed'); }
|
|
@@ -345,7 +536,8 @@ function startDashboardServer(paths, opts = {}) {
|
|
|
345
536
|
|
|
346
537
|
server.on('listening', () => {
|
|
347
538
|
const addr = server.address();
|
|
348
|
-
_instance = { server, port: addr.port, registry, token };
|
|
539
|
+
_instance = { server, port: addr.port, registry, token, hub };
|
|
540
|
+
attachGuardWatchers(hub);
|
|
349
541
|
if (!silent) {
|
|
350
542
|
console.log('');
|
|
351
543
|
console.log(' Cursor Guard Dashboard');
|
|
@@ -371,6 +563,7 @@ async function restartDashboard() {
|
|
|
371
563
|
const paths = [..._instance.registry.values()].map(p => p._path);
|
|
372
564
|
const port = _instance.port;
|
|
373
565
|
|
|
566
|
+
if (_instance.hub) teardownLivePush(_instance.hub);
|
|
374
567
|
_instance.server.close();
|
|
375
568
|
_instance = null;
|
|
376
569
|
|