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.
Files changed (130) hide show
  1. package/.github/workflows/publish.yml +6 -6
  2. package/.github/workflows/test.yml +1 -1
  3. package/.prettierrc +5 -3
  4. package/dist/index.js +24 -25
  5. package/dist/package.json +7 -3
  6. package/dist/src/agents/app.js +8 -8
  7. package/dist/src/agents/backend.js +3 -3
  8. package/dist/src/agents/devops.js +8 -8
  9. package/dist/src/agents/frontend.js +3 -3
  10. package/dist/src/agents/fullstack.js +3 -3
  11. package/dist/src/agents/index.js +18 -18
  12. package/dist/src/agents/quality.js +8 -8
  13. package/dist/src/agents/security.js +8 -8
  14. package/dist/src/client.js +115 -127
  15. package/dist/src/commands/api-keys.js +181 -202
  16. package/dist/src/commands/auth.js +16 -25
  17. package/dist/src/commands/autocomplete.js +8 -8
  18. package/dist/src/commands/billing.js +10 -19
  19. package/dist/src/commands/chat.js +139 -170
  20. package/dist/src/commands/clusters.js +21 -30
  21. package/dist/src/commands/code/__tests__/auth-sync.test.js +189 -186
  22. package/dist/src/commands/code/__tests__/fake-api-key-service.js +3 -13
  23. package/dist/src/commands/code/__tests__/fake-auth-service.js +21 -29
  24. package/dist/src/commands/code/__tests__/fake-command-runner.js +22 -33
  25. package/dist/src/commands/code/__tests__/fake-file-store.js +19 -41
  26. package/dist/src/commands/code/__tests__/fake-prompter.js +81 -97
  27. package/dist/src/commands/code/__tests__/setup-flow.test.js +295 -295
  28. package/dist/src/commands/code/adapters/clack-prompter.js +15 -32
  29. package/dist/src/commands/code/adapters/fs-file-store.js +25 -44
  30. package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -41
  31. package/dist/src/commands/code/auth-sync.js +215 -228
  32. package/dist/src/commands/code/errors.js +15 -12
  33. package/dist/src/commands/code/setup.js +390 -425
  34. package/dist/src/commands/code.js +279 -294
  35. package/dist/src/commands/index.js +5 -5
  36. package/dist/src/commands/models.js +16 -25
  37. package/dist/src/commands/users.js +9 -18
  38. package/dist/src/constants/command-structure.js +138 -138
  39. package/dist/src/services/api-key-service.js +132 -152
  40. package/dist/src/services/auth-service.js +81 -95
  41. package/dist/src/services/browser-auth.js +121 -131
  42. package/dist/src/services/chat-service.js +369 -386
  43. package/dist/src/services/cluster-service.js +47 -62
  44. package/dist/src/services/collaborator-service.js +9 -21
  45. package/dist/src/services/flux-service.js +13 -25
  46. package/dist/src/services/helm-service.js +9 -21
  47. package/dist/src/services/kubectl-service.js +15 -29
  48. package/dist/src/utils/config-checker.js +8 -8
  49. package/dist/src/utils/config-loader.js +109 -109
  50. package/dist/src/utils/default-api-key.js +129 -139
  51. package/dist/src/utils/env-manager.js +55 -66
  52. package/dist/src/utils/error-handler.js +62 -62
  53. package/dist/src/utils/logger.js +74 -67
  54. package/dist/src/utils/markdown-renderer.js +28 -28
  55. package/dist/src/utils/opencode-validator.js +67 -69
  56. package/dist/src/utils/token-manager.js +67 -65
  57. package/dist/tests/commands/chat.test.js +30 -39
  58. package/dist/tests/commands/code.test.js +186 -195
  59. package/dist/tests/utils/config-loader.test.js +107 -107
  60. package/dist/tests/utils/env-manager.test.js +81 -90
  61. package/dist/tests/utils/opencode-validator.test.js +42 -41
  62. package/dist/vitest.config.js +1 -1
  63. package/eslint.config.mjs +65 -30
  64. package/index.ts +30 -31
  65. package/package.json +7 -3
  66. package/src/agents/app.ts +9 -9
  67. package/src/agents/backend.ts +4 -4
  68. package/src/agents/devops.ts +9 -9
  69. package/src/agents/frontend.ts +4 -4
  70. package/src/agents/fullstack.ts +4 -4
  71. package/src/agents/index.ts +27 -25
  72. package/src/agents/quality.ts +9 -9
  73. package/src/agents/security.ts +9 -9
  74. package/src/agents/types.ts +10 -10
  75. package/src/client.ts +85 -77
  76. package/src/commands/api-keys.ts +180 -185
  77. package/src/commands/auth.ts +15 -14
  78. package/src/commands/autocomplete.ts +10 -10
  79. package/src/commands/billing.ts +13 -12
  80. package/src/commands/chat.ts +145 -142
  81. package/src/commands/clusters.ts +20 -19
  82. package/src/commands/code/__tests__/auth-sync.test.ts +176 -175
  83. package/src/commands/code/__tests__/fake-api-key-service.ts +2 -2
  84. package/src/commands/code/__tests__/fake-auth-service.ts +18 -18
  85. package/src/commands/code/__tests__/fake-command-runner.ts +28 -22
  86. package/src/commands/code/__tests__/fake-file-store.ts +15 -15
  87. package/src/commands/code/__tests__/fake-prompter.ts +86 -85
  88. package/src/commands/code/__tests__/setup-flow.test.ts +253 -251
  89. package/src/commands/code/adapters/clack-prompter.ts +32 -30
  90. package/src/commands/code/adapters/fs-file-store.ts +18 -17
  91. package/src/commands/code/adapters/spawn-command-runner.ts +20 -15
  92. package/src/commands/code/auth-sync.ts +210 -210
  93. package/src/commands/code/errors.ts +11 -11
  94. package/src/commands/code/ports/auth-services.ts +7 -7
  95. package/src/commands/code/ports/command-runner.ts +2 -2
  96. package/src/commands/code/ports/file-store.ts +3 -3
  97. package/src/commands/code/ports/prompter.ts +13 -13
  98. package/src/commands/code/setup.ts +408 -406
  99. package/src/commands/code.ts +288 -287
  100. package/src/commands/index.ts +11 -10
  101. package/src/commands/models.ts +19 -18
  102. package/src/commands/users.ts +11 -10
  103. package/src/constants/command-structure.ts +159 -159
  104. package/src/services/api-key-service.ts +85 -85
  105. package/src/services/auth-service.ts +55 -54
  106. package/src/services/browser-auth.ts +62 -62
  107. package/src/services/chat-service.ts +170 -171
  108. package/src/services/cluster-service.ts +28 -28
  109. package/src/services/collaborator-service.ts +6 -6
  110. package/src/services/flux-service.ts +17 -17
  111. package/src/services/helm-service.ts +11 -11
  112. package/src/services/kubectl-service.ts +12 -12
  113. package/src/types/api.d.ts +1933 -1933
  114. package/src/types/json.d.ts +1 -1
  115. package/src/utils/config-checker.ts +7 -7
  116. package/src/utils/config-loader.ts +130 -129
  117. package/src/utils/default-api-key.ts +81 -80
  118. package/src/utils/env-manager.ts +37 -37
  119. package/src/utils/error-handler.ts +64 -64
  120. package/src/utils/logger.ts +72 -66
  121. package/src/utils/markdown-renderer.ts +28 -28
  122. package/src/utils/opencode-validator.ts +72 -71
  123. package/src/utils/token-manager.ts +69 -68
  124. package/tests/commands/chat.test.ts +32 -31
  125. package/tests/commands/code.test.ts +182 -181
  126. package/tests/utils/config-loader.test.ts +111 -110
  127. package/tests/utils/env-manager.test.ts +83 -79
  128. package/tests/utils/opencode-validator.test.ts +43 -42
  129. package/tsconfig.json +2 -1
  130. package/vitest.config.ts +2 -2
