shieldcortex 3.4.27 → 3.4.29
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/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/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 +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_full.segment.rsc +1 -1
- 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/pages/404.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
- package/dist/api/routes/admin.js +14 -2
- package/dist/api/routes/memories.js +25 -5
- package/dist/api/routes/system.js +28 -0
- package/dist/database/init.js +45 -4
- package/dist/index.js +54 -1
- 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/memory/store.js +17 -0
- package/dist/tools/remember.d.ts +2 -2
- package/dist/tools/remember.js +2 -0
- package/package.json +1 -1
- /package/dashboard/.next/standalone/dashboard/.next/static/{HmcjI_64kRaQVUrHxobIe → c51lWSauvNC0BT832gfq4}/_buildManifest.js +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{HmcjI_64kRaQVUrHxobIe → c51lWSauvNC0BT832gfq4}/_clientMiddlewareManifest.json +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{HmcjI_64kRaQVUrHxobIe → c51lWSauvNC0BT832gfq4}/_ssgManifest.js +0 -0
|
@@ -10,6 +10,30 @@ import { detectContradictions, getContradictionsFor } from '../../memory/contrad
|
|
|
10
10
|
import { emitConsolidation } from '../events.js';
|
|
11
11
|
export function registerMemoryRoutes(app, deps) {
|
|
12
12
|
const { requireNotLocked, requireIronDomeAction } = deps;
|
|
13
|
+
function deriveLocalOpenClawSessionId(memory) {
|
|
14
|
+
if (typeof memory.metadata?.sessionId === 'string' && memory.metadata.sessionId.length > 0) {
|
|
15
|
+
return memory.metadata.sessionId;
|
|
16
|
+
}
|
|
17
|
+
if (memory.source?.startsWith('agent:openclaw-plugin:')) {
|
|
18
|
+
return memory.source.slice('agent:openclaw-plugin:'.length);
|
|
19
|
+
}
|
|
20
|
+
const tagSet = new Set(memory.tags.map((tag) => tag.toLowerCase()));
|
|
21
|
+
const looksOpenClaw = memory.sourceKind === 'hook'
|
|
22
|
+
|| memory.sourceKind === 'plugin'
|
|
23
|
+
|| memory.source?.includes('openclaw') === true
|
|
24
|
+
|| tagSet.has('session-end')
|
|
25
|
+
|| tagSet.has('session-stop')
|
|
26
|
+
|| tagSet.has('keyword-trigger')
|
|
27
|
+
|| tagSet.has('openclaw-hook')
|
|
28
|
+
|| tagSet.has('llm-output')
|
|
29
|
+
|| tagSet.has('realtime-plugin')
|
|
30
|
+
|| memory.project === 'openclaw';
|
|
31
|
+
if (!looksOpenClaw)
|
|
32
|
+
return null;
|
|
33
|
+
const createdAt = memory.createdAt.toISOString().slice(0, 10);
|
|
34
|
+
const projectBucket = memory.project?.trim() ? memory.project.trim() : 'unscoped';
|
|
35
|
+
return `legacy-openclaw:${projectBucket}:${createdAt}`;
|
|
36
|
+
}
|
|
13
37
|
app.get('/api/capture/openclaw/sessions', requireNotLocked, (req, res) => {
|
|
14
38
|
try {
|
|
15
39
|
const limit = Math.min(parseInt(req.query.limit, 10) || 20, 100);
|
|
@@ -52,11 +76,7 @@ export function registerMemoryRoutes(app, deps) {
|
|
|
52
76
|
return session;
|
|
53
77
|
};
|
|
54
78
|
for (const memory of openClawMemories) {
|
|
55
|
-
const sessionId =
|
|
56
|
-
? memory.metadata.sessionId
|
|
57
|
-
: memory.source?.startsWith('agent:openclaw-plugin:')
|
|
58
|
-
? memory.source.slice('agent:openclaw-plugin:'.length)
|
|
59
|
-
: null;
|
|
79
|
+
const sessionId = deriveLocalOpenClawSessionId(memory);
|
|
60
80
|
if (!sessionId)
|
|
61
81
|
continue;
|
|
62
82
|
const session = getSession(sessionId);
|
|
@@ -3,10 +3,38 @@ import { getCloudConfig, getCloudSyncControls, getDeviceId, getDeviceName, getDe
|
|
|
3
3
|
import { getQueueStats } from '../../cloud/sync-queue.js';
|
|
4
4
|
import { getDatabase } from '../../database/init.js';
|
|
5
5
|
import { getRequiredTier, isFeatureEnabled } from '../../license/gate.js';
|
|
6
|
+
import { getLicense } from '../../license/store.js';
|
|
7
|
+
import { getTrialStatus } from '../../license/trial.js';
|
|
8
|
+
import { existsSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { homedir } from 'os';
|
|
6
11
|
import { getControlStatus, isKillSwitchActive, pause, resume } from '../control.js';
|
|
7
12
|
import { checkForUpdates, getCurrentVersion, getRunningVersion, performUpdate, scheduleRestart, } from '../version.js';
|
|
8
13
|
export function registerSystemRoutes(app, deps) {
|
|
9
14
|
const { broadcast, clients, requireIronDomeAction } = deps;
|
|
15
|
+
app.get('/api/system/status', (_req, res) => {
|
|
16
|
+
try {
|
|
17
|
+
const licenseFile = join(homedir(), '.shieldcortex', 'license.json');
|
|
18
|
+
const licenseFileExists = existsSync(licenseFile);
|
|
19
|
+
const license = getLicense();
|
|
20
|
+
const trial = getTrialStatus(licenseFileExists);
|
|
21
|
+
const trialInfo = trial
|
|
22
|
+
? {
|
|
23
|
+
active: trial.active,
|
|
24
|
+
daysRemaining: trial.daysRemaining,
|
|
25
|
+
expiresAt: trial.expiresAt.slice(0, 10), // YYYY-MM-DD
|
|
26
|
+
}
|
|
27
|
+
: null;
|
|
28
|
+
res.json({
|
|
29
|
+
tier: license.valid ? license.tier : (trial?.active ? 'pro' : 'free'),
|
|
30
|
+
licenseValid: license.valid,
|
|
31
|
+
trial: trialInfo,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
res.status(500).json({ error: error.message });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
10
38
|
app.get('/api/control/status', (_req, res) => {
|
|
11
39
|
try {
|
|
12
40
|
res.json(getControlStatus());
|
package/dist/database/init.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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;
|
|
@@ -558,8 +558,49 @@ function runMigrations(database) {
|
|
|
558
558
|
database.exec('UPDATE memories SET updated_at = COALESCE(updated_at, created_at, CURRENT_TIMESTAMP) WHERE updated_at IS NULL');
|
|
559
559
|
database.exec("UPDATE memories SET status = COALESCE(status, 'active') WHERE status IS NULL OR status = ''");
|
|
560
560
|
database.exec('UPDATE memories SET pinned = COALESCE(pinned, 0) WHERE pinned IS NULL');
|
|
561
|
+
database.exec(`
|
|
562
|
+
UPDATE memories
|
|
563
|
+
SET source = CASE
|
|
564
|
+
WHEN source = 'user:direct' AND tags LIKE '%session-end%' THEN 'hook:openclaw-session-end'
|
|
565
|
+
WHEN source = 'user:direct' AND tags LIKE '%session-stop%' THEN 'hook:openclaw-session-stop'
|
|
566
|
+
WHEN source = 'user:direct' AND tags LIKE '%keyword-trigger%' THEN 'hook:openclaw-keyword'
|
|
567
|
+
WHEN source IS NULL AND tags LIKE '%llm-output%' THEN 'agent:openclaw-plugin'
|
|
568
|
+
ELSE source
|
|
569
|
+
END
|
|
570
|
+
WHERE
|
|
571
|
+
(source = 'user:direct' OR source IS NULL)
|
|
572
|
+
AND (
|
|
573
|
+
tags LIKE '%session-end%'
|
|
574
|
+
OR tags LIKE '%session-stop%'
|
|
575
|
+
OR tags LIKE '%keyword-trigger%'
|
|
576
|
+
OR tags LIKE '%llm-output%'
|
|
577
|
+
OR tags LIKE '%realtime-plugin%'
|
|
578
|
+
OR tags LIKE '%openclaw-hook%'
|
|
579
|
+
)
|
|
580
|
+
`);
|
|
561
581
|
database.exec("UPDATE memories SET source_kind = CASE WHEN source LIKE 'hook:%' THEN 'hook' WHEN source LIKE 'api:%' THEN 'api' WHEN source LIKE 'agent:%' THEN 'agent' WHEN source LIKE 'cli:%' THEN 'cli' ELSE COALESCE(source_kind, 'user') END WHERE source_kind IS NULL OR source_kind = ''");
|
|
562
582
|
database.exec("UPDATE memories SET capture_method = CASE WHEN tags LIKE '%auto-extracted%' THEN 'auto' WHEN source_kind = 'hook' THEN 'hook' WHEN source_kind = 'api' THEN 'api' WHEN source_kind = 'agent' THEN 'plugin' WHEN source_kind = 'cli' THEN 'manual' ELSE COALESCE(capture_method, 'manual') END WHERE capture_method IS NULL OR capture_method = ''");
|
|
583
|
+
database.exec(`
|
|
584
|
+
UPDATE memories
|
|
585
|
+
SET source_kind = CASE
|
|
586
|
+
WHEN tags LIKE '%session-end%' OR tags LIKE '%session-stop%' OR tags LIKE '%keyword-trigger%' OR tags LIKE '%openclaw-hook%' THEN 'hook'
|
|
587
|
+
WHEN tags LIKE '%llm-output%' OR tags LIKE '%realtime-plugin%' THEN 'agent'
|
|
588
|
+
ELSE source_kind
|
|
589
|
+
END,
|
|
590
|
+
capture_method = CASE
|
|
591
|
+
WHEN tags LIKE '%session-end%' OR tags LIKE '%session-stop%' OR tags LIKE '%openclaw-hook%' THEN 'auto'
|
|
592
|
+
WHEN tags LIKE '%keyword-trigger%' THEN 'hook'
|
|
593
|
+
WHEN tags LIKE '%llm-output%' OR tags LIKE '%realtime-plugin%' THEN 'auto'
|
|
594
|
+
ELSE capture_method
|
|
595
|
+
END
|
|
596
|
+
WHERE
|
|
597
|
+
tags LIKE '%session-end%'
|
|
598
|
+
OR tags LIKE '%session-stop%'
|
|
599
|
+
OR tags LIKE '%keyword-trigger%'
|
|
600
|
+
OR tags LIKE '%openclaw-hook%'
|
|
601
|
+
OR tags LIKE '%llm-output%'
|
|
602
|
+
OR tags LIKE '%realtime-plugin%'
|
|
603
|
+
`);
|
|
563
604
|
database.exec('UPDATE memories SET cloud_excluded = COALESCE(cloud_excluded, 0) WHERE cloud_excluded IS NULL');
|
|
564
605
|
database.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_uuid ON memories(uuid)');
|
|
565
606
|
database.exec('CREATE INDEX IF NOT EXISTS idx_memories_updated ON memories(updated_at DESC)');
|
package/dist/index.js
CHANGED
|
@@ -363,6 +363,59 @@ 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
|
+
}
|
|
366
419
|
// Handle --help / -h / help / unknown args
|
|
367
420
|
if (process.argv[2] === '--help' || process.argv[2] === '-h' || process.argv[2] === 'help') {
|
|
368
421
|
const bold = '\x1b[1m';
|
|
@@ -689,7 +742,7 @@ ${bold}DOCS${reset}
|
|
|
689
742
|
console.error(`Run 'shieldcortex --help' for a list of commands.`);
|
|
690
743
|
process.exit(1);
|
|
691
744
|
}
|
|
692
|
-
const { dbPath, mode } =
|
|
745
|
+
const { dbPath, mode } = parsedArgs;
|
|
693
746
|
let dashboardProcess = null;
|
|
694
747
|
if (mode === 'api') {
|
|
695
748
|
// API mode only - for dashboard visualization
|
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;
|