berget 1.3.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/.env.example +5 -0
  2. package/.github/workflows/publish.yml +56 -0
  3. package/.github/workflows/test.yml +38 -0
  4. package/AGENTS.md +184 -0
  5. package/README.md +177 -38
  6. package/TODO.md +2 -0
  7. package/blog-post.md +176 -0
  8. package/dist/index.js +11 -8
  9. package/dist/package.json +14 -3
  10. package/dist/src/commands/api-keys.js +4 -2
  11. package/dist/src/commands/chat.js +182 -23
  12. package/dist/src/commands/code.js +1424 -0
  13. package/dist/src/commands/index.js +2 -0
  14. package/dist/src/constants/command-structure.js +12 -0
  15. package/dist/src/schemas/opencode-schema.json +1121 -0
  16. package/dist/src/services/chat-service.js +10 -10
  17. package/dist/src/services/cluster-service.js +1 -1
  18. package/dist/src/utils/default-api-key.js +2 -2
  19. package/dist/src/utils/env-manager.js +86 -0
  20. package/dist/src/utils/error-handler.js +10 -3
  21. package/dist/src/utils/markdown-renderer.js +4 -4
  22. package/dist/src/utils/opencode-validator.js +122 -0
  23. package/dist/src/utils/token-manager.js +2 -2
  24. package/dist/tests/commands/chat.test.js +109 -0
  25. package/dist/tests/commands/code.test.js +414 -0
  26. package/dist/tests/utils/env-manager.test.js +148 -0
  27. package/dist/tests/utils/opencode-validator.test.js +103 -0
  28. package/dist/vitest.config.js +9 -0
  29. package/index.ts +67 -32
  30. package/opencode.json +182 -0
  31. package/package.json +14 -3
  32. package/src/client.ts +20 -20
  33. package/src/commands/api-keys.ts +93 -60
  34. package/src/commands/auth.ts +4 -2
  35. package/src/commands/billing.ts +6 -3
  36. package/src/commands/chat.ts +291 -97
  37. package/src/commands/clusters.ts +2 -2
  38. package/src/commands/code.ts +1696 -0
  39. package/src/commands/index.ts +2 -0
  40. package/src/commands/models.ts +3 -3
  41. package/src/commands/users.ts +2 -2
  42. package/src/constants/command-structure.ts +112 -58
  43. package/src/schemas/opencode-schema.json +991 -0
  44. package/src/services/api-key-service.ts +1 -1
  45. package/src/services/auth-service.ts +27 -25
  46. package/src/services/chat-service.ts +37 -44
  47. package/src/services/cluster-service.ts +5 -5
  48. package/src/services/collaborator-service.ts +3 -3
  49. package/src/services/flux-service.ts +2 -2
  50. package/src/services/helm-service.ts +2 -2
  51. package/src/services/kubectl-service.ts +3 -6
  52. package/src/types/api.d.ts +1032 -1010
  53. package/src/types/json.d.ts +3 -3
  54. package/src/utils/default-api-key.ts +54 -42
  55. package/src/utils/env-manager.ts +98 -0
  56. package/src/utils/error-handler.ts +24 -15
  57. package/src/utils/logger.ts +12 -12
  58. package/src/utils/markdown-renderer.ts +18 -18
  59. package/src/utils/opencode-validator.ts +134 -0
  60. package/src/utils/token-manager.ts +35 -23
  61. package/tests/commands/chat.test.ts +129 -0
  62. package/tests/commands/code.test.ts +505 -0
  63. package/tests/utils/env-manager.test.ts +199 -0
  64. package/tests/utils/opencode-validator.test.ts +118 -0
  65. package/tsconfig.json +8 -8
  66. package/vitest.config.ts +8 -0
  67. package/-27b-it +0 -0
