berget 1.4.0 → 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 (66) hide show
  1. package/.env.example +5 -0
  2. package/AGENTS.md +184 -0
  3. package/TODO.md +2 -0
  4. package/blog-post.md +176 -0
  5. package/dist/index.js +11 -8
  6. package/dist/package.json +7 -2
  7. package/dist/src/commands/api-keys.js +4 -2
  8. package/dist/src/commands/chat.js +21 -11
  9. package/dist/src/commands/code.js +1424 -0
  10. package/dist/src/commands/index.js +2 -0
  11. package/dist/src/constants/command-structure.js +12 -0
  12. package/dist/src/schemas/opencode-schema.json +1121 -0
  13. package/dist/src/services/cluster-service.js +1 -1
  14. package/dist/src/utils/default-api-key.js +2 -2
  15. package/dist/src/utils/env-manager.js +86 -0
  16. package/dist/src/utils/error-handler.js +10 -3
  17. package/dist/src/utils/markdown-renderer.js +4 -4
  18. package/dist/src/utils/opencode-validator.js +122 -0
  19. package/dist/src/utils/token-manager.js +2 -2
  20. package/dist/tests/commands/chat.test.js +20 -18
  21. package/dist/tests/commands/code.test.js +414 -0
  22. package/dist/tests/utils/env-manager.test.js +148 -0
  23. package/dist/tests/utils/opencode-validator.test.js +103 -0
  24. package/index.ts +67 -32
  25. package/opencode.json +182 -0
  26. package/package.json +7 -2
  27. package/src/client.ts +20 -20
  28. package/src/commands/api-keys.ts +93 -60
  29. package/src/commands/auth.ts +4 -2
  30. package/src/commands/billing.ts +6 -3
  31. package/src/commands/chat.ts +149 -107
  32. package/src/commands/clusters.ts +2 -2
  33. package/src/commands/code.ts +1696 -0
  34. package/src/commands/index.ts +2 -0
  35. package/src/commands/models.ts +3 -3
  36. package/src/commands/users.ts +2 -2
  37. package/src/constants/command-structure.ts +112 -58
  38. package/src/schemas/opencode-schema.json +991 -0
  39. package/src/services/api-key-service.ts +1 -1
  40. package/src/services/auth-service.ts +27 -25
  41. package/src/services/chat-service.ts +26 -23
  42. package/src/services/cluster-service.ts +5 -5
  43. package/src/services/collaborator-service.ts +3 -3
  44. package/src/services/flux-service.ts +2 -2
  45. package/src/services/helm-service.ts +2 -2
  46. package/src/services/kubectl-service.ts +3 -6
  47. package/src/types/api.d.ts +1032 -1010
  48. package/src/types/json.d.ts +3 -3
  49. package/src/utils/default-api-key.ts +54 -42
  50. package/src/utils/env-manager.ts +98 -0
  51. package/src/utils/error-handler.ts +24 -15
  52. package/src/utils/logger.ts +12 -12
  53. package/src/utils/markdown-renderer.ts +18 -18
  54. package/src/utils/opencode-validator.ts +134 -0
  55. package/src/utils/token-manager.ts +35 -23
  56. package/tests/commands/chat.test.ts +43 -31
  57. package/tests/commands/code.test.ts +505 -0
  58. package/tests/utils/env-manager.test.ts +199 -0
  59. package/tests/utils/opencode-validator.test.ts +118 -0
  60. package/tsconfig.json +8 -8
  61. package/-27b-it +0 -0
  62. package/examples/README.md +0 -95
  63. package/examples/ai-review.sh +0 -30
  64. package/examples/install-global-security-hook.sh +0 -170
  65. package/examples/security-check.sh +0 -102
  66. package/examples/smart-commit.sh +0 -26
