@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.
Files changed (87) hide show
  1. package/README.md +304 -1
  2. package/bin/boss-claude.js +1138 -0
  3. package/bin/commands/mode.js +250 -0
  4. package/bin/onyx-guard.js +259 -0
  5. package/bin/onyx-guard.sh +251 -0
  6. package/bin/prompts.js +284 -0
  7. package/bin/rollback.js +85 -0
  8. package/bin/setup-wizard.js +492 -0
  9. package/config/.env.example +17 -0
  10. package/lib/README.md +83 -0
  11. package/lib/agent-logger.js +61 -0
  12. package/lib/agents/memory-engineers/github-memory-engineer.js +251 -0
  13. package/lib/agents/memory-engineers/postgres-memory-engineer.js +633 -0
  14. package/lib/agents/memory-engineers/qdrant-memory-engineer.js +358 -0
  15. package/lib/agents/memory-engineers/redis-memory-engineer.js +383 -0
  16. package/lib/agents/memory-supervisor.js +526 -0
  17. package/lib/agents/registry.js +135 -0
  18. package/lib/auto-monitor.js +131 -0
  19. package/lib/checkpoint-hook.js +112 -0
  20. package/lib/checkpoint.js +319 -0
  21. package/lib/commentator.js +213 -0
  22. package/lib/context-scribe.js +120 -0
  23. package/lib/delegation-strategies.js +326 -0
  24. package/lib/hierarchy-validator.js +643 -0
  25. package/lib/index.js +15 -0
  26. package/lib/init-with-mode.js +261 -0
  27. package/lib/init.js +44 -6
  28. package/lib/memory-result-aggregator.js +252 -0
  29. package/lib/memory.js +35 -7
  30. package/lib/mode-enforcer.js +473 -0
  31. package/lib/onyx-banner.js +169 -0
  32. package/lib/onyx-identity.js +214 -0
  33. package/lib/onyx-monitor.js +381 -0
  34. package/lib/onyx-reminder.js +188 -0
  35. package/lib/onyx-tool-interceptor.js +341 -0
  36. package/lib/onyx-wrapper.js +315 -0
  37. package/lib/orchestrator-gate.js +334 -0
  38. package/lib/output-formatter.js +296 -0
  39. package/lib/postgres.js +1 -1
  40. package/lib/prompt-injector.js +220 -0
  41. package/lib/prompts.js +532 -0
  42. package/lib/session.js +153 -6
  43. package/lib/setup/README.md +187 -0
  44. package/lib/setup/env-manager.js +785 -0
  45. package/lib/setup/error-recovery.js +630 -0
  46. package/lib/setup/explain-scopes.js +385 -0
  47. package/lib/setup/github-instructions.js +333 -0
  48. package/lib/setup/github-repo.js +254 -0
  49. package/lib/setup/import-credentials.js +498 -0
  50. package/lib/setup/index.js +62 -0
  51. package/lib/setup/init-postgres.js +785 -0
  52. package/lib/setup/init-redis.js +456 -0
  53. package/lib/setup/integration-test.js +652 -0
  54. package/lib/setup/progress.js +357 -0
  55. package/lib/setup/rollback.js +670 -0
  56. package/lib/setup/rollback.test.js +452 -0
  57. package/lib/setup/setup-with-rollback.example.js +351 -0
  58. package/lib/setup/summary.js +400 -0
  59. package/lib/setup/test-github-setup.js +10 -0
  60. package/lib/setup/test-postgres-init.js +98 -0
  61. package/lib/setup/verify-setup.js +102 -0
  62. package/lib/task-agent-worker.js +235 -0
  63. package/lib/token-monitor.js +466 -0
  64. package/lib/tool-wrapper-integration.js +369 -0
  65. package/lib/tool-wrapper.js +387 -0
  66. package/lib/validators/README.md +497 -0
  67. package/lib/validators/config.js +583 -0
  68. package/lib/validators/config.test.js +175 -0
  69. package/lib/validators/github.js +310 -0
  70. package/lib/validators/github.test.js +61 -0
  71. package/lib/validators/index.js +15 -0
  72. package/lib/validators/postgres.js +525 -0
  73. package/package.json +98 -13
  74. package/scripts/benchmark-memory.js +433 -0
  75. package/scripts/check-secrets.sh +12 -0
  76. package/scripts/fetch-todos.mjs +148 -0
  77. package/scripts/graceful-shutdown.sh +156 -0
  78. package/scripts/install-onyx-hooks.js +373 -0
  79. package/scripts/install.js +119 -18
  80. package/scripts/redis-monitor.js +284 -0
  81. package/scripts/redis-setup.js +412 -0
  82. package/scripts/test-memory-retrieval.js +201 -0
  83. package/scripts/validate-exports.js +68 -0
  84. package/scripts/validate-package.js +120 -0
  85. package/scripts/verify-onyx-deployment.js +309 -0
  86. package/scripts/verify-redis-deployment.js +354 -0
  87. 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();