@robbiesrobotics/alice-agents 1.0.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/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # ๐Ÿง  A.L.I.C.E. โ€” 28 AI Agents for OpenClaw
2
+
3
+ **Adaptive Learning & Intelligent Coordination Engine**
4
+
5
+ One conversation. One orchestrator. Twenty-eight specialists. A.L.I.C.E. turns OpenClaw into a full AI team โ€” talk to Olivia, and she routes your request to the right expert.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ npx @robbiesrobotics/alice-agents
11
+ ```
12
+
13
+ That's it. The installer detects your OpenClaw installation, asks a few questions, and sets up everything.
14
+
15
+ ## What You Get
16
+
17
+ An orchestrator (Olivia) backed by specialist agents across every domain:
18
+
19
+ | Agent | Domain | Emoji | Tier |
20
+ |-------|--------|-------|------|
21
+ | **Olivia** | Orchestration | ๐Ÿง  | Starter |
22
+ | **Dylan** | Development | ๐Ÿ’ป | Starter |
23
+ | **Selena** | Security | ๐Ÿ›ก๏ธ | Starter |
24
+ | **Devon** | DevOps | ๐Ÿš€ | Starter |
25
+ | **Quinn** | QA/Testing | โœ… | Starter |
26
+ | **Felix** | Frontend | ๐Ÿ–ฅ๏ธ | Starter |
27
+ | **Daphne** | Documentation | ๐Ÿ“ | Starter |
28
+ | **Rowan** | Research | ๐Ÿ” | Starter |
29
+ | **Darius** | Data | ๐Ÿ“Š | Starter |
30
+ | **Sophie** | Support | ๐Ÿ’ฌ | Starter |
31
+ | **Hannah** | HR | ๐Ÿ‘ฅ | Pro |
32
+ | **Aiden** | Analytics | ๐Ÿ“ˆ | Pro |
33
+ | **Clara** | Communication | โœ๏ธ | Pro |
34
+ | **Avery** | Automation | โš™๏ธ | Pro |
35
+ | **Owen** | Operations | ๐Ÿ”ง | Pro |
36
+ | **Isaac** | Integration | ๐Ÿ”Œ | Pro |
37
+ | **Tommy** | Travel | โœˆ๏ธ | Pro |
38
+ | **Sloane** | Sales | ๐Ÿ’ผ | Pro |
39
+ | **Nadia** | UI/UX Design | ๐ŸŽจ | Pro |
40
+ | **Morgan** | Marketing | ๐Ÿ“ฃ | Pro |
41
+ | **Alex** | API Crawling | ๐Ÿ•ท๏ธ | Pro |
42
+ | **Uma** | UX Research | ๐Ÿงช | Pro |
43
+ | **Caleb** | CRM | ๐Ÿ—‚๏ธ | Pro |
44
+ | **Elena** | Estimation | ๐Ÿ“‹ | Pro |
45
+ | **Audrey** | Accounting | ๐Ÿ’ฐ | Pro |
46
+ | **Logan** | Legal | โš–๏ธ | Pro |
47
+ | **Eva** | Executive Assistant | ๐Ÿ“Œ | Pro |
48
+ | **Parker** | Project Management | ๐Ÿ“… | Pro |
49
+
50
+ ## Model Presets
51
+
52
+ | Preset | Models | Best For |
53
+ |--------|--------|----------|
54
+ | **Sonnet** (default) | claude-sonnet-4-6 for all | Balanced speed + quality |
55
+ | **Opus + Sonnet** | Opus for orchestrator, Sonnet for specialists | Maximum orchestration quality |
56
+ | **OpenAI** | GPT-4.1 / GPT-4.1-mini | OpenAI users |
57
+ | **Local (Ollama)** | Local models | Privacy, offline use |
58
+ | **Custom** | Your choice | Full control |
59
+
60
+ ## Install Options
61
+
62
+ ```bash
63
+ # Interactive install
64
+ npx @robbiesrobotics/alice-agents
65
+
66
+ # Non-interactive with defaults (Sonnet, Starter tier)
67
+ npx @robbiesrobotics/alice-agents --yes
68
+
69
+ # Show help
70
+ npx @robbiesrobotics/alice-agents --help
71
+ ```
72
+
73
+ ### Install Modes
74
+
75
+ - **Fresh** โ€” Replaces the agents section in `openclaw.json` (recommended for first install)
76
+ - **Merge** โ€” Adds A.L.I.C.E. agents alongside your existing agents
77
+ - **Upgrade** โ€” Updates product files (SOUL.md, AGENTS.md, etc.) without touching user customizations
78
+
79
+ ## Upgrade
80
+
81
+ Re-run the installer and choose "Upgrade":
82
+
83
+ ```bash
84
+ npx @robbiesrobotics/alice-agents
85
+ # Select: Upgrade
86
+ ```
87
+
88
+ This updates agent templates and config while preserving your PLAYBOOK.md, LEARNINGS.md, FEEDBACK.md, and memory/ directories.
89
+
90
+ ## Uninstall
91
+
92
+ ```bash
93
+ npx @robbiesrobotics/alice-agents --uninstall
94
+ ```
95
+
96
+ Removes A.L.I.C.E. agents from `openclaw.json` while preserving any non-ALICE agents. Creates a backup before making changes.
97
+
98
+ ## How It Works
99
+
100
+ 1. **You talk to Olivia** โ€” she's your single point of contact
101
+ 2. **Olivia routes to specialists** โ€” "Build me an API" โ†’ Dylan (Development)
102
+ 3. **Specialists do the work** โ€” using their domain-specific tools and expertise
103
+ 4. **Olivia synthesizes** โ€” combines results and presents them to you
104
+
105
+ Each agent has its own workspace with:
106
+ - `SOUL.md` โ€” personality and values
107
+ - `AGENTS.md` โ€” operating instructions
108
+ - `PLAYBOOK.md` โ€” learned patterns (evolves over time)
109
+ - `LEARNINGS.md` โ€” task log
110
+ - `memory/` โ€” persistent context between sessions
111
+
112
+ ## Requirements
113
+
114
+ - [OpenClaw](https://openclaw.com) installed and configured
115
+ - Node.js 18+
116
+ - At least one AI provider configured (Anthropic, OpenAI, or Ollama)
117
+
118
+ ## License
119
+
120
+ MIT
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runInstall, runUninstall } from '../lib/installer.mjs';
4
+
5
+ const args = process.argv.slice(2);
6
+ const flags = new Set(args);
7
+
8
+ if (flags.has('--help') || flags.has('-h')) {
9
+ console.log(`
10
+ alice-agents โ€” A.L.I.C.E. Agent Framework Installer
11
+
12
+ Usage:
13
+ npx @robbiesrobotics/alice-agents Interactive install
14
+ npx @robbiesrobotics/alice-agents --yes Non-interactive install with defaults
15
+ npx @robbiesrobotics/alice-agents --uninstall Remove A.L.I.C.E. agents from config
16
+ npx @robbiesrobotics/alice-agents --help Show this help
17
+
18
+ Options:
19
+ --yes Skip prompts, use defaults (Sonnet, Starter tier)
20
+ --uninstall Remove A.L.I.C.E. agents (preserves non-ALICE agents)
21
+ `);
22
+ process.exit(0);
23
+ }
24
+
25
+ if (flags.has('--uninstall')) {
26
+ runUninstall({ yes: flags.has('--yes') }).catch((err) => {
27
+ console.error(' โŒ Uninstall failed:', err.message);
28
+ process.exit(1);
29
+ });
30
+ } else {
31
+ runInstall({ yes: flags.has('--yes') }).catch((err) => {
32
+ console.error(' โŒ Install failed:', err.message);
33
+ process.exit(1);
34
+ });
35
+ }
@@ -0,0 +1,185 @@
1
+ import { readFileSync, writeFileSync, renameSync, existsSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { randomBytes } from 'node:crypto';
5
+
6
+ const OPENCLAW_DIR = join(homedir(), '.openclaw');
7
+ const CONFIG_PATH = join(OPENCLAW_DIR, 'openclaw.json');
8
+
9
+ export function configExists() {
10
+ return existsSync(CONFIG_PATH);
11
+ }
12
+
13
+ export function readConfig() {
14
+ const raw = readFileSync(CONFIG_PATH, 'utf8');
15
+ return JSON.parse(raw);
16
+ }
17
+
18
+ function backupConfig() {
19
+ const ts = Date.now();
20
+ const backupPath = join(OPENCLAW_DIR, `openclaw.json.bak.alice-${ts}`);
21
+ const raw = readFileSync(CONFIG_PATH, 'utf8');
22
+ writeFileSync(backupPath, raw, 'utf8');
23
+ return backupPath;
24
+ }
25
+
26
+ function writeConfigAtomic(config) {
27
+ const tmpPath = join(OPENCLAW_DIR, `.openclaw.json.tmp.${randomBytes(4).toString('hex')}`);
28
+ writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
29
+ renameSync(tmpPath, CONFIG_PATH);
30
+ }
31
+
32
+ function getModelConfig(preset, customModels) {
33
+ switch (preset) {
34
+ case 'sonnet':
35
+ return {
36
+ primary: 'anthropic/claude-sonnet-4-6',
37
+ orchestrator: 'anthropic/claude-sonnet-4-6',
38
+ fallbacks: ['anthropic/claude-sonnet-4-6', 'anthropic/claude-haiku-4-5'],
39
+ models: {
40
+ 'anthropic/claude-sonnet-4-6': {},
41
+ 'anthropic/claude-haiku-4-5': {},
42
+ },
43
+ };
44
+ case 'opus-sonnet':
45
+ return {
46
+ primary: 'anthropic/claude-sonnet-4-6',
47
+ orchestrator: 'anthropic/claude-opus-4-6',
48
+ fallbacks: ['anthropic/claude-opus-4-6', 'anthropic/claude-sonnet-4-6', 'anthropic/claude-haiku-4-5'],
49
+ models: {
50
+ 'anthropic/claude-opus-4-6': {},
51
+ 'anthropic/claude-sonnet-4-6': {},
52
+ 'anthropic/claude-haiku-4-5': {},
53
+ },
54
+ };
55
+ case 'openai':
56
+ return {
57
+ primary: 'openai/gpt-4.1-mini',
58
+ orchestrator: 'openai/gpt-4.1',
59
+ fallbacks: ['openai/gpt-4.1', 'openai/gpt-4.1-mini'],
60
+ models: {
61
+ 'openai/gpt-4.1': {},
62
+ 'openai/gpt-4.1-mini': {},
63
+ },
64
+ };
65
+ case 'local':
66
+ return {
67
+ primary: 'ollama/qwen3.5:27b',
68
+ orchestrator: 'ollama/qwen3.5:27b',
69
+ fallbacks: [],
70
+ models: {},
71
+ };
72
+ case 'custom':
73
+ return {
74
+ primary: customModels?.primary || 'anthropic/claude-sonnet-4-6',
75
+ orchestrator: customModels?.orchestrator || customModels?.primary || 'anthropic/claude-sonnet-4-6',
76
+ fallbacks: [],
77
+ models: {},
78
+ };
79
+ default:
80
+ return getModelConfig('sonnet');
81
+ }
82
+ }
83
+
84
+ function buildAgentEntry(agent, modelCfg) {
85
+ const entry = {
86
+ id: agent.id,
87
+ name: agent.name,
88
+ workspace: join(OPENCLAW_DIR, `workspace-${agent.id}`),
89
+ identity: {
90
+ name: agent.name,
91
+ theme: agent.theme,
92
+ emoji: agent.emoji,
93
+ },
94
+ sandbox: agent.sandbox,
95
+ tools: agent.tools,
96
+ };
97
+
98
+ // Orchestrator gets special model + extra config
99
+ if (agent.isOrchestrator) {
100
+ entry.default = true;
101
+ entry.model = modelCfg.orchestrator;
102
+ if (agent.extraConfig) {
103
+ Object.assign(entry, agent.extraConfig);
104
+ }
105
+ }
106
+
107
+ return entry;
108
+ }
109
+
110
+ export function mergeConfig({ agents, mode, preset, customModels }) {
111
+ const backupPath = backupConfig();
112
+ const config = readConfig();
113
+ const modelCfg = getModelConfig(preset, customModels);
114
+
115
+ // Build agent entries
116
+ const aliceEntries = agents.map((a) => buildAgentEntry(a, modelCfg));
117
+ const aliceIds = new Set(agents.map((a) => a.id));
118
+
119
+ if (mode === 'fresh') {
120
+ // Replace agents entirely
121
+ config.agents = config.agents || {};
122
+ config.agents.list = aliceEntries;
123
+ } else if (mode === 'merge') {
124
+ // Preserve non-ALICE agents, add/replace ALICE ones
125
+ config.agents = config.agents || {};
126
+ const existing = (config.agents.list || []).filter((a) => !aliceIds.has(a.id));
127
+ config.agents.list = [...aliceEntries, ...existing];
128
+ } else if (mode === 'upgrade') {
129
+ // Only update existing ALICE agents, don't add new ones
130
+ config.agents = config.agents || {};
131
+ const list = config.agents.list || [];
132
+ config.agents.list = list.map((existing) => {
133
+ if (aliceIds.has(existing.id)) {
134
+ const updated = aliceEntries.find((a) => a.id === existing.id);
135
+ return { ...updated, workspace: existing.workspace };
136
+ }
137
+ return existing;
138
+ });
139
+ }
140
+
141
+ // Set defaults
142
+ config.agents.defaults = config.agents.defaults || {};
143
+ config.agents.defaults.model = {
144
+ primary: modelCfg.primary,
145
+ fallbacks: modelCfg.fallbacks,
146
+ };
147
+ if (Object.keys(modelCfg.models).length > 0) {
148
+ config.agents.defaults.models = modelCfg.models;
149
+ }
150
+ config.agents.defaults.workspace = join(OPENCLAW_DIR, 'workspace-olivia');
151
+ config.agents.defaults.compaction = config.agents.defaults.compaction || { mode: 'safeguard' };
152
+ config.agents.defaults.maxConcurrent = config.agents.defaults.maxConcurrent || 4;
153
+ config.agents.defaults.subagents = config.agents.defaults.subagents || {
154
+ maxConcurrent: 4,
155
+ archiveAfterMinutes: 120,
156
+ runTimeoutSeconds: 900,
157
+ };
158
+
159
+ // Agent-to-agent communication
160
+ config.tools = config.tools || {};
161
+ config.tools.agentToAgent = config.tools.agentToAgent || {};
162
+ config.tools.agentToAgent.enabled = true;
163
+ config.tools.agentToAgent.allow = [...aliceIds];
164
+
165
+ writeConfigAtomic(config);
166
+ return { backupPath, agentCount: aliceEntries.length };
167
+ }
168
+
169
+ export function removeAliceAgents(agentIds) {
170
+ const backupPath = backupConfig();
171
+ const config = readConfig();
172
+ const idsToRemove = new Set(agentIds);
173
+
174
+ if (config.agents?.list) {
175
+ config.agents.list = config.agents.list.filter((a) => !idsToRemove.has(a.id));
176
+ }
177
+
178
+ // Clean up agentToAgent allow list
179
+ if (config.tools?.agentToAgent?.allow) {
180
+ config.tools.agentToAgent.allow = config.tools.agentToAgent.allow.filter((id) => !idsToRemove.has(id));
181
+ }
182
+
183
+ writeConfigAtomic(config);
184
+ return { backupPath };
185
+ }
@@ -0,0 +1,233 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { configExists, mergeConfig, removeAliceAgents } from './config-merger.mjs';
5
+ import { scaffoldAll } from './workspace-scaffolder.mjs';
6
+ import { readManifest, writeManifest } from './manifest.mjs';
7
+ import {
8
+ promptInstallMode,
9
+ promptUserInfo,
10
+ promptModelPreset,
11
+ promptCustomModel,
12
+ promptTier,
13
+ confirm,
14
+ input,
15
+ closePrompt,
16
+ detectUserName,
17
+ detectTimezone,
18
+ } from './prompter.mjs';
19
+
20
+ const __dirname = dirname(fileURLToPath(import.meta.url));
21
+
22
+ function loadAgentRegistry() {
23
+ const raw = readFileSync(join(__dirname, '..', 'templates', 'agents.json'), 'utf8');
24
+ return JSON.parse(raw);
25
+ }
26
+
27
+ function filterByTier(agents, tier) {
28
+ if (tier === 'pro') return agents;
29
+ return agents.filter((a) => a.tier === 'starter');
30
+ }
31
+
32
+ function printBanner() {
33
+ console.log();
34
+ console.log(' โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—');
35
+ console.log(' โ•‘ โ•‘');
36
+ console.log(' โ•‘ ๐Ÿง  A.L.I.C.E. Agent Framework Installer โ•‘');
37
+ console.log(' โ•‘ โ•‘');
38
+ console.log(' โ•‘ Adaptive Learning & Intelligent Coordination โ•‘');
39
+ console.log(' โ•‘ Engine โ€” 28 specialists, one conversation. โ•‘');
40
+ console.log(' โ•‘ โ•‘');
41
+ console.log(' โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•');
42
+ console.log();
43
+ }
44
+
45
+ function printSummary(mode, tier, agents, preset, userInfo) {
46
+ console.log('\n โ”€โ”€ Install Summary โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€');
47
+ console.log(` Mode: ${mode}`);
48
+ console.log(` Tier: ${tier} (${agents.length} agents)`);
49
+ console.log(` Model: ${preset}`);
50
+ console.log(` User: ${userInfo.name}`);
51
+ console.log(` Timezone: ${userInfo.timezone}`);
52
+ console.log(' โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€');
53
+ console.log();
54
+ console.log(' Agents:');
55
+ for (const a of agents) {
56
+ console.log(` ${a.emoji} ${a.name.padEnd(10)} โ€” ${a.domain}`);
57
+ }
58
+ console.log();
59
+ }
60
+
61
+ export async function runInstall(options = {}) {
62
+ const auto = options.yes || false;
63
+
64
+ printBanner();
65
+
66
+ // 1. Detect OpenClaw
67
+ if (!configExists()) {
68
+ console.error(' โŒ OpenClaw not found. Install OpenClaw first:');
69
+ console.error(' https://openclaw.com/docs/install');
70
+ console.error();
71
+ process.exit(1);
72
+ }
73
+ console.log(' โœ“ OpenClaw detected\n');
74
+
75
+ const allAgents = loadAgentRegistry();
76
+
77
+ // 2. Install mode
78
+ let mode;
79
+ if (auto) {
80
+ const manifest = readManifest();
81
+ mode = manifest ? 'upgrade' : 'fresh';
82
+ } else {
83
+ mode = await promptInstallMode();
84
+ }
85
+
86
+ if (mode === 'upgrade') {
87
+ const manifest = readManifest();
88
+ if (!manifest) {
89
+ console.log(' โš  No previous install found. Switching to fresh install.\n');
90
+ mode = 'fresh';
91
+ }
92
+ }
93
+
94
+ if (mode === 'fresh' && !auto) {
95
+ const ok = await confirm(' โš  This will replace the agents section in openclaw.json. Continue?');
96
+ if (!ok) {
97
+ console.log(' Aborted.');
98
+ closePrompt();
99
+ return;
100
+ }
101
+ }
102
+
103
+ // 3. User info
104
+ let userInfo;
105
+ if (auto) {
106
+ userInfo = { name: detectUserName(), timezone: detectTimezone(), notes: '' };
107
+ } else {
108
+ console.log('\n โ”€โ”€ About You โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n');
109
+ userInfo = await promptUserInfo();
110
+ }
111
+
112
+ // 4. Model preset
113
+ let preset, customModels;
114
+ if (auto) {
115
+ preset = 'sonnet';
116
+ } else {
117
+ preset = await promptModelPreset();
118
+ if (preset === 'custom') {
119
+ customModels = await promptCustomModel();
120
+ }
121
+ }
122
+
123
+ // 5. Tier selection
124
+ let tier;
125
+ if (auto) {
126
+ tier = 'starter';
127
+ } else {
128
+ tier = await promptTier();
129
+ }
130
+
131
+ const agents = filterByTier(allAgents, tier);
132
+
133
+ // 6. Confirmation
134
+ printSummary(mode, tier, agents, preset, userInfo);
135
+
136
+ if (!auto) {
137
+ const ok = await confirm(' Proceed with installation?');
138
+ if (!ok) {
139
+ console.log(' Aborted.');
140
+ closePrompt();
141
+ return;
142
+ }
143
+ }
144
+
145
+ closePrompt();
146
+
147
+ // Execute
148
+ console.log('\n Installing...\n');
149
+
150
+ // Merge config
151
+ const { backupPath, agentCount } = mergeConfig({
152
+ agents,
153
+ mode,
154
+ preset,
155
+ customModels,
156
+ });
157
+ console.log(` โœ“ Config updated (backup: ${backupPath})`);
158
+
159
+ // Scaffold workspaces
160
+ const results = scaffoldAll(agents, userInfo);
161
+ let newWorkspaces = 0;
162
+ let updatedWorkspaces = 0;
163
+ for (const r of results) {
164
+ if (r.skipped.length === 0) {
165
+ newWorkspaces++;
166
+ } else {
167
+ updatedWorkspaces++;
168
+ }
169
+ }
170
+ console.log(` โœ“ Workspaces: ${newWorkspaces} created, ${updatedWorkspaces} updated`);
171
+
172
+ // Write manifest
173
+ const existing = readManifest();
174
+ writeManifest({
175
+ installedAt: existing?.installedAt,
176
+ tier,
177
+ agents: agents.map((a) => a.id),
178
+ userName: userInfo.name,
179
+ userTimezone: userInfo.timezone,
180
+ modelPreset: preset,
181
+ });
182
+ console.log(' โœ“ Manifest written');
183
+
184
+ console.log(`\n ๐ŸŽ‰ A.L.I.C.E. installed! ${agentCount} agents ready.`);
185
+ console.log(' Restart OpenClaw to activate: openclaw gateway restart\n');
186
+ }
187
+
188
+ export async function runUninstall(options = {}) {
189
+ const auto = options.yes || false;
190
+
191
+ console.log('\n ๐Ÿง  A.L.I.C.E. Uninstaller\n');
192
+
193
+ const manifest = readManifest();
194
+ if (!manifest) {
195
+ console.error(' โŒ No A.L.I.C.E. installation found (no manifest).');
196
+ process.exit(1);
197
+ }
198
+
199
+ console.log(` Found: ${manifest.agents.length} agents installed`);
200
+ console.log(` Tier: ${manifest.tier}`);
201
+ console.log();
202
+
203
+ if (!auto) {
204
+ const { createInterface } = await import('node:readline');
205
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
206
+ const answer = await new Promise((resolve) => {
207
+ rl.question(' Remove A.L.I.C.E. agents from config? [y/N] ', resolve);
208
+ });
209
+ rl.close();
210
+ if (!answer.toLowerCase().startsWith('y')) {
211
+ console.log(' Aborted.');
212
+ return;
213
+ }
214
+ }
215
+
216
+ const { backupPath } = removeAliceAgents(manifest.agents);
217
+ console.log(` โœ“ Agents removed from config (backup: ${backupPath})`);
218
+
219
+ if (!auto) {
220
+ const { createInterface } = await import('node:readline');
221
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
222
+ const answer = await new Promise((resolve) => {
223
+ rl.question(' Also remove workspace directories? [y/N] ', resolve);
224
+ });
225
+ rl.close();
226
+ if (answer.toLowerCase().startsWith('y')) {
227
+ console.log(' โš  Workspace removal not implemented (safety measure).');
228
+ console.log(' Remove manually: rm -rf ~/.openclaw/workspace-{agent-id}');
229
+ }
230
+ }
231
+
232
+ console.log('\n โœ“ A.L.I.C.E. uninstalled. Restart OpenClaw: openclaw gateway restart\n');
233
+ }
@@ -0,0 +1,33 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+
5
+ const MANIFEST_NAME = '.alice-manifest.json';
6
+
7
+ export function getManifestPath() {
8
+ return join(homedir(), '.openclaw', MANIFEST_NAME);
9
+ }
10
+
11
+ export function readManifest() {
12
+ try {
13
+ const raw = readFileSync(getManifestPath(), 'utf8');
14
+ return JSON.parse(raw);
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+
20
+ export function writeManifest(data) {
21
+ const manifest = {
22
+ version: '1.0.0',
23
+ installedAt: data.installedAt || new Date().toISOString(),
24
+ updatedAt: new Date().toISOString(),
25
+ tier: data.tier,
26
+ agents: data.agents,
27
+ userName: data.userName,
28
+ userTimezone: data.userTimezone,
29
+ modelPreset: data.modelPreset,
30
+ };
31
+ writeFileSync(getManifestPath(), JSON.stringify(manifest, null, 2) + '\n', 'utf8');
32
+ return manifest;
33
+ }