berget 2.2.7 → 2.2.9
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/.github/workflows/publish.yml +6 -6
- package/.github/workflows/test.yml +1 -1
- package/.prettierrc +5 -3
- package/dist/index.js +24 -25
- package/dist/package.json +7 -3
- package/dist/src/agents/app.js +8 -8
- package/dist/src/agents/backend.js +3 -3
- package/dist/src/agents/devops.js +8 -8
- package/dist/src/agents/frontend.js +3 -3
- package/dist/src/agents/fullstack.js +3 -3
- package/dist/src/agents/index.js +18 -18
- package/dist/src/agents/quality.js +8 -8
- package/dist/src/agents/security.js +8 -8
- package/dist/src/client.js +115 -127
- package/dist/src/commands/api-keys.js +181 -202
- package/dist/src/commands/auth.js +16 -25
- package/dist/src/commands/autocomplete.js +8 -8
- package/dist/src/commands/billing.js +10 -19
- package/dist/src/commands/chat.js +139 -170
- package/dist/src/commands/clusters.js +21 -30
- package/dist/src/commands/code/__tests__/auth-sync.test.js +189 -186
- package/dist/src/commands/code/__tests__/fake-api-key-service.js +3 -13
- package/dist/src/commands/code/__tests__/fake-auth-service.js +21 -29
- package/dist/src/commands/code/__tests__/fake-command-runner.js +22 -33
- package/dist/src/commands/code/__tests__/fake-file-store.js +19 -41
- package/dist/src/commands/code/__tests__/fake-prompter.js +81 -97
- package/dist/src/commands/code/__tests__/setup-flow.test.js +295 -295
- package/dist/src/commands/code/adapters/clack-prompter.js +15 -32
- package/dist/src/commands/code/adapters/fs-file-store.js +25 -44
- package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -41
- package/dist/src/commands/code/auth-sync.js +215 -228
- package/dist/src/commands/code/errors.js +15 -12
- package/dist/src/commands/code/setup.js +390 -425
- package/dist/src/commands/code.js +279 -294
- package/dist/src/commands/index.js +5 -5
- package/dist/src/commands/models.js +16 -25
- package/dist/src/commands/users.js +9 -18
- package/dist/src/constants/command-structure.js +138 -138
- package/dist/src/services/api-key-service.js +132 -152
- package/dist/src/services/auth-service.js +81 -95
- package/dist/src/services/browser-auth.js +121 -131
- package/dist/src/services/chat-service.js +369 -386
- package/dist/src/services/cluster-service.js +47 -62
- package/dist/src/services/collaborator-service.js +9 -21
- package/dist/src/services/flux-service.js +13 -25
- package/dist/src/services/helm-service.js +9 -21
- package/dist/src/services/kubectl-service.js +15 -29
- package/dist/src/utils/config-checker.js +8 -8
- package/dist/src/utils/config-loader.js +109 -109
- package/dist/src/utils/default-api-key.js +129 -139
- package/dist/src/utils/env-manager.js +55 -66
- package/dist/src/utils/error-handler.js +62 -62
- package/dist/src/utils/logger.js +74 -67
- package/dist/src/utils/markdown-renderer.js +28 -28
- package/dist/src/utils/opencode-validator.js +67 -69
- package/dist/src/utils/token-manager.js +67 -65
- package/dist/tests/commands/chat.test.js +30 -39
- package/dist/tests/commands/code.test.js +186 -195
- package/dist/tests/utils/config-loader.test.js +107 -107
- package/dist/tests/utils/env-manager.test.js +81 -90
- package/dist/tests/utils/opencode-validator.test.js +42 -41
- package/dist/vitest.config.js +1 -1
- package/eslint.config.mjs +65 -30
- package/index.ts +30 -31
- package/package.json +7 -3
- package/src/agents/app.ts +9 -9
- package/src/agents/backend.ts +4 -4
- package/src/agents/devops.ts +9 -9
- package/src/agents/frontend.ts +4 -4
- package/src/agents/fullstack.ts +4 -4
- package/src/agents/index.ts +27 -25
- package/src/agents/quality.ts +9 -9
- package/src/agents/security.ts +9 -9
- package/src/agents/types.ts +10 -10
- package/src/client.ts +85 -77
- package/src/commands/api-keys.ts +180 -185
- package/src/commands/auth.ts +15 -14
- package/src/commands/autocomplete.ts +10 -10
- package/src/commands/billing.ts +13 -12
- package/src/commands/chat.ts +145 -142
- package/src/commands/clusters.ts +20 -19
- package/src/commands/code/__tests__/auth-sync.test.ts +176 -175
- package/src/commands/code/__tests__/fake-api-key-service.ts +2 -2
- package/src/commands/code/__tests__/fake-auth-service.ts +18 -18
- package/src/commands/code/__tests__/fake-command-runner.ts +28 -22
- package/src/commands/code/__tests__/fake-file-store.ts +15 -15
- package/src/commands/code/__tests__/fake-prompter.ts +86 -85
- package/src/commands/code/__tests__/setup-flow.test.ts +253 -251
- package/src/commands/code/adapters/clack-prompter.ts +32 -30
- package/src/commands/code/adapters/fs-file-store.ts +18 -17
- package/src/commands/code/adapters/spawn-command-runner.ts +20 -15
- package/src/commands/code/auth-sync.ts +210 -210
- package/src/commands/code/errors.ts +11 -11
- package/src/commands/code/ports/auth-services.ts +7 -7
- package/src/commands/code/ports/command-runner.ts +2 -2
- package/src/commands/code/ports/file-store.ts +3 -3
- package/src/commands/code/ports/prompter.ts +13 -13
- package/src/commands/code/setup.ts +408 -406
- package/src/commands/code.ts +288 -287
- package/src/commands/index.ts +11 -10
- package/src/commands/models.ts +19 -18
- package/src/commands/users.ts +11 -10
- package/src/constants/command-structure.ts +159 -159
- package/src/services/api-key-service.ts +85 -85
- package/src/services/auth-service.ts +55 -54
- package/src/services/browser-auth.ts +62 -62
- package/src/services/chat-service.ts +170 -171
- package/src/services/cluster-service.ts +28 -28
- package/src/services/collaborator-service.ts +6 -6
- package/src/services/flux-service.ts +17 -17
- package/src/services/helm-service.ts +11 -11
- package/src/services/kubectl-service.ts +12 -12
- package/src/types/api.d.ts +1933 -1933
- package/src/types/json.d.ts +1 -1
- package/src/utils/config-checker.ts +7 -7
- package/src/utils/config-loader.ts +130 -129
- package/src/utils/default-api-key.ts +81 -80
- package/src/utils/env-manager.ts +37 -37
- package/src/utils/error-handler.ts +64 -64
- package/src/utils/logger.ts +72 -66
- package/src/utils/markdown-renderer.ts +28 -28
- package/src/utils/opencode-validator.ts +72 -71
- package/src/utils/token-manager.ts +69 -68
- package/tests/commands/chat.test.ts +32 -31
- package/tests/commands/code.test.ts +182 -181
- package/tests/utils/config-loader.test.ts +111 -110
- package/tests/utils/env-manager.test.ts +83 -79
- package/tests/utils/opencode-validator.test.ts +43 -42
- package/tsconfig.json +2 -1
- package/vitest.config.ts +2 -2
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
4
|
-
|
|
5
|
-
export interface BrowserAuthResult {
|
|
6
|
-
success: boolean;
|
|
7
|
-
accessToken?: string;
|
|
8
|
-
refreshToken?: string;
|
|
9
|
-
expiresIn?: number;
|
|
10
|
-
error?: string;
|
|
11
|
-
}
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import * as http from 'node:http';
|
|
3
|
+
import * as net from 'node:net';
|
|
12
4
|
|
|
13
5
|
export interface BrowserAuthOptions {
|
|
14
|
-
keycloakUrl: string;
|
|
15
|
-
realm: string;
|
|
16
|
-
clientId: string;
|
|
17
6
|
callbackPort: number;
|
|
7
|
+
clientId: string;
|
|
18
8
|
debug?: boolean;
|
|
9
|
+
keycloakUrl: string;
|
|
10
|
+
realm: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface BrowserAuthResult {
|
|
14
|
+
accessToken?: string;
|
|
15
|
+
error?: string;
|
|
16
|
+
expiresIn?: number;
|
|
17
|
+
refreshToken?: string;
|
|
18
|
+
success: boolean;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export class BrowserAuth {
|
|
22
22
|
constructor(private readonly options: BrowserAuthOptions) {}
|
|
23
23
|
|
|
24
24
|
async start(): Promise<BrowserAuthResult> {
|
|
25
|
-
const {
|
|
25
|
+
const { callbackPort, clientId, debug, keycloakUrl, realm } = this.options;
|
|
26
26
|
|
|
27
27
|
// Generate PKCE code verifier and challenge
|
|
28
28
|
const codeVerifier = this.generateCodeVerifier();
|
|
29
29
|
const codeChallenge = this.generateCodeChallenge(codeVerifier);
|
|
30
|
-
const state = crypto.randomBytes(16).toString(
|
|
30
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
31
31
|
|
|
32
32
|
const redirectUri = `http://localhost:${callbackPort}/callback`;
|
|
33
33
|
|
|
34
34
|
// Build authorization URL
|
|
35
35
|
const authUrl = new URL(`${keycloakUrl}/realms/${realm}/protocol/openid-connect/auth`);
|
|
36
|
-
authUrl.searchParams.set(
|
|
37
|
-
authUrl.searchParams.set(
|
|
38
|
-
authUrl.searchParams.set(
|
|
39
|
-
authUrl.searchParams.set(
|
|
40
|
-
authUrl.searchParams.set(
|
|
41
|
-
authUrl.searchParams.set(
|
|
42
|
-
authUrl.searchParams.set(
|
|
36
|
+
authUrl.searchParams.set('client_id', clientId);
|
|
37
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
38
|
+
authUrl.searchParams.set('redirect_uri', redirectUri);
|
|
39
|
+
authUrl.searchParams.set('scope', 'openid email profile');
|
|
40
|
+
authUrl.searchParams.set('state', state);
|
|
41
|
+
authUrl.searchParams.set('code_challenge', codeChallenge);
|
|
42
|
+
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
43
43
|
|
|
44
44
|
// Create a promise that resolves when we receive the callback
|
|
45
45
|
const authResult = await new Promise<{
|
|
46
|
-
success: boolean;
|
|
47
46
|
code?: string;
|
|
48
47
|
error?: string;
|
|
49
|
-
|
|
48
|
+
success: boolean;
|
|
49
|
+
}>((resolve) => {
|
|
50
50
|
let resolved = false;
|
|
51
51
|
const sockets = new Set<net.Socket>();
|
|
52
52
|
|
|
53
|
-
const safeResolve = (result: {
|
|
53
|
+
const safeResolve = (result: { code?: string; error?: string; success: boolean }) => {
|
|
54
54
|
if (resolved) return;
|
|
55
55
|
resolved = true;
|
|
56
56
|
clearTimeout(timeoutHandle);
|
|
@@ -63,13 +63,13 @@ export class BrowserAuth {
|
|
|
63
63
|
resolve(result);
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
const server = http.createServer((
|
|
67
|
-
const requestUrl = new URL(
|
|
66
|
+
const server = http.createServer((request, res) => {
|
|
67
|
+
const requestUrl = new URL(request.url || '', `http://localhost:${callbackPort}`);
|
|
68
68
|
|
|
69
|
-
if (requestUrl.pathname ===
|
|
70
|
-
const receivedState = requestUrl.searchParams.get(
|
|
71
|
-
const code = requestUrl.searchParams.get(
|
|
72
|
-
const error = requestUrl.searchParams.get(
|
|
69
|
+
if (requestUrl.pathname === '/callback') {
|
|
70
|
+
const receivedState = requestUrl.searchParams.get('state') || '';
|
|
71
|
+
const code = requestUrl.searchParams.get('code') || '';
|
|
72
|
+
const error = requestUrl.searchParams.get('error') || '';
|
|
73
73
|
|
|
74
74
|
const errorPage = (title: string, message: string) => `
|
|
75
75
|
<!DOCTYPE html>
|
|
@@ -130,27 +130,27 @@ export class BrowserAuth {
|
|
|
130
130
|
// Set Connection: close so the browser doesn't keep the socket alive
|
|
131
131
|
// after we respond, and force-end the connection
|
|
132
132
|
if (error) {
|
|
133
|
-
res.writeHead(200, {
|
|
133
|
+
res.writeHead(200, { Connection: 'close', 'Content-Type': 'text/html; charset=utf-8' });
|
|
134
134
|
res.end(
|
|
135
135
|
errorPage(
|
|
136
|
-
|
|
137
|
-
requestUrl.searchParams.get(
|
|
138
|
-
)
|
|
136
|
+
'Authentication Failed',
|
|
137
|
+
requestUrl.searchParams.get('error_description') || error,
|
|
138
|
+
),
|
|
139
139
|
);
|
|
140
|
-
safeResolve({ success: false
|
|
140
|
+
safeResolve({ error, success: false });
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
if (receivedState !== state) {
|
|
145
|
-
res.writeHead(200, {
|
|
145
|
+
res.writeHead(200, { Connection: 'close', 'Content-Type': 'text/html; charset=utf-8' });
|
|
146
146
|
res.end(
|
|
147
|
-
errorPage(
|
|
147
|
+
errorPage('Authentication Failed', 'Invalid state parameter. Please try again.'),
|
|
148
148
|
);
|
|
149
|
-
safeResolve({
|
|
149
|
+
safeResolve({ error: 'Invalid state parameter', success: false });
|
|
150
150
|
return;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
res.writeHead(200, {
|
|
153
|
+
res.writeHead(200, { Connection: 'close', 'Content-Type': 'text/html; charset=utf-8' });
|
|
154
154
|
res.end(`
|
|
155
155
|
<!DOCTYPE html>
|
|
156
156
|
<html lang="en">
|
|
@@ -205,14 +205,14 @@ export class BrowserAuth {
|
|
|
205
205
|
</body>
|
|
206
206
|
</html>
|
|
207
207
|
`);
|
|
208
|
-
safeResolve({ success: true
|
|
208
|
+
safeResolve({ code, success: true });
|
|
209
209
|
}
|
|
210
210
|
});
|
|
211
211
|
|
|
212
212
|
// Track sockets so we can destroy them on shutdown
|
|
213
|
-
server.on(
|
|
213
|
+
server.on('connection', (socket: net.Socket) => {
|
|
214
214
|
sockets.add(socket);
|
|
215
|
-
socket.on(
|
|
215
|
+
socket.on('close', () => sockets.delete(socket));
|
|
216
216
|
});
|
|
217
217
|
|
|
218
218
|
server.listen(callbackPort, () => {
|
|
@@ -224,15 +224,15 @@ export class BrowserAuth {
|
|
|
224
224
|
// Set timeout for the server
|
|
225
225
|
const timeoutHandle = setTimeout(
|
|
226
226
|
() => {
|
|
227
|
-
safeResolve({
|
|
227
|
+
safeResolve({ error: 'Authentication timed out', success: false });
|
|
228
228
|
},
|
|
229
|
-
5 * 60 * 1000
|
|
229
|
+
5 * 60 * 1000,
|
|
230
230
|
); // 5 minute timeout
|
|
231
231
|
|
|
232
232
|
// Open browser
|
|
233
233
|
(async () => {
|
|
234
234
|
try {
|
|
235
|
-
const open = await import(
|
|
235
|
+
const open = await import('open').then((m) => m.default);
|
|
236
236
|
await open(authUrl.toString());
|
|
237
237
|
} catch {
|
|
238
238
|
// Browser failed to open - user must open URL manually
|
|
@@ -242,55 +242,55 @@ export class BrowserAuth {
|
|
|
242
242
|
|
|
243
243
|
if (!authResult.success || !authResult.code) {
|
|
244
244
|
return {
|
|
245
|
+
error: authResult.error || 'Unknown error',
|
|
245
246
|
success: false,
|
|
246
|
-
error: authResult.error || "Unknown error",
|
|
247
247
|
};
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
// Exchange authorization code for tokens
|
|
251
251
|
const tokenUrl = `${keycloakUrl}/realms/${realm}/protocol/openid-connect/token`;
|
|
252
252
|
const tokenResponse = await fetch(tokenUrl, {
|
|
253
|
-
method: "POST",
|
|
254
|
-
headers: {
|
|
255
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
256
|
-
},
|
|
257
253
|
body: new URLSearchParams({
|
|
258
|
-
grant_type: "authorization_code",
|
|
259
254
|
client_id: clientId,
|
|
260
255
|
code: authResult.code,
|
|
261
|
-
redirect_uri: redirectUri,
|
|
262
256
|
code_verifier: codeVerifier,
|
|
257
|
+
grant_type: 'authorization_code',
|
|
258
|
+
redirect_uri: redirectUri,
|
|
263
259
|
}).toString(),
|
|
260
|
+
headers: {
|
|
261
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
262
|
+
},
|
|
263
|
+
method: 'POST',
|
|
264
264
|
});
|
|
265
265
|
|
|
266
266
|
if (!tokenResponse.ok) {
|
|
267
267
|
const errorText = await tokenResponse.text();
|
|
268
268
|
return {
|
|
269
|
-
success: false,
|
|
270
269
|
error: `Failed to exchange code for tokens: ${errorText}`,
|
|
270
|
+
success: false,
|
|
271
271
|
};
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
const tokenData = (await tokenResponse.json()) as {
|
|
275
275
|
access_token: string;
|
|
276
|
-
refresh_token: string;
|
|
277
276
|
expires_in: number;
|
|
278
277
|
refresh_expires_in?: number;
|
|
278
|
+
refresh_token: string;
|
|
279
279
|
};
|
|
280
280
|
|
|
281
281
|
return {
|
|
282
|
-
success: true,
|
|
283
282
|
accessToken: tokenData.access_token,
|
|
284
|
-
refreshToken: tokenData.refresh_token,
|
|
285
283
|
expiresIn: tokenData.expires_in,
|
|
284
|
+
refreshToken: tokenData.refresh_token,
|
|
285
|
+
success: true,
|
|
286
286
|
};
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
private
|
|
290
|
-
return crypto.
|
|
289
|
+
private generateCodeChallenge(verifier: string): string {
|
|
290
|
+
return crypto.createHash('sha256').update(verifier).digest('base64url');
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
-
private
|
|
294
|
-
return crypto.
|
|
293
|
+
private generateCodeVerifier(): string {
|
|
294
|
+
return crypto.randomBytes(32).toString('base64url');
|
|
295
295
|
}
|
|
296
296
|
}
|