@robbiesrobotics/alice-agents 1.5.9 → 1.5.11

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/lib/installer.mjs CHANGED
@@ -14,6 +14,11 @@ import {
14
14
  isCloudRegistered,
15
15
  configureCloudFromSupabase,
16
16
  } from './mission-control.mjs';
17
+ import {
18
+ promptAndInstallRuntime,
19
+ printNoRuntimeDetected,
20
+ getRuntimeInfo,
21
+ } from './runtime-installer.mjs';
17
22
  import {
18
23
  promptInstallMode,
19
24
  promptUserInfo,
@@ -39,6 +44,7 @@ import { c, bold, dim, green, greenBold, red, yellow, cyan, gray,
39
44
  import { runSkillsWizardStep } from './skills.mjs';
40
45
  import { resolveCodingAgentPreference } from './coding-agent.mjs';
41
46
  import { loadAgentRegistry } from './agent-registry.mjs';
47
+ import { HermesAgentManager } from './hermes-agent.mjs';
42
48
 
43
49
  function commandExists(cmd) {
44
50
  const probe = process.platform === 'win32' ? 'where' : 'which';
@@ -54,6 +60,105 @@ function isOpenClawInstalled() {
54
60
  return commandExists('openclaw');
55
61
  }
56
62
 
63
+ // ── Hermes detection ─────────────────────────────────────────────────────────
64
+
65
+ function isHermesInstalled() {
66
+ if (!existsSync(join(homedir(), '.hermes', 'config.yaml'))) return false;
67
+ try {
68
+ execSync('hermes version', { stdio: 'pipe' });
69
+ return true;
70
+ } catch {
71
+ return false;
72
+ }
73
+ }
74
+
75
+ function getHermesVersion() {
76
+ try {
77
+ const output = execSync('hermes version 2>&1', { encoding: 'utf8', stdio: 'pipe' }).trim();
78
+ return output;
79
+ } catch {
80
+ return null;
81
+ }
82
+ }
83
+
84
+ async function installHermes(auto) {
85
+ console.log('');
86
+ printSection('Installing Hermes Agent');
87
+ console.log('');
88
+ console.log(` ${dim('Hermes Agent is required for the Hermes agent bridge.')}`);
89
+ console.log(` ${dim('Install:')} ${cyan('curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash')}`);
90
+ console.log('');
91
+
92
+ if (auto) {
93
+ console.log(` ${icons.info} ${dim('Non-interactive: running Hermes install script now...')}`);
94
+ console.log('');
95
+ try {
96
+ execSync('curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash', {
97
+ stdio: 'inherit',
98
+ timeout: 120000,
99
+ });
100
+ const version = getHermesVersion();
101
+ if (version) {
102
+ printStepDone('Hermes installed', version);
103
+ return { status: 'ok', version };
104
+ }
105
+ } catch (err) {
106
+ console.log(` ${icons.warn} ${yellow('Hermes install failed.')}`);
107
+ console.log(` ${dim('Install manually:')} ${cyan('curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash')}`);
108
+ return { status: 'error', reason: err.message };
109
+ }
110
+ }
111
+
112
+ const shouldInstall = await confirm(' Install Hermes Agent now?');
113
+ if (!shouldInstall) {
114
+ printStepSkip('Hermes Agent', 'not installed — Hermes agent bridge will be skipped');
115
+ return { status: 'skipped' };
116
+ }
117
+
118
+ try {
119
+ execSync('curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash', {
120
+ stdio: 'inherit',
121
+ timeout: 120000,
122
+ });
123
+ const version = getHermesVersion();
124
+ if (version) {
125
+ printStepDone('Hermes installed', version);
126
+ return { status: 'ok', version };
127
+ }
128
+ } catch (err) {
129
+ console.log(` ${icons.warn} ${yellow('Hermes install failed: ' + (err.message || 'unknown error'))}`);
130
+ console.log(` ${dim('Install manually:')} ${cyan('curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash')}`);
131
+ return { status: 'error', reason: err.message };
132
+ }
133
+
134
+ return { status: 'ok', version: getHermesVersion() };
135
+ }
136
+
137
+ async function checkHermes(auto, hermesBridgeRequested) {
138
+ const hermesFound = isHermesInstalled();
139
+ const version = hermesFound ? getHermesVersion() : null;
140
+
141
+ if (hermesFound && version) {
142
+ console.log(` ${icons.ok} ${green('Hermes detected:')} ${version}`);
143
+ return { status: 'found', version };
144
+ }
145
+
146
+ // Hermes not found
147
+ if (hermesBridgeRequested) {
148
+ console.log(` ${icons.warn} ${yellow('Hermes not found.')}`);
149
+ console.log(` ${dim('The --hermes-bridge flag requires Hermes to be installed.')}`);
150
+ const result = await installHermes(auto);
151
+ return result;
152
+ }
153
+
154
+ // Hermes not requested — just informational
155
+ console.log(` ${icons.info} ${dim('Hermes not found.')}`);
156
+ console.log(` ${dim('Hermes enables persistent-memory specialist agents for the A.L.I.C.E. team.')}`);
157
+ console.log(` ${dim('Install:')} ${cyan('curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash')}`);
158
+ console.log(` ${dim('Or re-run with')} ${cyan('--hermes-bridge')} ${dim('to install automatically.')}`);
159
+ return { status: 'not_found' };
160
+ }
161
+
57
162
  /**
58
163
  * On Linux, Docker requires the user to be in the docker group.
59
164
  * Detect this early and warn before OpenClaw's own preflight fails cryptically.
@@ -91,7 +196,7 @@ function checkLinuxDockerPermissions() {
91
196
  }
92
197
 
93
198
  async function detectRuntime() {
94
- // Check for NemoClaw binary
199
+ // Check for NemoClaw binary first (most specific)
95
200
  try {
96
201
  execSync('nemoclaw --version', { stdio: 'pipe' });
97
202
  return 'nemoclaw';
@@ -101,8 +206,28 @@ async function detectRuntime() {
101
206
  const nemoclawDir = join(homedir(), '.nemoclaw');
102
207
  if (existsSync(nemoclawDir)) return 'nemoclaw';
103
208
 
104
- // Fall back to OpenClaw
105
- return 'openclaw';
209
+ // Hermes is always standalone — check next
210
+ const hermesDir = join(homedir(), '.hermes');
211
+ const hermesConfig = join(hermesDir, 'config.yaml');
212
+ if (existsSync(hermesConfig)) {
213
+ try {
214
+ execSync('hermes version', { stdio: 'pipe' });
215
+ return 'hermes';
216
+ } catch {}
217
+ }
218
+
219
+ // Check for OpenClaw binary
220
+ try {
221
+ execSync('openclaw --version', { stdio: 'pipe' });
222
+ return 'openclaw';
223
+ } catch {}
224
+
225
+ // Check for OpenClaw directory (fallback)
226
+ const openclawDir = join(homedir(), '.openclaw');
227
+ if (existsSync(openclawDir)) return 'openclaw';
228
+
229
+ // Nothing found
230
+ return null;
106
231
  }
107
232
 
108
233
  function getOpenClawVersion() {
@@ -630,6 +755,150 @@ export async function runInstall(options = {}) {
630
755
  // 0. Linux Docker permission check — warn early before OpenClaw preflight fails
631
756
  checkLinuxDockerPermissions();
632
757
 
758
+ // 1. Detect runtime — Hermes, NemoClaw, OpenClaw, or none
759
+ let runtime = options.runtimeOverride || await detectRuntime();
760
+
761
+ // 1b. No runtime detected — offer to install one
762
+ if (!runtime) {
763
+ printNoRuntimeDetected();
764
+ const installed = await promptAndInstallRuntime();
765
+ if (!installed) {
766
+ // User skipped or install failed
767
+ console.log(` ${dim('Install a runtime manually, then re-run:')} ${cyan('npx @robbiesrobotics/alice-agents')}`);
768
+ console.log('');
769
+ process.exit(1);
770
+ }
771
+ runtime = installed;
772
+ // Re-check model after install
773
+ }
774
+
775
+ // 1c. User specified --runtime X but it's not installed
776
+ if (options.runtimeOverride && runtime !== options.runtimeOverride) {
777
+ const rtInfo = getRuntimeInfo(options.runtimeOverride);
778
+ if (rtInfo) {
779
+ console.log(` ${icons.info} ${dim(`You specified --runtime ${options.runtimeOverride} but ${rtInfo.name} is not installed.`)}`);
780
+ console.log('');
781
+ const installed = await promptAndInstallRuntime(options.runtimeOverride);
782
+ if (!installed) {
783
+ console.log(` ${dim('Install manually, then re-run:')} ${cyan('npx @robbiesrobotics/alice-agents --runtime ' + options.runtimeOverride)}`);
784
+ console.log('');
785
+ process.exit(1);
786
+ }
787
+ runtime = installed;
788
+ }
789
+ }
790
+
791
+ // ── HERMES-ONLY PATH ──────────────────────────────────────────────────────
792
+ if (runtime === 'hermes') {
793
+ // Lazy-import to avoid circular deps
794
+ const { installForHermes, getHermesAliceStatus } = await import('./hermes-installer.mjs');
795
+ const { detectUserName, detectTimezone } = await import('./prompter.mjs');
796
+
797
+ console.log(` ${icons.ok} ${greenBold('Hermes detected')} ${dim('─')} A.L.I.C.E. will run as a Hermes skill tree\n`);
798
+
799
+ // Check if already installed
800
+ const existingStatus = getHermesAliceStatus();
801
+ if (existingStatus.installed && manifest && !options.force) {
802
+ console.log(` ${icons.info} ${dim('A.L.I.C.E. already installed on Hermes')}`);
803
+ console.log(` ${dim('Run with --force to reinstall.')}\n`);
804
+ }
805
+
806
+ // Detect Hermes version and model
807
+ const { getHermesVersion, getHermesDefaultModel } = await import('./hermes-installer.mjs');
808
+ const hermesVersion = getHermesVersion();
809
+ const hermesModel = getHermesDefaultModel();
810
+ if (hermesVersion) {
811
+ console.log(` ${icons.ok} ${green('Hermes version:')} ${hermesVersion}`);
812
+ }
813
+ if (hermesModel) {
814
+ console.log(` ${icons.ok} ${green('Model:')} ${hermesModel}`);
815
+ } else {
816
+ console.log('');
817
+ console.log(` ${icons.fail} ${red('No model configured for Hermes')}`);
818
+ console.log('');
819
+ console.log(` ${dim('A model is required before installing A.L.I.C.E.')}`);
820
+ console.log(` Run: ${cyan('hermes model')}`);
821
+ console.log(` Then re-run: ${cyan('npx @robbiesrobotics/alice-agents --runtime hermes')}`);
822
+ console.log('');
823
+ process.exit(1);
824
+ }
825
+
826
+ // 2. Tier selection
827
+ let tier;
828
+ if (options.tierOverride) {
829
+ tier = normalizeTierOption(options.tierOverride);
830
+ } else if (auto) {
831
+ tier = 'starter';
832
+ } else {
833
+ tier = await promptTier();
834
+ }
835
+
836
+ // 3. User info
837
+ let userInfo;
838
+ if (auto) {
839
+ userInfo = { name: detectUserName(), timezone: detectTimezone(), notes: '' };
840
+ } else {
841
+ printSection('About You');
842
+ console.log('');
843
+ userInfo = await promptUserInfo();
844
+ }
845
+
846
+ // 4. Confirmation
847
+ const agents = (await import('./agent-registry.mjs')).loadAgentRegistry(tier);
848
+ if (!auto) {
849
+ printSection('Install Summary');
850
+ console.log('');
851
+ console.log(` ${dim('Runtime:')} ${green('Hermes Agent')}`);
852
+ console.log(` ${dim('Model:')} ${green(hermesModel || 'none')}`);
853
+ console.log(` ${dim('Tier:')} ${green(tier)} ${dim('(' + agents.length + ' agents)')}`);
854
+ console.log(` ${dim('User:')} ${green(userInfo.name)}`);
855
+ console.log('');
856
+ const ok = await confirm(' Proceed with Hermes install?');
857
+ if (!ok) {
858
+ console.log(' Aborted.');
859
+ closePrompt();
860
+ return;
861
+ }
862
+ }
863
+ closePrompt();
864
+
865
+ // Execute Hermes install
866
+ printSection('Installing A.L.I.C.E. on Hermes');
867
+ console.log('');
868
+
869
+ const result = await installForHermes({ tier, auto, userInfo });
870
+
871
+ // Write manifest
872
+ writeManifest({
873
+ installedAt: existingStatus?.installedAt || new Date().toISOString(),
874
+ tier,
875
+ agents: result.skills,
876
+ userName: userInfo.name,
877
+ userTimezone: userInfo.timezone,
878
+ modelPreset: 'hermes-detected',
879
+ runtime: 'hermes',
880
+ skills: [],
881
+ codingTool: null,
882
+ codingSkill: null,
883
+ missionControl: null,
884
+ });
885
+
886
+ console.log('');
887
+ printSeparator();
888
+ console.log('');
889
+ console.log(` ${icons.ok} ${greenBold('A.L.I.C.E. installed on Hermes!')} ${dim(result.skills.length + ' skills ready.')}`);
890
+ console.log('');
891
+ console.log(` ${dim('Start A.L.I.C.E.:')} ${cyan('hermes chat')}`);
892
+ console.log(` ${dim('List skills:')} ${cyan('hermes skills list')}`);
893
+ console.log(` ${dim('Skills dir:')} ${cyan('~/.hermes/skills/alice/')}`);
894
+ console.log('');
895
+ printSeparator();
896
+ console.log('');
897
+ return;
898
+ }
899
+ // ── END HERMES PATH ────────────────────────────────────────────────────────
900
+
901
+ // OpenClaw / NemoClaw path (existing behavior)
633
902
  // 1. Detect OpenClaw — offer to install if missing
634
903
  if (!isOpenClawInstalled() || !configExists()) {
635
904
  await installRuntime(auto);
@@ -645,7 +914,6 @@ export async function runInstall(options = {}) {
645
914
  // 1b. Check for OpenClaw updates
646
915
  await checkForOpenClawUpdate(auto);
647
916
 
648
- const runtime = await detectRuntime();
649
917
  if (runtime === 'nemoclaw') {
650
918
  console.log(` ${icons.ok} ${greenBold('NemoClaw detected')} ${dim('─')} agents run in OpenShell sandbox\n`);
651
919
  } else {
@@ -657,17 +925,26 @@ export async function runInstall(options = {}) {
657
925
  }
658
926
  }
659
927
 
928
+ // Hermes check (for OpenClaw+Hermes hybrid setups)
929
+ const hermesResult = await checkHermes(auto, !!options.hermesBridge);
930
+ const hermesReady = hermesResult.status === 'found' || hermesResult.status === 'ok';
931
+
660
932
  // Detect what models the user already has configured
661
933
  const detectedModels = detectAvailableModels();
662
934
  if (detectedModels?.hasModel) {
663
- console.log(` ${icons.ok} ${green('Detected configured model:')} ${detectedModels.primary}`);
935
+ console.log(` ${icons.ok} ${green('Model:')} ${detectedModels.primary}`);
664
936
  if (detectedModels.providers.length > 0) {
665
- console.log(` Providers: ${detectedModels.providers.join(', ')}\n`);
666
- } else {
667
- console.log();
937
+ console.log(` Providers: ${detectedModels.providers.join(', ')}`);
668
938
  }
939
+ console.log('');
669
940
  } else {
670
- console.log(` ${icons.info} ${dim('No model configured yet — you\'ll be prompted to choose one.')}\n`);
941
+ console.log(` ${icons.fail} ${red('No model configured for OpenClaw')}`);
942
+ console.log('');
943
+ console.log(` ${dim('A model is required before installing A.L.I.C.E.')}`);
944
+ console.log(` Run: ${cyan('openclaw configure')}`);
945
+ console.log(` Then re-run: ${cyan('npx @robbiesrobotics/alice-agents')}`);
946
+ console.log('');
947
+ process.exit(1);
671
948
  }
672
949
 
673
950
  // 2. Install mode
@@ -706,19 +983,9 @@ export async function runInstall(options = {}) {
706
983
  userInfo = await promptUserInfo();
707
984
  }
708
985
 
709
- // 4. Model preset
710
- let preset, customModels;
711
- if (auto) {
712
- // Non-interactive: use whatever the user already has configured.
713
- // Only fall back to sonnet if nothing is detected (e.g. fresh OpenClaw install
714
- // where the user explicitly set up Claude credentials).
715
- preset = detectedModels?.hasModel ? 'detected' : 'sonnet';
716
- } else {
717
- preset = await promptModelPreset(detectedModels);
718
- if (preset === 'custom') {
719
- customModels = await promptCustomModel();
720
- }
721
- }
986
+ // 4. Model — always use whatever is already configured; we already verified one exists above
987
+ const preset = 'detected';
988
+ const customModels = null; // not used when preset=detected
722
989
 
723
990
  // 5. Tier selection
724
991
  let tier;
@@ -964,6 +1231,19 @@ export async function runInstall(options = {}) {
964
1231
  sandboxName: 'my-assistant',
965
1232
  });
966
1233
 
1234
+ // Hermes agent bridge setup
1235
+ if (options.hermesBridge && hermesReady) {
1236
+ const hermes = new HermesAgentManager();
1237
+ const bridgeResult = hermes.setupBridge();
1238
+ if (bridgeResult.status === 'ok') {
1239
+ printStepDone('Hermes bridge', `v${bridgeResult.hermesVersion} connected`);
1240
+ } else {
1241
+ printStepSkip('Hermes bridge', bridgeResult.reason);
1242
+ }
1243
+ } else if (options.hermesBridge && !hermesReady) {
1244
+ printStepSkip('Hermes bridge', 'Hermes not installed');
1245
+ }
1246
+
967
1247
  // Write manifest
968
1248
  const existing = readManifest();
969
1249
  writeManifest({