vibecodingmachine-cli 2026.2.26-1739 → 2026.3.9-1621

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 (74) hide show
  1. package/bin/auth/auth-compliance.js +7 -1
  2. package/bin/commands/agent-commands.js +150 -228
  3. package/bin/commands/command-aliases.js +68 -0
  4. package/bin/vibecodingmachine.js +1 -2
  5. package/package.json +2 -2
  6. package/src/commands/agents/list.js +71 -115
  7. package/src/commands/agents-check.js +16 -4
  8. package/src/commands/analyze-file-sizes.js +1 -1
  9. package/src/commands/auto-direct/auto-provider-manager.js +290 -0
  10. package/src/commands/auto-direct/auto-status-display.js +331 -0
  11. package/src/commands/auto-direct/auto-utils.js +439 -0
  12. package/src/commands/auto-direct/file-operations.js +110 -0
  13. package/src/commands/auto-direct/provider-config.js +1 -1
  14. package/src/commands/auto-direct/provider-manager.js +1 -1
  15. package/src/commands/auto-direct/status-display.js +1 -1
  16. package/src/commands/auto-direct/utils.js +24 -18
  17. package/src/commands/auto-direct-refactored.js +413 -0
  18. package/src/commands/auto-direct.js +594 -188
  19. package/src/commands/requirements/commands.js +353 -0
  20. package/src/commands/requirements/default-handlers.js +272 -0
  21. package/src/commands/requirements/disable.js +97 -0
  22. package/src/commands/requirements/enable.js +97 -0
  23. package/src/commands/requirements/utils.js +194 -0
  24. package/src/commands/requirements-refactored.js +60 -0
  25. package/src/commands/requirements.js +38 -771
  26. package/src/commands/specs/disable.js +96 -0
  27. package/src/commands/specs/enable.js +96 -0
  28. package/src/trui/TruiInterface.js +5 -11
  29. package/src/trui/agents/AgentInterface.js +24 -396
  30. package/src/trui/agents/handlers/CommandHandler.js +93 -0
  31. package/src/trui/agents/handlers/ContextManager.js +117 -0
  32. package/src/trui/agents/handlers/DisplayHandler.js +243 -0
  33. package/src/trui/agents/handlers/HelpHandler.js +51 -0
  34. package/src/utils/auth.js +13 -111
  35. package/src/utils/config.js +5 -1
  36. package/src/utils/interactive/requirements-navigation.js +17 -15
  37. package/src/utils/interactive-broken.js +2 -2
  38. package/src/utils/provider-checker/agent-runner.js +15 -1
  39. package/src/utils/provider-checker/cli-installer.js +149 -7
  40. package/src/utils/provider-checker/opencode-checker.js +588 -0
  41. package/src/utils/provider-checker/provider-validator.js +88 -3
  42. package/src/utils/provider-checker/time-formatter.js +3 -2
  43. package/src/utils/provider-manager.js +28 -20
  44. package/src/utils/provider-registry.js +35 -3
  45. package/src/utils/requirements-navigator/index.js +94 -0
  46. package/src/utils/requirements-navigator/input-handler.js +217 -0
  47. package/src/utils/requirements-navigator/section-loader.js +188 -0
  48. package/src/utils/requirements-navigator/tree-builder.js +105 -0
  49. package/src/utils/requirements-navigator/tree-renderer.js +50 -0
  50. package/src/utils/requirements-navigator.js +2 -583
  51. package/src/utils/trui-clarifications.js +188 -0
  52. package/src/utils/trui-feedback.js +54 -1
  53. package/src/utils/trui-kiro-integration.js +398 -0
  54. package/src/utils/trui-main-handlers.js +194 -0
  55. package/src/utils/trui-main-menu.js +235 -0
  56. package/src/utils/trui-nav-agents.js +178 -25
  57. package/src/utils/trui-nav-requirements.js +203 -27
  58. package/src/utils/trui-nav-settings.js +114 -1
  59. package/src/utils/trui-nav-specifications.js +44 -3
  60. package/src/utils/trui-navigation-backup.js +603 -0
  61. package/src/utils/trui-navigation.js +70 -228
  62. package/src/utils/trui-provider-health.js +274 -0
  63. package/src/utils/trui-provider-manager.js +376 -0
  64. package/src/utils/trui-quick-menu.js +25 -1
  65. package/src/utils/trui-req-actions-backup.js +507 -0
  66. package/src/utils/trui-req-actions.js +148 -216
  67. package/src/utils/trui-req-editor.js +170 -0
  68. package/src/utils/trui-req-file-ops.js +278 -0
  69. package/src/utils/trui-req-tree-old.js +719 -0
  70. package/src/utils/trui-req-tree.js +348 -627
  71. package/src/utils/trui-specifications.js +25 -7
  72. package/src/utils/trui-windsurf.js +231 -10
  73. package/src/utils/welcome-screen-extracted.js +2 -2
  74. package/src/utils/welcome-screen.js +2 -2
