@robbiesrobotics/alice-agents 1.3.0 → 1.3.2
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/doctor.mjs +121 -2
- package/lib/installer.mjs +94 -16
- package/lib/license.mjs +50 -0
- package/lib/prompter.mjs +15 -0
- package/package.json +1 -1
- package/templates/workspaces/_shared/AGENTS.md +9 -0
package/lib/doctor.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
1
|
+
import { readFileSync, existsSync, accessSync, constants } from 'node:fs';
|
|
2
2
|
import { join, dirname } from 'node:path';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
3
|
+
import { homedir, platform } from 'node:os';
|
|
4
4
|
import { execSync } from 'node:child_process';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
|
|
@@ -42,6 +42,72 @@ function loadConfig() {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Check Docker socket accessibility.
|
|
47
|
+
* On Linux, Docker often requires sudo unless the user is in the docker group.
|
|
48
|
+
* Returns { present, accessible, needsSudo, hint }
|
|
49
|
+
*/
|
|
50
|
+
function checkDockerEnvironment() {
|
|
51
|
+
const isLinux = platform() === 'linux';
|
|
52
|
+
|
|
53
|
+
// Check if docker binary exists at all
|
|
54
|
+
let dockerInstalled = false;
|
|
55
|
+
try {
|
|
56
|
+
execSync('which docker', { stdio: 'pipe' });
|
|
57
|
+
dockerInstalled = true;
|
|
58
|
+
} catch {
|
|
59
|
+
// Docker not installed — not a blocker unless openclaw needs it
|
|
60
|
+
return { present: false, accessible: false, needsSudo: false, hint: null };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Try docker ps without sudo
|
|
64
|
+
try {
|
|
65
|
+
execSync('docker ps', { stdio: 'pipe' });
|
|
66
|
+
return { present: true, accessible: true, needsSudo: false, hint: null };
|
|
67
|
+
} catch (err) {
|
|
68
|
+
const msg = err.stderr?.toString() || '';
|
|
69
|
+
|
|
70
|
+
// Permission denied / cannot connect to daemon — classic sudo-required scenario
|
|
71
|
+
const isPermissionIssue =
|
|
72
|
+
msg.includes('permission denied') ||
|
|
73
|
+
msg.includes('Got permission denied') ||
|
|
74
|
+
msg.includes('Cannot connect to the Docker daemon') ||
|
|
75
|
+
msg.includes('dial unix') ||
|
|
76
|
+
msg.includes('connect: permission denied');
|
|
77
|
+
|
|
78
|
+
if (isLinux && isPermissionIssue) {
|
|
79
|
+
// Try sudo docker ps to confirm it works with elevated perms
|
|
80
|
+
let sudoWorks = false;
|
|
81
|
+
try {
|
|
82
|
+
execSync('sudo docker ps', { stdio: 'pipe', timeout: 5000 });
|
|
83
|
+
sudoWorks = true;
|
|
84
|
+
} catch {}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
present: true,
|
|
88
|
+
accessible: false,
|
|
89
|
+
needsSudo: true,
|
|
90
|
+
sudoWorks,
|
|
91
|
+
hint: sudoWorks
|
|
92
|
+
? `Docker requires sudo on this machine. Fix with:\n sudo usermod -aG docker $USER && newgrp docker\n Then log out and back in. Or run OpenClaw with sudo (not recommended for production).`
|
|
93
|
+
: `Docker found but not accessible. Check that the Docker daemon is running:\n sudo systemctl start docker\n Then add your user to the docker group:\n sudo usermod -aG docker $USER && newgrp docker`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Docker is installed but daemon isn't running
|
|
98
|
+
if (msg.includes('Is the docker daemon running') || msg.includes('Cannot connect')) {
|
|
99
|
+
return {
|
|
100
|
+
present: true,
|
|
101
|
+
accessible: false,
|
|
102
|
+
needsSudo: false,
|
|
103
|
+
hint: 'Docker daemon is not running. Start it:\n sudo systemctl start docker (Linux)\n open -a Docker (macOS)',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { present: true, accessible: false, needsSudo: false, hint: `docker ps failed: ${msg.slice(0, 100)}` };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
45
111
|
export async function runDoctor() {
|
|
46
112
|
console.log('\n 🩺 A.L.I.C.E. Doctor — Diagnostic Report\n');
|
|
47
113
|
let allOk = true;
|
|
@@ -157,6 +223,59 @@ export async function runDoctor() {
|
|
|
157
223
|
);
|
|
158
224
|
allOk = allOk && modelOk;
|
|
159
225
|
|
|
226
|
+
// 6. Docker environment check (Linux-aware)
|
|
227
|
+
const docker = checkDockerEnvironment();
|
|
228
|
+
if (docker.present) {
|
|
229
|
+
if (docker.accessible) {
|
|
230
|
+
check('Docker accessible', true);
|
|
231
|
+
} else if (docker.needsSudo) {
|
|
232
|
+
check(
|
|
233
|
+
'Docker requires sudo — user not in docker group',
|
|
234
|
+
false,
|
|
235
|
+
docker.hint
|
|
236
|
+
);
|
|
237
|
+
// Docker permission issue is a warning, not a hard failure for A.L.I.C.E. itself
|
|
238
|
+
// but it will break OpenClaw's own Docker features
|
|
239
|
+
console.log(' ℹ️ Note: This will affect OpenClaw features that use Docker.\n');
|
|
240
|
+
} else {
|
|
241
|
+
check('Docker daemon not running or not accessible', false, docker.hint);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// If docker not present at all, skip silently — not required for all setups
|
|
245
|
+
|
|
246
|
+
// 7. License check
|
|
247
|
+
const { checkProLicense } = await import('./license.mjs');
|
|
248
|
+
const manifest = (() => {
|
|
249
|
+
try {
|
|
250
|
+
const mPath = join(OPENCLAW_DIR, '.alice-manifest.json');
|
|
251
|
+
if (!existsSync(mPath)) return null;
|
|
252
|
+
return JSON.parse(readFileSync(mPath, 'utf8'));
|
|
253
|
+
} catch {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
})();
|
|
257
|
+
|
|
258
|
+
const licenseResult = await checkProLicense();
|
|
259
|
+
if (manifest?.tier === 'pro' || licenseResult.licensed) {
|
|
260
|
+
if (licenseResult.licensed) {
|
|
261
|
+
const maskedKey = licenseResult.key.slice(0, 13) + '****';
|
|
262
|
+
check(
|
|
263
|
+
`Pro license: ${maskedKey} (stored at ~/.alice/license)`,
|
|
264
|
+
true
|
|
265
|
+
);
|
|
266
|
+
} else {
|
|
267
|
+
check(
|
|
268
|
+
'Pro license: not found (running Starter tier)',
|
|
269
|
+
false,
|
|
270
|
+
'Purchase a Pro license at: https://getalice.av3.ai/pricing'
|
|
271
|
+
);
|
|
272
|
+
allOk = false;
|
|
273
|
+
}
|
|
274
|
+
} else {
|
|
275
|
+
// Starter tier — just note no license needed
|
|
276
|
+
check('License: Starter tier (no license required)', true);
|
|
277
|
+
}
|
|
278
|
+
|
|
160
279
|
// Summary
|
|
161
280
|
console.log();
|
|
162
281
|
if (allOk) {
|
package/lib/installer.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
promptModelPreset,
|
|
12
12
|
promptCustomModel,
|
|
13
13
|
promptTier,
|
|
14
|
+
promptLicenseKey,
|
|
14
15
|
confirm,
|
|
15
16
|
choose,
|
|
16
17
|
input,
|
|
@@ -28,6 +29,44 @@ function isOpenClawInstalled() {
|
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
/**
|
|
33
|
+
* On Linux, Docker requires the user to be in the docker group.
|
|
34
|
+
* Detect this early and warn before OpenClaw's own preflight fails cryptically.
|
|
35
|
+
*/
|
|
36
|
+
function checkLinuxDockerPermissions() {
|
|
37
|
+
if (process.platform !== 'linux') return;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
execSync('which docker', { stdio: 'pipe' });
|
|
41
|
+
} catch {
|
|
42
|
+
return; // Docker not installed — not our problem
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
execSync('docker ps', { stdio: 'pipe' });
|
|
47
|
+
return; // Works fine — user is in docker group
|
|
48
|
+
} catch (err) {
|
|
49
|
+
const msg = err.stderr?.toString() || '';
|
|
50
|
+
const isPermissionIssue =
|
|
51
|
+
msg.includes('permission denied') ||
|
|
52
|
+
msg.includes('Got permission denied') ||
|
|
53
|
+
msg.includes('Cannot connect to the Docker daemon') ||
|
|
54
|
+
msg.includes('connect: permission denied');
|
|
55
|
+
|
|
56
|
+
if (isPermissionIssue) {
|
|
57
|
+
console.log(' ⚠️ Docker permission issue detected.\n');
|
|
58
|
+
console.log(' Your user is not in the docker group. This will cause');
|
|
59
|
+
console.log(' OpenClaw to fail when it tries to access Docker.\n');
|
|
60
|
+
console.log(' Fix this now (recommended):');
|
|
61
|
+
console.log(' sudo usermod -aG docker $USER');
|
|
62
|
+
console.log(' newgrp docker\n');
|
|
63
|
+
console.log(' Or log out and back in after running the usermod command.');
|
|
64
|
+
console.log(' You can also run: npx @robbiesrobotics/alice-agents --doctor');
|
|
65
|
+
console.log(' after fixing to verify the issue is resolved.\n');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
31
70
|
async function detectRuntime() {
|
|
32
71
|
// Check for NemoClaw binary
|
|
33
72
|
try {
|
|
@@ -214,6 +253,9 @@ export async function runInstall(options = {}) {
|
|
|
214
253
|
|
|
215
254
|
printBanner();
|
|
216
255
|
|
|
256
|
+
// 0. Linux Docker permission check — warn early before OpenClaw preflight fails
|
|
257
|
+
checkLinuxDockerPermissions();
|
|
258
|
+
|
|
217
259
|
// 1. Detect OpenClaw — offer to install if missing
|
|
218
260
|
if (!isOpenClawInstalled() || !configExists()) {
|
|
219
261
|
await installRuntime(auto);
|
|
@@ -311,22 +353,58 @@ export async function runInstall(options = {}) {
|
|
|
311
353
|
}
|
|
312
354
|
|
|
313
355
|
if (tier === 'pro') {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
356
|
+
const { checkProLicense, validateLicenseRemote, storeLicense, isValidFormat } = await import('./license.mjs');
|
|
357
|
+
|
|
358
|
+
const existing = await checkProLicense();
|
|
359
|
+
|
|
360
|
+
if (existing.licensed) {
|
|
361
|
+
console.log(` ✅ Pro license found (${existing.key.slice(0, 12)}...)`);
|
|
362
|
+
} else if (auto) {
|
|
363
|
+
// --yes flag: skip interactive prompt, fallback to Starter if no stored license
|
|
364
|
+
console.log('');
|
|
365
|
+
console.log(' ℹ️ Pro tier requires a license key.');
|
|
366
|
+
console.log(' Run without --yes to enter your license key.');
|
|
367
|
+
console.log(' Falling back to Starter tier.');
|
|
368
|
+
console.log(' Purchase a license at: https://getalice.av3.ai/pricing');
|
|
369
|
+
tier = 'starter';
|
|
370
|
+
} else {
|
|
371
|
+
// No stored license — prompt for key
|
|
372
|
+
let key = '';
|
|
373
|
+
let attempts = 0;
|
|
374
|
+
|
|
375
|
+
while (attempts < 3) {
|
|
376
|
+
key = await promptLicenseKey();
|
|
377
|
+
|
|
378
|
+
if (!isValidFormat(key)) {
|
|
379
|
+
console.log(' ❌ Invalid format. Key must be ALICE-XXXX-XXXX-XXXX');
|
|
380
|
+
attempts++;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
console.log(' Validating key...');
|
|
385
|
+
const result = await validateLicenseRemote(key);
|
|
386
|
+
|
|
387
|
+
if (result.valid) {
|
|
388
|
+
storeLicense(key);
|
|
389
|
+
if (result.message === 'offline') {
|
|
390
|
+
console.log(' ✅ Key stored (offline — will validate on next run)');
|
|
391
|
+
} else {
|
|
392
|
+
console.log(' ✅ License verified! Welcome to A.L.I.C.E. Pro.');
|
|
393
|
+
}
|
|
394
|
+
break;
|
|
395
|
+
} else {
|
|
396
|
+
console.log(` ❌ Invalid key: ${result.message ?? 'Not recognized'}`);
|
|
397
|
+
attempts++;
|
|
398
|
+
|
|
399
|
+
if (attempts >= 3) {
|
|
400
|
+
console.log('');
|
|
401
|
+
console.log(' Too many invalid attempts. Falling back to Starter tier.');
|
|
402
|
+
console.log(' Purchase a license at: https://getalice.av3.ai/pricing');
|
|
403
|
+
tier = 'starter'; // fallback gracefully
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
330
408
|
}
|
|
331
409
|
|
|
332
410
|
const agents = allAgents;
|
package/lib/license.mjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// lib/license.mjs
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
|
|
6
|
+
const LICENSE_DIR = join(homedir(), '.alice');
|
|
7
|
+
const LICENSE_FILE = join(LICENSE_DIR, 'license');
|
|
8
|
+
const VALIDATE_URL = 'https://getalice.av3.ai/api/license/validate';
|
|
9
|
+
const KEY_REGEX = /^ALICE-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/;
|
|
10
|
+
|
|
11
|
+
export function readStoredLicense() {
|
|
12
|
+
try {
|
|
13
|
+
if (!existsSync(LICENSE_FILE)) return null;
|
|
14
|
+
return readFileSync(LICENSE_FILE, 'utf8').trim();
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function storeLicense(key) {
|
|
21
|
+
mkdirSync(LICENSE_DIR, { recursive: true });
|
|
22
|
+
writeFileSync(LICENSE_FILE, key.trim(), 'utf8');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isValidFormat(key) {
|
|
26
|
+
return KEY_REGEX.test(key?.trim()?.toUpperCase());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function validateLicenseRemote(key) {
|
|
30
|
+
// Returns { valid: boolean, plan: string, message?: string }
|
|
31
|
+
try {
|
|
32
|
+
const res = await fetch(`${VALIDATE_URL}?key=${encodeURIComponent(key)}`, {
|
|
33
|
+
signal: AbortSignal.timeout(5000),
|
|
34
|
+
});
|
|
35
|
+
if (!res.ok) return { valid: false, message: 'Validation service unavailable' };
|
|
36
|
+
return await res.json();
|
|
37
|
+
} catch {
|
|
38
|
+
// Network error — fail open (allow install, validate next time)
|
|
39
|
+
return { valid: true, plan: 'pro', message: 'offline' };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function checkProLicense() {
|
|
44
|
+
// Returns: { licensed: boolean, key: string|null, source: 'stored'|'none' }
|
|
45
|
+
const stored = readStoredLicense();
|
|
46
|
+
if (stored && isValidFormat(stored)) {
|
|
47
|
+
return { licensed: true, key: stored, source: 'stored' };
|
|
48
|
+
}
|
|
49
|
+
return { licensed: false, key: null, source: 'none' };
|
|
50
|
+
}
|
package/lib/prompter.mjs
CHANGED
|
@@ -110,3 +110,18 @@ export async function promptTier() {
|
|
|
110
110
|
{ label: 'Pro (18 more agents) — sign up at getalice.av3.ai/signup?plan=pro', value: 'pro' },
|
|
111
111
|
]);
|
|
112
112
|
}
|
|
113
|
+
|
|
114
|
+
export async function promptLicenseKey() {
|
|
115
|
+
// Show instructions, prompt for key, validate format
|
|
116
|
+
console.log('');
|
|
117
|
+
console.log(' A.L.I.C.E. Pro requires a license key.');
|
|
118
|
+
console.log(' Purchase at: https://getalice.av3.ai/pricing');
|
|
119
|
+
console.log(' Already have a key? Enter it below.');
|
|
120
|
+
console.log('');
|
|
121
|
+
|
|
122
|
+
return new Promise((resolve) => {
|
|
123
|
+
getRL().question(' License key (ALICE-XXXX-XXXX-XXXX): ', (answer) => {
|
|
124
|
+
resolve(answer.trim().toUpperCase());
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
package/package.json
CHANGED
|
@@ -48,3 +48,12 @@ Skip this for trivial lookups or single-command tasks. Write it for anything inv
|
|
|
48
48
|
- Don't exceed your domain without flagging it
|
|
49
49
|
- Don't run destructive commands without explicit risk callout
|
|
50
50
|
- `trash` > `rm`
|
|
51
|
+
|
|
52
|
+
## Tier Note
|
|
53
|
+
|
|
54
|
+
{{#if isPro}}
|
|
55
|
+
You are a **Pro tier** agent. Pro tier requires a valid A.L.I.C.E. Pro license key stored at `~/.alice/license`.
|
|
56
|
+
If you were installed without a license key, run the installer interactively to enter your key:
|
|
57
|
+
`npx @robbiesrobotics/alice-agents`
|
|
58
|
+
Purchase or manage your license at: https://getalice.av3.ai/pricing
|
|
59
|
+
{{/if}}
|