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