@tyvm/knowhow 0.0.35 โ†’ 0.0.37

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.
Files changed (99) hide show
  1. package/package.json +1 -1
  2. package/src/agents/tools/aiClient.ts +36 -0
  3. package/src/agents/tools/lintFile.ts +1 -1
  4. package/src/agents/tools/list.ts +34 -0
  5. package/src/agents/tools/ycmd/tools/diagnostics.ts +2 -0
  6. package/src/ai.ts +5 -4
  7. package/src/auth/browserLogin.ts +283 -0
  8. package/src/auth/errors.ts +6 -0
  9. package/src/auth/spinner.ts +23 -0
  10. package/src/chat/CliChatService.ts +25 -6
  11. package/src/chat/modules/AgentModule.ts +1 -2
  12. package/src/chat/modules/AskModule.ts +1 -2
  13. package/src/chat/types.ts +14 -4
  14. package/src/chat-old.ts +446 -0
  15. package/src/chat.ts +48 -433
  16. package/src/cli.ts +5 -12
  17. package/src/embeddings.ts +1 -1
  18. package/src/index.ts +0 -8
  19. package/src/login.ts +14 -1
  20. package/src/microphone.ts +0 -1
  21. package/src/plugins/downloader/downloader.ts +34 -122
  22. package/src/services/KnowhowClient.ts +3 -0
  23. package/src/services/index.ts +1 -2
  24. package/tests/manual/browser-login/README.md +189 -0
  25. package/tests/manual/browser-login/test_browser_login_basic.ts +115 -0
  26. package/tests/manual/browser-login/test_cli_integration.ts +169 -0
  27. package/tests/manual/browser-login/test_cross_platform_browser.ts +186 -0
  28. package/tests/manual/browser-login/test_error_scenarios.ts +223 -0
  29. package/tests/manual/cli/no-env.sh +256 -0
  30. package/ts_build/src/agents/tools/aiClient.d.ts +2 -0
  31. package/ts_build/src/agents/tools/aiClient.js +21 -1
  32. package/ts_build/src/agents/tools/aiClient.js.map +1 -1
  33. package/ts_build/src/agents/tools/lintFile.js +1 -1
  34. package/ts_build/src/agents/tools/lintFile.js.map +1 -1
  35. package/ts_build/src/agents/tools/list.js +32 -0
  36. package/ts_build/src/agents/tools/list.js.map +1 -1
  37. package/ts_build/src/agents/tools/ycmd/tools/diagnostics.js +1 -0
  38. package/ts_build/src/agents/tools/ycmd/tools/diagnostics.js.map +1 -1
  39. package/ts_build/src/ai.d.ts +1 -1
  40. package/ts_build/src/ai.js +2 -1
  41. package/ts_build/src/ai.js.map +1 -1
  42. package/ts_build/src/auth/browserLogin.d.ts +11 -0
  43. package/ts_build/src/auth/browserLogin.js +197 -0
  44. package/ts_build/src/auth/browserLogin.js.map +1 -0
  45. package/ts_build/src/auth/errors.d.ts +4 -0
  46. package/ts_build/src/auth/errors.js +13 -0
  47. package/ts_build/src/auth/errors.js.map +1 -0
  48. package/ts_build/src/auth/spinner.d.ts +7 -0
  49. package/ts_build/src/auth/spinner.js +23 -0
  50. package/ts_build/src/auth/spinner.js.map +1 -0
  51. package/ts_build/src/chat/CliChatService.d.ts +4 -3
  52. package/ts_build/src/chat/CliChatService.js +18 -4
  53. package/ts_build/src/chat/CliChatService.js.map +1 -1
  54. package/ts_build/src/chat/modules/AgentModule.d.ts +1 -1
  55. package/ts_build/src/chat/modules/AgentModule.js +1 -2
  56. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  57. package/ts_build/src/chat/modules/AskModule.js +1 -2
  58. package/ts_build/src/chat/modules/AskModule.js.map +1 -1
  59. package/ts_build/src/chat/types.d.ts +5 -3
  60. package/ts_build/src/chat-old.d.ts +13 -0
  61. package/ts_build/src/chat-old.js +340 -0
  62. package/ts_build/src/chat-old.js.map +1 -0
  63. package/ts_build/src/chat.d.ts +3 -13
  64. package/ts_build/src/chat.js +38 -331
  65. package/ts_build/src/chat.js.map +1 -1
  66. package/ts_build/src/chat2.d.ts +1 -1
  67. package/ts_build/src/chat2.js +2 -2
  68. package/ts_build/src/chat2.js.map +1 -1
  69. package/ts_build/src/cli.js +3 -9
  70. package/ts_build/src/cli.js.map +1 -1
  71. package/ts_build/src/embeddings.js.map +1 -1
  72. package/ts_build/src/index.d.ts +0 -2
  73. package/ts_build/src/index.js +1 -9
  74. package/ts_build/src/index.js.map +1 -1
  75. package/ts_build/src/login.d.ts +1 -1
  76. package/ts_build/src/login.js +14 -0
  77. package/ts_build/src/login.js.map +1 -1
  78. package/ts_build/src/microphone.js.map +1 -1
  79. package/ts_build/src/plugins/downloader/downloader.d.ts +1 -6
  80. package/ts_build/src/plugins/downloader/downloader.js +26 -97
  81. package/ts_build/src/plugins/downloader/downloader.js.map +1 -1
  82. package/ts_build/src/services/KnowhowClient.js +3 -0
  83. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  84. package/ts_build/src/services/index.js +1 -2
  85. package/ts_build/src/services/index.js.map +1 -1
  86. package/ts_build/tests/manual/browser-login/test_browser_login_basic.d.ts +2 -0
  87. package/ts_build/tests/manual/browser-login/test_browser_login_basic.js +108 -0
  88. package/ts_build/tests/manual/browser-login/test_browser_login_basic.js.map +1 -0
  89. package/ts_build/tests/manual/browser-login/test_cli_integration.d.ts +2 -0
  90. package/ts_build/tests/manual/browser-login/test_cli_integration.js +153 -0
  91. package/ts_build/tests/manual/browser-login/test_cli_integration.js.map +1 -0
  92. package/ts_build/tests/manual/browser-login/test_cross_platform_browser.d.ts +2 -0
  93. package/ts_build/tests/manual/browser-login/test_cross_platform_browser.js +159 -0
  94. package/ts_build/tests/manual/browser-login/test_cross_platform_browser.js.map +1 -0
  95. package/ts_build/tests/manual/browser-login/test_error_scenarios.d.ts +2 -0
  96. package/ts_build/tests/manual/browser-login/test_error_scenarios.js +197 -0
  97. package/ts_build/tests/manual/browser-login/test_error_scenarios.js.map +1 -0
  98. package/src/agents/vim/vim.ts +0 -152
  99. package/src/chat2.ts +0 -62
