berget 0.0.4 → 1.0.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/src/client.ts CHANGED
@@ -1,22 +1,23 @@
1
- import createClient from 'openapi-fetch';
2
- import type { paths } from './types/api';
3
- import * as fs from 'fs';
4
- import * as path from 'path';
5
- import * as os from 'os';
6
- import chalk from 'chalk';
1
+ import createClient from 'openapi-fetch'
2
+ import type { paths } from './types/api'
3
+ import * as fs from 'fs'
4
+ import * as path from 'path'
5
+ import * as os from 'os'
6
+ import chalk from 'chalk'
7
7
 
8
8
  // Configuration directory
9
- const CONFIG_DIR = path.join(os.homedir(), '.berget');
10
- const TOKEN_FILE = path.join(CONFIG_DIR, 'token.json');
9
+ const CONFIG_DIR = path.join(os.homedir(), '.berget')
10
+ const TOKEN_FILE = path.join(CONFIG_DIR, 'token.json')
11
11
 
12
12
  // API Base URL
13
13
  // Use --local flag to test against local API
14
- const isLocalMode = process.argv.includes('--local');
15
- const API_BASE_URL = process.env.BERGET_API_URL ||
16
- (isLocalMode ? 'http://localhost:3000' : 'https://api.berget.ai');
14
+ const isLocalMode = process.argv.includes('--local')
15
+ const API_BASE_URL =
16
+ process.env.BERGET_API_URL ||
17
+ (isLocalMode ? 'http://localhost:3000' : 'https://api.berget.ai')
17
18
 
18
19
  if (isLocalMode && !process.env.BERGET_API_URL) {
19
- console.log(chalk.yellow('Using local API endpoint: http://localhost:3000'));
20
+ console.log(chalk.yellow('Using local API endpoint: http://localhost:3000'))
20
21
  }
21
22
 
22
23
  // Create a typed client for the Berget API
@@ -24,100 +25,109 @@ export const apiClient = createClient<paths>({
24
25
  baseUrl: API_BASE_URL,
25
26
  headers: {
26
27
  'Content-Type': 'application/json',
27
- 'Accept': 'application/json'
28
- }
29
- });
28
+ Accept: 'application/json',
29
+ },
30
+ })
30
31
 
31
32
  // Authentication functions
32
33
  export const getAuthToken = (): string | null => {
33
34
  try {
34
35
  if (fs.existsSync(TOKEN_FILE)) {
35
- const tokenData = JSON.parse(fs.readFileSync(TOKEN_FILE, 'utf8'));
36
- return tokenData.accessToken;
36
+ const tokenData = JSON.parse(fs.readFileSync(TOKEN_FILE, 'utf8'))
37
+ return tokenData.accessToken
37
38
  }
38
39
  } catch (error) {
39
- console.error('Error reading auth token:', error);
40
+ console.error('Error reading auth token:', error)
40
41
  }
41
- return null;
42
- };
42
+ return null
43
+ }
43
44
 
44
45
  // Check if token is expired (JWT tokens have an exp claim)
45
46
  export const isTokenExpired = (token: string): boolean => {
46
47
  try {
47
- const base64Url = token.split('.')[1];
48
- const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
48
+ const base64Url = token.split('.')[1]
49
+ const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
49
50
  const jsonPayload = decodeURIComponent(
50
51
  atob(base64)
51
52
  .split('')
52
- .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
53
+ .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
53
54
  .join('')
54
- );
55
- const payload = JSON.parse(jsonPayload);
56
-
55
+ )
56
+ const payload = JSON.parse(jsonPayload)
57
+
57
58
  // Check if token has expired
58
59
  if (payload.exp) {
59
- return payload.exp * 1000 < Date.now();
60
+ return payload.exp * 1000 < Date.now()
60
61
  }
61
62
  } catch (error) {
62
63
  // If we can't decode the token, assume it's expired
63
- return true;
64
+ return true
64
65
  }
65
-
66
+
66
67
  // If there's no exp claim, assume it's valid
67
- return false;
68
- };
68
+ return false
69
+ }
69
70
 
