shieldcortex 3.0.4 → 3.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.
Files changed (92) hide show
  1. package/README.md +5 -2
  2. package/dashboard/.next/standalone/dashboard/.next/BUILD_ID +1 -1
  3. package/dashboard/.next/standalone/dashboard/.next/build-manifest.json +2 -2
  4. package/dashboard/.next/standalone/dashboard/.next/prerender-manifest.json +3 -3
  5. package/dashboard/.next/standalone/dashboard/.next/required-server-files.json +4 -4
  6. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.html +2 -2
  7. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.rsc +1 -1
  8. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.html +1 -1
  15. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.rsc +2 -2
  16. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  17. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  18. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  19. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  20. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  21. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  22. package/dashboard/.next/standalone/dashboard/.next/server/app/index.html +1 -1
  23. package/dashboard/.next/standalone/dashboard/.next/server/app/index.rsc +3 -3
  24. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  25. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_full.segment.rsc +3 -3
  26. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_head.segment.rsc +1 -1
  27. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_index.segment.rsc +2 -2
  28. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  29. package/dashboard/.next/standalone/dashboard/.next/server/app/page/react-loadable-manifest.json +1 -1
  30. package/dashboard/.next/standalone/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
  31. package/dashboard/.next/standalone/dashboard/.next/server/chunks/ssr/dashboard_3051539d._.js +1 -1
  32. package/dashboard/.next/standalone/dashboard/.next/server/pages/404.html +1 -1
  33. package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
  34. package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.js +1 -1
  35. package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.json +1 -1
  36. package/dashboard/.next/standalone/dashboard/.next/static/chunks/0a69eb25d08447ee.js +1 -0
  37. package/dashboard/.next/standalone/dashboard/.next/static/chunks/9232a2d99b47b21f.js +3 -0
  38. package/dashboard/.next/standalone/dashboard/.next/static/chunks/97537d3db46c8467.css +3 -0
  39. package/dashboard/.next/standalone/dashboard/.next/static/chunks/aa6e9b8a52353969.js +9 -0
  40. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-darwin-arm64/lib/sharp-darwin-arm64.node +0 -0
  41. package/dashboard/.next/standalone/{node_modules/@img/sharp-linux-x64 → dashboard/node_modules/@img/sharp-darwin-arm64}/package.json +7 -13
  42. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/README.md +2 -2
  43. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/lib/glib-2.0/include/glibconfig.h +8 -9
  44. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64/lib/libvips-cpp.so.8.17.3 → sharp-libvips-darwin-arm64/lib/libvips-cpp.8.17.3.dylib} +0 -0
  45. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/package.json +5 -11
  46. package/dashboard/.next/standalone/dashboard/server.js +1 -1
  47. package/dashboard/.next/standalone/{dashboard/node_modules/@img/sharp-linux-x64 → node_modules/@img/sharp-darwin-arm64}/package.json +7 -13
  48. package/dashboard/.next/standalone/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/package.json +5 -11
  49. package/dist/api/routes/system.js +67 -2
  50. package/dist/cloud/cli.d.ts +1 -0
  51. package/dist/cloud/cli.js +40 -0
  52. package/dist/cloud/config.d.ts +10 -0
  53. package/dist/cloud/config.js +54 -0
  54. package/dist/cloud/graph-sync.d.ts +45 -0
  55. package/dist/cloud/graph-sync.js +257 -0
  56. package/dist/cloud/memory-sync.d.ts +36 -0
  57. package/dist/cloud/memory-sync.js +183 -0
  58. package/dist/cloud/sync-queue.d.ts +24 -0
  59. package/dist/cloud/sync-queue.js +126 -7
  60. package/dist/database/init.js +24 -0
  61. package/dist/graph/backfill.js +3 -5
  62. package/dist/graph/resolve.d.ts +10 -0
  63. package/dist/graph/resolve.js +63 -1
  64. package/dist/index.d.ts +2 -0
  65. package/dist/index.js +61 -4
  66. package/dist/memory/store.js +45 -5
  67. package/dist/memory/types.d.ts +2 -0
  68. package/dist/service/install.d.ts +1 -0
  69. package/dist/service/install.js +43 -1
  70. package/dist/tools/forget.d.ts +2 -2
  71. package/dist/tools/recall.d.ts +3 -3
  72. package/package.json +1 -1
  73. package/dashboard/.next/standalone/dashboard/.next/static/chunks/313c0d327bbf244a.js +0 -9
  74. package/dashboard/.next/standalone/dashboard/.next/static/chunks/49c1cec591af1460.js +0 -3
  75. package/dashboard/.next/standalone/dashboard/.next/static/chunks/ca21f348cb163905.js +0 -1
  76. package/dashboard/.next/standalone/dashboard/.next/static/chunks/f4ca424319f58dc7.css +0 -3
  77. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/README.md +0 -46
  78. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/glib-2.0/include/glibconfig.h +0 -221
  79. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/index.js +0 -1
  80. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  81. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +0 -42
  82. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +0 -30
  83. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node +0 -0
  84. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linuxmusl-x64/lib/sharp-linuxmusl-x64.node +0 -0
  85. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linuxmusl-x64/package.json +0 -46
  86. package/dashboard/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +0 -42
  87. package/dashboard/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json +0 -46
  88. /package/dashboard/.next/standalone/dashboard/.next/static/{BEvyMAX62LQMyt5iSb-F9 → RnvqrTXo_jN8SuMdaNcIj}/_buildManifest.js +0 -0
  89. /package/dashboard/.next/standalone/dashboard/.next/static/{BEvyMAX62LQMyt5iSb-F9 → RnvqrTXo_jN8SuMdaNcIj}/_clientMiddlewareManifest.json +0 -0
  90. /package/dashboard/.next/standalone/dashboard/.next/static/{BEvyMAX62LQMyt5iSb-F9 → RnvqrTXo_jN8SuMdaNcIj}/_ssgManifest.js +0 -0
  91. /package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/lib/index.js +0 -0
  92. /package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/versions.json +0 -0
