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.
Files changed (49) hide show
  1. package/README.md +92 -0
  2. package/dist/index.js +7 -439
  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 +164 -0
  14. package/dist/src/services/api-key-service.js +34 -5
  15. package/dist/src/services/auth-service.js +83 -43
  16. package/dist/src/services/chat-service.js +177 -0
  17. package/dist/src/services/cluster-service.js +37 -2
  18. package/dist/src/services/collaborator-service.js +21 -4
  19. package/dist/src/services/flux-service.js +21 -4
  20. package/dist/src/services/helm-service.js +20 -3
  21. package/dist/src/services/kubectl-service.js +26 -5
  22. package/dist/src/utils/config-checker.js +50 -0
  23. package/dist/src/utils/default-api-key.js +111 -0
  24. package/dist/src/utils/token-manager.js +165 -0
  25. package/index.ts +5 -529
  26. package/package.json +6 -1
  27. package/src/client.ts +262 -80
  28. package/src/commands/api-keys.ts +364 -0
  29. package/src/commands/auth.ts +58 -0
  30. package/src/commands/autocomplete.ts +19 -0
  31. package/src/commands/billing.ts +41 -0
  32. package/src/commands/chat.ts +345 -0
  33. package/src/commands/clusters.ts +65 -0
  34. package/src/commands/index.ts +23 -0
  35. package/src/commands/models.ts +63 -0
  36. package/src/commands/users.ts +37 -0
  37. package/src/constants/command-structure.ts +184 -0
  38. package/src/services/api-key-service.ts +36 -5
  39. package/src/services/auth-service.ts +101 -44
  40. package/src/services/chat-service.ts +177 -0
  41. package/src/services/cluster-service.ts +37 -2
  42. package/src/services/collaborator-service.ts +23 -4
  43. package/src/services/flux-service.ts +23 -4
  44. package/src/services/helm-service.ts +22 -3
  45. package/src/services/kubectl-service.ts +28 -5
  46. package/src/types/api.d.ts +58 -192
  47. package/src/utils/config-checker.ts +23 -0
  48. package/src/utils/default-api-key.ts +94 -0
  49. package/src/utils/token-manager.ts +150 -0
