berget 1.0.0 → 1.2.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 (44) hide show
  1. package/README.md +92 -0
  2. package/dist/index.js +49 -467
  3. package/dist/package.json +35 -0
  4. package/dist/src/client.js +210 -102
  5. package/dist/src/commands/api-keys.js +277 -0
  6. package/dist/src/commands/auth.js +65 -0
  7. package/dist/src/commands/autocomplete.js +24 -0
  8. package/dist/src/commands/billing.js +53 -0
  9. package/dist/src/commands/chat.js +342 -0
  10. package/dist/src/commands/clusters.js +69 -0
  11. package/dist/src/commands/index.js +25 -0
  12. package/dist/src/commands/models.js +69 -0
  13. package/dist/src/commands/users.js +43 -0
  14. package/dist/src/constants/command-structure.js +14 -0
  15. package/dist/src/services/api-key-service.js +6 -16
  16. package/dist/src/services/auth-service.js +49 -47
  17. package/dist/src/services/chat-service.js +300 -0
  18. package/dist/src/utils/config-checker.js +50 -0
  19. package/dist/src/utils/default-api-key.js +237 -0
  20. package/dist/src/utils/error-handler.js +4 -4
  21. package/dist/src/utils/token-manager.js +165 -0
  22. package/index.ts +56 -566
  23. package/package.json +8 -2
  24. package/src/client.ts +279 -80
  25. package/src/commands/api-keys.ts +374 -0
  26. package/src/commands/auth.ts +58 -0
  27. package/src/commands/autocomplete.ts +19 -0
  28. package/src/commands/billing.ts +41 -0
  29. package/src/commands/chat.ts +445 -0
  30. package/src/commands/clusters.ts +65 -0
  31. package/src/commands/index.ts +23 -0
  32. package/src/commands/models.ts +63 -0
  33. package/src/commands/users.ts +37 -0
  34. package/src/constants/command-structure.ts +16 -0
  35. package/src/services/api-key-service.ts +12 -20
  36. package/src/services/auth-service.ts +90 -50
  37. package/src/services/chat-service.ts +295 -0
  38. package/src/types/api.d.ts +238 -178
  39. package/src/types/json.d.ts +4 -0
  40. package/src/utils/config-checker.ts +23 -0
  41. package/src/utils/default-api-key.ts +229 -0
  42. package/src/utils/error-handler.ts +4 -4
  43. package/src/utils/token-manager.ts +150 -0
  44. package/tsconfig.json +1 -1
