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
@@ -1,4 +1,5 @@
1
1
  import { createAuthenticatedClient } from '../client'
2
+ import { COMMAND_GROUPS, SUBCOMMANDS } from '../constants/command-structure'
2
3
 
3
4
  export interface Cluster {
4
5
  id: string
@@ -8,9 +9,19 @@ export interface Cluster {
8
9
  created: string
9
10
  }
10
11
 
12
+ /**
13
+ * Service for managing Kubernetes clusters
14
+ * Command group: clusters
15
+ */
11
16
  export class ClusterService {
12
17
  private static instance: ClusterService
13
18
  private client = createAuthenticatedClient()
19
+
20
+ // Command group name for this service
21
+ public static readonly COMMAND_GROUP = COMMAND_GROUPS.CLUSTERS
22
+
23
+ // Subcommands for this service
24
+ public static readonly COMMANDS = SUBCOMMANDS.CLUSTERS
14
25
 
15
26
  private constructor() {}
16
27
 
@@ -21,7 +32,11 @@ export class ClusterService {
21
32
  return ClusterService.instance
22
33
  }
23
34
 
24
- public async getClusterUsage(clusterId: string): Promise<any> {
35
+ /**
36
+ * Get resource usage for a cluster
37
+ * Command: berget clusters get-usage
38
+ */
39
+ public async getUsage(clusterId: string): Promise<any> {
25
40
  try {
26
41
  const { data, error } = await this.client.GET(
27
42
  '/v1/clusters/{clusterId}/usage',
@@ -37,7 +52,11 @@ export class ClusterService {
37
52
  }
38
53
  }
39
54
 
40
- public async listClusters(): Promise<Cluster[]> {
55
+ /**
56
+ * List all clusters
57
+ * Command: berget clusters list
58
+ */
59
+ public async list(): Promise<Cluster[]> {
41
60
  try {
42
61
  const { data, error } = await this.client.GET('/v1/clusters')
43
62
  if (error) throw new Error(JSON.stringify(error))
@@ -47,4 +66,20 @@ export class ClusterService {
47
66
  throw error
48
67
  }
49
68
  }
69
+
70
+ /**
71
+ * Get detailed information about a cluster
72
+ * Command: berget clusters describe
73
+ */
74
+ public async describe(clusterId: string): Promise<Cluster | null> {
75
+ try {
76
+ // This is a placeholder since the API doesn't have a specific endpoint
77
+ // In a real implementation, this would call a specific endpoint
78
+ const clusters = await this.list()
79
+ return clusters.find(cluster => cluster.id === clusterId) || null
80
+ } catch (error) {
81
+ console.error('Failed to describe cluster:', error)
82
+ throw error
83
+ }
84
+ }
50
85
  }
@@ -1,4 +1,5 @@
1
1
  import { createAuthenticatedClient } from '../client'
2
+ import { COMMAND_GROUPS, SUBCOMMANDS } from '../constants/command-structure'
2
3
 
3
4
  export interface Collaborator {
4
5
  username: string
@@ -6,9 +7,19 @@ export interface Collaborator {
6
7
  status: string
7
8
  }
8
9
 
10
+ /**
11
+ * Service for managing collaborators
12
+ * Command group: users
13
+ */
9
14
  export class CollaboratorService {
10
15
  private static instance: CollaboratorService
11
16
  private client = createAuthenticatedClient()
17
+
18
+ // Command group name for this service
19
+ public static readonly COMMAND_GROUP = COMMAND_GROUPS.USERS
20
+
21
+ // Subcommands for this service
22
+ public static readonly COMMANDS = SUBCOMMANDS.USERS
12
23
 
13
24
  private constructor() {}
14
25
 
@@ -19,16 +30,24 @@ export class CollaboratorService {
19
30
  return CollaboratorService.instance
20
31
  }
21
32
 
22
- // This endpoint is not available in the API
23
- public async addCollaborator(
33
+ /**
34
+ * Invite a new collaborator
35
+ * Command: berget users invite
36
+ * This endpoint is not available in the API
37
+ */
38
+ public async invite(
24
39
  clusterId: string,
25
40
  githubUsername: string
26
41
  ): Promise<Collaborator[]> {
27
42
  throw new Error('This functionality is not available in the API')
28
43
  }
29
44
 
30
- // This endpoint is not available in the API
31
- public async listCollaborators(clusterId: string): Promise<Collaborator[]> {
45
+ /**
46
+ * List all collaborators
47
+ * Command: berget users list
48
+ * This endpoint is not available in the API
49
+ */
50
+ public async list(clusterId: string): Promise<Collaborator[]> {
32
51
  throw new Error('This functionality is not available in the API')
33
52
  }
34
53
  }
@@ -1,4 +1,5 @@
1
1
  import { createAuthenticatedClient } from '../client'
2
+ import { COMMAND_GROUPS, SUBCOMMANDS } from '../constants/command-structure'
2
3
 
3
4
  export interface FluxInstallOptions {
4
5
  cluster: string
@@ -12,9 +13,19 @@ export interface FluxBootstrapOptions {
12
13
  personal?: boolean
13
14
  }
14
15
 
16
+ /**
17
+ * Service for managing Flux CD
18
+ * Command group: flux
19
+ */
15
20
  export class FluxService {
16
21
  private static instance: FluxService
17
22
  private client = createAuthenticatedClient()
23
+
24
+ // Command group name for this service
25
+ public static readonly COMMAND_GROUP = COMMAND_GROUPS.FLUX
26
+
27
+ // Subcommands for this service
28
+ public static readonly COMMANDS = SUBCOMMANDS.FLUX
18
29
 
19
30
  private constructor() {}
20
31
 
@@ -25,13 +36,21 @@ export class FluxService {
25
36
  return FluxService.instance
26
37
  }
27
38
 
28
- // This endpoint is not available in the API
29
- public async installFlux(options: FluxInstallOptions): Promise<boolean> {
39
+ /**
40
+ * Install Flux CD
41
+ * Command: berget flux install
42
+ * This endpoint is not available in the API
43
+ */
44
+ public async install(options: FluxInstallOptions): Promise<boolean> {
30
45
  throw new Error('This functionality is not available in the API')
31
46
  }
32
47
 
33
- // This endpoint is not available in the API
34
- public async bootstrapFlux(options: FluxBootstrapOptions): Promise<boolean> {
48
+ /**
49
+ * Bootstrap Flux CD
50
+ * Command: berget flux bootstrap
51
+ * This endpoint is not available in the API
52
+ */
53
+ public async bootstrap(options: FluxBootstrapOptions): Promise<boolean> {
35
54
  throw new Error('This functionality is not available in the API')
36
55
  }
37
56
  }
@@ -1,4 +1,5 @@
1
1
  import { createAuthenticatedClient } from '../client'
2
+ import { COMMAND_GROUPS, SUBCOMMANDS } from '../constants/command-structure'
2
3
 
3
4
  export interface HelmRepoAddOptions {
4
5
  name: string
@@ -12,9 +13,19 @@ export interface HelmInstallOptions {
12
13
  values?: Record<string, string>
13
14
  }
14
15
 
16
+ /**
17
+ * Service for managing Helm charts
18
+ * Command group: helm
19
+ */
15
20
  export class HelmService {
16
21
  private static instance: HelmService
17
22
  private client = createAuthenticatedClient()
23
+
24
+ // Command group name for this service
25
+ public static readonly COMMAND_GROUP = COMMAND_GROUPS.HELM
26
+
27
+ // Subcommands for this service
28
+ public static readonly COMMANDS = SUBCOMMANDS.HELM
18
29
 
19
30
  private constructor() {}
20
31
 
@@ -25,13 +36,21 @@ export class HelmService {
25
36
  return HelmService.instance
26
37
  }
27
38
 
28
- // This endpoint is not available in the API
39
+ /**
40
+ * Add a Helm repository
41
+ * Command: berget helm add-repo
42
+ * This endpoint is not available in the API
43
+ */
29
44
  public async addRepo(options: HelmRepoAddOptions): Promise<boolean> {
30
45
  throw new Error('This functionality is not available in the API')
31
46
  }
32
47
 
33
- // This endpoint is not available in the API
34
- public async installChart(options: HelmInstallOptions): Promise<any> {
48
+ /**
49
+ * Install a Helm chart
50
+ * Command: berget helm install
51
+ * This endpoint is not available in the API
52
+ */
53
+ public async install(options: HelmInstallOptions): Promise<any> {
35
54
  throw new Error('This functionality is not available in the API')
36
55
  }
37
56
  }
@@ -1,8 +1,19 @@
1
1
  import { createAuthenticatedClient } from '../client'
2
+ import { COMMAND_GROUPS, SUBCOMMANDS } from '../constants/command-structure'
2
3
 
4
+ /**
5
+ * Service for managing Kubernetes resources
6
+ * Command group: kubectl
7
+ */
3
8
  export class KubectlService {
4
9
  private static instance: KubectlService
5
10
  private client = createAuthenticatedClient()
11
+
12
+ // Command group name for this service
13
+ public static readonly COMMAND_GROUP = COMMAND_GROUPS.KUBECTL
14
+
15
+ // Subcommands for this service
16
+ public static readonly COMMANDS = SUBCOMMANDS.KUBECTL
6
17
 
7
18
  private constructor() {}
8
19
 
@@ -13,18 +24,30 @@ export class KubectlService {
13
24
  return KubectlService.instance
14
25
  }
15
26
 
16
- // This endpoint is not available in the API
27
+ /**
28
+ * Create a Kubernetes namespace
29
+ * Command: berget kubectl create-namespace
30
+ * This endpoint is not available in the API
31
+ */
17
32
  public async createNamespace(name: string): Promise<boolean> {
18
33
  throw new Error('This functionality is not available in the API')
19
34
  }
20
35
 
21
- // This endpoint is not available in the API
22
- public async applyConfiguration(filename: string): Promise<boolean> {
36
+ /**
37
+ * Apply a Kubernetes configuration
38
+ * Command: berget kubectl apply
39
+ * This endpoint is not available in the API
40
+ */
41
+ public async apply(filename: string): Promise<boolean> {
23
42
  throw new Error('This functionality is not available in the API')
24
43
  }
25
44
 
26
- // This endpoint is not available in the API
27
- public async getResources(
45
+ /**
46
+ * Get Kubernetes resources
47
+ * Command: berget kubectl get
48
+ * This endpoint is not available in the API
49
+ */
50
+ public async get(
28
51
  resource: string,
29
52
  namespace?: string
30
53
  ): Promise<any[]> {
@@ -351,16 +351,17 @@ export interface paths {
351
351
  "/v1/auth/login": {
352
352
  /**
353
353
  * OAuth login
354
- * @description Initiates OAuth flow for user authentication.
355
- *
356
- * If you don't have an account yet, you can create one during the login process
357
- * by clicking on the "Register" link on the login page.
358
- *
359
- * This is the recommended way to authenticate with the Berget AI API.
354
+ * @description Initiates OAuth login flow via Keycloak
360
355
  */
361
356
  get: {
357
+ parameters: {
358
+ query?: {
359
+ /** @description URL to redirect to after successful login */
360
+ redirect_uri?: string;
361
+ };
362
+ };
362
363
  responses: {
363
- /** @description Redirects to authentication provider */
364
+ /** @description Redirects to Keycloak for login */
364
365
  302: {
365
366
  content: never;
366
367
  };
@@ -370,98 +371,60 @@ export interface paths {
370
371
  "/v1/auth/callback": {
371
372
  /**
372
373
  * OAuth callback
373
- * @description Handles the callback from OAuth authentication. After successful authentication, you'll receive an access token to use for API requests.
374
+ * @description Handles Keycloak login callback and exchanges token
374
375
  */
375
376
  get: {
376
377
  parameters: {
377
- query?: {
378
- /** @description Authorization code */
379
- code?: string;
380
- /** @description State parameter for CSRF protection */
381
- state?: string;
378
+ query: {
379
+ code: string;
380
+ state: string;
382
381
  };
383
382
  };
384
383
  responses: {
385
- /** @description Redirects to frontend with token */
384
+ /** @description Redirects to frontend */
386
385
  302: {
387
386
  content: never;
388
387
  };
389
- /** @description Invalid request */
390
- 400: {
391
- content: never;
392
- };
393
- /** @description Authentication failed */
394
- 401: {
388
+ };
389
+ };
390
+ };
391
+ "/v1/auth/device": {
392
+ /** Initiate device authorization flow */
393
+ post: {
394
+ responses: {
395
+ /** @description Device authorization initiated */
396
+ 200: {
395
397
  content: never;
396
398
  };
397
399
  };
398
400
  };
399
401
  };
400
- "/v1/auth/keycloak": {
401
- /**
402
- * Authenticate with OAuth tokens
403
- * @description Exchange OAuth tokens for our system token
404
- */
402
+ "/v1/auth/device/token": {
403
+ /** Poll for device token */
405
404
  post: {
406
405
  requestBody: {
407
406
  content: {
408
407
  "application/json": {
409
- /** @description Subject identifier from authentication provider */
410
- sub: string;
411
- /**
412
- * Format: email
413
- * @description User's email address
414
- */
415
- email: string;
416
- /** @description User's full name */
417
- name?: string;
418
- /** @description User's preferred username */
419
- preferred_username?: string;
420
- /** @description OAuth access token */
421
- access_token: string;
422
- /** @description OAuth refresh token */
423
- refresh_token?: string;
424
- /** @description OAuth ID token */
425
- id_token?: string;
408
+ device_code?: string;
426
409
  };
427
410
  };
428
411
  };
429
412
  responses: {
430
- /** @description Authentication successful */
413
+ /** @description Token returned or pending status */
431
414
  200: {
432
- content: {
433
- "application/json": {
434
- /** @description JWT token for our system */
435
- token?: string;
436
- /** @description User information */
437
- user?: Record<string, never>;
438
- };
439
- };
440
- };
441
- /** @description Invalid request */
442
- 400: {
443
- content: never;
444
- };
445
- /** @description Authentication failed */
446
- 401: {
447
415
  content: never;
448
416
  };
449
417
  };
450
418
  };
451
419
  };
452
420
  "/v1/auth/register-url": {
453
- /**
454
- * Get registration URL
455
- * @description Returns the URL where users can register a new account.
456
- * This is the recommended way for new users to create accounts.
457
- */
421
+ /** Get Keycloak registration URL */
458
422
  get: {
459
423
  responses: {
460
- /** @description Registration URL */
424
+ /** @description Registration URL returned */
461
425
  200: {
462
426
  content: {
463
427
  "application/json": {
464
- /** @description URL to registration page */
465
428
  url?: string;
466
429
  };
467
430
  };
@@ -469,82 +432,14 @@ export interface paths {
469
432
  };
470
433
  };
471
434
  };
472
- "/v1/auth/device": {
473
- /**
474
- * Initiate device authorization flow
475
- * @description Initiates a device authorization flow for CLI tools.
476
- * Returns a verification URL and device code for the user to complete authentication.
477
- */
478
- post: {
479
- responses: {
480
- /** @description Device authorization initiated */
481
- 200: {
482
- content: {
483
- "application/json": {
484
- /** @description URL where the user should go to authenticate */
485
- verification_url?: string;
486
- /** @description Code the user should enter on the verification page */
487
- user_code?: string;
488
- /** @description Code used by the device to poll for authentication status */
489
- device_code?: string;
490
- /** @description Seconds until the device code expires */
491
- expires_in?: number;
492
- /** @description Polling interval in seconds */
493
- interval?: number;
494
- };
495
- };
496
- };
497
- };
498
- };
499
- };
500
- "/v1/auth/device/token": {
501
- /**
502
- * Poll for device authorization token
503
- * @description Polls for the completion of a device authorization flow.
504
- * The CLI tool should call this endpoint repeatedly until authentication is complete.
505
- */
506
- post: {
507
- requestBody: {
508
- content: {
509
- "application/json": {
510
- /** @description Device code received from the /device endpoint */
511
- device_code: string;
512
- };
513
- };
514
- };
515
- responses: {
516
- /** @description Authentication successful */
517
- 200: {
518
- content: {
519
- "application/json": {
520
- /** @description JWT token for API access */
521
- token?: string;
522
- /** @description User information */
523
- user?: Record<string, never>;
524
- };
525
- };
526
- };
527
- /** @description Invalid request */
528
- 400: {
529
- content: never;
530
- };
531
- /** @description Authentication pending or failed */
532
- 401: {
533
- content: never;
534
- };
535
- };
536
- };
537
- };
538
435
  "/v1/auth/logout": {
539
436
  /**
540
437
  * Logout
541
- * @description Redirects to logout endpoint to properly terminate the session.
542
- * Optionally accepts a redirect_uri query parameter to specify where to redirect after logout.
438
+ * @description Clears cookies and redirects to Keycloak logout
543
439
  */
544
440
  get: {
545
441
  parameters: {
546
442
  query?: {
547
- /** @description URL to redirect to after logout */
548
443
  redirect_uri?: string;
549
444
  };
550
445
  };
@@ -969,6 +864,28 @@ export interface paths {
969
864
  };
970
865
  };
971
866
  };
867
+ "/v1/users/me": {
868
+ /**
869
+ * Get current user profile
870
+ * @description Retrieves the profile of the currently authenticated user
871
+ */
872
+ get: {
873
+ responses: {
874
+ /** @description User profile */
875
+ 200: {
876
+ content: {
877
+ "application/json": components["schemas"]["UserProfile"];
878
+ };
879
+ };
880
+ /** @description Unauthorized */
881
+ 401: {
882
+ content: {
883
+ "application/json": components["schemas"]["ErrorResponse"];
884
+ };
885
+ };
886
+ };
887
+ };
888
+ };
972
889
  "/v1/users/{id}": {
973
890
  /**
974
891
  * Get user details
@@ -1073,28 +990,6 @@ export interface paths {
1073
990
  };
1074
991
  };
1075
992
  };
1076
- "/v1/users/me": {
1077
- /**
1078
- * Get current user profile
1079
- * @description Retrieves the profile of the currently authenticated user
1080
- */
1081
- get: {
1082
- responses: {
1083
- /** @description User profile */
1084
- 200: {
1085
- content: {
1086
- "application/json": components["schemas"]["UserProfile"];
1087
- };
1088
- };
1089
- /** @description Unauthorized */
1090
- 401: {
1091
- content: {
1092
- "application/json": components["schemas"]["ErrorResponse"];
1093
- };
1094
- };
1095
- };
1096
- };
1097
- };
1098
993
  "/v1/users/invite": {
1099
994
  /**
1100
995
  * Invite team member
@@ -1797,46 +1692,17 @@ export interface components {
1797
1692
  };
1798
1693
  };
1799
1694
  };
1800
- /** @description Pricing information for the model */
1801
- ModelPricing: {
1802
- /** @description Cost per token for input in the specified currency */
1803
- input: number;
1804
- /** @description Cost per token for output in the specified currency */
1805
- output: number;
1806
- /** @description The unit of pricing (e.g., "token") */
1807
- unit: string;
1808
- /** @description The currency of the pricing (e.g., "USD") */
1809
- currency: string;
1810
- };
1811
- /** @description Model capabilities */
1812
- ModelCapabilities: {
1813
- /** @description Whether the model supports vision/image input */
1814
- vision: boolean;
1815
- /** @description Whether the model supports function calling */
1816
- function_calling: boolean;
1817
- /** @description Whether the model supports JSON mode */
1818
- json_mode: boolean;
1819
- };
1820
1695
  Model: {
1821
1696
  /** @description Unique identifier for the model */
1822
1697
  id: string;
1823
- /**
1824
- * @description Object type
1825
- * @enum {string}
1826
- */
1827
- object: "model";
1828
- /** @description Unix timestamp of when the model was created */
1829
- created: number;
1830
- /** @description Organization that owns the model */
1831
- owned_by: string;
1832
- pricing: components["schemas"]["ModelPricing"];
1833
- capabilities: components["schemas"]["ModelCapabilities"];
1834
- };
1835
- ModelList: {
1836
- data: components["schemas"]["Model"][];
1837
- /** @enum {string} */
1838
- object: "list";
1698
+ /** @description Name of the model */
1699
+ name: string;
1700
+ /** @description Description of the model */
1701
+ description: string;
1702
+ /** @description Whether the model is active */
1703
+ active: boolean;
1839
1704
  };
1705
+ ModelList: components["schemas"]["Model"][];
1840
1706
  /** @description Cost information for this usage */
1841
1707
  TokenCost: {
1842
1708
  /** @description Cost amount */
@@ -0,0 +1,23 @@
1
+ import * as fs from 'fs'
2
+ import * as path from 'path'
3
+
4
+ /**
5
+ * Check for .bergetconfig file and handle cluster switching
6
+ */
7
+ export function checkBergetConfig(): void {
8
+ const configPath = path.join(process.cwd(), '.bergetconfig')
9
+ if (fs.existsSync(configPath)) {
10
+ try {
11
+ const config = fs.readFileSync(configPath, 'utf8')
12
+ const match = config.match(/cluster:\s*(.+)/)
13
+ if (match && match[1]) {
14
+ const clusterName = match[1].trim()
15
+ console.log(`🔄 Berget: Switched to cluster "${clusterName}"`)
16
+ console.log('✓ kubectl config updated')
17
+ console.log('')
18
+ }
19
+ } catch (error) {
20
+ // Silently ignore errors reading config
21
+ }
22
+ }
23
+ }