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.
- package/README.md +92 -0
- package/dist/index.js +7 -439
- package/dist/src/client.js +193 -102
- package/dist/src/commands/api-keys.js +271 -0
- package/dist/src/commands/auth.js +65 -0
- package/dist/src/commands/autocomplete.js +24 -0
- package/dist/src/commands/billing.js +53 -0
- package/dist/src/commands/chat.js +276 -0
- package/dist/src/commands/clusters.js +69 -0
- package/dist/src/commands/index.js +25 -0
- package/dist/src/commands/models.js +69 -0
- package/dist/src/commands/users.js +43 -0
- package/dist/src/constants/command-structure.js +164 -0
- package/dist/src/services/api-key-service.js +34 -5
- package/dist/src/services/auth-service.js +83 -43
- package/dist/src/services/chat-service.js +177 -0
- package/dist/src/services/cluster-service.js +37 -2
- package/dist/src/services/collaborator-service.js +21 -4
- package/dist/src/services/flux-service.js +21 -4
- package/dist/src/services/helm-service.js +20 -3
- package/dist/src/services/kubectl-service.js +26 -5
- package/dist/src/utils/config-checker.js +50 -0
- package/dist/src/utils/default-api-key.js +111 -0
- package/dist/src/utils/token-manager.js +165 -0
- package/index.ts +5 -529
- package/package.json +6 -1
- package/src/client.ts +262 -80
- package/src/commands/api-keys.ts +364 -0
- package/src/commands/auth.ts +58 -0
- package/src/commands/autocomplete.ts +19 -0
- package/src/commands/billing.ts +41 -0
- package/src/commands/chat.ts +345 -0
- package/src/commands/clusters.ts +65 -0
- package/src/commands/index.ts +23 -0
- package/src/commands/models.ts +63 -0
- package/src/commands/users.ts +37 -0
- package/src/constants/command-structure.ts +184 -0
- package/src/services/api-key-service.ts +36 -5
- package/src/services/auth-service.ts +101 -44
- package/src/services/chat-service.ts +177 -0
- package/src/services/cluster-service.ts +37 -2
- package/src/services/collaborator-service.ts +23 -4
- package/src/services/flux-service.ts +23 -4
- package/src/services/helm-service.ts +22 -3
- package/src/services/kubectl-service.ts +28 -5
- package/src/types/api.d.ts +58 -192
- package/src/utils/config-checker.ts +23 -0
- package/src/utils/default-api-key.ts +94 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
85
|
+
typedDeviceData.verification_url ||
|
|
86
|
+
'https://keycloak.berget.ai/device'
|
|
51
87
|
)}`
|
|
52
88
|
)
|
|
53
89
|
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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 (
|
|
63
|
-
|
|
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
|
-
|
|
124
|
+
typedDeviceData.expires_in !== undefined
|
|
125
|
+
? typedDeviceData.expires_in
|
|
126
|
+
: 900
|
|
84
127
|
const expiresAt = startTime + expiresIn * 1000
|
|
85
|
-
let pollInterval = (
|
|
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 =
|
|
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
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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 (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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
|
+
}
|