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
package/index.ts CHANGED
@@ -1,12 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { program } from 'commander'
4
- import * as fs from 'fs'
5
- import * as path from 'path'
6
- import { createAuthenticatedClient } from './src/client'
7
- import { handleError } from './src/utils/error-handler'
8
- import chalk from 'chalk'
9
- import { COMMAND_GROUPS, SUBCOMMANDS } from './src/constants/command-structure'
4
+ import { registerCommands } from './src/commands'
5
+ import { checkBergetConfig } from './src/utils/config-checker'
10
6
 
11
7
  // Set version and description
12
8
  program
@@ -23,567 +19,10 @@ program
23
19
  )
24
20
  .version(process.env.npm_package_version || '0.0.1', '-v, --version')
25
21
  .option('--local', 'Use local API endpoint (hidden)', false)
22
+ .option('--debug', 'Enable debug output', false)
26
23
 
27
- // Import services
28
- import { AuthService } from './src/services/auth-service'
29
- import { ApiKeyService, ApiKey } from './src/services/api-key-service'
30
- import { ClusterService, Cluster } from './src/services/cluster-service'
31
-
32
- // Auth commands
33
- const auth = program
34
- .command(AuthService.COMMAND_GROUP)
35
- .description('Manage authentication and authorization')
36
-
37
- auth
38
- .command(AuthService.COMMANDS.LOGIN)
39
- .description('Log in to Berget')
40
- .action(async () => {
41
- const authService = AuthService.getInstance()
42
- await authService.login()
43
- })
44
-
45
- auth
46
- .command(AuthService.COMMANDS.LOGOUT)
47
- .description('Log out from Berget')
48
- .action(() => {
49
- const { clearAuthToken } = require('./src/client')
50
- clearAuthToken()
51
- console.log(chalk.green('You have been logged out from Berget'))
52
- })
53
-
54
- auth
55
- .command(AuthService.COMMANDS.WHOAMI)
56
- .description('Show information about the logged in user')
57
- .action(async () => {
58
- try {
59
- const authService = AuthService.getInstance()
60
- const profile = await authService.whoami()
61
-
62
- if (profile) {
63
- console.log(
64
- chalk.bold(`Logged in as: ${profile.name || profile.login}`)
65
- )
66
- console.log(`Email: ${chalk.cyan(profile.email || 'Not available')}`)
67
- console.log(`Role: ${chalk.cyan(profile.role || 'Not available')}`)
68
-
69
- if (profile.company) {
70
- console.log(`Company: ${chalk.cyan(profile.company.name)}`)
71
- }
72
- } else {
73
- console.log(
74
- chalk.yellow('You are not logged in. Use `berget login` to log in.')
75
- )
76
- }
77
- } catch (error) {
78
- handleError('You are not logged in or an error occurred', error)
79
- }
80
- })
81
-
82
- // API Key commands
83
- const apiKey = program
84
- .command(ApiKeyService.COMMAND_GROUP)
85
- .description('Manage API keys')
86
-
87
- apiKey
88
- .command(ApiKeyService.COMMANDS.LIST)
89
- .description('List all API keys')
90
- .action(async () => {
91
- try {
92
- const apiKeyService = ApiKeyService.getInstance()
93
- const keys = await apiKeyService.list()
94
-
95
- if (keys.length === 0) {
96
- console.log(
97
- chalk.yellow(
98
- 'No API keys found. Create one with `berget api-key create --name <name>`'
99
- )
100
- )
101
- return
102
- }
103
-
104
- console.log(chalk.bold('Your API keys:'))
105
- console.log('')
106
-
107
- // Create a table-like format with headers
108
- console.log(
109
- chalk.dim('ID'.padEnd(10)) +
110
- chalk.dim('NAME'.padEnd(25)) +
111
- chalk.dim('PREFIX'.padEnd(12)) +
112
- chalk.dim('STATUS'.padEnd(12)) +
113
- chalk.dim('CREATED'.padEnd(12)) +
114
- chalk.dim('LAST USED')
115
- )
116
-
117
- console.log(chalk.dim('─'.repeat(85)))
118
-
119
- keys.forEach((key: ApiKey) => {
120
- const lastUsed = key.lastUsed ? key.lastUsed.substring(0, 10) : 'Never'
121
- const status = key.active
122
- ? chalk.green('● Active')
123
- : chalk.red('● Inactive')
124
-
125
- console.log(
126
- String(key.id).padEnd(10) +
127
- key.name.padEnd(25) +
128
- key.prefix.padEnd(12) +
129
- status.padEnd(12) +
130
- key.created.substring(0, 10).padEnd(12) +
131
- lastUsed
132
- )
133
- })
134
-
135
- console.log('')
136
- console.log(
137
- chalk.dim(
138
- 'Use `berget api-key create --name <name>` to create a new API key'
139
- )
140
- )
141
- console.log(
142
- chalk.dim('Use `berget api-key delete <id>` to delete an API key')
143
- )
144
- console.log(
145
- chalk.dim('Use `berget api-key rotate <id>` to rotate an API key')
146
- )
147
- } catch (error) {
148
- handleError('Failed to list API keys', error)
149
- }
150
- })
151
-
152
- apiKey
153
- .command(ApiKeyService.COMMANDS.CREATE)
154
- .description('Create a new API key')
155
- .option('--name <name>', 'Name of the API key')
156
- .option('--description <description>', 'Description of the API key')
157
- .action(async (options) => {
158
- try {
159
- if (!options.name) {
160
- console.error(chalk.red('Error: --name is required'))
161
- console.log('')
162
- console.log(
163
- 'Usage: berget api-key create --name <name> [--description <description>]'
164
- )
165
- return
166
- }
167
-
168
- console.log(chalk.blue('Creating API key...'))
169
-
170
- const apiKeyService = ApiKeyService.getInstance()
171
- const result = await apiKeyService.create({
172
- name: options.name,
173
- description: options.description,
174
- })
175
-
176
- console.log('')
177
- console.log(chalk.green('✓ API key created'))
178
- console.log('')
179
- console.log(chalk.bold('API key details:'))
180
- console.log('')
181
- console.log(`${chalk.dim('ID:')} ${result.id}`)
182
- console.log(`${chalk.dim('Name:')} ${result.name}`)
183
- if (result.description) {
184
- console.log(`${chalk.dim('Description:')} ${result.description}`)
185
- }
186
- console.log(
187
- `${chalk.dim('Created:')} ${new Date(
188
- result.created
189
- ).toLocaleString()}`
190
- )
191
- console.log('')
192
- console.log(chalk.bold('API key:'))
193
- console.log(chalk.cyan(result.key))
194
- console.log('')
195
- console.log(
196
- chalk.yellow('⚠️ IMPORTANT: Save this API key in a secure location.')
197
- )
198
- console.log(chalk.yellow(' It will not be displayed again.'))
199
-
200
- console.log('')
201
- console.log(
202
- chalk.dim(
203
- 'Use this key in your applications to authenticate with the Berget API.'
204
- )
205
- )
206
- } catch (error) {
207
- handleError('Failed to create API key', error)
208
- }
209
- })
210
-
211
- apiKey
212
- .command(ApiKeyService.COMMANDS.DELETE)
213
- .description('Delete an API key')
214
- .argument('<id>', 'ID of the API key to delete')
215
- .action(async (id) => {
216
- try {
217
- console.log(chalk.blue(`Deleting API key ${id}...`))
218
-
219
- const apiKeyService = ApiKeyService.getInstance()
220
- await apiKeyService.delete(id)
221
-
222
- console.log(chalk.green(`✓ API key ${id} has been deleted`))
223
- console.log('')
224
- console.log(
225
- chalk.dim(
226
- 'Applications using this key will no longer be able to authenticate.'
227
- )
228
- )
229
- console.log(
230
- chalk.dim('Use `berget api-key list` to see your remaining API keys.')
231
- )
232
- } catch (error) {
233
- handleError('Failed to delete API key', error)
234
- }
235
- })
236
-
237
- apiKey
238
- .command(ApiKeyService.COMMANDS.ROTATE)
239
- .description(
240
- 'Rotate an API key (creates a new one and invalidates the old one)'
241
- )
242
- .argument('<id>', 'ID of the API key to rotate')
243
- .action(async (id) => {
244
- try {
245
- console.log(chalk.blue(`Rotating API key ${id}...`))
246
- console.log(
247
- chalk.dim('This will invalidate the old key and generate a new one.')
248
- )
249
-
250
- const apiKeyService = ApiKeyService.getInstance()
251
- const result = await apiKeyService.rotate(id)
252
-
253
- console.log('')
254
- console.log(chalk.green('✓ API key rotated'))
255
- console.log('')
256
- console.log(chalk.bold('New API key details:'))
257
- console.log('')
258
- console.log(`${chalk.dim('ID:')} ${result.id}`)
259
- console.log(`${chalk.dim('Name:')} ${result.name}`)
260
- if (result.description) {
261
- console.log(`${chalk.dim('Description:')} ${result.description}`)
262
- }
263
- console.log(
264
- `${chalk.dim('Created:')} ${new Date(
265
- result.created
266
- ).toLocaleString()}`
267
- )
268
- console.log('')
269
- console.log(chalk.bold('New API key:'))
270
- console.log(chalk.cyan(result.key))
271
- console.log('')
272
- console.log(
273
- chalk.yellow(
274
- '⚠️ IMPORTANT: Update your applications with this new API key.'
275
- )
276
- )
277
- console.log(
278
- chalk.yellow(
279
- ' The old key has been invalidated and will no longer work.'
280
- )
281
- )
282
- console.log(chalk.yellow(' This new key will not be displayed again.'))
283
- } catch (error) {
284
- handleError('Failed to rotate API key', error)
285
- }
286
- })
287
-
288
- apiKey
289
- .command(ApiKeyService.COMMANDS.DESCRIBE)
290
- .description('Show usage statistics for an API key')
291
- .argument('<id>', 'ID of the API key')
292
- .option('--start <date>', 'Start date (YYYY-MM-DD)')
293
- .option('--end <date>', 'End date (YYYY-MM-DD)')
294
- .action(async (id, options) => {
295
- try {
296
- console.log(chalk.blue(`Fetching usage statistics for API key ${id}...`))
297
-
298
- const apiKeyService = ApiKeyService.getInstance()
299
- const usage = await apiKeyService.describe(id)
300
-
301
- console.log('')
302
- console.log(
303
- chalk.bold(`Usage statistics for API key: ${usage.name} (${id})`)
304
- )
305
- console.log('')
306
-
307
- // Period information
308
- console.log(
309
- chalk.dim(`Period: ${usage.period.start} to ${usage.period.end}`)
310
- )
311
- console.log('')
312
-
313
- // Request statistics
314
- console.log(chalk.bold('Request statistics:'))
315
- console.log(
316
- `Total requests: ${chalk.cyan(usage.requests.total.toLocaleString())}`
317
- )
318
-
319
- // Daily breakdown if available
320
- if (usage.requests.daily && usage.requests.daily.length > 0) {
321
- console.log('')
322
- console.log(chalk.bold('Daily breakdown:'))
323
- console.log(chalk.dim('─'.repeat(30)))
324
- console.log(chalk.dim('DATE'.padEnd(12) + 'REQUESTS'))
325
-
326
- usage.requests.daily.forEach((day: { date: string; count: number }) => {
327
- console.log(`${day.date.padEnd(12)}${day.count.toLocaleString()}`)
328
- })
329
- }
330
-
331
- // Model usage if available
332
- if (usage.models && usage.models.length > 0) {
333
- console.log('')
334
- console.log(chalk.bold('Model usage:'))
335
- console.log(chalk.dim('─'.repeat(70)))
336
- console.log(
337
- chalk.dim('MODEL'.padEnd(20)) +
338
- chalk.dim('REQUESTS'.padEnd(10)) +
339
- chalk.dim('INPUT'.padEnd(12)) +
340
- chalk.dim('OUTPUT'.padEnd(12)) +
341
- chalk.dim('TOTAL TOKENS')
342
- )
343
-
344
- usage.models.forEach(
345
- (model: {
346
- name: string
347
- requests: number
348
- tokens: {
349
- input: number
350
- output: number
351
- total: number
352
- }
353
- }) => {
354
- console.log(
355
- model.name.padEnd(20) +
356
- model.requests.toString().padEnd(10) +
357
- model.tokens.input.toLocaleString().padEnd(12) +
358
- model.tokens.output.toLocaleString().padEnd(12) +
359
- model.tokens.total.toLocaleString()
360
- )
361
- }
362
- )
363
- }
364
-
365
- console.log('')
366
- console.log(
367
- chalk.dim(
368
- 'Use these statistics to understand your API usage and optimize your costs.'
369
- )
370
- )
371
- } catch (error) {
372
- handleError('Failed to get API key usage', error)
373
- }
374
- })
375
-
376
- // Cluster commands
377
- const cluster = program
378
- .command(ClusterService.COMMAND_GROUP)
379
- .description('Manage Berget clusters')
380
-
381
- // Removed cluster create command as it's not available in the API
382
-
383
- cluster
384
- .command(ClusterService.COMMANDS.LIST)
385
- .description('List all Berget clusters')
386
- .action(async () => {
387
- try {
388
- const clusterService = ClusterService.getInstance()
389
- const clusters = await clusterService.list()
390
-
391
- console.log('NAME STATUS NODES CREATED')
392
- clusters.forEach((cluster: Cluster) => {
393
- console.log(
394
- `${cluster.name.padEnd(22)} ${cluster.status.padEnd(9)} ${String(
395
- cluster.nodes
396
- ).padEnd(8)} ${cluster.created}`
397
- )
398
- })
399
- } catch (error) {
400
- handleError('Failed to list clusters', error)
401
- }
402
- })
403
-
404
- cluster
405
- .command(ClusterService.COMMANDS.GET_USAGE)
406
- .description('Get usage metrics for a specific cluster')
407
- .argument('<clusterId>', 'Cluster ID')
408
- .action(async (clusterId) => {
409
- try {
410
- const clusterService = ClusterService.getInstance()
411
- const usage = await clusterService.getUsage(clusterId)
412
-
413
- console.log('Cluster Usage:')
414
- console.log(JSON.stringify(usage, null, 2))
415
- } catch (error) {
416
- handleError('Failed to get cluster usage', error)
417
- }
418
- })
419
-
420
- cluster
421
- .command(ClusterService.COMMANDS.DESCRIBE)
422
- .description('Get detailed information about a cluster')
423
- .argument('<clusterId>', 'Cluster ID')
424
- .action(async (clusterId) => {
425
- try {
426
- const clusterService = ClusterService.getInstance()
427
- const clusterInfo = await clusterService.describe(clusterId)
428
-
429
- console.log('Cluster Details:')
430
- console.log(JSON.stringify(clusterInfo, null, 2))
431
- } catch (error) {
432
- handleError('Failed to describe cluster', error)
433
- }
434
- })
435
-
436
- // Autocomplete command
437
- program
438
- .command('autocomplete')
439
- .command('install')
440
- .description('Install shell autocompletion')
441
- .action(() => {
442
- console.log(chalk.green('✓ Berget autocomplete installed in your shell'))
443
- console.log(chalk.green('✓ Shell completion for kubectl also installed'))
444
- console.log('')
445
- console.log('Restart your shell or run:')
446
- console.log(' source ~/.bashrc')
447
- })
448
-
449
- // Removed flux commands as they're not available in the API
450
-
451
- // Removed collaborator commands as they're not available in the API
452
-
453
- // Removed helm commands as they're not available in the API
454
-
455
- // Removed kubernetes-like commands as they're not available in the API
456
-
457
- // Add token usage command
458
- const billing = program
459
- .command(COMMAND_GROUPS.BILLING)
460
- .description('Manage billing and usage')
461
-
462
- billing
463
- .command(SUBCOMMANDS.BILLING.GET_USAGE)
464
- .description('Get token usage statistics')
465
- .option('--model <modelId>', 'Get usage for a specific model')
466
- .action(async (options) => {
467
- try {
468
- const client = createAuthenticatedClient()
469
- let response
470
-
471
- if (options.model) {
472
- const { data, error } = await client.GET('/v1/usage/tokens/{modelId}', {
473
- params: { path: { modelId: options.model } },
474
- })
475
- if (error) throw new Error(JSON.stringify(error))
476
- response = data
477
- } else {
478
- const { data, error } = await client.GET('/v1/usage/tokens')
479
- if (error) throw new Error(JSON.stringify(error))
480
- response = data
481
- }
482
-
483
- console.log('Token Usage:')
484
- console.log(JSON.stringify(response, null, 2))
485
- } catch (error) {
486
- handleError('Failed to get token usage', error)
487
- }
488
- })
489
-
490
- // Add models command
491
- const models = program
492
- .command(COMMAND_GROUPS.MODELS)
493
- .description('Manage AI models')
494
-
495
- models
496
- .command(SUBCOMMANDS.MODELS.LIST)
497
- .description('List available AI models')
498
- .option('--id <modelId>', 'Get details for a specific model')
499
- .action(async (options) => {
500
- try {
501
- const client = createAuthenticatedClient()
502
- let response
503
-
504
- if (options.id) {
505
- const { data, error } = await client.GET('/v1/models/{modelId}', {
506
- params: { path: { modelId: options.id } },
507
- })
508
- if (error) throw new Error(JSON.stringify(error))
509
- response = data
510
-
511
- console.log('Model Details:')
512
- console.log(JSON.stringify(response, null, 2))
513
- } else {
514
- const { data, error } = await client.GET('/v1/models')
515
- if (error) throw new Error(JSON.stringify(error))
516
- response = data
517
-
518
- console.log('Available Models:')
519
- console.log(
520
- 'ID OWNED BY CAPABILITIES'
521
- )
522
- response.data.forEach((model: any) => {
523
- const capabilities = []
524
- if (model.capabilities.vision) capabilities.push('vision')
525
- if (model.capabilities.function_calling)
526
- capabilities.push('function_calling')
527
- if (model.capabilities.json_mode) capabilities.push('json_mode')
528
-
529
- console.log(
530
- `${model.id.padEnd(24)} ${model.owned_by.padEnd(
531
- 25
532
- )} ${capabilities.join(', ')}`
533
- )
534
- })
535
- }
536
- } catch (error) {
537
- handleError('Failed to get models', error)
538
- }
539
- })
540
-
541
- // Add team command
542
- const users = program
543
- .command(COMMAND_GROUPS.USERS)
544
- .description('Manage users')
545
-
546
- users
547
- .command(SUBCOMMANDS.USERS.LIST)
548
- .description('List team members')
549
- .action(async () => {
550
- try {
551
- const client = createAuthenticatedClient()
552
- const { data, error } = await client.GET('/v1/users')
553
- if (error) throw new Error(JSON.stringify(error))
554
-
555
- console.log('Team Members:')
556
- console.log(
557
- 'NAME EMAIL ROLE'
558
- )
559
- data.forEach((user: any) => {
560
- console.log(
561
- `${user.name.padEnd(24)} ${user.email.padEnd(30)} ${user.role}`
562
- )
563
- })
564
- } catch (error) {
565
- handleError('Failed to list team members', error)
566
- }
567
- })
568
-
569
- // Auto-detect .bergetconfig and switch clusters
570
- const checkBergetConfig = () => {
571
- const configPath = path.join(process.cwd(), '.bergetconfig')
572
- if (fs.existsSync(configPath)) {
573
- try {
574
- const config = fs.readFileSync(configPath, 'utf8')
575
- const match = config.match(/cluster:\s*(.+)/)
576
- if (match && match[1]) {
577
- const clusterName = match[1].trim()
578
- console.log(`🔄 Berget: Switched to cluster "${clusterName}"`)
579
- console.log('✓ kubectl config updated')
580
- console.log('')
581
- }
582
- } catch (error) {
583
- // Silently ignore errors reading config
584
- }
585
- }
586
- }
24
+ // Register all commands
25
+ registerCommands(program)
587
26
 
588
27
  // Check for .bergetconfig if not running a command
589
28
  if (process.argv.length <= 2) {
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "berget",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "berget": "dist/index.js"
7
7
  },
8
8
  "private": false,
9
9
  "scripts": {
10
+ "start": "node --import tsx ./index.ts --local",
11
+ "login": "node --import tsx ./index.ts --local auth login",
12
+ "logout": "node --import tsx ./index.ts --local auth logout",
13
+ "whoami": "node --import tsx ./index.ts --local auth whoami",
10
14
  "build": "tsc",
11
15
  "prepublishOnly": "npm run build",
12
16
  "generate-types": "openapi-typescript https://api.berget.ai/openapi.json -o src/types/api.d.ts"
@@ -16,6 +20,7 @@
16
20
  "description": "This is a cli command for interacting with the AI infrastructure provider Berget",
17
21
  "devDependencies": {
18
22
  "@types/node": "^20.11.20",
23
+ "tsx": "^4.19.3",
19
24
  "typescript": "^5.3.3"
20
25
  },
21
26
  "dependencies": {