maistro 1.2.0 → 1.2.3
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/dist/app.d.ts +0 -23
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +10 -302
- package/dist/app.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +30 -0
- package/dist/config.js.map +1 -1
- package/dist/dependencyDetector.d.ts +0 -18
- package/dist/dependencyDetector.d.ts.map +1 -1
- package/dist/dependencyDetector.js +1 -640
- package/dist/dependencyDetector.js.map +1 -1
- package/dist/planner.d.ts +0 -1
- package/dist/planner.d.ts.map +1 -1
- package/dist/planner.js +2 -4
- package/dist/planner.js.map +1 -1
- package/dist/versionCheck.d.ts +2 -2
- package/dist/versionCheck.d.ts.map +1 -1
- package/dist/versionCheck.js +20 -6
- package/dist/versionCheck.js.map +1 -1
- package/package.json +1 -1
- package/dist/apiKeyValidator.d.ts +0 -69
- package/dist/apiKeyValidator.d.ts.map +0 -1
- package/dist/apiKeyValidator.js +0 -295
- package/dist/apiKeyValidator.js.map +0 -1
package/dist/app.d.ts
CHANGED
|
@@ -82,29 +82,6 @@ export declare class MaistroApp {
|
|
|
82
82
|
* Ask a single discovery question using full-screen approach
|
|
83
83
|
*/
|
|
84
84
|
private askDiscoveryQuestion;
|
|
85
|
-
/**
|
|
86
|
-
* Detect and collect missing external dependencies
|
|
87
|
-
* @param goal - The user's goal text to detect mentioned dependencies
|
|
88
|
-
* Returns the collected values or null if user cancelled
|
|
89
|
-
*/
|
|
90
|
-
private handleDependencyDetection;
|
|
91
|
-
/**
|
|
92
|
-
* Validate collected API keys against their respective services
|
|
93
|
-
* Returns the validated values (possibly updated after retries), or 'cancel' if user cancelled
|
|
94
|
-
*/
|
|
95
|
-
private validateCollectedKeys;
|
|
96
|
-
/**
|
|
97
|
-
* Check if a dependency ID is a generic API type that needs custom validation
|
|
98
|
-
*/
|
|
99
|
-
private isGenericApiDependency;
|
|
100
|
-
/**
|
|
101
|
-
* Ask user for a validation URL to test the API credentials
|
|
102
|
-
*/
|
|
103
|
-
private askForValidationUrl;
|
|
104
|
-
/**
|
|
105
|
-
* Save collected dependency values to .env file
|
|
106
|
-
*/
|
|
107
|
-
private saveDependenciesToEnv;
|
|
108
85
|
/**
|
|
109
86
|
* Format duration in milliseconds to human-readable string
|
|
110
87
|
*/
|
package/dist/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAiOA,qBAAa,UAAU;IACrB,OAAO,CAAC,EAAE,CAAgB;IAC1B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAS;IAG9B,OAAO,CAAC,cAAc,CAA+B;gBAEzC,WAAW,GAAE,MAAY;IAsBrC,OAAO,CAAC,eAAe;IASvB;;;OAGG;YACW,qBAAqB;IA+CnC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAiOA,qBAAa,UAAU;IACrB,OAAO,CAAC,EAAE,CAAgB;IAC1B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAS;IAG9B,OAAO,CAAC,cAAc,CAA+B;gBAEzC,WAAW,GAAE,MAAY;IAsBrC,OAAO,CAAC,eAAe;IASvB;;;OAGG;YACW,qBAAqB;IA+CnC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAoH5B;;;OAGG;YACW,iBAAiB;IAiE/B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA4C1B;;;OAGG;IACG,YAAY,CAAC,SAAS,UAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAqFtD;;;OAGG;YACW,iBAAiB;IAwD/B;;OAEG;YACW,sBAAsB;IAsCpC;;;OAGG;YACW,iBAAiB;IAoB/B;;;OAGG;YACW,oBAAoB;IAkClC;;OAEG;YACW,qBAAqB;IA4BnC;;OAEG;YACW,gBAAgB;IAoB9B;;;OAGG;YACW,qBAAqB;IAoEnC;;;OAGG;YACW,eAAe;IA8G7B;;;OAGG;YACW,uBAAuB;IAyErC;;OAEG;YACW,4BAA4B;IAiE1C;;OAEG;YACW,oBAAoB;IAgElC;;OAEG;IACH,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,UAAU;IAQlB;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;IA2J7B,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,gBAAgB,CAAK;IAE7B;;;OAGG;YACW,YAAY;IAqT1B;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IA2CxB;;OAEG;YACW,aAAa;IAsmB3B;;;OAGG;YACW,iBAAiB;IAgI/B;;OAEG;YACW,cAAc;IAwU5B,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,aAAa,CAAS;IAE9B;;OAEG;YACW,gBAAgB;IAkB9B;;OAEG;YACW,gBAAgB;IAe9B;;;OAGG;YACW,WAAW;IAsEzB;;;OAGG;YACW,mBAAmB;IA8BjC;;;OAGG;YACW,cAAc;IAkB5B;;;;OAIG;YACW,6BAA6B;IAqI3C;;;OAGG;YACW,aAAa;IA+B3B;;;;OAIG;YACW,sBAAsB;IAwPpC;;;;OAIG;YACW,sBAAsB;YA0GtB,UAAU;IA4HxB,OAAO,CAAC,YAAY;IAmFpB;;;OAGG;YACW,eAAe;IAqD7B;;OAEG;YACW,cAAc;IAsO5B;;OAEG;YACW,oBAAoB;IAwBlC;;OAEG;YACW,eAAe;IAU7B;;OAEG;YACW,wBAAwB;YAsBxB,gBAAgB;IA4B9B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsD;IAEpF;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAoB3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAO1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAKzB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAqB5B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAQ/B;;OAEG;IACH,OAAO,CAAC,eAAe;IASvB;;OAEG;IACH,OAAO,CAAC,sBAAsB;YAoEhB,SAAS;YAiIT,sBAAsB;CAkcrC"}
|
package/dist/app.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { resolve, basename, extname
|
|
1
|
+
import { resolve, basename, extname } from 'node:path';
|
|
2
2
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { exec, spawn } from 'node:child_process';
|
|
6
6
|
import { Orchestrator } from './orchestrator.js';
|
|
7
7
|
import { InteractiveUI, ExitRequestedError, formatTaskStatus, formatTaskStatusIcon, formatStatusName, formatBlockedStatus, progressBar } from './ui.js';
|
|
8
|
-
import { refinePlan, generateReadme, discoverRequirements, buildProjectContext, formatContextForDecomposition
|
|
8
|
+
import { refinePlan, generateReadme, discoverRequirements, buildProjectContext, formatContextForDecomposition } from './planner.js';
|
|
9
9
|
import { isTaskBlocked } from './taskQueue.js';
|
|
10
10
|
import { isClaudeCodeInstalled, isClaudeCodeAuthenticated, testClaudeInteraction, launchClaudeLogin, loadConfig, saveConfig, } from './config.js';
|
|
11
11
|
import { initLogger, getLogger } from './logger.js';
|
|
@@ -14,10 +14,9 @@ import { buildHeaderWithLogo } from './logo.js';
|
|
|
14
14
|
import { addImageFromFile, addImageFromUrl, addImageFromPaste, getImages, deleteImage, looksLikeImagePath, looksLikeImageUrl, formatImageSize, sanitizeImageId, generatePasteImageId, extractImageUrls, processDescriptionImageUrls, } from './imageManager.js';
|
|
15
15
|
import { InputBox, getDisplayWidth, charIndexAtDisplayColumn } from './inputBox.js';
|
|
16
16
|
import { startCaffeinate, stopCaffeinate, isCaffeinateAvailable, isCaffeinateRunning } from './caffeinate.js';
|
|
17
|
-
import { getPreventSleep, setPreventSleep, getConfigPath, detectTerminal, getShiftEnterEnabled, setShiftEnterEnabled, configureTerminalShiftEnter, removeTerminalShiftEnter, getNewlineHint, getCmdVPasteEnabled, setCmdVPasteEnabled, configureTerminalCmdVPaste, removeTerminalCmdVPaste, getCmdVPasteInstructions, } from './config.js';
|
|
17
|
+
import { getPreventSleep, setPreventSleep, getConfigPath, detectTerminal, getShiftEnterEnabled, setShiftEnterEnabled, configureTerminalShiftEnter, removeTerminalShiftEnter, getNewlineHint, getCmdVPasteEnabled, setCmdVPasteEnabled, configureTerminalCmdVPaste, removeTerminalCmdVPaste, getCmdVPasteInstructions, validateTerminalConfig, } from './config.js';
|
|
18
18
|
import { hasClipboardImage, saveClipboardImage, cleanupTempImage } from './clipboard.js';
|
|
19
|
-
import { isGitRepo
|
|
20
|
-
import { validateApiKeys } from './apiKeyValidator.js';
|
|
19
|
+
import { isGitRepo } from './git.js';
|
|
21
20
|
import { UI, TipsManager } from './constants.js';
|
|
22
21
|
import { getUpdateNotice, checkForUpdates, performUpdate, isAutoUpdatePromptEnabled, isAutoUpdateEnabled, } from './versionCheck.js';
|
|
23
22
|
/**
|
|
@@ -246,6 +245,8 @@ export class MaistroApp {
|
|
|
246
245
|
return;
|
|
247
246
|
}
|
|
248
247
|
}
|
|
248
|
+
// Validate terminal config (re-sync keybindings if needed)
|
|
249
|
+
validateTerminalConfig();
|
|
249
250
|
// Main loop - allows restart after clearing plan
|
|
250
251
|
while (true) {
|
|
251
252
|
try {
|
|
@@ -325,7 +326,10 @@ export class MaistroApp {
|
|
|
325
326
|
async handleUpdateCheck() {
|
|
326
327
|
const result = await checkForUpdates();
|
|
327
328
|
if (!result.updateAvailable || !result.latestVersion) {
|
|
328
|
-
|
|
329
|
+
if (result.error) {
|
|
330
|
+
getLogger()?.info('Update check skipped due to error', { error: result.error });
|
|
331
|
+
}
|
|
332
|
+
return;
|
|
329
333
|
}
|
|
330
334
|
// Check if silent auto-update is enabled
|
|
331
335
|
if (isAutoUpdateEnabled()) {
|
|
@@ -738,8 +742,6 @@ export class MaistroApp {
|
|
|
738
742
|
exitOnDoubleEscape: true, // Allow exit via double-escape during discovery
|
|
739
743
|
});
|
|
740
744
|
if (skipChoice === 's' || skipChoice === null) {
|
|
741
|
-
// Even when skipping discovery, still check for dependencies
|
|
742
|
-
await this.handleDependencyDetection(goal);
|
|
743
745
|
return null; // Skip discovery
|
|
744
746
|
}
|
|
745
747
|
// Show analyzing screen with spinner (with escape-to-exit support)
|
|
@@ -771,8 +773,6 @@ export class MaistroApp {
|
|
|
771
773
|
console.log(`\x1b[33mCouldn't analyze goal: ${result.error}\x1b[0m`);
|
|
772
774
|
console.log('\x1b[90mProceeding with direct planning...\x1b[0m\n');
|
|
773
775
|
await new Promise(r => setTimeout(r, 1500));
|
|
774
|
-
// Still check for dependencies even when discovery fails
|
|
775
|
-
await this.handleDependencyDetection(goal);
|
|
776
776
|
return null;
|
|
777
777
|
}
|
|
778
778
|
// Ask each question one at a time (full-screen for each)
|
|
@@ -788,12 +788,6 @@ export class MaistroApp {
|
|
|
788
788
|
}
|
|
789
789
|
answers[question.id] = answer;
|
|
790
790
|
}
|
|
791
|
-
// Run dependency detection after discovery questions
|
|
792
|
-
const depResult = await this.handleDependencyDetection(goal);
|
|
793
|
-
if (depResult === null) {
|
|
794
|
-
// User cancelled during dependency collection
|
|
795
|
-
return null;
|
|
796
|
-
}
|
|
797
791
|
// Show confirmation screen (full-screen)
|
|
798
792
|
this.ui.clear();
|
|
799
793
|
this.showHeader();
|
|
@@ -1001,292 +995,6 @@ export class MaistroApp {
|
|
|
1001
995
|
}
|
|
1002
996
|
return question.default || '';
|
|
1003
997
|
}
|
|
1004
|
-
/**
|
|
1005
|
-
* Detect and collect missing external dependencies
|
|
1006
|
-
* @param goal - The user's goal text to detect mentioned dependencies
|
|
1007
|
-
* Returns the collected values or null if user cancelled
|
|
1008
|
-
*/
|
|
1009
|
-
async handleDependencyDetection(goal) {
|
|
1010
|
-
// Detect dependencies first (without showing UI)
|
|
1011
|
-
const result = await detectDependencies(this.projectPath, goal);
|
|
1012
|
-
// Skip showing dependency screen entirely if no dependencies detected
|
|
1013
|
-
if (result.dependencies.length === 0) {
|
|
1014
|
-
return {};
|
|
1015
|
-
}
|
|
1016
|
-
// Show all detected dependencies
|
|
1017
|
-
this.ui.clear();
|
|
1018
|
-
this.showHeader();
|
|
1019
|
-
console.log('\x1b[1m── Detected Dependencies ──\x1b[0m\n');
|
|
1020
|
-
// Group by type for display
|
|
1021
|
-
const typeLabels = {
|
|
1022
|
-
api_key: 'API Keys',
|
|
1023
|
-
env_var: 'Environment Variables',
|
|
1024
|
-
config: 'Configuration',
|
|
1025
|
-
secret: 'Secrets',
|
|
1026
|
-
};
|
|
1027
|
-
const grouped = new Map();
|
|
1028
|
-
for (const dep of result.dependencies) {
|
|
1029
|
-
const existing = grouped.get(dep.type) || [];
|
|
1030
|
-
existing.push(dep);
|
|
1031
|
-
grouped.set(dep.type, existing);
|
|
1032
|
-
}
|
|
1033
|
-
for (const [type, deps] of grouped) {
|
|
1034
|
-
console.log(`\x1b[36m${typeLabels[type] || type}:\x1b[0m`);
|
|
1035
|
-
for (const dep of deps) {
|
|
1036
|
-
const status = dep.present ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
|
|
1037
|
-
const required = dep.required ? '' : ' \x1b[90m(optional)\x1b[0m';
|
|
1038
|
-
console.log(` ${status} ${dep.name}${required}`);
|
|
1039
|
-
if (dep.sources.length > 0) {
|
|
1040
|
-
console.log(` \x1b[90mFound in: ${dep.sources.slice(0, 3).join(', ')}${dep.sources.length > 3 ? '...' : ''}\x1b[0m`);
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
console.log('');
|
|
1044
|
-
}
|
|
1045
|
-
// Check if there are missing required dependencies
|
|
1046
|
-
const missing = result.missing;
|
|
1047
|
-
if (missing.length === 0) {
|
|
1048
|
-
console.log('\x1b[32m✓ All required dependencies are present.\x1b[0m\n');
|
|
1049
|
-
await new Promise(r => setTimeout(r, 1500));
|
|
1050
|
-
return {};
|
|
1051
|
-
}
|
|
1052
|
-
// Ask if user wants to provide missing dependencies
|
|
1053
|
-
const continueChoice = await this.ui.quickPick([
|
|
1054
|
-
{ key: 'p', label: 'Provide values', description: `Configure ${missing.length} missing ${missing.length === 1 ? 'dependency' : 'dependencies'}` },
|
|
1055
|
-
{ key: 's', label: 'Skip', description: 'Continue without configuring (may cause issues)' },
|
|
1056
|
-
], `${missing.length} required ${missing.length === 1 ? 'dependency is' : 'dependencies are'} missing. Would you like to provide them?`, undefined, { showInput: false });
|
|
1057
|
-
if (continueChoice === 's' || continueChoice === null) {
|
|
1058
|
-
return {};
|
|
1059
|
-
}
|
|
1060
|
-
// Collect each missing dependency one by one
|
|
1061
|
-
const collectedValues = {};
|
|
1062
|
-
for (let i = 0; i < missing.length; i++) {
|
|
1063
|
-
const dep = missing[i];
|
|
1064
|
-
const progressText = `Dependency ${i + 1} of ${missing.length}`;
|
|
1065
|
-
// Build instructions string
|
|
1066
|
-
let instructions = '';
|
|
1067
|
-
if (dep.obtainInstructions) {
|
|
1068
|
-
instructions += `\n\n\x1b[90m${dep.obtainInstructions}\x1b[0m`;
|
|
1069
|
-
}
|
|
1070
|
-
if (dep.obtainUrl) {
|
|
1071
|
-
instructions += `\n\x1b[90m→ ${dep.obtainUrl}\x1b[0m`;
|
|
1072
|
-
}
|
|
1073
|
-
if (dep.valueFormat) {
|
|
1074
|
-
instructions += `\n\x1b[90mFormat: ${dep.valueFormat}\x1b[0m`;
|
|
1075
|
-
}
|
|
1076
|
-
if (dep.description) {
|
|
1077
|
-
instructions += `\n\n\x1b[90m${dep.description}\x1b[0m`;
|
|
1078
|
-
}
|
|
1079
|
-
const value = await this.fullScreenTextInput({
|
|
1080
|
-
title: progressText,
|
|
1081
|
-
subtitle: `Enter value for \x1b[1m${dep.name}\x1b[0m${instructions}`,
|
|
1082
|
-
placeholder: dep.valueFormat || 'enter value...',
|
|
1083
|
-
hint: 'Enter to continue, Esc to skip this dependency',
|
|
1084
|
-
});
|
|
1085
|
-
if (value === null) {
|
|
1086
|
-
// User skipped this dependency
|
|
1087
|
-
continue;
|
|
1088
|
-
}
|
|
1089
|
-
if (value.trim()) {
|
|
1090
|
-
collectedValues[dep.id] = value.trim();
|
|
1091
|
-
// For generic API dependencies without a known validator, ask for validation URL
|
|
1092
|
-
if (!dep.validationUrl && this.isGenericApiDependency(dep.id)) {
|
|
1093
|
-
const validationUrl = await this.askForValidationUrl(dep);
|
|
1094
|
-
if (validationUrl) {
|
|
1095
|
-
dep.validationUrl = validationUrl;
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
// Validate and save collected values
|
|
1101
|
-
if (Object.keys(collectedValues).length > 0) {
|
|
1102
|
-
const validationResult = await this.validateCollectedKeys(collectedValues, missing);
|
|
1103
|
-
if (validationResult === 'cancel') {
|
|
1104
|
-
return null;
|
|
1105
|
-
}
|
|
1106
|
-
// validationResult contains the final values (possibly updated after retries)
|
|
1107
|
-
await this.saveDependenciesToEnv(validationResult);
|
|
1108
|
-
return validationResult;
|
|
1109
|
-
}
|
|
1110
|
-
return collectedValues;
|
|
1111
|
-
}
|
|
1112
|
-
/**
|
|
1113
|
-
* Validate collected API keys against their respective services
|
|
1114
|
-
* Returns the validated values (possibly updated after retries), or 'cancel' if user cancelled
|
|
1115
|
-
*/
|
|
1116
|
-
async validateCollectedKeys(values, deps) {
|
|
1117
|
-
let currentValues = { ...values };
|
|
1118
|
-
let validationResults;
|
|
1119
|
-
// Build dependency metadata for validation
|
|
1120
|
-
const dependencyMetadata = {};
|
|
1121
|
-
for (const dep of deps) {
|
|
1122
|
-
if (dep.validationUrl || dep.authMethod) {
|
|
1123
|
-
dependencyMetadata[dep.id] = {
|
|
1124
|
-
validationUrl: dep.validationUrl,
|
|
1125
|
-
authMethod: dep.authMethod,
|
|
1126
|
-
};
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
// Validation loop - allows retrying failed keys
|
|
1130
|
-
while (true) {
|
|
1131
|
-
// Show validation spinner
|
|
1132
|
-
this.ui.clear();
|
|
1133
|
-
this.showHeader();
|
|
1134
|
-
console.log('\x1b[1m── Validating Credentials ──\x1b[0m\n');
|
|
1135
|
-
const spinner = this.ui.spinner('Validating API keys...');
|
|
1136
|
-
validationResults = await validateApiKeys(currentValues, {
|
|
1137
|
-
timeout: 5000,
|
|
1138
|
-
dependencyMetadata,
|
|
1139
|
-
});
|
|
1140
|
-
spinner.stop();
|
|
1141
|
-
// Display results
|
|
1142
|
-
this.ui.clear();
|
|
1143
|
-
this.showHeader();
|
|
1144
|
-
console.log('\x1b[1m── Validation Results ──\x1b[0m\n');
|
|
1145
|
-
for (const [envVar, result] of Object.entries(validationResults.results)) {
|
|
1146
|
-
const dep = deps.find(d => d.id === envVar);
|
|
1147
|
-
const name = dep?.name || envVar;
|
|
1148
|
-
if (result.valid) {
|
|
1149
|
-
console.log(`\x1b[32m✓\x1b[0m ${name}`);
|
|
1150
|
-
if (result.skipped) {
|
|
1151
|
-
console.log(` \x1b[90m(${result.message})\x1b[0m`);
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
else {
|
|
1155
|
-
console.log(`\x1b[31m✗\x1b[0m ${name}`);
|
|
1156
|
-
console.log(` \x1b[90m${result.error || result.message}\x1b[0m`);
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
console.log('');
|
|
1160
|
-
// If all passed, continue
|
|
1161
|
-
if (validationResults.allPassed) {
|
|
1162
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
1163
|
-
return currentValues;
|
|
1164
|
-
}
|
|
1165
|
-
// Some validations failed - offer choices
|
|
1166
|
-
const choice = await this.ui.quickPick([
|
|
1167
|
-
{ key: 'r', label: 'Retry failed', description: 'Re-enter values for failed keys' },
|
|
1168
|
-
{ key: 's', label: 'Save anyway', description: 'Save values despite validation failure' },
|
|
1169
|
-
{ key: 'c', label: 'Cancel', description: 'Discard all values' },
|
|
1170
|
-
], 'Some validations failed. What would you like to do?', undefined, { showInput: false });
|
|
1171
|
-
if (choice === 'c' || choice === null) {
|
|
1172
|
-
return 'cancel';
|
|
1173
|
-
}
|
|
1174
|
-
if (choice === 's') {
|
|
1175
|
-
return currentValues;
|
|
1176
|
-
}
|
|
1177
|
-
// choice === 'r' - retry failed keys
|
|
1178
|
-
const failedKeys = Object.entries(validationResults.results)
|
|
1179
|
-
.filter(([, result]) => !result.valid)
|
|
1180
|
-
.map(([key]) => key);
|
|
1181
|
-
for (const envVar of failedKeys) {
|
|
1182
|
-
const dep = deps.find(d => d.id === envVar);
|
|
1183
|
-
if (!dep)
|
|
1184
|
-
continue;
|
|
1185
|
-
// Build instructions string
|
|
1186
|
-
let instructions = '';
|
|
1187
|
-
if (dep.obtainInstructions) {
|
|
1188
|
-
instructions += `\n\n\x1b[90m${dep.obtainInstructions}\x1b[0m`;
|
|
1189
|
-
}
|
|
1190
|
-
if (dep.obtainUrl) {
|
|
1191
|
-
instructions += `\n\x1b[90m→ ${dep.obtainUrl}\x1b[0m`;
|
|
1192
|
-
}
|
|
1193
|
-
if (dep.valueFormat) {
|
|
1194
|
-
instructions += `\n\x1b[90mFormat: ${dep.valueFormat}\x1b[0m`;
|
|
1195
|
-
}
|
|
1196
|
-
const value = await this.fullScreenTextInput({
|
|
1197
|
-
title: 'Retry Failed Key',
|
|
1198
|
-
subtitle: `Re-enter value for \x1b[1m${dep.name}\x1b[0m${instructions}`,
|
|
1199
|
-
placeholder: dep.valueFormat || 'enter value...',
|
|
1200
|
-
hint: 'Enter to continue, Esc to skip',
|
|
1201
|
-
initialValue: currentValues[envVar],
|
|
1202
|
-
});
|
|
1203
|
-
if (value !== null && value.trim()) {
|
|
1204
|
-
currentValues[envVar] = value.trim();
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
/**
|
|
1210
|
-
* Check if a dependency ID is a generic API type that needs custom validation
|
|
1211
|
-
*/
|
|
1212
|
-
isGenericApiDependency(depId) {
|
|
1213
|
-
const genericTypes = [
|
|
1214
|
-
'api_bearer_token',
|
|
1215
|
-
'api_key_generic',
|
|
1216
|
-
'api_authentication',
|
|
1217
|
-
'oauth_credentials',
|
|
1218
|
-
'basic_auth',
|
|
1219
|
-
];
|
|
1220
|
-
return genericTypes.includes(depId);
|
|
1221
|
-
}
|
|
1222
|
-
/**
|
|
1223
|
-
* Ask user for a validation URL to test the API credentials
|
|
1224
|
-
*/
|
|
1225
|
-
async askForValidationUrl(dep) {
|
|
1226
|
-
// Ask if user wants to provide a test URL
|
|
1227
|
-
const choice = await this.ui.quickPick([
|
|
1228
|
-
{ key: 'y', label: 'Yes', description: 'Provide an API endpoint to test the credentials' },
|
|
1229
|
-
{ key: 'n', label: 'Skip', description: 'Save credentials without testing' },
|
|
1230
|
-
], `Would you like to validate the ${dep.name} by testing an API endpoint?`, undefined, { showInput: false });
|
|
1231
|
-
if (choice !== 'y') {
|
|
1232
|
-
return null;
|
|
1233
|
-
}
|
|
1234
|
-
const url = await this.fullScreenTextInput({
|
|
1235
|
-
title: 'API Validation',
|
|
1236
|
-
subtitle: `Enter an API endpoint URL to test the ${dep.name}\n\n\x1b[90mThis should be a GET endpoint that requires authentication.\nExample: https://api.example.com/v1/me\x1b[0m`,
|
|
1237
|
-
placeholder: 'https://api.example.com/endpoint',
|
|
1238
|
-
hint: 'Enter to continue, Esc to skip validation',
|
|
1239
|
-
});
|
|
1240
|
-
return url?.trim() || null;
|
|
1241
|
-
}
|
|
1242
|
-
/**
|
|
1243
|
-
* Save collected dependency values to .env file
|
|
1244
|
-
*/
|
|
1245
|
-
async saveDependenciesToEnv(values) {
|
|
1246
|
-
const envPath = join(this.projectPath, '.env');
|
|
1247
|
-
let envContent = '';
|
|
1248
|
-
// Read existing .env if present
|
|
1249
|
-
if (existsSync(envPath)) {
|
|
1250
|
-
try {
|
|
1251
|
-
envContent = await readFile(envPath, 'utf-8');
|
|
1252
|
-
if (!envContent.endsWith('\n')) {
|
|
1253
|
-
envContent += '\n';
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
catch {
|
|
1257
|
-
// File doesn't exist or can't be read
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
// Add new values
|
|
1261
|
-
const newLines = [];
|
|
1262
|
-
for (const [key, value] of Object.entries(values)) {
|
|
1263
|
-
// Check if key already exists in the file
|
|
1264
|
-
const keyRegex = new RegExp(`^${key}\\s*=`, 'm');
|
|
1265
|
-
if (keyRegex.test(envContent)) {
|
|
1266
|
-
// Update existing value
|
|
1267
|
-
envContent = envContent.replace(keyRegex, `${key}=${value}`);
|
|
1268
|
-
}
|
|
1269
|
-
else {
|
|
1270
|
-
// Add new value
|
|
1271
|
-
newLines.push(`${key}=${value}`);
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
if (newLines.length > 0) {
|
|
1275
|
-
envContent += newLines.join('\n') + '\n';
|
|
1276
|
-
}
|
|
1277
|
-
try {
|
|
1278
|
-
writeFileSync(envPath, envContent);
|
|
1279
|
-
// Ensure .env is in .gitignore to prevent accidental commit of secrets
|
|
1280
|
-
const gitignoreResult = await ensureEnvInGitignore(this.projectPath);
|
|
1281
|
-
if (gitignoreResult.success && gitignoreResult.output.includes('Added')) {
|
|
1282
|
-
console.log(`\x1b[32m✓ Added .env to .gitignore\x1b[0m`);
|
|
1283
|
-
}
|
|
1284
|
-
console.log(`\x1b[32m✓ Saved ${Object.keys(values).length} ${Object.keys(values).length === 1 ? 'value' : 'values'} to .env\x1b[0m\n`);
|
|
1285
|
-
}
|
|
1286
|
-
catch (err) {
|
|
1287
|
-
console.log(`\x1b[33mWarning: Could not save to .env: ${err instanceof Error ? err.message : String(err)}\x1b[0m\n`);
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
998
|
/**
|
|
1291
999
|
* Format duration in milliseconds to human-readable string
|
|
1292
1000
|
*/
|