berget 2.2.6 → 2.2.8

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 (145) hide show
  1. package/.github/workflows/publish.yml +2 -2
  2. package/.github/workflows/test.yml +10 -4
  3. package/.husky/pre-commit +1 -0
  4. package/.prettierignore +15 -0
  5. package/.prettierrc +7 -3
  6. package/CONTRIBUTING.md +38 -0
  7. package/README.md +2 -148
  8. package/dist/index.js +10 -11
  9. package/dist/package.json +30 -2
  10. package/dist/src/agents/app.js +28 -0
  11. package/dist/src/agents/backend.js +25 -0
  12. package/dist/src/agents/devops.js +34 -0
  13. package/dist/src/agents/frontend.js +25 -0
  14. package/dist/src/agents/fullstack.js +25 -0
  15. package/dist/src/agents/index.js +61 -0
  16. package/dist/src/agents/quality.js +70 -0
  17. package/dist/src/agents/security.js +26 -0
  18. package/dist/src/agents/types.js +2 -0
  19. package/dist/src/client.js +97 -117
  20. package/dist/src/commands/api-keys.js +75 -90
  21. package/dist/src/commands/auth.js +7 -16
  22. package/dist/src/commands/autocomplete.js +1 -1
  23. package/dist/src/commands/billing.js +6 -17
  24. package/dist/src/commands/chat.js +68 -101
  25. package/dist/src/commands/clusters.js +9 -18
  26. package/dist/src/commands/code/__tests__/auth-sync.test.js +351 -0
  27. package/dist/src/commands/code/__tests__/fake-api-key-service.js +13 -0
  28. package/dist/src/commands/code/__tests__/fake-auth-service.js +47 -0
  29. package/dist/src/commands/code/__tests__/fake-command-runner.js +21 -34
  30. package/dist/src/commands/code/__tests__/fake-file-store.js +20 -33
  31. package/dist/src/commands/code/__tests__/fake-prompter.js +83 -57
  32. package/dist/src/commands/code/__tests__/setup-flow.test.js +359 -92
  33. package/dist/src/commands/code/adapters/clack-prompter.js +15 -22
  34. package/dist/src/commands/code/adapters/fs-file-store.js +26 -40
  35. package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -37
  36. package/dist/src/commands/code/auth-sync.js +270 -0
  37. package/dist/src/commands/code/errors.js +12 -9
  38. package/dist/src/commands/code/ports/auth-services.js +2 -0
  39. package/dist/src/commands/code/setup.js +387 -281
  40. package/dist/src/commands/code.js +205 -332
  41. package/dist/src/commands/index.js +5 -5
  42. package/dist/src/commands/models.js +6 -17
  43. package/dist/src/commands/users.js +5 -16
  44. package/dist/src/constants/command-structure.js +104 -104
  45. package/dist/src/services/api-key-service.js +132 -157
  46. package/dist/src/services/auth-service.js +89 -342
  47. package/dist/src/services/browser-auth.js +268 -0
  48. package/dist/src/services/chat-service.js +371 -401
  49. package/dist/src/services/cluster-service.js +47 -62
  50. package/dist/src/services/collaborator-service.js +10 -25
  51. package/dist/src/services/flux-service.js +14 -29
  52. package/dist/src/services/helm-service.js +10 -25
  53. package/dist/src/services/kubectl-service.js +16 -33
  54. package/dist/src/utils/config-checker.js +3 -3
  55. package/dist/src/utils/config-loader.js +95 -95
  56. package/dist/src/utils/default-api-key.js +124 -134
  57. package/dist/src/utils/env-manager.js +55 -66
  58. package/dist/src/utils/error-handler.js +20 -21
  59. package/dist/src/utils/logger.js +72 -65
  60. package/dist/src/utils/markdown-renderer.js +27 -27
  61. package/dist/src/utils/opencode-validator.js +63 -68
  62. package/dist/src/utils/token-manager.js +74 -45
  63. package/dist/tests/commands/chat.test.js +16 -25
  64. package/dist/tests/commands/code.test.js +95 -104
  65. package/dist/tests/utils/config-loader.test.js +48 -48
  66. package/dist/tests/utils/env-manager.test.js +43 -52
  67. package/dist/tests/utils/opencode-validator.test.js +22 -21
  68. package/dist/vitest.config.js +1 -1
  69. package/eslint.config.mjs +67 -0
  70. package/index.ts +35 -42
  71. package/package.json +30 -2
  72. package/src/agents/app.ts +27 -0
  73. package/src/agents/backend.ts +24 -0
  74. package/src/agents/devops.ts +33 -0
  75. package/src/agents/frontend.ts +24 -0
  76. package/src/agents/fullstack.ts +24 -0
  77. package/src/agents/index.ts +73 -0
  78. package/src/agents/quality.ts +69 -0
  79. package/src/agents/security.ts +26 -0
  80. package/src/agents/types.ts +17 -0
  81. package/src/client.ts +118 -152
  82. package/src/commands/api-keys.ts +241 -333
  83. package/src/commands/auth.ts +22 -27
  84. package/src/commands/autocomplete.ts +9 -9
  85. package/src/commands/billing.ts +20 -24
  86. package/src/commands/chat.ts +248 -338
  87. package/src/commands/clusters.ts +27 -26
  88. package/src/commands/code/__tests__/auth-sync.test.ts +482 -0
  89. package/src/commands/code/__tests__/fake-api-key-service.ts +13 -0
  90. package/src/commands/code/__tests__/fake-auth-service.ts +50 -0
  91. package/src/commands/code/__tests__/fake-command-runner.ts +45 -42
  92. package/src/commands/code/__tests__/fake-file-store.ts +32 -23
  93. package/src/commands/code/__tests__/fake-prompter.ts +116 -77
  94. package/src/commands/code/__tests__/setup-flow.test.ts +624 -268
  95. package/src/commands/code/adapters/clack-prompter.ts +53 -39
  96. package/src/commands/code/adapters/fs-file-store.ts +32 -27
  97. package/src/commands/code/adapters/spawn-command-runner.ts +38 -29
  98. package/src/commands/code/auth-sync.ts +329 -0
  99. package/src/commands/code/errors.ts +18 -18
  100. package/src/commands/code/ports/auth-services.ts +14 -0
  101. package/src/commands/code/ports/command-runner.ts +8 -4
  102. package/src/commands/code/ports/file-store.ts +5 -4
  103. package/src/commands/code/ports/prompter.ts +24 -18
  104. package/src/commands/code/setup.ts +570 -340
  105. package/src/commands/code.ts +338 -539
  106. package/src/commands/index.ts +20 -19
  107. package/src/commands/models.ts +28 -32
  108. package/src/commands/users.ts +15 -21
  109. package/src/constants/command-structure.ts +134 -157
  110. package/src/services/api-key-service.ts +105 -122
  111. package/src/services/auth-service.ts +99 -345
  112. package/src/services/browser-auth.ts +296 -0
  113. package/src/services/chat-service.ts +265 -299
  114. package/src/services/cluster-service.ts +42 -45
  115. package/src/services/collaborator-service.ts +14 -19
  116. package/src/services/flux-service.ts +23 -25
  117. package/src/services/helm-service.ts +19 -21
  118. package/src/services/kubectl-service.ts +17 -19
  119. package/src/types/api.d.ts +1905 -1907
  120. package/src/types/json.d.ts +2 -2
  121. package/src/utils/config-checker.ts +10 -10
  122. package/src/utils/config-loader.ts +162 -178
  123. package/src/utils/default-api-key.ts +114 -125
  124. package/src/utils/env-manager.ts +53 -57
  125. package/src/utils/error-handler.ts +61 -56
  126. package/src/utils/logger.ts +79 -73
  127. package/src/utils/markdown-renderer.ts +31 -31
  128. package/src/utils/opencode-validator.ts +85 -89
  129. package/src/utils/token-manager.ts +108 -87
  130. package/templates/agents/app.md +1 -0
  131. package/templates/agents/backend.md +1 -0
  132. package/templates/agents/devops.md +2 -0
  133. package/templates/agents/frontend.md +1 -0
  134. package/templates/agents/fullstack.md +1 -0
  135. package/templates/agents/quality.md +45 -40
  136. package/templates/agents/security.md +1 -0
  137. package/tests/commands/chat.test.ts +53 -62
  138. package/tests/commands/code.test.ts +265 -310
  139. package/tests/utils/config-loader.test.ts +189 -188
  140. package/tests/utils/env-manager.test.ts +110 -113
  141. package/tests/utils/opencode-validator.test.ts +52 -56
  142. package/tsconfig.json +4 -3
  143. package/vitest.config.ts +3 -3
  144. package/AGENTS.md +0 -374
  145. package/TODO.md +0 -19