package/blog-post.md ADDED
@@ -0,0 +1,176 @@
1
+ # OpenCode + Berget AI: Den ultimata AI-kodassistensen för svenska företag
2
+
3
+ I en värld där AI-drivna kodassistenter blir standard, står svenska företag inför ett kritiskt val: hur man balanserar produktivitetsvinster med data-suveränitet och efterlevnad. Med integrationen mellan OpenCode och Berget AI får du nu det bästa av två världar – en kraftfull, open source AI-kodassistent med svensk datainfrastruktur.
4
+
5
+ ## Vad är OpenCode?
6
+
7
+ OpenCode är en open source AI-kodassistent byggd för terminalen. Till skillnad från många kommersiella alternativ ger OpenCode dig full kontroll över din kod och dina data. Med över 26 000 GitHub-stjärnor och 200 000+ aktiva utvecklare varje månad har det blivit ett av de mest populära verktygen för AI-assisterad programmering.
8
+
9
+ **Nödvändiga funktioner i OpenCode:**
10
+
11
+ - 🎯 **Native TUI** – Responsivt terminalgränssnitt som fungerar i din befintliga workflow
12
+ - 🔄 **Multi-session** – Köra flera agenter parallellt på samma projekt
13
+ - 🔗 **Share links** – Dela sessioner för kodgranskning och felsökning
14
+ - 🤖 **Any model** – Stöd för 75+ LLM-providers via Models.dev
15
+ - 🛠️ **LSP enabled** – Automatisk laddning av rätt Language Servers för LLM:en
16
+
17
+ ## Berget AI: Svensk datainfrastruktur
18
+
19
+ Berget AI är en svensk AI-infrastrukturleverantör som säkerställer att din data aldrig lämnar Sverige. Detta är särskilt viktigt för företag som hanterar känslig information, personuppgifter eller skyddad kod.
20
+
21
+ **Fördelar med Berget AI:**
22
+
23
+ - 🇸🇪 **Data-suveränitet** – All data bearbetas inom Sverige
24
+ - ⚖️ **GDPR-kompatibilitet** – Full efterlevnad med EU:s dataskyddslagar
25
+ - 🔒 **Säkerhet** – Industriell säkerhet med kryptering och isolering
26
+ - 🏛️ **Reglering** – Enklare efterlevnad av svenska och EU-regleringar
27
+ - 🚀 **Prestanda** – Låg latens med svensk infrastruktur
28
+
29
+ ## Varför kombinationen är oslagbar
30
+
31
+ ### 1. Data som aldrig lämnar Sverige
32
+
33
+ När du använder OpenCode med Berget AI förblir all din kod – inklusive API-nycklar, konfigurationer och känslig affärslogik – inom Sveriges gränser. Detta eliminerar risken för dataexponering som kan uppstå med internationella molntjänster.
34
+
35
+ ```bash
36
+ # Initiera ditt projekt med AI-assistent
37
+ berget code init
38
+
39
+ # Din kod och data förblir alltid i Sverige ✅
40
+ ```
41
+
42
+ ### 2. Förenklad reglering och efterlevnad
43
+
44
+ För svenska företag, särskilt inom finans, vård och offentlig sektor, är efterlevnad avgörande. Med Berget AI får du:
45
+
46
+ - **GDPR-säker hantering** av personuppgifter i koden
47
+ - **Klassificerad information** hanteras säkert
48
+ - **Revisionsbara spår** av all AI-interaktion
49
+ - **Lokal lagring** av konfigurationer och API-nycklar
50
+
51
+ ### 3. Säker hantering av känslig kod
52
+
53
+ Många företag arbetar med kod som innehåller:
54
+
55
+ - API-nycklar och hemligheter
56
+ - Affärskritisk algoritmlogik
57
+ - Känslig kunddata
58
+ - Proprietära systemintegrationer
59
+
60
+ Med OpenCode + Berget AI kan du använda AI-assistens för:
61
+
62
+ ```bash
63
+ # Refaktorera känslig kod utan att lämna Sverige
64
+ berget code run "Refaktorera denna autentiseringsmodul"
65
+
66
+ # Få hjälp med API-integrationer säkert
67
+ berget code run "Optimera denna databasanslutning"
68
+ ```
69
+
70
+ ### 4. Projektspecifika API-nycklar
71
+
72
+ Varje projekt får sin egen unika API-nyckel som skapas automatiskt:
73
+
74
+ ```json
75
+ {
76
+ "model": "berget/deepseek-r1",
77
+ "apiKey": "projekt-specifik-nyckel",
78
+ "projectName": "mitt-svenska-projekt",
79
+ "provider": "berget",
80
+ "created": "2025-01-01T00:00:00.000Z",
81
+ "version": "1.0.0"
82
+ }
83
+ ```
84
+
85
+ Detta ger:
86
+
87
+ - 🔐 **Isolering** mellan projekt
88
+ - 📊 **Spårbarhet** av användning
89
+ - 🔄 **Enkel rotation** av nycklar
90
+ - 🎯 **Granulär kontroll** av åtkomst
91
+
92
+ ## Kom igång på 2 minuter
93
+
94
+ ### Steg 1: Installera OpenCode
95
+
96
+ ```bash
97
+ curl -fsSL https://opencode.ai/install | bash
98
+ ```
99
+
100
+ ### Steg 2: Initiera ditt projekt
101
+
102
+ ```bash
103
+ cd ditt-projekt
104
+ berget code init
105
+ ```
106
+
107
+ ### Steg 3: Starta AI-assistenen
108
+
109
+ ```bash
110
+ berget code run
111
+ ```
112
+
113
+ Det var allt! Du har nu en fullfjädrad AI-kodassistent med svensk datainfrastruktur.
114
+
115
+ ## Användningsfall för svenska företag
116
+
117
+ ### Finans & Försäkring
118
+
119
+ - Refaktorera transaktionslogik med full säkerhet
120
+ - Hjälp med compliance-kod (KYC, AML)
121
+ - Optimera riskberäkningsalgoritmer
122
+
123
+ ### Vård & Hälsa
124
+
125
+ - Utveckla patientdata-system med GDPR-säkerhet
126
+ - Hjälp med medicinsk kod och integrationer
127
+ - Säker hantering av journalsystem
128
+
129
+ ### Offentlig Sektor
130
+
131
+ - Utveckla e-tjänster med svensk data
132
+ - Hjälp med myndighetsintegrationer
133
+ - Säker hantering av medborgardata
134
+
135
+ ### Industri & Tillverkning
136
+
137
+ - Optimera PLC-kod och styrsystem
138
+ - Hjälp med IoT-integrationer
139
+ - Säker utveckling av produktionssystem
140
+
141
+ ## Teknisk integration
142
+
143
+ OpenCode + Berget AI använder den kraftfulla **GLM-4.6** modellen (för närvarande deepseek-r1) som är optimerad för kodning:
144
+
145
+ ```bash
146
+ # Se vilka modeller som finns tillgängliga
147
+ berget models list
148
+
149
+ # Använd specifik modell
150
+ berget code run --model berget/glm-4-6 "Hjälp mig optimera denna funktion"
151
+ ```
152
+
153
+ ## Framtiden för svensk AI-utveckling
154
+
155
+ Kombinationen av OpenCode och Berget AI representerar nästa generations AI-utveckling för svenska företag:
156
+
157
+ 1. **Produktivitet** – AI-assisterad kodning utan kompromisser
158
+ 2. **Säkerhet** – Full kontroll över din data och kod
159
+ 3. **Efterlevnad** – Inbyggt stöd för svenska och EU-regleringar
160
+ 4. **Flexibilitet** – Open source med frihet att välja modeller och verktyg
161
+ 5. **Skalbarhet** – Från små projekt till enterprise-nivå
162
+
163
+ ## Sammanfattning
164
+
165
+ OpenCode + Berget AI är inte bara ett verktyg – det är en strategisk lösning för svenska företag som vill leda inom AI-driven utveckling utan att kompromissa med säkerhet och efterlevnad.
166
+
167
+ Med svensk datainfrastruktur, projektspecifika API-nycklar och en open source AI-assistent får du det bästa av två världar: global AI-kompetens med lokal dataskydd.
168
+
169
+ **Redo att transformera din kodning med svensk AI?**
170
+
171
+ ```bash
172
+ # Börja idag
173
+ berget code init
174
+ ```
175
+
176
+ _Din kod, din data, ditt land – nu med AI-superkrafter._
package/dist/index.js CHANGED
@@ -19,7 +19,8 @@ commander_1.program
19
19
  | |_/ / __/ | | (_| | __/ |_ | | | |_| |_
20
20
  \\____/ \\___|_| \\__, |\\___|\\_\\_ \\_| |_/\\___/
21
21
  __/ |
22
- |___/ AI on European terms`)
22
+ |___/ AI on European terms
23
+ Version: ${package_json_1.version}`)
23
24
  .version(package_json_1.version, '-v, --version')
