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.
@@ -2,16 +2,98 @@ const path = require('path');
2
2
  const fs = require('fs-extra');
3
3
  const chalk = require('chalk');
4
4
  const chokidar = require('chokidar');
5
- const { getRepoPath } = require('../utils/config');
5
+ const { getRepoPath, setRepoPath } = require('../utils/config');
6
6
  const { getRequirementsPath } = require('vibecodingmachine-core');
7
7
 
8
8
  async function getReqPathOrExit() {
9
- const repoPath = await getRepoPath();
9
+ let repoPath = await getRepoPath();
10
+
11
+ // Auto-detect and initialize if we're in a git repository
12
+ if (!repoPath) {
13
+ const cwd = process.cwd();
14
+ const cwdResolved = path.resolve(cwd);
15
+
16
+ // Find the git root by walking up the directory tree
17
+ let currentDir = cwdResolved;
18
+ let foundGitRoot = false;
19
+ const maxDepth = 50; // Prevent infinite loops
20
+ let depth = 0;
21
+
22
+ // Walk up the directory tree looking for .git directory
23
+ while (depth < maxDepth && currentDir !== path.dirname(currentDir)) {
24
+ const gitPath = path.join(currentDir, '.git');
25
+ try {
26
+ if (await fs.pathExists(gitPath)) {
27
+ repoPath = currentDir;
28
+ foundGitRoot = true;
29
+ break;
30
+ }
31
+ } catch (err) {
32
+ // If we can't check, continue to parent
33
+ }
34
+ const parentDir = path.dirname(currentDir);
35
+ if (parentDir === currentDir) {
36
+ // Reached filesystem root
37
+ break;
38
+ }
39
+ currentDir = parentDir;
40
+ depth++;
41
+ }
42
+
43
+ // Fallback: if no .git found but .vibecodingmachine exists, use current dir
44
+ if (!foundGitRoot) {
45
+ try {
46
+ const vibecodingmachineDir = path.join(cwdResolved, '.vibecodingmachine');
47
+ if (await fs.pathExists(vibecodingmachineDir)) {
48
+ repoPath = cwdResolved;
49
+ foundGitRoot = true;
50
+ }
51
+ } catch (err) {
52
+ // Ignore errors
53
+ }
54
+ }
55
+
56
+ // Final fallback: use current directory if we have package.json
57
+ if (!foundGitRoot) {
58
+ try {
59
+ const packageJsonPath = path.join(cwdResolved, 'package.json');
60
+ if (await fs.pathExists(packageJsonPath)) {
61
+ repoPath = cwdResolved;
62
+ foundGitRoot = true;
63
+ }
64
+ } catch (err) {
65
+ // Ignore errors
66
+ }
67
+ }
68
+
69
+ // If we found a repo path, save it
70
+ if (repoPath) {
71
+ try {
72
+ await setRepoPath(repoPath);
73
+ } catch (err) {
74
+ // If setting fails, we still have repoPath set, so continue
75
+ }
76
+ }
77
+ }
78
+
79
+ // Final validation - ensure we have a valid path
80
+ // If we still don't have a repoPath, use current directory as absolute last resort
81
+ if (!repoPath) {
82
+ repoPath = path.resolve(process.cwd());
83
+ try {
84
+ await setRepoPath(repoPath);
85
+ } catch (err) {
86
+ // Even if we can't save it, we'll use it for this session
87
+ }
88
+ }
89
+
90
+ // At this point, repoPath MUST be set (we've tried everything including current directory)
10
91
  if (!repoPath) {
11
92
  console.log(chalk.yellow('No repository path configured'));
12
93
  console.log(chalk.gray('Use'), chalk.cyan('vcm repo:init'), chalk.gray('or'), chalk.cyan('vcm repo:set <path>'));
13
94
  process.exit(1);
14
95
  }
96
+
15
97
  // Use getRequirementsPath which handles hostname-specific files
16
98
  const reqPath = await getRequirementsPath(repoPath);
17
99
  return { repoPath, reqPath };
@@ -40,7 +122,7 @@ async function list(options) {
40
122
  }
41
123
  }
42
124
 
