zouroboros-cli 2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 marlandoj
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # zouroboros-cli
2
+
3
+ > Unified command-line interface for Zouroboros
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g zouroboros-cli
9
+ ```
10
+
11
+ Or use locally:
12
+
13
+ ```bash
14
+ npx zouroboros
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ # Initialize Zouroboros
21
+ zouroboros init
22
+
23
+ # Check health
24
+ zouroboros doctor
25
+
26
+ # Launch dashboard
27
+ zouroboros tui
28
+ ```
29
+
30
+ ## Commands
31
+
32
+ | Command | Description |
33
+ |---------|-------------|
34
+ | `init` | Initialize configuration |
35
+ | `doctor` | Health check |
36
+ | `config` | Manage configuration |
37
+ | `memory` | Memory system commands |
38
+ | `swarm` | Swarm orchestration |
39
+ | `persona` | Persona management |
40
+ | `workflow` | Interview, evaluate, autoloop |
41
+ | `heal` | Self-healing system |
42
+ | `tui` | Launch dashboard |
43
+
44
+ ## Examples
45
+
46
+ ```bash
47
+ # Initialize
48
+ zouroboros init
49
+
50
+ # Search memory
51
+ zouroboros memory search "project requirements"
52
+
53
+ # Run swarm campaign
54
+ zouroboros swarm run tasks.json --strategy reliable
55
+
56
+ # Create persona
57
+ zouroboros persona create "Health Coach" --domain healthcare --interactive
58
+
59
+ # Run introspection
60
+ zouroboros heal introspect --store
61
+
62
+ # Launch TUI dashboard
63
+ zouroboros tui
64
+ ```
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const agentsCommand: Command;
@@ -0,0 +1,44 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
4
+ import { join, dirname } from 'path';
5
+ import { homedir } from 'os';
6
+ const MARKER_PATH = join(homedir(), '.zouroboros', 'agents-registered.json');
7
+ export const agentsCommand = new Command('agents')
8
+ .description('Manage Zouroboros scheduled agents')
9
+ .addCommand(new Command('sync')
10
+ .description('Mark agents as registered after creating them in Zo Chat')
11
+ .action(() => {
12
+ const monorepoRoot = join(__dirname, '..', '..', '..');
13
+ const manifestPath = join(monorepoRoot, 'agents', 'manifest.json');
14
+ if (!existsSync(manifestPath)) {
15
+ console.error(chalk.red('agents/manifest.json not found'));
16
+ process.exit(1);
17
+ }
18
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
19
+ const agentEntries = Object.entries(manifest.agents || {});
20
+ const marker = {
21
+ registeredAt: new Date().toISOString(),
22
+ agents: agentEntries.map(([slug, spec]) => ({
23
+ slug,
24
+ title: spec.title,
25
+ })),
26
+ };
27
+ mkdirSync(dirname(MARKER_PATH), { recursive: true });
28
+ writeFileSync(MARKER_PATH, JSON.stringify(marker, null, 2));
29
+ console.log(chalk.green(`āœ“ Marked ${agentEntries.length} agents as registered`));
30
+ console.log(chalk.gray(` Written to ${MARKER_PATH}`));
31
+ }))
32
+ .addCommand(new Command('status')
33
+ .description('Show agent registration status')
34
+ .action(() => {
35
+ if (!existsSync(MARKER_PATH)) {
36
+ console.log(chalk.yellow('No agents registered. Run: zouroboros agents sync'));
37
+ return;
38
+ }
39
+ const marker = JSON.parse(readFileSync(MARKER_PATH, 'utf-8'));
40
+ console.log(chalk.green(`${marker.agents.length} agents registered at ${marker.registeredAt}`));
41
+ for (const agent of marker.agents) {
42
+ console.log(chalk.gray(` • ${agent.title}`));
43
+ }
44
+ }));
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const backupCommand: Command;
@@ -0,0 +1,122 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { loadConfig, createBackup, restoreBackup, listBackups, pruneBackups, formatBytes, } from 'zouroboros-core';
4
+ export const backupCommand = new Command('backup')
5
+ .description('Backup and restore Zouroboros data')
6
+ .addCommand(new Command('create')
7
+ .description('Create a new backup of memory DB, config, and registry')
8
+ .option('-l, --label <label>', 'Optional label for the backup')
9
+ .action((opts) => {
10
+ try {
11
+ const config = loadConfig();
12
+ const result = createBackup({ config, label: opts.label });
13
+ console.log(chalk.green('\nāœ… Backup created successfully\n'));
14
+ console.log(` Location: ${chalk.cyan(result.backupDir)}`);
15
+ console.log(` Files: ${result.manifest.files.length}`);
16
+ console.log(` Size: ${formatBytes(result.totalSizeBytes)}`);
17
+ console.log();
18
+ for (const file of result.manifest.files) {
19
+ console.log(` ${chalk.gray('•')} ${file.name} (${formatBytes(file.sizeBytes)})`);
20
+ }
21
+ // Auto-prune old backups
22
+ const pruned = pruneBackups(config);
23
+ if (pruned > 0) {
24
+ console.log(chalk.gray(`\n Pruned ${pruned} old backup(s)`));
25
+ }
26
+ console.log();
27
+ }
28
+ catch (err) {
29
+ console.error(chalk.red(`\nāŒ Backup failed: ${err instanceof Error ? err.message : String(err)}\n`));
30
+ process.exit(1);
31
+ }
32
+ }))
33
+ .addCommand(new Command('restore')
34
+ .description('Restore from a backup')
35
+ .argument('<name>', 'Backup name or path (use "backup list" to see available)')
36
+ .option('--dry-run', 'Show what would be restored without making changes')
37
+ .option('--skip-config', 'Skip restoring the config file')
38
+ .action((name, opts) => {
39
+ try {
40
+ const config = loadConfig();
41
+ const backups = listBackups(config);
42
+ // Resolve backup path: check if it's a name or full path
43
+ let backupDir = name;
44
+ const match = backups.find((b) => b.name === name);
45
+ if (match) {
46
+ backupDir = match.path;
47
+ }
48
+ if (opts.dryRun) {
49
+ console.log(chalk.yellow('\nšŸ” Dry run — no changes will be made\n'));
50
+ }
51
+ const result = restoreBackup(backupDir, {
52
+ dryRun: opts.dryRun,
53
+ skipConfig: opts.skipConfig,
54
+ });
55
+ const verb = opts.dryRun ? 'Would restore' : 'Restored';
56
+ console.log(chalk.green(`\nāœ… ${verb} from backup (${result.manifest.createdAt})\n`));
57
+ if (result.restoredFiles.length > 0) {
58
+ console.log(` ${chalk.cyan(verb)}:`);
59
+ for (const f of result.restoredFiles) {
60
+ console.log(` ${chalk.gray('•')} ${f}`);
61
+ }
62
+ }
63
+ if (result.skippedFiles.length > 0) {
64
+ console.log(`\n ${chalk.yellow('Skipped')}:`);
65
+ for (const f of result.skippedFiles) {
66
+ console.log(` ${chalk.gray('•')} ${f}`);
67
+ }
68
+ }
69
+ console.log();
70
+ }
71
+ catch (err) {
72
+ console.error(chalk.red(`\nāŒ Restore failed: ${err instanceof Error ? err.message : String(err)}\n`));
73
+ process.exit(1);
74
+ }
75
+ }))
76
+ .addCommand(new Command('list')
77
+ .description('List available backups')
78
+ .alias('ls')
79
+ .action(() => {
80
+ try {
81
+ const config = loadConfig();
82
+ const backups = listBackups(config);
83
+ if (backups.length === 0) {
84
+ console.log(chalk.yellow('\nNo backups found. Run "zouroboros backup create" to create one.\n'));
85
+ return;
86
+ }
87
+ console.log(chalk.cyan(`\nAvailable backups (${backups.length}):\n`));
88
+ for (const backup of backups) {
89
+ const date = new Date(backup.createdAt).toLocaleString();
90
+ console.log(` ${chalk.white(backup.name)} ${chalk.gray(date)} ${chalk.gray(formatBytes(backup.sizeBytes))} ${chalk.gray(`${backup.fileCount} files`)}`);
91
+ }
92
+ console.log();
93
+ }
94
+ catch (err) {
95
+ console.error(chalk.red(`\nāŒ ${err instanceof Error ? err.message : String(err)}\n`));
96
+ process.exit(1);
97
+ }
98
+ }))
99
+ .addCommand(new Command('prune')
100
+ .description('Remove old backups, keeping the most recent N')
101
+ .option('-k, --keep <count>', 'Number of backups to keep', '10')
102
+ .action((opts) => {
103
+ try {
104
+ const config = loadConfig();
105
+ const keep = parseInt(opts.keep, 10);
106
+ if (isNaN(keep) || keep < 1) {
107
+ console.error(chalk.red('--keep must be a positive number'));
108
+ process.exit(1);
109
+ }
110
+ const pruned = pruneBackups(config, keep);
111
+ if (pruned === 0) {
112
+ console.log(chalk.gray(`\nNo backups to prune (${listBackups(config).length} ≤ ${keep})\n`));
113
+ }
114
+ else {
115
+ console.log(chalk.green(`\nāœ… Pruned ${pruned} old backup(s), kept ${keep}\n`));
116
+ }
117
+ }
118
+ catch (err) {
119
+ console.error(chalk.red(`\nāŒ ${err instanceof Error ? err.message : String(err)}\n`));
120
+ process.exit(1);
121
+ }
122
+ }));
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const configCommand: Command;
@@ -0,0 +1,52 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { loadConfig, saveConfig } from 'zouroboros-core';
4
+ import { join } from 'path';
5
+ import { homedir } from 'os';
6
+ export const configCommand = new Command('config')
7
+ .description('Manage Zouroboros configuration')
8
+ .addCommand(new Command('get')
9
+ .description('Get a configuration value')
10
+ .argument('<key>', 'Configuration key (dot notation)')
11
+ .action((key) => {
12
+ const config = loadConfig();
13
+ const value = key.split('.').reduce((obj, k) => obj?.[k], config);
14
+ if (value === undefined) {
15
+ console.log(chalk.yellow(`Key '${key}' not found`));
16
+ process.exit(1);
17
+ }
18
+ console.log(typeof value === 'object' ? JSON.stringify(value, null, 2) : value);
19
+ }))
20
+ .addCommand(new Command('set')
21
+ .description('Set a configuration value')
22
+ .argument('<key>', 'Configuration key (dot notation)')
23
+ .argument('<value>', 'Value to set')
24
+ .action((key, value) => {
25
+ const configPath = join(homedir(), '.zouroboros', 'config.json');
26
+ const config = loadConfig(configPath);
27
+ const keys = key.split('.');
28
+ let target = config;
29
+ for (let i = 0; i < keys.length - 1; i++) {
30
+ if (!target[keys[i]])
31
+ target[keys[i]] = {};
32
+ target = target[keys[i]];
33
+ }
34
+ // Try to parse as JSON, fallback to string
35
+ try {
36
+ target[keys[keys.length - 1]] = JSON.parse(value);
37
+ }
38
+ catch {
39
+ target[keys[keys.length - 1]] = value;
40
+ }
41
+ saveConfig(config, configPath);
42
+ console.log(chalk.green(`āœ… Set ${key} = ${value}`));
43
+ }))
44
+ .addCommand(new Command('list')
45
+ .description('List all configuration values')
46
+ .alias('ls')
47
+ .action(() => {
48
+ const config = loadConfig();
49
+ console.log(chalk.cyan('\nZouroboros Configuration:\n'));
50
+ console.log(JSON.stringify(config, null, 2));
51
+ console.log();
52
+ }));
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const doctorCommand: Command;
@@ -0,0 +1,11 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { runDoctor } from '../utils/doctor.js';
4
+ export const doctorCommand = new Command('doctor')
5
+ .description('Run health check on all Zouroboros components')
6
+ .option('--fix', 'Attempt to fix issues automatically')
7
+ .action(async (options) => {
8
+ console.log(chalk.cyan('\nšŸ” Zouroboros Health Check\n'));
9
+ const healthy = await runDoctor({ fix: options.fix });
10
+ process.exit(healthy ? 0 : 1);
11
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const healCommand: Command;
@@ -0,0 +1,51 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ export const healCommand = new Command('heal')
4
+ .description('Self-healing system commands')
5
+ .addCommand(new Command('introspect')
6
+ .description('Run health introspection')
7
+ .option('--json', 'Output as JSON')
8
+ .option('--store', 'Store as memory episode')
9
+ .action(async (options) => {
10
+ const { introspect } = await import('zouroboros-selfheal');
11
+ const scorecard = await introspect({
12
+ json: options.json,
13
+ store: options.store,
14
+ });
15
+ if (options.json) {
16
+ console.log(JSON.stringify(scorecard, null, 2));
17
+ }
18
+ }))
19
+ .addCommand(new Command('prescribe')
20
+ .description('Generate improvement prescription')
21
+ .option('--live', 'Run introspect live')
22
+ .option('--target <metric>', 'Target specific metric')
23
+ .action(async (options) => {
24
+ console.log(chalk.cyan('\nšŸ”¬ Generating prescription...\n'));
25
+ const { prescribe } = await import('zouroboros-selfheal');
26
+ const prescription = await prescribe({
27
+ live: options.live,
28
+ target: options.target,
29
+ });
30
+ console.log(chalk.green('āœ… Prescription generated'));
31
+ console.log(chalk.gray(` Metric: ${prescription.metric.name}`));
32
+ console.log(chalk.gray(` Playbook: ${prescription.playbook.id}\n`));
33
+ }))
34
+ .addCommand(new Command('evolve')
35
+ .description('Execute prescribed improvement')
36
+ .requiredOption('--prescription <path>', 'Prescription file')
37
+ .action(async (options) => {
38
+ console.log(chalk.cyan('\n🧬 Executing evolution...\n'));
39
+ const { evolve } = await import('zouroboros-selfheal');
40
+ const result = await evolve({
41
+ prescription: options.prescription,
42
+ });
43
+ if (result.success) {
44
+ console.log(chalk.green('āœ… Evolution complete'));
45
+ console.log(chalk.gray(` Delta: ${result.delta}\n`));
46
+ }
47
+ else {
48
+ console.log(chalk.red('āŒ Evolution failed'));
49
+ console.log(chalk.gray(` Detail: ${result.detail}\n`));
50
+ }
51
+ }));
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const initCommand: Command;
@@ -0,0 +1,234 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { existsSync, mkdirSync } from 'fs';
4
+ import { execSync } from 'child_process';
5
+ import { join } from 'path';
6
+ import { homedir } from 'os';
7
+ import { saveConfig, DEFAULT_CONFIG } from 'zouroboros-core';
8
+ import { runDoctor } from '../utils/doctor.js';
9
+ export const initCommand = new Command('init')
10
+ .description('Initialize Zouroboros configuration')
11
+ .option('-f, --force', 'Overwrite existing configuration')
12
+ .option('--skip-doctor', 'Skip health check after initialization')
13
+ .option('--skip-ollama', 'Skip Ollama installation')
14
+ .action(async (options) => {
15
+ console.log(chalk.cyan('\nšŸā­• Initializing Zouroboros...\n'));
16
+ const configDir = join(homedir(), '.zouroboros');
17
+ const configPath = join(configDir, 'config.json');
18
+ // Check if already initialized
19
+ if (existsSync(configPath) && !options.force) {
20
+ console.log(chalk.yellow('āš ļø Zouroboros is already initialized.'));
21
+ console.log(chalk.gray(` Config: ${configPath}`));
22
+ console.log(chalk.gray('\n Use --force to reinitialize.\n'));
23
+ return;
24
+ }
25
+ // Create config directory
26
+ mkdirSync(configDir, { recursive: true });
27
+ // Create default configuration
28
+ const config = {
29
+ ...DEFAULT_CONFIG,
30
+ initializedAt: new Date().toISOString(),
31
+ };
32
+ saveConfig(config, configPath);
33
+ console.log(chalk.green('āœ… Configuration created'));
34
+ console.log(chalk.gray(` ${configPath}\n`));
35
+ // Create workspace directories
36
+ const workspaceDirs = [
37
+ join(configDir, 'logs'),
38
+ join(configDir, 'seeds'),
39
+ join(configDir, 'results'),
40
+ ];
41
+ for (const dir of workspaceDirs) {
42
+ mkdirSync(dir, { recursive: true });
43
+ }
44
+ console.log(chalk.green('āœ… Workspace directories created'));
45
+ console.log(chalk.gray(` ${configDir}/{logs,seeds,results}\n`));
46
+ // Initialize memory database
47
+ console.log(chalk.cyan('šŸ’¾ Initializing memory database...'));
48
+ try {
49
+ const dbPath = config.memory.dbPath;
50
+ const dbDir = join(dbPath, '..');
51
+ mkdirSync(dbDir, { recursive: true });
52
+ // Use sqlite3 CLI to create the schema (avoids needing bun:sqlite at install time)
53
+ const schemaSql = `
54
+ PRAGMA journal_mode = WAL;
55
+
56
+ CREATE TABLE IF NOT EXISTS facts (
57
+ id TEXT PRIMARY KEY,
58
+ persona TEXT,
59
+ entity TEXT NOT NULL,
60
+ key TEXT,
61
+ value TEXT NOT NULL,
62
+ text TEXT NOT NULL,
63
+ category TEXT DEFAULT 'fact' CHECK(category IN ('preference', 'fact', 'decision', 'convention', 'other', 'reference', 'project')),
64
+ decay_class TEXT DEFAULT 'medium' CHECK(decay_class IN ('permanent', 'long', 'medium', 'short')),
65
+ importance REAL DEFAULT 1.0,
66
+ source TEXT,
67
+ created_at INTEGER DEFAULT (strftime('%s', 'now')),
68
+ expires_at INTEGER,
69
+ last_accessed INTEGER DEFAULT (strftime('%s', 'now')),
70
+ confidence REAL DEFAULT 1.0,
71
+ metadata TEXT
72
+ );
73
+
74
+ CREATE TABLE IF NOT EXISTS fact_embeddings (
75
+ fact_id TEXT PRIMARY KEY REFERENCES facts(id) ON DELETE CASCADE,
76
+ embedding BLOB NOT NULL,
77
+ model TEXT DEFAULT 'nomic-embed-text',
78
+ created_at INTEGER DEFAULT (strftime('%s', 'now'))
79
+ );
80
+
81
+ CREATE TABLE IF NOT EXISTS episodes (
82
+ id TEXT PRIMARY KEY,
83
+ summary TEXT NOT NULL,
84
+ outcome TEXT NOT NULL CHECK(outcome IN ('success', 'failure', 'resolved', 'ongoing')),
85
+ happened_at INTEGER NOT NULL,
86
+ duration_ms INTEGER,
87
+ procedure_id TEXT,
88
+ metadata TEXT,
89
+ created_at INTEGER DEFAULT (strftime('%s', 'now'))
90
+ );
91
+
92
+ CREATE TABLE IF NOT EXISTS episode_entities (
93
+ episode_id TEXT NOT NULL REFERENCES episodes(id) ON DELETE CASCADE,
94
+ entity TEXT NOT NULL,
95
+ PRIMARY KEY (episode_id, entity)
96
+ );
97
+
98
+ CREATE TABLE IF NOT EXISTS procedures (
99
+ id TEXT PRIMARY KEY,
100
+ name TEXT NOT NULL,
101
+ version INTEGER DEFAULT 1,
102
+ steps TEXT NOT NULL,
103
+ success_count INTEGER DEFAULT 0,
104
+ failure_count INTEGER DEFAULT 0,
105
+ evolved_from TEXT,
106
+ created_at INTEGER DEFAULT (strftime('%s', 'now'))
107
+ );
108
+
109
+ CREATE TABLE IF NOT EXISTS open_loops (
110
+ id TEXT PRIMARY KEY,
111
+ summary TEXT NOT NULL,
112
+ entity TEXT NOT NULL,
113
+ status TEXT DEFAULT 'open' CHECK(status IN ('open', 'resolved')),
114
+ priority INTEGER DEFAULT 1,
115
+ created_at INTEGER DEFAULT (strftime('%s', 'now')),
116
+ resolved_at INTEGER
117
+ );
118
+
119
+ CREATE TABLE IF NOT EXISTS continuation_context (
120
+ id TEXT PRIMARY KEY,
121
+ conversation_id TEXT NOT NULL,
122
+ last_summary TEXT NOT NULL,
123
+ open_loop_ids TEXT,
124
+ entity_stack TEXT,
125
+ last_agent TEXT,
126
+ updated_at INTEGER DEFAULT (strftime('%s', 'now'))
127
+ );
128
+
129
+ CREATE TABLE IF NOT EXISTS cognitive_profiles (
130
+ entity TEXT PRIMARY KEY,
131
+ traits TEXT,
132
+ preferences TEXT,
133
+ interaction_count INTEGER DEFAULT 0,
134
+ last_interaction INTEGER,
135
+ created_at INTEGER DEFAULT (strftime('%s', 'now'))
136
+ );
137
+
138
+ CREATE TABLE IF NOT EXISTS _migrations (
139
+ id INTEGER PRIMARY KEY,
140
+ name TEXT NOT NULL,
141
+ applied_at INTEGER DEFAULT (strftime('%s', 'now'))
142
+ );
143
+
144
+ CREATE INDEX IF NOT EXISTS idx_facts_entity_key ON facts(entity, key);
145
+ CREATE INDEX IF NOT EXISTS idx_facts_decay ON facts(decay_class, expires_at);
146
+ CREATE INDEX IF NOT EXISTS idx_facts_category ON facts(category);
147
+ CREATE INDEX IF NOT EXISTS idx_episodes_happened ON episodes(happened_at);
148
+ CREATE INDEX IF NOT EXISTS idx_episodes_outcome ON episodes(outcome);
149
+ CREATE INDEX IF NOT EXISTS idx_episode_entities ON episode_entities(entity);
150
+ CREATE INDEX IF NOT EXISTS idx_open_loops_entity ON open_loops(entity, status);
151
+ `;
152
+ execSync(`sqlite3 "${dbPath}" <<'SCHEMA'\n${schemaSql}\nSCHEMA`, {
153
+ shell: '/bin/bash',
154
+ stdio: 'pipe',
155
+ });
156
+ console.log(chalk.green('āœ… Memory database initialized'));
157
+ console.log(chalk.gray(` ${dbPath}\n`));
158
+ }
159
+ catch (error) {
160
+ console.log(chalk.yellow('āš ļø Memory database initialization failed — will be created on first use'));
161
+ console.log(chalk.gray(` ${error instanceof Error ? error.message : String(error)}\n`));
162
+ }
163
+ // Install Ollama if not present
164
+ if (!options.skipOllama) {
165
+ console.log(chalk.cyan('šŸ¦™ Checking Ollama...'));
166
+ let ollamaAvailable = false;
167
+ try {
168
+ execSync('command -v ollama', { stdio: 'pipe' });
169
+ ollamaAvailable = true;
170
+ console.log(chalk.green('āœ… Ollama already installed'));
171
+ }
172
+ catch {
173
+ console.log(chalk.gray(' Ollama not found — installing...'));
174
+ try {
175
+ execSync('curl -fsSL https://ollama.com/install.sh | sh', {
176
+ stdio: 'inherit',
177
+ timeout: 120000,
178
+ });
179
+ ollamaAvailable = true;
180
+ console.log(chalk.green('āœ… Ollama installed'));
181
+ }
182
+ catch (error) {
183
+ console.log(chalk.yellow('āš ļø Ollama installation failed — vector search will be limited'));
184
+ console.log(chalk.gray(' Install manually: https://ollama.com/download\n'));
185
+ }
186
+ }
187
+ // Pull the embedding model if Ollama is available
188
+ if (ollamaAvailable) {
189
+ console.log(chalk.gray(' Pulling embedding model (nomic-embed-text)...'));
190
+ try {
191
+ // Ensure ollama is serving
192
+ try {
193
+ execSync('curl -sf http://localhost:11434/api/tags > /dev/null', { timeout: 3000 });
194
+ }
195
+ catch {
196
+ // Start ollama serve in background
197
+ execSync('nohup ollama serve > /dev/null 2>&1 &', { shell: '/bin/bash' });
198
+ // Wait briefly for it to start
199
+ execSync('sleep 3');
200
+ }
201
+ execSync('ollama pull nomic-embed-text', {
202
+ stdio: 'inherit',
203
+ timeout: 300000, // 5 min for model download
204
+ });
205
+ console.log(chalk.green('āœ… Embedding model ready\n'));
206
+ }
207
+ catch (error) {
208
+ console.log(chalk.yellow('āš ļø Could not pull embedding model — run manually: ollama pull nomic-embed-text\n'));
209
+ }
210
+ }
211
+ }
212
+ else {
213
+ console.log(chalk.gray('ā­ļø Skipping Ollama installation (--skip-ollama)\n'));
214
+ }
215
+ // Run doctor unless skipped
216
+ if (!options.skipDoctor) {
217
+ console.log(chalk.cyan('šŸ” Running health check...\n'));
218
+ const healthy = await runDoctor();
219
+ if (healthy) {
220
+ console.log(chalk.green('\nāœ… Zouroboros is ready to use!\n'));
221
+ console.log('Next steps:');
222
+ console.log(chalk.yellow(' zouroboros doctor') + chalk.gray(' - Check system health'));
223
+ console.log(chalk.yellow(' zouroboros --help') + chalk.gray(' - See all commands'));
224
+ console.log(chalk.yellow(' zouroboros tui') + chalk.gray(' - Launch dashboard\n'));
225
+ }
226
+ else {
227
+ console.log(chalk.yellow('\nāš ļø Some components need attention.\n'));
228
+ console.log('Run ' + chalk.yellow('zouroboros doctor') + ' for details.\n');
229
+ }
230
+ }
231
+ else {
232
+ console.log(chalk.green('\nāœ… Initialization complete!\n'));
233
+ }
234
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const memoryCommand: Command;
@@ -0,0 +1,32 @@
1
+ import { Command } from 'commander';
2
+ import { spawn } from 'child_process';
3
+ import { join } from 'path';
4
+ const MEMORY_SCRIPT = join(process.env.ZO_WORKSPACE || '/home/workspace', 'Skills/zo-memory-system/scripts/memory.ts');
5
+ export const memoryCommand = new Command('memory')
6
+ .description('Memory system commands')
7
+ .addCommand(new Command('search')
8
+ .description('Search memory')
9
+ .argument('<query>', 'Search query')
10
+ .option('--limit <n>', 'Limit results', '10')
11
+ .action((query, options) => {
12
+ spawn('bun', [MEMORY_SCRIPT, 'search', query, '--limit', options.limit], {
13
+ stdio: 'inherit'
14
+ });
15
+ }))
16
+ .addCommand(new Command('store')
17
+ .description('Store a fact')
18
+ .requiredOption('--entity <entity>', 'Entity name')
19
+ .requiredOption('--key <key>', 'Key')
20
+ .requiredOption('--value <value>', 'Value')
21
+ .action((options) => {
22
+ spawn('bun', [MEMORY_SCRIPT, 'store',
23
+ '--entity', options.entity,
24
+ '--key', options.key,
25
+ '--value', options.value
26
+ ], { stdio: 'inherit' });
27
+ }))
28
+ .addCommand(new Command('stats')
29
+ .description('Show memory statistics')
30
+ .action(() => {
31
+ spawn('bun', [MEMORY_SCRIPT, 'stats'], { stdio: 'inherit' });
32
+ }));
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const migrateCommand: Command;