24
25
  .option('--local', 'Use local API endpoint (hidden)', false)
25
26
  .option('--debug', 'Enable debug output', false);
@@ -34,19 +35,21 @@ if (process.argv.length <= 2) {
34
35
  console.log(chalk_1.default.blue(` ${chalk_1.default.bold('berget auth login')} - Log in to Berget`));
35
36
  console.log(chalk_1.default.blue(` ${chalk_1.default.bold('berget models list')} - List available AI models`));
36
37
  console.log(chalk_1.default.blue(` ${chalk_1.default.bold('berget chat run')} - Start a chat session`));
38
+ console.log(chalk_1.default.blue(` ${chalk_1.default.bold('berget code init')} - Initialize AI coding assistant`));
37
39
  console.log(chalk_1.default.blue(` ${chalk_1.default.bold('berget api-keys list')} - List your API keys`));
38
40
  console.log(chalk_1.default.blue(`\nRun ${chalk_1.default.bold('berget --help')} for a complete list of commands.`));
39
41
  }
40
42
  // Add helpful suggestions for common command mistakes
41
43
  const commonMistakes = {
42
- 'login': 'auth login',
43
- 'logout': 'auth logout',
44
- 'whoami': 'auth whoami',
44
+ login: 'auth login',
45
+ logout: 'auth logout',
46
+ whoami: 'auth whoami',
45
47
  'list-models': 'models list',
46
48
  'list-keys': 'api-keys list',
47
49
  'create-key': 'api-keys create',
48
50
  'list-clusters': 'clusters list',
49
- 'usage': 'billing usage'
51
+ usage: 'billing usage',
52
+ init: 'code init',
50
53
  };
