vibecodingmachine-cli 2026.3.10-1807 → 2026.3.14-1528

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.
Files changed (36) hide show
  1. package/README.md +85 -85
  2. package/bin/vibecodingmachine.js +3 -0
  3. package/package.json +4 -3
  4. package/scripts/postinstall.js +161 -161
  5. package/src/commands/auth.js +100 -100
  6. package/src/commands/auto-direct.js +16 -5
  7. package/src/commands/auto-execution.js +25 -0
  8. package/src/commands/auto-requirement-management.js +8 -8
  9. package/src/commands/auto-status-helpers.js +5 -3
  10. package/src/commands/computers.js +318 -318
  11. package/src/commands/feature.js +123 -123
  12. package/src/commands/locale.js +72 -72
  13. package/src/commands/repo.js +163 -163
  14. package/src/commands/setup.js +93 -93
  15. package/src/commands/sync.js +287 -287
  16. package/src/index.js +5 -5
  17. package/src/utils/agent-selector.js +50 -50
  18. package/src/utils/asset-cleanup.js +60 -60
  19. package/src/utils/auto-mode-ansi-ui.js +237 -237
  20. package/src/utils/auto-mode-simple-ui.js +141 -141
  21. package/src/utils/copy-with-progress.js +167 -167
  22. package/src/utils/download-with-progress.js +84 -84
  23. package/src/utils/keyboard-handler.js +153 -153
  24. package/src/utils/kiro-installer.js +178 -178
  25. package/src/utils/logger.js +4 -4
  26. package/src/utils/persistent-header.js +114 -114
  27. package/src/utils/prompt-helper.js +63 -63
  28. package/src/utils/provider-checker/agent-checker.js +25 -1
  29. package/src/utils/provider-checker/agent-runner.js +115 -37
  30. package/src/utils/provider-checker/agents-manager.js +210 -0
  31. package/src/utils/provider-checker/provider-validator.js +5 -49
  32. package/src/utils/provider-checker/requirements-manager.js +86 -65
  33. package/src/utils/provider-checker/test-requirements.js +25 -17
  34. package/src/utils/status-card.js +121 -121
  35. package/src/utils/stdout-interceptor.js +127 -127
  36. package/src/utils/user-tracking.js +299 -299
@@ -4,7 +4,7 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
 
6
6
  const { CLI_AUTO_INSTALL, isCLIAvailable, installCLI } = require('./cli-installer');
7
- const { getResultFilePath, addTestRequirement, removeTestRequirement } = require('./requirements-manager');
7
+ const { getResultFilePath } = require('./agents-manager');
8
8
  const { runAgentCheck } = require('./agent-runner');
9
9
 
