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.
Files changed (37) hide show
  1. package/README.md +92 -0
  2. package/dist/index.js +7 -471
  3. package/dist/src/client.js +193 -102
  4. package/dist/src/commands/api-keys.js +271 -0
  5. package/dist/src/commands/auth.js +65 -0
  6. package/dist/src/commands/autocomplete.js +24 -0
  7. package/dist/src/commands/billing.js +53 -0
  8. package/dist/src/commands/chat.js +276 -0
  9. package/dist/src/commands/clusters.js +69 -0
  10. package/dist/src/commands/index.js +25 -0
  11. package/dist/src/commands/models.js +69 -0
  12. package/dist/src/commands/users.js +43 -0
  13. package/dist/src/constants/command-structure.js +14 -0
  14. package/dist/src/services/auth-service.js +49 -47
  15. package/dist/src/services/chat-service.js +177 -0
  16. package/dist/src/utils/config-checker.js +50 -0
  17. package/dist/src/utils/default-api-key.js +111 -0
  18. package/dist/src/utils/token-manager.js +165 -0
  19. package/index.ts +5 -566
  20. package/package.json +6 -1
  21. package/src/client.ts +262 -80
  22. package/src/commands/api-keys.ts +364 -0
  23. package/src/commands/auth.ts +58 -0
  24. package/src/commands/autocomplete.ts +19 -0
  25. package/src/commands/billing.ts +41 -0
  26. package/src/commands/chat.ts +345 -0
  27. package/src/commands/clusters.ts +65 -0
  28. package/src/commands/index.ts +23 -0
  29. package/src/commands/models.ts +63 -0
  30. package/src/commands/users.ts +37 -0
  31. package/src/constants/command-structure.ts +16 -0
  32. package/src/services/auth-service.ts +90 -50
  33. package/src/services/chat-service.ts +177 -0
  34. package/src/types/api.d.ts +58 -192
  35. package/src/utils/config-checker.ts +23 -0
  36. package/src/utils/default-api-key.ts +94 -0
  37. 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
- deviceData.verification_url || 'https://auth.berget.ai/device'
85
+ typedDeviceData.verification_url ||
86
+ 'https://keycloak.berget.ai/device'
62
87
  )}`
63
88
  )
64
89
  )
65
- console.log(
66
- chalk.cyan(
67
- `2. Enter this code: ${chalk.bold(deviceData.user_code || '')}\n`
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 (deviceData.verification_url) {
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(deviceData.verification_url);
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
- deviceData.expires_in !== undefined ? deviceData.expires_in : 900
124
+ typedDeviceData.expires_in !== undefined
125
+ ? typedDeviceData.expires_in
126
+ : 900
97
127
  const expiresAt = startTime + expiresIn * 1000
98
- let pollInterval = (deviceData.interval || 5) * 1000
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 = deviceData.device_code || ''
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 && tokenData.token) {
174
- // Success!
175
- saveAuthToken(tokenData.token)
176
-
177
- process.stdout.write('\r' + ' '.repeat(50) + '\r') // Clear the spinner line
178
- console.log(chalk.green('✓ Successfully logged in to Berget'))
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 (tokenData.user) {
181
- const user = tokenData.user as any
182
- console.log(
183
- chalk.green(`Logged in as ${user.name || user.email || 'User'}`)
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
- return true
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
+ }