51
54
  // Add error handler for unknown commands
52
55
  commander_1.program.on('command:*', (operands) => {
@@ -58,11 +61,11 @@ commander_1.program.on('command:*', (operands) => {
58
61
  }
59
62
  else {
60
63
  // Try to find similar commands
61
- const availableCommands = commander_1.program.commands.map(cmd => cmd.name());
62
- const similarCommands = availableCommands.filter(cmd => cmd.includes(unknownCommand) || unknownCommand.includes(cmd));
64
+ const availableCommands = commander_1.program.commands.map((cmd) => cmd.name());
65
+ const similarCommands = availableCommands.filter((cmd) => cmd.includes(unknownCommand) || unknownCommand.includes(cmd));
63
66
  if (similarCommands.length > 0) {
64
67
  console.log(chalk_1.default.yellow('Similar commands:'));
65
- similarCommands.forEach(cmd => {
68
+ similarCommands.forEach((cmd) => {
66
69
  console.log(chalk_1.default.yellow(` ${chalk_1.default.bold(`berget ${cmd}`)}`));
67
70
  });
68
71
  }
package/dist/package.json CHANGED
@@ -1,17 +1,22 @@
1
1
  {
2
2
  "name": "berget",
3
- "version": "1.3.1",
3
+ "version": "2.0.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,21 +24,27 @@
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/dotenv": "^6.1.1",
22
28
  "@types/marked": "^5.0.2",
23
29
  "@types/marked-terminal": "^6.1.1",
24
30
  "@types/node": "^20.11.20",
25
31
  "tsx": "^4.19.3",
26
- "typescript": "^5.3.3"
32
+ "typescript": "^5.3.3",
33
+ "vitest": "^1.0.0"
27
34
  },
28
35
  "dependencies": {
36
+ "ajv": "^8.17.1",
37
+ "ajv-formats": "^3.0.1",
29
38
  "chalk": "^4.1.2",
30
39
  "commander": "^12.0.0",
40
+ "dotenv": "^17.2.3",
31
41
  "fs-extra": "^11.3.0",
32
42
  "marked": "^9.1.6",
33
43
  "marked-terminal": "^6.2.0",
34
44
  "open": "^9.1.0",
35
45
  "openapi-fetch": "^0.9.1",
36
46
  "openapi-typescript": "^6.7.4",
37
- "readline": "^1.3.0"
47
+ "readline": "^1.3.0",
48
+ "zod": "^4.1.12"
38
49
  }
39
50
  }
@@ -46,7 +46,9 @@ function registerApiKeyCommands(program) {
46
46
  chalk_1.default.dim('LAST USED'));
47
47
  console.log(chalk_1.default.dim('─'.repeat(85)));
48
48
  keys.forEach((key) => {
49
- const lastUsed = key.lastUsed ? key.lastUsed.substring(0, 10) : 'Never';
49
+ const lastUsed = key.lastUsed
50
+ ? key.lastUsed.substring(0, 10)
51
+ : 'Never';
50
52
  const status = key.active
51
53
  ? chalk_1.default.green('● Active')
52
54
  : chalk_1.default.red('● Inactive');
@@ -227,7 +229,7 @@ function registerApiKeyCommands(program) {
227
229
  try {
228
230
  const apiKeyService = api_key_service_1.ApiKeyService.getInstance();
229
231
  const keys = yield apiKeyService.list();
230
- const selectedKey = keys.find(key => key.id.toString() === id);
232
+ const selectedKey = keys.find((key) => key.id.toString() === id);
231
233
  if (!selectedKey) {
232
234
  console.error(chalk_1.default.red(`Error: API key with ID ${id} not found`));
233
235
  return;
@@ -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
  };
@@ -49,14 +56,16 @@ function registerChatCommands(program) {
49
56
  chat
50
57
  .command(command_structure_1.SUBCOMMANDS.CHAT.RUN)
51
58
  .description('Run a chat session with a specified model')
52
- .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)')
53
61
  .option('-s, --system <message>', 'System message')
54
62
  .option('-t, --temperature <temp>', 'Temperature (0-1)', parseFloat)
55
63
  .option('-m, --max-tokens <tokens>', 'Maximum tokens to generate', parseInt)
56
64
  .option('-k, --api-key <key>', 'API key to use for this chat session')
57
65
  .option('--api-key-id <id>', 'ID of the API key to use from your saved keys')
58
- .option('--stream', 'Stream the response')
59
- .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;
60
69
  try {
61
70
  const chatService = chat_service_1.ChatService.getInstance();
62
71
  // Check if we have an API key or need to get one
@@ -168,11 +177,6 @@ function registerChatCommands(program) {
168
177
  return;
169
178
  }
170
179
  }
171
- // Set up readline interface for user input
172
- const rl = readline_1.default.createInterface({
173
- input: process.stdin,
174
- output: process.stdout,
175
- });
176
180
  // Prepare messages array
177
181
  const messages = [];
178
182
  // Add system message if provided
@@ -182,12 +186,140 @@ function registerChatCommands(program) {
182
186
  content: options.system,
183
187
  });
184
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 &&
245
+ chunk.choices[0] &&
246
+ chunk.choices[0].delta &&
247
+ chunk.choices[0].delta.content) {
248
+ const content = chunk.choices[0].delta.content;
249
+ try {
250
+ process.stdout.write(content);
251
+ }
252
+ catch (error) {
253
+ // Handle EPIPE errors gracefully (when pipe is closed)
254
+ if (error.code === 'EPIPE') {
255
+ // Stop streaming if the pipe is closed
256
+ return;
257
+ }
258
+ throw error;
259
+ }
260
+ assistantResponse += content;
261
+ }
262
+ };
263
+ try {
264
+ yield chatService.createCompletion(completionOptions);
265
+ }
266
+ catch (streamError) {
267
+ console.error(chalk_1.default.red('\nStreaming error:'), streamError);
268
+ // Fallback to non-streaming if streaming fails
269
+ console.log(chalk_1.default.yellow('Falling back to non-streaming mode...'));
270
+ completionOptions.stream = false;
271
+ delete completionOptions.onChunk;
272
+ const response = yield chatService.createCompletion(completionOptions);
273
+ if (response &&
274
+ response.choices &&
275
+ response.choices[0] &&
276
+ response.choices[0].message) {
277
+ assistantResponse = response.choices[0].message.content;
278
+ console.log(assistantResponse);
279
+ }
280
+ }
281
+ console.log(); // Add newline at the end
282
+ return;
283
+ }
284
+ const response = yield chatService.createCompletion(completionOptions);
285
+ // Check if response has the expected structure
286
+ if (!response ||
287
+ !response.choices ||
288
+ !response.choices[0] ||
289
+ !response.choices[0].message) {
290
+ console.error(chalk_1.default.red('Error: Unexpected response format from API'));
291
+ console.error(chalk_1.default.red('Response:', JSON.stringify(response, null, 2)));
292
+ throw new Error('Unexpected response format from API');
293
+ }
294
+ // Get assistant's response
295
+ const assistantMessage = response.choices[0].message.content;
296
+ // Display the response
297
+ if ((0, markdown_renderer_1.containsMarkdown)(assistantMessage)) {
298
+ console.log((0, markdown_renderer_1.renderMarkdown)(assistantMessage));
299
+ }
300
+ else {
301
+ console.log(assistantMessage);
302
+ }
303
+ return;
304
+ }
305
+ catch (error) {
306
+ console.error(chalk_1.default.red('Error: Failed to get response'));
307
+ if (error instanceof Error) {
308
+ console.error(chalk_1.default.red(error.message));
309
+ }
310
+ process.exit(1);
311
+ }
312
+ }
313
+ // Set up readline interface for user input (only for interactive mode)
314
+ const rl = readline_1.default.createInterface({
315
+ input: process.stdin,
316
+ output: process.stdout,
317
+ });
185
318
  console.log(chalk_1.default.cyan('Chat with Berget AI (type "exit" to quit)'));
186
319
  console.log(chalk_1.default.cyan('----------------------------------------'));
187
320
  // Start the conversation loop
188
321
  const askQuestion = () => {
189
322
  rl.question(chalk_1.default.green('You: '), (input) => __awaiter(this, void 0, void 0, function* () {
190
- var _a;
191
323
  // Check if user wants to exit
192
324
  if (input.toLowerCase() === 'exit') {
193
325
  console.log(chalk_1.default.cyan('Goodbye!'));
@@ -202,35 +334,64 @@ function registerChatCommands(program) {
202
334
  try {
203
335
  // Call the API
204
336
  const completionOptions = {
205
- model: ((_a = options.args) === null || _a === void 0 ? void 0 : _a[0]) || 'google/gemma-3-27b-it',
337
+ model: model || 'openai/gpt-oss',
206
338
  messages: messages,
207
339
  temperature: options.temperature !== undefined ? options.temperature : 0.7,
208
340
  max_tokens: options.maxTokens || 4096,
209
- stream: options.stream || false
341
+ stream: options.stream !== false,
210
342
  };
211
343
  // Only add apiKey if it actually exists
212
344
  if (apiKey) {
213
345
  completionOptions.apiKey = apiKey;
214
346
  }
215
- // Add streaming support
216
- if (options.stream) {
347
+ // Add streaming support (now default)
348
+ if (completionOptions.stream) {
217
349
  let assistantResponse = '';
218
350
  console.log(chalk_1.default.blue('Assistant: '));
219
- // For streaming, we'll collect the response and render it at the end
220
- // since markdown needs the complete text to render properly
351
+ // Stream the response in real-time
221
352
  completionOptions.onChunk = (chunk) => {
222
- if (chunk.choices && chunk.choices[0] && chunk.choices[0].delta && chunk.choices[0].delta.content) {
353
+ if (chunk.choices &&
354
+ chunk.choices[0] &&
355
+ chunk.choices[0].delta &&
356
+ chunk.choices[0].delta.content) {
223
357
  const content = chunk.choices[0].delta.content;
224
- process.stdout.write(content);
358
+ try {
359
+ process.stdout.write(content);
360
+ }
361
+ catch (error) {
362
+ // Handle EPIPE errors gracefully (when pipe is closed)
363
+ if (error.code === 'EPIPE') {
364
+ // Stop streaming if the pipe is closed
365
+ return;
366
+ }
367
+ throw error;
368
+ }
225
369
  assistantResponse += content;
226
370
  }
227
371
  };
228
- yield chatService.createCompletion(completionOptions);
372
+ try {
373
+ yield chatService.createCompletion(completionOptions);
374
+ }
375
+ catch (streamError) {
376
+ console.error(chalk_1.default.red('\nStreaming error:'), streamError);
377
+ // Fallback to non-streaming if streaming fails
378
+ console.log(chalk_1.default.yellow('Falling back to non-streaming mode...'));
379
+ completionOptions.stream = false;
380
+ delete completionOptions.onChunk;
381
+ const response = yield chatService.createCompletion(completionOptions);
382
+ if (response &&
383
+ response.choices &&
384
+ response.choices[0] &&
385
+ response.choices[0].message) {
386
+ assistantResponse = response.choices[0].message.content;
387
+ console.log(assistantResponse);
388
+ }
389
+ }
229
390
  console.log('\n');
230
391
  // Add assistant response to messages
231
392
  messages.push({
232
393
  role: 'assistant',
233
- content: assistantResponse
394
+ content: assistantResponse,
234
395
  });
235
396
  // Continue the conversation
236
397
  askQuestion();
@@ -343,8 +504,7 @@ function registerChatCommands(program) {
343
504
  }
344
505
  console.log(chalk_1.default.bold('Available Chat Models:'));
345
506
  console.log(chalk_1.default.dim('─'.repeat(70)));
346
- console.log(chalk_1.default.dim('MODEL ID'.padEnd(40)) +
347
- chalk_1.default.dim('CAPABILITIES'));
507
+ console.log(chalk_1.default.dim('MODEL ID'.padEnd(40)) + chalk_1.default.dim('CAPABILITIES'));
348
508
  console.log(chalk_1.default.dim('─'.repeat(70)));
349
509
  // Filter to only show active models
350
510
  const activeModels = models.data.filter((model) => model.active === true);
@@ -358,8 +518,7 @@ function registerChatCommands(program) {
358
518
  capabilities.push('json_mode');
359
519
  // Format model ID in Huggingface compatible format (owner/model)
360
520
  const modelId = `${model.owned_by.toLowerCase()}/${model.id}`.padEnd(40);
361
- console.log(modelId +
362
- capabilities.join(', '));
521
+ console.log(modelId + capabilities.join(', '));
363
522
  });
364
523
  }
365
524
  catch (error) {