package/src/utils/auth.js CHANGED
@@ -8,101 +8,50 @@ 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 = 3001; // Use 3001 to avoid conflict with web dev server on 3000
11
+ const PREFERRED_PORT = 3001;
12
12
 
13
- // Load shared access denied HTML
14
13
  function getAccessDeniedHTML() {
15
14
  const accessDeniedPath = require.resolve('vibecodingmachine-core/src/auth/access-denied.html');
16
15
  return fs.readFileSync(accessDeniedPath, 'utf8');
17
16
  }
18
17
 
19
- // PKCE helper functions
20
- function generateCodeVerifier() {
21
- return crypto.randomBytes(32).toString('base64url');
22
- }
23
-
24
- function generateCodeChallenge(verifier) {
25
- return crypto.createHash('sha256').update(verifier).digest('base64url');
26
- }
18
+ function generateCodeVerifier() { return crypto.randomBytes(32).toString('base64url'); }
19
+ function generateCodeChallenge(verifier) { return crypto.createHash('sha256').update(verifier).digest('base64url'); }
27
20
 
28
21
  class CLIAuth {
29
- /**
30
- * Check if authenticated (uses shared storage)
31
- */
32
22
  async isAuthenticated() {
33
- // First check if current token is valid
34
23
  const isValid = await sharedAuth.isAuthenticated();
35
24
  if (isValid) {
36
- // Update user activity in database
37
25
  await this._updateUserActivity();
38
26
  return true;
39
27
  }
40
-
41
- // If not valid, try to refresh
42
28
  try {
43
29
  const refreshToken = await sharedAuth.getRefreshToken();
44
30
  if (refreshToken) {
45
- // console.log(chalk.gray('Refreshing session...'));
46
31
  const newTokens = await this._refreshTokens(refreshToken);
47
-
48
- // Validate new ID token
49
32
  await this._validateToken(newTokens.id_token);
50
-
51
- // Save new tokens
52
33
  await sharedAuth.saveToken(newTokens);
53
-
54
- // Update user activity in database
55
34
  await this._updateUserActivity();
56
35
  return true;
57
36
  }
58
- } catch (error) {
59
- // Refresh failed (token expired or revoked), user needs to login again
60
- // console.log(chalk.gray('Session refresh failed, please login again.'));
61
- }
62
-
37
+ } catch (error) {}
63
38
  return false;
64
39
  }
65
40
 
66
- /**
67
- * Get stored token (uses shared storage)
68
- */
69
- async getToken() {
70
- return await sharedAuth.getToken();
71
- }
41
+ async getToken() { return await sharedAuth.getToken(); }
72
42
 
73
- /**
74
- * Get auth token string for API requests
75
- * Ensures the token is valid and refreshed if necessary
76
- */
77
43
  async getAuthToken() {
78
- // Check if authenticated and refresh if needed
79
44
  const isAuth = await this.isAuthenticated();
80
45
  if (!isAuth) return null;
81
-
82
46
  const token = await sharedAuth.getToken();
83
47
  if (!token) return null;
84
-
85
- // Handle both string token and object with id_token
86
48
  return typeof token === 'string' ? token : token.id_token;
87
49
  }
88
50
 
89
- /**
90
- * Get user profile (uses shared storage)
91
- */
92
- async getUserProfile() {
93
- return await sharedAuth.getUserProfile();
94
- }
51
+ async getUserProfile() { return await sharedAuth.getUserProfile(); }
95
52
 
