claude-code-provider-switch 1.1.4 → 1.1.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/lib/menu.js CHANGED
@@ -9,6 +9,10 @@ const {
9
9
  getProviderDefaultModel,
10
10
  setDefaultProvider,
11
11
  setDefaultModel,
12
+ showApiKeyMenu,
13
+ getConfigurationSource,
14
+ getConfigurationPath,
15
+ hasGlobalConfiguration,
12
16
  } = require("./config");
13
17
  const {
14
18
  showModelSelection: showOpenRouterModelSelection,
@@ -27,12 +31,6 @@ async function showModelSelectionForProvider(provider) {
27
31
 
28
32
  switch (provider.id) {
29
33
  case "openrouter":
30
- if (!envVars.OPENROUTER_AUTH_TOKEN) {
31
- log("OpenRouter auth token required for model selection", "red");
32
- log("Please set OPENROUTER_AUTH_TOKEN in .env file", "yellow");
33
- log("Using default model...", "yellow");
34
- return getProviderDefaultModel("openrouter");
35
- }
36
34
  return await showOpenRouterModelSelection();
37
35
 
38
36
  case "ollama":
@@ -59,10 +57,12 @@ function showProviderMenu() {
59
57
  output: process.stdout,
60
58
  });
61
59
 
62
- // Get current defaults
60
+ // Get current defaults and configuration source
63
61
  const defaultProvider = getDefaultProvider();
64
62
  const defaultModel = getDefaultModel();
63
+ const configSource = getConfigurationSource();
65
64
 
65
+ // Build providers array dynamically
66
66
  const providers = [
67
67
  {
68
68
  id: "openrouter",
@@ -77,9 +77,25 @@ function showProviderMenu() {
77
77
  aliases: ["original", "orig", "def", "d"],
78
78
  },
79
79
  { id: "set-default", name: "Set as Default", aliases: ["set-default"] },
80
- { id: "help", name: "Help", aliases: ["help", "-h", "--help"] },
80
+ { id: "api-keys", name: "Manage API Keys", aliases: ["api-keys", "keys"] },
81
81
  ];
82
82
 
83
+ // Add "Save Configuration Locally" option only if global configuration exists
84
+ if (hasGlobalConfiguration()) {
85
+ providers.push({
86
+ id: "save-local",
87
+ name: "Save Configuration Locally",
88
+ aliases: ["save-local", "local", "save-locally"],
89
+ });
90
+ }
91
+
92
+ // Always add Help at the end
93
+ providers.push({
94
+ id: "help",
95
+ name: "Help",
96
+ aliases: ["help", "-h", "--help"],
97
+ });
98
+
83
99
  let selectedIndex = 0;
84
100
 
85
101
  function displayMenu() {
@@ -87,8 +103,18 @@ function showProviderMenu() {
87
103
  log("Claude Code Provider Switcher", "green");
88
104
  log("", "reset");
89
105
 
106
+ // Show configuration source with file path
107
+ const configSource = getConfigurationSource();
108
+ const configPath = getConfigurationPath();
109
+ log(`Configuration: ${configSource} (${configPath})`, "cyan");
110
+ log("", "reset");
111
+
90
112
  // Show current defaults
91
- if (defaultProvider && defaultProvider !== "default") {
113
+ if (
114
+ defaultProvider &&
115
+ defaultProvider !== null &&
116
+ defaultProvider !== "default"
117
+ ) {
92
118
  const providerName =
93
119
  providers.find((p) => p.id === defaultProvider)?.name ||
94
120
  defaultProvider;
@@ -98,10 +124,8 @@ function showProviderMenu() {
98
124
  `Current default: ${providerName}${currentModel ? ` (${currentModel})` : ""}`,
99
125
  "yellow",
100
126
  );
101
- } else {
102
- log("No default provider set", "yellow");
127
+ log("", "reset");
103
128
  }
104
- log("", "reset");
105
129
 
106
130
  log("Available providers:", "yellow");
107
131
  log("", "reset");
@@ -111,18 +135,21 @@ function showProviderMenu() {
111
135
  const isDefault = provider.id === defaultProvider;
112
136
  const defaultIndicator = isDefault ? " [DEFAULT]" : "";
113
137
  const isSetDefaultOption = provider.id === "set-default";
138
+ const isApiKeysOption = provider.id === "api-keys";
114
139
  const aliases =
115
140
  provider.aliases.length > 0
116
141
  ? ` Aliases: (${provider.aliases.join(", ")})`
117
142
  : "";
118
143
 
119
- // Highlight "Set as Default" option in orange to make it stand out
144
+ // Highlight special options with different colors
120
145
  const color =
121
146
  index === selectedIndex
122
147
  ? "green"
123
148
  : isSetDefaultOption
124
149
  ? "orange"
125
- : "reset";
150
+ : isApiKeysOption
151
+ ? "cyan"
152
+ : "reset";
126
153
 
127
154
  log(
128
155
  `${marker} ${index + 1}) ${provider.name}${defaultIndicator}${aliases}`,
@@ -130,51 +157,47 @@ function showProviderMenu() {
130
157
  );
131
158
  });
132
159
 
133
- log("", "reset");
134
- log("Controls:", "yellow");
135
- log("↑/↓ - Navigate", "reset");
136
- log("Enter - Select provider", "reset");
137
- log("1-6 - Quick select", "reset");
138
- log("ESC - Exit", "reset");
139
160
  log("", "reset");
140
161
 
141
162
  // Add helpful usage text
142
163
  log("💡 Quick Start:", "cyan");
143
164
  log("• Use ↑/↓ arrows to navigate providers", "reset");
144
165
  log("• Press Enter to launch selected provider", "reset");
166
+ log(`• Press 1-${providers.length} for quick select`, "reset");
145
167
  log("• Use 'Set as Default' to save your preferred provider", "reset");
146
168
  log("", "reset");
147
- log("Commands:", "yellow");
148
- log(
149
- " claude-switch - Show menu or use default",
150
- "reset",
151
- );
152
- log(
153
- " claude-switch openrouter - Use OpenRouter provider",
154
- "reset",
155
- );
156
- log(
157
- " claude-switch anthropic - Use Anthropic provider",
158
- "reset",
159
- );
160
- log(" claude-switch ollama - Use Ollama provider", "reset");
161
- log(
162
- " claude-switch set-default - Setup default provider",
163
- "reset",
164
- );
165
- log("", "reset");
166
- log("Model Selection:", "yellow");
167
- log(
168
- " claude-switch openrouter --model - Select OpenRouter model",
169
- "reset",
170
- );
171
- log(
172
- " claude-switch anthropic --model - Select Anthropic model",
173
- "reset",
174
- );
175
- log("", "reset");
176
- log("Type 'claude-switch --help' for complete documentation", "cyan");
177
- log("", "reset");
169
+ // Commented out Commands section for cleaner menu
170
+ // log("Commands:", "yellow");
171
+ // log(
172
+ // " claude-switch - Show menu or use default",
173
+ // "reset",
174
+ // );
175
+ // log(
176
+ // " claude-switch openrouter - Use OpenRouter provider",
177
+ // "reset",
178
+ // );
179
+ // log(
180
+ // " claude-switch anthropic - Use Anthropic provider",
181
+ // "reset",
182
+ // );
183
+ // log(" claude-switch ollama - Use Ollama provider", "reset");
184
+ // log(
185
+ // " claude-switch set-default - Setup default provider",
186
+ // "reset",
187
+ // );
188
+ // log("", "reset");
189
+ // log("Model Selection:", "yellow");
190
+ // log(
191
+ // " claude-switch openrouter --model - Select OpenRouter model",
192
+ // "reset",
193
+ // );
194
+ // log(
195
+ // " claude-switch anthropic --model - Select Anthropic model",
196
+ // "reset",
197
+ // );
198
+ // log("", "reset");
199
+ // log("Type 'claude-switch --help' for complete documentation", "cyan");
200
+ // log("", "reset");
178
201
  }
179
202
 
180
203
  displayMenu();
@@ -197,7 +220,7 @@ function showProviderMenu() {
197
220
  process.stdin.removeAllListeners("keypress");
198
221
  rl.close();
199
222
  resolve(providers[selectedIndex]);
200
- } else if (str && /^[1-6]$/.test(str)) {
223
+ } else if (str && /^[1-7]$/.test(str)) {
201
224
  const index = parseInt(str) - 1;
202
225
  if (index >= 0 && index < providers.length) {
203
226
  selectedIndex = index;
@@ -483,6 +506,11 @@ function showUsage() {
483
506
  );
484
507
  log(" show-defaults - Display current default settings", "reset");
485
508
  log(" clear-defaults - Reset all default settings", "reset");
509
+ log(" api-keys - Manage API keys for providers", "reset");
510
+ log(
511
+ " save-local - Save global configuration to local .env file",
512
+ "reset",
513
+ );
486
514
  log("", "reset");
487
515
  log("Options:", "reset");
488
516
  log(" --model - Show interactive model selection menu", "reset");
package/lib/ollama.js CHANGED
@@ -10,7 +10,7 @@ const {
10
10
  loadEnvFile,
11
11
  findBestMatchingModel,
12
12
  promptForApiKey,
13
- updateEnvFile,
13
+ updateConfigFile,
14
14
  } = require("./config");
15
15
  const { modelCache } = require("./cache");
16
16
  const { OLLAMA, CACHE, HTTP_STATUS, DEFAULT_MODELS } = require("./constants");
@@ -97,7 +97,7 @@ async function showModelSelection() {
97
97
  log("Ollama auth token is required for model selection", "red");
98
98
  process.exit(1);
99
99
  }
100
- updateEnvFile("OLLAMA_AUTH_TOKEN", newToken);
100
+ updateConfigFile("OLLAMA_AUTH_TOKEN", newToken, null);
101
101
  authToken = newToken;
102
102
  }
103
103
 
@@ -378,8 +378,6 @@ async function launchOllama(
378
378
  extraArgs = [],
379
379
  directModel = null,
380
380
  ) {
381
- log("Launching Claude Code with Ollama settings...", "green");
382
-
383
381
  const envVars = loadEnvFile();
384
382
  log(`Loading environment from: ${envVars.envFile}`, "yellow");
385
383
 
@@ -391,7 +389,7 @@ async function launchOllama(
391
389
  log("Press Enter to skip, or provide an auth token:", "reset");
392
390
  authToken = await promptForApiKey("Ollama (optional)", "OLLAMA_AUTH_TOKEN");
393
391
  if (authToken) {
394
- updateEnvFile("OLLAMA_AUTH_TOKEN", authToken);
392
+ updateConfigFile("OLLAMA_AUTH_TOKEN", authToken, null);
395
393
  envVars.OLLAMA_AUTH_TOKEN = authToken;
396
394
  }
397
395
  }
package/lib/openrouter.js CHANGED
@@ -9,7 +9,7 @@ const {
9
9
  loadEnvFile,
10
10
  findBestMatchingModel,
11
11
  promptForApiKey,
12
- updateEnvFile,
12
+ updateConfigFile,
13
13
  } = require("./config");
14
14
  const { modelCache } = require("./cache");
15
15
  const {
@@ -435,8 +435,6 @@ async function launchOpenRouter(
435
435
  extraArgs = [],
436
436
  directModel = null,
437
437
  ) {
438
- log("Launching Claude Code with OpenRouter settings...", "green");
439
-
440
438
  const envVars = loadEnvFile();
441
439
  log(`Loading environment from: ${envVars.envFile}`, "yellow");
442
440
 
@@ -450,7 +448,7 @@ async function launchOpenRouter(
450
448
  log("Error: OpenRouter auth token is required", "red");
451
449
  process.exit(1);
452
450
  }
453
- updateEnvFile("OPENROUTER_AUTH_TOKEN", authToken);
451
+ updateConfigFile("OPENROUTER_AUTH_TOKEN", authToken, null);
454
452
  envVars.OPENROUTER_AUTH_TOKEN = authToken;
455
453
  }
456
454
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-provider-switch",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "description": "Cross-platform Claude Code provider switcher (OpenRouter, Ollama, Anthropic)",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -9,7 +9,6 @@
9
9
  "scripts": {
10
10
  "start": "node bin/claude-switch.js",
11
11
  "dev": "node bin/claude-switch.js",
12
- "version": "npm version",
13
12
  "link": "npm link",
14
13
  "unlink": "npm unlink -g claude-code-provider-switch",
15
14
  "pub": "npm version patch && npm publish && git push origin main --tags",
@@ -22,15 +21,16 @@
22
21
  "test:all": "node test/run-tests.js"
23
22
  },
24
23
  "keywords": [
25
- "claude",
26
24
  "claude-code",
27
- "openrouter",
28
- "llm switcher",
29
- "llm provider switcher",
30
- "ai provider switcher",
31
- "ai model switcher",
32
- "ai model provider switcher",
33
25
  "claude code provider switcher",
26
+ "claude code model switcher",
27
+ "claude code llm switcher",
28
+ "claude code llm provider switcher",
29
+ "ai provider switcher",
30
+ "claude code ai model switcher",
31
+ "claude code ai model provider switcher",
32
+ "claude code OpenRouter",
33
+ "claude code Ollama",
34
34
  "cli"
35
35
  ],
36
36
  "author": "Adrian R",
@@ -41,7 +41,9 @@
41
41
  "files": [
42
42
  "bin/",
43
43
  "lib/",
44
- "README.md"
44
+ "test/",
45
+ "README.md",
46
+ "index.js"
45
47
  ],
46
48
  "preferGlobal": true
47
49
  }
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawn } = require("child_process");
4
+ const path = require("path");
5
+
6
+ console.log("🧪 Running complete claude-switch test suite...\n");
7
+
8
+ // Test files in order
9
+ const testFiles = [
10
+ { name: "Validation & Error Tests", file: "test-validation-errors.js" },
11
+ { name: "Provider Integration Tests", file: "test-provider-integration.js" },
12
+ { name: "Comprehensive Integration Tests", file: "test-comprehensive.js" },
13
+ ];
14
+
15
+ let currentTest = 0;
16
+
17
+ function runNextTest() {
18
+ if (currentTest >= testFiles.length) {
19
+ console.log("\n🎉 All test suites passed!");
20
+ console.log("\n📊 Test Coverage Summary:");
21
+ console.log(" ✅ Constants and configuration");
22
+ console.log(" ✅ Error handling and validation");
23
+ console.log(" ✅ Provider-specific functionality");
24
+ console.log(" ✅ Integration and end-to-end tests");
25
+ console.log(" ✅ Cache and performance tests");
26
+ console.log(" ✅ Environment and configuration tests");
27
+ process.exit(0);
28
+ return;
29
+ }
30
+
31
+ const test = testFiles[currentTest];
32
+ console.log(`📋 Running ${test.name}...`);
33
+
34
+ const testProcess = spawn("node", [path.join(__dirname, test.file)], {
35
+ stdio: "inherit",
36
+ });
37
+
38
+ testProcess.on("close", (code) => {
39
+ if (code === 0) {
40
+ console.log(`✅ ${test.name} passed!\n`);
41
+ currentTest++;
42
+ runNextTest();
43
+ } else {
44
+ console.log(`\n❌ ${test.name} failed!`);
45
+ process.exit(1);
46
+ }
47
+ });
48
+
49
+ testProcess.on("error", (error) => {
50
+ console.log(`\n💥 Failed to run ${test.name}: ${error.message}`);
51
+ process.exit(1);
52
+ });
53
+ }
54
+
55
+ // Start running tests
56
+ runNextTest();
@@ -0,0 +1,282 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { spawn } = require("child_process");
6
+
7
+ // Test configuration
8
+ const testConfig = {
9
+ timeout: 10000, // 10 seconds timeout for async operations
10
+ scriptPath: path.join(__dirname, "..", "bin", "claude-switch.js"),
11
+ envPath: path.join(__dirname, "..", ".env"),
12
+ testDir: __dirname,
13
+ };
14
+
15
+ // Utility functions
16
+ function runCommand(args, options = {}) {
17
+ return new Promise((resolve, reject) => {
18
+ const child = spawn("node", [testConfig.scriptPath, ...args], {
19
+ stdio: "pipe",
20
+ timeout: testConfig.timeout,
21
+ ...options,
22
+ });
23
+
24
+ let stdout = "";
25
+ let stderr = "";
26
+
27
+ child.stdout.on("data", (data) => {
28
+ stdout += data.toString();
29
+ });
30
+
31
+ child.stderr.on("data", (data) => {
32
+ stderr += data.toString();
33
+ });
34
+
35
+ child.on("close", (code) => {
36
+ resolve({ code, stdout, stderr });
37
+ });
38
+
39
+ child.on("error", (error) => {
40
+ reject(error);
41
+ });
42
+ });
43
+ }
44
+
45
+ function runCommandWithInput(args, input, options = {}) {
46
+ return new Promise((resolve, reject) => {
47
+ const child = spawn("node", [testConfig.scriptPath, ...args], {
48
+ stdio: ["pipe", "pipe", "pipe"],
49
+ timeout: testConfig.timeout,
50
+ ...options,
51
+ });
52
+
53
+ let stdout = "";
54
+ let stderr = "";
55
+
56
+ child.stdout.on("data", (data) => {
57
+ stdout += data.toString();
58
+ });
59
+
60
+ child.stderr.on("data", (data) => {
61
+ stderr += data.toString();
62
+ });
63
+
64
+ child.on("close", (code) => {
65
+ resolve({ code, stdout, stderr });
66
+ });
67
+
68
+ child.on("error", (error) => {
69
+ reject(error);
70
+ });
71
+
72
+ // Send input
73
+ if (input) {
74
+ child.stdin.write(input);
75
+ child.stdin.end();
76
+ }
77
+ });
78
+ }
79
+
80
+ async function test(description, testFn) {
81
+ try {
82
+ console.log(`🧪 ${description}`);
83
+ await testFn();
84
+ console.log(`✅ ${description} - PASSED`);
85
+ } catch (error) {
86
+ console.log(`❌ ${description} - FAILED: ${error.message}`);
87
+ process.exit(1);
88
+ }
89
+ }
90
+
91
+ // Test suite
92
+ async function runTests() {
93
+ console.log("🚀 Starting claude-switch test suite...\n");
94
+
95
+ // Test 1: Basic file existence
96
+ await test("Main script exists", () => {
97
+ if (!fs.existsSync(testConfig.scriptPath)) {
98
+ throw new Error("Main script not found");
99
+ }
100
+ });
101
+
102
+ await test("Environment file exists", () => {
103
+ if (!fs.existsSync(testConfig.envPath)) {
104
+ throw new Error("Environment file not found");
105
+ }
106
+ });
107
+
108
+ await test("Package.json exists", () => {
109
+ const packagePath = path.join(__dirname, "..", "package.json");
110
+ if (!fs.existsSync(packagePath)) {
111
+ throw new Error("Package.json not found");
112
+ }
113
+ });
114
+
115
+ // Test 2: Help command
116
+ await test("Help command works", async () => {
117
+ const result = await runCommand(["help"]);
118
+ if (result.code !== 0 || !result.stdout.includes("Usage:")) {
119
+ throw new Error(
120
+ `Help command failed. Code: ${result.code}, Output: ${result.stdout}`,
121
+ );
122
+ }
123
+ });
124
+
125
+ // Test 3: Version check
126
+ await test("Version information available", async () => {
127
+ const packagePath = path.join(__dirname, "..", "package.json");
128
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
129
+ if (!packageJson.version || !packageJson.version.includes("1.0.0")) {
130
+ throw new Error(
131
+ `Version check failed. Expected: 1.0.0, Found: ${packageJson.version}`,
132
+ );
133
+ }
134
+ });
135
+
136
+ // Test 4: Provider listing
137
+ await test("Provider listing works", async () => {
138
+ const result = await runCommand(["help"]);
139
+ if (result.code !== 0 || !result.stdout.includes("Providers:")) {
140
+ throw new Error(
141
+ `Provider listing failed. Code: ${result.code}, Output: ${result.stdout}`,
142
+ );
143
+ }
144
+ });
145
+
146
+ // Test 5: Default provider functionality
147
+ await test("Default provider functionality works", async () => {
148
+ const result = await runCommand(["show-defaults"]);
149
+ if (
150
+ result.code !== 0 ||
151
+ (!result.stdout.includes("Default provider:") &&
152
+ !result.stdout.includes("No default provider set"))
153
+ ) {
154
+ throw new Error(
155
+ `Default provider functionality failed. Code: ${result.code}, Output: ${result.stdout}`,
156
+ );
157
+ }
158
+ });
159
+
160
+ // Test 6: Model selection with OpenRouter (test with exit)
161
+ await test("OpenRouter model selection interface works", async () => {
162
+ const result = await runCommandWithInput(
163
+ ["openrouter", "--model"],
164
+ "exit\n",
165
+ );
166
+ // Exit code 1 is acceptable when using default model after exit
167
+ if (result.code !== 0 && result.code !== 1) {
168
+ throw new Error(
169
+ `OpenRouter model selection failed. Code: ${result.code}`,
170
+ );
171
+ }
172
+ if (!result.stdout.includes("Found") || !result.stdout.includes("models")) {
173
+ throw new Error(
174
+ `OpenRouter model selection failed. Output: ${result.stdout}`,
175
+ );
176
+ }
177
+ });
178
+
179
+ // Test 7: Model selection with Ollama (test with exit)
180
+ await test("Ollama model selection interface works", async () => {
181
+ const result = await runCommandWithInput(["ollama", "--model"], "exit\n");
182
+ // Exit code 1 is acceptable when using default model after exit
183
+ if (result.code !== 0 && result.code !== 1) {
184
+ throw new Error(`Ollama model selection failed. Code: ${result.code}`);
185
+ }
186
+ if (!result.stdout.includes("Found") || !result.stdout.includes("models")) {
187
+ throw new Error(
188
+ `Ollama model selection failed. Output: ${result.stdout}`,
189
+ );
190
+ }
191
+ });
192
+
193
+ // Test 8: Model selection with Anthropic (test with enter)
194
+ await test("Anthropic model selection interface works", async () => {
195
+ const result = await runCommandWithInput(["anthropic", "--model"], "\n");
196
+ // Accept both successful model selection and API key error as valid
197
+ if (
198
+ !result.stdout.includes("Found") &&
199
+ !result.stdout.includes("models") &&
200
+ !result.stdout.includes("API key not found") &&
201
+ !result.stdout.includes("API key is required")
202
+ ) {
203
+ throw new Error(`Anthropic model selection failed. Code: ${result.code}`);
204
+ }
205
+ });
206
+
207
+ // Test 9: Error handling for invalid provider
208
+ await test("Invalid provider error handling", async () => {
209
+ const result = await runCommand(["invalid-provider"]);
210
+ if (result.code === 0 || !result.stdout.includes("Unknown command")) {
211
+ throw new Error("Invalid provider should fail with error message");
212
+ }
213
+ });
214
+
215
+ // Test 10: Virtual scrolling indicators present
216
+ await test("Virtual scrolling indicators present", async () => {
217
+ const result = await runCommandWithInput(
218
+ ["openrouter", "--model"],
219
+ "exit\n",
220
+ );
221
+ if (!result.stdout.includes("Use ↑/↓ to scroll through results")) {
222
+ throw new Error("Virtual scrolling instructions not present");
223
+ }
224
+ });
225
+
226
+ // Test 11: Default model highlighting
227
+ await test("Default model highlighting works", async () => {
228
+ const result = await runCommandWithInput(
229
+ ["openrouter", "--model"],
230
+ "exit\n",
231
+ );
232
+ if (!result.stdout.includes("[DEFAULT]")) {
233
+ throw new Error("Default model not highlighted");
234
+ }
235
+ });
236
+
237
+ // Test 12: Selection options instructions
238
+ await test("Selection options instructions present", async () => {
239
+ const result = await runCommandWithInput(
240
+ ["openrouter", "--model"],
241
+ "exit\n",
242
+ );
243
+ const requiredInstructions = [
244
+ "Type number to select",
245
+ "Type model name to search",
246
+ "Press Enter to select the first model",
247
+ ];
248
+
249
+ for (const instruction of requiredInstructions) {
250
+ if (!result.stdout.includes(instruction)) {
251
+ throw new Error(`Missing instruction: ${instruction}`);
252
+ }
253
+ }
254
+ });
255
+
256
+ // Test 13: Environment variable caching
257
+ await test("Environment caching works", async () => {
258
+ // This test checks that the app doesn't crash and loads environment properly
259
+ const result1 = await runCommand(["help"]);
260
+ const result2 = await runCommand(["help"]);
261
+
262
+ if (result1.code !== 0 || result2.code !== 0) {
263
+ throw new Error("Environment caching test failed");
264
+ }
265
+ });
266
+
267
+ // Test 14: Main menu functionality
268
+ await test("Main menu interface works", async () => {
269
+ const result = await runCommandWithInput([], "7\n"); // Select option 7 (should work)
270
+ if (result.code !== 0) {
271
+ throw new Error("Main menu interface failed");
272
+ }
273
+ });
274
+
275
+ console.log("\n🎉 All tests passed! The application is working correctly.");
276
+ }
277
+
278
+ // Run tests
279
+ runTests().catch((error) => {
280
+ console.error("💥 Test suite failed:", error.message);
281
+ process.exit(1);
282
+ });