@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
package/bin/boss-claude.js
CHANGED
|
@@ -6,6 +6,54 @@ import { loadIdentity, updateIdentity } from '../lib/identity.js';
|
|
|
6
6
|
import { loadSession, saveSession } from '../lib/session.js';
|
|
7
7
|
import { searchMemory, saveMemory } from '../lib/memory.js';
|
|
8
8
|
import { getStatus } from '../lib/init.js';
|
|
9
|
+
import { importCredentials, validateCredentials, listSources } from '../lib/setup/import-credentials.js';
|
|
10
|
+
import {
|
|
11
|
+
getRedisStats,
|
|
12
|
+
resetRedis,
|
|
13
|
+
verifyRedis,
|
|
14
|
+
initializeRedis,
|
|
15
|
+
printInitResults
|
|
16
|
+
} from '../lib/setup/init-redis.js';
|
|
17
|
+
import { runIntegrationTests } from '../lib/setup/integration-test.js';
|
|
18
|
+
import { validateConfig, printReport } from '../lib/validators/config.js';
|
|
19
|
+
import { LOG_FILE } from '../lib/agent-logger.js';
|
|
20
|
+
import { formatLogDetails } from '../lib/output-formatter.js';
|
|
21
|
+
import hierarchyValidator from '../lib/hierarchy-validator.js';
|
|
22
|
+
import { AGENT_HIERARCHY } from '../lib/agents/registry.js';
|
|
23
|
+
import onyxReminder from '../lib/onyx-reminder.js';
|
|
24
|
+
import {
|
|
25
|
+
trackDelegation,
|
|
26
|
+
trackDirectAction,
|
|
27
|
+
getDelegationStats,
|
|
28
|
+
getRecentEvents,
|
|
29
|
+
setAlertThreshold,
|
|
30
|
+
getAlertThreshold,
|
|
31
|
+
resetTracking,
|
|
32
|
+
generateReport,
|
|
33
|
+
getFormattedStatus,
|
|
34
|
+
ALERT_LOG
|
|
35
|
+
} from '../lib/onyx-monitor.js';
|
|
36
|
+
import {
|
|
37
|
+
getCheckpointStatus,
|
|
38
|
+
recordCheckpoint,
|
|
39
|
+
getCheckpointHistory,
|
|
40
|
+
getDelegationEfficiency,
|
|
41
|
+
resetCheckpoint,
|
|
42
|
+
exportCheckpointData,
|
|
43
|
+
formatCheckpointPrompt
|
|
44
|
+
} from '../lib/checkpoint.js';
|
|
45
|
+
import { modeCommand } from './commands/mode.js';
|
|
46
|
+
import { spawn } from 'child_process';
|
|
47
|
+
import dotenv from 'dotenv';
|
|
48
|
+
import { join } from 'path';
|
|
49
|
+
import os from 'os';
|
|
50
|
+
import { existsSync } from 'fs';
|
|
51
|
+
|
|
52
|
+
// Load environment variables
|
|
53
|
+
const envPath = join(os.homedir(), '.boss-claude', '.env');
|
|
54
|
+
if (existsSync(envPath)) {
|
|
55
|
+
dotenv.config({ path: envPath });
|
|
56
|
+
}
|
|
9
57
|
|
|
10
58
|
const program = new Command();
|
|
11
59
|
|
|
@@ -14,6 +62,39 @@ program
|
|
|
14
62
|
.description('Boss Claude - Gamified AI assistant with persistent memory')
|
|
15
63
|
.version('1.0.0');
|
|
16
64
|
|
|
65
|
+
program
|
|
66
|
+
.command('setup')
|
|
67
|
+
.description('Auto-detect and import credentials')
|
|
68
|
+
.option('-l, --list', 'List all credential sources without importing')
|
|
69
|
+
.option('-v, --validate', 'Validate existing credentials')
|
|
70
|
+
.action(async (options) => {
|
|
71
|
+
try {
|
|
72
|
+
if (options.list) {
|
|
73
|
+
// List all sources
|
|
74
|
+
await listSources();
|
|
75
|
+
} else if (options.validate) {
|
|
76
|
+
// Validate existing credentials
|
|
77
|
+
console.log(chalk.blue('\n🔐 Validating credentials...\n'));
|
|
78
|
+
const result = await validateCredentials();
|
|
79
|
+
|
|
80
|
+
if (result.valid) {
|
|
81
|
+
console.log(chalk.green('✅ All credentials are valid!\n'));
|
|
82
|
+
} else {
|
|
83
|
+
console.log(chalk.red('❌ Credential validation failed:\n'));
|
|
84
|
+
result.errors.forEach(error => console.log(chalk.red(` • ${error}`)));
|
|
85
|
+
console.log();
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
// Import credentials
|
|
90
|
+
await importCredentials();
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error(chalk.red('❌ Setup failed:'), error.message);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
17
98
|
program
|
|
18
99
|
.command('init')
|
|
19
100
|
.description('Initialize Boss Claude in current repository')
|
|
@@ -110,6 +191,18 @@ program
|
|
|
110
191
|
console.log(` XP Earned: ${chalk.cyan('+' + session.xp_earned)}`);
|
|
111
192
|
console.log(` Tokens Banked: ${chalk.magenta('+' + session.tokens_earned.toLocaleString())}\n`);
|
|
112
193
|
|
|
194
|
+
// Show efficiency breakdown if available
|
|
195
|
+
if (session.efficiency) {
|
|
196
|
+
const eff = session.efficiency;
|
|
197
|
+
console.log(chalk.bold('⚡ Efficiency Breakdown:'));
|
|
198
|
+
console.log(` 🎺 ONYX Tokens: ${chalk.dim(eff.onyx_tokens.toLocaleString())}`);
|
|
199
|
+
console.log(` 🎻 Agent Tokens: ${chalk.green(eff.agent_tokens.toLocaleString())}`);
|
|
200
|
+
console.log(` 📈 Efficiency Ratio: ${chalk.yellow(eff.ratio)}`);
|
|
201
|
+
console.log(` 🎯 Delegations: ${chalk.blue(eff.delegations)}`);
|
|
202
|
+
console.log(chalk.bold(' 💎 Bonus XP: ') + chalk.cyan(`+${eff.efficiency_bonus}`) + chalk.dim(' (efficiency) ') +
|
|
203
|
+
chalk.cyan(`+${eff.delegation_bonus}`) + chalk.dim(' (delegation)\n'));
|
|
204
|
+
}
|
|
205
|
+
|
|
113
206
|
} catch (error) {
|
|
114
207
|
console.error(chalk.red('❌ Error saving session:'), error.message);
|
|
115
208
|
process.exit(1);
|
|
@@ -147,4 +240,1049 @@ program
|
|
|
147
240
|
}
|
|
148
241
|
});
|
|
149
242
|
|
|
243
|
+
program
|
|
244
|
+
.command('test')
|
|
245
|
+
.description('Run integration tests to verify system health')
|
|
246
|
+
.action(async () => {
|
|
247
|
+
try {
|
|
248
|
+
await runIntegrationTests();
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error(chalk.red('❌ Integration tests failed:'), error.message);
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
program
|
|
256
|
+
.command('validate')
|
|
257
|
+
.description('Validate ~/.boss-claude/.env configuration')
|
|
258
|
+
.option('--skip-optional', 'Skip validation of optional variables')
|
|
259
|
+
.action(async (options) => {
|
|
260
|
+
try {
|
|
261
|
+
const report = await validateConfig({
|
|
262
|
+
includeOptional: !options.skipOptional
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
printReport(report);
|
|
266
|
+
|
|
267
|
+
if (!report.valid) {
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error(chalk.red('❌ Validation failed:'), error.message);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Redis management commands
|
|
277
|
+
program
|
|
278
|
+
.command('redis:stats')
|
|
279
|
+
.description('Show Redis statistics and key counts')
|
|
280
|
+
.action(async () => {
|
|
281
|
+
try {
|
|
282
|
+
if (!process.env.REDIS_URL) {
|
|
283
|
+
console.error(chalk.red('❌ REDIS_URL not configured. Run: boss-claude setup\n'));
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
console.log(chalk.blue('\n📊 Redis Statistics\n'));
|
|
288
|
+
|
|
289
|
+
const stats = await getRedisStats(process.env.REDIS_URL);
|
|
290
|
+
|
|
291
|
+
if (stats.identity) {
|
|
292
|
+
console.log(chalk.bold('Boss Identity:'));
|
|
293
|
+
console.log(` Level: ${chalk.yellow(stats.identity.level)}`);
|
|
294
|
+
console.log(` XP: ${chalk.cyan(stats.identity.xp)}`);
|
|
295
|
+
console.log(` Token Bank: ${chalk.magenta(stats.identity.token_bank.toLocaleString())}`);
|
|
296
|
+
console.log(` Total Sessions: ${chalk.blue(stats.identity.total_sessions)}`);
|
|
297
|
+
console.log(` Repos Managed: ${chalk.green(stats.identity.repos_managed)}\n`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
console.log(chalk.bold('Data Structures:'));
|
|
301
|
+
console.log(` Session History: ${chalk.cyan(stats.totalSessions)} sessions`);
|
|
302
|
+
console.log(` Tracked Repos: ${chalk.green(stats.totalRepos)} repositories`);
|
|
303
|
+
console.log(` Active Sessions: ${chalk.yellow(stats.activeSessions)}`);
|
|
304
|
+
console.log(` Leaderboard Size: ${chalk.blue(stats.leaderboardSize)} users`);
|
|
305
|
+
console.log(` Cache Keys: ${chalk.magenta(stats.cacheKeys)}\n`);
|
|
306
|
+
|
|
307
|
+
if (Object.keys(stats.achievements).length > 0) {
|
|
308
|
+
console.log(chalk.bold('Achievements:'));
|
|
309
|
+
Object.entries(stats.achievements).forEach(([user, achievements]) => {
|
|
310
|
+
console.log(` ${chalk.cyan(user)}: ${achievements.length > 0 ? achievements.join(', ') : chalk.dim('none')}`);
|
|
311
|
+
});
|
|
312
|
+
console.log();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
} catch (error) {
|
|
316
|
+
console.error(chalk.red('❌ Error fetching Redis stats:'), error.message);
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
program
|
|
322
|
+
.command('redis:verify')
|
|
323
|
+
.description('Verify Redis connection and data structures')
|
|
324
|
+
.action(async () => {
|
|
325
|
+
try {
|
|
326
|
+
if (!process.env.REDIS_URL) {
|
|
327
|
+
console.error(chalk.red('❌ REDIS_URL not configured. Run: boss-claude setup\n'));
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
console.log(chalk.blue('\n🔍 Verifying Redis...\n'));
|
|
332
|
+
|
|
333
|
+
const result = await verifyRedis(process.env.REDIS_URL);
|
|
334
|
+
|
|
335
|
+
if (!result.connected) {
|
|
336
|
+
console.log(chalk.red('✗ Connection Failed'));
|
|
337
|
+
console.log(chalk.dim(` Error: ${result.error}\n`));
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
console.log(chalk.green('✓ Connection Successful'));
|
|
342
|
+
console.log(chalk.dim(` Redis Version: ${result.version}\n`));
|
|
343
|
+
|
|
344
|
+
console.log(chalk.bold('Data Structures:'));
|
|
345
|
+
console.log(result.structures.identity ?
|
|
346
|
+
chalk.green('✓ boss:identity exists') :
|
|
347
|
+
chalk.red('✗ boss:identity missing')
|
|
348
|
+
);
|
|
349
|
+
console.log(` ${result.structures.sessionHistory > 0 ? chalk.green('✓') : chalk.yellow('○')} boss:sessions:history (${result.structures.sessionHistory} sessions)`);
|
|
350
|
+
console.log(` ${result.structures.leaderboard > 0 ? chalk.green('✓') : chalk.yellow('○')} boss:leaderboard:xp (${result.structures.leaderboard} users)\n`);
|
|
351
|
+
|
|
352
|
+
if (result.healthCheck.passed) {
|
|
353
|
+
console.log(chalk.green('✓ All health checks passed\n'));
|
|
354
|
+
} else {
|
|
355
|
+
console.log(chalk.red('✗ Health check failed\n'));
|
|
356
|
+
Object.entries(result.healthCheck.details).forEach(([key, value]) => {
|
|
357
|
+
const status = value === 'OK' ? chalk.green('✓') : chalk.red('✗');
|
|
358
|
+
console.log(` ${status} ${key}: ${chalk.dim(value)}`);
|
|
359
|
+
});
|
|
360
|
+
console.log();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
} catch (error) {
|
|
364
|
+
console.error(chalk.red('❌ Verification failed:'), error.message);
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
program
|
|
370
|
+
.command('redis:init')
|
|
371
|
+
.description('Initialize or repair Redis data structures')
|
|
372
|
+
.option('-f, --force', 'Force re-initialization even if data exists')
|
|
373
|
+
.action(async (options) => {
|
|
374
|
+
try {
|
|
375
|
+
if (!process.env.REDIS_URL) {
|
|
376
|
+
console.error(chalk.red('❌ REDIS_URL not configured. Run: boss-claude setup\n'));
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (!process.env.GITHUB_USER) {
|
|
381
|
+
console.error(chalk.red('❌ GITHUB_USER not configured. Run: boss-claude setup\n'));
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
console.log(chalk.blue('\n🔧 Initializing Redis...\n'));
|
|
386
|
+
|
|
387
|
+
if (options.force) {
|
|
388
|
+
console.log(chalk.yellow('⚠️ Force mode: Will overwrite existing data\n'));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const results = await initializeRedis(
|
|
392
|
+
process.env.REDIS_URL,
|
|
393
|
+
process.env.GITHUB_USER,
|
|
394
|
+
options.force
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
printInitResults(results, process.env.GITHUB_USER);
|
|
398
|
+
|
|
399
|
+
if (results.healthCheck.passed) {
|
|
400
|
+
console.log(chalk.green('✓ Redis initialization successful!\n'));
|
|
401
|
+
} else {
|
|
402
|
+
console.log(chalk.yellow('⚠️ Initialization completed with warnings\n'));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
} catch (error) {
|
|
406
|
+
console.error(chalk.red('❌ Initialization failed:'), error.message);
|
|
407
|
+
process.exit(1);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
program
|
|
412
|
+
.command('redis:reset')
|
|
413
|
+
.description('Reset Redis to initial state (WARNING: Destructive!)')
|
|
414
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
415
|
+
.action(async (options) => {
|
|
416
|
+
try {
|
|
417
|
+
if (!process.env.REDIS_URL) {
|
|
418
|
+
console.error(chalk.red('❌ REDIS_URL not configured. Run: boss-claude setup\n'));
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (!process.env.GITHUB_USER) {
|
|
423
|
+
console.error(chalk.red('❌ GITHUB_USER not configured. Run: boss-claude setup\n'));
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
console.log(chalk.red('\n⚠️ WARNING: This will delete ALL Boss Claude data in Redis!\n'));
|
|
428
|
+
|
|
429
|
+
if (!options.confirm) {
|
|
430
|
+
const { confirm } = await import('./prompts.js');
|
|
431
|
+
const shouldContinue = await confirm('Are you absolutely sure?', false);
|
|
432
|
+
|
|
433
|
+
if (!shouldContinue) {
|
|
434
|
+
console.log(chalk.yellow('\nReset cancelled\n'));
|
|
435
|
+
process.exit(0);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
console.log(chalk.blue('\n🔄 Resetting Redis...\n'));
|
|
440
|
+
|
|
441
|
+
const results = await resetRedis(process.env.REDIS_URL, process.env.GITHUB_USER);
|
|
442
|
+
|
|
443
|
+
printInitResults(results, process.env.GITHUB_USER);
|
|
444
|
+
|
|
445
|
+
console.log(chalk.green('✓ Redis reset complete!\n'));
|
|
446
|
+
console.log(chalk.dim('All data has been deleted and structures re-initialized\n'));
|
|
447
|
+
|
|
448
|
+
} catch (error) {
|
|
449
|
+
console.error(chalk.red('❌ Reset failed:'), error.message);
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
program
|
|
455
|
+
.command('watch')
|
|
456
|
+
.description('Watch agent activity in real-time (companion window)')
|
|
457
|
+
.action(() => {
|
|
458
|
+
console.log(chalk.blue('\n👁️ Boss Claude Agent Monitor\n'));
|
|
459
|
+
console.log(chalk.dim(`Watching: ${LOG_FILE}`));
|
|
460
|
+
console.log(chalk.dim('Press Ctrl+C to stop\n'));
|
|
461
|
+
console.log(chalk.gray('─'.repeat(80)));
|
|
462
|
+
|
|
463
|
+
// Use tail -f to follow the log file
|
|
464
|
+
const tail = spawn('tail', ['-f', '-n', '20', LOG_FILE], {
|
|
465
|
+
stdio: ['ignore', 'pipe', 'inherit']
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// Format and colorize output
|
|
469
|
+
tail.stdout.on('data', (data) => {
|
|
470
|
+
const lines = data.toString().split('\n');
|
|
471
|
+
lines.forEach(line => {
|
|
472
|
+
if (!line.trim()) return;
|
|
473
|
+
|
|
474
|
+
// Parse log format: [timestamp] EVENT: Agent - Details
|
|
475
|
+
const match = line.match(/\[(.*?)\] (.*?): (.*?)(?:\s-\s(.*))?$/);
|
|
476
|
+
if (match) {
|
|
477
|
+
const [, timestamp, event, agent, details] = match;
|
|
478
|
+
const time = new Date(timestamp).toLocaleTimeString();
|
|
479
|
+
|
|
480
|
+
let eventColor = chalk.gray;
|
|
481
|
+
if (event === 'START') eventColor = chalk.cyan;
|
|
482
|
+
else if (event === 'COMPLETE') eventColor = chalk.green;
|
|
483
|
+
else if (event === 'ERROR') eventColor = chalk.red;
|
|
484
|
+
|
|
485
|
+
// Format details with intelligent line wrapping
|
|
486
|
+
let formattedDetails = '';
|
|
487
|
+
if (details) {
|
|
488
|
+
const wrappedDetails = formatLogDetails(details, {
|
|
489
|
+
multiline: true,
|
|
490
|
+
continuationPrefix: ' ' // Align with agent name
|
|
491
|
+
});
|
|
492
|
+
formattedDetails = chalk.dim(' - ' + wrappedDetails);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const formatted = `${chalk.dim(time)} ${eventColor(event.padEnd(10))} ${chalk.bold(agent)}${formattedDetails}`;
|
|
496
|
+
console.log(formatted);
|
|
497
|
+
} else {
|
|
498
|
+
// Fallback for non-matching lines
|
|
499
|
+
console.log(chalk.gray(line));
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
tail.on('error', (err) => {
|
|
505
|
+
if (err.code === 'ENOENT') {
|
|
506
|
+
console.error(chalk.red('\n❌ Log file not found. Agent activity will be logged here once tasks start.\n'));
|
|
507
|
+
console.log(chalk.dim(`Expected location: ${LOG_FILE}\n`));
|
|
508
|
+
} else {
|
|
509
|
+
console.error(chalk.red('❌ Error watching log:'), err.message);
|
|
510
|
+
}
|
|
511
|
+
process.exit(1);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// Handle graceful shutdown
|
|
515
|
+
process.on('SIGINT', () => {
|
|
516
|
+
console.log(chalk.yellow('\n\n👋 Stopped watching agent activity\n'));
|
|
517
|
+
tail.kill();
|
|
518
|
+
process.exit(0);
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
program
|
|
523
|
+
.command('commentate')
|
|
524
|
+
.description('Live commentary on agent activity')
|
|
525
|
+
.action(async () => {
|
|
526
|
+
const { startCommentator } = await import('../lib/commentator.js');
|
|
527
|
+
startCommentator();
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
// Hierarchy enforcement commands
|
|
531
|
+
program
|
|
532
|
+
.command('compliance-report')
|
|
533
|
+
.description('Generate hierarchy compliance report')
|
|
534
|
+
.option('-q, --quarterly', 'Show quarterly metrics')
|
|
535
|
+
.action(async (options) => {
|
|
536
|
+
try {
|
|
537
|
+
console.log(chalk.blue('\n📊 HIERARCHY COMPLIANCE REPORT\n'));
|
|
538
|
+
console.log(chalk.dim('━'.repeat(80)));
|
|
539
|
+
|
|
540
|
+
const report = hierarchyValidator.getViolationReport();
|
|
541
|
+
|
|
542
|
+
console.log(chalk.bold('\n📈 Overall Metrics:'));
|
|
543
|
+
console.log(` Total Validations: ${chalk.green(report.totalValidations)}`);
|
|
544
|
+
console.log(` Total Violations: ${chalk.red(report.totalViolations)}`);
|
|
545
|
+
|
|
546
|
+
if (report.totalValidations + report.totalViolations > 0) {
|
|
547
|
+
const complianceRate = report.complianceRate.toFixed(1);
|
|
548
|
+
const rateColor = report.complianceRate >= 80 ? chalk.green : report.complianceRate >= 60 ? chalk.yellow : chalk.red;
|
|
549
|
+
console.log(` Compliance Rate: ${rateColor(complianceRate + '%')}`);
|
|
550
|
+
} else {
|
|
551
|
+
console.log(` Compliance Rate: ${chalk.dim('N/A (no data)')}`);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (report.mostViolatedAgents.length > 0) {
|
|
555
|
+
console.log(chalk.bold('\n🚨 Most Violated Agents:'));
|
|
556
|
+
report.mostViolatedAgents.slice(0, 5).forEach((agent, idx) => {
|
|
557
|
+
console.log(` ${idx + 1}. ${chalk.yellow(agent.agent)}: ${chalk.red(agent.violations)} violations`);
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (report.violations.length > 0) {
|
|
562
|
+
console.log(chalk.bold('\n📋 Recent Violations:'));
|
|
563
|
+
report.violations.slice(-5).forEach(v => {
|
|
564
|
+
const date = new Date(v.timestamp).toLocaleString();
|
|
565
|
+
console.log(` ${chalk.dim(date)} - ${chalk.yellow(v.agent)}`);
|
|
566
|
+
console.log(` ${chalk.gray(v.reason)}`);
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
console.log(chalk.dim('\n━'.repeat(80)));
|
|
571
|
+
console.log(chalk.dim('💡 Use violations to improve delegation patterns\n'));
|
|
572
|
+
|
|
573
|
+
} catch (error) {
|
|
574
|
+
console.error(chalk.red('❌ Error generating compliance report:'), error.message);
|
|
575
|
+
process.exit(1);
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
program
|
|
580
|
+
.command('validate-agent')
|
|
581
|
+
.description('Validate that an agent follows hierarchy rules')
|
|
582
|
+
.argument('<agent-name>', 'Agent name to validate')
|
|
583
|
+
.option('-t, --task <task>', 'Task description (optional)')
|
|
584
|
+
.action(async (agentName, options) => {
|
|
585
|
+
try {
|
|
586
|
+
console.log(chalk.blue(`\n🔍 Validating agent: ${chalk.bold(agentName)}\n`));
|
|
587
|
+
|
|
588
|
+
// Check if agent exists in registry
|
|
589
|
+
const workerInfo = AGENT_HIERARCHY.workers[agentName];
|
|
590
|
+
const bossInfo = AGENT_HIERARCHY.bosses[agentName];
|
|
591
|
+
|
|
592
|
+
if (!workerInfo && !bossInfo) {
|
|
593
|
+
console.log(chalk.red('❌ Agent not found in hierarchy registry'));
|
|
594
|
+
console.log(chalk.dim('\nRegistered workers:'));
|
|
595
|
+
Object.keys(AGENT_HIERARCHY.workers).forEach(w => {
|
|
596
|
+
console.log(` - ${chalk.cyan(w)}`);
|
|
597
|
+
});
|
|
598
|
+
console.log(chalk.dim('\nRegistered bosses:'));
|
|
599
|
+
Object.keys(AGENT_HIERARCHY.bosses).forEach(b => {
|
|
600
|
+
console.log(` - ${chalk.green(b)}`);
|
|
601
|
+
});
|
|
602
|
+
process.exit(1);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if (workerInfo) {
|
|
606
|
+
console.log(chalk.bold('Agent Type:'), chalk.cyan('Worker'));
|
|
607
|
+
console.log(chalk.bold('Domain:'), chalk.green(workerInfo.domain));
|
|
608
|
+
console.log(chalk.bold('Assigned Boss:'), chalk.yellow(workerInfo.boss));
|
|
609
|
+
console.log(chalk.bold('Description:'), chalk.gray(workerInfo.description));
|
|
610
|
+
|
|
611
|
+
const bossInfo = AGENT_HIERARCHY.bosses[workerInfo.boss];
|
|
612
|
+
console.log(chalk.bold('Reports To:'), chalk.magenta(bossInfo.metaBoss));
|
|
613
|
+
console.log(chalk.green('\n✅ Valid worker agent in hierarchy'));
|
|
614
|
+
} else if (bossInfo) {
|
|
615
|
+
console.log(chalk.bold('Agent Type:'), chalk.yellow('Boss'));
|
|
616
|
+
console.log(chalk.bold('Domain:'), chalk.green(bossInfo.domain));
|
|
617
|
+
console.log(chalk.bold('Reports To:'), chalk.magenta(bossInfo.metaBoss));
|
|
618
|
+
console.log(chalk.bold('Description:'), chalk.gray(bossInfo.description));
|
|
619
|
+
console.log(chalk.bold('Manages Workers:'));
|
|
620
|
+
bossInfo.workers.forEach(w => {
|
|
621
|
+
console.log(` - ${chalk.cyan(w)}`);
|
|
622
|
+
});
|
|
623
|
+
console.log(chalk.green('\n✅ Valid boss agent in hierarchy'));
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
console.log();
|
|
627
|
+
|
|
628
|
+
} catch (error) {
|
|
629
|
+
console.error(chalk.red('❌ Error validating agent:'), error.message);
|
|
630
|
+
process.exit(1);
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
program
|
|
635
|
+
.command('hierarchy-tree')
|
|
636
|
+
.description('Display the complete agent hierarchy tree')
|
|
637
|
+
.action(() => {
|
|
638
|
+
try {
|
|
639
|
+
console.log(chalk.bold.blue('\n🌳 AGENT HIERARCHY TREE\n'));
|
|
640
|
+
console.log(chalk.dim('━'.repeat(80)));
|
|
641
|
+
|
|
642
|
+
console.log(chalk.bold('\nTier 3: Meta-Boss'));
|
|
643
|
+
console.log(` ${chalk.magenta('┗━ ' + AGENT_HIERARCHY.metaBoss)} ${chalk.dim('(final security gate)')}`);
|
|
644
|
+
|
|
645
|
+
console.log(chalk.bold('\nTier 2: Domain Bosses'));
|
|
646
|
+
Object.entries(AGENT_HIERARCHY.bosses).forEach(([name, info]) => {
|
|
647
|
+
console.log(` ${chalk.yellow('┣━ ' + name)} ${chalk.dim('(' + info.domain + ')')}`);
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
console.log(chalk.bold('\nTier 1: Worker Agents'));
|
|
651
|
+
Object.entries(AGENT_HIERARCHY.workers).forEach(([name, info], idx, arr) => {
|
|
652
|
+
const isLast = idx === arr.length - 1;
|
|
653
|
+
const prefix = isLast ? '┗━' : '┣━';
|
|
654
|
+
console.log(` ${chalk.cyan(prefix + ' ' + name)} ${chalk.dim('→ ' + info.boss)}`);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
console.log(chalk.dim('\n━'.repeat(80)));
|
|
658
|
+
console.log(chalk.dim('💡 Use "boss-claude validate-agent <name>" for details\n'));
|
|
659
|
+
|
|
660
|
+
} catch (error) {
|
|
661
|
+
console.error(chalk.red('❌ Error displaying hierarchy:'), error.message);
|
|
662
|
+
process.exit(1);
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
// ONYX Reminder commands
|
|
667
|
+
program
|
|
668
|
+
.command('onyx:show')
|
|
669
|
+
.description('Display ONYX orchestrator reminder')
|
|
670
|
+
.action(async () => {
|
|
671
|
+
try {
|
|
672
|
+
await onyxReminder.showOnyxReminder();
|
|
673
|
+
} catch (error) {
|
|
674
|
+
console.error(chalk.red('❌ Error showing ONYX reminder:'), error.message);
|
|
675
|
+
process.exit(1);
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
program
|
|
680
|
+
.command('onyx:status')
|
|
681
|
+
.description('Show ONYX reminder status and message count')
|
|
682
|
+
.action(async () => {
|
|
683
|
+
try {
|
|
684
|
+
const count = await onyxReminder.getOnyxMessageCount();
|
|
685
|
+
const interval = await onyxReminder.getOnyxInterval();
|
|
686
|
+
|
|
687
|
+
console.log(chalk.blue('\n⚡ ONYX REMINDER STATUS\n'));
|
|
688
|
+
console.log(chalk.bold('Configuration:'));
|
|
689
|
+
console.log(` Message Count: ${chalk.cyan(count)}`);
|
|
690
|
+
console.log(` Reminder Interval: ${chalk.yellow('Every ' + interval + ' messages')}`);
|
|
691
|
+
console.log(` Next Reminder: ${chalk.green('In ' + (interval - (count % interval)) + ' messages')}\n`);
|
|
692
|
+
|
|
693
|
+
if (count % interval === 0 && count > 0) {
|
|
694
|
+
console.log(chalk.yellow('⚡ Reminder is due NOW!\n'));
|
|
695
|
+
await onyxReminder.showOnyxReminder();
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
} catch (error) {
|
|
699
|
+
console.error(chalk.red('❌ Error getting ONYX status:'), error.message);
|
|
700
|
+
process.exit(1);
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
program
|
|
705
|
+
.command('onyx:set-interval')
|
|
706
|
+
.description('Set ONYX reminder interval')
|
|
707
|
+
.argument('<interval>', 'Number of messages between reminders')
|
|
708
|
+
.action(async (interval) => {
|
|
709
|
+
try {
|
|
710
|
+
const num = parseInt(interval, 10);
|
|
711
|
+
if (isNaN(num) || num < 1) {
|
|
712
|
+
console.error(chalk.red('❌ Interval must be a positive number\n'));
|
|
713
|
+
process.exit(1);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
await onyxReminder.setOnyxInterval(num);
|
|
717
|
+
console.log(chalk.green(`\n✅ ONYX reminder interval set to ${num} messages\n`));
|
|
718
|
+
|
|
719
|
+
} catch (error) {
|
|
720
|
+
console.error(chalk.red('❌ Error setting interval:'), error.message);
|
|
721
|
+
process.exit(1);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
program
|
|
726
|
+
.command('onyx:reset')
|
|
727
|
+
.description('Reset ONYX message counter to zero')
|
|
728
|
+
.action(async () => {
|
|
729
|
+
try {
|
|
730
|
+
await onyxReminder.resetOnyxCounter();
|
|
731
|
+
console.log(chalk.green('\n✅ ONYX message counter reset to zero\n'));
|
|
732
|
+
|
|
733
|
+
} catch (error) {
|
|
734
|
+
console.error(chalk.red('❌ Error resetting counter:'), error.message);
|
|
735
|
+
process.exit(1);
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
// Checkpoint commands - ONYX delegation accountability
|
|
740
|
+
program
|
|
741
|
+
.command('checkpoint:status')
|
|
742
|
+
.description('Show current checkpoint status and delegation efficiency')
|
|
743
|
+
.action(async () => {
|
|
744
|
+
try {
|
|
745
|
+
const status = await getCheckpointStatus();
|
|
746
|
+
const efficiency = await getDelegationEfficiency();
|
|
747
|
+
|
|
748
|
+
if (!status) {
|
|
749
|
+
console.log(chalk.yellow('\n⚠️ No active checkpoint session\n'));
|
|
750
|
+
console.log(chalk.dim('Checkpoint tracking starts automatically when ONYX begins work\n'));
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
console.log(chalk.blue('\n⚡ CHECKPOINT STATUS\n'));
|
|
755
|
+
console.log(chalk.dim('━'.repeat(80)));
|
|
756
|
+
|
|
757
|
+
console.log(chalk.bold('\n📊 Session Progress:'));
|
|
758
|
+
console.log(` Repository: ${chalk.cyan(status.repo)}`);
|
|
759
|
+
console.log(` Messages: ${chalk.yellow(status.message_count)}`);
|
|
760
|
+
console.log(` Checkpoints Passed: ${chalk.green(status.checkpoints_passed)}`);
|
|
761
|
+
console.log(` Next Checkpoint: ${chalk.magenta('In ' + (5 - (status.message_count % 5)) + ' messages')}`);
|
|
762
|
+
|
|
763
|
+
console.log(chalk.bold('\n🎯 Delegation Efficiency:'));
|
|
764
|
+
console.log(` Delegation Rate: ${chalk.green(efficiency.delegation_rate + '%')}`);
|
|
765
|
+
console.log(` Delegations: ${chalk.cyan(efficiency.delegations)}`);
|
|
766
|
+
console.log(` Token Burns: ${chalk.red(efficiency.token_burns)}`);
|
|
767
|
+
|
|
768
|
+
console.log(chalk.bold('\n💰 Token Economics:'));
|
|
769
|
+
console.log(` Tokens Saved: ${chalk.green('+' + efficiency.tokens_saved.toLocaleString())}`);
|
|
770
|
+
console.log(` Tokens Burned: ${chalk.red('-' + efficiency.tokens_burned.toLocaleString())}`);
|
|
771
|
+
console.log(` Net Savings: ${chalk.bold(efficiency.net_savings.toLocaleString())}`);
|
|
772
|
+
console.log(` Efficiency Score: ${chalk.yellow(efficiency.efficiency_score + '%')}`);
|
|
773
|
+
|
|
774
|
+
if (status.awaiting_response) {
|
|
775
|
+
console.log(chalk.bold.yellow('\n⏳ CHECKPOINT PENDING - Awaiting delegation decision'));
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
console.log(chalk.dim('\n━'.repeat(80)));
|
|
779
|
+
console.log(chalk.dim('💡 Use "boss-claude checkpoint:history" to see past decisions\n'));
|
|
780
|
+
|
|
781
|
+
} catch (error) {
|
|
782
|
+
console.error(chalk.red('❌ Error getting checkpoint status:'), error.message);
|
|
783
|
+
process.exit(1);
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
program
|
|
788
|
+
.command('checkpoint:record')
|
|
789
|
+
.description('Record checkpoint decision (delegated or burned)')
|
|
790
|
+
.option('-d, --delegated', 'Task was delegated to specialist')
|
|
791
|
+
.option('-b, --burned', 'Task was done directly (tokens burned)')
|
|
792
|
+
.option('-t, --tokens <number>', 'Tokens saved (if delegated) or burned (if not)', '0')
|
|
793
|
+
.option('-s, --specialist <name>', 'Specialist name (if delegated)')
|
|
794
|
+
.option('-j, --justification <text>', 'Justification for decision', '')
|
|
795
|
+
.action(async (options) => {
|
|
796
|
+
try {
|
|
797
|
+
if (!options.delegated && !options.burned) {
|
|
798
|
+
console.error(chalk.red('❌ Must specify either --delegated or --burned\n'));
|
|
799
|
+
process.exit(1);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if (options.delegated && options.burned) {
|
|
803
|
+
console.error(chalk.red('❌ Cannot specify both --delegated and --burned\n'));
|
|
804
|
+
process.exit(1);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const delegated = options.delegated;
|
|
808
|
+
const tokens = parseInt(options.tokens, 10);
|
|
809
|
+
const specialist = options.specialist || '';
|
|
810
|
+
const justification = options.justification;
|
|
811
|
+
|
|
812
|
+
if (delegated && !specialist) {
|
|
813
|
+
console.error(chalk.red('❌ Specialist name required when delegated\n'));
|
|
814
|
+
process.exit(1);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const result = await recordCheckpoint(
|
|
818
|
+
delegated,
|
|
819
|
+
delegated ? tokens : 0,
|
|
820
|
+
delegated ? 0 : tokens,
|
|
821
|
+
justification,
|
|
822
|
+
specialist
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
console.log(chalk.green('\n✅ Checkpoint recorded!\n'));
|
|
826
|
+
console.log(chalk.bold('Decision:'));
|
|
827
|
+
console.log(` Type: ${delegated ? chalk.green('DELEGATED') : chalk.red('BURNED')}`);
|
|
828
|
+
console.log(` Tokens: ${chalk.cyan(tokens.toLocaleString())}`);
|
|
829
|
+
if (specialist) {
|
|
830
|
+
console.log(` Specialist: ${chalk.yellow(specialist)}`);
|
|
831
|
+
}
|
|
832
|
+
if (justification) {
|
|
833
|
+
console.log(` Justification: ${chalk.gray(justification)}`);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
console.log(chalk.bold('\nUpdated Stats:'));
|
|
837
|
+
console.log(` Total Checkpoints: ${chalk.green(result.checkpoint.checkpoints_passed)}`);
|
|
838
|
+
console.log(` Delegation Rate: ${chalk.cyan(((result.checkpoint.delegation_count / result.checkpoint.checkpoints_passed) * 100).toFixed(1) + '%')}`);
|
|
839
|
+
|
|
840
|
+
console.log();
|
|
841
|
+
|
|
842
|
+
} catch (error) {
|
|
843
|
+
console.error(chalk.red('❌ Error recording checkpoint:'), error.message);
|
|
844
|
+
process.exit(1);
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
program
|
|
849
|
+
.command('checkpoint:history')
|
|
850
|
+
.description('Show checkpoint history')
|
|
851
|
+
.option('-l, --limit <number>', 'Number of records to show', '20')
|
|
852
|
+
.action(async (options) => {
|
|
853
|
+
try {
|
|
854
|
+
const limit = parseInt(options.limit, 10);
|
|
855
|
+
const history = await getCheckpointHistory(limit);
|
|
856
|
+
|
|
857
|
+
if (history.length === 0) {
|
|
858
|
+
console.log(chalk.yellow('\n⚠️ No checkpoint history found\n'));
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
console.log(chalk.blue(`\n📋 CHECKPOINT HISTORY (Last ${history.length} records)\n`));
|
|
863
|
+
console.log(chalk.dim('━'.repeat(80)));
|
|
864
|
+
|
|
865
|
+
history.forEach((record, idx) => {
|
|
866
|
+
const date = new Date(record.timestamp).toLocaleString();
|
|
867
|
+
const decision = record.delegated ? chalk.green('DELEGATED') : chalk.red('BURNED');
|
|
868
|
+
const tokens = record.delegated ? record.tokens_saved : record.tokens_burned;
|
|
869
|
+
|
|
870
|
+
console.log(chalk.bold(`\n${idx + 1}. ${date}`));
|
|
871
|
+
console.log(` Decision: ${decision}`);
|
|
872
|
+
console.log(` Tokens: ${chalk.cyan(tokens.toLocaleString())}`);
|
|
873
|
+
if (record.specialist) {
|
|
874
|
+
console.log(` Specialist: ${chalk.yellow(record.specialist)}`);
|
|
875
|
+
}
|
|
876
|
+
if (record.justification) {
|
|
877
|
+
console.log(` Justification: ${chalk.gray(record.justification)}`);
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
console.log(chalk.dim('\n━'.repeat(80)));
|
|
882
|
+
console.log(chalk.dim('💡 Use "boss-claude checkpoint:status" for current stats\n'));
|
|
883
|
+
|
|
884
|
+
} catch (error) {
|
|
885
|
+
console.error(chalk.red('❌ Error getting checkpoint history:'), error.message);
|
|
886
|
+
process.exit(1);
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
program
|
|
891
|
+
.command('checkpoint:export')
|
|
892
|
+
.description('Export checkpoint data for analysis')
|
|
893
|
+
.action(async () => {
|
|
894
|
+
try {
|
|
895
|
+
const data = await exportCheckpointData();
|
|
896
|
+
|
|
897
|
+
console.log(chalk.blue('\n📦 CHECKPOINT DATA EXPORT\n'));
|
|
898
|
+
console.log(JSON.stringify(data, null, 2));
|
|
899
|
+
console.log();
|
|
900
|
+
|
|
901
|
+
} catch (error) {
|
|
902
|
+
console.error(chalk.red('❌ Error exporting checkpoint data:'), error.message);
|
|
903
|
+
process.exit(1);
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
program
|
|
908
|
+
.command('checkpoint:reset')
|
|
909
|
+
.description('Reset checkpoint tracking for new session')
|
|
910
|
+
.action(async () => {
|
|
911
|
+
try {
|
|
912
|
+
const newCheckpoint = await resetCheckpoint();
|
|
913
|
+
|
|
914
|
+
console.log(chalk.green('\n✅ Checkpoint reset complete!\n'));
|
|
915
|
+
console.log(chalk.bold('New Session:'));
|
|
916
|
+
console.log(` Repository: ${chalk.cyan(newCheckpoint.repo)}`);
|
|
917
|
+
console.log(` Started: ${chalk.gray(newCheckpoint.started_at)}`);
|
|
918
|
+
console.log(` Checkpoints will trigger every ${chalk.yellow('5 messages')}\n`);
|
|
919
|
+
|
|
920
|
+
} catch (error) {
|
|
921
|
+
console.error(chalk.red('❌ Error resetting checkpoint:'), error.message);
|
|
922
|
+
process.exit(1);
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
// ONYX Delegation Monitoring Commands
|
|
927
|
+
program
|
|
928
|
+
.command('onyx-status')
|
|
929
|
+
.description('Show ONYX delegation ratio and monitoring status')
|
|
930
|
+
.action(async () => {
|
|
931
|
+
try {
|
|
932
|
+
const status = await getFormattedStatus();
|
|
933
|
+
console.log(status);
|
|
934
|
+
} catch (error) {
|
|
935
|
+
console.error(chalk.red('❌ Error getting ONYX status:'), error.message);
|
|
936
|
+
process.exit(1);
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
program
|
|
941
|
+
.command('onyx-report')
|
|
942
|
+
.description('Generate comprehensive ONYX delegation report')
|
|
943
|
+
.option('-e, --events <limit>', 'Number of recent events to include', '50')
|
|
944
|
+
.action(async (options) => {
|
|
945
|
+
try {
|
|
946
|
+
console.log(chalk.blue('\n📊 ONYX DELEGATION REPORT\n'));
|
|
947
|
+
console.log(chalk.dim('━'.repeat(80)));
|
|
948
|
+
|
|
949
|
+
const report = await generateReport({ eventLimit: parseInt(options.events) });
|
|
950
|
+
|
|
951
|
+
console.log(chalk.bold('\n📈 Overall Statistics:'));
|
|
952
|
+
console.log(` Total Actions: ${chalk.cyan(report.overall.total_actions)}`);
|
|
953
|
+
console.log(` Delegations: ${chalk.green(report.overall.total_delegations)}`);
|
|
954
|
+
console.log(` Direct Actions: ${chalk.yellow(report.overall.total_direct_actions)}`);
|
|
955
|
+
console.log(` Delegation Ratio: ${chalk.bold(report.overall.delegation_percentage + '%')}`);
|
|
956
|
+
console.log(` Alert Threshold: ${chalk.gray(report.overall.threshold_percentage + '%')}`);
|
|
957
|
+
|
|
958
|
+
const statusIcon = report.overall.meets_threshold ? chalk.green('✅ PASSING') : chalk.red('⚠️ BELOW THRESHOLD');
|
|
959
|
+
console.log(` Status: ${statusIcon}`);
|
|
960
|
+
|
|
961
|
+
console.log(chalk.bold('\n⏱️ Time-Based Metrics:'));
|
|
962
|
+
console.log(chalk.dim(' Last 24 Hours:'));
|
|
963
|
+
console.log(` Delegations: ${chalk.green(report.time_periods.last_24h.delegations)}`);
|
|
964
|
+
console.log(` Direct Actions: ${chalk.yellow(report.time_periods.last_24h.direct_actions)}`);
|
|
965
|
+
console.log(` Ratio: ${chalk.cyan(report.time_periods.last_24h.percentage + '%')}`);
|
|
966
|
+
|
|
967
|
+
console.log(chalk.dim(' Last 7 Days:'));
|
|
968
|
+
console.log(` Delegations: ${chalk.green(report.time_periods.last_7d.delegations)}`);
|
|
969
|
+
console.log(` Direct Actions: ${chalk.yellow(report.time_periods.last_7d.direct_actions)}`);
|
|
970
|
+
console.log(` Ratio: ${chalk.cyan(report.time_periods.last_7d.percentage + '%')}`);
|
|
971
|
+
|
|
972
|
+
if (Object.keys(report.overall.agent_breakdown).length > 0) {
|
|
973
|
+
console.log(chalk.bold('\n🤖 Agent Delegation Breakdown:'));
|
|
974
|
+
Object.entries(report.overall.agent_breakdown)
|
|
975
|
+
.sort((a, b) => b[1] - a[1])
|
|
976
|
+
.forEach(([agent, count]) => {
|
|
977
|
+
console.log(` ${chalk.cyan(agent)}: ${chalk.green(count)} tasks`);
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
if (Object.keys(report.overall.direct_action_breakdown).length > 0) {
|
|
982
|
+
console.log(chalk.bold('\n⚡ Direct Action Breakdown:'));
|
|
983
|
+
Object.entries(report.overall.direct_action_breakdown)
|
|
984
|
+
.sort((a, b) => b[1] - a[1])
|
|
985
|
+
.forEach(([type, count]) => {
|
|
986
|
+
console.log(` ${chalk.yellow(type)}: ${chalk.red(count)} actions`);
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
if (report.recent_events.length > 0) {
|
|
991
|
+
console.log(chalk.bold('\n📋 Recent Events:'));
|
|
992
|
+
report.recent_events.slice(0, 10).forEach(event => {
|
|
993
|
+
const time = new Date(event.timestamp).toLocaleTimeString();
|
|
994
|
+
if (event.type === 'delegation') {
|
|
995
|
+
console.log(` ${chalk.dim(time)} ${chalk.green('→')} Delegated to ${chalk.cyan(event.agent)}: ${chalk.gray(event.task)}`);
|
|
996
|
+
} else {
|
|
997
|
+
console.log(` ${chalk.dim(time)} ${chalk.red('•')} Direct action ${chalk.yellow(event.action_type)}: ${chalk.gray(event.description)}`);
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
console.log(chalk.dim('\n━'.repeat(80)));
|
|
1003
|
+
console.log(chalk.dim(`Generated: ${new Date(report.generated_at).toLocaleString()}`));
|
|
1004
|
+
console.log(chalk.dim(`Alert Log: ${ALERT_LOG}\n`));
|
|
1005
|
+
|
|
1006
|
+
} catch (error) {
|
|
1007
|
+
console.error(chalk.red('❌ Error generating report:'), error.message);
|
|
1008
|
+
process.exit(1);
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
program
|
|
1013
|
+
.command('onyx-track-delegation')
|
|
1014
|
+
.description('Track a task delegated to ONYX agent')
|
|
1015
|
+
.argument('<agent>', 'ONYX agent name')
|
|
1016
|
+
.argument('<task>', 'Task description')
|
|
1017
|
+
.action(async (agent, task) => {
|
|
1018
|
+
try {
|
|
1019
|
+
const stats = await trackDelegation(agent, task);
|
|
1020
|
+
|
|
1021
|
+
console.log(chalk.green(`\n✅ Delegation tracked: ${chalk.bold(agent)}`));
|
|
1022
|
+
console.log(` Task: ${chalk.gray(task)}`);
|
|
1023
|
+
console.log(` Current Ratio: ${chalk.cyan(stats.delegation_percentage + '%')} (${stats.total_delegations}/${stats.total_actions})`);
|
|
1024
|
+
|
|
1025
|
+
if (!stats.meets_threshold) {
|
|
1026
|
+
console.log(chalk.yellow(` ⚠️ Below threshold of ${stats.threshold_percentage}%`));
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
console.log();
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
console.error(chalk.red('❌ Error tracking delegation:'), error.message);
|
|
1032
|
+
process.exit(1);
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
program
|
|
1037
|
+
.command('onyx-track-direct')
|
|
1038
|
+
.description('Track a direct action (not delegated to ONYX)')
|
|
1039
|
+
.argument('<type>', 'Action type (e.g., file_edit, command_execution)')
|
|
1040
|
+
.argument('<description>', 'Action description')
|
|
1041
|
+
.action(async (type, description) => {
|
|
1042
|
+
try {
|
|
1043
|
+
const stats = await trackDirectAction(type, description);
|
|
1044
|
+
|
|
1045
|
+
console.log(chalk.yellow(`\n⚡ Direct action tracked: ${chalk.bold(type)}`));
|
|
1046
|
+
console.log(` Description: ${chalk.gray(description)}`);
|
|
1047
|
+
console.log(` Current Ratio: ${chalk.cyan(stats.delegation_percentage + '%')} (${stats.total_delegations}/${stats.total_actions})`);
|
|
1048
|
+
|
|
1049
|
+
if (!stats.meets_threshold) {
|
|
1050
|
+
console.log(chalk.red(` ⚠️ ALERT: Below threshold of ${stats.threshold_percentage}%`));
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
console.log();
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
console.error(chalk.red('❌ Error tracking direct action:'), error.message);
|
|
1056
|
+
process.exit(1);
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
program
|
|
1061
|
+
.command('onyx-set-threshold')
|
|
1062
|
+
.description('Set ONYX delegation ratio alert threshold')
|
|
1063
|
+
.argument('<threshold>', 'Threshold percentage (e.g., 95 for 95%)')
|
|
1064
|
+
.action(async (threshold) => {
|
|
1065
|
+
try {
|
|
1066
|
+
const num = parseFloat(threshold);
|
|
1067
|
+
if (isNaN(num) || num < 0 || num > 100) {
|
|
1068
|
+
console.error(chalk.red('❌ Threshold must be between 0 and 100\n'));
|
|
1069
|
+
process.exit(1);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
const newThreshold = await setAlertThreshold(num / 100);
|
|
1073
|
+
console.log(chalk.green(`\n✅ Alert threshold set to ${chalk.bold((newThreshold * 100).toFixed(0) + '%')}\n`));
|
|
1074
|
+
console.log(chalk.dim(' Alerts will trigger when delegation ratio drops below this threshold\n'));
|
|
1075
|
+
|
|
1076
|
+
} catch (error) {
|
|
1077
|
+
console.error(chalk.red('❌ Error setting threshold:'), error.message);
|
|
1078
|
+
process.exit(1);
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
program
|
|
1083
|
+
.command('onyx-events')
|
|
1084
|
+
.description('Show recent ONYX delegation and direct action events')
|
|
1085
|
+
.option('-l, --limit <number>', 'Number of events to show', '20')
|
|
1086
|
+
.action(async (options) => {
|
|
1087
|
+
try {
|
|
1088
|
+
const events = await getRecentEvents(parseInt(options.limit));
|
|
1089
|
+
|
|
1090
|
+
console.log(chalk.blue(`\n📜 Recent ONYX Events (${events.length})\n`));
|
|
1091
|
+
console.log(chalk.dim('━'.repeat(80)));
|
|
1092
|
+
|
|
1093
|
+
events.forEach((event, idx) => {
|
|
1094
|
+
const time = new Date(event.timestamp).toLocaleString();
|
|
1095
|
+
|
|
1096
|
+
if (event.type === 'delegation') {
|
|
1097
|
+
console.log(`\n${chalk.dim(`${idx + 1}.`)} ${chalk.green('DELEGATION')} ${chalk.dim(time)}`);
|
|
1098
|
+
console.log(` Agent: ${chalk.cyan(event.agent)}`);
|
|
1099
|
+
console.log(` Task: ${chalk.gray(event.task)}`);
|
|
1100
|
+
if (event.metadata && Object.keys(event.metadata).length > 0) {
|
|
1101
|
+
console.log(` Metadata: ${chalk.dim(JSON.stringify(event.metadata))}`);
|
|
1102
|
+
}
|
|
1103
|
+
} else {
|
|
1104
|
+
console.log(`\n${chalk.dim(`${idx + 1}.`)} ${chalk.yellow('DIRECT ACTION')} ${chalk.dim(time)}`);
|
|
1105
|
+
console.log(` Type: ${chalk.yellow(event.action_type)}`);
|
|
1106
|
+
console.log(` Description: ${chalk.gray(event.description)}`);
|
|
1107
|
+
if (event.metadata && Object.keys(event.metadata).length > 0) {
|
|
1108
|
+
console.log(` Metadata: ${chalk.dim(JSON.stringify(event.metadata))}`);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
console.log(chalk.dim('\n━'.repeat(80)));
|
|
1114
|
+
console.log(chalk.dim('💡 Use "boss-claude onyx-report" for comprehensive analysis\n'));
|
|
1115
|
+
|
|
1116
|
+
} catch (error) {
|
|
1117
|
+
console.error(chalk.red('❌ Error fetching events:'), error.message);
|
|
1118
|
+
process.exit(1);
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
program
|
|
1123
|
+
.command('onyx-reset-tracking')
|
|
1124
|
+
.description('Reset all ONYX delegation tracking data')
|
|
1125
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
1126
|
+
.action(async (options) => {
|
|
1127
|
+
try {
|
|
1128
|
+
console.log(chalk.yellow('\n⚠️ WARNING: This will reset all ONYX delegation tracking data!\n'));
|
|
1129
|
+
|
|
1130
|
+
if (!options.confirm) {
|
|
1131
|
+
const { default: inquirer } = await import('inquirer');
|
|
1132
|
+
const { confirm } = await inquirer.prompt([
|
|
1133
|
+
{
|
|
1134
|
+
type: 'confirm',
|
|
1135
|
+
name: 'confirm',
|
|
1136
|
+
message: 'Are you sure you want to reset all tracking data?',
|
|
1137
|
+
default: false
|
|
1138
|
+
}
|
|
1139
|
+
]);
|
|
1140
|
+
|
|
1141
|
+
if (!confirm) {
|
|
1142
|
+
console.log(chalk.yellow('Reset cancelled\n'));
|
|
1143
|
+
process.exit(0);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
await resetTracking();
|
|
1148
|
+
console.log(chalk.green('✅ ONYX delegation tracking has been reset\n'));
|
|
1149
|
+
|
|
1150
|
+
} catch (error) {
|
|
1151
|
+
console.error(chalk.red('❌ Error resetting tracking:'), error.message);
|
|
1152
|
+
process.exit(1);
|
|
1153
|
+
}
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
program
|
|
1157
|
+
.command('onyx-banner')
|
|
1158
|
+
.description('Display ONYX MODE banner (for testing and integration)')
|
|
1159
|
+
.option('-c, --compact', 'Display compact version')
|
|
1160
|
+
.option('-s, --stats', 'Display with statistics')
|
|
1161
|
+
.option('--no-color', 'Disable colorization')
|
|
1162
|
+
.action(async (options) => {
|
|
1163
|
+
try {
|
|
1164
|
+
const { displayOnyxBanner, displayOnyxBannerCompact, getBannerWithStats } = await import('../lib/onyx-banner.js');
|
|
1165
|
+
|
|
1166
|
+
if (options.stats) {
|
|
1167
|
+
console.log(getBannerWithStats(true));
|
|
1168
|
+
} else if (options.compact) {
|
|
1169
|
+
console.log(displayOnyxBannerCompact(options.color));
|
|
1170
|
+
} else {
|
|
1171
|
+
console.log(displayOnyxBanner(options.color));
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
} catch (error) {
|
|
1175
|
+
console.error(chalk.red('❌ Error displaying banner:'), error.message);
|
|
1176
|
+
process.exit(1);
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
// Mode enforcement commands
|
|
1181
|
+
program
|
|
1182
|
+
.command('mode')
|
|
1183
|
+
.description('Manage orchestrator mode enforcement')
|
|
1184
|
+
.argument('[subcommand]', 'Mode command (orchestrator|specialist|worker|review|learning|history|stats|blocked|reset|status)')
|
|
1185
|
+
.allowUnknownOption()
|
|
1186
|
+
.action(async (subcommand, options, command) => {
|
|
1187
|
+
try {
|
|
1188
|
+
// Pass all remaining arguments to mode command handler
|
|
1189
|
+
const args = command.args.slice(1);
|
|
1190
|
+
if (subcommand) {
|
|
1191
|
+
args.unshift(subcommand);
|
|
1192
|
+
}
|
|
1193
|
+
await modeCommand(args);
|
|
1194
|
+
} catch (error) {
|
|
1195
|
+
console.error(chalk.red('❌ Mode command failed:'), error.message);
|
|
1196
|
+
process.exit(1);
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
// Agent management commands (kill stuck workers)
|
|
1201
|
+
program
|
|
1202
|
+
.command('agent:list')
|
|
1203
|
+
.description('List currently running agents')
|
|
1204
|
+
.action(async () => {
|
|
1205
|
+
try {
|
|
1206
|
+
console.log(chalk.blue('\n🔍 Scanning for running agents...\n'));
|
|
1207
|
+
|
|
1208
|
+
// Check for Claude task processes
|
|
1209
|
+
const { execSync } = await import('child_process');
|
|
1210
|
+
try {
|
|
1211
|
+
const psOutput = execSync('ps aux | grep -E "claude|task" | grep -v grep', { encoding: 'utf8' });
|
|
1212
|
+
const lines = psOutput.trim().split('\n').filter(l => l.trim());
|
|
1213
|
+
|
|
1214
|
+
if (lines.length === 0) {
|
|
1215
|
+
console.log(chalk.gray('No agents currently running.'));
|
|
1216
|
+
} else {
|
|
1217
|
+
console.log(chalk.bold('Running Processes:'));
|
|
1218
|
+
lines.forEach(line => {
|
|
1219
|
+
const parts = line.split(/\s+/);
|
|
1220
|
+
const pid = parts[1];
|
|
1221
|
+
const cmd = parts.slice(10).join(' ').substring(0, 60);
|
|
1222
|
+
console.log(` ${chalk.cyan(pid)} - ${chalk.dim(cmd)}`);
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
} catch (e) {
|
|
1226
|
+
console.log(chalk.gray('No agents currently running.'));
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
} catch (error) {
|
|
1230
|
+
console.error(chalk.red('❌ Error listing agents:'), error.message);
|
|
1231
|
+
process.exit(1);
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
program
|
|
1236
|
+
.command('agent:kill')
|
|
1237
|
+
.description('Kill a stuck agent by PID')
|
|
1238
|
+
.argument('<pid>', 'Process ID to kill')
|
|
1239
|
+
.option('-f, --force', 'Force kill (SIGKILL instead of SIGTERM)')
|
|
1240
|
+
.action(async (pid, options) => {
|
|
1241
|
+
try {
|
|
1242
|
+
const signal = options.force ? 'SIGKILL' : 'SIGTERM';
|
|
1243
|
+
console.log(chalk.yellow(`\n🛑 Sending ${signal} to PID ${pid}...\n`));
|
|
1244
|
+
|
|
1245
|
+
const { execSync } = await import('child_process');
|
|
1246
|
+
execSync(`kill -${signal === 'SIGKILL' ? '9' : '15'} ${pid}`);
|
|
1247
|
+
|
|
1248
|
+
console.log(chalk.green(`✅ Signal sent to process ${pid}`));
|
|
1249
|
+
console.log(chalk.gray(' Agent should terminate shortly.'));
|
|
1250
|
+
|
|
1251
|
+
} catch (error) {
|
|
1252
|
+
if (error.message.includes('No such process')) {
|
|
1253
|
+
console.log(chalk.yellow(`⚠️ Process ${pid} not found (may have already exited)`));
|
|
1254
|
+
} else {
|
|
1255
|
+
console.error(chalk.red('❌ Error killing agent:'), error.message);
|
|
1256
|
+
process.exit(1);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
program
|
|
1262
|
+
.command('agent:kill-all')
|
|
1263
|
+
.description('Kill all stuck agents (nuclear option)')
|
|
1264
|
+
.option('-f, --force', 'Force kill all')
|
|
1265
|
+
.action(async (options) => {
|
|
1266
|
+
try {
|
|
1267
|
+
console.log(chalk.red.bold('\n⚠️ NUCLEAR OPTION: Killing all agent processes...\n'));
|
|
1268
|
+
|
|
1269
|
+
const { execSync } = await import('child_process');
|
|
1270
|
+
const signal = options.force ? '9' : '15';
|
|
1271
|
+
|
|
1272
|
+
try {
|
|
1273
|
+
// Kill claude task-related processes (but not the main Claude process)
|
|
1274
|
+
execSync(`pkill -${signal} -f "claude.*task" 2>/dev/null || true`, { encoding: 'utf8' });
|
|
1275
|
+
console.log(chalk.green('✅ Kill signal sent to all task agents'));
|
|
1276
|
+
} catch (e) {
|
|
1277
|
+
console.log(chalk.yellow('⚠️ No agents found to kill'));
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
console.log(chalk.gray('\nNote: Main Claude process is preserved.'));
|
|
1281
|
+
|
|
1282
|
+
} catch (error) {
|
|
1283
|
+
console.error(chalk.red('❌ Error:'), error.message);
|
|
1284
|
+
process.exit(1);
|
|
1285
|
+
}
|
|
1286
|
+
});
|
|
1287
|
+
|
|
150
1288
|
program.parse();
|