berget 1.3.1 → 2.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.
Files changed (67) hide show
  1. package/.env.example +5 -0
  2. package/.github/workflows/publish.yml +56 -0
  3. package/.github/workflows/test.yml +38 -0
  4. package/AGENTS.md +184 -0
  5. package/README.md +177 -38
  6. package/TODO.md +2 -0
  7. package/blog-post.md +176 -0
  8. package/dist/index.js +11 -8
  9. package/dist/package.json +14 -3
  10. package/dist/src/commands/api-keys.js +4 -2
  11. package/dist/src/commands/chat.js +182 -23
  12. package/dist/src/commands/code.js +1424 -0
  13. package/dist/src/commands/index.js +2 -0
  14. package/dist/src/constants/command-structure.js +12 -0
  15. package/dist/src/schemas/opencode-schema.json +1121 -0
  16. package/dist/src/services/chat-service.js +10 -10
  17. package/dist/src/services/cluster-service.js +1 -1
  18. package/dist/src/utils/default-api-key.js +2 -2
  19. package/dist/src/utils/env-manager.js +86 -0
  20. package/dist/src/utils/error-handler.js +10 -3
  21. package/dist/src/utils/markdown-renderer.js +4 -4
  22. package/dist/src/utils/opencode-validator.js +122 -0
  23. package/dist/src/utils/token-manager.js +2 -2
  24. package/dist/tests/commands/chat.test.js +109 -0
  25. package/dist/tests/commands/code.test.js +414 -0
  26. package/dist/tests/utils/env-manager.test.js +148 -0
  27. package/dist/tests/utils/opencode-validator.test.js +103 -0
  28. package/dist/vitest.config.js +9 -0
  29. package/index.ts +67 -32
  30. package/opencode.json +182 -0
  31. package/package.json +14 -3
  32. package/src/client.ts +20 -20
  33. package/src/commands/api-keys.ts +93 -60
  34. package/src/commands/auth.ts +4 -2
  35. package/src/commands/billing.ts +6 -3
  36. package/src/commands/chat.ts +291 -97
  37. package/src/commands/clusters.ts +2 -2
  38. package/src/commands/code.ts +1696 -0
  39. package/src/commands/index.ts +2 -0
  40. package/src/commands/models.ts +3 -3
  41. package/src/commands/users.ts +2 -2
  42. package/src/constants/command-structure.ts +112 -58
  43. package/src/schemas/opencode-schema.json +991 -0
  44. package/src/services/api-key-service.ts +1 -1
  45. package/src/services/auth-service.ts +27 -25
  46. package/src/services/chat-service.ts +37 -44
  47. package/src/services/cluster-service.ts +5 -5
  48. package/src/services/collaborator-service.ts +3 -3
  49. package/src/services/flux-service.ts +2 -2
  50. package/src/services/helm-service.ts +2 -2
  51. package/src/services/kubectl-service.ts +3 -6
  52. package/src/types/api.d.ts +1032 -1010
  53. package/src/types/json.d.ts +3 -3
  54. package/src/utils/default-api-key.ts +54 -42
  55. package/src/utils/env-manager.ts +98 -0
  56. package/src/utils/error-handler.ts +24 -15
  57. package/src/utils/logger.ts +12 -12
  58. package/src/utils/markdown-renderer.ts +18 -18
  59. package/src/utils/opencode-validator.ts +134 -0
  60. package/src/utils/token-manager.ts +35 -23
  61. package/tests/commands/chat.test.ts +129 -0
  62. package/tests/commands/code.test.ts +505 -0
  63. package/tests/utils/env-manager.test.ts +199 -0
  64. package/tests/utils/opencode-validator.test.ts +118 -0
  65. package/tsconfig.json +8 -8
  66. package/vitest.config.ts +8 -0
  67. package/-27b-it +0 -0
@@ -2,7 +2,11 @@ import { Command } from 'commander'
2
2
  import chalk from 'chalk'
3
3
  import readline from 'readline'
4
4
  import { COMMAND_GROUPS, SUBCOMMANDS } from '../constants/command-structure'
5
- import { ChatService, ChatMessage, ChatCompletionOptions } from '../services/chat-service'
5
+ import {
6
+ ChatService,
7
+ ChatMessage,
8
+ ChatCompletionOptions,
9
+ } from '../services/chat-service'
6
10
  import { ApiKeyService } from '../services/api-key-service'
