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 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;IAiH5B;;;OAGG;YACW,iBAAiB;IA8D/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;IAyH7B;;;OAGG;YACW,uBAAuB;IAyErC;;OAEG;YACW,4BAA4B;IAiE1C;;OAEG;YACW,oBAAoB;IAgElC;;;;OAIG;YACW,yBAAyB;IAgIvC;;;OAGG;YACW,qBAAqB;IAmHnC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAW9B;;OAEG;YACW,mBAAmB;IA0BjC;;OAEG;YACW,qBAAqB;IAiDnC;;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"}
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, join } from 'node:path';
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, detectDependencies } from './planner.js';
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, ensureEnvInGitignore } from './git.js';
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
- return; // No update available or error
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
  */