vibecodingmachine-cli 2026.2.26-1752 → 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.
- package/bin/auth/auth-compliance.js +7 -1
- package/bin/commands/agent-commands.js +150 -228
- package/bin/commands/command-aliases.js +68 -0
- package/bin/vibecodingmachine.js +1 -2
- package/package.json +2 -2
- package/src/commands/agents/list.js +71 -115
- package/src/commands/agents-check.js +16 -4
- package/src/commands/analyze-file-sizes.js +1 -1
- package/src/commands/auto-direct/auto-provider-manager.js +290 -0
- package/src/commands/auto-direct/auto-status-display.js +331 -0
- package/src/commands/auto-direct/auto-utils.js +439 -0
- package/src/commands/auto-direct/file-operations.js +110 -0
- package/src/commands/auto-direct/provider-config.js +1 -1
- package/src/commands/auto-direct/provider-manager.js +1 -1
- package/src/commands/auto-direct/status-display.js +1 -1
- package/src/commands/auto-direct/utils.js +24 -18
- package/src/commands/auto-direct-refactored.js +413 -0
- package/src/commands/auto-direct.js +594 -188
- package/src/commands/requirements/commands.js +353 -0
- package/src/commands/requirements/default-handlers.js +272 -0
- package/src/commands/requirements/disable.js +97 -0
- package/src/commands/requirements/enable.js +97 -0
- package/src/commands/requirements/utils.js +194 -0
- package/src/commands/requirements-refactored.js +60 -0
- package/src/commands/requirements.js +38 -771
- package/src/commands/specs/disable.js +96 -0
- package/src/commands/specs/enable.js +96 -0
- package/src/trui/TruiInterface.js +5 -11
- package/src/trui/agents/AgentInterface.js +24 -396
- package/src/trui/agents/handlers/CommandHandler.js +93 -0
- package/src/trui/agents/handlers/ContextManager.js +117 -0
- package/src/trui/agents/handlers/DisplayHandler.js +243 -0
- package/src/trui/agents/handlers/HelpHandler.js +51 -0
- package/src/utils/auth.js +13 -111
- package/src/utils/config.js +4 -0
- package/src/utils/interactive/requirements-navigation.js +17 -15
- package/src/utils/interactive-broken.js +2 -2
- package/src/utils/provider-checker/agent-runner.js +15 -1
- package/src/utils/provider-checker/cli-installer.js +149 -7
- package/src/utils/provider-checker/opencode-checker.js +588 -0
- package/src/utils/provider-checker/provider-validator.js +88 -3
- package/src/utils/provider-checker/time-formatter.js +3 -2
- package/src/utils/provider-manager.js +28 -20
- package/src/utils/provider-registry.js +35 -3
- package/src/utils/requirements-navigator/index.js +94 -0
- package/src/utils/requirements-navigator/input-handler.js +217 -0
- package/src/utils/requirements-navigator/section-loader.js +188 -0
- package/src/utils/requirements-navigator/tree-builder.js +105 -0
- package/src/utils/requirements-navigator/tree-renderer.js +50 -0
- package/src/utils/requirements-navigator.js +2 -583
- package/src/utils/trui-clarifications.js +188 -0
- package/src/utils/trui-feedback.js +54 -1
- package/src/utils/trui-kiro-integration.js +398 -0
- package/src/utils/trui-main-handlers.js +194 -0
- package/src/utils/trui-main-menu.js +235 -0
- package/src/utils/trui-nav-agents.js +178 -25
- package/src/utils/trui-nav-requirements.js +203 -27
- package/src/utils/trui-nav-settings.js +114 -1
- package/src/utils/trui-nav-specifications.js +44 -3
- package/src/utils/trui-navigation-backup.js +603 -0
- package/src/utils/trui-navigation.js +70 -228
- package/src/utils/trui-provider-health.js +274 -0
- package/src/utils/trui-provider-manager.js +376 -0
- package/src/utils/trui-quick-menu.js +25 -1
- package/src/utils/trui-req-actions-backup.js +507 -0
- package/src/utils/trui-req-actions.js +148 -216
- package/src/utils/trui-req-editor.js +170 -0
- package/src/utils/trui-req-file-ops.js +278 -0
- package/src/utils/trui-req-tree-old.js +719 -0
- package/src/utils/trui-req-tree.js +348 -627
- package/src/utils/trui-specifications.js +25 -7
- package/src/utils/trui-windsurf.js +231 -10
- package/src/utils/welcome-screen-extracted.js +2 -2
- 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;
|
|
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
|
-
|
|
20
|
-
function
|
|
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);
|
|
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
|
-
|
|
693
|
-
|
|
694
|
-
async
|
|
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)
|
package/src/utils/config.js
CHANGED
|
@@ -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(),
|
|
@@ -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:
|
|
213
|
-
{ name:
|
|
214
|
-
{ name:
|
|
215
|
-
{ name:
|
|
216
|
-
{ name:
|
|
217
|
-
{ name:
|
|
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:
|
|
223
|
-
{ name:
|
|
224
|
-
{ name:
|
|
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:
|
|
230
|
-
{ name:
|
|
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:
|
|
236
|
-
{ name:
|
|
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: `
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
}
|