@@ -0,0 +1,229 @@
1
+ import * as fs from 'fs'
2
+ import * as path from 'path'
3
+ import * as os from 'os'
4
+ import chalk from 'chalk'
5
+ import { ApiKeyService } from '../services/api-key-service'
6
+ import readline from 'readline'
7
+
8
+ interface DefaultApiKeyData {
9
+ id: string
10
+ name: string
11
+ prefix: string
12
+ key: string
13
+ }
14
+
15
+ /**
16
+ * Manages the default API key for chat commands
17
+ */
18
+ export class DefaultApiKeyManager {
19
+ private static instance: DefaultApiKeyManager
20
+ private configFilePath: string
21
+ private defaultApiKey: DefaultApiKeyData | null = null
22
+
23
+ private constructor() {
24
+ // Set up config file path in user's home directory
25
+ const bergetDir = path.join(os.homedir(), '.berget')
26
+ if (!fs.existsSync(bergetDir)) {
27
+ fs.mkdirSync(bergetDir, { recursive: true })
28
+ }
29
+ this.configFilePath = path.join(bergetDir, 'default-api-key.json')
30
+ this.loadConfig()
31
+ }
32
+
33
+ public static getInstance(): DefaultApiKeyManager {
34
+ if (!DefaultApiKeyManager.instance) {
35
+ DefaultApiKeyManager.instance = new DefaultApiKeyManager()
36
+ }
37
+ return DefaultApiKeyManager.instance
38
+ }
39
+
40
+ /**
41
+ * Load default API key from file
42
+ */
43
+ private loadConfig(): void {
44
+ try {
45
+ if (fs.existsSync(this.configFilePath)) {
46
+ const data = fs.readFileSync(this.configFilePath, 'utf8')
47
+ this.defaultApiKey = JSON.parse(data)
48
+ }
49
+ } catch (error) {
50
+ if (process.argv.includes('--debug')) {
51
+ console.error(chalk.dim('Failed to load default API key configuration'))
52
+ }
53
+ this.defaultApiKey = null
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Save default API key to file
59
+ */
60
+ private saveConfig(): void {
61
+ try {
62
+ if (this.defaultApiKey) {
63
+ fs.writeFileSync(this.configFilePath, JSON.stringify(this.defaultApiKey, null, 2))
64
+ // Set file permissions to be readable only by the owner
65
+ fs.chmodSync(this.configFilePath, 0o600)
66
+ } else {
67
+ // If default API key is null, remove the file
68
+ if (fs.existsSync(this.configFilePath)) {
69
+ fs.unlinkSync(this.configFilePath)
70
+ }
71
+ }
72
+ } catch (error) {
73
+ if (process.argv.includes('--debug')) {
74
+ console.error(chalk.dim('Failed to save default API key configuration'))
75
+ }
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Set the default API key
81
+ */
82
+ public setDefaultApiKey(id: string, name: string, prefix: string, key: string): void {
83
+ this.defaultApiKey = { id, name, prefix, key }
84
+ this.saveConfig()
85
+ }
86
+
87
+ /**
88
+ * Get the default API key string
89
+ */
90
+ public getDefaultApiKey(): string | null {
91
+ return this.defaultApiKey?.key || null
92
+ }
93
+
94
+ /**
95
+ * Get the default API key data object
96
+ */
97
+ public getDefaultApiKeyData(): DefaultApiKeyData | null {
98
+ return this.defaultApiKey
99
+ }
100
+
101
+ /**
102
+ * Clear the default API key
103
+ */
104
+ public clearDefaultApiKey(): void {
105
+ this.defaultApiKey = null
106
+ this.saveConfig()
107
+ }
108
+
109
+ /**
110
+ * Prompts the user to select a default API key if none is set
111
+ * @returns The selected API key or null if none was selected
112
+ */
113
+ public async promptForDefaultApiKey(): Promise<string | null> {
114
+ try {
115
+ const isDebug = process.argv.includes('--debug')
116
+
117
+ if (isDebug) {
118
+ console.log(chalk.yellow('DEBUG: promptForDefaultApiKey called'))
119
+ }
120
+
121
+ // If we already have a default API key, return it
122
+ if (this.defaultApiKey) {
123
+ if (isDebug) {
124
+ console.log(chalk.yellow('DEBUG: Using existing default API key'))
125
+ }
126
+ return this.defaultApiKey.key
127
+ }
128
+
129
+ if (isDebug) {
130
+ console.log(chalk.yellow('DEBUG: No default API key found, getting ApiKeyService'))
131
+ }
132
+
133
+ const apiKeyService = ApiKeyService.getInstance()
134
+
135
+ // Get all API keys
136
+ let apiKeys;
137
+ try {
138
+ if (isDebug) {
139
+ console.log(chalk.yellow('DEBUG: Calling apiKeyService.list()'))
140
+ }
141
+
142
+ apiKeys = await apiKeyService.list()
143
+
144
+ if (isDebug) {
145
+ console.log(chalk.yellow(`DEBUG: Got ${apiKeys ? apiKeys.length : 0} API keys`))
146
+ }
147
+
148
+ if (!apiKeys || apiKeys.length === 0) {
149
+ console.log(chalk.yellow('No API keys found. Create one with:'))
150
+ console.log(chalk.blue(' berget api-keys create --name "My Key"'))
151
+ return null
152
+ }
153
+ } catch (error) {
154
+ // Check if this is an authentication error
155
+ const errorMessage = error instanceof Error ? error.message : String(error);
156
+ const isAuthError = errorMessage.includes('Unauthorized') ||
157
+ errorMessage.includes('Authentication failed') ||
158
+ errorMessage.includes('AUTH_FAILED');
159
+
160
+ if (isAuthError) {
161
+ console.log(chalk.yellow('Authentication required. Please run `berget auth login` first.'));
162
+ } else {
163
+ console.log(chalk.red('Error fetching API keys:'));
164
+ if (error instanceof Error) {
165
+ console.log(chalk.red(error.message));
166
+ if (isDebug) {
167
+ console.log(chalk.yellow(`DEBUG: API key list error: ${error.message}`));
168
+ console.log(chalk.yellow(`DEBUG: Stack: ${error.stack}`));
169
+ }
170
+ }
171
+ }
172
+ return null;
173
+ }
174
+
175
+ console.log(chalk.blue('Select an API key to use as default:'))
176
+
177
+ // Display available API keys
178
+ apiKeys.forEach((key, index) => {
179
+ console.log(` ${index + 1}. ${key.name} (${key.prefix}...)`)
180
+ })
181
+
182
+ // Create readline interface for user input
183
+ const rl = readline.createInterface({
184
+ input: process.stdin,
185
+ output: process.stdout
186
+ })
187
+
188
+ // Prompt for selection
189
+ const selection = await new Promise<number>((resolve) => {
190
+ rl.question('Enter number (or press Enter to cancel): ', (answer) => {
191
+ rl.close()
192
+ const num = parseInt(answer.trim(), 10)
193
+ if (isNaN(num) || num < 1 || num > apiKeys.length) {
194
+ resolve(-1) // Invalid selection
195
+ } else {
196
+ resolve(num - 1) // Convert to zero-based index
197
+ }
198
+ })
199
+ })
200
+
201
+ if (selection === -1) {
202
+ console.log(chalk.yellow('No API key selected'))
203
+ return null
204
+ }
205
+
206
+ const selectedKey = apiKeys[selection]
207
+
208
+ // Create a new API key with the selected name
209
+ const newKey = await apiKeyService.create({
210
+ name: `CLI Default (copy of ${selectedKey.name})`,
211
+ description: 'Created automatically by the Berget CLI for default use'
212
+ })
213
+
214
+ // Save the new key as default
215
+ this.setDefaultApiKey(
216
+ newKey.id.toString(),
217
+ newKey.name,
218
+ newKey.key.substring(0, 8), // Use first 8 chars as prefix
219
+ newKey.key
220
+ )
221
+
222
+ console.log(chalk.green(`✓ Default API key set to: ${newKey.name}`))
223
+ return newKey.key
224
+ } catch (error) {
225
+ console.error(chalk.red('Failed to set default API key:'), error)
226
+ return null
227
+ }
228
+ }
229
+ }
@@ -30,11 +30,11 @@ export function handleError(message: string, error: any): void {
30
30
 
31
31
  // Check for authentication errors
32
32
  if (
33
- (typeof error === 'string' && error.includes('Unauthorized')) ||
34
- (error && error.message && error.message.includes('Unauthorized')) ||
35
- (error && error.code && error.code === 401)
33
+ (typeof error === 'string' && (error.includes('Unauthorized') || error.includes('Authentication failed'))) ||
34
+ (error && error.message && (error.message.includes('Unauthorized') || error.message.includes('Authentication failed'))) ||
35
+ (error && error.code && (error.code === 401 || error.code === 'AUTH_FAILED'))
36
36
  ) {
37
37
  console.error(chalk.yellow('\nYou need to be logged in to use this command.'));
38
- console.error(chalk.yellow('Run `berget login` to authenticate.'));
38
+ console.error(chalk.yellow('Run `berget auth login` to authenticate.'));
39
39
  }
40
40
  }
@@ -0,0 +1,150 @@
1
+ import * as fs from 'fs'
2
+ import * as path from 'path'
3
+ import * as os from 'os'
4
+ import chalk from 'chalk'
5
+
6
+ interface TokenData {
7
+ access_token: string
8
+ refresh_token: string
9
+ expires_at: number // timestamp in milliseconds
10
+ }
11
+
12
+ /**
13
+ * Manages authentication tokens including refresh functionality
14
+ */
15
+ export class TokenManager {
16
+ private static instance: TokenManager
17
+ private tokenFilePath: string
18
+ private tokenData: TokenData | null = null
19
+
20
+ private constructor() {
21
+ // Set up token file path in user's home directory
22
+ const bergetDir = path.join(os.homedir(), '.berget')
23
+ if (!fs.existsSync(bergetDir)) {
24
+ fs.mkdirSync(bergetDir, { recursive: true })
25
+ }
26
+ this.tokenFilePath = path.join(bergetDir, 'auth.json')
27
+ this.loadToken()
28
+ }
29
+
30
+ public static getInstance(): TokenManager {
31
+ if (!TokenManager.instance) {
32
+ TokenManager.instance = new TokenManager()
33
+ }
34
+ return TokenManager.instance
35
+ }
36
+
37
+ /**
38
+ * Load token data from file
39
+ */
40
+ private loadToken(): void {
41
+ try {
42
+ if (fs.existsSync(this.tokenFilePath)) {
43
+ const data = fs.readFileSync(this.tokenFilePath, 'utf8')
44
+ this.tokenData = JSON.parse(data)
45
+ }
46
+ } catch (error) {
47
+ console.error(chalk.dim('Failed to load authentication token'))
48
+ this.tokenData = null
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Save token data to file
54
+ */
55
+ private saveToken(): void {
56
+ try {
57
+ if (this.tokenData) {
58
+ fs.writeFileSync(this.tokenFilePath, JSON.stringify(this.tokenData, null, 2))
59
+ // Set file permissions to be readable only by the owner
60
+ fs.chmodSync(this.tokenFilePath, 0o600)
61
+ } else {
62
+ // If token data is null, remove the file
63
+ if (fs.existsSync(this.tokenFilePath)) {
64
+ fs.unlinkSync(this.tokenFilePath)
65
+ }
66
+ }
67
+ } catch (error) {
68
+ console.error(chalk.dim('Failed to save authentication token'))
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Get the current access token
74
+ * @returns The access token or null if not available
75
+ */
76
+ public getAccessToken(): string | null {
77
+ if (!this.tokenData) return null
78
+ return this.tokenData.access_token
79
+ }
80
+
81
+ /**
82
+ * Get the refresh token
83
+ * @returns The refresh token or null if not available
84
+ */
85
+ public getRefreshToken(): string | null {
86
+ if (!this.tokenData) return null
87
+ return this.tokenData.refresh_token
88
+ }
89
+
90
+ /**
91
+ * Check if the access token is expired
92
+ * @returns true if expired or about to expire (within 5 minutes), false otherwise
93
+ */
94
+ public isTokenExpired(): boolean {
95
+ if (!this.tokenData || !this.tokenData.expires_at) return true
96
+
97
+ try {
98
+ // Consider token expired if it's within 10 minutes of expiration
99
+ // Using a larger buffer to be more proactive about refreshing
100
+ const expirationBuffer = 10 * 60 * 1000 // 10 minutes in milliseconds
101
+ const isExpired = Date.now() + expirationBuffer >= this.tokenData.expires_at;
102
+
103
+ if (isExpired && process.argv.includes('--debug')) {
104
+ console.log(chalk.yellow(`DEBUG: Token expired or expiring soon. Current time: ${new Date().toISOString()}, Expiry: ${new Date(this.tokenData.expires_at).toISOString()}`));
105
+ }
106
+
107
+ return isExpired;
108
+ } catch (error) {
109
+ // If there's any error checking expiration, assume token is expired
110
+ console.error(chalk.dim(`Error checking token expiration: ${error instanceof Error ? error.message : String(error)}`));
111
+ return true;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Set new token data
117
+ * @param accessToken The new access token
118
+ * @param refreshToken The new refresh token
119
+ * @param expiresIn Expiration time in seconds
120
+ */
121
+ public setTokens(accessToken: string, refreshToken: string, expiresIn: number): void {
122
+ this.tokenData = {
123
+ access_token: accessToken,
124
+ refresh_token: refreshToken,
125
+ expires_at: Date.now() + (expiresIn * 1000)
126
+ }
127
+ this.saveToken()
128
+ }
129
+
130
+ /**
131
+ * Update just the access token and its expiration
132
+ * @param accessToken The new access token
133
+ * @param expiresIn Expiration time in seconds
134
+ */
135
+ public updateAccessToken(accessToken: string, expiresIn: number): void {
136
+ if (!this.tokenData) return
137
+
138
+ this.tokenData.access_token = accessToken
139
+ this.tokenData.expires_at = Date.now() + (expiresIn * 1000)
140
+ this.saveToken()
141
+ }
142
+
143
+ /**
144
+ * Clear all token data
145
+ */
146
+ public clearTokens(): void {
147
+ this.tokenData = null
148
+ this.saveToken()
149
+ }
150
+ }
package/tsconfig.json CHANGED
@@ -39,7 +39,7 @@
39
39
  // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
40
40
  // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
41
41
  // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
42
- // "resolveJsonModule": true, /* Enable importing .json files. */
42
+ "resolveJsonModule": true, /* Enable importing .json files. */
43
43
  // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
44
44
  // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
45
45