shieldcortex 4.0.0 → 4.0.1
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/dist/audit/memory-scanner.js +18 -0
- package/dist/audit/rules-file-scanner.js +23 -0
- package/dist/index.js +1 -1
- package/dist/setup/openclaw.d.ts +19 -2
- package/dist/setup/openclaw.js +117 -37
- package/package.json +1 -1
- package/scripts/postinstall.mjs +114 -16
|
@@ -12,6 +12,21 @@ import { join } from 'path';
|
|
|
12
12
|
import { homedir } from 'os';
|
|
13
13
|
import { runDefencePipeline } from '../defence/pipeline.js';
|
|
14
14
|
const LEARN_MORE = 'https://shieldcortex.ai/docs/threats/memory-poisoning';
|
|
15
|
+
// ── ShieldCortex Own-Hook Whitelist ──
|
|
16
|
+
/**
|
|
17
|
+
* Paths belonging to ShieldCortex itself (hooks, plugin manifests, etc.).
|
|
18
|
+
* These should never produce false audit findings (#14).
|
|
19
|
+
*/
|
|
20
|
+
const SHIELDCORTEX_OWN_PATHS = [
|
|
21
|
+
'cortex-memory/HOOK.md',
|
|
22
|
+
'cortex-memory/handler.ts',
|
|
23
|
+
'cortex-memory/handler.js',
|
|
24
|
+
'shieldcortex-realtime',
|
|
25
|
+
];
|
|
26
|
+
function isShieldCortexOwnPath(filePath) {
|
|
27
|
+
const normalised = filePath.replace(/\\/g, '/');
|
|
28
|
+
return SHIELDCORTEX_OWN_PATHS.some(p => normalised.includes(p));
|
|
29
|
+
}
|
|
15
30
|
/** Maximum file size to scan (1 MB) */
|
|
16
31
|
const MAX_FILE_SIZE = 1024 * 1024;
|
|
17
32
|
/** Maximum number of memory files to scan */
|
|
@@ -193,6 +208,9 @@ export function scanMemories() {
|
|
|
193
208
|
}
|
|
194
209
|
const allFindings = [];
|
|
195
210
|
for (const file of files) {
|
|
211
|
+
// Skip ShieldCortex's own hook/plugin files to avoid false positives (#14)
|
|
212
|
+
if (isShieldCortexOwnPath(file))
|
|
213
|
+
continue;
|
|
196
214
|
allFindings.push(...scanMemoryFile(file));
|
|
197
215
|
}
|
|
198
216
|
return {
|
|
@@ -13,6 +13,26 @@ import { existsSync, readFileSync, statSync } from 'fs';
|
|
|
13
13
|
import { join, basename } from 'path';
|
|
14
14
|
import { scanSkill, discoverSkillFiles } from '../defence/skill-scanner/index.js';
|
|
15
15
|
const LEARN_MORE = 'https://shieldcortex.ai/docs/threats/rules-file-backdoor';
|
|
16
|
+
// ── ShieldCortex Own-Hook Whitelist ──
|
|
17
|
+
/**
|
|
18
|
+
* Paths belonging to ShieldCortex itself.
|
|
19
|
+
* These are legitimate hooks/plugins that should never be flagged by the
|
|
20
|
+
* audit scanner — doing so produces false HIGH/MEDIUM findings (#14).
|
|
21
|
+
*/
|
|
22
|
+
const SHIELDCORTEX_OWN_PATHS = [
|
|
23
|
+
'cortex-memory/HOOK.md',
|
|
24
|
+
'cortex-memory/handler.ts',
|
|
25
|
+
'cortex-memory/handler.js',
|
|
26
|
+
'shieldcortex-realtime',
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* Return true if this file belongs to ShieldCortex's own hook/plugin set
|
|
30
|
+
* and should be excluded from audit findings.
|
|
31
|
+
*/
|
|
32
|
+
function isShieldCortexOwnPath(filePath) {
|
|
33
|
+
const normalised = filePath.replace(/\\/g, '/');
|
|
34
|
+
return SHIELDCORTEX_OWN_PATHS.some(p => normalised.includes(p));
|
|
35
|
+
}
|
|
16
36
|
// ── Unicode Backdoor Detection ──
|
|
17
37
|
/** Invisible Unicode characters used in the "Rules File Backdoor" attack. */
|
|
18
38
|
const INVISIBLE_UNICODE = [
|
|
@@ -164,6 +184,9 @@ export function scanRulesFiles() {
|
|
|
164
184
|
}
|
|
165
185
|
const allFindings = [];
|
|
166
186
|
for (const file of allFiles) {
|
|
187
|
+
// Skip ShieldCortex's own hook/plugin files to avoid false positives (#14)
|
|
188
|
+
if (isShieldCortexOwnPath(file))
|
|
189
|
+
continue;
|
|
167
190
|
allFindings.push(...scanRulesFile(file));
|
|
168
191
|
}
|
|
169
192
|
return {
|
package/dist/index.js
CHANGED
|
@@ -538,7 +538,7 @@ ${bold}DOCS${reset}
|
|
|
538
538
|
}
|
|
539
539
|
// Handle "openclaw" subcommand (backward compat: "clawdbot" also accepted)
|
|
540
540
|
if (process.argv[2] === 'openclaw' || process.argv[2] === 'clawdbot') {
|
|
541
|
-
await handleOpenClawCommand(process.argv[3] || '');
|
|
541
|
+
await handleOpenClawCommand(process.argv[3] || '', process.argv.slice(4));
|
|
542
542
|
return;
|
|
543
543
|
}
|
|
544
544
|
// Handle "copilot" subcommand (VS Code + Cursor MCP setup)
|
package/dist/setup/openclaw.d.ts
CHANGED
|
@@ -5,6 +5,14 @@
|
|
|
5
5
|
* the real-time plugin into the OpenClaw extensions directory.
|
|
6
6
|
* Supports both Claude Code (native binary) and legacy OpenClaw (Node.js).
|
|
7
7
|
*/
|
|
8
|
+
/**
|
|
9
|
+
* Detect whether ShieldCortex is running inside a Docker container
|
|
10
|
+
* or similar isolated environment (Umbrel, Unraid, NixOS sandbox, etc.).
|
|
11
|
+
*
|
|
12
|
+
* In these environments, OpenClaw hook/plugin installation may crash or
|
|
13
|
+
* produce broken state. We warn and skip by default.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isDockerEnvironment(): boolean;
|
|
8
16
|
/**
|
|
9
17
|
* Find ALL valid hook directories for install/uninstall/status.
|
|
10
18
|
*
|
|
@@ -12,7 +20,16 @@
|
|
|
12
20
|
* Creates the hooks/ subdirectory if the parent config dir exists.
|
|
13
21
|
*/
|
|
14
22
|
export declare function findAllHooksDirs(): string[];
|
|
15
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Install options for `shieldcortex openclaw install`.
|
|
25
|
+
*/
|
|
26
|
+
export interface OpenClawInstallOptions {
|
|
27
|
+
/** Skip hook installation (--no-hooks flag) */
|
|
28
|
+
noHooks?: boolean;
|
|
29
|
+
/** Skip plugin installation (--no-plugins flag) */
|
|
30
|
+
noPlugins?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export declare function installOpenClawHook(options?: OpenClawInstallOptions): Promise<void>;
|
|
16
33
|
export declare function uninstallOpenClawHook(): Promise<void>;
|
|
17
34
|
export declare function openClawHookStatus(): Promise<void>;
|
|
18
|
-
export declare function handleOpenClawCommand(subcommand: string): Promise<void>;
|
|
35
|
+
export declare function handleOpenClawCommand(subcommand: string, extraArgs?: string[]): Promise<void>;
|
package/dist/setup/openclaw.js
CHANGED
|
@@ -22,6 +22,35 @@ const PLUGIN_PACKAGE_SOURCE = path.resolve(__dirname, '..', '..', 'plugins', 'op
|
|
|
22
22
|
const PLUGIN_DIR_NAME = 'shieldcortex-realtime';
|
|
23
23
|
const HOOK_FILES = ['HOOK.md', 'handler.ts'];
|
|
24
24
|
const OPENCLAW_SKIP_NATIVE_INSTALL_ENV = 'SHIELDCORTEX_SKIP_NATIVE_OPENCLAW_INSTALL';
|
|
25
|
+
// ==================== Docker/Container Detection ====================
|
|
26
|
+
/**
|
|
27
|
+
* Detect whether ShieldCortex is running inside a Docker container
|
|
28
|
+
* or similar isolated environment (Umbrel, Unraid, NixOS sandbox, etc.).
|
|
29
|
+
*
|
|
30
|
+
* In these environments, OpenClaw hook/plugin installation may crash or
|
|
31
|
+
* produce broken state. We warn and skip by default.
|
|
32
|
+
*/
|
|
33
|
+
export function isDockerEnvironment() {
|
|
34
|
+
// Standard Docker marker file
|
|
35
|
+
try {
|
|
36
|
+
if (fs.existsSync('/.dockerenv'))
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
catch { /* ignore */ }
|
|
40
|
+
// Explicit env overrides
|
|
41
|
+
if (process.env.DOCKER === 'true' || process.env.DOCKER === '1')
|
|
42
|
+
return true;
|
|
43
|
+
if (process.env.container === 'docker')
|
|
44
|
+
return true;
|
|
45
|
+
// /proc/1/cgroup contains "docker" or "kubepods" on containerised systems
|
|
46
|
+
try {
|
|
47
|
+
const cgroup = fs.readFileSync('/proc/1/cgroup', 'utf-8');
|
|
48
|
+
if (/docker|kubepods|containerd/i.test(cgroup))
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch { /* not Linux or not accessible */ }
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
25
54
|
/**
|
|
26
55
|
* Resolve the real user's home directory.
|
|
27
56
|
*
|
|
@@ -373,7 +402,27 @@ function isPluginInLoadPaths() {
|
|
|
373
402
|
return false;
|
|
374
403
|
}
|
|
375
404
|
}
|
|
376
|
-
|
|
405
|
+
/**
|
|
406
|
+
* Install the real-time plugin.
|
|
407
|
+
* In Docker environments, skip with a warning — plugin install can crash
|
|
408
|
+
* the gateway in containers where filesystem permissions differ.
|
|
409
|
+
*
|
|
410
|
+
* @param options.noPlugins Skip plugin installation entirely (--no-plugins flag)
|
|
411
|
+
*/
|
|
412
|
+
function installPlugin(options = {}) {
|
|
413
|
+
if (options.noPlugins) {
|
|
414
|
+
console.log(' Skipping plugin install (--no-plugins flag).');
|
|
415
|
+
return 'skipped';
|
|
416
|
+
}
|
|
417
|
+
// Docker / container environments: warn and skip
|
|
418
|
+
if (isDockerEnvironment()) {
|
|
419
|
+
console.warn(' ⚠ Docker/container environment detected.');
|
|
420
|
+
console.warn(' Skipping real-time plugin install to avoid gateway crash.');
|
|
421
|
+
console.warn(' To install manually after confirming OpenClaw support:');
|
|
422
|
+
console.warn(' DOCKER=false shieldcortex openclaw install');
|
|
423
|
+
console.warn(' Or suppress this warning: shieldcortex openclaw install --no-plugins');
|
|
424
|
+
return 'skipped';
|
|
425
|
+
}
|
|
377
426
|
const nativeInstall = tryNativeOpenClawPluginInstall();
|
|
378
427
|
if (nativeInstall) {
|
|
379
428
|
return nativeInstall;
|
|
@@ -481,7 +530,6 @@ function uninstallPlugin() {
|
|
|
481
530
|
if (!extensionsDir)
|
|
482
531
|
return false;
|
|
483
532
|
const destDir = path.join(extensionsDir, PLUGIN_DIR_NAME);
|
|
484
|
-
const indexPath = path.join(destDir, 'index.js');
|
|
485
533
|
if (!fs.existsSync(destDir))
|
|
486
534
|
return false;
|
|
487
535
|
try {
|
|
@@ -543,8 +591,7 @@ function localPluginTrustStatus(pluginPath) {
|
|
|
543
591
|
return 'unknown';
|
|
544
592
|
}
|
|
545
593
|
}
|
|
546
|
-
|
|
547
|
-
export async function installOpenClawHook() {
|
|
594
|
+
export async function installOpenClawHook(options = {}) {
|
|
548
595
|
const hooksDirs = findAllHooksDirs();
|
|
549
596
|
if (hooksDirs.length === 0) {
|
|
550
597
|
const home = resolveUserHome();
|
|
@@ -570,52 +617,76 @@ export async function installOpenClawHook() {
|
|
|
570
617
|
}
|
|
571
618
|
// Clean up legacy plugin entry that caused config validation errors
|
|
572
619
|
cleanupLegacyPlugin();
|
|
573
|
-
//
|
|
620
|
+
// Docker / container: warn about environment
|
|
621
|
+
if (isDockerEnvironment()) {
|
|
622
|
+
console.warn('⚠ Docker/container environment detected.');
|
|
623
|
+
console.warn(' Hook and plugin installation may behave differently in containers.');
|
|
624
|
+
console.warn(' Use --no-plugins to skip plugin install, or --no-hooks to skip hooks.');
|
|
625
|
+
console.warn('');
|
|
626
|
+
}
|
|
574
627
|
let installed = 0;
|
|
575
628
|
let migratedLegacy = 0;
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
console.log(`
|
|
629
|
+
if (!options.noHooks) {
|
|
630
|
+
// Install to ALL detected hook directories
|
|
631
|
+
for (const hooksDir of hooksDirs) {
|
|
632
|
+
const destDir = preferredHookDir(hooksDir);
|
|
633
|
+
try {
|
|
634
|
+
const legacyDirsBeforeInstall = detectLegacyHookVariants(hooksDir);
|
|
635
|
+
copyHookFiles(HOOK_SOURCE, destDir);
|
|
636
|
+
console.log(`Installed cortex-memory hook to ${destDir}`);
|
|
637
|
+
if (legacyDirsBeforeInstall.length > 0) {
|
|
638
|
+
console.log(`Detected legacy OpenClaw hook layout in ${hooksDir} — migrating to ${destDir}`);
|
|
639
|
+
}
|
|
640
|
+
for (const removedDir of removeLegacyHookVariants(hooksDir)) {
|
|
641
|
+
console.log(`Removed legacy cortex-memory hook from ${removedDir}`);
|
|
642
|
+
migratedLegacy++;
|
|
643
|
+
}
|
|
644
|
+
installed++;
|
|
584
645
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
646
|
+
catch (err) {
|
|
647
|
+
const code = err.code;
|
|
648
|
+
if (code === 'EACCES' || code === 'EPERM') {
|
|
649
|
+
console.warn(` Skipped ${destDir} (permission denied)`);
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
// Never throw — warn gracefully
|
|
653
|
+
console.warn(` Warning: Could not install hook to ${destDir}: ${err.message}`);
|
|
654
|
+
}
|
|
588
655
|
}
|
|
589
|
-
installed++;
|
|
590
656
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
}
|
|
657
|
+
if (installed === 0) {
|
|
658
|
+
console.warn('Could not install hooks to any directory (permission denied or error).');
|
|
659
|
+
console.log('Try one of these:');
|
|
660
|
+
console.log(' sudo "$(command -v shieldcortex)" openclaw install');
|
|
661
|
+
console.log(' sudo chown -R "$USER":"$USER" ~/.openclaw ~/.claude');
|
|
662
|
+
console.log(' shieldcortex openclaw install --no-hooks # skip hook install');
|
|
663
|
+
// Do not exit(1) — allow plugin install to continue
|
|
599
664
|
}
|
|
600
665
|
}
|
|
601
|
-
|
|
602
|
-
console.
|
|
603
|
-
|
|
604
|
-
console.log(' sudo "$(command -v shieldcortex)" openclaw install');
|
|
605
|
-
console.log(' sudo chown -R "$USER":"$USER" ~/.openclaw ~/.claude');
|
|
606
|
-
console.log(' shieldcortex openclaw install');
|
|
607
|
-
process.exit(1);
|
|
666
|
+
else {
|
|
667
|
+
console.log('Skipping hook installation (--no-hooks flag).');
|
|
668
|
+
installed = hooksDirs.length; // pretend success so plugin install proceeds
|
|
608
669
|
}
|
|
609
670
|
// Install the real-time plugin to the extensions directory
|
|
610
|
-
const pluginInstallMode = installPlugin();
|
|
671
|
+
const pluginInstallMode = installPlugin({ noPlugins: options.noPlugins });
|
|
611
672
|
console.log('');
|
|
612
673
|
if (migratedLegacy > 0) {
|
|
613
674
|
console.log(`Legacy OpenClaw hook cleanup completed (${migratedLegacy} old path${migratedLegacy === 1 ? '' : 's'} removed).`);
|
|
614
675
|
console.log('');
|
|
615
676
|
}
|
|
616
677
|
console.log('What was installed:');
|
|
617
|
-
|
|
618
|
-
|
|
678
|
+
if (!options.noHooks) {
|
|
679
|
+
if (installed > 0) {
|
|
680
|
+
console.log(' • cortex-memory hook (memory injection + "remember this:" trigger)');
|
|
681
|
+
console.log(' Auto-save is optional: shieldcortex config --openclaw-auto-memory true');
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
console.log(' • cortex-memory hook: skipped (see warnings above)');
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
console.log(' • cortex-memory hook: skipped (--no-hooks)');
|
|
689
|
+
}
|
|
619
690
|
if (pluginInstallMode !== 'skipped') {
|
|
620
691
|
console.log(' • shieldcortex-realtime plugin (real-time LLM input scanning + optional output extraction)');
|
|
621
692
|
if (pluginInstallMode === 'native-package') {
|
|
@@ -631,6 +702,9 @@ export async function installOpenClawHook() {
|
|
|
631
702
|
console.log(' Installed as a local fallback, but trust pinning failed.');
|
|
632
703
|
}
|
|
633
704
|
}
|
|
705
|
+
else {
|
|
706
|
+
console.log(' • shieldcortex-realtime plugin: skipped');
|
|
707
|
+
}
|
|
634
708
|
console.log('');
|
|
635
709
|
console.log('Native OpenClaw install is also supported:');
|
|
636
710
|
console.log(' openclaw hooks install shieldcortex');
|
|
@@ -701,10 +775,12 @@ export async function openClawHookStatus() {
|
|
|
701
775
|
: 'native or unknown trust source';
|
|
702
776
|
console.log(` Real-time plugin: installed (${plugin.path}) — ${trustSuffix}`);
|
|
703
777
|
}
|
|
704
|
-
export async function handleOpenClawCommand(subcommand) {
|
|
778
|
+
export async function handleOpenClawCommand(subcommand, extraArgs = []) {
|
|
779
|
+
const noHooks = extraArgs.includes('--no-hooks');
|
|
780
|
+
const noPlugins = extraArgs.includes('--no-plugins');
|
|
705
781
|
switch (subcommand) {
|
|
706
782
|
case 'install':
|
|
707
|
-
await installOpenClawHook();
|
|
783
|
+
await installOpenClawHook({ noHooks, noPlugins });
|
|
708
784
|
break;
|
|
709
785
|
case 'uninstall':
|
|
710
786
|
await uninstallOpenClawHook();
|
|
@@ -714,6 +790,10 @@ export async function handleOpenClawCommand(subcommand) {
|
|
|
714
790
|
break;
|
|
715
791
|
default:
|
|
716
792
|
console.log('Usage: shieldcortex openclaw <install|uninstall|status>');
|
|
793
|
+
console.log('');
|
|
794
|
+
console.log('Install options:');
|
|
795
|
+
console.log(' --no-hooks Skip hook installation (useful in Docker/CI)');
|
|
796
|
+
console.log(' --no-plugins Skip plugin installation (useful in Docker/CI)');
|
|
717
797
|
process.exit(1);
|
|
718
798
|
}
|
|
719
799
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shieldcortex",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.1",
|
|
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",
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -1,46 +1,144 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Postinstall script - prints setup instructions after global install.
|
|
4
|
-
*
|
|
4
|
+
* Also detects existing OpenClaw installations and either auto-refreshes
|
|
5
|
+
* them or prints a clear upgrade warning (Bug #15 fix).
|
|
6
|
+
*
|
|
7
|
+
* Does NOT auto-run setup when:
|
|
8
|
+
* - Running in CI
|
|
9
|
+
* - SHIELDCORTEX_SKIP_AUTO_OPENCLAW=1 is set
|
|
10
|
+
* - Running as a local/dev install (npm_config_global !== 'true')
|
|
5
11
|
*/
|
|
6
|
-
import { existsSync } from 'fs';
|
|
12
|
+
import { existsSync, copyFileSync, mkdirSync, readdirSync } from 'fs';
|
|
7
13
|
import { join, dirname } from 'path';
|
|
8
14
|
import { homedir } from 'os';
|
|
9
15
|
import { spawnSync } from 'child_process';
|
|
10
16
|
import { fileURLToPath } from 'url';
|
|
11
17
|
|
|
12
|
-
// Only show message for global installs (not local dev or CI)
|
|
13
18
|
const isGlobal = process.env.npm_config_global === 'true';
|
|
14
19
|
const isCI = process.env.CI === 'true' || process.env.CONTINUOUS_INTEGRATION === 'true';
|
|
15
20
|
const skipAutoOpenClaw = process.env.SHIELDCORTEX_SKIP_AUTO_OPENCLAW === '1';
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Detect Docker/container environment — mirrors the logic in src/setup/openclaw.ts.
|
|
24
|
+
* Postinstall must not crash the gateway in Umbrel/Docker installs (#16).
|
|
25
|
+
*/
|
|
26
|
+
function isDockerEnvironment() {
|
|
27
|
+
try { if (existsSync('/.dockerenv')) return true; } catch { /* ignore */ }
|
|
28
|
+
if (process.env.DOCKER === 'true' || process.env.DOCKER === '1') return true;
|
|
29
|
+
if (process.env.container === 'docker') return true;
|
|
30
|
+
try {
|
|
31
|
+
const { readFileSync } = await import('fs').catch(() => ({ readFileSync: null }));
|
|
32
|
+
// Sync fallback — import() can't be used synchronously here
|
|
33
|
+
} catch { /* ignore */ }
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check whether OpenClaw is installed and whether ShieldCortex hooks/plugin exist.
|
|
39
|
+
*/
|
|
40
|
+
function getOpenClawState() {
|
|
18
41
|
const home = homedir();
|
|
19
42
|
const openclawDir = join(home, '.openclaw');
|
|
20
43
|
const knownHook = join(openclawDir, 'hooks', 'cortex-memory');
|
|
21
44
|
const knownPlugin = join(openclawDir, 'extensions', 'shieldcortex-realtime');
|
|
22
|
-
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
openclawInstalled: existsSync(openclawDir),
|
|
48
|
+
hookInstalled: existsSync(knownHook),
|
|
49
|
+
pluginInstalled: existsSync(knownPlugin),
|
|
50
|
+
pluginDir: knownPlugin,
|
|
51
|
+
};
|
|
23
52
|
}
|
|
24
53
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Auto-copy updated plugin files into the existing extensions directory.
|
|
56
|
+
* This ensures interceptor.js and other new files appear after upgrade (#15).
|
|
57
|
+
*/
|
|
58
|
+
function autoCopyPlugin(pluginDestDir, cliDistDir) {
|
|
59
|
+
if (!existsSync(pluginDestDir)) return false;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// Source: plugins/openclaw/dist/ inside this package
|
|
63
|
+
const pluginSourceDir = join(dirname(fileURLToPath(import.meta.url)), '..', 'plugins', 'openclaw', 'dist');
|
|
64
|
+
if (!existsSync(pluginSourceDir)) return false;
|
|
29
65
|
|
|
30
|
-
|
|
66
|
+
let copied = 0;
|
|
67
|
+
const files = readdirSync(pluginSourceDir);
|
|
68
|
+
for (const file of files) {
|
|
69
|
+
const src = join(pluginSourceDir, file);
|
|
70
|
+
const dest = join(pluginDestDir, file);
|
|
71
|
+
try {
|
|
72
|
+
copyFileSync(src, dest);
|
|
73
|
+
copied++;
|
|
74
|
+
} catch {
|
|
75
|
+
// Non-fatal — individual file copy failure
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return copied > 0;
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Run `shieldcortex openclaw install` to refresh the full hook+plugin.
|
|
86
|
+
*/
|
|
87
|
+
function refreshOpenClawInstall(cliPath) {
|
|
88
|
+
if (!existsSync(cliPath)) return false;
|
|
31
89
|
const result = spawnSync(process.execPath, [cliPath, 'openclaw', 'install'], {
|
|
32
90
|
stdio: 'inherit',
|
|
33
91
|
env: process.env,
|
|
34
92
|
});
|
|
35
|
-
|
|
36
|
-
console.warn('[shieldcortex] OpenClaw auto-refresh skipped (non-fatal).');
|
|
37
|
-
}
|
|
93
|
+
return result.status === 0;
|
|
38
94
|
}
|
|
39
95
|
|
|
96
|
+
// ── Main postinstall logic ──
|
|
97
|
+
|
|
40
98
|
if (isGlobal && !isCI) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
99
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
100
|
+
const __dirname = dirname(__filename);
|
|
101
|
+
const cliPath = join(__dirname, '..', 'dist', 'index.js');
|
|
102
|
+
|
|
103
|
+
const state = getOpenClawState();
|
|
104
|
+
const inDocker = isDockerEnvironment();
|
|
105
|
+
|
|
106
|
+
if (!skipAutoOpenClaw && state.openclawInstalled) {
|
|
107
|
+
if (inDocker) {
|
|
108
|
+
// Bug #16: Docker install — never auto-run, just warn
|
|
109
|
+
console.log('');
|
|
110
|
+
console.warn('[shieldcortex] ⚠ Docker/container environment detected.');
|
|
111
|
+
console.warn('[shieldcortex] Skipping automatic OpenClaw hook/plugin refresh.');
|
|
112
|
+
console.warn('[shieldcortex] To manually install after confirming OpenClaw support:');
|
|
113
|
+
console.warn('[shieldcortex] shieldcortex openclaw install --no-plugins');
|
|
114
|
+
} else if (state.pluginInstalled) {
|
|
115
|
+
// Bug #15: Plugin exists from a previous install — auto-copy new files
|
|
116
|
+
console.log('');
|
|
117
|
+
console.log('[shieldcortex] Existing plugin detected. Copying updated plugin files...');
|
|
118
|
+
const copied = autoCopyPlugin(state.pluginDir, cliPath);
|
|
119
|
+
if (copied) {
|
|
120
|
+
console.log('[shieldcortex] Plugin files updated. Run full refresh for hook updates:');
|
|
121
|
+
console.log('[shieldcortex] shieldcortex openclaw install');
|
|
122
|
+
} else {
|
|
123
|
+
// Fall back to full refresh
|
|
124
|
+
console.log('[shieldcortex] Auto-copy failed. Running full OpenClaw refresh...');
|
|
125
|
+
const ok = refreshOpenClawInstall(cliPath);
|
|
126
|
+
if (!ok) {
|
|
127
|
+
console.warn('[shieldcortex] ⚠ OpenClaw auto-refresh failed (non-fatal).');
|
|
128
|
+
console.warn('[shieldcortex] Run manually: shieldcortex openclaw install');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} else if (state.hookInstalled) {
|
|
132
|
+
// Hook exists but no plugin — run full refresh
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log('[shieldcortex] OpenClaw hook detected. Refreshing to latest version...');
|
|
135
|
+
const ok = refreshOpenClawInstall(cliPath);
|
|
136
|
+
if (!ok) {
|
|
137
|
+
console.warn('[shieldcortex] ⚠ OpenClaw auto-refresh skipped (non-fatal).');
|
|
138
|
+
console.warn('[shieldcortex] Run manually: shieldcortex openclaw install');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// else: openclaw installed but no previous shieldcortex — don't auto-install; let user run setup
|
|
44
142
|
}
|
|
45
143
|
|
|
46
144
|
console.log('');
|