70
71
  export const saveAuthToken = (token: string): void => {
71
72
  try {
72
73
  if (!fs.existsSync(CONFIG_DIR)) {
73
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
74
+ fs.mkdirSync(CONFIG_DIR, { recursive: true })
74
75
  }
75
- fs.writeFileSync(TOKEN_FILE, JSON.stringify({ accessToken: token }), 'utf8');
76
+ fs.writeFileSync(TOKEN_FILE, JSON.stringify({ accessToken: token }), 'utf8')
76
77
  // Set file permissions to be readable only by the owner
77
- fs.chmodSync(TOKEN_FILE, 0o600);
78
+ fs.chmodSync(TOKEN_FILE, 0o600)
78
79
  } catch (error) {
79
- console.error('Error saving auth token:', error);
80
+ console.error('Error saving auth token:', error)
80
81
  }
81
- };
82
+ }
82
83
 
83
84
  export const clearAuthToken = (): void => {
84
85
  try {
85
86
  if (fs.existsSync(TOKEN_FILE)) {
86
- fs.unlinkSync(TOKEN_FILE);
87
+ fs.unlinkSync(TOKEN_FILE)
87
88
  }
88
89
  } catch (error) {
89
- console.error('Error clearing auth token:', error);
90
+ console.error('Error clearing auth token:', error)
90
91
  }
91
- };
92
+ }
92
93
 
93
94
  // Create an authenticated client
94
95
  export const createAuthenticatedClient = () => {
95
- const token = getAuthToken();
96
-
96
+ const token = getAuthToken()
97
97
  if (!token) {
98
- console.warn(chalk.yellow('No authentication token found. Please run `berget login` first.'));
98
+ console.warn(
99
+ chalk.yellow(
100
+ 'No authentication token found. Please run `berget login` first.'
101
+ )
102
+ )
99
103
  } else if (isTokenExpired(token)) {
100
- console.warn(chalk.yellow('Your authentication token has expired. Please run `berget login` to get a new token.'));
104
+ console.warn(
105
+ chalk.yellow(
106
+ 'Your authentication token has expired. Please run `berget login` to get a new token.'
107
+ )
108
+ )
101
109
  // Optionally clear the expired token
102
- clearAuthToken();
110
+ clearAuthToken()
103
111
  return createClient<paths>({
104
112
  baseUrl: API_BASE_URL,
105
113
  headers: {
106
114
  'Content-Type': 'application/json',
107
- 'Accept': 'application/json'
108
- }
109
- });
115
+ Accept: 'application/json',
116
+ },
117
+ })
110
118
  }
