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.
- package/.github/workflows/publish.yml +2 -2
- package/.github/workflows/test.yml +10 -4
- package/.husky/pre-commit +1 -0
- package/.prettierignore +15 -0
- package/.prettierrc +7 -3
- package/CONTRIBUTING.md +38 -0
- package/README.md +2 -148
- package/dist/index.js +10 -11
- package/dist/package.json +30 -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 +97 -117
- package/dist/src/commands/api-keys.js +75 -90
- package/dist/src/commands/auth.js +7 -16
- package/dist/src/commands/autocomplete.js +1 -1
- package/dist/src/commands/billing.js +6 -17
- package/dist/src/commands/chat.js +68 -101
- package/dist/src/commands/clusters.js +9 -18
- package/dist/src/commands/code/__tests__/auth-sync.test.js +351 -0
- package/dist/src/commands/code/__tests__/fake-api-key-service.js +13 -0
- package/dist/src/commands/code/__tests__/fake-auth-service.js +47 -0
- package/dist/src/commands/code/__tests__/fake-command-runner.js +21 -34
- package/dist/src/commands/code/__tests__/fake-file-store.js +20 -33
- package/dist/src/commands/code/__tests__/fake-prompter.js +83 -57
- package/dist/src/commands/code/__tests__/setup-flow.test.js +359 -92
- package/dist/src/commands/code/adapters/clack-prompter.js +15 -22
- package/dist/src/commands/code/adapters/fs-file-store.js +26 -40
- package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -37
- package/dist/src/commands/code/auth-sync.js +270 -0
- package/dist/src/commands/code/errors.js +12 -9
- package/dist/src/commands/code/ports/auth-services.js +2 -0
- package/dist/src/commands/code/setup.js +387 -281
- package/dist/src/commands/code.js +205 -332
- package/dist/src/commands/index.js +5 -5
- package/dist/src/commands/models.js +6 -17
- package/dist/src/commands/users.js +5 -16
- package/dist/src/constants/command-structure.js +104 -104
- package/dist/src/services/api-key-service.js +132 -157
- package/dist/src/services/auth-service.js +89 -342
- package/dist/src/services/browser-auth.js +268 -0
- package/dist/src/services/chat-service.js +371 -401
- package/dist/src/services/cluster-service.js +47 -62
- package/dist/src/services/collaborator-service.js +10 -25
- package/dist/src/services/flux-service.js +14 -29
- package/dist/src/services/helm-service.js +10 -25
- package/dist/src/services/kubectl-service.js +16 -33
- package/dist/src/utils/config-checker.js +3 -3
- package/dist/src/utils/config-loader.js +95 -95
- package/dist/src/utils/default-api-key.js +124 -134
- package/dist/src/utils/env-manager.js +55 -66
- package/dist/src/utils/error-handler.js +20 -21
- package/dist/src/utils/logger.js +72 -65
- package/dist/src/utils/markdown-renderer.js +27 -27
- package/dist/src/utils/opencode-validator.js +63 -68
- package/dist/src/utils/token-manager.js +74 -45
- package/dist/tests/commands/chat.test.js +16 -25
- package/dist/tests/commands/code.test.js +95 -104
- package/dist/tests/utils/config-loader.test.js +48 -48
- package/dist/tests/utils/env-manager.test.js +43 -52
- package/dist/tests/utils/opencode-validator.test.js +22 -21
- package/dist/vitest.config.js +1 -1
- package/eslint.config.mjs +67 -0
- package/index.ts +35 -42
- package/package.json +30 -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 +73 -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 +118 -152
- package/src/commands/api-keys.ts +241 -333
- package/src/commands/auth.ts +22 -27
- package/src/commands/autocomplete.ts +9 -9
- package/src/commands/billing.ts +20 -24
- package/src/commands/chat.ts +248 -338
- package/src/commands/clusters.ts +27 -26
- package/src/commands/code/__tests__/auth-sync.test.ts +482 -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 +45 -42
- package/src/commands/code/__tests__/fake-file-store.ts +32 -23
- package/src/commands/code/__tests__/fake-prompter.ts +116 -77
- package/src/commands/code/__tests__/setup-flow.test.ts +624 -268
- package/src/commands/code/adapters/clack-prompter.ts +53 -39
- package/src/commands/code/adapters/fs-file-store.ts +32 -27
- package/src/commands/code/adapters/spawn-command-runner.ts +38 -29
- package/src/commands/code/auth-sync.ts +329 -0
- package/src/commands/code/errors.ts +18 -18
- 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 +570 -340
- package/src/commands/code.ts +338 -539
- package/src/commands/index.ts +20 -19
- package/src/commands/models.ts +28 -32
- package/src/commands/users.ts +15 -21
- package/src/constants/command-structure.ts +134 -157
- package/src/services/api-key-service.ts +105 -122
- package/src/services/auth-service.ts +99 -345
- package/src/services/browser-auth.ts +296 -0
- package/src/services/chat-service.ts +265 -299
- package/src/services/cluster-service.ts +42 -45
- package/src/services/collaborator-service.ts +14 -19
- package/src/services/flux-service.ts +23 -25
- package/src/services/helm-service.ts +19 -21
- package/src/services/kubectl-service.ts +17 -19
- package/src/types/api.d.ts +1905 -1907
- package/src/types/json.d.ts +2 -2
- package/src/utils/config-checker.ts +10 -10
- package/src/utils/config-loader.ts +162 -178
- package/src/utils/default-api-key.ts +114 -125
- package/src/utils/env-manager.ts +53 -57
- package/src/utils/error-handler.ts +61 -56
- package/src/utils/logger.ts +79 -73
- package/src/utils/markdown-renderer.ts +31 -31
- package/src/utils/opencode-validator.ts +85 -89
- package/src/utils/token-manager.ts +108 -87
- 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 +53 -62
- package/tests/commands/code.test.ts +265 -310
- package/tests/utils/config-loader.test.ts +189 -188
- package/tests/utils/env-manager.test.ts +110 -113
- package/tests/utils/opencode-validator.test.ts +52 -56
- package/tsconfig.json +4 -3
- package/vitest.config.ts +3 -3
- package/AGENTS.md +0 -374
- 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
|
|
8
|
+
const client_1 = require("../client");
|
|
43
9
|
const command_structure_1 = require("../constants/command-structure");
|
|
44
|
-
const
|
|
45
|
-
const
|
|
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 =
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
94
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
+
}
|