vibecodingmachine-cli 1.0.5 → 1.0.6
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/.allnightai/REQUIREMENTS.md +11 -11
- package/.allnightai/temp/auto-status.json +6 -0
- package/.env +7 -0
- package/.eslintrc.js +16 -16
- package/README.md +85 -85
- package/bin/vibecodingmachine.js +274 -274
- package/jest.config.js +8 -8
- package/logs/audit/2025-11-07.jsonl +2 -2
- package/package.json +62 -62
- package/scripts/README.md +128 -128
- package/scripts/auto-start-wrapper.sh +92 -92
- package/scripts/postinstall.js +81 -81
- package/src/commands/auth.js +96 -96
- package/src/commands/auto-direct.js +1748 -1748
- package/src/commands/auto.js +4692 -4692
- package/src/commands/auto.js.bak +710 -710
- package/src/commands/ide.js +70 -70
- package/src/commands/repo.js +159 -159
- package/src/commands/requirements.js +161 -161
- package/src/commands/setup.js +91 -91
- package/src/commands/status.js +88 -88
- package/src/index.js +5 -5
- package/src/utils/auth.js +571 -577
- package/src/utils/auto-mode-ansi-ui.js +238 -238
- package/src/utils/auto-mode-simple-ui.js +161 -161
- package/src/utils/auto-mode-ui.js.bak.blessed +207 -207
- package/src/utils/auto-mode.js +65 -65
- package/src/utils/config.js +64 -64
- package/src/utils/interactive.js +3616 -3616
- package/src/utils/keyboard-handler.js +153 -152
- package/src/utils/logger.js +4 -4
- package/src/utils/persistent-header.js +116 -116
- package/src/utils/provider-registry.js +128 -128
- package/src/utils/status-card.js +120 -120
- package/src/utils/stdout-interceptor.js +127 -127
- package/tests/auto-mode.test.js +37 -37
- package/tests/config.test.js +34 -34
package/src/commands/auto.js.bak
CHANGED
|
@@ -1,710 +1,710 @@
|
|
|
1
|
-
const chalk = require('chalk');
|
|
2
|
-
const ora = require('ora');
|
|
3
|
-
const { AppleScriptManager, ClineCLIManager, logIDEMessage } = require('@allnightai/core');
|
|
4
|
-
const { getRepoPath, getAutoConfig, setAutoConfig } = require('../utils/config');
|
|
5
|
-
const { checkAutoModeStatus, startAutoMode, stopAutoMode } = require('../utils/auto-mode');
|
|
6
|
-
const logger = require('../utils/logger');
|
|
7
|
-
|
|
8
|
-
const DEFAULT_INSTRUCTION_TEXT = 'Follow INSTRUCTIONS.md from .allnightai directory. CRITICAL: You MUST work through ALL status stages (PREPARE → ACT → CLEAN UP → VERIFY → DONE) and set the status to DONE in the REQUIREMENTS file before stopping. DO NOT stop working until the "🚦 Current Status" section shows "DONE". The AllNightAI app is running in autonomous mode and depends on you completing the requirement fully.';
|
|
9
|
-
|
|
10
|
-
async function start(options) {
|
|
11
|
-
const spinner = ora('Starting autonomous mode...').start();
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
const repoPath = await getRepoPath();
|
|
15
|
-
if (!repoPath) {
|
|
16
|
-
spinner.fail('No repository configured');
|
|
17
|
-
console.log(chalk.gray('Run'), chalk.cyan('allnightai repo:set <path>'), chalk.gray('or'), chalk.cyan('allnightai repo:init'));
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const config = {
|
|
22
|
-
ide: options.ide || 'cline',
|
|
23
|
-
maxChats: options.maxChats || null,
|
|
24
|
-
neverStop: options.neverStop || false
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
await setAutoConfig(config);
|
|
28
|
-
await startAutoMode(repoPath, config);
|
|
29
|
-
|
|
30
|
-
// Send initial instruction to IDE or Cline CLI
|
|
31
|
-
const textToSend = options.text || DEFAULT_INSTRUCTION_TEXT;
|
|
32
|
-
let messageSent = false;
|
|
33
|
-
|
|
34
|
-
if (config.ide === 'cline') {
|
|
35
|
-
// Use Cline CLI
|
|
36
|
-
spinner.text = 'Checking Cline CLI installation...';
|
|
37
|
-
const clineManager = new ClineCLIManager();
|
|
38
|
-
|
|
39
|
-
if (!clineManager.isInstalled()) {
|
|
40
|
-
spinner.text = 'Installing Cline CLI...';
|
|
41
|
-
const installResult = await clineManager.install();
|
|
42
|
-
|
|
43
|
-
if (!installResult.success) {
|
|
44
|
-
spinner.fail('Failed to install Cline CLI');
|
|
45
|
-
console.log(chalk.red('\n✗ Error:'), installResult.error);
|
|
46
|
-
console.log(chalk.gray(' You can manually install with:'), chalk.cyan('npm install -g @yaegaki/cline-cli'));
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
spinner.succeed('Cline CLI installed successfully');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Check if Cline CLI is configured (with valid API key)
|
|
54
|
-
// This will also auto-sync keys from saved files if needed
|
|
55
|
-
const isConfigured = clineManager.isConfigured();
|
|
56
|
-
|
|
57
|
-
if (!isConfigured) {
|
|
58
|
-
spinner.stop();
|
|
59
|
-
console.log(chalk.cyan('\n🔐 Setting up API for Cline CLI'));
|
|
60
|
-
console.log(chalk.gray(' No valid API key found - setting up now...\n'));
|
|
61
|
-
|
|
62
|
-
// Prompt for provider selection
|
|
63
|
-
const readline = require('readline');
|
|
64
|
-
const inquirer = require('inquirer');
|
|
65
|
-
|
|
66
|
-
// Check if Hugging Face key exists, if not default to Hugging Face
|
|
67
|
-
const hasHuggingFaceKey = !!clineManager.getSavedHuggingFaceKey();
|
|
68
|
-
const hasAnthropicKey = !!clineManager.getSavedAnthropicKey();
|
|
69
|
-
|
|
70
|
-
// If no Hugging Face key exists, default to Hugging Face and auto-open browser
|
|
71
|
-
let provider = 'huggingface';
|
|
72
|
-
let autoOpenBrowser = false;
|
|
73
|
-
|
|
74
|
-
if (!hasHuggingFaceKey && !hasAnthropicKey) {
|
|
75
|
-
// No keys at all - default to Hugging Face and auto-open browser
|
|
76
|
-
provider = 'huggingface';
|
|
77
|
-
autoOpenBrowser = true;
|
|
78
|
-
console.log(chalk.cyan('\n🔐 Setting up API for Cline CLI'));
|
|
79
|
-
console.log(chalk.gray(' No API key found. Defaulting to Hugging Face (free, no credit card required).\n'));
|
|
80
|
-
} else {
|
|
81
|
-
// At least one key exists, ask user to choose
|
|
82
|
-
const { selectedProvider } = await inquirer.prompt([{
|
|
83
|
-
type: 'list',
|
|
84
|
-
name: 'selectedProvider',
|
|
85
|
-
message: 'Select API provider:',
|
|
86
|
-
choices: [
|
|
87
|
-
{ name: 'Hugging Face (Free - No credit card required)', value: 'huggingface' },
|
|
88
|
-
{ name: 'Anthropic Claude (Requires credit card)', value: 'anthropic' }
|
|
89
|
-
],
|
|
90
|
-
default: hasHuggingFaceKey ? 'huggingface' : 'anthropic'
|
|
91
|
-
}]);
|
|
92
|
-
provider = selectedProvider;
|
|
93
|
-
autoOpenBrowser = false;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
let apiKey = null;
|
|
97
|
-
let providerName = '';
|
|
98
|
-
let modelId = '';
|
|
99
|
-
let apiKeyUrl = '';
|
|
100
|
-
let apiKeyPrompt = '';
|
|
101
|
-
let savedKeyFile = '';
|
|
102
|
-
|
|
103
|
-
if (provider === 'huggingface') {
|
|
104
|
-
providerName = 'Hugging Face';
|
|
105
|
-
modelId = 'meta-llama/Meta-Llama-3.1-8B-Instruct';
|
|
106
|
-
apiKeyUrl = 'https://huggingface.co/settings/tokens';
|
|
107
|
-
apiKeyPrompt = 'Enter your Hugging Face Access Token (starts with "hf_"):';
|
|
108
|
-
savedKeyFile = 'huggingface-api-key.txt';
|
|
109
|
-
// Check for saved API key first (like Anthropic/Google)
|
|
110
|
-
apiKey = clineManager.getSavedHuggingFaceKey();
|
|
111
|
-
} else {
|
|
112
|
-
providerName = 'Anthropic';
|
|
113
|
-
modelId = 'claude-3-5-sonnet-20241022';
|
|
114
|
-
apiKeyUrl = 'https://console.anthropic.com/settings/keys';
|
|
115
|
-
apiKeyPrompt = 'Enter your Anthropic API key:';
|
|
116
|
-
savedKeyFile = 'anthropic-api-key.txt';
|
|
117
|
-
apiKey = clineManager.getSavedAnthropicKey();
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (!apiKey) {
|
|
121
|
-
// Display prominent instructions
|
|
122
|
-
console.log('\n╔══════════════════════════════════════════════════════════════════╗');
|
|
123
|
-
console.log('║ ║');
|
|
124
|
-
console.log(`║ ⚠️ SETTING UP ${providerName.toUpperCase().padEnd(25)} ⚠️ ║`);
|
|
125
|
-
console.log('║ ║');
|
|
126
|
-
console.log(`║ Your browser will open to the ${providerName} API Keys page. ║`);
|
|
127
|
-
console.log('║ ║');
|
|
128
|
-
|
|
129
|
-
if (provider === 'huggingface') {
|
|
130
|
-
console.log('║ ╔══════════════════════════════════════════════════════╗ ║');
|
|
131
|
-
console.log('║ ║ ✓ FREE - NO CREDIT CARD REQUIRED! ║ ║');
|
|
132
|
-
console.log('║ ╚══════════════════════════════════════════════════════╝ ║');
|
|
133
|
-
console.log('║ ║');
|
|
134
|
-
console.log('║ Your browser will open to Hugging Face Access Tokens page. ║');
|
|
135
|
-
console.log('║ ║');
|
|
136
|
-
console.log('║ ╔══════════════════════════════════════════════════════╗ ║');
|
|
137
|
-
console.log('║ ║ ✓ CREATE NEW ACCESS TOKEN ║ ║');
|
|
138
|
-
console.log('║ ╚══════════════════════════════════════════════════════╝ ║');
|
|
139
|
-
console.log('║ ║');
|
|
140
|
-
console.log('║ 1. Sign up/login to Hugging Face (free account) ║');
|
|
141
|
-
console.log('║ - Visit https://huggingface.co/join if you need to sign up ║');
|
|
142
|
-
console.log('║ 2. Navigate to Settings → Access Tokens ║');
|
|
143
|
-
console.log('║ 3. Click "New token" button ║');
|
|
144
|
-
console.log('║ 4. Give it a name (e.g., "AllNightAI") ║');
|
|
145
|
-
console.log('║ 5. Select "Read" permissions (or "Write" if needed) ║');
|
|
146
|
-
console.log('║ 6. Click "Generate token" ║');
|
|
147
|
-
console.log('║ 7. Copy the token that appears (starts with "hf_") ║');
|
|
148
|
-
console.log('║ 8. Paste it below when prompted ║');
|
|
149
|
-
console.log('║ ║');
|
|
150
|
-
console.log('║ Note: The token will only be shown once - copy it immediately! ║');
|
|
151
|
-
} else {
|
|
152
|
-
console.log('║ ╔══════════════════════════════════════════════════════╗ ║');
|
|
153
|
-
console.log('║ ║ ✓ SELECT WORKSPACE & CREATE API KEY ║ ║');
|
|
154
|
-
console.log('║ ╚══════════════════════════════════════════════════════╝ ║');
|
|
155
|
-
console.log('║ ║');
|
|
156
|
-
console.log('║ 1. Select a workspace (choose "Default" or create new) ║');
|
|
157
|
-
console.log('║ 2. Click "+ Create Key" button ║');
|
|
158
|
-
console.log('║ 3. Give it a name (e.g., "AllNightAI") ║');
|
|
159
|
-
console.log('║ 4. Copy the API key that appears ║');
|
|
160
|
-
console.log('║ 5. Paste it below when prompted ║');
|
|
161
|
-
console.log('║ ║');
|
|
162
|
-
console.log('║ Note: Anthropic requires a credit card for API access. ║');
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
console.log('║ ║');
|
|
166
|
-
console.log('╚══════════════════════════════════════════════════════════════════╝');
|
|
167
|
-
|
|
168
|
-
if (autoOpenBrowser) {
|
|
169
|
-
console.log('\nOpening browser now...\n');
|
|
170
|
-
} else {
|
|
171
|
-
console.log('\nOpening browser in 3 seconds...\n');
|
|
172
|
-
// Give user time to read the instructions
|
|
173
|
-
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Open browser to API keys page
|
|
177
|
-
const { execSync } = require('child_process');
|
|
178
|
-
try {
|
|
179
|
-
const platform = require('os').platform();
|
|
180
|
-
|
|
181
|
-
if (platform === 'darwin') {
|
|
182
|
-
execSync(`open "${apiKeyUrl}"`);
|
|
183
|
-
} else if (platform === 'win32') {
|
|
184
|
-
execSync(`start "${apiKeyUrl}"`);
|
|
185
|
-
} else {
|
|
186
|
-
execSync(`xdg-open "${apiKeyUrl}"`);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
console.log(chalk.green(`✓ Browser opened to ${providerName} API Keys page\n`));
|
|
190
|
-
} catch (error) {
|
|
191
|
-
console.log(chalk.yellow('⚠ Could not open browser automatically'));
|
|
192
|
-
console.log(chalk.gray(` Please visit: ${apiKeyUrl}\n`));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Prompt for API key
|
|
196
|
-
const rl = readline.createInterface({
|
|
197
|
-
input: process.stdin,
|
|
198
|
-
output: process.stdout
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
apiKey = await new Promise((resolve) => {
|
|
202
|
-
rl.question(chalk.cyan(apiKeyPrompt + ' '), (answer) => {
|
|
203
|
-
rl.close();
|
|
204
|
-
resolve(answer.trim());
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
if (!apiKey || apiKey.length === 0) {
|
|
209
|
-
console.log(chalk.red('\n✗ Error: Access Token is required'));
|
|
210
|
-
console.log(chalk.gray(` Get an Access Token at: ${apiKeyUrl}`));
|
|
211
|
-
console.log(chalk.gray(` The token should start with "hf_"`));
|
|
212
|
-
process.exit(1);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Validate Hugging Face token format (starts with "hf_")
|
|
216
|
-
if (provider === 'huggingface' && !apiKey.startsWith('hf_')) {
|
|
217
|
-
console.log(chalk.yellow('\n⚠ Warning: Hugging Face tokens typically start with "hf_"'));
|
|
218
|
-
console.log(chalk.gray(' Please double-check that you copied the correct token.'));
|
|
219
|
-
console.log(chalk.gray(' If this is correct, you can continue.\n'));
|
|
220
|
-
// Don't exit - let user continue if they're sure
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Validate API key by making a test API call
|
|
224
|
-
spinner.start(`Validating ${providerName} API key...`);
|
|
225
|
-
const validationResult = await clineManager.validateApiKey(provider, apiKey);
|
|
226
|
-
|
|
227
|
-
if (!validationResult.valid) {
|
|
228
|
-
spinner.fail(`Invalid ${providerName} API key`);
|
|
229
|
-
console.log(chalk.red(`\n✗ Error: ${validationResult.error}`));
|
|
230
|
-
console.log(chalk.yellow('\n⚠ The API key you provided is invalid or expired.'));
|
|
231
|
-
console.log(chalk.gray(` Please get a new API key from: ${apiKeyUrl}`));
|
|
232
|
-
if (provider === 'huggingface') {
|
|
233
|
-
console.log(chalk.gray(' Make sure your token starts with "hf_" and has the correct permissions.'));
|
|
234
|
-
}
|
|
235
|
-
process.exit(1);
|
|
236
|
-
} else {
|
|
237
|
-
if (validationResult.warning) {
|
|
238
|
-
spinner.warn(`Validation incomplete: ${validationResult.warning}`);
|
|
239
|
-
console.log(chalk.yellow(` ⚠ ${validationResult.warning}`));
|
|
240
|
-
console.log(chalk.gray(' Continuing anyway - key format looks correct.'));
|
|
241
|
-
} else {
|
|
242
|
-
spinner.succeed(`${providerName} API key is valid`);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Save API key
|
|
247
|
-
let saveResult = false;
|
|
248
|
-
if (provider === 'huggingface') {
|
|
249
|
-
saveResult = clineManager.saveHuggingFaceKey(apiKey);
|
|
250
|
-
} else {
|
|
251
|
-
saveResult = clineManager.saveAnthropicKey(apiKey);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (!saveResult) {
|
|
255
|
-
console.log(chalk.yellow('⚠ Failed to save API key (will use for this session only)'));
|
|
256
|
-
} else {
|
|
257
|
-
console.log(chalk.green(`✓ API key saved to ~/.allnightai/${savedKeyFile}`));
|
|
258
|
-
}
|
|
259
|
-
} else {
|
|
260
|
-
console.log(chalk.green(`✓ Using saved API key from ~/.allnightai/${savedKeyFile}`));
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Initialize Cline CLI
|
|
264
|
-
spinner.start('Initializing Cline CLI...');
|
|
265
|
-
const initResult = await clineManager.init();
|
|
266
|
-
|
|
267
|
-
if (!initResult.success) {
|
|
268
|
-
spinner.fail('Failed to initialize Cline CLI');
|
|
269
|
-
console.log(chalk.red('\n✗ Error:'), initResult.error);
|
|
270
|
-
process.exit(1);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Configure Cline CLI with selected provider
|
|
274
|
-
spinner.text = `Configuring Cline CLI with ${providerName} API...`;
|
|
275
|
-
let configResult;
|
|
276
|
-
|
|
277
|
-
if (provider === 'huggingface') {
|
|
278
|
-
configResult = await clineManager.configureWithHuggingFace(apiKey, modelId);
|
|
279
|
-
} else {
|
|
280
|
-
configResult = await clineManager.configureWithAnthropic(apiKey);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (!configResult.success) {
|
|
284
|
-
spinner.fail('Failed to configure Cline CLI');
|
|
285
|
-
console.log(chalk.red('\n✗ Error:'), configResult.error);
|
|
286
|
-
process.exit(1);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
spinner.succeed('Cline CLI configured successfully');
|
|
290
|
-
console.log(chalk.green('\n✓ Setup complete!'));
|
|
291
|
-
console.log(chalk.gray(' Provider:'), chalk.cyan(providerName));
|
|
292
|
-
console.log(chalk.gray(' Model:'), chalk.cyan(modelId));
|
|
293
|
-
console.log(chalk.gray(' API Key:'), chalk.cyan(apiKey.substring(0, 20) + '...'));
|
|
294
|
-
console.log(chalk.gray(' Cline config:'), chalk.cyan(configResult.configPath));
|
|
295
|
-
console.log(chalk.gray('\n Starting Cline CLI now...\n'));
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Check for invalid key markers before running
|
|
299
|
-
const fs = require('fs');
|
|
300
|
-
const path = require('path');
|
|
301
|
-
const os = require('os');
|
|
302
|
-
const allnightDir = path.join(os.homedir(), '.allnightai');
|
|
303
|
-
const anthropicInvalidMarker = path.join(allnightDir, '.anthropic-key-invalid');
|
|
304
|
-
const huggingfaceInvalidMarker = path.join(allnightDir, '.huggingface-key-invalid');
|
|
305
|
-
|
|
306
|
-
if (fs.existsSync(anthropicInvalidMarker) || fs.existsSync(huggingfaceInvalidMarker)) {
|
|
307
|
-
spinner.fail('API key was marked as invalid');
|
|
308
|
-
console.log(chalk.red('\n✗ Error: Previous API key failed authentication'));
|
|
309
|
-
console.log(chalk.yellow('\n⚠ You need to set up a new API key.'));
|
|
310
|
-
console.log(chalk.gray(' Please restart auto mode to be prompted for a new API key.'));
|
|
311
|
-
console.log(chalk.gray(' You can choose Hugging Face (free, no credit card) or Anthropic.'));
|
|
312
|
-
process.exit(1);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Double-check configuration before running (in case it changed)
|
|
316
|
-
// This will also trigger auto-sync if key is in saved file but not config
|
|
317
|
-
const finalConfigCheck = clineManager.isConfigured();
|
|
318
|
-
if (!finalConfigCheck) {
|
|
319
|
-
spinner.fail('Cline CLI is not properly configured');
|
|
320
|
-
console.log(chalk.red('\n✗ Error: No valid API key found'));
|
|
321
|
-
console.log(chalk.yellow('\n⚠ Cline CLI config exists but API key is missing or invalid.'));
|
|
322
|
-
console.log(chalk.gray(' Please stop auto mode and restart it to set up your API key.'));
|
|
323
|
-
console.log(chalk.gray(' You will be prompted to configure Hugging Face or Anthropic API.'));
|
|
324
|
-
process.exit(1);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Pre-flight API key validation - test the key before starting Cline CLI
|
|
328
|
-
// This catches invalid keys immediately instead of waiting for 15-second timeout
|
|
329
|
-
spinner.text = 'Validating API key before starting...';
|
|
330
|
-
const configPath = path.join(os.homedir(), '.cline_cli', 'cline_cli_settings.json');
|
|
331
|
-
if (fs.existsSync(configPath)) {
|
|
332
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
333
|
-
const apiProvider = config.globalState?.apiProvider;
|
|
334
|
-
let apiKey = null;
|
|
335
|
-
|
|
336
|
-
if (apiProvider === 'anthropic') {
|
|
337
|
-
apiKey = config.globalState?.anthropicApiKey || process.env.ANTHROPIC_API_KEY || clineManager.getSavedAnthropicKey();
|
|
338
|
-
} else if (apiProvider === 'huggingface') {
|
|
339
|
-
apiKey = config.globalState?.huggingFaceApiKey || process.env.HUGGING_FACE_API_KEY || clineManager.getSavedHuggingFaceKey();
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (apiKey) {
|
|
343
|
-
const validationResult = await clineManager.validateApiKey(apiProvider, apiKey);
|
|
344
|
-
|
|
345
|
-
if (!validationResult.valid) {
|
|
346
|
-
spinner.fail('API key validation failed');
|
|
347
|
-
console.log(chalk.red(`\n✗ Error: ${validationResult.error}`));
|
|
348
|
-
console.log(chalk.yellow('\n⚠ Your API key is invalid or expired.'));
|
|
349
|
-
|
|
350
|
-
// Mark key as invalid immediately
|
|
351
|
-
try {
|
|
352
|
-
if (apiProvider === 'anthropic') {
|
|
353
|
-
delete config.globalState.anthropicApiKey;
|
|
354
|
-
delete config.globalState.apiProvider;
|
|
355
|
-
delete config.globalState.apiModelId;
|
|
356
|
-
const savedKeyFile = path.join(allnightDir, 'anthropic-api-key.txt');
|
|
357
|
-
if (fs.existsSync(savedKeyFile)) {
|
|
358
|
-
fs.unlinkSync(savedKeyFile);
|
|
359
|
-
}
|
|
360
|
-
const invalidMarker = path.join(allnightDir, '.anthropic-key-invalid');
|
|
361
|
-
fs.writeFileSync(invalidMarker, new Date().toISOString());
|
|
362
|
-
} else if (apiProvider === 'huggingface') {
|
|
363
|
-
delete config.globalState.huggingFaceApiKey;
|
|
364
|
-
delete config.globalState.apiProvider;
|
|
365
|
-
delete config.globalState.apiModelId;
|
|
366
|
-
const savedKeyFile = path.join(allnightDir, 'huggingface-api-key.txt');
|
|
367
|
-
if (fs.existsSync(savedKeyFile)) {
|
|
368
|
-
fs.unlinkSync(savedKeyFile);
|
|
369
|
-
}
|
|
370
|
-
const invalidMarker = path.join(allnightDir, '.huggingface-key-invalid');
|
|
371
|
-
fs.writeFileSync(invalidMarker, new Date().toISOString());
|
|
372
|
-
}
|
|
373
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 4));
|
|
374
|
-
} catch (error) {
|
|
375
|
-
// Non-fatal
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
console.log(chalk.gray('\n Invalid key has been removed. Restart auto mode to set up a new API key.'));
|
|
379
|
-
console.log(chalk.gray(' The browser will automatically open to get a new key.'));
|
|
380
|
-
process.exit(1);
|
|
381
|
-
} else {
|
|
382
|
-
if (validationResult.warning) {
|
|
383
|
-
spinner.warn(`Validation incomplete: ${validationResult.warning}`);
|
|
384
|
-
} else {
|
|
385
|
-
spinner.succeed('API key validated successfully');
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Force re-sync just before running to ensure key is in config file
|
|
392
|
-
// This handles edge cases where sync might have failed
|
|
393
|
-
try {
|
|
394
|
-
const configPath = path.join(os.homedir(), '.cline_cli', 'cline_cli_settings.json');
|
|
395
|
-
if (fs.existsSync(configPath)) {
|
|
396
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
397
|
-
const apiProvider = config.globalState?.apiProvider;
|
|
398
|
-
|
|
399
|
-
if (apiProvider === 'anthropic' && !config.globalState?.anthropicApiKey) {
|
|
400
|
-
const savedKey = clineManager.getSavedAnthropicKey();
|
|
401
|
-
if (savedKey && savedKey.trim().length > 0) {
|
|
402
|
-
config.globalState.anthropicApiKey = savedKey;
|
|
403
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 4));
|
|
404
|
-
console.log(chalk.green('✓ Synced API key to Cline CLI config before starting'));
|
|
405
|
-
}
|
|
406
|
-
} else if (apiProvider === 'huggingface' && !config.globalState?.huggingFaceApiKey) {
|
|
407
|
-
const savedKey = clineManager.getSavedHuggingFaceKey();
|
|
408
|
-
if (savedKey && savedKey.trim().length > 0) {
|
|
409
|
-
config.globalState.huggingFaceApiKey = savedKey;
|
|
410
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 4));
|
|
411
|
-
console.log(chalk.green('✓ Synced API key to Cline CLI config before starting'));
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
} catch (error) {
|
|
416
|
-
// Non-fatal - just log and continue
|
|
417
|
-
console.log(chalk.yellow(' ⚠ Could not pre-sync API key:', error.message));
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
spinner.text = 'Executing Cline CLI...';
|
|
421
|
-
console.log(chalk.gray('\n Running: cline-cli task "') + chalk.cyan(textToSend.substring(0, 60) + '...') + chalk.gray('" --workspace ') + chalk.cyan(repoPath) + chalk.gray(' --full-auto'));
|
|
422
|
-
|
|
423
|
-
// Log the command to audit log
|
|
424
|
-
logIDEMessage('cline', textToSend);
|
|
425
|
-
|
|
426
|
-
// Execute Cline CLI in background (non-blocking)
|
|
427
|
-
try {
|
|
428
|
-
console.log(chalk.gray('\n─────────────────────────────────────────────────────────'));
|
|
429
|
-
console.log(chalk.bold.cyan('Cline CLI Output:'));
|
|
430
|
-
console.log(chalk.gray('─────────────────────────────────────────────────────────\n'));
|
|
431
|
-
|
|
432
|
-
// Track error output for authentication detection
|
|
433
|
-
let errorOutputBuffer = '';
|
|
434
|
-
|
|
435
|
-
const proc = clineManager.runInBackground(
|
|
436
|
-
textToSend,
|
|
437
|
-
repoPath,
|
|
438
|
-
(output) => {
|
|
439
|
-
// Output to console in real-time (tee to stdout)
|
|
440
|
-
process.stdout.write(chalk.gray(output));
|
|
441
|
-
// Also log via logger for audit trail
|
|
442
|
-
if (output.trim()) {
|
|
443
|
-
logger.info('[Cline]', output.trim());
|
|
444
|
-
}
|
|
445
|
-
},
|
|
446
|
-
(error) => {
|
|
447
|
-
// Collect error output for analysis
|
|
448
|
-
errorOutputBuffer += error;
|
|
449
|
-
|
|
450
|
-
// Output errors to console in real-time
|
|
451
|
-
process.stderr.write(chalk.red(error));
|
|
452
|
-
// Also log via logger for audit trail
|
|
453
|
-
if (error.trim()) {
|
|
454
|
-
logger.error('[Cline Error]', error.trim());
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
);
|
|
458
|
-
|
|
459
|
-
spinner.succeed('Cline CLI started in background');
|
|
460
|
-
console.log(chalk.green('✓ Cline CLI is now running autonomously'));
|
|
461
|
-
console.log(chalk.gray(' Process ID:'), chalk.cyan(proc.pid));
|
|
462
|
-
console.log(chalk.gray(' Output is displayed below'));
|
|
463
|
-
console.log(chalk.gray(' Press Ctrl+C to stop\n'));
|
|
464
|
-
messageSent = true;
|
|
465
|
-
|
|
466
|
-
// Track stderr output to detect authentication errors
|
|
467
|
-
let errorOutput = '';
|
|
468
|
-
|
|
469
|
-
if (onError) {
|
|
470
|
-
const originalOnError = onError;
|
|
471
|
-
onError = (error) => {
|
|
472
|
-
errorOutput += error.toString();
|
|
473
|
-
originalOnError(error);
|
|
474
|
-
};
|
|
475
|
-
} else {
|
|
476
|
-
proc.stderr.on('data', (data) => {
|
|
477
|
-
errorOutput += data.toString();
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// Handle process exit
|
|
482
|
-
proc.on('close', (code) => {
|
|
483
|
-
console.log(chalk.gray('\n─────────────────────────────────────────────────────────'));
|
|
484
|
-
if (code === 0) {
|
|
485
|
-
console.log(chalk.green('✓ Cline CLI completed successfully'));
|
|
486
|
-
logger.info(chalk.green('Cline CLI completed successfully'));
|
|
487
|
-
} else {
|
|
488
|
-
console.log(chalk.red(`✗ Cline CLI exited with code ${code}`));
|
|
489
|
-
logger.error(chalk.red(`Cline CLI exited with code ${code}`));
|
|
490
|
-
|
|
491
|
-
// Detect authentication/API key errors
|
|
492
|
-
const lowerErrorOutput = errorOutputBuffer.toLowerCase();
|
|
493
|
-
const isAuthError = code === 255 ||
|
|
494
|
-
code === 1 ||
|
|
495
|
-
lowerErrorOutput.includes('maximum retries reached') ||
|
|
496
|
-
lowerErrorOutput.includes('authentication') ||
|
|
497
|
-
lowerErrorOutput.includes('api key') ||
|
|
498
|
-
lowerErrorOutput.includes('unauthorized') ||
|
|
499
|
-
lowerErrorOutput.includes('401') ||
|
|
500
|
-
lowerErrorOutput.includes('403') ||
|
|
501
|
-
lowerErrorOutput.includes('invalid api') ||
|
|
502
|
-
lowerErrorOutput.includes('expired');
|
|
503
|
-
|
|
504
|
-
if (isAuthError) {
|
|
505
|
-
console.log(chalk.yellow('\n⚠ Cline CLI failed - authentication error detected.'));
|
|
506
|
-
console.log(chalk.yellow(' This usually means your API key is missing, invalid, or expired.'));
|
|
507
|
-
|
|
508
|
-
// Clear invalid API key from BOTH config file AND saved file to force reconfiguration
|
|
509
|
-
try {
|
|
510
|
-
const fs = require('fs');
|
|
511
|
-
const path = require('path');
|
|
512
|
-
const os = require('os');
|
|
513
|
-
const configPath = path.join(os.homedir(), '.cline_cli', 'cline_cli_settings.json');
|
|
514
|
-
const allnightDir = path.join(os.homedir(), '.allnightai');
|
|
515
|
-
|
|
516
|
-
if (fs.existsSync(configPath)) {
|
|
517
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
518
|
-
const apiProvider = config.globalState?.apiProvider;
|
|
519
|
-
|
|
520
|
-
if (apiProvider === 'anthropic') {
|
|
521
|
-
// Clear the invalid key AND provider from config to force reconfiguration
|
|
522
|
-
delete config.globalState.anthropicApiKey;
|
|
523
|
-
delete config.globalState.apiProvider;
|
|
524
|
-
delete config.globalState.apiModelId;
|
|
525
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 4));
|
|
526
|
-
|
|
527
|
-
// Also clear saved key file and create invalid marker
|
|
528
|
-
const savedKeyFile = path.join(allnightDir, 'anthropic-api-key.txt');
|
|
529
|
-
if (fs.existsSync(savedKeyFile)) {
|
|
530
|
-
fs.unlinkSync(savedKeyFile);
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// Create marker file to prevent re-syncing invalid key
|
|
534
|
-
const invalidMarker = path.join(allnightDir, '.anthropic-key-invalid');
|
|
535
|
-
fs.writeFileSync(invalidMarker, new Date().toISOString());
|
|
536
|
-
|
|
537
|
-
console.log(chalk.gray('\n ✓ Cleared invalid API key from config and saved files'));
|
|
538
|
-
console.log(chalk.gray(' Next time you start auto mode, you will be prompted to set up a new API key.'));
|
|
539
|
-
} else if (apiProvider === 'huggingface') {
|
|
540
|
-
// Clear the invalid key AND provider from config to force reconfiguration
|
|
541
|
-
delete config.globalState.huggingFaceApiKey;
|
|
542
|
-
delete config.globalState.apiProvider;
|
|
543
|
-
delete config.globalState.apiModelId;
|
|
544
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 4));
|
|
545
|
-
|
|
546
|
-
// Also clear saved key file and create invalid marker
|
|
547
|
-
const savedKeyFile = path.join(allnightDir, 'huggingface-api-key.txt');
|
|
548
|
-
if (fs.existsSync(savedKeyFile)) {
|
|
549
|
-
fs.unlinkSync(savedKeyFile);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// Create marker file to prevent re-syncing invalid key
|
|
553
|
-
const invalidMarker = path.join(allnightDir, '.huggingface-key-invalid');
|
|
554
|
-
fs.writeFileSync(invalidMarker, new Date().toISOString());
|
|
555
|
-
|
|
556
|
-
console.log(chalk.gray('\n ✓ Cleared invalid API key from config and saved files'));
|
|
557
|
-
console.log(chalk.gray(' Next time you start auto mode, you will be prompted to set up a new API key.'));
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
} catch (clearError) {
|
|
561
|
-
// Non-fatal
|
|
562
|
-
console.log(chalk.yellow(' ⚠ Could not clear invalid config:', clearError.message));
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
console.log(chalk.gray('\n To fix this:'));
|
|
566
|
-
console.log(chalk.gray(' 1. Stop auto mode (press Ctrl+C or select "Auto Mode: Stop")'));
|
|
567
|
-
console.log(chalk.gray(' 2. Start auto mode again'));
|
|
568
|
-
console.log(chalk.gray(' 3. You will be prompted to set up a new API key'));
|
|
569
|
-
console.log(chalk.gray(' 4. Choose Hugging Face (free, no credit card) or Anthropic'));
|
|
570
|
-
console.log(chalk.gray(' 5. Enter a valid API key'));
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
console.log(chalk.gray('─────────────────────────────────────────────────────────\n'));
|
|
574
|
-
});
|
|
575
|
-
} catch (error) {
|
|
576
|
-
spinner.fail('Failed to start Cline CLI');
|
|
577
|
-
console.log(chalk.red('\n✗ Error:'), error.message);
|
|
578
|
-
}
|
|
579
|
-
} else {
|
|
580
|
-
// Use AppleScript for GUI IDEs
|
|
581
|
-
spinner.text = 'Sending initial message to IDE...';
|
|
582
|
-
const asManager = new AppleScriptManager();
|
|
583
|
-
|
|
584
|
-
try {
|
|
585
|
-
const result = await asManager.sendText(textToSend, config.ide);
|
|
586
|
-
|
|
587
|
-
if (!result.success) {
|
|
588
|
-
logIDEMessage(config.ide, `[FAILED] ${textToSend}`);
|
|
589
|
-
spinner.warn('Auto mode started but failed to send initial message to IDE');
|
|
590
|
-
console.log(chalk.yellow('\n⚠ Warning:'), result.error || 'Failed to send message');
|
|
591
|
-
console.log(chalk.gray(' Consider using'), chalk.cyan('Cline IDE'), chalk.gray('instead (set with'), chalk.cyan('allnightai'), chalk.gray('menu)'));
|
|
592
|
-
} else {
|
|
593
|
-
logIDEMessage(config.ide, textToSend);
|
|
594
|
-
messageSent = true;
|
|
595
|
-
spinner.succeed('Autonomous mode started and initial message sent');
|
|
596
|
-
console.log(chalk.green('\n✓ AllNightAI is now coding autonomously'));
|
|
597
|
-
}
|
|
598
|
-
} catch (sendError) {
|
|
599
|
-
logIDEMessage(config.ide, `[ERROR] ${textToSend}`);
|
|
600
|
-
spinner.warn('Auto mode started but failed to send initial message');
|
|
601
|
-
console.log(chalk.yellow('\n⚠ Warning:'), sendError.message);
|
|
602
|
-
console.log(chalk.gray(' Consider using'), chalk.cyan('Cline IDE'), chalk.gray('instead'));
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
// Format IDE name for display
|
|
607
|
-
const formatIDEName = (ide) => {
|
|
608
|
-
const ideNames = {
|
|
609
|
-
'cline': 'Cline IDE',
|
|
610
|
-
'cursor': 'Cursor',
|
|
611
|
-
'vscode': 'VS Code',
|
|
612
|
-
'windsurf': 'Windsurf'
|
|
613
|
-
};
|
|
614
|
-
return ideNames[ide] || ide;
|
|
615
|
-
};
|
|
616
|
-
console.log(chalk.gray(' IDE:'), chalk.cyan(formatIDEName(config.ide)));
|
|
617
|
-
|
|
618
|
-
if (config.neverStop) {
|
|
619
|
-
console.log(chalk.gray(' Mode:'), chalk.cyan('Never stop'));
|
|
620
|
-
} else if (config.maxChats) {
|
|
621
|
-
console.log(chalk.gray(' Max chats:'), chalk.cyan(config.maxChats));
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
console.log(chalk.gray('\nUse'), chalk.cyan('allnightai auto:stop'), chalk.gray('to stop'));
|
|
625
|
-
console.log(chalk.gray('Use'), chalk.cyan('allnightai status'), chalk.gray('to monitor progress'));
|
|
626
|
-
} catch (error) {
|
|
627
|
-
spinner.fail('Failed to start autonomous mode');
|
|
628
|
-
console.error(chalk.red('Error:'), error.message);
|
|
629
|
-
process.exit(1);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
async function stop() {
|
|
634
|
-
const spinner = ora('Stopping autonomous mode...').start();
|
|
635
|
-
|
|
636
|
-
try {
|
|
637
|
-
await stopAutoMode();
|
|
638
|
-
spinner.succeed('Autonomous mode stopped');
|
|
639
|
-
} catch (error) {
|
|
640
|
-
spinner.fail('Failed to stop autonomous mode');
|
|
641
|
-
console.error(chalk.red('Error:'), error.message);
|
|
642
|
-
process.exit(1);
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
async function status() {
|
|
647
|
-
try {
|
|
648
|
-
const autoStatus = await checkAutoModeStatus();
|
|
649
|
-
const config = await getAutoConfig();
|
|
650
|
-
|
|
651
|
-
console.log(chalk.bold('\nAuto Mode Status\n'));
|
|
652
|
-
|
|
653
|
-
if (autoStatus.running) {
|
|
654
|
-
console.log(chalk.green('●'), 'Running');
|
|
655
|
-
console.log(chalk.gray(' IDE:'), chalk.cyan(config.ide || 'cursor'));
|
|
656
|
-
console.log(chalk.gray(' Chat count:'), chalk.cyan(autoStatus.chatCount || 0));
|
|
657
|
-
|
|
658
|
-
if (config.neverStop) {
|
|
659
|
-
console.log(chalk.gray(' Mode:'), chalk.cyan('Never stop'));
|
|
660
|
-
} else if (config.maxChats) {
|
|
661
|
-
console.log(chalk.gray(' Max chats:'), chalk.cyan(config.maxChats));
|
|
662
|
-
const remaining = config.maxChats - (autoStatus.chatCount || 0);
|
|
663
|
-
console.log(chalk.gray(' Remaining:'), chalk.cyan(remaining));
|
|
664
|
-
}
|
|
665
|
-
} else {
|
|
666
|
-
console.log(chalk.gray('○'), 'Not running');
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
console.log();
|
|
670
|
-
} catch (error) {
|
|
671
|
-
console.error(chalk.red('Error checking status:'), error.message);
|
|
672
|
-
process.exit(1);
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
async function config(options) {
|
|
677
|
-
try {
|
|
678
|
-
const currentConfig = await getAutoConfig();
|
|
679
|
-
const newConfig = { ...currentConfig };
|
|
680
|
-
|
|
681
|
-
if (options.maxChats !== undefined) {
|
|
682
|
-
newConfig.maxChats = options.maxChats;
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
if (options.neverStop !== undefined) {
|
|
686
|
-
newConfig.neverStop = options.neverStop;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
await setAutoConfig(newConfig);
|
|
690
|
-
|
|
691
|
-
console.log(chalk.green('✓'), 'Auto mode configuration updated');
|
|
692
|
-
console.log(chalk.gray('\nCurrent settings:'));
|
|
693
|
-
|
|
694
|
-
if (newConfig.neverStop) {
|
|
695
|
-
console.log(chalk.gray(' Mode:'), chalk.cyan('Never stop'));
|
|
696
|
-
} else if (newConfig.maxChats) {
|
|
697
|
-
console.log(chalk.gray(' Max chats:'), chalk.cyan(newConfig.maxChats));
|
|
698
|
-
}
|
|
699
|
-
} catch (error) {
|
|
700
|
-
console.error(chalk.red('Error updating configuration:'), error.message);
|
|
701
|
-
process.exit(1);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
module.exports = {
|
|
706
|
-
start,
|
|
707
|
-
stop,
|
|
708
|
-
status,
|
|
709
|
-
config
|
|
710
|
-
};
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const { AppleScriptManager, ClineCLIManager, logIDEMessage } = require('@allnightai/core');
|
|
4
|
+
const { getRepoPath, getAutoConfig, setAutoConfig } = require('../utils/config');
|
|
5
|
+
const { checkAutoModeStatus, startAutoMode, stopAutoMode } = require('../utils/auto-mode');
|
|
6
|
+
const logger = require('../utils/logger');
|
|
7
|
+
|
|
8
|
+
const DEFAULT_INSTRUCTION_TEXT = 'Follow INSTRUCTIONS.md from .allnightai directory. CRITICAL: You MUST work through ALL status stages (PREPARE → ACT → CLEAN UP → VERIFY → DONE) and set the status to DONE in the REQUIREMENTS file before stopping. DO NOT stop working until the "🚦 Current Status" section shows "DONE". The AllNightAI app is running in autonomous mode and depends on you completing the requirement fully.';
|
|
9
|
+
|
|
10
|
+
async function start(options) {
|
|
11
|
+
const spinner = ora('Starting autonomous mode...').start();
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const repoPath = await getRepoPath();
|
|
15
|
+
if (!repoPath) {
|
|
16
|
+
spinner.fail('No repository configured');
|
|
17
|
+
console.log(chalk.gray('Run'), chalk.cyan('allnightai repo:set <path>'), chalk.gray('or'), chalk.cyan('allnightai repo:init'));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const config = {
|
|
22
|
+
ide: options.ide || 'cline',
|
|
23
|
+
maxChats: options.maxChats || null,
|
|
24
|
+
neverStop: options.neverStop || false
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
await setAutoConfig(config);
|
|
28
|
+
await startAutoMode(repoPath, config);
|
|
29
|
+
|
|
30
|
+
// Send initial instruction to IDE or Cline CLI
|
|
31
|
+
const textToSend = options.text || DEFAULT_INSTRUCTION_TEXT;
|
|
32
|
+
let messageSent = false;
|
|
33
|
+
|
|
34
|
+
if (config.ide === 'cline') {
|
|
35
|
+
// Use Cline CLI
|
|
36
|
+
spinner.text = 'Checking Cline CLI installation...';
|
|
37
|
+
const clineManager = new ClineCLIManager();
|
|
38
|
+
|
|
39
|
+
if (!clineManager.isInstalled()) {
|
|
40
|
+
spinner.text = 'Installing Cline CLI...';
|
|
41
|
+
const installResult = await clineManager.install();
|
|
42
|
+
|
|
43
|
+
if (!installResult.success) {
|
|
44
|
+
spinner.fail('Failed to install Cline CLI');
|
|
45
|
+
console.log(chalk.red('\n✗ Error:'), installResult.error);
|
|
46
|
+
console.log(chalk.gray(' You can manually install with:'), chalk.cyan('npm install -g @yaegaki/cline-cli'));
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
spinner.succeed('Cline CLI installed successfully');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check if Cline CLI is configured (with valid API key)
|
|
54
|
+
// This will also auto-sync keys from saved files if needed
|
|
55
|
+
const isConfigured = clineManager.isConfigured();
|
|
56
|
+
|
|
57
|
+
if (!isConfigured) {
|
|
58
|
+
spinner.stop();
|
|
59
|
+
console.log(chalk.cyan('\n🔐 Setting up API for Cline CLI'));
|
|
60
|
+
console.log(chalk.gray(' No valid API key found - setting up now...\n'));
|
|
61
|
+
|
|
62
|
+
// Prompt for provider selection
|
|
63
|
+
const readline = require('readline');
|
|
64
|
+
const inquirer = require('inquirer');
|
|
65
|
+
|
|
66
|
+
// Check if Hugging Face key exists, if not default to Hugging Face
|
|
67
|
+
const hasHuggingFaceKey = !!clineManager.getSavedHuggingFaceKey();
|
|
68
|
+
const hasAnthropicKey = !!clineManager.getSavedAnthropicKey();
|
|
69
|
+
|
|
70
|
+
// If no Hugging Face key exists, default to Hugging Face and auto-open browser
|
|
71
|
+
let provider = 'huggingface';
|
|
72
|
+
let autoOpenBrowser = false;
|
|
73
|
+
|
|
74
|
+
if (!hasHuggingFaceKey && !hasAnthropicKey) {
|
|
75
|
+
// No keys at all - default to Hugging Face and auto-open browser
|
|
76
|
+
provider = 'huggingface';
|
|
77
|
+
autoOpenBrowser = true;
|
|
78
|
+
console.log(chalk.cyan('\n🔐 Setting up API for Cline CLI'));
|
|
79
|
+
console.log(chalk.gray(' No API key found. Defaulting to Hugging Face (free, no credit card required).\n'));
|
|
80
|
+
} else {
|
|
81
|
+
// At least one key exists, ask user to choose
|
|
82
|
+
const { selectedProvider } = await inquirer.prompt([{
|
|
83
|
+
type: 'list',
|
|
84
|
+
name: 'selectedProvider',
|
|
85
|
+
message: 'Select API provider:',
|
|
86
|
+
choices: [
|
|
87
|
+
{ name: 'Hugging Face (Free - No credit card required)', value: 'huggingface' },
|
|
88
|
+
{ name: 'Anthropic Claude (Requires credit card)', value: 'anthropic' }
|
|
89
|
+
],
|
|
90
|
+
default: hasHuggingFaceKey ? 'huggingface' : 'anthropic'
|
|
91
|
+
}]);
|
|
92
|
+
provider = selectedProvider;
|
|
93
|
+
autoOpenBrowser = false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let apiKey = null;
|
|
97
|
+
let providerName = '';
|
|
98
|
+
let modelId = '';
|
|
99
|
+
let apiKeyUrl = '';
|
|
100
|
+
let apiKeyPrompt = '';
|
|
101
|
+
let savedKeyFile = '';
|
|
102
|
+
|
|
103
|
+
if (provider === 'huggingface') {
|
|
104
|
+
providerName = 'Hugging Face';
|
|
105
|
+
modelId = 'meta-llama/Meta-Llama-3.1-8B-Instruct';
|
|
106
|
+
apiKeyUrl = 'https://huggingface.co/settings/tokens';
|
|
107
|
+
apiKeyPrompt = 'Enter your Hugging Face Access Token (starts with "hf_"):';
|
|
108
|
+
savedKeyFile = 'huggingface-api-key.txt';
|
|
109
|
+
// Check for saved API key first (like Anthropic/Google)
|
|
110
|
+
apiKey = clineManager.getSavedHuggingFaceKey();
|
|
111
|
+
} else {
|
|
112
|
+
providerName = 'Anthropic';
|
|
113
|
+
modelId = 'claude-3-5-sonnet-20241022';
|
|
114
|
+
apiKeyUrl = 'https://console.anthropic.com/settings/keys';
|
|
115
|
+
apiKeyPrompt = 'Enter your Anthropic API key:';
|
|
116
|
+
savedKeyFile = 'anthropic-api-key.txt';
|
|
117
|
+
apiKey = clineManager.getSavedAnthropicKey();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!apiKey) {
|
|
121
|
+
// Display prominent instructions
|
|
122
|
+
console.log('\n╔══════════════════════════════════════════════════════════════════╗');
|
|
123
|
+
console.log('║ ║');
|
|
124
|
+
console.log(`║ ⚠️ SETTING UP ${providerName.toUpperCase().padEnd(25)} ⚠️ ║`);
|
|
125
|
+
console.log('║ ║');
|
|
126
|
+
console.log(`║ Your browser will open to the ${providerName} API Keys page. ║`);
|
|
127
|
+
console.log('║ ║');
|
|
128
|
+
|
|
129
|
+
if (provider === 'huggingface') {
|
|
130
|
+
console.log('║ ╔══════════════════════════════════════════════════════╗ ║');
|
|
131
|
+
console.log('║ ║ ✓ FREE - NO CREDIT CARD REQUIRED! ║ ║');
|
|
132
|
+
console.log('║ ╚══════════════════════════════════════════════════════╝ ║');
|
|
133
|
+
console.log('║ ║');
|
|
134
|
+
console.log('║ Your browser will open to Hugging Face Access Tokens page. ║');
|
|
135
|
+
console.log('║ ║');
|
|
136
|
+
console.log('║ ╔══════════════════════════════════════════════════════╗ ║');
|
|
137
|
+
console.log('║ ║ ✓ CREATE NEW ACCESS TOKEN ║ ║');
|
|
138
|
+
console.log('║ ╚══════════════════════════════════════════════════════╝ ║');
|
|
139
|
+
console.log('║ ║');
|
|
140
|
+
console.log('║ 1. Sign up/login to Hugging Face (free account) ║');
|
|
141
|
+
console.log('║ - Visit https://huggingface.co/join if you need to sign up ║');
|
|
142
|
+
console.log('║ 2. Navigate to Settings → Access Tokens ║');
|
|
143
|
+
console.log('║ 3. Click "New token" button ║');
|
|
144
|
+
console.log('║ 4. Give it a name (e.g., "AllNightAI") ║');
|
|
145
|
+
console.log('║ 5. Select "Read" permissions (or "Write" if needed) ║');
|
|
146
|
+
console.log('║ 6. Click "Generate token" ║');
|
|
147
|
+
console.log('║ 7. Copy the token that appears (starts with "hf_") ║');
|
|
148
|
+
console.log('║ 8. Paste it below when prompted ║');
|
|
149
|
+
console.log('║ ║');
|
|
150
|
+
console.log('║ Note: The token will only be shown once - copy it immediately! ║');
|
|
151
|
+
} else {
|
|
152
|
+
console.log('║ ╔══════════════════════════════════════════════════════╗ ║');
|
|
153
|
+
console.log('║ ║ ✓ SELECT WORKSPACE & CREATE API KEY ║ ║');
|
|
154
|
+
console.log('║ ╚══════════════════════════════════════════════════════╝ ║');
|
|
155
|
+
console.log('║ ║');
|
|
156
|
+
console.log('║ 1. Select a workspace (choose "Default" or create new) ║');
|
|
157
|
+
console.log('║ 2. Click "+ Create Key" button ║');
|
|
158
|
+
console.log('║ 3. Give it a name (e.g., "AllNightAI") ║');
|
|
159
|
+
console.log('║ 4. Copy the API key that appears ║');
|
|
160
|
+
console.log('║ 5. Paste it below when prompted ║');
|
|
161
|
+
console.log('║ ║');
|
|
162
|
+
console.log('║ Note: Anthropic requires a credit card for API access. ║');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
console.log('║ ║');
|
|
166
|
+
console.log('╚══════════════════════════════════════════════════════════════════╝');
|
|
167
|
+
|
|
168
|
+
if (autoOpenBrowser) {
|
|
169
|
+
console.log('\nOpening browser now...\n');
|
|
170
|
+
} else {
|
|
171
|
+
console.log('\nOpening browser in 3 seconds...\n');
|
|
172
|
+
// Give user time to read the instructions
|
|
173
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Open browser to API keys page
|
|
177
|
+
const { execSync } = require('child_process');
|
|
178
|
+
try {
|
|
179
|
+
const platform = require('os').platform();
|
|
180
|
+
|
|
181
|
+
if (platform === 'darwin') {
|
|
182
|
+
execSync(`open "${apiKeyUrl}"`);
|
|
183
|
+
} else if (platform === 'win32') {
|
|
184
|
+
execSync(`start "${apiKeyUrl}"`);
|
|
185
|
+
} else {
|
|
186
|
+
execSync(`xdg-open "${apiKeyUrl}"`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log(chalk.green(`✓ Browser opened to ${providerName} API Keys page\n`));
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.log(chalk.yellow('⚠ Could not open browser automatically'));
|
|
192
|
+
console.log(chalk.gray(` Please visit: ${apiKeyUrl}\n`));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Prompt for API key
|
|
196
|
+
const rl = readline.createInterface({
|
|
197
|
+
input: process.stdin,
|
|
198
|
+
output: process.stdout
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
apiKey = await new Promise((resolve) => {
|
|
202
|
+
rl.question(chalk.cyan(apiKeyPrompt + ' '), (answer) => {
|
|
203
|
+
rl.close();
|
|
204
|
+
resolve(answer.trim());
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
if (!apiKey || apiKey.length === 0) {
|
|
209
|
+
console.log(chalk.red('\n✗ Error: Access Token is required'));
|
|
210
|
+
console.log(chalk.gray(` Get an Access Token at: ${apiKeyUrl}`));
|
|
211
|
+
console.log(chalk.gray(` The token should start with "hf_"`));
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Validate Hugging Face token format (starts with "hf_")
|
|
216
|
+
if (provider === 'huggingface' && !apiKey.startsWith('hf_')) {
|
|
217
|
+
console.log(chalk.yellow('\n⚠ Warning: Hugging Face tokens typically start with "hf_"'));
|
|
218
|
+
console.log(chalk.gray(' Please double-check that you copied the correct token.'));
|
|
219
|
+
console.log(chalk.gray(' If this is correct, you can continue.\n'));
|
|
220
|
+
// Don't exit - let user continue if they're sure
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Validate API key by making a test API call
|
|
224
|
+
spinner.start(`Validating ${providerName} API key...`);
|
|
225
|
+
const validationResult = await clineManager.validateApiKey(provider, apiKey);
|
|
226
|
+
|
|
227
|
+
if (!validationResult.valid) {
|
|
228
|
+
spinner.fail(`Invalid ${providerName} API key`);
|
|
229
|
+
console.log(chalk.red(`\n✗ Error: ${validationResult.error}`));
|
|
230
|
+
console.log(chalk.yellow('\n⚠ The API key you provided is invalid or expired.'));
|
|
231
|
+
console.log(chalk.gray(` Please get a new API key from: ${apiKeyUrl}`));
|
|
232
|
+
if (provider === 'huggingface') {
|
|
233
|
+
console.log(chalk.gray(' Make sure your token starts with "hf_" and has the correct permissions.'));
|
|
234
|
+
}
|
|
235
|
+
process.exit(1);
|
|
236
|
+
} else {
|
|
237
|
+
if (validationResult.warning) {
|
|
238
|
+
spinner.warn(`Validation incomplete: ${validationResult.warning}`);
|
|
239
|
+
console.log(chalk.yellow(` ⚠ ${validationResult.warning}`));
|
|
240
|
+
console.log(chalk.gray(' Continuing anyway - key format looks correct.'));
|
|
241
|
+
} else {
|
|
242
|
+
spinner.succeed(`${providerName} API key is valid`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Save API key
|
|
247
|
+
let saveResult = false;
|
|
248
|
+
if (provider === 'huggingface') {
|
|
249
|
+
saveResult = clineManager.saveHuggingFaceKey(apiKey);
|
|
250
|
+
} else {
|
|
251
|
+
saveResult = clineManager.saveAnthropicKey(apiKey);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!saveResult) {
|
|
255
|
+
console.log(chalk.yellow('⚠ Failed to save API key (will use for this session only)'));
|
|
256
|
+
} else {
|
|
257
|
+
console.log(chalk.green(`✓ API key saved to ~/.allnightai/${savedKeyFile}`));
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
console.log(chalk.green(`✓ Using saved API key from ~/.allnightai/${savedKeyFile}`));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Initialize Cline CLI
|
|
264
|
+
spinner.start('Initializing Cline CLI...');
|
|
265
|
+
const initResult = await clineManager.init();
|
|
266
|
+
|
|
267
|
+
if (!initResult.success) {
|
|
268
|
+
spinner.fail('Failed to initialize Cline CLI');
|
|
269
|
+
console.log(chalk.red('\n✗ Error:'), initResult.error);
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Configure Cline CLI with selected provider
|
|
274
|
+
spinner.text = `Configuring Cline CLI with ${providerName} API...`;
|
|
275
|
+
let configResult;
|
|
276
|
+
|
|
277
|
+
if (provider === 'huggingface') {
|
|
278
|
+
configResult = await clineManager.configureWithHuggingFace(apiKey, modelId);
|
|
279
|
+
} else {
|
|
280
|
+
configResult = await clineManager.configureWithAnthropic(apiKey);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!configResult.success) {
|
|
284
|
+
spinner.fail('Failed to configure Cline CLI');
|
|
285
|
+
console.log(chalk.red('\n✗ Error:'), configResult.error);
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
spinner.succeed('Cline CLI configured successfully');
|
|
290
|
+
console.log(chalk.green('\n✓ Setup complete!'));
|
|
291
|
+
console.log(chalk.gray(' Provider:'), chalk.cyan(providerName));
|
|
292
|
+
console.log(chalk.gray(' Model:'), chalk.cyan(modelId));
|
|
293
|
+
console.log(chalk.gray(' API Key:'), chalk.cyan(apiKey.substring(0, 20) + '...'));
|
|
294
|
+
console.log(chalk.gray(' Cline config:'), chalk.cyan(configResult.configPath));
|
|
295
|
+
console.log(chalk.gray('\n Starting Cline CLI now...\n'));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Check for invalid key markers before running
|
|
299
|
+
const fs = require('fs');
|
|
300
|
+
const path = require('path');
|
|
301
|
+
const os = require('os');
|
|
302
|
+
const allnightDir = path.join(os.homedir(), '.allnightai');
|
|
303
|
+
const anthropicInvalidMarker = path.join(allnightDir, '.anthropic-key-invalid');
|
|
304
|
+
const huggingfaceInvalidMarker = path.join(allnightDir, '.huggingface-key-invalid');
|
|
305
|
+
|
|
306
|
+
if (fs.existsSync(anthropicInvalidMarker) || fs.existsSync(huggingfaceInvalidMarker)) {
|
|
307
|
+
spinner.fail('API key was marked as invalid');
|
|
308
|
+
console.log(chalk.red('\n✗ Error: Previous API key failed authentication'));
|
|
309
|
+
console.log(chalk.yellow('\n⚠ You need to set up a new API key.'));
|
|
310
|
+
console.log(chalk.gray(' Please restart auto mode to be prompted for a new API key.'));
|
|
311
|
+
console.log(chalk.gray(' You can choose Hugging Face (free, no credit card) or Anthropic.'));
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Double-check configuration before running (in case it changed)
|
|
316
|
+
// This will also trigger auto-sync if key is in saved file but not config
|
|
317
|
+
const finalConfigCheck = clineManager.isConfigured();
|
|
318
|
+
if (!finalConfigCheck) {
|
|
319
|
+
spinner.fail('Cline CLI is not properly configured');
|
|
320
|
+
console.log(chalk.red('\n✗ Error: No valid API key found'));
|
|
321
|
+
console.log(chalk.yellow('\n⚠ Cline CLI config exists but API key is missing or invalid.'));
|
|
322
|
+
console.log(chalk.gray(' Please stop auto mode and restart it to set up your API key.'));
|
|
323
|
+
console.log(chalk.gray(' You will be prompted to configure Hugging Face or Anthropic API.'));
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Pre-flight API key validation - test the key before starting Cline CLI
|
|
328
|
+
// This catches invalid keys immediately instead of waiting for 15-second timeout
|
|
329
|
+
spinner.text = 'Validating API key before starting...';
|
|
330
|
+
const configPath = path.join(os.homedir(), '.cline_cli', 'cline_cli_settings.json');
|
|
331
|
+
if (fs.existsSync(configPath)) {
|
|
332
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
333
|
+
const apiProvider = config.globalState?.apiProvider;
|
|
334
|
+
let apiKey = null;
|
|
335
|
+
|
|
336
|
+
if (apiProvider === 'anthropic') {
|
|
337
|
+
apiKey = config.globalState?.anthropicApiKey || process.env.ANTHROPIC_API_KEY || clineManager.getSavedAnthropicKey();
|
|
338
|
+
} else if (apiProvider === 'huggingface') {
|
|
339
|
+
apiKey = config.globalState?.huggingFaceApiKey || process.env.HUGGING_FACE_API_KEY || clineManager.getSavedHuggingFaceKey();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (apiKey) {
|
|
343
|
+
const validationResult = await clineManager.validateApiKey(apiProvider, apiKey);
|
|
344
|
+
|
|
345
|
+
if (!validationResult.valid) {
|
|
346
|
+
spinner.fail('API key validation failed');
|
|
347
|
+
console.log(chalk.red(`\n✗ Error: ${validationResult.error}`));
|
|
348
|
+
console.log(chalk.yellow('\n⚠ Your API key is invalid or expired.'));
|
|
349
|
+
|
|
350
|
+
// Mark key as invalid immediately
|
|
351
|
+
try {
|
|
352
|
+
if (apiProvider === 'anthropic') {
|
|
353
|
+
delete config.globalState.anthropicApiKey;
|
|
354
|
+
delete config.globalState.apiProvider;
|
|
355
|
+
delete config.globalState.apiModelId;
|
|
356
|
+
const savedKeyFile = path.join(allnightDir, 'anthropic-api-key.txt');
|
|
357
|
+
if (fs.existsSync(savedKeyFile)) {
|
|
358
|
+
fs.unlinkSync(savedKeyFile);
|
|
359
|
+
}
|
|
360
|
+
const invalidMarker = path.join(allnightDir, '.anthropic-key-invalid');
|
|
361
|
+
fs.writeFileSync(invalidMarker, new Date().toISOString());
|
|
362
|
+
} else if (apiProvider === 'huggingface') {
|
|
363
|
+
delete config.globalState.huggingFaceApiKey;
|
|
364
|
+
delete config.globalState.apiProvider;
|
|
365
|
+
delete config.globalState.apiModelId;
|
|
366
|
+
const savedKeyFile = path.join(allnightDir, 'huggingface-api-key.txt');
|
|
367
|
+
if (fs.existsSync(savedKeyFile)) {
|
|
368
|
+
fs.unlinkSync(savedKeyFile);
|
|
369
|
+
}
|
|
370
|
+
const invalidMarker = path.join(allnightDir, '.huggingface-key-invalid');
|
|
371
|
+
fs.writeFileSync(invalidMarker, new Date().toISOString());
|
|
372
|
+
}
|
|
373
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 4));
|
|
374
|
+
} catch (error) {
|
|
375
|
+
// Non-fatal
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
console.log(chalk.gray('\n Invalid key has been removed. Restart auto mode to set up a new API key.'));
|
|
379
|
+
console.log(chalk.gray(' The browser will automatically open to get a new key.'));
|
|
380
|
+
process.exit(1);
|
|
381
|
+
} else {
|
|
382
|
+
if (validationResult.warning) {
|
|
383
|
+
spinner.warn(`Validation incomplete: ${validationResult.warning}`);
|
|
384
|
+
} else {
|
|
385
|
+
spinner.succeed('API key validated successfully');
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Force re-sync just before running to ensure key is in config file
|
|
392
|
+
// This handles edge cases where sync might have failed
|
|
393
|
+
try {
|
|
394
|
+
const configPath = path.join(os.homedir(), '.cline_cli', 'cline_cli_settings.json');
|
|
395
|
+
if (fs.existsSync(configPath)) {
|
|
396
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
397
|
+
const apiProvider = config.globalState?.apiProvider;
|
|
398
|
+
|
|
399
|
+
if (apiProvider === 'anthropic' && !config.globalState?.anthropicApiKey) {
|
|
400
|
+
const savedKey = clineManager.getSavedAnthropicKey();
|
|
401
|
+
if (savedKey && savedKey.trim().length > 0) {
|
|
402
|
+
config.globalState.anthropicApiKey = savedKey;
|
|
403
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 4));
|
|
404
|
+
console.log(chalk.green('✓ Synced API key to Cline CLI config before starting'));
|
|
405
|
+
}
|
|
406
|
+
} else if (apiProvider === 'huggingface' && !config.globalState?.huggingFaceApiKey) {
|
|
407
|
+
const savedKey = clineManager.getSavedHuggingFaceKey();
|
|
408
|
+
if (savedKey && savedKey.trim().length > 0) {
|
|
409
|
+
config.globalState.huggingFaceApiKey = savedKey;
|
|
410
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 4));
|
|
411
|
+
console.log(chalk.green('✓ Synced API key to Cline CLI config before starting'));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
} catch (error) {
|
|
416
|
+
// Non-fatal - just log and continue
|
|
417
|
+
console.log(chalk.yellow(' ⚠ Could not pre-sync API key:', error.message));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
spinner.text = 'Executing Cline CLI...';
|
|
421
|
+
console.log(chalk.gray('\n Running: cline-cli task "') + chalk.cyan(textToSend.substring(0, 60) + '...') + chalk.gray('" --workspace ') + chalk.cyan(repoPath) + chalk.gray(' --full-auto'));
|
|
422
|
+
|
|
423
|
+
// Log the command to audit log
|
|
424
|
+
logIDEMessage('cline', textToSend);
|
|
425
|
+
|
|
426
|
+
// Execute Cline CLI in background (non-blocking)
|
|
427
|
+
try {
|
|
428
|
+
console.log(chalk.gray('\n─────────────────────────────────────────────────────────'));
|
|
429
|
+
console.log(chalk.bold.cyan('Cline CLI Output:'));
|
|
430
|
+
console.log(chalk.gray('─────────────────────────────────────────────────────────\n'));
|
|
431
|
+
|
|
432
|
+
// Track error output for authentication detection
|
|
433
|
+
let errorOutputBuffer = '';
|
|
434
|
+
|
|
435
|
+
const proc = clineManager.runInBackground(
|
|
436
|
+
textToSend,
|
|
437
|
+
repoPath,
|
|
438
|
+
(output) => {
|
|
439
|
+
// Output to console in real-time (tee to stdout)
|
|
440
|
+
process.stdout.write(chalk.gray(output));
|
|
441
|
+
// Also log via logger for audit trail
|
|
442
|
+
if (output.trim()) {
|
|
443
|
+
logger.info('[Cline]', output.trim());
|
|
444
|
+
}
|
|
445
|
+
},
|
|
446
|
+
(error) => {
|
|
447
|
+
// Collect error output for analysis
|
|
448
|
+
errorOutputBuffer += error;
|
|
449
|
+
|
|
450
|
+
// Output errors to console in real-time
|
|
451
|
+
process.stderr.write(chalk.red(error));
|
|
452
|
+
// Also log via logger for audit trail
|
|
453
|
+
if (error.trim()) {
|
|
454
|
+
logger.error('[Cline Error]', error.trim());
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
spinner.succeed('Cline CLI started in background');
|
|
460
|
+
console.log(chalk.green('✓ Cline CLI is now running autonomously'));
|
|
461
|
+
console.log(chalk.gray(' Process ID:'), chalk.cyan(proc.pid));
|
|
462
|
+
console.log(chalk.gray(' Output is displayed below'));
|
|
463
|
+
console.log(chalk.gray(' Press Ctrl+C to stop\n'));
|
|
464
|
+
messageSent = true;
|
|
465
|
+
|
|
466
|
+
// Track stderr output to detect authentication errors
|
|
467
|
+
let errorOutput = '';
|
|
468
|
+
|
|
469
|
+
if (onError) {
|
|
470
|
+
const originalOnError = onError;
|
|
471
|
+
onError = (error) => {
|
|
472
|
+
errorOutput += error.toString();
|
|
473
|
+
originalOnError(error);
|
|
474
|
+
};
|
|
475
|
+
} else {
|
|
476
|
+
proc.stderr.on('data', (data) => {
|
|
477
|
+
errorOutput += data.toString();
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Handle process exit
|
|
482
|
+
proc.on('close', (code) => {
|
|
483
|
+
console.log(chalk.gray('\n─────────────────────────────────────────────────────────'));
|
|
484
|
+
if (code === 0) {
|
|
485
|
+
console.log(chalk.green('✓ Cline CLI completed successfully'));
|
|
486
|
+
logger.info(chalk.green('Cline CLI completed successfully'));
|
|
487
|
+
} else {
|
|
488
|
+
console.log(chalk.red(`✗ Cline CLI exited with code ${code}`));
|
|
489
|
+
logger.error(chalk.red(`Cline CLI exited with code ${code}`));
|
|
490
|
+
|
|
491
|
+
// Detect authentication/API key errors
|
|
492
|
+
const lowerErrorOutput = errorOutputBuffer.toLowerCase();
|
|
493
|
+
const isAuthError = code === 255 ||
|
|
494
|
+
code === 1 ||
|
|
495
|
+
lowerErrorOutput.includes('maximum retries reached') ||
|
|
496
|
+
lowerErrorOutput.includes('authentication') ||
|
|
497
|
+
lowerErrorOutput.includes('api key') ||
|
|
498
|
+
lowerErrorOutput.includes('unauthorized') ||
|
|
499
|
+
lowerErrorOutput.includes('401') ||
|
|
500
|
+
lowerErrorOutput.includes('403') ||
|
|
501
|
+
lowerErrorOutput.includes('invalid api') ||
|
|
502
|
+
lowerErrorOutput.includes('expired');
|
|
503
|
+
|
|
504
|
+
if (isAuthError) {
|
|
505
|
+
console.log(chalk.yellow('\n⚠ Cline CLI failed - authentication error detected.'));
|
|
506
|
+
console.log(chalk.yellow(' This usually means your API key is missing, invalid, or expired.'));
|
|
507
|
+
|
|
508
|
+
// Clear invalid API key from BOTH config file AND saved file to force reconfiguration
|
|
509
|
+
try {
|
|
510
|
+
const fs = require('fs');
|
|
511
|
+
const path = require('path');
|
|
512
|
+
const os = require('os');
|
|
513
|
+
const configPath = path.join(os.homedir(), '.cline_cli', 'cline_cli_settings.json');
|
|
514
|
+
const allnightDir = path.join(os.homedir(), '.allnightai');
|
|
515
|
+
|
|
516
|
+
if (fs.existsSync(configPath)) {
|
|
517
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
518
|
+
const apiProvider = config.globalState?.apiProvider;
|
|
519
|
+
|
|
520
|
+
if (apiProvider === 'anthropic') {
|
|
521
|
+
// Clear the invalid key AND provider from config to force reconfiguration
|
|
522
|
+
delete config.globalState.anthropicApiKey;
|
|
523
|
+
delete config.globalState.apiProvider;
|
|
524
|
+
delete config.globalState.apiModelId;
|
|
525
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 4));
|
|
526
|
+
|
|
527
|
+
// Also clear saved key file and create invalid marker
|
|
528
|
+
const savedKeyFile = path.join(allnightDir, 'anthropic-api-key.txt');
|
|
529
|
+
if (fs.existsSync(savedKeyFile)) {
|
|
530
|
+
fs.unlinkSync(savedKeyFile);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Create marker file to prevent re-syncing invalid key
|
|
534
|
+
const invalidMarker = path.join(allnightDir, '.anthropic-key-invalid');
|
|
535
|
+
fs.writeFileSync(invalidMarker, new Date().toISOString());
|
|
536
|
+
|
|
537
|
+
console.log(chalk.gray('\n ✓ Cleared invalid API key from config and saved files'));
|
|
538
|
+
console.log(chalk.gray(' Next time you start auto mode, you will be prompted to set up a new API key.'));
|
|
539
|
+
} else if (apiProvider === 'huggingface') {
|
|
540
|
+
// Clear the invalid key AND provider from config to force reconfiguration
|
|
541
|
+
delete config.globalState.huggingFaceApiKey;
|
|
542
|
+
delete config.globalState.apiProvider;
|
|
543
|
+
delete config.globalState.apiModelId;
|
|
544
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 4));
|
|
545
|
+
|
|
546
|
+
// Also clear saved key file and create invalid marker
|
|
547
|
+
const savedKeyFile = path.join(allnightDir, 'huggingface-api-key.txt');
|
|
548
|
+
if (fs.existsSync(savedKeyFile)) {
|
|
549
|
+
fs.unlinkSync(savedKeyFile);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Create marker file to prevent re-syncing invalid key
|
|
553
|
+
const invalidMarker = path.join(allnightDir, '.huggingface-key-invalid');
|
|
554
|
+
fs.writeFileSync(invalidMarker, new Date().toISOString());
|
|
555
|
+
|
|
556
|
+
console.log(chalk.gray('\n ✓ Cleared invalid API key from config and saved files'));
|
|
557
|
+
console.log(chalk.gray(' Next time you start auto mode, you will be prompted to set up a new API key.'));
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
} catch (clearError) {
|
|
561
|
+
// Non-fatal
|
|
562
|
+
console.log(chalk.yellow(' ⚠ Could not clear invalid config:', clearError.message));
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
console.log(chalk.gray('\n To fix this:'));
|
|
566
|
+
console.log(chalk.gray(' 1. Stop auto mode (press Ctrl+C or select "Auto Mode: Stop")'));
|
|
567
|
+
console.log(chalk.gray(' 2. Start auto mode again'));
|
|
568
|
+
console.log(chalk.gray(' 3. You will be prompted to set up a new API key'));
|
|
569
|
+
console.log(chalk.gray(' 4. Choose Hugging Face (free, no credit card) or Anthropic'));
|
|
570
|
+
console.log(chalk.gray(' 5. Enter a valid API key'));
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
console.log(chalk.gray('─────────────────────────────────────────────────────────\n'));
|
|
574
|
+
});
|
|
575
|
+
} catch (error) {
|
|
576
|
+
spinner.fail('Failed to start Cline CLI');
|
|
577
|
+
console.log(chalk.red('\n✗ Error:'), error.message);
|
|
578
|
+
}
|
|
579
|
+
} else {
|
|
580
|
+
// Use AppleScript for GUI IDEs
|
|
581
|
+
spinner.text = 'Sending initial message to IDE...';
|
|
582
|
+
const asManager = new AppleScriptManager();
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
const result = await asManager.sendText(textToSend, config.ide);
|
|
586
|
+
|
|
587
|
+
if (!result.success) {
|
|
588
|
+
logIDEMessage(config.ide, `[FAILED] ${textToSend}`);
|
|
589
|
+
spinner.warn('Auto mode started but failed to send initial message to IDE');
|
|
590
|
+
console.log(chalk.yellow('\n⚠ Warning:'), result.error || 'Failed to send message');
|
|
591
|
+
console.log(chalk.gray(' Consider using'), chalk.cyan('Cline IDE'), chalk.gray('instead (set with'), chalk.cyan('allnightai'), chalk.gray('menu)'));
|
|
592
|
+
} else {
|
|
593
|
+
logIDEMessage(config.ide, textToSend);
|
|
594
|
+
messageSent = true;
|
|
595
|
+
spinner.succeed('Autonomous mode started and initial message sent');
|
|
596
|
+
console.log(chalk.green('\n✓ AllNightAI is now coding autonomously'));
|
|
597
|
+
}
|
|
598
|
+
} catch (sendError) {
|
|
599
|
+
logIDEMessage(config.ide, `[ERROR] ${textToSend}`);
|
|
600
|
+
spinner.warn('Auto mode started but failed to send initial message');
|
|
601
|
+
console.log(chalk.yellow('\n⚠ Warning:'), sendError.message);
|
|
602
|
+
console.log(chalk.gray(' Consider using'), chalk.cyan('Cline IDE'), chalk.gray('instead'));
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Format IDE name for display
|
|
607
|
+
const formatIDEName = (ide) => {
|
|
608
|
+
const ideNames = {
|
|
609
|
+
'cline': 'Cline IDE',
|
|
610
|
+
'cursor': 'Cursor',
|
|
611
|
+
'vscode': 'VS Code',
|
|
612
|
+
'windsurf': 'Windsurf'
|
|
613
|
+
};
|
|
614
|
+
return ideNames[ide] || ide;
|
|
615
|
+
};
|
|
616
|
+
console.log(chalk.gray(' IDE:'), chalk.cyan(formatIDEName(config.ide)));
|
|
617
|
+
|
|
618
|
+
if (config.neverStop) {
|
|
619
|
+
console.log(chalk.gray(' Mode:'), chalk.cyan('Never stop'));
|
|
620
|
+
} else if (config.maxChats) {
|
|
621
|
+
console.log(chalk.gray(' Max chats:'), chalk.cyan(config.maxChats));
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
console.log(chalk.gray('\nUse'), chalk.cyan('allnightai auto:stop'), chalk.gray('to stop'));
|
|
625
|
+
console.log(chalk.gray('Use'), chalk.cyan('allnightai status'), chalk.gray('to monitor progress'));
|
|
626
|
+
} catch (error) {
|
|
627
|
+
spinner.fail('Failed to start autonomous mode');
|
|
628
|
+
console.error(chalk.red('Error:'), error.message);
|
|
629
|
+
process.exit(1);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
async function stop() {
|
|
634
|
+
const spinner = ora('Stopping autonomous mode...').start();
|
|
635
|
+
|
|
636
|
+
try {
|
|
637
|
+
await stopAutoMode();
|
|
638
|
+
spinner.succeed('Autonomous mode stopped');
|
|
639
|
+
} catch (error) {
|
|
640
|
+
spinner.fail('Failed to stop autonomous mode');
|
|
641
|
+
console.error(chalk.red('Error:'), error.message);
|
|
642
|
+
process.exit(1);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
async function status() {
|
|
647
|
+
try {
|
|
648
|
+
const autoStatus = await checkAutoModeStatus();
|
|
649
|
+
const config = await getAutoConfig();
|
|
650
|
+
|
|
651
|
+
console.log(chalk.bold('\nAuto Mode Status\n'));
|
|
652
|
+
|
|
653
|
+
if (autoStatus.running) {
|
|
654
|
+
console.log(chalk.green('●'), 'Running');
|
|
655
|
+
console.log(chalk.gray(' IDE:'), chalk.cyan(config.ide || 'cursor'));
|
|
656
|
+
console.log(chalk.gray(' Chat count:'), chalk.cyan(autoStatus.chatCount || 0));
|
|
657
|
+
|
|
658
|
+
if (config.neverStop) {
|
|
659
|
+
console.log(chalk.gray(' Mode:'), chalk.cyan('Never stop'));
|
|
660
|
+
} else if (config.maxChats) {
|
|
661
|
+
console.log(chalk.gray(' Max chats:'), chalk.cyan(config.maxChats));
|
|
662
|
+
const remaining = config.maxChats - (autoStatus.chatCount || 0);
|
|
663
|
+
console.log(chalk.gray(' Remaining:'), chalk.cyan(remaining));
|
|
664
|
+
}
|
|
665
|
+
} else {
|
|
666
|
+
console.log(chalk.gray('○'), 'Not running');
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
console.log();
|
|
670
|
+
} catch (error) {
|
|
671
|
+
console.error(chalk.red('Error checking status:'), error.message);
|
|
672
|
+
process.exit(1);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
async function config(options) {
|
|
677
|
+
try {
|
|
678
|
+
const currentConfig = await getAutoConfig();
|
|
679
|
+
const newConfig = { ...currentConfig };
|
|
680
|
+
|
|
681
|
+
if (options.maxChats !== undefined) {
|
|
682
|
+
newConfig.maxChats = options.maxChats;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (options.neverStop !== undefined) {
|
|
686
|
+
newConfig.neverStop = options.neverStop;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
await setAutoConfig(newConfig);
|
|
690
|
+
|
|
691
|
+
console.log(chalk.green('✓'), 'Auto mode configuration updated');
|
|
692
|
+
console.log(chalk.gray('\nCurrent settings:'));
|
|
693
|
+
|
|
694
|
+
if (newConfig.neverStop) {
|
|
695
|
+
console.log(chalk.gray(' Mode:'), chalk.cyan('Never stop'));
|
|
696
|
+
} else if (newConfig.maxChats) {
|
|
697
|
+
console.log(chalk.gray(' Max chats:'), chalk.cyan(newConfig.maxChats));
|
|
698
|
+
}
|
|
699
|
+
} catch (error) {
|
|
700
|
+
console.error(chalk.red('Error updating configuration:'), error.message);
|
|
701
|
+
process.exit(1);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
module.exports = {
|
|
706
|
+
start,
|
|
707
|
+
stop,
|
|
708
|
+
status,
|
|
709
|
+
config
|
|
710
|
+
};
|