43
- async function add(name) {
125
+ async function add(name, pkg, description) {
44
126
  try {
45
127
  const { reqPath } = await getReqPathOrExit();
46
128
  await fs.ensureFile(reqPath);
@@ -48,7 +130,7 @@ async function add(name) {
48
130
 
49
131
  // Find the TODO section
50
132
  const todoSectionHeader = '## ⏳ Requirements not yet completed';
51
- if (!content.includes(todoSectionHeader)) {
133
+ if (!content.includes('Requirements not yet completed')) {
52
134
  content += '\n\n' + todoSectionHeader + '\n';
53
135
  }
54
136
 
@@ -60,10 +142,38 @@ async function add(name) {
60
142
  for (let i = 0; i < lines.length; i++) {
61
143
  newLines.push(lines[i]);
62
144
 
63
- // Insert right after the TODO section header (new header format)
64
- if (!inserted && lines[i].includes(todoSectionHeader)) {
145
+ // Insert right after the TODO section header
146
+ if (!inserted && lines[i].startsWith('##') && lines[i].includes('Requirements not yet completed')) {
65
147
  // Add requirement header
66
148
  newLines.push(`### ${name}`);
149
+
150
+ // Add package if provided (and not 'all')
151
+ if (pkg && Array.isArray(pkg) && pkg.length > 0) {
152
+ const packageValue = pkg.length === 1 ? pkg[0] : pkg.join(', ');
153
+ if (packageValue !== 'all') {
154
+ newLines.push(`PACKAGE: ${packageValue}`);
155
+ }
156
+ } else if (pkg && typeof pkg === 'string' && pkg !== 'all') {
157
+ newLines.push(`PACKAGE: ${pkg}`);
158
+ }
159
+
160
+ // Add description if provided
161
+ if (description && typeof description === 'string' && description.trim()) {
162
+ const descLines = description.split('\n');
163
+ descLines.forEach(line => {
164
+ if (line.trim()) {
165
+ newLines.push(line);
166
+ }
167
+ });
168
+ } else if (description && Array.isArray(description)) {
169
+ // Handle array of description lines
170
+ description.forEach(line => {
171
+ if (line && typeof line === 'string' && line.trim()) {
172
+ newLines.push(line);
173
+ }
174
+ });
175
+ }
176
+
67
177
  // Add blank line after requirement
68
178
  newLines.push('');
69
179
  inserted = true;
@@ -120,15 +230,10 @@ async function next() {
120
230
  async function edit() {
121
231
  try {
122
232
  const { reqPath } = await getReqPathOrExit();
123
- const opener = process.env.EDITOR ? null : 'open';
124
- if (process.env.EDITOR) {
125
- const { spawn } = require('child_process');
126
- const child = spawn(process.env.EDITOR, [reqPath], { stdio: 'inherit' });
127
- child.on('exit', (code) => process.exit(code || 0));
128
- } else {
129
- const { execSync } = require('child_process');
130
- execSync(`${opener} ${JSON.stringify(reqPath)}`);
131
- }
233
+ // Always use system default opener to avoid taking over the terminal
234
+ const { execSync } = require('child_process');
235
+ // Use 'open' for macOS (user's OS), could be expanded for others if needed
236
+ execSync(`open ${JSON.stringify(reqPath)}`);
132
237
  } catch (error) {
133
238
  console.error(chalk.red('Error opening file:'), error.message);
134
239
  process.exit(1);
@@ -148,13 +253,125 @@ async function watch() {
148
253
  }
149
254
  }
150
255
 
256
+ async function rename(oldTitle, newTitle, description) {
257
+ try {
258
+ const { reqPath } = await getReqPathOrExit();
259
+ if (!await fs.pathExists(reqPath)) {
260
+ console.log(chalk.yellow('No REQUIREMENTS.md found.'));
261
+ return;
262
+ }
263
+
264
+ // Read the requirements file
265
+ const content = await fs.readFile(reqPath, 'utf8');
266
+ const lines = content.split('\n');
267
+
268
+ // Find the requirement block
269
+ let requirementStartIndex = -1;
270
+ let requirementEndIndex = -1;
271
+
272
+ for (let i = 0; i < lines.length; i++) {
273
+ const line = lines[i].trim();
274
+ if (line.startsWith('###')) {
275
+ const title = line.replace(/^###\s*/, '').trim();
276
+ if (title === oldTitle) {
277
+ requirementStartIndex = i;
278
+ // Find the end of this requirement (next ### or ## header)
279
+ for (let j = i + 1; j < lines.length; j++) {
280
+ const nextLine = lines[j].trim();
281
+ if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
282
+ requirementEndIndex = j;
283
+ break;
284
+ }
285
+ }
286
+ if (requirementEndIndex === -1) {
287
+ requirementEndIndex = lines.length;
288
+ }
289
+ break;
290
+ }
291
+ }
292
+ }
293
+
294
+ if (requirementStartIndex === -1) {
295
+ console.log(chalk.yellow(`⚠️ Could not find requirement: "${oldTitle}"`));
296
+ return;
297
+ }
298
+
299
+ // Build new requirement block
300
+ const newBlock = [`### ${newTitle}`];
301
+
302
+ // If description provided, use it
303
+ if (description) {
304
+ if (Array.isArray(description)) {
305
+ description.forEach(line => {
306
+ if (line && line.trim()) {
307
+ newBlock.push(line);
308
+ }
309
+ });
310
+ } else if (typeof description === 'string') {
311
+ description.split('\n').forEach(line => {
312
+ if (line.trim()) {
313
+ newBlock.push(line);
314
+ }
315
+ });
316
+ }
317
+ } else {
318
+ // Keep existing details (skip the ### header line)
319
+ for (let i = requirementStartIndex + 1; i < requirementEndIndex; i++) {
320
+ const line = lines[i];
321
+ if (line.trim()) {
322
+ newBlock.push(line);
323
+ }
324
+ }
325
+ }
326
+
327
+ newBlock.push(''); // Blank line after requirement
328
+
329
+ // Replace the old block with the new one
330
+ lines.splice(requirementStartIndex, requirementEndIndex - requirementStartIndex, ...newBlock);
331
+
332
+ // Save
333
+ await fs.writeFile(reqPath, lines.join('\n'));
334
+ console.log(chalk.green('✓ Requirement renamed/updated'));
335
+ } catch (error) {
336
+ console.error(chalk.red('Error renaming requirement:'), error.message);
337
+ process.exit(1);
338
+ }
339
+ }
340
+
341
+ async function working() {
342
+ try {
343
+ const { reqPath } = await getReqPathOrExit();
344
+ if (!await fs.pathExists(reqPath)) {
345
+ console.log(chalk.yellow('No REQUIREMENTS.md found.'));
346
+ return;
347
+ }
348
+ const { getCurrentRequirement } = require('vibecodingmachine-core');
349
+ // Note: Core's getCurrentRequirement returns "Working on: X" or default text.
350
+ // It assumes process.cwd() or similar.
351
+ // But wait, Core's getCurrentRequirement takes 'repoPath'.
352
+ // And imported from core index.cjs might need repoPath.
353
+
354
+ // Let's manually implement it to be safe and use getReqPathOrExit's resolved path
355
+ // OR use the core helper but pass the correct repo path.
356
+ const { repoPath } = await getReqPathOrExit();
357
+ const coreResult = await getCurrentRequirement(repoPath);
358
+ console.log(chalk.cyan(coreResult));
359
+
360
+ } catch (error) {
361
+ console.error(chalk.red('Error getting working requirement:'), error.message);
362
+ process.exit(1);
363
+ }
364
+ }
365
+
151
366
  module.exports = {
152
367
  list,
153
368
  add,
154
369
  current,
370
+ working,
155
371
  next,
156
372
  edit,
157
- watch
373
+ watch,
374
+ rename
158
375
  };
159
376
 
160
377
 
@@ -64,7 +64,6 @@ async function progress() {
64
64
 
65
65
  async function logs(cmd) {
66
66
  const lines = parseInt((cmd && cmd.lines) || '50', 10);
67
- const repoPath = await getRepoPath();
68
67
  const defaultLog = path.join(process.cwd(), 'logs', 'security', 'security-2025-10-29.log');
69
68
  const logPath = await fs.pathExists(defaultLog) ? defaultLog : null;
70
69
  console.log(chalk.bold('\nRecent Logs'));
@@ -0,0 +1,280 @@
1
+ const chalk = require('chalk');
2
+ const Table = require('cli-table3');
3
+ const SyncEngine = require('vibecodingmachine-core/src/sync/sync-engine');
4
+
5
+ /**
6
+ * Trigger immediate sync
7
+ */
8
+ async function syncNow() {
9
+ const syncEngine = new SyncEngine();
10
+
11
+ try {
12
+ console.log(chalk.cyan('\n🔄 Starting sync...\n'));
13
+
14
+ await syncEngine.initialize();
15
+
16
+ // Listen for sync completion
17
+ const syncPromise = new Promise((resolve, reject) => {
18
+ syncEngine.once('sync-complete', (result) => {
19
+ if (result.status === 'error') {
20
+ reject(new Error(result.error || 'Sync failed'));
21
+ } else {
22
+ resolve(result);
23
+ }
24
+ });
25
+ });
26
+
27
+ // Start sync
28
+ await syncEngine.sync();
29
+
30
+ // Wait for completion
31
+ const result = await syncPromise;
32
+
33
+ const status = syncEngine.getStatus();
34
+
35
+ console.log(chalk.green('✓ Sync complete!\n'));
36
+ console.log(chalk.white('Last Sync: ') + new Date(status.lastSyncTime).toLocaleString());
37
+ console.log(chalk.white('Remote Changes: ') + (result.remoteChanges || 0));
38
+ console.log(chalk.white('Local Changes: ') + (result.localChanges || 0));
39
+ console.log(chalk.white('Conflicts: ') + (result.conflicts || 0));
40
+ console.log(chalk.white('Queued Changes: ') + status.queuedChanges);
41
+ console.log('');
42
+
43
+ } catch (error) {
44
+ console.error(chalk.red('\n✗ Sync failed:'), error.message);
45
+ console.log(chalk.gray('\nTip: Check AWS credentials and DynamoDB table configuration.\n'));
46
+ // Don't throw - just log the error
47
+ } finally {
48
+ syncEngine.stop();
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Show sync status and statistics
54
+ */
55
+ async function syncStatus() {
56
+ const syncEngine = new SyncEngine();
57
+
58
+ try {
59
+ await syncEngine.initialize();
60
+
61
+ const status = syncEngine.getStatus();
62
+
63
+ console.log('\n' + chalk.bold.cyan('Sync Status'));
64
+ console.log(chalk.gray('─'.repeat(60)));
65
+
66
+ // Connection status
67
+ const onlineIcon = status.isOnline ? chalk.green('●') : chalk.red('●');
68
+ const onlineText = status.isOnline ? 'Online' : 'Offline';
69
+ console.log(chalk.white('Connection: ') + onlineIcon + ' ' + onlineText);
70
+
71
+ // Sync status
72
+ const syncingIcon = status.isSyncing ? chalk.yellow('⟳') : chalk.gray('○');
73
+ const syncingText = status.isSyncing ? 'Syncing...' : 'Idle';
74
+ console.log(chalk.white('Status: ') + syncingIcon + ' ' + syncingText);
75
+
76
+ // Last sync
77
+ const lastSync = status.lastSyncTime
78
+ ? new Date(status.lastSyncTime).toLocaleString()
79
+ : 'Never';
80
+ console.log(chalk.white('Last Sync: ') + lastSync);
81
+
82
+ // Queued changes
83
+ const queueColor = status.queuedChanges > 0 ? chalk.yellow : chalk.gray;
84
+ console.log(chalk.white('Queued Changes: ') + queueColor(status.queuedChanges));
85
+
86
+ // Conflict strategy
87
+ console.log(chalk.white('Conflict Mode: ') + status.conflictStrategy);
88
+
89
+ // Computer ID
90
+ console.log(chalk.white('Computer ID: ') + status.computerId);
91
+
92
+ console.log('');
93
+
94
+ // Show recent sync events
95
+ const history = syncEngine.getHistory(5);
96
+ if (history.length > 0) {
97
+ console.log(chalk.bold.cyan('Recent Sync Events'));
98
+ console.log(chalk.gray('─'.repeat(60)));
99
+
100
+ for (const event of history.reverse()) {
101
+ const time = new Date(event.timestamp).toLocaleTimeString();
102
+ const icon = event.type === 'conflict-resolution' ? chalk.yellow('⚠') : chalk.gray('•');
103
+ console.log(`${icon} ${chalk.gray(time)} ${event.type}`);
104
+ }
105
+
106
+ console.log('');
107
+ }
108
+
109
+ } catch (error) {
110
+ console.error(chalk.red('\n✗ Failed to get sync status:'), error.message);
111
+ throw error;
112
+ } finally {
113
+ syncEngine.stop();
114
+ }
115
+ }
116
+
117
+ /**
118
+ * View pending changes in offline queue
119
+ */
120
+ async function viewQueue() {
121
+ const syncEngine = new SyncEngine();
122
+
123
+ try {
124
+ await syncEngine.initialize();
125
+
126
+ const status = syncEngine.getStatus();
127
+
128
+ if (status.queuedChanges === 0) {
129
+ console.log(chalk.gray('\nNo pending changes in queue.\n'));
130
+ return;
131
+ }
132
+
133
+ console.log(chalk.cyan(`\n📋 Offline Queue (${status.queuedChanges} changes)\n`));
134
+
135
+ // Create table
136
+ const table = new Table({
137
+ head: [
138
+ chalk.cyan('#'),
139
+ chalk.cyan('Type'),
140
+ chalk.cyan('Requirement'),
141
+ chalk.cyan('Timestamp')
142
+ ],
143
+ colWidths: [5, 15, 40, 20]
144
+ });
145
+
146
+ // Add rows (accessing internal queue - in production, add a public getter)
147
+ syncEngine.offlineQueue.forEach((change, index) => {
148
+ table.push([
149
+ index + 1,
150
+ change.type || 'update',
151
+ truncate(change.requirementName || change.requirementId || '-', 38),
152
+ new Date(change.timestamp).toLocaleString()
153
+ ]);
154
+ });
155
+
156
+ console.log(table.toString() + '\n');
157
+
158
+ console.log(chalk.gray('These changes will be synced when connection is restored.\n'));
159
+ console.log(chalk.white('Commands:'));
160
+ console.log(chalk.gray(' vcm sync:force ') + '- Force sync now');
161
+ console.log(chalk.gray(' vcm sync:now ') + '- Sync when online');
162
+ console.log('');
163
+
164
+ } catch (error) {
165
+ console.error(chalk.red('\n✗ Failed to view queue:'), error.message);
166
+ throw error;
167
+ } finally {
168
+ syncEngine.stop();
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Force sync even if offline
174
+ */
175
+ async function forceSync() {
176
+ const syncEngine = new SyncEngine();
177
+
178
+ try {
179
+ console.log(chalk.yellow('\n⚠ Forcing sync (ignoring offline status)...\n'));
180
+
181
+ await syncEngine.initialize();
182
+
183
+ // Temporarily set online
184
+ const wasOnline = syncEngine.isOnline;
185
+ syncEngine.setOnlineMode(true);
186
+
187
+ try {
188
+ await syncEngine.sync();
189
+ console.log(chalk.green('✓ Force sync complete!\n'));
190
+ } finally {
191
+ // Restore original online status
192
+ syncEngine.setOnlineMode(wasOnline);
193
+ }
194
+
195
+ } catch (error) {
196
+ console.error(chalk.red('\n✗ Force sync failed:'), error.message);
197
+ console.log(chalk.gray('\nThis usually means the server is unreachable.\n'));
198
+ throw error;
199
+ } finally {
200
+ syncEngine.stop();
201
+ }
202
+ }
203
+
204
+ /**
205
+ * View sync history
206
+ */
207
+ async function viewHistory(options = {}) {
208
+ const syncEngine = new SyncEngine();
209
+
210
+ try {
211
+ await syncEngine.initialize();
212
+
213
+ const limit = parseInt(options.limit) || 50;
214
+ const history = syncEngine.getHistory(limit);
215
+
216
+ if (history.length === 0) {
217
+ console.log(chalk.gray('\nNo sync history available.\n'));
218
+ return;
219
+ }
220
+
221
+ console.log(chalk.cyan(`\n📜 Sync History (last ${history.length} events)\n`));
222
+
223
+ // Create table
224
+ const table = new Table({
225
+ head: [
226
+ chalk.cyan('Time'),
227
+ chalk.cyan('Type'),
228
+ chalk.cyan('Details')
229
+ ],
230
+ colWidths: [20, 20, 50]
231
+ });
232
+
233
+ // Add rows
234
+ for (const event of history.reverse()) {
235
+ const time = new Date(event.timestamp).toLocaleString();
236
+ const type = event.type;
237
+ const details = getEventDetails(event);
238
+
239
+ table.push([time, type, details]);
240
+ }
241
+
242
+ console.log(table.toString() + '\n');
243
+
244
+ } catch (error) {
245
+ console.error(chalk.red('\n✗ Failed to view history:'), error.message);
246
+ throw error;
247
+ } finally {
248
+ syncEngine.stop();
249
+ }
250
+ }
251
+
252
+ // Helper functions
253
+
254
+ function truncate(str, maxLength) {
255
+ if (str.length <= maxLength) return str;
256
+ return str.substring(0, maxLength - 3) + '...';
257
+ }
258
+
259
+ function getEventDetails(event) {
260
+ switch (event.type) {
261
+ case 'conflict-resolution':
262
+ return `Resolved: ${event.resolution?.requirementId || 'unknown'}`;
263
+ case 'sync-complete':
264
+ return `Remote: ${event.remoteChanges || 0}, Local: ${event.localChanges || 0}`;
265
+ case 'remote-change-applied':
266
+ return `Applied: ${event.change?.requirementId || 'unknown'}`;
267
+ case 'local-change-pushed':
268
+ return `Pushed: ${event.change?.requirementId || 'unknown'}`;
269
+ default:
270
+ return JSON.stringify(event).substring(0, 47);
271
+ }
272
+ }
273
+
274
+ module.exports = {
275
+ syncNow,
276
+ syncStatus,
277
+ viewQueue,
278
+ forceSync,
279
+ viewHistory
280
+ };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Centralized agent selection logic
3
+ * Respects provider preferences order and ensures DRY principle
4
+ */
5
+
6
+ const { getProviderPreferences } = require('./provider-registry');
7
+
8
+ /**
9
+ * Get the effective agent based on provider preferences and options
10
+ * @param {Object} options - Command line options (may include ide)
11
+ * @param {Array} providerDefinitions - Array of provider definitions
12
+ * @param {Map} providerDefinitionMap - Map of provider definitions by ID
13
+ * @returns {Object} - { effectiveAgent: string, providerDef: Object }
14
+ */
15
+ async function getEffectiveAgent(options = {}, providerDefinitions, providerDefinitionMap) {
16
+ const prefs = await getProviderPreferences();
17
+
18
+ // Get all available providers in the order specified in preferences
19
+ const availableProviders = [];
20
+ for (const id of prefs.order) {
21
+ if (prefs.enabled[id] !== false && providerDefinitionMap.has(id)) {
22
+ availableProviders.push(id);
23
+ }
24
+ }
25
+
26
+ // If no providers are available, use the first one from definitions as fallback
27
+ if (availableProviders.length === 0) {
28
+ availableProviders.push(providerDefinitions[0]?.id || 'claude-code');
29
+ }
30
+
31
+ // Use the first available provider by default, unless overridden by options
32
+ let effectiveAgent = options.ide || availableProviders[0];
33
+
34
+ // If the requested agent isn't available, use the first available one
35
+ if (!availableProviders.includes(effectiveAgent)) {
36
+ effectiveAgent = availableProviders[0];
37
+ }
38
+
39
+ const providerDef = providerDefinitionMap.get(effectiveAgent);
40
+
41
+ return {
42
+ effectiveAgent,
43
+ providerDef,
44
+ availableProviders
45
+ };
46
+ }
47
+
48
+ module.exports = {
49
+ getEffectiveAgent
50
+ };