beth-copilot 1.0.6 → 1.0.11
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/assets/beth-portrait-small.txt +13 -0
- package/assets/beth-portrait.txt +60 -0
- package/assets/beth-questioning.png +0 -0
- package/assets/yellowstone-beth.png +0 -0
- package/bin/beth-animation.sh +155 -0
- package/bin/cli.js +791 -61
- package/bin/lib/animation.js +189 -0
- package/bin/lib/pathValidation.js +233 -0
- package/bin/lib/pathValidation.test.js +280 -0
- package/package.json +9 -2
- package/sbom.json +129 -0
- package/templates/.github/agents/beth.agent.md +71 -15
- package/templates/.github/agents/developer.agent.md +10 -0
- package/templates/.github/agents/product-manager.agent.md +10 -0
- package/templates/.github/agents/researcher.agent.md +10 -0
- package/templates/.github/agents/security-reviewer.agent.md +10 -0
- package/templates/.github/agents/tester.agent.md +10 -0
- package/templates/.github/agents/ux-designer.agent.md +10 -0
package/bin/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { dirname, join, relative } from 'path';
|
|
|
5
5
|
import { existsSync, mkdirSync, readdirSync, statSync, copyFileSync, readFileSync, writeFileSync } from 'fs';
|
|
6
6
|
import { createRequire } from 'module';
|
|
7
7
|
import { execSync, spawn } from 'child_process';
|
|
8
|
+
import { validateBeadsPath, validateBacklogPath, validateBinaryPath } from './lib/pathValidation.js';
|
|
8
9
|
|
|
9
10
|
const require = createRequire(import.meta.url);
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -18,12 +19,367 @@ const CURRENT_VERSION = packageJson.version;
|
|
|
18
19
|
const COLORS = {
|
|
19
20
|
reset: '\x1b[0m',
|
|
20
21
|
bright: '\x1b[1m',
|
|
22
|
+
dim: '\x1b[2m',
|
|
21
23
|
red: '\x1b[31m',
|
|
22
24
|
green: '\x1b[32m',
|
|
23
25
|
yellow: '\x1b[33m',
|
|
26
|
+
blue: '\x1b[34m',
|
|
27
|
+
magenta: '\x1b[35m',
|
|
24
28
|
cyan: '\x1b[36m',
|
|
29
|
+
white: '\x1b[37m',
|
|
30
|
+
bgRed: '\x1b[41m',
|
|
31
|
+
bgYellow: '\x1b[43m',
|
|
25
32
|
};
|
|
26
33
|
|
|
34
|
+
// Beth's dramatic ASCII banner
|
|
35
|
+
const BETH_ASCII = [
|
|
36
|
+
'██████╗ ███████╗████████╗██╗ ██╗',
|
|
37
|
+
'██╔══██╗██╔════╝╚══██╔══╝██║ ██║',
|
|
38
|
+
'██████╔╝█████╗ ██║ ███████║',
|
|
39
|
+
'██╔══██╗██╔══╝ ██║ ██╔══██║',
|
|
40
|
+
'██████╔╝███████╗ ██║ ██║ ██║',
|
|
41
|
+
'╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝',
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// Fire characters for animation (from light to intense)
|
|
45
|
+
const FIRE_CHARS = [' ', '.', ':', '*', 's', 'S', '#', '$', '&', '@'];
|
|
46
|
+
const FIRE_CHARS_SIMPLE = [' ', '.', '*', '^', ')', '(', '%', '#'];
|
|
47
|
+
|
|
48
|
+
// Generate a fire line with flickering effect
|
|
49
|
+
function generateFireLine(width, intensity, frame) {
|
|
50
|
+
let line = '';
|
|
51
|
+
for (let i = 0; i < width; i++) {
|
|
52
|
+
// Create wave pattern for fire
|
|
53
|
+
const wave = Math.sin((i + frame) * 0.3) * 0.5 + 0.5;
|
|
54
|
+
const noise = Math.random();
|
|
55
|
+
const heat = (intensity * wave * 0.7 + noise * 0.3);
|
|
56
|
+
|
|
57
|
+
if (heat > 0.85) {
|
|
58
|
+
line += FIRE_CHARS_SIMPLE[7]; // #
|
|
59
|
+
} else if (heat > 0.7) {
|
|
60
|
+
line += FIRE_CHARS_SIMPLE[6]; // %
|
|
61
|
+
} else if (heat > 0.55) {
|
|
62
|
+
line += FIRE_CHARS_SIMPLE[Math.random() > 0.5 ? 4 : 5]; // ) or (
|
|
63
|
+
} else if (heat > 0.4) {
|
|
64
|
+
line += FIRE_CHARS_SIMPLE[3]; // ^
|
|
65
|
+
} else if (heat > 0.25) {
|
|
66
|
+
line += FIRE_CHARS_SIMPLE[2]; // *
|
|
67
|
+
} else if (heat > 0.1) {
|
|
68
|
+
line += FIRE_CHARS_SIMPLE[1]; // .
|
|
69
|
+
} else {
|
|
70
|
+
line += ' ';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return line;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const BETH_TAGLINES = [
|
|
77
|
+
"I don't speak dipshit. I speak in consequences.",
|
|
78
|
+
"They broke my wings and forgot I had claws.",
|
|
79
|
+
"I'm the trailer park AND the tornado.",
|
|
80
|
+
"I don't do excuses. I do results.",
|
|
81
|
+
"You want my opinion? You're getting it either way.",
|
|
82
|
+
"I believe in lovin' with your whole soul and destroyin' anything that wants to kill what you love.",
|
|
83
|
+
"The sting never fades. That's the point.",
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
function sleep(ms) {
|
|
87
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function animateBethBanner() {
|
|
91
|
+
// Simple, clean fire animation
|
|
92
|
+
const RESET = '\x1b[0m';
|
|
93
|
+
const BRIGHT = '\x1b[1m';
|
|
94
|
+
|
|
95
|
+
// Fire color palette
|
|
96
|
+
const FIRE_COLORS = [
|
|
97
|
+
'\x1b[97m', // white (hottest)
|
|
98
|
+
'\x1b[93m', // bright yellow
|
|
99
|
+
'\x1b[33m', // yellow
|
|
100
|
+
'\x1b[38;5;214m', // gold
|
|
101
|
+
'\x1b[38;5;208m', // orange
|
|
102
|
+
'\x1b[91m', // red
|
|
103
|
+
'\x1b[31m', // dark red
|
|
104
|
+
'\x1b[38;5;52m', // ember
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
// BETH gradient (red to yellow)
|
|
108
|
+
const BETH_COLORS = [
|
|
109
|
+
'\x1b[38;5;196m', '\x1b[38;5;202m', '\x1b[38;5;208m',
|
|
110
|
+
'\x1b[38;5;214m', '\x1b[38;5;220m', '\x1b[38;5;226m',
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
// Convert to character arrays
|
|
114
|
+
const bethLines = BETH_ASCII.map(s => [...s]);
|
|
115
|
+
const W = bethLines[0].length;
|
|
116
|
+
const H = bethLines.length;
|
|
117
|
+
const FIRE_H = 4;
|
|
118
|
+
const TOTAL_H = H + FIRE_H;
|
|
119
|
+
|
|
120
|
+
// Helpers
|
|
121
|
+
const pick = arr => arr[Math.floor(Math.random() * arr.length)];
|
|
122
|
+
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
123
|
+
|
|
124
|
+
// Hide cursor and make space
|
|
125
|
+
process.stdout.write('\x1b[?25l\n');
|
|
126
|
+
for (let i = 0; i < TOTAL_H; i++) console.log('');
|
|
127
|
+
|
|
128
|
+
for (let frame = 0; frame < 70; frame++) {
|
|
129
|
+
process.stdout.write(`\x1b[${TOTAL_H}A`);
|
|
130
|
+
|
|
131
|
+
// BETH visibility (fades in from frame 15-45)
|
|
132
|
+
const vis = clamp((frame - 15) / 30, 0, 1);
|
|
133
|
+
// Fire dies down at end
|
|
134
|
+
const fireStrength = frame > 55 ? 1 - (frame - 55) / 15 : 1;
|
|
135
|
+
|
|
136
|
+
// Render BETH rows
|
|
137
|
+
for (let r = 0; r < H; r++) {
|
|
138
|
+
let line = '';
|
|
139
|
+
for (let c = 0; c < W; c++) {
|
|
140
|
+
const ch = bethLines[r][c];
|
|
141
|
+
if (ch === ' ') {
|
|
142
|
+
// Gap - show fire through it sometimes
|
|
143
|
+
if (Math.random() < 0.15 * fireStrength) {
|
|
144
|
+
const h = 0.3 + Math.random() * 0.3;
|
|
145
|
+
const ci = clamp(Math.floor((1 - h) * 5), 0, FIRE_COLORS.length - 1);
|
|
146
|
+
line += FIRE_COLORS[ci] + pick(['^', '*', '.']);
|
|
147
|
+
} else {
|
|
148
|
+
line += ' ';
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
// BETH character
|
|
152
|
+
if (Math.random() < vis) {
|
|
153
|
+
const ci = Math.floor((c / W) * BETH_COLORS.length);
|
|
154
|
+
const col = BETH_COLORS[clamp(ci, 0, BETH_COLORS.length - 1)];
|
|
155
|
+
line += (Math.random() > 0.95 ? '\x1b[97m' : col) + BRIGHT + ch;
|
|
156
|
+
} else {
|
|
157
|
+
// Not visible yet - show fire
|
|
158
|
+
const h = 0.5 + Math.random() * 0.5;
|
|
159
|
+
const ci = clamp(Math.floor((1 - h) * 4), 0, FIRE_COLORS.length - 1);
|
|
160
|
+
line += FIRE_COLORS[ci] + pick(['#', '@', '%', '&']);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
console.log(line + RESET);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Fire rows below
|
|
168
|
+
for (let fr = 0; fr < FIRE_H; fr++) {
|
|
169
|
+
let line = '';
|
|
170
|
+
const baseHeat = (1 - fr / FIRE_H) * fireStrength;
|
|
171
|
+
for (let c = 0; c < W; c++) {
|
|
172
|
+
const wave = Math.sin((c + frame * 2) * 0.15) * 0.15;
|
|
173
|
+
const heat = clamp(baseHeat + wave + (Math.random() - 0.5) * 0.3, 0, 1);
|
|
174
|
+
|
|
175
|
+
let ch;
|
|
176
|
+
if (heat > 0.6) ch = pick(['#', '@', '%']);
|
|
177
|
+
else if (heat > 0.35) ch = pick(['^', '*', '(', ')']);
|
|
178
|
+
else if (heat > 0.15) ch = pick(['.', ':', '*']);
|
|
179
|
+
else ch = ' ';
|
|
180
|
+
|
|
181
|
+
const ci = clamp(Math.floor((1 - heat) * 6), 0, FIRE_COLORS.length - 1);
|
|
182
|
+
line += FIRE_COLORS[ci] + ch;
|
|
183
|
+
}
|
|
184
|
+
console.log(line + RESET);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
await sleep(frame < 15 ? 80 : frame < 45 ? 50 : 60);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Final clean frame
|
|
191
|
+
process.stdout.write(`\x1b[${TOTAL_H}A`);
|
|
192
|
+
for (let r = 0; r < H; r++) {
|
|
193
|
+
let line = '';
|
|
194
|
+
for (let c = 0; c < W; c++) {
|
|
195
|
+
const ci = Math.floor((c / W) * BETH_COLORS.length);
|
|
196
|
+
line += BETH_COLORS[clamp(ci, 0, BETH_COLORS.length - 1)] + BRIGHT + bethLines[r][c];
|
|
197
|
+
}
|
|
198
|
+
console.log(line + RESET);
|
|
199
|
+
}
|
|
200
|
+
// Clear fire area with spaces
|
|
201
|
+
for (let fr = 0; fr < FIRE_H; fr++) {
|
|
202
|
+
console.log(' '.repeat(W));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
process.stdout.write('\x1b[?25h');
|
|
206
|
+
|
|
207
|
+
const tagline = BETH_TAGLINES[Math.floor(Math.random() * BETH_TAGLINES.length)];
|
|
208
|
+
console.log('');
|
|
209
|
+
process.stdout.write(COLORS.cyan + COLORS.bright + '"');
|
|
210
|
+
for (const ch of tagline) {
|
|
211
|
+
process.stdout.write(ch);
|
|
212
|
+
await sleep(18);
|
|
213
|
+
}
|
|
214
|
+
console.log('"' + COLORS.reset);
|
|
215
|
+
console.log('');
|
|
216
|
+
|
|
217
|
+
// Show version and quick help
|
|
218
|
+
console.log(`${COLORS.dim}v${CURRENT_VERSION}${COLORS.reset} ${COLORS.dim}AI Orchestrator for GitHub Copilot${COLORS.reset}`);
|
|
219
|
+
console.log('');
|
|
220
|
+
console.log(`${COLORS.bright}Commands:${COLORS.reset}`);
|
|
221
|
+
console.log(` ${COLORS.cyan}npx beth-copilot init${COLORS.reset} Install Beth in your project`);
|
|
222
|
+
console.log(` ${COLORS.cyan}npx beth-copilot help${COLORS.reset} Show full documentation`);
|
|
223
|
+
console.log('');
|
|
224
|
+
console.log(`${COLORS.bright}After install:${COLORS.reset} Open VS Code → Copilot Chat → ${COLORS.cyan}@Beth${COLORS.reset}`);
|
|
225
|
+
console.log('');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function showBethBannerStatic({ showQuickHelp = true } = {}) {
|
|
229
|
+
const bethColors = [
|
|
230
|
+
'\x1b[38;5;196m',
|
|
231
|
+
'\x1b[38;5;202m',
|
|
232
|
+
'\x1b[38;5;208m',
|
|
233
|
+
'\x1b[38;5;214m',
|
|
234
|
+
'\x1b[38;5;220m',
|
|
235
|
+
'\x1b[38;5;226m',
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
const fireColors = [
|
|
239
|
+
'\x1b[93m', // bright yellow
|
|
240
|
+
'\x1b[38;5;208m', // orange
|
|
241
|
+
'\x1b[91m', // red
|
|
242
|
+
'\x1b[38;5;52m', // dark red
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
console.log('\n');
|
|
246
|
+
const bethChars = BETH_ASCII.map(line => [...line]);
|
|
247
|
+
const bethWidth = bethChars[0].length;
|
|
248
|
+
|
|
249
|
+
// BETH with gradient
|
|
250
|
+
for (let row = 0; row < BETH_ASCII.length; row++) {
|
|
251
|
+
let line = '';
|
|
252
|
+
for (let c = 0; c < bethWidth; c++) {
|
|
253
|
+
const char = bethChars[row][c];
|
|
254
|
+
const colorIndex = Math.floor((c / bethWidth) * bethColors.length);
|
|
255
|
+
line += bethColors[Math.min(colorIndex, bethColors.length - 1)] + COLORS.bright + char;
|
|
256
|
+
}
|
|
257
|
+
console.log(line + COLORS.reset);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
const tagline = BETH_TAGLINES[Math.floor(Math.random() * BETH_TAGLINES.length)];
|
|
263
|
+
console.log('');
|
|
264
|
+
console.log(COLORS.cyan + COLORS.bright + '"' + tagline + '"' + COLORS.reset);
|
|
265
|
+
console.log('');
|
|
266
|
+
|
|
267
|
+
// Show version and quick help (optional)
|
|
268
|
+
if (showQuickHelp) {
|
|
269
|
+
console.log(`${COLORS.dim}v${CURRENT_VERSION}${COLORS.reset} ${COLORS.dim}AI Orchestrator for GitHub Copilot${COLORS.reset}`);
|
|
270
|
+
console.log('');
|
|
271
|
+
console.log(`${COLORS.bright}Commands:${COLORS.reset}`);
|
|
272
|
+
console.log(` ${COLORS.cyan}npx beth-copilot init${COLORS.reset} Install Beth in your project`);
|
|
273
|
+
console.log(` ${COLORS.cyan}npx beth-copilot help${COLORS.reset} Show full documentation`);
|
|
274
|
+
console.log('');
|
|
275
|
+
console.log(`${COLORS.bright}After install:${COLORS.reset} Open VS Code → Copilot Chat → ${COLORS.cyan}@Beth${COLORS.reset}`);
|
|
276
|
+
console.log('');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Compact Beth portrait with colors
|
|
281
|
+
const BETH_PORTRAIT = [
|
|
282
|
+
' .╭━━━━━━━╮.',
|
|
283
|
+
' ╭──╯ ▒▓▓▓▓▒ ╰──╮',
|
|
284
|
+
' ╱ ▓██████████▓ ╲',
|
|
285
|
+
' ╱ ████▓▓██▓▓████ ╲',
|
|
286
|
+
' │ ███ ◉ ██ ◉ ███ │',
|
|
287
|
+
' │ ███▄▄▄▄▄▄███ │',
|
|
288
|
+
' │ ▀██▄══▄██▀ │',
|
|
289
|
+
' │ ╰────╯ │',
|
|
290
|
+
' │ ▓██████████▓ │',
|
|
291
|
+
' ╲ ████████████ ╱',
|
|
292
|
+
' ╲ ▀██████████▀ ╱',
|
|
293
|
+
' ╰───╮ ╭───╯',
|
|
294
|
+
' ╰──────╯',
|
|
295
|
+
];
|
|
296
|
+
|
|
297
|
+
// Portrait animation for init command
|
|
298
|
+
async function animatePortrait() {
|
|
299
|
+
const AMBER = '\x1b[38;2;218;165;32m';
|
|
300
|
+
const GOLD = '\x1b[38;2;255;215;0m';
|
|
301
|
+
const SKIN = '\x1b[38;2;235;210;160m';
|
|
302
|
+
const DARK = '\x1b[38;2;139;90;43m';
|
|
303
|
+
const EYE = '\x1b[38;2;70;130;180m';
|
|
304
|
+
const LIP = '\x1b[38;2;180;80;80m';
|
|
305
|
+
const WHITE = '\x1b[38;2;255;255;255m';
|
|
306
|
+
const RESET = '\x1b[0m';
|
|
307
|
+
const BOLD = '\x1b[1m';
|
|
308
|
+
const DIM = '\x1b[2m';
|
|
309
|
+
|
|
310
|
+
// Hide cursor
|
|
311
|
+
process.stdout.write('\x1b[?25l');
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
// Clear screen
|
|
315
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
316
|
+
await sleep(200);
|
|
317
|
+
|
|
318
|
+
// Quick glitch effect
|
|
319
|
+
const glitchChars = '░▒▓█';
|
|
320
|
+
for (let frame = 0; frame < 3; frame++) {
|
|
321
|
+
process.stdout.write('\x1b[H');
|
|
322
|
+
for (let j = 0; j < 5; j++) {
|
|
323
|
+
const col = Math.floor(Math.random() * 20) + 5;
|
|
324
|
+
const row = Math.floor(Math.random() * 10) + 2;
|
|
325
|
+
const r = Math.floor(Math.random() * 150 + 100);
|
|
326
|
+
const g = Math.floor(Math.random() * 100 + 50);
|
|
327
|
+
const b = Math.floor(Math.random() * 50);
|
|
328
|
+
const char = glitchChars[Math.floor(Math.random() * glitchChars.length)];
|
|
329
|
+
process.stdout.write(`\x1b[${row};${col}H\x1b[38;2;${r};${g};${b}m${char.repeat(3)}`);
|
|
330
|
+
}
|
|
331
|
+
await sleep(50);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Display portrait with colors
|
|
335
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
336
|
+
console.log();
|
|
337
|
+
|
|
338
|
+
for (let i = 0; i < BETH_PORTRAIT.length; i++) {
|
|
339
|
+
let line = BETH_PORTRAIT[i];
|
|
340
|
+
// Colorize: frame in amber, face content in skin tones
|
|
341
|
+
line = line
|
|
342
|
+
.replace(/[╭╮╯╰│╱╲━.─]/g, `${AMBER}$&${RESET}`)
|
|
343
|
+
.replace(/[▓█▒░▀▄▐▌]/g, `${SKIN}$&${RESET}`)
|
|
344
|
+
.replace(/◉/g, `${EYE}◉${RESET}`)
|
|
345
|
+
.replace(/══/g, `${LIP}══${RESET}`);
|
|
346
|
+
console.log(' ' + line);
|
|
347
|
+
await sleep(40);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
await sleep(300);
|
|
351
|
+
|
|
352
|
+
// Banner below portrait
|
|
353
|
+
console.log();
|
|
354
|
+
console.log(` ${GOLD}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
|
355
|
+
console.log(` ${AMBER}${BOLD} B E T H${RESET}`);
|
|
356
|
+
console.log(` ${DIM}${WHITE} AI Agent Orchestrator${RESET}`);
|
|
357
|
+
console.log(` ${GOLD}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
|
358
|
+
|
|
359
|
+
await sleep(300);
|
|
360
|
+
|
|
361
|
+
// Typewriter quote
|
|
362
|
+
const quote = BETH_TAGLINES[Math.floor(Math.random() * BETH_TAGLINES.length)];
|
|
363
|
+
console.log();
|
|
364
|
+
process.stdout.write(` ${AMBER}"`);
|
|
365
|
+
for (const ch of quote) {
|
|
366
|
+
process.stdout.write(ch);
|
|
367
|
+
await sleep(20);
|
|
368
|
+
}
|
|
369
|
+
console.log(`"${RESET}`);
|
|
370
|
+
console.log();
|
|
371
|
+
|
|
372
|
+
} finally {
|
|
373
|
+
// Show cursor
|
|
374
|
+
process.stdout.write('\x1b[?25h');
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Detect if we can do animations (TTY and not piped)
|
|
379
|
+
function canAnimate() {
|
|
380
|
+
return process.stdout.isTTY && !process.env.CI && !process.env.NO_COLOR;
|
|
381
|
+
}
|
|
382
|
+
|
|
27
383
|
function log(message, color = '') {
|
|
28
384
|
console.log(`${color}${message}${COLORS.reset}`);
|
|
29
385
|
}
|
|
@@ -44,6 +400,32 @@ function logInfo(message) {
|
|
|
44
400
|
log(` ${message}`, COLORS.cyan);
|
|
45
401
|
}
|
|
46
402
|
|
|
403
|
+
function logDebug(message) {
|
|
404
|
+
if (globalThis.VERBOSE) {
|
|
405
|
+
log(` [debug] ${message}`, COLORS.yellow);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function showPathDiagnostics() {
|
|
410
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
411
|
+
const isWindows = process.platform === 'win32';
|
|
412
|
+
|
|
413
|
+
console.log('');
|
|
414
|
+
log('PATH Diagnostics:', COLORS.bright);
|
|
415
|
+
logInfo(`Platform: ${process.platform}`);
|
|
416
|
+
logInfo(`HOME: ${homeDir}`);
|
|
417
|
+
logInfo(`PATH: ${process.env.PATH}`);
|
|
418
|
+
|
|
419
|
+
if (isWindows) {
|
|
420
|
+
logInfo(`APPDATA: ${process.env.APPDATA || '(not set)'}`);
|
|
421
|
+
logInfo(`npm prefix: Run "npm config get prefix" to check`);
|
|
422
|
+
} else {
|
|
423
|
+
logInfo(`npm prefix: Run "npm config get prefix" to check`);
|
|
424
|
+
logInfo(`Common locations: ~/.local/bin, /usr/local/bin, ~/.npm-global/bin`);
|
|
425
|
+
}
|
|
426
|
+
console.log('');
|
|
427
|
+
}
|
|
428
|
+
|
|
47
429
|
async function checkForUpdates() {
|
|
48
430
|
try {
|
|
49
431
|
const response = await fetch('https://registry.npmjs.org/beth-copilot/latest', {
|
|
@@ -75,24 +457,96 @@ async function checkForUpdates() {
|
|
|
75
457
|
}
|
|
76
458
|
}
|
|
77
459
|
|
|
78
|
-
function
|
|
460
|
+
function getBacklogPath() {
|
|
461
|
+
// Check if backlog is available in PATH
|
|
79
462
|
try {
|
|
463
|
+
logDebug('Checking if backlog is in PATH...');
|
|
80
464
|
execSync('backlog --version', { stdio: 'ignore' });
|
|
81
|
-
|
|
465
|
+
logDebug('Found backlog in PATH');
|
|
466
|
+
return 'backlog';
|
|
82
467
|
} catch {
|
|
83
|
-
|
|
468
|
+
logDebug('backlog not in PATH, checking common locations...');
|
|
469
|
+
// Check common installation paths
|
|
470
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
471
|
+
const isWindows = process.platform === 'win32';
|
|
472
|
+
|
|
473
|
+
const commonPaths = isWindows ? [
|
|
474
|
+
join(process.env.APPDATA || '', 'npm', 'backlog.cmd'),
|
|
475
|
+
join(homeDir, 'AppData', 'Roaming', 'npm', 'backlog.cmd'),
|
|
476
|
+
join(homeDir, 'AppData', 'Local', 'npm-global', 'backlog.cmd'),
|
|
477
|
+
] : [
|
|
478
|
+
join(homeDir, '.local', 'bin', 'backlog'),
|
|
479
|
+
join(homeDir, 'bin', 'backlog'),
|
|
480
|
+
'/usr/local/bin/backlog',
|
|
481
|
+
join(homeDir, '.npm-global', 'bin', 'backlog'),
|
|
482
|
+
join(homeDir, '.bun', 'bin', 'backlog'),
|
|
483
|
+
];
|
|
484
|
+
|
|
485
|
+
for (const backlogPath of commonPaths) {
|
|
486
|
+
logDebug(`Checking: ${backlogPath}`);
|
|
487
|
+
if (existsSync(backlogPath)) {
|
|
488
|
+
logDebug(`Found at: ${backlogPath}`);
|
|
489
|
+
return backlogPath;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
logDebug('backlog not found in any common location');
|
|
494
|
+
return null;
|
|
84
495
|
}
|
|
85
496
|
}
|
|
86
497
|
|
|
87
|
-
function
|
|
498
|
+
function isBacklogCliInstalled() {
|
|
499
|
+
return getBacklogPath() !== null;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function getBeadsPath() {
|
|
503
|
+
// Check if bd is available in PATH
|
|
88
504
|
try {
|
|
505
|
+
logDebug('Checking if bd is in PATH...');
|
|
89
506
|
execSync('bd --version', { stdio: 'ignore' });
|
|
90
|
-
|
|
507
|
+
logDebug('Found bd in PATH');
|
|
508
|
+
return 'bd';
|
|
91
509
|
} catch {
|
|
92
|
-
|
|
510
|
+
logDebug('bd not in PATH, checking common locations...');
|
|
511
|
+
// Check common installation paths based on platform
|
|
512
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
513
|
+
const isWindows = process.platform === 'win32';
|
|
514
|
+
|
|
515
|
+
const commonPaths = isWindows ? [
|
|
516
|
+
// Windows: npm global, Go bin, local apps
|
|
517
|
+
join(process.env.APPDATA || '', 'npm', 'bd.cmd'),
|
|
518
|
+
join(homeDir, 'AppData', 'Roaming', 'npm', 'bd.cmd'),
|
|
519
|
+
join(homeDir, 'AppData', 'Local', 'Microsoft', 'WindowsApps', 'bd.exe'),
|
|
520
|
+
join(homeDir, 'go', 'bin', 'bd.exe'),
|
|
521
|
+
join(process.env.GOPATH || join(homeDir, 'go'), 'bin', 'bd.exe'),
|
|
522
|
+
] : [
|
|
523
|
+
// Unix: homebrew, npm global, go bin, local bin
|
|
524
|
+
'/opt/homebrew/bin/bd',
|
|
525
|
+
'/usr/local/bin/bd',
|
|
526
|
+
join(homeDir, '.local', 'bin', 'bd'),
|
|
527
|
+
join(homeDir, 'bin', 'bd'),
|
|
528
|
+
join(homeDir, '.npm-global', 'bin', 'bd'),
|
|
529
|
+
join(homeDir, 'go', 'bin', 'bd'),
|
|
530
|
+
join(process.env.GOPATH || join(homeDir, 'go'), 'bin', 'bd'),
|
|
531
|
+
];
|
|
532
|
+
|
|
533
|
+
for (const bdPath of commonPaths) {
|
|
534
|
+
logDebug(`Checking: ${bdPath}`);
|
|
535
|
+
if (existsSync(bdPath)) {
|
|
536
|
+
logDebug(`Found at: ${bdPath}`);
|
|
537
|
+
return bdPath;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
logDebug('bd not found in any common location');
|
|
542
|
+
return null;
|
|
93
543
|
}
|
|
94
544
|
}
|
|
95
545
|
|
|
546
|
+
function isBeadsInstalled() {
|
|
547
|
+
return getBeadsPath() !== null;
|
|
548
|
+
}
|
|
549
|
+
|
|
96
550
|
function isBeadsInitialized(cwd) {
|
|
97
551
|
// Check if .beads directory exists in the project
|
|
98
552
|
return existsSync(join(cwd, '.beads'));
|
|
@@ -113,9 +567,43 @@ async function promptYesNo(question) {
|
|
|
113
567
|
});
|
|
114
568
|
}
|
|
115
569
|
|
|
570
|
+
async function promptForInput(question) {
|
|
571
|
+
const readline = await import('readline');
|
|
572
|
+
const rl = readline.createInterface({
|
|
573
|
+
input: process.stdin,
|
|
574
|
+
output: process.stdout
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
return new Promise((resolve) => {
|
|
578
|
+
rl.question(`${question} `, (answer) => {
|
|
579
|
+
rl.close();
|
|
580
|
+
resolve(answer.trim());
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Installs the backlog.md CLI globally via npm.
|
|
587
|
+
*
|
|
588
|
+
* SECURITY NOTE - shell:true usage:
|
|
589
|
+
* - Required for cross-platform npm execution (npm.cmd on Windows, npm on Unix)
|
|
590
|
+
* - Arguments are HARDCODED - no user input is passed to the shell
|
|
591
|
+
* - Command injection risk: NONE (no dynamic/user-supplied values)
|
|
592
|
+
*
|
|
593
|
+
* Alternative considered: Using platform-specific binary names (npm.cmd vs npm)
|
|
594
|
+
* would eliminate shell:true but adds complexity and edge cases for non-standard installs.
|
|
595
|
+
*
|
|
596
|
+
* @returns {Promise<boolean>} True if installation succeeded and was verified
|
|
597
|
+
*/
|
|
116
598
|
async function installBacklogCli() {
|
|
117
|
-
|
|
599
|
+
const isWindows = process.platform === 'win32';
|
|
600
|
+
const isMac = process.platform === 'darwin';
|
|
601
|
+
|
|
602
|
+
log('\nInstalling backlog.md CLI via npm...', COLORS.cyan);
|
|
603
|
+
logInfo('npm install -g backlog.md');
|
|
118
604
|
|
|
605
|
+
// SECURITY: shell:true is required for cross-platform npm execution.
|
|
606
|
+
// All arguments are hardcoded constants - no user input reaches the shell.
|
|
119
607
|
return new Promise((resolve) => {
|
|
120
608
|
const child = spawn('npm', ['install', '-g', 'backlog.md'], {
|
|
121
609
|
stdio: 'inherit',
|
|
@@ -124,60 +612,159 @@ async function installBacklogCli() {
|
|
|
124
612
|
|
|
125
613
|
child.on('close', (code) => {
|
|
126
614
|
if (code === 0) {
|
|
127
|
-
|
|
128
|
-
|
|
615
|
+
// CRITICAL: Verify installation actually worked before claiming success
|
|
616
|
+
const verifiedPath = getBacklogPath();
|
|
617
|
+
if (verifiedPath) {
|
|
618
|
+
logSuccess('backlog.md CLI installed and verified!');
|
|
619
|
+
resolve(true);
|
|
620
|
+
} else {
|
|
621
|
+
logWarning('npm reported success but backlog CLI not found in PATH.');
|
|
622
|
+
logInfo('This can happen if npm global bin is not in your PATH.');
|
|
623
|
+
if (globalThis.VERBOSE) {
|
|
624
|
+
showPathDiagnostics();
|
|
625
|
+
} else {
|
|
626
|
+
logInfo('Run with --verbose for PATH diagnostics.');
|
|
627
|
+
}
|
|
628
|
+
console.log('');
|
|
629
|
+
showBacklogAlternatives(isMac);
|
|
630
|
+
resolve(false);
|
|
631
|
+
}
|
|
129
632
|
} else {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
logInfo('bun i -g backlog.md');
|
|
633
|
+
logError('npm install failed.');
|
|
634
|
+
console.log('');
|
|
635
|
+
showBacklogAlternatives(isMac);
|
|
134
636
|
resolve(false);
|
|
135
637
|
}
|
|
136
638
|
});
|
|
137
639
|
|
|
138
640
|
child.on('error', () => {
|
|
139
|
-
|
|
140
|
-
logInfo('npm
|
|
641
|
+
logError('Failed to run npm.');
|
|
642
|
+
logInfo('Make sure npm is installed and in your PATH.');
|
|
141
643
|
resolve(false);
|
|
142
644
|
});
|
|
143
645
|
});
|
|
144
646
|
}
|
|
145
647
|
|
|
648
|
+
function showBacklogAlternatives(isMac) {
|
|
649
|
+
logInfo('Alternative installation methods:');
|
|
650
|
+
if (isMac) {
|
|
651
|
+
logInfo(' Homebrew: brew install backlog-md');
|
|
652
|
+
}
|
|
653
|
+
logInfo(' Bun: bun install -g backlog.md');
|
|
654
|
+
logInfo('');
|
|
655
|
+
logInfo('Learn more: https://github.com/MrLesk/Backlog.md');
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Installs the beads CLI globally via npm.
|
|
660
|
+
*
|
|
661
|
+
* SECURITY NOTE - shell:true usage:
|
|
662
|
+
* - Required for cross-platform npm execution (npm.cmd on Windows, npm on Unix)
|
|
663
|
+
* - Arguments are HARDCODED - no user input is passed to the shell
|
|
664
|
+
* - Command injection risk: NONE (no dynamic/user-supplied values)
|
|
665
|
+
*
|
|
666
|
+
* Alternative considered: Using platform-specific binary names (npm.cmd vs npm)
|
|
667
|
+
* would eliminate shell:true but adds complexity and edge cases for non-standard installs.
|
|
668
|
+
*
|
|
669
|
+
* @returns {Promise<boolean>} True if installation succeeded and was verified
|
|
670
|
+
*/
|
|
146
671
|
async function installBeads() {
|
|
147
|
-
|
|
148
|
-
|
|
672
|
+
const isWindows = process.platform === 'win32';
|
|
673
|
+
const isMac = process.platform === 'darwin';
|
|
674
|
+
|
|
675
|
+
log('\nInstalling beads CLI via npm...', COLORS.cyan);
|
|
676
|
+
logInfo('npm install -g @beads/bd');
|
|
149
677
|
|
|
678
|
+
// SECURITY: shell:true is required for cross-platform npm execution.
|
|
679
|
+
// All arguments are hardcoded constants - no user input reaches the shell.
|
|
150
680
|
return new Promise((resolve) => {
|
|
151
|
-
const child = spawn('
|
|
681
|
+
const child = spawn('npm', ['install', '-g', '@beads/bd'], {
|
|
152
682
|
stdio: 'inherit',
|
|
153
683
|
shell: true
|
|
154
684
|
});
|
|
155
685
|
|
|
156
686
|
child.on('close', (code) => {
|
|
157
687
|
if (code === 0) {
|
|
158
|
-
|
|
159
|
-
|
|
688
|
+
// CRITICAL: Verify installation actually worked before claiming success
|
|
689
|
+
// npm can exit 0 even when the package isn't properly installed
|
|
690
|
+
const verifiedPath = getBeadsPath();
|
|
691
|
+
if (verifiedPath) {
|
|
692
|
+
logSuccess('beads CLI installed and verified!');
|
|
693
|
+
resolve(true);
|
|
694
|
+
} else {
|
|
695
|
+
logWarning('npm reported success but beads CLI not found in PATH.');
|
|
696
|
+
logInfo('This can happen if npm global bin is not in your PATH.');
|
|
697
|
+
if (globalThis.VERBOSE) {
|
|
698
|
+
showPathDiagnostics();
|
|
699
|
+
} else {
|
|
700
|
+
logInfo('Run with --verbose for PATH diagnostics.');
|
|
701
|
+
}
|
|
702
|
+
console.log('');
|
|
703
|
+
showBeadsAlternatives(isWindows, isMac);
|
|
704
|
+
resolve(false);
|
|
705
|
+
}
|
|
160
706
|
} else {
|
|
161
|
-
logError('
|
|
162
|
-
|
|
163
|
-
|
|
707
|
+
logError('npm install failed.');
|
|
708
|
+
console.log('');
|
|
709
|
+
showBeadsAlternatives(isWindows, isMac);
|
|
164
710
|
resolve(false);
|
|
165
711
|
}
|
|
166
712
|
});
|
|
167
713
|
|
|
168
714
|
child.on('error', () => {
|
|
169
|
-
logError('Failed to
|
|
170
|
-
logInfo('
|
|
715
|
+
logError('Failed to run npm.');
|
|
716
|
+
logInfo('Make sure npm is installed and in your PATH.');
|
|
171
717
|
resolve(false);
|
|
172
718
|
});
|
|
173
719
|
});
|
|
174
720
|
}
|
|
175
721
|
|
|
722
|
+
function showBeadsAlternatives(isWindows, isMac) {
|
|
723
|
+
logInfo('Alternative installation methods:');
|
|
724
|
+
if (isWindows) {
|
|
725
|
+
logInfo(' PowerShell: irm https://raw.githubusercontent.com/steveyegge/beads/main/install.ps1 | iex');
|
|
726
|
+
logInfo(' Go: go install github.com/steveyegge/beads/cmd/bd@latest');
|
|
727
|
+
} else {
|
|
728
|
+
if (isMac) {
|
|
729
|
+
logInfo(' Homebrew: brew install beads');
|
|
730
|
+
}
|
|
731
|
+
logInfo(' Script: curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash');
|
|
732
|
+
logInfo(' Go: go install github.com/steveyegge/beads/cmd/bd@latest');
|
|
733
|
+
}
|
|
734
|
+
logInfo('');
|
|
735
|
+
logInfo('Learn more: https://github.com/steveyegge/beads');
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Initializes beads in the current project directory.
|
|
740
|
+
*
|
|
741
|
+
* SECURITY NOTE - shell:true usage:
|
|
742
|
+
* - bdPath is validated via getBeadsPath() which only returns paths that:
|
|
743
|
+
* 1. Pass execSync('bd --version') verification, OR
|
|
744
|
+
* 2. Exist on disk (verified via existsSync) from a HARDCODED list of paths
|
|
745
|
+
* - Arguments are HARDCODED ('init') - no user input is passed to the shell
|
|
746
|
+
* - Command injection risk: LOW (bdPath is validated, no user input in args)
|
|
747
|
+
*
|
|
748
|
+
* The shell:true is used for PATH resolution consistency, though it could be
|
|
749
|
+
* eliminated since we have an absolute path. Kept for consistency with other
|
|
750
|
+
* spawn calls and to handle edge cases in shell script wrappers.
|
|
751
|
+
*
|
|
752
|
+
* @param {string} cwd - Current working directory (validated by caller)
|
|
753
|
+
* @returns {Promise<boolean>} True if initialization succeeded
|
|
754
|
+
*/
|
|
176
755
|
async function initializeBeads(cwd) {
|
|
177
756
|
log('\nInitializing beads in project...', COLORS.cyan);
|
|
178
757
|
|
|
758
|
+
const bdPath = getBeadsPath();
|
|
759
|
+
if (!bdPath) {
|
|
760
|
+
logWarning('Failed to initialize beads. Run manually: bd init');
|
|
761
|
+
return false;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// SECURITY: bdPath is validated by getBeadsPath() (existsSync check).
|
|
765
|
+
// Only 'init' argument is passed - no user input reaches the shell.
|
|
179
766
|
return new Promise((resolve) => {
|
|
180
|
-
const child = spawn(
|
|
767
|
+
const child = spawn(bdPath, ['init'], {
|
|
181
768
|
stdio: 'inherit',
|
|
182
769
|
shell: true,
|
|
183
770
|
cwd
|
|
@@ -201,8 +788,8 @@ async function initializeBeads(cwd) {
|
|
|
201
788
|
}
|
|
202
789
|
|
|
203
790
|
function showHelp() {
|
|
204
|
-
|
|
205
|
-
|
|
791
|
+
showBethBannerStatic({ showQuickHelp: false });
|
|
792
|
+
console.log(`${COLORS.bright}Beth${COLORS.reset} - AI Orchestrator for GitHub Copilot
|
|
206
793
|
|
|
207
794
|
${COLORS.bright}Usage:${COLORS.reset}
|
|
208
795
|
npx beth-copilot init [options] Initialize Beth in current directory
|
|
@@ -213,6 +800,7 @@ ${COLORS.bright}Options:${COLORS.reset}
|
|
|
213
800
|
--skip-backlog Don't create Backlog.md
|
|
214
801
|
--skip-mcp Don't create mcp.json.example
|
|
215
802
|
--skip-beads Skip beads check (not recommended)
|
|
803
|
+
--verbose Show detailed diagnostics on errors
|
|
216
804
|
|
|
217
805
|
${COLORS.bright}Examples:${COLORS.reset}
|
|
218
806
|
npx beth-copilot init Set up Beth in current project
|
|
@@ -281,10 +869,14 @@ ${COLORS.yellow}╔════════════════════
|
|
|
281
869
|
`);
|
|
282
870
|
}
|
|
283
871
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
872
|
+
// Show Beth's fire animation
|
|
873
|
+
if (canAnimate()) {
|
|
874
|
+
await animateBethBanner();
|
|
875
|
+
} else {
|
|
876
|
+
showBethBannerStatic({ showQuickHelp: false });
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
log(`${COLORS.yellow}Tip: Run with --verbose for detailed diagnostics if you hit issues.${COLORS.reset}`);
|
|
288
880
|
|
|
289
881
|
// Check if templates exist
|
|
290
882
|
if (!existsSync(TEMPLATES_DIR)) {
|
|
@@ -382,7 +974,10 @@ ${COLORS.cyan}"I don't do excuses. I do results."${COLORS.reset}
|
|
|
382
974
|
console.log('');
|
|
383
975
|
log('Checking beads (required for task tracking)...', COLORS.cyan);
|
|
384
976
|
|
|
385
|
-
|
|
977
|
+
let bdPath = getBeadsPath();
|
|
978
|
+
|
|
979
|
+
// Loop until beads is installed
|
|
980
|
+
while (!bdPath) {
|
|
386
981
|
logWarning('beads CLI is not installed.');
|
|
387
982
|
logInfo('Beth requires beads for task tracking. Agents use it to coordinate work.');
|
|
388
983
|
logInfo('Learn more: https://github.com/steveyegge/beads');
|
|
@@ -391,16 +986,70 @@ ${COLORS.cyan}"I don't do excuses. I do results."${COLORS.reset}
|
|
|
391
986
|
const shouldInstallBeads = await promptYesNo('Install beads CLI now? (required)');
|
|
392
987
|
if (shouldInstallBeads) {
|
|
393
988
|
const installed = await installBeads();
|
|
394
|
-
if (
|
|
395
|
-
|
|
396
|
-
|
|
989
|
+
if (installed) {
|
|
990
|
+
// Re-check for beads after installation
|
|
991
|
+
bdPath = getBeadsPath();
|
|
992
|
+
if (!bdPath) {
|
|
993
|
+
console.log('');
|
|
994
|
+
logWarning('beads installed but not found in common paths.');
|
|
995
|
+
logInfo('The installer may have placed it in a custom location.');
|
|
996
|
+
console.log('');
|
|
997
|
+
logInfo('Please try one of these options:');
|
|
998
|
+
logInfo(' 1. Open a NEW terminal and run: npx beth-copilot init');
|
|
999
|
+
logInfo(' 2. Add ~/.local/bin to your PATH and retry');
|
|
1000
|
+
logInfo(' 3. Run: source ~/.bashrc (or ~/.zshrc) then retry');
|
|
1001
|
+
console.log('');
|
|
1002
|
+
|
|
1003
|
+
const retryCheck = await promptYesNo('Retry detection? (select No to enter path manually)');
|
|
1004
|
+
if (retryCheck) {
|
|
1005
|
+
bdPath = getBeadsPath();
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// Allow manual path entry
|
|
1010
|
+
const customPath = await promptForInput('Enter full path to bd binary (or press Enter to retry installation):');
|
|
1011
|
+
if (customPath) {
|
|
1012
|
+
const validation = validateBeadsPath(customPath);
|
|
1013
|
+
if (validation.valid) {
|
|
1014
|
+
bdPath = validation.normalizedPath;
|
|
1015
|
+
logSuccess(`Found beads at: ${bdPath}`);
|
|
1016
|
+
} else {
|
|
1017
|
+
logError(`Invalid path: ${validation.error}`);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
} else {
|
|
1022
|
+
console.log('');
|
|
1023
|
+
logError('Installation script failed.');
|
|
1024
|
+
logInfo('You can try installing manually:');
|
|
1025
|
+
logInfo(' curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash');
|
|
1026
|
+
console.log('');
|
|
1027
|
+
}
|
|
1028
|
+
} else {
|
|
1029
|
+
console.log('');
|
|
1030
|
+
logError('beads is REQUIRED for Beth to function.');
|
|
1031
|
+
logInfo('Beth agents use beads to track tasks, dependencies, and coordinate work.');
|
|
1032
|
+
logInfo('Without beads, the multi-agent workflow will not work correctly.');
|
|
1033
|
+
console.log('');
|
|
1034
|
+
|
|
1035
|
+
const tryAgain = await promptYesNo('Would you like to try installing beads?');
|
|
1036
|
+
if (!tryAgain) {
|
|
1037
|
+
logError('Cannot continue without beads. Exiting.');
|
|
1038
|
+
logInfo('Install beads manually and run "npx beth-copilot init" again:');
|
|
1039
|
+
logInfo(' npm install -g @beads/bd');
|
|
397
1040
|
process.exit(1);
|
|
398
1041
|
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// Show path info if not in standard PATH
|
|
1046
|
+
if (bdPath && bdPath !== 'bd') {
|
|
1047
|
+
logSuccess(`beads CLI found at: ${bdPath}`);
|
|
1048
|
+
const isWindows = process.platform === 'win32';
|
|
1049
|
+
if (isWindows) {
|
|
1050
|
+
logInfo('Tip: Ensure npm global bin is in your PATH to use "bd" directly.');
|
|
399
1051
|
} else {
|
|
400
|
-
|
|
401
|
-
logInfo('Install beads and run "beth init" again:');
|
|
402
|
-
logInfo(' curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash');
|
|
403
|
-
process.exit(1);
|
|
1052
|
+
logInfo('Tip: Add ~/.local/bin or npm global bin to your PATH to use "bd" directly.');
|
|
404
1053
|
}
|
|
405
1054
|
} else {
|
|
406
1055
|
logSuccess('beads CLI is installed');
|
|
@@ -409,11 +1058,20 @@ ${COLORS.cyan}"I don't do excuses. I do results."${COLORS.reset}
|
|
|
409
1058
|
// Initialize beads in the project if not already done
|
|
410
1059
|
if (!isBeadsInitialized(cwd)) {
|
|
411
1060
|
logInfo('beads not initialized in this project.');
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
1061
|
+
let initialized = false;
|
|
1062
|
+
|
|
1063
|
+
while (!initialized) {
|
|
1064
|
+
const shouldInitBeads = await promptYesNo('Initialize beads now? (required)');
|
|
1065
|
+
if (shouldInitBeads) {
|
|
1066
|
+
initialized = await initializeBeads(cwd);
|
|
1067
|
+
if (!initialized) {
|
|
1068
|
+
logWarning('Initialization failed. Let\'s try again.');
|
|
1069
|
+
}
|
|
1070
|
+
} else {
|
|
1071
|
+
logError('beads must be initialized for Beth to work correctly.');
|
|
1072
|
+
logInfo('The .beads directory stores task tracking data used by all agents.');
|
|
1073
|
+
console.log('');
|
|
1074
|
+
}
|
|
417
1075
|
}
|
|
418
1076
|
} else {
|
|
419
1077
|
logSuccess('beads is initialized in this project');
|
|
@@ -422,22 +1080,90 @@ ${COLORS.cyan}"I don't do excuses. I do results."${COLORS.reset}
|
|
|
422
1080
|
logWarning('Skipped beads check (--skip-beads). Beth may not function correctly.');
|
|
423
1081
|
}
|
|
424
1082
|
|
|
425
|
-
// Check for backlog.md CLI (
|
|
426
|
-
if (!skipBacklog
|
|
427
|
-
console.log('');
|
|
428
|
-
logWarning('backlog.md CLI is not installed (optional).');
|
|
429
|
-
logInfo('The CLI provides TUI boards, web UI, and task management commands.');
|
|
430
|
-
logInfo('Learn more: https://github.com/MrLesk/Backlog.md');
|
|
1083
|
+
// Check for backlog.md CLI (REQUIRED for Beth)
|
|
1084
|
+
if (!skipBacklog) {
|
|
431
1085
|
console.log('');
|
|
1086
|
+
log('Checking backlog.md CLI (required for task management)...', COLORS.cyan);
|
|
432
1087
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
1088
|
+
let backlogPath = getBacklogPath();
|
|
1089
|
+
|
|
1090
|
+
// Loop until backlog.md is installed
|
|
1091
|
+
while (!backlogPath) {
|
|
1092
|
+
logWarning('backlog.md CLI is not installed.');
|
|
1093
|
+
logInfo('Beth requires backlog.md for human-readable task tracking and boards.');
|
|
1094
|
+
logInfo('Learn more: https://github.com/MrLesk/Backlog.md');
|
|
1095
|
+
console.log('');
|
|
1096
|
+
|
|
1097
|
+
const shouldInstall = await promptYesNo('Install backlog.md CLI now? (required)');
|
|
1098
|
+
if (shouldInstall) {
|
|
1099
|
+
const installed = await installBacklogCli();
|
|
1100
|
+
if (installed) {
|
|
1101
|
+
// Re-check for backlog after installation
|
|
1102
|
+
backlogPath = getBacklogPath();
|
|
1103
|
+
if (!backlogPath) {
|
|
1104
|
+
console.log('');
|
|
1105
|
+
logWarning('backlog.md installed but not found in common paths.');
|
|
1106
|
+
logInfo('The installer may have placed it in a custom location.');
|
|
1107
|
+
console.log('');
|
|
1108
|
+
logInfo('Please try one of these options:');
|
|
1109
|
+
logInfo(' 1. Open a NEW terminal and run: npx beth-copilot init');
|
|
1110
|
+
logInfo(' 2. Run: source ~/.bashrc (or ~/.zshrc) then retry');
|
|
1111
|
+
console.log('');
|
|
1112
|
+
|
|
1113
|
+
const retryCheck = await promptYesNo('Retry detection?');
|
|
1114
|
+
if (retryCheck) {
|
|
1115
|
+
backlogPath = getBacklogPath();
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
} else {
|
|
1119
|
+
console.log('');
|
|
1120
|
+
logError('Installation failed.');
|
|
1121
|
+
logInfo('You can try installing manually:');
|
|
1122
|
+
logInfo(' npm install -g backlog.md');
|
|
1123
|
+
if (process.platform === 'darwin') {
|
|
1124
|
+
logInfo(' brew install backlog-md');
|
|
1125
|
+
}
|
|
1126
|
+
logInfo(' bun install -g backlog.md');
|
|
1127
|
+
console.log('');
|
|
1128
|
+
}
|
|
1129
|
+
} else {
|
|
1130
|
+
console.log('');
|
|
1131
|
+
logError('backlog.md is REQUIRED for Beth to function.');
|
|
1132
|
+
logInfo('Beth uses Backlog.md to maintain human-readable task history and boards.');
|
|
1133
|
+
logInfo('This complements beads for a complete task management workflow.');
|
|
1134
|
+
console.log('');
|
|
1135
|
+
|
|
1136
|
+
const tryAgain = await promptYesNo('Would you like to try installing backlog.md?');
|
|
1137
|
+
if (!tryAgain) {
|
|
1138
|
+
logError('Cannot continue without backlog.md. Exiting.');
|
|
1139
|
+
logInfo('Install manually and run "npx beth-copilot init" again:');
|
|
1140
|
+
logInfo(' npm install -g backlog.md');
|
|
1141
|
+
process.exit(1);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
438
1144
|
}
|
|
439
|
-
|
|
440
|
-
logSuccess('backlog.md CLI is
|
|
1145
|
+
|
|
1146
|
+
logSuccess('backlog.md CLI is installed');
|
|
1147
|
+
} else {
|
|
1148
|
+
logWarning('Skipped backlog check (--skip-backlog). Beth may not function correctly.');
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// Final verification
|
|
1152
|
+
console.log('');
|
|
1153
|
+
log('Verifying installation...', COLORS.cyan);
|
|
1154
|
+
|
|
1155
|
+
const finalBeadsOk = skipBeads || getBeadsPath();
|
|
1156
|
+
const finalBacklogOk = skipBacklog || getBacklogPath();
|
|
1157
|
+
const finalBeadsInit = skipBeads || isBeadsInitialized(cwd);
|
|
1158
|
+
|
|
1159
|
+
if (finalBeadsOk && finalBacklogOk && finalBeadsInit) {
|
|
1160
|
+
logSuccess('All dependencies installed and configured!');
|
|
1161
|
+
} else {
|
|
1162
|
+
if (!finalBeadsOk) logError('beads CLI not found');
|
|
1163
|
+
if (!finalBacklogOk) logError('backlog.md CLI not found');
|
|
1164
|
+
if (!finalBeadsInit) logError('beads not initialized in project');
|
|
1165
|
+
logError('Setup incomplete. Please resolve issues above and run init again.');
|
|
1166
|
+
process.exit(1);
|
|
441
1167
|
}
|
|
442
1168
|
|
|
443
1169
|
// Next steps
|
|
@@ -458,7 +1184,7 @@ ${COLORS.cyan}"They broke my wings and forgot I had claws."${COLORS.reset}
|
|
|
458
1184
|
|
|
459
1185
|
// Input validation constants
|
|
460
1186
|
const ALLOWED_COMMANDS = ['init', 'help', '--help', '-h'];
|
|
461
|
-
const ALLOWED_FLAGS = ['--force', '--skip-backlog', '--skip-mcp', '--skip-beads'];
|
|
1187
|
+
const ALLOWED_FLAGS = ['--force', '--skip-backlog', '--skip-mcp', '--skip-beads', '--verbose'];
|
|
462
1188
|
const MAX_ARG_LENGTH = 50;
|
|
463
1189
|
|
|
464
1190
|
// Validate and sanitize input
|
|
@@ -488,10 +1214,14 @@ const options = {
|
|
|
488
1214
|
skipBacklog: args.includes('--skip-backlog'),
|
|
489
1215
|
skipMcp: args.includes('--skip-mcp'),
|
|
490
1216
|
skipBeads: args.includes('--skip-beads'),
|
|
1217
|
+
verbose: args.includes('--verbose'),
|
|
491
1218
|
};
|
|
492
1219
|
|
|
493
|
-
//
|
|
494
|
-
|
|
1220
|
+
// Set global verbose flag for logDebug
|
|
1221
|
+
globalThis.VERBOSE = options.verbose;
|
|
1222
|
+
|
|
1223
|
+
// Validate unknown flags (exclude --help which is handled as a command)
|
|
1224
|
+
const unknownFlags = args.filter(arg => arg.startsWith('--') && !ALLOWED_FLAGS.includes(arg) && arg !== '--help');
|
|
495
1225
|
if (unknownFlags.length > 0) {
|
|
496
1226
|
logError(`Unknown flag: ${unknownFlags[0].slice(0, MAX_ARG_LENGTH)}`);
|
|
497
1227
|
console.log('Run "npx beth-copilot help" for usage information.');
|