@robbiesrobotics/alice-agents 1.4.5 → 1.4.6
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 +19 -3
- package/bin/alice-install.mjs +22 -1
- package/lib/agent-registry.mjs +33 -0
- package/lib/coding-agent.mjs +187 -0
- package/lib/config-merger.mjs +5 -1
- package/lib/doctor.mjs +42 -24
- package/lib/installer.mjs +83 -19
- package/lib/license.mjs +158 -14
- package/lib/manifest.mjs +18 -9
- package/lib/mission-control.mjs +12 -0
- package/lib/release-guard.mjs +131 -0
- package/lib/skills.mjs +0 -1
- package/lib/workspace-scaffolder.mjs +11 -16
- package/package.json +5 -3
- package/templates/agents-pro.json +550 -0
- package/templates/mission-control-bridge/index.ts +21 -0
- package/templates/workspaces/dylan/SOUL.md +1 -1
- package/templates/workspaces/dylan/TOOLS.md +4 -4
- package/templates/workspaces/felix/SOUL.md +1 -1
- package/templates/workspaces/felix/TOOLS.md +3 -3
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ That's it. The installer detects your runtime (NemoClaw or OpenClaw) and sets ev
|
|
|
22
22
|
- installs the `mission-control-bridge` plugin into your OpenClaw home
|
|
23
23
|
- writes a portable local Mission Control config at `~/.openclaw/.alice-mission-control.json`
|
|
24
24
|
- enables the bridge in `openclaw.json` so your runtime can forward live telemetry to Mission Control
|
|
25
|
+
- installs a bundled `coding-agent` skill that prefers Codex for OpenAI defaults and Claude Code for Anthropic defaults
|
|
25
26
|
|
|
26
27
|
An orchestrator (A.L.I.C.E., also addressable as Alice or Olivia) backed by specialist agents across every domain:
|
|
27
28
|
|
|
@@ -79,11 +80,14 @@ When you install, the installer will **auto-detect your configured model** and l
|
|
|
79
80
|
# Interactive install
|
|
80
81
|
npx @robbiesrobotics/alice-agents
|
|
81
82
|
|
|
82
|
-
# Non-interactive with defaults (detected model if available, otherwise Sonnet; Starter tier)
|
|
83
|
+
# Non-interactive with defaults (detected model if available, otherwise Sonnet; Starter tier unless --tier pro)
|
|
83
84
|
npx @robbiesrobotics/alice-agents --yes
|
|
84
85
|
|
|
85
86
|
# Non-interactive Pro install with Mission Control Cloud enabled
|
|
86
|
-
npx @robbiesrobotics/alice-agents --cloud --cloud-token YOUR_TOKEN
|
|
87
|
+
npx @robbiesrobotics/alice-agents --yes --tier pro --license-key YOUR_KEY --cloud --cloud-token YOUR_TOKEN
|
|
88
|
+
|
|
89
|
+
# Force the coding tool preference for this install
|
|
90
|
+
npx @robbiesrobotics/alice-agents --yes --coding-tool codex
|
|
87
91
|
|
|
88
92
|
# Show help
|
|
89
93
|
npx @robbiesrobotics/alice-agents --help
|
|
@@ -100,7 +104,7 @@ npx @robbiesrobotics/alice-agents --help
|
|
|
100
104
|
If you're a Pro user with the cloud add-on, the installer can configure your local runtime for Mission Control in the same pass.
|
|
101
105
|
|
|
102
106
|
- Interactive install: choose `Pro`, validate your license, then enable the Mission Control Cloud add-on when prompted
|
|
103
|
-
- Non-interactive install: pass `--cloud`
|
|
107
|
+
- Non-interactive install: pass `--tier pro --license-key YOUR_KEY --cloud`
|
|
104
108
|
- Optional flags:
|
|
105
109
|
- `--cloud-token <token>` — access or ingest token for authenticated telemetry
|
|
106
110
|
- `--cloud-dashboard-url <url>` — defaults to `https://alice.av3.ai`
|
|
@@ -127,6 +131,18 @@ npx @robbiesrobotics/alice-agents --uninstall
|
|
|
127
131
|
|
|
128
132
|
Removes A.L.I.C.E. agents from `openclaw.json` while preserving any non-ALICE agents. Creates a backup before making changes.
|
|
129
133
|
|
|
134
|
+
## Maintainer Release Guard
|
|
135
|
+
|
|
136
|
+
Maintainer publishes are now gated.
|
|
137
|
+
|
|
138
|
+
- `npm publish` runs `prepublishOnly`
|
|
139
|
+
- that hook runs tests, syntax checks, and `npm run release:check`
|
|
140
|
+
- publish is blocked unless the package version has a matching git tag on `HEAD`
|
|
141
|
+
- publish is blocked unless `landing/content/changelog.md` contains the matching version entry
|
|
142
|
+
- publish is blocked unless `releases/vX.Y.Z.md` exists and is marked `Status: approved`
|
|
143
|
+
|
|
144
|
+
Use `releases/TEMPLATE.md` as the starting point for each release brief.
|
|
145
|
+
|
|
130
146
|
## How It Works
|
|
131
147
|
|
|
132
148
|
1. **You talk to A.L.I.C.E.** — she's your single point of contact
|
package/bin/alice-install.mjs
CHANGED
|
@@ -37,15 +37,22 @@ if (flags.has('--help') || flags.has('-h')) {
|
|
|
37
37
|
npx @robbiesrobotics/alice-agents --help Show this help
|
|
38
38
|
|
|
39
39
|
Options:
|
|
40
|
-
--yes Skip prompts, use
|
|
40
|
+
--yes Skip prompts, use detected model when available (otherwise Sonnet)
|
|
41
41
|
--update Non-interactive upgrade (alias for --yes with upgrade mode)
|
|
42
42
|
--uninstall Remove A.L.I.C.E. agents (preserves non-ALICE agents)
|
|
43
43
|
--doctor Run diagnostics and check install health
|
|
44
44
|
--cloud Enable Mission Control Cloud setup during install
|
|
45
45
|
--no-cloud Skip Mission Control Cloud setup during install
|
|
46
|
+
--tier <starter|pro> Force the install tier
|
|
47
|
+
--license-key <key> Provide a Pro license key for automation
|
|
48
|
+
--coding-tool <auto|claude|codex> Override the preferred coding CLI
|
|
46
49
|
--cloud-token <token> Mission Control ingest/access token
|
|
47
50
|
--cloud-dashboard-url <url> Mission Control dashboard URL
|
|
48
51
|
--cloud-ingest-url <url> Mission Control ingest endpoint
|
|
52
|
+
--cloud-team-id <id> Mission Control team UUID for hosted linkage
|
|
53
|
+
--cloud-team-slug <slug> Mission Control team slug
|
|
54
|
+
--cloud-team-name <name> Mission Control team display name
|
|
55
|
+
--cloud-team-plan <plan> Mission Control team plan
|
|
49
56
|
--version Print package version
|
|
50
57
|
`);
|
|
51
58
|
process.exit(0);
|
|
@@ -61,9 +68,16 @@ if (flags.has('--doctor')) {
|
|
|
61
68
|
yes: true,
|
|
62
69
|
modeOverride: 'upgrade',
|
|
63
70
|
cloud: flags.has('--cloud') ? true : flags.has('--no-cloud') ? false : undefined,
|
|
71
|
+
tierOverride: getFlagValue('--tier'),
|
|
72
|
+
licenseKey: getFlagValue('--license-key'),
|
|
73
|
+
codingTool: getFlagValue('--coding-tool'),
|
|
64
74
|
cloudToken: getFlagValue('--cloud-token'),
|
|
65
75
|
cloudDashboardUrl: getFlagValue('--cloud-dashboard-url'),
|
|
66
76
|
cloudIngestUrl: getFlagValue('--cloud-ingest-url'),
|
|
77
|
+
cloudTeamId: getFlagValue('--cloud-team-id'),
|
|
78
|
+
cloudTeamSlug: getFlagValue('--cloud-team-slug'),
|
|
79
|
+
cloudTeamName: getFlagValue('--cloud-team-name'),
|
|
80
|
+
cloudTeamPlan: getFlagValue('--cloud-team-plan'),
|
|
67
81
|
}).catch((err) => {
|
|
68
82
|
console.error(' ❌ Update failed:', err.message);
|
|
69
83
|
process.exit(1);
|
|
@@ -82,9 +96,16 @@ if (flags.has('--doctor')) {
|
|
|
82
96
|
runInstall({
|
|
83
97
|
yes: flags.has('--yes'),
|
|
84
98
|
cloud: flags.has('--cloud') ? true : flags.has('--no-cloud') ? false : undefined,
|
|
99
|
+
tierOverride: getFlagValue('--tier'),
|
|
100
|
+
licenseKey: getFlagValue('--license-key'),
|
|
101
|
+
codingTool: getFlagValue('--coding-tool'),
|
|
85
102
|
cloudToken: getFlagValue('--cloud-token'),
|
|
86
103
|
cloudDashboardUrl: getFlagValue('--cloud-dashboard-url'),
|
|
87
104
|
cloudIngestUrl: getFlagValue('--cloud-ingest-url'),
|
|
105
|
+
cloudTeamId: getFlagValue('--cloud-team-id'),
|
|
106
|
+
cloudTeamSlug: getFlagValue('--cloud-team-slug'),
|
|
107
|
+
cloudTeamName: getFlagValue('--cloud-team-name'),
|
|
108
|
+
cloudTeamPlan: getFlagValue('--cloud-team-plan'),
|
|
88
109
|
}).catch((err) => {
|
|
89
110
|
console.error(' ❌ Install failed:', err.message);
|
|
90
111
|
process.exit(1);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const TEMPLATES_DIR = join(__dirname, '..', 'templates');
|
|
7
|
+
|
|
8
|
+
const STARTER_TEMPLATE = 'agents-starter.json';
|
|
9
|
+
const PRO_ADDONS_TEMPLATE = 'agents-pro.json';
|
|
10
|
+
|
|
11
|
+
export function normalizeTier(tier) {
|
|
12
|
+
return tier === 'pro' ? 'pro' : 'starter';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function loadRegistryFile(filename) {
|
|
16
|
+
const raw = readFileSync(join(TEMPLATES_DIR, filename), 'utf8');
|
|
17
|
+
return JSON.parse(raw);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function loadAgentRegistry(tier = 'starter') {
|
|
21
|
+
const normalizedTier = normalizeTier(tier);
|
|
22
|
+
const starterAgents = loadRegistryFile(STARTER_TEMPLATE);
|
|
23
|
+
if (normalizedTier === 'starter') {
|
|
24
|
+
return starterAgents;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const proAddons = loadRegistryFile(PRO_ADDONS_TEMPLATE);
|
|
28
|
+
return [...starterAgents, ...proAddons];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getAgentIdsForTier(tier = 'starter') {
|
|
32
|
+
return loadAgentRegistry(tier).map((agent) => agent.id);
|
|
33
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export const CODING_TOOL_OVERRIDES = new Set(['auto', 'claude', 'codex']);
|
|
4
|
+
|
|
5
|
+
const TOOL_CONFIG = {
|
|
6
|
+
claude: {
|
|
7
|
+
id: 'claude',
|
|
8
|
+
name: 'Claude Code',
|
|
9
|
+
cli: 'claude',
|
|
10
|
+
skillHeading: 'Claude Code',
|
|
11
|
+
primaryExample: `claude --permission-mode bypassPermissions --print 'YOUR TASK HERE. Run tests to verify.'`,
|
|
12
|
+
reviewExample: `claude --permission-mode bypassPermissions --print 'Review the current changes for bugs, regressions, and missing tests. Output a numbered list of findings.'`,
|
|
13
|
+
},
|
|
14
|
+
codex: {
|
|
15
|
+
id: 'codex',
|
|
16
|
+
name: 'Codex',
|
|
17
|
+
cli: 'codex',
|
|
18
|
+
skillHeading: 'Codex',
|
|
19
|
+
primaryExample: `codex exec --full-auto -C /path/to/project 'YOUR TASK HERE. Run tests to verify.'`,
|
|
20
|
+
reviewExample: `codex review --base main 'Review the current changes for bugs, regressions, and missing tests.'`,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function commandExists(cmd) {
|
|
25
|
+
const probe = process.platform === 'win32' ? 'where' : 'which';
|
|
26
|
+
try {
|
|
27
|
+
execSync(`${probe} ${cmd}`, { stdio: 'pipe' });
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function normalizeProviderId(provider) {
|
|
35
|
+
if (!provider) return null;
|
|
36
|
+
if (provider === 'openai-codex') return 'openai';
|
|
37
|
+
return provider;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getModelProvider(model) {
|
|
41
|
+
if (!model || typeof model !== 'string' || !model.includes('/')) return null;
|
|
42
|
+
return normalizeProviderId(model.split('/')[0]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getPreferredToolForProvider(provider) {
|
|
46
|
+
if (provider === 'anthropic') return 'claude';
|
|
47
|
+
if (provider === 'openai') return 'codex';
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function validateOverride(override) {
|
|
52
|
+
const normalized = String(override || 'auto').trim().toLowerCase() || 'auto';
|
|
53
|
+
if (!CODING_TOOL_OVERRIDES.has(normalized)) {
|
|
54
|
+
throw new Error(`Invalid --coding-tool value "${override}". Use auto, claude, or codex.`);
|
|
55
|
+
}
|
|
56
|
+
return normalized;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function resolveCodingAgentPreference({ detectedModels = null, override = 'auto' } = {}) {
|
|
60
|
+
const normalizedOverride = validateOverride(override);
|
|
61
|
+
const available = {
|
|
62
|
+
claude: commandExists(TOOL_CONFIG.claude.cli),
|
|
63
|
+
codex: commandExists(TOOL_CONFIG.codex.cli),
|
|
64
|
+
};
|
|
65
|
+
const provider =
|
|
66
|
+
getModelProvider(detectedModels?.primary) ||
|
|
67
|
+
getModelProvider(detectedModels?.orchestrator) ||
|
|
68
|
+
(detectedModels?.providers || [])
|
|
69
|
+
.map(normalizeProviderId)
|
|
70
|
+
.find((entry) => entry === 'anthropic' || entry === 'openai') ||
|
|
71
|
+
null;
|
|
72
|
+
|
|
73
|
+
let preferredTool = normalizedOverride === 'auto' ? getPreferredToolForProvider(provider) : normalizedOverride;
|
|
74
|
+
|
|
75
|
+
if (!preferredTool) {
|
|
76
|
+
if (available.codex) {
|
|
77
|
+
preferredTool = 'codex';
|
|
78
|
+
} else if (available.claude) {
|
|
79
|
+
preferredTool = 'claude';
|
|
80
|
+
} else {
|
|
81
|
+
preferredTool = 'codex';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const fallbackTool = preferredTool === 'codex' ? 'claude' : 'codex';
|
|
86
|
+
const selectionReason =
|
|
87
|
+
normalizedOverride !== 'auto'
|
|
88
|
+
? `manual override (--coding-tool ${preferredTool})`
|
|
89
|
+
: provider
|
|
90
|
+
? `detected ${provider} as the default OpenClaw provider`
|
|
91
|
+
: available.codex || available.claude
|
|
92
|
+
? 'no provider-specific default detected; using the first available coding CLI'
|
|
93
|
+
: 'no coding CLI detected yet; generated guidance includes both Codex and Claude Code';
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
override: normalizedOverride,
|
|
97
|
+
provider,
|
|
98
|
+
preferredTool,
|
|
99
|
+
fallbackTool,
|
|
100
|
+
preferred: TOOL_CONFIG[preferredTool],
|
|
101
|
+
fallback: TOOL_CONFIG[fallbackTool],
|
|
102
|
+
available,
|
|
103
|
+
selectionReason,
|
|
104
|
+
skillId: 'coding-agent',
|
|
105
|
+
skillPath: '~/.openclaw/skills/coding-agent/SKILL.md',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function buildCodingAgentSkillContent(preference) {
|
|
110
|
+
const preferredAvailability = preference.available[preference.preferred.id]
|
|
111
|
+
? `${preference.preferred.cli} is installed on this machine.`
|
|
112
|
+
: `${preference.preferred.cli} is not currently installed; check before using it.`;
|
|
113
|
+
const fallbackAvailability = preference.available[preference.fallback.id]
|
|
114
|
+
? `${preference.fallback.cli} is also available as a fallback.`
|
|
115
|
+
: `${preference.fallback.cli} is the fallback if you install it later.`;
|
|
116
|
+
const providerLabel = preference.provider || 'unknown';
|
|
117
|
+
|
|
118
|
+
return `---
|
|
119
|
+
name: coding-agent
|
|
120
|
+
description: 'Delegate substantial coding work to the preferred coding CLI for this install. Use when: multi-file implementation, refactors, build/test verification, deep codebase exploration, or structured code review. Prefer ${preference.preferred.name} first, then fall back to ${preference.fallback.name} if needed.'
|
|
121
|
+
metadata:
|
|
122
|
+
{
|
|
123
|
+
"openclaw": { "emoji": "⚙️", "requires": { "anyBins": ["${preference.preferred.cli}", "${preference.fallback.cli}"] } }
|
|
124
|
+
}
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
# Coding Agent Skill
|
|
128
|
+
|
|
129
|
+
This bundled skill routes coding work through the preferred CLI for this install.
|
|
130
|
+
|
|
131
|
+
- Preferred tool: **${preference.preferred.name}**
|
|
132
|
+
- Fallback tool: **${preference.fallback.name}**
|
|
133
|
+
- Detection basis: ${preference.selectionReason}
|
|
134
|
+
- Current provider signal: ${providerLabel}
|
|
135
|
+
|
|
136
|
+
## Availability
|
|
137
|
+
|
|
138
|
+
- ${preferredAvailability}
|
|
139
|
+
- ${fallbackAvailability}
|
|
140
|
+
|
|
141
|
+
## When to use this skill
|
|
142
|
+
|
|
143
|
+
Use this skill when the task requires:
|
|
144
|
+
- Modifying multiple files
|
|
145
|
+
- Running build, lint, or test commands to verify correctness
|
|
146
|
+
- Exploring an unfamiliar codebase before implementing
|
|
147
|
+
- A focused code review with concrete findings
|
|
148
|
+
|
|
149
|
+
## Preferred path: ${preference.preferred.skillHeading}
|
|
150
|
+
|
|
151
|
+
\`\`\`
|
|
152
|
+
${preference.preferred.primaryExample}
|
|
153
|
+
\`\`\`
|
|
154
|
+
|
|
155
|
+
Review-only example:
|
|
156
|
+
|
|
157
|
+
\`\`\`
|
|
158
|
+
${preference.preferred.reviewExample}
|
|
159
|
+
\`\`\`
|
|
160
|
+
|
|
161
|
+
## Fallback path: ${preference.fallback.skillHeading}
|
|
162
|
+
|
|
163
|
+
\`\`\`
|
|
164
|
+
${preference.fallback.primaryExample}
|
|
165
|
+
\`\`\`
|
|
166
|
+
|
|
167
|
+
Review-only example:
|
|
168
|
+
|
|
169
|
+
\`\`\`
|
|
170
|
+
${preference.fallback.reviewExample}
|
|
171
|
+
\`\`\`
|
|
172
|
+
|
|
173
|
+
## Rules
|
|
174
|
+
|
|
175
|
+
1. Use the preferred tool first unless it is unavailable or blocked.
|
|
176
|
+
2. Always point the tool at the project root before asking it to work.
|
|
177
|
+
3. Ask it to run the relevant verification commands before finishing.
|
|
178
|
+
4. Summarize what changed and any remaining risks when reporting back.
|
|
179
|
+
5. Do not use this skill for trivial one-line edits or pure planning.
|
|
180
|
+
|
|
181
|
+
## Quick check
|
|
182
|
+
|
|
183
|
+
\`\`\`
|
|
184
|
+
which ${preference.preferred.cli} || which ${preference.fallback.cli}
|
|
185
|
+
\`\`\`
|
|
186
|
+
`;
|
|
187
|
+
}
|
package/lib/config-merger.mjs
CHANGED
|
@@ -272,7 +272,11 @@ export function mergeConfig({ agents, mode, preset, customModels }) {
|
|
|
272
272
|
config.tools = config.tools || {};
|
|
273
273
|
config.tools.agentToAgent = config.tools.agentToAgent || {};
|
|
274
274
|
config.tools.agentToAgent.enabled = true;
|
|
275
|
-
config.tools.agentToAgent.allow
|
|
275
|
+
const mergedAllow = new Set(config.tools.agentToAgent.allow || []);
|
|
276
|
+
for (const id of aliceIds) {
|
|
277
|
+
mergedAllow.add(id);
|
|
278
|
+
}
|
|
279
|
+
config.tools.agentToAgent.allow = [...mergedAllow];
|
|
276
280
|
|
|
277
281
|
writeConfigAtomic(config);
|
|
278
282
|
return { backupPath, agentCount: aliceEntries.length, effectivePreset, warning };
|
package/lib/doctor.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { execSync } from 'node:child_process';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { icons, greenBold, green, red, yellow, cyan, dim, bold,
|
|
7
7
|
printSection, printSeparator, separator } from './colors.mjs';
|
|
8
|
+
import { getAgentIdsForTier } from './agent-registry.mjs';
|
|
8
9
|
|
|
9
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
const HOME = homedir();
|
|
@@ -20,10 +21,8 @@ function commandExists(cmd) {
|
|
|
20
21
|
}
|
|
21
22
|
const OPENCLAW_DIR = join(HOME, '.openclaw');
|
|
22
23
|
|
|
23
|
-
const STARTER_AGENTS =
|
|
24
|
-
|
|
25
|
-
'felix', 'daphne', 'rowan', 'darius', 'sophie',
|
|
26
|
-
];
|
|
24
|
+
const STARTER_AGENTS = getAgentIdsForTier('starter');
|
|
25
|
+
const ALL_ALICE_AGENTS = getAgentIdsForTier('pro');
|
|
27
26
|
|
|
28
27
|
function normalizeProviderId(provider) {
|
|
29
28
|
if (!provider) return null;
|
|
@@ -202,10 +201,23 @@ export async function runDoctor() {
|
|
|
202
201
|
}
|
|
203
202
|
|
|
204
203
|
// 3. A.L.I.C.E. agents in config
|
|
204
|
+
const manifest = (() => {
|
|
205
|
+
try {
|
|
206
|
+
const mPath = join(OPENCLAW_DIR, '.alice-manifest.json');
|
|
207
|
+
if (!existsSync(mPath)) return null;
|
|
208
|
+
return JSON.parse(readFileSync(mPath, 'utf8'));
|
|
209
|
+
} catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
})();
|
|
205
213
|
const configAgents = getConfigAgents(config);
|
|
206
214
|
const agentsInConfig = configAgents
|
|
207
|
-
.filter((a) => a &&
|
|
215
|
+
.filter((a) => a && ALL_ALICE_AGENTS.includes(a.id))
|
|
208
216
|
.map((a) => a.id);
|
|
217
|
+
const expectedTier = manifest?.tier === 'pro' || agentsInConfig.some((id) => !STARTER_AGENTS.includes(id))
|
|
218
|
+
? 'pro'
|
|
219
|
+
: 'starter';
|
|
220
|
+
const expectedAgents = expectedTier === 'pro' ? ALL_ALICE_AGENTS : STARTER_AGENTS;
|
|
209
221
|
|
|
210
222
|
const agentsOk = agentsInConfig.length > 0;
|
|
211
223
|
check(
|
|
@@ -217,17 +229,17 @@ export async function runDoctor() {
|
|
|
217
229
|
);
|
|
218
230
|
allOk = allOk && agentsOk;
|
|
219
231
|
|
|
220
|
-
// Check for missing agents from
|
|
221
|
-
if (agentsInConfig.length > 0 && agentsInConfig.length <
|
|
222
|
-
const missing =
|
|
232
|
+
// Check for missing agents from the expected tier roster
|
|
233
|
+
if (agentsInConfig.length > 0 && agentsInConfig.length < expectedAgents.length) {
|
|
234
|
+
const missing = expectedAgents.filter((id) => !agentsInConfig.includes(id));
|
|
223
235
|
check(
|
|
224
|
-
`All
|
|
236
|
+
`All ${expectedTier} agents present (missing: ${missing.join(', ')})`,
|
|
225
237
|
false,
|
|
226
238
|
'Run: npx @robbiesrobotics/alice-agents --update'
|
|
227
239
|
);
|
|
228
240
|
allOk = false;
|
|
229
|
-
} else if (agentsInConfig.length ===
|
|
230
|
-
check(
|
|
241
|
+
} else if (agentsInConfig.length === expectedAgents.length) {
|
|
242
|
+
check(`All ${expectedTier} agents present`, true);
|
|
231
243
|
}
|
|
232
244
|
|
|
233
245
|
// 4. Agent workspaces exist on disk
|
|
@@ -277,9 +289,11 @@ export async function runDoctor() {
|
|
|
277
289
|
);
|
|
278
290
|
// Docker permission issue is a warning, not a hard failure for A.L.I.C.E. itself
|
|
279
291
|
// but it will break OpenClaw's own Docker features
|
|
292
|
+
allOk = false;
|
|
280
293
|
console.log(` ${icons.info} ${dim('This will affect OpenClaw features that use Docker.')}\n`);
|
|
281
294
|
} else {
|
|
282
295
|
check('Docker daemon not running or not accessible', false, docker.hint);
|
|
296
|
+
allOk = false;
|
|
283
297
|
}
|
|
284
298
|
}
|
|
285
299
|
|
|
@@ -291,24 +305,28 @@ export async function runDoctor() {
|
|
|
291
305
|
|
|
292
306
|
// 7. License check
|
|
293
307
|
const { checkProLicense } = await import('./license.mjs');
|
|
294
|
-
const manifest = (() => {
|
|
295
|
-
try {
|
|
296
|
-
const mPath = join(OPENCLAW_DIR, '.alice-manifest.json');
|
|
297
|
-
if (!existsSync(mPath)) return null;
|
|
298
|
-
return JSON.parse(readFileSync(mPath, 'utf8'));
|
|
299
|
-
} catch {
|
|
300
|
-
return null;
|
|
301
|
-
}
|
|
302
|
-
})();
|
|
303
308
|
|
|
304
309
|
const licenseResult = await checkProLicense();
|
|
305
310
|
if (manifest?.tier === 'pro' || licenseResult.licensed) {
|
|
306
311
|
if (licenseResult.licensed) {
|
|
307
312
|
const maskedKey = licenseResult.key.slice(0, 13) + '****';
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
313
|
+
if (licenseResult.provisional) {
|
|
314
|
+
check(
|
|
315
|
+
`Pro license: ${maskedKey} (temporary grace until ${licenseResult.graceUntil})`,
|
|
316
|
+
false,
|
|
317
|
+
'Reconnect to the network and rerun the installer to complete validation'
|
|
318
|
+
);
|
|
319
|
+
allOk = false;
|
|
320
|
+
} else {
|
|
321
|
+
check(
|
|
322
|
+
`Pro license: ${maskedKey} (stored at ~/.alice/license)`,
|
|
323
|
+
true
|
|
324
|
+
);
|
|
325
|
+
if (licenseResult.needsRevalidation) {
|
|
326
|
+
console.log(` ${icons.info} ${dim('Validation service unavailable — using cached entitlement for now.')}`);
|
|
327
|
+
console.log('');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
312
330
|
} else {
|
|
313
331
|
check(
|
|
314
332
|
'Pro license: not found (running Starter tier)',
|
package/lib/installer.mjs
CHANGED
|
@@ -27,6 +27,8 @@ import { c, bold, dim, green, greenBold, red, yellow, cyan, gray,
|
|
|
27
27
|
icons, separator, printSection, printSeparator, printBox,
|
|
28
28
|
printStepDone, printStepFail, printStepSkip } from './colors.mjs';
|
|
29
29
|
import { runSkillsWizardStep } from './skills.mjs';
|
|
30
|
+
import { resolveCodingAgentPreference } from './coding-agent.mjs';
|
|
31
|
+
import { loadAgentRegistry } from './agent-registry.mjs';
|
|
30
32
|
|
|
31
33
|
function commandExists(cmd) {
|
|
32
34
|
const probe = process.platform === 'win32' ? 'where' : 'which';
|
|
@@ -105,7 +107,11 @@ function getOpenClawVersion() {
|
|
|
105
107
|
|
|
106
108
|
function getLatestNpmVersion() {
|
|
107
109
|
try {
|
|
108
|
-
const output = execSync('npm view openclaw version', {
|
|
110
|
+
const output = execSync('npm view openclaw version', {
|
|
111
|
+
stdio: 'pipe',
|
|
112
|
+
encoding: 'utf8',
|
|
113
|
+
timeout: 5000,
|
|
114
|
+
});
|
|
109
115
|
return output.trim();
|
|
110
116
|
} catch {
|
|
111
117
|
return null;
|
|
@@ -421,11 +427,6 @@ async function installRuntime(auto) {
|
|
|
421
427
|
|
|
422
428
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
423
429
|
|
|
424
|
-
function loadAgentRegistry() {
|
|
425
|
-
const raw = readFileSync(join(__dirname, '..', 'templates', 'agents-starter.json'), 'utf8');
|
|
426
|
-
return JSON.parse(raw);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
430
|
function printBanner() {
|
|
430
431
|
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url)));
|
|
431
432
|
const version = pkg.version || '';
|
|
@@ -446,6 +447,13 @@ function printSummary(mode, tier, agents, preset, userInfo, detectedModels) {
|
|
|
446
447
|
return printSummaryWithOptions(mode, tier, agents, preset, userInfo, detectedModels, null);
|
|
447
448
|
}
|
|
448
449
|
|
|
450
|
+
function normalizeTierOption(tier) {
|
|
451
|
+
if (!tier) return null;
|
|
452
|
+
const normalized = String(tier).trim().toLowerCase();
|
|
453
|
+
if (normalized === 'starter' || normalized === 'pro') return normalized;
|
|
454
|
+
throw new Error(`Invalid --tier value "${tier}". Use starter or pro.`);
|
|
455
|
+
}
|
|
456
|
+
|
|
449
457
|
function printSummaryWithOptions(mode, tier, agents, preset, userInfo, detectedModels, missionControl) {
|
|
450
458
|
const modelLabel =
|
|
451
459
|
preset === 'detected'
|
|
@@ -542,8 +550,6 @@ export async function runInstall(options = {}) {
|
|
|
542
550
|
console.log(` ${icons.info} ${dim('No model configured yet — you\'ll be prompted to choose one.')}\n`);
|
|
543
551
|
}
|
|
544
552
|
|
|
545
|
-
const allAgents = loadAgentRegistry();
|
|
546
|
-
|
|
547
553
|
// 2. Install mode
|
|
548
554
|
let mode;
|
|
549
555
|
if (options.modeOverride) {
|
|
@@ -596,7 +602,9 @@ export async function runInstall(options = {}) {
|
|
|
596
602
|
|
|
597
603
|
// 5. Tier selection
|
|
598
604
|
let tier;
|
|
599
|
-
if (
|
|
605
|
+
if (options.tierOverride) {
|
|
606
|
+
tier = normalizeTierOption(options.tierOverride);
|
|
607
|
+
} else if (auto) {
|
|
600
608
|
tier = 'starter';
|
|
601
609
|
} else {
|
|
602
610
|
tier = await promptTier();
|
|
@@ -604,16 +612,49 @@ export async function runInstall(options = {}) {
|
|
|
604
612
|
|
|
605
613
|
if (tier === 'pro') {
|
|
606
614
|
const { checkProLicense, validateLicenseRemote, storeLicense, isValidFormat } = await import('./license.mjs');
|
|
615
|
+
const explicitLicenseKey = String(options.licenseKey || '').trim();
|
|
607
616
|
|
|
608
|
-
const existing = await checkProLicense();
|
|
617
|
+
const existing = await checkProLicense({ revalidate: true });
|
|
609
618
|
|
|
610
619
|
if (existing.licensed) {
|
|
611
|
-
|
|
620
|
+
if (existing.provisional) {
|
|
621
|
+
printStepDone(`Pro license found (${existing.key.slice(0, 12)}...)`, `temporary grace until ${existing.graceUntil}`);
|
|
622
|
+
} else if (existing.needsRevalidation) {
|
|
623
|
+
printStepDone(`Pro license found (${existing.key.slice(0, 12)}...)`, 'using cached validation while the service is unavailable');
|
|
624
|
+
} else {
|
|
625
|
+
printStepDone(`Pro license found (${existing.key.slice(0, 12)}...)`);
|
|
626
|
+
}
|
|
627
|
+
} else if (auto && explicitLicenseKey) {
|
|
628
|
+
if (!isValidFormat(explicitLicenseKey)) {
|
|
629
|
+
throw new Error('Invalid --license-key format. Key must be ALICE-XXXX-XXXX-XXXX.');
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
console.log(' Validating provided Pro license key...');
|
|
633
|
+
const result = await validateLicenseRemote(explicitLicenseKey);
|
|
634
|
+
if (result.valid) {
|
|
635
|
+
storeLicense(explicitLicenseKey, {
|
|
636
|
+
status: 'validated',
|
|
637
|
+
plan: result.plan || 'pro',
|
|
638
|
+
transport: result.transport,
|
|
639
|
+
source: 'remote',
|
|
640
|
+
});
|
|
641
|
+
printStepDone('License verified! Welcome to A.L.I.C.E. Pro.');
|
|
642
|
+
} else if (result.graceEligible) {
|
|
643
|
+
const graceRecord = storeLicense(explicitLicenseKey, {
|
|
644
|
+
status: 'grace',
|
|
645
|
+
plan: 'pro',
|
|
646
|
+
transport: result.transport,
|
|
647
|
+
source: 'grace',
|
|
648
|
+
});
|
|
649
|
+
printStepDone('Key stored', `temporary grace until ${graceRecord.graceUntil}`);
|
|
650
|
+
} else {
|
|
651
|
+
throw new Error(`Pro license validation failed: ${result.message ?? 'Not recognized'}`);
|
|
652
|
+
}
|
|
612
653
|
} else if (auto) {
|
|
613
654
|
// --yes flag: skip interactive prompt, fallback to Starter if no stored license
|
|
614
655
|
console.log('');
|
|
615
656
|
console.log(` ${icons.info} ${dim('Pro tier requires a license key.')}`);
|
|
616
|
-
console.log(` ${dim('Run without --yes to enter your license key.')}`);
|
|
657
|
+
console.log(` ${dim('Run without --yes to enter your license key, or pass --license-key for automation.')}`);
|
|
617
658
|
console.log(` ${dim('Falling back to Starter tier.')}`);
|
|
618
659
|
console.log(` ${dim('Purchase a license at:')} ${cyan('https://getalice.av3.ai/pricing')}`);
|
|
619
660
|
tier = 'starter';
|
|
@@ -635,12 +676,22 @@ export async function runInstall(options = {}) {
|
|
|
635
676
|
const result = await validateLicenseRemote(key);
|
|
636
677
|
|
|
637
678
|
if (result.valid) {
|
|
638
|
-
storeLicense(key
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
}
|
|
679
|
+
storeLicense(key, {
|
|
680
|
+
status: 'validated',
|
|
681
|
+
plan: result.plan || 'pro',
|
|
682
|
+
transport: result.transport,
|
|
683
|
+
source: 'remote',
|
|
684
|
+
});
|
|
685
|
+
printStepDone('License verified! Welcome to A.L.I.C.E. Pro.');
|
|
686
|
+
break;
|
|
687
|
+
} else if (result.graceEligible) {
|
|
688
|
+
const graceRecord = storeLicense(key, {
|
|
689
|
+
status: 'grace',
|
|
690
|
+
plan: 'pro',
|
|
691
|
+
transport: result.transport,
|
|
692
|
+
source: 'grace',
|
|
693
|
+
});
|
|
694
|
+
printStepDone('Key stored', `temporary grace until ${graceRecord.graceUntil}`);
|
|
644
695
|
break;
|
|
645
696
|
} else {
|
|
646
697
|
printStepFail(`Invalid key: ${result.message ?? 'Not recognized'}`);
|
|
@@ -684,12 +735,21 @@ export async function runInstall(options = {}) {
|
|
|
684
735
|
dashboardUrl,
|
|
685
736
|
ingestUrl,
|
|
686
737
|
sourceNode,
|
|
738
|
+
teamId: String(options.cloudTeamId || existingMissionControl?.teamId || '').trim(),
|
|
739
|
+
teamSlug: String(options.cloudTeamSlug || existingMissionControl?.teamSlug || '').trim(),
|
|
740
|
+
teamName: String(options.cloudTeamName || existingMissionControl?.teamName || '').trim(),
|
|
741
|
+
teamPlan: String(options.cloudTeamPlan || existingMissionControl?.teamPlan || '').trim(),
|
|
687
742
|
hasIngestToken: !!ingestToken,
|
|
688
743
|
ingestToken,
|
|
689
744
|
};
|
|
690
745
|
}
|
|
691
746
|
}
|
|
692
747
|
|
|
748
|
+
const codingAgent = resolveCodingAgentPreference({
|
|
749
|
+
detectedModels,
|
|
750
|
+
override: options.codingTool,
|
|
751
|
+
});
|
|
752
|
+
const allAgents = loadAgentRegistry(tier);
|
|
693
753
|
const agents = allAgents;
|
|
694
754
|
|
|
695
755
|
// 6. Confirmation
|
|
@@ -733,7 +793,7 @@ export async function runInstall(options = {}) {
|
|
|
733
793
|
}
|
|
734
794
|
|
|
735
795
|
// Scaffold workspaces
|
|
736
|
-
const results = scaffoldAll(agents, userInfo);
|
|
796
|
+
const { workspaces: results, installedSkill } = scaffoldAll(agents, userInfo, codingAgent);
|
|
737
797
|
let newWorkspaces = 0;
|
|
738
798
|
let updatedWorkspaces = 0;
|
|
739
799
|
for (const r of results) {
|
|
@@ -744,6 +804,7 @@ export async function runInstall(options = {}) {
|
|
|
744
804
|
}
|
|
745
805
|
}
|
|
746
806
|
printStepDone('Workspaces', `${newWorkspaces} created, ${updatedWorkspaces} updated`);
|
|
807
|
+
printStepDone('Coding agent skill', `${installedSkill.preferred.name} preferred, ${installedSkill.fallback.name} fallback`);
|
|
747
808
|
|
|
748
809
|
// Skills installation step
|
|
749
810
|
const finalRuntimeForSkills = await detectRuntime();
|
|
@@ -762,6 +823,9 @@ export async function runInstall(options = {}) {
|
|
|
762
823
|
userName: userInfo.name,
|
|
763
824
|
userTimezone: userInfo.timezone,
|
|
764
825
|
modelPreset: effectivePreset,
|
|
826
|
+
skills: [...new Set([...(existing?.skills || []), installedSkill.skillId, ...(skillsInstalled || [])])],
|
|
827
|
+
codingTool: installedSkill.preferredTool,
|
|
828
|
+
codingSkill: installedSkill.skillId,
|
|
765
829
|
missionControl: missionControl
|
|
766
830
|
? {
|
|
767
831
|
enabled: true,
|