10
10
  /**
@@ -28,21 +28,11 @@ async function checkProvider(providerId, config = {}, repoPath, onProgress = nul
28
28
  return { status: 'error', message: `Unknown provider: ${providerId}`, checkedAt: new Date().toISOString(), requirementLeftPending: false };
29
29
  }
30
30
 
31
- // No platform restrictions needed anymore - all IDEs have cross-platform support
32
-
33
31
  if (!repoPath) {
34
32
  console.error(`[AGENT CHECK] No repository path available`);
35
33
  return { status: 'error', message: 'No repository path available', checkedAt: new Date().toISOString(), requirementLeftPending: false };
36
34
  }
37
35
 
38
- const { getRequirementsPath } = require('vibecodingmachine-core');
39
- let reqPath;
40
- try {
41
- reqPath = await getRequirementsPath(repoPath);
42
- } catch {
43
- reqPath = path.join(repoPath, '.vibecodingmachine', 'REQUIREMENTS.md');
44
- }
45
-
46
36
  // Ensure .vibecodingmachine directory exists
47
37
  const vibeDir = path.join(repoPath, '.vibecodingmachine');
48
38
  const tempDir = path.join(vibeDir, 'temp');
@@ -55,26 +45,6 @@ async function checkProvider(providerId, config = {}, repoPath, onProgress = nul
55
45
  return { status: 'error', message: `Could not create .vibecodingmachine: ${err.message}`, checkedAt: new Date().toISOString(), requirementLeftPending: false };
56
46
  }
57
47
 
58
- // Ensure REQUIREMENTS.md exists with proper structure
59
- try {
60
- if (!fs.existsSync(reqPath)) {
61
- const initialRequirements = `# Requirements for this project
62
-
63
- ## ⏳ Requirements not yet completed
64
-
65
- ## ✅ Verified by AI
66
-
67
- `;
68
- fs.writeFileSync(reqPath, initialRequirements, 'utf8');
69
- console.log(`[AGENT CHECK] Created initial REQUIREMENTS.md`);
70
- } else {
71
- console.log(`[AGENT CHECK] REQUIREMENTS.md already exists`);
72
- }
73
- } catch (err) {
74
- console.error(`[AGENT CHECK] Failed to ensure REQUIREMENTS.md: ${err.message}`);
75
- return { status: 'error', message: `Could not create REQUIREMENTS.md: ${err.message}`, checkedAt: new Date().toISOString(), requirementLeftPending: false };
76
- }
77
-
78
48
  const resultFile = getResultFilePath(repoPath);
79
49
 
80
50
  // Special handling: VS Code Copilot CLI is often installed but not authenticated.
@@ -235,11 +205,6 @@ async function checkProvider(providerId, config = {}, repoPath, onProgress = nul
235
205
  }
236
206
  }
237
207
 
238
- // Add test requirement (idempotent — safe to call even if already present from a prior failed agent)
239
- try { addTestRequirement(reqPath, resultFile); } catch (err) {
240
- return { status: 'error', message: `Could not write to REQUIREMENTS.md: ${err.message}`, checkedAt: new Date().toISOString(), requirementLeftPending: false };
241
- }
242
-
243
208
  // IDE providers generally don't need CLI system configuration to work
244
209
  // They can communicate directly with the IDE without requiring VCM CLI setup
245
210
 
@@ -248,20 +213,17 @@ async function checkProvider(providerId, config = {}, repoPath, onProgress = nul
248
213
  const result = await runAgentCheck(providerId, def, repoPath, resultFile, timeoutMs, signal, onProgress);
249
214
 
250
215
  if (result.status === 'success') {
251
- // Agent completed — requirement has been moved to Verified; remove it and the result file
252
- try { removeTestRequirement(reqPath); } catch { }
216
+ // Agent completed — clean up result file
253
217
  try { fs.unlinkSync(resultFile); } catch { }
254
218
  return { ...result, requirementLeftPending: false };
255
219
  }
256
220
 
257
- // Agent failed — leave the requirement in pending for the next agent
258
- return { ...result, requirementLeftPending: true };
221
+ // Agent failed
222
+ return { ...result, requirementLeftPending: false };
259
223
  }
260
224
 
261
225
  /**
262
226
  * Check all providers sequentially.
263
- * The test requirement is shared: it stays pending between agents until one
264
- * succeeds, then it's removed. If all fail, it's removed at the end.
265
227
  *
266
228
  * Returns { [providerId]: checkResult }
267
229
  */
@@ -275,14 +237,8 @@ async function checkAllProviders(providerIds, config = {}, repoPath, onProgress
275
237
  if (onProgress) onProgress(id, 'done', results[id].status === 'error' ? results[id].message : null);
276
238
  }
277
239
 
278
- // Final cleanup — remove requirement if still pending (all agents failed or skipped)
240
+ // Final cleanup — remove result file if it exists
279
241
  if (repoPath) {
280
- const { getRequirementsPath } = require('vibecodingmachine-core');
281
- let reqPath;
282
- try { reqPath = await getRequirementsPath(repoPath); } catch {
283
- reqPath = path.join(repoPath, '.vibecodingmachine', 'REQUIREMENTS.md');
284
- }
285
- try { removeTestRequirement(reqPath); } catch { }
286
242
  try { fs.unlinkSync(getResultFilePath(repoPath)); } catch { }
287
243
  }
288
244
 
@@ -3,10 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
 
