@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.
- package/package.json +1 -1
- package/src/agents/tools/aiClient.ts +36 -0
- package/src/agents/tools/lintFile.ts +1 -1
- package/src/agents/tools/list.ts +34 -0
- package/src/agents/tools/ycmd/tools/diagnostics.ts +2 -0
- package/src/ai.ts +5 -4
- package/src/auth/browserLogin.ts +283 -0
- package/src/auth/errors.ts +6 -0
- package/src/auth/spinner.ts +23 -0
- package/src/chat/CliChatService.ts +25 -6
- package/src/chat/modules/AgentModule.ts +1 -2
- package/src/chat/modules/AskModule.ts +1 -2
- package/src/chat/types.ts +14 -4
- package/src/chat-old.ts +446 -0
- package/src/chat.ts +48 -433
- package/src/cli.ts +5 -12
- package/src/embeddings.ts +1 -1
- package/src/index.ts +0 -8
- package/src/login.ts +14 -1
- package/src/microphone.ts +0 -1
- package/src/plugins/downloader/downloader.ts +34 -122
- package/src/services/KnowhowClient.ts +3 -0
- package/src/services/index.ts +1 -2
- package/tests/manual/browser-login/README.md +189 -0
- package/tests/manual/browser-login/test_browser_login_basic.ts +115 -0
- package/tests/manual/browser-login/test_cli_integration.ts +169 -0
- package/tests/manual/browser-login/test_cross_platform_browser.ts +186 -0
- package/tests/manual/browser-login/test_error_scenarios.ts +223 -0
- package/tests/manual/cli/no-env.sh +256 -0
- package/ts_build/src/agents/tools/aiClient.d.ts +2 -0
- package/ts_build/src/agents/tools/aiClient.js +21 -1
- package/ts_build/src/agents/tools/aiClient.js.map +1 -1
- package/ts_build/src/agents/tools/lintFile.js +1 -1
- package/ts_build/src/agents/tools/lintFile.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +32 -0
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/agents/tools/ycmd/tools/diagnostics.js +1 -0
- package/ts_build/src/agents/tools/ycmd/tools/diagnostics.js.map +1 -1
- package/ts_build/src/ai.d.ts +1 -1
- package/ts_build/src/ai.js +2 -1
- package/ts_build/src/ai.js.map +1 -1
- package/ts_build/src/auth/browserLogin.d.ts +11 -0
- package/ts_build/src/auth/browserLogin.js +197 -0
- package/ts_build/src/auth/browserLogin.js.map +1 -0
- package/ts_build/src/auth/errors.d.ts +4 -0
- package/ts_build/src/auth/errors.js +13 -0
- package/ts_build/src/auth/errors.js.map +1 -0
- package/ts_build/src/auth/spinner.d.ts +7 -0
- package/ts_build/src/auth/spinner.js +23 -0
- package/ts_build/src/auth/spinner.js.map +1 -0
- package/ts_build/src/chat/CliChatService.d.ts +4 -3
- package/ts_build/src/chat/CliChatService.js +18 -4
- package/ts_build/src/chat/CliChatService.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.d.ts +1 -1
- package/ts_build/src/chat/modules/AgentModule.js +1 -2
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/AskModule.js +1 -2
- package/ts_build/src/chat/modules/AskModule.js.map +1 -1
- package/ts_build/src/chat/types.d.ts +5 -3
- package/ts_build/src/chat-old.d.ts +13 -0
- package/ts_build/src/chat-old.js +340 -0
- package/ts_build/src/chat-old.js.map +1 -0
- package/ts_build/src/chat.d.ts +3 -13
- package/ts_build/src/chat.js +38 -331
- package/ts_build/src/chat.js.map +1 -1
- package/ts_build/src/chat2.d.ts +1 -1
- package/ts_build/src/chat2.js +2 -2
- package/ts_build/src/chat2.js.map +1 -1
- package/ts_build/src/cli.js +3 -9
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/embeddings.js.map +1 -1
- package/ts_build/src/index.d.ts +0 -2
- package/ts_build/src/index.js +1 -9
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/login.d.ts +1 -1
- package/ts_build/src/login.js +14 -0
- package/ts_build/src/login.js.map +1 -1
- package/ts_build/src/microphone.js.map +1 -1
- package/ts_build/src/plugins/downloader/downloader.d.ts +1 -6
- package/ts_build/src/plugins/downloader/downloader.js +26 -97
- package/ts_build/src/plugins/downloader/downloader.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.js +3 -0
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/index.js +1 -2
- package/ts_build/src/services/index.js.map +1 -1
- package/ts_build/tests/manual/browser-login/test_browser_login_basic.d.ts +2 -0
- package/ts_build/tests/manual/browser-login/test_browser_login_basic.js +108 -0
- package/ts_build/tests/manual/browser-login/test_browser_login_basic.js.map +1 -0
- package/ts_build/tests/manual/browser-login/test_cli_integration.d.ts +2 -0
- package/ts_build/tests/manual/browser-login/test_cli_integration.js +153 -0
- package/ts_build/tests/manual/browser-login/test_cli_integration.js.map +1 -0
- package/ts_build/tests/manual/browser-login/test_cross_platform_browser.d.ts +2 -0
- package/ts_build/tests/manual/browser-login/test_cross_platform_browser.js +159 -0
- package/ts_build/tests/manual/browser-login/test_cross_platform_browser.js.map +1 -0
- package/ts_build/tests/manual/browser-login/test_error_scenarios.d.ts +2 -0
- package/ts_build/tests/manual/browser-login/test_error_scenarios.js +197 -0
- package/ts_build/tests/manual/browser-login/test_error_scenarios.js.map +1 -0
- package/src/agents/vim/vim.ts +0 -152
- 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
|
|
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
|
-
//
|
|
111
|
-
const
|
|
112
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
139
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
162
|
+
const transcript = await openAi.audio.transcriptions
|
|
251
163
|
.create({
|
|
252
164
|
file: fs.createReadStream(file),
|
|
253
165
|
model: "whisper-1",
|
package/src/services/index.ts
CHANGED
|
@@ -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(
|
|
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
|
+
});
|