7
11
  import { AuthService } from '../services/auth-service'
8
12
  import { handleError } from '../utils/error-handler'
@@ -37,66 +41,75 @@ export function registerChatCommands(program: Command): void {
37
41
  chat
38
42
  .command(SUBCOMMANDS.CHAT.RUN)
39
43
  .description('Run a chat session with a specified model')
40
- .argument('[model]', 'Model to use (default: google/gemma-3-27b-it)')
44
+ .argument('[model]', 'Model to use (default: openai/gpt-oss)')
45
+ .argument('[message]', 'Message to send directly (skips interactive mode)')
41
46
  .option('-s, --system <message>', 'System message')
42
47
  .option('-t, --temperature <temp>', 'Temperature (0-1)', parseFloat)
43
48
  .option('-m, --max-tokens <tokens>', 'Maximum tokens to generate', parseInt)
44
49
  .option('-k, --api-key <key>', 'API key to use for this chat session')
45
50
  .option(
46
51
  '--api-key-id <id>',
47
- 'ID of the API key to use from your saved keys'
52
+ 'ID of the API key to use from your saved keys',
48
53
  )
49
- .option('--stream', 'Stream the response')
50
- .action(async (options) => {
54
+ .option(
55
+ '--no-stream',
56
+ 'Disable streaming (streaming is enabled by default)',
57
+ )
58
+ .action(async (model, message, options) => {
51
59
  try {
52
60
  const chatService = ChatService.getInstance()
53
61
 
54
62
  // Check if we have an API key or need to get one
55
63
  let apiKey = options.apiKey
56
64
  let apiKeyId = options.apiKeyId
57
-
65
+
58
66
  // Check for environment variable first
59
- const envApiKey = process.env.BERGET_API_KEY;
67
+ const envApiKey = process.env.BERGET_API_KEY
60
68
  if (envApiKey) {
61
69
  console.log(
62
- chalk.dim(`Using API key from BERGET_API_KEY environment variable`)
70
+ chalk.dim(`Using API key from BERGET_API_KEY environment variable`),
63
71
  )
64
- apiKey = envApiKey;
65
-
72
+ apiKey = envApiKey
73
+
66
74
  // Debug the API key (first few characters only)
67
75
  if (process.argv.includes('--debug')) {
68
76
  console.log(
69
- chalk.yellow(`DEBUG: API key from env starts with: ${envApiKey.substring(0, 4)}...`)
77
+ chalk.yellow(
78
+ `DEBUG: API key from env starts with: ${envApiKey.substring(0, 4)}...`,
79
+ ),
70
80
  )
71
81
  }
72
82
  }
73
83
  // If API key is already provided via command line, use it
74
84
  else if (options.apiKey) {
75
- console.log(
76
- chalk.dim(`Using API key from command line argument`)
77
- )
78
- apiKey = options.apiKey;
85
+ console.log(chalk.dim(`Using API key from command line argument`))
86
+ apiKey = options.apiKey
79
87
  }
80
88
  // If no API key or API key ID provided and no env var, check for default API key
81
89
  else if (!apiKey && !apiKeyId) {
82
90
  try {
83
91
  const defaultApiKeyManager = DefaultApiKeyManager.getInstance()
84
- const defaultApiKeyData = defaultApiKeyManager.getDefaultApiKeyData()
92
+ const defaultApiKeyData =
93
+ defaultApiKeyManager.getDefaultApiKeyData()
85
94
 
86
95
  if (defaultApiKeyData) {
87
96
  apiKeyId = defaultApiKeyData.id
88
97
  apiKey = defaultApiKeyData.key
89
-
98
+
90
99
  if (apiKey) {
91
100
  console.log(
92
- chalk.dim(`Using default API key: ${defaultApiKeyData.name}`)
101
+ chalk.dim(`Using default API key: ${defaultApiKeyData.name}`),
93
102
  )
94
103
  } else {
95
104
  console.log(
96
- chalk.yellow(`Default API key "${defaultApiKeyData.name}" exists but the key value is missing.`)
105
+ chalk.yellow(
106
+ `Default API key "${defaultApiKeyData.name}" exists but the key value is missing.`,
107
+ ),
97
108
  )
98
109
  console.log(
99
- chalk.yellow(`Try rotating the key with: berget api-keys rotate ${defaultApiKeyData.id}`)
110
+ chalk.yellow(
111
+ `Try rotating the key with: berget api-keys rotate ${defaultApiKeyData.id}`,
112
+ ),
100
113
  )
101
114
  }
102
115
  } else {
@@ -109,24 +122,24 @@ export function registerChatCommands(program: Command): void {
109
122
  if (!apiKey) {
110
123
  console.log(
111
124
  chalk.red(
112
- 'Error: An API key is required to use the chat command.'
113
- )
125
+ 'Error: An API key is required to use the chat command.',
126
+ ),
114
127
  )
115
128
  console.log(chalk.yellow('You can:'))
116
129
  console.log(
117
130
  chalk.yellow(
118
- '1. Create an API key with: berget api-keys create --name "My Key"'
119
- )
131
+ '1. Create an API key with: berget api-keys create --name "My Key"',
132
+ ),
120
133
  )
121
134
  console.log(
122
135
  chalk.yellow(
123
- '2. Set a default API key with: berget api-keys set-default <id>'
124
- )
136
+ '2. Set a default API key with: berget api-keys set-default <id>',
137
+ ),
125
138
  )
126
139
  console.log(
127
140
  chalk.yellow(
128
- '3. Provide an API key with the --api-key option'
129
- )
141
+ '3. Provide an API key with the --api-key option',
142
+ ),
130
143
  )
131
144
  return
132
145
  }
@@ -134,7 +147,7 @@ export function registerChatCommands(program: Command): void {
134
147
  } catch (error) {
135
148
  if (process.argv.includes('--debug')) {
136
149
  console.log(
137
- chalk.yellow('DEBUG: Error checking default API key:')
150
+ chalk.yellow('DEBUG: Error checking default API key:'),
138
151
  )
139
152
  console.log(chalk.yellow(String(error)))
140
153
  }
@@ -147,14 +160,14 @@ export function registerChatCommands(program: Command): void {
147
160
  const apiKeyService = ApiKeyService.getInstance()
148
161
  const keys = await apiKeyService.list()
149
162
  const selectedKey = keys.find(
150
- (key) => key.id.toString() === options.apiKeyId
163
+ (key) => key.id.toString() === options.apiKeyId,
151
164
  )
152
165
 
153
166
  if (!selectedKey) {
154
167
  console.log(
155
168
  chalk.yellow(
156
- `API key with ID ${options.apiKeyId} not found. Using default authentication.`
157
- )
169
+ `API key with ID ${options.apiKeyId} not found. Using default authentication.`,
170
+ ),
158
171
  )
159
172
  } else {
160
173
  console.log(chalk.dim(`Using API key: ${selectedKey.name}`))
@@ -163,37 +176,43 @@ export function registerChatCommands(program: Command): void {
163
176
  if (
164
177
  await confirm(
165
178
  chalk.yellow(
166
- `To use API key "${selectedKey.name}", it needs to be rotated. This will invalidate the current key. Continue? (y/n)`
167
- )
179
+ `To use API key "${selectedKey.name}", it needs to be rotated. This will invalidate the current key. Continue? (y/n)`,
180
+ ),
168
181
  )
169
182
  ) {
170
183
  const rotatedKey = await apiKeyService.rotate(options.apiKeyId)
171
184
  apiKey = rotatedKey.key
172
185
  console.log(
173
186
  chalk.green(
174
- `API key "${selectedKey.name}" rotated successfully.`
175
- )
187
+ `API key "${selectedKey.name}" rotated successfully.`,
188
+ ),
176
189
  )
177
190
  } else {
178
191
  console.log(
179
- chalk.yellow('Using default authentication instead.')
192
+ chalk.yellow('Using default authentication instead.'),
180
193
  )
181
194
  }
182
195
  }
183
196
  } catch (error) {
184
197
  // Check if this is an authentication error
185
- const errorMessage = error instanceof Error ? error.message : String(error);
186
- const isAuthError = errorMessage.includes('Unauthorized') ||
187
- errorMessage.includes('Authentication failed') ||
188
- errorMessage.includes('AUTH_FAILED');
189
-
198
+ const errorMessage =
199
+ error instanceof Error ? error.message : String(error)
200
+ const isAuthError =
201
+ errorMessage.includes('Unauthorized') ||
202
+ errorMessage.includes('Authentication failed') ||
203
+ errorMessage.includes('AUTH_FAILED')
204
+
190
205
  if (isAuthError) {
191
- console.log(chalk.yellow('Authentication required. Please run `berget auth login` first.'));
206
+ console.log(
207
+ chalk.yellow(
208
+ 'Authentication required. Please run `berget auth login` first.',
209
+ ),
210
+ )
192
211
  } else {
193
- console.error(chalk.red('Error fetching API key:'));
194
- console.error(error);
212
+ console.error(chalk.red('Error fetching API key:'))
213
+ console.error(error)
195
214
  }
196
- console.log(chalk.yellow('Using default authentication instead.'));
215
+ console.log(chalk.yellow('Using default authentication instead.'))
197
216
  }
198
217
  }
199
218
 
@@ -207,23 +226,17 @@ export function registerChatCommands(program: Command): void {
207
226
  console.log(chalk.yellow('1. Log in with `berget auth login`'))
208
227
  console.log(chalk.yellow('2. Provide an API key with `--api-key`'))
209
228
  console.log(
210
- chalk.yellow('3. Provide an API key ID with `--api-key-id`')
229
+ chalk.yellow('3. Provide an API key ID with `--api-key-id`'),
211
230
  )
212
231
  console.log(
213
232
  chalk.yellow(
214
- '4. Set a default API key with `berget api-keys set-default <id>`'
215
- )
233
+ '4. Set a default API key with `berget api-keys set-default <id>`',
234
+ ),
216
235
  )
217
236
  return
218
237
  }
219
238
  }
220
239
 
221
- // Set up readline interface for user input
222
- const rl = readline.createInterface({
223
- input: process.stdin,
224
- output: process.stdout,
225
- })
226
-
227
240
  // Prepare messages array
228
241
  const messages: ChatMessage[] = []
229
242
 
@@ -235,6 +248,151 @@ export function registerChatCommands(program: Command): void {
235
248
  })
236
249
  }
