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,184 @@
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
+ CHAT: 'chat',
19
+ }
20
+
21
+ // Subcommands for each group
22
+ export const SUBCOMMANDS = {
23
+ // Auth commands
24
+ AUTH: {
25
+ LOGIN: 'login',
26
+ LOGOUT: 'logout',
27
+ WHOAMI: 'whoami',
28
+ },
29
+
30
+ // Chat commands
31
+ CHAT: {
32
+ RUN: 'run',
33
+ LIST: 'list',
34
+ },
35
+
36
+ // API Keys commands
37
+ API_KEYS: {
38
+ LIST: 'list',
39
+ CREATE: 'create',
40
+ DELETE: 'delete',
41
+ ROTATE: 'rotate',
42
+ DESCRIBE: 'describe',
43
+ SET_DEFAULT: 'set-default',
44
+ GET_DEFAULT: 'get-default',
45
+ },
46
+
47
+ // Clusters commands
48
+ CLUSTERS: {
49
+ LIST: 'list',
50
+ DESCRIBE: 'describe',
51
+ GET_USAGE: 'get-usage',
52
+ },
53
+
54
+ // Apps commands
55
+ APPS: {
56
+ LIST_TEMPLATES: 'list-templates',
57
+ DESCRIBE_TEMPLATE: 'describe-template',
58
+ LIST_INSTALLATIONS: 'list-installations',
59
+ INSTALL: 'install',
60
+ UNINSTALL: 'uninstall',
61
+ DESCRIBE_INSTALLATION: 'describe-installation',
62
+ },
63
+
64
+ // Models commands
65
+ MODELS: {
66
+ LIST: 'list',
67
+ DESCRIBE: 'describe',
68
+ },
69
+
70
+ // Helm commands
71
+ HELM: {
72
+ ADD_REPO: 'add-repo',
73
+ INSTALL: 'install',
74
+ },
75
+
76
+ // Kubectl commands
77
+ KUBECTL: {
78
+ CREATE_NAMESPACE: 'create-namespace',
79
+ APPLY: 'apply',
80
+ GET: 'get',
81
+ },
82
+
83
+ // Flux commands
84
+ FLUX: {
85
+ INSTALL: 'install',
86
+ BOOTSTRAP: 'bootstrap',
87
+ },
88
+
89
+ // Users commands
90
+ USERS: {
91
+ LIST: 'list',
92
+ DESCRIBE: 'describe',
93
+ UPDATE: 'update',
94
+ INVITE: 'invite',
95
+ },
96
+
97
+ // Billing commands
98
+ BILLING: {
99
+ GET_USAGE: 'get-usage',
100
+ LIST_INVOICES: 'list-invoices',
101
+ DESCRIBE_INVOICE: 'describe-invoice',
102
+ LIST_PAYMENT_METHODS: 'list-payment-methods',
103
+ ADD_PAYMENT_METHOD: 'add-payment-method',
104
+ REMOVE_PAYMENT_METHOD: 'remove-payment-method',
105
+ UPDATE_SUBSCRIPTION: 'update-subscription',
106
+ },
107
+ }
108
+
109
+ // Command descriptions
110
+ export const COMMAND_DESCRIPTIONS = {
111
+ // Auth group
112
+ [COMMAND_GROUPS.AUTH]: 'Manage authentication and authorization',
113
+ [`${COMMAND_GROUPS.AUTH} ${SUBCOMMANDS.AUTH.LOGIN}`]: 'Log in to Berget AI',
114
+ [`${COMMAND_GROUPS.AUTH} ${SUBCOMMANDS.AUTH.LOGOUT}`]: 'Log out from Berget AI',
115
+ [`${COMMAND_GROUPS.AUTH} ${SUBCOMMANDS.AUTH.WHOAMI}`]: 'Display current user information',
116
+
117
+ // API Keys group
118
+ [COMMAND_GROUPS.API_KEYS]: 'Manage API keys',
119
+ [`${COMMAND_GROUPS.API_KEYS} ${SUBCOMMANDS.API_KEYS.LIST}`]: 'List all API keys',
120
+ [`${COMMAND_GROUPS.API_KEYS} ${SUBCOMMANDS.API_KEYS.CREATE}`]: 'Create a new API key',
121
+ [`${COMMAND_GROUPS.API_KEYS} ${SUBCOMMANDS.API_KEYS.DELETE}`]: 'Delete an API key',
122
+ [`${COMMAND_GROUPS.API_KEYS} ${SUBCOMMANDS.API_KEYS.ROTATE}`]: 'Rotate an API key',
123
+ [`${COMMAND_GROUPS.API_KEYS} ${SUBCOMMANDS.API_KEYS.DESCRIBE}`]: 'Get usage statistics for an API key',
124
+ [`${COMMAND_GROUPS.API_KEYS} ${SUBCOMMANDS.API_KEYS.SET_DEFAULT}`]: 'Set an API key as the default for chat commands',
125
+ [`${COMMAND_GROUPS.API_KEYS} ${SUBCOMMANDS.API_KEYS.GET_DEFAULT}`]: 'Show the current default API key',
126
+
127
+ // Clusters group
128
+ [COMMAND_GROUPS.CLUSTERS]: 'Manage Kubernetes clusters',
129
+ [`${COMMAND_GROUPS.CLUSTERS} ${SUBCOMMANDS.CLUSTERS.LIST}`]: 'List all clusters',
130
+ [`${COMMAND_GROUPS.CLUSTERS} ${SUBCOMMANDS.CLUSTERS.DESCRIBE}`]: 'Get detailed information about a cluster',
131
+ [`${COMMAND_GROUPS.CLUSTERS} ${SUBCOMMANDS.CLUSTERS.GET_USAGE}`]: 'Get resource usage for a cluster',
132
+
133
+ // Apps group
134
+ [COMMAND_GROUPS.APPS]: 'Manage applications',
135
+ [`${COMMAND_GROUPS.APPS} ${SUBCOMMANDS.APPS.LIST_TEMPLATES}`]: 'List available application templates',
136
+ [`${COMMAND_GROUPS.APPS} ${SUBCOMMANDS.APPS.DESCRIBE_TEMPLATE}`]: 'Get detailed information about an application template',
137
+ [`${COMMAND_GROUPS.APPS} ${SUBCOMMANDS.APPS.LIST_INSTALLATIONS}`]: 'List installed applications',
138
+ [`${COMMAND_GROUPS.APPS} ${SUBCOMMANDS.APPS.INSTALL}`]: 'Install an application',
139
+ [`${COMMAND_GROUPS.APPS} ${SUBCOMMANDS.APPS.UNINSTALL}`]: 'Uninstall an application',
140
+ [`${COMMAND_GROUPS.APPS} ${SUBCOMMANDS.APPS.DESCRIBE_INSTALLATION}`]: 'Get detailed information about an installed application',
141
+
142
+ // Models group
143
+ [COMMAND_GROUPS.MODELS]: 'Manage AI models',
144
+ [`${COMMAND_GROUPS.MODELS} ${SUBCOMMANDS.MODELS.LIST}`]: 'List available AI models',
145
+ [`${COMMAND_GROUPS.MODELS} ${SUBCOMMANDS.MODELS.DESCRIBE}`]: 'Get detailed information about an AI model',
146
+
147
+ // Helm group
148
+ [COMMAND_GROUPS.HELM]: 'Manage Helm charts',
149
+ [`${COMMAND_GROUPS.HELM} ${SUBCOMMANDS.HELM.ADD_REPO}`]: 'Add a Helm repository',
150
+ [`${COMMAND_GROUPS.HELM} ${SUBCOMMANDS.HELM.INSTALL}`]: 'Install a Helm chart',
151
+
152
+ // Kubectl group
153
+ [COMMAND_GROUPS.KUBECTL]: 'Manage Kubernetes resources',
154
+ [`${COMMAND_GROUPS.KUBECTL} ${SUBCOMMANDS.KUBECTL.CREATE_NAMESPACE}`]: 'Create a Kubernetes namespace',
155
+ [`${COMMAND_GROUPS.KUBECTL} ${SUBCOMMANDS.KUBECTL.APPLY}`]: 'Apply a Kubernetes configuration',
156
+ [`${COMMAND_GROUPS.KUBECTL} ${SUBCOMMANDS.KUBECTL.GET}`]: 'Get Kubernetes resources',
157
+
158
+ // Flux group
159
+ [COMMAND_GROUPS.FLUX]: 'Manage Flux CD',
160
+ [`${COMMAND_GROUPS.FLUX} ${SUBCOMMANDS.FLUX.INSTALL}`]: 'Install Flux CD',
161
+ [`${COMMAND_GROUPS.FLUX} ${SUBCOMMANDS.FLUX.BOOTSTRAP}`]: 'Bootstrap Flux CD',
162
+
163
+ // Users group
164
+ [COMMAND_GROUPS.USERS]: 'Manage users',
165
+ [`${COMMAND_GROUPS.USERS} ${SUBCOMMANDS.USERS.LIST}`]: 'List all users in your organization',
166
+ [`${COMMAND_GROUPS.USERS} ${SUBCOMMANDS.USERS.DESCRIBE}`]: 'Get detailed information about a user',
167
+ [`${COMMAND_GROUPS.USERS} ${SUBCOMMANDS.USERS.UPDATE}`]: 'Update user information',
168
+ [`${COMMAND_GROUPS.USERS} ${SUBCOMMANDS.USERS.INVITE}`]: 'Invite a new user to your organization',
169
+
170
+ // Billing group
171
+ [COMMAND_GROUPS.BILLING]: 'Manage billing and usage',
172
+ [`${COMMAND_GROUPS.BILLING} ${SUBCOMMANDS.BILLING.GET_USAGE}`]: 'Get current usage metrics',
173
+ [`${COMMAND_GROUPS.BILLING} ${SUBCOMMANDS.BILLING.LIST_INVOICES}`]: 'List all invoices',
174
+ [`${COMMAND_GROUPS.BILLING} ${SUBCOMMANDS.BILLING.DESCRIBE_INVOICE}`]: 'Get detailed information about an invoice',
175
+ [`${COMMAND_GROUPS.BILLING} ${SUBCOMMANDS.BILLING.LIST_PAYMENT_METHODS}`]: 'List all payment methods',
176
+ [`${COMMAND_GROUPS.BILLING} ${SUBCOMMANDS.BILLING.ADD_PAYMENT_METHOD}`]: 'Add a new payment method',
177
+ [`${COMMAND_GROUPS.BILLING} ${SUBCOMMANDS.BILLING.REMOVE_PAYMENT_METHOD}`]: 'Remove a payment method',
178
+ [`${COMMAND_GROUPS.BILLING} ${SUBCOMMANDS.BILLING.UPDATE_SUBSCRIPTION}`]: 'Update subscription plan',
179
+
180
+ // Chat group
181
+ [COMMAND_GROUPS.CHAT]: 'Interact with AI chat models',
182
+ [`${COMMAND_GROUPS.CHAT} ${SUBCOMMANDS.CHAT.RUN}`]: 'Run a chat session with a specified model',
183
+ [`${COMMAND_GROUPS.CHAT} ${SUBCOMMANDS.CHAT.LIST}`]: 'List available chat models',
184
+ }
@@ -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 } }
@@ -4,14 +4,25 @@ import {
4
4
  clearAuthToken,
5
5
  apiClient,
6
6
  } from '../client'
