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
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Agent } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export const agent: Agent = {
|
|
4
|
+
config: {
|
|
5
|
+
name: "security",
|
|
6
|
+
description:
|
|
7
|
+
"Security specialist for pentesting, OWASP compliance, and vulnerability assessments.",
|
|
8
|
+
mode: "subagent",
|
|
9
|
+
temperature: 0.2,
|
|
10
|
+
top_p: 0.8,
|
|
11
|
+
permission: {
|
|
12
|
+
edit: "deny",
|
|
13
|
+
bash: "allow",
|
|
14
|
+
webfetch: "allow",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
systemPrompt: `Voice: Scandinavian calm—precise, concise, confident. You are Berget Code Security agent. Expert in application security, penetration testing, and OWASP standards. Core responsibilities: Conduct security assessments and penetration tests, Validate OWASP Top 10 compliance, Review code for security vulnerabilities, Implement security headers and Content Security Policy (CSP), Audit API security, Check for sensitive data exposure, Validate input sanitization and output encoding, Assess dependency security and supply chain risks. Tools and techniques: OWASP ZAP, Burp Suite, security linters, dependency scanners, manual code review. Always provide specific, actionable security recommendations with priority levels.
|
|
18
|
+
|
|
19
|
+
GIT WORKFLOW RULES (CRITICAL):
|
|
20
|
+
- NEVER push directly to main branch - ALWAYS use pull requests
|
|
21
|
+
- NEVER use 'git add .' - ALWAYS add specific files with 'git add path/to/file'
|
|
22
|
+
- ALWAYS clean up test files, documentation files, and temporary artifacts before committing
|
|
23
|
+
- ALWAYS ensure git history maintains production quality - no test commits, no debugging code
|
|
24
|
+
- ALWAYS create descriptive commit messages following project conventions
|
|
25
|
+
- ALWAYS run tests and build before creating PR`,
|
|
26
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface AgentConfig {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
mode?: "primary" | "subagent";
|
|
5
|
+
temperature?: number;
|
|
6
|
+
top_p?: number;
|
|
7
|
+
permission?: {
|
|
8
|
+
edit?: string;
|
|
9
|
+
bash?: string;
|
|
10
|
+
webfetch?: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface Agent {
|
|
15
|
+
config: AgentConfig;
|
|
16
|
+
systemPrompt: string;
|
|
17
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -1,67 +1,64 @@
|
|
|
1
|
-
import createClient from
|
|
2
|
-
import type { paths } from
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import chalk from 'chalk'
|
|
7
|
-
import { TokenManager } from './utils/token-manager'
|
|
8
|
-
import { logger } from './utils/logger'
|
|
1
|
+
import createClient from "openapi-fetch";
|
|
2
|
+
import type { paths } from "./types/api";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { TokenManager } from "./utils/token-manager";
|
|
5
|
+
import { logger } from "./utils/logger";
|
|
9
6
|
|
|
10
7
|
// API Base URL
|
|
11
8
|
// Use --local flag to test against local API
|
|
12
9
|
// Use --stage flag to test against stage API
|
|
13
|
-
const isLocalMode = process.argv.includes(
|
|
14
|
-
const isStageMode = process.argv.includes(
|
|
10
|
+
const isLocalMode = process.argv.includes("--local");
|
|
11
|
+
const isStageMode = process.argv.includes("--stage");
|
|
15
12
|
|
|
16
13
|
export const API_BASE_URL =
|
|
17
14
|
process.env.BERGET_API_URL ||
|
|
18
|
-
(isLocalMode
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
(isLocalMode
|
|
16
|
+
? "http://localhost:3000"
|
|
17
|
+
: isStageMode
|
|
18
|
+
? "https://api.stage.berget.ai"
|
|
19
|
+
: "https://api.berget.ai"); // production default
|
|
21
20
|
|
|
22
21
|
if (isLocalMode && !process.env.BERGET_API_URL) {
|
|
23
|
-
logger.debug(
|
|
22
|
+
logger.debug("Using local API endpoint: http://localhost:3000");
|
|
24
23
|
} else if (isStageMode && !process.env.BERGET_API_URL) {
|
|
25
|
-
logger.debug(
|
|
24
|
+
logger.debug("Using stage API endpoint: https://api.stage.berget.ai");
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
// Create a typed client for the Berget API
|
|
29
28
|
export const apiClient = createClient<paths>({
|
|
30
29
|
baseUrl: API_BASE_URL,
|
|
31
30
|
headers: {
|
|
32
|
-
|
|
33
|
-
Accept:
|
|
31
|
+
"Content-Type": "application/json",
|
|
32
|
+
Accept: "application/json",
|
|
34
33
|
},
|
|
35
|
-
})
|
|
34
|
+
});
|
|
36
35
|
|
|
37
36
|
// Authentication functions
|
|
38
37
|
export const getAuthToken = (): string | null => {
|
|
39
|
-
const tokenManager = TokenManager.getInstance()
|
|
40
|
-
return tokenManager.getAccessToken()
|
|
41
|
-
}
|
|
38
|
+
const tokenManager = TokenManager.getInstance();
|
|
39
|
+
return tokenManager.getAccessToken();
|
|
40
|
+
};
|
|
42
41
|
|
|
43
42
|
export const saveAuthToken = (
|
|
44
43
|
accessToken: string,
|
|
45
44
|
refreshToken: string,
|
|
46
|
-
expiresIn: number = 3600
|
|
45
|
+
expiresIn: number = 3600
|
|
47
46
|
): void => {
|
|
48
|
-
const tokenManager = TokenManager.getInstance()
|
|
49
|
-
tokenManager.setTokens(accessToken, refreshToken, expiresIn)
|
|
50
|
-
}
|
|
47
|
+
const tokenManager = TokenManager.getInstance();
|
|
48
|
+
tokenManager.setTokens(accessToken, refreshToken, expiresIn);
|
|
49
|
+
};
|
|
51
50
|
|
|
52
51
|
export const clearAuthToken = (): void => {
|
|
53
|
-
const tokenManager = TokenManager.getInstance()
|
|
54
|
-
tokenManager.clearTokens()
|
|
55
|
-
}
|
|
52
|
+
const tokenManager = TokenManager.getInstance();
|
|
53
|
+
tokenManager.clearTokens();
|
|
54
|
+
};
|
|
56
55
|
|
|
57
56
|
// Create an authenticated client with refresh token support
|
|
58
57
|
export const createAuthenticatedClient = () => {
|
|
59
|
-
const tokenManager = TokenManager.getInstance()
|
|
58
|
+
const tokenManager = TokenManager.getInstance();
|
|
60
59
|
|
|
61
60
|
if (!tokenManager.getAccessToken()) {
|
|
62
|
-
logger.debug(
|
|
63
|
-
'No authentication token found. Please run `berget auth login` first.',
|
|
64
|
-
)
|
|
61
|
+
logger.debug("No authentication token found. Please run `berget auth login` first.");
|
|
65
62
|
}
|
|
66
63
|
|
|
67
64
|
// Create the base client
|
|
@@ -70,268 +67,229 @@ export const createAuthenticatedClient = () => {
|
|
|
70
67
|
headers: tokenManager.getAccessToken()
|
|
71
68
|
? {
|
|
72
69
|
Authorization: `Bearer ${tokenManager.getAccessToken()}`,
|
|
73
|
-
|
|
74
|
-
Accept:
|
|
70
|
+
"Content-Type": "application/json",
|
|
71
|
+
Accept: "application/json",
|
|
75
72
|
}
|
|
76
73
|
: {
|
|
77
|
-
|
|
78
|
-
Accept:
|
|
74
|
+
"Content-Type": "application/json",
|
|
75
|
+
Accept: "application/json",
|
|
79
76
|
},
|
|
80
|
-
})
|
|
77
|
+
});
|
|
81
78
|
|
|
82
79
|
// Wrap the client to handle token refresh
|
|
83
80
|
return new Proxy(client, {
|
|
84
81
|
get(target, prop: string | symbol) {
|
|
85
82
|
// For HTTP methods (GET, POST, etc.), add token refresh logic
|
|
86
83
|
if (
|
|
87
|
-
typeof target[prop as keyof typeof target] ===
|
|
88
|
-
[
|
|
84
|
+
typeof target[prop as keyof typeof target] === "function" &&
|
|
85
|
+
["GET", "POST", "PUT", "DELETE", "PATCH"].includes(String(prop))
|
|
89
86
|
) {
|
|
90
87
|
return async (...args: any[]) => {
|
|
91
88
|
// Check if token is expired before making the request
|
|
92
89
|
if (tokenManager.isTokenExpired() && tokenManager.getRefreshToken()) {
|
|
93
|
-
await refreshAccessToken(tokenManager)
|
|
90
|
+
await refreshAccessToken(tokenManager);
|
|
94
91
|
}
|
|
95
92
|
|
|
96
93
|
// Update the Authorization header with the current token
|
|
97
|
-
if (
|
|
98
|
-
!args[1]
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (!args[1]) args[1] = {}
|
|
102
|
-
if (!args[1].headers) args[1].headers = {}
|
|
103
|
-
args[1].headers.Authorization = `Bearer ${tokenManager.getAccessToken()}`
|
|
94
|
+
if (!args[1]?.headers?.Authorization && tokenManager.getAccessToken()) {
|
|
95
|
+
if (!args[1]) args[1] = {};
|
|
96
|
+
if (!args[1].headers) args[1].headers = {};
|
|
97
|
+
args[1].headers.Authorization = `Bearer ${tokenManager.getAccessToken()}`;
|
|
104
98
|
}
|
|
105
99
|
|
|
106
100
|
// Make the original request
|
|
107
|
-
let result
|
|
101
|
+
let result;
|
|
108
102
|
try {
|
|
109
|
-
result = await (target[prop as keyof typeof target] as Function)(
|
|
110
|
-
...args,
|
|
111
|
-
)
|
|
103
|
+
result = await (target[prop as keyof typeof target] as Function)(...args);
|
|
112
104
|
} catch (requestError) {
|
|
113
105
|
logger.debug(
|
|
114
106
|
`Request error: ${
|
|
115
|
-
requestError instanceof Error
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}`,
|
|
119
|
-
)
|
|
107
|
+
requestError instanceof Error ? requestError.message : String(requestError)
|
|
108
|
+
}`
|
|
109
|
+
);
|
|
120
110
|
return {
|
|
121
111
|
error: {
|
|
122
112
|
message: `Request failed: ${
|
|
123
|
-
requestError instanceof Error
|
|
124
|
-
? requestError.message
|
|
125
|
-
: String(requestError)
|
|
113
|
+
requestError instanceof Error ? requestError.message : String(requestError)
|
|
126
114
|
}`,
|
|
127
115
|
},
|
|
128
|
-
}
|
|
116
|
+
};
|
|
129
117
|
}
|
|
130
118
|
|
|
131
119
|
// If we get an auth error, try to refresh the token and retry
|
|
132
120
|
if (result.error) {
|
|
133
121
|
// Detect various forms of authentication errors
|
|
134
|
-
let isAuthError = false
|
|
122
|
+
let isAuthError = false;
|
|
135
123
|
|
|
136
124
|
try {
|
|
137
125
|
// Standard 401 Unauthorized
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
result.error.status === 401
|
|
141
|
-
) {
|
|
142
|
-
isAuthError = true
|
|
126
|
+
if (typeof result.error === "object" && result.error.status === 401) {
|
|
127
|
+
isAuthError = true;
|
|
143
128
|
}
|
|
144
129
|
// OAuth specific errors
|
|
145
130
|
else if (
|
|
146
131
|
result.error.error &&
|
|
147
|
-
(result.error.error.code ===
|
|
148
|
-
result.error.error.code ===
|
|
149
|
-
result.error.error.message ===
|
|
150
|
-
result.error.error.message?.toLowerCase().includes(
|
|
151
|
-
result.error.error.message
|
|
152
|
-
?.toLowerCase()
|
|
153
|
-
.includes('unauthorized'))
|
|
132
|
+
(result.error.error.code === "invalid_token" ||
|
|
133
|
+
result.error.error.code === "token_expired" ||
|
|
134
|
+
result.error.error.message === "Invalid API key" ||
|
|
135
|
+
result.error.error.message?.toLowerCase().includes("token") ||
|
|
136
|
+
result.error.error.message?.toLowerCase().includes("unauthorized"))
|
|
154
137
|
) {
|
|
155
|
-
isAuthError = true
|
|
138
|
+
isAuthError = true;
|
|
156
139
|
}
|
|
157
140
|
// Message-based detection as fallback
|
|
158
141
|
else if (
|
|
159
|
-
typeof result.error ===
|
|
160
|
-
(result.error.toLowerCase().includes(
|
|
161
|
-
result.error.toLowerCase().includes(
|
|
162
|
-
result.error.toLowerCase().includes(
|
|
142
|
+
typeof result.error === "string" &&
|
|
143
|
+
(result.error.toLowerCase().includes("unauthorized") ||
|
|
144
|
+
result.error.toLowerCase().includes("token") ||
|
|
145
|
+
result.error.toLowerCase().includes("auth"))
|
|
163
146
|
) {
|
|
164
|
-
isAuthError = true
|
|
147
|
+
isAuthError = true;
|
|
165
148
|
}
|
|
166
|
-
} catch
|
|
149
|
+
} catch {
|
|
167
150
|
// If we can't parse the error structure, do a simple string check
|
|
168
|
-
const errorStr = String(result.error)
|
|
151
|
+
const errorStr = String(result.error);
|
|
169
152
|
if (
|
|
170
|
-
errorStr.toLowerCase().includes(
|
|
171
|
-
errorStr.toLowerCase().includes(
|
|
172
|
-
errorStr.toLowerCase().includes(
|
|
153
|
+
errorStr.toLowerCase().includes("unauthorized") ||
|
|
154
|
+
errorStr.toLowerCase().includes("token") ||
|
|
155
|
+
errorStr.toLowerCase().includes("auth")
|
|
173
156
|
) {
|
|
174
|
-
isAuthError = true
|
|
157
|
+
isAuthError = true;
|
|
175
158
|
}
|
|
176
159
|
}
|
|
177
160
|
|
|
178
161
|
if (isAuthError && tokenManager.getRefreshToken()) {
|
|
179
|
-
logger.debug(
|
|
180
|
-
logger.debug(
|
|
181
|
-
`Error details: ${JSON.stringify(result.error, null, 2)}`,
|
|
182
|
-
)
|
|
162
|
+
logger.debug("Auth error detected, attempting token refresh");
|
|
163
|
+
logger.debug(`Error details: ${JSON.stringify(result.error, null, 2)}`);
|
|
183
164
|
|
|
184
|
-
const refreshed = await refreshAccessToken(tokenManager)
|
|
165
|
+
const refreshed = await refreshAccessToken(tokenManager);
|
|
185
166
|
if (refreshed) {
|
|
186
|
-
logger.debug(
|
|
167
|
+
logger.debug("Token refreshed successfully, retrying request");
|
|
187
168
|
|
|
188
169
|
// Update the Authorization header with the new token
|
|
189
|
-
if (!args[1]) args[1] = {}
|
|
190
|
-
if (!args[1].headers) args[1].headers = {}
|
|
191
|
-
args[1].headers.Authorization = `Bearer ${tokenManager.getAccessToken()}
|
|
170
|
+
if (!args[1]) args[1] = {};
|
|
171
|
+
if (!args[1].headers) args[1].headers = {};
|
|
172
|
+
args[1].headers.Authorization = `Bearer ${tokenManager.getAccessToken()}`;
|
|
192
173
|
|
|
193
174
|
// Retry the request
|
|
194
|
-
return await (target[prop as keyof typeof target] as Function)(
|
|
195
|
-
...args,
|
|
196
|
-
)
|
|
175
|
+
return await (target[prop as keyof typeof target] as Function)(...args);
|
|
197
176
|
} else {
|
|
198
|
-
logger.debug(
|
|
177
|
+
logger.debug("Token refresh failed");
|
|
199
178
|
|
|
200
179
|
// Add a more helpful error message for users
|
|
201
|
-
if (typeof result.error ===
|
|
180
|
+
if (typeof result.error === "object") {
|
|
202
181
|
result.error.userMessage =
|
|
203
|
-
|
|
182
|
+
"Your session has expired. Please run `berget auth login` to log in again.";
|
|
204
183
|
}
|
|
205
184
|
}
|
|
206
185
|
}
|
|
207
186
|
}
|
|
208
187
|
|
|
209
|
-
return result
|
|
210
|
-
}
|
|
188
|
+
return result;
|
|
189
|
+
};
|
|
211
190
|
}
|
|
212
191
|
|
|
213
192
|
// For other properties, just return the original
|
|
214
|
-
return target[prop as keyof typeof target]
|
|
193
|
+
return target[prop as keyof typeof target];
|
|
215
194
|
},
|
|
216
|
-
})
|
|
217
|
-
}
|
|
195
|
+
});
|
|
196
|
+
};
|
|
218
197
|
|
|
219
198
|
// Keycloak configuration for token refresh (must match auth-service.ts)
|
|
220
|
-
const KEYCLOAK_URL =
|
|
221
|
-
?
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
const KEYCLOAK_CLIENT_ID = 'berget-code'
|
|
199
|
+
const KEYCLOAK_URL =
|
|
200
|
+
isStageMode || isLocalMode ? "https://keycloak.stage.berget.ai" : "https://keycloak.berget.ai";
|
|
201
|
+
const KEYCLOAK_REALM = "berget";
|
|
202
|
+
const KEYCLOAK_CLIENT_ID = "berget-code";
|
|
225
203
|
|
|
226
204
|
// Helper function to refresh the access token
|
|
227
|
-
async function refreshAccessToken(
|
|
228
|
-
tokenManager: TokenManager,
|
|
229
|
-
): Promise<boolean> {
|
|
205
|
+
async function refreshAccessToken(tokenManager: TokenManager): Promise<boolean> {
|
|
230
206
|
try {
|
|
231
|
-
const refreshToken = tokenManager.getRefreshToken()
|
|
232
|
-
if (!refreshToken) return false
|
|
207
|
+
const refreshToken = tokenManager.getRefreshToken();
|
|
208
|
+
if (!refreshToken) return false;
|
|
233
209
|
|
|
234
|
-
logger.debug(
|
|
210
|
+
logger.debug("Attempting to refresh access token");
|
|
235
211
|
|
|
236
212
|
// Refresh directly against Keycloak (berget-code is a public PKCE client)
|
|
237
213
|
try {
|
|
238
214
|
const response = await fetch(
|
|
239
215
|
`${KEYCLOAK_URL}/realms/${KEYCLOAK_REALM}/protocol/openid-connect/token`,
|
|
240
216
|
{
|
|
241
|
-
method:
|
|
217
|
+
method: "POST",
|
|
242
218
|
headers: {
|
|
243
|
-
|
|
219
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
244
220
|
},
|
|
245
221
|
body: new URLSearchParams({
|
|
246
|
-
grant_type:
|
|
222
|
+
grant_type: "refresh_token",
|
|
247
223
|
client_id: KEYCLOAK_CLIENT_ID,
|
|
248
224
|
refresh_token: refreshToken,
|
|
249
225
|
}),
|
|
250
|
-
}
|
|
251
|
-
)
|
|
226
|
+
}
|
|
227
|
+
);
|
|
252
228
|
|
|
253
229
|
// Handle HTTP errors
|
|
254
230
|
if (!response.ok) {
|
|
255
|
-
logger.debug(
|
|
256
|
-
`Token refresh error: HTTP ${response.status} ${response.statusText}`,
|
|
257
|
-
)
|
|
231
|
+
logger.debug(`Token refresh error: HTTP ${response.status} ${response.statusText}`);
|
|
258
232
|
|
|
259
233
|
// Check if the refresh token itself is expired or invalid
|
|
260
234
|
if (response.status === 401 || response.status === 403) {
|
|
261
235
|
console.warn(
|
|
262
|
-
chalk.yellow(
|
|
263
|
-
|
|
264
|
-
),
|
|
265
|
-
)
|
|
236
|
+
chalk.yellow("Your refresh token has expired. Please run `berget auth login` again.")
|
|
237
|
+
);
|
|
266
238
|
// Clear tokens if unauthorized - they're invalid
|
|
267
|
-
tokenManager.clearTokens()
|
|
239
|
+
tokenManager.clearTokens();
|
|
268
240
|
} else {
|
|
269
241
|
console.warn(
|
|
270
|
-
chalk.yellow(
|
|
271
|
-
|
|
272
|
-
),
|
|
273
|
-
)
|
|
242
|
+
chalk.yellow(`Failed to refresh token: ${response.status} ${response.statusText}`)
|
|
243
|
+
);
|
|
274
244
|
}
|
|
275
|
-
return false
|
|
245
|
+
return false;
|
|
276
246
|
}
|
|
277
247
|
|
|
278
248
|
// Parse the response
|
|
279
|
-
const contentType = response.headers.get(
|
|
280
|
-
if (!contentType || !contentType.includes(
|
|
281
|
-
console.warn(
|
|
282
|
-
|
|
283
|
-
)
|
|
284
|
-
return false
|
|
249
|
+
const contentType = response.headers.get("content-type");
|
|
250
|
+
if (!contentType || !contentType.includes("application/json")) {
|
|
251
|
+
console.warn(chalk.yellow(`Unexpected content type in response: ${contentType}`));
|
|
252
|
+
return false;
|
|
285
253
|
}
|
|
286
254
|
|
|
287
|
-
const data = await response.json()
|
|
255
|
+
const data = await response.json();
|
|
288
256
|
|
|
289
257
|
// Validate the response data
|
|
290
258
|
if (!data || !data.token) {
|
|
291
|
-
console.warn(
|
|
292
|
-
|
|
293
|
-
'Invalid token response. Please run `berget auth login` again.',
|
|
294
|
-
),
|
|
295
|
-
)
|
|
296
|
-
return false
|
|
259
|
+
console.warn(chalk.yellow("Invalid token response. Please run `berget auth login` again."));
|
|
260
|
+
return false;
|
|
297
261
|
}
|
|
298
262
|
|
|
299
|
-
logger.debug(
|
|
263
|
+
logger.debug("Token refreshed successfully");
|
|
300
264
|
|
|
301
265
|
// Update the token
|
|
302
|
-
tokenManager.updateAccessToken(data.token, data.expires_in || 3600)
|
|
266
|
+
tokenManager.updateAccessToken(data.token, data.expires_in || 3600);
|
|
303
267
|
|
|
304
268
|
// If a new refresh token was provided, update that too
|
|
305
269
|
if (data.refresh_token) {
|
|
306
|
-
tokenManager.setTokens(
|
|
307
|
-
|
|
308
|
-
data.refresh_token,
|
|
309
|
-
data.expires_in || 3600,
|
|
310
|
-
)
|
|
311
|
-
logger.debug('Refresh token also updated')
|
|
270
|
+
tokenManager.setTokens(data.token, data.refresh_token, data.expires_in || 3600);
|
|
271
|
+
logger.debug("Refresh token also updated");
|
|
312
272
|
}
|
|
313
273
|
} catch (fetchError) {
|
|
314
274
|
console.warn(
|
|
315
275
|
chalk.yellow(
|
|
316
276
|
`Failed to refresh token: ${
|
|
317
|
-
fetchError instanceof Error
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
)
|
|
323
|
-
return false
|
|
277
|
+
fetchError instanceof Error ? fetchError.message : String(fetchError)
|
|
278
|
+
}`
|
|
279
|
+
)
|
|
280
|
+
);
|
|
281
|
+
return false;
|
|
324
282
|
}
|
|
325
283
|
|
|
326
|
-
return true
|
|
284
|
+
return true;
|
|
327
285
|
} catch (error) {
|
|
328
286
|
console.warn(
|
|
329
287
|
chalk.yellow(
|
|
330
288
|
`Failed to refresh authentication token: ${
|
|
331
289
|
error instanceof Error ? error.message : String(error)
|
|
332
|
-
}
|
|
333
|
-
)
|
|
334
|
-
)
|
|
335
|
-
return false
|
|
290
|
+
}`
|
|
291
|
+
)
|
|
292
|
+
);
|
|
293
|
+
return false;
|
|
336
294
|
}
|
|
337
295
|
}
|