shieldcortex 3.4.28 → 3.4.30

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 (81) hide show
  1. package/README.md +51 -13
  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.html +1 -1
  14. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.rsc +1 -1
  15. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  16. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  17. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  18. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  19. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  20. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  21. package/dashboard/.next/standalone/dashboard/.next/server/app/index.html +1 -1
  22. package/dashboard/.next/standalone/dashboard/.next/server/app/index.rsc +2 -2
  23. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  24. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_full.segment.rsc +2 -2
  25. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_head.segment.rsc +1 -1
  26. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_index.segment.rsc +1 -1
  27. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  28. package/dashboard/.next/standalone/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
  29. package/dashboard/.next/standalone/dashboard/.next/server/chunks/ssr/dashboard_3051539d._.js +1 -1
  30. package/dashboard/.next/standalone/dashboard/.next/server/pages/404.html +1 -1
  31. package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
  32. package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.js +1 -1
  33. package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.json +1 -1
  34. package/dashboard/.next/standalone/dashboard/.next/static/chunks/{c95cfef94573d615.js → 55858d6b0b93278b.js} +1 -1
  35. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-darwin-arm64 → sharp-libvips-linux-x64}/README.md +2 -2
  36. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-darwin-arm64 → sharp-libvips-linux-x64}/lib/glib-2.0/include/glibconfig.h +9 -8
  37. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-darwin-arm64/lib/libvips-cpp.8.17.3.dylib → sharp-libvips-linux-x64/lib/libvips-cpp.so.8.17.3} +0 -0
  38. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-darwin-arm64 → sharp-libvips-linux-x64}/package.json +11 -5
  39. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/README.md +46 -0
  40. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/glib-2.0/include/glibconfig.h +221 -0
  41. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/index.js +1 -0
  42. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  43. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +42 -0
  44. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +30 -0
  45. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node +0 -0
  46. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-darwin-arm64 → sharp-linux-x64}/package.json +13 -7
  47. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linuxmusl-x64/lib/sharp-linuxmusl-x64.node +0 -0
  48. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linuxmusl-x64/package.json +46 -0
  49. package/dashboard/.next/standalone/dashboard/server.js +1 -1
  50. package/dashboard/.next/standalone/node_modules/@img/{sharp-libvips-darwin-arm64 → sharp-libvips-linux-x64}/package.json +11 -5
  51. package/dashboard/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +42 -0
  52. package/dashboard/.next/standalone/node_modules/@img/{sharp-darwin-arm64 → sharp-linux-x64}/package.json +13 -7
  53. package/dashboard/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json +46 -0
  54. package/dist/api/routes/admin.js +14 -2
  55. package/dist/api/routes/system.js +63 -0
  56. package/dist/cli/stats-banner.d.ts +6 -0
  57. package/dist/cli/stats-banner.js +48 -0
  58. package/dist/cli/stats-command.d.ts +4 -0
  59. package/dist/cli/stats-command.js +83 -0
  60. package/dist/database/init.js +4 -4
  61. package/dist/defence/audit/index.d.ts +2 -2
  62. package/dist/defence/audit/index.js +1 -1
  63. package/dist/defence/audit/queries.d.ts +12 -0
  64. package/dist/defence/audit/queries.js +40 -0
  65. package/dist/index.js +69 -2
  66. package/dist/license/cli.js +50 -22
  67. package/dist/license/index.d.ts +1 -1
  68. package/dist/license/index.js +1 -1
  69. package/dist/license/store.d.ts +4 -1
  70. package/dist/license/store.js +38 -15
  71. package/dist/license/trial.d.ts +48 -0
  72. package/dist/license/trial.js +128 -0
  73. package/dist/server.js +26 -8
  74. package/dist/setup/openclaw.js +75 -17
  75. package/package.json +1 -1
  76. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-darwin-arm64/lib/sharp-darwin-arm64.node +0 -0
  77. /package/dashboard/.next/standalone/dashboard/.next/static/{oqbBQnPvKRkfU3j_7cE7c → 5yxbHrMFACE5ILOtSpx42}/_buildManifest.js +0 -0
  78. /package/dashboard/.next/standalone/dashboard/.next/static/{oqbBQnPvKRkfU3j_7cE7c → 5yxbHrMFACE5ILOtSpx42}/_clientMiddlewareManifest.json +0 -0
  79. /package/dashboard/.next/standalone/dashboard/.next/static/{oqbBQnPvKRkfU3j_7cE7c → 5yxbHrMFACE5ILOtSpx42}/_ssgManifest.js +0 -0
  80. /package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-darwin-arm64 → sharp-libvips-linux-x64}/lib/index.js +0 -0
  81. /package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-darwin-arm64 → sharp-libvips-linux-x64}/versions.json +0 -0
