vibecodingmachine-cli 2025.12.1-534 ā 2025.12.22-2230
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/bin/vibecodingmachine.js +301 -12
- package/package.json +5 -2
- package/repro_open.js +13 -0
- package/reproduce_issue.js +160 -0
- package/scripts/postinstall.js +80 -0
- package/src/commands/auth.js +0 -1
- package/src/commands/auto-direct.js +455 -136
- package/src/commands/auto.js +488 -163
- package/src/commands/computers.js +306 -0
- package/src/commands/repo.js +0 -1
- package/src/commands/requirements-remote.js +308 -0
- package/src/commands/requirements.js +233 -16
- package/src/commands/status.js +0 -1
- package/src/commands/sync.js +280 -0
- package/src/utils/agent-selector.js +50 -0
- package/src/utils/antigravity-installer.js +212 -0
- package/src/utils/antigravity-js-handler.js +60 -0
- package/src/utils/asset-cleanup.js +60 -0
- package/src/utils/auth.js +232 -8
- package/src/utils/auto-mode-ansi-ui.js +0 -1
- package/src/utils/auto-mode-simple-ui.js +3 -23
- package/src/utils/compliance-check.js +166 -0
- package/src/utils/config.js +27 -1
- package/src/utils/copy-with-progress.js +167 -0
- package/src/utils/download-with-progress.js +84 -0
- package/src/utils/first-run.js +410 -0
- package/src/utils/interactive.js +1197 -391
- package/src/utils/kiro-installer.js +178 -0
- package/src/utils/persistent-header.js +1 -3
- package/src/utils/provider-registry.js +13 -4
- package/src/utils/status-card.js +2 -1
- package/src/utils/user-tracking.js +300 -0
- package/tests/requirements-navigator-buildtree-await.test.js +28 -0
package/src/utils/interactive.js
CHANGED
|
@@ -8,7 +8,8 @@ const readline = require('readline');
|
|
|
8
8
|
const repo = require('../commands/repo');
|
|
9
9
|
const auto = require('../commands/auto');
|
|
10
10
|
const status = require('../commands/status');
|
|
11
|
-
const
|
|
11
|
+
const requirements = require('../commands/requirements');
|
|
12
|
+
const { getRepoPath, readConfig, writeConfig, getAutoConfig } = require('./config');
|
|
12
13
|
const { getProviderPreferences, saveProviderPreferences, getProviderDefinitions } = require('../utils/provider-registry');
|
|
13
14
|
const { checkAutoModeStatus } = require('./auto-mode');
|
|
14
15
|
const {
|
|
@@ -18,6 +19,7 @@ const {
|
|
|
18
19
|
requirementsExists,
|
|
19
20
|
isComputerNameEnabled
|
|
20
21
|
} = require('vibecodingmachine-core');
|
|
22
|
+
const pkg = require('../../package.json');
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
25
|
* Format IDE name for display
|
|
@@ -32,7 +34,8 @@ function formatIDEName(ide) {
|
|
|
32
34
|
'cline': 'Cline CLI',
|
|
33
35
|
'cursor': 'Cursor',
|
|
34
36
|
'vscode': 'VS Code',
|
|
35
|
-
'windsurf': 'Windsurf'
|
|
37
|
+
'windsurf': 'Windsurf',
|
|
38
|
+
'kiro': 'AWS Kiro'
|
|
36
39
|
};
|
|
37
40
|
return ideNames[ide] || ide;
|
|
38
41
|
}
|
|
@@ -52,6 +55,7 @@ function getAgentDisplayName(agentType) {
|
|
|
52
55
|
if (agentType === 'cursor') return 'Cursor IDE Agent';
|
|
53
56
|
if (agentType === 'windsurf') return 'Windsurf IDE Agent';
|
|
54
57
|
if (agentType === 'antigravity') return 'Google Antigravity IDE Agent';
|
|
58
|
+
if (agentType === 'kiro') return 'AWS Kiro AI IDE Agent';
|
|
55
59
|
if (agentType === 'vscode') return 'VS Code IDE Agent';
|
|
56
60
|
|
|
57
61
|
// Claude Code CLI
|
|
@@ -210,95 +214,9 @@ function formatPath(fullPath) {
|
|
|
210
214
|
|
|
211
215
|
async function countRequirements() {
|
|
212
216
|
try {
|
|
213
|
-
const {
|
|
217
|
+
const { getProjectRequirementStats } = require('vibecodingmachine-core');
|
|
214
218
|
const repoPath = await getRepoPath();
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (!reqPath || !await fs.pathExists(reqPath)) {
|
|
218
|
-
return null;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const content = await fs.readFile(reqPath, 'utf8');
|
|
222
|
-
|
|
223
|
-
// Count requirements in each section
|
|
224
|
-
let todoCount = 0;
|
|
225
|
-
let toVerifyCount = 0;
|
|
226
|
-
let verifiedCount = 0;
|
|
227
|
-
|
|
228
|
-
// Split by sections
|
|
229
|
-
const lines = content.split('\n');
|
|
230
|
-
let currentSection = '';
|
|
231
|
-
|
|
232
|
-
for (const line of lines) {
|
|
233
|
-
const trimmed = line.trim();
|
|
234
|
-
|
|
235
|
-
// Check for requirement headers first (###), then section headers (##)
|
|
236
|
-
// This prevents ### from being treated as section headers
|
|
237
|
-
if (trimmed.startsWith('###')) {
|
|
238
|
-
// Count requirements (### headers in new format)
|
|
239
|
-
// IMPORTANT: Only count if we're in a recognized section
|
|
240
|
-
if (currentSection) {
|
|
241
|
-
const requirementText = trimmed.replace(/^###\s*/, '').trim();
|
|
242
|
-
if (requirementText) { // Only count if requirement text is not empty
|
|
243
|
-
if (currentSection === 'todo') {
|
|
244
|
-
todoCount++;
|
|
245
|
-
} else if (currentSection === 'toverify') {
|
|
246
|
-
toVerifyCount++;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
} else if (trimmed.startsWith('##') && !trimmed.startsWith('###')) {
|
|
251
|
-
// Detect section headers (must start with ## but not ###)
|
|
252
|
-
if (trimmed.includes('ā³ Requirements not yet completed') ||
|
|
253
|
-
trimmed.includes('Requirements not yet completed')) {
|
|
254
|
-
currentSection = 'todo';
|
|
255
|
-
} else if (trimmed.includes('š TO VERIFY BY HUMAN') ||
|
|
256
|
-
trimmed.includes('TO VERIFY BY HUMAN') ||
|
|
257
|
-
trimmed.includes('š TO VERIFY') ||
|
|
258
|
-
trimmed.includes('TO VERIFY') ||
|
|
259
|
-
trimmed.includes('ā
Verified by AI') ||
|
|
260
|
-
trimmed.includes('Verified by AI')) {
|
|
261
|
-
currentSection = 'toverify';
|
|
262
|
-
} else {
|
|
263
|
-
// Any other section header clears the current section
|
|
264
|
-
currentSection = '';
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Count verified requirements from CHANGELOG.md (at repository root)
|
|
270
|
-
const allnightDir = await getVibeCodingMachineDir();
|
|
271
|
-
if (allnightDir) {
|
|
272
|
-
// CHANGELOG.md is at the repository root
|
|
273
|
-
// If .vibecodingmachine is inside repo: go up one level
|
|
274
|
-
// If .vibecodingmachine is sibling (../.vibecodingmachine-reponame): go up one level then into repo
|
|
275
|
-
let changelogPath;
|
|
276
|
-
const allnightStatus = await require('vibecodingmachine-core').checkVibeCodingMachineExists();
|
|
277
|
-
|
|
278
|
-
if (allnightStatus.insideExists) {
|
|
279
|
-
// .vibecodingmachine is inside repo, so go up one level
|
|
280
|
-
changelogPath = path.join(path.dirname(allnightDir), 'CHANGELOG.md');
|
|
281
|
-
} else if (allnightStatus.siblingExists) {
|
|
282
|
-
// .vibecodingmachine is sibling, use current working directory
|
|
283
|
-
changelogPath = path.join(process.cwd(), 'CHANGELOG.md');
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (changelogPath && await fs.pathExists(changelogPath)) {
|
|
287
|
-
const changelogContent = await fs.readFile(changelogPath, 'utf8');
|
|
288
|
-
// Count entries that look like completed requirements
|
|
289
|
-
// Each entry typically starts with "- " followed by date/description
|
|
290
|
-
const changelogLines = changelogContent.split('\n');
|
|
291
|
-
for (const line of changelogLines) {
|
|
292
|
-
const trimmed = line.trim();
|
|
293
|
-
// Count lines that start with "- " and have substantial content (not just empty bullets)
|
|
294
|
-
if (trimmed.startsWith('- ') && trimmed.length > 10) {
|
|
295
|
-
verifiedCount++;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return { todoCount, toVerifyCount, verifiedCount };
|
|
219
|
+
return await getProjectRequirementStats(repoPath);
|
|
302
220
|
} catch (error) {
|
|
303
221
|
return null;
|
|
304
222
|
}
|
|
@@ -350,36 +268,27 @@ async function getCurrentProgress() {
|
|
|
350
268
|
}
|
|
351
269
|
|
|
352
270
|
async function showWelcomeScreen() {
|
|
271
|
+
|
|
272
|
+
|
|
353
273
|
const repoPath = process.cwd(); // Always use current working directory
|
|
354
274
|
const autoStatus = await checkAutoModeStatus();
|
|
355
|
-
const allnightStatus = await checkVibeCodingMachineExists();
|
|
356
275
|
const hostname = getHostname();
|
|
357
|
-
const requirementsFilename = await getRequirementsFilename();
|
|
358
|
-
const useHostname = await isComputerNameEnabled();
|
|
359
276
|
|
|
360
277
|
// Get current IDE from config
|
|
361
278
|
const { getAutoConfig } = require('./config');
|
|
362
279
|
const autoConfig = await getAutoConfig();
|
|
363
|
-
const currentIDE = autoConfig.ide || autoStatus.ide || 'claude-code';
|
|
364
280
|
|
|
365
281
|
// Check for requirements file
|
|
366
282
|
const hasRequirements = await requirementsExists();
|
|
367
|
-
let requirementsLocation = '';
|
|
368
|
-
|
|
369
|
-
if (allnightStatus.insideExists) {
|
|
370
|
-
requirementsLocation = '.vibecodingmachine';
|
|
371
|
-
} else if (allnightStatus.siblingExists) {
|
|
372
|
-
requirementsLocation = path.basename(allnightStatus.siblingDir);
|
|
373
|
-
}
|
|
374
283
|
|
|
375
284
|
// Count requirements if file exists
|
|
376
285
|
const counts = hasRequirements ? await countRequirements() : null;
|
|
377
286
|
|
|
378
|
-
// Clear the screen
|
|
287
|
+
// Clear the screen using console.clear() for better cross-platform compatibility
|
|
288
|
+
// This ensures proper screen refresh and prevents text overlap
|
|
379
289
|
console.clear();
|
|
380
290
|
|
|
381
291
|
// Get version from package.json
|
|
382
|
-
const pkg = require('../../package.json');
|
|
383
292
|
const version = `v${pkg.version}`;
|
|
384
293
|
|
|
385
294
|
// Display welcome banner with version
|
|
@@ -419,14 +328,18 @@ async function showWelcomeScreen() {
|
|
|
419
328
|
const icon = stageIcons[progress.status] || 'ā³';
|
|
420
329
|
const statusColor = progress.status === 'DONE' ? chalk.green : chalk.magenta;
|
|
421
330
|
|
|
331
|
+
// Truncate requirement text first, THEN apply color to fix box alignment
|
|
332
|
+
const requirementText = progress.requirement ? progress.requirement.substring(0, 60) + (progress.requirement.length > 60 ? '...' : '') : 'No requirement';
|
|
333
|
+
|
|
422
334
|
console.log(boxen(
|
|
423
335
|
statusColor.bold(`${icon} ${progress.status}`) + '\n' +
|
|
424
|
-
chalk.gray(
|
|
336
|
+
chalk.gray(requirementText),
|
|
425
337
|
{
|
|
426
338
|
padding: { left: 1, right: 1, top: 0, bottom: 0 },
|
|
427
339
|
margin: 0,
|
|
428
340
|
borderStyle: 'round',
|
|
429
|
-
borderColor: 'magenta'
|
|
341
|
+
borderColor: 'magenta',
|
|
342
|
+
width: 70
|
|
430
343
|
}
|
|
431
344
|
));
|
|
432
345
|
}
|
|
@@ -475,7 +388,7 @@ async function showRequirementsTree() {
|
|
|
475
388
|
};
|
|
476
389
|
|
|
477
390
|
// Build tree structure
|
|
478
|
-
const buildTree = () => {
|
|
391
|
+
const buildTree = async () => {
|
|
479
392
|
tree.items = [];
|
|
480
393
|
|
|
481
394
|
// Root: Requirements
|
|
@@ -485,63 +398,72 @@ async function showRequirementsTree() {
|
|
|
485
398
|
tree.items.push({ level: 1, type: 'add', label: 'ā Add new requirement', key: 'add-one' });
|
|
486
399
|
tree.items.push({ level: 1, type: 'add', label: 'ā Add multiple requirements', key: 'add-many' });
|
|
487
400
|
|
|
488
|
-
//
|
|
401
|
+
// Use pre-calculated stats and labels from shared logic
|
|
402
|
+
const stats = await countRequirements();
|
|
403
|
+
const { todoCount, toVerifyCount, verifiedCount, total, todoLabel, toVerifyLabel, verifiedLabel } = stats || {
|
|
404
|
+
todoCount: 0, toVerifyCount: 0, verifiedCount: 0, total: 0,
|
|
405
|
+
todoLabel: 'ā³ TODO (0 - 0%)', toVerifyLabel: 'ā
TO VERIFY (0 - 0%)', verifiedLabel: 'š VERIFIED (0 - 0%)'
|
|
406
|
+
};
|
|
407
|
+
|
|
489
408
|
const verifiedReqs = tree.verifiedReqs || [];
|
|
490
409
|
const verifyReqs = tree.verifyReqs || [];
|
|
491
410
|
const clarificationReqs = tree.clarificationReqs || [];
|
|
492
411
|
const todoReqs = tree.todoReqs || [];
|
|
493
412
|
const recycledReqs = tree.recycledReqs || [];
|
|
494
|
-
const total = verifiedReqs.length + verifyReqs.length + clarificationReqs.length + todoReqs.length + recycledReqs.length;
|
|
495
|
-
|
|
496
|
-
const verifiedPercent = total > 0 ? Math.round((verifiedReqs.length / total) * 100) : 0;
|
|
497
|
-
const verifyPercent = total > 0 ? Math.round((verifyReqs.length / total) * 100) : 0;
|
|
498
|
-
const clarificationPercent = total > 0 ? Math.round((clarificationReqs.length / total) * 100) : 0;
|
|
499
|
-
const todoPercent = total > 0 ? Math.round((todoReqs.length / total) * 100) : 0;
|
|
500
|
-
const recycledPercent = total > 0 ? Math.round((recycledReqs.length / total) * 100) : 0;
|
|
501
413
|
|
|
502
|
-
// VERIFIED section (first)
|
|
503
|
-
|
|
414
|
+
// VERIFIED section (first) - only show if has requirements
|
|
415
|
+
if (verifiedReqs.length > 0 || verifiedCount > 0) {
|
|
416
|
+
tree.items.push({ level: 1, type: 'section', label: `š ${verifiedLabel}`, key: 'verified' });
|
|
504
417
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
418
|
+
if (tree.expanded.verified) {
|
|
419
|
+
verifiedReqs.forEach((req, idx) => {
|
|
420
|
+
tree.items.push({ level: 2, type: 'verified', label: req, key: `verified-${idx}` });
|
|
421
|
+
});
|
|
422
|
+
}
|
|
509
423
|
}
|
|
510
424
|
|
|
511
|
-
// TO VERIFY section (second)
|
|
512
|
-
|
|
425
|
+
// TO VERIFY section (second) - only show if has requirements
|
|
426
|
+
if (verifyReqs.length > 0 || toVerifyCount > 0) {
|
|
427
|
+
tree.items.push({ level: 1, type: 'section', label: `ā
${toVerifyLabel}`, key: 'verify', section: 'ā
Verified by AI screenshot' });
|
|
513
428
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
429
|
+
if (tree.expanded.verify) {
|
|
430
|
+
verifyReqs.forEach((req, idx) => {
|
|
431
|
+
tree.items.push({ level: 2, type: 'requirement', label: req.title, key: `verify-${idx}`, req, sectionKey: 'verify' });
|
|
432
|
+
});
|
|
433
|
+
}
|
|
518
434
|
}
|
|
519
435
|
|
|
520
|
-
// NEEDING CLARIFICATION section (third)
|
|
521
|
-
|
|
436
|
+
// NEEDING CLARIFICATION section (third) - only show if has requirements
|
|
437
|
+
if (clarificationReqs.length > 0) {
|
|
438
|
+
tree.items.push({ level: 1, type: 'section', label: `ā NEEDING CLARIFICATION (${clarificationReqs.length} - ${clarificationPercent}%)`, key: 'clarification', section: 'ā Requirements needing manual feedback' });
|
|
522
439
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
440
|
+
if (tree.expanded.clarification) {
|
|
441
|
+
clarificationReqs.forEach((req, idx) => {
|
|
442
|
+
tree.items.push({ level: 2, type: 'clarification', label: req.title, key: `clarification-${idx}`, req, sectionKey: 'clarification' });
|
|
443
|
+
});
|
|
444
|
+
}
|
|
527
445
|
}
|
|
528
446
|
|
|
529
|
-
// TODO section (fourth)
|
|
530
|
-
|
|
447
|
+
// TODO section (fourth) - only show if has requirements
|
|
448
|
+
if (todoReqs.length > 0 || todoCount > 0) {
|
|
449
|
+
tree.items.push({ level: 1, type: 'section', label: `ā³ ${todoLabel}`, key: 'todo', section: 'ā³ Requirements not yet completed' });
|
|
531
450
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
451
|
+
if (tree.expanded.todo) {
|
|
452
|
+
todoReqs.forEach((req, idx) => {
|
|
453
|
+
tree.items.push({ level: 2, type: 'requirement', label: req.title, key: `todo-${idx}`, req, sectionKey: 'todo' });
|
|
454
|
+
});
|
|
455
|
+
}
|
|
536
456
|
}
|
|
537
457
|
|
|
538
|
-
// RECYCLED section (last)
|
|
539
|
-
|
|
458
|
+
// RECYCLED section (last) - only show if has requirements
|
|
459
|
+
if (recycledReqs.length > 0) {
|
|
460
|
+
tree.items.push({ level: 1, type: 'section', label: `ā»ļø RECYCLED (${recycledReqs.length} - ${recycledPercent}%)`, key: 'recycled', section: 'ā»ļø Recycled' });
|
|
540
461
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
462
|
+
if (tree.expanded.recycled) {
|
|
463
|
+
recycledReqs.forEach((req, idx) => {
|
|
464
|
+
tree.items.push({ level: 2, type: 'recycled', label: req.title, key: `recycled-${idx}`, req, sectionKey: 'recycled' });
|
|
465
|
+
});
|
|
466
|
+
}
|
|
545
467
|
}
|
|
546
468
|
}
|
|
547
469
|
};
|
|
@@ -563,27 +485,96 @@ async function showRequirementsTree() {
|
|
|
563
485
|
|
|
564
486
|
// For TO VERIFY section, check multiple possible section titles
|
|
565
487
|
const sectionTitles = sectionKey === 'verify'
|
|
566
|
-
? ['š TO VERIFY BY HUMAN', 'TO VERIFY BY HUMAN', 'š TO VERIFY', 'TO VERIFY', 'ā
Verified by AI screenshot', 'Verified by AI screenshot']
|
|
488
|
+
? ['š TO VERIFY BY HUMAN', 'TO VERIFY BY HUMAN', 'š TO VERIFY', 'TO VERIFY', 'ā
Verified by AI screenshot', 'Verified by AI screenshot', 'Verified by AI screenshot. Needs Human to Verify and move to CHANGELOG']
|
|
567
489
|
: [sectionTitle];
|
|
568
490
|
|
|
491
|
+
// For TO VERIFY, we need to find the exact section header
|
|
492
|
+
// The section header is: ## ā
Verified by AI screenshot. Needs Human to Verify and move to CHANGELOG
|
|
493
|
+
const toVerifySectionHeader = '## ā
Verified by AI screenshot. Needs Human to Verify and move to CHANGELOG';
|
|
494
|
+
|
|
569
495
|
for (let i = 0; i < lines.length; i++) {
|
|
570
496
|
const line = lines[i];
|
|
497
|
+
const trimmed = line.trim();
|
|
571
498
|
|
|
572
499
|
// Check if this line matches any of the section titles
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
500
|
+
// IMPORTANT: Only check section headers (lines starting with ##), not requirement text
|
|
501
|
+
if (trimmed.startsWith('##') && !trimmed.startsWith('###')) {
|
|
502
|
+
// Reset inSection if we hit a section header that's not our target section
|
|
503
|
+
if (sectionKey === 'verify' && inSection) {
|
|
504
|
+
// Check if this is still a TO VERIFY section header
|
|
505
|
+
if (!isStillToVerify) {
|
|
506
|
+
// This will be handled by the "leaving section" check below, but ensure we don't process it as entering
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (sectionKey === 'verify') {
|
|
510
|
+
// For TO VERIFY, check for the specific section header with exact matching
|
|
511
|
+
// Must match the exact TO VERIFY section header, not just any line containing "TO VERIFY"
|
|
512
|
+
const isToVerifyHeader = trimmed === '## š TO VERIFY BY HUMAN' ||
|
|
513
|
+
trimmed.startsWith('## š TO VERIFY BY HUMAN') ||
|
|
514
|
+
trimmed === '## š TO VERIFY' ||
|
|
515
|
+
trimmed.startsWith('## š TO VERIFY') ||
|
|
516
|
+
trimmed === '## TO VERIFY' ||
|
|
517
|
+
trimmed.startsWith('## TO VERIFY') ||
|
|
518
|
+
trimmed === '## ā
TO VERIFY' ||
|
|
519
|
+
trimmed.startsWith('## ā
TO VERIFY') ||
|
|
520
|
+
trimmed === toVerifySectionHeader ||
|
|
521
|
+
(trimmed.startsWith(toVerifySectionHeader) && trimmed.includes('Needs Human to Verify'));
|
|
522
|
+
|
|
523
|
+
if (isToVerifyHeader) {
|
|
524
|
+
// Make sure it's not a VERIFIED section (without TO VERIFY)
|
|
525
|
+
if (!trimmed.includes('## š VERIFIED') && !trimmed.match(/^##\s+VERIFIED$/i) && !trimmed.includes('š VERIFIED')) {
|
|
526
|
+
inSection = true;
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
} else {
|
|
530
|
+
// If we hit a different section header and we're looking for TO VERIFY, make sure we're not in section
|
|
531
|
+
// This prevents incorrectly reading from TODO or other sections
|
|
532
|
+
if (trimmed.includes('ā³ Requirements not yet completed') ||
|
|
533
|
+
trimmed.includes('Requirements not yet completed') ||
|
|
534
|
+
trimmed === '## š VERIFIED' ||
|
|
535
|
+
trimmed.startsWith('## š VERIFIED')) {
|
|
536
|
+
// We're in TODO or VERIFIED section, not TO VERIFY - reset
|
|
537
|
+
inSection = false;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
} else if (sectionTitles.some(title => trimmed.includes(title))) {
|
|
541
|
+
inSection = true;
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
576
544
|
}
|
|
577
545
|
|
|
578
|
-
|
|
579
|
-
|
|
546
|
+
// Check if we're leaving the section (new section header that doesn't match)
|
|
547
|
+
if (inSection && trimmed.startsWith('##') && !trimmed.startsWith('###')) {
|
|
548
|
+
// If this is a new section header and it's not one of our section titles, we've left the section
|
|
549
|
+
if (sectionKey === 'verify') {
|
|
550
|
+
// For TO VERIFY, only break if this is clearly a different section
|
|
551
|
+
// Check for specific section headers that indicate we've left TO VERIFY
|
|
552
|
+
|
|
553
|
+
if (isVerifiedSection || isTodoSection || isRecycledSection || isClarificationSection) {
|
|
554
|
+
break; // Different section, we've left TO VERIFY
|
|
555
|
+
}
|
|
556
|
+
// Otherwise, continue - might be REJECTED or CHANGELOG which are not section boundaries for TO VERIFY
|
|
557
|
+
} else {
|
|
558
|
+
// For other sections, break if it's a new section header that doesn't match
|
|
559
|
+
if (!sectionTitles.some(title => trimmed.includes(title))) {
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
580
563
|
}
|
|
581
564
|
|
|
582
565
|
// Read requirements in new format (### header)
|
|
583
566
|
if (inSection && line.trim().startsWith('###')) {
|
|
584
|
-
const title = line.trim().replace(/^###\s*/, '');
|
|
567
|
+
const title = line.trim().replace(/^###\s*/, '').trim();
|
|
568
|
+
|
|
569
|
+
// Skip malformed requirements (title is just a package name, empty, or too short)
|
|
570
|
+
// Common package names that shouldn't be requirement titles
|
|
571
|
+
const packageNames = ['cli', 'core', 'electron-app', 'web', 'mobile', 'vscode-extension', 'sync-server'];
|
|
572
|
+
if (!title || title.length === 0 || packageNames.includes(title.toLowerCase())) {
|
|
573
|
+
continue; // Skip this malformed requirement
|
|
574
|
+
}
|
|
575
|
+
|
|
585
576
|
const details = [];
|
|
586
|
-
let
|
|
577
|
+
let pkg = null;
|
|
587
578
|
|
|
588
579
|
// Read package and description
|
|
589
580
|
for (let j = i + 1; j < lines.length; j++) {
|
|
@@ -594,18 +585,29 @@ async function showRequirementsTree() {
|
|
|
594
585
|
}
|
|
595
586
|
// Check for PACKAGE line
|
|
596
587
|
if (nextLine.startsWith('PACKAGE:')) {
|
|
597
|
-
|
|
588
|
+
pkg = nextLine.replace(/^PACKAGE:\s*/, '').trim();
|
|
598
589
|
} else if (nextLine && !nextLine.startsWith('PACKAGE:')) {
|
|
599
590
|
// Description line
|
|
600
591
|
details.push(nextLine);
|
|
601
592
|
}
|
|
602
593
|
}
|
|
603
594
|
|
|
604
|
-
requirements.push({ title, details,
|
|
595
|
+
requirements.push({ title, details, pkg, lineIndex: i });
|
|
605
596
|
}
|
|
606
597
|
}
|
|
607
598
|
|
|
608
|
-
|
|
599
|
+
// Remove duplicates based on title (keep first occurrence)
|
|
600
|
+
const seenTitles = new Set();
|
|
601
|
+
const uniqueRequirements = [];
|
|
602
|
+
for (const req of requirements) {
|
|
603
|
+
const normalizedTitle = req.title.replace(/^TRY AGAIN \(\d+(st|nd|rd|th) time\):\s*/i, '').trim();
|
|
604
|
+
if (!seenTitles.has(normalizedTitle)) {
|
|
605
|
+
seenTitles.add(normalizedTitle);
|
|
606
|
+
uniqueRequirements.push(req);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return uniqueRequirements;
|
|
609
611
|
};
|
|
610
612
|
|
|
611
613
|
// Load VERIFIED requirements from CHANGELOG
|
|
@@ -721,7 +723,7 @@ async function showRequirementsTree() {
|
|
|
721
723
|
tree.recycledReqs = await loadSection('recycled', 'ā»ļø Recycled');
|
|
722
724
|
|
|
723
725
|
let inTree = true;
|
|
724
|
-
buildTree();
|
|
726
|
+
await buildTree();
|
|
725
727
|
|
|
726
728
|
while (inTree) {
|
|
727
729
|
console.clear();
|
|
@@ -828,7 +830,7 @@ async function showRequirementsTree() {
|
|
|
828
830
|
if (tree.expanded[current.key]) {
|
|
829
831
|
// Collapse expanded section
|
|
830
832
|
tree.expanded[current.key] = false;
|
|
831
|
-
buildTree();
|
|
833
|
+
await buildTree();
|
|
832
834
|
} else if (current.level > 0) {
|
|
833
835
|
// Go to parent
|
|
834
836
|
for (let i = tree.selected - 1; i >= 0; i--) {
|
|
@@ -841,10 +843,12 @@ async function showRequirementsTree() {
|
|
|
841
843
|
// At root level, go back to main menu
|
|
842
844
|
inTree = false;
|
|
843
845
|
}
|
|
844
|
-
} else if (key.name === 'up') {
|
|
846
|
+
} else if (key.name === 'k' || key.name === 'up') {
|
|
845
847
|
tree.selected = Math.max(0, tree.selected - 1);
|
|
846
|
-
|
|
848
|
+
await buildTree();
|
|
849
|
+
} else if (key.name === 'j' || key.name === 'down') {
|
|
847
850
|
tree.selected = Math.min(tree.items.length - 1, tree.selected + 1);
|
|
851
|
+
await buildTree();
|
|
848
852
|
} else if (key.name === 'right' || key.name === 'return' || key.name === 'space') {
|
|
849
853
|
const current = tree.items[tree.selected];
|
|
850
854
|
if (!current) continue; // Safety check
|
|
@@ -861,19 +865,19 @@ async function showRequirementsTree() {
|
|
|
861
865
|
} else if (current.key === 'recycled') {
|
|
862
866
|
tree.recycledReqs = await loadSection(current.key, current.section);
|
|
863
867
|
}
|
|
864
|
-
buildTree();
|
|
868
|
+
await buildTree();
|
|
865
869
|
} else {
|
|
866
870
|
tree.expanded[current.key] = false;
|
|
867
|
-
buildTree();
|
|
871
|
+
await buildTree();
|
|
868
872
|
}
|
|
869
873
|
} else if (current.type === 'requirement') {
|
|
870
874
|
// Show requirement actions
|
|
871
875
|
await showRequirementActions(current.req, current.sectionKey, tree);
|
|
872
|
-
buildTree();
|
|
876
|
+
await buildTree();
|
|
873
877
|
} else if (current.type === 'clarification') {
|
|
874
878
|
// Show clarification requirement with questions
|
|
875
879
|
await showClarificationActions(current.req, tree, loadClarification);
|
|
876
|
-
buildTree();
|
|
880
|
+
await buildTree();
|
|
877
881
|
} else if (current.type === 'verified') {
|
|
878
882
|
// Show verified item details (read-only)
|
|
879
883
|
console.clear();
|
|
@@ -900,7 +904,7 @@ async function showRequirementsTree() {
|
|
|
900
904
|
await handleAddRequirement(current.key);
|
|
901
905
|
// Reload TODO section
|
|
902
906
|
tree.todoReqs = await loadSection('todo', 'ā³ Requirements not yet completed');
|
|
903
|
-
buildTree();
|
|
907
|
+
await buildTree();
|
|
904
908
|
}
|
|
905
909
|
} else if (key.name === 'r') {
|
|
906
910
|
const current = tree.items[tree.selected];
|
|
@@ -908,15 +912,21 @@ async function showRequirementsTree() {
|
|
|
908
912
|
|
|
909
913
|
if (current.type === 'requirement') {
|
|
910
914
|
await deleteRequirement(current.req, current.sectionKey, tree);
|
|
911
|
-
|
|
915
|
+
// Reload the section that the requirement was deleted from
|
|
916
|
+
if (current.sectionKey === 'todo') {
|
|
917
|
+
tree.todoReqs = await loadSection('todo', 'ā³ Requirements not yet completed');
|
|
918
|
+
} else if (current.sectionKey === 'verify') {
|
|
919
|
+
tree.verifyReqs = await loadSection('verify', 'ā
Verified by AI screenshot');
|
|
920
|
+
}
|
|
921
|
+
await buildTree();
|
|
912
922
|
} else if (current.type === 'clarification') {
|
|
913
923
|
await deleteClarification(current.req, tree);
|
|
914
924
|
tree.clarificationReqs = await loadClarification();
|
|
915
|
-
buildTree();
|
|
925
|
+
await buildTree();
|
|
916
926
|
} else if (current.type === 'recycled') {
|
|
917
|
-
await
|
|
927
|
+
await permanentlyDeleteRequirement(current.req, current.sectionKey, tree);
|
|
918
928
|
tree.recycledReqs = await loadSection('recycled', 'ā»ļø Recycled');
|
|
919
|
-
buildTree();
|
|
929
|
+
await buildTree();
|
|
920
930
|
}
|
|
921
931
|
} else if (key.name === 'j') {
|
|
922
932
|
const current = tree.items[tree.selected];
|
|
@@ -924,7 +934,7 @@ async function showRequirementsTree() {
|
|
|
924
934
|
|
|
925
935
|
if (current.type === 'requirement') {
|
|
926
936
|
await moveRequirementDown(current.req, current.sectionKey, tree);
|
|
927
|
-
buildTree();
|
|
937
|
+
await buildTree();
|
|
928
938
|
// Move selection down to follow the item
|
|
929
939
|
if (tree.selected < tree.items.length - 1) {
|
|
930
940
|
tree.selected++;
|
|
@@ -936,7 +946,7 @@ async function showRequirementsTree() {
|
|
|
936
946
|
|
|
937
947
|
if (current.type === 'requirement') {
|
|
938
948
|
await moveRequirementUp(current.req, current.sectionKey, tree);
|
|
939
|
-
buildTree();
|
|
949
|
+
await buildTree();
|
|
940
950
|
// Move selection up to follow the item
|
|
941
951
|
if (tree.selected > 0) {
|
|
942
952
|
tree.selected--;
|
|
@@ -948,7 +958,7 @@ async function showRequirementsTree() {
|
|
|
948
958
|
|
|
949
959
|
if (current.type === 'requirement') {
|
|
950
960
|
await promoteRequirement(current.req, current.sectionKey, tree, loadSection, loadVerified);
|
|
951
|
-
buildTree();
|
|
961
|
+
await buildTree();
|
|
952
962
|
}
|
|
953
963
|
} else if (key.name === 'd') {
|
|
954
964
|
const current = tree.items[tree.selected];
|
|
@@ -959,12 +969,12 @@ async function showRequirementsTree() {
|
|
|
959
969
|
await moveClarificationToTodo(current.req, tree);
|
|
960
970
|
tree.clarificationReqs = await loadClarification();
|
|
961
971
|
tree.todoReqs = await loadSection('todo', 'ā³ Requirements not yet completed');
|
|
962
|
-
buildTree();
|
|
972
|
+
await buildTree();
|
|
963
973
|
} else if (current.type === 'requirement' || current.type === 'verified') {
|
|
964
974
|
const sectionKey = current.type === 'verified' ? 'verified' : current.sectionKey;
|
|
965
975
|
const reqTitle = current.type === 'verified' ? current.label : current.req.title;
|
|
966
976
|
await demoteRequirement(reqTitle, sectionKey, tree, loadSection, loadVerified);
|
|
967
|
-
buildTree();
|
|
977
|
+
await buildTree();
|
|
968
978
|
}
|
|
969
979
|
}
|
|
970
980
|
}
|
|
@@ -1404,8 +1414,9 @@ async function showClarificationActions(req, tree, loadClarification) {
|
|
|
1404
1414
|
}
|
|
1405
1415
|
async function showRequirementActions(req, sectionKey, tree) {
|
|
1406
1416
|
const actions = [
|
|
1407
|
-
{ label: '
|
|
1408
|
-
{ label: '
|
|
1417
|
+
{ label: 'āļø Rename/Edit', value: 'rename' },
|
|
1418
|
+
{ label: 'š Thumbs up (promote to Verified)', value: 'thumbs-up' },
|
|
1419
|
+
{ label: 'š Thumbs down (demote to TODO)', value: 'thumbs-down' },
|
|
1409
1420
|
{ label: 'ā¬ļø Move up', value: 'move-up' },
|
|
1410
1421
|
{ label: 'ā¬ļø Move down', value: 'move-down' },
|
|
1411
1422
|
{ label: 'šļø Delete', value: 'delete' }
|
|
@@ -1477,7 +1488,9 @@ async function showRequirementActions(req, sectionKey, tree) {
|
|
|
1477
1488
|
|
|
1478
1489
|
if (!key) continue;
|
|
1479
1490
|
|
|
1480
|
-
if (
|
|
1491
|
+
if (key.ctrl && key.name === 'c') {
|
|
1492
|
+
process.exit(0);
|
|
1493
|
+
} else if (key.name === 'escape' || key.name === 'left') {
|
|
1481
1494
|
return; // Go back
|
|
1482
1495
|
} else if (key.name === 'up') {
|
|
1483
1496
|
selected = Math.max(0, selected - 1);
|
|
@@ -1536,11 +1549,156 @@ async function performRequirementAction(action, req, sectionKey, tree) {
|
|
|
1536
1549
|
console.log(chalk.green('\nā Deleted\n'));
|
|
1537
1550
|
}
|
|
1538
1551
|
break;
|
|
1552
|
+
case 'rename':
|
|
1553
|
+
await renameRequirement(req, sectionKey, tree);
|
|
1554
|
+
break;
|
|
1539
1555
|
}
|
|
1540
1556
|
|
|
1541
1557
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1542
1558
|
}
|
|
1543
1559
|
|
|
1560
|
+
// Helper to rename requirement (title and description)
|
|
1561
|
+
async function renameRequirement(req, sectionKey, tree) {
|
|
1562
|
+
const { getRequirementsPath } = require('vibecodingmachine-core');
|
|
1563
|
+
const reqPath = await getRequirementsPath();
|
|
1564
|
+
|
|
1565
|
+
console.log(chalk.cyan('\nāļø Rename/Edit Requirement\n'));
|
|
1566
|
+
console.log(chalk.gray('Current title:'), chalk.white(req.title));
|
|
1567
|
+
if (req.details.length > 0) {
|
|
1568
|
+
console.log(chalk.gray('Current description:'));
|
|
1569
|
+
req.details.forEach(line => console.log(chalk.white(' ' + line)));
|
|
1570
|
+
}
|
|
1571
|
+
console.log();
|
|
1572
|
+
|
|
1573
|
+
const answers = await inquirer.prompt([
|
|
1574
|
+
{
|
|
1575
|
+
type: 'input',
|
|
1576
|
+
name: 'title',
|
|
1577
|
+
message: 'New title (leave blank to keep current):',
|
|
1578
|
+
default: ''
|
|
1579
|
+
}
|
|
1580
|
+
]);
|
|
1581
|
+
|
|
1582
|
+
const newTitle = answers.title.trim() || req.title;
|
|
1583
|
+
|
|
1584
|
+
// Ask for description using multi-line input
|
|
1585
|
+
console.log(chalk.gray('\nEnter new description (leave blank to keep current).'));
|
|
1586
|
+
console.log(chalk.gray('Press Enter twice on empty line to finish:\n'));
|
|
1587
|
+
|
|
1588
|
+
const descriptionLines = [];
|
|
1589
|
+
let emptyLineCount = 0;
|
|
1590
|
+
let isFirstLine = true;
|
|
1591
|
+
let newDescription = '';
|
|
1592
|
+
let keptCurrent = false;
|
|
1593
|
+
|
|
1594
|
+
while (true) {
|
|
1595
|
+
try {
|
|
1596
|
+
const { line } = await inquirer.prompt([{
|
|
1597
|
+
type: 'input',
|
|
1598
|
+
name: 'line',
|
|
1599
|
+
message: isFirstLine ? 'Description:' : ''
|
|
1600
|
+
}]);
|
|
1601
|
+
|
|
1602
|
+
if (isFirstLine && line.trim() === '') {
|
|
1603
|
+
// If first line is empty, keep current description
|
|
1604
|
+
keptCurrent = true;
|
|
1605
|
+
break;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
isFirstLine = false;
|
|
1609
|
+
|
|
1610
|
+
if (line.trim() === '') {
|
|
1611
|
+
emptyLineCount++;
|
|
1612
|
+
if (emptyLineCount >= 2) break;
|
|
1613
|
+
} else {
|
|
1614
|
+
emptyLineCount = 0;
|
|
1615
|
+
descriptionLines.push(line);
|
|
1616
|
+
}
|
|
1617
|
+
} catch (err) {
|
|
1618
|
+
break;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
if (!keptCurrent) {
|
|
1623
|
+
newDescription = descriptionLines.join('\n');
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// Read the requirements file
|
|
1627
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
1628
|
+
const lines = content.split('\n');
|
|
1629
|
+
|
|
1630
|
+
// Find the requirement block
|
|
1631
|
+
let requirementStartIndex = -1;
|
|
1632
|
+
let requirementEndIndex = -1;
|
|
1633
|
+
|
|
1634
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1635
|
+
const line = lines[i].trim();
|
|
1636
|
+
if (line.startsWith('###')) {
|
|
1637
|
+
const title = line.replace(/^###\s*/, '').trim();
|
|
1638
|
+
if (title === req.title) {
|
|
1639
|
+
requirementStartIndex = i;
|
|
1640
|
+
// Find the end of this requirement (next ### or ## header)
|
|
1641
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
1642
|
+
const nextLine = lines[j].trim();
|
|
1643
|
+
if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
|
|
1644
|
+
requirementEndIndex = j;
|
|
1645
|
+
break;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
if (requirementEndIndex === -1) {
|
|
1649
|
+
requirementEndIndex = lines.length;
|
|
1650
|
+
}
|
|
1651
|
+
break;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
if (requirementStartIndex === -1) {
|
|
1657
|
+
console.log(chalk.yellow('ā ļø Could not find requirement to rename'));
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
// Build new requirement block
|
|
1662
|
+
const newBlock = [`### ${newTitle}`];
|
|
1663
|
+
|
|
1664
|
+
// If new description provided, use it; otherwise keep existing details
|
|
1665
|
+
if (newDescription) {
|
|
1666
|
+
newDescription.split('\n').forEach(line => {
|
|
1667
|
+
if (line.trim()) {
|
|
1668
|
+
newBlock.push(line);
|
|
1669
|
+
}
|
|
1670
|
+
});
|
|
1671
|
+
} else {
|
|
1672
|
+
// Keep existing details (skip the ### header line)
|
|
1673
|
+
for (let i = requirementStartIndex + 1; i < requirementEndIndex; i++) {
|
|
1674
|
+
const line = lines[i];
|
|
1675
|
+
// Skip empty lines at the start and PACKAGE lines if we want to preserve them
|
|
1676
|
+
if (line.trim()) {
|
|
1677
|
+
newBlock.push(line);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
newBlock.push(''); // Blank line after requirement
|
|
1683
|
+
|
|
1684
|
+
// Replace the old block with the new one
|
|
1685
|
+
lines.splice(requirementStartIndex, requirementEndIndex - requirementStartIndex, ...newBlock);
|
|
1686
|
+
|
|
1687
|
+
// Save
|
|
1688
|
+
await fs.writeFile(reqPath, lines.join('\n'));
|
|
1689
|
+
console.log(chalk.green('\nā Requirement renamed/updated\n'));
|
|
1690
|
+
|
|
1691
|
+
// Update the tree data
|
|
1692
|
+
const reqList = getRequirementList(tree, sectionKey);
|
|
1693
|
+
const reqIndex = reqList.findIndex(r => r.title === req.title);
|
|
1694
|
+
if (reqIndex !== -1) {
|
|
1695
|
+
reqList[reqIndex].title = newTitle;
|
|
1696
|
+
if (newDescription) {
|
|
1697
|
+
reqList[reqIndex].details = newDescription.split('\n').filter(line => line.trim());
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1544
1702
|
// Helper to move requirement to recycled section (used to delete)
|
|
1545
1703
|
async function deleteRequirement(req, sectionKey, tree) {
|
|
1546
1704
|
const reqList = getRequirementList(tree, sectionKey);
|
|
@@ -1560,6 +1718,61 @@ async function deleteRequirement(req, sectionKey, tree) {
|
|
|
1560
1718
|
}
|
|
1561
1719
|
}
|
|
1562
1720
|
|
|
1721
|
+
// Helper to permanently delete requirement from file (used for recycled items)
|
|
1722
|
+
async function permanentlyDeleteRequirement(req, sectionKey, tree) {
|
|
1723
|
+
const reqList = getRequirementList(tree, sectionKey);
|
|
1724
|
+
const reqIndex = reqList.findIndex(r => r.title === req.title);
|
|
1725
|
+
|
|
1726
|
+
if (reqIndex === -1) return;
|
|
1727
|
+
|
|
1728
|
+
const { getRequirementsPath } = require('vibecodingmachine-core');
|
|
1729
|
+
const reqPath = await getRequirementsPath();
|
|
1730
|
+
|
|
1731
|
+
const truncatedTitle = req.title.substring(0, 50) + (req.title.length > 50 ? '...' : '');
|
|
1732
|
+
if (await confirmAction(`Permanently delete? (r/y/N)`)) {
|
|
1733
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
1734
|
+
const lines = content.split('\n');
|
|
1735
|
+
|
|
1736
|
+
// Find the requirement block (### header format)
|
|
1737
|
+
let requirementStartIndex = -1;
|
|
1738
|
+
let requirementEndIndex = -1;
|
|
1739
|
+
|
|
1740
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1741
|
+
const line = lines[i].trim();
|
|
1742
|
+
if (line.startsWith('###')) {
|
|
1743
|
+
const title = line.replace(/^###\s*/, '').trim();
|
|
1744
|
+
if (title && title === req.title) {
|
|
1745
|
+
requirementStartIndex = i;
|
|
1746
|
+
// Find the end of this requirement (next ### or ## header)
|
|
1747
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
1748
|
+
const nextLine = lines[j].trim();
|
|
1749
|
+
if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
|
|
1750
|
+
requirementEndIndex = j;
|
|
1751
|
+
break;
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
if (requirementEndIndex === -1) {
|
|
1755
|
+
requirementEndIndex = lines.length;
|
|
1756
|
+
}
|
|
1757
|
+
break;
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
if (requirementStartIndex === -1) {
|
|
1763
|
+
console.log(chalk.yellow('ā ļø Could not find requirement to delete'));
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
// Remove the requirement from the file completely
|
|
1768
|
+
lines.splice(requirementStartIndex, requirementEndIndex - requirementStartIndex);
|
|
1769
|
+
|
|
1770
|
+
// Save
|
|
1771
|
+
await fs.writeFile(reqPath, lines.join('\n'));
|
|
1772
|
+
reqList.splice(reqIndex, 1);
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1563
1776
|
// Helper to move requirement to recycled section
|
|
1564
1777
|
async function moveRequirementToRecycled(reqPath, requirementTitle, fromSection) {
|
|
1565
1778
|
const content = await fs.readFile(reqPath, 'utf8');
|
|
@@ -1573,7 +1786,7 @@ async function moveRequirementToRecycled(reqPath, requirementTitle, fromSection)
|
|
|
1573
1786
|
const line = lines[i].trim();
|
|
1574
1787
|
if (line.startsWith('###')) {
|
|
1575
1788
|
const title = line.replace(/^###\s*/, '').trim();
|
|
1576
|
-
if (title && title
|
|
1789
|
+
if (title && title === requirementTitle) {
|
|
1577
1790
|
requirementStartIndex = i;
|
|
1578
1791
|
// Find the end of this requirement (next ### or ## header)
|
|
1579
1792
|
for (let j = i + 1; j < lines.length; j++) {
|
|
@@ -1600,8 +1813,25 @@ async function moveRequirementToRecycled(reqPath, requirementTitle, fromSection)
|
|
|
1600
1813
|
const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
|
|
1601
1814
|
|
|
1602
1815
|
// Remove the requirement from its current location
|
|
1816
|
+
// Also remove any trailing blank lines that might be left behind
|
|
1603
1817
|
lines.splice(requirementStartIndex, requirementEndIndex - requirementStartIndex);
|
|
1604
1818
|
|
|
1819
|
+
// Clean up: remove any orphaned blank lines or malformed entries left after removal
|
|
1820
|
+
// Check if there's a blank line followed by a malformed requirement (just "cli", "core", etc.)
|
|
1821
|
+
if (requirementStartIndex < lines.length) {
|
|
1822
|
+
const nextLine = lines[requirementStartIndex]?.trim();
|
|
1823
|
+
const packageNames = ['cli', 'core', 'electron-app', 'web', 'mobile', 'vscode-extension', 'sync-server'];
|
|
1824
|
+
// If the next line is just a package name (likely orphaned), remove it
|
|
1825
|
+
if (nextLine && packageNames.includes(nextLine.toLowerCase()) &&
|
|
1826
|
+
!nextLine.startsWith('###') && !nextLine.startsWith('PACKAGE:')) {
|
|
1827
|
+
lines.splice(requirementStartIndex, 1);
|
|
1828
|
+
}
|
|
1829
|
+
// Remove any blank lines left after removal
|
|
1830
|
+
while (requirementStartIndex < lines.length && lines[requirementStartIndex]?.trim() === '') {
|
|
1831
|
+
lines.splice(requirementStartIndex, 1);
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1605
1835
|
// Find or create Recycled section
|
|
1606
1836
|
let recycledIndex = -1;
|
|
1607
1837
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -1703,8 +1933,41 @@ async function demoteRequirement(reqTitle, sectionKey, tree, loadSection, loadVe
|
|
|
1703
1933
|
const reqPath = await getRequirementsPath();
|
|
1704
1934
|
|
|
1705
1935
|
if (sectionKey === 'verify') {
|
|
1706
|
-
//
|
|
1707
|
-
|
|
1936
|
+
// Prompt for explanation of what went wrong
|
|
1937
|
+
// Prompt for explanation of what went wrong
|
|
1938
|
+
console.log(chalk.gray('\nWhat went wrong? (This will be added to help the AI agent)'));
|
|
1939
|
+
console.log(chalk.gray('Press Enter twice on empty line to finish:\n'));
|
|
1940
|
+
|
|
1941
|
+
const explanationLines = [];
|
|
1942
|
+
let emptyLineCount = 0;
|
|
1943
|
+
let isFirstLine = true;
|
|
1944
|
+
|
|
1945
|
+
while (true) {
|
|
1946
|
+
try {
|
|
1947
|
+
const { line } = await inquirer.prompt([{
|
|
1948
|
+
type: 'input',
|
|
1949
|
+
name: 'line',
|
|
1950
|
+
message: isFirstLine ? 'Explanation:' : ''
|
|
1951
|
+
}]);
|
|
1952
|
+
|
|
1953
|
+
isFirstLine = false;
|
|
1954
|
+
|
|
1955
|
+
if (line.trim() === '') {
|
|
1956
|
+
emptyLineCount++;
|
|
1957
|
+
if (emptyLineCount >= 2) break;
|
|
1958
|
+
} else {
|
|
1959
|
+
emptyLineCount = 0;
|
|
1960
|
+
explanationLines.push(line);
|
|
1961
|
+
}
|
|
1962
|
+
} catch (err) {
|
|
1963
|
+
break;
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
const explanation = explanationLines.join('\n');
|
|
1968
|
+
|
|
1969
|
+
// TO VERIFY -> TODO: Use shared function with explanation
|
|
1970
|
+
const success = await demoteVerifyToTodo(reqPath, reqTitle, explanation);
|
|
1708
1971
|
if (success) {
|
|
1709
1972
|
// Reload sections
|
|
1710
1973
|
tree.todoReqs = await loadSection('todo', 'ā³ Requirements not yet completed');
|
|
@@ -1734,86 +1997,104 @@ async function handleAddRequirement(type) {
|
|
|
1734
1997
|
// Ensure it's an array
|
|
1735
1998
|
if (typeof selectedPackage === 'string') selectedPackage = [selectedPackage];
|
|
1736
1999
|
|
|
1737
|
-
let askPackage = !config.lastPackage;
|
|
1738
|
-
let name = '';
|
|
1739
|
-
let pkg = selectedPackage;
|
|
1740
|
-
|
|
1741
2000
|
while (true) {
|
|
1742
|
-
|
|
1743
|
-
|
|
2001
|
+
const pkgDisplay = selectedPackage.join(', ');
|
|
2002
|
+
|
|
2003
|
+
// Custom menu to allow "Up arrow" to select package
|
|
2004
|
+
// Default to "Enter Requirement Name" (index 1) so user can just type
|
|
2005
|
+
const { action } = await inquirer.prompt([{
|
|
2006
|
+
type: 'list',
|
|
2007
|
+
name: 'action',
|
|
2008
|
+
message: 'New Requirement:',
|
|
2009
|
+
choices: [
|
|
2010
|
+
{ name: `š¦ Package: ${pkgDisplay}`, value: 'package' },
|
|
2011
|
+
{ name: 'š Enter Requirement Name', value: 'name' },
|
|
2012
|
+
{ name: 'ā Cancel', value: 'cancel' }
|
|
2013
|
+
],
|
|
2014
|
+
default: 1
|
|
2015
|
+
}]);
|
|
2016
|
+
|
|
2017
|
+
if (action === 'cancel') return;
|
|
2018
|
+
|
|
2019
|
+
if (action === 'package') {
|
|
2020
|
+
// When showing checkboxes, if "all" is currently selected along with specific packages,
|
|
2021
|
+
// unselect "all" first so user sees only specific packages checked
|
|
2022
|
+
let displayPackages = selectedPackage;
|
|
2023
|
+
if (selectedPackage.includes('all') && selectedPackage.length > 1) {
|
|
2024
|
+
displayPackages = selectedPackage.filter(p => p !== 'all');
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
const { pkg } = await inquirer.prompt([{
|
|
1744
2028
|
type: 'checkbox',
|
|
1745
2029
|
name: 'pkg',
|
|
1746
2030
|
message: 'Select package(s):',
|
|
1747
2031
|
choices: packages.map(p => ({
|
|
1748
2032
|
name: p,
|
|
1749
2033
|
value: p,
|
|
1750
|
-
checked:
|
|
2034
|
+
checked: displayPackages.includes(p)
|
|
1751
2035
|
})),
|
|
1752
2036
|
validate: (answer) => {
|
|
1753
2037
|
if (answer.length < 1) return 'You must choose at least one package.';
|
|
1754
2038
|
return true;
|
|
1755
2039
|
}
|
|
1756
2040
|
}]);
|
|
1757
|
-
pkg = answer.pkg;
|
|
1758
2041
|
|
|
1759
|
-
//
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
// Ask for requirement name
|
|
1766
|
-
const pkgDisplay = pkg.join(', ');
|
|
1767
|
-
const nameAnswer = await inquirer.prompt([{
|
|
1768
|
-
type: 'input',
|
|
1769
|
-
name: 'name',
|
|
1770
|
-
message: `Enter requirement name (Package: ${pkgDisplay}) [Type < to change package]:`
|
|
1771
|
-
}]);
|
|
1772
|
-
|
|
1773
|
-
name = nameAnswer.name;
|
|
2042
|
+
// When selecting specific packages, unselect "all"
|
|
2043
|
+
let finalPackage = pkg;
|
|
2044
|
+
// If both "all" and specific packages are selected, keep only specific packages
|
|
2045
|
+
if (pkg.includes('all') && pkg.length > 1) {
|
|
2046
|
+
finalPackage = pkg.filter(p => p !== 'all');
|
|
2047
|
+
}
|
|
1774
2048
|
|
|
1775
|
-
|
|
1776
|
-
|
|
2049
|
+
selectedPackage = finalPackage;
|
|
2050
|
+
config.lastPackage = selectedPackage;
|
|
2051
|
+
await writeConfig(config);
|
|
1777
2052
|
continue;
|
|
1778
2053
|
}
|
|
1779
2054
|
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
// Then ask for multiline description (press Enter twice to finish)
|
|
1784
|
-
console.log(chalk.gray('\nEnter description (press Enter twice on empty line to finish):\n'));
|
|
1785
|
-
const descriptionLines = [];
|
|
1786
|
-
let emptyLineCount = 0;
|
|
1787
|
-
let isFirstLine = true;
|
|
1788
|
-
|
|
1789
|
-
while (true) {
|
|
1790
|
-
try {
|
|
1791
|
-
const { line } = await inquirer.prompt([{
|
|
2055
|
+
if (action === 'name') {
|
|
2056
|
+
const { name } = await inquirer.prompt([{
|
|
1792
2057
|
type: 'input',
|
|
1793
|
-
name: '
|
|
1794
|
-
message:
|
|
2058
|
+
name: 'name',
|
|
2059
|
+
message: `Enter requirement name (Package: ${pkgDisplay}):`
|
|
1795
2060
|
}]);
|
|
1796
2061
|
|
|
1797
|
-
|
|
2062
|
+
if (!name || !name.trim()) continue;
|
|
1798
2063
|
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
2064
|
+
// Ask for description
|
|
2065
|
+
console.log(chalk.gray('\nEnter description (press Enter twice on empty line to finish):\n'));
|
|
2066
|
+
const descriptionLines = [];
|
|
2067
|
+
let emptyLineCount = 0;
|
|
2068
|
+
let isFirstLine = true;
|
|
2069
|
+
|
|
2070
|
+
while (true) {
|
|
2071
|
+
try {
|
|
2072
|
+
const { line } = await inquirer.prompt([{
|
|
2073
|
+
type: 'input',
|
|
2074
|
+
name: 'line',
|
|
2075
|
+
message: isFirstLine ? 'Description:' : ''
|
|
2076
|
+
}]);
|
|
2077
|
+
|
|
2078
|
+
isFirstLine = false;
|
|
2079
|
+
|
|
2080
|
+
if (line.trim() === '') {
|
|
2081
|
+
emptyLineCount++;
|
|
2082
|
+
if (emptyLineCount >= 2) break;
|
|
2083
|
+
} else {
|
|
2084
|
+
emptyLineCount = 0;
|
|
2085
|
+
descriptionLines.push(line);
|
|
2086
|
+
}
|
|
2087
|
+
} catch (err) {
|
|
2088
|
+
break;
|
|
1803
2089
|
}
|
|
1804
|
-
} else {
|
|
1805
|
-
emptyLineCount = 0;
|
|
1806
|
-
descriptionLines.push(line);
|
|
1807
2090
|
}
|
|
1808
|
-
|
|
1809
|
-
|
|
2091
|
+
|
|
2092
|
+
const description = descriptionLines.join('\n');
|
|
2093
|
+
await reqCommands.add(name, selectedPackage, description);
|
|
2094
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
2095
|
+
return;
|
|
1810
2096
|
}
|
|
1811
2097
|
}
|
|
1812
|
-
|
|
1813
|
-
const description = descriptionLines.join('\n');
|
|
1814
|
-
await reqCommands.add(name, pkg, description);
|
|
1815
|
-
// Message already printed by reqCommands.add()
|
|
1816
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1817
2098
|
} catch (err) {
|
|
1818
2099
|
// ESC pressed
|
|
1819
2100
|
}
|
|
@@ -1826,7 +2107,7 @@ async function handleAddRequirement(type) {
|
|
|
1826
2107
|
while (!done) {
|
|
1827
2108
|
try {
|
|
1828
2109
|
// Ask for package
|
|
1829
|
-
const { package } = await inquirer.prompt([{
|
|
2110
|
+
const { package: pkg } = await inquirer.prompt([{
|
|
1830
2111
|
type: 'list',
|
|
1831
2112
|
name: 'package',
|
|
1832
2113
|
message: `Package for requirement ${requirements.length + 1}:`,
|
|
@@ -1875,7 +2156,7 @@ async function handleAddRequirement(type) {
|
|
|
1875
2156
|
}
|
|
1876
2157
|
|
|
1877
2158
|
const description = descriptionLines.join('\n');
|
|
1878
|
-
requirements.push({ name, package, description });
|
|
2159
|
+
requirements.push({ name, package: pkg, description });
|
|
1879
2160
|
}
|
|
1880
2161
|
} catch (err) {
|
|
1881
2162
|
done = true;
|
|
@@ -1986,6 +2267,7 @@ async function showRequirementsBySection(sectionTitle) {
|
|
|
1986
2267
|
{ name: 'š Thumbs down (deprioritize)', value: 'thumbs-down' },
|
|
1987
2268
|
{ name: 'ā¬ļø Move up', value: 'move-up' },
|
|
1988
2269
|
{ name: 'ā¬ļø Move down', value: 'move-down' },
|
|
2270
|
+
{ name: 'ā»ļø Recycle (move to Recycled)', value: 'recycle' },
|
|
1989
2271
|
{ name: 'šļø Delete', value: 'delete' }
|
|
1990
2272
|
]
|
|
1991
2273
|
}]);
|
|
@@ -2026,6 +2308,15 @@ async function showRequirementsBySection(sectionTitle) {
|
|
|
2026
2308
|
console.log(chalk.yellow('\nā Already at bottom\n'));
|
|
2027
2309
|
}
|
|
2028
2310
|
break;
|
|
2311
|
+
case 'recycle':
|
|
2312
|
+
const recycledReq = requirements.splice(selectedIndex, 1)[0];
|
|
2313
|
+
await moveToRecycled(reqPath, recycledReq.title, sectionTitle);
|
|
2314
|
+
console.log(chalk.cyan('\nā Moved to Recycled section\n'));
|
|
2315
|
+
if (requirements.length === 0) {
|
|
2316
|
+
console.log(chalk.gray('No more requirements in this section.\n'));
|
|
2317
|
+
inRequirementsList = false;
|
|
2318
|
+
}
|
|
2319
|
+
break;
|
|
2029
2320
|
case 'delete':
|
|
2030
2321
|
const { confirmDelete } = await inquirer.prompt([{
|
|
2031
2322
|
type: 'confirm',
|
|
@@ -2162,7 +2453,6 @@ async function showQuickMenu(items, initialSelectedIndex = 0) {
|
|
|
2162
2453
|
if (selectedIndex >= items.length) selectedIndex = 0;
|
|
2163
2454
|
|
|
2164
2455
|
let isFirstRender = true;
|
|
2165
|
-
let lastLinesPrinted = 0;
|
|
2166
2456
|
|
|
2167
2457
|
// Helper to calculate visual lines occupied by text
|
|
2168
2458
|
const getVisualLineCount = (text) => {
|
|
@@ -2187,12 +2477,18 @@ async function showQuickMenu(items, initialSelectedIndex = 0) {
|
|
|
2187
2477
|
return lineCount;
|
|
2188
2478
|
};
|
|
2189
2479
|
|
|
2190
|
-
const displayMenu = () => {
|
|
2191
|
-
// Clear
|
|
2192
|
-
if (!isFirstRender
|
|
2193
|
-
//
|
|
2194
|
-
|
|
2195
|
-
|
|
2480
|
+
const displayMenu = async () => {
|
|
2481
|
+
// Clear entire screen on navigation to prevent text overlap with banner
|
|
2482
|
+
if (!isFirstRender) {
|
|
2483
|
+
// No need to console.clear() here as showWelcomeScreen does it,
|
|
2484
|
+
// but ensuring it clears before we start waiting is fine too.
|
|
2485
|
+
console.clear();
|
|
2486
|
+
// Reprint the banner and status info
|
|
2487
|
+
try {
|
|
2488
|
+
await showWelcomeScreen();
|
|
2489
|
+
} catch (err) {
|
|
2490
|
+
console.error('Error displaying banner:', err);
|
|
2491
|
+
}
|
|
2196
2492
|
}
|
|
2197
2493
|
isFirstRender = false;
|
|
2198
2494
|
|
|
@@ -2296,7 +2592,7 @@ async function showQuickMenu(items, initialSelectedIndex = 0) {
|
|
|
2296
2592
|
process.stdin.setRawMode(true);
|
|
2297
2593
|
}
|
|
2298
2594
|
|
|
2299
|
-
const onKeypress = (str, key) => {
|
|
2595
|
+
const onKeypress = async (str, key) => {
|
|
2300
2596
|
if (!key) return;
|
|
2301
2597
|
|
|
2302
2598
|
// Ctrl+C to exit
|
|
@@ -2342,21 +2638,27 @@ async function showQuickMenu(items, initialSelectedIndex = 0) {
|
|
|
2342
2638
|
|
|
2343
2639
|
// Arrow keys for navigation
|
|
2344
2640
|
if (key.name === 'up') {
|
|
2345
|
-
//
|
|
2346
|
-
let
|
|
2347
|
-
while (
|
|
2348
|
-
|
|
2641
|
+
// Search backwards for the previous selectable item
|
|
2642
|
+
let testIndex = selectedIndex - 1;
|
|
2643
|
+
while (testIndex >= 0) {
|
|
2644
|
+
if (items[testIndex].type !== 'blank' && items[testIndex].type !== 'info') {
|
|
2645
|
+
selectedIndex = testIndex;
|
|
2646
|
+
await displayMenu();
|
|
2647
|
+
break;
|
|
2648
|
+
}
|
|
2649
|
+
testIndex--;
|
|
2349
2650
|
}
|
|
2350
|
-
selectedIndex = newIndex;
|
|
2351
|
-
displayMenu();
|
|
2352
2651
|
} else if (key.name === 'down') {
|
|
2353
|
-
//
|
|
2354
|
-
let
|
|
2355
|
-
while (
|
|
2356
|
-
|
|
2652
|
+
// Search forwards for the next selectable item
|
|
2653
|
+
let testIndex = selectedIndex + 1;
|
|
2654
|
+
while (testIndex < items.length) {
|
|
2655
|
+
if (items[testIndex].type !== 'blank' && items[testIndex].type !== 'info') {
|
|
2656
|
+
selectedIndex = testIndex;
|
|
2657
|
+
await displayMenu();
|
|
2658
|
+
break;
|
|
2659
|
+
}
|
|
2660
|
+
testIndex++;
|
|
2357
2661
|
}
|
|
2358
|
-
selectedIndex = newIndex;
|
|
2359
|
-
displayMenu();
|
|
2360
2662
|
} else if (key.name === 'return' || key.name === 'right') {
|
|
2361
2663
|
// Don't allow selecting blank or info lines
|
|
2362
2664
|
if (items[selectedIndex].type !== 'blank' && items[selectedIndex].type !== 'info') {
|
|
@@ -2379,44 +2681,129 @@ async function showProviderManagerMenu() {
|
|
|
2379
2681
|
let selectedIndex = 0;
|
|
2380
2682
|
let dirty = false;
|
|
2381
2683
|
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
const
|
|
2684
|
+
const { fetchQuotaForAgent } = require('vibecodingmachine-core/src/quota-management');
|
|
2685
|
+
|
|
2686
|
+
const debugQuota = process.env.VCM_DEBUG_QUOTA === '1' || process.env.VCM_DEBUG_QUOTA === 'true';
|
|
2385
2687
|
|
|
2386
|
-
const
|
|
2688
|
+
const formatDuration = (ms) => {
|
|
2689
|
+
if (!ms || ms <= 0) return 'now';
|
|
2690
|
+
const totalSeconds = Math.ceil(ms / 1000);
|
|
2691
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
2692
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
2693
|
+
const seconds = totalSeconds % 60;
|
|
2694
|
+
|
|
2695
|
+
if (hours > 0) return `${hours}h ${minutes}m`;
|
|
2696
|
+
if (minutes > 0) return `${minutes}m ${seconds}s`;
|
|
2697
|
+
return `${seconds}s`;
|
|
2698
|
+
};
|
|
2699
|
+
|
|
2700
|
+
const render = async () => {
|
|
2387
2701
|
process.stdout.write('\x1Bc');
|
|
2388
2702
|
console.log(chalk.bold.cyan('ā Provider Order & Availability\n'));
|
|
2703
|
+
|
|
2704
|
+
// Fetch quota info
|
|
2705
|
+
const sharedAuth = require('vibecodingmachine-core/src/auth/shared-auth-storage');
|
|
2706
|
+
const autoConfig = await getAutoConfig();
|
|
2707
|
+
const quotaInfo = await sharedAuth.canRunAutoMode();
|
|
2708
|
+
const remaining = Math.max(0, (quotaInfo.maxIterations || 10) - (quotaInfo.todayUsage || 0));
|
|
2709
|
+
|
|
2710
|
+
// Calculate time until reset (midnight)
|
|
2711
|
+
const now = new Date();
|
|
2712
|
+
const tonight = new Date(now);
|
|
2713
|
+
tonight.setHours(24, 0, 0, 0);
|
|
2714
|
+
const msUntilReset = tonight.getTime() - now.getTime();
|
|
2715
|
+
const hoursUntilReset = Math.floor(msUntilReset / (1000 * 60 * 60));
|
|
2716
|
+
const minsUntilReset = Math.floor((msUntilReset % (1000 * 60 * 60)) / (1000 * 60));
|
|
2717
|
+
|
|
2718
|
+
// Display quota as time-based instead of numeric (0/1 or 1/1 format)
|
|
2719
|
+
let quotaDisplay;
|
|
2720
|
+
if (remaining === 0) {
|
|
2721
|
+
// Rate limit active - show when it resets (in red)
|
|
2722
|
+
quotaDisplay = chalk.gray(' Overall Quota: ') + chalk.red(`ā° Rate limit resets in ${hoursUntilReset}h ${minsUntilReset}m`);
|
|
2723
|
+
} else {
|
|
2724
|
+
// Quota available - show when it resets (in green)
|
|
2725
|
+
quotaDisplay = chalk.gray(' Overall Quota: ') + chalk.green(`ā Available (${remaining}/${quotaInfo.maxIterations})`) + chalk.gray(' ⢠Resets in ') + chalk.cyan(`${hoursUntilReset}h ${minsUntilReset}m`);
|
|
2726
|
+
}
|
|
2727
|
+
console.log(quotaDisplay);
|
|
2389
2728
|
console.log(chalk.gray(' ā/ā move selection j/k reorder e enable d disable Space toggle Enter save/select Esc cancel\n'));
|
|
2390
2729
|
|
|
2391
|
-
order.
|
|
2730
|
+
for (let idx = 0; idx < order.length; idx++) {
|
|
2731
|
+
const id = order[idx];
|
|
2392
2732
|
const def = defMap.get(id);
|
|
2393
|
-
if (!def)
|
|
2733
|
+
if (!def) continue;
|
|
2394
2734
|
const isSelected = idx === selectedIndex;
|
|
2395
2735
|
const isEnabled = enabled[id] !== false;
|
|
2396
|
-
|
|
2736
|
+
|
|
2737
|
+
// Check for Kiro installation
|
|
2738
|
+
let isInstalled = true;
|
|
2739
|
+
if (id === 'kiro') {
|
|
2740
|
+
try {
|
|
2741
|
+
const { isKiroInstalled } = require('./kiro-installer');
|
|
2742
|
+
isInstalled = isKiroInstalled();
|
|
2743
|
+
} catch (e) {
|
|
2744
|
+
// Ignore error provider checks
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
// Determine status emoji: disabled = red alert, rate limited = green circle, enabled = green circle
|
|
2749
|
+
let statusEmoji;
|
|
2750
|
+
if (id === 'kiro' && !isInstalled) {
|
|
2751
|
+
statusEmoji = 'š”'; // Yellow for not installed
|
|
2752
|
+
} else {
|
|
2753
|
+
statusEmoji = !isEnabled ? 'šØ' : 'š¢';
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2397
2756
|
const typeLabel = def.type === 'ide' ? chalk.cyan('IDE') : chalk.cyan('LLM');
|
|
2398
2757
|
const prefix = isSelected ? chalk.cyan('āÆ') : ' ';
|
|
2399
|
-
let line = `${prefix} ${idx + 1}. ${def.name} ${chalk.gray(`(${def.id})`)} ${typeLabel}
|
|
2400
|
-
|
|
2401
|
-
//
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
}
|
|
2415
|
-
|
|
2758
|
+
let line = `${prefix} ${statusEmoji} ${idx + 1}. ${def.name} ${chalk.gray(`(${def.id})`)} ${typeLabel}`;
|
|
2759
|
+
|
|
2760
|
+
// Fetch and display specific quota for this agent
|
|
2761
|
+
try {
|
|
2762
|
+
// Find the active model for this provider if possible
|
|
2763
|
+
let model = def.defaultModel || id;
|
|
2764
|
+
if (id === 'groq') model = autoConfig.groqModel || model;
|
|
2765
|
+
else if (id === 'anthropic') model = autoConfig.anthropicModel || model;
|
|
2766
|
+
else if (id === 'ollama') {
|
|
2767
|
+
const preferredModel = autoConfig.llmModel && autoConfig.llmModel.includes('ollama/')
|
|
2768
|
+
? autoConfig.llmModel.split('/')[1]
|
|
2769
|
+
: autoConfig.llmModel || autoConfig.aiderModel;
|
|
2770
|
+
model = (preferredModel && preferredModel !== id) ? preferredModel : model;
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
const agentId = `${id}:${model}`;
|
|
2774
|
+
const quota = await fetchQuotaForAgent(agentId);
|
|
2775
|
+
|
|
2776
|
+
if (debugQuota) {
|
|
2777
|
+
const resetMs = quota?.resetsAt ? (new Date(quota.resetsAt).getTime() - Date.now()) : null;
|
|
2778
|
+
console.error(`[VCM_DEBUG_QUOTA] provider=${id} model=${model} type=${quota?.type} remaining=${quota?.remaining} limit=${quota?.limit} resetsAt=${quota?.resetsAt ? new Date(quota.resetsAt).toISOString() : 'null'} resetIn=${resetMs !== null ? formatDuration(resetMs) : 'null'}`);
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
if (quota.type === 'infinite') {
|
|
2782
|
+
line += ` ${chalk.gray('[Quota: Infinite]')}`;
|
|
2783
|
+
} else if (quota.type === 'rate-limit') {
|
|
2784
|
+
if (quota.isExceeded()) {
|
|
2785
|
+
if (quota.resetsAt) {
|
|
2786
|
+
const msUntilReset = new Date(quota.resetsAt).getTime() - Date.now();
|
|
2787
|
+
line += ` ${chalk.red(`[ā³ resets in ${formatDuration(msUntilReset)}]`)}`;
|
|
2788
|
+
} else {
|
|
2789
|
+
line += ` ${chalk.red('[Rate limited]')}`;
|
|
2790
|
+
}
|
|
2791
|
+
} else {
|
|
2792
|
+
// Show time until rate limit starts (when it resets)
|
|
2793
|
+
if (quota.resetsAt) {
|
|
2794
|
+
const msUntilReset = new Date(quota.resetsAt).getTime() - Date.now();
|
|
2795
|
+
line += ` ${chalk.green(`[ā available ⢠resets in ${formatDuration(msUntilReset)}]`)}`;
|
|
2796
|
+
} else {
|
|
2797
|
+
line += ` ${chalk.green('[Available]')}`;
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
} catch (e) {
|
|
2802
|
+
// Silently skip if quota fetch fails
|
|
2416
2803
|
}
|
|
2417
2804
|
|
|
2418
2805
|
console.log(line);
|
|
2419
|
-
}
|
|
2806
|
+
}
|
|
2420
2807
|
|
|
2421
2808
|
console.log();
|
|
2422
2809
|
if (dirty) {
|
|
@@ -2426,7 +2813,12 @@ async function showProviderManagerMenu() {
|
|
|
2426
2813
|
}
|
|
2427
2814
|
};
|
|
2428
2815
|
|
|
2429
|
-
|
|
2816
|
+
if (process.env.VCM_RENDER_ONCE === '1' || process.env.VCM_RENDER_ONCE === 'true') {
|
|
2817
|
+
await render();
|
|
2818
|
+
return;
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
return new Promise(async (resolve) => {
|
|
2430
2822
|
const cleanup = () => {
|
|
2431
2823
|
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
2432
2824
|
process.stdin.setRawMode(false);
|
|
@@ -2441,6 +2833,18 @@ async function showProviderManagerMenu() {
|
|
|
2441
2833
|
await saveProviderPreferences(order, enabled);
|
|
2442
2834
|
}
|
|
2443
2835
|
if (selectedId) {
|
|
2836
|
+
// Check for Kiro installation if selected
|
|
2837
|
+
if (selectedId === 'kiro') {
|
|
2838
|
+
try {
|
|
2839
|
+
const { isKiroInstalled, installKiro } = require('./kiro-installer');
|
|
2840
|
+
if (!isKiroInstalled()) {
|
|
2841
|
+
await installKiro();
|
|
2842
|
+
}
|
|
2843
|
+
} catch (e) {
|
|
2844
|
+
console.log(chalk.red('Error checking/installing Kiro: ' + e.message));
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2444
2848
|
const { setAutoConfig } = require('./config');
|
|
2445
2849
|
await setAutoConfig({ agent: selectedId, ide: selectedId });
|
|
2446
2850
|
const def = defMap.get(selectedId);
|
|
@@ -2457,15 +2861,15 @@ async function showProviderManagerMenu() {
|
|
|
2457
2861
|
resolve(null);
|
|
2458
2862
|
};
|
|
2459
2863
|
|
|
2460
|
-
const moveSelection = (delta) => {
|
|
2864
|
+
const moveSelection = async (delta) => {
|
|
2461
2865
|
const next = selectedIndex + delta;
|
|
2462
2866
|
if (next >= 0 && next < order.length) {
|
|
2463
2867
|
selectedIndex = next;
|
|
2464
|
-
render();
|
|
2868
|
+
await render();
|
|
2465
2869
|
}
|
|
2466
2870
|
};
|
|
2467
2871
|
|
|
2468
|
-
const reorder = (delta) => {
|
|
2872
|
+
const reorder = async (delta) => {
|
|
2469
2873
|
const target = selectedIndex + delta;
|
|
2470
2874
|
if (target < 0 || target >= order.length) return;
|
|
2471
2875
|
const temp = order[selectedIndex];
|
|
@@ -2473,17 +2877,17 @@ async function showProviderManagerMenu() {
|
|
|
2473
2877
|
order[target] = temp;
|
|
2474
2878
|
selectedIndex = target;
|
|
2475
2879
|
dirty = true;
|
|
2476
|
-
render();
|
|
2880
|
+
await render();
|
|
2477
2881
|
};
|
|
2478
2882
|
|
|
2479
|
-
const toggle = (value) => {
|
|
2883
|
+
const toggle = async (value) => {
|
|
2480
2884
|
const id = order[selectedIndex];
|
|
2481
2885
|
enabled[id] = value;
|
|
2482
2886
|
dirty = true;
|
|
2483
|
-
render();
|
|
2887
|
+
await render();
|
|
2484
2888
|
};
|
|
2485
2889
|
|
|
2486
|
-
const onKeypress = (str, key = {}) => {
|
|
2890
|
+
const onKeypress = async (str, key = {}) => {
|
|
2487
2891
|
if (key.ctrl && key.name === 'c') {
|
|
2488
2892
|
cancel();
|
|
2489
2893
|
return;
|
|
@@ -2491,30 +2895,31 @@ async function showProviderManagerMenu() {
|
|
|
2491
2895
|
|
|
2492
2896
|
switch (key.name) {
|
|
2493
2897
|
case 'up':
|
|
2494
|
-
moveSelection(-1);
|
|
2898
|
+
await moveSelection(-1);
|
|
2495
2899
|
break;
|
|
2496
2900
|
case 'down':
|
|
2497
|
-
moveSelection(1);
|
|
2901
|
+
await moveSelection(1);
|
|
2498
2902
|
break;
|
|
2499
2903
|
case 'j':
|
|
2500
|
-
reorder(1);
|
|
2904
|
+
await reorder(1);
|
|
2501
2905
|
break;
|
|
2502
2906
|
case 'k':
|
|
2503
|
-
reorder(-1);
|
|
2907
|
+
await reorder(-1);
|
|
2504
2908
|
break;
|
|
2505
2909
|
case 'e':
|
|
2506
|
-
toggle(true);
|
|
2910
|
+
await toggle(true);
|
|
2507
2911
|
break;
|
|
2508
2912
|
case 'd':
|
|
2509
|
-
toggle(false);
|
|
2913
|
+
await toggle(false);
|
|
2510
2914
|
break;
|
|
2511
2915
|
case 'space':
|
|
2512
|
-
toggle(!(enabled[order[selectedIndex]] !== false));
|
|
2916
|
+
await toggle(!(enabled[order[selectedIndex]] !== false));
|
|
2513
2917
|
break;
|
|
2514
2918
|
case 'return':
|
|
2515
2919
|
saveAndExit(order[selectedIndex]);
|
|
2516
2920
|
break;
|
|
2517
2921
|
case 'escape':
|
|
2922
|
+
case 'left':
|
|
2518
2923
|
case 'x':
|
|
2519
2924
|
cancel();
|
|
2520
2925
|
break;
|
|
@@ -2530,11 +2935,11 @@ async function showProviderManagerMenu() {
|
|
|
2530
2935
|
process.stdin.on('keypress', onKeypress);
|
|
2531
2936
|
process.stdin.resume();
|
|
2532
2937
|
|
|
2533
|
-
render();
|
|
2938
|
+
await render();
|
|
2534
2939
|
});
|
|
2535
2940
|
}
|
|
2536
2941
|
|
|
2537
|
-
async function showSettings() {
|
|
2942
|
+
/* async function showSettings() {
|
|
2538
2943
|
console.log(chalk.bold.cyan('\nāļø Settings\n'));
|
|
2539
2944
|
|
|
2540
2945
|
const { setConfigValue } = require('vibecodingmachine-core');
|
|
@@ -2620,6 +3025,276 @@ async function showSettings() {
|
|
|
2620
3025
|
}
|
|
2621
3026
|
}
|
|
2622
3027
|
|
|
3028
|
+
/**
|
|
3029
|
+
* Show cloud sync management menu
|
|
3030
|
+
*/
|
|
3031
|
+
async function showCloudSyncMenu() {
|
|
3032
|
+
console.clear();
|
|
3033
|
+
console.log(chalk.bold.cyan('\nāļø Cloud Sync Management\n'));
|
|
3034
|
+
|
|
3035
|
+
// Check if cloud sync is configured
|
|
3036
|
+
try {
|
|
3037
|
+
const SyncEngine = require('vibecodingmachine-core/src/sync/sync-engine');
|
|
3038
|
+
const testEngine = new SyncEngine();
|
|
3039
|
+
await testEngine.initialize();
|
|
3040
|
+
testEngine.stop();
|
|
3041
|
+
} catch (error) {
|
|
3042
|
+
console.log(chalk.yellow('ā ļø Cloud sync is not configured.\n'));
|
|
3043
|
+
console.log(chalk.white('To set up cloud sync:\n'));
|
|
3044
|
+
console.log(chalk.gray('1. Run: ') + chalk.cyan('./scripts/setup-cloud-sync.sh'));
|
|
3045
|
+
console.log(chalk.gray('2. Add AWS configuration to your .env file'));
|
|
3046
|
+
console.log(chalk.gray('3. Restart vcm\n'));
|
|
3047
|
+
console.log(chalk.gray('For more info, see: ') + chalk.cyan('docs/CLOUD_SYNC.md\n'));
|
|
3048
|
+
|
|
3049
|
+
console.log(chalk.gray('Press Enter to return to main menu...'));
|
|
3050
|
+
await new Promise(resolve => {
|
|
3051
|
+
const rl = readline.createInterface({
|
|
3052
|
+
input: process.stdin,
|
|
3053
|
+
output: process.stdout
|
|
3054
|
+
});
|
|
3055
|
+
rl.question('', () => {
|
|
3056
|
+
rl.close();
|
|
3057
|
+
resolve();
|
|
3058
|
+
});
|
|
3059
|
+
});
|
|
3060
|
+
return;
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
const computerCommands = require('../commands/computers');
|
|
3064
|
+
const syncCommands = require('../commands/sync');
|
|
3065
|
+
|
|
3066
|
+
const choices = [
|
|
3067
|
+
{ name: 'š View All Computers', value: 'computers' },
|
|
3068
|
+
{ name: 'š„ļø Manage Another Computer\'s Requirements', value: 'manage-remote' },
|
|
3069
|
+
{ name: 'š Sync Now', value: 'sync-now' },
|
|
3070
|
+
{ name: 'š Sync Status', value: 'sync-status' },
|
|
3071
|
+
{ name: 'š Sync History', value: 'sync-history' },
|
|
3072
|
+
{ name: 'š View Offline Queue', value: 'sync-queue' },
|
|
3073
|
+
{ name: 'š„ļø Register This Computer', value: 'register' },
|
|
3074
|
+
{ name: 'šÆ Update Focus Area', value: 'update-focus' },
|
|
3075
|
+
{ name: chalk.gray('ā Back to Main Menu'), value: 'back' }
|
|
3076
|
+
];
|
|
3077
|
+
|
|
3078
|
+
const { action } = await inquirer.prompt([
|
|
3079
|
+
{
|
|
3080
|
+
type: 'list',
|
|
3081
|
+
name: 'action',
|
|
3082
|
+
message: 'Select an option:',
|
|
3083
|
+
choices: choices
|
|
3084
|
+
}
|
|
3085
|
+
]);
|
|
3086
|
+
|
|
3087
|
+
switch (action) {
|
|
3088
|
+
case 'computers':
|
|
3089
|
+
try {
|
|
3090
|
+
await computerCommands.listComputers();
|
|
3091
|
+
} catch (error) {
|
|
3092
|
+
console.log(chalk.red('\nā Error: ') + error.message);
|
|
3093
|
+
console.log(chalk.gray('\nTip: Make sure AWS credentials are configured and DynamoDB tables exist.'));
|
|
3094
|
+
}
|
|
3095
|
+
console.log(chalk.gray('\nPress Enter to continue...'));
|
|
3096
|
+
await new Promise(resolve => {
|
|
3097
|
+
const rl = readline.createInterface({
|
|
3098
|
+
input: process.stdin,
|
|
3099
|
+
output: process.stdout
|
|
3100
|
+
});
|
|
3101
|
+
rl.question('', () => {
|
|
3102
|
+
rl.close();
|
|
3103
|
+
resolve();
|
|
3104
|
+
});
|
|
3105
|
+
});
|
|
3106
|
+
await showCloudSyncMenu();
|
|
3107
|
+
break;
|
|
3108
|
+
|
|
3109
|
+
case 'manage-remote':
|
|
3110
|
+
try {
|
|
3111
|
+
// First, get list of computers
|
|
3112
|
+
const SyncEngine = require('vibecodingmachine-core/src/sync/sync-engine');
|
|
3113
|
+
const { ScanCommand } = require('@aws-sdk/lib-dynamodb');
|
|
3114
|
+
|
|
3115
|
+
const syncEngine = new SyncEngine();
|
|
3116
|
+
await syncEngine.initialize();
|
|
3117
|
+
|
|
3118
|
+
const tableName = 'vibecodingmachine-computers';
|
|
3119
|
+
const command = new ScanCommand({ TableName: tableName });
|
|
3120
|
+
const response = await syncEngine.dynamoClient.send(command);
|
|
3121
|
+
const computers = response.Items || [];
|
|
3122
|
+
|
|
3123
|
+
syncEngine.stop();
|
|
3124
|
+
|
|
3125
|
+
if (computers.length === 0) {
|
|
3126
|
+
console.log(chalk.yellow('\nā No computers registered yet.\n'));
|
|
3127
|
+
} else {
|
|
3128
|
+
// Let user select a computer
|
|
3129
|
+
const computerChoices = computers.map(c => ({
|
|
3130
|
+
name: `${c.hostname || c.computerId} - ${c.focusArea || 'No focus'}`,
|
|
3131
|
+
value: c.computerId
|
|
3132
|
+
}));
|
|
3133
|
+
computerChoices.push({ name: chalk.gray('ā Cancel'), value: null });
|
|
3134
|
+
|
|
3135
|
+
const { selectedComputer } = await inquirer.prompt([
|
|
3136
|
+
{
|
|
3137
|
+
type: 'list',
|
|
3138
|
+
name: 'selectedComputer',
|
|
3139
|
+
message: 'Select computer to manage:',
|
|
3140
|
+
choices: computerChoices
|
|
3141
|
+
}
|
|
3142
|
+
]);
|
|
3143
|
+
|
|
3144
|
+
if (selectedComputer) {
|
|
3145
|
+
const remoteReqCommands = require('../commands/requirements-remote');
|
|
3146
|
+
await remoteReqCommands.manageRemoteRequirements(selectedComputer);
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
} catch (error) {
|
|
3150
|
+
console.log(chalk.red('\nā Error: ') + error.message);
|
|
3151
|
+
}
|
|
3152
|
+
await showCloudSyncMenu();
|
|
3153
|
+
break;
|
|
3154
|
+
|
|
3155
|
+
case 'sync-now':
|
|
3156
|
+
try {
|
|
3157
|
+
await syncCommands.syncNow();
|
|
3158
|
+
} catch (error) {
|
|
3159
|
+
console.log(chalk.red('\nā Error: ') + error.message);
|
|
3160
|
+
}
|
|
3161
|
+
console.log(chalk.gray('\nPress Enter to continue...'));
|
|
3162
|
+
await new Promise(resolve => {
|
|
3163
|
+
const rl = readline.createInterface({
|
|
3164
|
+
input: process.stdin,
|
|
3165
|
+
output: process.stdout
|
|
3166
|
+
});
|
|
3167
|
+
rl.question('', () => {
|
|
3168
|
+
rl.close();
|
|
3169
|
+
resolve();
|
|
3170
|
+
});
|
|
3171
|
+
});
|
|
3172
|
+
await showCloudSyncMenu();
|
|
3173
|
+
break;
|
|
3174
|
+
|
|
3175
|
+
case 'sync-status':
|
|
3176
|
+
try {
|
|
3177
|
+
await syncCommands.syncStatus();
|
|
3178
|
+
} catch (error) {
|
|
3179
|
+
console.log(chalk.red('\nā Error: ') + error.message);
|
|
3180
|
+
}
|
|
3181
|
+
console.log(chalk.gray('\nPress Enter to continue...'));
|
|
3182
|
+
await new Promise(resolve => {
|
|
3183
|
+
const rl = readline.createInterface({
|
|
3184
|
+
input: process.stdin,
|
|
3185
|
+
output: process.stdout
|
|
3186
|
+
});
|
|
3187
|
+
rl.question('', () => {
|
|
3188
|
+
rl.close();
|
|
3189
|
+
resolve();
|
|
3190
|
+
});
|
|
3191
|
+
});
|
|
3192
|
+
await showCloudSyncMenu();
|
|
3193
|
+
break;
|
|
3194
|
+
|
|
3195
|
+
case 'sync-history':
|
|
3196
|
+
try {
|
|
3197
|
+
await syncCommands.viewHistory({ limit: 50 });
|
|
3198
|
+
} catch (error) {
|
|
3199
|
+
console.log(chalk.red('\nā Error: ') + error.message);
|
|
3200
|
+
}
|
|
3201
|
+
console.log(chalk.gray('\nPress Enter to continue...'));
|
|
3202
|
+
await new Promise(resolve => {
|
|
3203
|
+
const rl = readline.createInterface({
|
|
3204
|
+
input: process.stdin,
|
|
3205
|
+
output: process.stdout
|
|
3206
|
+
});
|
|
3207
|
+
rl.question('', () => {
|
|
3208
|
+
rl.close();
|
|
3209
|
+
resolve();
|
|
3210
|
+
});
|
|
3211
|
+
});
|
|
3212
|
+
await showCloudSyncMenu();
|
|
3213
|
+
break;
|
|
3214
|
+
|
|
3215
|
+
case 'sync-queue':
|
|
3216
|
+
try {
|
|
3217
|
+
await syncCommands.viewQueue();
|
|
3218
|
+
} catch (error) {
|
|
3219
|
+
console.log(chalk.red('\nā Error: ') + error.message);
|
|
3220
|
+
}
|
|
3221
|
+
console.log(chalk.gray('\nPress Enter to continue...'));
|
|
3222
|
+
await new Promise(resolve => {
|
|
3223
|
+
const rl = readline.createInterface({
|
|
3224
|
+
input: process.stdin,
|
|
3225
|
+
output: process.stdout
|
|
3226
|
+
});
|
|
3227
|
+
rl.question('', () => {
|
|
3228
|
+
rl.close();
|
|
3229
|
+
resolve();
|
|
3230
|
+
});
|
|
3231
|
+
});
|
|
3232
|
+
await showCloudSyncMenu();
|
|
3233
|
+
break;
|
|
3234
|
+
|
|
3235
|
+
case 'register':
|
|
3236
|
+
try {
|
|
3237
|
+
const { focusArea } = await inquirer.prompt([
|
|
3238
|
+
{
|
|
3239
|
+
type: 'input',
|
|
3240
|
+
name: 'focusArea',
|
|
3241
|
+
message: 'Enter focus area for this computer:',
|
|
3242
|
+
default: 'General Development'
|
|
3243
|
+
}
|
|
3244
|
+
]);
|
|
3245
|
+
await computerCommands.registerComputer(focusArea);
|
|
3246
|
+
} catch (error) {
|
|
3247
|
+
console.log(chalk.red('\nā Error: ') + error.message);
|
|
3248
|
+
}
|
|
3249
|
+
console.log(chalk.gray('\nPress Enter to continue...'));
|
|
3250
|
+
await new Promise(resolve => {
|
|
3251
|
+
const rl = readline.createInterface({
|
|
3252
|
+
input: process.stdin,
|
|
3253
|
+
output: process.stdout
|
|
3254
|
+
});
|
|
3255
|
+
rl.question('', () => {
|
|
3256
|
+
rl.close();
|
|
3257
|
+
resolve();
|
|
3258
|
+
});
|
|
3259
|
+
});
|
|
3260
|
+
await showCloudSyncMenu();
|
|
3261
|
+
break;
|
|
3262
|
+
|
|
3263
|
+
case 'update-focus':
|
|
3264
|
+
try {
|
|
3265
|
+
const { newFocus } = await inquirer.prompt([
|
|
3266
|
+
{
|
|
3267
|
+
type: 'input',
|
|
3268
|
+
name: 'newFocus',
|
|
3269
|
+
message: 'Enter new focus area:'
|
|
3270
|
+
}
|
|
3271
|
+
]);
|
|
3272
|
+
if (newFocus) {
|
|
3273
|
+
await computerCommands.updateFocus(newFocus);
|
|
3274
|
+
}
|
|
3275
|
+
} catch (error) {
|
|
3276
|
+
console.log(chalk.red('\nā Error: ') + error.message);
|
|
3277
|
+
}
|
|
3278
|
+
console.log(chalk.gray('\nPress Enter to continue...'));
|
|
3279
|
+
await new Promise(resolve => {
|
|
3280
|
+
const rl = readline.createInterface({
|
|
3281
|
+
input: process.stdin,
|
|
3282
|
+
output: process.stdout
|
|
3283
|
+
});
|
|
3284
|
+
rl.question('', () => {
|
|
3285
|
+
rl.close();
|
|
3286
|
+
resolve();
|
|
3287
|
+
});
|
|
3288
|
+
});
|
|
3289
|
+
await showCloudSyncMenu();
|
|
3290
|
+
break;
|
|
3291
|
+
|
|
3292
|
+
case 'back':
|
|
3293
|
+
// Return to main menu
|
|
3294
|
+
break;
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
|
|
2623
3298
|
async function startInteractive() {
|
|
2624
3299
|
// STRICT AUTH CHECK (only if enabled)
|
|
2625
3300
|
const authEnabled = process.env.AUTH_ENABLED === 'true';
|
|
@@ -2660,6 +3335,11 @@ async function startInteractive() {
|
|
|
2660
3335
|
|
|
2661
3336
|
await showWelcomeScreen();
|
|
2662
3337
|
|
|
3338
|
+
if (process.env.VCM_OPEN_PROVIDER_MENU === '1' || process.env.VCM_OPEN_PROVIDER_MENU === 'true') {
|
|
3339
|
+
await showProviderManagerMenu();
|
|
3340
|
+
return;
|
|
3341
|
+
}
|
|
3342
|
+
|
|
2663
3343
|
let exit = false;
|
|
2664
3344
|
let lastSelectedIndex = 0; // Track last selected menu item
|
|
2665
3345
|
while (!exit) {
|
|
@@ -2679,12 +3359,22 @@ async function startInteractive() {
|
|
|
2679
3359
|
// Build dynamic menu items - settings at top (gray, no letters), actions below (with letters)
|
|
2680
3360
|
const items = [];
|
|
2681
3361
|
|
|
2682
|
-
// Get
|
|
2683
|
-
const
|
|
2684
|
-
|
|
3362
|
+
// Get first ENABLED agent from provider preferences
|
|
3363
|
+
const { getProviderPreferences } = require('../utils/provider-registry');
|
|
3364
|
+
const prefs = await getProviderPreferences();
|
|
3365
|
+
let firstEnabledAgent = null;
|
|
3366
|
+
for (const agentId of prefs.order) {
|
|
3367
|
+
if (prefs.enabled[agentId] !== false) {
|
|
3368
|
+
firstEnabledAgent = agentId;
|
|
3369
|
+
break;
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
// Fallback to current agent if no enabled agents found
|
|
3373
|
+
const displayAgent = firstEnabledAgent || autoConfig.agent || autoConfig.ide || 'ollama';
|
|
3374
|
+
let agentDisplay = `First Agent: ${chalk.cyan(getAgentDisplayName(displayAgent))}`;
|
|
2685
3375
|
|
|
2686
3376
|
// Check for rate limits (for LLM-based agents and Claude Code)
|
|
2687
|
-
if (
|
|
3377
|
+
if (displayAgent === 'ollama' || displayAgent === 'groq' || displayAgent === 'anthropic' || displayAgent === 'bedrock' || displayAgent === 'claude-code') {
|
|
2688
3378
|
try {
|
|
2689
3379
|
const ProviderManager = require('vibecodingmachine-core/src/ide-integration/provider-manager.cjs');
|
|
2690
3380
|
const providerManager = new ProviderManager();
|
|
@@ -2699,44 +3389,47 @@ async function startInteractive() {
|
|
|
2699
3389
|
|
|
2700
3390
|
// Get the model based on the current agent type
|
|
2701
3391
|
let model;
|
|
2702
|
-
if (
|
|
3392
|
+
if (displayAgent === 'groq') {
|
|
2703
3393
|
model = config.auto?.groqModel || config.auto?.aiderModel || config.auto?.llmModel;
|
|
2704
3394
|
// Remove groq/ prefix if present
|
|
2705
3395
|
if (model && model.includes('groq/')) {
|
|
2706
3396
|
model = model.split('/')[1];
|
|
2707
3397
|
}
|
|
2708
|
-
} else if (
|
|
3398
|
+
} else if (displayAgent === 'anthropic') {
|
|
2709
3399
|
model = config.auto?.anthropicModel || config.auto?.aiderModel || config.auto?.llmModel;
|
|
2710
|
-
} else if (
|
|
3400
|
+
} else if (displayAgent === 'ollama') {
|
|
2711
3401
|
const rawModel = config.auto?.llmModel || config.auto?.aiderModel;
|
|
2712
3402
|
// Only use if it doesn't have groq/ prefix
|
|
2713
3403
|
model = rawModel && !rawModel.includes('groq/') ? rawModel : null;
|
|
2714
|
-
} else if (
|
|
3404
|
+
} else if (displayAgent === 'bedrock') {
|
|
2715
3405
|
model = 'anthropic.claude-sonnet-4-v1';
|
|
2716
|
-
} else if (
|
|
3406
|
+
} else if (displayAgent === 'claude-code') {
|
|
2717
3407
|
model = 'claude-code-cli';
|
|
2718
3408
|
}
|
|
2719
3409
|
|
|
2720
3410
|
// For Claude Code, use fixed model name
|
|
2721
|
-
const checkModel =
|
|
2722
|
-
const provider =
|
|
3411
|
+
const checkModel = displayAgent === 'claude-code' ? 'claude-code-cli' : model;
|
|
3412
|
+
const provider = displayAgent === 'ollama' ? 'ollama' : displayAgent;
|
|
2723
3413
|
|
|
2724
3414
|
if (checkModel) {
|
|
2725
3415
|
const timeUntilReset = providerManager.getTimeUntilReset(provider, checkModel);
|
|
2726
3416
|
|
|
2727
3417
|
if (timeUntilReset) {
|
|
2728
|
-
|
|
2729
|
-
const
|
|
2730
|
-
const
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
}
|
|
2739
|
-
|
|
3418
|
+
// Format time remaining in human-readable format
|
|
3419
|
+
const hours = Math.floor(timeUntilReset / (1000 * 60 * 60));
|
|
3420
|
+
const minutes = Math.floor((timeUntilReset % (1000 * 60 * 60)) / (1000 * 60));
|
|
3421
|
+
const seconds = Math.floor((timeUntilReset % (1000 * 60)) / 1000);
|
|
3422
|
+
|
|
3423
|
+
let timeStr = '';
|
|
3424
|
+
if (hours > 0) {
|
|
3425
|
+
timeStr = `${hours}h ${minutes}m`;
|
|
3426
|
+
} else if (minutes > 0) {
|
|
3427
|
+
timeStr = `${minutes}m ${seconds}s`;
|
|
3428
|
+
} else {
|
|
3429
|
+
timeStr = `${seconds}s`;
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
agentDisplay += ` ${chalk.red('ā° Rate limit resets in ' + timeStr)}`;
|
|
2740
3433
|
}
|
|
2741
3434
|
}
|
|
2742
3435
|
}
|
|
@@ -2754,9 +3447,6 @@ async function startInteractive() {
|
|
|
2754
3447
|
autoConfig.maxChats ? `Stop after ${autoConfig.maxChats}` :
|
|
2755
3448
|
'Never Stop';
|
|
2756
3449
|
|
|
2757
|
-
// Get restart CLI setting from autoConfig
|
|
2758
|
-
const restartCLI = autoConfig.restartCLI ? chalk.green('Enabled ā') : chalk.yellow('Disabled ā');
|
|
2759
|
-
|
|
2760
3450
|
if (autoStatus.running) {
|
|
2761
3451
|
items.push({
|
|
2762
3452
|
type: 'setting',
|
|
@@ -2778,45 +3468,27 @@ async function startInteractive() {
|
|
|
2778
3468
|
value: 'setting:auto-stop-condition'
|
|
2779
3469
|
});
|
|
2780
3470
|
|
|
2781
|
-
// Add
|
|
2782
|
-
items.push({
|
|
2783
|
-
type: 'setting',
|
|
2784
|
-
name: ` āā Restart CLI after each completed requirement: ${restartCLI}`,
|
|
2785
|
-
value: 'setting:restart-cli'
|
|
2786
|
-
});
|
|
2787
|
-
|
|
2788
|
-
// Add setup alias setting
|
|
2789
|
-
items.push({
|
|
2790
|
-
type: 'setting',
|
|
2791
|
-
name: ` āā Setup 'vcm' alias`,
|
|
2792
|
-
value: 'setting:setup-alias'
|
|
2793
|
-
});
|
|
2794
|
-
|
|
2795
|
-
// Add current agent setting
|
|
3471
|
+
// Add current agent setting (rate limit info already included in agentDisplay if applicable)
|
|
2796
3472
|
items.push({
|
|
2797
3473
|
type: 'setting',
|
|
2798
3474
|
name: ` āā ${agentDisplay}`,
|
|
2799
3475
|
value: 'setting:agent'
|
|
2800
3476
|
});
|
|
2801
3477
|
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
3478
|
// Add Requirements as a selectable setting with counts
|
|
2805
3479
|
const hasRequirements = await requirementsExists();
|
|
2806
3480
|
const counts = hasRequirements ? await countRequirements() : null;
|
|
2807
3481
|
let requirementsText = 'Requirements: ';
|
|
2808
3482
|
if (counts) {
|
|
3483
|
+
// Calculate actual iterations: lesser of stop after number and TODO requirements
|
|
3484
|
+
const actualIterations = autoConfig.neverStop ? counts.todoCount :
|
|
3485
|
+
Math.min(autoConfig.maxChats || counts.todoCount, counts.todoCount);
|
|
2809
3486
|
const total = counts.todoCount + counts.toVerifyCount + counts.verifiedCount;
|
|
2810
3487
|
if (total > 0) {
|
|
2811
|
-
const todoPercent = Math.round((
|
|
3488
|
+
const todoPercent = Math.round((actualIterations / total) * 100);
|
|
2812
3489
|
const toVerifyPercent = Math.round((counts.toVerifyCount / total) * 100);
|
|
2813
3490
|
const verifiedPercent = Math.round((counts.verifiedCount / total) * 100);
|
|
2814
|
-
requirementsText += `${chalk.yellow(
|
|
2815
|
-
|
|
2816
|
-
// Add warning if no TODO requirements
|
|
2817
|
-
if (counts.todoCount === 0) {
|
|
2818
|
-
requirementsText += ` ${chalk.red('ā ļø No requirements to work on')}`;
|
|
2819
|
-
}
|
|
3491
|
+
requirementsText += `${chalk.yellow(actualIterations + ' (' + todoPercent + '%) TODO')}, ${chalk.cyan(counts.toVerifyCount + ' (' + toVerifyPercent + '%) TO VERIFY')}, ${chalk.green(counts.verifiedCount + ' (' + verifiedPercent + '%) VERIFIED')}`;
|
|
2820
3492
|
} else {
|
|
2821
3493
|
requirementsText = '';
|
|
2822
3494
|
}
|
|
@@ -2836,10 +3508,57 @@ async function startInteractive() {
|
|
|
2836
3508
|
|
|
2837
3509
|
items.push({
|
|
2838
3510
|
type: 'setting',
|
|
2839
|
-
name: ` āā Use Hostname in Req File: ${useHostname ? chalk.green('
|
|
3511
|
+
name: ` āā Use Hostname in Req File: ${useHostname ? chalk.green('ā') : chalk.red('š')} ${useHostname ? 'ā ENABLED' : 'š DISABLED'}`,
|
|
2840
3512
|
value: 'setting:hostname'
|
|
2841
3513
|
});
|
|
2842
3514
|
|
|
3515
|
+
// Add Stages configuration
|
|
3516
|
+
const { getStages } = require('./config');
|
|
3517
|
+
const configuredStages = await getStages();
|
|
3518
|
+
const stagesCount = configuredStages.length;
|
|
3519
|
+
items.push({
|
|
3520
|
+
type: 'setting',
|
|
3521
|
+
name: ` āā Configure Stages: ${chalk.cyan(stagesCount + ' stages')}`,
|
|
3522
|
+
value: 'setting:stages'
|
|
3523
|
+
});
|
|
3524
|
+
|
|
3525
|
+
// Cloud Sync Status
|
|
3526
|
+
try {
|
|
3527
|
+
const SyncEngine = require('vibecodingmachine-core/src/sync/sync-engine');
|
|
3528
|
+
const syncEngine = new SyncEngine();
|
|
3529
|
+
|
|
3530
|
+
// Try to initialize, but don't fail if AWS not configured
|
|
3531
|
+
try {
|
|
3532
|
+
await syncEngine.initialize();
|
|
3533
|
+
const syncStatus = syncEngine.getStatus();
|
|
3534
|
+
syncEngine.stop();
|
|
3535
|
+
|
|
3536
|
+
const onlineIcon = syncStatus.isOnline ? chalk.green('ā') : chalk.red('ā');
|
|
3537
|
+
const onlineText = syncStatus.isOnline ? 'Online' : 'Offline';
|
|
3538
|
+
const queueText = syncStatus.queuedChanges > 0 ? chalk.yellow(` (${syncStatus.queuedChanges} queued)`) : '';
|
|
3539
|
+
|
|
3540
|
+
items.push({
|
|
3541
|
+
type: 'setting',
|
|
3542
|
+
name: `Cloud Sync: ${onlineIcon} ${onlineText}${queueText}`,
|
|
3543
|
+
value: 'setting:cloud-sync'
|
|
3544
|
+
});
|
|
3545
|
+
} catch (initError) {
|
|
3546
|
+
// Initialization failed - AWS not configured or credentials missing
|
|
3547
|
+
items.push({
|
|
3548
|
+
type: 'setting',
|
|
3549
|
+
name: `Cloud Sync: ${chalk.gray('ā Not configured')}`,
|
|
3550
|
+
value: 'setting:cloud-sync-setup'
|
|
3551
|
+
});
|
|
3552
|
+
}
|
|
3553
|
+
} catch (error) {
|
|
3554
|
+
// Module not found or other error - show disabled
|
|
3555
|
+
items.push({
|
|
3556
|
+
type: 'setting',
|
|
3557
|
+
name: `Cloud Sync: ${chalk.gray('ā Disabled')}`,
|
|
3558
|
+
value: 'setting:cloud-sync-setup'
|
|
3559
|
+
});
|
|
3560
|
+
}
|
|
3561
|
+
|
|
2843
3562
|
// Add "Next TODO Requirement" as a separate menu item if there are TODO items
|
|
2844
3563
|
if (counts && counts.todoCount > 0) {
|
|
2845
3564
|
// Get the actual next requirement text (new header format)
|
|
@@ -2886,32 +3605,32 @@ async function startInteractive() {
|
|
|
2886
3605
|
if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
|
|
2887
3606
|
break;
|
|
2888
3607
|
}
|
|
3608
|
+
|
|
2889
3609
|
description += nextLine + '\n';
|
|
2890
3610
|
}
|
|
2891
|
-
nextReqText = title;
|
|
3611
|
+
nextReqText = title + '\n' + description;
|
|
2892
3612
|
break;
|
|
2893
3613
|
}
|
|
2894
3614
|
}
|
|
2895
3615
|
}
|
|
3616
|
+
items.push({
|
|
3617
|
+
type: 'info',
|
|
3618
|
+
name: ` āā Next TODO Requirement: ${nextReqText}`,
|
|
3619
|
+
value: 'info:next-requirement'
|
|
3620
|
+
});
|
|
2896
3621
|
}
|
|
2897
3622
|
} catch (err) {
|
|
2898
|
-
console.error('Error
|
|
3623
|
+
console.error('Error reading requirements file:', err.message);
|
|
2899
3624
|
}
|
|
2900
|
-
|
|
2901
|
-
// Add "Next TODO Requirement" to the menu
|
|
2902
|
-
items.push({
|
|
2903
|
-
type: 'info',
|
|
2904
|
-
name: `Next TODO Requirement: ${nextReqText}`,
|
|
2905
|
-
value: 'next-req'
|
|
2906
|
-
});
|
|
2907
3625
|
}
|
|
2908
3626
|
|
|
3627
|
+
|
|
2909
3628
|
// Add warning message if no TODO requirements and Auto Mode is stopped
|
|
2910
3629
|
if (counts && counts.todoCount === 0 && !autoStatus.running) {
|
|
2911
3630
|
items.push({
|
|
2912
|
-
type: '
|
|
3631
|
+
type: 'info',
|
|
2913
3632
|
name: chalk.red(' ā ļø No requirements to work on - cannot start Auto Mode'),
|
|
2914
|
-
value: '
|
|
3633
|
+
value: 'info:no-requirements'
|
|
2915
3634
|
});
|
|
2916
3635
|
}
|
|
2917
3636
|
|
|
@@ -2924,6 +3643,8 @@ async function startInteractive() {
|
|
|
2924
3643
|
items.push({ type: 'action', name: 'Initialize repository (.vibecodingmachine)', value: 'repo:init' });
|
|
2925
3644
|
}
|
|
2926
3645
|
|
|
3646
|
+
items.push({ type: 'action', name: 'View All Computers', value: 'computers:list' });
|
|
3647
|
+
items.push({ type: 'action', name: 'Sync Now', value: 'sync:now' });
|
|
2927
3648
|
items.push({ type: 'action', name: 'Logout', value: 'logout' });
|
|
2928
3649
|
items.push({ type: 'action', name: 'Exit', value: 'exit' });
|
|
2929
3650
|
|
|
@@ -2968,7 +3689,6 @@ async function startInteractive() {
|
|
|
2968
3689
|
const path = require('path');
|
|
2969
3690
|
const os = require('os');
|
|
2970
3691
|
const yaml = require('js-yaml');
|
|
2971
|
-
const { spawn } = require('child_process');
|
|
2972
3692
|
const configPath = path.join(os.homedir(), '.continue', 'config.yaml');
|
|
2973
3693
|
|
|
2974
3694
|
if (fs.existsSync(configPath)) {
|
|
@@ -3159,6 +3879,53 @@ async function startInteractive() {
|
|
|
3159
3879
|
await showWelcomeScreen();
|
|
3160
3880
|
break;
|
|
3161
3881
|
}
|
|
3882
|
+
case 'setting:stages': {
|
|
3883
|
+
// Configure stages
|
|
3884
|
+
const { getStages, setStages, DEFAULT_STAGES } = require('./config');
|
|
3885
|
+
const inquirer = require('inquirer');
|
|
3886
|
+
|
|
3887
|
+
const currentStages = await getStages();
|
|
3888
|
+
|
|
3889
|
+
console.log(chalk.cyan('\nšØ Configure Workflow Stages\n'));
|
|
3890
|
+
console.log(chalk.gray('Select the stages you want to include in the auto-mode workflow.'));
|
|
3891
|
+
console.log(chalk.gray('Stages will be executed in the order shown.\n'));
|
|
3892
|
+
|
|
3893
|
+
const { selectedStages } = await inquirer.prompt([
|
|
3894
|
+
{
|
|
3895
|
+
type: 'checkbox',
|
|
3896
|
+
name: 'selectedStages',
|
|
3897
|
+
message: 'Select stages:',
|
|
3898
|
+
choices: DEFAULT_STAGES.map(stage => ({
|
|
3899
|
+
name: stage,
|
|
3900
|
+
checked: currentStages.includes(stage)
|
|
3901
|
+
})),
|
|
3902
|
+
validate: (answer) => {
|
|
3903
|
+
if (answer.length < 1) {
|
|
3904
|
+
return 'You must select at least one stage.';
|
|
3905
|
+
}
|
|
3906
|
+
return true;
|
|
3907
|
+
},
|
|
3908
|
+
loop: false,
|
|
3909
|
+
pageSize: 15
|
|
3910
|
+
}
|
|
3911
|
+
]);
|
|
3912
|
+
|
|
3913
|
+
// Preserve order from DEFAULT_STAGES for selected items
|
|
3914
|
+
// This ensures stages always run in the correct logical order
|
|
3915
|
+
const newStages = DEFAULT_STAGES.filter(stage => selectedStages.includes(stage));
|
|
3916
|
+
|
|
3917
|
+
await setStages(newStages);
|
|
3918
|
+
console.log(chalk.green('\nā'), `Stages configuration updated: ${newStages.join(' ā ')}\n`);
|
|
3919
|
+
|
|
3920
|
+
const { continue: _ } = await inquirer.prompt([{
|
|
3921
|
+
type: 'input',
|
|
3922
|
+
name: 'continue',
|
|
3923
|
+
message: 'Press Enter to return to menu...'
|
|
3924
|
+
}]);
|
|
3925
|
+
|
|
3926
|
+
await showWelcomeScreen();
|
|
3927
|
+
break;
|
|
3928
|
+
}
|
|
3162
3929
|
case 'setting:auto-start': {
|
|
3163
3930
|
try {
|
|
3164
3931
|
console.log(chalk.bold.cyan('\nš Starting Auto Mode...\n'));
|
|
@@ -3313,13 +4080,13 @@ async function startInteractive() {
|
|
|
3313
4080
|
const { maxChats } = await inquirer.prompt([{
|
|
3314
4081
|
type: 'input',
|
|
3315
4082
|
name: 'maxChats',
|
|
3316
|
-
message: 'Max chats (
|
|
4083
|
+
message: 'Max chats (0 for never stop):',
|
|
3317
4084
|
default: defaultMaxChats
|
|
3318
4085
|
}]);
|
|
3319
4086
|
|
|
3320
4087
|
// Update config
|
|
3321
4088
|
const newConfig = { ...currentConfig };
|
|
3322
|
-
if (maxChats && maxChats.trim() !== '') {
|
|
4089
|
+
if (maxChats && maxChats.trim() !== '' && maxChats.trim() !== '0') {
|
|
3323
4090
|
newConfig.maxChats = parseInt(maxChats);
|
|
3324
4091
|
newConfig.neverStop = false;
|
|
3325
4092
|
console.log(chalk.green('\nā'), `Stop condition updated: ${chalk.cyan(`Stop after ${newConfig.maxChats}`)}\n`);
|
|
@@ -3336,43 +4103,82 @@ async function startInteractive() {
|
|
|
3336
4103
|
await showWelcomeScreen();
|
|
3337
4104
|
break;
|
|
3338
4105
|
}
|
|
3339
|
-
case 'setting:
|
|
3340
|
-
//
|
|
3341
|
-
|
|
3342
|
-
const { getAutoConfig, setAutoConfig } = require('./config');
|
|
3343
|
-
const currentConfig = await getAutoConfig();
|
|
3344
|
-
const newConfig = { ...currentConfig, restartCLI: !currentConfig.restartCLI };
|
|
3345
|
-
await setAutoConfig(newConfig);
|
|
3346
|
-
const statusText = newConfig.restartCLI ? chalk.green('enabled') : chalk.yellow('disabled');
|
|
3347
|
-
console.log(chalk.green('\nā'), `Restart CLI after each completed requirement ${statusText}\n`);
|
|
3348
|
-
} catch (error) {
|
|
3349
|
-
console.log(chalk.red('\nā Error updating restart CLI setting:', error.message));
|
|
3350
|
-
}
|
|
4106
|
+
case 'setting:requirements': {
|
|
4107
|
+
// Show tree-style requirements navigator
|
|
4108
|
+
await showRequirementsTree();
|
|
3351
4109
|
await showWelcomeScreen();
|
|
3352
4110
|
break;
|
|
3353
4111
|
}
|
|
3354
|
-
case '
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
4112
|
+
case 'repo:init':
|
|
4113
|
+
await repo.initRepo();
|
|
4114
|
+
break;
|
|
4115
|
+
|
|
4116
|
+
case 'computers:list': {
|
|
4117
|
+
const computerCommands = require('../commands/computers');
|
|
4118
|
+
await computerCommands.listComputers();
|
|
4119
|
+
console.log(chalk.gray('\nPress Enter to continue...'));
|
|
4120
|
+
await new Promise(resolve => {
|
|
4121
|
+
const rl = readline.createInterface({
|
|
4122
|
+
input: process.stdin,
|
|
4123
|
+
output: process.stdout
|
|
4124
|
+
});
|
|
4125
|
+
rl.question('', () => {
|
|
4126
|
+
rl.close();
|
|
4127
|
+
resolve();
|
|
4128
|
+
});
|
|
4129
|
+
});
|
|
3364
4130
|
await showWelcomeScreen();
|
|
3365
4131
|
break;
|
|
3366
4132
|
}
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
4133
|
+
|
|
4134
|
+
case 'sync:now': {
|
|
4135
|
+
const syncCommands = require('../commands/sync');
|
|
4136
|
+
await syncCommands.syncNow();
|
|
4137
|
+
console.log(chalk.gray('\nPress Enter to continue...'));
|
|
4138
|
+
await new Promise(resolve => {
|
|
4139
|
+
const rl = readline.createInterface({
|
|
4140
|
+
input: process.stdin,
|
|
4141
|
+
output: process.stdout
|
|
4142
|
+
});
|
|
4143
|
+
rl.question('', () => {
|
|
4144
|
+
rl.close();
|
|
4145
|
+
resolve();
|
|
4146
|
+
});
|
|
4147
|
+
});
|
|
3370
4148
|
await showWelcomeScreen();
|
|
3371
4149
|
break;
|
|
3372
4150
|
}
|
|
3373
|
-
|
|
3374
|
-
|
|
4151
|
+
|
|
4152
|
+
case 'setting:cloud-sync': {
|
|
4153
|
+
await showCloudSyncMenu();
|
|
4154
|
+
await showWelcomeScreen();
|
|
3375
4155
|
break;
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
case 'setting:cloud-sync-setup': {
|
|
4159
|
+
console.clear();
|
|
4160
|
+
console.log(chalk.bold.cyan('\nāļø Cloud Sync Setup\n'));
|
|
4161
|
+
console.log(chalk.yellow('Cloud sync is not configured yet.\n'));
|
|
4162
|
+
console.log(chalk.white('To set up cloud sync:\n'));
|
|
4163
|
+
console.log(chalk.gray('1. Run: ') + chalk.cyan('./scripts/setup-cloud-sync.sh'));
|
|
4164
|
+
console.log(chalk.gray('2. Add AWS configuration to your .env file'));
|
|
4165
|
+
console.log(chalk.gray('3. Register this computer with: ') + chalk.cyan('vcm computer:register "<focus>"'));
|
|
4166
|
+
console.log(chalk.gray('\nFor more info, see: ') + chalk.cyan('docs/CLOUD_SYNC.md\n'));
|
|
4167
|
+
|
|
4168
|
+
console.log(chalk.gray('Press Enter to continue...'));
|
|
4169
|
+
await new Promise(resolve => {
|
|
4170
|
+
const rl = readline.createInterface({
|
|
4171
|
+
input: process.stdin,
|
|
4172
|
+
output: process.stdout
|
|
4173
|
+
});
|
|
4174
|
+
rl.question('', () => {
|
|
4175
|
+
rl.close();
|
|
4176
|
+
resolve();
|
|
4177
|
+
});
|
|
4178
|
+
});
|
|
4179
|
+
await showWelcomeScreen();
|
|
4180
|
+
break;
|
|
4181
|
+
}
|
|
3376
4182
|
case 'auto:start': {
|
|
3377
4183
|
const { ide, maxChats } = await inquirer.prompt([
|
|
3378
4184
|
{
|
|
@@ -3393,12 +4199,12 @@ async function startInteractive() {
|
|
|
3393
4199
|
{
|
|
3394
4200
|
type: 'input',
|
|
3395
4201
|
name: 'maxChats',
|
|
3396
|
-
message: 'Max chats (
|
|
4202
|
+
message: 'Max chats (0 for never stop):',
|
|
3397
4203
|
default: ''
|
|
3398
4204
|
}
|
|
3399
4205
|
]);
|
|
3400
4206
|
const options = { ide };
|
|
3401
|
-
if (maxChats) {
|
|
4207
|
+
if (maxChats && maxChats.trim() !== '0') {
|
|
3402
4208
|
options.maxChats = parseInt(maxChats);
|
|
3403
4209
|
} else {
|
|
3404
4210
|
options.neverStop = true;
|