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.
- package/README.md +51 -13
- package/dashboard/.next/standalone/dashboard/.next/BUILD_ID +1 -1
- package/dashboard/.next/standalone/dashboard/.next/build-manifest.json +2 -2
- package/dashboard/.next/standalone/dashboard/.next/prerender-manifest.json +3 -3
- package/dashboard/.next/standalone/dashboard/.next/required-server-files.json +4 -4
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/chunks/ssr/dashboard_3051539d._.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/pages/404.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.json +1 -1
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/{c95cfef94573d615.js → 55858d6b0b93278b.js} +1 -1
- package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-darwin-arm64 → sharp-libvips-linux-x64}/README.md +2 -2
- 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
- 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
- package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-darwin-arm64 → sharp-libvips-linux-x64}/package.json +11 -5
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/README.md +46 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/glib-2.0/include/glibconfig.h +221 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/index.js +1 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/libvips-cpp.so.8.17.3 +0 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +42 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +30 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node +0 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-darwin-arm64 → sharp-linux-x64}/package.json +13 -7
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linuxmusl-x64/lib/sharp-linuxmusl-x64.node +0 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linuxmusl-x64/package.json +46 -0
- package/dashboard/.next/standalone/dashboard/server.js +1 -1
- package/dashboard/.next/standalone/node_modules/@img/{sharp-libvips-darwin-arm64 → sharp-libvips-linux-x64}/package.json +11 -5
- package/dashboard/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +42 -0
- package/dashboard/.next/standalone/node_modules/@img/{sharp-darwin-arm64 → sharp-linux-x64}/package.json +13 -7
- package/dashboard/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json +46 -0
- package/dist/api/routes/admin.js +14 -2
- package/dist/api/routes/system.js +63 -0
- package/dist/cli/stats-banner.d.ts +6 -0
- package/dist/cli/stats-banner.js +48 -0
- package/dist/cli/stats-command.d.ts +4 -0
- package/dist/cli/stats-command.js +83 -0
- package/dist/database/init.js +4 -4
- package/dist/defence/audit/index.d.ts +2 -2
- package/dist/defence/audit/index.js +1 -1
- package/dist/defence/audit/queries.d.ts +12 -0
- package/dist/defence/audit/queries.js +40 -0
- package/dist/index.js +69 -2
- package/dist/license/cli.js +50 -22
- package/dist/license/index.d.ts +1 -1
- package/dist/license/index.js +1 -1
- package/dist/license/store.d.ts +4 -1
- package/dist/license/store.js +38 -15
- package/dist/license/trial.d.ts +48 -0
- package/dist/license/trial.js +128 -0
- package/dist/server.js +26 -8
- package/dist/setup/openclaw.js +75 -17
- package/package.json +1 -1
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-darwin-arm64/lib/sharp-darwin-arm64.node +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{oqbBQnPvKRkfU3j_7cE7c → 5yxbHrMFACE5ILOtSpx42}/_buildManifest.js +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{oqbBQnPvKRkfU3j_7cE7c → 5yxbHrMFACE5ILOtSpx42}/_clientMiddlewareManifest.json +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{oqbBQnPvKRkfU3j_7cE7c → 5yxbHrMFACE5ILOtSpx42}/_ssgManifest.js +0 -0
- /package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-darwin-arm64 → sharp-libvips-linux-x64}/lib/index.js +0 -0
- /package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-darwin-arm64 → sharp-libvips-linux-x64}/versions.json +0 -0
package/dist/license/cli.js
CHANGED
|
@@ -5,9 +5,15 @@
|
|
|
5
5
|
* shieldcortex license status
|
|
6
6
|
* shieldcortex license deactivate
|
|
7
7
|
*/
|
|
8
|
-
import { activateLicense, deactivateLicense, getLicense, getLicenseFile } from './store.js';
|
|
8
|
+
import { activateLicense, deactivateLicense, getLicense, getLicenseFile, getTrialStatus } from './store.js';
|
|
9
9
|
import { listFeatures } from './gate.js';
|
|
10
10
|
import { validateOnceNow } from './validate.js';
|
|
11
|
+
import { existsSync } from 'fs';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
import { homedir } from 'os';
|
|
14
|
+
function getLicenseFilePath() {
|
|
15
|
+
return join(process.env.SHIELDCORTEX_CONFIG_DIR || join(homedir(), '.shieldcortex'), 'license.json');
|
|
16
|
+
}
|
|
11
17
|
const bold = '\x1b[1m';
|
|
12
18
|
const reset = '\x1b[0m';
|
|
13
19
|
const green = '\x1b[32m';
|
|
@@ -72,31 +78,53 @@ async function handleActivate(key) {
|
|
|
72
78
|
function handleStatus() {
|
|
73
79
|
const info = getLicense();
|
|
74
80
|
const file = getLicenseFile();
|
|
81
|
+
const licenseFileExists = existsSync(getLicenseFilePath());
|
|
82
|
+
const trial = getTrialStatus(licenseFileExists);
|
|
75
83
|
console.log(`\n${bold}ShieldCortex Licence${reset}`);
|
|
76
84
|
console.log('═'.repeat(40));
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
console.log(
|
|
80
|
-
console.log(`
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
if (info.valid) {
|
|
86
|
+
// Paid license active — show full license info, no trial messaging
|
|
87
|
+
console.log(` Tier: ${tierBadge(info.tier)}`);
|
|
88
|
+
console.log(` Email: ${info.email}`);
|
|
89
|
+
if (info.expiresAt) {
|
|
90
|
+
const daysStr = info.daysUntilExpiry !== null && info.daysUntilExpiry > 0
|
|
91
|
+
? `(${info.daysUntilExpiry} days remaining)`
|
|
92
|
+
: info.daysUntilExpiry !== null && info.daysUntilExpiry <= 0
|
|
93
|
+
? `${red}(expired)${reset}`
|
|
94
|
+
: '';
|
|
95
|
+
console.log(` Expires: ${info.expiresAt.toLocaleDateString()} ${daysStr}`);
|
|
96
|
+
}
|
|
97
|
+
if (file?.lastValidatedAt) {
|
|
98
|
+
console.log(` Validated: ${new Date(file.lastValidatedAt).toLocaleString()}`);
|
|
99
|
+
}
|
|
100
|
+
if (file?.validationStatus) {
|
|
101
|
+
const statusColor = file.validationStatus === 'valid' ? green :
|
|
102
|
+
file.validationStatus === 'revoked' ? red : yellow;
|
|
103
|
+
console.log(` Status: ${statusColor}${file.validationStatus}${reset}`);
|
|
104
|
+
}
|
|
92
105
|
}
|
|
93
|
-
if (
|
|
94
|
-
|
|
106
|
+
else if (trial?.active) {
|
|
107
|
+
// Trial active — no paid license
|
|
108
|
+
const expiryDate = new Date(trial.expiresAt).toLocaleDateString();
|
|
109
|
+
console.log(` Tier: ${tierBadge('pro')} ${dim}(trial)${reset}`);
|
|
110
|
+
console.log(` 🎁 Pro Trial: ${yellow}${trial.daysRemaining} day${trial.daysRemaining !== 1 ? 's' : ''} remaining${reset} (expires ${expiryDate})`);
|
|
111
|
+
console.log(`\n Upgrade to keep Pro features after the trial:`);
|
|
112
|
+
console.log(` ${cyan}https://shieldcortex.ai/pricing${reset}`);
|
|
113
|
+
console.log(` shieldcortex license activate <key>`);
|
|
95
114
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
115
|
+
else {
|
|
116
|
+
// No license and no active trial
|
|
117
|
+
console.log(` Tier: ${tierBadge('free')}`);
|
|
118
|
+
if (trial && !trial.active) {
|
|
119
|
+
// Trial existed but expired
|
|
120
|
+
console.log(`\n ${yellow}Pro trial expired.${reset} Upgrade at ${cyan}https://shieldcortex.ai/pricing${reset}`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.log(`\n No licence activated.`);
|
|
124
|
+
console.log(` Upgrade at ${cyan}https://shieldcortex.ai/pricing${reset}`);
|
|
125
|
+
}
|
|
126
|
+
console.log(` Activate: shieldcortex license activate <key>\n`);
|
|
127
|
+
return;
|
|
100
128
|
}
|
|
101
129
|
console.log(`\n${bold}Features:${reset}`);
|
|
102
130
|
const features = listFeatures();
|
package/dist/license/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* License module — re-exports for public API and internal use.
|
|
3
3
|
*/
|
|
4
|
-
export { getLicense, getLicenseTier, getLicenseFile, clearLicenseCache } from './store.js';
|
|
4
|
+
export { getLicense, getLicenseTier, getLicenseFile, clearLicenseCache, getTrialStatus, isTrialActive, getTrialDaysRemaining } from './store.js';
|
|
5
5
|
export { verifyLicenseKey, parseLicensePayload } from './verify.js';
|
|
6
6
|
export { isFeatureEnabled, requireFeature, getRequiredTier, listFeatures, FeatureGatedError, } from './gate.js';
|
|
7
7
|
export { scheduleOnlineValidation } from './validate.js';
|
package/dist/license/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* License module — re-exports for public API and internal use.
|
|
3
3
|
*/
|
|
4
4
|
// ── Public API (exported from lib.ts) ────────────────────
|
|
5
|
-
export { getLicense, getLicenseTier, getLicenseFile, clearLicenseCache } from './store.js';
|
|
5
|
+
export { getLicense, getLicenseTier, getLicenseFile, clearLicenseCache, getTrialStatus, isTrialActive, getTrialDaysRemaining } from './store.js';
|
|
6
6
|
export { verifyLicenseKey, parseLicensePayload } from './verify.js';
|
|
7
7
|
export { isFeatureEnabled, requireFeature, getRequiredTier, listFeatures, FeatureGatedError, } from './gate.js';
|
|
8
8
|
export { scheduleOnlineValidation } from './validate.js';
|
package/dist/license/store.d.ts
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* License is cached in memory after first read.
|
|
11
11
|
*/
|
|
12
12
|
import type { LicenseTier, LicenseInfo, LicenseFile } from './keys.js';
|
|
13
|
+
import { getTrialStatus, isTrialActive, getTrialDaysRemaining } from './trial.js';
|
|
14
|
+
export { isTrialActive, getTrialDaysRemaining, getTrialStatus };
|
|
13
15
|
/** Clear the in-memory cache (useful for testing or after activation). */
|
|
14
16
|
export declare function clearLicenseCache(): void;
|
|
15
17
|
/**
|
|
@@ -19,7 +21,8 @@ export declare function clearLicenseCache(): void;
|
|
|
19
21
|
export declare function getLicense(): LicenseInfo;
|
|
20
22
|
/**
|
|
21
23
|
* Quick accessor for the current licence tier.
|
|
22
|
-
* Returns '
|
|
24
|
+
* Returns 'pro' during an active trial (if no paid license is active).
|
|
25
|
+
* Returns 'free' otherwise.
|
|
23
26
|
*/
|
|
24
27
|
export declare function getLicenseTier(): LicenseTier;
|
|
25
28
|
/**
|
package/dist/license/store.js
CHANGED
|
@@ -13,13 +13,20 @@ import { readFileSync, writeFileSync, existsSync, unlinkSync, mkdirSync } from '
|
|
|
13
13
|
import { join } from 'path';
|
|
14
14
|
import { homedir } from 'os';
|
|
15
15
|
import { verifyLicenseKey } from './verify.js';
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
import { getTrialStatus, isTrialActive, getTrialDaysRemaining, clearTrialCache, } from './trial.js';
|
|
17
|
+
export { isTrialActive, getTrialDaysRemaining, getTrialStatus };
|
|
18
|
+
function getConfigDir() {
|
|
19
|
+
return process.env.SHIELDCORTEX_CONFIG_DIR || join(homedir(), '.shieldcortex');
|
|
20
|
+
}
|
|
21
|
+
function getLicenseFilePath() {
|
|
22
|
+
return join(getConfigDir(), 'license.json');
|
|
23
|
+
}
|
|
18
24
|
// ── Cache ────────────────────────────────────────────────
|
|
19
25
|
let cachedLicense = null;
|
|
20
26
|
/** Clear the in-memory cache (useful for testing or after activation). */
|
|
21
27
|
export function clearLicenseCache() {
|
|
22
28
|
cachedLicense = null;
|
|
29
|
+
clearTrialCache();
|
|
23
30
|
}
|
|
24
31
|
// ── Read ─────────────────────────────────────────────────
|
|
25
32
|
/**
|
|
@@ -39,11 +46,12 @@ export function getLicense() {
|
|
|
39
46
|
subscriptionId: null,
|
|
40
47
|
};
|
|
41
48
|
try {
|
|
42
|
-
|
|
49
|
+
const licenseFile = getLicenseFilePath();
|
|
50
|
+
if (!existsSync(licenseFile)) {
|
|
43
51
|
cachedLicense = FREE;
|
|
44
52
|
return FREE;
|
|
45
53
|
}
|
|
46
|
-
const raw = JSON.parse(readFileSync(
|
|
54
|
+
const raw = JSON.parse(readFileSync(licenseFile, 'utf-8'));
|
|
47
55
|
if (!raw.key) {
|
|
48
56
|
cachedLicense = FREE;
|
|
49
57
|
return FREE;
|
|
@@ -68,10 +76,19 @@ export function getLicense() {
|
|
|
68
76
|
}
|
|
69
77
|
/**
|
|
70
78
|
* Quick accessor for the current licence tier.
|
|
71
|
-
* Returns '
|
|
79
|
+
* Returns 'pro' during an active trial (if no paid license is active).
|
|
80
|
+
* Returns 'free' otherwise.
|
|
72
81
|
*/
|
|
73
82
|
export function getLicenseTier() {
|
|
74
|
-
|
|
83
|
+
const license = getLicense();
|
|
84
|
+
// Paid license takes priority — trial is irrelevant
|
|
85
|
+
if (license.valid)
|
|
86
|
+
return license.tier;
|
|
87
|
+
// No paid license: check if trial is active
|
|
88
|
+
const licenseFileExists = existsSync(getLicenseFilePath());
|
|
89
|
+
if (isTrialActive(licenseFileExists))
|
|
90
|
+
return 'pro';
|
|
91
|
+
return license.tier; // 'free'
|
|
75
92
|
}
|
|
76
93
|
/**
|
|
77
94
|
* Read the raw license file data (for status display).
|
|
@@ -79,9 +96,10 @@ export function getLicenseTier() {
|
|
|
79
96
|
*/
|
|
80
97
|
export function getLicenseFile() {
|
|
81
98
|
try {
|
|
82
|
-
|
|
99
|
+
const licenseFile = getLicenseFilePath();
|
|
100
|
+
if (!existsSync(licenseFile))
|
|
83
101
|
return null;
|
|
84
|
-
return JSON.parse(readFileSync(
|
|
102
|
+
return JSON.parse(readFileSync(licenseFile, 'utf-8'));
|
|
85
103
|
}
|
|
86
104
|
catch {
|
|
87
105
|
return null;
|
|
@@ -104,8 +122,10 @@ export function activateLicense(key) {
|
|
|
104
122
|
lastValidatedAt: null,
|
|
105
123
|
validationStatus: 'unvalidated',
|
|
106
124
|
};
|
|
107
|
-
|
|
108
|
-
|
|
125
|
+
const configDir = getConfigDir();
|
|
126
|
+
const licenseFile = getLicenseFilePath();
|
|
127
|
+
mkdirSync(configDir, { recursive: true });
|
|
128
|
+
writeFileSync(licenseFile, JSON.stringify(file, null, 2) + '\n', { mode: 0o600 });
|
|
109
129
|
// Invalidate cache
|
|
110
130
|
cachedLicense = info;
|
|
111
131
|
return info;
|
|
@@ -115,14 +135,16 @@ export function activateLicense(key) {
|
|
|
115
135
|
*/
|
|
116
136
|
export function deactivateLicense() {
|
|
117
137
|
try {
|
|
118
|
-
|
|
119
|
-
|
|
138
|
+
const licenseFile = getLicenseFilePath();
|
|
139
|
+
if (existsSync(licenseFile)) {
|
|
140
|
+
unlinkSync(licenseFile);
|
|
120
141
|
}
|
|
121
142
|
}
|
|
122
143
|
catch {
|
|
123
144
|
// Best effort
|
|
124
145
|
}
|
|
125
146
|
cachedLicense = null;
|
|
147
|
+
clearTrialCache();
|
|
126
148
|
}
|
|
127
149
|
/**
|
|
128
150
|
* Update the validation status and timestamp in the license file.
|
|
@@ -130,12 +152,13 @@ export function deactivateLicense() {
|
|
|
130
152
|
*/
|
|
131
153
|
export function updateValidationStatus(status) {
|
|
132
154
|
try {
|
|
133
|
-
|
|
155
|
+
const licenseFile = getLicenseFilePath();
|
|
156
|
+
if (!existsSync(licenseFile))
|
|
134
157
|
return;
|
|
135
|
-
const raw = JSON.parse(readFileSync(
|
|
158
|
+
const raw = JSON.parse(readFileSync(licenseFile, 'utf-8'));
|
|
136
159
|
raw.validationStatus = status;
|
|
137
160
|
raw.lastValidatedAt = new Date().toISOString();
|
|
138
|
-
writeFileSync(
|
|
161
|
+
writeFileSync(licenseFile, JSON.stringify(raw, null, 2) + '\n', { mode: 0o600 });
|
|
139
162
|
// If revoked or expired, invalidate cache so next getLicense() re-verifies
|
|
140
163
|
if (status === 'revoked' || status === 'expired') {
|
|
141
164
|
cachedLicense = null;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 14-day Pro trial — auto-granted on first install, no signup required.
|
|
3
|
+
*
|
|
4
|
+
* Trial state is stored in ~/.shieldcortex/trial.json.
|
|
5
|
+
* It is NEVER reset on reinstall (file persists across npm updates).
|
|
6
|
+
* An active paid license always takes priority over the trial.
|
|
7
|
+
*/
|
|
8
|
+
export interface TrialFile {
|
|
9
|
+
startedAt: string;
|
|
10
|
+
durationDays: number;
|
|
11
|
+
acknowledged: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface TrialStatus {
|
|
14
|
+
active: boolean;
|
|
15
|
+
daysRemaining: number;
|
|
16
|
+
startedAt: string;
|
|
17
|
+
expiresAt: string;
|
|
18
|
+
justCreated: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** Clear the in-memory trial cache (useful for testing). */
|
|
21
|
+
export declare function clearTrialCache(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Read (and lazily create) the trial state.
|
|
24
|
+
*
|
|
25
|
+
* - If `trial.json` already exists: read and return its status.
|
|
26
|
+
* - If `trial.json` does NOT exist AND `license.json` does NOT exist: create it now (first install).
|
|
27
|
+
* - If `trial.json` does NOT exist AND `license.json` DOES exist: don't create — user already has a paid license.
|
|
28
|
+
* - Returns null if no trial applies.
|
|
29
|
+
*
|
|
30
|
+
* Pass `licenseFileExists` to avoid a second disk read inside store.ts.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getTrialStatus(licenseFileExists: boolean): TrialStatus | null;
|
|
33
|
+
/**
|
|
34
|
+
* Whether a trial is currently active (Pro features unlocked by trial).
|
|
35
|
+
* Only returns true when no paid license is in effect — callers should
|
|
36
|
+
* check the license first and only fall back to this.
|
|
37
|
+
*/
|
|
38
|
+
export declare function isTrialActive(licenseFileExists: boolean): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Days remaining in the trial (0 if expired or no trial).
|
|
41
|
+
*/
|
|
42
|
+
export declare function getTrialDaysRemaining(licenseFileExists: boolean): number;
|
|
43
|
+
/**
|
|
44
|
+
* Mark the welcome message as acknowledged (suppress on subsequent runs).
|
|
45
|
+
*/
|
|
46
|
+
export declare function acknowledgeTrialWelcome(): void;
|
|
47
|
+
/** Exposed for tests — returns the full trial file path */
|
|
48
|
+
export declare function getTrialFilePath(): string;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 14-day Pro trial — auto-granted on first install, no signup required.
|
|
3
|
+
*
|
|
4
|
+
* Trial state is stored in ~/.shieldcortex/trial.json.
|
|
5
|
+
* It is NEVER reset on reinstall (file persists across npm updates).
|
|
6
|
+
* An active paid license always takes priority over the trial.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
const TRIAL_DURATION_DAYS = 14;
|
|
12
|
+
// ── Cache ────────────────────────────────────────────────
|
|
13
|
+
let cachedTrial = undefined; // undefined = not loaded yet
|
|
14
|
+
function getConfigDir() {
|
|
15
|
+
return process.env.SHIELDCORTEX_CONFIG_DIR || join(homedir(), '.shieldcortex');
|
|
16
|
+
}
|
|
17
|
+
function getTrialFile() {
|
|
18
|
+
return join(getConfigDir(), 'trial.json');
|
|
19
|
+
}
|
|
20
|
+
/** Clear the in-memory trial cache (useful for testing). */
|
|
21
|
+
export function clearTrialCache() {
|
|
22
|
+
cachedTrial = undefined;
|
|
23
|
+
}
|
|
24
|
+
// ── Helpers ──────────────────────────────────────────────
|
|
25
|
+
function computeStatus(data, justCreated = false) {
|
|
26
|
+
const startMs = new Date(data.startedAt).getTime();
|
|
27
|
+
const expiryMs = startMs + data.durationDays * 24 * 60 * 60 * 1000;
|
|
28
|
+
const nowMs = Date.now();
|
|
29
|
+
const msRemaining = expiryMs - nowMs;
|
|
30
|
+
const daysRemaining = Math.max(0, Math.ceil(msRemaining / (24 * 60 * 60 * 1000)));
|
|
31
|
+
return {
|
|
32
|
+
active: nowMs < expiryMs,
|
|
33
|
+
daysRemaining,
|
|
34
|
+
startedAt: data.startedAt,
|
|
35
|
+
expiresAt: new Date(expiryMs).toISOString(),
|
|
36
|
+
justCreated,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// ── Public API ────────────────────────────────────────────
|
|
40
|
+
/**
|
|
41
|
+
* Read (and lazily create) the trial state.
|
|
42
|
+
*
|
|
43
|
+
* - If `trial.json` already exists: read and return its status.
|
|
44
|
+
* - If `trial.json` does NOT exist AND `license.json` does NOT exist: create it now (first install).
|
|
45
|
+
* - If `trial.json` does NOT exist AND `license.json` DOES exist: don't create — user already has a paid license.
|
|
46
|
+
* - Returns null if no trial applies.
|
|
47
|
+
*
|
|
48
|
+
* Pass `licenseFileExists` to avoid a second disk read inside store.ts.
|
|
49
|
+
*/
|
|
50
|
+
export function getTrialStatus(licenseFileExists) {
|
|
51
|
+
// Skip trial if env var set (useful for tests and CI)
|
|
52
|
+
if (process.env.SHIELDCORTEX_SKIP_TRIAL === '1')
|
|
53
|
+
return null;
|
|
54
|
+
if (cachedTrial !== undefined)
|
|
55
|
+
return cachedTrial;
|
|
56
|
+
try {
|
|
57
|
+
const trialFile = getTrialFile();
|
|
58
|
+
if (existsSync(trialFile)) {
|
|
59
|
+
const data = JSON.parse(readFileSync(trialFile, 'utf-8'));
|
|
60
|
+
// Return full status whether active or expired — callers use status.active to distinguish.
|
|
61
|
+
// Only return null when no trial file exists at all.
|
|
62
|
+
const status = computeStatus(data, false);
|
|
63
|
+
cachedTrial = status;
|
|
64
|
+
return cachedTrial;
|
|
65
|
+
}
|
|
66
|
+
// No trial file — only create on first install (no license either)
|
|
67
|
+
if (licenseFileExists) {
|
|
68
|
+
cachedTrial = null;
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
// First run — create the trial file
|
|
72
|
+
mkdirSync(getConfigDir(), { recursive: true });
|
|
73
|
+
const now = new Date().toISOString();
|
|
74
|
+
const trialData = {
|
|
75
|
+
startedAt: now,
|
|
76
|
+
durationDays: TRIAL_DURATION_DAYS,
|
|
77
|
+
acknowledged: false,
|
|
78
|
+
};
|
|
79
|
+
writeFileSync(trialFile, JSON.stringify(trialData, null, 2) + '\n', { mode: 0o600 });
|
|
80
|
+
const status = computeStatus(trialData, true);
|
|
81
|
+
cachedTrial = status; // always active on creation
|
|
82
|
+
return cachedTrial;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
cachedTrial = null;
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Whether a trial is currently active (Pro features unlocked by trial).
|
|
91
|
+
* Only returns true when no paid license is in effect — callers should
|
|
92
|
+
* check the license first and only fall back to this.
|
|
93
|
+
*/
|
|
94
|
+
export function isTrialActive(licenseFileExists) {
|
|
95
|
+
return getTrialStatus(licenseFileExists)?.active === true;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Days remaining in the trial (0 if expired or no trial).
|
|
99
|
+
*/
|
|
100
|
+
export function getTrialDaysRemaining(licenseFileExists) {
|
|
101
|
+
return getTrialStatus(licenseFileExists)?.daysRemaining ?? 0;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Mark the welcome message as acknowledged (suppress on subsequent runs).
|
|
105
|
+
*/
|
|
106
|
+
export function acknowledgeTrialWelcome() {
|
|
107
|
+
try {
|
|
108
|
+
const trialFile = getTrialFile();
|
|
109
|
+
if (!existsSync(trialFile))
|
|
110
|
+
return;
|
|
111
|
+
const data = JSON.parse(readFileSync(trialFile, 'utf-8'));
|
|
112
|
+
if (!data.acknowledged) {
|
|
113
|
+
data.acknowledged = true;
|
|
114
|
+
writeFileSync(trialFile, JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
|
|
115
|
+
}
|
|
116
|
+
// Update cache
|
|
117
|
+
if (cachedTrial) {
|
|
118
|
+
cachedTrial = { ...cachedTrial, justCreated: false };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Best effort
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** Exposed for tests — returns the full trial file path */
|
|
126
|
+
export function getTrialFilePath() {
|
|
127
|
+
return getTrialFile();
|
|
128
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -20,7 +20,7 @@ import { getHighPriorityMemories, getRecentMemories, getRelatedMemories, createM
|
|
|
20
20
|
import { detectContradictions } from './memory/contradiction.js';
|
|
21
21
|
import { handleGraphQuery, handleGraphEntities, handleGraphExplain } from './tools/graph.js';
|
|
22
22
|
import { checkDatabaseSize } from './database/init.js';
|
|
23
|
-
import { queryAuditLogs, getAuditStats } from './defence/audit/index.js';
|
|
23
|
+
import { queryAuditLogs, getAuditStats, getLifetimeStats } from './defence/audit/index.js';
|
|
24
24
|
import { scanExistingMemories } from './defence/scanner/index.js';
|
|
25
25
|
import { resolveSource } from './defence/trust/env-detector.js';
|
|
26
26
|
import { logAudit } from './defence/audit/logger.js';
|
|
@@ -614,16 +614,34 @@ but you can use this tool to check for new contradictions at any time.`, {
|
|
|
614
614
|
timeRange: z.enum(['24h', '7d', '30d']).default('24h'),
|
|
615
615
|
}, { title: 'Defence Statistics', readOnlyHint: true, destructiveHint: false, idempotentHint: true }, async (args) => {
|
|
616
616
|
const stats = getAuditStats(args.timeRange);
|
|
617
|
+
const fmt = new Intl.NumberFormat('en-US');
|
|
618
|
+
const n = (v) => fmt.format(v);
|
|
617
619
|
const lines = [
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
`
|
|
621
|
-
`
|
|
622
|
-
`
|
|
623
|
-
`
|
|
620
|
+
`🛡️ ShieldCortex Defence Stats (${args.timeRange})`,
|
|
621
|
+
`${'─'.repeat(36)}`,
|
|
622
|
+
` Total operations: ${n(stats.totalOperations)}`,
|
|
623
|
+
` Allowed: ${n(stats.allowedCount)}`,
|
|
624
|
+
` Blocked: ${n(stats.blockedCount)}`,
|
|
625
|
+
` Quarantined: ${n(stats.quarantinedCount)}`,
|
|
626
|
+
` Top sources: ${stats.topSources.map(s => `${s.source}(${n(s.count)})`).join(', ') || 'none'}`,
|
|
624
627
|
];
|
|
625
628
|
if (Object.keys(stats.threatBreakdown).length > 0) {
|
|
626
|
-
lines.push(`
|
|
629
|
+
lines.push(` Threat types: ${Object.entries(stats.threatBreakdown).map(([k, v]) => `${k}:${n(v)}`).join(', ')}`);
|
|
630
|
+
}
|
|
631
|
+
// Append lifetime totals
|
|
632
|
+
try {
|
|
633
|
+
const lifetime = getLifetimeStats();
|
|
634
|
+
lines.push('');
|
|
635
|
+
lines.push(`All-Time Totals`);
|
|
636
|
+
lines.push(`${'─'.repeat(36)}`);
|
|
637
|
+
lines.push(` Total scans: ${n(lifetime.totalScans)}`);
|
|
638
|
+
lines.push(` Threats blocked: ${n(lifetime.threatsBlocked)}`);
|
|
639
|
+
lines.push(` Quarantined: ${n(lifetime.quarantined)}`);
|
|
640
|
+
lines.push(` Credential leaks: ${n(lifetime.credentialLeaks)}`);
|
|
641
|
+
lines.push(` Memories protected: ${n(lifetime.memoriesProtected)}`);
|
|
642
|
+
}
|
|
643
|
+
catch {
|
|
644
|
+
// Lifetime stats unavailable — not a problem
|
|
627
645
|
}
|
|
628
646
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
629
647
|
});
|
package/dist/setup/openclaw.js
CHANGED
|
@@ -233,15 +233,28 @@ function cleanupLegacyPlugin() {
|
|
|
233
233
|
try {
|
|
234
234
|
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
235
235
|
const config = JSON.parse(raw);
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
236
|
+
let changed = false;
|
|
237
|
+
// Remove legacy entries (old format before native installs)
|
|
238
|
+
if (config.plugins?.entries?.['shieldcortex-realtime']) {
|
|
239
|
+
delete config.plugins.entries['shieldcortex-realtime'];
|
|
240
|
+
if (Object.keys(config.plugins.entries).length === 0)
|
|
241
|
+
delete config.plugins.entries;
|
|
242
|
+
changed = true;
|
|
243
|
+
}
|
|
244
|
+
// Remove stale full-path entries from plugins.allow
|
|
245
|
+
// (pre-v2026.3 format used raw file paths instead of plugin IDs)
|
|
246
|
+
if (Array.isArray(config.plugins?.allow)) {
|
|
247
|
+
const before = config.plugins.allow.length;
|
|
248
|
+
config.plugins.allow = config.plugins.allow.filter((e) => !e.includes(PLUGIN_DIR_NAME) || e === PLUGIN_DIR_NAME);
|
|
249
|
+
if (config.plugins.allow.length < before)
|
|
250
|
+
changed = true;
|
|
251
|
+
}
|
|
241
252
|
if (config.plugins && Object.keys(config.plugins).length === 0)
|
|
242
253
|
delete config.plugins;
|
|
243
|
-
|
|
244
|
-
|
|
254
|
+
if (changed) {
|
|
255
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
256
|
+
console.log('Cleaned up legacy plugin entries from openclaw.json');
|
|
257
|
+
}
|
|
245
258
|
}
|
|
246
259
|
catch {
|
|
247
260
|
// Non-critical — don't fail the install
|
|
@@ -250,9 +263,10 @@ function cleanupLegacyPlugin() {
|
|
|
250
263
|
function openClawConfigPath() {
|
|
251
264
|
return path.join(resolveUserHome(), '.openclaw', 'openclaw.json');
|
|
252
265
|
}
|
|
253
|
-
function trustLocalPlugin(
|
|
266
|
+
function trustLocalPlugin(installDir, version) {
|
|
254
267
|
const configPath = openClawConfigPath();
|
|
255
268
|
const configDir = path.dirname(configPath);
|
|
269
|
+
const pluginId = PLUGIN_DIR_NAME; // "shieldcortex-realtime"
|
|
256
270
|
try {
|
|
257
271
|
if (!fs.existsSync(configDir)) {
|
|
258
272
|
fs.mkdirSync(configDir, { recursive: true });
|
|
@@ -263,12 +277,33 @@ function trustLocalPlugin(indexPath) {
|
|
|
263
277
|
const allow = Array.isArray(plugins.allow)
|
|
264
278
|
? (plugins.allow ?? [])
|
|
265
279
|
: [];
|
|
266
|
-
|
|
267
|
-
|
|
280
|
+
// Remove any stale full-path entries for this plugin
|
|
281
|
+
const cleaned = allow.filter((e) => !e.includes(PLUGIN_DIR_NAME) || e === pluginId);
|
|
282
|
+
if (!cleaned.includes(pluginId)) {
|
|
283
|
+
cleaned.push(pluginId);
|
|
284
|
+
}
|
|
285
|
+
// Add installs entry so OpenClaw recognises the plugin
|
|
286
|
+
const installs = typeof plugins.installs === 'object' && plugins.installs !== null
|
|
287
|
+
? plugins.installs
|
|
288
|
+
: {};
|
|
289
|
+
installs[pluginId] = {
|
|
290
|
+
source: 'local',
|
|
291
|
+
installPath: installDir,
|
|
292
|
+
version,
|
|
293
|
+
installedAt: new Date().toISOString(),
|
|
294
|
+
};
|
|
295
|
+
// Add entries to enable the plugin
|
|
296
|
+
const entries = typeof plugins.entries === 'object' && plugins.entries !== null
|
|
297
|
+
? plugins.entries
|
|
298
|
+
: {};
|
|
299
|
+
if (!entries[pluginId]) {
|
|
300
|
+
entries[pluginId] = { enabled: true };
|
|
268
301
|
}
|
|
269
302
|
config.plugins = {
|
|
270
303
|
...plugins,
|
|
271
|
-
allow,
|
|
304
|
+
allow: cleaned,
|
|
305
|
+
installs,
|
|
306
|
+
entries,
|
|
272
307
|
};
|
|
273
308
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
274
309
|
return true;
|
|
@@ -383,13 +418,27 @@ function installPlugin() {
|
|
|
383
418
|
catch {
|
|
384
419
|
console.warn(` Warning: ${indexDest} copied but not readable`);
|
|
385
420
|
}
|
|
386
|
-
|
|
387
|
-
|
|
421
|
+
// Read plugin version from manifest
|
|
422
|
+
let pluginVersion = 'unknown';
|
|
423
|
+
try {
|
|
424
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(destDir, 'openclaw.plugin.json'), 'utf-8'));
|
|
425
|
+
pluginVersion = manifest.version ?? pluginVersion;
|
|
426
|
+
}
|
|
427
|
+
catch {
|
|
428
|
+
// Fall back to package.json version
|
|
429
|
+
try {
|
|
430
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf-8'));
|
|
431
|
+
pluginVersion = pkg.version ?? pluginVersion;
|
|
432
|
+
}
|
|
433
|
+
catch { /* keep "unknown" */ }
|
|
434
|
+
}
|
|
435
|
+
if (trustLocalPlugin(destDir, pluginVersion)) {
|
|
436
|
+
console.log('Registered plugin in OpenClaw config (plugins.allow + installs)');
|
|
388
437
|
console.log(`Installed real-time plugin to ${destDir}`);
|
|
389
438
|
return 'trusted-local-copy';
|
|
390
439
|
}
|
|
391
440
|
else {
|
|
392
|
-
console.warn(' Warning: Could not
|
|
441
|
+
console.warn(' Warning: Could not register plugin in OpenClaw config');
|
|
393
442
|
console.log(`Installed real-time plugin to ${destDir}`);
|
|
394
443
|
return 'untrusted-local-copy';
|
|
395
444
|
}
|
|
@@ -421,10 +470,18 @@ function uninstallPlugin() {
|
|
|
421
470
|
if (fs.existsSync(configPath)) {
|
|
422
471
|
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
423
472
|
const config = JSON.parse(raw);
|
|
473
|
+
const pluginId = PLUGIN_DIR_NAME;
|
|
424
474
|
if (Array.isArray(config.plugins?.allow)) {
|
|
425
|
-
|
|
426
|
-
|
|
475
|
+
// Remove both old file-path entries and new short-name entries
|
|
476
|
+
config.plugins.allow = config.plugins.allow.filter((entry) => !entry.includes(pluginId));
|
|
477
|
+
}
|
|
478
|
+
if (config.plugins?.installs?.[pluginId]) {
|
|
479
|
+
delete config.plugins.installs[pluginId];
|
|
480
|
+
}
|
|
481
|
+
if (config.plugins?.entries?.[pluginId]) {
|
|
482
|
+
delete config.plugins.entries[pluginId];
|
|
427
483
|
}
|
|
484
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
428
485
|
}
|
|
429
486
|
fs.rmSync(destDir, { recursive: true });
|
|
430
487
|
console.log(`Removed real-time plugin from ${destDir}`);
|
|
@@ -457,7 +514,8 @@ function localPluginTrustStatus(pluginPath) {
|
|
|
457
514
|
return 'unknown';
|
|
458
515
|
try {
|
|
459
516
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
460
|
-
|
|
517
|
+
const allow = config.plugins?.allow;
|
|
518
|
+
if (Array.isArray(allow) && (allow.includes(PLUGIN_DIR_NAME) || allow.includes(indexPath))) {
|
|
461
519
|
return 'trusted';
|
|
462
520
|
}
|
|
463
521
|
return 'untrusted';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shieldcortex",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.30",
|
|
4
4
|
"description": "Trustworthy memory and security for AI agents. Recall debugging, review queue, OpenClaw session capture, and memory poisoning defence for Claude Code, Codex, OpenClaw, LangChain, and MCP agents.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|