vibecodingmachine-cli 2025.12.25-25 → 2026.1.3-2209
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 +84 -118
- package/logs/audit/2025-12-27.jsonl +1 -0
- package/logs/audit/2026-01-03.jsonl +2 -0
- package/package.json +2 -2
- package/src/commands/auth.js +5 -1
- package/src/commands/auto-direct.js +219 -213
- package/src/commands/auto.js +6 -3
- package/src/commands/computers.js +9 -0
- package/src/commands/feature.js +123 -0
- package/src/commands/repo.js +27 -22
- package/src/commands/requirements-remote.js +24 -1
- package/src/commands/requirements.js +129 -9
- package/src/commands/setup.js +2 -1
- package/src/commands/sync.js +7 -1
- package/src/utils/auth.js +20 -13
- package/src/utils/config.js +14 -1
- package/src/utils/first-run.js +8 -6
- package/src/utils/interactive.js +613 -53
- package/src/utils/prompt-helper.js +64 -0
- package/tests/home-bootstrap.test.js +76 -0
package/src/utils/interactive.js
CHANGED
|
@@ -5,6 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const fs = require('fs-extra');
|
|
7
7
|
const readline = require('readline');
|
|
8
|
+
const { execSync } = require('child_process');
|
|
8
9
|
const repo = require('../commands/repo');
|
|
9
10
|
const auto = require('../commands/auto');
|
|
10
11
|
const status = require('../commands/status');
|
|
@@ -22,12 +23,94 @@ const {
|
|
|
22
23
|
detectLocale,
|
|
23
24
|
setLocale
|
|
24
25
|
} = require('vibecodingmachine-core');
|
|
26
|
+
const { promptWithDefaultsOnce } = require('./prompt-helper');
|
|
25
27
|
|
|
26
28
|
// Initialize locale detection for interactive mode
|
|
27
29
|
const detectedLocale = detectLocale();
|
|
28
30
|
setLocale(detectedLocale);
|
|
29
31
|
const pkg = require('../../package.json');
|
|
30
32
|
|
|
33
|
+
async function checkAppOrBinary(names = [], binaries = []) {
|
|
34
|
+
// names: app bundle base names (e.g., 'Cursor' -> /Applications/Cursor.app)
|
|
35
|
+
// binaries: CLI binary names to check on PATH (e.g., 'code')
|
|
36
|
+
const platform = os.platform();
|
|
37
|
+
// Check common application directories
|
|
38
|
+
if (platform === 'darwin') {
|
|
39
|
+
const appDirs = ['/Applications', path.join(os.homedir(), 'Applications')];
|
|
40
|
+
for (const appName of names) {
|
|
41
|
+
for (const dir of appDirs) {
|
|
42
|
+
try {
|
|
43
|
+
const p = path.join(dir, `${appName}.app`);
|
|
44
|
+
if (await fs.pathExists(p)) {
|
|
45
|
+
// Ensure this is a real application bundle (has Contents/MacOS executable)
|
|
46
|
+
try {
|
|
47
|
+
const macosDir = path.join(p, 'Contents', 'MacOS');
|
|
48
|
+
const exists = await fs.pathExists(macosDir);
|
|
49
|
+
if (exists) {
|
|
50
|
+
const files = await fs.readdir(macosDir);
|
|
51
|
+
if (files && files.length > 0) {
|
|
52
|
+
// Prefer to ensure the app is usable: use spctl to assess, fallback to quarantine xattr
|
|
53
|
+
try {
|
|
54
|
+
// spctl returns non-zero on rejected/invalid apps
|
|
55
|
+
execSync(`spctl --assess -v "${p}"`, { stdio: 'ignore', timeout: 5000 });
|
|
56
|
+
// additionally validate codesign quickly (timeout to avoid hangs)
|
|
57
|
+
try {
|
|
58
|
+
execSync(`codesign -v --deep --strict "${p}"`, { stdio: 'ignore', timeout: 5000 });
|
|
59
|
+
return true;
|
|
60
|
+
} catch (csErr) {
|
|
61
|
+
// codesign failed or timed out — treat as not usable/damaged
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
} catch (e) {
|
|
65
|
+
// spctl failed or timed out — check if app has quarantine attribute
|
|
66
|
+
try {
|
|
67
|
+
const out = execSync(`xattr -p com.apple.quarantine "${p}" 2>/dev/null || true`, { encoding: 'utf8' }).trim();
|
|
68
|
+
if (!out) {
|
|
69
|
+
// no quarantine attribute but spctl failed — be conservative and treat as not installed
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
// If quarantine attribute exists, treat as not installed (damaged/not allowed)
|
|
73
|
+
return false;
|
|
74
|
+
} catch (e2) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch (e) {
|
|
81
|
+
// if we can't stat inside, be conservative and continue searching
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch (e) {
|
|
85
|
+
/* ignore */
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check PATH for known binaries
|
|
92
|
+
for (const bin of binaries) {
|
|
93
|
+
try {
|
|
94
|
+
execSync(`which ${bin}`, { stdio: 'ignore' });
|
|
95
|
+
return true;
|
|
96
|
+
} catch (e) {
|
|
97
|
+
/* not found */
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check common Homebrew bin locations
|
|
102
|
+
const brewPaths = ['/opt/homebrew/bin', '/usr/local/bin'];
|
|
103
|
+
for (const bin of binaries) {
|
|
104
|
+
for (const brew of brewPaths) {
|
|
105
|
+
try {
|
|
106
|
+
if (await fs.pathExists(path.join(brew, bin))) return true;
|
|
107
|
+
} catch (e) { /* ignore */ }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
31
114
|
/**
|
|
32
115
|
* Translate workflow stage names
|
|
33
116
|
*/
|
|
@@ -47,6 +130,78 @@ function translateStage(stage) {
|
|
|
47
130
|
return key ? t(key) : stage;
|
|
48
131
|
}
|
|
49
132
|
|
|
133
|
+
function normalizeProjectDirName(input) {
|
|
134
|
+
const s = String(input || '').trim().toLowerCase();
|
|
135
|
+
const replaced = s.replace(/\s+/g, '-');
|
|
136
|
+
const cleaned = replaced.replace(/[^a-z0-9._-]/g, '-');
|
|
137
|
+
const collapsed = cleaned.replace(/-+/g, '-').replace(/^[-._]+|[-._]+$/g, '');
|
|
138
|
+
return collapsed || 'my-project';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function bootstrapProjectIfInHomeDir() {
|
|
142
|
+
let cwdResolved = path.resolve(process.cwd());
|
|
143
|
+
let homeResolved = path.resolve(os.homedir());
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
cwdResolved = await fs.realpath(cwdResolved);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
// Ignore
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
homeResolved = await fs.realpath(homeResolved);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
// Ignore
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (cwdResolved !== homeResolved) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const codeDir = path.join(homeResolved, 'code');
|
|
162
|
+
const codeDirExists = await fs.pathExists(codeDir);
|
|
163
|
+
|
|
164
|
+
if (!codeDirExists) {
|
|
165
|
+
const { shouldCreateCodeDir } = await inquirer.prompt([
|
|
166
|
+
{
|
|
167
|
+
type: 'confirm',
|
|
168
|
+
name: 'shouldCreateCodeDir',
|
|
169
|
+
message: `No code directory found at ${codeDir}. Would you like to create it?`,
|
|
170
|
+
default: true
|
|
171
|
+
}
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
if (!shouldCreateCodeDir) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
await fs.ensureDir(codeDir);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const { projectName } = await inquirer.prompt([
|
|
182
|
+
{
|
|
183
|
+
type: 'input',
|
|
184
|
+
name: 'projectName',
|
|
185
|
+
message: 'What is the project name you want to create?',
|
|
186
|
+
validate: (value) => {
|
|
187
|
+
const trimmed = String(value || '').trim();
|
|
188
|
+
return trimmed.length > 0 || 'Please enter a project name.';
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
const dirName = normalizeProjectDirName(projectName);
|
|
194
|
+
const projectDir = path.join(codeDir, dirName);
|
|
195
|
+
await fs.ensureDir(projectDir);
|
|
196
|
+
|
|
197
|
+
process.chdir(projectDir);
|
|
198
|
+
try {
|
|
199
|
+
await writeConfig({ ...(await readConfig()), repoPath: projectDir });
|
|
200
|
+
} catch (err) {
|
|
201
|
+
// Best-effort: interactive mode will still use process.cwd()
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
50
205
|
/**
|
|
51
206
|
* Format IDE name for display
|
|
52
207
|
* @param {string} ide - Internal IDE identifier
|
|
@@ -248,6 +403,39 @@ async function countRequirements() {
|
|
|
248
403
|
}
|
|
249
404
|
}
|
|
250
405
|
|
|
406
|
+
async function getSyncStatus() {
|
|
407
|
+
try {
|
|
408
|
+
const SyncEngine = require('vibecodingmachine-core/src/sync/sync-engine');
|
|
409
|
+
const syncEngine = new SyncEngine();
|
|
410
|
+
await syncEngine.initialize();
|
|
411
|
+
const status = syncEngine.getStatus();
|
|
412
|
+
syncEngine.stop();
|
|
413
|
+
|
|
414
|
+
if (!status.isOnline && status.queuedChanges > 0) {
|
|
415
|
+
return `[Offline: ${status.queuedChanges} queued]`;
|
|
416
|
+
} else if (!status.isOnline) {
|
|
417
|
+
return '[Offline]';
|
|
418
|
+
} else if (status.isSyncing) {
|
|
419
|
+
return '[Syncing...]';
|
|
420
|
+
} else if (status.lastSyncTime) {
|
|
421
|
+
const timeSinceSync = Date.now() - status.lastSyncTime;
|
|
422
|
+
const minutesAgo = Math.floor(timeSinceSync / (1000 * 60));
|
|
423
|
+
if (minutesAgo < 1) {
|
|
424
|
+
return '[Sync: ✓ just now]';
|
|
425
|
+
} else if (minutesAgo < 60) {
|
|
426
|
+
return `[Sync: ✓ ${minutesAgo}m ago]`;
|
|
427
|
+
} else {
|
|
428
|
+
const hoursAgo = Math.floor(minutesAgo / 60);
|
|
429
|
+
return `[Sync: ✓ ${hoursAgo}h ago]`;
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
432
|
+
return '[Never synced]';
|
|
433
|
+
}
|
|
434
|
+
} catch (error) {
|
|
435
|
+
return '[Sync unavailable]';
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
251
439
|
async function getCurrentProgress() {
|
|
252
440
|
try {
|
|
253
441
|
const { getRequirementsPath } = require('vibecodingmachine-core');
|
|
@@ -333,6 +521,22 @@ async function showWelcomeScreen() {
|
|
|
333
521
|
// Display repository and system info
|
|
334
522
|
console.log();
|
|
335
523
|
console.log(chalk.gray(t('system.repo').padEnd(25)), formatPath(repoPath));
|
|
524
|
+
|
|
525
|
+
// Display git branch if in a git repo
|
|
526
|
+
try {
|
|
527
|
+
const { isGitRepo, getCurrentBranch, hasUncommittedChanges } = require('vibecodingmachine-core');
|
|
528
|
+
if (isGitRepo(repoPath)) {
|
|
529
|
+
const branch = getCurrentBranch(repoPath);
|
|
530
|
+
if (branch) {
|
|
531
|
+
const isDirty = hasUncommittedChanges(repoPath);
|
|
532
|
+
const branchDisplay = isDirty ? `${chalk.cyan(branch)} ${chalk.yellow(t('system.git.status.dirty'))}` : chalk.cyan(branch);
|
|
533
|
+
console.log(chalk.gray(t('system.git.branch').padEnd(25)), branchDisplay);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
} catch (error) {
|
|
537
|
+
// Ignore git display errors
|
|
538
|
+
}
|
|
539
|
+
|
|
336
540
|
console.log(chalk.gray(t('system.computer.name').padEnd(25)), chalk.cyan(hostname));
|
|
337
541
|
|
|
338
542
|
// Display auto mode progress if running
|
|
@@ -432,16 +636,16 @@ async function showRequirementsTree() {
|
|
|
432
636
|
};
|
|
433
637
|
|
|
434
638
|
// Create localized labels
|
|
435
|
-
const localizedTodoLabel = todoCount > 0 ?
|
|
436
|
-
`⏳ ${t('requirements.section.todo')} (${todoCount} - ${Math.round((todoCount / total) * 100)}%)` :
|
|
639
|
+
const localizedTodoLabel = todoCount > 0 ?
|
|
640
|
+
`⏳ ${t('requirements.section.todo')} (${todoCount} - ${Math.round((todoCount / total) * 100)}%)` :
|
|
437
641
|
`⏳ ${t('requirements.section.todo')} (0 - 0%)`;
|
|
438
|
-
|
|
439
|
-
const localizedToVerifyLabel = toVerifyCount > 0 ?
|
|
440
|
-
`✅ ${t('requirements.section.to.verify')} (${toVerifyCount} - ${Math.round((toVerifyCount / total) * 100)}%)` :
|
|
642
|
+
|
|
643
|
+
const localizedToVerifyLabel = toVerifyCount > 0 ?
|
|
644
|
+
`✅ ${t('requirements.section.to.verify')} (${toVerifyCount} - ${Math.round((toVerifyCount / total) * 100)}%)` :
|
|
441
645
|
`✅ ${t('requirements.section.to.verify')} (0 - 0%)`;
|
|
442
|
-
|
|
443
|
-
const localizedVerifiedLabel = verifiedCount > 0 ?
|
|
444
|
-
`🎉 ${t('requirements.section.verified')} (${verifiedCount} - ${Math.round((verifiedCount / total) * 100)}%)` :
|
|
646
|
+
|
|
647
|
+
const localizedVerifiedLabel = verifiedCount > 0 ?
|
|
648
|
+
`🎉 ${t('requirements.section.verified')} (${verifiedCount} - ${Math.round((verifiedCount / total) * 100)}%)` :
|
|
445
649
|
`🎉 ${t('requirements.section.verified')} (0 - 0%)`;
|
|
446
650
|
|
|
447
651
|
const verifiedReqs = tree.verifiedReqs || [];
|
|
@@ -450,6 +654,10 @@ async function showRequirementsTree() {
|
|
|
450
654
|
const todoReqs = tree.todoReqs || [];
|
|
451
655
|
const recycledReqs = tree.recycledReqs || [];
|
|
452
656
|
|
|
657
|
+
// Calculate percentages for clarification and recycled sections
|
|
658
|
+
const clarificationPercent = total > 0 ? Math.round((clarificationReqs.length / total) * 100) : 0;
|
|
659
|
+
const recycledPercent = total > 0 ? Math.round((recycledReqs.length / total) * 100) : 0;
|
|
660
|
+
|
|
453
661
|
// VERIFIED section (first) - only show if has requirements
|
|
454
662
|
if (verifiedReqs.length > 0 || verifiedCount > 0) {
|
|
455
663
|
tree.items.push({ level: 1, type: 'section', label: localizedVerifiedLabel, key: 'verified' });
|
|
@@ -708,49 +916,99 @@ async function showRequirementsTree() {
|
|
|
708
916
|
|
|
709
917
|
let inSection = false;
|
|
710
918
|
const requirements = [];
|
|
711
|
-
let currentReq = null;
|
|
712
919
|
|
|
713
920
|
for (let i = 0; i < lines.length; i++) {
|
|
714
921
|
const line = lines[i];
|
|
922
|
+
const trimmed = line.trim();
|
|
715
923
|
|
|
716
|
-
if
|
|
924
|
+
// Check if we're entering the clarification section
|
|
925
|
+
if (trimmed.includes('❓ Requirements needing manual feedback')) {
|
|
717
926
|
inSection = true;
|
|
718
927
|
continue;
|
|
719
928
|
}
|
|
720
929
|
|
|
721
|
-
|
|
722
|
-
|
|
930
|
+
// Check if we're leaving the section (hit another ## section)
|
|
931
|
+
if (inSection && trimmed.startsWith('##') && !trimmed.startsWith('###')) {
|
|
723
932
|
break;
|
|
724
933
|
}
|
|
725
934
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
if (!
|
|
747
|
-
|
|
935
|
+
// Read requirements in new format (### header)
|
|
936
|
+
if (inSection && trimmed.startsWith('###')) {
|
|
937
|
+
const title = trimmed.replace(/^###\s*/, '').trim();
|
|
938
|
+
|
|
939
|
+
// Skip empty titles
|
|
940
|
+
if (!title || title.length === 0) {
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
const details = [];
|
|
945
|
+
const questions = [];
|
|
946
|
+
let pkg = null;
|
|
947
|
+
let findings = null;
|
|
948
|
+
let currentQuestion = null;
|
|
949
|
+
|
|
950
|
+
// Read package, description, and clarifying questions
|
|
951
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
952
|
+
const nextLine = lines[j].trim();
|
|
953
|
+
|
|
954
|
+
// Stop if we hit another requirement or section
|
|
955
|
+
if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
|
|
956
|
+
break;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// Check for PACKAGE line
|
|
960
|
+
if (nextLine.startsWith('PACKAGE:')) {
|
|
961
|
+
pkg = nextLine.replace(/^PACKAGE:\s*/, '').trim();
|
|
962
|
+
}
|
|
963
|
+
// Check for AI findings
|
|
964
|
+
else if (nextLine.startsWith('**AI found in codebase:**')) {
|
|
965
|
+
// Next line will be the findings
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
else if (nextLine.startsWith('**What went wrong')) {
|
|
969
|
+
// Description line
|
|
970
|
+
details.push(nextLine);
|
|
971
|
+
}
|
|
972
|
+
else if (nextLine.startsWith('**Clarifying questions:**')) {
|
|
973
|
+
// Start of questions section
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
else if (nextLine.match(/^\d+\./)) {
|
|
977
|
+
// Save previous question if exists
|
|
978
|
+
if (currentQuestion) {
|
|
979
|
+
questions.push(currentQuestion);
|
|
980
|
+
}
|
|
981
|
+
// This is a new question
|
|
982
|
+
currentQuestion = { question: nextLine, response: null };
|
|
748
983
|
}
|
|
984
|
+
else if (currentQuestion && nextLine && !nextLine.startsWith('PACKAGE:') && !nextLine.startsWith('**')) {
|
|
985
|
+
// This might be a response to the current question or description/findings
|
|
986
|
+
if (!findings && !currentQuestion.response && questions.length === 0 && !nextLine.match(/^\d+\./)) {
|
|
987
|
+
// This is findings content
|
|
988
|
+
findings = nextLine;
|
|
989
|
+
} else if (currentQuestion && !currentQuestion.response) {
|
|
990
|
+
// This is a response to the current question
|
|
991
|
+
currentQuestion.response = nextLine;
|
|
992
|
+
} else {
|
|
993
|
+
// Description line
|
|
994
|
+
details.push(nextLine);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
else if (nextLine && !nextLine.startsWith('PACKAGE:') && !nextLine.startsWith('**')) {
|
|
998
|
+
// Description line
|
|
999
|
+
details.push(nextLine);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Save last question if exists
|
|
1004
|
+
if (currentQuestion) {
|
|
1005
|
+
questions.push(currentQuestion);
|
|
749
1006
|
}
|
|
1007
|
+
|
|
1008
|
+
requirements.push({ title, details, pkg, questions, findings, lineIndex: i });
|
|
750
1009
|
}
|
|
751
1010
|
}
|
|
752
1011
|
|
|
753
|
-
if (currentReq) requirements.push(currentReq);
|
|
754
1012
|
return requirements;
|
|
755
1013
|
};
|
|
756
1014
|
|
|
@@ -2357,7 +2615,7 @@ async function showRequirementsBySection(sectionTitle) {
|
|
|
2357
2615
|
}
|
|
2358
2616
|
break;
|
|
2359
2617
|
case 'delete':
|
|
2360
|
-
const { confirmDelete } = await
|
|
2618
|
+
const { confirmDelete } = await promptWithDefaultsOnce([{
|
|
2361
2619
|
type: 'confirm',
|
|
2362
2620
|
name: 'confirmDelete',
|
|
2363
2621
|
message: 'Are you sure you want to delete this requirement?',
|
|
@@ -2773,23 +3031,39 @@ async function showProviderManagerMenu() {
|
|
|
2773
3031
|
const isSelected = idx === selectedIndex;
|
|
2774
3032
|
const isEnabled = enabled[id] !== false;
|
|
2775
3033
|
|
|
2776
|
-
// Check for
|
|
3034
|
+
// Check installation status for all IDEs
|
|
2777
3035
|
let isInstalled = true;
|
|
2778
|
-
if (
|
|
3036
|
+
if (def.type === 'ide') {
|
|
2779
3037
|
try {
|
|
2780
|
-
|
|
2781
|
-
|
|
3038
|
+
if (id === 'kiro') {
|
|
3039
|
+
const { isKiroInstalled } = require('./kiro-installer');
|
|
3040
|
+
isInstalled = isKiroInstalled();
|
|
3041
|
+
} else if (id === 'cursor') {
|
|
3042
|
+
isInstalled = await checkAppOrBinary(['Cursor'], ['cursor']);
|
|
3043
|
+
} else if (id === 'windsurf') {
|
|
3044
|
+
isInstalled = await checkAppOrBinary(['Windsurf'], ['windsurf']);
|
|
3045
|
+
} else if (id === 'vscode') {
|
|
3046
|
+
isInstalled = await checkAppOrBinary(['Visual Studio Code', 'Visual Studio Code - Insiders'], ['code', 'code-insiders']);
|
|
3047
|
+
} else if (id === 'antigravity') {
|
|
3048
|
+
isInstalled = await checkAppOrBinary(['Antigravity'], ['antigravity']);
|
|
3049
|
+
} else {
|
|
3050
|
+
// For other IDEs, try binary name same as id
|
|
3051
|
+
isInstalled = await checkAppOrBinary([], [id]);
|
|
3052
|
+
}
|
|
2782
3053
|
} catch (e) {
|
|
2783
|
-
//
|
|
3054
|
+
// If detection fails, assume not installed
|
|
3055
|
+
isInstalled = false;
|
|
2784
3056
|
}
|
|
2785
3057
|
}
|
|
2786
3058
|
|
|
2787
|
-
// Determine status emoji: disabled = red alert,
|
|
3059
|
+
// Determine status emoji: disabled = red alert, not installed = yellow, enabled = green
|
|
2788
3060
|
let statusEmoji;
|
|
2789
|
-
if (
|
|
3061
|
+
if (!isEnabled) {
|
|
3062
|
+
statusEmoji = '🚨'; // Red for disabled
|
|
3063
|
+
} else if (def.type === 'ide' && !isInstalled) {
|
|
2790
3064
|
statusEmoji = '🟡'; // Yellow for not installed
|
|
2791
3065
|
} else {
|
|
2792
|
-
statusEmoji =
|
|
3066
|
+
statusEmoji = '🟢'; // Green for enabled and installed (or LLM)
|
|
2793
3067
|
}
|
|
2794
3068
|
|
|
2795
3069
|
const typeLabel = def.type === 'ide' ? chalk.cyan('IDE') : chalk.cyan('LLM');
|
|
@@ -3372,8 +3646,36 @@ async function startInteractive() {
|
|
|
3372
3646
|
const { stopAutoMode } = require('./auto-mode');
|
|
3373
3647
|
await stopAutoMode('startup');
|
|
3374
3648
|
|
|
3649
|
+
await bootstrapProjectIfInHomeDir();
|
|
3650
|
+
|
|
3375
3651
|
await showWelcomeScreen();
|
|
3376
3652
|
|
|
3653
|
+
// Check if .vibecodingmachine directory exists, offer to create if not
|
|
3654
|
+
const allnightStatusCheck = await checkVibeCodingMachineExists();
|
|
3655
|
+
if (!allnightStatusCheck.insideExists && !allnightStatusCheck.siblingExists) {
|
|
3656
|
+
console.log(chalk.yellow('\n⚠️ No .vibecodingmachine directory found'));
|
|
3657
|
+
console.log(chalk.gray('This directory is needed to save requirements and track progress.\n'));
|
|
3658
|
+
|
|
3659
|
+
const { shouldInit } = await inquirer.prompt([
|
|
3660
|
+
{
|
|
3661
|
+
type: 'confirm',
|
|
3662
|
+
name: 'shouldInit',
|
|
3663
|
+
message: 'Would you like to create the .vibecodingmachine directory now?',
|
|
3664
|
+
default: true
|
|
3665
|
+
}
|
|
3666
|
+
]);
|
|
3667
|
+
|
|
3668
|
+
if (shouldInit) {
|
|
3669
|
+
// Use the initRepo function to create the directory
|
|
3670
|
+
const { initRepo } = require('../commands/repo');
|
|
3671
|
+
await initRepo({ location: 'inside' });
|
|
3672
|
+
console.log('');
|
|
3673
|
+
} else {
|
|
3674
|
+
console.log(chalk.yellow('\n⚠️ Warning: Without the .vibecodingmachine directory, requirements cannot be saved.'));
|
|
3675
|
+
console.log(chalk.gray('You can create it later by running: ') + chalk.cyan('vcm repo:init\n'));
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
|
|
3377
3679
|
if (process.env.VCM_OPEN_PROVIDER_MENU === '1' || process.env.VCM_OPEN_PROVIDER_MENU === 'true') {
|
|
3378
3680
|
await showProviderManagerMenu();
|
|
3379
3681
|
return;
|
|
@@ -3516,7 +3818,9 @@ async function startInteractive() {
|
|
|
3516
3818
|
|
|
3517
3819
|
// Add Requirements as a selectable setting with counts
|
|
3518
3820
|
const hasRequirements = await requirementsExists();
|
|
3519
|
-
const
|
|
3821
|
+
const { getComputerFilter } = require('./config');
|
|
3822
|
+
const computerFilter = await getComputerFilter();
|
|
3823
|
+
const counts = hasRequirements ? await countRequirements(computerFilter) : null;
|
|
3520
3824
|
let requirementsText = t('interactive.requirements') + ': ';
|
|
3521
3825
|
if (counts) {
|
|
3522
3826
|
// Use actual counts for display (not capped by maxChats)
|
|
@@ -3538,11 +3842,19 @@ async function startInteractive() {
|
|
|
3538
3842
|
if (requirementsText !== '') {
|
|
3539
3843
|
items.push({
|
|
3540
3844
|
type: 'setting',
|
|
3541
|
-
name: requirementsText
|
|
3845
|
+
name: requirementsText + ` ${chalk.blue('Sync Status: ' + (await getSyncStatus()))}`,
|
|
3542
3846
|
value: 'setting:requirements'
|
|
3543
3847
|
});
|
|
3544
3848
|
}
|
|
3545
3849
|
|
|
3850
|
+
// Add requirement filtering by computer
|
|
3851
|
+
const filterText = computerFilter ? chalk.cyan(computerFilter) : chalk.gray('All Computers');
|
|
3852
|
+
items.push({
|
|
3853
|
+
type: 'setting',
|
|
3854
|
+
name: ` └─ Filter Requirements by Computer: ${filterText}`,
|
|
3855
|
+
value: 'setting:filter-requirements'
|
|
3856
|
+
});
|
|
3857
|
+
|
|
3546
3858
|
items.push({
|
|
3547
3859
|
type: 'setting',
|
|
3548
3860
|
name: ` └─ ${t('interactive.hostname.enabled')}: ${useHostname ? chalk.green('✓') : chalk.red('🛑')} ${useHostname ? '✓ ENABLED' : '🛑 ' + t('interactive.hostname.disabled')}`,
|
|
@@ -3559,6 +3871,83 @@ async function startInteractive() {
|
|
|
3559
3871
|
value: 'setting:stages'
|
|
3560
3872
|
});
|
|
3561
3873
|
|
|
3874
|
+
// Add Admin Dashboard - Multi-Computer Dashboard
|
|
3875
|
+
items.push({
|
|
3876
|
+
type: 'setting',
|
|
3877
|
+
name: ` └─ Admin Dashboard: ${chalk.cyan('Multi-Computer Dashboard')}`,
|
|
3878
|
+
value: 'setting:admin-dashboard-multi-computer'
|
|
3879
|
+
});
|
|
3880
|
+
|
|
3881
|
+
// Add Admin Dashboard - Compliance and Terms Tracking
|
|
3882
|
+
items.push({
|
|
3883
|
+
type: 'setting',
|
|
3884
|
+
name: ` └─ Admin Dashboard: ${chalk.cyan('Compliance and Terms Tracking')}`,
|
|
3885
|
+
value: 'setting:admin-dashboard-compliance'
|
|
3886
|
+
});
|
|
3887
|
+
|
|
3888
|
+
// Add Admin Dashboard - Authentication and Security
|
|
3889
|
+
items.push({
|
|
3890
|
+
type: 'setting',
|
|
3891
|
+
name: ` └─ Admin Dashboard: ${chalk.cyan('Authentication and Security')}`,
|
|
3892
|
+
value: 'setting:admin-dashboard-auth'
|
|
3893
|
+
});
|
|
3894
|
+
|
|
3895
|
+
// Add Admin Dashboard - Terms and Privacy Policy
|
|
3896
|
+
items.push({
|
|
3897
|
+
type: 'setting',
|
|
3898
|
+
name: ` └─ Admin Dashboard: ${chalk.cyan('Terms and Privacy Policy')}`,
|
|
3899
|
+
value: 'setting:admin-dashboard-terms'
|
|
3900
|
+
});
|
|
3901
|
+
|
|
3902
|
+
// Add Admin Dashboard - Deployment and Infrastructure
|
|
3903
|
+
items.push({
|
|
3904
|
+
type: 'setting',
|
|
3905
|
+
name: ` └─ Admin Dashboard: ${chalk.cyan('Deployment and Infrastructure')}`,
|
|
3906
|
+
value: 'setting:admin-dashboard-deployment'
|
|
3907
|
+
});
|
|
3908
|
+
|
|
3909
|
+
// Add Cloud Sync Infrastructure - AWS DynamoDB Setup
|
|
3910
|
+
items.push({
|
|
3911
|
+
type: 'setting',
|
|
3912
|
+
name: ` └─ Cloud Sync Infrastructure: ${chalk.cyan('AWS DynamoDB Setup')}`,
|
|
3913
|
+
value: 'setting:cloud-sync-dynamodb'
|
|
3914
|
+
});
|
|
3915
|
+
|
|
3916
|
+
// Add Cloud Sync Infrastructure - Lambda Functions
|
|
3917
|
+
items.push({
|
|
3918
|
+
type: 'setting',
|
|
3919
|
+
name: ` └─ Cloud Sync Infrastructure: ${chalk.cyan('Lambda Functions')}`,
|
|
3920
|
+
value: 'setting:cloud-sync-lambda'
|
|
3921
|
+
});
|
|
3922
|
+
|
|
3923
|
+
// Add Cloud Sync Infrastructure - API Gateway Setup
|
|
3924
|
+
items.push({
|
|
3925
|
+
type: 'setting',
|
|
3926
|
+
name: ` └─ Cloud Sync: ${chalk.cyan('AWS DynamoDB Setup')}`,
|
|
3927
|
+
value: 'setting:cloud-sync-dynamodb'
|
|
3928
|
+
});
|
|
3929
|
+
|
|
3930
|
+
// Add Cloud Sync Infrastructure - AWS API Gateway
|
|
3931
|
+
items.push({
|
|
3932
|
+
type: 'setting',
|
|
3933
|
+
name: ` └─ Cloud Sync: ${chalk.cyan('AWS API Gateway')}`,
|
|
3934
|
+
value: 'setting:cloud-sync-api-gateway'
|
|
3935
|
+
});
|
|
3936
|
+
|
|
3937
|
+
// Add Cloud Sync Infrastructure - AWS Cognito Authentication
|
|
3938
|
+
items.push({
|
|
3939
|
+
type: 'setting',
|
|
3940
|
+
name: ` └─ Cloud Sync Infrastructure: ${chalk.cyan('AWS Cognito Authentication')}`,
|
|
3941
|
+
value: 'setting:cloud-sync-cognito'
|
|
3942
|
+
});
|
|
3943
|
+
|
|
3944
|
+
// Add Cloud Sync Infrastructure - AWS DynamoDB Setup
|
|
3945
|
+
items.push({
|
|
3946
|
+
type: 'setting',
|
|
3947
|
+
name: ` └─ Cloud Sync Infrastructure: ${chalk.cyan('AWS DynamoDB Setup')}`,
|
|
3948
|
+
value: 'setting:cloud-sync-dynamodb'
|
|
3949
|
+
});
|
|
3950
|
+
|
|
3562
3951
|
// Cloud Sync Status
|
|
3563
3952
|
try {
|
|
3564
3953
|
const SyncEngine = require('vibecodingmachine-core/src/sync/sync-engine');
|
|
@@ -3596,7 +3985,82 @@ async function startInteractive() {
|
|
|
3596
3985
|
});
|
|
3597
3986
|
}
|
|
3598
3987
|
|
|
3988
|
+
// Computer and Usage Tracking Status
|
|
3989
|
+
try {
|
|
3990
|
+
const os = require('os');
|
|
3991
|
+
const hostname = os.hostname();
|
|
3992
|
+
const platform = os.platform();
|
|
3993
|
+
const arch = os.arch();
|
|
3994
|
+
const usageIcon = chalk.blue('●');
|
|
3995
|
+
const trackingText = `${hostname} (${platform}/${arch})`;
|
|
3996
|
+
|
|
3997
|
+
items.push({
|
|
3998
|
+
type: 'setting',
|
|
3999
|
+
name: `${t('interactive.usage.tracking') || 'Usage Tracking'}: ${usageIcon} ${chalk.cyan(trackingText)}`,
|
|
4000
|
+
value: 'setting:usage-tracking'
|
|
4001
|
+
});
|
|
4002
|
+
} catch (error) {
|
|
4003
|
+
// Tracking info unavailable
|
|
4004
|
+
items.push({
|
|
4005
|
+
type: 'setting',
|
|
4006
|
+
name: `Usage Tracking: ${chalk.gray('● Unavailable')}`,
|
|
4007
|
+
value: 'setting:usage-tracking'
|
|
4008
|
+
});
|
|
4009
|
+
}
|
|
4010
|
+
|
|
4011
|
+
// Add requirement number tracking status
|
|
4012
|
+
items.push({
|
|
4013
|
+
type: 'setting',
|
|
4014
|
+
name: ` └─ ${t('interactive.requirements.tracking')}: ${counts ? chalk.green('✓ ENABLED') : chalk.red('🛑 DISABLED')}`,
|
|
4015
|
+
value: 'setting:requirements-tracking'
|
|
4016
|
+
});
|
|
4017
|
+
const { readConfig } = require('./config');
|
|
4018
|
+
const config = await readConfig();
|
|
4019
|
+
const lastReqNumber = config.lastRequirementNumber || 0;
|
|
4020
|
+
items.push({
|
|
4021
|
+
type: 'setting',
|
|
4022
|
+
name: ` └─ ${t('interactive.last.requirement.number')}: ${chalk.cyan('R' + lastReqNumber)}`,
|
|
4023
|
+
value: 'setting:req-number'
|
|
4024
|
+
});
|
|
4025
|
+
|
|
3599
4026
|
// Add "Next TODO Requirement" as a separate menu item if there are TODO items
|
|
4027
|
+
if (counts && counts.todoCount > 0) {
|
|
4028
|
+
items.push({
|
|
4029
|
+
type: 'setting',
|
|
4030
|
+
name: ` └─ ${t('interactive.next.todo.requirement')}`,
|
|
4031
|
+
value: 'setting:next-todo-requirement'
|
|
4032
|
+
});
|
|
4033
|
+
}
|
|
4034
|
+
|
|
4035
|
+
// Save last tab opened
|
|
4036
|
+
const lastTabOpened = config.lastTabOpened;
|
|
4037
|
+
if (lastTabOpened) {
|
|
4038
|
+
items.push({
|
|
4039
|
+
type: 'setting',
|
|
4040
|
+
name: ` └─ ${t('interactive.last.tab.opened')}: ${lastTabOpened}`,
|
|
4041
|
+
value: 'setting:last-tab-opened'
|
|
4042
|
+
});
|
|
4043
|
+
}
|
|
4044
|
+
items.push({
|
|
4045
|
+
type: 'setting',
|
|
4046
|
+
name: `Start Auto: Switch to Console tab`,
|
|
4047
|
+
value: 'setting:start-auto'
|
|
4048
|
+
});
|
|
4049
|
+
// TODO: Implement getNextTodoRequirement function
|
|
4050
|
+
// if (counts && counts.todoCount > 0) {
|
|
4051
|
+
// const { getNextTodoRequirement } = require('./auto-mode');
|
|
4052
|
+
// const nextRequirement = await getNextTodoRequirement();
|
|
4053
|
+
// if (nextRequirement) {
|
|
4054
|
+
// const requirementPreview = nextRequirement.length > 80
|
|
4055
|
+
// ? nextRequirement.substring(0, 77) + '...'
|
|
4056
|
+
// : nextRequirement;
|
|
4057
|
+
// items.push({
|
|
4058
|
+
// type: 'info',
|
|
4059
|
+
// name: ` └─ ${t('interactive.next.requirement')}: ${chalk.cyan(requirementPreview)}`,
|
|
4060
|
+
// value: 'info:next-requirement'
|
|
4061
|
+
// });
|
|
4062
|
+
// }
|
|
4063
|
+
// }
|
|
3600
4064
|
if (counts && counts.todoCount > 0) {
|
|
3601
4065
|
// Get the actual next requirement text (new header format)
|
|
3602
4066
|
let nextReqText = '...';
|
|
@@ -3920,6 +4384,69 @@ async function startInteractive() {
|
|
|
3920
4384
|
await showWelcomeScreen();
|
|
3921
4385
|
break;
|
|
3922
4386
|
}
|
|
4387
|
+
case 'setting:filter-requirements': {
|
|
4388
|
+
// Configure requirement filtering by computer
|
|
4389
|
+
const { getComputerFilter, setComputerFilter } = require('./config');
|
|
4390
|
+
const inquirer = require('inquirer');
|
|
4391
|
+
const repoPath = await getRepoPath();
|
|
4392
|
+
|
|
4393
|
+
if (!repoPath) {
|
|
4394
|
+
console.log(chalk.yellow('No repository configured. Use repo:init or repo:set first.'));
|
|
4395
|
+
await showWelcomeScreen();
|
|
4396
|
+
break;
|
|
4397
|
+
}
|
|
4398
|
+
|
|
4399
|
+
// Find all REQUIREMENTS-*.md files to get available computers
|
|
4400
|
+
const vibeDir = path.join(repoPath, '.vibecodingmachine');
|
|
4401
|
+
const files = await fs.pathExists(vibeDir) ? await fs.readdir(vibeDir) : [];
|
|
4402
|
+
const reqFiles = files.filter(f => f.startsWith('REQUIREMENTS') && f.endsWith('.md'));
|
|
4403
|
+
|
|
4404
|
+
const computers = [];
|
|
4405
|
+
for (const file of reqFiles) {
|
|
4406
|
+
const filePath = path.join(vibeDir, file);
|
|
4407
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
4408
|
+
const hostnameMatch = content.match(/- \*\*Hostname\*\*:\s*(.+)/);
|
|
4409
|
+
if (hostnameMatch) {
|
|
4410
|
+
computers.push(hostnameMatch[1].trim());
|
|
4411
|
+
}
|
|
4412
|
+
}
|
|
4413
|
+
|
|
4414
|
+
if (computers.length === 0) {
|
|
4415
|
+
console.log(chalk.yellow('No computers found in requirements files.'));
|
|
4416
|
+
await showWelcomeScreen();
|
|
4417
|
+
break;
|
|
4418
|
+
}
|
|
4419
|
+
|
|
4420
|
+
const currentFilter = await getComputerFilter();
|
|
4421
|
+
|
|
4422
|
+
console.log(chalk.cyan(`\n🖥️ ${t('interactive.filter.requirements.by.computer')}\n`));
|
|
4423
|
+
console.log(chalk.gray('Select which computer\'s requirements to show, or "All Computers" to show all.\n'));
|
|
4424
|
+
|
|
4425
|
+
const { selectedComputer } = await inquirer.prompt([
|
|
4426
|
+
{
|
|
4427
|
+
type: 'list',
|
|
4428
|
+
name: 'selectedComputer',
|
|
4429
|
+
message: t('interactive.select.computer'),
|
|
4430
|
+
choices: [
|
|
4431
|
+
{ name: chalk.gray('All Computers'), value: null },
|
|
4432
|
+
...computers.map(c => ({
|
|
4433
|
+
name: c === currentFilter ? chalk.cyan(`${c} (current)`) : c,
|
|
4434
|
+
value: c
|
|
4435
|
+
}))
|
|
4436
|
+
],
|
|
4437
|
+
default: currentFilter || null,
|
|
4438
|
+
loop: false,
|
|
4439
|
+
pageSize: 15
|
|
4440
|
+
}
|
|
4441
|
+
]);
|
|
4442
|
+
|
|
4443
|
+
await setComputerFilter(selectedComputer);
|
|
4444
|
+
|
|
4445
|
+
const filterText = selectedComputer ? chalk.cyan(selectedComputer) : chalk.gray('All Computers');
|
|
4446
|
+
console.log(chalk.green('\n✓'), `Requirements filter set to: ${filterText}\n`);
|
|
4447
|
+
await showWelcomeScreen();
|
|
4448
|
+
break;
|
|
4449
|
+
}
|
|
3923
4450
|
case 'setting:stages': {
|
|
3924
4451
|
// Configure stages
|
|
3925
4452
|
const { getStages, setStages, DEFAULT_STAGES } = require('./config');
|
|
@@ -3927,11 +4454,11 @@ async function startInteractive() {
|
|
|
3927
4454
|
|
|
3928
4455
|
// Override inquirer's checkbox help text for current locale
|
|
3929
4456
|
const CheckboxPrompt = require('inquirer/lib/prompts/checkbox');
|
|
3930
|
-
const originalGetHelpText = CheckboxPrompt.prototype.getHelp || function() {
|
|
4457
|
+
const originalGetHelpText = CheckboxPrompt.prototype.getHelp || function () {
|
|
3931
4458
|
return '(Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed)';
|
|
3932
4459
|
};
|
|
3933
4460
|
|
|
3934
|
-
CheckboxPrompt.prototype.getHelp = function() {
|
|
4461
|
+
CheckboxPrompt.prototype.getHelp = function () {
|
|
3935
4462
|
const locale = detectLocale();
|
|
3936
4463
|
if (locale === 'es') {
|
|
3937
4464
|
return '(Presiona <espacio> para seleccionar, <a> para alternar todo, <i> para invertir selección, y <enter> para proceder)';
|
|
@@ -4013,8 +4540,23 @@ async function startInteractive() {
|
|
|
4013
4540
|
const { getAutoConfig } = require('./config');
|
|
4014
4541
|
const currentConfig = await getAutoConfig();
|
|
4015
4542
|
|
|
4543
|
+
// Get first enabled agent from provider preferences (this is what user expects)
|
|
4544
|
+
const { getProviderPreferences } = require('../utils/provider-registry');
|
|
4545
|
+
const prefs = await getProviderPreferences();
|
|
4546
|
+
console.log(chalk.gray('[DEBUG] Provider preferences:'), JSON.stringify(prefs, null, 2));
|
|
4547
|
+
let firstEnabledAgent = null;
|
|
4548
|
+
for (const agentId of prefs.order) {
|
|
4549
|
+
if (prefs.enabled[agentId] !== false) {
|
|
4550
|
+
firstEnabledAgent = agentId;
|
|
4551
|
+
console.log(chalk.gray('[DEBUG] Found first enabled agent:'), firstEnabledAgent);
|
|
4552
|
+
break;
|
|
4553
|
+
}
|
|
4554
|
+
}
|
|
4555
|
+
const agentToUse = firstEnabledAgent || currentIDE;
|
|
4556
|
+
console.log(chalk.gray('[DEBUG] Agent to use:'), agentToUse, '(firstEnabled:', firstEnabledAgent, ', fallback:', currentIDE, ')');
|
|
4557
|
+
|
|
4016
4558
|
// Use saved maxChats/neverStop settings
|
|
4017
|
-
const options = { ide:
|
|
4559
|
+
const options = { ide: agentToUse };
|
|
4018
4560
|
if (currentConfig.neverStop) {
|
|
4019
4561
|
options.neverStop = true;
|
|
4020
4562
|
} else if (currentConfig.maxChats) {
|
|
@@ -4034,8 +4576,7 @@ async function startInteractive() {
|
|
|
4034
4576
|
}
|
|
4035
4577
|
|
|
4036
4578
|
// ALWAYS use auto:direct (supports both LLM and IDE agents with proper looping)
|
|
4037
|
-
|
|
4038
|
-
console.log(chalk.gray('[DEBUG] Using auto:direct for agent:', currentAgent));
|
|
4579
|
+
console.log(chalk.gray('[DEBUG] Using auto:direct for agent:', agentToUse));
|
|
4039
4580
|
const { handleAutoStart: handleDirectAutoStart } = require('../commands/auto-direct');
|
|
4040
4581
|
await handleDirectAutoStart({ maxChats: options.maxChats });
|
|
4041
4582
|
|
|
@@ -4286,6 +4827,21 @@ async function startInteractive() {
|
|
|
4286
4827
|
const hostname = getHostname();
|
|
4287
4828
|
console.log('[DEBUG] Repo:', repoPath, 'Hostname:', hostname);
|
|
4288
4829
|
|
|
4830
|
+
// Get current git branch if in a git repo
|
|
4831
|
+
let currentBranch = '';
|
|
4832
|
+
if (repoPath) {
|
|
4833
|
+
try {
|
|
4834
|
+
const { execSync } = require('child_process');
|
|
4835
|
+
currentBranch = execSync('git branch --show-current', {
|
|
4836
|
+
cwd: repoPath,
|
|
4837
|
+
encoding: 'utf8'
|
|
4838
|
+
}).trim();
|
|
4839
|
+
} catch (err) {
|
|
4840
|
+
// Not a git repo or git not available
|
|
4841
|
+
currentBranch = '';
|
|
4842
|
+
}
|
|
4843
|
+
}
|
|
4844
|
+
|
|
4289
4845
|
// Build menu content for header
|
|
4290
4846
|
const menuContent = `╭───────────────────────────────────────────────────────╮
|
|
4291
4847
|
│ │
|
|
@@ -4295,7 +4851,7 @@ async function startInteractive() {
|
|
|
4295
4851
|
╰───────────────────────────────────────────────────────╯
|
|
4296
4852
|
|
|
4297
4853
|
${t('system.repo').padEnd(25)} ${repoPath || 'Not set'}
|
|
4298
|
-
${t('system.computer.name').padEnd(25)} ${hostname}
|
|
4854
|
+
${currentBranch ? t('system.git.branch').padEnd(25) + ' ' + currentBranch + '\n' : ''}${t('system.computer.name').padEnd(25)} ${hostname}
|
|
4299
4855
|
Current IDE: ${formatIDEName(ide)}
|
|
4300
4856
|
AI Provider: ${getCurrentAIProvider(ide) || 'N/A'}
|
|
4301
4857
|
Max Chats: ${maxChats || 'Never stop'}`;
|
|
@@ -4466,7 +5022,11 @@ Max Chats: ${maxChats || 'Never stop'}`;
|
|
|
4466
5022
|
}
|
|
4467
5023
|
}
|
|
4468
5024
|
|
|
4469
|
-
module.exports = {
|
|
5025
|
+
module.exports = {
|
|
5026
|
+
startInteractive,
|
|
5027
|
+
bootstrapProjectIfInHomeDir,
|
|
5028
|
+
normalizeProjectDirName
|
|
5029
|
+
};
|
|
4470
5030
|
|
|
4471
5031
|
|
|
4472
5032
|
|