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
|
@@ -0,0 +1,268 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.BrowserAuth = void 0;
|
|
27
|
+
const crypto = __importStar(require("node:crypto"));
|
|
28
|
+
const http = __importStar(require("node:http"));
|
|
29
|
+
class BrowserAuth {
|
|
30
|
+
options;
|
|
31
|
+
constructor(options) {
|
|
32
|
+
this.options = options;
|
|
33
|
+
}
|
|
34
|
+
async start() {
|
|
35
|
+
const { callbackPort, clientId, debug, keycloakUrl, realm } = this.options;
|
|
36
|
+
// Generate PKCE code verifier and challenge
|
|
37
|
+
const codeVerifier = this.generateCodeVerifier();
|
|
38
|
+
const codeChallenge = this.generateCodeChallenge(codeVerifier);
|
|
39
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
40
|
+
const redirectUri = `http://localhost:${callbackPort}/callback`;
|
|
41
|
+
// Build authorization URL
|
|
42
|
+
const authUrl = new URL(`${keycloakUrl}/realms/${realm}/protocol/openid-connect/auth`);
|
|
43
|
+
authUrl.searchParams.set('client_id', clientId);
|
|
44
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
45
|
+
authUrl.searchParams.set('redirect_uri', redirectUri);
|
|
46
|
+
authUrl.searchParams.set('scope', 'openid email profile');
|
|
47
|
+
authUrl.searchParams.set('state', state);
|
|
48
|
+
authUrl.searchParams.set('code_challenge', codeChallenge);
|
|
49
|
+
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
50
|
+
// Create a promise that resolves when we receive the callback
|
|
51
|
+
const authResult = await new Promise((resolve) => {
|
|
52
|
+
let resolved = false;
|
|
53
|
+
const sockets = new Set();
|
|
54
|
+
const safeResolve = (result) => {
|
|
55
|
+
if (resolved)
|
|
56
|
+
return;
|
|
57
|
+
resolved = true;
|
|
58
|
+
clearTimeout(timeoutHandle);
|
|
59
|
+
server.close();
|
|
60
|
+
// Force-close all active sockets so the server stops immediately
|
|
61
|
+
for (const socket of sockets) {
|
|
62
|
+
socket.destroy();
|
|
63
|
+
}
|
|
64
|
+
sockets.clear();
|
|
65
|
+
resolve(result);
|
|
66
|
+
};
|
|
67
|
+
const server = http.createServer((request, res) => {
|
|
68
|
+
const requestUrl = new URL(request.url || '', `http://localhost:${callbackPort}`);
|
|
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
|
+
const errorPage = (title, message) => `
|
|
74
|
+
<!DOCTYPE html>
|
|
75
|
+
<html lang="en">
|
|
76
|
+
<head>
|
|
77
|
+
<meta charset="UTF-8">
|
|
78
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
79
|
+
<title>Berget - Authentication Failed</title>
|
|
80
|
+
<style>
|
|
81
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
82
|
+
body {
|
|
83
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
84
|
+
display: flex;
|
|
85
|
+
justify-content: center;
|
|
86
|
+
align-items: center;
|
|
87
|
+
min-height: 100vh;
|
|
88
|
+
background: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 50%, #16213e 100%);
|
|
89
|
+
color: #fff;
|
|
90
|
+
}
|
|
91
|
+
.container {
|
|
92
|
+
text-align: center;
|
|
93
|
+
padding: 3rem;
|
|
94
|
+
max-width: 400px;
|
|
95
|
+
}
|
|
96
|
+
.icon {
|
|
97
|
+
width: 80px;
|
|
98
|
+
height: 80px;
|
|
99
|
+
background: linear-gradient(135deg, #f87171 0%, #ef4444 100%);
|
|
100
|
+
border-radius: 50%;
|
|
101
|
+
display: flex;
|
|
102
|
+
align-items: center;
|
|
103
|
+
justify-content: center;
|
|
104
|
+
margin: 0 auto 1.5rem;
|
|
105
|
+
box-shadow: 0 4px 20px rgba(248, 113, 113, 0.3);
|
|
106
|
+
}
|
|
107
|
+
.icon svg { width: 40px; height: 40px; stroke: #fff; stroke-width: 3; }
|
|
108
|
+
h1 { font-size: 1.5rem; font-weight: 600; margin-bottom: 0.75rem; color: #fff; }
|
|
109
|
+
p { color: #94a3b8; font-size: 0.95rem; line-height: 1.5; }
|
|
110
|
+
.brand { margin-top: 2rem; opacity: 0.5; font-size: 0.8rem; letter-spacing: 0.05em; }
|
|
111
|
+
</style>
|
|
112
|
+
</head>
|
|
113
|
+
<body>
|
|
114
|
+
<div class="container">
|
|
115
|
+
<div class="icon">
|
|
116
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
117
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
118
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
119
|
+
</svg>
|
|
120
|
+
</div>
|
|
121
|
+
<h1>${title}</h1>
|
|
122
|
+
<p>${message}</p>
|
|
123
|
+
<div class="brand">BERGET</div>
|
|
124
|
+
</div>
|
|
125
|
+
</body>
|
|
126
|
+
</html>
|
|
127
|
+
`;
|
|
128
|
+
// Set Connection: close so the browser doesn't keep the socket alive
|
|
129
|
+
// after we respond, and force-end the connection
|
|
130
|
+
if (error) {
|
|
131
|
+
res.writeHead(200, { Connection: 'close', 'Content-Type': 'text/html; charset=utf-8' });
|
|
132
|
+
res.end(errorPage('Authentication Failed', requestUrl.searchParams.get('error_description') || error));
|
|
133
|
+
safeResolve({ error, success: false });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (receivedState !== state) {
|
|
137
|
+
res.writeHead(200, { Connection: 'close', 'Content-Type': 'text/html; charset=utf-8' });
|
|
138
|
+
res.end(errorPage('Authentication Failed', 'Invalid state parameter. Please try again.'));
|
|
139
|
+
safeResolve({ error: 'Invalid state parameter', success: false });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
res.writeHead(200, { Connection: 'close', 'Content-Type': 'text/html; charset=utf-8' });
|
|
143
|
+
res.end(`
|
|
144
|
+
<!DOCTYPE html>
|
|
145
|
+
<html lang="en">
|
|
146
|
+
<head>
|
|
147
|
+
<meta charset="UTF-8">
|
|
148
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
149
|
+
<title>Berget - Authentication Successful</title>
|
|
150
|
+
<style>
|
|
151
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
152
|
+
body {
|
|
153
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
154
|
+
display: flex;
|
|
155
|
+
justify-content: center;
|
|
156
|
+
align-items: center;
|
|
157
|
+
min-height: 100vh;
|
|
158
|
+
background: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 50%, #16213e 100%);
|
|
159
|
+
color: #fff;
|
|
160
|
+
}
|
|
161
|
+
.container {
|
|
162
|
+
text-align: center;
|
|
163
|
+
padding: 3rem;
|
|
164
|
+
max-width: 400px;
|
|
165
|
+
}
|
|
166
|
+
.icon {
|
|
167
|
+
width: 80px;
|
|
168
|
+
height: 80px;
|
|
169
|
+
background: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
|
|
170
|
+
border-radius: 50%;
|
|
171
|
+
display: flex;
|
|
172
|
+
align-items: center;
|
|
173
|
+
justify-content: center;
|
|
174
|
+
margin: 0 auto 1.5rem;
|
|
175
|
+
box-shadow: 0 4px 20px rgba(74, 222, 128, 0.3);
|
|
176
|
+
}
|
|
177
|
+
.icon svg { width: 40px; height: 40px; stroke: #fff; stroke-width: 3; }
|
|
178
|
+
h1 { font-size: 1.5rem; font-weight: 600; margin-bottom: 0.75rem; color: #fff; }
|
|
179
|
+
p { color: #94a3b8; font-size: 0.95rem; line-height: 1.5; }
|
|
180
|
+
.brand { margin-top: 2rem; opacity: 0.5; font-size: 0.8rem; letter-spacing: 0.05em; }
|
|
181
|
+
</style>
|
|
182
|
+
</head>
|
|
183
|
+
<body>
|
|
184
|
+
<div class="container">
|
|
185
|
+
<div class="icon">
|
|
186
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
187
|
+
<polyline points="20 6 9 17 4 12"></polyline>
|
|
188
|
+
</svg>
|
|
189
|
+
</div>
|
|
190
|
+
<h1>Authentication Successful</h1>
|
|
191
|
+
<p>You can close this window and return to your terminal.</p>
|
|
192
|
+
<div class="brand">BERGET</div>
|
|
193
|
+
</div>
|
|
194
|
+
</body>
|
|
195
|
+
</html>
|
|
196
|
+
`);
|
|
197
|
+
safeResolve({ code, success: true });
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
// Track sockets so we can destroy them on shutdown
|
|
201
|
+
server.on('connection', (socket) => {
|
|
202
|
+
sockets.add(socket);
|
|
203
|
+
socket.on('close', () => sockets.delete(socket));
|
|
204
|
+
});
|
|
205
|
+
server.listen(callbackPort, () => {
|
|
206
|
+
if (debug) {
|
|
207
|
+
console.log(`Callback server listening on port ${callbackPort}`);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
// Set timeout for the server
|
|
211
|
+
const timeoutHandle = setTimeout(() => {
|
|
212
|
+
safeResolve({ error: 'Authentication timed out', success: false });
|
|
213
|
+
}, 5 * 60 * 1000); // 5 minute timeout
|
|
214
|
+
// Open browser
|
|
215
|
+
(async () => {
|
|
216
|
+
try {
|
|
217
|
+
const open = await Promise.resolve().then(() => __importStar(require('open'))).then((m) => m.default);
|
|
218
|
+
await open(authUrl.toString());
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Browser failed to open - user must open URL manually
|
|
222
|
+
}
|
|
223
|
+
})();
|
|
224
|
+
});
|
|
225
|
+
if (!authResult.success || !authResult.code) {
|
|
226
|
+
return {
|
|
227
|
+
error: authResult.error || 'Unknown error',
|
|
228
|
+
success: false,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
// Exchange authorization code for tokens
|
|
232
|
+
const tokenUrl = `${keycloakUrl}/realms/${realm}/protocol/openid-connect/token`;
|
|
233
|
+
const tokenResponse = await fetch(tokenUrl, {
|
|
234
|
+
body: new URLSearchParams({
|
|
235
|
+
client_id: clientId,
|
|
236
|
+
code: authResult.code,
|
|
237
|
+
code_verifier: codeVerifier,
|
|
238
|
+
grant_type: 'authorization_code',
|
|
239
|
+
redirect_uri: redirectUri,
|
|
240
|
+
}).toString(),
|
|
241
|
+
headers: {
|
|
242
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
243
|
+
},
|
|
244
|
+
method: 'POST',
|
|
245
|
+
});
|
|
246
|
+
if (!tokenResponse.ok) {
|
|
247
|
+
const errorText = await tokenResponse.text();
|
|
248
|
+
return {
|
|
249
|
+
error: `Failed to exchange code for tokens: ${errorText}`,
|
|
250
|
+
success: false,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const tokenData = (await tokenResponse.json());
|
|
254
|
+
return {
|
|
255
|
+
accessToken: tokenData.access_token,
|
|
256
|
+
expiresIn: tokenData.expires_in,
|
|
257
|
+
refreshToken: tokenData.refresh_token,
|
|
258
|
+
success: true,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
generateCodeChallenge(verifier) {
|
|
262
|
+
return crypto.createHash('sha256').update(verifier).digest('base64url');
|
|
263
|
+
}
|
|
264
|
+
generateCodeVerifier() {
|
|
265
|
+
return crypto.randomBytes(32).toString('base64url');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
exports.BrowserAuth = BrowserAuth;
|