@@ -1,13 +1,13 @@
1
1
  {
2
- "name": "@img/sharp-darwin-arm64",
2
+ "name": "@img/sharp-linux-x64",
3
3
  "version": "0.34.5",
4
- "description": "Prebuilt sharp for use with macOS 64-bit ARM",
4
+ "description": "Prebuilt sharp for use with Linux (glibc) x64",
5
5
  "author": "Lovell Fuller <npm@lovell.info>",
6
6
  "homepage": "https://sharp.pixelplumbing.com",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/lovell/sharp.git",
10
- "directory": "npm/darwin-arm64"
10
+ "directory": "npm/linux-x64"
11
11
  },
12
12
  "license": "Apache-2.0",
13
13
  "funding": {
@@ -15,7 +15,7 @@
15
15
  },
16
16
  "preferUnplugged": true,
17
17
  "optionalDependencies": {
18
- "@img/sharp-libvips-darwin-arm64": "1.2.4"
18
+ "@img/sharp-libvips-linux-x64": "1.2.4"
19
19
  },
20
20
  "files": [
21
21
  "lib"
@@ -25,16 +25,22 @@
25
25
  },
26
26
  "type": "commonjs",
27
27
  "exports": {
28
- "./sharp.node": "./lib/sharp-darwin-arm64.node",
28
+ "./sharp.node": "./lib/sharp-linux-x64.node",
29
29
  "./package": "./package.json"
30
30
  },
31
31
  "engines": {
32
32
  "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
33
33
  },
34
+ "config": {
35
+ "glibc": ">=2.26"
36
+ },
34
37
  "os": [
35
- "darwin"
38
+ "linux"
39
+ ],
40
+ "libc": [
41
+ "glibc"
36
42
  ],
37
43
  "cpu": [
38
- "arm64"
44
+ "x64"
39
45
  ]
40
46
  }
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@img/sharp-linuxmusl-x64",
3
+ "version": "0.34.5",
4
+ "description": "Prebuilt sharp for use with Linux (musl) x64",
5
+ "author": "Lovell Fuller <npm@lovell.info>",
6
+ "homepage": "https://sharp.pixelplumbing.com",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/lovell/sharp.git",
10
+ "directory": "npm/linuxmusl-x64"
11
+ },
12
+ "license": "Apache-2.0",
13
+ "funding": {
14
+ "url": "https://opencollective.com/libvips"
15
+ },
16
+ "preferUnplugged": true,
17
+ "optionalDependencies": {
18
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
19
+ },
20
+ "files": [
21
+ "lib"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "type": "commonjs",
27
+ "exports": {
28
+ "./sharp.node": "./lib/sharp-linuxmusl-x64.node",
29
+ "./package": "./package.json"
30
+ },
31
+ "engines": {
32
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
33
+ },
34
+ "config": {
35
+ "musl": ">=1.2.2"
36
+ },
37
+ "os": [
38
+ "linux"
39
+ ],
40
+ "libc": [
41
+ "musl"
42
+ ],
43
+ "cpu": [
44
+ "x64"
45
+ ]
46
+ }
@@ -2,9 +2,12 @@ import { getDatabase } from '../../database/init.js';
2
2
  import { getCloudConfig } from '../../cloud/config.js';
3
3
  import { queryAgentOperations, queryAgentRegistry, queryAgentTimeline, queryAuditLogs, getAuditStats } from '../../defence/audit/queries.js';