@@ -8,11 +8,12 @@ import { execAsync, fileExists, readFile, mkdir } from "../../utils";
8
8
  import OpenAI from "openai";
9
9
  import { Clients } from "../../clients";
10
10
  import { Models } from "../../types";
11
+ import { openai } from "../../ai";
11
12
 
12
13
  const logger = Logger();
13
14
 
14
15
  export class DownloaderService {
15
- constructor(private openAi: OpenAI, private clients: typeof Clients) {}
16
+ constructor(private clients: typeof Clients) {}
16
17
 
17
18
  async askGptVision(
18
19
  imageUrl: string,
@@ -65,36 +66,6 @@ export class DownloaderService {
65
66
  return info;
66
67
  }
67
68
 
68
- private async acquireLock(
69
- lockPath: string,
70
- timeoutMs = 30000
71
- ): Promise<() => void> {
72
- const startTime = Date.now();
73
-
74
- while (Date.now() - startTime < timeoutMs) {
75
- try {
76
- await fs.promises.writeFile(lockPath, process.pid.toString(), {
77
- flag: "wx",
78
- });
79
- console.log(`Lock acquired: ${lockPath}`);
80
- return async () => {
81
- try {
82
- await fs.promises.unlink(lockPath);
83
- console.log(`Lock released: ${lockPath}`);
84
- } catch (error) {
85
- console.warn(`Failed to release lock ${lockPath}:`, error);
86
- }
87
- };
88
- } catch (error) {
89
- if (error.code !== "EEXIST") {
90
- throw error;
91
- }
92
- await new Promise((resolve) => setTimeout(resolve, 100));
93
- }
94
- }
95
- throw new Error(`Failed to acquire lock ${lockPath} within ${timeoutMs}ms`);
96
- }
97
-
98
69
  public async chunk(
99
70
  filePath: string,
100
71
  outputDir: string,
@@ -107,54 +78,43 @@ export class DownloaderService {
107
78
  console.log({ fileName, fileExt });
108
79
  console.log("Chunking file", filePath);
109
80
 
110
- // Create lock to prevent concurrent chunking of the same file
111
- const lockPath = path.join(outputDir, `${fileName}.chunking.lock`);
112
- const releaseLock = await this.acquireLock(lockPath);
113
-
114
- try {
115
- // create a temp directory
116
- const outputDirPath = path.join(outputDir, `${fileName}/chunks`);
117
- await fs.promises.mkdir(outputDirPath, { recursive: true });
118
- const existingFolderFiles = await fs.promises.readdir(outputDirPath);
119
- const existingChunkNames = existingFolderFiles.filter(
120
- (f) => f.includes("chunk") && f.endsWith(".mp3")
121
- );
81
+ // create a temp directory
82
+ const outputDirPath = path.join(outputDir, `${fileName}/chunks`);
83
+ await fs.promises.mkdir(outputDirPath, { recursive: true });
84
+ const doneFilePath = path.join(outputDirPath, ".chunking_done");
122
85
 
123
- if (existingChunkNames.length > 0) {
124
- if (reuseExistingChunks) {
125
- console.log("Chunks already exist, skipping");
126
- await releaseLock();
127
- const names = existingChunkNames.map((chunkName) =>
128
- path.join(outputDirPath, chunkName)
129
- );
130
- return names;
131
- } else {
132
- for (const file of existingFolderFiles) {
133
- fs.rmSync(path.join(outputDirPath, file), { recursive: true });
134
- }
86
+ const doneFileExists = await fileExists(doneFilePath);
87
+ const existingFolderFiles = await fs.promises.readdir(outputDirPath);
88
+ const existingChunkNames = existingFolderFiles.filter(
89
+ (f) => f.includes("chunk") && f.endsWith(".mp3")
90
+ );
91
+
92
+ if (existingChunkNames.length > 0 && doneFileExists) {
93
+ if (reuseExistingChunks) {
94
+ console.log("Chunks already exist, skipping");
95
+ const names = existingChunkNames.map((chunkName) =>
96
+ path.join(outputDirPath, chunkName)
97
+ );
98
+ return names;
99
+ } else {
100
+ for (const file of existingFolderFiles) {
101
+ fs.rmSync(path.join(outputDirPath, file), { recursive: true });
135
102
  }
136
103
  }
104
+ }
137
105
 
138
- const command = `ffmpeg -i "${filePath}" -f segment -segment_time ${CHUNK_LENGTH_SECONDS} -map 0:a:0 -acodec mp3 -vn "${outputDirPath}/chunk%04d.mp3"`;
139
- await execAsync(command);
106
+ const command = `ffmpeg -i "${filePath}" -f segment -segment_time ${CHUNK_LENGTH_SECONDS} -map 0:a:0 -acodec mp3 -vn "${outputDirPath}/chunk%04d.mp3"`;
107
+ await execAsync(command);
108
+ await fs.promises.writeFile(doneFilePath, "done");
140
109
 
141
- const folderFiles = await fs.promises.readdir(outputDirPath);
142
- const chunkNames = folderFiles.filter(
143
- (f) => f.includes("chunk") && f.endsWith(".mp3")
144
- );
145
- console.log("Chunked into", chunkNames.length, "chunks");
146
- return chunkNames.map((chunkName) => path.join(outputDirPath, chunkName));
147
- } catch (error) {
148
- console.error("Error during chunking:", error);
149
- throw error;
150
- } finally {
151
- await releaseLock();
152
- }
110
+ const folderFiles = await fs.promises.readdir(outputDirPath);
111
+ const chunkNames = folderFiles.filter(
112
+ (f) => f.includes("chunk") && f.endsWith(".mp3")
113
+ );
114
+ console.log("Chunked into", chunkNames.length, "chunks");
115
+ return chunkNames.map((chunkName) => path.join(outputDirPath, chunkName));
153
116
  }
154
117
 
155
- // Add transcription locking to prevent race conditions
156
- private transcriptionLocks = new Map<string, Promise<TranscriptChunk[]>>();
157
-
158
118
  public async *streamTranscription(
159
119
  files: string[],
160
120
  outputPath: string,
@@ -171,56 +131,8 @@ export class DownloaderService {
171
131
  return;
172
132
  }
173
133
 
174
- // Prevent concurrent transcription of the same output file
175
- const lockKey = outputPath;
176
- if (this.transcriptionLocks.has(lockKey)) {
177
- console.log("Waiting for concurrent transcription to complete...");
178
- await this.transcriptionLocks.get(lockKey);
179
- // After waiting, check again if file exists
180
- const existsNow = await fileExists(outputPath);
181
- if (existsNow && reusePreviousTranscript) {
182
- console.log(
183
- "Transcription completed by concurrent process, using cached data"
184
- );
185
- const contents = await readFile(outputPath);
186
- const data = JSON.parse(contents.toString()) as TranscriptChunk[];
187
- for (const item of data) {
188
- yield item;
189
- }
190
- return;
191
- }
192
- }
193
-
194
- // Create a promise for this transcription operation
195
- const transcriptionPromise = (async () => {
196
- const results: TranscriptChunk[] = [];
197
- for await (const chunk of this.performTranscription(
198
- files,
199
- outputPath,
200
- reusePreviousTranscript
201
- )) {
202
- results.push(chunk);
203
- }
204
- return results;
205
- })();
206
- this.transcriptionLocks.set(lockKey, transcriptionPromise);
207
-
208
- try {
209
- const results = await transcriptionPromise;
210
- for (const chunk of results) {
211
- yield chunk;
212
- }
213
- } finally {
214
- this.transcriptionLocks.delete(lockKey);
215
- }
216
- }
217
-
218
- private async *performTranscription(
219
- files: string[],
220
- outputPath: string,
221
- reusePreviousTranscript: boolean
222
- ): AsyncGenerator<TranscriptChunk> {
223
134
  const allTranscripts = [];
135
+ const openAi = openai();
224
136
  for (const file of files) {
225
137
  const chunkName = path.parse(file).name;
226
138
  const chunkTranscriptPath = path.join(
@@ -247,7 +159,7 @@ export class DownloaderService {
247
159
  }
248
160
 
249
161
  console.log("Transcribing", file);
250
- const transcript = await this.openAi.audio.transcriptions
162
+ const transcript = await openAi.audio.transcriptions
251
163
  .create({
252
164
  file: fs.createReadStream(file),
253
165
  model: "whisper-1",
@@ -128,6 +128,9 @@ export class KnowhowSimpleClient {
128
128
  getModels() {
129
129
  this.checkJwt();
130
130
  return axios.get(`${this.baseUrl}/api/proxy/v1/models`, {
131
+ params: {
132
+ type: 'all'
133
+ },
131
134
  headers: this.headers,
132
135
  });
133
136
  }
@@ -1,4 +1,3 @@
1
- import { openai } from "../ai";
2
1
  import { DownloaderService } from "../plugins/downloader/downloader";
3
2
  import { Clients } from "../clients";
4
3
  import { Plugins } from "../plugins/plugins";
@@ -41,7 +40,7 @@ export const services = (): typeof Singletons => {
41
40
  const Tools = new ToolsService();
42
41
  const Events = new EventService();
43
42
  const Agents = new AgentService(Tools, Events);
44
- const Downloader = new DownloaderService(openai, Clients);
43
+ const Downloader = new DownloaderService(Clients);
45
44
  Singletons = {
46
45
  Tools,
47
46
  Events,
@@ -0,0 +1,189 @@
1
+ # Browser Login Manual Tests
2
+
3
+ This directory contains comprehensive manual tests for the browser-based login functionality in the Knowhow CLI.
4
+
5
+ ## Test Overview
6
+
7
+ The browser login implementation includes the following key features:
8
+ - Browser-based authentication as the default login method
9
+ - Cross-platform browser opening (macOS, Windows, Linux)
10
+ - Polling mechanism with exponential backoff
11
+ - JWT token retrieval and secure storage
12
+ - Graceful error handling and user cancellation
13
+ - Backwards compatibility with `--jwt` flag
14
+
15
+ ## Test Files
16
+
17
+ ### 1. `test_browser_login_basic.ts`
18
+ **Purpose**: Tests the core browser login flow end-to-end
19
+
20
+ **What it tests**:
21
+ - Session creation with the API
22
+ - Browser opening for user authentication
23
+ - Authentication polling and completion
24
+ - JWT retrieval and secure storage
25
+ - File permissions (0o600)
26
+ - JWT format validation
27
+
28
+ **Prerequisites**:
29
+ - Valid `KNOWHOW_API_URL` environment variable
30
+ - Network connection to Knowhow API
31
+ - Default browser available
32
+
33
+ **Usage**:
34
+ ```bash
35
+ npx tsx ./tests/manual/browser-login/test_browser_login_basic.ts
36
+ ```
37
+
38
+ **Manual steps required**:
39
+ - Complete authentication in the opened browser window
40
+ - Verify browser opened to correct URL
41
+
42
+ ### 2. `test_cli_integration.ts`
43
+ **Purpose**: Tests CLI command integration and backwards compatibility
44
+
45
+ **What it tests**:
46
+ - `knowhow login` uses browser login by default
47
+ - `knowhow login --jwt` prompts for manual JWT input
48
+ - Command help shows correct options
49
+ - JWT file creation through CLI
50
+
51
+ **Prerequisites**:
52
+ - Built CLI application (`npm run build`)
53
+ - Valid `KNOWHOW_API_URL` environment variable
54
+
55
+ **Usage**:
56
+ ```bash
57
+ npx tsx ./tests/manual/browser-login/test_cli_integration.ts
58
+ ```
59
+
60
+ **Manual steps required**:
61
+ - Complete browser authentication when prompted
62
+ - Verify help output is correct
63
+
64
+ ### 3. `test_cross_platform_browser.ts`
65
+ **Purpose**: Tests browser opening across different operating systems
66
+
67
+ **What it tests**:
68
+ - Platform detection (macOS, Windows, Linux)
69
+ - Correct browser command selection (`open`, `start`, `xdg-open`)
70
+ - Browser command availability
71
+ - URL handling with special characters
72
+ - Graceful fallback when browser opening fails
73
+
74
+ **Prerequisites**:
75
+ - Default browser installed
76
+ - Platform-specific browser commands available
77
+
78
+ **Usage**:
79
+ ```bash
80
+ npx tsx ./tests/manual/browser-login/test_cross_platform_browser.ts
81
+ ```
82
+
83
+ **Manual steps required**:
84
+ - Verify browser opens to test URLs
85
+ - Confirm platform-specific behavior
86
+
87
+ ### 4. `test_error_scenarios.ts`
88
+ **Purpose**: Tests error handling and edge cases
89
+
90
+ **What it tests**:
91
+ - Invalid API URL handling
92
+ - Missing API URL configuration
93
+ - JWT validation with various inputs
94
+ - File permission handling
95
+ - Error code propagation
96
+ - Graceful cancellation mechanisms
97
+
98
+ **Prerequisites**:
99
+ - Ability to modify environment variables temporarily
100
+
101
+ **Usage**:
102
+ ```bash
103
+ npx tsx ./tests/manual/browser-login/test_error_scenarios.ts
104
+ ```
105
+
106
+ **Manual steps required**:
107
+ - None (fully automated error scenario testing)
108
+
109
+ ## Running All Tests
110
+
111
+ To run all tests in sequence:
112
+
113
+ ```bash
114
+ # Run individual tests
115
+ npx tsx ./tests/manual/browser-login/test_error_scenarios.ts
116
+ npx tsx ./tests/manual/browser-login/test_cross_platform_browser.ts
117
+ npx tsx ./tests/manual/browser-login/test_cli_integration.ts
118
+ npx tsx ./tests/manual/browser-login/test_browser_login_basic.ts
119
+ ```
120
+
121
+ ## Test Results Interpretation
122
+
123
+ ### โœ… PASSED
124
+ Test completed successfully with expected behavior.
125
+
126
+ ### โš ๏ธ WARNING
127
+ Test completed but with minor issues or platform-specific concerns that don't prevent functionality.
128
+
129
+ ### โŒ FAILED
130
+ Test failed due to errors or unexpected behavior that needs to be addressed.
131
+
132
+ ### Manual Verification Required
133
+ Some tests require manual verification (e.g., confirming browser opened correctly) as they test user interaction flows.
134
+
135
+ ## Common Issues and Troubleshooting
136
+
137
+ ### Browser Not Opening
138
+ - **Symptoms**: Browser opening fails or command not found
139
+ - **Solutions**:
140
+ - Ensure default browser is installed
141
+ - Check platform-specific command availability (`open`, `start`, `xdg-open`)
142
+ - Verify display environment for Linux systems
143
+
144
+ ### Network Errors
145
+ - **Symptoms**: API connection failures or timeouts
146
+ - **Solutions**:
147
+ - Verify `KNOWHOW_API_URL` is set correctly
148
+ - Check network connectivity
149
+ - Confirm API endpoints are accessible
150
+
151
+ ### Permission Errors
152
+ - **Symptoms**: Cannot create or write JWT files
153
+ - **Solutions**:
154
+ - Check write permissions in project directory
155
+ - Verify `.knowhow` directory can be created
156
+ - Check for filesystem restrictions
157
+
158
+ ### Authentication Timeout
159
+ - **Symptoms**: Polling times out before user completes authentication
160
+ - **Solutions**:
161
+ - Complete authentication more quickly
162
+ - Check if browser session is blocked by ad blockers
163
+ - Verify correct authentication URL
164
+
165
+ ## Integration with CI/CD
166
+
167
+ These manual tests are designed for human verification and are not suitable for automated CI/CD pipelines. For automated testing, consider:
168
+
169
+ 1. Mocking the browser opening functionality
170
+ 2. Creating integration tests with test authentication endpoints
171
+ 3. Unit testing individual components separately
172
+
173
+ ## Security Considerations
174
+
175
+ When running these tests:
176
+ - JWT tokens are stored temporarily and cleaned up
177
+ - File permissions are tested to ensure secure storage (0o600)
178
+ - No sensitive data should be logged or persisted beyond test execution
179
+ - Tests clean up after themselves to avoid leaving test artifacts
180
+
181
+ ## Contributing
182
+
183
+ When adding new tests:
184
+ 1. Follow the existing test file naming pattern
185
+ 2. Include comprehensive error handling
186
+ 3. Provide clear manual steps in comments
187
+ 4. Clean up any created files or state
188
+ 5. Add appropriate timeout handling
189
+ 6. Update this README with new test descriptions
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env npx tsx
2
+
3
+ /**
4
+ * Manual Test: Basic Browser Login Flow
5
+ *
6
+ * This test validates the complete browser-based authentication flow:
7
+ * 1. Creates a login session with the API
8
+ * 2. Opens browser for user authentication
9
+ * 3. Polls for authentication completion
10
+ * 4. Retrieves and stores JWT token
11
+ *
12
+ * Prerequisites:
13
+ * - Valid KNOWHOW_API_URL environment variable
14
+ * - Network connection to Knowhow API
15
+ * - Browser available on the system
16
+ *
17
+ * Usage: npx tsx ./tests/manual/browser-login/test_browser_login_basic.ts
18
+ */
19
+
20
+ import { BrowserLoginService } from '../../../src/auth/browserLogin';
21
+ import * as fs from 'fs';
22
+ import * as path from 'path';
23
+
24
+ async function testBasicBrowserLogin(): Promise<void> {
25
+ console.log('\n=== Basic Browser Login Test ===\n');
26
+
27
+ let success = false;
28
+ const configDir = path.join(process.cwd(), '.knowhow');
29
+ const jwtFile = path.join(configDir, '.jwt');
30
+
31
+ // Clean up any existing JWT file
32
+ if (fs.existsSync(jwtFile)) {
33
+ fs.unlinkSync(jwtFile);
34
+ console.log('๐Ÿงน Cleaned up existing JWT file');
35
+ }
36
+
37
+ try {
38
+ console.log('1. Initializing BrowserLoginService...');
39
+ const browserLogin = new BrowserLoginService();
40
+
41
+ console.log('2. Starting browser login flow...');
42
+ console.log(' Note: This will open your browser and require manual authentication');
43
+ console.log(' Please complete the authentication process in your browser');
44
+
45
+ await browserLogin.login();
46
+
47
+ console.log('3. Verifying JWT file was created...');
48
+ if (fs.existsSync(jwtFile)) {
49
+ const jwtContent = fs.readFileSync(jwtFile, 'utf8');
50
+
51
+ // Check file permissions
52
+ const stats = fs.statSync(jwtFile);
53
+ const permissions = stats.mode & parseInt('777', 8);
54
+
55
+ console.log(` โœ… JWT file created: ${jwtFile}`);
56
+ console.log(` โœ… JWT length: ${jwtContent.length} characters`);
57
+ console.log(` โœ… File permissions: ${permissions.toString(8)} (should be 600)`);
58
+
59
+ // Basic JWT validation
60
+ const parts = jwtContent.split('.');
61
+ if (parts.length === 3) {
62
+ console.log(' โœ… JWT has correct structure (3 parts)');
63
+ success = true;
64
+ } else {
65
+ console.log(' โŒ JWT has incorrect structure');
66
+ }
67
+
68
+ if (permissions === parseInt('600', 8)) {
69
+ console.log(' โœ… JWT file has correct permissions (600)');
70
+ } else {
71
+ console.log(` โš ๏ธ JWT file permissions may be incorrect: ${permissions.toString(8)}`);
72
+ }
73
+
74
+ } else {
75
+ console.log(' โŒ JWT file was not created');
76
+ }
77
+
78
+ } catch (error) {
79
+ console.error('โŒ Test failed with error:', error.message);
80
+
81
+ if (error.code === 'USER_CANCELLED') {
82
+ console.log(' โ„น๏ธ Authentication was cancelled by user (this is expected for Ctrl+C)');
83
+ } else if (error.code === 'TIMEOUT') {
84
+ console.log(' โš ๏ธ Authentication timed out (user may not have completed authentication)');
85
+ } else if (error.code === 'NETWORK_ERROR') {
86
+ console.log(' โŒ Network error - check API connectivity');
87
+ }
88
+ }
89
+
90
+ console.log('\n=== Test Results ===');
91
+ if (success) {
92
+ console.log('โœ… Browser login test PASSED');
93
+ console.log(' - Session created successfully');
94
+ console.log(' - Browser opened for authentication');
95
+ console.log(' - JWT retrieved and stored securely');
96
+ process.exit(0);
97
+ } else {
98
+ console.log('โŒ Browser login test FAILED');
99
+ console.log(' See error details above');
100
+ process.exit(1);
101
+ }
102
+ }
103
+
104
+ // Handle graceful shutdown
105
+ process.on('SIGINT', () => {
106
+ console.log('\n\n๐Ÿ›‘ Test interrupted by user (Ctrl+C)');
107
+ console.log(' This tests the graceful cancellation feature');
108
+ process.exit(0);
109
+ });
110
+
111
+ // Run the test
112
+ testBasicBrowserLogin().catch((error) => {
113
+ console.error('Unhandled error:', error);
114
+ process.exit(1);
115
+ });