@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,188 @@
1
+ import Redis from 'ioredis';
2
+ import dotenv from 'dotenv';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+ import { existsSync } from 'fs';
6
+ import os from 'os';
7
+ import chalk from 'chalk';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+
12
+ // Load environment variables from ~/.boss-claude/.env
13
+ const envPath = join(os.homedir(), '.boss-claude', '.env');
14
+ if (existsSync(envPath)) {
15
+ dotenv.config({ path: envPath });
16
+ }
17
+
18
+ let redis = null;
19
+
20
+ function getRedis() {
21
+ if (!redis) {
22
+ if (!process.env.REDIS_URL) {
23
+ throw new Error('REDIS_URL not found. Please run: boss-claude init');
24
+ }
25
+ redis = new Redis(process.env.REDIS_URL);
26
+ }
27
+ return redis;
28
+ }
29
+
30
+ const REMINDER_KEY = 'boss:onyx:message_count';
31
+ const DEFAULT_INTERVAL = 5; // Show reminder every 5 messages
32
+
33
+ /**
34
+ * Increment message counter and check if reminder should be displayed
35
+ * @param {number} interval - Number of messages between reminders (default: 5)
36
+ * @returns {Promise<{shouldShow: boolean, count: number, reminder: string|null}>}
37
+ */
38
+ export async function checkOnyxReminder(interval = DEFAULT_INTERVAL) {
39
+ try {
40
+ const client = getRedis();
41
+
42
+ // Increment counter
43
+ const count = await client.incr(REMINDER_KEY);
44
+
45
+ // Check if we should show reminder
46
+ const shouldShow = count % interval === 0;
47
+
48
+ let reminder = null;
49
+ if (shouldShow) {
50
+ reminder = formatReminder(count);
51
+ }
52
+
53
+ return {
54
+ shouldShow,
55
+ count,
56
+ reminder
57
+ };
58
+ } catch (error) {
59
+ console.error('ONYX Reminder Error:', error.message);
60
+ return {
61
+ shouldShow: false,
62
+ count: 0,
63
+ reminder: null
64
+ };
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Format the ONYX reminder message
70
+ * @param {number} count - Current message count
71
+ * @returns {string}
72
+ */
73
+ function formatReminder(count) {
74
+ return `
75
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
76
+ ⚔ CRITICAL REMINDER ⚔
77
+
78
+ ${chalk.bold.red('ONYX = ORCHESTRATOR ONLY')}
79
+
80
+ ${chalk.yellow('ONYX must NEVER execute tasks directly.')}
81
+ ${chalk.yellow('ONYX must ALWAYS delegate to specialized engineers.')}
82
+
83
+ Message Count: ${count}
84
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
85
+ `;
86
+ }
87
+
88
+ /**
89
+ * Reset the message counter (useful for new sessions)
90
+ * @returns {Promise<void>}
91
+ */
92
+ export async function resetOnyxCounter() {
93
+ try {
94
+ const client = getRedis();
95
+ await client.del(REMINDER_KEY);
96
+ } catch (error) {
97
+ console.error('Error resetting ONYX counter:', error.message);
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Get current message count
103
+ * @returns {Promise<number>}
104
+ */
105
+ export async function getOnyxMessageCount() {
106
+ try {
107
+ const client = getRedis();
108
+ const count = await client.get(REMINDER_KEY);
109
+ return parseInt(count || '0', 10);
110
+ } catch (error) {
111
+ console.error('Error getting ONYX count:', error.message);
112
+ return 0;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Set custom interval for reminders
118
+ * @param {number} interval - Number of messages between reminders
119
+ * @returns {Promise<void>}
120
+ */
121
+ export async function setOnyxInterval(interval) {
122
+ try {
123
+ const client = getRedis();
124
+ await client.set('boss:onyx:interval', interval);
125
+ } catch (error) {
126
+ console.error('Error setting ONYX interval:', error.message);
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Get current reminder interval
132
+ * @returns {Promise<number>}
133
+ */
134
+ export async function getOnyxInterval() {
135
+ try {
136
+ const client = getRedis();
137
+ const interval = await client.get('boss:onyx:interval');
138
+ return parseInt(interval || DEFAULT_INTERVAL.toString(), 10);
139
+ } catch (error) {
140
+ console.error('Error getting ONYX interval:', error.message);
141
+ return DEFAULT_INTERVAL;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Auto-hook for Claude Code - Call this after every message
147
+ * This is the main function to integrate into conversation flow
148
+ * @param {number} customInterval - Optional custom interval
149
+ * @returns {Promise<string|null>}
150
+ */
151
+ export async function onyxAutoReminder(customInterval = null) {
152
+ try {
153
+ const interval = customInterval || await getOnyxInterval();
154
+ const { shouldShow, reminder } = await checkOnyxReminder(interval);
155
+
156
+ if (shouldShow && reminder) {
157
+ console.log(reminder);
158
+ return reminder;
159
+ }
160
+
161
+ return null;
162
+ } catch (error) {
163
+ console.error('ONYX Auto-Reminder Error:', error.message);
164
+ return null;
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Manual trigger - Force display the reminder
170
+ * @returns {Promise<string>}
171
+ */
172
+ export async function showOnyxReminder() {
173
+ const count = await getOnyxMessageCount();
174
+ const reminder = formatReminder(count);
175
+ console.log(reminder);
176
+ return reminder;
177
+ }
178
+
179
+ // Export for CLI usage
180
+ export default {
181
+ checkOnyxReminder,
182
+ resetOnyxCounter,
183
+ getOnyxMessageCount,
184
+ setOnyxInterval,
185
+ getOnyxInterval,
186
+ onyxAutoReminder,
187
+ showOnyxReminder
188
+ };
@@ -0,0 +1,341 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ONYX TOOL INTERCEPTOR - Pre-Hook System
4
+ *
5
+ * Automatically intercepts Read/Write/Edit/Bash/Grep/Glob tool calls
6
+ * and blocks them, forcing delegation to Task tool instead.
7
+ *
8
+ * EXECUTABLE CODE - Not documentation
9
+ */
10
+
11
+ import chalk from 'chalk';
12
+ import inquirer from 'inquirer';
13
+
14
+ // Define ONYX agent name
15
+ const ONYX_AGENT = 'ONYX';
16
+
17
+ // Tools forbidden for ONYX (must delegate instead)
18
+ const FORBIDDEN_TOOLS = [
19
+ 'Read',
20
+ 'Write',
21
+ 'Edit',
22
+ 'Bash',
23
+ 'Grep',
24
+ 'Glob',
25
+ 'NotebookEdit'
26
+ ];
27
+
28
+ // ONYX is allowed to use these tools
29
+ const ALLOWED_TOOLS = [
30
+ 'Task', // Primary delegation tool
31
+ 'WebFetch', // Research allowed
32
+ 'WebSearch', // Research allowed
33
+ 'TodoWrite', // Planning allowed
34
+ 'Skill' // Can invoke skills
35
+ ];
36
+
37
+ /**
38
+ * ONYX Tool Interceptor Class
39
+ * Hooks into tool calls and enforces delegation rules
40
+ */
41
+ export class OnyxToolInterceptor {
42
+ constructor(config = {}) {
43
+ this.agentName = config.agentName || ONYX_AGENT;
44
+ this.forbiddenTools = config.forbiddenTools || FORBIDDEN_TOOLS;
45
+ this.allowedTools = config.allowedTools || ALLOWED_TOOLS;
46
+ this.violations = [];
47
+ this.blockCount = 0;
48
+ this.delegationCount = 0;
49
+ this.enabled = true;
50
+ }
51
+
52
+ /**
53
+ * PRE-HOOK: Intercept tool call before execution
54
+ * Returns { allowed: boolean, reason: string, delegateTo?: string }
55
+ */
56
+ async interceptToolCall(toolName, toolParams = {}) {
57
+ if (!this.enabled) {
58
+ return { allowed: true, reason: 'Interceptor disabled' };
59
+ }
60
+
61
+ // Check if this is ONYX agent
62
+ if (this.agentName !== ONYX_AGENT) {
63
+ return { allowed: true, reason: 'Not ONYX agent - no restrictions' };
64
+ }
65
+
66
+ // Check if tool is forbidden
67
+ const isForbidden = this.forbiddenTools.includes(toolName);
68
+ const isAllowed = this.allowedTools.includes(toolName);
69
+
70
+ if (isForbidden) {
71
+ this.blockCount++;
72
+
73
+ console.log(chalk.red('\n🚨 ONYX TOOL BLOCK TRIGGERED'));
74
+ console.log(chalk.dim('═'.repeat(80)));
75
+ console.log(chalk.white(`Agent: ${chalk.bold(this.agentName)}`));
76
+ console.log(chalk.white(`Forbidden Tool: ${chalk.red(toolName)}`));
77
+ console.log(chalk.white(`Params: ${chalk.dim(JSON.stringify(toolParams, null, 2))}`));
78
+
79
+ console.log(chalk.yellow('\nšŸ“‹ ONYX DELEGATION RULE:'));
80
+ console.log(chalk.dim(' ONYX is META-BOSS and NEVER touches files directly'));
81
+ console.log(chalk.dim(' ONYX delegates to Task tool → Task tool uses file operations'));
82
+ console.log(chalk.red(` FORBIDDEN: ONYX → ${toolName} (direct file access)`));
83
+ console.log(chalk.green(` ALLOWED: ONYX → Task → ${toolName} (delegated access)`));
84
+
85
+ console.log(chalk.dim('\n═'.repeat(80)));
86
+
87
+ // Generate delegation message
88
+ const delegationMessage = this._generateDelegationMessage(toolName, toolParams);
89
+
90
+ // Prompt user for action
91
+ const action = await this._promptBlockedAction(toolName, delegationMessage);
92
+
93
+ if (action.action === 'delegate') {
94
+ this.delegationCount++;
95
+ console.log(chalk.green('\nāœ… Delegating to Task tool...'));
96
+ console.log(chalk.dim(`Message: ${delegationMessage}`));
97
+
98
+ return {
99
+ allowed: false,
100
+ blocked: true,
101
+ reason: 'ONYX cannot use file tools directly - must delegate to Task',
102
+ delegateTo: 'Task',
103
+ delegationMessage,
104
+ toolName,
105
+ toolParams
106
+ };
107
+ }
108
+
109
+ if (action.action === 'override') {
110
+ // Log violation but allow
111
+ this._recordViolation(toolName, toolParams, action.justification);
112
+
113
+ console.log(chalk.yellow('\nāš ļø Override granted - violation logged'));
114
+ console.log(chalk.dim(`Justification: ${action.justification}`));
115
+
116
+ return {
117
+ allowed: true,
118
+ override: true,
119
+ reason: `Override: ${action.justification}`,
120
+ violation: true
121
+ };
122
+ }
123
+
124
+ if (action.action === 'reject') {
125
+ console.log(chalk.red('\nāŒ Tool call rejected - ONYX must delegate'));
126
+
127
+ return {
128
+ allowed: false,
129
+ blocked: true,
130
+ rejected: true,
131
+ reason: 'ONYX delegation rule violation - must use Task tool'
132
+ };
133
+ }
134
+ }
135
+
136
+ if (isAllowed) {
137
+ // Tool is explicitly allowed for ONYX
138
+ return {
139
+ allowed: true,
140
+ reason: `${toolName} is allowed for ONYX`
141
+ };
142
+ }
143
+
144
+ // Unknown tool - allow but warn
145
+ console.log(chalk.yellow(`\nāš ļø Unknown tool for ONYX: ${toolName}`));
146
+ console.log(chalk.dim('Allowing by default - add to ALLOWED_TOOLS or FORBIDDEN_TOOLS'));
147
+
148
+ return {
149
+ allowed: true,
150
+ reason: 'Unknown tool - allowed by default',
151
+ warning: true
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Generate Task tool delegation message
157
+ */
158
+ _generateDelegationMessage(toolName, toolParams) {
159
+ const messages = {
160
+ Read: `Read the file at ${toolParams.file_path || '[file_path]'}`,
161
+ Write: `Write content to ${toolParams.file_path || '[file_path]'}`,
162
+ Edit: `Edit ${toolParams.file_path || '[file_path]'} - replace "${(toolParams.old_string || '').substring(0, 50)}..." with new content`,
163
+ Bash: `Execute bash command: ${toolParams.command || '[command]'}`,
164
+ Grep: `Search for pattern "${toolParams.pattern || '[pattern]'}" in codebase`,
165
+ Glob: `Find files matching pattern "${toolParams.pattern || '[pattern]'}"`
166
+ };
167
+
168
+ const baseMessage = messages[toolName] || `Execute ${toolName} tool`;
169
+
170
+ return `ONYX DELEGATION: ${baseMessage}\n\nReason: ONYX is meta-boss and delegates file operations to specialist agents.`;
171
+ }
172
+
173
+ /**
174
+ * Prompt user when tool is blocked
175
+ */
176
+ async _promptBlockedAction(toolName, delegationMessage) {
177
+ const choices = [
178
+ {
179
+ name: `āœ… Delegate to Task tool (RECOMMENDED)`,
180
+ value: 'delegate',
181
+ short: 'Delegate'
182
+ },
183
+ {
184
+ name: `āš ļø Override: Allow ONYX to use ${toolName} (logs violation)`,
185
+ value: 'override',
186
+ short: 'Override'
187
+ },
188
+ {
189
+ name: `āŒ Reject: Block this operation entirely`,
190
+ value: 'reject',
191
+ short: 'Reject'
192
+ }
193
+ ];
194
+
195
+ const answer = await inquirer.prompt([
196
+ {
197
+ type: 'list',
198
+ name: 'action',
199
+ message: chalk.bold(`ONYX attempted to use ${chalk.red(toolName)}. How to proceed?`),
200
+ choices,
201
+ default: 0
202
+ }
203
+ ]);
204
+
205
+ if (answer.action === 'override') {
206
+ const justification = await inquirer.prompt([
207
+ {
208
+ type: 'input',
209
+ name: 'justification',
210
+ message: chalk.yellow('Provide justification for override (will be logged):'),
211
+ validate: (input) => {
212
+ if (input.length < 20) {
213
+ return 'Justification must be at least 20 characters';
214
+ }
215
+ return true;
216
+ }
217
+ }
218
+ ]);
219
+
220
+ return {
221
+ action: 'override',
222
+ justification: justification.justification
223
+ };
224
+ }
225
+
226
+ return { action: answer.action };
227
+ }
228
+
229
+ /**
230
+ * Record violation for audit trail
231
+ */
232
+ _recordViolation(toolName, toolParams, justification) {
233
+ const violation = {
234
+ timestamp: new Date().toISOString(),
235
+ agent: this.agentName,
236
+ tool: toolName,
237
+ params: toolParams,
238
+ justification,
239
+ severity: 'medium'
240
+ };
241
+
242
+ this.violations.push(violation);
243
+
244
+ return violation;
245
+ }
246
+
247
+ /**
248
+ * Get statistics
249
+ */
250
+ getStats() {
251
+ return {
252
+ blockCount: this.blockCount,
253
+ delegationCount: this.delegationCount,
254
+ violationCount: this.violations.length,
255
+ complianceRate: this.delegationCount / (this.delegationCount + this.violations.length) * 100 || 0,
256
+ violations: this.violations
257
+ };
258
+ }
259
+
260
+ /**
261
+ * Print report
262
+ */
263
+ printReport() {
264
+ const stats = this.getStats();
265
+
266
+ console.log(chalk.blue('\nšŸ“Š ONYX TOOL INTERCEPTOR REPORT'));
267
+ console.log(chalk.dim('═'.repeat(80)));
268
+ console.log(chalk.white(`Total Blocks: ${chalk.magenta(stats.blockCount)}`));
269
+ console.log(chalk.white(`Delegations: ${chalk.green(stats.delegationCount)}`));
270
+ console.log(chalk.white(`Violations: ${chalk.red(stats.violationCount)}`));
271
+ console.log(chalk.white(`Compliance Rate: ${chalk.cyan(stats.complianceRate.toFixed(1) + '%')}`));
272
+ console.log(chalk.dim('═'.repeat(80)));
273
+
274
+ if (stats.violations.length > 0) {
275
+ console.log(chalk.yellow('\nāš ļø VIOLATIONS:'));
276
+ stats.violations.forEach((v, i) => {
277
+ console.log(chalk.dim(`\n${i + 1}. ${v.tool} - ${v.timestamp}`));
278
+ console.log(chalk.dim(` Justification: ${v.justification}`));
279
+ });
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Enable/disable interceptor
285
+ */
286
+ setEnabled(enabled) {
287
+ this.enabled = enabled;
288
+ console.log(chalk.cyan(`\nONYX Tool Interceptor: ${enabled ? 'ENABLED' : 'DISABLED'}`));
289
+ }
290
+
291
+ /**
292
+ * Reset counters
293
+ */
294
+ reset() {
295
+ this.blockCount = 0;
296
+ this.delegationCount = 0;
297
+ this.violations = [];
298
+ console.log(chalk.green('\nāœ… ONYX Tool Interceptor reset'));
299
+ }
300
+ }
301
+
302
+ // Singleton instance
303
+ const interceptor = new OnyxToolInterceptor();
304
+
305
+ export default interceptor;
306
+
307
+ /**
308
+ * INTEGRATION EXAMPLE:
309
+ *
310
+ * // In ONYX agent wrapper or Claude Code integration:
311
+ *
312
+ * import onyxInterceptor from '@cpretzinger/boss-claude/lib/onyx-tool-interceptor.js';
313
+ *
314
+ * async function executeToolCall(toolName, toolParams) {
315
+ * // PRE-HOOK: Intercept before execution
316
+ * const result = await onyxInterceptor.interceptToolCall(toolName, toolParams);
317
+ *
318
+ * if (!result.allowed) {
319
+ * if (result.delegateTo === 'Task') {
320
+ * // Auto-delegate to Task tool
321
+ * return await executeTaskTool({
322
+ * description: result.delegationMessage,
323
+ * context: { originalTool: toolName, originalParams: toolParams }
324
+ * });
325
+ * }
326
+ *
327
+ * // Tool call blocked
328
+ * throw new Error(result.reason);
329
+ * }
330
+ *
331
+ * if (result.override) {
332
+ * console.warn('ONYX delegation override - violation logged');
333
+ * }
334
+ *
335
+ * // Execute original tool
336
+ * return await executeTool(toolName, toolParams);
337
+ * }
338
+ *
339
+ * // Print report at end of session
340
+ * onyxInterceptor.printReport();
341
+ */