237
250
 
251
+ // Check if input is being piped in
252
+ let inputMessage = message
253
+ let stdinContent = ''
254
+
255
+ if (!process.stdin.isTTY) {
256
+ // Read from stdin (piped input)
257
+ const chunks = []
258
+ for await (const chunk of process.stdin) {
259
+ chunks.push(chunk)
260
+ }
261
+ stdinContent = Buffer.concat(chunks).toString('utf8').trim()
262
+ }
263
+
264
+ // Combine stdin content with message if both exist
265
+ if (stdinContent && message) {
266
+ inputMessage = `${stdinContent}\n\n${message}`
267
+ } else if (stdinContent && !message) {
268
+ inputMessage = stdinContent
269
+ }
270
+
271
+ // If a message is provided (either as argument, from stdin, or both), send it directly and exit
272
+ if (inputMessage) {
273
+ // Add user message
274
+ messages.push({
275
+ role: 'user',
276
+ content: inputMessage,
277
+ })
278
+
279
+ try {
280
+ // Call the API
281
+ const completionOptions: ChatCompletionOptions = {
282
+ model: model || 'openai/gpt-oss',
283
+ messages: messages,
284
+ temperature:
285
+ options.temperature !== undefined ? options.temperature : 0.7,
286
+ max_tokens: options.maxTokens || 4096,
287
+ stream: options.stream !== false,
288
+ }
289
+
290
+ // Only add apiKey if it actually exists
291
+ if (apiKey) {
292
+ completionOptions.apiKey = apiKey
293
+ }
294
+
295
+ // Add streaming support (now default)
296
+ if (completionOptions.stream) {
297
+ let assistantResponse = ''
298
+
299
+ // Stream the response in real-time
300
+ completionOptions.onChunk = (chunk: any) => {
301
+ if (
302
+ chunk.choices &&
303
+ chunk.choices[0] &&
304
+ chunk.choices[0].delta &&
305
+ chunk.choices[0].delta.content
306
+ ) {
307
+ const content = chunk.choices[0].delta.content
308
+ try {
309
+ process.stdout.write(content)
310
+ } catch (error: any) {
311
+ // Handle EPIPE errors gracefully (when pipe is closed)
312
+ if (error.code === 'EPIPE') {
313
+ // Stop streaming if the pipe is closed
314
+ return
315
+ }
316
+ throw error
317
+ }
318
+ assistantResponse += content
319
+ }
320
+ }
321
+
322
+ try {
323
+ await chatService.createCompletion(completionOptions)
324
+ } catch (streamError) {
325
+ console.error(chalk.red('\nStreaming error:'), streamError)
326
+
327
+ // Fallback to non-streaming if streaming fails
328
+ console.log(
329
+ chalk.yellow('Falling back to non-streaming mode...'),
330
+ )
331
+ completionOptions.stream = false
332
+ delete completionOptions.onChunk
333
+
334
+ const response =
335
+ await chatService.createCompletion(completionOptions)
336
+
337
+ if (
338
+ response &&
339
+ response.choices &&
340
+ response.choices[0] &&
341
+ response.choices[0].message
342
+ ) {
343
+ assistantResponse = response.choices[0].message.content
344
+ console.log(assistantResponse)
345
+ }
346
+ }
347
+ console.log() // Add newline at the end
348
+ return
349
+ }
350
+
351
+ const response =
352
+ await chatService.createCompletion(completionOptions)
353
+
354
+ // Check if response has the expected structure
355
+ if (
356
+ !response ||
357
+ !response.choices ||
358
+ !response.choices[0] ||
359
+ !response.choices[0].message
360
+ ) {
361
+ console.error(
362
+ chalk.red('Error: Unexpected response format from API'),
363
+ )
364
+ console.error(
365
+ chalk.red('Response:', JSON.stringify(response, null, 2)),
366
+ )
367
+ throw new Error('Unexpected response format from API')
368
+ }
369
+
370
+ // Get assistant's response
371
+ const assistantMessage = response.choices[0].message.content
372
+
373
+ // Display the response
374
+ if (containsMarkdown(assistantMessage)) {
375
+ console.log(renderMarkdown(assistantMessage))
376
+ } else {
377
+ console.log(assistantMessage)
378
+ }
379
+
380
+ return
381
+ } catch (error) {
382
+ console.error(chalk.red('Error: Failed to get response'))
383
+ if (error instanceof Error) {
384
+ console.error(chalk.red(error.message))
385
+ }
386
+ process.exit(1)
387
+ }
388
+ }
389
+
390
+ // Set up readline interface for user input (only for interactive mode)
391
+ const rl = readline.createInterface({
392
+ input: process.stdin,
393
+ output: process.stdout,
394
+ })
395
+
238
396
  console.log(chalk.cyan('Chat with Berget AI (type "exit" to quit)'))
