maiass 5.9.5 → 5.9.7
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/lib/commit.js +173 -89
- package/lib/maiass-variables.js +1 -0
- package/lib/secure-storage.js +161 -0
- package/package.json +1 -1
package/lib/commit.js
CHANGED
|
@@ -13,6 +13,51 @@ import { logCommit } from './devlog.js';
|
|
|
13
13
|
import colors from './colors.js';
|
|
14
14
|
import chalk from 'chalk';
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Simple spinner for AI API calls
|
|
18
|
+
*/
|
|
19
|
+
class Spinner {
|
|
20
|
+
constructor(message = 'Working') {
|
|
21
|
+
this.message = message;
|
|
22
|
+
this.frames = ['\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f'];
|
|
23
|
+
this.frameIndex = 0;
|
|
24
|
+
this.intervalId = null;
|
|
25
|
+
this.isSpinning = false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
start() {
|
|
29
|
+
if (this.isSpinning) return;
|
|
30
|
+
this.isSpinning = true;
|
|
31
|
+
|
|
32
|
+
// Hide cursor
|
|
33
|
+
process.stderr.write('\x1B[?25l');
|
|
34
|
+
|
|
35
|
+
this.intervalId = setInterval(() => {
|
|
36
|
+
const frame = this.frames[this.frameIndex];
|
|
37
|
+
process.stderr.write(`\r${colors.BYellow(frame)} ${this.message}...`);
|
|
38
|
+
this.frameIndex = (this.frameIndex + 1) % this.frames.length;
|
|
39
|
+
}, 100);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
stop(success = true) {
|
|
43
|
+
if (!this.isSpinning) return;
|
|
44
|
+
this.isSpinning = false;
|
|
45
|
+
|
|
46
|
+
if (this.intervalId) {
|
|
47
|
+
clearInterval(this.intervalId);
|
|
48
|
+
this.intervalId = null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Clear line and show cursor
|
|
52
|
+
process.stderr.write('\r' + ' '.repeat(100) + '\r');
|
|
53
|
+
process.stderr.write('\x1B[?25h');
|
|
54
|
+
|
|
55
|
+
if (success) {
|
|
56
|
+
process.stderr.write(`${colors.BGreen('\u2713')} Done\n`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
16
61
|
/**
|
|
17
62
|
* Get color for credit display based on remaining credits (matches bashmaiass)
|
|
18
63
|
* @param {number} credits - Remaining credits
|
|
@@ -373,111 +418,150 @@ ${gitDiff}`;
|
|
|
373
418
|
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Request body: ${JSON.stringify(requestBody, null, 2)}`);
|
|
374
419
|
}
|
|
375
420
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
headers: {
|
|
379
|
-
'Content-Type': 'application/json',
|
|
380
|
-
'Authorization': `Bearer ${maiassToken}`,
|
|
381
|
-
'X-Machine-Fingerprint': generateMachineFingerprint(),
|
|
382
|
-
'X-Client-Name': getClientName(),
|
|
383
|
-
'X-Client-Version': getClientVersion(),
|
|
384
|
-
'X-Subscription-ID': process.env.MAIASS_SUBSCRIPTION_ID || ''
|
|
385
|
-
},
|
|
386
|
-
body: JSON.stringify(requestBody)
|
|
387
|
-
});
|
|
421
|
+
// Set timeout for API call (default 30 seconds)
|
|
422
|
+
const timeout = parseInt(process.env.MAIASS_AI_TIMEOUT || '30') * 1000;
|
|
388
423
|
|
|
389
424
|
if (debugMode) {
|
|
390
|
-
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG]
|
|
391
|
-
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Response headers: ${JSON.stringify(Object.fromEntries(response.headers), null, 2)}`);
|
|
425
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] API timeout set to ${timeout/1000} seconds`);
|
|
392
426
|
}
|
|
393
|
-
|
|
394
|
-
if (!response.ok) {
|
|
395
|
-
const errorText = await response.text();
|
|
396
|
-
if (debugMode) {
|
|
397
|
-
log.debug(SYMBOLS.WARNING, `[MAIASS DEBUG] Response error body: ${errorText}`);
|
|
398
|
-
}
|
|
399
|
-
log.error(SYMBOLS.WARNING, `AI API request failed: ${response.status} ${response.statusText}`);
|
|
400
|
-
return null;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const data = await response.json();
|
|
404
|
-
|
|
405
|
-
if (debugMode) {
|
|
406
|
-
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Response data: ${JSON.stringify(data, null, 2)}`);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if (data.choices && data.choices.length > 0) {
|
|
410
|
-
let suggestion = data.choices[0].message.content.trim();
|
|
411
427
|
|
|
412
|
-
|
|
428
|
+
// Start spinner (unless in debug mode)
|
|
429
|
+
const spinner = !debugMode ? new Spinner('Waiting for AI response') : null;
|
|
430
|
+
if (spinner) spinner.start();
|
|
431
|
+
|
|
432
|
+
try {
|
|
433
|
+
// Fetch with timeout using AbortController
|
|
434
|
+
const controller = new AbortController();
|
|
435
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
436
|
+
|
|
437
|
+
const response = await fetch(aiEndpoint, {
|
|
438
|
+
method: 'POST',
|
|
439
|
+
headers: {
|
|
440
|
+
'Content-Type': 'application/json',
|
|
441
|
+
'Authorization': `Bearer ${maiassToken}`,
|
|
442
|
+
'X-Machine-Fingerprint': generateMachineFingerprint(),
|
|
443
|
+
'X-Client-Name': getClientName(),
|
|
444
|
+
'X-Client-Version': getClientVersion(),
|
|
445
|
+
'X-Subscription-ID': process.env.MAIASS_SUBSCRIPTION_ID || ''
|
|
446
|
+
},
|
|
447
|
+
body: JSON.stringify(requestBody),
|
|
448
|
+
signal: controller.signal
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
clearTimeout(timeoutId);
|
|
452
|
+
|
|
453
|
+
// Stop spinner on success
|
|
454
|
+
if (spinner) spinner.stop(true);
|
|
455
|
+
|
|
413
456
|
if (debugMode) {
|
|
414
|
-
log.debug(SYMBOLS.INFO,
|
|
415
|
-
log.debug(SYMBOLS.INFO,
|
|
457
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Response status: ${response.status} ${response.statusText}`);
|
|
458
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Response headers: ${JSON.stringify(Object.fromEntries(response.headers), null, 2)}`);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (!response.ok) {
|
|
462
|
+
const errorText = await response.text();
|
|
463
|
+
if (debugMode) {
|
|
464
|
+
log.debug(SYMBOLS.WARNING, `[MAIASS DEBUG] Response error body: ${errorText}`);
|
|
465
|
+
}
|
|
466
|
+
log.error(SYMBOLS.WARNING, `AI API request failed: ${response.status} ${response.statusText}`);
|
|
467
|
+
return null;
|
|
416
468
|
}
|
|
417
469
|
|
|
418
|
-
//
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
470
|
+
// Parse JSON response inside the same try block
|
|
471
|
+
const data = await response.json();
|
|
472
|
+
|
|
473
|
+
if (debugMode) {
|
|
474
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Response data: ${JSON.stringify(data, null, 2)}`);
|
|
422
475
|
}
|
|
423
476
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
477
|
+
if (data.choices && data.choices.length > 0) {
|
|
478
|
+
let suggestion = data.choices[0].message.content.trim();
|
|
479
|
+
|
|
480
|
+
// Enhanced debug logging: output the suggestion returned by AI
|
|
481
|
+
if (debugMode) {
|
|
482
|
+
log.debug(SYMBOLS.INFO, '[MAIASS DEBUG] --- AI SUGGESTION RETURNED ---');
|
|
483
|
+
log.debug(SYMBOLS.INFO, suggestion);
|
|
484
|
+
}
|
|
429
485
|
|
|
430
|
-
//
|
|
431
|
-
if (
|
|
432
|
-
|
|
486
|
+
// Clean up any quotes that might wrap the entire response
|
|
487
|
+
if ((suggestion.startsWith("'") && suggestion.endsWith("'")) ||
|
|
488
|
+
(suggestion.startsWith('"') && suggestion.endsWith('"'))) {
|
|
489
|
+
suggestion = suggestion.slice(1, -1).trim();
|
|
433
490
|
}
|
|
434
491
|
|
|
435
|
-
//
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
492
|
+
// Extract credit information from billing data
|
|
493
|
+
let creditsUsed, creditsRemaining;
|
|
494
|
+
if (data.billing) {
|
|
495
|
+
creditsUsed = data.billing.credits_used;
|
|
496
|
+
creditsRemaining = data.billing.credits_remaining;
|
|
497
|
+
|
|
498
|
+
// Show warnings if available
|
|
499
|
+
if (data.billing.warning) {
|
|
500
|
+
log.warning(SYMBOLS.WARNING, data.billing.warning);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Display proxy messages if available
|
|
504
|
+
if (data.messages && Array.isArray(data.messages)) {
|
|
505
|
+
data.messages.forEach(message => {
|
|
506
|
+
const icon = message.icon || '';
|
|
507
|
+
const text = message.text || '';
|
|
508
|
+
|
|
509
|
+
switch (message.type) {
|
|
510
|
+
case 'error':
|
|
511
|
+
log.error(icon, text);
|
|
512
|
+
break;
|
|
513
|
+
case 'warning':
|
|
514
|
+
log.warning(icon, text);
|
|
515
|
+
break;
|
|
516
|
+
case 'info':
|
|
517
|
+
log.info(icon, text);
|
|
518
|
+
break;
|
|
519
|
+
case 'notice':
|
|
520
|
+
log.blue(icon, text);
|
|
521
|
+
break;
|
|
522
|
+
case 'success':
|
|
523
|
+
log.success(icon, text);
|
|
524
|
+
break;
|
|
525
|
+
default:
|
|
526
|
+
log.plain(`${icon} ${text}`);
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
} else if (data.usage) {
|
|
531
|
+
// Fallback to legacy token display
|
|
532
|
+
const totalTokens = data.usage.total_tokens || 0;
|
|
533
|
+
const promptTokens = data.usage.prompt_tokens || 0;
|
|
534
|
+
const completionTokens = data.usage.completion_tokens || 0;
|
|
535
|
+
log.info(SYMBOLS.INFO, `Total Tokens: ${totalTokens} (${promptTokens} + ${completionTokens})`);
|
|
461
536
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
suggestion,
|
|
540
|
+
creditsUsed,
|
|
541
|
+
creditsRemaining
|
|
542
|
+
};
|
|
468
543
|
}
|
|
469
544
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
545
|
+
if (debugMode) {
|
|
546
|
+
log.debug(SYMBOLS.WARNING, '[MAIASS DEBUG] No valid AI response received - no choices in response data');
|
|
547
|
+
}
|
|
548
|
+
return null;
|
|
549
|
+
} catch (error) {
|
|
550
|
+
// Stop spinner on error
|
|
551
|
+
if (spinner) spinner.stop(false);
|
|
552
|
+
|
|
553
|
+
if (error.name === 'AbortError') {
|
|
554
|
+
log.error(SYMBOLS.WARNING, `AI request timed out after ${timeout/1000} seconds`);
|
|
555
|
+
log.info(SYMBOLS.INFO, 'The AI service might be slow or unresponsive. Try again or increase MAIASS_AI_TIMEOUT.');
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (debugMode) {
|
|
560
|
+
log.debug(SYMBOLS.WARNING, `[MAIASS DEBUG] AI suggestion error details: ${error.stack || error.message}`);
|
|
561
|
+
}
|
|
562
|
+
log.error(SYMBOLS.WARNING, `AI suggestion failed: ${error.message}`);
|
|
563
|
+
return null;
|
|
479
564
|
}
|
|
480
|
-
return null;
|
|
481
565
|
} catch (error) {
|
|
482
566
|
if (debugMode) {
|
|
483
567
|
log.debug(SYMBOLS.WARNING, `[MAIASS DEBUG] AI suggestion error details: ${error.stack || error.message}`);
|
package/lib/maiass-variables.js
CHANGED
|
@@ -18,6 +18,7 @@ export const MAIASS_VARIABLES = {
|
|
|
18
18
|
'MAIASS_AI_MODEL': { default: 'gpt-3.5-turbo', description: 'AI model to use' },
|
|
19
19
|
'MAIASS_AI_TEMPERATURE': { default: '0.7', description: 'AI temperature setting' },
|
|
20
20
|
'MAIASS_AI_MAX_CHARACTERS': { default: '8000', description: 'Max characters for AI requests' },
|
|
21
|
+
'MAIASS_AI_TIMEOUT': { default: '30', description: 'AI request timeout in seconds' },
|
|
21
22
|
'MAIASS_AI_COMMIT_MESSAGE_STYLE': { default: 'bullet', description: 'Commit message style' },
|
|
22
23
|
|
|
23
24
|
// Version file system
|
package/lib/secure-storage.js
CHANGED
|
@@ -1,11 +1,67 @@
|
|
|
1
1
|
// Secure storage functionality for nodemaiass
|
|
2
2
|
// Uses OS keychain (macOS) or secret-tool (Linux) for sensitive data storage
|
|
3
|
+
// Windows uses encrypted file storage in AppData
|
|
3
4
|
// Compatible with bashmaiass approach but uses NODEMAIASS service names
|
|
4
5
|
|
|
5
6
|
import { execSync } from 'child_process';
|
|
6
7
|
import os from 'os';
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import crypto from 'crypto';
|
|
7
11
|
import { log, logger } from './logger.js';
|
|
8
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Encryption key derivation for Windows storage
|
|
15
|
+
* Uses machine-specific data to create a unique encryption key
|
|
16
|
+
* @returns {Buffer} Encryption key
|
|
17
|
+
*/
|
|
18
|
+
function getEncryptionKey() {
|
|
19
|
+
// Use machine-specific data to derive a key
|
|
20
|
+
// This provides basic protection while being deterministic per machine
|
|
21
|
+
const machineId = os.hostname() + os.userInfo().username;
|
|
22
|
+
return crypto.createHash('sha256').update(machineId).digest();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Encrypt data for Windows storage
|
|
27
|
+
* @param {string} data - Data to encrypt
|
|
28
|
+
* @returns {string} Encrypted data (base64)
|
|
29
|
+
*/
|
|
30
|
+
function encryptData(data) {
|
|
31
|
+
const key = getEncryptionKey();
|
|
32
|
+
const iv = crypto.randomBytes(16);
|
|
33
|
+
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
|
|
34
|
+
|
|
35
|
+
let encrypted = cipher.update(data, 'utf8', 'base64');
|
|
36
|
+
encrypted += cipher.final('base64');
|
|
37
|
+
|
|
38
|
+
// Prepend IV to encrypted data
|
|
39
|
+
return iv.toString('base64') + ':' + encrypted;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Decrypt data from Windows storage
|
|
44
|
+
* @param {string} encryptedData - Encrypted data (base64 with IV)
|
|
45
|
+
* @returns {string} Decrypted data
|
|
46
|
+
*/
|
|
47
|
+
function decryptData(encryptedData) {
|
|
48
|
+
const key = getEncryptionKey();
|
|
49
|
+
const parts = encryptedData.split(':');
|
|
50
|
+
|
|
51
|
+
if (parts.length !== 2) {
|
|
52
|
+
throw new Error('Invalid encrypted data format');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const iv = Buffer.from(parts[0], 'base64');
|
|
56
|
+
const encrypted = parts[1];
|
|
57
|
+
|
|
58
|
+
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
|
|
59
|
+
let decrypted = decipher.update(encrypted, 'base64', 'utf8');
|
|
60
|
+
decrypted += decipher.final('utf8');
|
|
61
|
+
|
|
62
|
+
return decrypted;
|
|
63
|
+
}
|
|
64
|
+
|
|
9
65
|
/**
|
|
10
66
|
* Get environment-specific service name for secure storage
|
|
11
67
|
* Uses NODEMAIASS prefix to differentiate from bashmaiass
|
|
@@ -46,6 +102,42 @@ export function storeSecureVariable(varName, varValue) {
|
|
|
46
102
|
execSync(`security add-generic-password -U -s "${serviceName}" -a "${varName}" -w "${varValue}"`, {
|
|
47
103
|
stdio: 'pipe'
|
|
48
104
|
});
|
|
105
|
+
} else if (os.platform() === 'win32') {
|
|
106
|
+
// Windows: Use encrypted file storage in AppData
|
|
107
|
+
// cmdkey doesn't support password retrieval, so we use a file-based approach
|
|
108
|
+
const storageDir = path.join(os.homedir(), 'AppData', 'Local', 'maiass');
|
|
109
|
+
const storageFile = path.join(storageDir, `${serviceName}.dat`);
|
|
110
|
+
|
|
111
|
+
// Create directory if it doesn't exist
|
|
112
|
+
if (!fs.existsSync(storageDir)) {
|
|
113
|
+
fs.mkdirSync(storageDir, { recursive: true, mode: 0o700 });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Read existing data or create new
|
|
117
|
+
let data = {};
|
|
118
|
+
if (fs.existsSync(storageFile)) {
|
|
119
|
+
try {
|
|
120
|
+
const encrypted = fs.readFileSync(storageFile, 'utf8');
|
|
121
|
+
const decrypted = decryptData(encrypted);
|
|
122
|
+
data = JSON.parse(decrypted);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
// If decryption fails, start fresh
|
|
125
|
+
if (debugMode) {
|
|
126
|
+
logger.debug(`Could not read existing storage file: ${error.message}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Update the value
|
|
132
|
+
data[varName] = varValue;
|
|
133
|
+
|
|
134
|
+
// Encrypt and save
|
|
135
|
+
const encrypted = encryptData(JSON.stringify(data));
|
|
136
|
+
fs.writeFileSync(storageFile, encrypted, { mode: 0o600 });
|
|
137
|
+
|
|
138
|
+
if (debugMode) {
|
|
139
|
+
logger.debug(`Stored ${varName} in Windows secure storage (encrypted file)`);
|
|
140
|
+
}
|
|
49
141
|
} else {
|
|
50
142
|
// Linux: Use secret-tool if available
|
|
51
143
|
try {
|
|
@@ -96,6 +188,33 @@ export function retrieveSecureVariable(varName) {
|
|
|
96
188
|
stdio: 'pipe',
|
|
97
189
|
encoding: 'utf8'
|
|
98
190
|
}).trim();
|
|
191
|
+
} else if (os.platform() === 'win32') {
|
|
192
|
+
// Windows: Read from encrypted file storage
|
|
193
|
+
const storageDir = path.join(os.homedir(), 'AppData', 'Local', 'maiass');
|
|
194
|
+
const storageFile = path.join(storageDir, `${serviceName}.dat`);
|
|
195
|
+
|
|
196
|
+
if (!fs.existsSync(storageFile)) {
|
|
197
|
+
if (debugMode) {
|
|
198
|
+
logger.debug(`Storage file does not exist: ${storageFile}`);
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const encrypted = fs.readFileSync(storageFile, 'utf8');
|
|
205
|
+
const decrypted = decryptData(encrypted);
|
|
206
|
+
const data = JSON.parse(decrypted);
|
|
207
|
+
value = data[varName] || null;
|
|
208
|
+
|
|
209
|
+
if (debugMode && value) {
|
|
210
|
+
logger.debug(`Retrieved ${varName} from Windows secure storage (encrypted file)`);
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
if (debugMode) {
|
|
214
|
+
logger.debug(`Could not retrieve ${varName}: ${error.message}`);
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
99
218
|
} else {
|
|
100
219
|
// Linux: Use secret-tool if available
|
|
101
220
|
try {
|
|
@@ -144,6 +263,44 @@ export function removeSecureVariable(varName) {
|
|
|
144
263
|
execSync(`security delete-generic-password -s "${serviceName}" -a "${varName}"`, {
|
|
145
264
|
stdio: 'pipe'
|
|
146
265
|
});
|
|
266
|
+
} else if (os.platform() === 'win32') {
|
|
267
|
+
// Windows: Remove from encrypted file storage
|
|
268
|
+
const storageDir = path.join(os.homedir(), 'AppData', 'Local', 'maiass');
|
|
269
|
+
const storageFile = path.join(storageDir, `${serviceName}.dat`);
|
|
270
|
+
|
|
271
|
+
if (!fs.existsSync(storageFile)) {
|
|
272
|
+
// File doesn't exist, nothing to remove
|
|
273
|
+
if (debugMode) {
|
|
274
|
+
logger.debug(`Storage file does not exist, nothing to remove`);
|
|
275
|
+
}
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const encrypted = fs.readFileSync(storageFile, 'utf8');
|
|
281
|
+
const decrypted = decryptData(encrypted);
|
|
282
|
+
const data = JSON.parse(decrypted);
|
|
283
|
+
|
|
284
|
+
// Remove the variable
|
|
285
|
+
delete data[varName];
|
|
286
|
+
|
|
287
|
+
// If no more data, delete the file
|
|
288
|
+
if (Object.keys(data).length === 0) {
|
|
289
|
+
fs.unlinkSync(storageFile);
|
|
290
|
+
if (debugMode) {
|
|
291
|
+
logger.debug(`Removed storage file (no more credentials)`);
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
// Save updated data
|
|
295
|
+
const newEncrypted = encryptData(JSON.stringify(data));
|
|
296
|
+
fs.writeFileSync(storageFile, newEncrypted, { mode: 0o600 });
|
|
297
|
+
}
|
|
298
|
+
} catch (error) {
|
|
299
|
+
if (debugMode) {
|
|
300
|
+
logger.debug(`Error removing ${varName}: ${error.message}`);
|
|
301
|
+
}
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
147
304
|
} else {
|
|
148
305
|
// Linux: secret-tool doesn't have direct delete, but we can try to clear it
|
|
149
306
|
try {
|
|
@@ -236,7 +393,11 @@ export function isSecureStorageAvailable() {
|
|
|
236
393
|
if (os.platform() === 'darwin') {
|
|
237
394
|
execSync('which security', { stdio: 'pipe' });
|
|
238
395
|
return true;
|
|
396
|
+
} else if (os.platform() === 'win32') {
|
|
397
|
+
// Windows: Always available (uses encrypted file storage)
|
|
398
|
+
return true;
|
|
239
399
|
} else {
|
|
400
|
+
// Linux: Check for secret-tool
|
|
240
401
|
execSync('which secret-tool', { stdio: 'pipe' });
|
|
241
402
|
return true;
|
|
242
403
|
}
|