@@ -1,377 +1,124 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
- return new (P || (P = Promise))(function (resolve, reject) {
28
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
- step((generator = generator.apply(thisArg, _arguments || [])).next());
32
- });
33
- };
34
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
35
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
36
4
  };
37
5
  Object.defineProperty(exports, "__esModule", { value: true });
38
6
  exports.AuthService = void 0;
39
- const client_1 = require("../client");
40
- // We'll use dynamic import for 'open' to support ESM modules in CommonJS
41
7
  const chalk_1 = __importDefault(require("chalk"));
42
- const error_handler_1 = require("../utils/error-handler");
8
+ const client_1 = require("../client");
43
9
  const command_structure_1 = require("../constants/command-structure");
44
- const http = __importStar(require("http"));
45
- const crypto = __importStar(require("crypto"));
46
- const url = __importStar(require("url"));
10
+ const error_handler_1 = require("../utils/error-handler");
11
+ const browser_auth_1 = require("./browser-auth");
47
12
  // Keycloak configuration based on environment
48
13
  const isStageMode = process.argv.includes('--stage');
49
14
  const isLocalMode = process.argv.includes('--local');
50
- const KEYCLOAK_URL = (isStageMode || isLocalMode)
51
- ? 'https://keycloak.stage.berget.ai'
52
- : 'https://keycloak.berget.ai';
15
+ const KEYCLOAK_URL = isStageMode || isLocalMode ? 'https://keycloak.stage.berget.ai' : 'https://keycloak.berget.ai';
53
16
  const KEYCLOAK_REALM = 'berget';