239
397
  console.log(chalk.cyan('----------------------------------------'))
240
398
 
@@ -257,51 +415,87 @@ export function registerChatCommands(program: Command): void {
257
415
  try {
258
416
  // Call the API
259
417
  const completionOptions: ChatCompletionOptions = {
260
- model: options.args?.[0] || 'google/gemma-3-27b-it',
418
+ model: model || 'openai/gpt-oss',
261
419
  messages: messages,
262
420
  temperature:
263
421
  options.temperature !== undefined ? options.temperature : 0.7,
264
422
  max_tokens: options.maxTokens || 4096,
265
- stream: options.stream || false
423
+ stream: options.stream !== false,
266
424
  }
267
425
 
268
426
  // Only add apiKey if it actually exists
269
427
  if (apiKey) {
270
428
  completionOptions.apiKey = apiKey
271
429
  }
272
-
273
- // Add streaming support
274
- if (options.stream) {
430
+
431
+ // Add streaming support (now default)
432
+ if (completionOptions.stream) {
275
433
  let assistantResponse = ''
276
434
  console.log(chalk.blue('Assistant: '))
277
-
278
- // For streaming, we'll collect the response and render it at the end
279
- // since markdown needs the complete text to render properly
435
+
436
+ // Stream the response in real-time
280
437
  completionOptions.onChunk = (chunk: any) => {
281
- if (chunk.choices && chunk.choices[0] && chunk.choices[0].delta && chunk.choices[0].delta.content) {
438
+ if (
439
+ chunk.choices &&
440
+ chunk.choices[0] &&
441
+ chunk.choices[0].delta &&
442
+ chunk.choices[0].delta.content
443
+ ) {
282
444
  const content = chunk.choices[0].delta.content
283
- process.stdout.write(content)
445
+ try {
446
+ process.stdout.write(content)
447
+ } catch (error: any) {
448
+ // Handle EPIPE errors gracefully (when pipe is closed)
449
+ if (error.code === 'EPIPE') {
450
+ // Stop streaming if the pipe is closed
451
+ return
452
+ }
453
+ throw error
454
+ }
284
455
  assistantResponse += content
285
456
  }
286
457
  }
287
-
288
- await chatService.createCompletion(completionOptions)
458
+
459
+ try {
460
+ await chatService.createCompletion(completionOptions)
461
+ } catch (streamError) {
462
+ console.error(chalk.red('\nStreaming error:'), streamError)
463
+
464
+ // Fallback to non-streaming if streaming fails
465
+ console.log(
466
+ chalk.yellow('Falling back to non-streaming mode...'),
467
+ )
468
+ completionOptions.stream = false
469
+ delete completionOptions.onChunk
470
+
471
+ const response =
472
+ await chatService.createCompletion(completionOptions)
473
+
474
+ if (
475
+ response &&
476
+ response.choices &&
477
+ response.choices[0] &&
478
+ response.choices[0].message
479
+ ) {
480
+ assistantResponse = response.choices[0].message.content
481
+ console.log(assistantResponse)
482
+ }
483
+ }
289
484
  console.log('\n')
290
-
485
+
291
486
  // Add assistant response to messages
292
487
  messages.push({
293
488
  role: 'assistant',
294
- content: assistantResponse
489
+ content: assistantResponse,
295
490
  })
296
-
491
+
297
492
  // Continue the conversation
298
493
  askQuestion()
299
494
  return
300
495
  }
301
-
302
- const response = await chatService.createCompletion(
303
- completionOptions
304
- )
496
+
497
+ const response =
498
+ await chatService.createCompletion(completionOptions)
305
499
 
306
500
  // Debug output
307
501
  if (program.opts().debug) {
@@ -317,10 +511,10 @@ export function registerChatCommands(program: Command): void {
317
511
  !response.choices[0].message
318
512
  ) {
319
513
  console.error(
320
- chalk.red('Error: Unexpected response format from API')
514
+ chalk.red('Error: Unexpected response format from API'),
321
515
  )
322
516
  console.error(
323
- chalk.red('Response:', JSON.stringify(response, null, 2))
517
+ chalk.red('Response:', JSON.stringify(response, null, 2)),
324
518
  )
325
519
  throw new Error('Unexpected response format from API')
326
520
  }
@@ -336,14 +530,14 @@ export function registerChatCommands(program: Command): void {
336
530
 
337
531
  // Display the response
338
532
  console.log(chalk.blue('Assistant: '))
339
-
533
+
340
534
  // Check if the response contains markdown and render it if it does
341
535
  if (containsMarkdown(assistantMessage)) {
342
536
  console.log(renderMarkdown(assistantMessage))
343
537
  } else {
344
538
  console.log(assistantMessage)
345
539
  }
346
-
540
+
347
541
  console.log() // Empty line for better readability
348
542
 
349
543
  // Continue the conversation
@@ -372,7 +566,7 @@ export function registerChatCommands(program: Command): void {
372
566
  .option('-k, --api-key <key>', 'API key to use for this request')
373
567
  .option(
374
568
  '--api-key-id <id>',
375
- 'ID of the API key to use from your saved keys'
569
+ 'ID of the API key to use from your saved keys',
376
570
  )
377
571
  .action(async (options) => {
378
572
  try {
@@ -388,7 +582,7 @@ export function registerChatCommands(program: Command): void {
388
582
  if (defaultApiKeyData) {
389
583
  apiKeyId = defaultApiKeyData.id
390
584
  console.log(
391
- chalk.dim(`Using default API key: ${defaultApiKeyData.name}`)
585
+ chalk.dim(`Using default API key: ${defaultApiKeyData.name}`),
392
586
  )
393
587
  }
394
588
  }
@@ -398,14 +592,14 @@ export function registerChatCommands(program: Command): void {
398
592
  const apiKeyService = ApiKeyService.getInstance()
399
593
  const keys = await apiKeyService.list()
400
594
  const selectedKey = keys.find(
401
- (key) => key.id.toString() === options.apiKeyId
595
+ (key) => key.id.toString() === options.apiKeyId,
402
596
  )
403
597
 
404
598
  if (!selectedKey) {
405
599
  console.log(
406
600
  chalk.yellow(
407
- `API key with ID ${options.apiKeyId} not found. Using default authentication.`
408
- )
601
+ `API key with ID ${options.apiKeyId} not found. Using default authentication.`,
602
+ ),
409
603
  )
410
604
  } else {
411
605
  console.log(chalk.dim(`Using API key: ${selectedKey.name}`))
@@ -414,20 +608,20 @@ export function registerChatCommands(program: Command): void {
414
608
  if (
415
609
  await confirm(
416
610
  chalk.yellow(
417
- `To use API key "${selectedKey.name}", it needs to be rotated. This will invalidate the current key. Continue? (y/n)`
418
- )
611
+ `To use API key "${selectedKey.name}", it needs to be rotated. This will invalidate the current key. Continue? (y/n)`,
612
+ ),
419
613
  )
420
614
  ) {
421
615
  const rotatedKey = await apiKeyService.rotate(options.apiKeyId)
422
616
  apiKey = rotatedKey.key
423
617
  console.log(
424
618
  chalk.green(
425
- `API key "${selectedKey.name}" rotated successfully.`
426
- )
619
+ `API key "${selectedKey.name}" rotated successfully.`,
620
+ ),
427
621
  )
428
622
  } else {
429
623
  console.log(
430
- chalk.yellow('Using default authentication instead.')
624
+ chalk.yellow('Using default authentication instead.'),
431
625
  )
432
626
  }
433
627
  }
@@ -450,14 +644,15 @@ export function registerChatCommands(program: Command): void {
450
644
  console.log(chalk.bold('Available Chat Models:'))
451
645
  console.log(chalk.dim('─'.repeat(70)))
452
646
  console.log(
453
- chalk.dim('MODEL ID'.padEnd(40)) +
454
- chalk.dim('CAPABILITIES')
647
+ chalk.dim('MODEL ID'.padEnd(40)) + chalk.dim('CAPABILITIES'),
455
648
  )
456
649
  console.log(chalk.dim('─'.repeat(70)))
457
650
 
458
651
  // Filter to only show active models
459
- const activeModels = models.data.filter((model: any) => model.active === true);
460
-
652
+ const activeModels = models.data.filter(
653
+ (model: any) => model.active === true,
654
+ )
655
+
461
656
  activeModels.forEach((model: any) => {
462
657
  const capabilities = []
463
658
  if (model.capabilities.vision) capabilities.push('vision')
@@ -466,12 +661,11 @@ export function registerChatCommands(program: Command): void {
466
661
  if (model.capabilities.json_mode) capabilities.push('json_mode')
467
662
 
468
663
  // Format model ID in Huggingface compatible format (owner/model)
469
- const modelId = `${model.owned_by.toLowerCase()}/${model.id}`.padEnd(40)
470
-
471
- console.log(
472
- modelId +
473
- capabilities.join(', ')
664
+ const modelId = `${model.owned_by.toLowerCase()}/${model.id}`.padEnd(
665
+ 40,
474
666
  )
667
+
668
+ console.log(modelId + capabilities.join(', '))
475
669
  })
476
670
  } catch (error) {
477
671
  handleError('Failed to list chat models', error)
@@ -22,8 +22,8 @@ export function registerClusterCommands(program: Command): void {
22
22
  clusters.forEach((cluster: Cluster) => {
23
23
  console.log(
24
24
  `${cluster.name.padEnd(22)} ${cluster.status.padEnd(9)} ${String(
25
- cluster.nodes
26
- ).padEnd(8)} ${cluster.created}`
25
+ cluster.nodes,
26
+ ).padEnd(8)} ${cluster.created}`,
27
27
  )
28
28
  })
29
29
  } catch (error) {