4
4
  import { approveQuarantineItem, approveQuarantineItems, rejectQuarantineItem, rejectQuarantineItems, } from '../../defence/quarantine/review.js';
5
- import { getLicense, activateLicense, deactivateLicense } from '../../license/store.js';
5
+ import { getLicense, getLicenseTier, getTrialStatus, activateLicense, deactivateLicense } from '../../license/store.js';
6
6
  import { listFeatures } from '../../license/gate.js';
7
7
  import { validateOnceNow } from '../../license/validate.js';
8
+ import { existsSync } from 'fs';
9
+ import { join } from 'path';
10
+ import { homedir } from 'os';
8
11
  export function registerAdminRoutes(app, deps) {
9
12
  const { brainWorker, requireNotLocked, requireProFeature, requireIronDomeAction } = deps;
10
13
  app.get('/api/v1/audit', (req, res) => {
@@ -278,13 +281,22 @@ export function registerAdminRoutes(app, deps) {
278
281
  app.get('/api/license/status', (_req, res) => {
279
282
  try {
280
283
  const info = getLicense();
284
+ const licenseFile = join(process.env.SHIELDCORTEX_CONFIG_DIR || join(homedir(), '.shieldcortex'), 'license.json');
285
+ const trial = getTrialStatus(existsSync(licenseFile));
281
286
  res.json({
282
- tier: info.tier,
287
+ tier: getLicenseTier(),
283
288
  valid: info.valid,
284
289
  email: info.email,
285
290
  expiresAt: info.expiresAt?.toISOString() ?? null,
286
291
  daysUntilExpiry: info.daysUntilExpiry,
287
292
  teamId: info.teamId,
293
+ trial: trial
294
+ ? {
295
+ active: trial.active,
296
+ daysRemaining: trial.daysRemaining,
297
+ expiresAt: trial.expiresAt,
298
+ }
299
+ : null,
288
300
  features: listFeatures(),
289
301
  });
290
302
  }
@@ -1,12 +1,75 @@
1
1
  import { WebSocket } from 'ws';
2
+ import { getLifetimeStats } from '../../defence/audit/queries.js';
3
+ import { getAuditStats } from '../../defence/audit/queries.js';
4
+ import { isDatabaseInitialized } from '../../database/init.js';
2
5
  import { getCloudConfig, getCloudSyncControls, getDeviceId, getDeviceName, getDefenceMode, getOpenClawMemoryConfig, isConfigTampered, readRawConfig, setCloudConfig, setCloudSyncControls, setDefenceMode, setOpenClawMemoryConfig, } from '../../cloud/config.js';
3
6
  import { getQueueStats } from '../../cloud/sync-queue.js';
4
7
  import { getDatabase } from '../../database/init.js';
5
8
  import { getRequiredTier, isFeatureEnabled } from '../../license/gate.js';
9
+ import { getLicense } from '../../license/store.js';
10
+ import { getTrialStatus } from '../../license/trial.js';
11
+ import { existsSync } from 'fs';
12
+ import { join } from 'path';
13
+ import { homedir } from 'os';
6
14
  import { getControlStatus, isKillSwitchActive, pause, resume } from '../control.js';
7
15
  import { checkForUpdates, getCurrentVersion, getRunningVersion, performUpdate, scheduleRestart, } from '../version.js';
8
16
  export function registerSystemRoutes(app, deps) {
9
17
  const { broadcast, clients, requireIronDomeAction } = deps;
18
+ app.get('/api/system/status', (_req, res) => {
19
+ try {
20
+ const licenseFile = join(homedir(), '.shieldcortex', 'license.json');
21
+ const licenseFileExists = existsSync(licenseFile);
22
+ const license = getLicense();
23
+ const trial = getTrialStatus(licenseFileExists);
24
+ const trialInfo = trial
25
+ ? {
26
+ active: trial.active,
27
+ daysRemaining: trial.daysRemaining,
28
+ expiresAt: trial.expiresAt.slice(0, 10), // YYYY-MM-DD
29
+ }
30
+ : null;
31
+ // Stats (best effort — never fail the status endpoint)
32
+ let stats = null;
33
+ try {
34
+ if (isDatabaseInitialized()) {
35
+ const lifetime = getLifetimeStats();
36
+ const h24 = getAuditStats('24h');
37
+ const d7 = getAuditStats('7d');
38
+ stats = {
39
+ lifetime: {
40
+ totalScans: lifetime.totalScans,
41
+ threatsBlocked: lifetime.threatsBlocked,
42
+ quarantined: lifetime.quarantined,
43
+ credentialLeaks: lifetime.credentialLeaks,
44
+ memoriesProtected: lifetime.memoriesProtected,
45
+ },
46
+ last24h: {
47
+ totalScans: h24.totalOperations,
48
+ blocked: h24.blockedCount,
49
+ quarantined: h24.quarantinedCount,
50
+ },
51
+ last7d: {
52
+ totalScans: d7.totalOperations,
53
+ blocked: d7.blockedCount,
54
+ quarantined: d7.quarantinedCount,
55
+ },
56
+ };
57
+ }
58
+ }
59
+ catch {
60
+ // Stats unavailable — still return the rest of the status
61
+ }
62
+ res.json({
63
+ tier: license.valid ? license.tier : (trial?.active ? 'pro' : 'free'),
64
+ licenseValid: license.valid,
65
+ trial: trialInfo,
66
+ ...(stats ? { stats } : {}),
67
+ });
68
+ }
69
+ catch (error) {
70
+ res.status(500).json({ error: error.message });
71
+ }
72
+ });
10
73
  app.get('/api/control/status', (_req, res) => {
11
74
  try {
12
75
  res.json(getControlStatus());
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Stats banner — compact one-liner showing what ShieldCortex has protected
3
+ */
4
+ import type { LifetimeStats } from '../defence/audit/queries.js';
5
+ export declare function formatStatsBanner(stats: LifetimeStats): string | null;
6
+ export declare function printStatsBanner(): Promise<void>;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Stats banner — compact one-liner showing what ShieldCortex has protected
3
+ */
4
+ const fmt = new Intl.NumberFormat('en-US');
5
+ function n(num) {
6
+ return fmt.format(num);
7
+ }
8
+ const GREEN = '\x1b[32m';
9
+ const DIM = '\x1b[2m';
10
+ const BOLD = '\x1b[1m';
11
+ const RESET = '\x1b[0m';
12
+ export function formatStatsBanner(stats) {
13
+ // Nothing to show on a fresh install
14
+ if (stats.totalScans === 0)
15
+ return null;
16
+ const parts = [];
17
+ if (stats.threatsBlocked > 0 || stats.quarantined > 0) {
18
+ const blocked = stats.threatsBlocked + stats.quarantined;
19
+ parts.push(`${GREEN}${BOLD}${n(blocked)}${RESET}${DIM} threats blocked${RESET}`);
20
+ }
21
+ if (stats.credentialLeaks > 0) {
22
+ parts.push(`${GREEN}${BOLD}${n(stats.credentialLeaks)}${RESET}${DIM} credential leak${stats.credentialLeaks !== 1 ? 's' : ''} caught${RESET}`);
23
+ }
24
+ if (stats.memoriesProtected > 0) {
25
+ parts.push(`${GREEN}${BOLD}${n(stats.memoriesProtected)}${RESET}${DIM} memories scanned${RESET}`);
26
+ }
27
+ if (parts.length === 0) {
28
+ // Scans happened but nothing notable — show scan count
29
+ parts.push(`${GREEN}${BOLD}${n(stats.totalScans)}${RESET}${DIM} scans completed${RESET}`);
30
+ }
31
+ return `🛡️ ShieldCortex: ${parts.join(`${DIM} · ${RESET}`)}`;
32
+ }
33
+ export async function printStatsBanner() {
34
+ try {
35
+ const { isDatabaseInitialized } = await import('../database/init.js');
36
+ if (!isDatabaseInitialized())
37
+ return;
38
+ const { getLifetimeStats } = await import('../defence/audit/queries.js');
39
+ const stats = getLifetimeStats();
40
+ const banner = formatStatsBanner(stats);
41
+ if (banner) {
42
+ console.error(banner); // stderr so MCP stdout stays clean
43
+ }
44
+ }
45
+ catch {
46
+ // Never crash startup
47
+ }
48
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * shieldcortex stats — detailed security report
3
+ */
4
+ export declare function runStatsCommand(): Promise<void>;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * shieldcortex stats — detailed security report
3
+ */
4
+ import { isDatabaseInitialized } from '../database/init.js';
5
+ import { getLifetimeStats } from '../defence/audit/queries.js';
6
+ import { getAuditStats } from '../defence/audit/queries.js';
7
+ import { getLicense } from '../license/store.js';
8
+ import { getTrialStatus } from '../license/trial.js';
9
+ import { existsSync } from 'fs';
10
+ import { join } from 'path';
11
+ import { homedir } from 'os';
12
+ const fmt = new Intl.NumberFormat('en-US');
13
+ function n(num) {
14
+ return fmt.format(num);
15
+ }
16
+ const BOLD = '\x1b[1m';
17
+ const DIM = '\x1b[2m';
18
+ const GREEN = '\x1b[32m';
19
+ const CYAN = '\x1b[36m';
20
+ const RESET = '\x1b[0m';
21
+ function row(label, value, width = 36) {
22
+ const l = label + ':';
23
+ const v = typeof value === 'number' ? n(value) : value;
24
+ return ` ${l.padEnd(width - v.toString().length - 2)}${GREEN}${v}${RESET}`;
25
+ }
26
+ function section(title) {
27
+ return `\n ${BOLD}${title}${RESET}\n ${'─'.repeat(34)}`;
28
+ }
29
+ export async function runStatsCommand() {
30
+ if (!isDatabaseInitialized()) {
31
+ // Try to init the default db first
32
+ try {
33
+ const { initDatabase } = await import('../database/init.js');
34
+ const dbPath = process.env.CLAUDE_MEMORY_DB || join(homedir(), '.shieldcortex', 'memories.db');
35
+ initDatabase(dbPath);
36
+ }
37
+ catch {
38
+ console.log(`\n No database found. Run ShieldCortex first to start collecting stats.\n`);
39
+ return;
40
+ }
41
+ }
42
+ try {
43
+ const lifetime = getLifetimeStats();
44
+ const last24h = getAuditStats('24h');
45
+ const last7d = getAuditStats('7d');
46
+ const licenseFile = join(homedir(), '.shieldcortex', 'license.json');
47
+ const license = getLicense();
48
+ const trial = getTrialStatus(existsSync(licenseFile));
49
+ const tier = license.valid ? license.tier : (trial?.active ? 'pro (trial)' : 'free');
50
+ console.log(`\n ${BOLD}🛡️ ShieldCortex Security Report${RESET} ${DIM}[${tier}]${RESET}`);
51
+ console.log(section('All Time'));
52
+ console.log(row('Total scans', lifetime.totalScans));
53
+ console.log(row('Threats blocked', lifetime.threatsBlocked));
54
+ console.log(row('Quarantined', lifetime.quarantined));
55
+ console.log(row('Credential leaks', lifetime.credentialLeaks));
56
+ console.log(row('Memories protected', lifetime.memoriesProtected));
57
+ console.log(section('Last 24 Hours'));
58
+ console.log(row('Scans', last24h.totalOperations));
59
+ console.log(row('Blocked', last24h.blockedCount));
60
+ console.log(row('Quarantined', last24h.quarantinedCount));
61
+ console.log(section('Last 7 Days'));
62
+ console.log(row('Scans', last7d.totalOperations));
63
+ console.log(row('Blocked', last7d.blockedCount));
64
+ console.log(row('Quarantined', last7d.quarantinedCount));
65
+ const threats = last7d.threatBreakdown;
66
+ const threatEntries = Object.entries(threats).sort((a, b) => b[1] - a[1]).slice(0, 5);
67
+ if (threatEntries.length > 0) {
68
+ console.log(section('Top Threat Types (7d)'));
69
+ for (const [type, count] of threatEntries) {
70
+ console.log(row(type, count));
71
+ }
72
+ }
73
+ const isPro = license.valid || trial?.active;
74
+ if (!isPro) {
75
+ console.log(`\n ${DIM}Upgrade to Pro for custom detection patterns.${RESET}`);
76
+ console.log(` ${CYAN}https://shieldcortex.ai/pricing${RESET}`);
77
+ }
78
+ console.log();
79
+ }
80
+ catch (err) {
81
+ console.error(' Failed to load stats:', err.message);
82
+ }
83
+ }
@@ -266,12 +266,12 @@ function attemptDumpRecovery(dbPath) {
266
266
  return null;
267
267
  // Back up the corrupt file
268
268
  const backupPath = backupCorruptDatabase(dbPath);
269
- console.log(`[database] Backed up corrupt database to: ${backupPath}`);
269
+ console.error(`[database] Backed up corrupt database to: ${backupPath}`);
270
270
  // Create fresh database and import the dump
271
271
  const freshDb = new Database(dbPath);
272
272
  try {
273
273
  freshDb.exec(dumpOutput);
274
- console.log('[database] Successfully recovered data via dump/reimport.');
274
+ console.error('[database] Successfully recovered data via dump/reimport.');
275
275
  return freshDb;
276
276
  }
277
277
  catch {
@@ -330,7 +330,7 @@ function attemptFtsRecovery(database) {
330
330
  database.exec(`INSERT INTO memories_fts(memories_fts) VALUES('rebuild')`);
331
331
  const postRepairIntegrity = runIntegrityCheck(database);
332
332
  if (postRepairIntegrity === 'ok') {
333
- console.log('[database] Successfully rebuilt memories_fts after integrity failure.');
333
+ console.error('[database] Successfully rebuilt memories_fts after integrity failure.');
334
334
  return true;
335
335
  }
336
336
  console.warn(`[database] FTS rebuild did not fully repair integrity: ${postRepairIntegrity}`);
@@ -374,7 +374,7 @@ export function initDatabase(dbPath) {
374
374
  // Store path for size monitoring
375
375
  currentDbPath = expandedPath;
376
376
  acquireStartupLock(expandedPath);
377
- console.log(`[database] Startup runtime=${resolveRuntimeInfo().kind} db=${expandedPath} wal=${existsSync(expandedPath + '-wal')} shm=${existsSync(expandedPath + '-shm')}`);
377
+ console.error(`[database] Startup runtime=${resolveRuntimeInfo().kind} db=${expandedPath} wal=${existsSync(expandedPath + '-wal')} shm=${existsSync(expandedPath + '-shm')}`);
378
378
  const healthyBackups = listHealthyBackups(expandedPath);
379
379
  // Wrap the initial open in try/catch to handle corrupt files gracefully
380
380
  let database;
@@ -1,3 +1,3 @@
1
1
  export { logAudit, createContentHash } from './logger.js';
2
- export { queryAuditLogs, getAuditStats, queryAgentRegistry, queryAgentTimeline, queryAgentOperations } from './queries.js';
3
- export type { AuditQueryOptions, AuditStats, AgentInfo, AgentTimelinePoint } from './queries.js';
2
+ export { queryAuditLogs, getAuditStats, getLifetimeStats, queryAgentRegistry, queryAgentTimeline, queryAgentOperations } from './queries.js';
3
+ export type { AuditQueryOptions, AuditStats, LifetimeStats, AgentInfo, AgentTimelinePoint } from './queries.js';
@@ -1,2 +1,2 @@
1
1
  export { logAudit, createContentHash } from './logger.js';
2
- export { queryAuditLogs, getAuditStats, queryAgentRegistry, queryAgentTimeline, queryAgentOperations } from './queries.js';
2
+ export { queryAuditLogs, getAuditStats, getLifetimeStats, queryAgentRegistry, queryAgentTimeline, queryAgentOperations } from './queries.js';
@@ -68,6 +68,18 @@ export declare function queryAuditLogs(options?: AuditQueryOptions): AuditEntry[
68
68
  * Get aggregate audit statistics for a time range.
69
69
  */
70
70
  export declare function getAuditStats(timeRange: '24h' | '7d' | '30d', project?: string): AuditStats;
71
+ export interface LifetimeStats {
72
+ totalScans: number;
73
+ threatsBlocked: number;
74
+ quarantined: number;
75
+ credentialLeaks: number;
76
+ memoriesProtected: number;
77
+ }
78
+ /**
79
+ * Get all-time aggregate stats from the defence_audit table.
80
+ * Fast COUNT-only queries — no heavy computation.
81
+ */
82
+ export declare function getLifetimeStats(): LifetimeStats;
71
83
  /**
72
84
  * Get distinct agents aggregated from audit logs.
73
85
  */
@@ -106,6 +106,46 @@ export function getAuditStats(timeRange, project) {
106
106
  threatBreakdown,
107
107
  };
108
108
  }
109
+ /**
110
+ * Get all-time aggregate stats from the defence_audit table.
111
+ * Fast COUNT-only queries — no heavy computation.
112
+ */
113
+ export function getLifetimeStats() {
114
+ const db = getDatabase();
115
+ // Counts by firewall result
116
+ const counts = db.prepare(`
117
+ SELECT firewall_result, COUNT(*) as cnt
118
+ FROM defence_audit
119
+ GROUP BY firewall_result
120
+ `).all();
121
+ let totalScans = 0;
122
+ let threatsBlocked = 0;
123
+ let quarantined = 0;
124
+ let memoriesProtected = 0;
125
+ for (const row of counts) {
126
+ totalScans += row.cnt;
127
+ if (row.firewall_result === 'BLOCK')
128
+ threatsBlocked = row.cnt;
129
+ else if (row.firewall_result === 'QUARANTINE')
130
+ quarantined = row.cnt;
131
+ else if (row.firewall_result === 'ALLOW')
132
+ memoriesProtected = row.cnt;
133
+ }
134
+ // Credential leaks: threat_indicators contains 'credential' (case-insensitive)
135
+ const credRow = db.prepare(`
136
+ SELECT COUNT(*) as cnt
137
+ FROM defence_audit
138
+ WHERE LOWER(threat_indicators) LIKE '%credential%'
139
+ `).get();
140
+ const credentialLeaks = credRow?.cnt ?? 0;
141
+ return {
142
+ totalScans,
143
+ threatsBlocked,
144
+ quarantined,
145
+ credentialLeaks,
146
+ memoriesProtected,
147
+ };
148
+ }
109
149
  // ── Agent Query Functions ──
110
150
  /**
111
151
  * Get distinct agents aggregated from audit logs.
package/dist/index.js CHANGED
@@ -363,6 +363,67 @@ async function isLocalDashboardRunning() {
363
363
  async function main() {
364
364
  // Warn if npx is serving a stale cached version
365
365
  checkVersionStaleness();
366
+ const parsedArgs = parseArgs();
367
+ // ── Trial welcome / expiry warning ──────────────────────
368
+ // Only show for interactive CLI commands (not MCP server mode — stdout must stay clean for JSON-RPC)
369
+ if (process.argv[2] && parsedArgs.mode !== 'mcp') {
370
+ try {
371
+ const { existsSync } = await import('fs');
372
+ const { join } = await import('path');
373
+ const { homedir } = await import('os');
374
+ const { getTrialStatus } = await import('./license/trial.js');
375
+ const { acknowledgeTrialWelcome } = await import('./license/trial.js');
376
+ const { getLicense } = await import('./license/store.js');
377
+ const licenseFile = join(homedir(), '.shieldcortex', 'license.json');
378
+ const licenseFileExists = existsSync(licenseFile);
379
+ const activeLicense = getLicense();
380
+ // Only show trial messages when no paid license is active
381
+ if (!activeLicense.valid) {
382
+ const trial = getTrialStatus(licenseFileExists);
383
+ if (trial?.justCreated) {
384
+ // First ever run — show welcome message
385
+ const expiryDate = new Date(trial.expiresAt).toLocaleDateString();
386
+ const bold = '\x1b[1m';
387
+ const reset = '\x1b[0m';
388
+ const green = '\x1b[32m';
389
+ const cyan = '\x1b[36m';
390
+ const yellow = '\x1b[33m';
391
+ console.log(`\n${bold}🎁 Welcome to ShieldCortex!${reset}\n`);
392
+ console.log(`You have a ${yellow}14-day Pro trial${reset} — all Pro features are unlocked.\n`);
393
+ console.log(` ${green}✓${reset} Custom injection patterns (up to 50)`);
394
+ console.log(` ${green}✓${reset} Custom Iron Dome policies`);
395
+ console.log(` ${green}✓${reset} Custom firewall rules`);
396
+ console.log(` ${green}✓${reset} Audit export (JSON/CSV)`);
397
+ console.log(` ${green}✓${reset} Skill scanner deep mode`);
398
+ console.log(`\nYour trial expires on ${bold}${expiryDate}${reset}. Upgrade anytime at:`);
399
+ console.log(` ${cyan}https://shieldcortex.ai/pricing${reset}`);
400
+ console.log(`\nRun: shieldcortex license status\n`);
401
+ acknowledgeTrialWelcome();
402
+ }
403
+ else if (trial?.active && trial.daysRemaining <= 3) {
404
+ // Trial expiring soon — show warning
405
+ const yellow = '\x1b[33m';
406
+ const cyan = '\x1b[36m';
407
+ const reset = '\x1b[0m';
408
+ const days = trial.daysRemaining;
409
+ console.error(`${yellow}⚠️ Pro trial expires in ${days} day${days !== 1 ? 's' : ''}. Upgrade to keep Pro features:${reset}`);
410
+ console.error(` shieldcortex license activate <key>`);
411
+ console.error(` ${cyan}https://shieldcortex.ai/pricing${reset}\n`);
412
+ }
413
+ }
414
+ }
415
+ catch {
416
+ // Best effort — never let trial messaging crash the CLI
417
+ }
418
+ // Show stats banner (threats blocked etc.) for interactive CLI modes
419
+ try {
420
+ const { printStatsBanner } = await import('./cli/stats-banner.js');
421
+ await printStatsBanner();
422
+ }
423
+ catch {
424
+ // Never fail startup over stats
425
+ }
426
+ }
366
427
  // Handle --help / -h / help / unknown args
367
428
  if (process.argv[2] === '--help' || process.argv[2] === '-h' || process.argv[2] === 'help') {
368
429
  const bold = '\x1b[1m';
@@ -535,6 +596,12 @@ ${bold}DOCS${reset}
535
596
  await handleLicenseCommand(process.argv.slice(3));
536
597
  return;
537
598
  }
599
+ // Handle "stats" subcommand — detailed security report
600
+ if (process.argv[2] === 'stats') {
601
+ const { runStatsCommand } = await import('./cli/stats-command.js');
602
+ await runStatsCommand();
603
+ return;
604
+ }
538
605
  // Handle "audit" subcommand — full security audit of agent environment
539
606
  if (process.argv[2] === 'audit') {
540
607
  const { handleAuditCommand } = await import('./cli/audit.js');
@@ -681,7 +748,7 @@ ${bold}DOCS${reset}
681
748
  'doctor', 'quickstart', 'setup', 'install', 'migrate', 'uninstall', 'hook',
682
749
  'openclaw', 'clawdbot', 'copilot', 'codex', 'service', 'config', 'status',
683
750
  'graph', 'license', 'licence', 'audit', 'iron-dome', 'scan', 'cloud',
684
- 'scan-skill', 'scan-skills', 'dashboard', 'api', 'worker',
751
+ 'scan-skill', 'scan-skills', 'dashboard', 'api', 'worker', 'stats',
685
752
  ]);
686
753
  const arg = process.argv[2];
687
754
  if (arg && !arg.startsWith('-') && !knownCommands.has(arg)) {
@@ -689,7 +756,7 @@ ${bold}DOCS${reset}
689
756
  console.error(`Run 'shieldcortex --help' for a list of commands.`);
690
757
  process.exit(1);
691
758
  }
692
- const { dbPath, mode } = parseArgs();
759
+ const { dbPath, mode } = parsedArgs;
693
760
  let dashboardProcess = null;
694
761
  if (mode === 'api') {
695
762
  // API mode only - for dashboard visualization