54
17
  const KEYCLOAK_CLIENT_ID = 'berget-code';
55
18
  const CALLBACK_PORT = 8787;
56
- /**
57
- * Generate a random string for PKCE code_verifier
58
- */
59
- function generateCodeVerifier() {
60
- return crypto.randomBytes(32).toString('base64url');
61
- }
62
- /**
63
- * Generate code_challenge from code_verifier using S256 method
64
- */
65
- function generateCodeChallenge(verifier) {
66
- return crypto.createHash('sha256').update(verifier).digest('base64url');
67
- }
68
19
  /**
69
20
  * Service for authentication operations
70
21
  * Command group: auth
71
22
  */
72
23
  class AuthService {
73
- constructor() {
74
- this.client = (0, client_1.createAuthenticatedClient)();
75
- }
24
+ // Command group name for this service
25
+ static COMMAND_GROUP = command_structure_1.COMMAND_GROUPS.AUTH;
26
+ // Subcommands for this service
27
+ static COMMANDS = command_structure_1.SUBCOMMANDS.AUTH;
28
+ static instance;
29
+ constructor() { }
76
30
  static getInstance() {
77
31
  if (!AuthService.instance) {
78
32
  AuthService.instance = new AuthService();
79
33
  }
80
34
  return AuthService.instance;
81
35
  }
82
- whoami() {
83
- return __awaiter(this, void 0, void 0, function* () {
36
+ /**
37
+ * Browser-based PKCE login for interactive CLI use.
38
+ * Prints status to stdout/stderr. Use loginInteractive() when you need
39
+ * a silent, UI-agnostic result (e.g. inside the setup wizard).
40
+ */
41
+ async login() {
42
+ try {
43
+ (0, client_1.clearAuthToken)();
44
+ console.log(chalk_1.default.blue('Initiating login process...'));
45
+ const auth = makeBrowserAuth(process.argv.includes('--debug'));
46
+ const result = await auth.start();
47
+ if (!result.success) {
48
+ console.log(chalk_1.default.red(`\nAuthentication failed: ${result.error || 'Unknown error'}`));
49
+ return false;
50
+ }
51
+ (0, client_1.saveAuthToken)(result.accessToken, result.refreshToken, result.expiresIn);
52
+ if (process.argv.includes('--debug')) {
53
+ console.log(chalk_1.default.yellow('DEBUG: Token data received:'));
54
+ console.log(chalk_1.default.yellow(JSON.stringify({
55
+ expires_in: result.expiresIn,
56
+ }, null, 2)));
57
+ }
58
+ console.log(chalk_1.default.green('\n✓ Successfully logged in to Berget'));
84
59
  try {
85
- // Create fresh client to ensure we have the latest token
86
- const client = (0, client_1.createAuthenticatedClient)();
87
- const { data: profile, error } = yield client.GET('/v1/users/me');
88
- if (error) {
89
- return null;
60
+ const profile = await this.whoami();
61
+ if (profile?.email) {
62
+ console.log(chalk_1.default.green(`Logged in as ${profile.name || profile.email}`));
90
63
  }
91
- return profile;
92
64
  }
93
- catch (error) {
94
- return null;
65
+ catch {
66
+ // Ignore errors fetching profile
95
67
  }
96
- });
68
+ console.log(chalk_1.default.cyan('\nNext steps:'));
69
+ console.log(chalk_1.default.cyan(' • Create an API key: berget api-keys create'));
70
+ console.log(chalk_1.default.cyan(' • Setup OpenCode: berget code init'));
71
+ return true;
72
+ }
73
+ catch (error) {
74
+ (0, error_handler_1.handleError)('Login failed', error);
75
+ return false;
76
+ }
97
77
  }
98
- login() {
99
- return __awaiter(this, void 0, void 0, function* () {
100
- try {
101
- // Clear any existing token to ensure a fresh login
102
- (0, client_1.clearAuthToken)();
103
- console.log(chalk_1.default.blue('Initiating login process...'));
104
- // Generate PKCE code verifier and challenge
105
- const codeVerifier = generateCodeVerifier();
106
- const codeChallenge = generateCodeChallenge(codeVerifier);
107
- const state = crypto.randomBytes(16).toString('hex');
108
- const redirectUri = `http://localhost:${CALLBACK_PORT}/callback`;
109
- // Build authorization URL
110
- const authUrl = new URL(`${KEYCLOAK_URL}/realms/${KEYCLOAK_REALM}/protocol/openid-connect/auth`);
111
- authUrl.searchParams.set('client_id', KEYCLOAK_CLIENT_ID);
112
- authUrl.searchParams.set('response_type', 'code');
113
- authUrl.searchParams.set('redirect_uri', redirectUri);
114
- authUrl.searchParams.set('scope', 'openid email profile');
115
- authUrl.searchParams.set('state', state);
116
- authUrl.searchParams.set('code_challenge', codeChallenge);
117
- authUrl.searchParams.set('code_challenge_method', 'S256');
118
- // Create a promise that resolves when we receive the callback
119
- const authResult = yield new Promise((resolve) => {
120
- const server = http.createServer((req, res) => __awaiter(this, void 0, void 0, function* () {
121
- const parsedUrl = url.parse(req.url || '', true);
122
- if (parsedUrl.pathname === '/callback') {
123
- const receivedState = parsedUrl.query.state;
124
- const code = parsedUrl.query.code;
125
- const error = parsedUrl.query.error;
126
- const errorPage = (title, message) => `
127
- <!DOCTYPE html>
128
- <html lang="en">
129
- <head>
130
- <meta charset="UTF-8">
131
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
132
- <title>Berget - Authentication Failed</title>
133
- <style>
134
- * { margin: 0; padding: 0; box-sizing: border-box; }
135
- body {
136
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
137
- display: flex;
138
- justify-content: center;
139
- align-items: center;
140
- min-height: 100vh;
141
- background: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 50%, #16213e 100%);
142
- color: #fff;
143
- }
144
- .container {
145
- text-align: center;
146
- padding: 3rem;
147
- max-width: 400px;
148
- }
149
- .icon {
150
- width: 80px;
151
- height: 80px;
152
- background: linear-gradient(135deg, #f87171 0%, #ef4444 100%);
153
- border-radius: 50%;
154
- display: flex;
155
- align-items: center;
156
- justify-content: center;
157
- margin: 0 auto 1.5rem;
158
- box-shadow: 0 4px 20px rgba(248, 113, 113, 0.3);
159
- }
160
- .icon svg {
161
- width: 40px;
162
- height: 40px;
163
- stroke: #fff;
164
- stroke-width: 3;
165
- }
166
- h1 {
167
- font-size: 1.5rem;
168
- font-weight: 600;
169
- margin-bottom: 0.75rem;
170
- color: #fff;
171
- }
172
- p {
173
- color: #94a3b8;
174
- font-size: 0.95rem;
175
- line-height: 1.5;
176
- }
177
- .brand {
178
- margin-top: 2rem;
179
- opacity: 0.5;
180
- font-size: 0.8rem;
181
- letter-spacing: 0.05em;
182
- }
183
- </style>
184
- </head>
185
- <body>
186
- <div class="container">
187
- <div class="icon">
188
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
189
- <line x1="18" y1="6" x2="6" y2="18"></line>
190
- <line x1="6" y1="6" x2="18" y2="18"></line>
191
- </svg>
192
- </div>
193
- <h1>${title}</h1>
194
- <p>${message}</p>
195
- <div class="brand">BERGET</div>
196
- </div>
197
- </body>
198
- </html>
199
- `;
200
- if (error) {
201
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
202
- res.end(errorPage('Authentication Failed', String(parsedUrl.query.error_description || error)));
203
- server.close();
204
- resolve({ success: false, error });
205
- return;
206
- }
207
- if (receivedState !== state) {
208
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
209
- res.end(errorPage('Authentication Failed', 'Invalid state parameter. Please try again.'));
210
- server.close();
211
- resolve({ success: false, error: 'Invalid state parameter' });
212
- return;
213
- }
214
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
215
- res.end(`
216
- <!DOCTYPE html>
217
- <html lang="en">
218
- <head>
219
- <meta charset="UTF-8">
220
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
221
- <title>Berget - Authentication Successful</title>
222
- <style>
223
- * { margin: 0; padding: 0; box-sizing: border-box; }
224
- body {
225
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
226
- display: flex;
227
- justify-content: center;
228
- align-items: center;
229
- min-height: 100vh;
230
- background: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 50%, #16213e 100%);
231
- color: #fff;
232
- }
233
- .container {
234
- text-align: center;
235
- padding: 3rem;
236
- max-width: 400px;
237
- }
238
- .icon {
239
- width: 80px;
240
- height: 80px;
241
- background: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
242
- border-radius: 50%;
243
- display: flex;
244
- align-items: center;
245
- justify-content: center;
246
- margin: 0 auto 1.5rem;
247
- box-shadow: 0 4px 20px rgba(74, 222, 128, 0.3);
248
- }
249
- .icon svg {
250
- width: 40px;
251
- height: 40px;
252
- stroke: #fff;
253
- stroke-width: 3;
254
- }
255
- h1 {
256
- font-size: 1.5rem;
257
- font-weight: 600;
258
- margin-bottom: 0.75rem;
259
- color: #fff;
260
- }
261
- p {
262
- color: #94a3b8;
263
- font-size: 0.95rem;
264
- line-height: 1.5;
265
- }
266
- .brand {
267
- margin-top: 2rem;
268
- opacity: 0.5;
269
- font-size: 0.8rem;
270
- letter-spacing: 0.05em;
271
- }
272
- </style>
273
- </head>
274
- <body>
275
- <div class="container">
276
- <div class="icon">
277
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
278
- <polyline points="20 6 9 17 4 12"></polyline>
279
- </svg>
280
- </div>
281
- <h1>Authentication Successful</h1>
282
- <p>You can close this window and return to your terminal.</p>
283
- <div class="brand">BERGET</div>
284
- </div>
285
- </body>
286
- </html>
287
- `);
288
- server.close();
289
- resolve({ success: true, code });
290
- }
291
- }));
292
- server.listen(CALLBACK_PORT, () => {
293
- if (process.argv.includes('--debug')) {
294
- console.log(chalk_1.default.dim(`Callback server listening on port ${CALLBACK_PORT}`));
295
- }
296
- });
297
- // Set timeout for the server
298
- setTimeout(() => {
299
- server.close();
300
- resolve({ success: false, error: 'Authentication timed out' });
301
- }, 5 * 60 * 1000) // 5 minute timeout
302
- ;
303
- (() => __awaiter(this, void 0, void 0, function* () {
304
- try {
305
- const open = yield Promise.resolve().then(() => __importStar(require('open'))).then((m) => m.default);
306
- yield open(authUrl.toString());
307
- console.log(chalk_1.default.dim('Browser opened for authentication...'));
308
- }
309
- catch (_a) {
310
- console.log(chalk_1.default.cyan('\nPlease open this URL in your browser:'));
311
- console.log(chalk_1.default.bold(authUrl.toString()));
312
- }
313
- }))();
314
- });
315
- if (!authResult.success || !authResult.code) {
316
- console.log(chalk_1.default.red(`\nAuthentication failed: ${authResult.error || 'Unknown error'}`));
317
- return false;
318
- }
319
- // Exchange authorization code for tokens
320
- console.log(chalk_1.default.dim('Exchanging authorization code for tokens...'));
321
- const tokenUrl = `${KEYCLOAK_URL}/realms/${KEYCLOAK_REALM}/protocol/openid-connect/token`;
322
- const tokenResponse = yield fetch(tokenUrl, {
323
- method: 'POST',
324
- headers: {
325
- 'Content-Type': 'application/x-www-form-urlencoded',
326
- },
327
- body: new URLSearchParams({
328
- grant_type: 'authorization_code',
329
- client_id: KEYCLOAK_CLIENT_ID,
330
- code: authResult.code,
331
- redirect_uri: redirectUri,
332
- code_verifier: codeVerifier,
333
- }).toString(),
334
- });
335
- if (!tokenResponse.ok) {
336
- const errorText = yield tokenResponse.text();
337
- console.log(chalk_1.default.red(`\nFailed to exchange code for tokens: ${errorText}`));
338
- return false;
339
- }
340
- const tokenData = (yield tokenResponse.json());
341
- // Save tokens
342
- (0, client_1.saveAuthToken)(tokenData.access_token, tokenData.refresh_token, tokenData.expires_in);
343
- if (process.argv.includes('--debug')) {
344
- console.log(chalk_1.default.yellow('DEBUG: Token data received:'));
345
- console.log(chalk_1.default.yellow(JSON.stringify({
346
- expires_in: tokenData.expires_in,
347
- refresh_expires_in: tokenData.refresh_expires_in,
348
- }, null, 2)));
349
- }
350
- console.log(chalk_1.default.green('\n✓ Successfully logged in to Berget'));
351
- // Try to get user info
352
- try {
353
- const profile = yield this.whoami();
354
- if (profile === null || profile === void 0 ? void 0 : profile.email) {
355
- console.log(chalk_1.default.green(`Logged in as ${profile.name || profile.email}`));
356
- }
357
- }
358
- catch (_a) {
359
- // Ignore errors fetching profile
360
- }
361
- console.log(chalk_1.default.cyan('\nNext steps:'));
362
- console.log(chalk_1.default.cyan(' • Create an API key: berget api-keys create'));
363
- console.log(chalk_1.default.cyan(' • Setup OpenCode: berget code init'));
364
- return true;
78
+ /**
79
+ * Browser-based PKCE login for wizard / programmatic use.
80
+ * Does NOT print to stdout — returns tokens so callers can display
81
+ * their own UI (e.g. via clack/prompts).
82
+ */
83
+ async loginInteractive() {
84
+ try {
85
+ (0, client_1.clearAuthToken)();
86
+ const auth = makeBrowserAuth(process.argv.includes('--debug'));
87
+ const result = await auth.start();
88
+ if (result.success) {
89
+ (0, client_1.saveAuthToken)(result.accessToken, result.refreshToken, result.expiresIn);
365
90
  }
366
- catch (error) {
367
- (0, error_handler_1.handleError)('Login failed', error);
368
- return false;
91
+ return result;
92
+ }
93
+ catch (error) {
94
+ return {
95
+ error: error instanceof Error ? error.message : String(error),
96
+ success: false,
97
+ };
98
+ }
99
+ }
100
+ async whoami() {
101
+ try {
102
+ // Create fresh client to ensure we have the latest token
103
+ const client = (0, client_1.createAuthenticatedClient)();
104
+ const { data: profile, error } = await client.GET('/v1/users/me');
105
+ if (error) {
106
+ return null;
369
107
  }
370
- });
108
+ return profile;
109
+ }
110
+ catch {
111
+ return null;
112
+ }
371
113
  }
372
114
  }
373
115
  exports.AuthService = AuthService;
374
- // Command group name for this service
375
- AuthService.COMMAND_GROUP = command_structure_1.COMMAND_GROUPS.AUTH;
376
- // Subcommands for this service
377
- AuthService.COMMANDS = command_structure_1.SUBCOMMANDS.AUTH;
116
+ function makeBrowserAuth(debug) {
117
+ return new browser_auth_1.BrowserAuth({
118
+ callbackPort: CALLBACK_PORT,
119
+ clientId: KEYCLOAK_CLIENT_ID,
120
+ debug,
121
+ keycloakUrl: KEYCLOAK_URL,
122
+ realm: KEYCLOAK_REALM,
123
+ });
124
+ }