111
-
119
+
112
120
  return createClient<paths>({
113
121
  baseUrl: API_BASE_URL,
114
- headers: token ? {
115
- 'Authorization': `Bearer ${token}`,
116
- 'Content-Type': 'application/json',
117
- 'Accept': 'application/json'
118
- } : {
119
- 'Content-Type': 'application/json',
120
- 'Accept': 'application/json'
121
- },
122
- });
123
- };
122
+ headers: token
123
+ ? {
124
+ Authorization: `Bearer ${token}`,
125
+ 'Content-Type': 'application/json',
126
+ Accept: 'application/json',
127
+ }
128
+ : {
129
+ 'Content-Type': 'application/json',
130
+ Accept: 'application/json',
131
+ },
132
+ })
133
+ }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Command structure constants for the CLI
3
+ * Following patterns from AWS CLI and Google Cloud CLI
4
+ */
5
+
6
+ // Main command groups
7
+ export const COMMAND_GROUPS = {
8
+ AUTH: 'auth',
9
+ API_KEYS: 'api-keys',
10
+ CLUSTERS: 'clusters',
11
+ APPS: 'apps',
12
+ MODELS: 'models',
13
+ HELM: 'helm',
14
+ KUBECTL: 'kubectl',
15
+ FLUX: 'flux',
16
+ USERS: 'users',
17
+ BILLING: 'billing',
18
+ }
19
+
20
+ // Subcommands for each group
21
+ export const SUBCOMMANDS = {
22
+ // Auth commands
23
+ AUTH: {
24
+ LOGIN: 'login',
25
+ LOGOUT: 'logout',
26
+ WHOAMI: 'whoami',
27
+ },
28
+
29
+ // API Keys commands
30
+ API_KEYS: {
31
+ LIST: 'list',
32
+ CREATE: 'create',
33
+ DELETE: 'delete',
34
+ ROTATE: 'rotate',
35
+ DESCRIBE: 'describe',
36
+ },
37
+
38
+ // Clusters commands
39
+ CLUSTERS: {
40
+ LIST: 'list',
41
+ DESCRIBE: 'describe',
42
+ GET_USAGE: 'get-usage',
43
+ },
44
+
45
+ // Apps commands
46
+ APPS: {
47
+ LIST_TEMPLATES: 'list-templates',
48
+ DESCRIBE_TEMPLATE: 'describe-template',
49
+ LIST_INSTALLATIONS: 'list-installations',
50
+ INSTALL: 'install',
51
+ UNINSTALL: 'uninstall',
52
+ DESCRIBE_INSTALLATION: 'describe-installation',
53
+ },
54
+
55
+ // Models commands
56
+ MODELS: {
57
+ LIST: 'list',
58
+ DESCRIBE: 'describe',
59
+ },
60
+
61
+ // Helm commands
62
+ HELM: {
63
+ ADD_REPO: 'add-repo',
64
+ INSTALL: 'install',
65
+ },
66
+
67
+ // Kubectl commands
68
+ KUBECTL: {
69
+ CREATE_NAMESPACE: 'create-namespace',
70
+ APPLY: 'apply',
71
+ GET: 'get',
72
+ },
73
+
74
+ // Flux commands
75
+ FLUX: {
76
+ INSTALL: 'install',
77
+ BOOTSTRAP: 'bootstrap',
78
+ },
79
+
80
+ // Users commands
81
+ USERS: {
82
+ LIST: 'list',
83
+ DESCRIBE: 'describe',
84
+ UPDATE: 'update',
85
+ INVITE: 'invite',
86
+ },
87
+
88
+ // Billing commands
89
+ BILLING: {
90
+ GET_USAGE: 'get-usage',
91
+ LIST_INVOICES: 'list-invoices',
92
+ DESCRIBE_INVOICE: 'describe-invoice',
93
+ LIST_PAYMENT_METHODS: 'list-payment-methods',
94
+ ADD_PAYMENT_METHOD: 'add-payment-method',
95
+ REMOVE_PAYMENT_METHOD: 'remove-payment-method',
96
+ UPDATE_SUBSCRIPTION: 'update-subscription',
97
+ },
98
+ }
99
+
100
+ // Command descriptions
101
+ export const COMMAND_DESCRIPTIONS = {
102
+ // Auth group
103
+ [COMMAND_GROUPS.AUTH]: 'Manage authentication and authorization',
104
+ [`${COMMAND_GROUPS.AUTH} ${SUBCOMMANDS.AUTH.LOGIN}`]: 'Log in to Berget AI',
105
+ [`${COMMAND_GROUPS.AUTH} ${SUBCOMMANDS.AUTH.LOGOUT}`]: 'Log out from Berget AI',
106
+ [`${COMMAND_GROUPS.AUTH} ${SUBCOMMANDS.AUTH.WHOAMI}`]: 'Display current user information',
107
+
108
+ // API Keys group
109
+ [COMMAND_GROUPS.API_KEYS]: 'Manage API keys',
110
+ [`${COMMAND_GROUPS.API_KEYS} ${SUBCOMMANDS.API_KEYS.LIST}`]: 'List all API keys',
111
+ [`${COMMAND_GROUPS.API_KEYS} ${SUBCOMMANDS.API_KEYS.CREATE}`]: 'Create a new API key',
112
+ [`${COMMAND_GROUPS.API_KEYS} ${SUBCOMMANDS.API_KEYS.DELETE}`]: 'Delete an API key',
113
+ [`${COMMAND_GROUPS.API_KEYS} ${SUBCOMMANDS.API_KEYS.ROTATE}`]: 'Rotate an API key',
114
+ [`${COMMAND_GROUPS.API_KEYS} ${SUBCOMMANDS.API_KEYS.DESCRIBE}`]: 'Get usage statistics for an API key',
115
+
116
+ // Clusters group
117
+ [COMMAND_GROUPS.CLUSTERS]: 'Manage Kubernetes clusters',
118
+ [`${COMMAND_GROUPS.CLUSTERS} ${SUBCOMMANDS.CLUSTERS.LIST}`]: 'List all clusters',
119
+ [`${COMMAND_GROUPS.CLUSTERS} ${SUBCOMMANDS.CLUSTERS.DESCRIBE}`]: 'Get detailed information about a cluster',
120
+ [`${COMMAND_GROUPS.CLUSTERS} ${SUBCOMMANDS.CLUSTERS.GET_USAGE}`]: 'Get resource usage for a cluster',
121
+
122
+ // Apps group
123
+ [COMMAND_GROUPS.APPS]: 'Manage applications',
124
+ [`${COMMAND_GROUPS.APPS} ${SUBCOMMANDS.APPS.LIST_TEMPLATES}`]: 'List available application templates',
125
+ [`${COMMAND_GROUPS.APPS} ${SUBCOMMANDS.APPS.DESCRIBE_TEMPLATE}`]: 'Get detailed information about an application template',
126
+ [`${COMMAND_GROUPS.APPS} ${SUBCOMMANDS.APPS.LIST_INSTALLATIONS}`]: 'List installed applications',
127
+ [`${COMMAND_GROUPS.APPS} ${SUBCOMMANDS.APPS.INSTALL}`]: 'Install an application',
128
+ [`${COMMAND_GROUPS.APPS} ${SUBCOMMANDS.APPS.UNINSTALL}`]: 'Uninstall an application',
129
+ [`${COMMAND_GROUPS.APPS} ${SUBCOMMANDS.APPS.DESCRIBE_INSTALLATION}`]: 'Get detailed information about an installed application',
130
+
131
+ // Models group
132
+ [COMMAND_GROUPS.MODELS]: 'Manage AI models',
133
+ [`${COMMAND_GROUPS.MODELS} ${SUBCOMMANDS.MODELS.LIST}`]: 'List available AI models',
134
+ [`${COMMAND_GROUPS.MODELS} ${SUBCOMMANDS.MODELS.DESCRIBE}`]: 'Get detailed information about an AI model',
135
+
136
+ // Helm group
137
+ [COMMAND_GROUPS.HELM]: 'Manage Helm charts',
138
+ [`${COMMAND_GROUPS.HELM} ${SUBCOMMANDS.HELM.ADD_REPO}`]: 'Add a Helm repository',
139
+ [`${COMMAND_GROUPS.HELM} ${SUBCOMMANDS.HELM.INSTALL}`]: 'Install a Helm chart',
140
+
141
+ // Kubectl group
142
+ [COMMAND_GROUPS.KUBECTL]: 'Manage Kubernetes resources',
143
+ [`${COMMAND_GROUPS.KUBECTL} ${SUBCOMMANDS.KUBECTL.CREATE_NAMESPACE}`]: 'Create a Kubernetes namespace',
144
+ [`${COMMAND_GROUPS.KUBECTL} ${SUBCOMMANDS.KUBECTL.APPLY}`]: 'Apply a Kubernetes configuration',
145
+ [`${COMMAND_GROUPS.KUBECTL} ${SUBCOMMANDS.KUBECTL.GET}`]: 'Get Kubernetes resources',
146
+
147
+ // Flux group
148
+ [COMMAND_GROUPS.FLUX]: 'Manage Flux CD',
149
+ [`${COMMAND_GROUPS.FLUX} ${SUBCOMMANDS.FLUX.INSTALL}`]: 'Install Flux CD',
150
+ [`${COMMAND_GROUPS.FLUX} ${SUBCOMMANDS.FLUX.BOOTSTRAP}`]: 'Bootstrap Flux CD',
151
+
152
+ // Users group
153
+ [COMMAND_GROUPS.USERS]: 'Manage users',
154
+ [`${COMMAND_GROUPS.USERS} ${SUBCOMMANDS.USERS.LIST}`]: 'List all users in your organization',
155
+ [`${COMMAND_GROUPS.USERS} ${SUBCOMMANDS.USERS.DESCRIBE}`]: 'Get detailed information about a user',
156
+ [`${COMMAND_GROUPS.USERS} ${SUBCOMMANDS.USERS.UPDATE}`]: 'Update user information',
157
+ [`${COMMAND_GROUPS.USERS} ${SUBCOMMANDS.USERS.INVITE}`]: 'Invite a new user to your organization',
158
+
159
+ // Billing group
160
+ [COMMAND_GROUPS.BILLING]: 'Manage billing and usage',
161
+ [`${COMMAND_GROUPS.BILLING} ${SUBCOMMANDS.BILLING.GET_USAGE}`]: 'Get current usage metrics',
162
+ [`${COMMAND_GROUPS.BILLING} ${SUBCOMMANDS.BILLING.LIST_INVOICES}`]: 'List all invoices',
163
+ [`${COMMAND_GROUPS.BILLING} ${SUBCOMMANDS.BILLING.DESCRIBE_INVOICE}`]: 'Get detailed information about an invoice',
164
+ [`${COMMAND_GROUPS.BILLING} ${SUBCOMMANDS.BILLING.LIST_PAYMENT_METHODS}`]: 'List all payment methods',
165
+ [`${COMMAND_GROUPS.BILLING} ${SUBCOMMANDS.BILLING.ADD_PAYMENT_METHOD}`]: 'Add a new payment method',
166
+ [`${COMMAND_GROUPS.BILLING} ${SUBCOMMANDS.BILLING.REMOVE_PAYMENT_METHOD}`]: 'Remove a payment method',
167
+ [`${COMMAND_GROUPS.BILLING} ${SUBCOMMANDS.BILLING.UPDATE_SUBSCRIPTION}`]: 'Update subscription plan',
168
+ }
@@ -1,5 +1,6 @@
1
1
  import { createAuthenticatedClient } from '../client'
