berget 1.3.0 → 1.4.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.
@@ -8,6 +8,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
12
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
13
+ var m = o[Symbol.asyncIterator], i;
14
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
15
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
16
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
17
+ };
11
18
  var __importDefault = (this && this.__importDefault) || function (mod) {
12
19
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
20
  };
@@ -21,6 +28,7 @@ const api_key_service_1 = require("../services/api-key-service");
21
28
  const auth_service_1 = require("../services/auth-service");
22
29
  const error_handler_1 = require("../utils/error-handler");
23
30
  const default_api_key_1 = require("../utils/default-api-key");
31
+ const markdown_renderer_1 = require("../utils/markdown-renderer");
24
32
  /**
25
33
  * Helper function to get user confirmation
26
34
  */
@@ -48,14 +56,16 @@ function registerChatCommands(program) {
48
56
  chat
49
57
  .command(command_structure_1.SUBCOMMANDS.CHAT.RUN)
50
58
  .description('Run a chat session with a specified model')
51
- .argument('[model]', 'Model to use (default: google/gemma-3-27b-it)')
59
+ .argument('[model]', 'Model to use (default: openai/gpt-oss)')
60
+ .argument('[message]', 'Message to send directly (skips interactive mode)')
52
61
  .option('-s, --system <message>', 'System message')
53
62
  .option('-t, --temperature <temp>', 'Temperature (0-1)', parseFloat)
54
63
  .option('-m, --max-tokens <tokens>', 'Maximum tokens to generate', parseInt)
55
64
  .option('-k, --api-key <key>', 'API key to use for this chat session')
56
65
  .option('--api-key-id <id>', 'ID of the API key to use from your saved keys')
57
- .option('--stream', 'Stream the response')
58
- .action((options) => __awaiter(this, void 0, void 0, function* () {
66
+ .option('--no-stream', 'Disable streaming (streaming is enabled by default)')
67
+ .action((model, message, options) => __awaiter(this, void 0, void 0, function* () {
68
+ var _a, e_1, _b, _c;
59
69
  try {
60
70
  const chatService = chat_service_1.ChatService.getInstance();
61
71
  // Check if we have an API key or need to get one
@@ -167,11 +177,6 @@ function registerChatCommands(program) {
167
177
  return;
168
178
  }
169
179
  }
170
- // Set up readline interface for user input
171
- const rl = readline_1.default.createInterface({
172
- input: process.stdin,
173
- output: process.stdout,
174
- });
175
180
  // Prepare messages array
176
181
  const messages = [];
177
182
  // Add system message if provided
@@ -181,12 +186,134 @@ function registerChatCommands(program) {
181
186
  content: options.system,
182
187
  });
183
188
  }
189
+ // Check if input is being piped in
190
+ let inputMessage = message;
191
+ let stdinContent = '';
192
+ if (!process.stdin.isTTY) {
193
+ // Read from stdin (piped input)
194
+ const chunks = [];
195
+ try {
196
+ for (var _d = true, _e = __asyncValues(process.stdin), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
197
+ _c = _f.value;
198
+ _d = false;
199
+ const chunk = _c;
200
+ chunks.push(chunk);
201
+ }
202
+ }
203
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
204
+ finally {
205
+ try {
206
+ if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
207
+ }
208
+ finally { if (e_1) throw e_1.error; }
209
+ }
210
+ stdinContent = Buffer.concat(chunks).toString('utf8').trim();
211
+ }
212
+ // Combine stdin content with message if both exist
213
+ if (stdinContent && message) {
214
+ inputMessage = `${stdinContent}\n\n${message}`;
215
+ }
216
+ else if (stdinContent && !message) {
217
+ inputMessage = stdinContent;
218
+ }
219
+ // If a message is provided (either as argument, from stdin, or both), send it directly and exit
220
+ if (inputMessage) {
221
+ // Add user message
222
+ messages.push({
223
+ role: 'user',
224
+ content: inputMessage,
225
+ });
226
+ try {
227
+ // Call the API
228
+ const completionOptions = {
229
+ model: model || 'openai/gpt-oss',
230
+ messages: messages,
231
+ temperature: options.temperature !== undefined ? options.temperature : 0.7,
232
+ max_tokens: options.maxTokens || 4096,
233
+ stream: options.stream !== false
234
+ };
235
+ // Only add apiKey if it actually exists
236
+ if (apiKey) {
237
+ completionOptions.apiKey = apiKey;
238
+ }
239
+ // Add streaming support (now default)
240
+ if (completionOptions.stream) {
241
+ let assistantResponse = '';
242
+ // Stream the response in real-time
243
+ completionOptions.onChunk = (chunk) => {
244
+ if (chunk.choices && chunk.choices[0] && chunk.choices[0].delta && chunk.choices[0].delta.content) {
245
+ const content = chunk.choices[0].delta.content;
246
+ try {
247
+ process.stdout.write(content);
248
+ }
249
+ catch (error) {
250
+ // Handle EPIPE errors gracefully (when pipe is closed)
251
+ if (error.code === 'EPIPE') {
252
+ // Stop streaming if the pipe is closed
253
+ return;
254
+ }
255
+ throw error;
256
+ }
257
+ assistantResponse += content;
258
+ }
259
+ };
260
+ try {
261
+ yield chatService.createCompletion(completionOptions);
262
+ }
263
+ catch (streamError) {
264
+ console.error(chalk_1.default.red('\nStreaming error:'), streamError);
265
+ // Fallback to non-streaming if streaming fails
266
+ console.log(chalk_1.default.yellow('Falling back to non-streaming mode...'));
267
+ completionOptions.stream = false;
268
+ delete completionOptions.onChunk;
269
+ const response = yield chatService.createCompletion(completionOptions);
270
+ if (response && response.choices && response.choices[0] && response.choices[0].message) {
271
+ assistantResponse = response.choices[0].message.content;
272
+ console.log(assistantResponse);
273
+ }
274
+ }
275
+ console.log(); // Add newline at the end
276
+ return;
277
+ }
278
+ const response = yield chatService.createCompletion(completionOptions);
279
+ // Check if response has the expected structure
280
+ if (!response ||
281
+ !response.choices ||
282
+ !response.choices[0] ||
283
+ !response.choices[0].message) {
284
+ console.error(chalk_1.default.red('Error: Unexpected response format from API'));
285
+ console.error(chalk_1.default.red('Response:', JSON.stringify(response, null, 2)));
286
+ throw new Error('Unexpected response format from API');
287
+ }
288
+ // Get assistant's response
289
+ const assistantMessage = response.choices[0].message.content;
290
+ // Display the response
291
+ if ((0, markdown_renderer_1.containsMarkdown)(assistantMessage)) {
292
+ console.log((0, markdown_renderer_1.renderMarkdown)(assistantMessage));
293
+ }
294
+ else {
295
+ console.log(assistantMessage);
296
+ }
297
+ return;
298
+ }
299
+ catch (error) {
300
+ console.error(chalk_1.default.red('Error: Failed to get response'));
301
+ if (error instanceof Error) {
302
+ console.error(chalk_1.default.red(error.message));
303
+ }
304
+ process.exit(1);
305
+ }
306
+ }
307
+ // Set up readline interface for user input (only for interactive mode)
308
+ const rl = readline_1.default.createInterface({
309
+ input: process.stdin,
310
+ output: process.stdout,
311
+ });
184
312
  console.log(chalk_1.default.cyan('Chat with Berget AI (type "exit" to quit)'));
185
313
  console.log(chalk_1.default.cyan('----------------------------------------'));
186
314
  // Start the conversation loop
187
315
  const askQuestion = () => {
188
316
  rl.question(chalk_1.default.green('You: '), (input) => __awaiter(this, void 0, void 0, function* () {
189
- var _a;
190
317
  // Check if user wants to exit
191
318
  if (input.toLowerCase() === 'exit') {
192
319
  console.log(chalk_1.default.cyan('Goodbye!'));
@@ -201,28 +328,53 @@ function registerChatCommands(program) {
201
328
  try {
202
329
  // Call the API
203
330
  const completionOptions = {
204
- model: ((_a = options.args) === null || _a === void 0 ? void 0 : _a[0]) || 'google/gemma-3-27b-it',
331
+ model: model || 'openai/gpt-oss',
205
332
  messages: messages,
206
333
  temperature: options.temperature !== undefined ? options.temperature : 0.7,
207
334
  max_tokens: options.maxTokens || 4096,
208
- stream: options.stream || false
335
+ stream: options.stream !== false
209
336
  };
210
337
  // Only add apiKey if it actually exists
211
338
  if (apiKey) {
212
339
  completionOptions.apiKey = apiKey;
213
340
  }
214
- // Add streaming support
215
- if (options.stream) {
341
+ // Add streaming support (now default)
342
+ if (completionOptions.stream) {
216
343
  let assistantResponse = '';
217
- process.stdout.write(chalk_1.default.blue('Assistant: '));
344
+ console.log(chalk_1.default.blue('Assistant: '));
345
+ // Stream the response in real-time
218
346
  completionOptions.onChunk = (chunk) => {
219
347
  if (chunk.choices && chunk.choices[0] && chunk.choices[0].delta && chunk.choices[0].delta.content) {
220
348
  const content = chunk.choices[0].delta.content;
221
- process.stdout.write(content);
349
+ try {
350
+ process.stdout.write(content);
351
+ }
352
+ catch (error) {
353
+ // Handle EPIPE errors gracefully (when pipe is closed)
354
+ if (error.code === 'EPIPE') {
355
+ // Stop streaming if the pipe is closed
356
+ return;
357
+ }
358
+ throw error;
359
+ }
222
360
  assistantResponse += content;
223
361
  }
224
362
  };
225
- yield chatService.createCompletion(completionOptions);
363
+ try {
364
+ yield chatService.createCompletion(completionOptions);
365
+ }
366
+ catch (streamError) {
367
+ console.error(chalk_1.default.red('\nStreaming error:'), streamError);
368
+ // Fallback to non-streaming if streaming fails
369
+ console.log(chalk_1.default.yellow('Falling back to non-streaming mode...'));
370
+ completionOptions.stream = false;
371
+ delete completionOptions.onChunk;
372
+ const response = yield chatService.createCompletion(completionOptions);
373
+ if (response && response.choices && response.choices[0] && response.choices[0].message) {
374
+ assistantResponse = response.choices[0].message.content;
375
+ console.log(assistantResponse);
376
+ }
377
+ }
226
378
  console.log('\n');
227
379
  // Add assistant response to messages
228
380
  messages.push({
@@ -256,7 +408,14 @@ function registerChatCommands(program) {
256
408
  content: assistantMessage,
257
409
  });
258
410
  // Display the response
259
- console.log(chalk_1.default.blue('Assistant: ') + assistantMessage);
411
+ console.log(chalk_1.default.blue('Assistant: '));
412
+ // Check if the response contains markdown and render it if it does
413
+ if ((0, markdown_renderer_1.containsMarkdown)(assistantMessage)) {
414
+ console.log((0, markdown_renderer_1.renderMarkdown)(assistantMessage));
415
+ }
416
+ else {
417
+ console.log(assistantMessage);
418
+ }
260
419
  console.log(); // Empty line for better readability
261
420
  // Continue the conversation
262
421
  askQuestion();
@@ -333,11 +492,12 @@ function registerChatCommands(program) {
333
492
  }
334
493
  console.log(chalk_1.default.bold('Available Chat Models:'));
335
494
  console.log(chalk_1.default.dim('─'.repeat(70)));
336
- console.log(chalk_1.default.dim('MODEL ID'.padEnd(30)) +
337
- chalk_1.default.dim('OWNER'.padEnd(25)) +
495
+ console.log(chalk_1.default.dim('MODEL ID'.padEnd(40)) +
338
496
  chalk_1.default.dim('CAPABILITIES'));
339
497
  console.log(chalk_1.default.dim('─'.repeat(70)));
340
- models.data.forEach((model) => {
498
+ // Filter to only show active models
499
+ const activeModels = models.data.filter((model) => model.active === true);
500
+ activeModels.forEach((model) => {
341
501
  const capabilities = [];
342
502
  if (model.capabilities.vision)
343
503
  capabilities.push('vision');
@@ -345,8 +505,9 @@ function registerChatCommands(program) {
345
505
  capabilities.push('function_calling');
346
506
  if (model.capabilities.json_mode)
347
507
  capabilities.push('json_mode');
348
- console.log(model.id.padEnd(30) +
349
- model.owned_by.padEnd(25) +
508
+ // Format model ID in Huggingface compatible format (owner/model)
509
+ const modelId = `${model.owned_by.toLowerCase()}/${model.id}`.padEnd(40);
510
+ console.log(modelId +
350
511
  capabilities.join(', '));
351
512
  });
352
513
  }
@@ -44,7 +44,7 @@ function registerModelCommands(program) {
44
44
  throw new Error(JSON.stringify(error));
45
45
  response = data;
46
46
  console.log('Available Models:');
47
- console.log('ID OWNED BY CAPABILITIES');
47
+ console.log('ID OWNED BY CAPABILITIES');
48
48
  // Ensure response has the expected structure
49
49
  const modelData = response;
50
50
  if (modelData.data) {
@@ -56,7 +56,7 @@ function registerModelCommands(program) {
56
56
  capabilities.push('function_calling');
57
57
  if (model.capabilities.json_mode)
58
58
  capabilities.push('json_mode');
59
- console.log(`${model.id.padEnd(24)} ${model.owned_by.padEnd(25)} ${capabilities.join(', ')}`);
59
+ console.log(`${model.root.padEnd(50)} ${model.owned_by.padEnd(24)} ${capabilities.join(', ')}`);
60
60
  });
61
61
  }
62
62
  }
@@ -256,26 +256,26 @@ class ChatService {
256
256
  */
257
257
  handleStreamingResponse(options, headers) {
258
258
  return __awaiter(this, void 0, void 0, function* () {
259
- logger_1.logger.debug('Handling streaming response');
260
- // Create URL with query parameters
261
- const url = new URL(`${client_1.API_BASE_URL}/v1/chat/completions`);
262
- // Debug the headers and options
263
- logger_1.logger.debug('Streaming headers:');
264
- logger_1.logger.debug(JSON.stringify(headers, null, 2));
265
- logger_1.logger.debug('Streaming options:');
266
- logger_1.logger.debug(JSON.stringify(Object.assign(Object.assign({}, options), { onChunk: options.onChunk ? 'function present' : 'no function' }), null, 2));
259
+ // Use the same base URL as the client
260
+ const baseUrl = process.env.API_BASE_URL || 'https://api.berget.ai';
261
+ const url = new URL(`${baseUrl}/v1/chat/completions`);
267
262
  try {
263
+ logger_1.logger.debug(`Making streaming request to: ${url.toString()}`);
264
+ logger_1.logger.debug(`Headers:`, JSON.stringify(headers, null, 2));
265
+ logger_1.logger.debug(`Body:`, JSON.stringify(options, null, 2));
268
266
  // Make fetch request directly to handle streaming
269
267
  const response = yield fetch(url.toString(), {
270
268
  method: 'POST',
271
269
  headers: Object.assign({ 'Content-Type': 'application/json', Accept: 'text/event-stream' }, headers),
272
270
  body: JSON.stringify(options),
273
271
  });
272
+ logger_1.logger.debug(`Response status: ${response.status}`);
273
+ logger_1.logger.debug(`Response headers:`, JSON.stringify(Object.fromEntries(response.headers.entries()), null, 2));
274
274
  if (!response.ok) {
275
275
  const errorText = yield response.text();
276
276
  logger_1.logger.error(`Stream request failed: ${response.status} ${response.statusText}`);
277
- logger_1.logger.debug(`Error response: ${errorText}`);
278
- throw new Error(`Stream request failed: ${response.status} ${response.statusText}`);
277
+ logger_1.logger.error(`Error response: ${errorText}`);
278
+ throw new Error(`Stream request failed: ${response.status} ${response.statusText} - ${errorText}`);
279
279
  }
280
280
  if (!response.body) {
281
281
  throw new Error('No response body received');
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.containsMarkdown = exports.renderMarkdown = void 0;
7
+ const marked_1 = require("marked");
8
+ const marked_terminal_1 = __importDefault(require("marked-terminal"));
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ // Configure marked to use the terminal renderer
11
+ marked_1.marked.setOptions({
12
+ renderer: new marked_terminal_1.default({
13
+ // Customize the rendering options
14
+ code: chalk_1.default.cyan,
15
+ blockquote: chalk_1.default.gray.italic,
16
+ table: chalk_1.default.white,
17
+ listitem: chalk_1.default.yellow,
18
+ strong: chalk_1.default.bold,
19
+ em: chalk_1.default.italic,
20
+ heading: chalk_1.default.bold.blueBright,
21
+ hr: chalk_1.default.gray,
22
+ link: chalk_1.default.blue.underline,
23
+ // Adjust the width to fit the terminal
24
+ width: process.stdout.columns || 80,
25
+ // Customize code block rendering
26
+ codespan: chalk_1.default.cyan
27
+ })
28
+ });
29
+ /**
30
+ * Render markdown text to terminal-friendly formatted text
31
+ * @param markdown The markdown text to render
32
+ * @returns Formatted text for terminal display
33
+ */
34
+ function renderMarkdown(markdown) {
35
+ if (!markdown)
36
+ return '';
37
+ try {
38
+ // Convert markdown to terminal-friendly text
39
+ return (0, marked_1.marked)(markdown);
40
+ }
41
+ catch (error) {
42
+ // If rendering fails, return the original text
43
+ console.error(`Error rendering markdown: ${error}`);
44
+ return markdown;
45
+ }
46
+ }
47
+ exports.renderMarkdown = renderMarkdown;
48
+ /**
49
+ * Check if a string contains markdown formatting
50
+ * @param text The text to check
51
+ * @returns True if the text contains markdown formatting
52
+ */
53
+ function containsMarkdown(text) {
54
+ if (!text)
55
+ return false;
56
+ // Check for common markdown patterns
57
+ const markdownPatterns = [
58
+ /^#+\s+/m, // Headers
59
+ /\*\*.*?\*\*/, // Bold
60
+ /\*.*?\*/, // Italic
61
+ /`.*?`/, // Inline code
62
+ /```[\s\S]*?```/, // Code blocks
63
+ /\[.*?\]\(.*?\)/, // Links
64
+ /^\s*[-*+]\s+/m, // Lists
65
+ /^\s*\d+\.\s+/m, // Numbered lists
66
+ /^\s*>\s+/m, // Blockquotes
67
+ /\|.*\|.*\|/, // Tables
68
+ /^---+$/m, // Horizontal rules
69
+ /^===+$/m // Alternative headers
70
+ ];
71
+ return markdownPatterns.some(pattern => pattern.test(text));
72
+ }
73
+ exports.containsMarkdown = containsMarkdown;
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const vitest_1 = require("vitest");
13
+ const commander_1 = require("commander");
14
+ const chat_1 = require("../../src/commands/chat");
15
+ const chat_service_1 = require("../../src/services/chat-service");
16
+ const default_api_key_1 = require("../../src/utils/default-api-key");
17
+ // Mock dependencies
18
+ vitest_1.vi.mock('../../src/services/chat-service');
19
+ vitest_1.vi.mock('../../src/utils/default-api-key');
20
+ vitest_1.vi.mock('readline', () => ({
21
+ createInterface: vitest_1.vi.fn(() => ({
22
+ question: vitest_1.vi.fn(),
23
+ close: vitest_1.vi.fn()
24
+ }))
25
+ }));
26
+ (0, vitest_1.describe)('Chat Commands', () => {
27
+ let program;
28
+ let mockChatService;
29
+ let mockDefaultApiKeyManager;
30
+ (0, vitest_1.beforeEach)(() => {
31
+ program = new commander_1.Command();
32
+ // Mock ChatService
33
+ mockChatService = {
34
+ createCompletion: vitest_1.vi.fn(),
35
+ listModels: vitest_1.vi.fn()
36
+ };
37
+ vitest_1.vi.mocked(chat_service_1.ChatService.getInstance).mockReturnValue(mockChatService);
38
+ // Mock DefaultApiKeyManager
39
+ mockDefaultApiKeyManager = {
40
+ getDefaultApiKeyData: vitest_1.vi.fn(),
41
+ promptForDefaultApiKey: vitest_1.vi.fn()
42
+ };
43
+ vitest_1.vi.mocked(default_api_key_1.DefaultApiKeyManager.getInstance).mockReturnValue(mockDefaultApiKeyManager);
44
+ (0, chat_1.registerChatCommands)(program);
45
+ });
46
+ (0, vitest_1.afterEach)(() => {
47
+ vitest_1.vi.clearAllMocks();
48
+ });
49
+ (0, vitest_1.describe)('chat run command', () => {
50
+ (0, vitest_1.it)('should use openai/gpt-oss as default model', () => {
51
+ const chatCommand = program.commands.find(cmd => cmd.name() === 'chat');
52
+ const runCommand = chatCommand === null || chatCommand === void 0 ? void 0 : chatCommand.commands.find(cmd => cmd.name() === 'run');
53
+ (0, vitest_1.expect)(runCommand).toBeDefined();
54
+ // Check the help text which contains the default model
55
+ const helpText = runCommand === null || runCommand === void 0 ? void 0 : runCommand.helpInformation();
56
+ (0, vitest_1.expect)(helpText).toContain('openai/gpt-oss');
57
+ });
58
+ (0, vitest_1.it)('should have streaming enabled by default', () => {
59
+ const chatCommand = program.commands.find(cmd => cmd.name() === 'chat');
60
+ const runCommand = chatCommand === null || chatCommand === void 0 ? void 0 : chatCommand.commands.find(cmd => cmd.name() === 'run');
61
+ (0, vitest_1.expect)(runCommand).toBeDefined();
62
+ // Check that the option is --no-stream (meaning streaming is default)
63
+ const streamOption = runCommand === null || runCommand === void 0 ? void 0 : runCommand.options.find(opt => opt.long === '--no-stream');
64
+ (0, vitest_1.expect)(streamOption).toBeDefined();
65
+ (0, vitest_1.expect)(streamOption === null || streamOption === void 0 ? void 0 : streamOption.description).toContain('Disable streaming');
66
+ });
67
+ (0, vitest_1.it)('should create completion with correct default options', () => __awaiter(void 0, void 0, void 0, function* () {
68
+ // Mock API key
69
+ process.env.BERGET_API_KEY = 'test-key';
70
+ // Mock successful completion
71
+ mockChatService.createCompletion.mockResolvedValue({
72
+ choices: [{
73
+ message: { content: 'Test response' }
74
+ }]
75
+ });
76
+ // This would normally test the actual command execution
77
+ // but since it involves readline interaction, we just verify
78
+ // that the service would be called with correct defaults
79
+ (0, vitest_1.expect)(mockChatService.createCompletion).not.toHaveBeenCalled();
80
+ // Clean up
81
+ delete process.env.BERGET_API_KEY;
82
+ }));
83
+ });
84
+ (0, vitest_1.describe)('chat list command', () => {
85
+ (0, vitest_1.it)('should list available models', () => __awaiter(void 0, void 0, void 0, function* () {
86
+ const mockModels = {
87
+ data: [
88
+ {
89
+ id: 'gpt-oss',
90
+ owned_by: 'openai',
91
+ active: true,
92
+ capabilities: {
93
+ vision: false,
94
+ function_calling: true,
95
+ json_mode: true
96
+ }
97
+ }
98
+ ]
99
+ };
100
+ mockChatService.listModels.mockResolvedValue(mockModels);
101
+ const chatCommand = program.commands.find(cmd => cmd.name() === 'chat');
102
+ const listCommand = chatCommand === null || chatCommand === void 0 ? void 0 : chatCommand.commands.find(cmd => cmd.name() === 'list');
103
+ (0, vitest_1.expect)(listCommand).toBeDefined();
104
+ (0, vitest_1.expect)(listCommand === null || listCommand === void 0 ? void 0 : listCommand.description()).toBe('List available chat models');
105
+ }));
106
+ });
107
+ });
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const config_1 = require("vitest/config");
4
+ exports.default = (0, config_1.defineConfig)({
5
+ test: {
6
+ globals: true,
7
+ environment: 'node',
8
+ },
9
+ });
@@ -0,0 +1,95 @@
1
+ # Berget CLI Examples
2
+
3
+ This folder contains practical examples of how you can use Berget CLI for various automation tasks.
4
+
5
+ ## Scripts
6
+
7
+ ### smart-commit.sh
8
+ Automatic generation of conventional commit messages based on git diff.
9
+
10
+ ```bash
11
+ # Make the script executable
12
+ chmod +x examples/smart-commit.sh
13
+
14
+ # Use it
15
+ git add .
16
+ ./examples/smart-commit.sh
17
+ ```
18
+
19
+ ### ai-review.sh
20
+ AI-driven code review that analyzes files for quality, bugs, and security aspects.
21
+
22
+ ```bash
23
+ # Make the script executable
24
+ chmod +x examples/ai-review.sh
25
+
26
+ # Review a file
27
+ ./examples/ai-review.sh src/main.js
28
+ ```
29
+
30
+ ### security-check.sh
31
+ Security review of git commits that blocks commits with critical security risks.
32
+
33
+ ```bash
34
+ # Make the script executable
35
+ chmod +x examples/security-check.sh
36
+
37
+ # Run security check
38
+ git add .
39
+ ./examples/security-check.sh
40
+ ```
41
+
42
+ ## Installation
43
+
44
+ To use these scripts:
45
+
46
+ 1. Copy them to your `~/bin` folder or another location in your PATH
47
+ 2. Make them executable with `chmod +x`
48
+ 3. Make sure you have Berget CLI installed and configured
49
+
50
+ ```bash
51
+ # Copy to ~/bin
52
+ cp examples/*.sh ~/bin/
53
+
54
+ # Make them executable
55
+ chmod +x ~/bin/smart-commit.sh ~/bin/ai-review.sh ~/bin/security-check.sh
56
+ ```
57
+
58
+ ## Global Security Hook
59
+
60
+ For maximum security, you can install a global git hook that automatically runs security checks before every push:
61
+
62
+ ```bash
63
+ # Install the global security hook
64
+ chmod +x examples/install-global-security-hook.sh
65
+ ./examples/install-global-security-hook.sh
66
+ ```
67
+
68
+ This will:
69
+ - Create a global pre-push hook that runs on all repositories
70
+ - Automatically analyze commits for security vulnerabilities using OWASP Top 20
71
+ - Block pushes with critical security issues
72
+ - Warn about medium-risk issues and allow you to choose
73
+
74
+ The hook will run automatically before every `git push`. To bypass it temporarily (not recommended):
75
+ ```bash
76
+ git push --no-verify
77
+ ```
78
+
79
+ ## Git Aliases
80
+
81
+ You can also add these as git aliases:
82
+
83
+ ```bash
84
+ git config --global alias.ai-commit '!~/bin/smart-commit.sh'
85
+ git config --global alias.ai-review '!~/bin/ai-review.sh'
86
+ git config --global alias.security-check '!~/bin/security-check.sh'
87
+ ```
88
+
89
+ Then you can use:
90
+
91
+ ```bash
92
+ git ai-commit
93
+ git ai-review src/main.js
94
+ git security-check
95
+ ```
@@ -0,0 +1,30 @@
1
+ #!/bin/bash
2
+ # AI code review using Berget AI
3
+ # Usage: ./ai-review.sh <filename>
4
+ set -e
5
+
6
+ if [[ $# -eq 0 ]]; then
7
+ echo "Usage: ai-review <file>"
8
+ exit 1
9
+ fi
10
+
11
+ FILE="$1"
12
+
13
+ if [[ ! -f "$FILE" ]]; then
14
+ echo "Error: File '$FILE' does not exist"
15
+ exit 1
16
+ fi
17
+
18
+ echo "🔍 Reviewing $FILE with AI..."
19
+ echo "================================"
20
+
21
+ cat "$FILE" | npx berget chat run openai/gpt-oss "
22
+ Review this code and provide feedback on:
23
+ 1. Code quality and readability
24
+ 2. Potential bugs or issues
25
+ 3. Performance improvements
26
+ 4. Best practices
27
+ 5. Security aspects
28
+
29
+ Provide concrete suggestions for improvements:
30
+ "