7
- import open from 'open'
7
+ // We'll use dynamic import for 'open' to support ESM modules in CommonJS
8
8
  import chalk from 'chalk'
9
9
  import { handleError } from '../utils/error-handler'
10
+ import { COMMAND_GROUPS, SUBCOMMANDS } from '../constants/command-structure'
10
11
 
12
+ /**
13
+ * Service for authentication operations
14
+ * Command group: auth
15
+ */
11
16
  export class AuthService {
12
17
  private static instance: AuthService
13
18
  private client = createAuthenticatedClient()
14
19
 
20
+ // Command group name for this service
21
+ public static readonly COMMAND_GROUP = COMMAND_GROUPS.AUTH
22
+
23
+ // Subcommands for this service
24
+ public static readonly COMMANDS = SUBCOMMANDS.AUTH
25
+
15
26
  private constructor() {}
16
27
 
17
28
  public static getInstance(): AuthService {
@@ -21,6 +32,21 @@ export class AuthService {
21
32
  return AuthService.instance
22
33
  }
23
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
+
24
50
  public async login(): Promise<boolean> {
25
51
  try {
26
52
  // Clear any existing token to ensure a fresh login
@@ -42,25 +68,40 @@ export class AuthService {
42
68
  )
43
69
  }
44
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
+
45
80
  // Display information to user
46
81
  console.log(chalk.cyan('\nTo complete login:'))
47
82
  console.log(
48
83
  chalk.cyan(
49
84
  `1. Open this URL: ${chalk.bold(
50
- deviceData.verification_url || 'https://auth.berget.ai/device'
85
+ typedDeviceData.verification_url ||
86
+ 'https://keycloak.berget.ai/device'
51
87
  )}`
52
88
  )
53
89
  )
54
- console.log(
55
- chalk.cyan(
56
- `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
+ )
57
97
  )
58
- )
59
98
 
60
99
  // Try to open browser automatically
61
100
  try {
62
- if (deviceData.verification_url) {
63
- await open(deviceData.verification_url)
101
+ if (typedDeviceData.verification_url) {
102
+ // Use dynamic import for the 'open' package
103
+ const open = await import('open').then((m) => m.default)
104
+ await open(typedDeviceData.verification_url)
64
105
  console.log(
65
106
  chalk.dim(
66
107
  "Browser opened automatically. If it didn't open, please use the URL above."
@@ -80,9 +121,11 @@ export class AuthService {
80
121
  // Step 2: Poll for completion
81
122
  const startTime = Date.now()
82
123
  const expiresIn =
83
- deviceData.expires_in !== undefined ? deviceData.expires_in : 900
124
+ typedDeviceData.expires_in !== undefined
125
+ ? typedDeviceData.expires_in
126
+ : 900
84
127
  const expiresAt = startTime + expiresIn * 1000
85
- let pollInterval = (deviceData.interval || 5) * 1000
128
+ let pollInterval = (typedDeviceData.interval || 5) * 1000
86
129
 
87
130
  const spinner = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
88
131
  let spinnerIdx = 0
@@ -98,7 +141,7 @@ export class AuthService {
98
141
  spinnerIdx = (spinnerIdx + 1) % spinner.length
99
142
 
100
143
  // Check if authentication is complete
101
- const deviceCode = deviceData.device_code || ''
144
+ const deviceCode = typedDeviceData.device_code || ''
102
145
  const { data: tokenData, error: tokenError } = await apiClient.POST(
103
146
  '/v1/auth/device/token',
104
147
  {
@@ -157,21 +200,56 @@ export class AuthService {
157
200
  }
158
201
  continue
159
202
  }
160
- } else if (tokenData && tokenData.token) {
161
- // Success!
162
- saveAuthToken(tokenData.token)
163
-
164
- process.stdout.write('\r' + ' '.repeat(50) + '\r') // Clear the spinner line
165
- 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
+ }
166
216
 
167
- if (tokenData.user) {
168
- const user = tokenData.user as any
169
- console.log(
170
- 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
171
223
  )
172
- }
173
224
 
174
- 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
+ }
175
253
  }
176
254
  }
177
255
 
@@ -182,25 +260,4 @@ export class AuthService {
182
260
  return false
183
261
  }
184
262
  }
185
-
186
- public async isAuthenticated(): Promise<boolean> {
187
- try {
188
- // Call an API endpoint that requires authentication
189
- const { data, error } = await this.client.GET('/v1/users/me')
190
- return !!data && !error
191
- } catch {
192
- return false
193
- }
194
- }
195
-
196
- public async getUserProfile() {
197
- try {
198
- const { data, error } = await this.client.GET('/v1/users/me')
199
- if (error) throw new Error(JSON.stringify(error))
200
- return data
201
- } catch (error) {
202
- handleError('Failed to get user profile', error)
203
- throw error
204
- }
205
- }
206
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
+ }