berget 1.0.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 -471
- 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 +14 -0
- package/dist/src/services/auth-service.js +49 -47
- package/dist/src/services/chat-service.js +177 -0
- 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 -566
- 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 +16 -0
- package/src/services/auth-service.ts +90 -50
- package/src/services/chat-service.ts +177 -0
- 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
|
@@ -16,10 +16,10 @@ import { COMMAND_GROUPS, SUBCOMMANDS } from '../constants/command-structure'
|
|
|
16
16
|
export class AuthService {
|
|
17
17
|
private static instance: AuthService
|
|
18
18
|
private client = createAuthenticatedClient()
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
// Command group name for this service
|
|
21
21
|
public static readonly COMMAND_GROUP = COMMAND_GROUPS.AUTH
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
// Subcommands for this service
|
|
24
24
|
public static readonly COMMANDS = SUBCOMMANDS.AUTH
|
|
25
25
|
|
|
@@ -32,6 +32,21 @@ export class AuthService {
|
|
|
32
32
|
return AuthService.instance
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
public async whoami(): Promise<any> {
|
|
36
|
+
try {
|
|
37
|
+
const { data: profile, error } = await this.client.GET('/v1/users/me')
|
|
38
|
+
if (error) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
error ? JSON.stringify(error) : 'Failed to get user profile'
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
return profile
|
|
44
|
+
} catch (error) {
|
|
45
|
+
handleError('Failed to get user profile', error)
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
35
50
|
public async login(): Promise<boolean> {
|
|
36
51
|
try {
|
|
37
52
|
// Clear any existing token to ensure a fresh login
|
|
@@ -53,27 +68,40 @@ export class AuthService {
|
|
|
53
68
|
)
|
|
54
69
|
}
|
|
55
70
|
|
|
71
|
+
// Type assertion for deviceData
|
|
72
|
+
const typedDeviceData = deviceData as {
|
|
73
|
+
verification_url?: string
|
|
74
|
+
user_code?: string
|
|
75
|
+
device_code?: string
|
|
76
|
+
expires_in?: number
|
|
77
|
+
interval?: number
|
|
78
|
+
}
|
|
79
|
+
|
|
56
80
|
// Display information to user
|
|
57
81
|
console.log(chalk.cyan('\nTo complete login:'))
|
|
58
82
|
console.log(
|
|
59
83
|
chalk.cyan(
|
|
60
84
|
`1. Open this URL: ${chalk.bold(
|
|
61
|
-
|
|
85
|
+
typedDeviceData.verification_url ||
|
|
86
|
+
'https://keycloak.berget.ai/device'
|
|
62
87
|
)}`
|
|
63
88
|
)
|
|
64
89
|
)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
90
|
+
if (!typedDeviceData.verification_url)
|
|
91
|
+
console.log(
|
|
92
|
+
chalk.cyan(
|
|
93
|
+
`2. Enter this code: ${chalk.bold(
|
|
94
|
+
typedDeviceData.user_code || ''
|
|
95
|
+
)}\n`
|
|
96
|
+
)
|
|
68
97
|
)
|
|
69
|
-
)
|
|
70
98
|
|
|
71
99
|
// Try to open browser automatically
|
|
72
100
|
try {
|
|
73
|
-
if (
|
|
101
|
+
if (typedDeviceData.verification_url) {
|
|
74
102
|
// Use dynamic import for the 'open' package
|
|
75
|
-
const open = await import('open').then(m => m.default)
|
|
76
|
-
await open(
|
|
103
|
+
const open = await import('open').then((m) => m.default)
|
|
104
|
+
await open(typedDeviceData.verification_url)
|
|
77
105
|
console.log(
|
|
78
106
|
chalk.dim(
|
|
79
107
|
"Browser opened automatically. If it didn't open, please use the URL above."
|
|
@@ -93,9 +121,11 @@ export class AuthService {
|
|
|
93
121
|
// Step 2: Poll for completion
|
|
94
122
|
const startTime = Date.now()
|
|
95
123
|
const expiresIn =
|
|
96
|
-
|
|
124
|
+
typedDeviceData.expires_in !== undefined
|
|
125
|
+
? typedDeviceData.expires_in
|
|
126
|
+
: 900
|
|
97
127
|
const expiresAt = startTime + expiresIn * 1000
|
|
98
|
-
let pollInterval = (
|
|
128
|
+
let pollInterval = (typedDeviceData.interval || 5) * 1000
|
|
99
129
|
|
|
100
130
|
const spinner = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
101
131
|
let spinnerIdx = 0
|
|
@@ -111,7 +141,7 @@ export class AuthService {
|
|
|
111
141
|
spinnerIdx = (spinnerIdx + 1) % spinner.length
|
|
112
142
|
|
|
113
143
|
// Check if authentication is complete
|
|
114
|
-
const deviceCode =
|
|
144
|
+
const deviceCode = typedDeviceData.device_code || ''
|
|
115
145
|
const { data: tokenData, error: tokenError } = await apiClient.POST(
|
|
116
146
|
'/v1/auth/device/token',
|
|
117
147
|
{
|
|
@@ -170,21 +200,56 @@ export class AuthService {
|
|
|
170
200
|
}
|
|
171
201
|
continue
|
|
172
202
|
}
|
|
173
|
-
} else if (tokenData
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
203
|
+
} else if (tokenData) {
|
|
204
|
+
// Type assertion for tokenData
|
|
205
|
+
const typedTokenData = tokenData as {
|
|
206
|
+
token?: string
|
|
207
|
+
refresh_token?: string
|
|
208
|
+
expires_in?: number
|
|
209
|
+
refresh_expires_in?: number
|
|
210
|
+
user?: {
|
|
211
|
+
id?: string
|
|
212
|
+
email?: string
|
|
213
|
+
name?: string
|
|
214
|
+
}
|
|
215
|
+
}
|
|
179
216
|
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
217
|
+
if (typedTokenData.token) {
|
|
218
|
+
// Success!
|
|
219
|
+
saveAuthToken(
|
|
220
|
+
typedTokenData.token,
|
|
221
|
+
typedTokenData.refresh_token || '',
|
|
222
|
+
typedTokenData.expires_in || 3600
|
|
184
223
|
)
|
|
185
|
-
}
|
|
186
224
|
|
|
187
|
-
|
|
225
|
+
if (process.argv.includes('--debug')) {
|
|
226
|
+
console.log(chalk.yellow('DEBUG: Token data received:'))
|
|
227
|
+
console.log(
|
|
228
|
+
chalk.yellow(
|
|
229
|
+
JSON.stringify(
|
|
230
|
+
{
|
|
231
|
+
expires_in: typedTokenData.expires_in,
|
|
232
|
+
refresh_expires_in: typedTokenData.refresh_expires_in,
|
|
233
|
+
},
|
|
234
|
+
null,
|
|
235
|
+
2
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
process.stdout.write('\r' + ' '.repeat(50) + '\r') // Clear the spinner line
|
|
242
|
+
console.log(chalk.green('✓ Successfully logged in to Berget'))
|
|
243
|
+
|
|
244
|
+
if (typedTokenData.user) {
|
|
245
|
+
const user = typedTokenData.user
|
|
246
|
+
console.log(
|
|
247
|
+
chalk.green(`Logged in as ${user.name || user.email || 'User'}`)
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return true
|
|
252
|
+
}
|
|
188
253
|
}
|
|
189
254
|
}
|
|
190
255
|
|
|
@@ -195,29 +260,4 @@ export class AuthService {
|
|
|
195
260
|
return false
|
|
196
261
|
}
|
|
197
262
|
}
|
|
198
|
-
|
|
199
|
-
public async isAuthenticated(): Promise<boolean> {
|
|
200
|
-
try {
|
|
201
|
-
// Call an API endpoint that requires authentication
|
|
202
|
-
const { data, error } = await this.client.GET('/v1/users/me')
|
|
203
|
-
return !!data && !error
|
|
204
|
-
} catch {
|
|
205
|
-
return false
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Get current user profile
|
|
211
|
-
* Command: berget auth whoami
|
|
212
|
-
*/
|
|
213
|
-
public async whoami() {
|
|
214
|
-
try {
|
|
215
|
-
const { data, error } = await this.client.GET('/v1/users/me')
|
|
216
|
-
if (error) throw new Error(JSON.stringify(error))
|
|
217
|
-
return data
|
|
218
|
-
} catch (error) {
|
|
219
|
-
handleError('Failed to get user profile', error)
|
|
220
|
-
throw error
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
263
|
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { createAuthenticatedClient } from '../client'
|
|
2
|
+
import { COMMAND_GROUPS, SUBCOMMANDS } from '../constants/command-structure'
|
|
3
|
+
import chalk from 'chalk'
|
|
4
|
+
|
|
5
|
+
export interface ChatMessage {
|
|
6
|
+
role: 'system' | 'user' | 'assistant'
|
|
7
|
+
content: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ChatCompletionOptions {
|
|
11
|
+
model: string
|
|
12
|
+
messages: ChatMessage[]
|
|
13
|
+
temperature?: number
|
|
14
|
+
max_tokens?: number
|
|
15
|
+
stream?: boolean
|
|
16
|
+
top_p?: number
|
|
17
|
+
apiKey?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Service for interacting with the chat API
|
|
22
|
+
* Command group: chat
|
|
23
|
+
*/
|
|
24
|
+
export class ChatService {
|
|
25
|
+
private static instance: ChatService
|
|
26
|
+
private client = createAuthenticatedClient()
|
|
27
|
+
|
|
28
|
+
// Command group name for this service
|
|
29
|
+
public static readonly COMMAND_GROUP = 'chat'
|
|
30
|
+
|
|
31
|
+
// Subcommands for this service
|
|
32
|
+
public static readonly COMMANDS = {
|
|
33
|
+
RUN: 'run',
|
|
34
|
+
LIST: 'list'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private constructor() {}
|
|
38
|
+
|
|
39
|
+
public static getInstance(): ChatService {
|
|
40
|
+
if (!ChatService.instance) {
|
|
41
|
+
ChatService.instance = new ChatService()
|
|
42
|
+
}
|
|
43
|
+
return ChatService.instance
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create a chat completion
|
|
48
|
+
* Command: berget chat completion
|
|
49
|
+
*/
|
|
50
|
+
public async createCompletion(options: ChatCompletionOptions): Promise<any> {
|
|
51
|
+
try {
|
|
52
|
+
const headers: Record<string, string> = {}
|
|
53
|
+
|
|
54
|
+
// Check if debug is enabled
|
|
55
|
+
const isDebug = process.argv.includes('--debug')
|
|
56
|
+
|
|
57
|
+
if (isDebug) {
|
|
58
|
+
console.log(chalk.yellow('DEBUG: Chat completion options:'))
|
|
59
|
+
console.log(chalk.yellow(JSON.stringify(options, null, 2)))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// If an API key is provided, use it for this request
|
|
63
|
+
if (options.apiKey) {
|
|
64
|
+
headers['Authorization'] = `Bearer ${options.apiKey}`
|
|
65
|
+
// Remove apiKey from options before sending to API
|
|
66
|
+
const { apiKey, ...requestOptions } = options
|
|
67
|
+
|
|
68
|
+
if (isDebug) {
|
|
69
|
+
console.log(chalk.yellow('DEBUG: Using provided API key'))
|
|
70
|
+
console.log(chalk.yellow('DEBUG: Request options:'))
|
|
71
|
+
console.log(chalk.yellow(JSON.stringify(requestOptions, null, 2)))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const { data, error } = await this.client.POST('/v1/chat/completions', {
|
|
75
|
+
body: requestOptions,
|
|
76
|
+
headers
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
if (isDebug) {
|
|
80
|
+
console.log(chalk.yellow('DEBUG: API response:'))
|
|
81
|
+
console.log(chalk.yellow(JSON.stringify({ data, error }, null, 2)))
|
|
82
|
+
|
|
83
|
+
// Output the complete response data for debugging
|
|
84
|
+
console.log(chalk.yellow('DEBUG: Complete response data:'))
|
|
85
|
+
console.log(chalk.yellow(JSON.stringify(data, null, 2)))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (error) throw new Error(JSON.stringify(error))
|
|
89
|
+
return data
|
|
90
|
+
} else {
|
|
91
|
+
// Use the default authenticated client
|
|
92
|
+
if (isDebug) {
|
|
93
|
+
console.log(chalk.yellow('DEBUG: Using default authentication'))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const { data, error } = await this.client.POST('/v1/chat/completions', {
|
|
97
|
+
body: options
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
if (isDebug) {
|
|
101
|
+
console.log(chalk.yellow('DEBUG: API response:'))
|
|
102
|
+
console.log(chalk.yellow(JSON.stringify({ data, error }, null, 2)))
|
|
103
|
+
|
|
104
|
+
// Output the complete response data for debugging
|
|
105
|
+
console.log(chalk.yellow('DEBUG: Complete response data:'))
|
|
106
|
+
console.log(chalk.yellow(JSON.stringify(data, null, 2)))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (error) throw new Error(JSON.stringify(error))
|
|
110
|
+
return data
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
// Improved error handling
|
|
114
|
+
let errorMessage = 'Failed to create chat completion';
|
|
115
|
+
|
|
116
|
+
if (error instanceof Error) {
|
|
117
|
+
try {
|
|
118
|
+
// Try to parse the error message as JSON
|
|
119
|
+
const parsedError = JSON.parse(error.message);
|
|
120
|
+
if (parsedError.error && parsedError.error.message) {
|
|
121
|
+
errorMessage = `Chat error: ${parsedError.error.message}`;
|
|
122
|
+
}
|
|
123
|
+
} catch (e) {
|
|
124
|
+
// If parsing fails, use the original error message
|
|
125
|
+
errorMessage = `Chat error: ${error.message}`;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.error(chalk.red(errorMessage));
|
|
130
|
+
throw new Error(errorMessage);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* List available models
|
|
136
|
+
* Command: berget chat list
|
|
137
|
+
*/
|
|
138
|
+
public async listModels(apiKey?: string): Promise<any> {
|
|
139
|
+
try {
|
|
140
|
+
if (apiKey) {
|
|
141
|
+
const headers = {
|
|
142
|
+
'Authorization': `Bearer ${apiKey}`
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const { data, error } = await this.client.GET('/v1/models', { headers })
|
|
146
|
+
if (error) throw new Error(JSON.stringify(error))
|
|
147
|
+
return data
|
|
148
|
+
} else {
|
|
149
|
+
const { data, error } = await this.client.GET('/v1/models')
|
|
150
|
+
if (error) throw new Error(JSON.stringify(error))
|
|
151
|
+
return data
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
// Improved error handling
|
|
155
|
+
let errorMessage = 'Failed to list models';
|
|
156
|
+
|
|
157
|
+
if (error instanceof Error) {
|
|
158
|
+
try {
|
|
159
|
+
// Try to parse the error message as JSON
|
|
160
|
+
const parsedError = JSON.parse(error.message);
|
|
161
|
+
if (parsedError.error) {
|
|
162
|
+
errorMessage = `Models error: ${typeof parsedError.error === 'string' ?
|
|
163
|
+
parsedError.error :
|
|
164
|
+
(parsedError.error.message || JSON.stringify(parsedError.error))}`;
|
|
165
|
+
}
|
|
166
|
+
} catch (e) {
|
|
167
|
+
// If parsing fails, use the original error message
|
|
168
|
+
errorMessage = `Models error: ${error.message}`;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
console.error(chalk.red(errorMessage));
|
|
173
|
+
throw new Error(errorMessage);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
}
|