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 +21 -0
- package/README.md +64 -0
- package/dist/commands/agents.d.ts +2 -0
- package/dist/commands/agents.js +44 -0
- package/dist/commands/backup.d.ts +2 -0
- package/dist/commands/backup.js +122 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +52 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +11 -0
- package/dist/commands/heal.d.ts +2 -0
- package/dist/commands/heal.js +51 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +234 -0
- package/dist/commands/memory.d.ts +2 -0
- package/dist/commands/memory.js +32 -0
- package/dist/commands/migrate.d.ts +2 -0
- package/dist/commands/migrate.js +112 -0
- package/dist/commands/persona.d.ts +2 -0
- package/dist/commands/persona.js +45 -0
- package/dist/commands/skills.d.ts +2 -0
- package/dist/commands/skills.js +48 -0
- package/dist/commands/swarm.d.ts +2 -0
- package/dist/commands/swarm.js +32 -0
- package/dist/commands/tui.d.ts +2 -0
- package/dist/commands/tui.js +11 -0
- package/dist/commands/workflow.d.ts +3 -0
- package/dist/commands/workflow.js +87 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +83 -0
- package/dist/utils/doctor.d.ts +6 -0
- package/dist/utils/doctor.js +384 -0
- package/package.json +54 -0
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,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,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,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,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,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,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,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
|
+
}));
|