2
2
  import { handleError } from '../utils/error-handler'
3
+ import { COMMAND_GROUPS, SUBCOMMANDS } from '../constants/command-structure'
3
4
 
4
5
  export interface ApiKey {
5
6
  id: number
@@ -25,9 +26,19 @@ export interface ApiKeyResponse {
25
26
  created: string
26
27
  }
27
28
 
29
+ /**
30
+ * Service for managing API keys
31
+ * Command group: api-keys
32
+ */
28
33
  export class ApiKeyService {
29
34
  private static instance: ApiKeyService
30
35
  private client = createAuthenticatedClient()
36
+
37
+ // Command group name for this service
38
+ public static readonly COMMAND_GROUP = COMMAND_GROUPS.API_KEYS
39
+
40
+ // Subcommands for this service
41
+ public static readonly COMMANDS = SUBCOMMANDS.API_KEYS
31
42
 
32
43
  private constructor() {}
33
44
 
@@ -38,7 +49,11 @@ export class ApiKeyService {
38
49
  return ApiKeyService.instance
39
50
  }
40
51
 
41
- public async listApiKeys(): Promise<ApiKey[]> {
52
+ /**
53
+ * List all API keys
54
+ * Command: berget api-keys list
55
+ */
56
+ public async list(): Promise<ApiKey[]> {
42
57
  try {
43
58
  const { data, error } = await this.client.GET('/v1/api-keys')
44
59
  if (error) {
@@ -60,7 +75,11 @@ export class ApiKeyService {
60
75
  }
61
76
  }
62
77
 
63
- public async createApiKey(options: CreateApiKeyOptions): Promise<ApiKeyResponse> {
78
+ /**
79
+ * Create a new API key
80
+ * Command: berget api-keys create
81
+ */
82
+ public async create(options: CreateApiKeyOptions): Promise<ApiKeyResponse> {
64
83
  try {
65
84
  const { data, error } = await this.client.POST('/v1/api-keys', {
66
85
  body: options
@@ -73,7 +92,11 @@ export class ApiKeyService {
73
92
  }
74
93
  }
75
94
 
76
- public async deleteApiKey(id: string): Promise<boolean> {
95
+ /**
96
+ * Delete an API key
97
+ * Command: berget api-keys delete
98
+ */
99
+ public async delete(id: string): Promise<boolean> {
77
100
  try {
78
101
  const { error } = await this.client.DELETE('/v1/api-keys/{id}', {
79
102
  params: { path: { id } }
@@ -86,7 +109,11 @@ export class ApiKeyService {
86
109
  }
87
110
  }
88
111
 
89
- public async rotateApiKey(id: string): Promise<ApiKeyResponse> {
112
+ /**
113
+ * Rotate an API key
114
+ * Command: berget api-keys rotate
115
+ */
116
+ public async rotate(id: string): Promise<ApiKeyResponse> {
90
117
  try {
91
118
  const { data, error } = await this.client.PUT('/v1/api-keys/{id}/rotate', {
92
119
  params: { path: { id } }
@@ -99,7 +126,11 @@ export class ApiKeyService {
99
126
  }
100
127
  }
101
128
 
102
- public async getApiKeyUsage(id: string): Promise<any> {
129
+ /**
130
+ * Get usage statistics for an API key
131
+ * Command: berget api-keys describe
132
+ */
133
+ public async describe(id: string): Promise<any> {
103
134
  try {
104
135
  const { data, error } = await this.client.GET('/v1/api-keys/{id}/usage', {
105
136
  params: { path: { id } }