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.
@@ -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)
@@ -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
- export declare function installOpenClawHook(): Promise<void>;
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>;
@@ -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
- function installPlugin() {
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
- // ==================== Commands ====================
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
- // Install to ALL detected hook directories
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
- for (const hooksDir of hooksDirs) {
577
- const destDir = preferredHookDir(hooksDir);
578
- try {
579
- const legacyDirsBeforeInstall = detectLegacyHookVariants(hooksDir);
580
- copyHookFiles(HOOK_SOURCE, destDir);
581
- console.log(`Installed cortex-memory hook to ${destDir}`);
582
- if (legacyDirsBeforeInstall.length > 0) {
583
- console.log(`Detected legacy OpenClaw hook layout in ${hooksDir} — migrating to ${destDir}`);
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
- for (const removedDir of removeLegacyHookVariants(hooksDir)) {
586
- console.log(`Removed legacy cortex-memory hook from ${removedDir}`);
587
- migratedLegacy++;
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
- catch (err) {
592
- const code = err.code;
593
- if (code === 'EACCES' || code === 'EPERM') {
594
- console.warn(` Skipped ${destDir} (permission denied)`);
595
- }
596
- else {
597
- throw err;
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
- if (installed === 0) {
602
- console.error('Could not install to any hook directory (permission denied).');
603
- console.log('Try one of these:');
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
- console.log(' • cortex-memory hook (memory injection + "remember this:" trigger)');
618
- console.log(' Auto-save is optional: shieldcortex config --openclaw-auto-memory true');
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.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",
@@ -1,46 +1,144 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * Postinstall script - prints setup instructions after global install.
4
- * Does NOT auto-run setup (can fail in CI, user might not have Claude Code).
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
- function shouldRefreshOpenClaw() {
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
- return existsSync(openclawDir) || existsSync(knownHook) || existsSync(knownPlugin);
45
+
46
+ return {
47
+ openclawInstalled: existsSync(openclawDir),
48
+ hookInstalled: existsSync(knownHook),
49
+ pluginInstalled: existsSync(knownPlugin),
50
+ pluginDir: knownPlugin,
51
+ };
23
52
  }
24
53
 
25
- function refreshOpenClawInstall() {
26
- const __filename = fileURLToPath(import.meta.url);
27
- const __dirname = dirname(__filename);
28
- const cliPath = join(__dirname, '..', 'dist', 'index.js');
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
- if (!existsSync(cliPath)) return;
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
- if (result.status !== 0) {
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
- if (!skipAutoOpenClaw && shouldRefreshOpenClaw()) {
42
- console.log('\n[shieldcortex] OpenClaw detected. Refreshing hook/plugin to latest version...');
43
- refreshOpenClawInstall();
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('');