96
- /**
97
- * Exchange authorization code for tokens using Cognito token endpoint
98
- * @param {string} code - Authorization code
99
- * @param {string} codeVerifier - PKCE code verifier
100
- * @param {string} redirectUri - Redirect URI used in authorization request
101
- * @returns {Promise<string>} ID token
102
- */
103
53
  async _exchangeCodeForTokens(code, codeVerifier, redirectUri) {
104
54
  const https = require('https');
105
-
106
55
  const tokenEndpoint = `https://${COGNITO_DOMAIN}/oauth2/token`;
107
56
  const postData = new URLSearchParams({
108
57
  grant_type: 'authorization_code',
@@ -111,7 +60,6 @@ class CLIAuth {
111
60
  redirect_uri: redirectUri,
112
61
  code_verifier: codeVerifier
113
62
  }).toString();
114
-
115
63
  return new Promise((resolve, reject) => {
116
64
  const options = {
117
65
  method: 'POST',
@@ -120,41 +68,34 @@ class CLIAuth {
120
68
  'Content-Length': postData.length
121
69
  }
122
70
  };
123
-
124
71
  const req = https.request(tokenEndpoint, options, (res) => {
125
72
  let data = '';
126
73
  res.on('data', (chunk) => { data += chunk; });
127
74
  res.on('end', () => {
128
75
  if (res.statusCode === 200) {
129
76
  const tokens = JSON.parse(data);
130
- resolve(tokens); // Return full tokens object
77
+ resolve(tokens);
131
78
  } else {
132
79
  reject(new Error(`Token exchange failed: ${res.statusCode} - ${data}`));
133
80
  }
134
81
  });
135
82
  });
136
-
137
83
  req.on('error', (error) => {
138
84
  reject(new Error(`Token exchange request failed: ${error.message}`));
139
85
  });
140
-
141
86
  req.write(postData);
142
87
  req.end();
143
88
  });
144
89
  }
145
- /**
146
- * Refresh tokens using refresh_token
147
- */
90
+
148
91
  async _refreshTokens(refreshToken) {
149
92
  const https = require('https');
150
-
151
93
  const tokenEndpoint = `https://${COGNITO_DOMAIN}/oauth2/token`;
152
94
  const postData = new URLSearchParams({
153
95
  grant_type: 'refresh_token',
154
96
  client_id: CLIENT_ID,
155
97
  refresh_token: refreshToken
156
98
  }).toString();
157
-
158
99
  return new Promise((resolve, reject) => {
159
100
  const options = {
160
101
  method: 'POST',
@@ -163,14 +104,12 @@ class CLIAuth {
163
104
  'Content-Length': postData.length
164
105
  }
165
106
  };
166
-
167
107
  const req = https.request(tokenEndpoint, options, (res) => {
168
108
  let data = '';
169
109
  res.on('data', (chunk) => { data += chunk; });
170
110
  res.on('end', () => {
171
111
  if (res.statusCode === 200) {
172
112
  const tokens = JSON.parse(data);
173
- // Cognito might not return a new refresh token, so we keep the old one if not provided
174
113
  if (!tokens.refresh_token) {
175
114
  tokens.refresh_token = refreshToken;
176
115
  }
@@ -180,33 +119,22 @@ class CLIAuth {
180
119
  }
181
120
  });
182
121
  });
183
-
184
122
  req.on('error', (error) => {
185
123
  reject(new Error(`Token refresh request failed: ${error.message}`));
186
124
  });
187
-
188
125
  req.write(postData);
189
126
  req.end();
190
127
  });
191
128
  }
192
- /**
193
- * Validate JWT token with signature verification
194
- * @param {string} idToken - JWT token to validate
195
- * @throws {Error} If token is invalid
196
- */
129
+
197
130
  async _validateToken(idToken) {
198
- // Validate token is a string
199
131
  if (!idToken || typeof idToken !== 'string') {
200
132
  throw new Error('Token must be a non-empty string');
201
133
  }
202
-
203
- // Validate JWT format (must have 3 parts)
204
134
  const parts = idToken.split('.');
205
135
  if (parts.length !== 3) {
206
136
  throw new Error('Invalid JWT format - must have 3 parts separated by dots');
207
137
  }
208
-
209
- // CRITICAL: Verify JWT signature using Cognito's public keys
210
138
  const jwt = require('jsonwebtoken');
211
139
  const jwksClient = require('jwks-rsa');
212
140
 
@@ -688,37 +616,11 @@ class CLIAuth {
688
616
  }
689
617
  }
690
618
 
691
- /**
692
- * Logout (uses shared storage)
693
- */
694
- async logout() {
695
- return await sharedAuth.logout();
696
- }
697
-
698
- /**
699
- * Check if can run auto mode (uses shared storage)
700
- */
701
- async canRunAutoMode() {
702
- return await sharedAuth.canRunAutoMode();
703
- }
619
+ async logout() { return await sharedAuth.logout(); }
620
+ async canRunAutoMode() { return await sharedAuth.canRunAutoMode(); }
621
+ async logIteration() { return await sharedAuth.logIteration(); }
622
+ async activateLicense(licenseKey) { return await sharedAuth.activateLicense(licenseKey); }
704
623
 
705
- /**
706
- * Log iteration (uses shared storage)
707
- */
708
- async logIteration() {
709
- return await sharedAuth.logIteration();
710
- }
711
-
712
- /**
713
- * Activate license key (uses shared storage)
714
- */
715
- async activateLicense(licenseKey) {
716
- return await sharedAuth.activateLicense(licenseKey);
717
- }
718
-
719
- /**
720
- * Register or update user in enhanced database system
721
- */
722
624
  async _registerUserInDatabase(idToken) {
723
625
  try {
724
626
  // Decode JWT to get user info (without verification since we already validated)
@@ -51,6 +51,10 @@ async function getEffectiveRepoPath() {
51
51
  const configured = await getRepoPath();
52
52
  if (configured) return configured;
53
53
  try {
54
+ // Skip git operations if environment variable is set
55
+ if (process.env.VCM_SKIP_GIT_CHECKS) {
56
+ return process.cwd();
57
+ }
54
58
  return execSync('git rev-parse --show-toplevel', {
55
59
  encoding: 'utf8',
56
60
  cwd: process.cwd(),
@@ -139,7 +143,7 @@ async function setAutoTimeout(timeoutMs) {
139
143
 
140
144
  // Timeout configuration for adaptive timeout management (T052)
141
145
  // Using core TimeoutConfigManager for consistency with Electron app
142
- const { TimeoutConfigManager } = require('../../../core/src/timeout-management');
146
+ const { TimeoutConfigManager } = require('vibecodingmachine-core/src/timeout-management');
143
147
 
144
148
  const DEFAULT_TIMEOUT_CONFIG = Object.freeze({
145
149
  mode: 'fixed', // 'fixed' or 'adaptive'
@@ -192,7 +192,7 @@ async function showRequirementDetails(req, sectionKey, tree) {
192
192
  type: 'list',
193
193
  name: 'action',
194
194
  message: 'What would you like to do?',
195
- choices: getRequirementActions(sectionKey)
195
+ choices: getRequirementActions(sectionKey, req.title)
196
196
  }
197
197
  ]);
198
198
 
@@ -201,39 +201,41 @@ async function showRequirementDetails(req, sectionKey, tree) {
201
201
  }
202
202
  }
203
203
 
204
- function getRequirementActions(sectionKey) {
204
+ function getRequirementActions(sectionKey, requirementTitle = '') {
205
205
  const baseActions = [
206
206
  { name: '⬅️ Back', value: 'back' }
207
207
  ];
208
208
 
209
+ const escTitle = requirementTitle.replace(/'/g, "\\'");
210
+
209
211
  switch (sectionKey) {
210
212
  case 'todo':
211
213
  return [
212
- { name: '✏️ Rename/Edit', value: 'rename' },
213
- { name: '👍 Thumbs up (promote to Verified)', value: 'thumbs-up' },
214
- { name: '❓ Needs clarification', value: 'needs-clarification' },
215
- { name: '🗑️ Delete', value: 'delete' },
216
- { name: '⬆️ Move up', value: 'move-up' },
217
- { name: '⬇️ Move down', value: 'move-down' },
214
+ { name: `✏️ Rename/Edit\n ${chalk.gray(`Equivalent: app update requirements --rename '${escTitle}' '<new-title>'`)}`, value: 'rename' },
215
+ { name: `👍 Thumbs up (promote to Verified)\n ${chalk.gray(`Equivalent: app update requirements --promote '${escTitle}'`)}`, value: 'thumbs-up' },
216
+ { name: `❓ Needs clarification\n ${chalk.gray(`Equivalent: app update requirements --clarify '${escTitle}'`)}`, value: 'needs-clarification' },
217
+ { name: `🗑️ Delete\n ${chalk.gray(`Equivalent: app remove requirements '${escTitle}'`)}`, value: 'delete' },
218
+ { name: `⬆️ Move up\n ${chalk.gray(`Equivalent: app update requirements --move-up '${escTitle}'`)}`, value: 'move-up' },
219
+ { name: `⬇️ Move down\n ${chalk.gray(`Equivalent: app update requirements --move-down '${escTitle}'`)}`, value: 'move-down' },
218
220
  ...baseActions
219
221
  ];
220
222
  case 'clarification':
221
223
  return [
222
- { name: '✍️ Add/Edit Responses', value: 'edit-responses' },
223
- { name: '↩️ Move back to TODO (after clarification)', value: 'move-to-todo' },
224
- { name: '🗑️ Delete', value: 'delete' },
224
+ { name: `✍️ Add/Edit Responses\n ${chalk.gray(`Equivalent: app update requirements --edit-response '${escTitle}'`)}`, value: 'edit-responses' },
225
+ { name: `↩️ Move back to TODO (after clarification)\n ${chalk.gray(`Equivalent: app update requirements --move-to-todo '${escTitle}'`)}`, value: 'move-to-todo' },
226
+ { name: `🗑️ Delete\n ${chalk.gray(`Equivalent: app remove requirements '${escTitle}'`)}`, value: 'delete' },
225
227
  ...baseActions
226
228
  ];
227
229
  case 'verify':
228
230
  return [
229
- { name: '↩️ Move back to TODO', value: 'move-to-todo' },
230
- { name: '🗑️ Delete', value: 'delete' },
231
+ { name: `↩️ Move back to TODO\n ${chalk.gray(`Equivalent: app update requirements --move-to-todo '${escTitle}'`)}`, value: 'move-to-todo' },
232
+ { name: `🗑️ Delete\n ${chalk.gray(`Equivalent: app remove requirements '${escTitle}'`)}`, value: 'delete' },
231
233
  ...baseActions
232
234
  ];
233
235
  case 'recycled':
234
236
  return [
235
- { name: '♻️ Restore to TODO', value: 'restore' },
236
- { name: '🗑️ Permanently delete', value: 'permanent-delete' },
237
+ { name: `♻️ Restore to TODO\n ${chalk.gray(`Equivalent: app update requirements --restore '${escTitle}'`)}`, value: 'restore' },
238
+ { name: `🗑️ Permanently delete\n ${chalk.gray(`Equivalent: app remove requirements '${escTitle}' --permanent`)}`, value: 'permanent-delete' },
237
239
  ...baseActions
238
240
  ];
239
241
  default:
@@ -120,9 +120,9 @@ async function startInteractive() {
120
120
  const repoPath = process.cwd();
121
121
  console.log(chalk.gray(t('system.repo').padEnd(25)), formatPath(repoPath));
122
122
 
123
- // Display git branch if in a git repo
123
+ // Display git branch if in a git repo (skip if environment variable is set)
124
124
  try {
125
- if (isGitRepo(repoPath)) {
125
+ if (!process.env.VCM_SKIP_GIT_CHECKS && isGitRepo(repoPath)) {
126
126
  const branch = getCurrentBranch(repoPath);
127
127
  if (branch) {
128
128
  const isDirty = hasUncommittedChanges(repoPath);
@@ -24,6 +24,10 @@ const CLI_ENTRY_POINT = path.resolve(path.join(__dirname, '../../../bin/vibecodi
24
24
  async function runAgentCheck(providerId, def, repoPath, resultFile, timeoutMs, signal = null, onProgress = null) {
25
25
  const checkedAt = new Date().toISOString();
26
26
 
27
+ if (onProgress) {
28
+ onProgress(providerId, 'loading', null, 'Loading...');
29
+ }
30
+
27
31
  // Ensure temp dir exists
28
32
  fs.mkdirSync(path.dirname(resultFile), { recursive: true });
29
33
  // Remove stale result file so we detect a fresh write
@@ -157,6 +161,7 @@ async function runAgentCheck(providerId, def, repoPath, resultFile, timeoutMs, s
157
161
  try {
158
162
  const content = fs.readFileSync(resultFile, 'utf8');
159
163
  if (content.includes('VCM_CHECK_OK')) {
164
+ if (onProgress) onProgress(providerId, 'response_detected', null, 'Response detected.');
160
165
  done({ status: 'success', message: 'End-to-end check passed', checkedAt });
161
166
  }
162
167
  } catch { }
@@ -180,7 +185,7 @@ async function runAgentCheck(providerId, def, repoPath, resultFile, timeoutMs, s
180
185
  }
181
186
  console.warn(`[AGENT CHECK] Timeout for ${providerId} after ${timeoutMs / 1000}s${diagnosis}`);
182
187
 
183
- const timeoutResult = { status: 'error', message: `No response within ${timeoutMs / 1000}s${ideLaunchNote}${diagnosis}`, checkedAt };
188
+ const timeoutResult = { status: 'error', message: `Timeout${ideLaunchNote}${diagnosis}`, checkedAt };
184
189
 
185
190
  // For IDE timeout, also check for quota limit (in output + UI)
186
191
  if (def.type === 'ide') {
@@ -200,6 +205,11 @@ async function runAgentCheck(providerId, def, repoPath, resultFile, timeoutMs, s
200
205
  }
201
206
  }, 1000); // Check every second
202
207
 
208
+ // Send initial checking status
209
+ if (onProgress) {
210
+ onProgress(providerId, 'awaiting', null, 'Awaiting response...');
211
+ }
212
+
203
213
  let args;
204
214
  if (def.type === 'ide') {
205
215
  args = [CLI_ENTRY_POINT, 'auto:start', '--ide', def.ide || providerId, '--max-chats', '1'];
@@ -265,6 +275,10 @@ async function runAgentCheck(providerId, def, repoPath, resultFile, timeoutMs, s
265
275
  detached: false
266
276
  });
267
277
 
278
+ if (onProgress) {
279
+ onProgress(providerId, 'sending', null, 'Sending text...');
280
+ }
281
+
268
282
  console.log(`[AGENT CHECK] Child process spawned for ${providerId}, PID: ${child.pid}`);
269
283
 
270
284
  child.stdout.on('data', d => {
@@ -17,6 +17,22 @@ const CLI_AUTO_INSTALL = {
17
17
  path.join(os.homedir(), '.opencode', 'bin'),
18
18
  ]
19
19
  },
20
+ 'vscode-copilot-cli': {
21
+ cmd: 'copilot',
22
+ type: 'homebrew',
23
+ installScript: 'brew install --cask copilot-cli',
24
+ // Alternative installation methods
25
+ alternatives: [
26
+ { type: 'npm', cmd: 'npm install -g @github/copilot' },
27
+ { type: 'winget', cmd: 'winget install GitHub.Copilot' },
28
+ { type: 'direct', cmd: 'curl -fsSL https://gh.io/copilot-install | bash' }
29
+ ],
30
+ // Well-known install locations that may not be in Electron's PATH
31
+ knownPaths: [
32
+ path.join(os.homedir(), '.local', 'bin'),
33
+ '/usr/local/bin',
34
+ ]
35
+ },
20
36
  };
21
37
 
22
38
  /**
@@ -74,44 +90,170 @@ function isCLIAvailable(cmd, providerId) {
74
90
  * Returns { installed: bool, note: string }.
75
91
  */
76
92
  async function installCLI(providerId) {
93
+ console.log(`[CLI INSTALLER] Starting installation for provider: ${providerId}`);
94
+
77
95
  const config = CLI_AUTO_INSTALL[providerId];
78
96
  if (!config) {
97
+ console.log(`[CLI INSTALLER] No auto-install config found for ${providerId}`);
79
98
  return { installed: false, note: `Unknown provider: ${providerId}` };
80
99
  }
81
100
 
101
+ console.log(`[CLI INSTALLER] Install config for ${providerId}:`, config);
102
+
103
+ // Check if already installed first
104
+ console.log(`[CLI INSTALLER] Checking if ${providerId} is already installed...`);
105
+ if (isCLIAvailable(config.cmd, providerId)) {
106
+ console.log(`[CLI INSTALLER] ${providerId} is already installed`);
107
+ return { installed: true, note: 'Already installed' };
108
+ }
109
+ console.log(`[CLI INSTALLER] ${providerId} is not installed, proceeding with installation...`);
110
+
82
111
  // Augmented PATH so the install script and post-install verification can find binaries
83
112
  const augPath = getAugmentedPath(providerId);
84
113
  const spawnEnv = { ...process.env, PATH: augPath };
114
+ console.log(`[CLI INSTALLER] Using augmented PATH: ${augPath}`);
85
115
 
86
116
  return new Promise((resolve) => {
87
117
  if (config.type === 'direct' && config.installScript) {
118
+ console.log(`[CLI INSTALLER] Installing ${providerId} via direct script...`);
88
119
  // Direct installation using shell script
89
120
  const proc = spawn('bash', ['-c', config.installScript], { stdio: ['ignore', 'pipe', 'pipe'], env: spawnEnv });
90
121
  let out = '';
91
- proc.stdout.on('data', d => { out += d.toString(); });
92
- proc.stderr.on('data', d => { out += d.toString(); });
93
- proc.on('error', () => resolve({ installed: false, note: `Failed to run install script for ${providerId}` }));
122
+ let hasStartedDownload = false;
123
+ let hasStartedInstall = false;
124
+
125
+ proc.stdout.on('data', d => {
126
+ out += d.toString();
127
+ console.log(`[CLI INSTALLER] STDOUT: ${d.toString().trim()}`);
128
+
129
+ // Detect actual progress from output
130
+ const output = d.toString().toLowerCase();
131
+ if (output.includes('download') || output.includes('fetch')) {
132
+ hasStartedDownload = true;
133
+ // We could emit a progress event here if we had access to onProgress
134
+ }
135
+ if (output.includes('install') || output.includes('unpack')) {
136
+ hasStartedInstall = true;
137
+ // We could emit a progress event here if we had access to onProgress
138
+ }
139
+ });
140
+
141
+ proc.stderr.on('data', d => {
142
+ out += d.toString();
143
+ console.log(`[CLI INSTALLER] STDERR: ${d.toString().trim()}`);
144
+ });
145
+
146
+ proc.on('error', (err) => {
147
+ console.log(`[CLI INSTALLER] Process error for ${providerId}: ${err.message}`);
148
+ resolve({ installed: false, note: `Failed to run install script for ${providerId}` });
149
+ });
150
+
94
151
  proc.on('close', (code) => {
152
+ console.log(`[CLI INSTALLER] Install script for ${providerId} exited with code: ${code}`);
153
+ console.log(`[CLI INSTALLER] Checking if ${providerId} is now available...`);
95
154
  if (code === 0 && isCLIAvailable(config.cmd, providerId)) {
155
+ console.log(`[CLI INSTALLER] ${providerId} successfully installed via direct script`);
96
156
  resolve({ installed: true, note: `Installed ${providerId} via direct script` });
97
157
  } else {
158
+ console.log(`[CLI INSTALLER] Install script for ${providerId} failed or tool not found after install`);
98
159
  resolve({ installed: false, note: `Install script for ${providerId} exited (${code})` });
99
160
  }
100
161
  });
101
- } else {
162
+ } else if (config.type === 'homebrew' && config.installScript) {
163
+ console.log(`[CLI INSTALLER] Installing ${providerId} via homebrew...`);
164
+ // Homebrew installation
165
+ const proc = spawn('bash', ['-c', config.installScript], { stdio: ['ignore', 'pipe', 'pipe'], env: spawnEnv });
166
+ let out = '';
167
+ let hasStartedDownload = false;
168
+ let hasStartedInstall = false;
169
+
170
+ proc.stdout.on('data', d => {
171
+ out += d.toString();
172
+ console.log(`[CLI INSTALLER] STDOUT: ${d.toString().trim()}`);
173
+
174
+ // Detect actual progress from output
175
+ const output = d.toString().toLowerCase();
176
+ if (output.includes('download') || output.includes('fetch')) {
177
+ hasStartedDownload = true;
178
+ }
179
+ if (output.includes('install') || output.includes('unpack')) {
180
+ hasStartedInstall = true;
181
+ }
182
+ });
183
+
184
+ proc.stderr.on('data', d => {
185
+ out += d.toString();
186
+ console.log(`[CLI INSTALLER] STDERR: ${d.toString().trim()}`);
187
+ });
188
+
189
+ proc.on('error', (err) => {
190
+ console.log(`[CLI INSTALLER] Process error for ${providerId}: ${err.message}`);
191
+ resolve({ installed: false, note: `Failed to run homebrew install for ${providerId}` });
192
+ });
193
+
194
+ proc.on('close', (code) => {
195
+ console.log(`[CLI INSTALLER] Homebrew install for ${providerId} exited with code: ${code}`);
196
+ console.log(`[CLI INSTALLER] Checking if ${providerId} is now available...`);
197
+ if (code === 0 && isCLIAvailable(config.cmd, providerId)) {
198
+ console.log(`[CLI INSTALLER] ${providerId} successfully installed via homebrew`);
199
+ resolve({ installed: true, note: `Installed ${providerId} via homebrew` });
200
+ } else {
201
+ // Try alternatives if homebrew fails
202
+ if (config.alternatives && config.alternatives.length > 0) {
203
+ console.log(`[CLI INSTALLER] Homebrew failed for ${providerId}, trying alternatives`);
204
+ resolve({ installed: false, note: `Homebrew install failed, trying alternatives for ${providerId}` });
205
+ } else {
206
+ console.log(`[CLI INSTALLER] Homebrew install for ${providerId} failed with no alternatives`);
207
+ resolve({ installed: false, note: `Homebrew install for ${providerId} exited (${code})` });
208
+ }
209
+ }
210
+ });
211
+ } else if (config.type === 'npm') {
212
+ console.log(`[CLI INSTALLER] Installing ${providerId} via npm...`);
102
213
  // NPM installation
103
214
  const proc = spawn('npm', ['install', '-g', config.pkg], { stdio: ['ignore', 'pipe', 'pipe'], env: spawnEnv });
104
215
  let out = '';
105
- proc.stdout.on('data', d => { out += d.toString(); });
106
- proc.stderr.on('data', d => { out += d.toString(); });
107
- proc.on('error', () => resolve({ installed: false, note: `Failed to run npm install -g ${config.pkg}` }));
216
+ let hasStartedDownload = false;
217
+ let hasStartedInstall = false;
218
+
219
+ proc.stdout.on('data', d => {
220
+ out += d.toString();
221
+ console.log(`[CLI INSTALLER] STDOUT: ${d.toString().trim()}`);
222
+
223
+ // Detect actual progress from output
224
+ const output = d.toString().toLowerCase();
225
+ if (output.includes('download') || output.includes('fetch')) {
226
+ hasStartedDownload = true;
227
+ }
228
+ if (output.includes('install') || output.includes('unpack')) {
229
+ hasStartedInstall = true;
230
+ }
231
+ });
232
+
233
+ proc.stderr.on('data', d => {
234
+ out += d.toString();
235
+ console.log(`[CLI INSTALLER] STDERR: ${d.toString().trim()}`);
236
+ });
237
+
238
+ proc.on('error', (err) => {
239
+ console.log(`[CLI INSTALLER] Process error for ${providerId}: ${err.message}`);
240
+ resolve({ installed: false, note: `Failed to run npm install -g ${config.pkg}` });
241
+ });
242
+
108
243
  proc.on('close', (code) => {
244
+ console.log(`[CLI INSTALLER] npm install for ${providerId} exited with code: ${code}`);
245
+ console.log(`[CLI INSTALLER] Checking if ${providerId} is now available...`);
109
246
  if (code === 0 && isCLIAvailable(config.cmd, providerId)) {
247
+ console.log(`[CLI INSTALLER] ${providerId} successfully installed via npm`);
110
248
  resolve({ installed: true, note: `Installed ${config.pkg} via npm` });
111
249
  } else {
250
+ console.log(`[CLI INSTALLER] npm install for ${providerId} failed or tool not found after install`);
112
251
  resolve({ installed: false, note: `npm install -g ${config.pkg} exited (${code})` });
113
252
  }
114
253
  });
254
+ } else {
255
+ console.log(`[CLI INSTALLER] No installation method available for ${providerId} (type: ${config.type})`);
256
+ resolve({ installed: false, note: `No installation method for ${providerId}` });
115
257
  }
116
258
  });
117
259
  }