vibecodingmachine-cli 2025.12.1-534 → 2025.12.6-1702
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 +92 -4
- package/package.json +5 -2
- package/repro_open.js +13 -0
- package/scripts/postinstall.js +80 -0
- package/src/commands/auto-direct.js +401 -97
- package/src/commands/auto.js +343 -115
- package/src/commands/computers.js +306 -0
- package/src/commands/requirements-remote.js +304 -0
- package/src/commands/requirements.js +204 -13
- package/src/commands/sync.js +280 -0
- package/src/utils/asset-cleanup.js +61 -0
- package/src/utils/auth.js +84 -7
- package/src/utils/auto-mode-simple-ui.js +2 -22
- package/src/utils/first-run.js +293 -0
- package/src/utils/interactive.js +1027 -217
- package/src/utils/kiro-installer.js +146 -0
- package/src/utils/provider-registry.js +8 -0
- package/src/utils/status-card.js +2 -1
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const Table = require('cli-table3');
|
|
3
|
+
const SyncEngine = require('vibecodingmachine-core/src/sync/sync-engine');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Display all registered computers with their status
|
|
7
|
+
*/
|
|
8
|
+
async function listComputers(options = {}) {
|
|
9
|
+
const syncEngine = new SyncEngine();
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
await syncEngine.initialize();
|
|
13
|
+
|
|
14
|
+
// Fetch all computers from DynamoDB
|
|
15
|
+
const computers = await fetchAllComputers(syncEngine);
|
|
16
|
+
|
|
17
|
+
if (computers.length === 0) {
|
|
18
|
+
console.log(chalk.yellow('\nNo computers registered yet.\n'));
|
|
19
|
+
console.log(chalk.gray('Computers will appear here once they sync for the first time.\n'));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Filter by focus area if specified
|
|
24
|
+
let filteredComputers = computers;
|
|
25
|
+
if (options.focus) {
|
|
26
|
+
filteredComputers = computers.filter(c =>
|
|
27
|
+
c.focusArea && c.focusArea.toLowerCase().includes(options.focus.toLowerCase())
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Filter by status if specified
|
|
32
|
+
if (options.status) {
|
|
33
|
+
filteredComputers = filteredComputers.filter(c =>
|
|
34
|
+
c.status && c.status.toLowerCase() === options.status.toLowerCase()
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Create table
|
|
39
|
+
const table = new Table({
|
|
40
|
+
head: [
|
|
41
|
+
chalk.cyan('Computer'),
|
|
42
|
+
chalk.cyan('Focus Area'),
|
|
43
|
+
chalk.cyan('Status'),
|
|
44
|
+
chalk.cyan('Current Requirement'),
|
|
45
|
+
chalk.cyan('Progress'),
|
|
46
|
+
chalk.cyan('Last Sync')
|
|
47
|
+
],
|
|
48
|
+
colWidths: [25, 30, 12, 35, 10, 15]
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Add rows
|
|
52
|
+
for (const computer of filteredComputers) {
|
|
53
|
+
const statusIcon = getStatusIcon(computer.status);
|
|
54
|
+
const statusText = `${statusIcon} ${computer.status || 'unknown'}`;
|
|
55
|
+
const progress = computer.progress ? `${computer.progress}%` : '-';
|
|
56
|
+
const lastSync = computer.lastSyncTime
|
|
57
|
+
? formatTimeSince(computer.lastSyncTime)
|
|
58
|
+
: 'never';
|
|
59
|
+
|
|
60
|
+
table.push([
|
|
61
|
+
computer.hostname || computer.computerId,
|
|
62
|
+
computer.focusArea || '-',
|
|
63
|
+
statusText,
|
|
64
|
+
truncate(computer.currentRequirement || '-', 33),
|
|
65
|
+
progress,
|
|
66
|
+
lastSync
|
|
67
|
+
]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log('\n' + table.toString() + '\n');
|
|
71
|
+
|
|
72
|
+
// Show summary
|
|
73
|
+
const activeCount = computers.filter(c => c.status === 'active').length;
|
|
74
|
+
const idleCount = computers.filter(c => c.status === 'idle').length;
|
|
75
|
+
const errorCount = computers.filter(c => c.status === 'error').length;
|
|
76
|
+
|
|
77
|
+
console.log(chalk.gray(`Total: ${computers.length} computers`));
|
|
78
|
+
console.log(chalk.green(` Active: ${activeCount}`));
|
|
79
|
+
console.log(chalk.yellow(` Idle: ${idleCount}`));
|
|
80
|
+
if (errorCount > 0) {
|
|
81
|
+
console.log(chalk.red(` Error: ${errorCount}`));
|
|
82
|
+
}
|
|
83
|
+
console.log('');
|
|
84
|
+
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error(chalk.red('\n✗ Failed to fetch computers:'), error.message);
|
|
87
|
+
throw error;
|
|
88
|
+
} finally {
|
|
89
|
+
syncEngine.stop();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Show detailed status of a specific computer
|
|
95
|
+
*/
|
|
96
|
+
async function showComputerStatus(computerId, options = {}) {
|
|
97
|
+
const syncEngine = new SyncEngine();
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
await syncEngine.initialize();
|
|
101
|
+
|
|
102
|
+
const computer = await fetchComputer(syncEngine, computerId);
|
|
103
|
+
|
|
104
|
+
if (!computer) {
|
|
105
|
+
console.log(chalk.red(`\n✗ Computer '${computerId}' not found.\n`));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Display computer details
|
|
110
|
+
console.log('\n' + chalk.bold.cyan('Computer Details'));
|
|
111
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
112
|
+
console.log(chalk.white('Hostname: ') + (computer.hostname || computer.computerId));
|
|
113
|
+
console.log(chalk.white('Computer ID: ') + computer.computerId);
|
|
114
|
+
console.log(chalk.white('Focus Area: ') + (computer.focusArea || '-'));
|
|
115
|
+
console.log(chalk.white('OS: ') + (computer.os || '-'));
|
|
116
|
+
console.log(chalk.white('Status: ') + getStatusIcon(computer.status) + ' ' + (computer.status || 'unknown'));
|
|
117
|
+
console.log(chalk.white('Last Sync: ') + (computer.lastSyncTime ? formatTimeSince(computer.lastSyncTime) : 'never'));
|
|
118
|
+
console.log('');
|
|
119
|
+
|
|
120
|
+
// Current work
|
|
121
|
+
console.log(chalk.bold.cyan('Current Work'));
|
|
122
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
123
|
+
console.log(chalk.white('Requirement: ') + (computer.currentRequirement || 'None'));
|
|
124
|
+
console.log(chalk.white('Progress: ') + (computer.progress ? `${computer.progress}%` : '-'));
|
|
125
|
+
console.log(chalk.white('Time Active: ') + (computer.timeActive || '-'));
|
|
126
|
+
console.log(chalk.white('IDE: ') + (computer.ide || '-'));
|
|
127
|
+
console.log('');
|
|
128
|
+
|
|
129
|
+
// Statistics
|
|
130
|
+
if (computer.stats) {
|
|
131
|
+
console.log(chalk.bold.cyan('Statistics'));
|
|
132
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
133
|
+
console.log(chalk.white('Total Requirements: ') + (computer.stats.total || 0));
|
|
134
|
+
console.log(chalk.white('Completed: ') + chalk.green(computer.stats.completed || 0));
|
|
135
|
+
console.log(chalk.white('In Progress: ') + chalk.yellow(computer.stats.inProgress || 0));
|
|
136
|
+
console.log(chalk.white('To Verify: ') + chalk.blue(computer.stats.toVerify || 0));
|
|
137
|
+
console.log(chalk.white('Completion Rate: ') + (computer.stats.completionRate || '0%'));
|
|
138
|
+
console.log('');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error(chalk.red('\n✗ Failed to fetch computer status:'), error.message);
|
|
143
|
+
throw error;
|
|
144
|
+
} finally {
|
|
145
|
+
syncEngine.stop();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Register current computer with focus area
|
|
151
|
+
*/
|
|
152
|
+
async function registerComputer(focusArea, options = {}) {
|
|
153
|
+
const syncEngine = new SyncEngine();
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
await syncEngine.initialize();
|
|
157
|
+
|
|
158
|
+
const os = require('os');
|
|
159
|
+
const computerData = {
|
|
160
|
+
computerId: syncEngine.computerId,
|
|
161
|
+
hostname: os.hostname(),
|
|
162
|
+
focusArea: focusArea,
|
|
163
|
+
os: `${os.platform()} ${os.release()}`,
|
|
164
|
+
registeredAt: Date.now(),
|
|
165
|
+
lastSyncTime: Date.now(),
|
|
166
|
+
status: 'idle'
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
await saveComputer(syncEngine, computerData);
|
|
170
|
+
|
|
171
|
+
console.log(chalk.green('\n✓ Computer registered successfully!\n'));
|
|
172
|
+
console.log(chalk.white('Computer ID: ') + computerData.computerId);
|
|
173
|
+
console.log(chalk.white('Hostname: ') + computerData.hostname);
|
|
174
|
+
console.log(chalk.white('Focus Area: ') + computerData.focusArea);
|
|
175
|
+
console.log('');
|
|
176
|
+
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error(chalk.red('\n✗ Failed to register computer:'), error.message);
|
|
179
|
+
throw error;
|
|
180
|
+
} finally {
|
|
181
|
+
syncEngine.stop();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Update focus area for current computer
|
|
187
|
+
*/
|
|
188
|
+
async function updateFocus(newFocusArea, options = {}) {
|
|
189
|
+
const syncEngine = new SyncEngine();
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
await syncEngine.initialize();
|
|
193
|
+
|
|
194
|
+
const computer = await fetchComputer(syncEngine, syncEngine.computerId);
|
|
195
|
+
|
|
196
|
+
if (!computer) {
|
|
197
|
+
console.log(chalk.yellow('\n⚠ Computer not registered. Registering now...\n'));
|
|
198
|
+
await registerComputer(newFocusArea, options);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
computer.focusArea = newFocusArea;
|
|
203
|
+
computer.lastSyncTime = Date.now();
|
|
204
|
+
|
|
205
|
+
await saveComputer(syncEngine, computer);
|
|
206
|
+
|
|
207
|
+
console.log(chalk.green('\n✓ Focus area updated!\n'));
|
|
208
|
+
console.log(chalk.white('Computer: ') + computer.hostname);
|
|
209
|
+
console.log(chalk.white('New Focus: ') + newFocusArea);
|
|
210
|
+
console.log('');
|
|
211
|
+
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error(chalk.red('\n✗ Failed to update focus area:'), error.message);
|
|
214
|
+
throw error;
|
|
215
|
+
} finally {
|
|
216
|
+
syncEngine.stop();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Helper functions
|
|
221
|
+
|
|
222
|
+
function getStatusIcon(status) {
|
|
223
|
+
switch (status) {
|
|
224
|
+
case 'active': return chalk.green('●');
|
|
225
|
+
case 'idle': return chalk.yellow('●');
|
|
226
|
+
case 'error': return chalk.red('●');
|
|
227
|
+
case 'offline': return chalk.gray('●');
|
|
228
|
+
default: return chalk.gray('○');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function formatTimeSince(timestamp) {
|
|
233
|
+
const now = Date.now();
|
|
234
|
+
const diff = now - timestamp;
|
|
235
|
+
|
|
236
|
+
const seconds = Math.floor(diff / 1000);
|
|
237
|
+
const minutes = Math.floor(seconds / 60);
|
|
238
|
+
const hours = Math.floor(minutes / 60);
|
|
239
|
+
const days = Math.floor(hours / 24);
|
|
240
|
+
|
|
241
|
+
if (days > 0) return `${days}d ago`;
|
|
242
|
+
if (hours > 0) return `${hours}h ago`;
|
|
243
|
+
if (minutes > 0) return `${minutes}m ago`;
|
|
244
|
+
return `${seconds}s ago`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function truncate(str, maxLength) {
|
|
248
|
+
if (str.length <= maxLength) return str;
|
|
249
|
+
return str.substring(0, maxLength - 3) + '...';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function fetchAllComputers(syncEngine) {
|
|
253
|
+
const { ScanCommand } = require('@aws-sdk/lib-dynamodb');
|
|
254
|
+
|
|
255
|
+
const tableName = 'vibecodingmachine-computers';
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const command = new ScanCommand({
|
|
259
|
+
TableName: tableName
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const response = await syncEngine.dynamoClient.send(command);
|
|
263
|
+
return response.Items || [];
|
|
264
|
+
} catch (error) {
|
|
265
|
+
// Table might not exist yet
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function fetchComputer(syncEngine, computerId) {
|
|
271
|
+
const { GetCommand } = require('@aws-sdk/lib-dynamodb');
|
|
272
|
+
|
|
273
|
+
const tableName = 'vibecodingmachine-computers';
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
const command = new GetCommand({
|
|
277
|
+
TableName: tableName,
|
|
278
|
+
Key: { computerId }
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const response = await syncEngine.dynamoClient.send(command);
|
|
282
|
+
return response.Item;
|
|
283
|
+
} catch (error) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async function saveComputer(syncEngine, computerData) {
|
|
289
|
+
const { PutCommand } = require('@aws-sdk/lib-dynamodb');
|
|
290
|
+
|
|
291
|
+
const tableName = 'vibecodingmachine-computers';
|
|
292
|
+
|
|
293
|
+
const command = new PutCommand({
|
|
294
|
+
TableName: tableName,
|
|
295
|
+
Item: computerData
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
await syncEngine.dynamoClient.send(command);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
module.exports = {
|
|
302
|
+
listComputers,
|
|
303
|
+
showComputerStatus,
|
|
304
|
+
registerComputer,
|
|
305
|
+
updateFocus
|
|
306
|
+
};
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const inquirer = require('inquirer');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* List requirements for a specific computer
|
|
8
|
+
*/
|
|
9
|
+
async function listRemoteRequirements(computerId, options = {}) {
|
|
10
|
+
try {
|
|
11
|
+
const repoPath = process.cwd();
|
|
12
|
+
const reqFilename = `REQUIREMENTS-${computerId}.md`;
|
|
13
|
+
const reqPath = path.join(repoPath, '.vibecodingmachine', reqFilename);
|
|
14
|
+
|
|
15
|
+
if (!await fs.pathExists(reqPath)) {
|
|
16
|
+
console.log(chalk.yellow(`\n⚠ Requirements file not found for ${computerId}\n`));
|
|
17
|
+
console.log(chalk.gray(`Expected: ${reqPath}\n`));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
22
|
+
const sections = parseRequirements(content);
|
|
23
|
+
|
|
24
|
+
console.log(chalk.bold.cyan(`\n📋 Requirements for ${computerId}\n`));
|
|
25
|
+
|
|
26
|
+
// Show counts
|
|
27
|
+
console.log(chalk.white('Summary:'));
|
|
28
|
+
console.log(chalk.yellow(` TODO: ${sections.todo.length}`));
|
|
29
|
+
console.log(chalk.cyan(` TO VERIFY: ${sections.toVerify.length}`));
|
|
30
|
+
console.log(chalk.green(` VERIFIED: ${sections.verified.length}`));
|
|
31
|
+
console.log('');
|
|
32
|
+
|
|
33
|
+
// Show TODO requirements
|
|
34
|
+
if (sections.todo.length > 0) {
|
|
35
|
+
console.log(chalk.bold.yellow('⏳ TODO Requirements:\n'));
|
|
36
|
+
sections.todo.forEach((req, idx) => {
|
|
37
|
+
console.log(chalk.yellow(`${idx + 1}. `) + req.title);
|
|
38
|
+
if (req.description) {
|
|
39
|
+
console.log(chalk.gray(' ' + req.description.substring(0, 100) + '...'));
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
console.log('');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Show TO VERIFY requirements
|
|
46
|
+
if (sections.toVerify.length > 0) {
|
|
47
|
+
console.log(chalk.bold.cyan('🔍 TO VERIFY Requirements:\n'));
|
|
48
|
+
sections.toVerify.forEach((req, idx) => {
|
|
49
|
+
console.log(chalk.cyan(`${idx + 1}. `) + req.title);
|
|
50
|
+
});
|
|
51
|
+
console.log('');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error(chalk.red('\n✗ Failed to list requirements:'), error.message);
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Add a requirement to another computer's requirements file
|
|
62
|
+
*/
|
|
63
|
+
async function addRemoteRequirement(computerId, requirementText, options = {}) {
|
|
64
|
+
try {
|
|
65
|
+
const repoPath = process.cwd();
|
|
66
|
+
const reqFilename = `REQUIREMENTS-${computerId}.md`;
|
|
67
|
+
const reqPath = path.join(repoPath, '.vibecodingmachine', reqFilename);
|
|
68
|
+
|
|
69
|
+
if (!await fs.pathExists(reqPath)) {
|
|
70
|
+
console.log(chalk.yellow(`\n⚠ Requirements file not found for ${computerId}\n`));
|
|
71
|
+
console.log(chalk.gray('Creating new requirements file...\n'));
|
|
72
|
+
|
|
73
|
+
// Create basic requirements file structure
|
|
74
|
+
const template = `# 🤖 Vibe Coding Machine – Requirements File
|
|
75
|
+
|
|
76
|
+
## 💻 Computer Information
|
|
77
|
+
- **Hostname**: ${computerId}
|
|
78
|
+
- **Type**: Unknown
|
|
79
|
+
- **OS**: Unknown
|
|
80
|
+
|
|
81
|
+
## 🎯 Focus
|
|
82
|
+
**General Development**
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## RESPONSE FROM LAST CHAT
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
## 🚦 Current Status
|
|
90
|
+
DONE
|
|
91
|
+
|
|
92
|
+
## ⏳ Requirements not yet completed
|
|
93
|
+
|
|
94
|
+
## 🔍 TO VERIFY BY HUMAN
|
|
95
|
+
|
|
96
|
+
## 📝 VERIFIED
|
|
97
|
+
|
|
98
|
+
## ❓ Requirements needing manual feedback
|
|
99
|
+
|
|
100
|
+
## REJECTED by Human
|
|
101
|
+
|
|
102
|
+
## ✅ Verified by AI screenshot. Needs Human to Verify and move to CHANGELOG
|
|
103
|
+
|
|
104
|
+
## ♻️ Recycled
|
|
105
|
+
|
|
106
|
+
## CHANGELOG
|
|
107
|
+
`;
|
|
108
|
+
await fs.writeFile(reqPath, template, 'utf8');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Read current content
|
|
112
|
+
let content = await fs.readFile(reqPath, 'utf8');
|
|
113
|
+
|
|
114
|
+
// Find the TODO section
|
|
115
|
+
const todoMarker = '## ⏳ Requirements not yet completed';
|
|
116
|
+
const todoIndex = content.indexOf(todoMarker);
|
|
117
|
+
|
|
118
|
+
if (todoIndex === -1) {
|
|
119
|
+
console.log(chalk.red('\n✗ Could not find TODO section in requirements file\n'));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Find the next section after TODO
|
|
124
|
+
const nextSectionIndex = content.indexOf('\n## ', todoIndex + todoMarker.length);
|
|
125
|
+
|
|
126
|
+
// Insert the new requirement
|
|
127
|
+
const newRequirement = `\n### ${requirementText}\n`;
|
|
128
|
+
|
|
129
|
+
if (nextSectionIndex === -1) {
|
|
130
|
+
// No next section, append at end
|
|
131
|
+
content = content.substring(0, todoIndex + todoMarker.length) + newRequirement + content.substring(todoIndex + todoMarker.length);
|
|
132
|
+
} else {
|
|
133
|
+
// Insert before next section
|
|
134
|
+
content = content.substring(0, nextSectionIndex) + newRequirement + content.substring(nextSectionIndex);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Write back
|
|
138
|
+
await fs.writeFile(reqPath, content, 'utf8');
|
|
139
|
+
|
|
140
|
+
console.log(chalk.green(`\n✓ Added requirement to ${computerId}\n`));
|
|
141
|
+
console.log(chalk.white('Requirement: ') + requirementText);
|
|
142
|
+
console.log('');
|
|
143
|
+
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error(chalk.red('\n✗ Failed to add requirement:'), error.message);
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Interactive menu for managing another computer's requirements
|
|
152
|
+
*/
|
|
153
|
+
async function manageRemoteRequirements(computerId) {
|
|
154
|
+
const choices = [
|
|
155
|
+
{ name: '📋 View Requirements', value: 'view' },
|
|
156
|
+
{ name: '➕ Add Requirement', value: 'add' },
|
|
157
|
+
{ name: '📝 Edit Requirements File', value: 'edit' },
|
|
158
|
+
{ name: chalk.gray('← Back'), value: 'back' }
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
const { action } = await inquirer.prompt([
|
|
162
|
+
{
|
|
163
|
+
type: 'list',
|
|
164
|
+
name: 'action',
|
|
165
|
+
message: `Manage requirements for ${computerId}:`,
|
|
166
|
+
choices: choices
|
|
167
|
+
}
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
switch (action) {
|
|
171
|
+
case 'view':
|
|
172
|
+
await listRemoteRequirements(computerId);
|
|
173
|
+
console.log(chalk.gray('Press Enter to continue...'));
|
|
174
|
+
await new Promise(resolve => {
|
|
175
|
+
const readline = require('readline');
|
|
176
|
+
const rl = readline.createInterface({
|
|
177
|
+
input: process.stdin,
|
|
178
|
+
output: process.stdout
|
|
179
|
+
});
|
|
180
|
+
rl.question('', () => {
|
|
181
|
+
rl.close();
|
|
182
|
+
resolve();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
await manageRemoteRequirements(computerId);
|
|
186
|
+
break;
|
|
187
|
+
|
|
188
|
+
case 'add':
|
|
189
|
+
const { requirement } = await inquirer.prompt([
|
|
190
|
+
{
|
|
191
|
+
type: 'input',
|
|
192
|
+
name: 'requirement',
|
|
193
|
+
message: 'Enter requirement title:'
|
|
194
|
+
}
|
|
195
|
+
]);
|
|
196
|
+
|
|
197
|
+
if (requirement) {
|
|
198
|
+
await addRemoteRequirement(computerId, requirement);
|
|
199
|
+
console.log(chalk.gray('Press Enter to continue...'));
|
|
200
|
+
await new Promise(resolve => {
|
|
201
|
+
const readline = require('readline');
|
|
202
|
+
const rl = readline.createInterface({
|
|
203
|
+
input: process.stdin,
|
|
204
|
+
output: process.stdout
|
|
205
|
+
});
|
|
206
|
+
rl.question('', () => {
|
|
207
|
+
rl.close();
|
|
208
|
+
resolve();
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
await manageRemoteRequirements(computerId);
|
|
213
|
+
break;
|
|
214
|
+
|
|
215
|
+
case 'edit':
|
|
216
|
+
const repoPath = process.cwd();
|
|
217
|
+
const reqFilename = `REQUIREMENTS-${computerId}.md`;
|
|
218
|
+
const reqPath = path.join(repoPath, '.vibecodingmachine', reqFilename);
|
|
219
|
+
|
|
220
|
+
if (await fs.pathExists(reqPath)) {
|
|
221
|
+
const { spawn } = require('child_process');
|
|
222
|
+
const editor = process.env.EDITOR || 'nano';
|
|
223
|
+
|
|
224
|
+
console.log(chalk.cyan(`\nOpening ${reqFilename} in ${editor}...\n`));
|
|
225
|
+
|
|
226
|
+
const child = spawn(editor, [reqPath], {
|
|
227
|
+
stdio: 'inherit'
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
await new Promise((resolve) => {
|
|
231
|
+
child.on('exit', resolve);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
console.log(chalk.green('\n✓ File saved\n'));
|
|
235
|
+
} else {
|
|
236
|
+
console.log(chalk.yellow(`\n⚠ Requirements file not found\n`));
|
|
237
|
+
}
|
|
238
|
+
await manageRemoteRequirements(computerId);
|
|
239
|
+
break;
|
|
240
|
+
|
|
241
|
+
case 'back':
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Parse requirements from markdown content
|
|
248
|
+
*/
|
|
249
|
+
function parseRequirements(content) {
|
|
250
|
+
const sections = {
|
|
251
|
+
todo: [],
|
|
252
|
+
toVerify: [],
|
|
253
|
+
verified: []
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const lines = content.split('\n');
|
|
257
|
+
let currentSection = null;
|
|
258
|
+
let currentRequirement = null;
|
|
259
|
+
|
|
260
|
+
for (let i = 0; i < lines.length; i++) {
|
|
261
|
+
const line = lines[i];
|
|
262
|
+
|
|
263
|
+
// Detect sections
|
|
264
|
+
if (line.includes('## ⏳ Requirements not yet completed')) {
|
|
265
|
+
currentSection = 'todo';
|
|
266
|
+
continue;
|
|
267
|
+
} else if (line.includes('## 🔍 TO VERIFY')) {
|
|
268
|
+
currentSection = 'toVerify';
|
|
269
|
+
continue;
|
|
270
|
+
} else if (line.includes('## 📝 VERIFIED')) {
|
|
271
|
+
currentSection = 'verified';
|
|
272
|
+
continue;
|
|
273
|
+
} else if (line.startsWith('## ') && !line.startsWith('###')) {
|
|
274
|
+
currentSection = null;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Parse requirements
|
|
279
|
+
if (currentSection && line.startsWith('### ')) {
|
|
280
|
+
if (currentRequirement) {
|
|
281
|
+
sections[currentSection].push(currentRequirement);
|
|
282
|
+
}
|
|
283
|
+
currentRequirement = {
|
|
284
|
+
title: line.replace(/^###\s*/, '').trim(),
|
|
285
|
+
description: ''
|
|
286
|
+
};
|
|
287
|
+
} else if (currentRequirement && line.trim() && !line.startsWith('#')) {
|
|
288
|
+
currentRequirement.description += line + '\n';
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Add last requirement
|
|
293
|
+
if (currentRequirement && currentSection) {
|
|
294
|
+
sections[currentSection].push(currentRequirement);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return sections;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
module.exports = {
|
|
301
|
+
listRemoteRequirements,
|
|
302
|
+
addRemoteRequirement,
|
|
303
|
+
manageRemoteRequirements
|
|
304
|
+
};
|