6
- const TEST_REQ_TITLE = 'VCM agent connectivity check';
7
- const PENDING_HEADER = '## ⏳ Requirements not yet completed';
8
- const IN_PROGRESS_HEADER = '## 🔨 Current In Progress Requirement';
9
- const VERIFIED_HEADER = '## ✅ Verified by AI';
6
+ const TEST_REQ_TITLE = '✅ ACTION REQUIRED - VCM Connectivity Test';
10
7
 
11
8
  /**
12
9
  * Return the absolute path to the result file used by the check.
@@ -16,94 +13,118 @@ function getResultFilePath(repoPath) {
16
13
  }
17
14
 
18
15
  /**
19
- * Add the test requirement as the FIRST item in the pending section.
20
- * The first item in the "Requirements not yet completed" section is the current in-progress item.
21
- * Idempotent — if it's already there, do nothing.
16
+ * Add test requirement to requirements file
22
17
  */
23
- function addTestRequirement(reqPath, resultFile) {
24
- let content = '';
25
- try { content = fs.readFileSync(reqPath, 'utf8'); } catch {}
18
+ function addTestRequirement(reqPath, resultFilePath) {
19
+ if (!fs.existsSync(reqPath)) {
20
+ // Create the file with basic structure if it doesn't exist
21
+ const basicContent = `## Requirements
26
22
 
27
- // Already present?
28
- if (content.includes(TEST_REQ_TITLE)) return;
23
+ `;
24
+ fs.writeFileSync(reqPath, basicContent, 'utf8');
25
+ }
29
26
 
30
- const testBlock = `- ${TEST_REQ_TITLE}
31
- 🚦 Current Status: [PREPARE]
27
+ const content = fs.readFileSync(reqPath, 'utf8');
28
+
29
+ // Check if test requirement already exists
30
+ if (content.includes(TEST_REQ_TITLE)) {
31
+ return;
32
+ }
32
33
 
33
- **AUTOMATED CONNECTIVITY TEST - Please read and acknowledge:**
34
+ const testRequirement = `- ${TEST_REQ_TITLE}
35
+ 🚦 Status: [PREPARE]
34
36
 
35
- This is a simple test to verify VCM can communicate with your IDE.
37
+ **✅ ACTION REQUIRED - VCM Connectivity Test:**
36
38
 
37
- **To complete this test, update the status above to [DONE]:**
38
- - Change \`🚦 Current Status: [PREPARE]\` to \`🚦 Current Status: [DONE]\`
39
+ VCM is testing if it can communicate with your IDE. To complete this test:
39
40
 
40
- That's it! VCM will detect the status change and mark the connectivity test as successful.
41
+ **Change the status above from [PREPARE] to [DONE] and save the file.**
41
42
 
42
- **Alternative (if you want to create a file):**
43
- 1. Create file: \`${resultFile}\`
44
- 2. Write: \`VCM_CHECK_OK\`
45
- 3. Save
43
+ Example:
44
+ Before: 🚦 Status: [PREPARE]
45
+ After: 🚦 Status: [DONE]
46
46
 
47
- Either method works - status update is simplest.
47
+ That's it! VCM will detect the status change within seconds.
48
48
 
49
49
  `;
50
50
 
51
- const idx = content.indexOf(PENDING_HEADER);
52
- if (idx !== -1) {
53
- const afterHeader = idx + PENDING_HEADER.length;
54
- // Insert after the header, then existing content follows
55
- const before = content.slice(0, afterHeader);
56
- const after = content.slice(afterHeader).replace(/^\n+/, '');
57
- content = before + '\n\n' + testBlock + after;
51
+ // Find the Requirements section and add the test requirement
52
+ const requirementsMatch = content.match(/## Requirements\s*\n/);
53
+ if (requirementsMatch) {
54
+ const insertIndex = requirementsMatch.index + requirementsMatch[0].length;
55
+ const updatedContent = content.slice(0, insertIndex) + testRequirement + content.slice(insertIndex);
56
+ fs.writeFileSync(reqPath, updatedContent, 'utf8');
58
57
  } else {
59
- // No pending section prepend one
60
- content = PENDING_HEADER + '\n\n' + testBlock + '\n' + (content || VERIFIED_HEADER + '\n');
58
+ // If no Requirements section found, append to end
59
+ const updatedContent = content + testRequirement;
60
+ fs.writeFileSync(reqPath, updatedContent, 'utf8');
61
61
  }
62
-
63
- fs.writeFileSync(reqPath, content, 'utf8');
64
62
  }
65
63
 
66
64
  /**
67
- * Remove the test requirement from REQUIREMENTS.md regardless of which section it's in.
68
- * Also cleans up triple-blank lines left behind.
65
+ * Remove test requirement from requirements file
69
66
  */
70
67
  function removeTestRequirement(reqPath) {
71
- let content = '';
72
- try { content = fs.readFileSync(reqPath, 'utf8'); } catch { return; }
73
-
74
- if (!content.includes(TEST_REQ_TITLE)) return;
75
-
76
- const lines = content.split('\n');
77
-
78
- // Find the ### header line for this requirement
79
- let blockStart = -1;
80
- for (let i = 0; i < lines.length; i++) {
81
- if (lines[i].includes(TEST_REQ_TITLE) || (lines[i].startsWith('###') && i + 1 < lines.length && lines[i + 1].includes(TEST_REQ_TITLE))) {
82
- // Walk back to find the ### line
83
- blockStart = lines[i].startsWith('###') ? i : i - 1;
84
- if (blockStart < 0) blockStart = i;
85
- break;
86
- }
68
+ if (!fs.existsSync(reqPath)) {
69
+ return;
70
+ }
71
+
72
+ const content = fs.readFileSync(reqPath, 'utf8');
73
+
74
+ // Find the test requirement section
75
+ const startIndex = content.indexOf(`- ${TEST_REQ_TITLE}`);
76
+ if (startIndex === -1) {
77
+ return;
78
+ }
79
+
80
+ // Find the end of the test requirement (next requirement or end of file)
81
+ let endIndex = content.indexOf('\n- ', startIndex + 1);
82
+ if (endIndex === -1) {
83
+ endIndex = content.length;
87
84
  }
88
85
 
89
- if (blockStart === -1) return;
86
+ // Remove the test requirement and clean up extra blank lines
87
+ const beforeTest = content.slice(0, startIndex);
88
+ const afterTest = content.slice(endIndex);
89
+
90
+ // Clean up triple blank lines
91
+ const cleanedContent = (beforeTest + afterTest).replace(/\n{3,}/g, '\n\n');
92
+
93
+ fs.writeFileSync(reqPath, cleanedContent, 'utf8');
94
+ }
95
+
96
+ /**
97
+ * Extract test requirement from requirements file
98
+ */
99
+ function extractTestRequirement(reqPath) {
100
+ if (!fs.existsSync(reqPath)) {
101
+ return null;
102
+ }
103
+
104
+ const content = fs.readFileSync(reqPath, 'utf8');
105
+
106
+ // Find the test requirement section
107
+ const startIndex = content.indexOf(`- ${TEST_REQ_TITLE}`);
108
+ if (startIndex === -1) {
109
+ return null;
110
+ }
90
111
 
91
- // Find end of block (next ### or ##, or EOF)
92
- let blockEnd = blockStart + 1;
93
- while (blockEnd < lines.length && !lines[blockEnd].startsWith('###') && !lines[blockEnd].startsWith('## ')) {
94
- blockEnd++;
112
+ // Find the end of the test requirement (next requirement or end of file)
113
+ let endIndex = content.indexOf('\n- ', startIndex + 1);
114
+ if (endIndex === -1) {
115
+ endIndex = content.length;
95
116
  }
96
117
 
97
- lines.splice(blockStart, blockEnd - blockStart);
98
- const cleaned = lines.join('\n').replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';
99
- fs.writeFileSync(reqPath, cleaned, 'utf8');
118
+ // Extract the test requirement content including the title line
119
+ const testContent = content.slice(startIndex, endIndex);
120
+
121
+ return testContent.trim();
100
122
  }
101
123
 
102
124
  module.exports = {
103
- TEST_REQ_TITLE,
104
- PENDING_HEADER,
105
- VERIFIED_HEADER,
106
125
  getResultFilePath,
107
126
  addTestRequirement,
108
- removeTestRequirement
127
+ removeTestRequirement,
128
+ extractTestRequirement,
129
+ TEST_REQ_TITLE
109
130
  };
@@ -8,8 +8,7 @@ const fs = require('fs');
8
8
  const path = require('path');
9
9
 
10
10
  const TEST_REQ_TITLE = 'VCM agent connectivity check';
11
- const PENDING_HEADER = '## Requirements not yet completed';
12
- const VERIFIED_HEADER = '## ✅ Verified by AI';
11
+ const REQUIREMENTS_HEADER = '## Requirements';
13
12
 
14
13
  /**
15
14
  * Return the absolute path to the result file used by the check.
@@ -29,27 +28,36 @@ function addTestRequirement(reqPath, resultFile) {
29
28
  // Check if test requirement already exists
30
29
  if (content.includes(TEST_REQ_TITLE)) return;
31
30
 
32
- // Find the start of the pending section
33
- const pendingStart = content.indexOf(PENDING_HEADER);
34
- if (pendingStart === -1) return;
31
+ // Clean up old header format if it exists
32
+ content = content.replace(/## ⏳ Requirements not yet completed\s*\n/g, '');
35
33
 
36
- // Find the end of the pending section (next ## header or end of file)
37
- const afterPending = content.indexOf('\n##', pendingStart + 1);
38
- const insertPos = afterPending === -1 ? content.length : afterPending;
34
+ // Find the Requirements section
35
+ const requirementsStart = content.indexOf(REQUIREMENTS_HEADER);
36
+ if (requirementsStart === -1) {
37
+ // If no Requirements section, create one at the beginning
38
+ content = REQUIREMENTS_HEADER + '\n\n' + content;
39
+ }
39
40
 
40
- // Build the test requirement content
41
- const testReq = `\n### ${TEST_REQ_TITLE}
41
+ // Find where to insert (after the Requirements header)
42
+ const headerEnd = content.indexOf(REQUIREMENTS_HEADER) + REQUIREMENTS_HEADER.length;
43
+ const insertPos = headerEnd;
42
44
 
43
- **Purpose:** Verify agent connectivity and functionality
45
+ // Build the test requirement content with status tracking
46
+ const testReq = `
47
+ - ${TEST_REQ_TITLE}
48
+ 🚦 Status: [PREPARE]
44
49
 
45
- **Expected Results:**
46
- - Agent responds to test prompt
47
- - No errors or timeouts
48
- - Proper formatting and structure
50
+ **✅ ACTION REQUIRED - VCM Connectivity Test:**
49
51
 
50
- **Test Command:** \`vibecodingmachine agent test\`
52
+ VCM is testing if it can communicate with your IDE. To complete this test:
51
53
 
52
- **Verification:** Check TEMP_agent_check.txt for results
54
+ **Change the status above from [PREPARE] to [DONE] and save the file.**
55
+
56
+ Example:
57
+ Before: 🚦 Status: [PREPARE]
58
+ After: 🚦 Status: [DONE]
59
+
60
+ That's it! VCM will detect the status change within seconds.
53
61
 
54
62
  `;
55
63
 
@@ -1,121 +1,121 @@
1
- const chalk = require('chalk');
2
- const boxen = require('boxen');
3
-
4
- /**
5
- * Render a status card showing current requirement progress
6
- * Similar to the purple card in the GUI
7
- * @param {object} status - Current status object
8
- * @param {string} status.requirement - Current requirement being worked on
9
- * @param {string} status.step - Current step (PREPARE, ACT, CLEAN UP, VERIFY, DONE)
10
- * @param {number} status.chatCount - Current chat count
11
- * @param {number|null} status.maxChats - Maximum chats or null for unlimited
12
- * @param {number} status.progress - Progress percentage (0-100)
13
- * @returns {string} Formatted status card
14
- */
15
- function renderStatusCard(status) {
16
- const {
17
- requirement = 'No requirement loaded',
18
- step = 'UNKNOWN',
19
- chatCount = 0,
20
- maxChats = null,
21
- progress = 0
22
- } = status;
23
-
24
- // Step color mapping
25
- const stepColors = {
26
- 'PREPARE': chalk.cyan,
27
- 'ACT': chalk.yellow,
28
- 'CLEAN UP': chalk.magenta,
29
- 'VERIFY': chalk.blue,
30
- 'DONE': chalk.green,
31
- 'UNKNOWN': chalk.gray
32
- };
33
-
34
- const stepColor = stepColors[step] || chalk.gray;
35
-
36
- // Progress bar
37
- const barWidth = 30;
38
- const filledWidth = Math.round((progress / 100) * barWidth);
39
- const emptyWidth = barWidth - filledWidth;
40
- const progressBar = chalk.green('█'.repeat(filledWidth)) + chalk.gray('░'.repeat(emptyWidth));
41
-
42
- // Chat counter
43
- const chatDisplay = maxChats
44
- ? `Chat ${chatCount}/${maxChats}`
45
- : `Chat ${chatCount} (unlimited)`;
46
-
47
- // Build card content
48
- const content = [
49
- chalk.bold('📋 Current Requirement'),
50
- '',
51
- chalk.white(requirement.length > 60 ? requirement.substring(0, 57) + '...' : requirement),
52
- '',
53
- chalk.bold('🚦 Status: ') + stepColor.bold(step),
54
- '',
55
- `${progressBar} ${progress}%`,
56
- '',
57
- chalk.gray(chatDisplay)
58
- ].join('\n');
59
-
60
- // Render with boxen (purple/magenta border like the GUI)
61
- return boxen(content, {
62
- padding: 1,
63
- margin: { top: 0, right: 0, bottom: 1, left: 0 },
64
- borderStyle: 'round',
65
- borderColor: 'magenta',
66
- title: 'Auto Mode Status',
67
- titleAlignment: 'center',
68
- width: 80
69
- });
70
- }
71
-
72
- /**
73
- * Clear the terminal and move cursor to top
74
- */
75
- function clearAndMoveToTop() {
76
- // ANSI escape codes
77
- process.stdout.write('\x1B[2J'); // Clear entire screen
78
- process.stdout.write('\x1B[H'); // Move cursor to home (top-left)
79
- }
80
-
81
- /**
82
- * Move cursor up N lines
83
- * @param {number} lines - Number of lines to move up
84
- */
85
- function moveCursorUp(lines) {
86
- process.stdout.write(`\x1B[${lines}A`);
87
- }
88
-
89
- /**
90
- * Save cursor position
91
- */
92
- function saveCursor() {
93
- process.stdout.write('\x1B[s');
94
- }
95
-
96
- /**
97
- * Restore cursor position
98
- */
99
- function restoreCursor() {
100
- process.stdout.write('\x1B[u');
101
- }
102
-
103
- /**
104
- * Render the menu header and status card together
105
- * @param {string} menuContent - The menu content to display
106
- * @param {object} status - Status object for the status card
107
- */
108
- function renderHeaderWithStatus(menuContent, status) {
109
- clearAndMoveToTop();
110
- console.log(menuContent);
111
- console.log(renderStatusCard(status));
112
- }
113
-
114
- module.exports = {
115
- renderStatusCard,
116
- clearAndMoveToTop,
117
- moveCursorUp,
118
- saveCursor,
119
- restoreCursor,
120
- renderHeaderWithStatus
121
- };
1
+ const chalk = require('chalk');
2
+ const boxen = require('boxen');
3
+
4
+ /**
5
+ * Render a status card showing current requirement progress
6
+ * Similar to the purple card in the GUI
7
+ * @param {object} status - Current status object
8
+ * @param {string} status.requirement - Current requirement being worked on
9
+ * @param {string} status.step - Current step (PREPARE, ACT, CLEAN UP, VERIFY, DONE)
10
+ * @param {number} status.chatCount - Current chat count
11
+ * @param {number|null} status.maxChats - Maximum chats or null for unlimited
12
+ * @param {number} status.progress - Progress percentage (0-100)
13
+ * @returns {string} Formatted status card
14
+ */
15
+ function renderStatusCard(status) {
16
+ const {
17
+ requirement = 'No requirement loaded',
18
+ step = 'UNKNOWN',
19
+ chatCount = 0,
20
+ maxChats = null,
21
+ progress = 0
22
+ } = status;
23
+
24
+ // Step color mapping
25
+ const stepColors = {
26
+ 'PREPARE': chalk.cyan,
27
+ 'ACT': chalk.yellow,
28
+ 'CLEAN UP': chalk.magenta,
29
+ 'VERIFY': chalk.blue,
30
+ 'DONE': chalk.green,
31
+ 'UNKNOWN': chalk.gray
32
+ };
33
+
34
+ const stepColor = stepColors[step] || chalk.gray;
35
+
36
+ // Progress bar
37
+ const barWidth = 30;
38
+ const filledWidth = Math.round((progress / 100) * barWidth);
39
+ const emptyWidth = barWidth - filledWidth;
40
+ const progressBar = chalk.green('█'.repeat(filledWidth)) + chalk.gray('░'.repeat(emptyWidth));
41
+
42
+ // Chat counter
43
+ const chatDisplay = maxChats
44
+ ? `Chat ${chatCount}/${maxChats}`
45
+ : `Chat ${chatCount} (unlimited)`;
46
+
47
+ // Build card content
48
+ const content = [
49
+ chalk.bold('📋 Current Requirement'),
50
+ '',
51
+ chalk.white(requirement.length > 60 ? requirement.substring(0, 57) + '...' : requirement),
52
+ '',
53
+ chalk.bold('🚦 Status: ') + stepColor.bold(step),
54
+ '',
55
+ `${progressBar} ${progress}%`,
56
+ '',
57
+ chalk.gray(chatDisplay)
58
+ ].join('\n');
59
+
60
+ // Render with boxen (purple/magenta border like the GUI)
61
+ return boxen(content, {
62
+ padding: 1,
63
+ margin: { top: 0, right: 0, bottom: 1, left: 0 },
64
+ borderStyle: 'round',
65
+ borderColor: 'magenta',
66
+ title: 'Auto Mode Status',
67
+ titleAlignment: 'center',
68
+ width: 80
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Clear the terminal and move cursor to top
74
+ */
75
+ function clearAndMoveToTop() {
76
+ // ANSI escape codes
77
+ process.stdout.write('\x1B[2J'); // Clear entire screen
78
+ process.stdout.write('\x1B[H'); // Move cursor to home (top-left)
79
+ }
80
+
81
+ /**
82
+ * Move cursor up N lines
83
+ * @param {number} lines - Number of lines to move up
84
+ */
85
+ function moveCursorUp(lines) {
86
+ process.stdout.write(`\x1B[${lines}A`);
87
+ }
88
+
89
+ /**
90
+ * Save cursor position
91
+ */
92
+ function saveCursor() {
93
+ process.stdout.write('\x1B[s');
94
+ }
95
+
96
+ /**
97
+ * Restore cursor position
98
+ */
99
+ function restoreCursor() {
100
+ process.stdout.write('\x1B[u');
101
+ }
102
+
103
+ /**
104
+ * Render the menu header and status card together
105
+ * @param {string} menuContent - The menu content to display
106
+ * @param {object} status - Status object for the status card
107
+ */
108
+ function renderHeaderWithStatus(menuContent, status) {
109
+ clearAndMoveToTop();
110
+ console.log(menuContent);
111
+ console.log(renderStatusCard(status));
112
+ }
113
+
114
+ module.exports = {
115
+ renderStatusCard,
116
+ clearAndMoveToTop,
117
+ moveCursorUp,
118
+ saveCursor,
119
+ restoreCursor,
120
+ renderHeaderWithStatus
121
+ };