berget 0.1.0 → 1.1.0
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/README.md +92 -0
- package/dist/index.js +7 -439
- package/dist/src/client.js +193 -102
- package/dist/src/commands/api-keys.js +271 -0
- package/dist/src/commands/auth.js +65 -0
- package/dist/src/commands/autocomplete.js +24 -0
- package/dist/src/commands/billing.js +53 -0
- package/dist/src/commands/chat.js +276 -0
- package/dist/src/commands/clusters.js +69 -0
- package/dist/src/commands/index.js +25 -0
- package/dist/src/commands/models.js +69 -0
- package/dist/src/commands/users.js +43 -0
- package/dist/src/constants/command-structure.js +164 -0
- package/dist/src/services/api-key-service.js +34 -5
- package/dist/src/services/auth-service.js +83 -43
- package/dist/src/services/chat-service.js +177 -0
- package/dist/src/services/cluster-service.js +37 -2
- package/dist/src/services/collaborator-service.js +21 -4
- package/dist/src/services/flux-service.js +21 -4
- package/dist/src/services/helm-service.js +20 -3
- package/dist/src/services/kubectl-service.js +26 -5
- package/dist/src/utils/config-checker.js +50 -0
- package/dist/src/utils/default-api-key.js +111 -0
- package/dist/src/utils/token-manager.js +165 -0
- package/index.ts +5 -529
- package/package.json +6 -1
- package/src/client.ts +262 -80
- package/src/commands/api-keys.ts +364 -0
- package/src/commands/auth.ts +58 -0
- package/src/commands/autocomplete.ts +19 -0
- package/src/commands/billing.ts +41 -0
- package/src/commands/chat.ts +345 -0
- package/src/commands/clusters.ts +65 -0
- package/src/commands/index.ts +23 -0
- package/src/commands/models.ts +63 -0
- package/src/commands/users.ts +37 -0
- package/src/constants/command-structure.ts +184 -0
- package/src/services/api-key-service.ts +36 -5
- package/src/services/auth-service.ts +101 -44
- package/src/services/chat-service.ts +177 -0
- package/src/services/cluster-service.ts +37 -2
- package/src/services/collaborator-service.ts +23 -4
- package/src/services/flux-service.ts +23 -4
- package/src/services/helm-service.ts +22 -3
- package/src/services/kubectl-service.ts +28 -5
- package/src/types/api.d.ts +58 -192
- package/src/utils/config-checker.ts +23 -0
- package/src/utils/default-api-key.ts +94 -0
- package/src/utils/token-manager.ts +150 -0
package/src/client.ts
CHANGED
|
@@ -4,10 +4,7 @@ import * as fs from 'fs'
|
|
|
4
4
|
import * as path from 'path'
|
|
5
5
|
import * as os from 'os'
|
|
6
6
|
import chalk from 'chalk'
|
|
7
|
-
|
|
8
|
-
// Configuration directory
|
|
9
|
-
const CONFIG_DIR = path.join(os.homedir(), '.berget')
|
|
10
|
-
const TOKEN_FILE = path.join(CONFIG_DIR, 'token.json')
|
|
7
|
+
import { TokenManager } from './utils/token-manager'
|
|
11
8
|
|
|
12
9
|
// API Base URL
|
|
13
10
|
// Use --local flag to test against local API
|
|
@@ -16,7 +13,7 @@ const API_BASE_URL =
|
|
|
16
13
|
process.env.BERGET_API_URL ||
|
|
17
14
|
(isLocalMode ? 'http://localhost:3000' : 'https://api.berget.ai')
|
|
18
15
|
|
|
19
|
-
if (isLocalMode && !process.env.BERGET_API_URL) {
|
|
16
|
+
if (isLocalMode && !process.env.BERGET_API_URL && process.argv.includes('--debug')) {
|
|
20
17
|
console.log(chalk.yellow('Using local API endpoint: http://localhost:3000'))
|
|
21
18
|
}
|
|
22
19
|
|
|
@@ -31,97 +28,42 @@ export const apiClient = createClient<paths>({
|
|
|
31
28
|
|
|
32
29
|
// Authentication functions
|
|
33
30
|
export const getAuthToken = (): string | null => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const tokenData = JSON.parse(fs.readFileSync(TOKEN_FILE, 'utf8'))
|
|
37
|
-
return tokenData.accessToken
|
|
38
|
-
}
|
|
39
|
-
} catch (error) {
|
|
40
|
-
console.error('Error reading auth token:', error)
|
|
41
|
-
}
|
|
42
|
-
return null
|
|
31
|
+
const tokenManager = TokenManager.getInstance()
|
|
32
|
+
return tokenManager.getAccessToken()
|
|
43
33
|
}
|
|
44
34
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
.split('')
|
|
53
|
-
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
|
54
|
-
.join('')
|
|
55
|
-
)
|
|
56
|
-
const payload = JSON.parse(jsonPayload)
|
|
57
|
-
|
|
58
|
-
// Check if token has expired
|
|
59
|
-
if (payload.exp) {
|
|
60
|
-
return payload.exp * 1000 < Date.now()
|
|
61
|
-
}
|
|
62
|
-
} catch (error) {
|
|
63
|
-
// If we can't decode the token, assume it's expired
|
|
64
|
-
return true
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// If there's no exp claim, assume it's valid
|
|
68
|
-
return false
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export const saveAuthToken = (token: string): void => {
|
|
72
|
-
try {
|
|
73
|
-
if (!fs.existsSync(CONFIG_DIR)) {
|
|
74
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true })
|
|
75
|
-
}
|
|
76
|
-
fs.writeFileSync(TOKEN_FILE, JSON.stringify({ accessToken: token }), 'utf8')
|
|
77
|
-
// Set file permissions to be readable only by the owner
|
|
78
|
-
fs.chmodSync(TOKEN_FILE, 0o600)
|
|
79
|
-
} catch (error) {
|
|
80
|
-
console.error('Error saving auth token:', error)
|
|
81
|
-
}
|
|
35
|
+
export const saveAuthToken = (
|
|
36
|
+
accessToken: string,
|
|
37
|
+
refreshToken: string,
|
|
38
|
+
expiresIn: number = 3600
|
|
39
|
+
): void => {
|
|
40
|
+
const tokenManager = TokenManager.getInstance()
|
|
41
|
+
tokenManager.setTokens(accessToken, refreshToken, expiresIn)
|
|
82
42
|
}
|
|
83
43
|
|
|
84
44
|
export const clearAuthToken = (): void => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
fs.unlinkSync(TOKEN_FILE)
|
|
88
|
-
}
|
|
89
|
-
} catch (error) {
|
|
90
|
-
console.error('Error clearing auth token:', error)
|
|
91
|
-
}
|
|
45
|
+
const tokenManager = TokenManager.getInstance()
|
|
46
|
+
tokenManager.clearTokens()
|
|
92
47
|
}
|
|
93
48
|
|
|
94
|
-
// Create an authenticated client
|
|
49
|
+
// Create an authenticated client with refresh token support
|
|
95
50
|
export const createAuthenticatedClient = () => {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
chalk.yellow(
|
|
100
|
-
'No authentication token found. Please run `berget login` first.'
|
|
101
|
-
)
|
|
102
|
-
)
|
|
103
|
-
} else if (isTokenExpired(token)) {
|
|
51
|
+
const tokenManager = TokenManager.getInstance()
|
|
52
|
+
|
|
53
|
+
if (!tokenManager.getAccessToken() && process.argv.includes('--debug')) {
|
|
104
54
|
console.warn(
|
|
105
55
|
chalk.yellow(
|
|
106
|
-
'
|
|
56
|
+
'No authentication token found. Please run `berget auth login` first.'
|
|
107
57
|
)
|
|
108
58
|
)
|
|
109
|
-
// Optionally clear the expired token
|
|
110
|
-
clearAuthToken()
|
|
111
|
-
return createClient<paths>({
|
|
112
|
-
baseUrl: API_BASE_URL,
|
|
113
|
-
headers: {
|
|
114
|
-
'Content-Type': 'application/json',
|
|
115
|
-
Accept: 'application/json',
|
|
116
|
-
},
|
|
117
|
-
})
|
|
118
59
|
}
|
|
119
60
|
|
|
120
|
-
|
|
61
|
+
// Create the base client
|
|
62
|
+
const client = createClient<paths>({
|
|
121
63
|
baseUrl: API_BASE_URL,
|
|
122
|
-
headers:
|
|
64
|
+
headers: tokenManager.getAccessToken()
|
|
123
65
|
? {
|
|
124
|
-
Authorization: `Bearer ${
|
|
66
|
+
Authorization: `Bearer ${tokenManager.getAccessToken()}`,
|
|
125
67
|
'Content-Type': 'application/json',
|
|
126
68
|
Accept: 'application/json',
|
|
127
69
|
}
|
|
@@ -130,4 +72,244 @@ export const createAuthenticatedClient = () => {
|
|
|
130
72
|
Accept: 'application/json',
|
|
131
73
|
},
|
|
132
74
|
})
|
|
75
|
+
|
|
76
|
+
// Wrap the client to handle token refresh
|
|
77
|
+
return new Proxy(client, {
|
|
78
|
+
get(target, prop: string | symbol) {
|
|
79
|
+
// For HTTP methods (GET, POST, etc.), add token refresh logic
|
|
80
|
+
if (
|
|
81
|
+
typeof target[prop as keyof typeof target] === 'function' &&
|
|
82
|
+
['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(String(prop))
|
|
83
|
+
) {
|
|
84
|
+
return async (...args: any[]) => {
|
|
85
|
+
// Check if token is expired before making the request
|
|
86
|
+
if (tokenManager.isTokenExpired() && tokenManager.getRefreshToken()) {
|
|
87
|
+
await refreshAccessToken(tokenManager)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Update the Authorization header with the current token
|
|
91
|
+
if (tokenManager.getAccessToken()) {
|
|
92
|
+
if (!args[1]) args[1] = {}
|
|
93
|
+
if (!args[1].headers) args[1].headers = {}
|
|
94
|
+
args[1].headers.Authorization = `Bearer ${tokenManager.getAccessToken()}`
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Make the original request
|
|
98
|
+
let result
|
|
99
|
+
try {
|
|
100
|
+
result = await (target[prop as keyof typeof target] as Function)(
|
|
101
|
+
...args
|
|
102
|
+
)
|
|
103
|
+
} catch (requestError) {
|
|
104
|
+
if (process.argv.includes('--debug')) {
|
|
105
|
+
console.log(
|
|
106
|
+
chalk.red(
|
|
107
|
+
`DEBUG: Request error: ${
|
|
108
|
+
requestError instanceof Error
|
|
109
|
+
? requestError.message
|
|
110
|
+
: String(requestError)
|
|
111
|
+
}`
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
error: {
|
|
117
|
+
message: `Request failed: ${
|
|
118
|
+
requestError instanceof Error
|
|
119
|
+
? requestError.message
|
|
120
|
+
: String(requestError)
|
|
121
|
+
}`,
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// If we get an auth error, try to refresh the token and retry
|
|
127
|
+
if (result.error) {
|
|
128
|
+
// Detect various forms of authentication errors
|
|
129
|
+
const isAuthError =
|
|
130
|
+
// Standard 401 Unauthorized
|
|
131
|
+
(typeof result.error === 'object' && result.error.status === 401) ||
|
|
132
|
+
// OAuth specific errors
|
|
133
|
+
(result.error.error &&
|
|
134
|
+
(result.error.error.code === 'invalid_token' ||
|
|
135
|
+
result.error.error.code === 'token_expired' ||
|
|
136
|
+
result.error.error.message === 'Invalid API key' ||
|
|
137
|
+
result.error.error.message?.toLowerCase().includes('token') ||
|
|
138
|
+
result.error.error.message?.toLowerCase().includes('unauthorized'))) ||
|
|
139
|
+
// Message-based detection as fallback
|
|
140
|
+
(typeof result.error === 'string' &&
|
|
141
|
+
(result.error.toLowerCase().includes('unauthorized') ||
|
|
142
|
+
result.error.toLowerCase().includes('token') ||
|
|
143
|
+
result.error.toLowerCase().includes('auth')))
|
|
144
|
+
|
|
145
|
+
if (isAuthError && tokenManager.getRefreshToken()) {
|
|
146
|
+
if (process.argv.includes('--debug')) {
|
|
147
|
+
console.log(
|
|
148
|
+
chalk.yellow(
|
|
149
|
+
'DEBUG: Auth error detected, attempting token refresh'
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
console.log(
|
|
153
|
+
chalk.yellow(
|
|
154
|
+
`DEBUG: Error details: ${JSON.stringify(
|
|
155
|
+
result.error,
|
|
156
|
+
null,
|
|
157
|
+
2
|
|
158
|
+
)}`
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const refreshed = await refreshAccessToken(tokenManager)
|
|
164
|
+
if (refreshed) {
|
|
165
|
+
if (process.argv.includes('--debug')) {
|
|
166
|
+
console.log(
|
|
167
|
+
chalk.green(
|
|
168
|
+
'DEBUG: Token refreshed successfully, retrying request'
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Update the Authorization header with the new token
|
|
174
|
+
if (!args[1]) args[1] = {}
|
|
175
|
+
if (!args[1].headers) args[1].headers = {}
|
|
176
|
+
args[1].headers.Authorization = `Bearer ${tokenManager.getAccessToken()}`
|
|
177
|
+
|
|
178
|
+
// Retry the request
|
|
179
|
+
return await (target[prop as keyof typeof target] as Function)(
|
|
180
|
+
...args
|
|
181
|
+
)
|
|
182
|
+
} else {
|
|
183
|
+
if (process.argv.includes('--debug')) {
|
|
184
|
+
console.log(chalk.red('DEBUG: Token refresh failed'))
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Add a more helpful error message for users
|
|
188
|
+
if (typeof result.error === 'object') {
|
|
189
|
+
result.error.userMessage = 'Your session has expired. Please run `berget auth login` to log in again.'
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return result
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// For other properties, just return the original
|
|
200
|
+
return target[prop as keyof typeof target]
|
|
201
|
+
},
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Helper function to refresh the access token
|
|
206
|
+
async function refreshAccessToken(
|
|
207
|
+
tokenManager: TokenManager
|
|
208
|
+
): Promise<boolean> {
|
|
209
|
+
try {
|
|
210
|
+
const refreshToken = tokenManager.getRefreshToken()
|
|
211
|
+
if (!refreshToken) return false
|
|
212
|
+
|
|
213
|
+
if (process.argv.includes('--debug')) {
|
|
214
|
+
console.log(chalk.yellow('DEBUG: Attempting to refresh access token'))
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Use fetch directly since this endpoint might not be in the OpenAPI spec
|
|
218
|
+
try {
|
|
219
|
+
const response = await fetch(`${API_BASE_URL}/v1/auth/refresh`, {
|
|
220
|
+
method: 'POST',
|
|
221
|
+
headers: {
|
|
222
|
+
'Content-Type': 'application/json',
|
|
223
|
+
Accept: 'application/json',
|
|
224
|
+
},
|
|
225
|
+
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Handle HTTP errors
|
|
229
|
+
if (!response.ok) {
|
|
230
|
+
if (process.argv.includes('--debug')) {
|
|
231
|
+
console.log(
|
|
232
|
+
chalk.yellow(`DEBUG: Token refresh error: HTTP ${response.status} ${response.statusText}`)
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Check if the refresh token itself is expired or invalid
|
|
237
|
+
if (response.status === 401 || response.status === 403) {
|
|
238
|
+
console.warn(
|
|
239
|
+
chalk.yellow(
|
|
240
|
+
'Your refresh token has expired. Please run `berget auth login` again.'
|
|
241
|
+
)
|
|
242
|
+
)
|
|
243
|
+
// Clear tokens if unauthorized - they're invalid
|
|
244
|
+
tokenManager.clearTokens()
|
|
245
|
+
} else {
|
|
246
|
+
console.warn(
|
|
247
|
+
chalk.yellow(
|
|
248
|
+
`Failed to refresh token: ${response.status} ${response.statusText}`
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
return false
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Parse the response
|
|
256
|
+
const contentType = response.headers.get('content-type')
|
|
257
|
+
if (!contentType || !contentType.includes('application/json')) {
|
|
258
|
+
console.warn(
|
|
259
|
+
chalk.yellow(`Unexpected content type in response: ${contentType}`)
|
|
260
|
+
)
|
|
261
|
+
return false
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const data = await response.json()
|
|
265
|
+
|
|
266
|
+
// Validate the response data
|
|
267
|
+
if (!data || !data.token) {
|
|
268
|
+
console.warn(
|
|
269
|
+
chalk.yellow(
|
|
270
|
+
'Invalid token response. Please run `berget auth login` again.'
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
return false
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (process.argv.includes('--debug')) {
|
|
277
|
+
console.log(chalk.green('DEBUG: Token refreshed successfully'))
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Update the token
|
|
281
|
+
tokenManager.updateAccessToken(
|
|
282
|
+
data.token,
|
|
283
|
+
data.expires_in || 3600
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
// If a new refresh token was provided, update that too
|
|
287
|
+
if (data.refresh_token) {
|
|
288
|
+
tokenManager.setTokens(data.token, data.refresh_token, data.expires_in || 3600)
|
|
289
|
+
if (process.argv.includes('--debug')) {
|
|
290
|
+
console.log(chalk.green('DEBUG: Refresh token also updated'))
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
} catch (fetchError) {
|
|
294
|
+
console.warn(
|
|
295
|
+
chalk.yellow(
|
|
296
|
+
`Failed to refresh token: ${
|
|
297
|
+
fetchError instanceof Error ? fetchError.message : String(fetchError)
|
|
298
|
+
}`
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
return false
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return true
|
|
305
|
+
} catch (error) {
|
|
306
|
+
console.warn(
|
|
307
|
+
chalk.yellow(
|
|
308
|
+
`Failed to refresh authentication token: ${
|
|
309
|
+
error instanceof Error ? error.message : String(error)
|
|
310
|
+
}`
|
|
311
|
+
)
|
|
312
|
+
)
|
|
313
|
+
return false
|
|
314
|
+
}
|
|
133
315
|
}
|