vibecodingmachine-cli 2025.12.25-1541 → 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
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { getRepoPath } = require('../utils/config');
|
|
3
|
+
const {
|
|
4
|
+
createRequirementBranch,
|
|
5
|
+
mergeRequirementBranch,
|
|
6
|
+
removeRequirementFeature,
|
|
7
|
+
listRequirementBranches,
|
|
8
|
+
getCurrentBranch
|
|
9
|
+
} = require('vibecodingmachine-core');
|
|
10
|
+
|
|
11
|
+
async function start(requirementTitle) {
|
|
12
|
+
try {
|
|
13
|
+
const repoPath = await getRepoPath();
|
|
14
|
+
if (!repoPath) {
|
|
15
|
+
console.log(chalk.yellow('No repository configured'));
|
|
16
|
+
console.log(chalk.gray('Run'), chalk.cyan('vcm repo:set <path>'));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log(chalk.cyan(`Creating feature branch for: ${requirementTitle}`));
|
|
21
|
+
const result = await createRequirementBranch(repoPath, requirementTitle);
|
|
22
|
+
|
|
23
|
+
if (result.success) {
|
|
24
|
+
if (result.alreadyExists) {
|
|
25
|
+
console.log(chalk.yellow(`✓ Switched to existing branch: ${result.branchName}`));
|
|
26
|
+
} else {
|
|
27
|
+
console.log(chalk.green(`✓ Created and switched to branch: ${result.branchName}`));
|
|
28
|
+
}
|
|
29
|
+
console.log(chalk.gray(` Parent branch: ${result.parentBranch}`));
|
|
30
|
+
} else {
|
|
31
|
+
console.log(chalk.red(`✗ Failed to create branch: ${result.error}`));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error(chalk.red('Error creating feature branch:'), error.message);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function finish(requirementTitle, options = {}) {
|
|
41
|
+
try {
|
|
42
|
+
const repoPath = await getRepoPath();
|
|
43
|
+
if (!repoPath) {
|
|
44
|
+
console.log(chalk.yellow('No repository configured'));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const parentBranch = options.parent || 'main';
|
|
49
|
+
|
|
50
|
+
console.log(chalk.cyan(`Merging feature branch for: ${requirementTitle}`));
|
|
51
|
+
const result = await mergeRequirementBranch(repoPath, requirementTitle, parentBranch);
|
|
52
|
+
|
|
53
|
+
if (result.success) {
|
|
54
|
+
console.log(chalk.green(`✓ Feature merged and branch deleted`));
|
|
55
|
+
} else {
|
|
56
|
+
console.log(chalk.red(`✗ Failed to merge branch: ${result.error}`));
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(chalk.red('Error merging feature branch:'), error.message);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function remove(requirementTitle) {
|
|
66
|
+
try {
|
|
67
|
+
const repoPath = await getRepoPath();
|
|
68
|
+
if (!repoPath) {
|
|
69
|
+
console.log(chalk.yellow('No repository configured'));
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log(chalk.cyan(`Removing feature for: ${requirementTitle}`));
|
|
74
|
+
const result = await removeRequirementFeature(repoPath, requirementTitle);
|
|
75
|
+
|
|
76
|
+
if (result.success) {
|
|
77
|
+
console.log(chalk.green(`✓ Feature removed (merge reverted)`));
|
|
78
|
+
} else {
|
|
79
|
+
console.log(chalk.red(`✗ Failed to remove feature: ${result.error}`));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error(chalk.red('Error removing feature:'), error.message);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function list() {
|
|
89
|
+
try {
|
|
90
|
+
const repoPath = await getRepoPath();
|
|
91
|
+
if (!repoPath) {
|
|
92
|
+
console.log(chalk.yellow('No repository configured'));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const branches = listRequirementBranches(repoPath);
|
|
97
|
+
const currentBranch = getCurrentBranch(repoPath);
|
|
98
|
+
|
|
99
|
+
if (branches.length === 0) {
|
|
100
|
+
console.log(chalk.yellow('No requirement branches found'));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log(chalk.cyan('\nRequirement Branches:\n'));
|
|
105
|
+
branches.forEach(({ branchName, reqNumber }) => {
|
|
106
|
+
const isCurrent = branchName === currentBranch;
|
|
107
|
+
const marker = isCurrent ? chalk.green('* ') : ' ';
|
|
108
|
+
const color = isCurrent ? chalk.green : chalk.white;
|
|
109
|
+
console.log(`${marker}${color(branchName)}`);
|
|
110
|
+
});
|
|
111
|
+
console.log();
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error(chalk.red('Error listing branches:'), error.message);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
start,
|
|
120
|
+
finish,
|
|
121
|
+
remove,
|
|
122
|
+
list
|
|
123
|
+
};
|
package/src/commands/repo.js
CHANGED
|
@@ -43,34 +43,39 @@ async function getRepo() {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
async function initRepo() {
|
|
46
|
+
async function initRepo(options = {}) {
|
|
47
47
|
try {
|
|
48
48
|
const cwd = process.cwd();
|
|
49
49
|
const repoName = path.basename(cwd);
|
|
50
50
|
const insideDir = path.join(cwd, '.vibecodingmachine');
|
|
51
51
|
const siblingDir = path.join(path.dirname(cwd), `.vibecodingmachine-${repoName}`);
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
53
|
+
let location = options.location;
|
|
54
|
+
|
|
55
|
+
if (location !== 'inside' && location !== 'sibling') {
|
|
56
|
+
// Ask user where to create the directory
|
|
57
|
+
const answer = await inquirer.prompt([
|
|
58
|
+
{
|
|
59
|
+
type: 'list',
|
|
60
|
+
name: 'location',
|
|
61
|
+
message: 'Where would you like to create the VibeCodingMachine directory?',
|
|
62
|
+
choices: [
|
|
63
|
+
{
|
|
64
|
+
name: `Inside this repository (${chalk.cyan('.vibecodingmachine')}) - suggested`,
|
|
65
|
+
value: 'inside',
|
|
66
|
+
short: 'Inside repository'
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: `As a sibling directory (${chalk.cyan(`../.vibecodingmachine-${repoName}`)}) - keeps configs separate from code`,
|
|
70
|
+
value: 'sibling',
|
|
71
|
+
short: 'Sibling directory'
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
default: 'inside'
|
|
75
|
+
}
|
|
76
|
+
]);
|
|
77
|
+
location = answer.location;
|
|
78
|
+
}
|
|
74
79
|
|
|
75
80
|
const allnightDir = location === 'inside' ? insideDir : siblingDir;
|
|
76
81
|
|
|
@@ -2,13 +2,18 @@ const chalk = require('chalk');
|
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const inquirer = require('inquirer');
|
|
5
|
-
const { t } = require('vibecodingmachine-core');
|
|
5
|
+
const { t, errorReporter } = require('vibecodingmachine-core');
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* List requirements for a specific computer
|
|
9
9
|
*/
|
|
10
10
|
async function listRemoteRequirements(computerId) {
|
|
11
11
|
try {
|
|
12
|
+
// Validate input
|
|
13
|
+
if (!computerId || typeof computerId !== 'string') {
|
|
14
|
+
throw new TypeError('computerId must be a non-empty string');
|
|
15
|
+
}
|
|
16
|
+
|
|
12
17
|
const repoPath = process.cwd();
|
|
13
18
|
const reqFilename = `REQUIREMENTS-${computerId}.md`;
|
|
14
19
|
const reqPath = path.join(repoPath, '.vibecodingmachine', reqFilename);
|
|
@@ -54,6 +59,10 @@ async function listRemoteRequirements(computerId) {
|
|
|
54
59
|
|
|
55
60
|
} catch (error) {
|
|
56
61
|
console.error(chalk.red('\n✗ Failed to list requirements:'), error.message);
|
|
62
|
+
await errorReporter.reportError(error, {
|
|
63
|
+
command: 'listRemoteRequirements',
|
|
64
|
+
computerId
|
|
65
|
+
});
|
|
57
66
|
throw error;
|
|
58
67
|
}
|
|
59
68
|
}
|
|
@@ -63,6 +72,15 @@ async function listRemoteRequirements(computerId) {
|
|
|
63
72
|
*/
|
|
64
73
|
async function addRemoteRequirement(computerId, requirementText) {
|
|
65
74
|
try {
|
|
75
|
+
// Validate inputs
|
|
76
|
+
if (!computerId || typeof computerId !== 'string') {
|
|
77
|
+
throw new TypeError('computerId must be a non-empty string');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!requirementText || typeof requirementText !== 'string') {
|
|
81
|
+
throw new TypeError('requirementText must be a non-empty string');
|
|
82
|
+
}
|
|
83
|
+
|
|
66
84
|
const repoPath = process.cwd();
|
|
67
85
|
const reqFilename = `REQUIREMENTS-${computerId}.md`;
|
|
68
86
|
const reqPath = path.join(repoPath, '.vibecodingmachine', reqFilename);
|
|
@@ -144,6 +162,11 @@ DONE
|
|
|
144
162
|
|
|
145
163
|
} catch (error) {
|
|
146
164
|
console.error(chalk.red('\n✗ Failed to add requirement:'), error.message);
|
|
165
|
+
await errorReporter.reportError(error, {
|
|
166
|
+
command: 'addRemoteRequirement',
|
|
167
|
+
computerId,
|
|
168
|
+
requirementText
|
|
169
|
+
});
|
|
147
170
|
throw error;
|
|
148
171
|
}
|
|
149
172
|
}
|
|
@@ -101,17 +101,113 @@ async function getReqPathOrExit() {
|
|
|
101
101
|
|
|
102
102
|
async function list(options) {
|
|
103
103
|
try {
|
|
104
|
-
const { reqPath } = await getReqPathOrExit();
|
|
105
|
-
|
|
104
|
+
const { repoPath, reqPath } = await getReqPathOrExit();
|
|
105
|
+
|
|
106
|
+
// Handle --all-computers flag
|
|
107
|
+
if (options && options.allComputers) {
|
|
108
|
+
const vibeDir = path.join(repoPath, '.vibecodingmachine');
|
|
109
|
+
if (!await fs.pathExists(vibeDir)) {
|
|
110
|
+
console.log(chalk.yellow('No .vibecodingmachine directory found.'));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Find all REQUIREMENTS-*.md files
|
|
115
|
+
const files = await fs.readdir(vibeDir);
|
|
116
|
+
const reqFiles = files.filter(f => f.startsWith('REQUIREMENTS') && f.endsWith('.md'));
|
|
117
|
+
|
|
118
|
+
if (reqFiles.length === 0) {
|
|
119
|
+
console.log(chalk.yellow('No requirements files found.'));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Extract computer info and requirements from each file
|
|
124
|
+
for (const file of reqFiles) {
|
|
125
|
+
const filePath = path.join(vibeDir, file);
|
|
126
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
127
|
+
const lines = content.split('\n');
|
|
128
|
+
|
|
129
|
+
// Extract hostname from file
|
|
130
|
+
let hostname = 'Unknown';
|
|
131
|
+
const hostnameMatch = content.match(/- \*\*Hostname\*\*:\s*(.+)/);
|
|
132
|
+
if (hostnameMatch) {
|
|
133
|
+
hostname = hostnameMatch[1].trim();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Extract focus area
|
|
137
|
+
let focus = '';
|
|
138
|
+
const focusMatch = content.match(/## 🎯 Focus\n\*\*(.+)\*\*/);
|
|
139
|
+
if (focusMatch) {
|
|
140
|
+
focus = focusMatch[1].trim();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Filter by focus if specified
|
|
144
|
+
if (options.focus && focus && !focus.toLowerCase().includes(options.focus.toLowerCase())) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
console.log(chalk.blue.bold(`\n📍 ${hostname}`) + (focus ? chalk.gray(` (${focus})`) : ''));
|
|
149
|
+
|
|
150
|
+
// List requirements from this file
|
|
151
|
+
for (const line of lines) {
|
|
152
|
+
if (line.startsWith('### ') || line.startsWith('## ')) {
|
|
153
|
+
const statusFilter = options.status ? String(options.status).toLowerCase() : null;
|
|
154
|
+
if (!statusFilter || line.toLowerCase().includes(statusFilter)) {
|
|
155
|
+
console.log(' ' + line);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Single computer mode (default or with --computer filter)
|
|
164
|
+
let targetReqPath = reqPath;
|
|
165
|
+
|
|
166
|
+
// If computer filter specified, find that computer's requirements file
|
|
167
|
+
if (options && options.computer) {
|
|
168
|
+
const vibeDir = path.join(repoPath, '.vibecodingmachine');
|
|
169
|
+
const files = await fs.readdir(vibeDir);
|
|
170
|
+
const computerFile = files.find(f =>
|
|
171
|
+
f.startsWith('REQUIREMENTS') &&
|
|
172
|
+
f.endsWith('.md') &&
|
|
173
|
+
f.toLowerCase().includes(options.computer.toLowerCase())
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (!computerFile) {
|
|
177
|
+
console.log(chalk.yellow(`No requirements file found for computer: ${options.computer}`));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
targetReqPath = path.join(vibeDir, computerFile);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!await fs.pathExists(targetReqPath)) {
|
|
106
185
|
console.log(chalk.yellow('No REQUIREMENTS.md found.'));
|
|
107
186
|
return;
|
|
108
187
|
}
|
|
109
|
-
|
|
188
|
+
|
|
189
|
+
const content = await fs.readFile(targetReqPath, 'utf8');
|
|
110
190
|
const lines = content.split('\n');
|
|
111
|
-
|
|
191
|
+
|
|
192
|
+
// Extract focus area for focus filtering
|
|
193
|
+
let fileFocus = '';
|
|
194
|
+
const focusMatch = content.match(/## 🎯 Focus\n\*\*(.+)\*\*/);
|
|
195
|
+
if (focusMatch) {
|
|
196
|
+
fileFocus = focusMatch[1].trim();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Check focus filter
|
|
200
|
+
if (options && options.focus) {
|
|
201
|
+
if (!fileFocus || !fileFocus.toLowerCase().includes(options.focus.toLowerCase())) {
|
|
202
|
+
console.log(chalk.yellow(`No requirements match focus area: ${options.focus}`));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const statusFilter = options && options.status ? String(options.status).toLowerCase() : null;
|
|
112
208
|
for (const line of lines) {
|
|
113
209
|
if (line.startsWith('### ') || line.startsWith('## ')) {
|
|
114
|
-
if (!
|
|
210
|
+
if (!statusFilter || line.toLowerCase().includes(statusFilter)) {
|
|
115
211
|
console.log(line);
|
|
116
212
|
}
|
|
117
213
|
}
|
|
@@ -124,10 +220,14 @@ async function list(options) {
|
|
|
124
220
|
|
|
125
221
|
async function add(name, pkg, description) {
|
|
126
222
|
try {
|
|
127
|
-
const { reqPath } = await getReqPathOrExit();
|
|
223
|
+
const { reqPath, repoPath } = await getReqPathOrExit();
|
|
128
224
|
await fs.ensureFile(reqPath);
|
|
129
225
|
let content = await fs.readFile(reqPath, 'utf8').catch(() => '');
|
|
130
226
|
|
|
227
|
+
// Get next requirement number
|
|
228
|
+
const { getNextRequirementNumber } = require('vibecodingmachine-core');
|
|
229
|
+
const reqNumber = await getNextRequirementNumber(repoPath);
|
|
230
|
+
|
|
131
231
|
// Find the TODO section
|
|
132
232
|
const todoSectionHeader = '## ⏳ Requirements not yet completed';
|
|
133
233
|
if (!content.includes('Requirements not yet completed')) {
|
|
@@ -144,8 +244,8 @@ async function add(name, pkg, description) {
|
|
|
144
244
|
|
|
145
245
|
// Insert right after the TODO section header
|
|
146
246
|
if (!inserted && lines[i].startsWith('##') && lines[i].includes('Requirements not yet completed')) {
|
|
147
|
-
// Add requirement header
|
|
148
|
-
newLines.push(`### ${name}`);
|
|
247
|
+
// Add requirement header with number
|
|
248
|
+
newLines.push(`### R${reqNumber}: ${name}`);
|
|
149
249
|
|
|
150
250
|
// Add package if provided (and not 'all')
|
|
151
251
|
if (pkg && Array.isArray(pkg) && pkg.length > 0) {
|
|
@@ -363,6 +463,25 @@ async function working() {
|
|
|
363
463
|
}
|
|
364
464
|
}
|
|
365
465
|
|
|
466
|
+
async function numberAll() {
|
|
467
|
+
try {
|
|
468
|
+
const { reqPath, repoPath } = await getReqPathOrExit();
|
|
469
|
+
if (!await fs.pathExists(reqPath)) {
|
|
470
|
+
console.log(chalk.yellow('No REQUIREMENTS.md found.'));
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
console.log(chalk.cyan('Numbering all existing requirements...'));
|
|
475
|
+
const { numberAllRequirements } = require('vibecodingmachine-core');
|
|
476
|
+
const numbered = await numberAllRequirements(reqPath, repoPath);
|
|
477
|
+
|
|
478
|
+
console.log(chalk.green(`✓ Numbered ${numbered} requirements`));
|
|
479
|
+
} catch (error) {
|
|
480
|
+
console.error(chalk.red('Error numbering requirements:'), error.message);
|
|
481
|
+
process.exit(1);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
366
485
|
module.exports = {
|
|
367
486
|
list,
|
|
368
487
|
add,
|
|
@@ -371,7 +490,8 @@ module.exports = {
|
|
|
371
490
|
next,
|
|
372
491
|
edit,
|
|
373
492
|
watch,
|
|
374
|
-
rename
|
|
493
|
+
rename,
|
|
494
|
+
numberAll
|
|
375
495
|
};
|
|
376
496
|
|
|
377
497
|
|
package/src/commands/setup.js
CHANGED
|
@@ -4,6 +4,7 @@ const os = require('os');
|
|
|
4
4
|
const chalk = require('chalk');
|
|
5
5
|
const inquirer = require('inquirer');
|
|
6
6
|
const { t } = require('vibecodingmachine-core');
|
|
7
|
+
const { promptWithDefaultsOnce } = require('../utils/prompt-helper');
|
|
7
8
|
|
|
8
9
|
async function setupAlias() {
|
|
9
10
|
console.log(chalk.bold.cyan(`\n🛠️ ${t('setup.alias.title')}\n`));
|
|
@@ -62,7 +63,7 @@ async function setupAlias() {
|
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
// Confirm with user
|
|
65
|
-
const { confirm } = await
|
|
66
|
+
const { confirm } = await promptWithDefaultsOnce([
|
|
66
67
|
{
|
|
67
68
|
type: 'confirm',
|
|
68
69
|
name: 'confirm',
|
package/src/commands/sync.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const Table = require('cli-table3');
|
|
3
3
|
const SyncEngine = require('vibecodingmachine-core/src/sync/sync-engine');
|
|
4
|
-
const { t } = require('vibecodingmachine-core');
|
|
4
|
+
const { t, errorReporter } = require('vibecodingmachine-core');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Trigger immediate sync
|
|
@@ -44,6 +44,9 @@ async function syncNow() {
|
|
|
44
44
|
} catch (error) {
|
|
45
45
|
console.error(chalk.red(`\n✗ ${t('sync.failed')}`), error.message);
|
|
46
46
|
console.log(chalk.gray(`\n${t('sync.tip.aws')}\n`));
|
|
47
|
+
await errorReporter.reportError(error, {
|
|
48
|
+
command: 'syncNow'
|
|
49
|
+
});
|
|
47
50
|
// Don't throw - just log the error
|
|
48
51
|
} finally {
|
|
49
52
|
syncEngine.stop();
|
|
@@ -109,6 +112,9 @@ async function syncStatus() {
|
|
|
109
112
|
|
|
110
113
|
} catch (error) {
|
|
111
114
|
console.error(chalk.red(`\n✗ ${t('sync.status.failed')}`), error.message);
|
|
115
|
+
await errorReporter.reportError(error, {
|
|
116
|
+
command: 'syncStatus'
|
|
117
|
+
});
|
|
112
118
|
throw error;
|
|
113
119
|
} finally {
|
|
114
120
|
syncEngine.stop();
|
package/src/utils/auth.js
CHANGED
|
@@ -8,7 +8,7 @@ const sharedAuth = require('vibecodingmachine-core/src/auth/shared-auth-storage'
|
|
|
8
8
|
// AWS Cognito configuration
|
|
9
9
|
const COGNITO_DOMAIN = process.env.COGNITO_DOMAIN || 'vibecodingmachine-auth-1763598779.auth.us-east-1.amazoncognito.com';
|
|
10
10
|
const CLIENT_ID = process.env.COGNITO_APP_CLIENT_ID || '3tbe1i2g36uqule92iuk6snuo3'; // Public client (no secret)
|
|
11
|
-
const PREFERRED_PORT =
|
|
11
|
+
const PREFERRED_PORT = 3001; // Use 3001 to avoid conflict with web dev server on 3000
|
|
12
12
|
|
|
13
13
|
// Load shared access denied HTML
|
|
14
14
|
function getAccessDeniedHTML() {
|
|
@@ -70,6 +70,17 @@ class CLIAuth {
|
|
|
70
70
|
return await sharedAuth.getToken();
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Get auth token string for API requests
|
|
75
|
+
*/
|
|
76
|
+
async getAuthToken() {
|
|
77
|
+
const token = await sharedAuth.getToken();
|
|
78
|
+
if (!token) return null;
|
|
79
|
+
|
|
80
|
+
// Handle both string token and object with id_token
|
|
81
|
+
return typeof token === 'string' ? token : token.id_token;
|
|
82
|
+
}
|
|
83
|
+
|
|
73
84
|
/**
|
|
74
85
|
* Get user profile (uses shared storage)
|
|
75
86
|
*/
|
|
@@ -332,12 +343,10 @@ class CLIAuth {
|
|
|
332
343
|
|
|
333
344
|
if (!portAvailable) {
|
|
334
345
|
console.log(chalk.red(`\n❌ Port ${PORT} is already in use!\n`));
|
|
335
|
-
console.log(chalk.yellow(
|
|
336
|
-
console.log(chalk.white(
|
|
346
|
+
console.log(chalk.yellow(`Port ${PORT} is required for authentication (OAuth callback).\n`));
|
|
347
|
+
console.log(chalk.white(`To fix this, find and stop the process using port ${PORT}:\n`));
|
|
337
348
|
console.log(chalk.gray(' 1. Find the process: ') + chalk.cyan(`lsof -i :${PORT}`));
|
|
338
|
-
console.log(chalk.gray(' 2. Stop it: ') + chalk.cyan('kill <PID
|
|
339
|
-
console.log(chalk.gray('\nOr if you\'re running the web dev server:'));
|
|
340
|
-
console.log(chalk.gray(' ') + chalk.cyan('cd packages/web && npm stop\n'));
|
|
349
|
+
console.log(chalk.gray(' 2. Stop it: ') + chalk.cyan('kill <PID>\n'));
|
|
341
350
|
throw new Error(`Port ${PORT} is already in use. Please free up this port and try again.`);
|
|
342
351
|
}
|
|
343
352
|
|
|
@@ -595,7 +604,7 @@ class CLIAuth {
|
|
|
595
604
|
`client_id=${CLIENT_ID}&` +
|
|
596
605
|
`response_type=code&` +
|
|
597
606
|
`scope=email+openid+profile&` +
|
|
598
|
-
`redirect_uri=http://localhost
|
|
607
|
+
`redirect_uri=http://localhost:${PREFERRED_PORT}/callback&` +
|
|
599
608
|
`code_challenge=${codeChallenge}&` +
|
|
600
609
|
`code_challenge_method=S256&` +
|
|
601
610
|
`identity_provider=Google&` +
|
|
@@ -610,7 +619,7 @@ class CLIAuth {
|
|
|
610
619
|
console.log(chalk.yellow('3. Browser will redirect to localhost and show "connection refused" error'));
|
|
611
620
|
console.log(chalk.yellow(' ' + chalk.gray('(This is expected - localhost is not accessible from your device)')));
|
|
612
621
|
console.log(chalk.yellow('4. Copy the FULL URL from your browser address bar'));
|
|
613
|
-
console.log(chalk.yellow(' ' + chalk.gray(
|
|
622
|
+
console.log(chalk.yellow(' ' + chalk.gray(`(It will start with: http://localhost:${PREFERRED_PORT}/callback?code=...)`)));
|
|
614
623
|
console.log(chalk.yellow('5. Paste it below\n'));
|
|
615
624
|
|
|
616
625
|
const { callbackUrl } = await inquirer.prompt([{
|
|
@@ -624,8 +633,8 @@ class CLIAuth {
|
|
|
624
633
|
if (!input.includes('code=')) {
|
|
625
634
|
return 'Invalid URL - should contain "code=" parameter';
|
|
626
635
|
}
|
|
627
|
-
if (!input.includes(
|
|
628
|
-
return
|
|
636
|
+
if (!input.includes(`localhost:${PREFERRED_PORT}/callback`)) {
|
|
637
|
+
return `Invalid callback URL - should be from localhost:${PREFERRED_PORT}/callback`;
|
|
629
638
|
}
|
|
630
639
|
return true;
|
|
631
640
|
}
|
|
@@ -640,14 +649,12 @@ class CLIAuth {
|
|
|
640
649
|
throw new Error('No authorization code found in URL');
|
|
641
650
|
}
|
|
642
651
|
|
|
643
|
-
// Exchange code for tokens
|
|
644
|
-
console.log(chalk.gray('Exchanging authorization code for tokens...'));
|
|
645
652
|
// Exchange code for tokens
|
|
646
653
|
console.log(chalk.gray('Exchanging authorization code for tokens...'));
|
|
647
654
|
const tokens = await this._exchangeCodeForTokens(
|
|
648
655
|
code,
|
|
649
656
|
codeVerifier,
|
|
650
|
-
|
|
657
|
+
`http://localhost:${PREFERRED_PORT}/callback`
|
|
651
658
|
);
|
|
652
659
|
|
|
653
660
|
const idToken = tokens.id_token;
|
package/src/utils/config.js
CHANGED
|
@@ -75,6 +75,17 @@ async function setStages(stages) {
|
|
|
75
75
|
await writeConfig(cfg);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
async function getComputerFilter() {
|
|
79
|
+
const cfg = await readConfig();
|
|
80
|
+
return cfg.computerFilter || null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function setComputerFilter(computerName) {
|
|
84
|
+
const cfg = await readConfig();
|
|
85
|
+
cfg.computerFilter = computerName;
|
|
86
|
+
await writeConfig(cfg);
|
|
87
|
+
}
|
|
88
|
+
|
|
78
89
|
module.exports = {
|
|
79
90
|
getRepoPath,
|
|
80
91
|
setRepoPath,
|
|
@@ -84,7 +95,9 @@ module.exports = {
|
|
|
84
95
|
writeConfig,
|
|
85
96
|
getStages,
|
|
86
97
|
setStages,
|
|
87
|
-
DEFAULT_STAGES
|
|
98
|
+
DEFAULT_STAGES,
|
|
99
|
+
getComputerFilter,
|
|
100
|
+
setComputerFilter
|
|
88
101
|
};
|
|
89
102
|
|
|
90
103
|
|
package/src/utils/first-run.js
CHANGED
|
@@ -7,6 +7,7 @@ const os = require('os');
|
|
|
7
7
|
const { getProviderDefinitions, saveProviderPreferences, getDefaultProviderOrder } = require('./provider-registry');
|
|
8
8
|
const { isKiroInstalled } = require('./kiro-installer');
|
|
9
9
|
const { t } = require('vibecodingmachine-core');
|
|
10
|
+
const { promptWithDefaultsOnce } = require('./prompt-helper');
|
|
10
11
|
|
|
11
12
|
const { execSync } = require('child_process');
|
|
12
13
|
|
|
@@ -120,7 +121,7 @@ async function checkFirstRun() {
|
|
|
120
121
|
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
121
122
|
|
|
122
123
|
// --- NEW: Vibe Coding Introduction ---
|
|
123
|
-
const { showIntro } = await
|
|
124
|
+
const { showIntro } = await promptWithDefaultsOnce([
|
|
124
125
|
{
|
|
125
126
|
type: 'confirm',
|
|
126
127
|
name: 'showIntro',
|
|
@@ -198,7 +199,7 @@ async function checkFirstRun() {
|
|
|
198
199
|
|
|
199
200
|
// 3. Status Report & Selection
|
|
200
201
|
if (detectedIDEs.length > 0) {
|
|
201
|
-
console.log(chalk.green('\n✓ We found these IDEs and enabled them
|
|
202
|
+
console.log(chalk.green('\n✓ We found these IDEs already installed and enabled them:'));
|
|
202
203
|
detectedIDEs.forEach(ide => {
|
|
203
204
|
console.log(chalk.gray(` • ${ide.name}`));
|
|
204
205
|
});
|
|
@@ -206,19 +207,19 @@ async function checkFirstRun() {
|
|
|
206
207
|
|
|
207
208
|
let selectedIDEs = [];
|
|
208
209
|
|
|
209
|
-
//
|
|
210
|
+
// Show all uninstalled IDEs for selection
|
|
210
211
|
if (otherIDEs.length > 0) {
|
|
211
212
|
console.log(); // Spacing
|
|
212
213
|
const choices = otherIDEs.map(ide => ({
|
|
213
214
|
name: ide.name,
|
|
214
215
|
value: ide.id,
|
|
215
|
-
checked: true //
|
|
216
|
+
checked: true // All IDEs enabled by default as requested
|
|
216
217
|
}));
|
|
217
218
|
|
|
218
219
|
const response = await inquirer.prompt([{
|
|
219
220
|
type: 'checkbox',
|
|
220
221
|
name: 'selectedIDEs',
|
|
221
|
-
message: 'Select
|
|
222
|
+
message: 'Select IDEs to install & enable (uncheck any you don\'t want):',
|
|
222
223
|
choices: choices,
|
|
223
224
|
pageSize: 10
|
|
224
225
|
}]);
|
|
@@ -331,6 +332,7 @@ async function checkFirstRun() {
|
|
|
331
332
|
if (def && def.type === 'ide') {
|
|
332
333
|
const isDetectedNow = reDetected.includes(id);
|
|
333
334
|
const isSelected = selectedIDEs.includes(id);
|
|
335
|
+
// Enable if detected OR selected during first run
|
|
334
336
|
enabledMap[id] = isDetectedNow || isSelected;
|
|
335
337
|
} else {
|
|
336
338
|
enabledMap[id] = true; // Keep LLMs enabled by default
|
|
@@ -345,7 +347,7 @@ async function checkFirstRun() {
|
|
|
345
347
|
|
|
346
348
|
// --- NEW: CLI Usage Onboarding ---
|
|
347
349
|
// Moved here so IDEs are set up first
|
|
348
|
-
const { isFamiliar } = await
|
|
350
|
+
const { isFamiliar } = await promptWithDefaultsOnce([
|
|
349
351
|
{
|
|
350
352
|
type: 'confirm',
|
|
351
353
|
name: 'isFamiliar',
|