@@ -7,6 +7,7 @@ import { dirname, join } from 'path';
7
7
  import { homedir } from 'os';
8
8
  import { fileURLToPath } from 'url';
9
9
  import { execSync } from 'child_process';
10
+ import { randomUUID } from 'crypto';
10
11
  const _currentFile = fileURLToPath(import.meta.url);
11
12
  const _currentDir = dirname(_currentFile);
12
13
  let db = null;
@@ -260,6 +261,25 @@ function runMigrations(database) {
260
261
  if (!columnNames.has('source')) {
261
262
  database.exec("ALTER TABLE memories ADD COLUMN source TEXT DEFAULT 'user:direct'");
262
263
  }
264
+ if (!columnNames.has('uuid')) {
265
+ database.exec("ALTER TABLE memories ADD COLUMN uuid TEXT");
266
+ }
267
+ if (!columnNames.has('updated_at')) {
268
+ database.exec('ALTER TABLE memories ADD COLUMN updated_at TIMESTAMP');
269
+ }
270
+ try {
271
+ const rowsWithoutUuid = database.prepare('SELECT id FROM memories WHERE uuid IS NULL OR uuid = ?').all('');
272
+ const setUuid = database.prepare('UPDATE memories SET uuid = ? WHERE id = ?');
273
+ for (const row of rowsWithoutUuid) {
274
+ setUuid.run(randomUUID(), row.id);
275
+ }
276
+ database.exec('UPDATE memories SET updated_at = COALESCE(updated_at, created_at, CURRENT_TIMESTAMP) WHERE updated_at IS NULL');
277
+ database.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_uuid ON memories(uuid)');
278
+ database.exec('CREATE INDEX IF NOT EXISTS idx_memories_updated ON memories(updated_at DESC)');
279
+ }
280
+ catch {
281
+ // Safe to ignore on partially migrated databases
282
+ }
263
283
  // Migration: Defence tables (defence_audit, quarantine, fragmentation_entities)
264
284
  try {
265
285
  database.exec(`
@@ -673,6 +693,7 @@ function getInlineSchema() {
673
693
  return `
674
694
  CREATE TABLE IF NOT EXISTS memories (
675
695
  id INTEGER PRIMARY KEY AUTOINCREMENT,
696
+ uuid TEXT NOT NULL UNIQUE,
676
697
  type TEXT NOT NULL CHECK(type IN ('short_term', 'long_term', 'episodic')),
677
698
  category TEXT NOT NULL DEFAULT 'note',
678
699
  title TEXT NOT NULL,
@@ -684,6 +705,7 @@ function getInlineSchema() {
684
705
  access_count INTEGER DEFAULT 0,
685
706
  last_accessed TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
686
707
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
708
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
687
709
  metadata TEXT DEFAULT '{}',
688
710
  embedding BLOB,
689
711
  scope TEXT DEFAULT 'project',
@@ -721,11 +743,13 @@ function getInlineSchema() {
721
743
  END;
722
744
 
723
745
  CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
746
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_uuid ON memories(uuid);
724
747
  CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project);
725
748
  CREATE INDEX IF NOT EXISTS idx_memories_category ON memories(category);
726
749
  CREATE INDEX IF NOT EXISTS idx_memories_salience ON memories(salience DESC);
727
750
  CREATE INDEX IF NOT EXISTS idx_memories_decayed_score ON memories(decayed_score DESC);
728
751
  CREATE INDEX IF NOT EXISTS idx_memories_last_accessed ON memories(last_accessed DESC);
752
+ CREATE INDEX IF NOT EXISTS idx_memories_updated ON memories(updated_at DESC);
729
753
 
730
754
  CREATE TABLE IF NOT EXISTS sessions (
731
755
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -1,8 +1,8 @@
1
1
  import { getDatabase } from '../database/init.js';
2
2
  import { extractFromMemory } from './extract.js';
3
- import { processExtractionResult } from './resolve.js';
3
+ import { replaceMemoryGraph } from './resolve.js';
4
4
  /** Bump this when extract.ts logic changes to force re-extraction */
5
- const EXTRACTION_VERSION = 2;
5
+ const EXTRACTION_VERSION = 3;
6
6
  export function backfillGraph(options) {
7
7
  const db = getDatabase();
8
8
  const force = options?.force ?? false;
@@ -32,9 +32,7 @@ export function backfillGraph(options) {
32
32
  for (const mem of memories) {
33
33
  try {
34
34
  const extraction = extractFromMemory(mem.title, mem.content, mem.category);
35
- if (extraction.entities.length > 0) {
36
- processExtractionResult(extraction, mem.id);
37
- }
35
+ replaceMemoryGraph(mem.id, extraction);
38
36
  // Mark this memory as extracted at the current version
39
37
  if (updateVersion) {
40
38
  updateVersion.run(EXTRACTION_VERSION, mem.id);
@@ -2,4 +2,14 @@ import type { EntityType, ExtractionResult } from './extract.js';
2
2
  export declare function levenshtein(a: string, b: string): number;
3
3
  export declare function resolveEntity(name: string, type: EntityType): number;
4
4
  export declare function mergeEntities(keepId: number, removeId: number): void;
5
+ export declare function removeMemoryGraph(memoryId: number): {
6
+ removedEntityLinks: number;
7
+ removedTriples: number;
8
+ orphanEntitiesPruned: number;
9
+ };
10
+ export declare function replaceMemoryGraph(memoryId: number, result: ExtractionResult): {
11
+ removedEntityLinks: number;
12
+ removedTriples: number;
13
+ orphanEntitiesPruned: number;
14
+ };
5
15
  export declare function processExtractionResult(result: ExtractionResult, memoryId: number): void;
@@ -93,7 +93,7 @@ export function mergeEntities(keepId, removeId) {
93
93
  db.prepare('DELETE FROM entities WHERE id = ?').run(removeId);
94
94
  })();
95
95
  }
96
- export function processExtractionResult(result, memoryId) {
96
+ function applyExtractionResult(result, memoryId) {
97
97
  const db = getDatabase();
98
98
  const nameToId = new Map();
99
99
  // 1. Resolve all entities
@@ -123,3 +123,65 @@ export function processExtractionResult(result, memoryId) {
123
123
  updateCount.run(id);
124
124
  }
125
125
  }
126
+ function pruneOrphanEntities(candidateIds) {
127
+ if (candidateIds.length === 0)
128
+ return 0;
129
+ const db = getDatabase();
130
+ const placeholders = candidateIds.map(() => '?').join(', ');
131
+ const orphanRows = db.prepare(`
132
+ SELECT e.id
133
+ FROM entities e
134
+ WHERE e.id IN (${placeholders})
135
+ AND NOT EXISTS (SELECT 1 FROM memory_entities me WHERE me.entity_id = e.id)
136
+ AND NOT EXISTS (SELECT 1 FROM triples t WHERE t.subject_id = e.id OR t.object_id = e.id)
137
+ `).all(...candidateIds);
138
+ if (orphanRows.length === 0)
139
+ return 0;
140
+ const orphanIds = orphanRows.map((row) => row.id);
141
+ const orphanPlaceholders = orphanIds.map(() => '?').join(', ');
142
+ db.prepare(`DELETE FROM entities WHERE id IN (${orphanPlaceholders})`).run(...orphanIds);
143
+ return orphanIds.length;
144
+ }
145
+ function clearMemoryGraphSlice(memoryId) {
146
+ const db = getDatabase();
147
+ const linkedEntityRows = db.prepare(`
148
+ SELECT DISTINCT entity_id
149
+ FROM memory_entities
150
+ WHERE memory_id = ?
151
+ `).all(memoryId);
152
+ const linkedEntityIds = linkedEntityRows.map((row) => row.entity_id);
153
+ const removedTriples = Number(db.prepare('DELETE FROM triples WHERE source_memory_id = ?').run(memoryId).changes ?? 0);
154
+ const removedEntityLinks = Number(db.prepare('DELETE FROM memory_entities WHERE memory_id = ?').run(memoryId).changes ?? 0);
155
+ if (linkedEntityIds.length > 0) {
156
+ const placeholders = linkedEntityIds.map(() => '?').join(', ');
157
+ db.prepare(`
158
+ UPDATE entities
159
+ SET memory_count = CASE WHEN memory_count > 0 THEN memory_count - 1 ELSE 0 END
160
+ WHERE id IN (${placeholders})
161
+ `).run(...linkedEntityIds);
162
+ }
163
+ const orphanEntitiesPruned = pruneOrphanEntities(linkedEntityIds);
164
+ return {
165
+ removedEntityLinks,
166
+ removedTriples,
167
+ orphanEntitiesPruned,
168
+ };
169
+ }
170
+ export function removeMemoryGraph(memoryId) {
171
+ const db = getDatabase();
172
+ return db.transaction(() => clearMemoryGraphSlice(memoryId))();
173
+ }
174
+ export function replaceMemoryGraph(memoryId, result) {
175
+ const db = getDatabase();
176
+ return db.transaction(() => {
177
+ const cleared = clearMemoryGraphSlice(memoryId);
178
+ if (result.entities.length > 0) {
179
+ applyExtractionResult(result, memoryId);
180
+ }
181
+ return cleared;
182
+ })();
183
+ }
184
+ export function processExtractionResult(result, memoryId) {
185
+ const db = getDatabase();
186
+ db.transaction(() => applyExtractionResult(result, memoryId))();
187
+ }
package/dist/index.d.ts CHANGED
@@ -25,6 +25,7 @@
25
25
  * shieldcortex hook session-start # Run session-start hook (for settings.json)
26
26
  * shieldcortex hook session-end # Run session-end hook (for settings.json)
27
27
  * shieldcortex service install # Auto-start dashboard on login
28
+ * shieldcortex service repair # Rebuild auto-start service with current install path
28
29
  * shieldcortex service uninstall # Remove auto-start
29
30
  * shieldcortex service status # Check service status
30
31
  * shieldcortex openclaw install # Install OpenClaw hook
@@ -32,6 +33,7 @@
32
33
  * shieldcortex openclaw status # Check OpenClaw hook status
33
34
  * shieldcortex config --openclaw-auto-memory true # Enable OpenClaw auto-memory extraction
34
35
  * shieldcortex config --openclaw-auto-memory false # Disable OpenClaw auto-memory extraction
36
+ * shieldcortex cloud sync --full # Backfill local memories + graph to ShieldCortex Cloud
35
37
  * shieldcortex copilot install # Configure MCP server for VS Code + Cursor
36
38
  * shieldcortex copilot uninstall # Remove MCP server configuration
37
39
  * shieldcortex copilot status # Check MCP server configuration
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@
25
25
  * shieldcortex hook session-start # Run session-start hook (for settings.json)
26
26
  * shieldcortex hook session-end # Run session-end hook (for settings.json)
27
27
  * shieldcortex service install # Auto-start dashboard on login
28
+ * shieldcortex service repair # Rebuild auto-start service with current install path
28
29
  * shieldcortex service uninstall # Remove auto-start
29
30
  * shieldcortex service status # Check service status
30
31
  * shieldcortex openclaw install # Install OpenClaw hook
@@ -32,6 +33,7 @@
32
33
  * shieldcortex openclaw status # Check OpenClaw hook status
33
34
  * shieldcortex config --openclaw-auto-memory true # Enable OpenClaw auto-memory extraction
34
35
  * shieldcortex config --openclaw-auto-memory false # Disable OpenClaw auto-memory extraction
36
+ * shieldcortex cloud sync --full # Backfill local memories + graph to ShieldCortex Cloud
35
37
  * shieldcortex copilot install # Configure MCP server for VS Code + Cursor
36
38
  * shieldcortex copilot uninstall # Remove MCP server configuration
37
39
  * shieldcortex copilot status # Check MCP server configuration
@@ -47,6 +49,7 @@
47
49
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
48
50
  import { spawn } from 'child_process';
49
51
  import { fileURLToPath } from 'url';
52
+ import http from 'http';
50
53
  import path from 'path';
51
54
  import fs from 'fs';
52
55
  import { createServer } from './server.js';
@@ -295,6 +298,25 @@ function startDashboard() {
295
298
  });
296
299
  return dashboard;
297
300
  }
301
+ function checkLocalHttp(url, expectedStatus = [200], timeoutMs = 1200) {
302
+ return new Promise((resolve) => {
303
+ const req = http.get(url, { timeout: timeoutMs }, (res) => {
304
+ res.resume();
305
+ resolve(Boolean(res.statusCode && expectedStatus.includes(res.statusCode)));
306
+ });
307
+ req.on('timeout', () => {
308
+ req.destroy();
309
+ resolve(false);
310
+ });
311
+ req.on('error', () => resolve(false));
312
+ });
313
+ }
314
+ async function isLocalApiRunning() {
315
+ return checkLocalHttp('http://127.0.0.1:3001/api/health');
316
+ }
317
+ async function isLocalDashboardRunning() {
318
+ return checkLocalHttp('http://127.0.0.1:3030', [200, 307, 308]);
319
+ }
298
320
  /**
299
321
  * Main entry point
300
322
  */
@@ -321,6 +343,7 @@ ${bold}COMMANDS${reset}
321
343
  ${cyan}doctor${reset} Diagnose installation issues
322
344
  ${cyan}quickstart${reset} [target] Detect the fastest setup path
323
345
  ${cyan}config${reset} [options] Configure cloud sync and settings
346
+ ${cyan}cloud${reset} sync --full Backfill local memories + graph to ShieldCortex Cloud
324
347
  ${cyan}license${reset} <action> Manage licence key (activate, status, deactivate)
325
348
  ${cyan}iron-dome${reset} <action> Manage behaviour protection layer
326
349
  ${cyan}audit${reset} [options] Run a full security audit
@@ -343,6 +366,7 @@ ${bold}EXAMPLES${reset}
343
366
  shieldcortex dashboard
344
367
  shieldcortex license activate sc_pro_...
345
368
  shieldcortex config --cloud-enable --cloud-api-key <key>
369
+ shieldcortex cloud sync --full
346
370
 
347
371
  ${bold}DOCS${reset}
348
372
  https://shieldcortex.ai/docs
@@ -423,6 +447,12 @@ ${bold}DOCS${reset}
423
447
  handleCloudConfig(process.argv.slice(3));
424
448
  return;
425
449
  }
450
+ // Handle "cloud" subcommand
451
+ if (process.argv[2] === 'cloud') {
452
+ const { handleCloudCommand } = await import('./cloud/cli.js');
453
+ await handleCloudCommand(process.argv.slice(3));
454
+ return;
455
+ }
426
456
  // Handle "status" subcommand
427
457
  if (process.argv[2] === 'status') {
428
458
  const { handleStatusCommand } = await import('./setup/status.js');
@@ -602,7 +632,7 @@ ${bold}DOCS${reset}
602
632
  const knownCommands = new Set([
603
633
  'doctor', 'quickstart', 'setup', 'install', 'migrate', 'uninstall', 'hook',
604
634
  'openclaw', 'clawdbot', 'copilot', 'service', 'config', 'status',
605
- 'graph', 'license', 'licence', 'audit', 'iron-dome', 'scan',
635
+ 'graph', 'license', 'licence', 'audit', 'iron-dome', 'scan', 'cloud',
606
636
  'scan-skill', 'scan-skills', 'dashboard', 'api',
607
637
  ]);
608
638
  const arg = process.argv[2];
@@ -615,14 +645,36 @@ ${bold}DOCS${reset}
615
645
  let dashboardProcess = null;
616
646
  if (mode === 'api') {
617
647
  // API mode only - for dashboard visualization
648
+ if (await isLocalApiRunning()) {
649
+ console.log('ShieldCortex API is already running at http://localhost:3001');
650
+ return;
651
+ }
618
652
  console.log('Starting ShieldCortex in API mode...');
619
653
  startVisualizationServer(dbPath);
620
654
  }
621
655
  else if (mode === 'dashboard') {
622
656
  // Dashboard mode - API + Next.js dashboard
623
657
  console.log('Starting ShieldCortex with Dashboard...');
624
- startVisualizationServer(dbPath);
625
- dashboardProcess = startDashboard();
658
+ const apiRunning = await isLocalApiRunning();
659
+ const dashboardRunning = await isLocalDashboardRunning();
660
+ if (apiRunning && dashboardRunning) {
661
+ console.log('ShieldCortex dashboard is already running.');
662
+ console.log('Dashboard: http://localhost:3030');
663
+ console.log('API: http://localhost:3001');
664
+ return;
665
+ }
666
+ if (!apiRunning) {
667
+ startVisualizationServer(dbPath);
668
+ }
669
+ else {
670
+ console.log('Reusing existing local ShieldCortex API on http://localhost:3001');
671
+ }
672
+ if (!dashboardRunning) {
673
+ dashboardProcess = startDashboard();
674
+ }
675
+ else {
676
+ console.log('Dashboard UI is already running on http://localhost:3030');
677
+ }
626
678
  // Kill dashboard child process on any exit (prevents orphans)
627
679
  const killDashboard = () => {
628
680
  if (dashboardProcess && !dashboardProcess.killed) {
@@ -648,7 +700,12 @@ ${bold}DOCS${reset}
648
700
  else if (mode === 'both') {
649
701
  // Both modes - API in background, MCP in foreground
650
702
  console.log('Starting ShieldCortex in both modes...');
651
- startVisualizationServer(dbPath);
703
+ if (!(await isLocalApiRunning())) {
704
+ startVisualizationServer(dbPath);
705
+ }
706
+ else {
707
+ console.log('Reusing existing local ShieldCortex API on http://localhost:3001');
708
+ }
652
709
  await startMcpServer(dbPath);
653
710
  }
654
711
  else {
@@ -4,6 +4,7 @@
4
4
  * Core CRUD operations for the memory database.
5
5
  * Handles storage, retrieval, and management of memories.
6
6
  */
7
+ import { randomUUID } from 'crypto';
7
8
  import { getDatabase, isDatabaseInitialized } from '../database/init.js';
8
9
  import { DEFAULT_CONFIG, } from './types.js';
9
10
  import { calculateSalience, suggestCategory, extractTags, } from './salience.js';
@@ -14,9 +15,11 @@ import { emitMemoryCreated, emitMemoryAccessed, emitMemoryDeleted, emitMemoryUpd
14
15
  import { generateEmbedding, cosineSimilarity } from '../embeddings/index.js';
15
16
  import { isPaused } from '../api/control.js';
16
17
  import { extractFromMemory } from '../graph/extract.js';
17
- import { processExtractionResult } from '../graph/resolve.js';
18
+ import { processExtractionResult, removeMemoryGraph, replaceMemoryGraph } from '../graph/resolve.js';
18
19
  import { runDefencePipeline, storeFragmentationData } from '../defence/index.js';
19
20
  import { syncQuarantineToCloud } from '../cloud/quarantine-sync.js';
21
+ import { syncGraphDeleteForMemoryToCloud, syncGraphForMemoryToCloud } from '../cloud/graph-sync.js';
22
+ import { syncMemoryDeleteToCloud, syncMemoryUpsertToCloud } from '../cloud/memory-sync.js';
20
23
  import { isFeatureEnabled } from '../license/gate.js';
21
24
  import { checkAccess } from '../defence/trust/access-control.js';
22
25
  import { scoreSource } from '../defence/trust/source-scorer.js';
@@ -125,6 +128,7 @@ function safeJsonParse(value, fallback) {
125
128
  export function rowToMemory(row) {
126
129
  return {
127
130
  id: row.id,
131
+ uuid: row.uuid,
128
132
  type: row.type,
129
133
  category: row.category,
130
134
  title: row.title,
@@ -135,6 +139,7 @@ export function rowToMemory(row) {
135
139
  accessCount: row.access_count,
136
140
  lastAccessed: new Date(row.last_accessed),
137
141
  createdAt: new Date(row.created_at),
142
+ updatedAt: new Date(row.updated_at ?? row.created_at),
138
143
  decayedScore: row.decayed_score ?? row.salience,
139
144
  metadata: safeJsonParse(row.metadata, {}),
140
145
  embedding: row.embedding,
@@ -353,8 +358,8 @@ export function addMemory(input, config = DEFAULT_CONFIG, source) {
353
358
  (detectGlobalPattern(input.content, category, tags) ? 'global' : 'project');
354
359
  const transferable = input.transferable ?? (scope === 'global' ? 1 : 0);
355
360
  const stmt = db.prepare(`
356
- INSERT INTO memories (type, category, title, content, project, tags, salience, metadata, scope, transferable)
357
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
361
+ INSERT INTO memories (uuid, type, category, title, content, project, tags, salience, metadata, scope, transferable, updated_at)
362
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
358
363
  `);
359
364
  // Anti-bloat: Truncate content if too large
360
365
  const truncationResult = truncateContent(input.content);
@@ -366,7 +371,8 @@ export function addMemory(input, config = DEFAULT_CONFIG, source) {
366
371
  };
367
372
  // Transaction: INSERT + defence UPDATE must be atomic to prevent wrong trust scores
368
373
  const insertedId = db.transaction(() => {
369
- const result = stmt.run(type, category, input.title, truncationResult.content, input.project || null, JSON.stringify(tags), salience, JSON.stringify(input.metadata || {}), scope, transferable);
374
+ const memoryUuid = randomUUID();
375
+ const result = stmt.run(memoryUuid, type, category, input.title, truncationResult.content, input.project || null, JSON.stringify(tags), salience, JSON.stringify(input.metadata || {}), scope, transferable);
370
376
  if (defenceResult) {
371
377
  db.prepare(`UPDATE memories SET trust_score = ?, sensitivity_level = ?, source = ? WHERE id = ?`)
372
378
  .run(defenceResult.trust.score, defenceResult.sensitivity.level, `${source.type}:${source.identifier}`, result.lastInsertRowid);
@@ -380,6 +386,9 @@ export function addMemory(input, config = DEFAULT_CONFIG, source) {
380
386
  persistEvent('memory_created', { memory });
381
387
  // Webhook notification (fire-and-forget)
382
388
  dispatchWebhook('memory_created', { id: memory.id, title: memory.title, category: memory.category });
389
+ if (isFeatureEnabled('cloud_sync')) {
390
+ syncMemoryUpsertToCloud(memory);
391
+ }
383
392
  // ORGANIC FEATURE: Auto-link to related memories
384
393
  // This builds the knowledge graph automatically as memories are created
385
394
  try {
@@ -397,6 +406,9 @@ export function addMemory(input, config = DEFAULT_CONFIG, source) {
397
406
  const extraction = extractFromMemory(input.title, truncationResult.content, category);
398
407
  if (extraction.entities.length > 0) {
399
408
  processExtractionResult(extraction, memory.id);
409
+ if (isFeatureEnabled('cloud_sync')) {
410
+ syncGraphForMemoryToCloud(memory.id);
411
+ }
400
412
  }
401
413
  }
402
414
  catch (e) {
@@ -532,12 +544,28 @@ export function updateMemory(id, updates) {
532
544
  if (fields.length === 0)
533
545
  return existing;
534
546
  values.push(id);
535
- db.prepare(`UPDATE memories SET ${fields.join(', ')} WHERE id = ?`).run(...values);
547
+ db.prepare(`UPDATE memories SET ${fields.join(', ')}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`).run(...values);
536
548
  const updatedMemory = getMemoryById(id);
549
+ const shouldRefreshGraph = updates.title !== undefined ||
550
+ updates.content !== undefined ||
551
+ updates.category !== undefined;
552
+ if (shouldRefreshGraph) {
553
+ try {
554
+ const extraction = extractFromMemory(updatedMemory.title, updatedMemory.content, updatedMemory.category);
555
+ replaceMemoryGraph(updatedMemory.id, extraction);
556
+ }
557
+ catch (e) {
558
+ console.error('[shieldcortex] Entity extraction refresh failed:', e);
559
+ }
560
+ }
537
561
  // Emit event for real-time dashboard (in-process)
538
562
  emitMemoryUpdated(updatedMemory);
539
563
  // Persist event for cross-process IPC (MCP → Dashboard)
540
564
  persistEvent('memory_updated', { memory: updatedMemory });
565
+ if (isFeatureEnabled('cloud_sync')) {
566
+ syncMemoryUpsertToCloud(updatedMemory);
567
+ syncGraphForMemoryToCloud(updatedMemory.id);
568
+ }
541
569
  return updatedMemory;
542
570
  }
543
571
  /**
@@ -558,9 +586,21 @@ export function deleteMemory(id, source) {
558
586
  }
559
587
  // Get memory before deletion for event
560
588
  const memory = getMemoryById(id);
589
+ if (memory) {
590
+ try {
591
+ removeMemoryGraph(id);
592
+ }
593
+ catch (e) {
594
+ console.error('[shieldcortex] Graph cleanup before delete failed:', e);
595
+ }
596
+ }
561
597
  const result = db.prepare('DELETE FROM memories WHERE id = ?').run(id);
562
598
  // Emit event for real-time dashboard (in-process)
563
599
  if (result.changes > 0 && memory) {
600
+ if (isFeatureEnabled('cloud_sync')) {
601
+ syncMemoryDeleteToCloud(memory);
602
+ syncGraphDeleteForMemoryToCloud(memory);
603
+ }
564
604
  emitMemoryDeleted(id, memory.title);
565
605
  // Persist event for cross-process IPC (MCP → Dashboard)
566
606
  persistEvent('memory_deleted', { memoryId: id, title: memory.title });
@@ -5,6 +5,7 @@ export type MemoryType = 'short_term' | 'long_term' | 'episodic';
5
5
  export type MemoryCategory = 'architecture' | 'pattern' | 'preference' | 'error' | 'context' | 'learning' | 'todo' | 'note' | 'relationship' | 'custom';
6
6
  export interface Memory {
7
7
  id: number;
8
+ uuid: string;
8
9
  type: MemoryType;
9
10
  category: MemoryCategory;
10
11
  title: string;
@@ -15,6 +16,7 @@ export interface Memory {
15
16
  accessCount: number;
16
17
  lastAccessed: Date;
17
18
  createdAt: Date;
19
+ updatedAt: Date;
18
20
  decayedScore: number;
19
21
  metadata: Record<string, unknown>;
20
22
  embedding?: Buffer;
@@ -7,6 +7,7 @@
7
7
  * - Windows: VBS script in Startup folder
8
8
  */
9
9
  export declare function installService(): Promise<void>;
10
+ export declare function repairService(): Promise<void>;
10
11
  export declare function uninstallService(options?: {
11
12
  cleanLogs?: boolean;
12
13
  }): Promise<void>;
@@ -31,6 +31,33 @@ function getServiceConfig() {
31
31
  logsDir,
32
32
  };
33
33
  }
34
+ function inspectServiceEntryPoint(platform, servicePath) {
35
+ if (!fs.existsSync(servicePath)) {
36
+ return { entryPoint: null, stale: false };
37
+ }
38
+ try {
39
+ const content = fs.readFileSync(servicePath, 'utf-8');
40
+ let entryPoint = null;
41
+ if (platform === 'macos') {
42
+ const matches = [...content.matchAll(/<string>([^<]+)<\/string>/g)].map((match) => match[1]);
43
+ entryPoint = matches.find((value) => value.endsWith('index.js')) ?? null;
44
+ }
45
+ else if (platform === 'linux') {
46
+ const match = content.match(/ExecStart=\S+\s+(\S+index\.js)/);
47
+ entryPoint = match?.[1] ?? null;
48
+ }
49
+ else {
50
+ const match = content.match(/Run\s+\"\"[^\"]+\"\"\s+\"\"([^\"]+index\.js)\"\"/);
51
+ entryPoint = match?.[1] ?? null;
52
+ }
53
+ const currentEntryPoint = getServiceConfig().entryPoint;
54
+ const stale = !!entryPoint && (entryPoint.includes('/.npm/_npx/') || entryPoint !== currentEntryPoint);
55
+ return { entryPoint, stale };
56
+ }
57
+ catch {
58
+ return { entryPoint: null, stale: false };
59
+ }
60
+ }
34
61
  function getServicePath(platform) {
35
62
  switch (platform) {
36
63
  case 'macos':
@@ -91,6 +118,10 @@ export async function installService() {
91
118
  console.log(` API: http://localhost:3001`);
92
119
  console.log(` Dashboard: http://localhost:3030`);
93
120
  }
121
+ export async function repairService() {
122
+ await uninstallService();
123
+ await installService();
124
+ }
94
125
  function cleanLogsDirectory() {
95
126
  const logsDir = path.join(os.homedir(), '.shieldcortex', 'logs');
96
127
  if (fs.existsSync(logsDir)) {
@@ -131,9 +162,14 @@ export async function serviceStatus() {
131
162
  const platform = detectPlatform();
132
163
  const servicePath = getServicePath(platform);
133
164
  const installed = fs.existsSync(servicePath);
165
+ const inspection = inspectServiceEntryPoint(platform, servicePath);
134
166
  console.log(`Platform: ${platform}`);
135
167
  console.log(`Installed: ${installed ? 'yes' : 'no'}`);
136
168
  console.log(`Path: ${servicePath}`);
169
+ if (inspection.entryPoint) {
170
+ console.log(`Entry: ${inspection.entryPoint}`);
171
+ console.log(`Healthy: ${inspection.stale ? 'no (repair recommended)' : 'yes'}`);
172
+ }
137
173
  if (!installed)
138
174
  return;
139
175
  // Check if running
@@ -158,12 +194,18 @@ export async function serviceStatus() {
158
194
  catch {
159
195
  console.log('Running: no');
160
196
  }
197
+ if (inspection.stale) {
198
+ console.log('Repair: shieldcortex service repair');
199
+ }
161
200
  }
162
201
  export async function handleServiceCommand(subcommand) {
163
202
  switch (subcommand) {
164
203
  case 'install':
165
204
  await installService();
166
205
  break;
206
+ case 'repair':
207
+ await repairService();
208
+ break;
167
209
  case 'uninstall':
168
210
  await uninstallService({ cleanLogs: process.argv.includes('--clean-logs') });
169
211
  break;
@@ -171,7 +213,7 @@ export async function handleServiceCommand(subcommand) {
171
213
  await serviceStatus();
172
214
  break;
173
215
  default:
174
- console.log('Usage: shieldcortex service <install|uninstall|status>');
216
+ console.log('Usage: shieldcortex service <install|repair|uninstall|status>');
175
217
  process.exit(1);
176
218
  }
177
219
  }
@@ -32,8 +32,8 @@ export declare const forgetSchema: z.ZodObject<{
32
32
  } | undefined;
33
33
  project?: string | undefined;
34
34
  id?: number | undefined;
35
- query?: string | undefined;
36
35
  category?: "architecture" | "pattern" | "preference" | "error" | "context" | "learning" | "todo" | "note" | "relationship" | "custom" | undefined;
36
+ query?: string | undefined;
37
37
  olderThan?: number | undefined;
38
38
  belowSalience?: number | undefined;
39
39
  }, {
@@ -43,8 +43,8 @@ export declare const forgetSchema: z.ZodObject<{
43
43
  } | undefined;
44
44
  project?: string | undefined;
45
45
  id?: number | undefined;
46
- query?: string | undefined;
47
46
  category?: "architecture" | "pattern" | "preference" | "error" | "context" | "learning" | "todo" | "note" | "relationship" | "custom" | undefined;
47
+ query?: string | undefined;
48
48
  olderThan?: number | undefined;
49
49
  belowSalience?: number | undefined;
50
50
  dryRun?: boolean | undefined;
@@ -37,9 +37,9 @@ export declare const recallSchema: z.ZodObject<{
37
37
  } | undefined;
38
38
  project?: string | undefined;
39
39
  type?: "short_term" | "long_term" | "episodic" | undefined;
40
- query?: string | undefined;
41
40
  category?: "architecture" | "pattern" | "preference" | "error" | "context" | "learning" | "todo" | "note" | "relationship" | "custom" | undefined;
42
41
  tags?: string[] | undefined;
42
+ query?: string | undefined;
43
43
  }, {
44
44
  source?: {
45
45
  type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
@@ -48,10 +48,10 @@ export declare const recallSchema: z.ZodObject<{
48
48
  project?: string | undefined;
49
49
  mode?: "search" | "important" | "recent" | undefined;
50
50
  type?: "short_term" | "long_term" | "episodic" | undefined;
51
- limit?: number | undefined;
52
- query?: string | undefined;
53
51
  category?: "architecture" | "pattern" | "preference" | "error" | "context" | "learning" | "todo" | "note" | "relationship" | "custom" | undefined;
54
52
  tags?: string[] | undefined;
53
+ limit?: number | undefined;
54
+ query?: string | undefined;
55
55
  includeDecayed?: boolean | undefined;
56
56
  includeGlobal?: boolean | undefined;
57
57
  }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shieldcortex",
3
- "version": "3.0.4",
3
+ "version": "3.1.0",
4
4
  "description": "Persistent brain for AI agents. Knowledge graphs, memory decay, contradiction detection, Iron Dome behaviour protection — plus the only defence pipeline that stops memory poisoning. Works with Claude Code, OpenClaw, LangChain, and any MCP agent.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",