@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/lib/prompts.js
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BOSS CLAUDE INTERACTIVE CLI PROMPTS
|
|
3
|
+
* Beautiful, reusable prompt system with validation and colors
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
|
|
10
|
+
// ==================== COLOR THEME ====================
|
|
11
|
+
export const theme = {
|
|
12
|
+
primary: chalk.hex('#00D9FF'), // Cyan
|
|
13
|
+
success: chalk.hex('#00FF88'), // Green
|
|
14
|
+
warning: chalk.hex('#FFB800'), // Orange
|
|
15
|
+
error: chalk.hex('#FF3366'), // Red
|
|
16
|
+
info: chalk.hex('#A78BFA'), // Purple
|
|
17
|
+
muted: chalk.gray,
|
|
18
|
+
bold: chalk.bold,
|
|
19
|
+
dim: chalk.dim,
|
|
20
|
+
|
|
21
|
+
// Gradients (simulated with multiple colors)
|
|
22
|
+
gradient: (text) => {
|
|
23
|
+
const colors = ['#00D9FF', '#00FF88', '#FFB800'];
|
|
24
|
+
return text.split('').map((char, i) =>
|
|
25
|
+
chalk.hex(colors[i % colors.length])(char)
|
|
26
|
+
).join('');
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// ==================== VALIDATORS ====================
|
|
31
|
+
export const validators = {
|
|
32
|
+
required: (input) => {
|
|
33
|
+
return input.trim() !== '' || 'This field is required';
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
email: (input) => {
|
|
37
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
38
|
+
return emailRegex.test(input) || 'Please enter a valid email address';
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
url: (input) => {
|
|
42
|
+
try {
|
|
43
|
+
new URL(input);
|
|
44
|
+
return true;
|
|
45
|
+
} catch {
|
|
46
|
+
return 'Please enter a valid URL';
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
number: (input) => {
|
|
51
|
+
return !isNaN(parseFloat(input)) || 'Please enter a valid number';
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
integer: (input) => {
|
|
55
|
+
return Number.isInteger(Number(input)) || 'Please enter a whole number';
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
positive: (input) => {
|
|
59
|
+
return parseFloat(input) > 0 || 'Please enter a positive number';
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
minLength: (min) => (input) => {
|
|
63
|
+
return input.length >= min || `Must be at least ${min} characters`;
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
maxLength: (max) => (input) => {
|
|
67
|
+
return input.length <= max || `Must be no more than ${max} characters`;
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
pattern: (regex, message) => (input) => {
|
|
71
|
+
return regex.test(input) || message;
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
custom: (fn, message) => (input) => {
|
|
75
|
+
return fn(input) || message;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// ==================== PROMPT FUNCTIONS ====================
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Text input prompt
|
|
83
|
+
*/
|
|
84
|
+
export async function promptText({
|
|
85
|
+
message,
|
|
86
|
+
defaultValue = '',
|
|
87
|
+
validate = null,
|
|
88
|
+
transform = null,
|
|
89
|
+
placeholder = ''
|
|
90
|
+
}) {
|
|
91
|
+
const answers = await inquirer.prompt([{
|
|
92
|
+
type: 'input',
|
|
93
|
+
name: 'value',
|
|
94
|
+
message: theme.primary(message),
|
|
95
|
+
default: defaultValue,
|
|
96
|
+
validate: validate || (() => true),
|
|
97
|
+
transformer: transform,
|
|
98
|
+
prefix: theme.bold('?'),
|
|
99
|
+
suffix: placeholder ? theme.dim(` (${placeholder})`) : ''
|
|
100
|
+
}]);
|
|
101
|
+
|
|
102
|
+
return answers.value;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Password/secret input (hidden)
|
|
107
|
+
*/
|
|
108
|
+
export async function promptPassword({
|
|
109
|
+
message,
|
|
110
|
+
validate = null,
|
|
111
|
+
mask = '*'
|
|
112
|
+
}) {
|
|
113
|
+
const answers = await inquirer.prompt([{
|
|
114
|
+
type: 'password',
|
|
115
|
+
name: 'value',
|
|
116
|
+
message: theme.primary(message),
|
|
117
|
+
mask,
|
|
118
|
+
validate: validate || validators.required,
|
|
119
|
+
prefix: theme.bold('?')
|
|
120
|
+
}]);
|
|
121
|
+
|
|
122
|
+
return answers.value;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Confirmation prompt (yes/no)
|
|
127
|
+
*/
|
|
128
|
+
export async function promptConfirm({
|
|
129
|
+
message,
|
|
130
|
+
defaultValue = false
|
|
131
|
+
}) {
|
|
132
|
+
const answers = await inquirer.prompt([{
|
|
133
|
+
type: 'confirm',
|
|
134
|
+
name: 'value',
|
|
135
|
+
message: theme.primary(message),
|
|
136
|
+
default: defaultValue,
|
|
137
|
+
prefix: theme.bold('?')
|
|
138
|
+
}]);
|
|
139
|
+
|
|
140
|
+
return answers.value;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Select from list (single choice)
|
|
145
|
+
*/
|
|
146
|
+
export async function promptSelect({
|
|
147
|
+
message,
|
|
148
|
+
choices,
|
|
149
|
+
defaultValue = null,
|
|
150
|
+
loop = true
|
|
151
|
+
}) {
|
|
152
|
+
const formattedChoices = choices.map(choice => {
|
|
153
|
+
if (typeof choice === 'string') {
|
|
154
|
+
return { name: choice, value: choice };
|
|
155
|
+
}
|
|
156
|
+
return choice;
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const answers = await inquirer.prompt([{
|
|
160
|
+
type: 'list',
|
|
161
|
+
name: 'value',
|
|
162
|
+
message: theme.primary(message),
|
|
163
|
+
choices: formattedChoices,
|
|
164
|
+
default: defaultValue,
|
|
165
|
+
loop,
|
|
166
|
+
prefix: theme.bold('?')
|
|
167
|
+
}]);
|
|
168
|
+
|
|
169
|
+
return answers.value;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Multi-select from list (checkboxes)
|
|
174
|
+
*/
|
|
175
|
+
export async function promptMultiSelect({
|
|
176
|
+
message,
|
|
177
|
+
choices,
|
|
178
|
+
defaultValues = [],
|
|
179
|
+
validate = null
|
|
180
|
+
}) {
|
|
181
|
+
const formattedChoices = choices.map(choice => {
|
|
182
|
+
if (typeof choice === 'string') {
|
|
183
|
+
return {
|
|
184
|
+
name: choice,
|
|
185
|
+
value: choice,
|
|
186
|
+
checked: defaultValues.includes(choice)
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
...choice,
|
|
191
|
+
checked: choice.checked || defaultValues.includes(choice.value)
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const answers = await inquirer.prompt([{
|
|
196
|
+
type: 'checkbox',
|
|
197
|
+
name: 'value',
|
|
198
|
+
message: theme.primary(message),
|
|
199
|
+
choices: formattedChoices,
|
|
200
|
+
validate: validate || ((answer) => answer.length > 0 || 'Select at least one option'),
|
|
201
|
+
prefix: theme.bold('?')
|
|
202
|
+
}]);
|
|
203
|
+
|
|
204
|
+
return answers.value;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Number input with validation
|
|
209
|
+
*/
|
|
210
|
+
export async function promptNumber({
|
|
211
|
+
message,
|
|
212
|
+
defaultValue = null,
|
|
213
|
+
min = null,
|
|
214
|
+
max = null,
|
|
215
|
+
integer = false
|
|
216
|
+
}) {
|
|
217
|
+
const validate = (input) => {
|
|
218
|
+
if (integer && !Number.isInteger(Number(input))) {
|
|
219
|
+
return 'Please enter a whole number';
|
|
220
|
+
}
|
|
221
|
+
const num = parseFloat(input);
|
|
222
|
+
if (isNaN(num)) return 'Please enter a valid number';
|
|
223
|
+
if (min !== null && num < min) return `Must be at least ${min}`;
|
|
224
|
+
if (max !== null && num > max) return `Must be no more than ${max}`;
|
|
225
|
+
return true;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const result = await promptText({
|
|
229
|
+
message,
|
|
230
|
+
defaultValue: defaultValue !== null ? String(defaultValue) : '',
|
|
231
|
+
validate
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
return integer ? parseInt(result, 10) : parseFloat(result);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Editor prompt (opens text editor)
|
|
239
|
+
*/
|
|
240
|
+
export async function promptEditor({
|
|
241
|
+
message,
|
|
242
|
+
defaultValue = '',
|
|
243
|
+
validate = null
|
|
244
|
+
}) {
|
|
245
|
+
const answers = await inquirer.prompt([{
|
|
246
|
+
type: 'editor',
|
|
247
|
+
name: 'value',
|
|
248
|
+
message: theme.primary(message),
|
|
249
|
+
default: defaultValue,
|
|
250
|
+
validate: validate || (() => true),
|
|
251
|
+
prefix: theme.bold('?')
|
|
252
|
+
}]);
|
|
253
|
+
|
|
254
|
+
return answers.value;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Autocomplete search prompt
|
|
259
|
+
*/
|
|
260
|
+
export async function promptSearch({
|
|
261
|
+
message,
|
|
262
|
+
choices,
|
|
263
|
+
defaultValue = ''
|
|
264
|
+
}) {
|
|
265
|
+
// Note: Requires inquirer-autocomplete-prompt plugin
|
|
266
|
+
// For now, fallback to regular select with search
|
|
267
|
+
return promptSelect({ message, choices, defaultValue });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ==================== SPINNER UTILITIES ====================
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Show loading spinner
|
|
274
|
+
*/
|
|
275
|
+
export function spinner(text, type = 'dots') {
|
|
276
|
+
return ora({
|
|
277
|
+
text: theme.info(text),
|
|
278
|
+
spinner: type,
|
|
279
|
+
color: 'cyan'
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Run async task with spinner
|
|
285
|
+
*/
|
|
286
|
+
export async function withSpinner(text, task) {
|
|
287
|
+
const spin = spinner(text).start();
|
|
288
|
+
try {
|
|
289
|
+
const result = await task();
|
|
290
|
+
spin.succeed(theme.success(text + ' ✓'));
|
|
291
|
+
return result;
|
|
292
|
+
} catch (error) {
|
|
293
|
+
spin.fail(theme.error(text + ' ✗'));
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ==================== DISPLAY UTILITIES ====================
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Print styled header
|
|
302
|
+
*/
|
|
303
|
+
export function header(text) {
|
|
304
|
+
console.log('\n' + theme.bold(theme.gradient(text)));
|
|
305
|
+
console.log(theme.dim('─'.repeat(text.length)) + '\n');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Print success message
|
|
310
|
+
*/
|
|
311
|
+
export function success(text) {
|
|
312
|
+
console.log(theme.success('✓ ') + text);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Print error message
|
|
317
|
+
*/
|
|
318
|
+
export function error(text) {
|
|
319
|
+
console.log(theme.error('✗ ') + text);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Print warning message
|
|
324
|
+
*/
|
|
325
|
+
export function warning(text) {
|
|
326
|
+
console.log(theme.warning('⚠ ') + text);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Print info message
|
|
331
|
+
*/
|
|
332
|
+
export function info(text) {
|
|
333
|
+
console.log(theme.info('ℹ ') + text);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Print muted/dim text
|
|
338
|
+
*/
|
|
339
|
+
export function muted(text) {
|
|
340
|
+
console.log(theme.muted(text));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Print table
|
|
345
|
+
*/
|
|
346
|
+
export function table(data) {
|
|
347
|
+
console.table(data);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Print key-value pairs
|
|
352
|
+
*/
|
|
353
|
+
export function keyValue(pairs) {
|
|
354
|
+
const maxKeyLength = Math.max(...Object.keys(pairs).map(k => k.length));
|
|
355
|
+
|
|
356
|
+
Object.entries(pairs).forEach(([key, value]) => {
|
|
357
|
+
const paddedKey = key.padEnd(maxKeyLength);
|
|
358
|
+
console.log(theme.dim(paddedKey) + ' : ' + theme.primary(value));
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Print divider
|
|
364
|
+
*/
|
|
365
|
+
export function divider(char = '─', length = 50) {
|
|
366
|
+
console.log(theme.dim(char.repeat(length)));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Clear console
|
|
371
|
+
*/
|
|
372
|
+
export function clear() {
|
|
373
|
+
console.clear();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Print blank line
|
|
378
|
+
*/
|
|
379
|
+
export function blank(count = 1) {
|
|
380
|
+
console.log('\n'.repeat(count - 1));
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ==================== PRESET PROMPTS ====================
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Prompt for Redis connection
|
|
387
|
+
*/
|
|
388
|
+
export async function promptRedisConnection() {
|
|
389
|
+
header('Redis Connection');
|
|
390
|
+
|
|
391
|
+
const host = await promptText({
|
|
392
|
+
message: 'Redis host',
|
|
393
|
+
defaultValue: 'localhost',
|
|
394
|
+
validate: validators.required
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
const port = await promptNumber({
|
|
398
|
+
message: 'Redis port',
|
|
399
|
+
defaultValue: 6379,
|
|
400
|
+
min: 1,
|
|
401
|
+
max: 65535,
|
|
402
|
+
integer: true
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const requiresAuth = await promptConfirm({
|
|
406
|
+
message: 'Requires authentication?',
|
|
407
|
+
defaultValue: false
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
let password = '';
|
|
411
|
+
if (requiresAuth) {
|
|
412
|
+
password = await promptPassword({
|
|
413
|
+
message: 'Redis password'
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return { host, port, password };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Prompt for API key
|
|
422
|
+
*/
|
|
423
|
+
export async function promptApiKey(serviceName) {
|
|
424
|
+
header(`${serviceName} API Key`);
|
|
425
|
+
|
|
426
|
+
const apiKey = await promptPassword({
|
|
427
|
+
message: `Enter ${serviceName} API key`,
|
|
428
|
+
validate: validators.required
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
return apiKey;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Prompt for project setup
|
|
436
|
+
*/
|
|
437
|
+
export async function promptProjectSetup() {
|
|
438
|
+
header('Project Setup');
|
|
439
|
+
|
|
440
|
+
const name = await promptText({
|
|
441
|
+
message: 'Project name',
|
|
442
|
+
validate: validators.required,
|
|
443
|
+
placeholder: 'my-awesome-project'
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const description = await promptText({
|
|
447
|
+
message: 'Description (optional)',
|
|
448
|
+
defaultValue: ''
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
const type = await promptSelect({
|
|
452
|
+
message: 'Project type',
|
|
453
|
+
choices: [
|
|
454
|
+
{ name: 'Node.js Library', value: 'library' },
|
|
455
|
+
{ name: 'CLI Tool', value: 'cli' },
|
|
456
|
+
{ name: 'Web App', value: 'web' },
|
|
457
|
+
{ name: 'API Service', value: 'api' }
|
|
458
|
+
]
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const features = await promptMultiSelect({
|
|
462
|
+
message: 'Select features',
|
|
463
|
+
choices: [
|
|
464
|
+
'TypeScript',
|
|
465
|
+
'Testing',
|
|
466
|
+
'Linting',
|
|
467
|
+
'CI/CD',
|
|
468
|
+
'Documentation'
|
|
469
|
+
]
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
return { name, description, type, features };
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Prompt for confirmation with warning
|
|
477
|
+
*/
|
|
478
|
+
export async function promptDangerousAction(action, details = '') {
|
|
479
|
+
warning(`You are about to ${action}`);
|
|
480
|
+
if (details) {
|
|
481
|
+
muted(details);
|
|
482
|
+
}
|
|
483
|
+
blank();
|
|
484
|
+
|
|
485
|
+
return promptConfirm({
|
|
486
|
+
message: 'Are you sure you want to continue?',
|
|
487
|
+
defaultValue: false
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// ==================== EXPORTS ====================
|
|
492
|
+
|
|
493
|
+
export default {
|
|
494
|
+
// Core prompts
|
|
495
|
+
promptText,
|
|
496
|
+
promptPassword,
|
|
497
|
+
promptConfirm,
|
|
498
|
+
promptSelect,
|
|
499
|
+
promptMultiSelect,
|
|
500
|
+
promptNumber,
|
|
501
|
+
promptEditor,
|
|
502
|
+
promptSearch,
|
|
503
|
+
|
|
504
|
+
// Spinner utilities
|
|
505
|
+
spinner,
|
|
506
|
+
withSpinner,
|
|
507
|
+
|
|
508
|
+
// Display utilities
|
|
509
|
+
header,
|
|
510
|
+
success,
|
|
511
|
+
error,
|
|
512
|
+
warning,
|
|
513
|
+
info,
|
|
514
|
+
muted,
|
|
515
|
+
table,
|
|
516
|
+
keyValue,
|
|
517
|
+
divider,
|
|
518
|
+
clear,
|
|
519
|
+
blank,
|
|
520
|
+
|
|
521
|
+
// Preset prompts
|
|
522
|
+
promptRedisConnection,
|
|
523
|
+
promptApiKey,
|
|
524
|
+
promptProjectSetup,
|
|
525
|
+
promptDangerousAction,
|
|
526
|
+
|
|
527
|
+
// Validators
|
|
528
|
+
validators,
|
|
529
|
+
|
|
530
|
+
// Theme
|
|
531
|
+
theme
|
|
532
|
+
};
|
package/lib/session.js
CHANGED
|
@@ -66,7 +66,11 @@ export async function loadSession() {
|
|
|
66
66
|
repo,
|
|
67
67
|
started_at: new Date().toISOString(),
|
|
68
68
|
messages: [],
|
|
69
|
-
tokens_used: 0
|
|
69
|
+
tokens_used: 0,
|
|
70
|
+
// Track conductor vs agent tokens for efficiency bonus
|
|
71
|
+
onyx_tokens: 0, // Tokens used by ONYX (orchestration overhead)
|
|
72
|
+
agent_tokens: 0, // Tokens used by spawned agents (actual work)
|
|
73
|
+
delegations: 0 // Number of Task tool delegations
|
|
70
74
|
};
|
|
71
75
|
}
|
|
72
76
|
|
|
@@ -90,7 +94,10 @@ export async function saveSession(summary, tags) {
|
|
|
90
94
|
const session = sessionData ? JSON.parse(sessionData) : {
|
|
91
95
|
started_at: new Date().toISOString(),
|
|
92
96
|
messages: [],
|
|
93
|
-
tokens_used: 0
|
|
97
|
+
tokens_used: 0,
|
|
98
|
+
onyx_tokens: 0,
|
|
99
|
+
agent_tokens: 0,
|
|
100
|
+
delegations: 0
|
|
94
101
|
};
|
|
95
102
|
|
|
96
103
|
// Save to GitHub Issues
|
|
@@ -101,8 +108,25 @@ export async function saveSession(summary, tags) {
|
|
|
101
108
|
tags: tags ? tags.split(',').map(t => t.trim()) : []
|
|
102
109
|
});
|
|
103
110
|
|
|
104
|
-
// Calculate rewards
|
|
105
|
-
|
|
111
|
+
// Calculate rewards with EFFICIENCY MULTIPLIER
|
|
112
|
+
// Formula: agent_tokens / onyx_tokens = efficiency ratio
|
|
113
|
+
// The more work agents do vs ONYX overhead, the higher the bonus
|
|
114
|
+
const baseXP = 50;
|
|
115
|
+
const onyxTokens = session.onyx_tokens || 1000; // Minimum 1000 to avoid division issues
|
|
116
|
+
const agentTokens = session.agent_tokens || 0;
|
|
117
|
+
const delegations = session.delegations || 0;
|
|
118
|
+
|
|
119
|
+
// Efficiency bonus: floor(agent_tokens / onyx_tokens), capped at 100
|
|
120
|
+
// Example: 600k agent / 20k onyx = 30x efficiency = 30 bonus XP
|
|
121
|
+
let efficiencyBonus = 0;
|
|
122
|
+
if (agentTokens > 0 && onyxTokens > 0) {
|
|
123
|
+
efficiencyBonus = Math.min(100, Math.floor(agentTokens / onyxTokens));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Delegation bonus: +2 XP per successful delegation (up to 20 bonus)
|
|
127
|
+
const delegationBonus = Math.min(20, delegations * 2);
|
|
128
|
+
|
|
129
|
+
const xpEarned = baseXP + efficiencyBonus + delegationBonus;
|
|
106
130
|
const tokensEarned = session.tokens_used || 0;
|
|
107
131
|
|
|
108
132
|
await addXP(xpEarned);
|
|
@@ -131,7 +155,16 @@ export async function saveSession(summary, tags) {
|
|
|
131
155
|
...memory,
|
|
132
156
|
repo_name: repo.name,
|
|
133
157
|
xp_earned: xpEarned,
|
|
134
|
-
tokens_earned: tokensEarned
|
|
158
|
+
tokens_earned: tokensEarned,
|
|
159
|
+
// Efficiency metrics
|
|
160
|
+
efficiency: {
|
|
161
|
+
onyx_tokens: onyxTokens,
|
|
162
|
+
agent_tokens: agentTokens,
|
|
163
|
+
ratio: agentTokens > 0 ? (agentTokens / onyxTokens).toFixed(1) + 'x' : '0x',
|
|
164
|
+
efficiency_bonus: efficiencyBonus,
|
|
165
|
+
delegation_bonus: delegationBonus,
|
|
166
|
+
delegations: delegations
|
|
167
|
+
}
|
|
135
168
|
};
|
|
136
169
|
}
|
|
137
170
|
|
|
@@ -149,10 +182,124 @@ export async function updateSessionTokens(tokens) {
|
|
|
149
182
|
const session = data ? JSON.parse(data) : {
|
|
150
183
|
started_at: new Date().toISOString(),
|
|
151
184
|
messages: [],
|
|
152
|
-
tokens_used: 0
|
|
185
|
+
tokens_used: 0,
|
|
186
|
+
onyx_tokens: 0,
|
|
187
|
+
agent_tokens: 0,
|
|
188
|
+
delegations: 0
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
session.tokens_used = (session.tokens_used || 0) + tokens;
|
|
192
|
+
|
|
193
|
+
await client.set(sessionKey, JSON.stringify(session));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Track tokens used by ONYX (orchestration overhead)
|
|
198
|
+
* Lower is better - ONYX should delegate, not do work
|
|
199
|
+
*/
|
|
200
|
+
export async function trackOnyxTokens(tokens) {
|
|
201
|
+
const repo = await getCurrentRepo();
|
|
202
|
+
if (!repo) return;
|
|
203
|
+
|
|
204
|
+
const client = getRedis();
|
|
205
|
+
const sessionKey = `boss:session:${repo.name}:current`;
|
|
206
|
+
|
|
207
|
+
const data = await client.get(sessionKey);
|
|
208
|
+
const session = data ? JSON.parse(data) : {
|
|
209
|
+
started_at: new Date().toISOString(),
|
|
210
|
+
messages: [],
|
|
211
|
+
tokens_used: 0,
|
|
212
|
+
onyx_tokens: 0,
|
|
213
|
+
agent_tokens: 0,
|
|
214
|
+
delegations: 0
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
session.onyx_tokens = (session.onyx_tokens || 0) + tokens;
|
|
218
|
+
session.tokens_used = (session.tokens_used || 0) + tokens;
|
|
219
|
+
|
|
220
|
+
await client.set(sessionKey, JSON.stringify(session));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Track tokens used by spawned agents (actual work)
|
|
225
|
+
* Higher is better - agents should do the heavy lifting
|
|
226
|
+
*/
|
|
227
|
+
export async function trackAgentTokens(tokens) {
|
|
228
|
+
const repo = await getCurrentRepo();
|
|
229
|
+
if (!repo) return;
|
|
230
|
+
|
|
231
|
+
const client = getRedis();
|
|
232
|
+
const sessionKey = `boss:session:${repo.name}:current`;
|
|
233
|
+
|
|
234
|
+
const data = await client.get(sessionKey);
|
|
235
|
+
const session = data ? JSON.parse(data) : {
|
|
236
|
+
started_at: new Date().toISOString(),
|
|
237
|
+
messages: [],
|
|
238
|
+
tokens_used: 0,
|
|
239
|
+
onyx_tokens: 0,
|
|
240
|
+
agent_tokens: 0,
|
|
241
|
+
delegations: 0
|
|
153
242
|
};
|
|
154
243
|
|
|
244
|
+
session.agent_tokens = (session.agent_tokens || 0) + tokens;
|
|
155
245
|
session.tokens_used = (session.tokens_used || 0) + tokens;
|
|
156
246
|
|
|
157
247
|
await client.set(sessionKey, JSON.stringify(session));
|
|
158
248
|
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Record a delegation via Task tool
|
|
252
|
+
* More delegations = following the conductor protocol
|
|
253
|
+
*/
|
|
254
|
+
export async function recordDelegation() {
|
|
255
|
+
const repo = await getCurrentRepo();
|
|
256
|
+
if (!repo) return;
|
|
257
|
+
|
|
258
|
+
const client = getRedis();
|
|
259
|
+
const sessionKey = `boss:session:${repo.name}:current`;
|
|
260
|
+
|
|
261
|
+
const data = await client.get(sessionKey);
|
|
262
|
+
const session = data ? JSON.parse(data) : {
|
|
263
|
+
started_at: new Date().toISOString(),
|
|
264
|
+
messages: [],
|
|
265
|
+
tokens_used: 0,
|
|
266
|
+
onyx_tokens: 0,
|
|
267
|
+
agent_tokens: 0,
|
|
268
|
+
delegations: 0
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
session.delegations = (session.delegations || 0) + 1;
|
|
272
|
+
|
|
273
|
+
await client.set(sessionKey, JSON.stringify(session));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get current efficiency stats for display
|
|
278
|
+
*/
|
|
279
|
+
export async function getEfficiencyStats() {
|
|
280
|
+
const repo = await getCurrentRepo();
|
|
281
|
+
if (!repo) return null;
|
|
282
|
+
|
|
283
|
+
const client = getRedis();
|
|
284
|
+
const sessionKey = `boss:session:${repo.name}:current`;
|
|
285
|
+
|
|
286
|
+
const data = await client.get(sessionKey);
|
|
287
|
+
if (!data) return null;
|
|
288
|
+
|
|
289
|
+
const session = JSON.parse(data);
|
|
290
|
+
const onyxTokens = session.onyx_tokens || 0;
|
|
291
|
+
const agentTokens = session.agent_tokens || 0;
|
|
292
|
+
|
|
293
|
+
const ratio = onyxTokens > 0 ? agentTokens / onyxTokens : 0;
|
|
294
|
+
const projectedBonus = Math.min(100, Math.floor(ratio));
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
onyx_tokens: onyxTokens,
|
|
298
|
+
agent_tokens: agentTokens,
|
|
299
|
+
total_tokens: session.tokens_used || 0,
|
|
300
|
+
delegations: session.delegations || 0,
|
|
301
|
+
efficiency_ratio: ratio.toFixed(1) + 'x',
|
|
302
|
+
projected_bonus_xp: projectedBonus,
|
|
303
|
+
delegation_bonus_xp: Math.min(20, (session.delegations || 0) * 2)
|
|
304
|
+
};
|
|
305
|
+
}
|