@@ -1,56 +1,56 @@
1
- import * as http from "http";
2
- import * as net from "net";
3
- import * as crypto from "crypto";
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 { keycloakUrl, realm, clientId, callbackPort, debug } = this.options;
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("hex");
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("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");
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
- }>(resolve => {
48
+ success: boolean;
49
+ }>((resolve) => {
50
50
  let resolved = false;
51
51
  const sockets = new Set<net.Socket>();
52
52
 
53
- const safeResolve = (result: { success: boolean; code?: string; error?: string }) => {
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((req, res) => {
67
- const requestUrl = new URL(req.url || "", `http://localhost:${callbackPort}`);
66
+ const server = http.createServer((request, res) => {
67
+ const requestUrl = new URL(request.url || '', `http://localhost:${callbackPort}`);
68
68
 
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") || "";
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, { "Content-Type": "text/html; charset=utf-8", Connection: "close" });
133
+ res.writeHead(200, { Connection: 'close', 'Content-Type': 'text/html; charset=utf-8' });
134
134
  res.end(
135
135
  errorPage(
136
- "Authentication Failed",
137
- requestUrl.searchParams.get("error_description") || error
138
- )
136
+ 'Authentication Failed',
137
+ requestUrl.searchParams.get('error_description') || error,
138
+ ),
139
139
  );
140
- safeResolve({ success: false, error });
140
+ safeResolve({ error, success: false });
141
141
  return;
142
142
  }
143
143
 
144
144
  if (receivedState !== state) {
145
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", Connection: "close" });
145
+ res.writeHead(200, { Connection: 'close', 'Content-Type': 'text/html; charset=utf-8' });
146
146
  res.end(
147
- errorPage("Authentication Failed", "Invalid state parameter. Please try again.")
147
+ errorPage('Authentication Failed', 'Invalid state parameter. Please try again.'),
148
148
  );
149
- safeResolve({ success: false, error: "Invalid state parameter" });
149
+ safeResolve({ error: 'Invalid state parameter', success: false });
150
150
  return;
151
151
  }
152
152
 
153
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", Connection: "close" });
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, code });
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("connection", (socket: net.Socket) => {
213
+ server.on('connection', (socket: net.Socket) => {
214
214
  sockets.add(socket);
215
- socket.on("close", () => sockets.delete(socket));
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({ success: false, error: "Authentication timed out" });
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("open").then(m => m.default);
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 generateCodeVerifier(): string {
290
- return crypto.randomBytes(32).toString("base64url");
289
+ private generateCodeChallenge(verifier: string): string {
290
+ return crypto.createHash('sha256').update(verifier).digest('base64url');
291
291
  }
292
292
 
293
- private generateCodeChallenge(verifier: string): string {
294
- return crypto.createHash("sha256").update(verifier).digest("base64url");
293
+ private generateCodeVerifier(): string {
294
+ return crypto.randomBytes(32).toString('base64url');
295
295
  }
296
296
  }