zouroboros-core 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,85 @@
1
+ # zouroboros-core
2
+
3
+ > Core types, configuration, and utilities for Zouroboros
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install zouroboros-core
9
+ # or
10
+ pnpm add zouroboros-core
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```typescript
16
+ import {
17
+ loadConfig,
18
+ saveConfig,
19
+ setConfigValue,
20
+ DEFAULT_CONFIG,
21
+ ZouroborosConfig
22
+ } from 'zouroboros-core';
23
+
24
+ // Load configuration
25
+ const config = loadConfig();
26
+
27
+ // Get a nested value
28
+ const dbPath = getConfigValue<string>(config, 'memory.dbPath');
29
+
30
+ // Update configuration
31
+ const updated = setConfigValue(config, 'memory.autoCapture', false);
32
+ saveConfig(updated);
33
+
34
+ // Initialize new configuration
35
+ const newConfig = await initConfig({
36
+ workspaceRoot: '/home/workspace',
37
+ dataDir: '/home/user/.zouroboros'
38
+ });
39
+ ```
40
+
41
+ ## API
42
+
43
+ ### Types
44
+
45
+ All core types are exported from this package:
46
+
47
+ - `ZouroborosConfig` - Main configuration interface
48
+ - `MemoryConfig`, `MemoryEntry`, `EpisodicMemory` - Memory system types
49
+ - `SwarmConfig`, `SwarmCampaign`, `SwarmTask` - Swarm orchestration types
50
+ - `Persona`, `SafetyRule` - Persona management types
51
+ - `SeedSpec`, `EvaluationReport` - Workflow types
52
+ - And more...
53
+
54
+ ### Configuration
55
+
56
+ - `loadConfig(path?)` - Load configuration from file
57
+ - `saveConfig(config, path?)` - Save configuration to file
58
+ - `initConfig(options?)` - Initialize new configuration
59
+ - `getConfigValue(config, path)` - Get nested config value
60
+ - `setConfigValue(config, path, value)` - Set nested config value
61
+ - `validateConfig(config)` - Validate configuration structure
62
+ - `mergeConfig(partial)` - Merge partial config with defaults
63
+
64
+ ### Constants
65
+
66
+ - `DEFAULT_CONFIG` - Default configuration object
67
+ - `DEFAULT_CONFIG_PATH` - Default configuration file path
68
+ - `ZOUROBOROS_VERSION` - Current version
69
+ - `DECAY_DAYS` - Memory decay periods
70
+ - `COMPLEXITY_THRESHOLDS` - Complexity scoring thresholds
71
+ - And more...
72
+
73
+ ### Utilities
74
+
75
+ - `generateUUID()` - Generate UUID v4
76
+ - `now()` - Get current ISO timestamp
77
+ - `retry(fn, options)` - Retry with exponential backoff
78
+ - `formatBytes(bytes)` - Format bytes to human readable
79
+ - `formatDuration(ms)` - Format milliseconds to duration
80
+ - `deepMerge(target, source)` - Deep merge objects
81
+ - And more...
82
+
83
+ ## License
84
+
85
+ MIT
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Backup and restore utilities for Zouroboros
3
+ *
4
+ * Handles creating timestamped backups of the memory database and config,
5
+ * and restoring from backup archives.
6
+ */
7
+ import type { ZouroborosConfig } from './types.js';
8
+ export interface BackupManifest {
9
+ version: string;
10
+ createdAt: string;
11
+ hostname: string;
12
+ files: BackupFile[];
13
+ }
14
+ export interface BackupFile {
15
+ name: string;
16
+ originalPath: string;
17
+ sizeBytes: number;
18
+ }
19
+ export interface BackupResult {
20
+ backupDir: string;
21
+ manifest: BackupManifest;
22
+ totalSizeBytes: number;
23
+ }
24
+ export interface RestoreResult {
25
+ restoredFiles: string[];
26
+ skippedFiles: string[];
27
+ manifest: BackupManifest;
28
+ }
29
+ /**
30
+ * Get the backup root directory
31
+ */
32
+ export declare function getBackupDir(config?: ZouroborosConfig): string;
33
+ /**
34
+ * Create a timestamped backup of the memory database and configuration.
35
+ */
36
+ export declare function createBackup(options?: {
37
+ config?: ZouroborosConfig;
38
+ configPath?: string;
39
+ label?: string;
40
+ }): BackupResult;
41
+ /**
42
+ * Restore from a backup directory.
43
+ */
44
+ export declare function restoreBackup(backupDir: string, options?: {
45
+ dryRun?: boolean;
46
+ skipConfig?: boolean;
47
+ }): RestoreResult;
48
+ /**
49
+ * List all available backups, sorted newest first.
50
+ */
51
+ export declare function listBackups(config?: ZouroborosConfig): {
52
+ name: string;
53
+ path: string;
54
+ createdAt: string;
55
+ sizeBytes: number;
56
+ fileCount: number;
57
+ }[];
58
+ /**
59
+ * Prune old backups, keeping only the most recent `keep` backups.
60
+ */
61
+ export declare function pruneBackups(config?: ZouroborosConfig, keep?: number): number;
62
+ /**
63
+ * Format bytes to human-readable string
64
+ */
65
+ export declare function formatBytes(bytes: number): string;
package/dist/backup.js ADDED
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Backup and restore utilities for Zouroboros
3
+ *
4
+ * Handles creating timestamped backups of the memory database and config,
5
+ * and restoring from backup archives.
6
+ */
7
+ import { existsSync, mkdirSync, readdirSync, statSync, unlinkSync, copyFileSync, readFileSync, writeFileSync } from 'fs';
8
+ import { join, dirname } from 'path';
9
+ import { DEFAULT_CONFIG_PATH, DEFAULT_DATA_DIR } from './constants.js';
10
+ import { loadConfig, validateConfig } from './config/loader.js';
11
+ const BACKUP_DIR_NAME = 'backups';
12
+ const MAX_BACKUPS = 10;
13
+ /**
14
+ * Get the backup root directory
15
+ */
16
+ export function getBackupDir(config) {
17
+ const dataDir = config?.core.dataDir ?? DEFAULT_DATA_DIR;
18
+ return join(dataDir, BACKUP_DIR_NAME);
19
+ }
20
+ /**
21
+ * Create a timestamped backup of the memory database and configuration.
22
+ */
23
+ export function createBackup(options = {}) {
24
+ const config = options.config ?? loadConfig(options.configPath);
25
+ const configPath = options.configPath ?? DEFAULT_CONFIG_PATH;
26
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 23);
27
+ const label = options.label ? `-${options.label}` : '';
28
+ const backupName = `backup-${timestamp}${label}`;
29
+ const backupRoot = getBackupDir(config);
30
+ const backupDir = join(backupRoot, backupName);
31
+ mkdirSync(backupDir, { recursive: true });
32
+ const files = [];
33
+ // Backup memory database
34
+ if (existsSync(config.memory.dbPath)) {
35
+ const dest = join(backupDir, 'memory.db');
36
+ copyFileSync(config.memory.dbPath, dest);
37
+ const stat = statSync(dest);
38
+ files.push({
39
+ name: 'memory.db',
40
+ originalPath: config.memory.dbPath,
41
+ sizeBytes: stat.size,
42
+ });
43
+ // Also back up WAL/SHM if they exist
44
+ for (const suffix of ['-wal', '-shm']) {
45
+ const walPath = config.memory.dbPath + suffix;
46
+ if (existsSync(walPath)) {
47
+ const walDest = join(backupDir, `memory.db${suffix}`);
48
+ copyFileSync(walPath, walDest);
49
+ const walStat = statSync(walDest);
50
+ files.push({
51
+ name: `memory.db${suffix}`,
52
+ originalPath: walPath,
53
+ sizeBytes: walStat.size,
54
+ });
55
+ }
56
+ }
57
+ }
58
+ // Backup config file
59
+ if (existsSync(configPath)) {
60
+ const dest = join(backupDir, 'config.json');
61
+ copyFileSync(configPath, dest);
62
+ const stat = statSync(dest);
63
+ files.push({
64
+ name: 'config.json',
65
+ originalPath: configPath,
66
+ sizeBytes: stat.size,
67
+ });
68
+ }
69
+ // Backup executor registry
70
+ if (existsSync(config.swarm.registryPath)) {
71
+ const dest = join(backupDir, 'executor-registry.json');
72
+ copyFileSync(config.swarm.registryPath, dest);
73
+ const stat = statSync(dest);
74
+ files.push({
75
+ name: 'executor-registry.json',
76
+ originalPath: config.swarm.registryPath,
77
+ sizeBytes: stat.size,
78
+ });
79
+ }
80
+ const manifest = {
81
+ version: config.version,
82
+ createdAt: new Date().toISOString(),
83
+ hostname: process.env.HOSTNAME ?? 'unknown',
84
+ files,
85
+ };
86
+ writeFileSync(join(backupDir, 'manifest.json'), JSON.stringify(manifest, null, 2), 'utf-8');
87
+ const totalSizeBytes = files.reduce((sum, f) => sum + f.sizeBytes, 0);
88
+ return { backupDir, manifest, totalSizeBytes };
89
+ }
90
+ /**
91
+ * Restore from a backup directory.
92
+ */
93
+ export function restoreBackup(backupDir, options = {}) {
94
+ const manifestPath = join(backupDir, 'manifest.json');
95
+ if (!existsSync(manifestPath)) {
96
+ throw new Error(`No manifest.json found in ${backupDir}. Not a valid backup.`);
97
+ }
98
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
99
+ const restoredFiles = [];
100
+ const skippedFiles = [];
101
+ for (const file of manifest.files) {
102
+ const sourcePath = join(backupDir, file.name);
103
+ if (!existsSync(sourcePath)) {
104
+ skippedFiles.push(`${file.name} (missing from backup)`);
105
+ continue;
106
+ }
107
+ if (options.skipConfig && file.name === 'config.json') {
108
+ skippedFiles.push(`${file.name} (--skip-config)`);
109
+ continue;
110
+ }
111
+ // Validate config before restoring it
112
+ if (file.name === 'config.json') {
113
+ try {
114
+ const content = readFileSync(sourcePath, 'utf-8');
115
+ const parsed = JSON.parse(content);
116
+ validateConfig(parsed);
117
+ }
118
+ catch (err) {
119
+ skippedFiles.push(`${file.name} (validation failed: ${err instanceof Error ? err.message : String(err)})`);
120
+ continue;
121
+ }
122
+ }
123
+ if (!options.dryRun) {
124
+ const destDir = dirname(file.originalPath);
125
+ if (!existsSync(destDir)) {
126
+ mkdirSync(destDir, { recursive: true });
127
+ }
128
+ copyFileSync(sourcePath, file.originalPath);
129
+ }
130
+ restoredFiles.push(file.originalPath);
131
+ }
132
+ return { restoredFiles, skippedFiles, manifest };
133
+ }
134
+ /**
135
+ * List all available backups, sorted newest first.
136
+ */
137
+ export function listBackups(config) {
138
+ const backupRoot = getBackupDir(config);
139
+ if (!existsSync(backupRoot))
140
+ return [];
141
+ const entries = readdirSync(backupRoot, { withFileTypes: true })
142
+ .filter((d) => d.isDirectory() && d.name.startsWith('backup-'));
143
+ return entries
144
+ .map((entry) => {
145
+ const dir = join(backupRoot, entry.name);
146
+ const manifestPath = join(dir, 'manifest.json');
147
+ if (!existsSync(manifestPath)) {
148
+ return null;
149
+ }
150
+ try {
151
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
152
+ const totalSize = manifest.files.reduce((s, f) => s + f.sizeBytes, 0);
153
+ return {
154
+ name: entry.name,
155
+ path: dir,
156
+ createdAt: manifest.createdAt,
157
+ sizeBytes: totalSize,
158
+ fileCount: manifest.files.length,
159
+ };
160
+ }
161
+ catch {
162
+ return null;
163
+ }
164
+ })
165
+ .filter((b) => b !== null)
166
+ .sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.name.localeCompare(a.name));
167
+ }
168
+ /**
169
+ * Prune old backups, keeping only the most recent `keep` backups.
170
+ */
171
+ export function pruneBackups(config, keep = MAX_BACKUPS) {
172
+ const backups = listBackups(config);
173
+ let pruned = 0;
174
+ if (backups.length <= keep)
175
+ return 0;
176
+ const toRemove = backups.slice(keep);
177
+ for (const backup of toRemove) {
178
+ const files = readdirSync(backup.path);
179
+ for (const file of files) {
180
+ unlinkSync(join(backup.path, file));
181
+ }
182
+ // Remove empty directory
183
+ try {
184
+ const { rmdirSync } = require('fs');
185
+ rmdirSync(backup.path);
186
+ }
187
+ catch {
188
+ // Directory may not be empty if nested; acceptable
189
+ }
190
+ pruned++;
191
+ }
192
+ return pruned;
193
+ }
194
+ /**
195
+ * Format bytes to human-readable string
196
+ */
197
+ export function formatBytes(bytes) {
198
+ if (bytes === 0)
199
+ return '0 B';
200
+ const units = ['B', 'KB', 'MB', 'GB'];
201
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
202
+ return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`;
203
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * ECC-002: Slash Commands Hub
3
+ *
4
+ * First-class CLI-style commands callable from any persona conversation.
5
+ * Zero-dependency command parser with unified help, registration, and routing.
6
+ * Supports subcommands: /memory search "query" → routes to memory.search handler.
7
+ */
8
+ import type { HookSystem } from './hooks.js';
9
+ export interface CommandDefinition {
10
+ name: string;
11
+ aliases: string[];
12
+ description: string;
13
+ usage: string;
14
+ category: string;
15
+ args: ArgDefinition[];
16
+ handler: CommandHandler;
17
+ subcommands?: Map<string, SubcommandDefinition>;
18
+ hidden?: boolean;
19
+ }
20
+ export interface SubcommandDefinition {
21
+ name: string;
22
+ description: string;
23
+ usage: string;
24
+ args: ArgDefinition[];
25
+ handler: CommandHandler;
26
+ }
27
+ export interface ArgDefinition {
28
+ name: string;
29
+ description: string;
30
+ required: boolean;
31
+ type: 'string' | 'number' | 'boolean' | 'flag';
32
+ default?: unknown;
33
+ }
34
+ export interface ParsedCommand {
35
+ name: string;
36
+ subcommand?: string;
37
+ args: Record<string, unknown>;
38
+ raw: string;
39
+ flags: Set<string>;
40
+ }
41
+ export interface CommandResult {
42
+ success: boolean;
43
+ output: string;
44
+ data?: unknown;
45
+ error?: string;
46
+ }
47
+ export type CommandHandler = (parsed: ParsedCommand) => CommandResult | Promise<CommandResult>;
48
+ export declare class CommandHub {
49
+ private commands;
50
+ private aliases;
51
+ private hooks;
52
+ /** Wire to hook system for command.execute events */
53
+ wireHooks(hooks: HookSystem): void;
54
+ register(definition: CommandDefinition): void;
55
+ /** Register a subcommand under an existing command */
56
+ registerSubcommand(commandName: string, sub: SubcommandDefinition): void;
57
+ unregister(name: string): boolean;
58
+ resolve(nameOrAlias: string): CommandDefinition | null;
59
+ parse(input: string): ParsedCommand | null;
60
+ execute(input: string): Promise<CommandResult>;
61
+ help(commandName?: string): string;
62
+ suggest(partial: string): string[];
63
+ list(category?: string): CommandDefinition[];
64
+ getCategories(): Record<string, number>;
65
+ private tokenize;
66
+ private coerceArg;
67
+ private levenshtein;
68
+ }
69
+ export declare function createCommandHub(): CommandHub;