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.
@@ -0,0 +1,170 @@
1
+ #!/bin/bash
2
+ # Install global git security hook
3
+ # This script sets up a global pre-push hook that runs security checks on all repositories
4
+
5
+ set -e
6
+
7
+ echo "🔧 Installing global git security hook..."
8
+
9
+ # Create global git hooks directory
10
+ GLOBAL_HOOKS_DIR="$HOME/.git-hooks"
11
+ mkdir -p "$GLOBAL_HOOKS_DIR"
12
+
13
+ # Create the pre-push hook
14
+ cat > "$GLOBAL_HOOKS_DIR/pre-push" << 'EOF'
15
+ #!/bin/bash
16
+ # Global pre-push security hook using Berget AI
17
+ # This hook runs automatically before every git push
18
+
19
+ set -e
20
+
21
+ # Colors for output
22
+ RED='\033[0;31m'
23
+ GREEN='\033[0;32m'
24
+ YELLOW='\033[1;33m'
25
+ BLUE='\033[0;34m'
26
+ NC='\033[0m' # No Color
27
+
28
+ echo -e "${BLUE}🔒 Running security check before push...${NC}"
29
+
30
+ # Check if we're in a git repository
31
+ if ! git rev-parse --git-dir > /dev/null 2>&1; then
32
+ echo -e "${RED}Error: Not in a git repository${NC}"
33
+ exit 1
34
+ fi
35
+
36
+ # Check if there are any commits to push
37
+ if [[ -z $(git log @{u}.. --oneline 2>/dev/null) ]]; then
38
+ echo -e "${GREEN}✅ No new commits to push${NC}"
39
+ exit 0
40
+ fi
41
+
42
+ # Get the diff of commits being pushed
43
+ DIFF=$(git diff @{u}.. 2>/dev/null || git diff HEAD~1)
44
+
45
+ if [[ -z "$DIFF" ]]; then
46
+ echo -e "${GREEN}✅ No changes to analyze${NC}"
47
+ exit 0
48
+ fi
49
+
50
+ echo -e "${BLUE}Analyzing security risks in commits being pushed...${NC}"
51
+
52
+ # Check if Berget CLI is available
53
+ if ! command -v npx > /dev/null 2>&1; then
54
+ echo -e "${YELLOW}⚠️ npx not found. Skipping security check.${NC}"
55
+ echo -e "${YELLOW}Install Node.js and npm to enable security checks.${NC}"
56
+ exit 0
57
+ fi
58
+
59
+ # Run security analysis
60
+ SECURITY_REPORT=$(echo "$DIFF" | npx berget chat run openai/gpt-oss "
61
+ Analyze this git diff for security vulnerabilities using OWASP Top 20 Code Review recommendations:
62
+
63
+ **OWASP Top 20 Security Categories to Check:**
64
+
65
+ 1. **A01 - Broken Access Control**: Authorization bypasses, privilege escalation, insecure direct object references
66
+ 2. **A02 - Cryptographic Failures**: Weak encryption, hardcoded keys, insecure random number generation, plain text storage
67
+ 3. **A03 - Injection**: SQL injection, NoSQL injection, command injection, LDAP injection, XSS
68
+ 4. **A04 - Insecure Design**: Missing security controls, threat modeling gaps, insecure architecture patterns
69
+ 5. **A05 - Security Misconfiguration**: Default credentials, unnecessary features enabled, verbose error messages
70
+ 6. **A06 - Vulnerable Components**: Outdated dependencies, known vulnerable libraries, unpatched components
71
+ 7. **A07 - Authentication Failures**: Weak passwords, session management flaws, credential stuffing vulnerabilities
72
+ 8. **A08 - Software Integrity Failures**: Unsigned code, insecure CI/CD pipelines, auto-update without verification
73
+ 9. **A09 - Logging Failures**: Insufficient logging, sensitive data in logs, log injection
74
+ 10. **A10 - Server-Side Request Forgery**: SSRF vulnerabilities, unvalidated URLs, internal service access
75
+
76
+ **Additional Critical Areas:**
77
+ 11. **Input Validation**: Insufficient sanitization, buffer overflows, format string vulnerabilities
78
+ 12. **Output Encoding**: XSS prevention, content type validation, encoding bypasses
79
+ 13. **File Operations**: Path traversal, file upload vulnerabilities, insecure file permissions
80
+ 14. **Network Security**: Insecure protocols, certificate validation, CSRF protection
81
+ 15. **Session Management**: Session fixation, insecure cookies, session timeout issues
82
+ 16. **Error Handling**: Information disclosure, stack traces in production, verbose error messages
83
+ 17. **Business Logic**: Race conditions, workflow bypasses, price manipulation
84
+ 18. **API Security**: Rate limiting, input validation, authentication on all endpoints
85
+ 19. **Mobile Security**: Insecure data storage, weak encryption, certificate pinning
86
+ 20. **Cloud Security**: Misconfigured permissions, exposed storage, insecure defaults
87
+
88
+ **Assessment Criteria:**
89
+ - 🟢 SAFE: No security risks identified according to OWASP guidelines
90
+ - 🟡 WARNING: Minor security risks that should be addressed (OWASP Medium risk)
91
+ - 🔴 CRITICAL: Serious security risks that MUST be addressed immediately (OWASP High/Critical risk)
92
+
93
+ **Required Response Format:**
94
+ **SECURITY ASSESSMENT: [🟢/🟡/🔴] [SAFE/WARNING/CRITICAL]**
95
+
96
+ **OWASP CATEGORIES AFFECTED:**
97
+ - [List specific OWASP categories if any vulnerabilities found]
98
+
99
+ **IDENTIFIED RISKS:**
100
+ - [List specific vulnerabilities with OWASP category references]
101
+
102
+ **RECOMMENDATIONS:**
103
+ - [Concrete remediation steps following OWASP secure coding practices]
104
+
105
+ **COMPLIANCE NOTES:**
106
+ - [Any additional security considerations or compliance requirements]
107
+
108
+ Diff to analyze:
109
+ \`\`\`diff
110
+ $DIFF
111
+ \`\`\`
112
+ " 2>/dev/null)
113
+
114
+ if [[ $? -ne 0 ]] || [[ -z "$SECURITY_REPORT" ]]; then
115
+ echo -e "${YELLOW}⚠️ Security analysis failed or unavailable. Proceeding with push.${NC}"
116
+ echo -e "${YELLOW}Make sure you have BERGET_API_KEY set or are logged in with 'npx berget auth login'${NC}"
117
+ exit 0
118
+ fi
119
+
120
+ echo "$SECURITY_REPORT"
121
+ echo ""
122
+
123
+ # Extract security level from report
124
+ if echo "$SECURITY_REPORT" | grep -q "🔴.*CRITICAL"; then
125
+ echo -e "${RED}❌ CRITICAL security risks identified!${NC}"
126
+ echo -e "${RED}Push blocked. Address security issues before pushing.${NC}"
127
+ echo ""
128
+ echo -e "${YELLOW}To bypass this check (NOT RECOMMENDED):${NC}"
129
+ echo -e "${YELLOW}git push --no-verify${NC}"
130
+ exit 1
131
+ elif echo "$SECURITY_REPORT" | grep -q "🟡.*WARNING"; then
132
+ echo -e "${YELLOW}⚠️ Security warnings identified.${NC}"
133
+ read -p "Do you want to continue with push despite warnings? (y/N): " -n 1 -r
134
+ echo
135
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
136
+ echo -e "${YELLOW}Push cancelled. Address security issues first.${NC}"
137
+ echo ""
138
+ echo -e "${YELLOW}To bypass this check (NOT RECOMMENDED):${NC}"
139
+ echo -e "${YELLOW}git push --no-verify${NC}"
140
+ exit 1
141
+ fi
142
+ elif echo "$SECURITY_REPORT" | grep -q "🟢.*SAFE"; then
143
+ echo -e "${GREEN}✅ No security risks identified. Safe to push!${NC}"
144
+ else
145
+ echo -e "${YELLOW}⚠️ Could not determine security status. Proceeding with caution.${NC}"
146
+ fi
147
+
148
+ echo -e "${GREEN}Security check complete. Proceeding with push...${NC}"
149
+ EOF
150
+
151
+ # Make the hook executable
152
+ chmod +x "$GLOBAL_HOOKS_DIR/pre-push"
153
+
154
+ # Configure git to use the global hooks directory
155
+ git config --global core.hooksPath "$GLOBAL_HOOKS_DIR"
156
+
157
+ echo -e "${GREEN}✅ Global security hook installed successfully!${NC}"
158
+ echo ""
159
+ echo -e "${BLUE}The security hook will now run automatically before every 'git push' in all repositories.${NC}"
160
+ echo ""
161
+ echo -e "${YELLOW}Requirements:${NC}"
162
+ echo -e " • Node.js and npm installed"
163
+ echo -e " • Berget CLI configured (npx berget auth login or BERGET_API_KEY set)"
164
+ echo ""
165
+ echo -e "${YELLOW}To disable the hook temporarily:${NC}"
166
+ echo -e " git push --no-verify"
167
+ echo ""
168
+ echo -e "${YELLOW}To uninstall the global hook:${NC}"
169
+ echo -e " git config --global --unset core.hooksPath"
170
+ echo -e " rm -rf $GLOBAL_HOOKS_DIR"
@@ -0,0 +1,102 @@
1
+ #!/bin/bash
2
+ # Security check for git commits using Berget AI
3
+ # Usage: ./security-check.sh
4
+ set -e
5
+
6
+ echo "🔒 Security review of commits..."
7
+ echo "===================================="
8
+
9
+ # Check if there are staged changes
10
+ if [[ -z $(git diff --cached) ]]; then
11
+ echo "No staged changes found. Run 'git add' first."
12
+ exit 1
13
+ fi
14
+
15
+ # Get diff for security review
16
+ DIFF=$(git diff --cached)
17
+
18
+ echo "Analyzing security risks in staged changes..."
19
+
20
+ SECURITY_REPORT=$(echo "$DIFF" | npx berget chat run openai/gpt-oss "
21
+ Analyze this git diff for security vulnerabilities using OWASP Top 20 Code Review recommendations:
22
+
23
+ **OWASP Top 20 Security Categories to Check:**
24
+
25
+ 1. **A01 - Broken Access Control**: Authorization bypasses, privilege escalation, insecure direct object references
26
+ 2. **A02 - Cryptographic Failures**: Weak encryption, hardcoded keys, insecure random number generation, plain text storage
27
+ 3. **A03 - Injection**: SQL injection, NoSQL injection, command injection, LDAP injection, XSS
28
+ 4. **A04 - Insecure Design**: Missing security controls, threat modeling gaps, insecure architecture patterns
29
+ 5. **A05 - Security Misconfiguration**: Default credentials, unnecessary features enabled, verbose error messages
30
+ 6. **A06 - Vulnerable Components**: Outdated dependencies, known vulnerable libraries, unpatched components
31
+ 7. **A07 - Authentication Failures**: Weak passwords, session management flaws, credential stuffing vulnerabilities
32
+ 8. **A08 - Software Integrity Failures**: Unsigned code, insecure CI/CD pipelines, auto-update without verification
33
+ 9. **A09 - Logging Failures**: Insufficient logging, sensitive data in logs, log injection
34
+ 10. **A10 - Server-Side Request Forgery**: SSRF vulnerabilities, unvalidated URLs, internal service access
35
+
36
+ **Additional Critical Areas:**
37
+ 11. **Input Validation**: Insufficient sanitization, buffer overflows, format string vulnerabilities
38
+ 12. **Output Encoding**: XSS prevention, content type validation, encoding bypasses
39
+ 13. **File Operations**: Path traversal, file upload vulnerabilities, insecure file permissions
40
+ 14. **Network Security**: Insecure protocols, certificate validation, CSRF protection
41
+ 15. **Session Management**: Session fixation, insecure cookies, session timeout issues
42
+ 16. **Error Handling**: Information disclosure, stack traces in production, verbose error messages
43
+ 17. **Business Logic**: Race conditions, workflow bypasses, price manipulation
44
+ 18. **API Security**: Rate limiting, input validation, authentication on all endpoints
45
+ 19. **Mobile Security**: Insecure data storage, weak encryption, certificate pinning
46
+ 20. **Cloud Security**: Misconfigured permissions, exposed storage, insecure defaults
47
+
48
+ **Assessment Criteria:**
49
+ - 🟢 SAFE: No security risks identified according to OWASP guidelines
50
+ - 🟡 WARNING: Minor security risks that should be addressed (OWASP Medium risk)
51
+ - 🔴 CRITICAL: Serious security risks that MUST be addressed immediately (OWASP High/Critical risk)
52
+
53
+ **Required Response Format:**
54
+ **SECURITY ASSESSMENT: [🟢/🟡/🔴] [SAFE/WARNING/CRITICAL]**
55
+
56
+ **OWASP CATEGORIES AFFECTED:**
57
+ - [List specific OWASP categories if any vulnerabilities found]
58
+
59
+ **IDENTIFIED RISKS:**
60
+ - [List specific vulnerabilities with OWASP category references]
61
+
62
+ **RECOMMENDATIONS:**
63
+ - [Concrete remediation steps following OWASP secure coding practices]
64
+
65
+ **COMPLIANCE NOTES:**
66
+ - [Any additional security considerations or compliance requirements]
67
+
68
+ Diff to analyze:
69
+ \`\`\`diff
70
+ $DIFF
71
+ \`\`\`
72
+ ")
73
+
74
+ echo "$SECURITY_REPORT"
75
+ echo ""
76
+
77
+ # Extract security level from report
78
+ if echo "$SECURITY_REPORT" | grep -q "🔴.*CRITICAL"; then
79
+ echo "❌ CRITICAL security risks identified!"
80
+ echo "Commit blocked. Address security issues before continuing."
81
+ exit 1
82
+ elif echo "$SECURITY_REPORT" | grep -q "🟡.*WARNING"; then
83
+ echo "⚠️ Security warnings identified."
84
+ read -p "Do you want to continue with commit despite warnings? (y/N): " -n 1 -r
85
+ echo
86
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
87
+ echo "Commit cancelled. Address security issues first."
88
+ exit 1
89
+ fi
90
+ elif echo "$SECURITY_REPORT" | grep -q "🟢.*SAFE"; then
91
+ echo "✅ No security risks identified. Safe to continue!"
92
+ else
93
+ echo "⚠️ Could not determine security status. Review manually."
94
+ read -p "Do you want to continue with commit? (y/N): " -n 1 -r
95
+ echo
96
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
97
+ echo "Commit cancelled."
98
+ exit 1
99
+ fi
100
+ fi
101
+
102
+ echo "Security review complete. You can now run 'git commit'."
@@ -0,0 +1,26 @@
1
+ #!/bin/bash
2
+ # Smart commit generator using Berget AI
3
+ # Usage: ./smart-commit.sh
4
+ set -e
5
+
6
+ # Check if there are staged changes
7
+ if [[ -z $(git diff --cached) ]]; then
8
+ echo "No staged changes found. Run 'git add' first."
9
+ exit 1
10
+ fi
11
+
12
+ # Generate commit message
13
+ COMMIT_MSG=$(git diff --cached | npx berget chat run openai/gpt-oss "Generate a conventional commit message for this staged diff. Reply with only the commit message, nothing else:")
14
+
15
+ echo "Suggested commit message:"
16
+ echo " $COMMIT_MSG"
17
+ echo
18
+
19
+ read -p "Do you want to use this message? (y/N): " -n 1 -r
20
+ echo
21
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
22
+ git commit -m "$COMMIT_MSG"
23
+ echo "✅ Commit created!"
24
+ else
25
+ echo "❌ Commit cancelled"
26
+ fi
package/package.json CHANGED
@@ -1,17 +1,22 @@
1
1
  {
2
2
  "name": "berget",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "berget": "dist/index.js"
7
7
  },
8
8
  "private": false,
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
9
12
  "scripts": {
10
13
  "start": "node --import tsx ./index.ts --local",
11
14
  "login": "node --import tsx ./index.ts --local auth login",
12
15
  "logout": "node --import tsx ./index.ts --local auth logout",
13
16
  "whoami": "node --import tsx ./index.ts --local auth whoami",
14
17
  "build": "tsc",
18
+ "test": "vitest",
19
+ "test:run": "vitest run",
15
20
  "prepublishOnly": "npm run build",
16
21
  "generate-types": "openapi-typescript https://api.berget.ai/openapi.json -o src/types/api.d.ts"
17
22
  },
@@ -19,14 +24,19 @@
19
24
  "license": "MIT",
20
25
  "description": "This is a cli command for interacting with the AI infrastructure provider Berget",
21
26
  "devDependencies": {
27
+ "@types/marked": "^5.0.2",
28
+ "@types/marked-terminal": "^6.1.1",
22
29
  "@types/node": "^20.11.20",
23
30
  "tsx": "^4.19.3",
24
- "typescript": "^5.3.3"
31
+ "typescript": "^5.3.3",
32
+ "vitest": "^1.0.0"
25
33
  },
26
34
  "dependencies": {
27
35
  "chalk": "^4.1.2",
28
36
  "commander": "^12.0.0",
29
37
  "fs-extra": "^11.3.0",
38
+ "marked": "^9.1.6",
39
+ "marked-terminal": "^6.2.0",
30
40
  "open": "^9.1.0",
31
41
  "openapi-fetch": "^0.9.1",
32
42
  "openapi-typescript": "^6.7.4",
@@ -7,6 +7,7 @@ import { ApiKeyService } from '../services/api-key-service'
7
7
  import { AuthService } from '../services/auth-service'
8
8
  import { handleError } from '../utils/error-handler'
9
9
  import { DefaultApiKeyManager } from '../utils/default-api-key'
10
+ import { renderMarkdown, containsMarkdown } from '../utils/markdown-renderer'
10
11
 
11
12
  /**
12
13
  * Helper function to get user confirmation
@@ -36,7 +37,8 @@ export function registerChatCommands(program: Command): void {
36
37
  chat
37
38
  .command(SUBCOMMANDS.CHAT.RUN)
38
39
  .description('Run a chat session with a specified model')
39
- .argument('[model]', 'Model to use (default: google/gemma-3-27b-it)')
40
+ .argument('[model]', 'Model to use (default: openai/gpt-oss)')
41
+ .argument('[message]', 'Message to send directly (skips interactive mode)')
40
42
  .option('-s, --system <message>', 'System message')
41
43
  .option('-t, --temperature <temp>', 'Temperature (0-1)', parseFloat)
42
44
  .option('-m, --max-tokens <tokens>', 'Maximum tokens to generate', parseInt)
@@ -45,8 +47,8 @@ export function registerChatCommands(program: Command): void {
45
47
  '--api-key-id <id>',
46
48
  'ID of the API key to use from your saved keys'
47
49
  )
48
- .option('--stream', 'Stream the response')
49
- .action(async (options) => {
50
+ .option('--no-stream', 'Disable streaming (streaming is enabled by default)')
51
+ .action(async (model, message, options) => {
50
52
  try {
51
53
  const chatService = ChatService.getInstance()
52
54
 
@@ -217,12 +219,6 @@ export function registerChatCommands(program: Command): void {
217
219
  }
218
220
  }
219
221
 
220
- // Set up readline interface for user input
221
- const rl = readline.createInterface({
222
- input: process.stdin,
223
- output: process.stdout,
224
- })
225
-
226
222
  // Prepare messages array
227
223
  const messages: ChatMessage[] = []
228
224
 
@@ -234,6 +230,139 @@ export function registerChatCommands(program: Command): void {
234
230
  })
235
231
  }
236
232
 
233
+ // Check if input is being piped in
234
+ let inputMessage = message
235
+ let stdinContent = ''
236
+
237
+ if (!process.stdin.isTTY) {
238
+ // Read from stdin (piped input)
239
+ const chunks = []
240
+ for await (const chunk of process.stdin) {
241
+ chunks.push(chunk)
242
+ }
243
+ stdinContent = Buffer.concat(chunks).toString('utf8').trim()
244
+ }
245
+
246
+ // Combine stdin content with message if both exist
247
+ if (stdinContent && message) {
248
+ inputMessage = `${stdinContent}\n\n${message}`
249
+ } else if (stdinContent && !message) {
250
+ inputMessage = stdinContent
251
+ }
252
+
253
+ // If a message is provided (either as argument, from stdin, or both), send it directly and exit
254
+ if (inputMessage) {
255
+ // Add user message
256
+ messages.push({
257
+ role: 'user',
258
+ content: inputMessage,
259
+ })
260
+
261
+ try {
262
+ // Call the API
263
+ const completionOptions: ChatCompletionOptions = {
264
+ model: model || 'openai/gpt-oss',
265
+ messages: messages,
266
+ temperature:
267
+ options.temperature !== undefined ? options.temperature : 0.7,
268
+ max_tokens: options.maxTokens || 4096,
269
+ stream: options.stream !== false
270
+ }
271
+
272
+ // Only add apiKey if it actually exists
273
+ if (apiKey) {
274
+ completionOptions.apiKey = apiKey
275
+ }
276
+
277
+ // Add streaming support (now default)
278
+ if (completionOptions.stream) {
279
+ let assistantResponse = ''
280
+
281
+ // Stream the response in real-time
282
+ completionOptions.onChunk = (chunk: any) => {
283
+ if (chunk.choices && chunk.choices[0] && chunk.choices[0].delta && chunk.choices[0].delta.content) {
284
+ const content = chunk.choices[0].delta.content
285
+ try {
286
+ process.stdout.write(content)
287
+ } catch (error: any) {
288
+ // Handle EPIPE errors gracefully (when pipe is closed)
289
+ if (error.code === 'EPIPE') {
290
+ // Stop streaming if the pipe is closed
291
+ return
292
+ }
293
+ throw error
294
+ }
295
+ assistantResponse += content
296
+ }
297
+ }
298
+
299
+ try {
300
+ await chatService.createCompletion(completionOptions)
301
+ } catch (streamError) {
302
+ console.error(chalk.red('\nStreaming error:'), streamError)
303
+
304
+ // Fallback to non-streaming if streaming fails
305
+ console.log(chalk.yellow('Falling back to non-streaming mode...'))
306
+ completionOptions.stream = false
307
+ delete completionOptions.onChunk
308
+
309
+ const response = await chatService.createCompletion(completionOptions)
310
+
311
+ if (response && response.choices && response.choices[0] && response.choices[0].message) {
312
+ assistantResponse = response.choices[0].message.content
313
+ console.log(assistantResponse)
314
+ }
315
+ }
316
+ console.log() // Add newline at the end
317
+ return
318
+ }
319
+
320
+ const response = await chatService.createCompletion(
321
+ completionOptions
322
+ )
323
+
324
+ // Check if response has the expected structure
325
+ if (
326
+ !response ||
327
+ !response.choices ||
328
+ !response.choices[0] ||
329
+ !response.choices[0].message
330
+ ) {
331
+ console.error(
332
+ chalk.red('Error: Unexpected response format from API')
333
+ )
334
+ console.error(
335
+ chalk.red('Response:', JSON.stringify(response, null, 2))
336
+ )
337
+ throw new Error('Unexpected response format from API')
338
+ }
339
+
340
+ // Get assistant's response
341
+ const assistantMessage = response.choices[0].message.content
342
+
343
+ // Display the response
344
+ if (containsMarkdown(assistantMessage)) {
345
+ console.log(renderMarkdown(assistantMessage))
346
+ } else {
347
+ console.log(assistantMessage)
348
+ }
349
+
350
+ return
351
+ } catch (error) {
352
+ console.error(chalk.red('Error: Failed to get response'))
353
+ if (error instanceof Error) {
354
+ console.error(chalk.red(error.message))
355
+ }
356
+ process.exit(1)
357
+ }
358
+ }
359
+
360
+ // Set up readline interface for user input (only for interactive mode)
361
+ const rl = readline.createInterface({
362
+ input: process.stdin,
363
+ output: process.stdout,
364
+ })
365
+
237
366
  console.log(chalk.cyan('Chat with Berget AI (type "exit" to quit)'))
238
367
  console.log(chalk.cyan('----------------------------------------'))
239
368
 
@@ -256,12 +385,12 @@ export function registerChatCommands(program: Command): void {
256
385
  try {
257
386
  // Call the API
258
387
  const completionOptions: ChatCompletionOptions = {
259
- model: options.args?.[0] || 'google/gemma-3-27b-it',
388
+ model: model || 'openai/gpt-oss',
260
389
  messages: messages,
261
390
  temperature:
262
391
  options.temperature !== undefined ? options.temperature : 0.7,
263
392
  max_tokens: options.maxTokens || 4096,
264
- stream: options.stream || false
393
+ stream: options.stream !== false
265
394
  }
266
395
 
267
396
  // Only add apiKey if it actually exists
@@ -269,20 +398,46 @@ export function registerChatCommands(program: Command): void {
269
398
  completionOptions.apiKey = apiKey
270
399
  }
271
400
 
272
- // Add streaming support
273
- if (options.stream) {
401
+ // Add streaming support (now default)
402
+ if (completionOptions.stream) {
274
403
  let assistantResponse = ''
275
- process.stdout.write(chalk.blue('Assistant: '))
404
+ console.log(chalk.blue('Assistant: '))
276
405
 
406
+ // Stream the response in real-time
277
407
  completionOptions.onChunk = (chunk: any) => {
278
408
  if (chunk.choices && chunk.choices[0] && chunk.choices[0].delta && chunk.choices[0].delta.content) {
279
409
  const content = chunk.choices[0].delta.content
280
- process.stdout.write(content)
410
+ try {
411
+ process.stdout.write(content)
412
+ } catch (error: any) {
413
+ // Handle EPIPE errors gracefully (when pipe is closed)
414
+ if (error.code === 'EPIPE') {
415
+ // Stop streaming if the pipe is closed
416
+ return
417
+ }
418
+ throw error
419
+ }
281
420
  assistantResponse += content
282
421
  }
283
422
  }
284
423
 
285
- await chatService.createCompletion(completionOptions)
424
+ try {
425
+ await chatService.createCompletion(completionOptions)
426
+ } catch (streamError) {
427
+ console.error(chalk.red('\nStreaming error:'), streamError)
428
+
429
+ // Fallback to non-streaming if streaming fails
430
+ console.log(chalk.yellow('Falling back to non-streaming mode...'))
431
+ completionOptions.stream = false
432
+ delete completionOptions.onChunk
433
+
434
+ const response = await chatService.createCompletion(completionOptions)
435
+
436
+ if (response && response.choices && response.choices[0] && response.choices[0].message) {
437
+ assistantResponse = response.choices[0].message.content
438
+ console.log(assistantResponse)
439
+ }
440
+ }
286
441
  console.log('\n')
287
442
 
288
443
  // Add assistant response to messages
@@ -332,7 +487,15 @@ export function registerChatCommands(program: Command): void {
332
487
  })
333
488
 
334
489
  // Display the response
335
- console.log(chalk.blue('Assistant: ') + assistantMessage)
490
+ console.log(chalk.blue('Assistant: '))
491
+
492
+ // Check if the response contains markdown and render it if it does
493
+ if (containsMarkdown(assistantMessage)) {
494
+ console.log(renderMarkdown(assistantMessage))
495
+ } else {
496
+ console.log(assistantMessage)
497
+ }
498
+
336
499
  console.log() // Empty line for better readability
337
500
 
338
501
  // Continue the conversation
@@ -439,23 +602,27 @@ export function registerChatCommands(program: Command): void {
439
602
  console.log(chalk.bold('Available Chat Models:'))
440
603
  console.log(chalk.dim('─'.repeat(70)))
441
604
  console.log(
442
- chalk.dim('MODEL ID'.padEnd(30)) +
443
- chalk.dim('OWNER'.padEnd(25)) +
605
+ chalk.dim('MODEL ID'.padEnd(40)) +
444
606
  chalk.dim('CAPABILITIES')
445
607
  )
446
608
  console.log(chalk.dim('─'.repeat(70)))
447
609
 
448
- models.data.forEach((model: any) => {
610
+ // Filter to only show active models
611
+ const activeModels = models.data.filter((model: any) => model.active === true);
612
+
613
+ activeModels.forEach((model: any) => {
449
614
  const capabilities = []
450
615
  if (model.capabilities.vision) capabilities.push('vision')
451
616
  if (model.capabilities.function_calling)
452
617
  capabilities.push('function_calling')
453
618
  if (model.capabilities.json_mode) capabilities.push('json_mode')
454
619
 
620
+ // Format model ID in Huggingface compatible format (owner/model)
621
+ const modelId = `${model.owned_by.toLowerCase()}/${model.id}`.padEnd(40)
622
+
455
623
  console.log(
456
- model.id.padEnd(30) +
457
- model.owned_by.padEnd(25) +
458
- capabilities.join(', ')
624
+ modelId +
625
+ capabilities.join(', ')
459
626
  )
460
627
  })
461
628
  } catch (error) {
@@ -36,10 +36,10 @@ export function registerModelCommands(program: Command): void {
36
36
 
37
37
  console.log('Available Models:')
38
38
  console.log(
39
- 'ID OWNED BY CAPABILITIES'
39
+ 'ID OWNED BY CAPABILITIES'
40
40
  )
41
41
  // Ensure response has the expected structure
42
- const modelData = response as { data?: any[] };
42
+ const modelData = response as { data?: any[] }
43
43
  if (modelData.data) {
44
44
  modelData.data.forEach((model: any) => {
45
45
  const capabilities = []
@@ -49,8 +49,8 @@ export function registerModelCommands(program: Command): void {
49
49
  if (model.capabilities.json_mode) capabilities.push('json_mode')
50
50
 
51
51
  console.log(
52
- `${model.id.padEnd(24)} ${model.owned_by.padEnd(
53
- 25
52
+ `${model.root.padEnd(50)} ${model.owned_by.padEnd(
53
+ 24
54
54
  )} ${capabilities.join(', ')}`
55
55
  )
56
56
  })