berget 2.2.6 → 2.2.8

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