@robbiesrobotics/alice-agents 1.3.3 → 1.4.0
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/bin/alice-install.mjs +7 -0
- package/lib/colors.mjs +102 -0
- package/lib/doctor.mjs +61 -16
- package/lib/installer.mjs +351 -93
- package/lib/skills.mjs +310 -0
- package/package.json +3 -3
package/bin/alice-install.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { readFileSync } from 'node:fs';
|
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
5
|
import { runInstall, runUninstall } from '../lib/installer.mjs';
|
|
6
6
|
import { runDoctor } from '../lib/doctor.mjs';
|
|
7
|
+
import { runSkillsManager } from '../lib/skills.mjs';
|
|
7
8
|
|
|
8
9
|
const args = process.argv.slice(2);
|
|
9
10
|
const flags = new Set(args);
|
|
@@ -24,6 +25,7 @@ if (flags.has('--help') || flags.has('-h')) {
|
|
|
24
25
|
npx @robbiesrobotics/alice-agents --update Non-interactive upgrade to latest agents
|
|
25
26
|
npx @robbiesrobotics/alice-agents --uninstall Remove A.L.I.C.E. agents from config
|
|
26
27
|
npx @robbiesrobotics/alice-agents --doctor Run diagnostics on your A.L.I.C.E. install
|
|
28
|
+
npx @robbiesrobotics/alice-agents --skills Manage skills (install, remove, browse)
|
|
27
29
|
npx @robbiesrobotics/alice-agents --version Show version
|
|
28
30
|
npx @robbiesrobotics/alice-agents --help Show this help
|
|
29
31
|
|
|
@@ -47,6 +49,11 @@ if (flags.has('--doctor')) {
|
|
|
47
49
|
console.error(' ❌ Update failed:', err.message);
|
|
48
50
|
process.exit(1);
|
|
49
51
|
});
|
|
52
|
+
} else if (flags.has('--skills')) {
|
|
53
|
+
runSkillsManager().catch((err) => {
|
|
54
|
+
console.error(` ✗ Skills manager failed: ${err.message}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
});
|
|
50
57
|
} else if (flags.has('--uninstall')) {
|
|
51
58
|
runUninstall({ yes: flags.has('--yes') }).catch((err) => {
|
|
52
59
|
console.error(' ❌ Uninstall failed:', err.message);
|
package/lib/colors.mjs
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// lib/colors.mjs
|
|
2
|
+
// ANSI color helper — no external deps, NO_COLOR + non-TTY safe
|
|
3
|
+
// Brand: A.L.I.C.E. / NemoClaw green (#76b900 → \x1b[92m bright green)
|
|
4
|
+
|
|
5
|
+
const enabled =
|
|
6
|
+
process.stdout.isTTY &&
|
|
7
|
+
!process.env.NO_COLOR &&
|
|
8
|
+
process.env.TERM !== 'dumb';
|
|
9
|
+
|
|
10
|
+
const esc = (code) => (str) => enabled ? `\x1b[${code}m${str}\x1b[0m` : str;
|
|
11
|
+
|
|
12
|
+
export const c = {
|
|
13
|
+
green: esc('92'),
|
|
14
|
+
greenDim: esc('32'),
|
|
15
|
+
success: esc('92'),
|
|
16
|
+
error: esc('91'),
|
|
17
|
+
warning: esc('93'),
|
|
18
|
+
info: esc('96'),
|
|
19
|
+
accent: esc('95'),
|
|
20
|
+
bold: esc('1'),
|
|
21
|
+
dim: esc('2'),
|
|
22
|
+
italic: esc('3'),
|
|
23
|
+
underline:esc('4'),
|
|
24
|
+
white: esc('97'),
|
|
25
|
+
gray: esc('90'),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const bold = (s) => c.bold(s);
|
|
29
|
+
export const dim = (s) => c.dim(s);
|
|
30
|
+
export const green = (s) => c.green(s);
|
|
31
|
+
export const greenBold = (s) => enabled ? `\x1b[1m\x1b[92m${s}\x1b[0m` : s;
|
|
32
|
+
export const red = (s) => c.error(s);
|
|
33
|
+
export const yellow = (s) => c.warning(s);
|
|
34
|
+
export const cyan = (s) => c.info(s);
|
|
35
|
+
export const gray = (s) => c.gray(s);
|
|
36
|
+
export const white = (s) => c.white(s);
|
|
37
|
+
|
|
38
|
+
export const icons = {
|
|
39
|
+
ok: green('✔'),
|
|
40
|
+
fail: red('✗'),
|
|
41
|
+
warn: yellow('⚠'),
|
|
42
|
+
info: cyan('ℹ'),
|
|
43
|
+
pkg: '📦',
|
|
44
|
+
arrow: dim('›'),
|
|
45
|
+
bullet:dim('·'),
|
|
46
|
+
check: green('✓'),
|
|
47
|
+
cross: red('✗'),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const SEP_WIDTH = 50;
|
|
51
|
+
const SEP_CHAR = '─';
|
|
52
|
+
|
|
53
|
+
export function separator() {
|
|
54
|
+
return dim(SEP_CHAR.repeat(SEP_WIDTH));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function printSection(title) {
|
|
58
|
+
const totalDashes = SEP_WIDTH - title.length - 4;
|
|
59
|
+
const left = SEP_CHAR.repeat(2);
|
|
60
|
+
const right = SEP_CHAR.repeat(Math.max(0, totalDashes));
|
|
61
|
+
console.log(`\n ${dim(left)} ${greenBold(title)} ${dim(right)}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function printSeparator() {
|
|
65
|
+
console.log(` ${separator()}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
69
|
+
|
|
70
|
+
export function printBox(lines, { title = null, padding = 1 } = {}) {
|
|
71
|
+
const innerWidth = Math.max(...lines.map(l => stripAnsi(l).length)) + padding * 2;
|
|
72
|
+
const w = innerWidth;
|
|
73
|
+
const TL = '╭', TR = '╮', BL = '╰', BR = '╯', H = '─', V = '│';
|
|
74
|
+
|
|
75
|
+
const topBar = title
|
|
76
|
+
? `${TL}${H} ${greenBold(title)} ${H.repeat(Math.max(0, w - stripAnsi(title).length - 4))}${TR}`
|
|
77
|
+
: `${TL}${H.repeat(w + 2)}${TR}`;
|
|
78
|
+
|
|
79
|
+
console.log(` ${dim(topBar)}`);
|
|
80
|
+
lines.forEach(line => {
|
|
81
|
+
const pad = ' '.repeat(padding);
|
|
82
|
+
const raw = stripAnsi(line);
|
|
83
|
+
const trail = ' '.repeat(Math.max(0, w - raw.length - padding));
|
|
84
|
+
console.log(` ${dim(V)}${pad}${line}${trail}${dim(V)}`);
|
|
85
|
+
});
|
|
86
|
+
console.log(` ${dim(`${BL}${H.repeat(w + 2)}${BR}`)}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function printStepDone(label, detail = '') {
|
|
90
|
+
const suffix = detail ? ` ${dim(detail)}` : '';
|
|
91
|
+
console.log(` ${icons.ok} ${label}${suffix}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function printStepFail(label, reason = '') {
|
|
95
|
+
const suffix = reason ? ` ${red(reason)}` : '';
|
|
96
|
+
console.log(` ${icons.fail} ${label}${suffix}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function printStepSkip(label, reason = '') {
|
|
100
|
+
const suffix = reason ? ` ${dim(reason)}` : '';
|
|
101
|
+
console.log(` ${dim('–')} ${dim(label)}${suffix}`);
|
|
102
|
+
}
|
package/lib/doctor.mjs
CHANGED
|
@@ -3,9 +3,21 @@ import { join, dirname } from 'node:path';
|
|
|
3
3
|
import { homedir, platform } from 'node:os';
|
|
4
4
|
import { execSync } from 'node:child_process';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { icons, greenBold, green, red, yellow, cyan, dim, bold,
|
|
7
|
+
printSection, printSeparator, separator } from './colors.mjs';
|
|
6
8
|
|
|
7
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
10
|
const HOME = homedir();
|
|
11
|
+
|
|
12
|
+
function commandExists(cmd) {
|
|
13
|
+
const probe = process.platform === 'win32' ? 'where' : 'which';
|
|
14
|
+
try {
|
|
15
|
+
execSync(`${probe} ${cmd}`, { stdio: 'pipe' });
|
|
16
|
+
return true;
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
9
21
|
const OPENCLAW_DIR = join(HOME, '.openclaw');
|
|
10
22
|
|
|
11
23
|
const STARTER_AGENTS = [
|
|
@@ -14,9 +26,9 @@ const STARTER_AGENTS = [
|
|
|
14
26
|
];
|
|
15
27
|
|
|
16
28
|
function check(label, ok, hint) {
|
|
17
|
-
const icon = ok ?
|
|
18
|
-
console.log(` ${icon} ${label}`);
|
|
19
|
-
if (!ok && hint) console.log(` → ${hint}`);
|
|
29
|
+
const icon = ok ? icons.ok : icons.fail;
|
|
30
|
+
console.log(` ${icon} ${ok ? green(label) : red(label)}`);
|
|
31
|
+
if (!ok && hint) console.log(` ${dim('→')} ${dim(hint)}`);
|
|
20
32
|
return ok;
|
|
21
33
|
}
|
|
22
34
|
|
|
@@ -51,12 +63,7 @@ function checkDockerEnvironment() {
|
|
|
51
63
|
const isLinux = platform() === 'linux';
|
|
52
64
|
|
|
53
65
|
// Check if docker binary exists at all
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
execSync('which docker', { stdio: 'pipe' });
|
|
57
|
-
dockerInstalled = true;
|
|
58
|
-
} catch {
|
|
59
|
-
// Docker not installed — not a blocker unless openclaw needs it
|
|
66
|
+
if (!commandExists('docker')) {
|
|
60
67
|
return { present: false, accessible: false, needsSudo: false, hint: null };
|
|
61
68
|
}
|
|
62
69
|
|
|
@@ -109,7 +116,10 @@ function checkDockerEnvironment() {
|
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
export async function runDoctor() {
|
|
112
|
-
console.log('
|
|
119
|
+
console.log('');
|
|
120
|
+
printSection('A.L.I.C.E. Doctor');
|
|
121
|
+
console.log(` ${dim('Diagnostic Report')}`);
|
|
122
|
+
console.log('');
|
|
113
123
|
let allOk = true;
|
|
114
124
|
|
|
115
125
|
// 1. Runtime check
|
|
@@ -130,7 +140,7 @@ export async function runDoctor() {
|
|
|
130
140
|
|
|
131
141
|
if (!configExists) {
|
|
132
142
|
allOk = false;
|
|
133
|
-
console.log(
|
|
143
|
+
console.log(`\n ${icons.warn} ${yellow('Cannot continue checks — openclaw.json missing.')}\n`);
|
|
134
144
|
return false;
|
|
135
145
|
}
|
|
136
146
|
|
|
@@ -143,7 +153,7 @@ export async function runDoctor() {
|
|
|
143
153
|
);
|
|
144
154
|
if (!configValid) {
|
|
145
155
|
allOk = false;
|
|
146
|
-
console.log(
|
|
156
|
+
console.log(`\n ${icons.warn} ${yellow('Cannot continue checks — config is invalid JSON.')}\n`);
|
|
147
157
|
return false;
|
|
148
158
|
}
|
|
149
159
|
|
|
@@ -236,11 +246,16 @@ export async function runDoctor() {
|
|
|
236
246
|
);
|
|
237
247
|
// Docker permission issue is a warning, not a hard failure for A.L.I.C.E. itself
|
|
238
248
|
// but it will break OpenClaw's own Docker features
|
|
239
|
-
console.log(
|
|
249
|
+
console.log(` ${icons.info} ${dim('This will affect OpenClaw features that use Docker.')}\n`);
|
|
240
250
|
} else {
|
|
241
251
|
check('Docker daemon not running or not accessible', false, docker.hint);
|
|
242
252
|
}
|
|
243
253
|
}
|
|
254
|
+
|
|
255
|
+
const isWSL = !!process.env.WSL_DISTRO_NAME || !!process.env.WSLENV;
|
|
256
|
+
if (isWSL && docker.present) {
|
|
257
|
+
console.log(" ℹ️ WSL2 detected. If Docker has cgroup issues, run: nemoclaw setup-spark\n");
|
|
258
|
+
}
|
|
244
259
|
// If docker not present at all, skip silently — not required for all setups
|
|
245
260
|
|
|
246
261
|
// 7. License check
|
|
@@ -276,13 +291,43 @@ export async function runDoctor() {
|
|
|
276
291
|
check('License: Starter tier (no license required)', true);
|
|
277
292
|
}
|
|
278
293
|
|
|
294
|
+
// 8. Skills disk check
|
|
295
|
+
const skillsManifestPath = join(OPENCLAW_DIR, '.alice-manifest.json');
|
|
296
|
+
const skillsManifestData = (() => {
|
|
297
|
+
try {
|
|
298
|
+
if (!existsSync(skillsManifestPath)) return null;
|
|
299
|
+
return JSON.parse(readFileSync(skillsManifestPath, 'utf8'));
|
|
300
|
+
} catch { return null; }
|
|
301
|
+
})();
|
|
302
|
+
|
|
303
|
+
const installedSkills = skillsManifestData?.skills || [];
|
|
304
|
+
if (installedSkills.length > 0) {
|
|
305
|
+
const missingSkills = installedSkills.filter(
|
|
306
|
+
id => !existsSync(join(HOME, '.openclaw', 'skills', id))
|
|
307
|
+
);
|
|
308
|
+
if (missingSkills.length > 0) {
|
|
309
|
+
check(
|
|
310
|
+
`Skills missing on disk: ${missingSkills.join(', ')}`,
|
|
311
|
+
false,
|
|
312
|
+
'Run: npx @robbiesrobotics/alice-agents --skills to reinstall'
|
|
313
|
+
);
|
|
314
|
+
allOk = false;
|
|
315
|
+
} else {
|
|
316
|
+
check(`Skills installed (${installedSkills.length}): ${installedSkills.join(', ')}`, true);
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
console.log(` ${dim('–')} ${dim('No skills installed (optional)')}`);
|
|
320
|
+
}
|
|
321
|
+
|
|
279
322
|
// Summary
|
|
280
|
-
console.log();
|
|
323
|
+
console.log('');
|
|
281
324
|
if (allOk) {
|
|
282
|
-
console.log(
|
|
325
|
+
console.log(` ${icons.ok} ${greenBold('A.L.I.C.E. is healthy!')}`);
|
|
283
326
|
} else {
|
|
284
|
-
console.log('
|
|
327
|
+
console.log(` ${icons.warn} ${yellow('Issues found')} ${dim('─ follow the hints above to fix them.')}`);
|
|
285
328
|
}
|
|
329
|
+
printSeparator();
|
|
330
|
+
console.log('');
|
|
286
331
|
|
|
287
332
|
return allOk;
|
|
288
333
|
}
|
package/lib/installer.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
2
2
|
import { join, dirname } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { execSync } from 'node:child_process';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
5
6
|
import { configExists, mergeConfig, removeAliceAgents, detectAvailableModels } from './config-merger.mjs';
|
|
6
7
|
import { scaffoldAll } from './workspace-scaffolder.mjs';
|
|
7
8
|
import { readManifest, writeManifest } from './manifest.mjs';
|
|
@@ -19,16 +20,25 @@ import {
|
|
|
19
20
|
detectUserName,
|
|
20
21
|
detectTimezone,
|
|
21
22
|
} from './prompter.mjs';
|
|
23
|
+
import { c, bold, dim, green, greenBold, red, yellow, cyan, gray,
|
|
24
|
+
icons, separator, printSection, printSeparator, printBox,
|
|
25
|
+
printStepDone, printStepFail, printStepSkip } from './colors.mjs';
|
|
26
|
+
import { runSkillsWizardStep } from './skills.mjs';
|
|
22
27
|
|
|
23
|
-
function
|
|
28
|
+
function commandExists(cmd) {
|
|
29
|
+
const probe = process.platform === 'win32' ? 'where' : 'which';
|
|
24
30
|
try {
|
|
25
|
-
execSync(
|
|
31
|
+
execSync(`${probe} ${cmd}`, { stdio: 'pipe' });
|
|
26
32
|
return true;
|
|
27
33
|
} catch {
|
|
28
34
|
return false;
|
|
29
35
|
}
|
|
30
36
|
}
|
|
31
37
|
|
|
38
|
+
function isOpenClawInstalled() {
|
|
39
|
+
return commandExists('openclaw');
|
|
40
|
+
}
|
|
41
|
+
|
|
32
42
|
/**
|
|
33
43
|
* On Linux, Docker requires the user to be in the docker group.
|
|
34
44
|
* Detect this early and warn before OpenClaw's own preflight fails cryptically.
|
|
@@ -36,9 +46,7 @@ function isOpenClawInstalled() {
|
|
|
36
46
|
function checkLinuxDockerPermissions() {
|
|
37
47
|
if (process.platform !== 'linux') return;
|
|
38
48
|
|
|
39
|
-
|
|
40
|
-
execSync('which docker', { stdio: 'pipe' });
|
|
41
|
-
} catch {
|
|
49
|
+
if (!commandExists('docker')) {
|
|
42
50
|
return; // Docker not installed — not our problem
|
|
43
51
|
}
|
|
44
52
|
|
|
@@ -54,7 +62,7 @@ function checkLinuxDockerPermissions() {
|
|
|
54
62
|
msg.includes('connect: permission denied');
|
|
55
63
|
|
|
56
64
|
if (isPermissionIssue) {
|
|
57
|
-
console.log('
|
|
65
|
+
console.log(` ${icons.warn} ${yellow('Docker permission issue detected.')}\n`);
|
|
58
66
|
console.log(' Your user is not in the docker group. This will cause');
|
|
59
67
|
console.log(' OpenClaw to fail when it tries to access Docker.\n');
|
|
60
68
|
console.log(' Fix this now (recommended):');
|
|
@@ -75,7 +83,7 @@ async function detectRuntime() {
|
|
|
75
83
|
} catch {}
|
|
76
84
|
|
|
77
85
|
// Check for NemoClaw directory
|
|
78
|
-
const nemoclawDir = join(
|
|
86
|
+
const nemoclawDir = join(homedir(), '.nemoclaw');
|
|
79
87
|
if (existsSync(nemoclawDir)) return 'nemoclaw';
|
|
80
88
|
|
|
81
89
|
// Fall back to OpenClaw
|
|
@@ -105,16 +113,17 @@ async function checkForOpenClawUpdate(auto) {
|
|
|
105
113
|
const current = getOpenClawVersion();
|
|
106
114
|
if (!current) return;
|
|
107
115
|
|
|
108
|
-
console.log(` OpenClaw version: ${current}`);
|
|
116
|
+
console.log(` ${dim('OpenClaw version:')} ${green(current)}`);
|
|
109
117
|
|
|
110
118
|
const latest = getLatestNpmVersion();
|
|
111
119
|
if (!latest) {
|
|
112
|
-
console.log('
|
|
120
|
+
console.log(` ${icons.warn} ${yellow('Could not check for updates (npm registry unreachable)')}\n`);
|
|
113
121
|
return;
|
|
114
122
|
}
|
|
115
123
|
|
|
116
124
|
if (current === latest) {
|
|
117
|
-
|
|
125
|
+
printStepDone('OpenClaw is up to date', latest);
|
|
126
|
+
console.log('');
|
|
118
127
|
return;
|
|
119
128
|
}
|
|
120
129
|
|
|
@@ -126,13 +135,15 @@ async function checkForOpenClawUpdate(auto) {
|
|
|
126
135
|
}
|
|
127
136
|
|
|
128
137
|
if (shouldUpdate) {
|
|
129
|
-
console.log(
|
|
138
|
+
console.log(` ${icons.pkg} ${bold('Updating OpenClaw...')}\n`);
|
|
130
139
|
try {
|
|
131
140
|
execSync('npm install -g openclaw@latest', { stdio: 'inherit' });
|
|
132
141
|
const updated = getOpenClawVersion();
|
|
133
|
-
console.log(
|
|
142
|
+
console.log('');
|
|
143
|
+
printStepDone('OpenClaw updated', updated || latest);
|
|
144
|
+
console.log('');
|
|
134
145
|
} catch {
|
|
135
|
-
console.log(
|
|
146
|
+
console.log(`\n ${icons.warn} ${yellow('Update failed — continuing with current version')}\n`);
|
|
136
147
|
}
|
|
137
148
|
} else {
|
|
138
149
|
console.log();
|
|
@@ -140,51 +151,267 @@ async function checkForOpenClawUpdate(auto) {
|
|
|
140
151
|
}
|
|
141
152
|
|
|
142
153
|
async function installRuntime(auto) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
console.log(
|
|
154
|
+
const plat = process.platform;
|
|
155
|
+
const isWSL = !!process.env.WSL_DISTRO_NAME || !!process.env.WSLENV;
|
|
156
|
+
|
|
157
|
+
console.log(` ${icons.warn} ${yellow('No agent runtime detected.')}\n`);
|
|
158
|
+
|
|
159
|
+
// Windows (native, not WSL)
|
|
160
|
+
if (plat === "win32") {
|
|
161
|
+
console.log(" A.L.I.C.E. requires OpenClaw or NemoClaw to run.\n");
|
|
162
|
+
console.log(" NemoClaw (the recommended runtime with enterprise sandbox) requires");
|
|
163
|
+
console.log(" Linux — but you can get it on Windows via WSL2 (Windows Subsystem for Linux).\n");
|
|
164
|
+
|
|
165
|
+
const wslAvailable = commandExists("wsl");
|
|
166
|
+
|
|
167
|
+
if (wslAvailable) {
|
|
168
|
+
let wslDistros = "";
|
|
169
|
+
try {
|
|
170
|
+
// WSL --list output on Windows can be UTF-16LE encoded — decode from Buffer
|
|
171
|
+
const raw = execSync("wsl --list --quiet", { stdio: "pipe" });
|
|
172
|
+
wslDistros = raw.toString("utf16le").replace(/\0/g, "").trim();
|
|
173
|
+
} catch {}
|
|
174
|
+
|
|
175
|
+
const hasDistro = wslDistros.length > 0 &&
|
|
176
|
+
!wslDistros.toLowerCase().includes("no installed") &&
|
|
177
|
+
!wslDistros.toLowerCase().includes("has no");
|
|
178
|
+
|
|
179
|
+
if (hasDistro) {
|
|
180
|
+
console.log(" ✔ WSL2 detected with an installed distro.\n");
|
|
181
|
+
console.log(" Recommended: Install NemoClaw inside WSL2 for full sandbox support.\n");
|
|
182
|
+
|
|
183
|
+
let choice;
|
|
184
|
+
if (auto) {
|
|
185
|
+
choice = "wsl-nemoclaw";
|
|
186
|
+
} else {
|
|
187
|
+
choice = await choose(" How would you like to proceed?", [
|
|
188
|
+
{ label: "Install NemoClaw inside WSL2 (Recommended)", value: "wsl-nemoclaw" },
|
|
189
|
+
{ label: "Install OpenClaw for Windows (no sandbox)", value: "openclaw-win" },
|
|
190
|
+
]);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (choice === "wsl-nemoclaw") {
|
|
194
|
+
console.log("\n 📦 Installing NemoClaw inside WSL2...\n");
|
|
195
|
+
console.log(" This will run the NemoClaw installer in your default WSL2 distro.\n");
|
|
196
|
+
try {
|
|
197
|
+
execSync(
|
|
198
|
+
"wsl bash -c \"curl -fsSL https://nvidia.com/nemoclaw.sh | bash\"",
|
|
199
|
+
{ stdio: "inherit" }
|
|
200
|
+
);
|
|
201
|
+
console.log("\n ✔ NemoClaw installed in WSL2.\n");
|
|
202
|
+
console.log(" ℹ️ Note: On Ubuntu 24.04 / WSL2, Docker may need a cgroup fix.");
|
|
203
|
+
console.log(" If onboard fails, run inside WSL2: nemoclaw setup-spark\n");
|
|
204
|
+
} catch {
|
|
205
|
+
console.error("\n ❌ WSL2 NemoClaw install failed. Try manually inside WSL2:");
|
|
206
|
+
console.error(" wsl bash -c \"curl -fsSL https://nvidia.com/nemoclaw.sh | bash\"\n");
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
console.log("\n 📦 Installing OpenClaw for Windows...\n");
|
|
211
|
+
try {
|
|
212
|
+
execSync("npm install -g openclaw", { stdio: "inherit" });
|
|
213
|
+
console.log("\n ✔ OpenClaw installed.\n");
|
|
214
|
+
console.log(" 💡 Tip: Set up WSL2 + NemoClaw later for sandbox mode.\n");
|
|
215
|
+
} catch {
|
|
216
|
+
console.error("\n ❌ Failed to install OpenClaw:");
|
|
217
|
+
console.error(" npm install -g openclaw\n");
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
console.log(" ℹ️ WSL is installed but no Linux distro is set up yet.\n");
|
|
223
|
+
|
|
224
|
+
let shouldSetup;
|
|
225
|
+
if (auto) {
|
|
226
|
+
shouldSetup = true;
|
|
227
|
+
} else {
|
|
228
|
+
shouldSetup = await confirm(
|
|
229
|
+
" Set up Ubuntu in WSL2 now and install NemoClaw? (Recommended)"
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (shouldSetup) {
|
|
234
|
+
console.log("\n 📦 Setting up Ubuntu in WSL2...\n");
|
|
235
|
+
console.log(" This will install Ubuntu and then install NemoClaw inside it.");
|
|
236
|
+
console.log(" You may be prompted to create a Linux username and password.\n");
|
|
237
|
+
try {
|
|
238
|
+
// Modern syntax (Win11/Win10 modern): wsl --install Ubuntu
|
|
239
|
+
// On older Win10 builds, -d flag is required: wsl --install --distribution Ubuntu
|
|
240
|
+
execSync("wsl --install Ubuntu", { stdio: "inherit" });
|
|
241
|
+
execSync("wsl --set-default-version 2", { stdio: "inherit" });
|
|
242
|
+
console.log("\n ✔ Ubuntu installed in WSL2.\n");
|
|
243
|
+
console.log(" 📦 Installing NemoClaw inside WSL2...\n");
|
|
244
|
+
execSync(
|
|
245
|
+
"wsl bash -c \"curl -fsSL https://nvidia.com/nemoclaw.sh | bash\"",
|
|
246
|
+
{ stdio: "inherit" }
|
|
247
|
+
);
|
|
248
|
+
console.log("\n ✔ NemoClaw installed in WSL2.\n");
|
|
249
|
+
console.log(" ℹ️ If onboard fails on Ubuntu 24.04: run inside WSL2: nemoclaw setup-spark\n");
|
|
250
|
+
} catch {
|
|
251
|
+
console.error("\n ❌ WSL2 setup failed. Try manually:");
|
|
252
|
+
console.error(" 1. wsl --install Ubuntu (or: wsl --install --distribution Ubuntu on older Win10)");
|
|
253
|
+
console.error(" 2. wsl bash -c \"curl -fsSL https://nvidia.com/nemoclaw.sh | bash\"\n");
|
|
254
|
+
console.error(" Falling back to OpenClaw for Windows...\n");
|
|
255
|
+
try {
|
|
256
|
+
execSync("npm install -g openclaw", { stdio: "inherit" });
|
|
257
|
+
console.log(" ✔ OpenClaw installed as fallback.\n");
|
|
258
|
+
} catch {
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
console.log("\n 📦 Installing OpenClaw for Windows...\n");
|
|
264
|
+
try {
|
|
265
|
+
execSync("npm install -g openclaw", { stdio: "inherit" });
|
|
266
|
+
console.log("\n ✔ OpenClaw installed.\n");
|
|
267
|
+
console.log(" 💡 Tip: Run \"wsl --install Ubuntu\" later, then re-run this installer");
|
|
268
|
+
console.log(" to get NemoClaw with full sandbox support.\n");
|
|
269
|
+
} catch {
|
|
270
|
+
console.error("\n ❌ Failed to install OpenClaw:");
|
|
271
|
+
console.error(" npm install -g openclaw\n");
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
console.log(" WSL2 is not installed on this machine.\n");
|
|
278
|
+
|
|
279
|
+
let shouldInstallWSL;
|
|
280
|
+
if (auto) {
|
|
281
|
+
shouldInstallWSL = false;
|
|
282
|
+
} else {
|
|
283
|
+
shouldInstallWSL = await confirm(
|
|
284
|
+
" Install WSL2 + Ubuntu now for full NemoClaw support? (Recommended)"
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (shouldInstallWSL) {
|
|
289
|
+
console.log("\n 📦 Installing WSL2 and Ubuntu...\n");
|
|
290
|
+
console.log(" ⚠️ This requires a system restart after WSL2 installs.");
|
|
291
|
+
console.log(" After restarting, re-run: npx @robbiesrobotics/alice-agents\n");
|
|
292
|
+
try {
|
|
293
|
+
execSync("wsl --install", { stdio: "inherit" });
|
|
294
|
+
try { execSync("wsl --set-default-version 2", { stdio: "pipe" }); } catch {}
|
|
295
|
+
console.log("\n ✔ WSL2 installation initiated.");
|
|
296
|
+
console.log(" 🔁 Please restart your computer, then re-run the installer.\n");
|
|
297
|
+
process.exit(0);
|
|
298
|
+
} catch {
|
|
299
|
+
console.error("\n ❌ WSL2 install failed. Enable it manually:");
|
|
300
|
+
console.error(" 1. Open PowerShell as Administrator");
|
|
301
|
+
console.error(" 2. Run: wsl --install");
|
|
302
|
+
console.error(" 3. Restart your computer");
|
|
303
|
+
console.error(" 4. Re-run: npx @robbiesrobotics/alice-agents\n");
|
|
304
|
+
console.error(" Falling back to OpenClaw for Windows...\n");
|
|
305
|
+
try {
|
|
306
|
+
execSync("npm install -g openclaw", { stdio: "inherit" });
|
|
307
|
+
console.log(" ✔ OpenClaw installed as fallback.\n");
|
|
308
|
+
} catch {
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
} else {
|
|
313
|
+
console.log("\n 📦 Installing OpenClaw for Windows...\n");
|
|
314
|
+
console.log(" A.L.I.C.E. will run without the NemoClaw sandbox.\n");
|
|
315
|
+
try {
|
|
316
|
+
execSync("npm install -g openclaw", { stdio: "inherit" });
|
|
317
|
+
console.log("\n ✔ OpenClaw installed.\n");
|
|
318
|
+
console.log(" 💡 To enable NemoClaw later:");
|
|
319
|
+
console.log(" 1. Open PowerShell as Administrator: wsl --install");
|
|
320
|
+
console.log(" 2. Restart, then re-run: npx @robbiesrobotics/alice-agents\n");
|
|
321
|
+
} catch {
|
|
322
|
+
console.error("\n ❌ Failed to install OpenClaw:");
|
|
323
|
+
console.error(" npm install -g openclaw\n");
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
execSync("openclaw configure", { stdio: "inherit" });
|
|
331
|
+
} catch {
|
|
332
|
+
console.error("\n ⚠️ Configuration incomplete. Run manually: openclaw configure\n");
|
|
333
|
+
process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// macOS
|
|
339
|
+
if (plat === "darwin") {
|
|
340
|
+
console.log(" A.L.I.C.E. requires a compatible runtime. NemoClaw requires Linux");
|
|
341
|
+
console.log(" (OpenShell uses Linux kernel primitives: Landlock, seccomp, netns).\n");
|
|
342
|
+
console.log(" On macOS, OpenClaw will be installed. For NemoClaw sandbox mode,");
|
|
343
|
+
console.log(" use a Linux VM or deploy remotely with: nemoclaw deploy <brev-instance>\n");
|
|
344
|
+
|
|
345
|
+
console.log(" 📦 Installing OpenClaw...\n");
|
|
346
|
+
try {
|
|
347
|
+
execSync("npm install -g openclaw", { stdio: "inherit" });
|
|
348
|
+
console.log("\n ✔ OpenClaw installed.\n");
|
|
349
|
+
} catch {
|
|
350
|
+
console.error("\n ❌ Failed to install OpenClaw:");
|
|
351
|
+
console.error(" npm install -g openclaw\n");
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
try {
|
|
356
|
+
execSync("openclaw configure", { stdio: "inherit" });
|
|
357
|
+
console.log("\n ✓ OpenClaw configured\n");
|
|
358
|
+
} catch {
|
|
359
|
+
console.error("\n ⚠️ Configuration incomplete. Run manually: openclaw configure\n");
|
|
360
|
+
process.exit(1);
|
|
361
|
+
}
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Linux / WSL2
|
|
366
|
+
console.log(" A.L.I.C.E. requires a compatible runtime. We recommend NemoClaw —");
|
|
367
|
+
console.log(" NVIDIA\u2019s secure, open-source distribution that includes OpenClaw");
|
|
368
|
+
console.log(" plus enterprise-grade security (OpenShell sandbox, local AI models).\n");
|
|
369
|
+
|
|
370
|
+
if (isWSL) {
|
|
371
|
+
console.log(" ℹ️ WSL2 environment detected.");
|
|
372
|
+
console.log(" If NemoClaw onboard fails, run: nemoclaw setup-spark");
|
|
373
|
+
console.log(" (applies the required cgroup v2 Docker fix for WSL2)\n");
|
|
374
|
+
}
|
|
147
375
|
|
|
148
376
|
let choice;
|
|
149
377
|
if (auto) {
|
|
150
|
-
choice =
|
|
378
|
+
choice = "nemoclaw";
|
|
151
379
|
} else {
|
|
152
|
-
choice = await choose(
|
|
153
|
-
{ label:
|
|
154
|
-
{ label:
|
|
380
|
+
choice = await choose(" Which would you like to install?", [
|
|
381
|
+
{ label: "NemoClaw (Recommended)", value: "nemoclaw" },
|
|
382
|
+
{ label: "OpenClaw only", value: "openclaw" },
|
|
155
383
|
]);
|
|
156
384
|
}
|
|
157
385
|
|
|
158
|
-
if (choice ===
|
|
159
|
-
console.log(
|
|
386
|
+
if (choice === "nemoclaw") {
|
|
387
|
+
console.log(" 📦 Installing NemoClaw...\n");
|
|
160
388
|
try {
|
|
161
|
-
execSync(
|
|
162
|
-
console.log(
|
|
163
|
-
} catch
|
|
164
|
-
console.error(
|
|
165
|
-
console.error(
|
|
389
|
+
execSync("curl -fsSL https://nvidia.com/nemoclaw.sh | bash", { stdio: "inherit" });
|
|
390
|
+
console.log("\n ✔ NemoClaw installed — agents will run in OpenShell sandbox\n");
|
|
391
|
+
} catch {
|
|
392
|
+
console.error("\n ❌ Failed to install NemoClaw. Try manually:");
|
|
393
|
+
console.error(" curl -fsSL https://nvidia.com/nemoclaw.sh | bash\n");
|
|
166
394
|
process.exit(1);
|
|
167
395
|
}
|
|
168
396
|
} else {
|
|
169
|
-
console.log(
|
|
397
|
+
console.log(" 📦 Installing OpenClaw...\n");
|
|
170
398
|
try {
|
|
171
|
-
execSync(
|
|
172
|
-
console.log(
|
|
173
|
-
} catch
|
|
174
|
-
console.error(
|
|
175
|
-
console.error(
|
|
399
|
+
execSync("npm install -g openclaw", { stdio: "inherit" });
|
|
400
|
+
console.log("\n ✔ OpenClaw installed\n");
|
|
401
|
+
} catch {
|
|
402
|
+
console.error("\n ❌ Failed to install OpenClaw:");
|
|
403
|
+
console.error(" npm install -g openclaw\n");
|
|
176
404
|
process.exit(1);
|
|
177
405
|
}
|
|
178
406
|
}
|
|
179
407
|
|
|
180
|
-
// Run openclaw configure
|
|
181
408
|
try {
|
|
182
|
-
execSync(
|
|
183
|
-
console.log(
|
|
409
|
+
execSync("openclaw configure", { stdio: "inherit" });
|
|
410
|
+
console.log("\n ✓ OpenClaw configured\n");
|
|
184
411
|
} catch {
|
|
185
|
-
console.error(
|
|
186
|
-
console.error(
|
|
187
|
-
console.error(
|
|
412
|
+
console.error("\n ⚠️ Configuration incomplete. Run manually:");
|
|
413
|
+
console.error(" openclaw configure");
|
|
414
|
+
console.error(" Then: npx @robbiesrobotics/alice-agents\n");
|
|
188
415
|
process.exit(1);
|
|
189
416
|
}
|
|
190
417
|
}
|
|
@@ -197,39 +424,43 @@ function loadAgentRegistry() {
|
|
|
197
424
|
}
|
|
198
425
|
|
|
199
426
|
function printBanner() {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
console.log('
|
|
203
|
-
console.log(
|
|
204
|
-
console.log(
|
|
205
|
-
console.log('
|
|
206
|
-
console.log('
|
|
207
|
-
console.log(
|
|
208
|
-
console.log(
|
|
209
|
-
console.log();
|
|
427
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url)));
|
|
428
|
+
const version = pkg.version || '';
|
|
429
|
+
console.log('');
|
|
430
|
+
console.log(` ${dim('╭──────────────────────────────────────────────╮')}`);
|
|
431
|
+
console.log(` ${dim('│')} ${dim('│')}`);
|
|
432
|
+
console.log(` ${dim('│')} ${greenBold('A . L . I . C . E .')} ${dim('│')}`);
|
|
433
|
+
console.log(` ${dim('│')} ${dim('Adaptive Learning & Intelligent Coordination')} ${dim('│')}`);
|
|
434
|
+
console.log(` ${dim('│')} ${dim('Engine — Multi-Agent Orchestration')} ${dim('│')}`);
|
|
435
|
+
console.log(` ${dim('│')} ${dim('│')}`);
|
|
436
|
+
console.log(` ${dim('│')} ${dim('v' + version)} ${green('●')} ${dim('10 starter agents, one team')} ${dim('│')}`);
|
|
437
|
+
console.log(` ${dim('│')} ${dim('│')}`);
|
|
438
|
+
console.log(` ${dim('╰──────────────────────────────────────────────╯')}`);
|
|
439
|
+
console.log('');
|
|
210
440
|
}
|
|
211
441
|
|
|
212
442
|
function printSummary(mode, tier, agents, preset, userInfo, detectedModels) {
|
|
213
443
|
const modelLabel =
|
|
214
444
|
preset === 'detected'
|
|
215
|
-
? `${detectedModels?.primary || 'your configured model'} (detected)`
|
|
216
|
-
: preset === 'custom'
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
445
|
+
? `${detectedModels?.primary || 'your configured model'} ${dim('(detected)')}`
|
|
446
|
+
: preset === 'custom' ? 'custom' : preset;
|
|
447
|
+
|
|
448
|
+
printSection('Install Summary');
|
|
449
|
+
console.log('');
|
|
450
|
+
|
|
451
|
+
const lines = [
|
|
452
|
+
`${dim('Mode:')} ${green(mode)}`,
|
|
453
|
+
`${dim('Tier:')} ${green(tier)} ${dim('(' + agents.length + ' agents)')}`,
|
|
454
|
+
`${dim('Model:')} ${green(modelLabel)}`,
|
|
455
|
+
`${dim('User:')} ${green(userInfo.name)}`,
|
|
456
|
+
`${dim('Timezone:')} ${green(userInfo.timezone)}`,
|
|
457
|
+
'',
|
|
458
|
+
`${dim('Agents:')}`,
|
|
459
|
+
...agents.map(a => ` ${icons.bullet} ${green(a.emoji)} ${bold(a.name.padEnd(10))} ${dim('─')} ${a.domain}`),
|
|
460
|
+
];
|
|
461
|
+
|
|
462
|
+
printBox(lines, { title: 'Ready to install', padding: 2 });
|
|
463
|
+
console.log('');
|
|
233
464
|
}
|
|
234
465
|
|
|
235
466
|
export async function runInstall(options = {}) {
|
|
@@ -237,7 +468,7 @@ export async function runInstall(options = {}) {
|
|
|
237
468
|
|
|
238
469
|
// Check health flag first (before banner)
|
|
239
470
|
if (process.argv.includes('--health')) {
|
|
240
|
-
const healthPath = join(
|
|
471
|
+
const healthPath = join(homedir(), '.openclaw', '.alice-health-alert.json');
|
|
241
472
|
if (existsSync(healthPath)) {
|
|
242
473
|
const alerts = JSON.parse(readFileSync(healthPath, 'utf8'));
|
|
243
474
|
console.log('\n⚠️ A.L.I.C.E. Health Report\n');
|
|
@@ -263,7 +494,8 @@ export async function runInstall(options = {}) {
|
|
|
263
494
|
|
|
264
495
|
// Final check
|
|
265
496
|
if (!configExists()) {
|
|
266
|
-
|
|
497
|
+
printStepFail('OpenClaw config not found', 'Run: openclaw configure');
|
|
498
|
+
console.log('');
|
|
267
499
|
process.exit(1);
|
|
268
500
|
}
|
|
269
501
|
|
|
@@ -272,23 +504,27 @@ export async function runInstall(options = {}) {
|
|
|
272
504
|
|
|
273
505
|
const runtime = await detectRuntime();
|
|
274
506
|
if (runtime === 'nemoclaw') {
|
|
275
|
-
console.log('
|
|
507
|
+
console.log(` ${icons.ok} ${greenBold('NemoClaw detected')} ${dim('─')} agents run in OpenShell sandbox\n`);
|
|
276
508
|
} else {
|
|
277
|
-
console.log('
|
|
278
|
-
|
|
509
|
+
console.log(` ${icons.ok} ${green('OpenClaw detected')}\n`);
|
|
510
|
+
if (process.platform === "linux" || process.env.WSL_DISTRO_NAME || process.env.WSLENV) {
|
|
511
|
+
console.log(` ${icons.info} ${dim('Tip:')} Upgrade to NemoClaw for enterprise security: https://nvidia.com/nemoclaw\n`);
|
|
512
|
+
} else if (process.platform === "darwin") {
|
|
513
|
+
console.log(` ${icons.info} ${dim('Tip:')} NemoClaw requires Linux. For sandbox mode, use a Linux VM or Brev remote deploy.\n`);
|
|
514
|
+
}
|
|
279
515
|
}
|
|
280
516
|
|
|
281
517
|
// Detect what models the user already has configured
|
|
282
518
|
const detectedModels = detectAvailableModels();
|
|
283
519
|
if (detectedModels?.hasModel) {
|
|
284
|
-
console.log(`
|
|
520
|
+
console.log(` ${icons.ok} ${green('Detected configured model:')} ${detectedModels.primary}`);
|
|
285
521
|
if (detectedModels.providers.length > 0) {
|
|
286
522
|
console.log(` Providers: ${detectedModels.providers.join(', ')}\n`);
|
|
287
523
|
} else {
|
|
288
524
|
console.log();
|
|
289
525
|
}
|
|
290
526
|
} else {
|
|
291
|
-
console.log('
|
|
527
|
+
console.log(` ${icons.info} ${dim('No model configured yet — you\'ll be prompted to choose one.')}\n`);
|
|
292
528
|
}
|
|
293
529
|
|
|
294
530
|
const allAgents = loadAgentRegistry();
|
|
@@ -307,7 +543,7 @@ export async function runInstall(options = {}) {
|
|
|
307
543
|
if (mode === 'upgrade') {
|
|
308
544
|
const manifest = readManifest();
|
|
309
545
|
if (!manifest) {
|
|
310
|
-
console.log('
|
|
546
|
+
console.log(` ${icons.warn} ${yellow('No previous install found. Switching to fresh install.')}\n`);
|
|
311
547
|
mode = 'fresh';
|
|
312
548
|
}
|
|
313
549
|
}
|
|
@@ -326,7 +562,8 @@ export async function runInstall(options = {}) {
|
|
|
326
562
|
if (auto) {
|
|
327
563
|
userInfo = { name: detectUserName(), timezone: detectTimezone(), notes: '' };
|
|
328
564
|
} else {
|
|
329
|
-
|
|
565
|
+
printSection('About You');
|
|
566
|
+
console.log('');
|
|
330
567
|
userInfo = await promptUserInfo();
|
|
331
568
|
}
|
|
332
569
|
|
|
@@ -358,14 +595,14 @@ export async function runInstall(options = {}) {
|
|
|
358
595
|
const existing = await checkProLicense();
|
|
359
596
|
|
|
360
597
|
if (existing.licensed) {
|
|
361
|
-
|
|
598
|
+
printStepDone(`Pro license found (${existing.key.slice(0, 12)}...)`);
|
|
362
599
|
} else if (auto) {
|
|
363
600
|
// --yes flag: skip interactive prompt, fallback to Starter if no stored license
|
|
364
601
|
console.log('');
|
|
365
|
-
console.log('
|
|
366
|
-
console.log('
|
|
367
|
-
console.log('
|
|
368
|
-
console.log('
|
|
602
|
+
console.log(` ${icons.info} ${dim('Pro tier requires a license key.')}`);
|
|
603
|
+
console.log(` ${dim('Run without --yes to enter your license key.')}`);
|
|
604
|
+
console.log(` ${dim('Falling back to Starter tier.')}`);
|
|
605
|
+
console.log(` ${dim('Purchase a license at:')} ${cyan('https://getalice.av3.ai/pricing')}`);
|
|
369
606
|
tier = 'starter';
|
|
370
607
|
} else {
|
|
371
608
|
// No stored license — prompt for key
|
|
@@ -376,7 +613,7 @@ export async function runInstall(options = {}) {
|
|
|
376
613
|
key = await promptLicenseKey();
|
|
377
614
|
|
|
378
615
|
if (!isValidFormat(key)) {
|
|
379
|
-
|
|
616
|
+
printStepFail('Invalid format. Key must be ALICE-XXXX-XXXX-XXXX');
|
|
380
617
|
attempts++;
|
|
381
618
|
continue;
|
|
382
619
|
}
|
|
@@ -387,13 +624,13 @@ export async function runInstall(options = {}) {
|
|
|
387
624
|
if (result.valid) {
|
|
388
625
|
storeLicense(key);
|
|
389
626
|
if (result.message === 'offline') {
|
|
390
|
-
|
|
627
|
+
printStepDone('Key stored', 'offline — will validate on next run');
|
|
391
628
|
} else {
|
|
392
|
-
|
|
629
|
+
printStepDone('License verified! Welcome to A.L.I.C.E. Pro.');
|
|
393
630
|
}
|
|
394
631
|
break;
|
|
395
632
|
} else {
|
|
396
|
-
|
|
633
|
+
printStepFail(`Invalid key: ${result.message ?? 'Not recognized'}`);
|
|
397
634
|
attempts++;
|
|
398
635
|
|
|
399
636
|
if (attempts >= 3) {
|
|
@@ -424,7 +661,8 @@ export async function runInstall(options = {}) {
|
|
|
424
661
|
closePrompt();
|
|
425
662
|
|
|
426
663
|
// Execute
|
|
427
|
-
|
|
664
|
+
printSection('Installing');
|
|
665
|
+
console.log('');
|
|
428
666
|
|
|
429
667
|
// Merge config
|
|
430
668
|
const { backupPath, agentCount } = mergeConfig({
|
|
@@ -433,7 +671,7 @@ export async function runInstall(options = {}) {
|
|
|
433
671
|
preset,
|
|
434
672
|
customModels,
|
|
435
673
|
});
|
|
436
|
-
|
|
674
|
+
printStepDone('Config updated', `backup: ${backupPath}`);
|
|
437
675
|
|
|
438
676
|
// Scaffold workspaces
|
|
439
677
|
const results = scaffoldAll(agents, userInfo);
|
|
@@ -446,7 +684,15 @@ export async function runInstall(options = {}) {
|
|
|
446
684
|
updatedWorkspaces++;
|
|
447
685
|
}
|
|
448
686
|
}
|
|
449
|
-
|
|
687
|
+
printStepDone('Workspaces', `${newWorkspaces} created, ${updatedWorkspaces} updated`);
|
|
688
|
+
|
|
689
|
+
// Skills installation step
|
|
690
|
+
const finalRuntimeForSkills = await detectRuntime();
|
|
691
|
+
const skillsInstalled = await runSkillsWizardStep({
|
|
692
|
+
auto,
|
|
693
|
+
nemoclaw: finalRuntimeForSkills === 'nemoclaw',
|
|
694
|
+
sandboxName: 'my-assistant',
|
|
695
|
+
});
|
|
450
696
|
|
|
451
697
|
// Write manifest
|
|
452
698
|
const existing = readManifest();
|
|
@@ -458,14 +704,26 @@ export async function runInstall(options = {}) {
|
|
|
458
704
|
userTimezone: userInfo.timezone,
|
|
459
705
|
modelPreset: preset,
|
|
460
706
|
});
|
|
461
|
-
|
|
707
|
+
printStepDone('Manifest written');
|
|
462
708
|
|
|
463
|
-
console.log(
|
|
709
|
+
console.log('');
|
|
710
|
+
printSeparator();
|
|
711
|
+
console.log('');
|
|
712
|
+
console.log(` ${icons.ok} ${greenBold('A.L.I.C.E. installed!')} ${dim(agentCount + ' agents ready.')}`);
|
|
713
|
+
console.log('');
|
|
464
714
|
const finalRuntime = await detectRuntime();
|
|
465
715
|
if (finalRuntime === 'nemoclaw') {
|
|
466
|
-
console.log(
|
|
716
|
+
console.log(` ${icons.ok} ${green('Secure mode')} ${dim('─')} OpenShell sandbox active`);
|
|
717
|
+
} else {
|
|
718
|
+
console.log(` ${icons.info} ${dim('Runtime: OpenClaw')}`);
|
|
467
719
|
}
|
|
468
|
-
console.log('
|
|
720
|
+
console.log('');
|
|
721
|
+
console.log(` ${dim('Manage skills:')} ${cyan('npx @robbiesrobotics/alice-agents --skills')}`);
|
|
722
|
+
console.log(` ${dim('Health check:')} ${cyan('npx @robbiesrobotics/alice-agents --doctor')}`);
|
|
723
|
+
console.log(` ${dim('Restart runtime:')} ${cyan('openclaw gateway restart')}`);
|
|
724
|
+
console.log('');
|
|
725
|
+
printSeparator();
|
|
726
|
+
console.log('');
|
|
469
727
|
}
|
|
470
728
|
|
|
471
729
|
export async function runUninstall(options = {}) {
|
package/lib/skills.mjs
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
// lib/skills.mjs
|
|
2
|
+
// A.L.I.C.E. Skills Manager — install, list, remove skills via clawhub
|
|
3
|
+
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import { existsSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { homedir } from 'node:os';
|
|
8
|
+
import { readManifest, writeManifest } from './manifest.mjs';
|
|
9
|
+
import { icons, greenBold, green, red, yellow, cyan, dim, bold,
|
|
10
|
+
printSection, printSeparator, printBox, printStepDone,
|
|
11
|
+
printStepFail, printStepSkip, separator } from './colors.mjs';
|
|
12
|
+
import { confirm, choose, closePrompt } from './prompter.mjs';
|
|
13
|
+
|
|
14
|
+
const OPENCLAW_DIR = join(homedir(), '.openclaw');
|
|
15
|
+
const SKILLS_DIR = join(OPENCLAW_DIR, 'skills');
|
|
16
|
+
|
|
17
|
+
export const SKILL_CATALOG = [
|
|
18
|
+
{
|
|
19
|
+
category: 'Productivity',
|
|
20
|
+
icon: '📅',
|
|
21
|
+
skills: [
|
|
22
|
+
{ id: 'apple-reminders', label: 'Apple Reminders', desc: 'Manage reminders from the terminal' },
|
|
23
|
+
{ id: 'apple-notes', label: 'Apple Notes', desc: 'Create and search notes' },
|
|
24
|
+
{ id: 'things-mac', label: 'Things 3', desc: 'Task management via Things 3 on macOS' },
|
|
25
|
+
{ id: 'gog', label: 'Google Workspace', desc: 'Gmail, Calendar, Drive, Docs, Sheets' },
|
|
26
|
+
{ id: 'obsidian', label: 'Obsidian', desc: 'Work with Obsidian vaults' },
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
category: 'Web & Research',
|
|
31
|
+
icon: '🌐',
|
|
32
|
+
skills: [
|
|
33
|
+
{ id: 'blogwatcher', label: 'Blog Watcher', desc: 'Monitor RSS/Atom feeds for updates' },
|
|
34
|
+
{ id: 'weather', label: 'Weather', desc: 'Current weather and forecasts (no API key)' },
|
|
35
|
+
{ id: 'summarize', label: 'Summarize', desc: 'Summarize URLs, podcasts, YouTube videos' },
|
|
36
|
+
{ id: 'gifgrep', label: 'GIF Search', desc: 'Search and download GIFs' },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
category: 'Communication',
|
|
41
|
+
icon: '💬',
|
|
42
|
+
skills: [
|
|
43
|
+
{ id: 'imsg', label: 'iMessage', desc: 'Send and read iMessages/SMS' },
|
|
44
|
+
{ id: 'wacli', label: 'WhatsApp', desc: 'WhatsApp messaging via CLI' },
|
|
45
|
+
{ id: 'himalaya', label: 'Email', desc: 'IMAP/SMTP email via himalaya' },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
category: 'Developer Tools',
|
|
50
|
+
icon: '⚙️',
|
|
51
|
+
skills: [
|
|
52
|
+
{ id: 'github', label: 'GitHub', desc: 'Issues, PRs, CI via gh CLI' },
|
|
53
|
+
{ id: 'gh-issues', label: 'GitHub Issues Bot', desc: 'Auto-fix issues and open PRs' },
|
|
54
|
+
{ id: 'coding-agent', label: 'Coding Agent', desc: 'Delegate tasks to Codex / Claude Code' },
|
|
55
|
+
{ id: '1password', label: '1Password', desc: 'Secrets and credentials via op CLI' },
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
category: 'Smart Home & IoT',
|
|
60
|
+
icon: '🏠',
|
|
61
|
+
skills: [
|
|
62
|
+
{ id: 'openhue', label: 'Philips Hue', desc: 'Control Hue lights and scenes' },
|
|
63
|
+
{ id: 'sonoscli', label: 'Sonos', desc: 'Control Sonos speakers' },
|
|
64
|
+
{ id: 'blucli', label: 'BluOS', desc: 'BluOS audio system control' },
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
// Egress endpoints needed per skill beyond NemoClaw baseline
|
|
70
|
+
const SKILL_POLICY_ENDPOINTS = {
|
|
71
|
+
'weather': ['wttr.in:443', 'api.open-meteo.com:443'],
|
|
72
|
+
'gog': ['oauth2.googleapis.com:443', 'www.googleapis.com:443', 'accounts.google.com:443'],
|
|
73
|
+
'gifgrep': ['api.giphy.com:443', 'api.tenor.com:443'],
|
|
74
|
+
'blogwatcher': ['*:443'],
|
|
75
|
+
'summarize': ['*:443'],
|
|
76
|
+
// github, gh-issues, apple-*, things-mac, obsidian, imsg, wacli, himalaya, coding-agent, 1password,
|
|
77
|
+
// openhue, sonoscli, blucli — no extra endpoints needed or use dynamic approval
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
function commandExists(cmd) {
|
|
81
|
+
const probe = process.platform === 'win32' ? 'where' : 'which';
|
|
82
|
+
try { execSync(`${probe} ${cmd}`, { stdio: 'pipe' }); return true; }
|
|
83
|
+
catch { return false; }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isClawhubAvailable() {
|
|
87
|
+
return commandExists('clawhub');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isSkillInstalled(skillId) {
|
|
91
|
+
return existsSync(join(SKILLS_DIR, skillId));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function getInstalledSkills() {
|
|
95
|
+
const manifest = readManifest();
|
|
96
|
+
return manifest?.skills || [];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function detectNemoclawSandboxName() {
|
|
100
|
+
const manifest = readManifest();
|
|
101
|
+
if (manifest?.nemoclawSandbox) return manifest.nemoclawSandbox;
|
|
102
|
+
try {
|
|
103
|
+
const out = execSync('nemoclaw list', { stdio: 'pipe', encoding: 'utf8' });
|
|
104
|
+
const match = out.match(/^(\S+)\s/m);
|
|
105
|
+
if (match) return match[1];
|
|
106
|
+
} catch {}
|
|
107
|
+
return 'my-assistant';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function applyNemoclawPolicyForSkill(skillId, sandboxName) {
|
|
111
|
+
const endpoints = SKILL_POLICY_ENDPOINTS[skillId];
|
|
112
|
+
if (!endpoints || endpoints.length === 0) return;
|
|
113
|
+
|
|
114
|
+
if (endpoints.includes('*:443')) {
|
|
115
|
+
console.log(` ${icons.info} ${cyan(skillId)} ${dim('requires dynamic egress — approve via')} ${cyan('openshell term')}`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const policyContent = [
|
|
120
|
+
'network:',
|
|
121
|
+
` - name: alice_${skillId}`,
|
|
122
|
+
' endpoints:',
|
|
123
|
+
...endpoints.map(e => ` - ${e}`),
|
|
124
|
+
' binaries:',
|
|
125
|
+
' - /usr/local/bin/openclaw',
|
|
126
|
+
' rules:',
|
|
127
|
+
' - methods: [GET, POST]',
|
|
128
|
+
'',
|
|
129
|
+
].join('\n');
|
|
130
|
+
|
|
131
|
+
const tmpPath = join(homedir(), `.alice-policy-${skillId}.yaml`);
|
|
132
|
+
writeFileSync(tmpPath, policyContent, 'utf8');
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
execSync(`openshell policy set ${tmpPath}`, { stdio: 'pipe' });
|
|
136
|
+
console.log(` ${icons.ok} ${dim('Policy updated for')} ${green(skillId)} ${dim('─')} ${endpoints.join(', ')}`);
|
|
137
|
+
} catch {
|
|
138
|
+
console.log(` ${icons.warn} ${yellow('Could not auto-apply policy for')} ${skillId}`);
|
|
139
|
+
console.log(` ${dim('Run manually:')} openshell policy set ${tmpPath}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function installSkill(skillId, { nemoclaw = false, sandboxName = 'my-assistant' } = {}) {
|
|
144
|
+
if (isSkillInstalled(skillId)) {
|
|
145
|
+
printStepSkip(skillId, 'already installed');
|
|
146
|
+
return { status: 'skipped' };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!isClawhubAvailable()) {
|
|
150
|
+
printStepFail(skillId, 'clawhub not found — install: npm install -g clawhub');
|
|
151
|
+
return { status: 'error', reason: 'clawhub not available' };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
execSync(`clawhub install ${skillId}`, { stdio: 'pipe' });
|
|
156
|
+
printStepDone(skillId);
|
|
157
|
+
if (nemoclaw) {
|
|
158
|
+
await applyNemoclawPolicyForSkill(skillId, sandboxName);
|
|
159
|
+
}
|
|
160
|
+
return { status: 'ok' };
|
|
161
|
+
} catch (err) {
|
|
162
|
+
printStepFail(skillId, err.message?.slice(0, 60));
|
|
163
|
+
return { status: 'error', reason: err.message };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function runSkillsWizardStep({ auto = false, nemoclaw = false, sandboxName = 'my-assistant' } = {}) {
|
|
168
|
+
printSection('Skills & Tools');
|
|
169
|
+
console.log('');
|
|
170
|
+
console.log(` ${dim('Skills extend your agents with real-world capabilities.')}`);
|
|
171
|
+
console.log(` ${dim('Installed via')} ${green('clawhub')} ${dim('— the OpenClaw skill marketplace.')}`);
|
|
172
|
+
console.log('');
|
|
173
|
+
|
|
174
|
+
if (!isClawhubAvailable()) {
|
|
175
|
+
console.log(` ${icons.warn} ${yellow('clawhub not found.')} ${dim('Skills cannot be installed right now.')}`);
|
|
176
|
+
console.log(` ${dim('Install it with:')} ${cyan('npm install -g clawhub')}`);
|
|
177
|
+
console.log(` ${dim('Then run:')} ${cyan('npx @robbiesrobotics/alice-agents --skills')}`);
|
|
178
|
+
console.log('');
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (auto) {
|
|
183
|
+
console.log(` ${icons.info} ${dim('Skipping skills in non-interactive mode.')}`);
|
|
184
|
+
console.log(` ${dim('Run')} ${cyan('npx @robbiesrobotics/alice-agents --skills')} ${dim('to install later.')}`);
|
|
185
|
+
console.log('');
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const shouldInstall = await confirm(' Install recommended skills now?');
|
|
190
|
+
if (!shouldInstall) {
|
|
191
|
+
printStepSkip('Skills', 'skipped — run --skills to install later');
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.log('');
|
|
196
|
+
const selected = [];
|
|
197
|
+
|
|
198
|
+
for (const group of SKILL_CATALOG) {
|
|
199
|
+
printSection(`${group.icon} ${group.category}`);
|
|
200
|
+
console.log('');
|
|
201
|
+
|
|
202
|
+
for (const skill of group.skills) {
|
|
203
|
+
const already = isSkillInstalled(skill.id);
|
|
204
|
+
if (already) {
|
|
205
|
+
console.log(` ${icons.check} ${green(skill.label)} ${dim('─ already installed')}`);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const yes = await confirm(` Install ${green(skill.label)}? ${dim('─')} ${dim(skill.desc)}`);
|
|
209
|
+
if (yes) selected.push(skill.id);
|
|
210
|
+
}
|
|
211
|
+
console.log('');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (selected.length === 0) {
|
|
215
|
+
console.log(` ${icons.info} ${dim('No skills selected.')}`);
|
|
216
|
+
console.log('');
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
printSection('Installing Skills');
|
|
221
|
+
console.log('');
|
|
222
|
+
|
|
223
|
+
if (nemoclaw) {
|
|
224
|
+
console.log(` ${icons.info} ${cyan('NemoClaw detected')} ${dim('─ policy endpoints will be applied automatically.')}`);
|
|
225
|
+
console.log('');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const results = [];
|
|
229
|
+
for (const skillId of selected) {
|
|
230
|
+
const result = await installSkill(skillId, { nemoclaw, sandboxName });
|
|
231
|
+
results.push({ skillId, ...result });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const ok = results.filter(r => r.status === 'ok').length;
|
|
235
|
+
const err = results.filter(r => r.status === 'error').length;
|
|
236
|
+
|
|
237
|
+
console.log('');
|
|
238
|
+
console.log(` ${separator()}`);
|
|
239
|
+
console.log(` ${icons.ok} ${green(String(ok))} ${dim('skills installed')}${err ? ` ${icons.warn} ${yellow(String(err) + ' failed')}` : ''}`);
|
|
240
|
+
console.log('');
|
|
241
|
+
|
|
242
|
+
const manifest = readManifest();
|
|
243
|
+
if (manifest) {
|
|
244
|
+
const already = manifest.skills || [];
|
|
245
|
+
const newOnes = results.filter(r => r.status === 'ok').map(r => r.skillId);
|
|
246
|
+
writeManifest({ ...manifest, skills: [...new Set([...already, ...newOnes])] });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return selected;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export async function runSkillsManager() {
|
|
253
|
+
console.log('');
|
|
254
|
+
printSection('A.L.I.C.E. Skills Manager');
|
|
255
|
+
console.log('');
|
|
256
|
+
|
|
257
|
+
const installed = getInstalledSkills();
|
|
258
|
+
|
|
259
|
+
if (installed.length > 0) {
|
|
260
|
+
console.log(` ${dim('Installed skills:')}`);
|
|
261
|
+
installed.forEach(id => {
|
|
262
|
+
const onDisk = isSkillInstalled(id);
|
|
263
|
+
console.log(` ${onDisk ? icons.ok : icons.warn} ${onDisk ? green(id) : yellow(id + ' (missing on disk)')}`);
|
|
264
|
+
});
|
|
265
|
+
console.log('');
|
|
266
|
+
} else {
|
|
267
|
+
console.log(` ${icons.info} ${dim('No skills installed yet.')}`);
|
|
268
|
+
console.log('');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const action = await choose(' What would you like to do?', [
|
|
272
|
+
{ label: 'Browse & install skills', value: 'install' },
|
|
273
|
+
{ label: 'Remove a skill', value: 'remove' },
|
|
274
|
+
{ label: 'Exit', value: 'exit' },
|
|
275
|
+
]);
|
|
276
|
+
|
|
277
|
+
if (action === 'exit') { closePrompt(); return; }
|
|
278
|
+
|
|
279
|
+
if (action === 'install') {
|
|
280
|
+
const nemoclaw = commandExists('nemoclaw');
|
|
281
|
+
const sandboxName = nemoclaw ? await detectNemoclawSandboxName() : null;
|
|
282
|
+
await runSkillsWizardStep({ auto: false, nemoclaw, sandboxName });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (action === 'remove') {
|
|
286
|
+
if (installed.length === 0) {
|
|
287
|
+
console.log(` ${icons.info} ${dim('Nothing to remove.')}`);
|
|
288
|
+
closePrompt();
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const toRemove = await choose(' Which skill to remove?', [
|
|
292
|
+
...installed.map(id => ({ label: id, value: id })),
|
|
293
|
+
{ label: 'Cancel', value: null },
|
|
294
|
+
]);
|
|
295
|
+
if (toRemove) {
|
|
296
|
+
try {
|
|
297
|
+
execSync(`clawhub uninstall ${toRemove}`, { stdio: 'inherit' });
|
|
298
|
+
printStepDone(toRemove, 'removed');
|
|
299
|
+
const manifest = readManifest();
|
|
300
|
+
if (manifest) {
|
|
301
|
+
writeManifest({ ...manifest, skills: (manifest.skills || []).filter(s => s !== toRemove) });
|
|
302
|
+
}
|
|
303
|
+
} catch {
|
|
304
|
+
printStepFail(toRemove, 'remove failed');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
closePrompt();
|
|
310
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@robbiesrobotics/alice-agents",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A.L.I.C.E.
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "A.L.I.C.E. — 28 AI agents for OpenClaw. One conversation, one team.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"alice-agents": "bin/alice-install.mjs"
|
|
7
7
|
},
|
|
@@ -35,4 +35,4 @@
|
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"access": "public"
|
|
37
37
|
}
|
|
38
|
-
}
|
|
38
|
+
}
|