@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/README.md +152 -132
- package/bin/alice-install.mjs +27 -35
- package/lib/hermes-agent.mjs +449 -0
- package/lib/hermes-installer.mjs +338 -0
- package/lib/installer.mjs +302 -22
- package/lib/runtime-installer.mjs +314 -0
- package/lib/skills.mjs +128 -4
- package/package.json +3 -3
- package/templates/workspaces/_shared/AGENTS-hermes.md +54 -0
- package/templates/workspaces/_shared/AGENTS.md +25 -0
- package/templates/workspaces/_shared/SOUL-hermes.md +35 -0
- package/templates/workspaces/_shared/hermes-agent-skill.md +40 -0
- package/templates/workspaces/_shared/hermes-orchestrator-skill.md +150 -0
- package/templates/workspaces/_shared/hermes-specialist-skill.md +109 -0
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
|
-
//
|
|
105
|
-
|
|
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('
|
|
935
|
+
console.log(` ${icons.ok} ${green('Model:')} ${detectedModels.primary}`);
|
|
664
936
|
if (detectedModels.providers.length > 0) {
|
|
665
|
-
console.log(` Providers: ${detectedModels.providers.join(', ')}
|
|
666
|
-
} else {
|
|
667
|
-
console.log();
|
|
937
|
+
console.log(` Providers: ${detectedModels.providers.join(', ')}`);
|
|
668
938
|
}
|
|
939
|
+
console.log('');
|
|
669
940
|
} else {
|
|
670
|
-
console.log(` ${icons.
|
|
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
|
|
710
|
-
|
|
711
|
-
|
|
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({
|