@@ -0,0 +1,345 @@
1
+ import { Command } from 'commander'
2
+ import chalk from 'chalk'
3
+ import readline from 'readline'
4
+ import { COMMAND_GROUPS, SUBCOMMANDS } from '../constants/command-structure'
5
+ import { ChatService, ChatMessage } from '../services/chat-service'
6
+ import { ApiKeyService } from '../services/api-key-service'
7
+ import { AuthService } from '../services/auth-service'
8
+ import { handleError } from '../utils/error-handler'
9
+ import { DefaultApiKeyManager } from '../utils/default-api-key'
10
+
11
+ /**
12
+ * Helper function to get user confirmation
13
+ */
14
+ async function confirm(question: string): Promise<boolean> {
15
+ const rl = readline.createInterface({
16
+ input: process.stdin,
17
+ output: process.stdout,
18
+ })
19
+
20
+ return new Promise<boolean>((resolve) => {
21
+ rl.question(question, (answer) => {
22
+ rl.close()
23
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes')
24
+ })
25
+ })
26
+ }
27
+
28
+ /**
29
+ * Register chat commands
30
+ */
31
+ export function registerChatCommands(program: Command): void {
32
+ const chat = program
33
+ .command(COMMAND_GROUPS.CHAT)
34
+ .description('Interact with AI chat models')
35
+
36
+ chat
37
+ .command(SUBCOMMANDS.CHAT.RUN)
38
+ .description('Run a chat session with a specified model')
39
+ .argument('[model]', 'Model to use (default: berget-70b-instruct)')
40
+ .option('-s, --system <message>', 'System message')
41
+ .option('-t, --temperature <temp>', 'Temperature (0-1)', parseFloat)
42
+ .option('-m, --max-tokens <tokens>', 'Maximum tokens to generate', parseInt)
43
+ .option('-k, --api-key <key>', 'API key to use for this chat session')
44
+ .option(
45
+ '--api-key-id <id>',
46
+ 'ID of the API key to use from your saved keys'
47
+ )
48
+ .action(async (options) => {
49
+ try {
50
+ const chatService = ChatService.getInstance()
51
+
52
+ // Check if we have an API key or need to get one
53
+ let apiKey = options.apiKey
54
+ let apiKeyId = options.apiKeyId
55
+
56
+ // If no API key or API key ID provided, check for default API key
57
+ if (!apiKey && !apiKeyId) {
58
+ const defaultApiKeyManager = DefaultApiKeyManager.getInstance()
59
+ const defaultApiKey = defaultApiKeyManager.getDefaultApiKey()
60
+
61
+ if (defaultApiKey) {
62
+ apiKeyId = defaultApiKey.id
63
+ console.log(
64
+ chalk.dim(`Using default API key: ${defaultApiKey.name}`)
65
+ )
66
+ }
67
+ }
68
+
69
+ // If no direct API key, try to get one from API key ID
70
+ if (!apiKey && apiKeyId) {
71
+ try {
72
+ const apiKeyService = ApiKeyService.getInstance()
73
+ const keys = await apiKeyService.list()
74
+ const selectedKey = keys.find(
75
+ (key) => key.id.toString() === options.apiKeyId
76
+ )
77
+
78
+ if (!selectedKey) {
79
+ console.log(
80
+ chalk.yellow(
81
+ `API key with ID ${options.apiKeyId} not found. Using default authentication.`
82
+ )
83
+ )
84
+ } else {
85
+ console.log(chalk.dim(`Using API key: ${selectedKey.name}`))
86
+
87
+ // We need to rotate the key to get the actual key value
88
+ if (
89
+ await confirm(
90
+ chalk.yellow(
91
+ `To use API key "${selectedKey.name}", it needs to be rotated. This will invalidate the current key. Continue? (y/n)`
92
+ )
93
+ )
94
+ ) {
95
+ const rotatedKey = await apiKeyService.rotate(options.apiKeyId)
96
+ apiKey = rotatedKey.key
97
+ console.log(
98
+ chalk.green(
99
+ `API key "${selectedKey.name}" rotated successfully.`
100
+ )
101
+ )
102
+ } else {
103
+ console.log(
104
+ chalk.yellow('Using default authentication instead.')
105
+ )
106
+ }
107
+ }
108
+ } catch (error) {
109
+ console.error(chalk.red('Error fetching API key:'))
110
+ console.error(error)
111
+ console.log(chalk.yellow('Using default authentication instead.'))
112
+ }
113
+ }
114
+
115
+ // Verify we have authentication before starting chat
116
+ if (!apiKey) {
117
+ try {
118
+ AuthService.getInstance()
119
+ } catch (error) {
120
+ console.log(chalk.red('Error: Authentication required for chat'))
121
+ console.log(chalk.yellow('Please either:'))
122
+ console.log(chalk.yellow('1. Log in with `berget auth login`'))
123
+ console.log(chalk.yellow('2. Provide an API key with `--api-key`'))
124
+ console.log(
125
+ chalk.yellow('3. Provide an API key ID with `--api-key-id`')
126
+ )
127
+ console.log(
128
+ chalk.yellow(
129
+ '4. Set a default API key with `berget api-keys set-default <id>`'
130
+ )
131
+ )
132
+ return
133
+ }
134
+ }
135
+
136
+ // Set up readline interface for user input
137
+ const rl = readline.createInterface({
138
+ input: process.stdin,
139
+ output: process.stdout,
140
+ })
141
+
142
+ // Prepare messages array
143
+ const messages: ChatMessage[] = []
144
+
145
+ // Add system message if provided
146
+ if (options.system) {
147
+ messages.push({
148
+ role: 'system',
149
+ content: options.system,
150
+ })
151
+ }
152
+
153
+ console.log(chalk.cyan('Chat with Berget AI (type "exit" to quit)'))
154
+ console.log(chalk.cyan('----------------------------------------'))
155
+
156
+ // Start the conversation loop
157
+ const askQuestion = () => {
158
+ rl.question(chalk.green('You: '), async (input) => {
159
+ // Check if user wants to exit
160
+ if (input.toLowerCase() === 'exit') {
161
+ console.log(chalk.cyan('Goodbye!'))
162
+ rl.close()
163
+ return
164
+ }
165
+
166
+ // Add user message
167
+ messages.push({
168
+ role: 'user',
169
+ content: input,
170
+ })
171
+
172
+ try {
173
+ // Call the API
174
+ const response = await chatService.createCompletion({
175
+ model: options.args?.[0] || 'berget-70b-instruct',
176
+ messages: messages,
177
+ temperature:
178
+ options.temperature !== undefined ? options.temperature : 0.7,
179
+ max_tokens: options.maxTokens || 4096,
180
+ apiKey: apiKey,
181
+ })
182
+
183
+ // Debug output
184
+ if (program.opts().debug) {
185
+ console.log(chalk.yellow('DEBUG: Full response:'))
186
+ console.log(chalk.yellow(JSON.stringify(response, null, 2)))
187
+ }
188
+
189
+ // Check if response has the expected structure
190
+ if (
191
+ !response ||
192
+ !response.choices ||
193
+ !response.choices[0] ||
194
+ !response.choices[0].message
195
+ ) {
196
+ console.error(
197
+ chalk.red('Error: Unexpected response format from API')
198
+ )
199
+ console.error(
200
+ chalk.red('Response:', JSON.stringify(response, null, 2))
201
+ )
202
+ throw new Error('Unexpected response format from API')
203
+ }
204
+
205
+ // Get assistant's response
206
+ const assistantMessage = response.choices[0].message.content
207
+
208
+ // Add to messages array
209
+ messages.push({
210
+ role: 'assistant',
211
+ content: assistantMessage,
212
+ })
213
+
214
+ // Display the response
215
+ console.log(chalk.blue('Assistant: ') + assistantMessage)
216
+ console.log() // Empty line for better readability
217
+
218
+ // Continue the conversation
219
+ askQuestion()
220
+ } catch (error) {
221
+ console.error(chalk.red('Error: Failed to get response'))
222
+ if (error instanceof Error) {
223
+ console.error(chalk.red(error.message))
224
+ }
225
+ // Continue despite error
226
+ askQuestion()
227
+ }
228
+ })
229
+ }
230
+
231
+ // Start the conversation
232
+ askQuestion()
233
+ } catch (error) {
234
+ handleError('Failed to create chat completion', error)
235
+ }
236
+ })
237
+
238
+ chat
239
+ .command(SUBCOMMANDS.CHAT.LIST)
240
+ .description('List available chat models')
241
+ .option('-k, --api-key <key>', 'API key to use for this request')
242
+ .option(
243
+ '--api-key-id <id>',
244
+ 'ID of the API key to use from your saved keys'
245
+ )
246
+ .action(async (options) => {
247
+ try {
248
+ // If API key ID is provided, fetch the actual key
249
+ let apiKey = options.apiKey
250
+ let apiKeyId = options.apiKeyId
251
+
252
+ // If no API key or API key ID provided, check for default API key
253
+ if (!apiKey && !apiKeyId) {
254
+ const defaultApiKeyManager = DefaultApiKeyManager.getInstance()
255
+ const defaultApiKey = defaultApiKeyManager.getDefaultApiKey()
256
+
257
+ if (defaultApiKey) {
258
+ apiKeyId = defaultApiKey.id
259
+ console.log(
260
+ chalk.dim(`Using default API key: ${defaultApiKey.name}`)
261
+ )
262
+ }
263
+ }
264
+
265
+ if (apiKeyId && !apiKey) {
266
+ try {
267
+ const apiKeyService = ApiKeyService.getInstance()
268
+ const keys = await apiKeyService.list()
269
+ const selectedKey = keys.find(
270
+ (key) => key.id.toString() === options.apiKeyId
271
+ )
272
+
273
+ if (!selectedKey) {
274
+ console.log(
275
+ chalk.yellow(
276
+ `API key with ID ${options.apiKeyId} not found. Using default authentication.`
277
+ )
278
+ )
279
+ } else {
280
+ console.log(chalk.dim(`Using API key: ${selectedKey.name}`))
281
+
282
+ // We need to rotate the key to get the actual key value
283
+ if (
284
+ await confirm(
285
+ chalk.yellow(
286
+ `To use API key "${selectedKey.name}", it needs to be rotated. This will invalidate the current key. Continue? (y/n)`
287
+ )
288
+ )
289
+ ) {
290
+ const rotatedKey = await apiKeyService.rotate(options.apiKeyId)
291
+ apiKey = rotatedKey.key
292
+ console.log(
293
+ chalk.green(
294
+ `API key "${selectedKey.name}" rotated successfully.`
295
+ )
296
+ )
297
+ } else {
298
+ console.log(
299
+ chalk.yellow('Using default authentication instead.')
300
+ )
301
+ }
302
+ }
303
+ } catch (error) {
304
+ console.error(chalk.red('Error fetching API key:'))
305
+ console.error(error)
306
+ console.log(chalk.yellow('Using default authentication instead.'))
307
+ }
308
+ }
309
+
310
+ const chatService = ChatService.getInstance()
311
+ const models = await chatService.listModels(apiKey)
312
+
313
+ // Debug output
314
+ if (program.opts().debug) {
315
+ console.log(chalk.yellow('DEBUG: Models response:'))
316
+ console.log(chalk.yellow(JSON.stringify(models, null, 2)))
317
+ }
318
+
319
+ console.log(chalk.bold('Available Chat Models:'))
320
+ console.log(chalk.dim('─'.repeat(70)))
321
+ console.log(
322
+ chalk.dim('MODEL ID'.padEnd(30)) +
323
+ chalk.dim('OWNER'.padEnd(25)) +
324
+ chalk.dim('CAPABILITIES')
325
+ )
326
+ console.log(chalk.dim('─'.repeat(70)))
327
+
328
+ models.data.forEach((model: any) => {
329
+ const capabilities = []
330
+ if (model.capabilities.vision) capabilities.push('vision')
331
+ if (model.capabilities.function_calling)
332
+ capabilities.push('function_calling')
333
+ if (model.capabilities.json_mode) capabilities.push('json_mode')
334
+
335
+ console.log(
336
+ model.id.padEnd(30) +
337
+ model.owned_by.padEnd(25) +
338
+ capabilities.join(', ')
339
+ )
340
+ })
341
+ } catch (error) {
342
+ handleError('Failed to list chat models', error)
343
+ }
344
+ })
345
+ }
@@ -0,0 +1,65 @@
1
+ import { Command } from 'commander'
2
+ import { ClusterService, Cluster } from '../services/cluster-service'
3
+ import { handleError } from '../utils/error-handler'
4
+
5
+ /**
6
+ * Register cluster commands
7
+ */
8
+ export function registerClusterCommands(program: Command): void {
9
+ const cluster = program
10
+ .command(ClusterService.COMMAND_GROUP)
11
+ .description('Manage Berget clusters')
12
+
13
+ cluster
14
+ .command(ClusterService.COMMANDS.LIST)
15
+ .description('List all Berget clusters')
16
+ .action(async () => {
17
+ try {
18
+ const clusterService = ClusterService.getInstance()
19
+ const clusters = await clusterService.list()
20
+
21
+ console.log('NAME STATUS NODES CREATED')
22
+ clusters.forEach((cluster: Cluster) => {
23
+ console.log(
24
+ `${cluster.name.padEnd(22)} ${cluster.status.padEnd(9)} ${String(
25
+ cluster.nodes
26
+ ).padEnd(8)} ${cluster.created}`
27
+ )
28
+ })
29
+ } catch (error) {
30
+ handleError('Failed to list clusters', error)
31
+ }
32
+ })
33
+
34
+ cluster
35
+ .command(ClusterService.COMMANDS.GET_USAGE)
36
+ .description('Get usage metrics for a specific cluster')
37
+ .argument('<clusterId>', 'Cluster ID')
38
+ .action(async (clusterId) => {
39
+ try {
40
+ const clusterService = ClusterService.getInstance()
41
+ const usage = await clusterService.getUsage(clusterId)
42
+
43
+ console.log('Cluster Usage:')
44
+ console.log(JSON.stringify(usage, null, 2))
45
+ } catch (error) {
46
+ handleError('Failed to get cluster usage', error)
47
+ }
48
+ })
49
+
50
+ cluster
51
+ .command(ClusterService.COMMANDS.DESCRIBE)
52
+ .description('Get detailed information about a cluster')
53
+ .argument('<clusterId>', 'Cluster ID')
54
+ .action(async (clusterId) => {
55
+ try {
56
+ const clusterService = ClusterService.getInstance()
57
+ const clusterInfo = await clusterService.describe(clusterId)
58
+
59
+ console.log('Cluster Details:')
60
+ console.log(JSON.stringify(clusterInfo, null, 2))
61
+ } catch (error) {
62
+ handleError('Failed to describe cluster', error)
63
+ }
64
+ })
65
+ }
@@ -0,0 +1,23 @@
1
+ import { Command } from 'commander'
2
+ import { registerAuthCommands } from './auth'
3
+ import { registerApiKeyCommands } from './api-keys'
4
+ import { registerClusterCommands } from './clusters'
5
+ import { registerBillingCommands } from './billing'
6
+ import { registerModelCommands } from './models'
7
+ import { registerUserCommands } from './users'
8
+ import { registerChatCommands } from './chat'
9
+ import { registerAutocompleteCommands } from './autocomplete'
10
+
11
+ /**
12
+ * Register all command groups with the program
13
+ */
14
+ export function registerCommands(program: Command): void {
15
+ registerAuthCommands(program)
16
+ registerApiKeyCommands(program)
17
+ registerClusterCommands(program)
18
+ registerBillingCommands(program)
19
+ registerModelCommands(program)
20
+ registerUserCommands(program)
21
+ registerChatCommands(program)
22
+ registerAutocompleteCommands(program)
23
+ }
@@ -0,0 +1,63 @@
1
+ import { Command } from 'commander'
2
+ import { COMMAND_GROUPS, SUBCOMMANDS } from '../constants/command-structure'
3
+ import { createAuthenticatedClient } from '../client'
4
+ import { handleError } from '../utils/error-handler'
5
+
6
+ /**
7
+ * Register models commands
8
+ */
9
+ export function registerModelCommands(program: Command): void {
10
+ const models = program
11
+ .command(COMMAND_GROUPS.MODELS)
12
+ .description('Manage AI models')
13
+
14
+ models
15
+ .command(SUBCOMMANDS.MODELS.LIST)
16
+ .description('List available AI models')
17
+ .option('--id <modelId>', 'Get details for a specific model')
18
+ .action(async (options) => {
19
+ try {
20
+ const client = createAuthenticatedClient()
21
+ let response
22
+
23
+ if (options.id) {
24
+ const { data, error } = await client.GET('/v1/models/{modelId}', {
25
+ params: { path: { modelId: options.id } },
26
+ })
27
+ if (error) throw new Error(JSON.stringify(error))
28
+ response = data
29
+
30
+ console.log('Model Details:')
31
+ console.log(JSON.stringify(response, null, 2))
32
+ } else {
33
+ const { data, error } = await client.GET('/v1/models')
34
+ if (error) throw new Error(JSON.stringify(error))
35
+ response = data
36
+
37
+ console.log('Available Models:')
38
+ console.log(
39
+ 'ID OWNED BY CAPABILITIES'
40
+ )
41
+ // Ensure response has the expected structure
42
+ const modelData = response as { data?: any[] };
43
+ if (modelData.data) {
44
+ modelData.data.forEach((model: any) => {
45
+ const capabilities = []
46
+ if (model.capabilities.vision) capabilities.push('vision')
47
+ if (model.capabilities.function_calling)
48
+ capabilities.push('function_calling')
49
+ if (model.capabilities.json_mode) capabilities.push('json_mode')
50
+
51
+ console.log(
52
+ `${model.id.padEnd(24)} ${model.owned_by.padEnd(
53
+ 25
54
+ )} ${capabilities.join(', ')}`
55
+ )
56
+ })
57
+ }
58
+ }
59
+ } catch (error) {
60
+ handleError('Failed to get models', error)
61
+ }
62
+ })
63
+ }
@@ -0,0 +1,37 @@
1
+ import { Command } from 'commander'
2
+ import { COMMAND_GROUPS, SUBCOMMANDS } from '../constants/command-structure'
3
+ import { createAuthenticatedClient } from '../client'
4
+ import { handleError } from '../utils/error-handler'
5
+ import chalk from 'chalk'
6
+
7
+ /**
8
+ * Register user commands
9
+ */
10
+ export function registerUserCommands(program: Command): void {
11
+ const users = program
12
+ .command(COMMAND_GROUPS.USERS)
13
+ .description('Manage users')
14
+
15
+ users
16
+ .command(SUBCOMMANDS.USERS.LIST)
17
+ .description('List team members')
18
+ .action(async () => {
19
+ try {
20
+ const client = createAuthenticatedClient()
21
+ const { data, error } = await client.GET('/v1/users')
22
+ if (error) throw new Error(JSON.stringify(error))
23
+
24
+ console.log('Team Members:')
25
+ console.log(
26
+ 'NAME EMAIL ROLE'
27
+ )
28
+ data.forEach((user: any) => {
29
+ console.log(
30
+ `${user.name.padEnd(24)} ${user.email.padEnd(30)} ${user.role}`
31
+ )
32
+ })
33
+ } catch (error) {
34
+ handleError('Failed to list team members', error)
35
+ }
36
+ })
37
+ }