@cpretzinger/boss-claude 1.0.0 ā 1.0.2
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 +304 -1
- package/bin/boss-claude.js +1138 -0
- package/bin/commands/mode.js +250 -0
- package/bin/onyx-guard.js +259 -0
- package/bin/onyx-guard.sh +251 -0
- package/bin/prompts.js +284 -0
- package/bin/rollback.js +85 -0
- package/bin/setup-wizard.js +492 -0
- package/config/.env.example +17 -0
- package/lib/README.md +83 -0
- package/lib/agent-logger.js +61 -0
- package/lib/agents/memory-engineers/github-memory-engineer.js +251 -0
- package/lib/agents/memory-engineers/postgres-memory-engineer.js +633 -0
- package/lib/agents/memory-engineers/qdrant-memory-engineer.js +358 -0
- package/lib/agents/memory-engineers/redis-memory-engineer.js +383 -0
- package/lib/agents/memory-supervisor.js +526 -0
- package/lib/agents/registry.js +135 -0
- package/lib/auto-monitor.js +131 -0
- package/lib/checkpoint-hook.js +112 -0
- package/lib/checkpoint.js +319 -0
- package/lib/commentator.js +213 -0
- package/lib/context-scribe.js +120 -0
- package/lib/delegation-strategies.js +326 -0
- package/lib/hierarchy-validator.js +643 -0
- package/lib/index.js +15 -0
- package/lib/init-with-mode.js +261 -0
- package/lib/init.js +44 -6
- package/lib/memory-result-aggregator.js +252 -0
- package/lib/memory.js +35 -7
- package/lib/mode-enforcer.js +473 -0
- package/lib/onyx-banner.js +169 -0
- package/lib/onyx-identity.js +214 -0
- package/lib/onyx-monitor.js +381 -0
- package/lib/onyx-reminder.js +188 -0
- package/lib/onyx-tool-interceptor.js +341 -0
- package/lib/onyx-wrapper.js +315 -0
- package/lib/orchestrator-gate.js +334 -0
- package/lib/output-formatter.js +296 -0
- package/lib/postgres.js +1 -1
- package/lib/prompt-injector.js +220 -0
- package/lib/prompts.js +532 -0
- package/lib/session.js +153 -6
- package/lib/setup/README.md +187 -0
- package/lib/setup/env-manager.js +785 -0
- package/lib/setup/error-recovery.js +630 -0
- package/lib/setup/explain-scopes.js +385 -0
- package/lib/setup/github-instructions.js +333 -0
- package/lib/setup/github-repo.js +254 -0
- package/lib/setup/import-credentials.js +498 -0
- package/lib/setup/index.js +62 -0
- package/lib/setup/init-postgres.js +785 -0
- package/lib/setup/init-redis.js +456 -0
- package/lib/setup/integration-test.js +652 -0
- package/lib/setup/progress.js +357 -0
- package/lib/setup/rollback.js +670 -0
- package/lib/setup/rollback.test.js +452 -0
- package/lib/setup/setup-with-rollback.example.js +351 -0
- package/lib/setup/summary.js +400 -0
- package/lib/setup/test-github-setup.js +10 -0
- package/lib/setup/test-postgres-init.js +98 -0
- package/lib/setup/verify-setup.js +102 -0
- package/lib/task-agent-worker.js +235 -0
- package/lib/token-monitor.js +466 -0
- package/lib/tool-wrapper-integration.js +369 -0
- package/lib/tool-wrapper.js +387 -0
- package/lib/validators/README.md +497 -0
- package/lib/validators/config.js +583 -0
- package/lib/validators/config.test.js +175 -0
- package/lib/validators/github.js +310 -0
- package/lib/validators/github.test.js +61 -0
- package/lib/validators/index.js +15 -0
- package/lib/validators/postgres.js +525 -0
- package/package.json +98 -13
- package/scripts/benchmark-memory.js +433 -0
- package/scripts/check-secrets.sh +12 -0
- package/scripts/fetch-todos.mjs +148 -0
- package/scripts/graceful-shutdown.sh +156 -0
- package/scripts/install-onyx-hooks.js +373 -0
- package/scripts/install.js +119 -18
- package/scripts/redis-monitor.js +284 -0
- package/scripts/redis-setup.js +412 -0
- package/scripts/test-memory-retrieval.js +201 -0
- package/scripts/validate-exports.js +68 -0
- package/scripts/validate-package.js +120 -0
- package/scripts/verify-onyx-deployment.js +309 -0
- package/scripts/verify-redis-deployment.js +354 -0
- package/scripts/verify-redis-init.js +219 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Redis Health Monitor for Boss Claude
|
|
4
|
+
*
|
|
5
|
+
* Monitors Redis connection health, key statistics, and memory usage.
|
|
6
|
+
* Useful for debugging session issues and tracking long-term stability.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import Redis from 'ioredis';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import dotenv from 'dotenv';
|
|
12
|
+
import { existsSync } from 'fs';
|
|
13
|
+
import { join } from 'path';
|
|
14
|
+
import os from 'os';
|
|
15
|
+
|
|
16
|
+
// Load environment
|
|
17
|
+
const envPath = join(os.homedir(), '.boss-claude', '.env');
|
|
18
|
+
if (existsSync(envPath)) {
|
|
19
|
+
dotenv.config({ path: envPath });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!process.env.REDIS_URL) {
|
|
23
|
+
console.log(chalk.red('Error: REDIS_URL not found in ~/.boss-claude/.env'));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const redis = new Redis(process.env.REDIS_URL);
|
|
28
|
+
|
|
29
|
+
async function getKeyStats() {
|
|
30
|
+
const stats = {
|
|
31
|
+
total: 0,
|
|
32
|
+
identity: 0,
|
|
33
|
+
sessions: 0,
|
|
34
|
+
repos: 0,
|
|
35
|
+
history: 0,
|
|
36
|
+
achievements: 0,
|
|
37
|
+
cache: 0
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const allKeys = await redis.keys('boss:*');
|
|
41
|
+
stats.total = allKeys.length;
|
|
42
|
+
|
|
43
|
+
for (const key of allKeys) {
|
|
44
|
+
if (key === 'boss:identity') stats.identity++;
|
|
45
|
+
else if (key.startsWith('boss:session:')) stats.sessions++;
|
|
46
|
+
else if (key.startsWith('boss:repo:')) stats.repos++;
|
|
47
|
+
else if (key === 'boss:sessions:history') stats.history++;
|
|
48
|
+
else if (key.startsWith('boss:achievements:')) stats.achievements++;
|
|
49
|
+
else if (key.startsWith('boss:cache:')) stats.cache++;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return stats;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function getIdentityInfo() {
|
|
56
|
+
const data = await redis.get('boss:identity');
|
|
57
|
+
if (!data) return null;
|
|
58
|
+
|
|
59
|
+
const identity = JSON.parse(data);
|
|
60
|
+
const xpForNext = identity.level * 100;
|
|
61
|
+
const progress = (identity.xp / xpForNext) * 100;
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
...identity,
|
|
65
|
+
xp_for_next_level: xpForNext,
|
|
66
|
+
level_progress: Math.round(progress)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function getActiveSessions() {
|
|
71
|
+
const sessionKeys = await redis.keys('boss:session:*:current');
|
|
72
|
+
const sessions = [];
|
|
73
|
+
|
|
74
|
+
for (const key of sessionKeys) {
|
|
75
|
+
const data = await redis.get(key);
|
|
76
|
+
const session = JSON.parse(data);
|
|
77
|
+
const repoName = key.replace('boss:session:', '').replace(':current', '');
|
|
78
|
+
|
|
79
|
+
sessions.push({
|
|
80
|
+
repo: repoName,
|
|
81
|
+
started_at: session.started_at,
|
|
82
|
+
duration: Math.round((Date.now() - new Date(session.started_at).getTime()) / 1000 / 60),
|
|
83
|
+
tokens: session.tokens_used || 0
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return sessions;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function getRepoStats() {
|
|
91
|
+
const repoKeys = await redis.keys('boss:repo:*');
|
|
92
|
+
const repos = [];
|
|
93
|
+
|
|
94
|
+
for (const key of repoKeys) {
|
|
95
|
+
const data = await redis.get(key);
|
|
96
|
+
const repo = JSON.parse(data);
|
|
97
|
+
repos.push(repo);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Sort by last_active descending
|
|
101
|
+
repos.sort((a, b) => new Date(b.last_active) - new Date(a.last_active));
|
|
102
|
+
|
|
103
|
+
return repos.slice(0, 5); // Top 5 most recent
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function getSessionHistory() {
|
|
107
|
+
const count = await redis.zcard('boss:sessions:history');
|
|
108
|
+
const recent = await redis.zrevrange('boss:sessions:history', 0, 4, 'WITHSCORES');
|
|
109
|
+
|
|
110
|
+
const sessions = [];
|
|
111
|
+
for (let i = 0; i < recent.length; i += 2) {
|
|
112
|
+
const issueId = recent[i];
|
|
113
|
+
const timestamp = parseInt(recent[i + 1]);
|
|
114
|
+
sessions.push({
|
|
115
|
+
issue: issueId,
|
|
116
|
+
timestamp: new Date(timestamp).toISOString(),
|
|
117
|
+
relative: getRelativeTime(timestamp)
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { total: count, recent: sessions };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function getCacheStats() {
|
|
125
|
+
const cacheKeys = await redis.keys('boss:cache:*');
|
|
126
|
+
const stats = { total: cacheKeys.length, entries: [] };
|
|
127
|
+
|
|
128
|
+
for (const key of cacheKeys) {
|
|
129
|
+
const ttl = await redis.ttl(key);
|
|
130
|
+
const data = await redis.get(key);
|
|
131
|
+
|
|
132
|
+
if (data) {
|
|
133
|
+
const cached = JSON.parse(data);
|
|
134
|
+
stats.entries.push({
|
|
135
|
+
query: cached.query || 'unknown',
|
|
136
|
+
ttl: ttl,
|
|
137
|
+
cached_at: cached.cached_at
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return stats;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function testPerformance() {
|
|
146
|
+
const iterations = 100;
|
|
147
|
+
const testKey = 'boss:monitor:test';
|
|
148
|
+
|
|
149
|
+
// Single operation latency
|
|
150
|
+
const singleStart = Date.now();
|
|
151
|
+
await redis.set(testKey, 'test');
|
|
152
|
+
await redis.get(testKey);
|
|
153
|
+
const singleLatency = Date.now() - singleStart;
|
|
154
|
+
|
|
155
|
+
// Pipeline latency
|
|
156
|
+
const pipelineStart = Date.now();
|
|
157
|
+
const pipeline = redis.pipeline();
|
|
158
|
+
for (let i = 0; i < iterations; i++) {
|
|
159
|
+
pipeline.get(testKey);
|
|
160
|
+
}
|
|
161
|
+
await pipeline.exec();
|
|
162
|
+
const pipelineLatency = Date.now() - pipelineStart;
|
|
163
|
+
|
|
164
|
+
await redis.del(testKey);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
single_roundtrip_ms: singleLatency,
|
|
168
|
+
pipeline_100ops_ms: pipelineLatency,
|
|
169
|
+
pipeline_ops_per_sec: Math.round((iterations / pipelineLatency) * 1000)
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function getRelativeTime(timestamp) {
|
|
174
|
+
const seconds = Math.floor((Date.now() - timestamp) / 1000);
|
|
175
|
+
|
|
176
|
+
if (seconds < 60) return `${seconds}s ago`;
|
|
177
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
|
|
178
|
+
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
|
|
179
|
+
return `${Math.floor(seconds / 86400)}d ago`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function main() {
|
|
183
|
+
console.log(chalk.bold.cyan('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
184
|
+
console.log(chalk.bold.cyan('ā BOSS CLAUDE - REDIS MONITOR ā'));
|
|
185
|
+
console.log(chalk.bold.cyan('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
// Test connection
|
|
189
|
+
await redis.ping();
|
|
190
|
+
console.log(chalk.green('ā Connected to Redis\n'));
|
|
191
|
+
|
|
192
|
+
// Get key statistics
|
|
193
|
+
console.log(chalk.cyan('š Key Statistics'));
|
|
194
|
+
const keyStats = await getKeyStats();
|
|
195
|
+
console.log(` Total keys: ${chalk.yellow(keyStats.total)}`);
|
|
196
|
+
console.log(` Identity: ${keyStats.identity ? chalk.green('ā') : chalk.red('ā')}`);
|
|
197
|
+
console.log(` Active sessions: ${chalk.yellow(keyStats.sessions)}`);
|
|
198
|
+
console.log(` Repos managed: ${chalk.yellow(keyStats.repos)}`);
|
|
199
|
+
console.log(` Session history: ${keyStats.history ? chalk.green('ā') : chalk.red('ā')}`);
|
|
200
|
+
console.log(` Achievements: ${chalk.yellow(keyStats.achievements)}`);
|
|
201
|
+
console.log(` Cache entries: ${chalk.yellow(keyStats.cache)}\n`);
|
|
202
|
+
|
|
203
|
+
// Identity info
|
|
204
|
+
console.log(chalk.cyan('š¤ Boss Claude Identity'));
|
|
205
|
+
const identity = await getIdentityInfo();
|
|
206
|
+
if (identity) {
|
|
207
|
+
console.log(` Level: ${chalk.yellow(identity.level)} (${identity.level_progress}% to next)`);
|
|
208
|
+
console.log(` XP: ${chalk.yellow(identity.xp)}/${identity.xp_for_next_level}`);
|
|
209
|
+
console.log(` Token Bank: ${chalk.yellow(identity.token_bank.toLocaleString())}`);
|
|
210
|
+
console.log(` Total Sessions: ${chalk.yellow(identity.total_sessions)}`);
|
|
211
|
+
console.log(` Repos Managed: ${chalk.yellow(identity.repos_managed)}`);
|
|
212
|
+
console.log(` Created: ${chalk.gray(new Date(identity.created_at).toLocaleString())}\n`);
|
|
213
|
+
} else {
|
|
214
|
+
console.log(chalk.red(' Not initialized\n'));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Active sessions
|
|
218
|
+
const activeSessions = await getActiveSessions();
|
|
219
|
+
if (activeSessions.length > 0) {
|
|
220
|
+
console.log(chalk.cyan('ā” Active Sessions'));
|
|
221
|
+
activeSessions.forEach(session => {
|
|
222
|
+
console.log(` ${chalk.yellow(session.repo)}`);
|
|
223
|
+
console.log(` Duration: ${session.duration}m`);
|
|
224
|
+
console.log(` Tokens: ${session.tokens.toLocaleString()}`);
|
|
225
|
+
});
|
|
226
|
+
console.log('');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Recent repos
|
|
230
|
+
console.log(chalk.cyan('š Recent Repositories (Top 5)'));
|
|
231
|
+
const repos = await getRepoStats();
|
|
232
|
+
if (repos.length > 0) {
|
|
233
|
+
repos.forEach(repo => {
|
|
234
|
+
console.log(` ${chalk.yellow(repo.name)} - ${repo.session_count} sessions`);
|
|
235
|
+
console.log(` Last active: ${chalk.gray(getRelativeTime(new Date(repo.last_active).getTime()))}`);
|
|
236
|
+
});
|
|
237
|
+
} else {
|
|
238
|
+
console.log(chalk.gray(' No repos yet'));
|
|
239
|
+
}
|
|
240
|
+
console.log('');
|
|
241
|
+
|
|
242
|
+
// Session history
|
|
243
|
+
console.log(chalk.cyan('š Session History'));
|
|
244
|
+
const history = await getSessionHistory();
|
|
245
|
+
console.log(` Total sessions: ${chalk.yellow(history.total)}`);
|
|
246
|
+
if (history.recent.length > 0) {
|
|
247
|
+
console.log(` Recent sessions:`);
|
|
248
|
+
history.recent.forEach(session => {
|
|
249
|
+
console.log(` ${chalk.gray(session.relative)} - ${session.issue}`);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
console.log('');
|
|
253
|
+
|
|
254
|
+
// Cache stats
|
|
255
|
+
const cacheStats = await getCacheStats();
|
|
256
|
+
if (cacheStats.total > 0) {
|
|
257
|
+
console.log(chalk.cyan('š¾ Cache Statistics'));
|
|
258
|
+
console.log(` Total entries: ${chalk.yellow(cacheStats.total)}`);
|
|
259
|
+
cacheStats.entries.slice(0, 3).forEach(entry => {
|
|
260
|
+
console.log(` ${entry.query} (TTL: ${entry.ttl}s)`);
|
|
261
|
+
});
|
|
262
|
+
console.log('');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Performance test
|
|
266
|
+
console.log(chalk.cyan('ā” Performance Test'));
|
|
267
|
+
const perf = await testPerformance();
|
|
268
|
+
console.log(` Single round-trip: ${chalk.yellow(perf.single_roundtrip_ms + 'ms')}`);
|
|
269
|
+
console.log(` Pipeline (100 ops): ${chalk.yellow(perf.pipeline_100ops_ms + 'ms')}`);
|
|
270
|
+
console.log(` Pipeline throughput: ${chalk.yellow(perf.pipeline_ops_per_sec.toLocaleString() + ' ops/sec')}\n`);
|
|
271
|
+
|
|
272
|
+
// Health summary
|
|
273
|
+
const health = identity && history.total >= 0 ? chalk.green('HEALTHY') : chalk.yellow('NEEDS SETUP');
|
|
274
|
+
console.log(chalk.white(`Status: ${health}\n`));
|
|
275
|
+
|
|
276
|
+
} catch (error) {
|
|
277
|
+
console.log(chalk.red('Error:'), error.message);
|
|
278
|
+
process.exit(1);
|
|
279
|
+
} finally {
|
|
280
|
+
await redis.quit();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
main();
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Redis Setup and Verification Script for Boss Claude
|
|
4
|
+
*
|
|
5
|
+
* This script:
|
|
6
|
+
* 1. Tests connection to the Redis instance (from REDIS_URL env var)
|
|
7
|
+
* 2. Verifies read/write operations
|
|
8
|
+
* 3. Documents the complete key schema
|
|
9
|
+
* 4. Initializes necessary data structures
|
|
10
|
+
* 5. Provides performance benchmarks
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import Redis from 'ioredis';
|
|
14
|
+
import chalk from 'chalk';
|
|
15
|
+
|
|
16
|
+
const REDIS_URL = process.env.REDIS_URL;
|
|
17
|
+
if (!REDIS_URL) {
|
|
18
|
+
console.error('ā REDIS_URL environment variable is required');
|
|
19
|
+
console.error('Set it with: export REDIS_URL=redis://...');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Boss Claude Redis Schema
|
|
24
|
+
const SCHEMA = {
|
|
25
|
+
identity: {
|
|
26
|
+
key: 'boss:identity',
|
|
27
|
+
type: 'string (JSON)',
|
|
28
|
+
ttl: 'none',
|
|
29
|
+
description: 'Global Boss Claude identity (level, XP, token bank, total sessions)',
|
|
30
|
+
example: {
|
|
31
|
+
level: 1,
|
|
32
|
+
xp: 0,
|
|
33
|
+
token_bank: 0,
|
|
34
|
+
total_sessions: 0,
|
|
35
|
+
repos_managed: 0,
|
|
36
|
+
created_at: '2025-01-30T18:00:00.000Z',
|
|
37
|
+
updated_at: '2025-01-30T18:00:00.000Z'
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
session_current: {
|
|
41
|
+
key: 'boss:session:{repo_name}:current',
|
|
42
|
+
type: 'string (JSON)',
|
|
43
|
+
ttl: 'none (deleted on save)',
|
|
44
|
+
description: 'Active session data for a specific repo',
|
|
45
|
+
example: {
|
|
46
|
+
started_at: '2025-01-30T18:00:00.000Z',
|
|
47
|
+
messages: [],
|
|
48
|
+
tokens_used: 0,
|
|
49
|
+
tasks_completed: 0,
|
|
50
|
+
files_modified: []
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
repo_stats: {
|
|
54
|
+
key: 'boss:repo:{repo_name}',
|
|
55
|
+
type: 'string (JSON)',
|
|
56
|
+
ttl: 'none',
|
|
57
|
+
description: 'Per-repo statistics and metadata',
|
|
58
|
+
example: {
|
|
59
|
+
name: 'BOSS-claude',
|
|
60
|
+
path: '/Users/craigpretzinger/projects/BOSS-claude',
|
|
61
|
+
session_count: 0,
|
|
62
|
+
first_seen: '2025-01-30T18:00:00.000Z',
|
|
63
|
+
last_active: '2025-01-30T18:00:00.000Z',
|
|
64
|
+
total_tokens: 0,
|
|
65
|
+
total_xp_earned: 0
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
session_history: {
|
|
69
|
+
key: 'boss:sessions:history',
|
|
70
|
+
type: 'sorted set',
|
|
71
|
+
ttl: 'none',
|
|
72
|
+
description: 'Historical sessions sorted by timestamp',
|
|
73
|
+
score: 'unix timestamp',
|
|
74
|
+
value: 'session_id (links to GitHub issue)'
|
|
75
|
+
},
|
|
76
|
+
achievement: {
|
|
77
|
+
key: 'boss:achievements:{username}',
|
|
78
|
+
type: 'set',
|
|
79
|
+
ttl: 'none',
|
|
80
|
+
description: 'Unlocked achievements',
|
|
81
|
+
example: ['first_session', 'level_5', 'token_saver', 'perfect_execution']
|
|
82
|
+
},
|
|
83
|
+
leaderboard: {
|
|
84
|
+
key: 'boss:leaderboard:xp',
|
|
85
|
+
type: 'sorted set',
|
|
86
|
+
ttl: 'none',
|
|
87
|
+
description: 'Global XP leaderboard',
|
|
88
|
+
score: 'total XP',
|
|
89
|
+
value: 'username'
|
|
90
|
+
},
|
|
91
|
+
cache_memory: {
|
|
92
|
+
key: 'boss:cache:memory:{query_hash}',
|
|
93
|
+
type: 'string (JSON)',
|
|
94
|
+
ttl: '3600 (1 hour)',
|
|
95
|
+
description: 'Cached memory search results',
|
|
96
|
+
example: {
|
|
97
|
+
query: 'redis setup',
|
|
98
|
+
results: [],
|
|
99
|
+
cached_at: '2025-01-30T18:00:00.000Z'
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
async function testConnection() {
|
|
105
|
+
console.log(chalk.cyan('\nš Testing Redis Connection...\n'));
|
|
106
|
+
|
|
107
|
+
const redis = new Redis(REDIS_URL, {
|
|
108
|
+
connectTimeout: 10000,
|
|
109
|
+
maxRetriesPerRequest: 3,
|
|
110
|
+
retryStrategy(times) {
|
|
111
|
+
const delay = Math.min(times * 50, 2000);
|
|
112
|
+
return delay;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
redis.on('connect', () => {
|
|
118
|
+
console.log(chalk.green(`ā Connected to Redis (${new URL(REDIS_URL).host})`));
|
|
119
|
+
resolve(redis);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
redis.on('error', (err) => {
|
|
123
|
+
console.log(chalk.red('ā Connection failed:'), err.message);
|
|
124
|
+
reject(err);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
redis.on('ready', () => {
|
|
128
|
+
console.log(chalk.green('ā Redis client ready'));
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function verifyReadWrite(redis) {
|
|
134
|
+
console.log(chalk.cyan('\nš Testing Read/Write Operations...\n'));
|
|
135
|
+
|
|
136
|
+
const testKey = 'boss:test:connection';
|
|
137
|
+
const testValue = JSON.stringify({
|
|
138
|
+
timestamp: new Date().toISOString(),
|
|
139
|
+
test: 'Boss Claude Redis Setup'
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
// Write test
|
|
144
|
+
await redis.set(testKey, testValue);
|
|
145
|
+
console.log(chalk.green('ā Write operation successful'));
|
|
146
|
+
|
|
147
|
+
// Read test
|
|
148
|
+
const retrieved = await redis.get(testKey);
|
|
149
|
+
if (retrieved === testValue) {
|
|
150
|
+
console.log(chalk.green('ā Read operation successful'));
|
|
151
|
+
} else {
|
|
152
|
+
throw new Error('Data mismatch');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// TTL test
|
|
156
|
+
await redis.setex('boss:test:ttl', 60, 'expires in 60s');
|
|
157
|
+
const ttl = await redis.ttl('boss:test:ttl');
|
|
158
|
+
console.log(chalk.green(`ā TTL operation successful (${ttl}s remaining)`));
|
|
159
|
+
|
|
160
|
+
// Cleanup
|
|
161
|
+
await redis.del(testKey, 'boss:test:ttl');
|
|
162
|
+
console.log(chalk.green('ā Delete operation successful'));
|
|
163
|
+
|
|
164
|
+
return true;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.log(chalk.red('ā Read/Write test failed:'), error.message);
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function checkExistingData(redis) {
|
|
172
|
+
console.log(chalk.cyan('\nš Checking Existing Data...\n'));
|
|
173
|
+
|
|
174
|
+
// Check for existing identity
|
|
175
|
+
const identity = await redis.get('boss:identity');
|
|
176
|
+
if (identity) {
|
|
177
|
+
console.log(chalk.yellow('ā Existing identity found:'));
|
|
178
|
+
console.log(JSON.stringify(JSON.parse(identity), null, 2));
|
|
179
|
+
} else {
|
|
180
|
+
console.log(chalk.gray('ā¹ No existing identity (will be created on first use)'));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Check for repo data
|
|
184
|
+
const repoKeys = await redis.keys('boss:repo:*');
|
|
185
|
+
if (repoKeys.length > 0) {
|
|
186
|
+
console.log(chalk.yellow(`ā Found ${repoKeys.length} existing repo(s):`));
|
|
187
|
+
for (const key of repoKeys) {
|
|
188
|
+
const data = await redis.get(key);
|
|
189
|
+
const repo = JSON.parse(data);
|
|
190
|
+
console.log(` - ${repo.name} (${repo.session_count} sessions)`);
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
console.log(chalk.gray('ā¹ No existing repos'));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check for active sessions
|
|
197
|
+
const sessionKeys = await redis.keys('boss:session:*:current');
|
|
198
|
+
if (sessionKeys.length > 0) {
|
|
199
|
+
console.log(chalk.yellow(`ā Found ${sessionKeys.length} active session(s):`));
|
|
200
|
+
sessionKeys.forEach(key => {
|
|
201
|
+
const repoName = key.replace('boss:session:', '').replace(':current', '');
|
|
202
|
+
console.log(` - ${repoName}`);
|
|
203
|
+
});
|
|
204
|
+
} else {
|
|
205
|
+
console.log(chalk.gray('ā¹ No active sessions'));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function benchmarkOperations(redis) {
|
|
210
|
+
console.log(chalk.cyan('\nā” Running Performance Benchmarks...\n'));
|
|
211
|
+
|
|
212
|
+
const iterations = 1000;
|
|
213
|
+
const testData = JSON.stringify({ test: 'data', timestamp: Date.now() });
|
|
214
|
+
|
|
215
|
+
// SET benchmark
|
|
216
|
+
const setStart = Date.now();
|
|
217
|
+
for (let i = 0; i < iterations; i++) {
|
|
218
|
+
await redis.set(`boss:bench:${i}`, testData);
|
|
219
|
+
}
|
|
220
|
+
const setDuration = Date.now() - setStart;
|
|
221
|
+
const setOpsPerSec = Math.round((iterations / setDuration) * 1000);
|
|
222
|
+
|
|
223
|
+
console.log(chalk.green(`ā SET: ${iterations} ops in ${setDuration}ms (${setOpsPerSec} ops/sec)`));
|
|
224
|
+
|
|
225
|
+
// GET benchmark
|
|
226
|
+
const getStart = Date.now();
|
|
227
|
+
for (let i = 0; i < iterations; i++) {
|
|
228
|
+
await redis.get(`boss:bench:${i}`);
|
|
229
|
+
}
|
|
230
|
+
const getDuration = Date.now() - getStart;
|
|
231
|
+
const getOpsPerSec = Math.round((iterations / getDuration) * 1000);
|
|
232
|
+
|
|
233
|
+
console.log(chalk.green(`ā GET: ${iterations} ops in ${getDuration}ms (${getOpsPerSec} ops/sec)`));
|
|
234
|
+
|
|
235
|
+
// Pipeline benchmark
|
|
236
|
+
const pipelineStart = Date.now();
|
|
237
|
+
const pipeline = redis.pipeline();
|
|
238
|
+
for (let i = 0; i < iterations; i++) {
|
|
239
|
+
pipeline.get(`boss:bench:${i}`);
|
|
240
|
+
}
|
|
241
|
+
await pipeline.exec();
|
|
242
|
+
const pipelineDuration = Date.now() - pipelineStart;
|
|
243
|
+
const pipelineOpsPerSec = Math.round((iterations / pipelineDuration) * 1000);
|
|
244
|
+
|
|
245
|
+
console.log(chalk.green(`ā PIPELINE: ${iterations} ops in ${pipelineDuration}ms (${pipelineOpsPerSec} ops/sec)`));
|
|
246
|
+
|
|
247
|
+
// Cleanup
|
|
248
|
+
const keys = await redis.keys('boss:bench:*');
|
|
249
|
+
if (keys.length > 0) {
|
|
250
|
+
await redis.del(...keys);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
console.log(chalk.gray(`\nā¹ Cleaned up ${iterations} test keys`));
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
set: { duration: setDuration, opsPerSec: setOpsPerSec },
|
|
257
|
+
get: { duration: getDuration, opsPerSec: getOpsPerSec },
|
|
258
|
+
pipeline: { duration: pipelineDuration, opsPerSec: pipelineOpsPerSec }
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function displayServerInfo(redis) {
|
|
263
|
+
console.log(chalk.cyan('\nš„ļø Redis Server Information...\n'));
|
|
264
|
+
|
|
265
|
+
const info = await redis.info();
|
|
266
|
+
const lines = info.split('\r\n');
|
|
267
|
+
|
|
268
|
+
const extractValue = (key) => {
|
|
269
|
+
const line = lines.find(l => l.startsWith(key));
|
|
270
|
+
return line ? line.split(':')[1] : 'N/A';
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
console.log(chalk.white('Server:'));
|
|
274
|
+
console.log(` Redis Version: ${extractValue('redis_version')}`);
|
|
275
|
+
console.log(` OS: ${extractValue('os')}`);
|
|
276
|
+
console.log(` Uptime: ${Math.round(extractValue('uptime_in_seconds') / 86400)} days`);
|
|
277
|
+
|
|
278
|
+
console.log(chalk.white('\nMemory:'));
|
|
279
|
+
const usedMemory = extractValue('used_memory_human');
|
|
280
|
+
const maxMemory = extractValue('maxmemory_human');
|
|
281
|
+
console.log(` Used: ${usedMemory}`);
|
|
282
|
+
console.log(` Max: ${maxMemory || 'unlimited'}`);
|
|
283
|
+
|
|
284
|
+
console.log(chalk.white('\nStats:'));
|
|
285
|
+
console.log(` Total Connections: ${extractValue('total_connections_received')}`);
|
|
286
|
+
console.log(` Total Commands: ${extractValue('total_commands_processed')}`);
|
|
287
|
+
console.log(` Connected Clients: ${extractValue('connected_clients')}`);
|
|
288
|
+
|
|
289
|
+
const dbInfo = lines.find(l => l.startsWith('db0'));
|
|
290
|
+
if (dbInfo) {
|
|
291
|
+
const keys = dbInfo.match(/keys=(\d+)/)[1];
|
|
292
|
+
console.log(` Keys in DB: ${keys}`);
|
|
293
|
+
} else {
|
|
294
|
+
console.log(` Keys in DB: 0`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function printSchema() {
|
|
299
|
+
console.log(chalk.cyan('\nš Boss Claude Redis Schema\n'));
|
|
300
|
+
|
|
301
|
+
Object.entries(SCHEMA).forEach(([name, spec]) => {
|
|
302
|
+
console.log(chalk.white.bold(`${name.toUpperCase()}`));
|
|
303
|
+
console.log(` Key: ${chalk.yellow(spec.key)}`);
|
|
304
|
+
console.log(` Type: ${spec.type}`);
|
|
305
|
+
console.log(` TTL: ${spec.ttl}`);
|
|
306
|
+
console.log(` Description: ${spec.description}`);
|
|
307
|
+
if (spec.example) {
|
|
308
|
+
console.log(` Example: ${chalk.gray(JSON.stringify(spec.example, null, 2).split('\n').join('\n '))}`);
|
|
309
|
+
}
|
|
310
|
+
console.log('');
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function initializeStructures(redis) {
|
|
315
|
+
console.log(chalk.cyan('\nš Initializing Data Structures...\n'));
|
|
316
|
+
|
|
317
|
+
// Check if identity exists
|
|
318
|
+
const existingIdentity = await redis.get('boss:identity');
|
|
319
|
+
|
|
320
|
+
if (!existingIdentity) {
|
|
321
|
+
const identity = {
|
|
322
|
+
level: 1,
|
|
323
|
+
xp: 0,
|
|
324
|
+
token_bank: 0,
|
|
325
|
+
total_sessions: 0,
|
|
326
|
+
repos_managed: 0,
|
|
327
|
+
created_at: new Date().toISOString()
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
await redis.set('boss:identity', JSON.stringify(identity));
|
|
331
|
+
console.log(chalk.green('ā Created default Boss Claude identity (Level 1)'));
|
|
332
|
+
} else {
|
|
333
|
+
console.log(chalk.yellow('ā Identity already exists (skipping)'));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Initialize session history sorted set (if not exists)
|
|
337
|
+
const historyExists = await redis.exists('boss:sessions:history');
|
|
338
|
+
if (!historyExists) {
|
|
339
|
+
// Create empty sorted set
|
|
340
|
+
await redis.zadd('boss:sessions:history', 0, 'placeholder');
|
|
341
|
+
await redis.zrem('boss:sessions:history', 'placeholder');
|
|
342
|
+
console.log(chalk.green('ā Initialized session history sorted set'));
|
|
343
|
+
} else {
|
|
344
|
+
const count = await redis.zcard('boss:sessions:history');
|
|
345
|
+
console.log(chalk.yellow(`ā Session history already exists (${count} entries)`));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
console.log(chalk.green('\nā Initialization complete!'));
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async function main() {
|
|
352
|
+
console.log(chalk.bold.cyan('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
353
|
+
console.log(chalk.bold.cyan('ā BOSS CLAUDE - REDIS SETUP & VERIFY ā'));
|
|
354
|
+
console.log(chalk.bold.cyan('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
355
|
+
|
|
356
|
+
let redis;
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
// Test connection
|
|
360
|
+
redis = await testConnection();
|
|
361
|
+
|
|
362
|
+
// Display server info
|
|
363
|
+
await displayServerInfo(redis);
|
|
364
|
+
|
|
365
|
+
// Verify read/write
|
|
366
|
+
const rwSuccess = await verifyReadWrite(redis);
|
|
367
|
+
if (!rwSuccess) {
|
|
368
|
+
throw new Error('Read/Write verification failed');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Check existing data
|
|
372
|
+
await checkExistingData(redis);
|
|
373
|
+
|
|
374
|
+
// Run benchmarks
|
|
375
|
+
const benchmarks = await benchmarkOperations(redis);
|
|
376
|
+
|
|
377
|
+
// Initialize structures
|
|
378
|
+
await initializeStructures(redis);
|
|
379
|
+
|
|
380
|
+
// Print schema documentation
|
|
381
|
+
printSchema();
|
|
382
|
+
|
|
383
|
+
console.log(chalk.bold.green('\nā
ALL TESTS PASSED!\n'));
|
|
384
|
+
console.log(chalk.white('Connection Details:'));
|
|
385
|
+
console.log(` Host: ${chalk.yellow(new URL(REDIS_URL).host)}`);
|
|
386
|
+
console.log(` Database: ${chalk.yellow('0 (default)')}`);
|
|
387
|
+
console.log(` Status: ${chalk.green('Connected')}`);
|
|
388
|
+
|
|
389
|
+
console.log(chalk.white('\nPerformance:'));
|
|
390
|
+
console.log(` SET ops/sec: ${chalk.yellow(benchmarks.set.opsPerSec.toLocaleString())}`);
|
|
391
|
+
console.log(` GET ops/sec: ${chalk.yellow(benchmarks.get.opsPerSec.toLocaleString())}`);
|
|
392
|
+
console.log(` PIPELINE ops/sec: ${chalk.yellow(benchmarks.pipeline.opsPerSec.toLocaleString())}`);
|
|
393
|
+
|
|
394
|
+
console.log(chalk.white('\nNext Steps:'));
|
|
395
|
+
console.log(` 1. Update ${chalk.yellow('~/.boss-claude/.env')} with new REDIS_URL`);
|
|
396
|
+
console.log(` 2. Run ${chalk.yellow('npm install -g .')} to install boss-claude globally`);
|
|
397
|
+
console.log(` 3. Run ${chalk.yellow('boss-claude status')} to verify installation`);
|
|
398
|
+
|
|
399
|
+
} catch (error) {
|
|
400
|
+
console.log(chalk.bold.red('\nā SETUP FAILED!\n'));
|
|
401
|
+
console.log(chalk.red('Error:'), error.message);
|
|
402
|
+
console.log(chalk.red('Stack:'), error.stack);
|
|
403
|
+
process.exit(1);
|
|
404
|
+
} finally {
|
|
405
|
+
if (redis) {
|
|
406
|
+
await redis.quit();
|
|
407
|
+
console.log(chalk.gray('\n[Connection closed]'));
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
main();
|