@@ -0,0 +1,1696 @@
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 { ApiKeyService, CreateApiKeyOptions } from '../services/api-key-service'
6
+ import { AuthService } from '../services/auth-service'
7
+ import { handleError } from '../utils/error-handler'
8
+ import * as fs from 'fs'
9
+ import { readFile, writeFile } from 'fs/promises'
10
+ import path from 'path'
11
+ import { spawn } from 'child_process'
12
+ import { updateEnvFile } from '../utils/env-manager'
13
+ import { createAuthenticatedClient } from '../client'
14
+
15
+ // Centralized model configuration
16
+ const MODEL_CONFIG = {
17
+ // Model names used in agent configurations (with provider prefix)
18
+ AGENT_MODELS: {
19
+ primary: 'berget/deepseek-r1',
20
+ small: 'berget/gpt-oss',
21
+ },
22
+ // Model definitions in provider configuration (without prefix)
23
+ PROVIDER_MODELS: {
24
+ 'deepseek-r1': {
25
+ name: 'GLM-4.6',
26
+ limit: {
27
+ output: 4000,
28
+ context: 90000,
29
+ },
30
+ },
31
+ 'gpt-oss': {
32
+ name: 'GPT-OSS',
33
+ limit: {
34
+ output: 4000,
35
+ context: 128000,
36
+ },
37
+ },
38
+ 'llama-8b': {
39
+ name: 'llama-3.1-8b',
40
+ limit: {
41
+ output: 4000,
42
+ context: 128000,
43
+ },
44
+ },
45
+ },
46
+ }
47
+
48
+ /**
49
+ * Check if current directory has git
50
+ */
51
+ function hasGit(): boolean {
52
+ try {
53
+ return fs.existsSync(path.join(process.cwd(), '.git'))
54
+ } catch {
55
+ return false
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Merge opencode configurations using chat completions API
61
+ */
62
+ async function mergeConfigurations(
63
+ currentConfig: any,
64
+ latestConfig: any,
65
+ ): Promise<any> {
66
+ try {
67
+ const client = createAuthenticatedClient()
68
+
69
+ console.log(chalk.blue('🤖 Using AI to merge configurations...'))
70
+
71
+ const mergePrompt = `You are a configuration merge specialist. Merge these two OpenCode configurations:
72
+
73
+ CURRENT CONFIG (user's customizations):
74
+ ${JSON.stringify(currentConfig, null, 2)}
75
+
76
+ LATEST CONFIG (new updates):
77
+ ${JSON.stringify(latestConfig, null, 2)}
78
+
79
+ Merge rules:
80
+ 1. Preserve ALL user customizations from current config
81
+ 2. Add ALL new features and improvements from latest config
82
+ 3. For conflicts, prefer user's customizations but add new latest features
83
+ 4. Maintain valid JSON structure
84
+ 5. Keep the merged configuration complete and functional
85
+
86
+ Return ONLY the merged JSON configuration, no explanations.`
87
+
88
+ const response = await client.POST('/v1/chat/completions', {
89
+ body: {
90
+ model: MODEL_CONFIG.AGENT_MODELS.primary,
91
+ messages: [
92
+ {
93
+ role: 'user',
94
+ content: mergePrompt,
95
+ },
96
+ ],
97
+ temperature: 0.1,
98
+ max_tokens: 8000,
99
+ },
100
+ })
101
+
102
+ if (response.error) {
103
+ console.warn(chalk.yellow('⚠️ AI merge failed, using fallback merge'))
104
+ return fallbackMerge(currentConfig, latestConfig)
105
+ }
106
+
107
+ const content = response.data?.choices?.[0]?.message?.content
108
+ if (!content) {
109
+ console.warn(chalk.yellow('⚠️ No AI response, using fallback merge'))
110
+ return fallbackMerge(currentConfig, latestConfig)
111
+ }
112
+
113
+ try {
114
+ const mergedConfig = JSON.parse(content.trim())
115
+ console.log(chalk.green('✓ AI merge completed successfully'))
116
+ return mergedConfig
117
+ } catch (parseError) {
118
+ console.warn(
119
+ chalk.yellow('⚠️ AI response invalid, using fallback merge'),
120
+ )
121
+ return fallbackMerge(currentConfig, latestConfig)
122
+ }
123
+ } catch (error) {
124
+ console.warn(chalk.yellow('⚠️ AI merge unavailable, using fallback merge'))
125
+ return fallbackMerge(currentConfig, latestConfig)
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Fallback merge logic when AI merge is unavailable
131
+ */
132
+ function fallbackMerge(currentConfig: any, latestConfig: any): any {
133
+ console.log(chalk.blue('🔀 Using fallback merge logic...'))
134
+
135
+ const merged = { ...latestConfig }
136
+
137
+ // Preserve user customizations
138
+ if (currentConfig.theme && currentConfig.theme !== latestConfig.theme) {
139
+ merged.theme = currentConfig.theme
140
+ }
141
+
142
+ if (currentConfig.share && currentConfig.share !== latestConfig.share) {
143
+ merged.share = currentConfig.share
144
+ }
145
+
146
+ // Merge custom agents while preserving new ones
147
+ if (currentConfig.agent) {
148
+ merged.agent = { ...latestConfig.agent }
149
+
150
+ // Add any custom agents from current config
151
+ Object.keys(currentConfig.agent).forEach((agentName) => {
152
+ if (!latestConfig.agent[agentName]) {
153
+ merged.agent[agentName] = currentConfig.agent[agentName]
154
+ console.log(chalk.cyan(` • Preserved custom agent: ${agentName}`))
155
+ }
156
+ })
157
+ }
158
+
159
+ // Merge custom commands while preserving new ones
160
+ if (currentConfig.commands) {
161
+ merged.commands = { ...latestConfig.commands }
162
+
163
+ Object.keys(currentConfig.commands).forEach((commandName) => {
164
+ if (!latestConfig.commands[commandName]) {
165
+ merged.commands[commandName] = currentConfig.commands[commandName]
166
+ console.log(chalk.cyan(` • Preserved custom command: ${commandName}`))
167
+ }
168
+ })
169
+ }
170
+
171
+ // Preserve custom provider settings if user has modified them
172
+ if (currentConfig.provider) {
173
+ merged.provider = { ...latestConfig.provider }
174
+
175
+ // Deep merge provider settings
176
+ Object.keys(currentConfig.provider).forEach((providerName) => {
177
+ if (merged.provider[providerName]) {
178
+ merged.provider[providerName] = {
179
+ ...merged.provider[providerName],
180
+ ...currentConfig.provider[providerName],
181
+ }
182
+ } else {
183
+ merged.provider[providerName] = currentConfig.provider[providerName]
184
+ }
185
+ })
186
+ }
187
+
188
+ return merged
189
+ }
190
+
191
+ /**
192
+ * Helper function to get user confirmation
193
+ */
194
+ async function confirm(question: string, autoYes = false): Promise<boolean> {
195
+ if (autoYes) {
196
+ return true
197
+ }
198
+
199
+ return new Promise((resolve) => {
200
+ const rl = readline.createInterface({
201
+ input: process.stdin,
202
+ output: process.stdout,
203
+ })
204
+
205
+ rl.question(question, (answer) => {
206
+ rl.close()
207
+ resolve(
208
+ answer.toLowerCase() === 'y' ||
209
+ answer.toLowerCase() === 'yes' ||
210
+ answer === '',
211
+ )
212
+ })
213
+ })
214
+ }
215
+
216
+ /**
217
+ * Helper function to get user choice from options
218
+ */
219
+ async function askChoice(
220
+ question: string,
221
+ options: string[],
222
+ defaultChoice?: string,
223
+ ): Promise<string> {
224
+ return new Promise((resolve) => {
225
+ const rl = readline.createInterface({
226
+ input: process.stdin,
227
+ output: process.stdout,
228
+ })
229
+
230
+ rl.question(question, (answer) => {
231
+ rl.close()
232
+
233
+ const trimmed = answer.trim().toLowerCase()
234
+
235
+ // Handle numeric input (1, 2, etc.)
236
+ const numericIndex = parseInt(trimmed) - 1
237
+ if (numericIndex >= 0 && numericIndex < options.length) {
238
+ resolve(options[numericIndex])
239
+ return
240
+ }
241
+
242
+ // Handle text input
243
+ const matchingOption = options.find((option) =>
244
+ option.toLowerCase().startsWith(trimmed),
245
+ )
246
+
247
+ if (matchingOption) {
248
+ resolve(matchingOption)
249
+ } else if (defaultChoice) {
250
+ resolve(defaultChoice)
251
+ } else {
252
+ resolve(options[0]) // Default to first option
253
+ }
254
+ })
255
+ })
256
+ }
257
+
258
+ /**
259
+ * Helper function to get user input
260
+ */
261
+ async function getInput(
262
+ question: string,
263
+ defaultValue: string,
264
+ autoYes = false,
265
+ ): Promise<string> {
266
+ if (autoYes) {
267
+ return defaultValue
268
+ }
269
+
270
+ const rl = readline.createInterface({
271
+ input: process.stdin,
272
+ output: process.stdout,
273
+ })
274
+
275
+ return new Promise<string>((resolve) => {
276
+ rl.question(question, (answer) => {
277
+ rl.close()
278
+ resolve(answer.trim() || defaultValue)
279
+ })
280
+ })
281
+ }
282
+
283
+ /**
284
+ * Get project name from current directory or package.json
285
+ */
286
+ function getProjectName(): string {
287
+ try {
288
+ const packageJsonPath = path.join(process.cwd(), 'package.json')
289
+ if (fs.existsSync(packageJsonPath)) {
290
+ const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8')
291
+ const packageJson = JSON.parse(packageJsonContent)
292
+ return packageJson.name || path.basename(process.cwd())
293
+ }
294
+ } catch (error) {
295
+ // Ignore error and fallback to directory name
296
+ }
297
+ return path.basename(process.cwd())
298
+ }
299
+
300
+ /**
301
+ * Check if opencode is installed
302
+ */
303
+ function checkOpencodeInstalled(): Promise<boolean> {
304
+ return new Promise((resolve) => {
305
+ const child = spawn('opencode', ['--version'], {
306
+ stdio: 'pipe',
307
+ shell: true,
308
+ })
309
+
310
+ child.on('close', (code) => {
311
+ resolve(code === 0)
312
+ })
313
+
314
+ child.on('error', () => {
315
+ resolve(false)
316
+ })
317
+ })
318
+ }
319
+
320
+ /**
321
+ * Install opencode via npm
322
+ */
323
+ async function installOpencode(): Promise<boolean> {
324
+ console.log(chalk.cyan('Installing OpenCode via npm...'))
325
+
326
+ try {
327
+ await new Promise<void>((resolve, reject) => {
328
+ const install = spawn('npm', ['install', '-g', 'opencode-ai'], {
329
+ stdio: 'inherit',
330
+ shell: true,
331
+ })
332
+
333
+ install.on('close', (code) => {
334
+ if (code === 0) {
335
+ console.log(chalk.green('✓ OpenCode installed successfully!'))
336
+ resolve()
337
+ } else {
338
+ reject(new Error(`Installation failed with code ${code}`))
339
+ }
340
+ })
341
+
342
+ install.on('error', reject)
343
+ })
344
+
345
+ // Verify installation
346
+ const opencodeInstalled = await checkOpencodeInstalled()
347
+ if (!opencodeInstalled) {
348
+ console.log(
349
+ chalk.yellow('Installation completed but opencode command not found.'),
350
+ )
351
+ console.log(
352
+ chalk.yellow(
353
+ 'You may need to restart your terminal or check your PATH.',
354
+ ),
355
+ )
356
+ return false
357
+ }
358
+
359
+ return true
360
+ } catch (error) {
361
+ console.error(chalk.red('Failed to install OpenCode:'))
362
+ console.error(error instanceof Error ? error.message : String(error))
363
+ console.log(chalk.blue('\nAlternative installation methods:'))
364
+ console.log(chalk.blue(' curl -fsSL https://opencode.ai/install | bash'))
365
+ console.log(chalk.blue(' Or visit: https://opencode.ai/docs'))
366
+ return false
367
+ }
368
+ }
369
+
370
+ /**
371
+ * Ensure opencode is installed, offering to install if not
372
+ */
373
+ async function ensureOpencodeInstalled(autoYes = false): Promise<boolean> {
374
+ let opencodeInstalled = await checkOpencodeInstalled()
375
+ if (!opencodeInstalled) {
376
+ if (!autoYes) {
377
+ console.log(chalk.red('OpenCode is not installed.'))
378
+ console.log(
379
+ chalk.blue('OpenCode is required for the AI coding assistant.'),
380
+ )
381
+ }
382
+
383
+ if (
384
+ await confirm(
385
+ 'Would you like to install OpenCode automatically? (Y/n): ',
386
+ autoYes,
387
+ )
388
+ ) {
389
+ opencodeInstalled = await installOpencode()
390
+ } else {
391
+ if (!autoYes) {
392
+ console.log(chalk.blue('\nInstallation cancelled.'))
393
+ console.log(
394
+ chalk.blue(
395
+ 'To install manually: curl -fsSL https://opencode.ai/install | bash',
396
+ ),
397
+ )
398
+ console.log(chalk.blue('Or visit: https://opencode.ai/docs'))
399
+ }
400
+ }
401
+ }
402
+
403
+ return opencodeInstalled
404
+ }
405
+
406
+ /**
407
+ * Register code commands
408
+ */
409
+ export function registerCodeCommands(program: Command): void {
410
+ const code = program
411
+ .command(COMMAND_GROUPS.CODE)
412
+ .description('AI-powered coding assistant with OpenCode')
413
+
414
+ code
415
+ .command(SUBCOMMANDS.CODE.INIT)
416
+ .description('Initialize project for AI coding assistant')
417
+ .option('-n, --name <name>', 'Project name (defaults to directory name)')
418
+ .option('-f, --force', 'Overwrite existing configuration')
419
+ .option(
420
+ '-y, --yes',
421
+ 'Automatically answer yes to all prompts (for automation)',
422
+ )
423
+ .action(async (options) => {
424
+ try {
425
+ const projectName = options.name || getProjectName()
426
+ const configPath = path.join(process.cwd(), 'opencode.json')
427
+
428
+ // Check if already initialized
429
+ if (fs.existsSync(configPath) && !options.force) {
430
+ if (!options.yes) {
431
+ console.log(
432
+ chalk.yellow('Project already initialized for OpenCode.'),
433
+ )
434
+ console.log(chalk.dim(`Config file: ${configPath}`))
435
+ }
436
+
437
+ if (
438
+ await confirm('Do you want to reinitialize? (Y/n): ', options.yes)
439
+ ) {
440
+ // Continue with reinitialization
441
+ } else {
442
+ return
443
+ }
444
+ }
445
+
446
+ // Ensure opencode is installed
447
+ if (!(await ensureOpencodeInstalled(options.yes))) {
448
+ return
449
+ }
450
+
451
+ // Check if we have an API key in environment first
452
+ if (process.env.BERGET_API_KEY) {
453
+ console.log(chalk.blue('🔑 Using BERGET_API_KEY from environment - no authentication required'))
454
+ } else {
455
+ // Only require authentication if we don't have an API key
456
+ try {
457
+ const authService = AuthService.getInstance()
458
+ // This will throw if not authenticated
459
+ await authService.whoami()
460
+ } catch (error) {
461
+ console.log(chalk.red('❌ Not authenticated with Berget AI.'))
462
+ console.log(chalk.blue('To get started, you have two options:'))
463
+ console.log('')
464
+ console.log(chalk.yellow('Option 1: Use an existing API key (recommended)'))
465
+ console.log(chalk.cyan(' Set BERGET_API_KEY environment variable:'))
466
+ console.log(chalk.dim(' export BERGET_API_KEY=your_api_key_here'))
467
+ console.log(chalk.cyan(' Or create a .env file in your project:'))
468
+ console.log(chalk.dim(' echo "BERGET_API_KEY=your_api_key_here" > .env'))
469
+ console.log('')
470
+ console.log(chalk.yellow('Option 2: Login and create a new API key'))
471
+ console.log(chalk.cyan(' berget auth login'))
472
+ console.log(chalk.cyan(` berget ${COMMAND_GROUPS.CODE} ${SUBCOMMANDS.CODE.INIT}`))
473
+ console.log('')
474
+ console.log(chalk.blue('Then try again.'))
475
+ return
476
+ }
477
+ }
478
+
479
+ console.log(
480
+ chalk.cyan(`Initializing OpenCode for project: ${projectName}`),
481
+ )
482
+
483
+ // Handle API key selection or creation
484
+ let apiKey: string
485
+ let keyName: string
486
+
487
+ try {
488
+ const apiKeyService = ApiKeyService.getInstance()
489
+
490
+ // Check for environment variable first (regardless of automation mode)
491
+ if (process.env.BERGET_API_KEY) {
492
+ console.log(chalk.blue('🔑 Using BERGET_API_KEY from environment'))
493
+ apiKey = process.env.BERGET_API_KEY
494
+ keyName = `env-key-${projectName}`
495
+ } else {
496
+ // List existing API keys
497
+ if (!options.yes) {
498
+ console.log(chalk.blue('\n📋 Checking existing API keys...'))
499
+ }
500
+ const existingKeys = await apiKeyService.list()
501
+
502
+ if (existingKeys.length > 0 && !options.yes) {
503
+ console.log(chalk.blue('Found existing API keys:'))
504
+ console.log(chalk.dim('─'.repeat(60)))
505
+ existingKeys.forEach((key, index) => {
506
+ console.log(
507
+ `${chalk.cyan((index + 1).toString())}. ${chalk.bold(key.name)} (${key.prefix}...)`,
508
+ )
509
+ console.log(
510
+ chalk.dim(
511
+ ` Created: ${new Date(key.created).toLocaleDateString('sv-SE')}`,
512
+ ),
513
+ )
514
+ console.log(
515
+ chalk.dim(
516
+ ` Last used: ${key.lastUsed ? new Date(key.lastUsed).toLocaleDateString('sv-SE') : 'Never'}`,
517
+ ),
518
+ )
519
+ if (index < existingKeys.length - 1) console.log()
520
+ })
521
+ console.log(chalk.dim('─'.repeat(60)))
522
+ console.log(
523
+ chalk.cyan(`${existingKeys.length + 1}. Create a new API key`),
524
+ )
525
+
526
+ // Get user choice
527
+ const choice = await new Promise<string>((resolve) => {
528
+ const rl = readline.createInterface({
529
+ input: process.stdin,
530
+ output: process.stdout,
531
+ })
532
+ rl.question(
533
+ chalk.blue(
534
+ '\nSelect an option (1-' +
535
+ (existingKeys.length + 1) +
536
+ '): ',
537
+ ),
538
+ (answer) => {
539
+ rl.close()
540
+ resolve(answer.trim())
541
+ },
542
+ )
543
+ })
544
+
545
+ const choiceIndex = parseInt(choice) - 1
546
+
547
+ if (choiceIndex >= 0 && choiceIndex < existingKeys.length) {
548
+ // Use existing key
549
+ const selectedKey = existingKeys[choiceIndex]
550
+ keyName = selectedKey.name
551
+
552
+ // We need to rotate the key to get the actual key value
553
+ console.log(
554
+ chalk.yellow(
555
+ `\n🔄 Rotating API key "${selectedKey.name}" to get the key value...`,
556
+ ),
557
+ )
558
+
559
+ if (
560
+ await confirm(
561
+ chalk.yellow(
562
+ 'This will invalidate the current key. Continue? (Y/n): ',
563
+ ),
564
+ options.yes,
565
+ )
566
+ ) {
567
+ const rotatedKey = await apiKeyService.rotate(
568
+ selectedKey.id.toString(),
569
+ )
570
+ apiKey = rotatedKey.key
571
+ console.log(chalk.green(`✓ API key rotated successfully`))
572
+ } else {
573
+ console.log(
574
+ chalk.yellow(
575
+ 'Cancelled. Please select a different option or create a new key.',
576
+ ),
577
+ )
578
+ return
579
+ }
580
+ } else if (choiceIndex === existingKeys.length) {
581
+ // Create new key
582
+ console.log(chalk.blue('\n🔑 Creating new API key...'))
583
+
584
+ const defaultKeyName = `opencode-${projectName}-${Date.now()}`
585
+ const customName = await getInput(
586
+ chalk.blue(`Enter key name (default: ${defaultKeyName}): `),
587
+ defaultKeyName,
588
+ options.yes,
589
+ )
590
+
591
+ keyName = customName
592
+ const createOptions: CreateApiKeyOptions = { name: keyName }
593
+ const keyData = await apiKeyService.create(createOptions)
594
+ apiKey = keyData.key
595
+ console.log(chalk.green(`✓ Created new API key: ${keyName}`))
596
+ } else {
597
+ console.log(chalk.red('Invalid selection.'))
598
+ return
599
+ }
600
+ } else {
601
+ // No existing keys or automation mode - create new one
602
+ if (!options.yes) {
603
+ console.log(chalk.yellow('No existing API keys found.'))
604
+ console.log(chalk.blue('Creating a new API key...'))
605
+ }
606
+
607
+ const defaultKeyName = `opencode-${projectName}-${Date.now()}`
608
+ const customName = await getInput(
609
+ chalk.blue(`Enter key name (default: ${defaultKeyName}): `),
610
+ defaultKeyName,
611
+ options.yes,
612
+ )
613
+
614
+ keyName = customName
615
+ const createOptions: CreateApiKeyOptions = { name: keyName }
616
+ const keyData = await apiKeyService.create(createOptions)
617
+ apiKey = keyData.key
618
+ console.log(chalk.green(`✓ Created new API key: ${keyName}`))
619
+ }
620
+ }
621
+ } catch (error) {
622
+ if (process.env.BERGET_API_KEY) {
623
+ console.log(chalk.yellow('⚠️ Could not verify API key with Berget API, but continuing with environment key'))
624
+ console.log(chalk.dim('This might be due to network issues or an invalid key'))
625
+ } else {
626
+ console.error(chalk.red('❌ Failed to handle API keys:'))
627
+ console.log(chalk.blue('This could be due to:'))
628
+ console.log(chalk.dim(' • Network connectivity issues'))
629
+ console.log(chalk.dim(' • Invalid authentication credentials'))
630
+ console.log(chalk.dim(' • API service temporarily unavailable'))
631
+ console.log('')
632
+ console.log(chalk.blue('Try using an API key directly:'))
633
+ console.log(chalk.cyan(' export BERGET_API_KEY=your_api_key_here'))
634
+ console.log(chalk.cyan(` berget ${COMMAND_GROUPS.CODE} ${SUBCOMMANDS.CODE.INIT} --yes`))
635
+ handleError('API key operation failed', error)
636
+ }
637
+ return
638
+ }
639
+
640
+ // Prepare .env file path for safe update
641
+ const envPath = path.join(process.cwd(), '.env')
642
+
643
+ // Create opencode.json config with optimized agent-based format
644
+ const config = {
645
+ $schema: 'https://opencode.ai/config.json',
646
+ username: 'berget-code',
647
+ theme: 'berget-dark',
648
+ share: 'manual',
649
+ autoupdate: true,
650
+ model: MODEL_CONFIG.AGENT_MODELS.primary,
651
+ small_model: MODEL_CONFIG.AGENT_MODELS.small,
652
+ agent: {
653
+ fullstack: {
654
+ model: MODEL_CONFIG.AGENT_MODELS.primary,
655
+ temperature: 0.3,
656
+ top_p: 0.9,
657
+ mode: 'primary',
658
+ permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
659
+ description:
660
+ 'Router/coordinator agent for full-stack development with schema-driven architecture',
661
+ prompt:
662
+ 'Voice: Scandinavian calm—precise, concise, confident; no fluff. You are Berget Code Fullstack agent. Act as a router and coordinator in a monorepo. Bottom-up schema: database → OpenAPI → generated types. Top-down types: API → UI → components. Use openapi-fetch and Zod at every boundary; compile-time errors are desired when contracts change. Routing rules: if task/paths match /apps/frontend or React (.tsx) → use frontend; if /apps/app or Expo/React Native → app; if /infra, /k8s, flux-system, kustomization.yaml, Helm values → devops; if /services, Koa routers, services/adapters/domain → backend. If ambiguous, remain fullstack and outline the end-to-end plan, then delegate subtasks to the right persona. Security: validate inputs; secrets via FluxCD SOPS/Sealed Secrets. Documentation is generated from code—never duplicated. CRITICAL: When all implementation tasks are complete and ready for merge, ALWAYS invoke @quality subagent to handle testing, building, and complete PR management including URL provision.',
663
+ },
664
+ frontend: {
665
+ model: MODEL_CONFIG.AGENT_MODELS.primary,
666
+ temperature: 0.4,
667
+ top_p: 0.9,
668
+ mode: 'primary',
669
+ permission: { edit: 'allow', bash: 'deny', webfetch: 'allow' },
670
+ note: 'Bash access is denied for frontend persona to prevent shell command execution in UI environments. This restriction enforces security and architectural boundaries.',
671
+ description:
672
+ 'Builds Scandinavian, type-safe UIs with React, Tailwind, Shadcn.',
673
+ prompt:
674
+ 'You are Berget Code Frontend agent. Voice: Scandinavian calm—precise, concise, confident. React 18 + TypeScript. Tailwind + Shadcn UI only via the design system (index.css, tailwind.config.ts). Use semantic tokens for color/spacing/typography/motion; never ad-hoc classes or inline colors. Components are pure and responsive; props-first data; minimal global state (Zustand/Jotai). Accessibility and keyboard navigation mandatory. Mock data only at init under /data via typed hooks (e.g., useProducts() reading /data/products.json). Design: minimal, balanced, quiet motion. CRITICAL: When all frontend implementation tasks are complete and ready for merge, ALWAYS invoke @quality subagent to handle testing, building, and complete PR management including URL provision.',
675
+ },
676
+ backend: {
677
+ model: MODEL_CONFIG.AGENT_MODELS.primary,
678
+ temperature: 0.3,
679
+ top_p: 0.9,
680
+ mode: 'primary',
681
+ permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
682
+ description:
683
+ 'Functional, modular Koa + TypeScript services; schema-first with code quality focus.',
684
+ prompt:
685
+ 'You are Berget Code Backend agent. Voice: Scandinavian calm—precise, concise, confident. TypeScript + Koa. Prefer many small pure functions; avoid big try/catch blocks. Routes thin; logic in services/adapters/domain. Validate with Zod; auto-generate OpenAPI. Adapters isolate external systems; domain never depends on framework. Test with supertest; idempotent and stateless by default. Each microservice emits an OpenAPI contract; changes propagate upward to types. Code Quality & Refactoring Principles: Apply Single Responsibility Principle, fail fast with explicit errors, eliminate code duplication, remove nested complexity, use descriptive error codes, keep functions under 30 lines. Always leave code cleaner and more readable than you found it. CRITICAL: When all backend implementation tasks are complete and ready for merge, ALWAYS invoke @quality subagent to handle testing, building, and complete PR management including URL provision.',
686
+ },
687
+ devops: {
688
+ model: MODEL_CONFIG.AGENT_MODELS.primary,
689
+ temperature: 0.3,
690
+ top_p: 0.8,
691
+ mode: 'primary',
692
+ permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
693
+ description:
694
+ 'Declarative GitOps infra with FluxCD, Kustomize, Helm, operators.',
695
+ prompt:
696
+ 'You are Berget Code DevOps agent. Voice: Scandinavian calm—precise, concise, confident. Start simple: k8s/{deployment,service,ingress}. Add FluxCD sync to repo and image automation. Use Kustomize bases/overlays (staging, production). Add dependencies via Helm from upstream sources; prefer native operators when available (CloudNativePG, cert-manager, external-dns). SemVer with -rc tags keeps CI environments current. Observability with Prometheus/Grafana. No manual kubectl in production—Git is the source of truth.',
697
+ },
698
+ app: {
699
+ model: MODEL_CONFIG.AGENT_MODELS.primary,
700
+ temperature: 0.4,
701
+ top_p: 0.9,
702
+ mode: 'primary',
703
+ permission: { edit: 'allow', bash: 'deny', webfetch: 'allow' },
704
+ note: 'Bash access is denied for app persona to prevent shell command execution in mobile/Expo environments. This restriction enforces security and architectural boundaries.',
705
+ description:
706
+ 'Expo + React Native apps; props-first, offline-aware, shared tokens.',
707
+ prompt:
708
+ 'You are Berget Code App agent. Voice: Scandinavian calm—precise, concise, confident. Expo + React Native + TypeScript. Structure by components/hooks/services/navigation. Components are pure; data via props; refactor shared logic into hooks/stores. Share tokens with frontend. Mock data in /data via typed hooks; later replace with live APIs. Offline via SQLite/MMKV; notifications via Expo. Request permissions only when needed. Subtle, meaningful motion; light/dark parity.',
709
+ },
710
+ security: {
711
+ model: MODEL_CONFIG.AGENT_MODELS.primary,
712
+ temperature: 0.2,
713
+ top_p: 0.8,
714
+ mode: 'subagent',
715
+ permission: { edit: 'deny', bash: 'allow', webfetch: 'allow' },
716
+ description:
717
+ 'Security specialist for pentesting, OWASP compliance, and vulnerability assessments.',
718
+ prompt:
719
+ 'Voice: Scandinavian calm—precise, concise, confident. You are Berget Code Security agent. Expert in application security, penetration testing, and OWASP standards. Core responsibilities: Conduct security assessments and penetration tests, Validate OWASP Top 10 compliance, Review code for security vulnerabilities, Implement security headers and Content Security Policy (CSP), Audit API security, Check for sensitive data exposure, Validate input sanitization and output encoding, Assess dependency security and supply chain risks. Tools and techniques: OWASP ZAP, Burp Suite, security linters, dependency scanners, manual code review. Always provide specific, actionable security recommendations with priority levels.',
720
+ },
721
+ quality: {
722
+ model: MODEL_CONFIG.AGENT_MODELS.primary,
723
+ temperature: 0.1,
724
+ top_p: 0.9,
725
+ mode: 'subagent',
726
+ permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
727
+ description:
728
+ 'Quality assurance specialist for testing, building, and PR management.',
729
+ prompt:
730
+ 'Voice: Scandinavian calm—precise, concise, confident. You are Berget Code Quality agent. Specialist in code quality assurance, testing, building, and pull request management.\n\nCore responsibilities:\n - Run comprehensive test suites (npm test, npm run test, jest, vitest)\n - Execute build processes (npm run build, webpack, vite, tsc)\n - Create and manage pull requests with proper descriptions\n - Monitor GitHub for Copilot/reviewer comments\n - Ensure code quality standards are met\n - Validate linting and formatting (npm run lint, prettier)\n - Check test coverage and performance benchmarks\n - Handle CI/CD pipeline validation\n\nCommon CLI commands:\n - npm test or npm run test (run test suite)\n - npm run build (build project)\n - npm run lint (run linting)\n - npm run format (format code)\n - npm run test:coverage (check coverage)\n - gh pr create (create pull request)\n - gh pr view --comments (check PR comments)\n - git add . && git commit -m "message" && git push (commit and push)\n\nPR Workflow:\n 1. Ensure all tests pass: npm test\n 2. Build successfully: npm run build\n 3. Create/update PR with clear description\n 4. Monitor for reviewer comments\n 5. Address feedback promptly\n 6. Update PR with fixes\n 7. Ensure CI checks pass\n\nAlways provide specific command examples and wait for processes to complete before proceeding.',
731
+ },
732
+ },
733
+ command: {
734
+ fullstack: {
735
+ description: 'Switch to Fullstack (router)',
736
+ template: '{{input}}',
737
+ agent: 'fullstack',
738
+ },
739
+ route: {
740
+ description:
741
+ 'Let Fullstack auto-route to the right persona based on files/intent',
742
+ template: 'ROUTE {{input}}',
743
+ agent: 'fullstack',
744
+ subtask: true,
745
+ },
746
+ frontend: {
747
+ description: 'Switch to Frontend persona',
748
+ template: '{{input}}',
749
+ agent: 'frontend',
750
+ },
751
+ backend: {
752
+ description: 'Switch to Backend persona',
753
+ template: '{{input}}',
754
+ agent: 'backend',
755
+ },
756
+ devops: {
757
+ description: 'Switch to DevOps persona',
758
+ template: '{{input}}',
759
+ agent: 'devops',
760
+ },
761
+ app: {
762
+ description: 'Switch to App persona',
763
+ template: '{{input}}',
764
+ agent: 'app',
765
+ },
766
+ quality: {
767
+ description:
768
+ 'Switch to Quality agent for testing, building, and PR management',
769
+ template: '{{input}}',
770
+ agent: 'quality',
771
+ },
772
+ },
773
+ watcher: {
774
+ ignore: ['node_modules', 'dist', '.git', 'coverage'],
775
+ },
776
+ provider: {
777
+ berget: {
778
+ npm: '@ai-sdk/openai-compatible',
779
+ name: 'Berget AI',
780
+ options: { baseURL: 'https://api.berget.ai/v1' },
781
+ models: MODEL_CONFIG.PROVIDER_MODELS,
782
+ },
783
+ },
784
+ }
785
+
786
+ // Ask for permission to create config files
787
+ if (!options.yes) {
788
+ console.log(chalk.blue('\nAbout to create configuration files:'))
789
+ console.log(chalk.dim(`Config: ${configPath}`))
790
+ console.log(chalk.dim(`Environment: ${envPath}`))
791
+ console.log(
792
+ chalk.dim(
793
+ `Documentation: ${path.join(process.cwd(), 'AGENTS.md')} (if not exists)`,
794
+ ),
795
+ )
796
+ console.log(
797
+ chalk.dim(
798
+ `Environment: ${path.join(process.cwd(), '.env')} will be updated`,
799
+ ),
800
+ )
801
+ console.log(
802
+ chalk.dim('This will configure OpenCode to use Berget AI models.'),
803
+ )
804
+ console.log(chalk.cyan('\n💡 Benefits:'))
805
+ console.log(
806
+ chalk.cyan(
807
+ ' • API key stored separately in .env file (not committed to Git)',
808
+ ),
809
+ )
810
+ console.log(
811
+ chalk.cyan(' • Easy cost separation per project/customer'),
812
+ )
813
+ console.log(
814
+ chalk.cyan(' • Secure key management with environment variables'),
815
+ )
816
+ console.log(
817
+ chalk.cyan(
818
+ " • Project-specific agent documentation (won't overwrite existing)",
819
+ ),
820
+ )
821
+ }
822
+
823
+ if (
824
+ await confirm('\nCreate configuration files? (Y/n): ', options.yes)
825
+ ) {
826
+ try {
827
+ // Safely update .env file using dotenv
828
+ await updateEnvFile({
829
+ envPath,
830
+ key: 'BERGET_API_KEY',
831
+ value: apiKey,
832
+ comment: `Berget AI Configuration for ${projectName} - Generated by berget code init - Do not commit to version control`,
833
+ })
834
+
835
+ // Create opencode.json
836
+ await writeFile(configPath, JSON.stringify(config, null, 2))
837
+ console.log(chalk.green(`✓ Created opencode.json`))
838
+ console.log(chalk.dim(` Model: ${config.model}`))
839
+ console.log(chalk.dim(` Small Model: ${config.small_model}`))
840
+ console.log(chalk.dim(` Theme: ${config.theme}`))
841
+ console.log(
842
+ chalk.dim(` API Key: Stored in .env as BERGET_API_KEY`),
843
+ )
844
+
845
+ // Create AGENTS.md documentation only if it doesn't exist
846
+ const agentsMdPath = path.join(process.cwd(), 'AGENTS.md')
847
+ if (!fs.existsSync(agentsMdPath)) {
848
+ const agentsMdContent = `# Berget Code Agents
849
+
850
+ This document describes the specialized agents available in this project for use with OpenCode.
851
+
852
+ ## Available Agents
853
+
854
+ ### Primary Agents
855
+
856
+ #### fullstack
857
+ Router/coordinator agent for full-stack development with schema-driven architecture. Handles routing between different personas based on file paths and task requirements.
858
+
859
+ **Use when:**
860
+ - Working across multiple parts of a monorepo
861
+ - Need to coordinate between frontend, backend, devops, and app
862
+ - Starting new projects and need to determine tech stack
863
+
864
+ **Key features:**
865
+ - Schema-driven development (database → OpenAPI → types)
866
+ - Automatic routing to appropriate persona
867
+ - Tech stack discovery and recommendations
868
+
869
+ #### frontend
870
+ Builds Scandinavian, type-safe UIs with React, Tailwind, and Shadcn.
871
+
872
+ **Use when:**
873
+ - Working with React components (.tsx files)
874
+ - Frontend development in /apps/frontend
875
+ - UI/UX implementation
876
+
877
+ **Key features:**
878
+ - Design system integration
879
+ - Semantic tokens and accessibility
880
+ - Props-first component architecture
881
+
882
+ #### backend
883
+ Functional, modular Koa + TypeScript services with schema-first approach and code quality focus.
884
+
885
+ **Use when:**
886
+ - Working with Koa routers and services
887
+ - Backend development in /services
888
+ - API development and database work
889
+
890
+ **Key features:**
891
+ - Zod validation and OpenAPI generation
892
+ - Code quality and refactoring principles
893
+ - PR workflow integration
894
+
895
+ #### devops
896
+ Declarative GitOps infrastructure with FluxCD, Kustomize, Helm, and operators.
897
+
898
+ **Use when:**
899
+ - Working with Kubernetes manifests
900
+ - Infrastructure in /infra or /k8s
901
+ - CI/CD and deployment configurations
902
+
903
+ **Key features:**
904
+ - GitOps workflows
905
+ - Operator-first approach
906
+ - SemVer with release candidates
907
+
908
+ #### app
909
+ Expo + React Native applications with props-first architecture and offline awareness.
910
+
911
+ **Use when:**
912
+ - Mobile app development with Expo
913
+ - React Native projects in /apps/app
914
+ - Cross-platform mobile development
915
+
916
+ **Key features:**
917
+ - Shared design tokens with frontend
918
+ - Offline-first architecture
919
+ - Expo integration
920
+
921
+ ### Subagents
922
+
923
+ #### security
924
+ Security specialist for penetration testing, OWASP compliance, and vulnerability assessments.
925
+
926
+ **Use when:**
927
+ - Need security review of code changes
928
+ - OWASP Top 10 compliance checks
929
+ - Vulnerability assessments
930
+
931
+ **Key features:**
932
+ - OWASP standards compliance
933
+ - Security best practices
934
+ - Actionable remediation strategies
935
+
936
+ #### quality
937
+ Quality assurance specialist for testing, building, and PR management.
938
+
939
+ **Use when:**
940
+ - Need to run test suites and build processes
941
+ - Creating or updating pull requests
942
+ - Monitoring GitHub for reviewer comments
943
+ - Ensuring code quality standards
944
+
945
+ **Key features:**
946
+ - Comprehensive testing and building workflows
947
+ - PR creation and management
948
+ - GitHub integration for reviewer feedback
949
+ - CLI command expertise for quality assurance
950
+
951
+ ## Usage
952
+
953
+ ### Switching Agents
954
+ Use the \`<tab>\` key to cycle through primary agents during a session.
955
+
956
+ ### Manual Agent Selection
957
+ Use commands to switch to specific agents:
958
+ - \`/fullstack\` - Switch to Fullstack agent
959
+ - \`/frontend\` - Switch to Frontend agent
960
+ - \`/backend\` - Switch to Backend agent
961
+ - \`/devops\` - Switch to DevOps agent
962
+ - \`/app\` - Switch to App agent
963
+ - \`/quality\` - Switch to Quality agent for testing and PR management
964
+
965
+ ### Using Subagents
966
+ Mention subagents with \`@\` symbol:
967
+ - \`@security review this authentication implementation\`
968
+ - \`@quality run tests and create PR for these changes\`
969
+
970
+ ## Routing Rules
971
+
972
+ The fullstack agent automatically routes tasks based on file patterns:
973
+
974
+ - \`/apps/frontend\` or \`.tsx\` files → frontend
975
+ - \`/apps/app\` or Expo/React Native → app
976
+ - \`/infra\`, \`/k8s\`, FluxCD, Helm → devops
977
+ - \`/services\`, Koa routers → backend
978
+
979
+ ## Configuration
980
+
981
+ All agents are configured in \`opencode.json\` with:
982
+ - Specialized prompts and temperature settings
983
+ - Appropriate tool permissions
984
+ - Model optimizations for their specific tasks
985
+
986
+ ## Environment Setup
987
+
988
+ Configure \`.env\` with your API key:
989
+ \`\`\`
990
+ BERGET_API_KEY=your_api_key_here
991
+ \`\`\`
992
+
993
+ ## Workflow
994
+
995
+ All agents follow these principles:
996
+ - Never work directly in main branch
997
+ - Follow branch strategy and commit conventions
998
+ - Create PRs for new functionality
999
+ - Run tests before committing
1000
+ - Address reviewer feedback promptly
1001
+
1002
+ ---
1003
+
1004
+ *Generated by berget code init for ${projectName}*
1005
+ `
1006
+
1007
+ await writeFile(agentsMdPath, agentsMdContent)
1008
+ console.log(chalk.green(`✓ Created AGENTS.md`))
1009
+ console.log(
1010
+ chalk.dim(` Documentation for available agents and usage`),
1011
+ )
1012
+ } else {
1013
+ console.log(
1014
+ chalk.yellow(`⚠ AGENTS.md already exists, skipping creation`),
1015
+ )
1016
+ }
1017
+
1018
+ // Check if .gitignore exists and add .env if not already there
1019
+ const gitignorePath = path.join(process.cwd(), '.gitignore')
1020
+ let gitignoreContent = ''
1021
+
1022
+ if (fs.existsSync(gitignorePath)) {
1023
+ gitignoreContent = fs.readFileSync(gitignorePath, 'utf8')
1024
+ }
1025
+
1026
+ if (!gitignoreContent.includes('.env')) {
1027
+ gitignoreContent +=
1028
+ (gitignoreContent.endsWith('\n') ? '' : '\n') + '.env\n'
1029
+ await writeFile(gitignorePath, gitignoreContent)
1030
+ console.log(chalk.green(`✓ Added .env to .gitignore`))
1031
+ }
1032
+ } catch (error) {
1033
+ console.error(chalk.red('Failed to create config files:'))
1034
+ handleError('Config file creation failed', error)
1035
+ return
1036
+ }
1037
+ } else {
1038
+ console.log(chalk.yellow('Configuration file creation cancelled.'))
1039
+ return
1040
+ }
1041
+
1042
+ console.log(chalk.green('\n✅ Project initialized successfully!'))
1043
+ console.log(chalk.blue('Next steps:'))
1044
+ console.log(
1045
+ chalk.blue(` berget ${COMMAND_GROUPS.CODE} ${SUBCOMMANDS.CODE.RUN}`),
1046
+ )
1047
+ console.log(chalk.blue(' Or run: opencode'))
1048
+ } catch (error) {
1049
+ handleError('Failed to initialize project', error)
1050
+ }
1051
+ })
1052
+
1053
+ code
1054
+ .command(SUBCOMMANDS.CODE.RUN)
1055
+ .description('Run AI coding assistant')
1056
+ .argument('[prompt]', 'Prompt to send directly to OpenCode')
1057
+ .option('-m, --model <model>', 'Model to use (overrides config)')
1058
+ .option('-a, --analysis', 'Use fast analysis model for context building')
1059
+ .option('--no-config', 'Run without loading project config')
1060
+ .option(
1061
+ '-y, --yes',
1062
+ 'Automatically answer yes to all prompts (for automation)',
1063
+ )
1064
+ .action(async (prompt: string, options: any) => {
1065
+ try {
1066
+ const configPath = path.join(process.cwd(), 'opencode.json')
1067
+
1068
+ // Ensure opencode is installed
1069
+ if (!(await ensureOpencodeInstalled(options.yes))) {
1070
+ return
1071
+ }
1072
+
1073
+ let config: any = null
1074
+ if (!options.noConfig && fs.existsSync(configPath)) {
1075
+ try {
1076
+ const configContent = await readFile(configPath, 'utf8')
1077
+ config = JSON.parse(configContent)
1078
+ console.log(
1079
+ chalk.dim(`Loaded config for project: ${config.projectName}`),
1080
+ )
1081
+ console.log(
1082
+ chalk.dim(
1083
+ `Models: Analysis=${config.analysisModel}, Build=${config.buildModel}`,
1084
+ ),
1085
+ )
1086
+ } catch (error) {
1087
+ console.log(chalk.yellow('Warning: Failed to load opencode.json'))
1088
+ }
1089
+ }
1090
+
1091
+ if (!config) {
1092
+ console.log(chalk.yellow('No project configuration found.'))
1093
+ console.log(
1094
+ chalk.blue(
1095
+ `Run ${chalk.bold(`berget ${COMMAND_GROUPS.CODE} ${SUBCOMMANDS.CODE.INIT}`)} first.`,
1096
+ ),
1097
+ )
1098
+ return
1099
+ }
1100
+
1101
+ // Set environment variables for opencode
1102
+ const env = { ...process.env }
1103
+ env.OPENCODE_API_KEY = config.apiKey
1104
+
1105
+ // Prepare opencode command
1106
+ const opencodeArgs: string[] = []
1107
+
1108
+ if (prompt) {
1109
+ opencodeArgs.push('run', prompt)
1110
+ }
1111
+
1112
+ // Choose model based on analysis flag or override
1113
+ let selectedModel = options.model || config.buildModel
1114
+ if (options.analysis && !options.model) {
1115
+ selectedModel = config.analysisModel
1116
+ }
1117
+
1118
+ if (selectedModel) {
1119
+ opencodeArgs.push('--model', selectedModel)
1120
+ }
1121
+
1122
+ console.log(chalk.cyan('Starting OpenCode...'))
1123
+
1124
+ // Spawn opencode process
1125
+ const opencode = spawn('opencode', opencodeArgs, {
1126
+ stdio: 'inherit',
1127
+ env: env,
1128
+ shell: true,
1129
+ })
1130
+
1131
+ opencode.on('close', (code) => {
1132
+ if (code !== 0) {
1133
+ console.log(chalk.red(`OpenCode exited with code ${code}`))
1134
+ }
1135
+ })
1136
+
1137
+ opencode.on('error', (error) => {
1138
+ console.error(chalk.red('Failed to start OpenCode:'))
1139
+ console.error(error.message)
1140
+ })
1141
+ } catch (error) {
1142
+ handleError('Failed to run OpenCode', error)
1143
+ }
1144
+ })
1145
+
1146
+ code
1147
+ .command(SUBCOMMANDS.CODE.UPDATE)
1148
+ .description('Update OpenCode and agents to latest versions')
1149
+ .option('-f, --force', 'Force update even if already latest')
1150
+ .option(
1151
+ '-y, --yes',
1152
+ 'Automatically answer yes to all prompts (for automation)',
1153
+ )
1154
+ .action(async (options) => {
1155
+ try {
1156
+ console.log(chalk.cyan('🔄 Updating OpenCode configuration...'))
1157
+
1158
+ // Ensure opencode is installed first
1159
+ if (!(await ensureOpencodeInstalled(options.yes))) {
1160
+ return
1161
+ }
1162
+
1163
+ const configPath = path.join(process.cwd(), 'opencode.json')
1164
+
1165
+ // Check if project is initialized
1166
+ if (!fs.existsSync(configPath)) {
1167
+ console.log(chalk.red('❌ No OpenCode configuration found.'))
1168
+ console.log(
1169
+ chalk.blue(
1170
+ `Run ${chalk.bold(`berget ${COMMAND_GROUPS.CODE} ${SUBCOMMANDS.CODE.INIT}`)} first.`,
1171
+ ),
1172
+ )
1173
+ return
1174
+ }
1175
+
1176
+ // Read current configuration
1177
+ let currentConfig: any
1178
+ try {
1179
+ const configContent = await readFile(configPath, 'utf8')
1180
+ currentConfig = JSON.parse(configContent)
1181
+ } catch (error) {
1182
+ console.error(chalk.red('Failed to read current opencode.json:'))
1183
+ handleError('Config read failed', error)
1184
+ return
1185
+ }
1186
+
1187
+ console.log(chalk.blue('📋 Current configuration:'))
1188
+ console.log(chalk.dim(` Model: ${currentConfig.model}`))
1189
+ console.log(chalk.dim(` Theme: ${currentConfig.theme}`))
1190
+ console.log(
1191
+ chalk.dim(
1192
+ ` Agents: ${Object.keys(currentConfig.agent || {}).length} configured`,
1193
+ ),
1194
+ )
1195
+
1196
+ // Create latest configuration with all improvements
1197
+ const latestConfig = {
1198
+ $schema: 'https://opencode.ai/config.json',
1199
+ username: 'berget-code',
1200
+ theme: 'berget-dark',
1201
+ share: 'manual',
1202
+ autoupdate: true,
1203
+ model: MODEL_CONFIG.AGENT_MODELS.primary,
1204
+ small_model: MODEL_CONFIG.AGENT_MODELS.small,
1205
+ agent: {
1206
+ fullstack: {
1207
+ model: MODEL_CONFIG.AGENT_MODELS.primary,
1208
+ temperature: 0.3,
1209
+ top_p: 0.9,
1210
+ mode: 'primary',
1211
+ permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
1212
+ description:
1213
+ 'Router/coordinator agent for full-stack development with schema-driven architecture',
1214
+ prompt:
1215
+ 'Voice: Scandinavian calm—precise, concise, confident; no fluff. You are Berget Code Fullstack agent. Act as a router and coordinator in a monorepo. Bottom-up schema: database → OpenAPI → generated types. Top-down types: API → UI → components. Use openapi-fetch and Zod at every boundary; compile-time errors are desired when contracts change. Routing rules: if task/paths match /apps/frontend or React (.tsx) → use frontend; if /apps/app or Expo/React Native → app; if /infra, /k8s, flux-system, kustomization.yaml, Helm values → devops; if /services, Koa routers, services/adapters/domain → backend. If ambiguous, remain fullstack and outline the end-to-end plan, then delegate subtasks to the right persona. Security: validate inputs; secrets via FluxCD SOPS/Sealed Secrets. Documentation is generated from code—never duplicated. CRITICAL: When all implementation tasks are complete and ready for merge, ALWAYS invoke @quality subagent to handle testing, building, and complete PR management including URL provision.',
1216
+ },
1217
+ frontend: {
1218
+ model: MODEL_CONFIG.AGENT_MODELS.primary,
1219
+ temperature: 0.4,
1220
+ top_p: 0.9,
1221
+ mode: 'primary',
1222
+ permission: { edit: 'allow', bash: 'deny', webfetch: 'allow' },
1223
+ note: 'Bash access is denied for frontend persona to prevent shell command execution in UI environments. This restriction enforces security and architectural boundaries.',
1224
+ description:
1225
+ 'Builds Scandinavian, type-safe UIs with React, Tailwind, Shadcn.',
1226
+ prompt:
1227
+ 'You are Berget Code Frontend agent. Voice: Scandinavian calm—precise, concise, confident. React 18 + TypeScript. Tailwind + Shadcn UI only via the design system (index.css, tailwind.config.ts). Use semantic tokens for color/spacing/typography/motion; never ad-hoc classes or inline colors. Components are pure and responsive; props-first data; minimal global state (Zustand/Jotai). Accessibility and keyboard navigation mandatory. Mock data only at init under /data via typed hooks (e.g., useProducts() reading /data/products.json). Design: minimal, balanced, quiet motion. CRITICAL: When all frontend implementation tasks are complete and ready for merge, ALWAYS invoke @quality subagent to handle testing, building, and complete PR management including URL provision.',
1228
+ },
1229
+ backend: {
1230
+ model: MODEL_CONFIG.AGENT_MODELS.primary,
1231
+ temperature: 0.3,
1232
+ top_p: 0.9,
1233
+ mode: 'primary',
1234
+ permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
1235
+ description:
1236
+ 'Functional, modular Koa + TypeScript services; schema-first with code quality focus.',
1237
+ prompt:
1238
+ 'You are Berget Code Backend agent. Voice: Scandinavian calm—precise, concise, confident. TypeScript + Koa. Prefer many small pure functions; avoid big try/catch blocks. Routes thin; logic in services/adapters/domain. Validate with Zod; auto-generate OpenAPI. Adapters isolate external systems; domain never depends on framework. Test with supertest; idempotent and stateless by default. Each microservice emits an OpenAPI contract; changes propagate upward to types. Code Quality & Refactoring Principles: Apply Single Responsibility Principle, fail fast with explicit errors, eliminate code duplication, remove nested complexity, use descriptive error codes, keep functions under 30 lines. Always leave code cleaner and more readable than you found it. CRITICAL: When all backend implementation tasks are complete and ready for merge, ALWAYS invoke @quality subagent to handle testing, building, and complete PR management including URL provision.',
1239
+ },
1240
+ devops: {
1241
+ model: MODEL_CONFIG.AGENT_MODELS.primary,
1242
+ temperature: 0.3,
1243
+ top_p: 0.8,
1244
+ mode: 'primary',
1245
+ permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
1246
+ description:
1247
+ 'Declarative GitOps infra with FluxCD, Kustomize, Helm, operators.',
1248
+ prompt:
1249
+ 'You are Berget Code DevOps agent. Voice: Scandinavian calm—precise, concise, confident. Start simple: k8s/{deployment,service,ingress}. Add FluxCD sync to repo and image automation. Use Kustomize bases/overlays (staging, production). Add dependencies via Helm from upstream sources; prefer native operators when available (CloudNativePG, cert-manager, external-dns). SemVer with -rc tags keeps CI environments current. Observability with Prometheus/Grafana. No manual kubectl in production—Git is the source of truth. For testing, building, and PR management, use @quality subagent.',
1250
+ },
1251
+ app: {
1252
+ model: MODEL_CONFIG.AGENT_MODELS.primary,
1253
+ temperature: 0.4,
1254
+ top_p: 0.9,
1255
+ mode: 'primary',
1256
+ permission: { edit: 'allow', bash: 'deny', webfetch: 'allow' },
1257
+ note: 'Bash access is denied for app persona to prevent shell command execution in mobile/Expo environments. This restriction enforces security and architectural boundaries.',
1258
+ description:
1259
+ 'Expo + React Native apps; props-first, offline-aware, shared tokens.',
1260
+ prompt:
1261
+ 'You are Berget Code App agent. Voice: Scandinavian calm—precise, concise, confident. Expo + React Native + TypeScript. Structure by components/hooks/services/navigation. Components are pure; data via props; refactor shared logic into hooks/stores. Share tokens with frontend. Mock data in /data via typed hooks; later replace with live APIs. Offline via SQLite/MMKV; notifications via Expo. Request permissions only when needed. Subtle, meaningful motion; light/dark parity. For testing, building, and PR management, use @quality subagent.',
1262
+ },
1263
+ security: {
1264
+ model: MODEL_CONFIG.AGENT_MODELS.primary,
1265
+ temperature: 0.2,
1266
+ top_p: 0.8,
1267
+ mode: 'subagent',
1268
+ permission: { edit: 'deny', bash: 'allow', webfetch: 'allow' },
1269
+ description:
1270
+ 'Security specialist for pentesting, OWASP compliance, and vulnerability assessments.',
1271
+ prompt:
1272
+ 'Voice: Scandinavian calm—precise, concise, confident. You are Berget Code Security agent. Expert in application security, penetration testing, and OWASP standards. Core responsibilities: Conduct security assessments and penetration tests, Validate OWASP Top 10 compliance, Review code for security vulnerabilities, Implement security headers and Content Security Policy (CSP), Audit API security, Check for sensitive data exposure, Validate input sanitization and output encoding, Assess dependency security and supply chain risks. Tools and techniques: OWASP ZAP, Burp Suite, security linters, dependency scanners, manual code review. Always provide specific, actionable security recommendations with priority levels. Workflow: Always follow branch_strategy and commit_convention from workflow section. Never work directly in main. Agent awareness: Review code from all personas (frontend, backend, app, devops). If implementation changes are needed, suggest <tab> to switch to appropriate persona after security assessment.',
1273
+ },
1274
+ quality: {
1275
+ model: MODEL_CONFIG.AGENT_MODELS.primary,
1276
+ temperature: 0.1,
1277
+ top_p: 0.9,
1278
+ mode: 'subagent',
1279
+ permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
1280
+ description:
1281
+ 'Quality assurance specialist for testing, building, and complete PR management.',
1282
+ prompt:
1283
+ 'Voice: Scandinavian calm—precise, concise, confident. You are Berget Code Quality agent. Specialist in code quality assurance, testing, building, and complete pull request lifecycle management.\n\nCore responsibilities:\n - Run comprehensive test suites (npm test, npm run test, jest, vitest)\n - Execute build processes (npm run build, webpack, vite, tsc)\n - Create and manage pull requests with proper descriptions\n - Handle merge conflicts and keep main updated\n - Monitor GitHub for reviewer comments and address them\n - Ensure code quality standards are met\n - Validate linting and formatting (npm run lint, prettier)\n - Check test coverage and performance benchmarks\n - Handle CI/CD pipeline validation\n\nComplete PR Workflow:\n 1. Ensure all tests pass: npm test\n 2. Build successfully: npm run build\n 3. Commit all changes with proper message\n 4. Push to feature branch\n 5. Update main branch and handle merge conflicts\n 6. Create or update PR with comprehensive description\n 7. Monitor for reviewer comments\n 8. Address feedback and push updates\n 9. Always provide PR URL for user review\n\nEssential CLI commands:\n - npm test or npm run test (run test suite)\n - npm run build (build project)\n - npm run lint (run linting)\n - npm run format (format code)\n - npm run test:coverage (check coverage)\n - git add . && git commit -m "message" && git push (commit and push)\n - git checkout main && git pull origin main (update main)\n - git checkout feature-branch && git merge main (handle conflicts)\n - gh pr create --title "title" --body "body" (create PR)\n - gh pr view --comments (check PR comments)\n - gh pr edit --title "title" --body "body" (update PR)\n\nPR Creation Process:\n - Always include clear summary of changes\n - List technical details and improvements\n - Include testing and validation results\n - Add any breaking changes or migration notes\n - Provide PR URL immediately after creation\n\nMerge Conflict Resolution:\n - Always update main before creating/updating PR\n - Handle conflicts automatically when possible\n - If conflicts require human input, clearly explain what\'s needed\n - Re-run tests after conflict resolution\n - Ensure CI checks pass before finalizing\n\nReviewer Comment Handling:\n - Monitor PR for new comments regularly\n - Address each comment specifically\n - Push fixes and update PR accordingly\n - Always provide updated PR URL after changes\n - Continue monitoring until all feedback is addressed\n\nCRITICAL: When invoked by other agents (@quality), you MUST:\n - Complete all testing and building tasks\n - Handle entire PR creation/update process\n - Provide PR URL at the end\n - Ensure main branch is properly merged\n - Handle any merge conflicts automatically\n\nAlways provide specific command examples and wait for processes to complete before proceeding.\nWorkflow: Always follow branch_strategy and commit_convention from workflow section. Never work directly in main.\nAgent awareness: Can be invoked by any primary agent (@quality) for complete testing, building, and PR management. You are the final step before user review - ensure everything is perfect.',
1284
+ },
1285
+ },
1286
+ command: {
1287
+ fullstack: {
1288
+ description: 'Switch to Fullstack (router)',
1289
+ template: '{{input}}',
1290
+ agent: 'fullstack',
1291
+ },
1292
+ route: {
1293
+ description:
1294
+ 'Let Fullstack auto-route to the right persona based on files/intent',
1295
+ template: 'ROUTE {{input}}',
1296
+ agent: 'fullstack',
1297
+ subtask: true,
1298
+ },
1299
+ frontend: {
1300
+ description: 'Switch to Frontend persona',
1301
+ template: '{{input}}',
1302
+ agent: 'frontend',
1303
+ },
1304
+ backend: {
1305
+ description: 'Switch to Backend persona',
1306
+ template: '{{input}}',
1307
+ agent: 'backend',
1308
+ },
1309
+ devops: {
1310
+ description: 'Switch to DevOps persona',
1311
+ template: '{{input}}',
1312
+ agent: 'devops',
1313
+ },
1314
+ app: {
1315
+ description: 'Switch to App persona',
1316
+ template: '{{input}}',
1317
+ agent: 'app',
1318
+ },
1319
+ security: {
1320
+ description:
1321
+ 'Switch to Security persona for pentesting and OWASP compliance',
1322
+ template: '{{input}}',
1323
+ agent: 'security',
1324
+ },
1325
+ quality: {
1326
+ description:
1327
+ 'Switch to Quality agent for testing, building, and PR management',
1328
+ template: '{{input}}',
1329
+ agent: 'quality',
1330
+ },
1331
+ },
1332
+ watcher: {
1333
+ ignore: ['node_modules', 'dist', '.git', 'coverage'],
1334
+ },
1335
+ provider: {
1336
+ berget: {
1337
+ npm: '@ai-sdk/openai-compatible',
1338
+ name: 'Berget AI',
1339
+ options: {
1340
+ baseURL: 'https://api.berget.ai/v1',
1341
+ apiKey: '{env:BERGET_API_KEY}',
1342
+ },
1343
+ models: MODEL_CONFIG.PROVIDER_MODELS,
1344
+ },
1345
+ },
1346
+ }
1347
+
1348
+ // Check if update is needed
1349
+ const needsUpdate =
1350
+ JSON.stringify(currentConfig) !== JSON.stringify(latestConfig)
1351
+
1352
+ if (!needsUpdate && !options.force) {
1353
+ console.log(chalk.green('✅ Already using the latest configuration!'))
1354
+ return
1355
+ }
1356
+
1357
+ if (needsUpdate) {
1358
+ console.log(chalk.blue('\n🔄 Updates available:'))
1359
+
1360
+ // Compare agents
1361
+ const currentAgents = Object.keys(currentConfig.agent || {})
1362
+ const latestAgents = Object.keys(latestConfig.agent)
1363
+ const newAgents = latestAgents.filter(
1364
+ (agent) => !currentAgents.includes(agent),
1365
+ )
1366
+
1367
+ if (newAgents.length > 0) {
1368
+ console.log(chalk.cyan(` • New agents: ${newAgents.join(', ')}`))
1369
+ }
1370
+
1371
+ // Check for quality agent specifically
1372
+ if (!currentConfig.agent?.quality && latestConfig.agent.quality) {
1373
+ console.log(
1374
+ chalk.cyan(' • Quality subagent for testing and PR management'),
1375
+ )
1376
+ }
1377
+
1378
+ // Check for security subagent mode
1379
+ if (currentConfig.agent?.security?.mode !== 'subagent') {
1380
+ console.log(
1381
+ chalk.cyan(
1382
+ ' • Security agent converted to subagent (read-only)',
1383
+ ),
1384
+ )
1385
+ }
1386
+
1387
+ // Check for GLM-4.6 optimizations
1388
+ if (
1389
+ !currentConfig.provider?.berget?.models?.[MODEL_CONFIG.AGENT_MODELS.primary.replace('berget/', '')]?.limit?.context
1390
+ ) {
1391
+ console.log(
1392
+ chalk.cyan(' • GLM-4.6 token limits and auto-compaction'),
1393
+ )
1394
+ }
1395
+
1396
+ console.log(chalk.cyan(' • Latest agent prompts and improvements'))
1397
+ }
1398
+
1399
+ if (options.force) {
1400
+ console.log(chalk.yellow('🔧 Force update requested'))
1401
+ }
1402
+
1403
+ if (!options.yes) {
1404
+ console.log(
1405
+ chalk.blue(
1406
+ '\nThis will update your OpenCode configuration with the latest improvements.',
1407
+ ),
1408
+ )
1409
+
1410
+ // Check if user has git for backup
1411
+ const hasGitRepo = hasGit()
1412
+ if (!hasGitRepo) {
1413
+ console.log(
1414
+ chalk.yellow(
1415
+ '⚠️ No .git repository detected - backup will be created',
1416
+ ),
1417
+ )
1418
+ } else {
1419
+ console.log(
1420
+ chalk.green('✓ Git repository detected - changes are tracked'),
1421
+ )
1422
+ }
1423
+ }
1424
+
1425
+ // Ask user what they want to do
1426
+ console.log(chalk.blue('\nChoose update strategy:'))
1427
+ console.log(
1428
+ chalk.cyan(
1429
+ '1) Replace - Use latest configuration (your customizations will be lost)',
1430
+ ),
1431
+ )
1432
+ console.log(
1433
+ chalk.cyan(
1434
+ '2) Merge - Combine latest updates with your customizations (recommended)',
1435
+ ),
1436
+ )
1437
+
1438
+ let mergeChoice: 'replace' | 'merge' = 'merge'
1439
+
1440
+ if (!options.yes) {
1441
+ const choice = await askChoice(
1442
+ '\nYour choice (1-2, default: 2): ',
1443
+ ['replace', 'merge'],
1444
+ 'merge',
1445
+ )
1446
+ mergeChoice = choice as 'replace' | 'merge'
1447
+ }
1448
+
1449
+ if (
1450
+ await confirm(`\nProceed with ${mergeChoice}? (Y/n): `, options.yes)
1451
+ ) {
1452
+ try {
1453
+ let finalConfig: any
1454
+ let backupPath: string | null = null
1455
+
1456
+ // Create backup if no git
1457
+ if (!hasGit()) {
1458
+ backupPath = `${configPath}.backup.${Date.now()}`
1459
+ await writeFile(
1460
+ backupPath,
1461
+ JSON.stringify(currentConfig, null, 2),
1462
+ )
1463
+ console.log(
1464
+ chalk.green(
1465
+ `✓ Backed up current config to ${path.basename(backupPath)}`,
1466
+ ),
1467
+ )
1468
+ }
1469
+
1470
+ if (mergeChoice === 'merge') {
1471
+ // Merge configurations
1472
+ finalConfig = await mergeConfigurations(
1473
+ currentConfig,
1474
+ latestConfig,
1475
+ )
1476
+ console.log(
1477
+ chalk.green('✓ Merged configurations with latest updates'),
1478
+ )
1479
+ } else {
1480
+ // Replace with latest
1481
+ finalConfig = latestConfig
1482
+ console.log(chalk.green('✓ Replaced with latest configuration'))
1483
+ }
1484
+
1485
+ // Write final configuration
1486
+ await writeFile(configPath, JSON.stringify(finalConfig, null, 2))
1487
+ console.log(
1488
+ chalk.green(
1489
+ `✓ Updated opencode.json with ${mergeChoice} strategy`,
1490
+ ),
1491
+ )
1492
+
1493
+ // Update AGENTS.md if it doesn't exist
1494
+ const agentsMdPath = path.join(process.cwd(), 'AGENTS.md')
1495
+ if (!fs.existsSync(agentsMdPath)) {
1496
+ const agentsMdContent = `# Berget Code Agents
1497
+
1498
+ This document describes the specialized agents available in this project for use with OpenCode.
1499
+
1500
+ ## Available Agents
1501
+
1502
+ ### Primary Agents
1503
+
1504
+ #### fullstack
1505
+ Router/coordinator agent for full-stack development with schema-driven architecture. Handles routing between different personas based on file paths and task requirements.
1506
+
1507
+ **Use when:**
1508
+ - Working across multiple parts of a monorepo
1509
+ - Need to coordinate between frontend, backend, devops, and app
1510
+ - Starting new projects and need to determine tech stack
1511
+
1512
+ **Key features:**
1513
+ - Schema-driven development (database → OpenAPI → types)
1514
+ - Automatic routing to appropriate persona
1515
+ - Tech stack discovery and recommendations
1516
+
1517
+ #### frontend
1518
+ Builds Scandinavian, type-safe UIs with React, Tailwind, and Shadcn.
1519
+
1520
+ **Use when:**
1521
+ - Working with React components (.tsx files)
1522
+ - Frontend development in /apps/frontend
1523
+ - UI/UX implementation
1524
+
1525
+ **Key features:**
1526
+ - Design system integration
1527
+ - Semantic tokens and accessibility
1528
+ - Props-first component architecture
1529
+
1530
+ #### backend
1531
+ Functional, modular Koa + TypeScript services with schema-first approach and code quality focus.
1532
+
1533
+ **Use when:**
1534
+ - Working with Koa routers and services
1535
+ - Backend development in /services
1536
+ - API development and database work
1537
+
1538
+ **Key features:**
1539
+ - Zod validation and OpenAPI generation
1540
+ - Code quality and refactoring principles
1541
+ - PR workflow integration
1542
+
1543
+ #### devops
1544
+ Declarative GitOps infrastructure with FluxCD, Kustomize, Helm, and operators.
1545
+
1546
+ **Use when:**
1547
+ - Working with Kubernetes manifests
1548
+ - Infrastructure in /infra or /k8s
1549
+ - CI/CD and deployment configurations
1550
+
1551
+ **Key features:**
1552
+ - GitOps workflows
1553
+ - Operator-first approach
1554
+ - SemVer with release candidates
1555
+
1556
+ #### app
1557
+ Expo + React Native applications with props-first architecture and offline awareness.
1558
+
1559
+ **Use when:**
1560
+ - Mobile app development with Expo
1561
+ - React Native projects in /apps/app
1562
+ - Cross-platform mobile development
1563
+
1564
+ **Key features:**
1565
+ - Shared design tokens with frontend
1566
+ - Offline-first architecture
1567
+ - Expo integration
1568
+
1569
+ ### Subagents
1570
+
1571
+ #### security
1572
+ Security specialist for penetration testing, OWASP compliance, and vulnerability assessments.
1573
+
1574
+ **Use when:**
1575
+ - Need security review of code changes
1576
+ - OWASP Top 10 compliance checks
1577
+ - Vulnerability assessments
1578
+
1579
+ **Key features:**
1580
+ - OWASP standards compliance
1581
+ - Security best practices
1582
+ - Actionable remediation strategies
1583
+
1584
+ #### quality
1585
+ Quality assurance specialist for testing, building, and PR management.
1586
+
1587
+ **Use when:**
1588
+ - Need to run test suites and build processes
1589
+ - Creating or updating pull requests
1590
+ - Monitoring GitHub for reviewer comments
1591
+ - Ensuring code quality standards
1592
+
1593
+ **Key features:**
1594
+ - Comprehensive testing and building workflows
1595
+ - PR creation and management
1596
+ - GitHub integration for reviewer feedback
1597
+ - CLI command expertise for quality assurance
1598
+
1599
+ ## Usage
1600
+
1601
+ ### Switching Agents
1602
+ Use the \`<tab>\` key to cycle through primary agents during a session.
1603
+
1604
+ ### Manual Agent Selection
1605
+ Use commands to switch to specific agents:
1606
+ - \`/fullstack\` - Switch to Fullstack agent
1607
+ - \`/frontend\` - Switch to Frontend agent
1608
+ - \`/backend\` - Switch to Backend agent
1609
+ - \`/devops\` - Switch to DevOps agent
1610
+ - \`/app\` - Switch to App agent
1611
+ - \`/quality\` - Switch to Quality agent for testing and PR management
1612
+
1613
+ ### Using Subagents
1614
+ Mention subagents with \`@\` symbol:
1615
+ - \`@security review this authentication implementation\`
1616
+ - \`@quality run tests and create PR for these changes\`
1617
+
1618
+ ## Routing Rules
1619
+
1620
+ The fullstack agent automatically routes tasks based on file patterns:
1621
+
1622
+ - \`/apps/frontend\` or \`.tsx\` files → frontend
1623
+ - \`/apps/app\` or Expo/React Native → app
1624
+ - \`/infra\`, \`/k8s\`, FluxCD, Helm → devops
1625
+ - \`/services\`, Koa routers → backend
1626
+
1627
+ ## Configuration
1628
+
1629
+ All agents are configured in \`opencode.json\` with:
1630
+ - Specialized prompts and temperature settings
1631
+ - Appropriate tool permissions
1632
+ - Model optimizations for their specific tasks
1633
+
1634
+ ## Environment Setup
1635
+
1636
+ Configure \`.env\` with your API key:
1637
+ \`\`\`
1638
+ BERGET_API_KEY=your_api_key_here
1639
+ \`\`\`
1640
+
1641
+ ## Workflow
1642
+
1643
+ All agents follow these principles:
1644
+ - Never work directly in main branch
1645
+ - Follow branch strategy and commit conventions
1646
+ - Create PRs for new functionality
1647
+ - Run tests before committing
1648
+ - Address reviewer feedback promptly
1649
+
1650
+ ---
1651
+
1652
+ *Updated by berget code update*
1653
+ `
1654
+
1655
+ await writeFile(agentsMdPath, agentsMdContent)
1656
+ console.log(chalk.green('✓ Updated AGENTS.md documentation'))
1657
+ }
1658
+
1659
+ console.log(chalk.green('\n✅ Update completed successfully!'))
1660
+ console.log(chalk.blue('New features available:'))
1661
+ console.log(
1662
+ chalk.cyan(' • @quality subagent for testing and PR management'),
1663
+ )
1664
+ console.log(
1665
+ chalk.cyan(' • @security subagent for security reviews'),
1666
+ )
1667
+ console.log(chalk.cyan(' • Improved agent prompts and routing'))
1668
+ console.log(chalk.cyan(' • GLM-4.6 token optimizations'))
1669
+ console.log(chalk.blue('\nTry these new commands:'))
1670
+ console.log(chalk.cyan(' @quality run tests and create PR'))
1671
+ console.log(chalk.cyan(' @security review this code'))
1672
+ } catch (error) {
1673
+ console.error(chalk.red('Failed to update configuration:'))
1674
+ handleError('Update failed', error)
1675
+
1676
+ // Restore from backup if update failed
1677
+ try {
1678
+ await writeFile(
1679
+ configPath,
1680
+ JSON.stringify(currentConfig, null, 2),
1681
+ )
1682
+ console.log(
1683
+ chalk.yellow('📁 Restored original configuration from backup'),
1684
+ )
1685
+ } catch (restoreError) {
1686
+ console.error(chalk.red('Failed to restore backup:'))
1687
+ }
1688
+ }
1689
+ } else {
1690
+ console.log(chalk.yellow('Update cancelled.'))
1691
+ }
1692
+ } catch (error) {
1693
+ handleError('Failed to update OpenCode configuration', error)
1694
+ }
1695
+ })
1696
+ }