berget 2.2.5 → 2.2.7

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 (148) hide show
  1. package/.github/workflows/publish.yml +8 -8
  2. package/.github/workflows/test.yml +12 -6
  3. package/.husky/pre-commit +1 -0
  4. package/.prettierignore +15 -0
  5. package/.prettierrc +5 -3
  6. package/CONTRIBUTING.md +38 -0
  7. package/README.md +2 -148
  8. package/dist/index.js +21 -21
  9. package/dist/package.json +30 -2
  10. package/dist/src/agents/app.js +28 -0
  11. package/dist/src/agents/backend.js +25 -0
  12. package/dist/src/agents/devops.js +34 -0
  13. package/dist/src/agents/frontend.js +25 -0
  14. package/dist/src/agents/fullstack.js +25 -0
  15. package/dist/src/agents/index.js +61 -0
  16. package/dist/src/agents/quality.js +70 -0
  17. package/dist/src/agents/security.js +26 -0
  18. package/dist/src/agents/types.js +2 -0
  19. package/dist/src/client.js +54 -62
  20. package/dist/src/commands/api-keys.js +132 -140
  21. package/dist/src/commands/auth.js +9 -9
  22. package/dist/src/commands/autocomplete.js +9 -9
  23. package/dist/src/commands/billing.js +7 -9
  24. package/dist/src/commands/chat.js +90 -92
  25. package/dist/src/commands/clusters.js +12 -12
  26. package/dist/src/commands/code/__tests__/auth-sync.test.js +348 -0
  27. package/dist/src/commands/code/__tests__/fake-api-key-service.js +23 -0
  28. package/dist/src/commands/code/__tests__/fake-auth-service.js +55 -0
  29. package/dist/src/commands/code/__tests__/fake-command-runner.js +50 -0
  30. package/dist/src/commands/code/__tests__/fake-file-store.js +55 -0
  31. package/dist/src/commands/code/__tests__/fake-prompter.js +133 -0
  32. package/dist/src/commands/code/__tests__/setup-flow.test.js +505 -0
  33. package/dist/src/commands/code/adapters/clack-prompter.js +81 -0
  34. package/dist/src/commands/code/adapters/fs-file-store.js +80 -0
  35. package/dist/src/commands/code/adapters/spawn-command-runner.js +53 -0
  36. package/dist/src/commands/code/auth-sync.js +283 -0
  37. package/dist/src/commands/code/errors.js +27 -0
  38. package/dist/src/commands/code/ports/auth-services.js +2 -0
  39. package/dist/src/commands/code/ports/command-runner.js +2 -0
  40. package/dist/src/commands/code/ports/file-store.js +2 -0
  41. package/dist/src/commands/code/ports/prompter.js +2 -0
  42. package/dist/src/commands/code/setup.js +533 -0
  43. package/dist/src/commands/code.js +223 -779
  44. package/dist/src/commands/models.js +13 -15
  45. package/dist/src/commands/users.js +6 -8
  46. package/dist/src/constants/command-structure.js +116 -114
  47. package/dist/src/services/api-key-service.js +43 -48
  48. package/dist/src/services/auth-service.js +60 -299
  49. package/dist/src/services/browser-auth.js +278 -0
  50. package/dist/src/services/chat-service.js +78 -91
  51. package/dist/src/services/cluster-service.js +6 -6
  52. package/dist/src/services/collaborator-service.js +5 -8
  53. package/dist/src/services/flux-service.js +5 -8
  54. package/dist/src/services/helm-service.js +5 -8
  55. package/dist/src/services/kubectl-service.js +7 -10
  56. package/dist/src/utils/config-checker.js +5 -5
  57. package/dist/src/utils/config-loader.js +25 -25
  58. package/dist/src/utils/default-api-key.js +23 -23
  59. package/dist/src/utils/env-manager.js +7 -7
  60. package/dist/src/utils/error-handler.js +60 -61
  61. package/dist/src/utils/logger.js +7 -7
  62. package/dist/src/utils/markdown-renderer.js +2 -2
  63. package/dist/src/utils/opencode-validator.js +17 -20
  64. package/dist/src/utils/token-manager.js +38 -11
  65. package/dist/tests/commands/chat.test.js +24 -24
  66. package/dist/tests/commands/code.test.js +169 -138
  67. package/dist/tests/utils/config-loader.test.js +114 -114
  68. package/dist/tests/utils/env-manager.test.js +57 -57
  69. package/dist/tests/utils/opencode-validator.test.js +44 -43
  70. package/dist/vitest.config.js +1 -1
  71. package/eslint.config.mjs +47 -0
  72. package/index.ts +42 -48
  73. package/package.json +30 -2
  74. package/src/agents/app.ts +27 -0
  75. package/src/agents/backend.ts +24 -0
  76. package/src/agents/devops.ts +33 -0
  77. package/src/agents/frontend.ts +24 -0
  78. package/src/agents/fullstack.ts +24 -0
  79. package/src/agents/index.ts +71 -0
  80. package/src/agents/quality.ts +69 -0
  81. package/src/agents/security.ts +26 -0
  82. package/src/agents/types.ts +17 -0
  83. package/src/client.ts +125 -167
  84. package/src/commands/api-keys.ts +261 -358
  85. package/src/commands/auth.ts +24 -30
  86. package/src/commands/autocomplete.ts +12 -12
  87. package/src/commands/billing.ts +22 -27
  88. package/src/commands/chat.ts +230 -323
  89. package/src/commands/clusters.ts +33 -33
  90. package/src/commands/code/__tests__/auth-sync.test.ts +481 -0
  91. package/src/commands/code/__tests__/fake-api-key-service.ts +13 -0
  92. package/src/commands/code/__tests__/fake-auth-service.ts +50 -0
  93. package/src/commands/code/__tests__/fake-command-runner.ts +44 -0
  94. package/src/commands/code/__tests__/fake-file-store.ts +44 -0
  95. package/src/commands/code/__tests__/fake-prompter.ts +121 -0
  96. package/src/commands/code/__tests__/setup-flow.test.ts +628 -0
  97. package/src/commands/code/adapters/clack-prompter.ts +55 -0
  98. package/src/commands/code/adapters/fs-file-store.ts +37 -0
  99. package/src/commands/code/adapters/spawn-command-runner.ts +40 -0
  100. package/src/commands/code/auth-sync.ts +329 -0
  101. package/src/commands/code/errors.ts +23 -0
  102. package/src/commands/code/ports/auth-services.ts +14 -0
  103. package/src/commands/code/ports/command-runner.ts +10 -0
  104. package/src/commands/code/ports/file-store.ts +7 -0
  105. package/src/commands/code/ports/prompter.ts +29 -0
  106. package/src/commands/code/setup.ts +630 -0
  107. package/src/commands/code.ts +335 -1074
  108. package/src/commands/index.ts +19 -19
  109. package/src/commands/models.ts +32 -37
  110. package/src/commands/users.ts +15 -22
  111. package/src/constants/command-structure.ts +120 -140
  112. package/src/services/api-key-service.ts +96 -113
  113. package/src/services/auth-service.ts +92 -339
  114. package/src/services/browser-auth.ts +296 -0
  115. package/src/services/chat-service.ts +246 -279
  116. package/src/services/cluster-service.ts +29 -32
  117. package/src/services/collaborator-service.ts +13 -18
  118. package/src/services/flux-service.ts +16 -18
  119. package/src/services/helm-service.ts +16 -18
  120. package/src/services/kubectl-service.ts +12 -14
  121. package/src/types/api.d.ts +924 -926
  122. package/src/types/json.d.ts +3 -3
  123. package/src/utils/config-checker.ts +10 -10
  124. package/src/utils/config-loader.ts +110 -127
  125. package/src/utils/default-api-key.ts +81 -93
  126. package/src/utils/env-manager.ts +36 -40
  127. package/src/utils/error-handler.ts +83 -78
  128. package/src/utils/logger.ts +41 -41
  129. package/src/utils/markdown-renderer.ts +11 -11
  130. package/src/utils/opencode-validator.ts +51 -56
  131. package/src/utils/token-manager.ts +84 -64
  132. package/templates/agents/app.md +23 -0
  133. package/templates/agents/backend.md +23 -0
  134. package/templates/agents/devops.md +30 -0
  135. package/templates/agents/frontend.md +25 -0
  136. package/templates/agents/fullstack.md +23 -0
  137. package/templates/agents/quality.md +69 -0
  138. package/templates/agents/security.md +21 -0
  139. package/tests/commands/chat.test.ts +60 -70
  140. package/tests/commands/code.test.ts +346 -345
  141. package/tests/utils/config-loader.test.ts +260 -260
  142. package/tests/utils/env-manager.test.ts +127 -134
  143. package/tests/utils/opencode-validator.test.ts +65 -69
  144. package/tsconfig.json +2 -2
  145. package/vitest.config.ts +3 -3
  146. package/AGENTS.md +0 -374
  147. package/TODO.md +0 -19
  148. package/opencode.json +0 -146
@@ -1,247 +1,44 @@
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 { handleError } from '../utils/error-handler'
6
- import * as fs from 'fs'
7
- import { readFile, writeFile } from 'fs/promises'
8
- import path from 'path'
9
- import { spawn } from 'child_process'
10
- import { createAuthenticatedClient } from '../client'
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 { handleError } from "../utils/error-handler";
6
+ import { runSetupCommand } from "./code/setup";
7
+ import * as fs from "fs";
8
+ import { readFile, writeFile } from "fs/promises";
9
+ import path from "path";
10
+ import { spawn } from "child_process";
11
11
 
12
12
  /**
13
13
  * Check if current directory has git
14
14
  */
15
15
  function hasGit(): boolean {
16
16
  try {
17
- return fs.existsSync(path.join(process.cwd(), '.git'))
17
+ return fs.existsSync(path.join(process.cwd(), ".git"));
18
18
  } catch {
19
- return false
19
+ return false;
20
20
  }
21
21
  }
22
22
 
23
- /**
24
- * Merge opencode configurations using chat completions API
25
- */
26
- async function mergeConfigurations(
27
- currentConfig: any,
28
- latestConfig: any
29
- ): Promise<any> {
30
- try {
31
- const client = createAuthenticatedClient()
32
-
33
- console.log(chalk.blue('🤖 Using AI to merge configurations...'))
34
-
35
- const mergePrompt = `You are a configuration merge specialist. Merge these two OpenCode configurations:
36
-
37
- CURRENT CONFIG (user's customizations):
38
- ${JSON.stringify(currentConfig, null, 2)}
39
-
40
- LATEST CONFIG (new updates):
41
- ${JSON.stringify(latestConfig, null, 2)}
42
-
43
- Merge rules:
44
- 1. Preserve ALL user customizations from current config
45
- 2. Add ALL new features and improvements from latest config
46
- 3. For conflicts, prefer user's customizations but add new latest features
47
- 4. Maintain valid JSON structure
48
- 5. Keep the merged configuration complete and functional
49
-
50
- Return ONLY the merged JSON configuration, no explanations.`
51
-
52
- const response = await client.POST('/v1/chat/completions', {
53
- body: {
54
- model: 'glm-4.7',
55
- messages: [
56
- {
57
- role: 'user',
58
- content: mergePrompt,
59
- },
60
- ],
61
- temperature: 0.1,
62
- max_tokens: 8000,
63
- },
64
- })
65
-
66
- if (response.error) {
67
- console.warn(chalk.yellow('⚠️ AI merge failed, using fallback merge'))
68
- return fallbackMerge(currentConfig, latestConfig)
69
- }
70
-
71
- const content = response.data?.choices?.[0]?.message?.content
72
- if (!content) {
73
- console.warn(chalk.yellow('⚠️ No AI response, using fallback merge'))
74
- return fallbackMerge(currentConfig, latestConfig)
75
- }
76
-
77
- try {
78
- const mergedConfig = JSON.parse(content.trim())
79
- console.log(chalk.green('✓ AI merge completed successfully'))
80
- return mergedConfig
81
- } catch (parseError) {
82
- console.warn(
83
- chalk.yellow('⚠️ AI response invalid, using fallback merge')
84
- )
85
- return fallbackMerge(currentConfig, latestConfig)
86
- }
87
- } catch (error) {
88
- console.warn(chalk.yellow('⚠️ AI merge unavailable, using fallback merge'))
89
- return fallbackMerge(currentConfig, latestConfig)
90
- }
91
- }
92
-
93
- /**
94
- * Fallback merge logic when AI merge is unavailable
95
- */
96
- function fallbackMerge(currentConfig: any, latestConfig: any): any {
97
- console.log(chalk.blue('🔀 Using fallback merge logic...'))
98
-
99
- const merged = { ...latestConfig }
100
-
101
- // Preserve user customizations
102
- if (currentConfig.theme && currentConfig.theme !== latestConfig.theme) {
103
- merged.theme = currentConfig.theme
104
- }
105
-
106
- if (currentConfig.share && currentConfig.share !== latestConfig.share) {
107
- merged.share = currentConfig.share
108
- }
109
-
110
- // Merge custom agents while preserving new ones
111
- if (currentConfig.agent) {
112
- merged.agent = { ...latestConfig.agent }
113
-
114
- // Add any custom agents from current config
115
- Object.keys(currentConfig.agent).forEach((agentName) => {
116
- if (!latestConfig.agent[agentName]) {
117
- merged.agent[agentName] = currentConfig.agent[agentName]
118
- console.log(chalk.cyan(` • Preserved custom agent: ${agentName}`))
119
- }
120
- })
121
- }
122
-
123
- // Merge custom commands while preserving new ones
124
- if (currentConfig.commands) {
125
- merged.commands = { ...latestConfig.commands }
126
-
127
- Object.keys(currentConfig.commands).forEach((commandName) => {
128
- if (!latestConfig.commands[commandName]) {
129
- merged.commands[commandName] = currentConfig.commands[commandName]
130
- console.log(chalk.cyan(` • Preserved custom command: ${commandName}`))
131
- }
132
- })
133
- }
134
-
135
- // Preserve custom provider settings if user has modified them
136
- if (currentConfig.provider) {
137
- merged.provider = { ...latestConfig.provider }
138
-
139
- // Deep merge provider settings
140
- Object.keys(currentConfig.provider).forEach((providerName) => {
141
- if (merged.provider[providerName]) {
142
- merged.provider[providerName] = {
143
- ...merged.provider[providerName],
144
- ...currentConfig.provider[providerName],
145
- }
146
- } else {
147
- merged.provider[providerName] = currentConfig.provider[providerName]
148
- }
149
- })
150
- }
151
-
152
- return merged
153
- }
154
-
155
23
  /**
156
24
  * Helper function to get user confirmation
157
25
  */
158
26
  async function confirm(question: string, autoYes = false): Promise<boolean> {
159
27
  if (autoYes) {
160
- return true
28
+ return true;
161
29
  }
162
30
 
163
- return new Promise((resolve) => {
31
+ return new Promise(resolve => {
164
32
  const rl = readline.createInterface({
165
33
  input: process.stdin,
166
34
  output: process.stdout,
167
- })
168
-
169
- rl.question(question, (answer) => {
170
- rl.close()
171
- resolve(
172
- answer.toLowerCase() === 'y' ||
173
- answer.toLowerCase() === 'yes' ||
174
- answer === ''
175
- )
176
- })
177
- })
178
- }
179
-
180
- /**
181
- * Helper function to get user choice from options
182
- */
183
- async function askChoice(
184
- question: string,
185
- options: string[],
186
- defaultChoice?: string
187
- ): Promise<string> {
188
- return new Promise((resolve) => {
189
- const rl = readline.createInterface({
190
- input: process.stdin,
191
- output: process.stdout,
192
- })
193
-
194
- rl.question(question, (answer) => {
195
- rl.close()
196
-
197
- const trimmed = answer.trim().toLowerCase()
35
+ });
198
36
 
199
- // Handle numeric input (1, 2, etc.)
200
- const numericIndex = parseInt(trimmed) - 1
201
- if (numericIndex >= 0 && numericIndex < options.length) {
202
- resolve(options[numericIndex])
203
- return
204
- }
205
-
206
- // Handle text input
207
- const matchingOption = options.find((option) =>
208
- option.toLowerCase().startsWith(trimmed)
209
- )
210
-
211
- if (matchingOption) {
212
- resolve(matchingOption)
213
- } else if (defaultChoice) {
214
- resolve(defaultChoice)
215
- } else {
216
- resolve(options[0]) // Default to first option
217
- }
218
- })
219
- })
220
- }
221
-
222
- /**
223
- * Helper function to get user input
224
- */
225
- async function getInput(
226
- question: string,
227
- defaultValue: string,
228
- autoYes = false
229
- ): Promise<string> {
230
- if (autoYes) {
231
- return defaultValue
232
- }
233
-
234
- const rl = readline.createInterface({
235
- input: process.stdin,
236
- output: process.stdout,
237
- })
238
-
239
- return new Promise<string>((resolve) => {
240
- rl.question(question, (answer) => {
241
- rl.close()
242
- resolve(answer.trim() || defaultValue)
243
- })
244
- })
37
+ rl.question(question, answer => {
38
+ rl.close();
39
+ resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes" || answer === "");
40
+ });
41
+ });
245
42
  }
246
43
 
247
44
  /**
@@ -249,164 +46,84 @@ async function getInput(
249
46
  */
250
47
  function getProjectName(): string {
251
48
  try {
252
- const packageJsonPath = path.join(process.cwd(), 'package.json')
49
+ const packageJsonPath = path.join(process.cwd(), "package.json");
253
50
  if (fs.existsSync(packageJsonPath)) {
254
- const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8')
255
- const packageJson = JSON.parse(packageJsonContent)
256
- return packageJson.name || path.basename(process.cwd())
51
+ const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8");
52
+ const packageJson = JSON.parse(packageJsonContent);
53
+ return packageJson.name || path.basename(process.cwd());
257
54
  }
258
- } catch (error) {
55
+ } catch {
259
56
  // Ignore error and fallback to directory name
260
57
  }
261
- return path.basename(process.cwd())
58
+ return path.basename(process.cwd());
262
59
  }
263
60
 
264
61
  /**
265
- * Load the latest agent configuration from embedded config
62
+ * Get the path to the bundled agent templates directory
266
63
  */
267
- async function loadLatestAgentConfig(): Promise<any> {
268
- // Return the latest agent configuration directly - no file reading needed
269
- return {
270
- fullstack: {
271
- temperature: 0.3,
272
- top_p: 0.9,
273
- mode: 'primary',
274
- permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
275
- description:
276
- 'Router/coordinator agent for full-stack development with schema-driven architecture',
277
- prompt:
278
- "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.\n\nGIT WORKFLOW RULES (CRITICAL):\n- NEVER push directly to main branch - ALWAYS use pull requests\n- NEVER use 'git add .' - ALWAYS add specific files with 'git add path/to/file'\n- ALWAYS clean up test files, documentation files, and temporary artifacts before committing\n- ALWAYS ensure git history maintains production quality - no test commits, no debugging code\n- ALWAYS create descriptive commit messages following project conventions\n- ALWAYS run tests and build before creating PR\n\nCRITICAL: 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.",
279
- },
280
- frontend: {
281
- temperature: 0.4,
282
- top_p: 0.9,
283
- mode: 'primary',
284
- permission: { edit: 'allow', bash: 'deny', webfetch: 'allow' },
285
- note: 'Bash access is denied for frontend persona to prevent shell command execution in UI environments. This restriction enforces security and architectural boundaries.',
286
- description:
287
- 'Builds Scandinavian, type-safe UIs with React, Tailwind, Shadcn.',
288
- prompt:
289
- '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.\n\nIMPORTANT: You have NO bash access and cannot run git commands. When your frontend implementation tasks are complete, inform the user that changes are ready and suggest using /pr command to create a pull request with proper testing and quality checks.\n\nCODE QUALITY RULES:\n- Write clean, production-ready code\n- Follow React and TypeScript best practices\n- Ensure accessibility and responsive design\n- Use semantic tokens from design system\n- Test your components manually when possible\n- Document any complex logic with comments\n\nCRITICAL: When frontend implementation is complete, ALWAYS inform the user to use "/pr" command to handle testing, building, and pull request creation.',
290
- },
291
- backend: {
292
- temperature: 0.3,
293
- top_p: 0.9,
294
- mode: 'primary',
295
- permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
296
- description:
297
- 'Functional, modular Koa + TypeScript services; schema-first with code quality focus.',
298
- prompt:
299
- "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.\n\nGIT WORKFLOW RULES (CRITICAL):\n- NEVER push directly to main branch - ALWAYS use pull requests\n- NEVER use 'git add .' - ALWAYS add specific files with 'git add path/to/file'\n- ALWAYS clean up test files, documentation files, and temporary artifacts before committing\n- ALWAYS ensure git history maintains production quality - no test commits, no debugging code\n- ALWAYS create descriptive commit messages following project conventions\n- ALWAYS run tests and build before creating PR\n\nCRITICAL: 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.",
300
- },
301
- devops: {
302
- temperature: 0.3,
303
- top_p: 0.8,
304
- mode: 'primary',
305
- permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
306
- description:
307
- 'Declarative GitOps infra with FluxCD, Kustomize, Helm, operators.',
308
- prompt:
309
- "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.\n\nGIT WORKFLOW RULES (CRITICAL):\n- NEVER push directly to main branch - ALWAYS use pull requests\n- NEVER use 'git add .' - ALWAYS add specific files with 'git add path/to/file'\n- ALWAYS clean up test files, documentation files, and temporary artifacts before committing\n- ALWAYS ensure git history maintains production quality - no test commits, no debugging code\n- ALWAYS create descriptive commit messages following project conventions\n- ALWAYS run tests and build before creating PR\n\nHelm Values Configuration Process:\n1. Documentation First Approach: Always fetch official documentation from Artifact Hub/GitHub for the specific chart version before writing values. Search Artifact Hub for exact chart version documentation, check the chart's GitHub repository for official docs and examples, verify the exact version being used in the deployment.\n2. Validation Requirements: Check for available validation schemas before committing YAML files. Use Helm's built-in validation tools (helm lint, helm template). Validate against JSON schema if available for the chart. Ensure YAML syntax correctness with linters.\n3. Standard Workflow: Identify chart name and exact version. Fetch official documentation from Artifact Hub/GitHub. Check for available schemas and validation tools. Write values according to official documentation. Validate against schema (if available). Test with helm template or helm lint. Commit validated YAML files.\n4. Quality Assurance: Never commit unvalidated Helm values. Use helm dependency update when adding new charts. Test rendering with helm template --dry-run before deployment. Document any custom values with comments referencing official docs.",
310
- },
311
- app: {
312
- temperature: 0.4,
313
- top_p: 0.9,
314
- mode: 'primary',
315
- permission: { edit: 'allow', bash: 'deny', webfetch: 'allow' },
316
- note: 'Bash access is denied for app persona to prevent shell command execution in mobile/Expo environments. This restriction enforces security and architectural boundaries.',
317
- description:
318
- 'Expo + React Native apps; props-first, offline-aware, shared tokens.',
319
- prompt:
320
- "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.\n\nGIT WORKFLOW RULES (CRITICAL):\n- NEVER push directly to main branch - ALWAYS use pull requests\n- NEVER use 'git add .' - ALWAYS add specific files with 'git add path/to/file'\n- ALWAYS clean up test files, documentation files, and temporary artifacts before committing\n- ALWAYS ensure git history maintains production quality - no test commits, no debugging code\n- ALWAYS create descriptive commit messages following project conventions\n- ALWAYS run tests and build before creating PR\n\nCRITICAL: When all app implementation tasks are complete and ready for merge, ALWAYS invoke @quality subagent to handle testing, building, and complete PR management including URL provision.",
321
- },
322
- security: {
323
- temperature: 0.2,
324
- top_p: 0.8,
325
- mode: 'subagent',
326
- permission: { edit: 'deny', bash: 'allow', webfetch: 'allow' },
327
- description:
328
- 'Security specialist for pentesting, OWASP compliance, and vulnerability assessments.',
329
- prompt:
330
- "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.\n\nGIT WORKFLOW RULES (CRITICAL):\n- NEVER push directly to main branch - ALWAYS use pull requests\n- NEVER use 'git add .' - ALWAYS add specific files with 'git add path/to/file'\n- ALWAYS clean up test files, documentation files, and temporary artifacts before committing\n- ALWAYS ensure git history maintains production quality - no test commits, no debugging code\n- ALWAYS create descriptive commit messages following project conventions\n- ALWAYS run tests and build before creating PR",
331
- },
332
- quality: {
333
- temperature: 0.1,
334
- top_p: 0.9,
335
- mode: 'subagent',
336
- permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
337
- description:
338
- 'Quality assurance specialist for testing, building, and PR management.',
339
- prompt:
340
- "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\\nGIT WORKFLOW RULES (CRITICAL - ENFORCE STRICTLY):\\n - NEVER push directly to main branch - ALWAYS use pull requests\\n - NEVER use 'git add .' - ALWAYS add specific files with 'git add path/to/file'\\n - ALWAYS clean up test files, documentation files, and temporary artifacts before committing\\n - ALWAYS ensure git history maintains production quality - no test commits, no debugging code\\n - ALWAYS create descriptive commit messages following project conventions\\n - ALWAYS run tests and build before creating PR\\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 specific/files && git commit -m \\\"message\\\" && git push (NEVER use git add .)\\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.",
341
- },
342
- }
64
+ function getAgentTemplatesDir(): string {
65
+ return path.resolve(__dirname, "../../templates/agents");
343
66
  }
344
67
 
345
68
  /**
346
69
  * Check if opencode is installed
347
70
  */
348
71
  function checkOpencodeInstalled(): Promise<boolean> {
349
- return new Promise((resolve) => {
350
- const child = spawn('which', ['opencode'], {
351
- stdio: 'pipe',
352
- })
353
-
354
- child.on('close', (code) => {
355
- resolve(code === 0)
356
- })
357
-
358
- child.on('error', () => {
359
- resolve(false)
360
- })
361
- })
72
+ return new Promise(resolve => {
73
+ const child = spawn("which", ["opencode"], {
74
+ stdio: "pipe",
75
+ });
76
+
77
+ child.on("close", code => {
78
+ resolve(code === 0);
79
+ });
80
+
81
+ child.on("error", () => {
82
+ resolve(false);
83
+ });
84
+ });
362
85
  }
363
86
 
364
87
  /**
365
88
  * Install opencode via npm
366
89
  */
367
90
  async function installOpencode(): Promise<boolean> {
368
- console.log(chalk.cyan('Installing OpenCode via npm...'))
91
+ console.log(chalk.cyan("Installing OpenCode via npm..."));
369
92
 
370
93
  try {
371
94
  await new Promise<void>((resolve, reject) => {
372
- const install = spawn('npm', ['install', '-g', 'opencode-ai@1.3'], {
373
- stdio: 'inherit',
374
- })
95
+ const install = spawn("npm", ["install", "-g", "opencode-ai@1.3"], {
96
+ stdio: "inherit",
97
+ });
375
98
 
376
- install.on('close', (code) => {
99
+ install.on("close", code => {
377
100
  if (code === 0) {
378
- console.log(chalk.green('✓ OpenCode installed successfully!'))
379
- resolve()
101
+ console.log(chalk.green("✓ OpenCode installed successfully!"));
102
+ resolve();
380
103
  } else {
381
- reject(new Error(`Installation failed with code ${code}`))
104
+ reject(new Error(`Installation failed with code ${code}`));
382
105
  }
383
- })
106
+ });
384
107
 
385
- install.on('error', reject)
386
- })
108
+ install.on("error", reject);
109
+ });
387
110
 
388
111
  // Verify installation
389
- const opencodeInstalled = await checkOpencodeInstalled()
112
+ const opencodeInstalled = await checkOpencodeInstalled();
390
113
  if (!opencodeInstalled) {
391
- console.log(
392
- chalk.yellow('Installation completed but opencode command not found.')
393
- )
394
- console.log(
395
- chalk.yellow(
396
- 'You may need to restart your terminal or check your PATH.'
397
- )
398
- )
399
- return false
114
+ console.log(chalk.yellow("Installation completed but opencode command not found."));
115
+ console.log(chalk.yellow("You may need to restart your terminal or check your PATH."));
116
+ return false;
400
117
  }
401
118
 
402
- return true
119
+ return true;
403
120
  } catch (error) {
404
- console.error(chalk.red('Failed to install OpenCode:'))
405
- console.error(error instanceof Error ? error.message : String(error))
406
- console.log(chalk.blue('\nAlternative installation methods:'))
407
- console.log(chalk.blue(' curl -fsSL https://opencode.ai/install | sh'))
408
- console.log(chalk.blue(' Or visit: https://opencode.ai/docs'))
409
- return false
121
+ console.error(chalk.red("Failed to install OpenCode:"));
122
+ console.error(error instanceof Error ? error.message : String(error));
123
+ console.log(chalk.blue("\nAlternative installation methods:"));
124
+ console.log(chalk.blue(" curl -fsSL https://opencode.ai/install | sh"));
125
+ console.log(chalk.blue(" Or visit: https://opencode.ai/docs"));
126
+ return false;
410
127
  }
411
128
  }
412
129
 
@@ -414,36 +131,27 @@ async function installOpencode(): Promise<boolean> {
414
131
  * Ensure opencode is installed, offering to install if not
415
132
  */
416
133
  async function ensureOpencodeInstalled(autoYes = false): Promise<boolean> {
417
- let opencodeInstalled = await checkOpencodeInstalled()
134
+ let opencodeInstalled = await checkOpencodeInstalled();
418
135
  if (!opencodeInstalled) {
419
136
  if (!autoYes) {
420
- console.log(chalk.red('OpenCode is not installed.'))
421
- console.log(
422
- chalk.blue('OpenCode is required for the AI coding assistant.')
423
- )
137
+ console.log(chalk.red("OpenCode is not installed."));
138
+ console.log(chalk.blue("OpenCode is required for the AI coding assistant."));
424
139
  }
425
140
 
426
- if (
427
- await confirm(
428
- 'Would you like to install OpenCode automatically? (Y/n): ',
429
- autoYes
430
- )
431
- ) {
432
- opencodeInstalled = await installOpencode()
141
+ if (await confirm("Would you like to install OpenCode automatically? (Y/n): ", autoYes)) {
142
+ opencodeInstalled = await installOpencode();
433
143
  } else {
434
144
  if (!autoYes) {
435
- console.log(chalk.blue('\nInstallation cancelled.'))
145
+ console.log(chalk.blue("\nInstallation cancelled."));
436
146
  console.log(
437
- chalk.blue(
438
- 'To install manually: curl -fsSL https://opencode.ai/install | bash'
439
- )
440
- )
441
- console.log(chalk.blue('Or visit: https://opencode.ai/docs'))
147
+ chalk.blue("To install manually: curl -fsSL https://opencode.ai/install | bash")
148
+ );
149
+ console.log(chalk.blue("Or visit: https://opencode.ai/docs"));
442
150
  }
443
151
  }
444
152
  }
445
153
 
446
- return opencodeInstalled
154
+ return opencodeInstalled;
447
155
  }
448
156
 
449
157
  /**
@@ -452,915 +160,468 @@ async function ensureOpencodeInstalled(autoYes = false): Promise<boolean> {
452
160
  export function registerCodeCommands(program: Command): void {
453
161
  const code = program
454
162
  .command(COMMAND_GROUPS.CODE)
455
- .description('AI-powered coding assistant with OpenCode')
163
+ .description("AI-powered coding assistant with OpenCode");
164
+
165
+ if (process.env.BERGET_EXPERIMENTAL) {
166
+ code
167
+ .command("setup")
168
+ .description("Interactive setup for Berget AI coding tools")
169
+ .action(async () => {
170
+ try {
171
+ await runSetupCommand();
172
+ } catch (error) {
173
+ handleError("Setup failed", error);
174
+ }
175
+ });
176
+ }
456
177
 
457
178
  code
458
179
  .command(SUBCOMMANDS.CODE.INIT)
459
- .description('Initialize project for AI coding assistant')
460
- .option('-n, --name <name>', 'Project name (defaults to directory name)')
461
- .option('-f, --force', 'Overwrite existing configuration')
462
- .option(
463
- '-y, --yes',
464
- 'Automatically answer yes to all prompts (for automation)'
465
- )
466
- .action(async (options) => {
180
+ .description("Initialize project for AI coding assistant")
181
+ .option("-n, --name <name>", "Project name (defaults to directory name)")
182
+ .option("-f, --force", "Overwrite existing configuration")
183
+ .option("-y, --yes", "Automatically answer yes to all prompts (for automation)")
184
+ .action(async options => {
467
185
  try {
468
- const projectName = options.name || getProjectName()
469
- const configPath = path.join(process.cwd(), 'opencode.json')
186
+ const projectName = options.name || getProjectName();
187
+ const configPath = path.join(process.cwd(), "opencode.json");
470
188
 
471
189
  // Check if already initialized
472
190
  if (fs.existsSync(configPath) && !options.force) {
473
191
  if (!options.yes) {
474
- console.log(
475
- chalk.yellow('Project already initialized for OpenCode.')
476
- )
477
- console.log(chalk.dim(`Config file: ${configPath}`))
192
+ console.log(chalk.yellow("Project already initialized for OpenCode."));
193
+ console.log(chalk.dim(`Config file: ${configPath}`));
478
194
  }
479
195
 
480
- if (
481
- await confirm('Do you want to reinitialize? (Y/n): ', options.yes)
482
- ) {
196
+ if (await confirm("Do you want to reinitialize? (Y/n): ", options.yes)) {
483
197
  // Continue with reinitialization
484
198
  } else {
485
- return
199
+ return;
486
200
  }
487
201
  }
488
202
 
489
203
  // Ensure opencode is installed
490
204
  if (!(await ensureOpencodeInstalled(options.yes))) {
491
- return
205
+ return;
492
206
  }
493
207
 
494
- console.log(
495
- chalk.cyan(`Initializing OpenCode for project: ${projectName}`)
496
- )
497
-
498
- // Load latest agent configuration from our own codebase
499
- const latestAgentConfig = await loadLatestAgentConfig()
208
+ console.log(chalk.cyan(`Initializing OpenCode for project: ${projectName}`));
500
209
 
501
- // Use hardcoded defaults for init - never try to load from project
502
- // Create opencode.json config — plugin handles auth, models, and provider
503
210
  const config = {
504
- $schema: 'https://opencode.ai/config.json',
505
- plugin: ['@bergetai/opencode-auth@1.0.16'],
506
- username: 'berget-code',
507
- theme: 'berget-dark',
508
- share: 'manual',
509
- autoupdate: true,
510
- agent: {
511
- fullstack: {
512
- temperature: 0.3,
513
- top_p: 0.9,
514
- mode: 'primary',
515
- permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
516
- description:
517
- 'Router/coordinator agent for full-stack development with schema-driven architecture',
518
- prompt:
519
- '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.',
520
- },
521
- frontend: {
522
- temperature: 0.4,
523
- top_p: 0.9,
524
- mode: 'primary',
525
- permission: { edit: 'allow', bash: 'deny', webfetch: 'allow' },
526
- note: 'Bash access is denied for frontend persona to prevent shell command execution in UI environments. This restriction enforces security and architectural boundaries.',
527
- description:
528
- 'Builds Scandinavian, type-safe UIs with React, Tailwind, Shadcn.',
529
- prompt:
530
- '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.',
531
- },
532
- backend: {
533
- temperature: 0.3,
534
- top_p: 0.9,
535
- mode: 'primary',
536
- permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
537
- description:
538
- 'Functional, modular Koa + TypeScript services; schema-first with code quality focus.',
539
- prompt:
540
- '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.',
541
- },
542
- // Use centralized devops configuration with Helm guidelines
543
- devops: latestAgentConfig.devops || {
544
- temperature: 0.3,
545
- top_p: 0.8,
546
- mode: 'primary',
547
- permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
548
- description:
549
- 'Declarative GitOps infra with FluxCD, Kustomize, Helm, operators.',
550
- prompt:
551
- '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.',
552
- },
553
- app: {
554
- temperature: 0.4,
555
- top_p: 0.9,
556
- mode: 'primary',
557
- permission: { edit: 'allow', bash: 'deny', webfetch: 'allow' },
558
- note: 'Bash access is denied for app persona to prevent shell command execution in mobile/Expo environments. This restriction enforces security and architectural boundaries.',
559
- description:
560
- 'Expo + React Native apps; props-first, offline-aware, shared tokens.',
561
- prompt:
562
- '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.',
563
- },
564
- security: {
565
- temperature: 0.2,
566
- top_p: 0.8,
567
- mode: 'subagent',
568
- permission: { edit: 'deny', bash: 'allow', webfetch: 'allow' },
569
- description:
570
- 'Security specialist for pentesting, OWASP compliance, and vulnerability assessments.',
571
- prompt:
572
- '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.',
573
- },
574
- quality: {
575
- temperature: 0.1,
576
- top_p: 0.9,
577
- mode: 'subagent',
578
- permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
579
- description:
580
- 'Quality assurance specialist for testing, building, and PR management.',
581
- prompt:
582
- '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.',
583
- },
584
- },
585
- command: {
586
- fullstack: {
587
- description: 'Switch to Fullstack (router)',
588
- template: '{{input}}',
589
- agent: 'fullstack',
590
- },
591
- route: {
592
- description:
593
- 'Let Fullstack auto-route to the right persona based on files/intent',
594
- template: 'ROUTE {{input}}',
595
- agent: 'fullstack',
596
- subtask: true,
597
- },
598
- frontend: {
599
- description: 'Switch to Frontend persona',
600
- template: '{{input}}',
601
- agent: 'frontend',
602
- },
603
- backend: {
604
- description: 'Switch to Backend persona',
605
- template: '{{input}}',
606
- agent: 'backend',
607
- },
608
- devops: {
609
- description: 'Switch to DevOps persona',
610
- template: '{{input}}',
611
- agent: 'devops',
612
- },
613
- app: {
614
- description: 'Switch to App persona',
615
- template: '{{input}}',
616
- agent: 'app',
617
- },
618
- quality: {
619
- description:
620
- 'Switch to Quality agent for testing, building, and PR management',
621
- template: '{{input}}',
622
- agent: 'quality',
623
- },
624
- },
625
- watcher: {
626
- ignore: ['node_modules', 'dist', '.git', 'coverage'],
627
- },
628
- }
211
+ $schema: "https://opencode.ai/config.json",
212
+ plugin: ["@bergetai/opencode-auth@1.0.16"],
213
+ };
214
+
215
+ const agentsDir = path.join(process.cwd(), ".opencode", "agents");
216
+ const templatesDir = getAgentTemplatesDir();
629
217
 
630
- // Ask for permission to create config files
631
218
  if (!options.yes) {
632
- console.log(chalk.blue('\nAbout to create configuration files:'))
633
- console.log(chalk.dim(`Config: ${configPath}`))
634
- console.log(
635
- chalk.dim('This will configure OpenCode with the Berget auth plugin.')
636
- )
219
+ console.log(chalk.blue("\nAbout to create configuration files:"));
220
+ console.log(chalk.dim(`Config: ${configPath}`));
221
+ console.log(chalk.dim(`Agents: ${agentsDir}/`));
222
+ console.log(chalk.dim("This will configure OpenCode with the Berget auth plugin."));
637
223
  }
638
224
 
639
- if (
640
- await confirm('\nCreate configuration files? (Y/n): ', options.yes)
641
- ) {
225
+ if (await confirm("\nCreate configuration files? (Y/n): ", options.yes)) {
642
226
  try {
643
- // Create opencode.json
644
- await writeFile(configPath, JSON.stringify(config, null, 2))
645
- console.log(chalk.green(`✓ Created opencode.json`))
646
- console.log(chalk.dim(` Plugin: @bergetai/opencode-auth`))
647
- console.log(chalk.dim(` Theme: ${config.theme}`))
227
+ await writeFile(configPath, JSON.stringify(config, null, 2));
228
+ console.log(chalk.green("✓ Created opencode.json"));
229
+ console.log(chalk.dim(" Plugin: @bergetai/opencode-auth"));
230
+
231
+ fs.mkdirSync(agentsDir, { recursive: true });
232
+ const templateFiles = fs.readdirSync(templatesDir).filter(f => f.endsWith(".md"));
233
+ for (const file of templateFiles) {
234
+ const src = path.join(templatesDir, file);
235
+ const dest = path.join(agentsDir, file);
236
+ fs.copyFileSync(src, dest);
237
+ }
238
+ console.log(
239
+ chalk.green(
240
+ `✓ Created ${templateFiles.length} agent definitions in .opencode/agents/`
241
+ )
242
+ );
648
243
  } catch (error) {
649
- console.error(chalk.red('Failed to create config files:'))
650
- handleError('Config file creation failed', error)
651
- return
244
+ console.error(chalk.red("Failed to create config files:"));
245
+ handleError("Config file creation failed", error);
246
+ return;
652
247
  }
653
248
  } else {
654
- console.log(chalk.yellow('Configuration file creation cancelled.'))
655
- return
249
+ console.log(chalk.yellow("Configuration file creation cancelled."));
250
+ return;
656
251
  }
657
252
 
658
- console.log(chalk.green('\n✅ Project initialized successfully!'))
659
- console.log(chalk.blue('\nNext steps:'))
660
- console.log(chalk.cyan(' 1. Run: opencode'))
661
- console.log(chalk.cyan(' 2. Type: /connect'))
662
- console.log(chalk.cyan(' 3. Choose your auth method:'))
663
- console.log(chalk.dim(' • "Login with Berget" — Berget Code team members (SSO)'))
664
- console.log(chalk.dim(' • "Enter API Key" — API key users (console.berget.ai)'))
253
+ console.log(chalk.green("\n✅ Project initialized successfully!"));
254
+ console.log(chalk.blue("\nNext steps:"));
255
+ console.log(chalk.cyan(" 1. Run: opencode"));
256
+ console.log(chalk.cyan(" 2. Type: /connect"));
257
+ console.log(chalk.cyan(" 3. Choose your auth method:"));
258
+ console.log(chalk.dim(' • "Login with Berget" — Berget Code team members (SSO)'));
259
+ console.log(chalk.dim(' • "Enter API Key" — API key users (console.berget.ai)'));
665
260
  } catch (error) {
666
- handleError('Failed to initialize project', error)
261
+ handleError("Failed to initialize project", error);
667
262
  }
668
- })
263
+ });
669
264
 
670
265
  code
671
266
  .command(SUBCOMMANDS.CODE.RUN)
672
- .description('Run AI coding assistant')
673
- .argument('[prompt]', 'Prompt to send directly to OpenCode')
674
- .option('-m, --model <model>', 'Model to use (overrides config)')
675
- .option('-a, --analysis', 'Use fast analysis model for context building')
676
- .option('--no-config', 'Run without loading project config')
677
- .option(
678
- '-y, --yes',
679
- 'Automatically answer yes to all prompts (for automation)'
680
- )
267
+ .description("Run AI coding assistant")
268
+ .argument("[prompt]", "Prompt to send directly to OpenCode")
269
+ .option("-m, --model <model>", "Model to use (overrides config)")
270
+ .option("-a, --analysis", "Use fast analysis model for context building")
271
+ .option("--no-config", "Run without loading project config")
272
+ .option("-y, --yes", "Automatically answer yes to all prompts (for automation)")
681
273
  .action(async (prompt: string, options: any) => {
682
274
  try {
683
- const configPath = path.join(process.cwd(), 'opencode.json')
275
+ const configPath = path.join(process.cwd(), "opencode.json");
684
276
 
685
277
  // Ensure opencode is installed
686
278
  if (!(await ensureOpencodeInstalled(options.yes))) {
687
- return
279
+ return;
688
280
  }
689
281
 
690
- let config: any = null
282
+ let config: any = null;
691
283
  if (!options.noConfig && fs.existsSync(configPath)) {
692
284
  try {
693
- const configContent = await readFile(configPath, 'utf8')
694
- config = JSON.parse(configContent)
695
- console.log(
696
- chalk.dim(`Loaded config for project: ${config.projectName}`)
697
- )
285
+ const configContent = await readFile(configPath, "utf8");
286
+ config = JSON.parse(configContent);
287
+ console.log(chalk.dim(`Loaded config for project: ${config.projectName}`));
698
288
  console.log(
699
- chalk.dim(
700
- `Models: Analysis=${config.analysisModel}, Build=${config.buildModel}`
701
- )
702
- )
703
- } catch (error) {
704
- console.log(chalk.yellow('Warning: Failed to load opencode.json'))
289
+ chalk.dim(`Models: Analysis=${config.analysisModel}, Build=${config.buildModel}`)
290
+ );
291
+ } catch {
292
+ console.log(chalk.yellow("Warning: Failed to load opencode.json"));
705
293
  }
706
294
  }
707
295
 
708
296
  if (!config) {
709
- console.log(chalk.yellow('No project configuration found.'))
297
+ console.log(chalk.yellow("No project configuration found."));
710
298
  console.log(
711
299
  chalk.blue(
712
- `Run ${chalk.bold(
713
- `berget ${COMMAND_GROUPS.CODE} ${SUBCOMMANDS.CODE.INIT}`
714
- )} first.`
300
+ `Run ${chalk.bold(`berget ${COMMAND_GROUPS.CODE} ${SUBCOMMANDS.CODE.INIT}`)} first.`
715
301
  )
716
- )
717
- return
302
+ );
303
+ return;
718
304
  }
719
305
 
720
306
  // Prepare opencode command
721
- const env = { ...process.env }
722
- const opencodeArgs: string[] = []
307
+ const env = { ...process.env };
308
+ const opencodeArgs: string[] = [];
723
309
 
724
310
  // Read --stage and --local from root program options
725
311
  // (these flags are registered at program level, not subcommand level)
726
- const isStage = process.argv.includes('--stage')
727
- const isLocal = process.argv.includes('--local')
312
+ const isStage = process.argv.includes("--stage");
313
+ const isLocal = process.argv.includes("--local");
728
314
 
729
315
  if (isStage) {
730
- console.log(chalk.cyan('Using Berget stage environment'))
731
- env.BERGET_API_URL = 'https://api.stage.berget.ai'
732
- env.BERGET_INFERENCE_URL = 'https://api.stage.berget.ai/v1'
316
+ console.log(chalk.cyan("Using Berget stage environment"));
317
+ env.BERGET_API_URL = "https://api.stage.berget.ai";
318
+ env.BERGET_INFERENCE_URL = "https://api.stage.berget.ai/v1";
733
319
  } else if (isLocal) {
734
- console.log(chalk.cyan('Using local development environment'))
735
- env.BERGET_API_URL = 'http://localhost:3000'
736
- env.BERGET_INFERENCE_URL = 'http://localhost:3000/v1'
320
+ console.log(chalk.cyan("Using local development environment"));
321
+ env.BERGET_API_URL = "http://localhost:3000";
322
+ env.BERGET_INFERENCE_URL = "http://localhost:3000/v1";
737
323
  }
738
324
 
739
325
  if (prompt) {
740
- opencodeArgs.push('run', prompt)
326
+ opencodeArgs.push("run", prompt);
741
327
  }
742
328
 
743
329
  // Choose model based on analysis flag or override
744
- let selectedModel = options.model || config.buildModel
330
+ let selectedModel = options.model || config.buildModel;
745
331
  if (options.analysis && !options.model) {
746
- selectedModel = config.analysisModel
332
+ selectedModel = config.analysisModel;
747
333
  }
748
334
 
749
335
  if (selectedModel) {
750
- opencodeArgs.push('--model', selectedModel)
336
+ opencodeArgs.push("--model", selectedModel);
751
337
  }
752
338
 
753
- console.log(chalk.cyan('Starting OpenCode...'))
339
+ console.log(chalk.cyan("Starting OpenCode..."));
754
340
 
755
341
  // Spawn opencode process
756
- const opencode = spawn('opencode', opencodeArgs, {
757
- stdio: 'inherit',
342
+ const opencode = spawn("opencode", opencodeArgs, {
343
+ stdio: "inherit",
758
344
  env: env,
759
- })
345
+ });
760
346
 
761
- opencode.on('close', (code) => {
347
+ opencode.on("close", code => {
762
348
  if (code !== 0) {
763
- console.log(chalk.red(`OpenCode exited with code ${code}`))
349
+ console.log(chalk.red(`OpenCode exited with code ${code}`));
764
350
  }
765
- })
351
+ });
766
352
 
767
- opencode.on('error', (error) => {
768
- console.error(chalk.red('Failed to start OpenCode:'))
769
- console.error(error.message)
770
- })
353
+ opencode.on("error", error => {
354
+ console.error(chalk.red("Failed to start OpenCode:"));
355
+ console.error(error.message);
356
+ });
771
357
  } catch (error) {
772
- handleError('Failed to run OpenCode', error)
358
+ handleError("Failed to run OpenCode", error);
773
359
  }
774
- })
360
+ });
775
361
 
776
362
  code
777
363
  .command(SUBCOMMANDS.CODE.SERVE)
778
- .description('Start OpenCode web server')
779
- .option('-p, --port <port>', 'Port to run the server on (default: 3000)')
780
- .option(
781
- '-h, --host <host>',
782
- 'Host to bind the server to (default: localhost)'
783
- )
784
- .option(
785
- '-y, --yes',
786
- 'Automatically answer yes to all prompts (for automation)'
787
- )
788
- .action(async (options) => {
364
+ .description("Start OpenCode web server")
365
+ .option("-p, --port <port>", "Port to run the server on (default: 3000)")
366
+ .option("-h, --host <host>", "Host to bind the server to (default: localhost)")
367
+ .option("-y, --yes", "Automatically answer yes to all prompts (for automation)")
368
+ .action(async options => {
789
369
  try {
790
370
  // Ensure opencode is installed
791
371
  if (!(await ensureOpencodeInstalled(options.yes))) {
792
- return
372
+ return;
793
373
  }
794
374
 
795
- console.log(chalk.cyan('🚀 Starting OpenCode web server...'))
375
+ console.log(chalk.cyan("🚀 Starting OpenCode web server..."));
796
376
 
797
377
  // Prepare opencode serve command
798
- const serveArgs: string[] = ['serve']
378
+ const serveArgs: string[] = ["serve"];
799
379
 
800
380
  if (options.port) {
801
- serveArgs.push('--port', options.port)
381
+ serveArgs.push("--port", options.port);
802
382
  }
803
383
 
804
384
  if (options.host) {
805
- serveArgs.push('--host', options.host)
385
+ serveArgs.push("--host", options.host);
806
386
  }
807
387
 
808
388
  // Spawn opencode serve process
809
- const opencode = spawn('opencode', serveArgs, {
810
- stdio: 'inherit',
811
- })
389
+ const opencode = spawn("opencode", serveArgs, {
390
+ stdio: "inherit",
391
+ });
812
392
 
813
- opencode.on('close', (code) => {
393
+ opencode.on("close", code => {
814
394
  if (code !== 0) {
815
- console.log(chalk.red(`OpenCode server exited with code ${code}`))
395
+ console.log(chalk.red(`OpenCode server exited with code ${code}`));
816
396
  }
817
- })
397
+ });
818
398
 
819
- opencode.on('error', (error) => {
820
- console.error(chalk.red('Failed to start OpenCode server:'))
821
- console.error(error.message)
822
- })
399
+ opencode.on("error", error => {
400
+ console.error(chalk.red("Failed to start OpenCode server:"));
401
+ console.error(error.message);
402
+ });
823
403
  } catch (error) {
824
- handleError('Failed to start OpenCode server', error)
404
+ handleError("Failed to start OpenCode server", error);
825
405
  }
826
- })
406
+ });
827
407
 
828
408
  code
829
409
  .command(SUBCOMMANDS.CODE.UPDATE)
830
- .description('Update OpenCode and agents to latest versions')
831
- .option('-f, --force', 'Force update even if already latest')
832
- .option(
833
- '-y, --yes',
834
- 'Automatically answer yes to all prompts (for automation)'
835
- )
836
- .action(async (options) => {
410
+ .description("Update OpenCode and agents to latest versions")
411
+ .option("-f, --force", "Force update even if already latest")
412
+ .option("-y, --yes", "Automatically answer yes to all prompts (for automation)")
413
+ .action(async options => {
837
414
  try {
838
- console.log(chalk.cyan('🔄 Updating OpenCode configuration...'))
415
+ console.log(chalk.cyan("🔄 Updating OpenCode configuration..."));
839
416
 
840
417
  // Ensure opencode is installed first
841
418
  if (!(await ensureOpencodeInstalled(options.yes))) {
842
- return
419
+ return;
843
420
  }
844
421
 
845
- const configPath = path.join(process.cwd(), 'opencode.json')
422
+ const configPath = path.join(process.cwd(), "opencode.json");
846
423
 
847
424
  // Check if project is initialized
848
425
  if (!fs.existsSync(configPath)) {
849
- console.log(chalk.red('❌ No OpenCode configuration found.'))
426
+ console.log(chalk.red("❌ No OpenCode configuration found."));
850
427
  console.log(
851
428
  chalk.blue(
852
- `Run ${chalk.bold(
853
- `berget ${COMMAND_GROUPS.CODE} ${SUBCOMMANDS.CODE.INIT}`
854
- )} first.`
429
+ `Run ${chalk.bold(`berget ${COMMAND_GROUPS.CODE} ${SUBCOMMANDS.CODE.INIT}`)} first.`
855
430
  )
856
- )
857
- return
431
+ );
432
+ return;
858
433
  }
859
434
 
860
435
  // Read current configuration
861
- let currentConfig: any
436
+ let currentConfig: any;
862
437
  try {
863
- const configContent = await readFile(configPath, 'utf8')
864
- currentConfig = JSON.parse(configContent)
438
+ const configContent = await readFile(configPath, "utf8");
439
+ currentConfig = JSON.parse(configContent);
865
440
  } catch (error) {
866
- console.error(chalk.red('Failed to read current opencode.json:'))
867
- handleError('Config read failed', error)
868
- return
441
+ console.error(chalk.red("Failed to read current opencode.json:"));
442
+ handleError("Config read failed", error);
443
+ return;
869
444
  }
870
445
 
871
- console.log(chalk.blue('📋 Current configuration:'))
872
- console.log(chalk.dim(` Model: ${currentConfig.model}`))
873
- console.log(chalk.dim(` Theme: ${currentConfig.theme}`))
874
- console.log(
875
- chalk.dim(
876
- ` Agents: ${
877
- Object.keys(currentConfig.agent || {}).length
878
- } configured`
879
- )
880
- )
881
-
882
- // Load latest agent configuration to ensure consistency
883
- const latestAgentConfig = await loadLatestAgentConfig()
884
-
885
- // Create latest configuration — plugin handles auth, models, and provider
886
- const latestConfig = {
887
- $schema: 'https://opencode.ai/config.json',
888
- plugin: ['@bergetai/opencode-auth@1.0.16'],
889
- username: 'berget-code',
890
- theme: 'berget-dark',
891
- share: 'manual',
892
- autoupdate: true,
893
- agent: {
894
- fullstack: {
895
- temperature: 0.3,
896
- top_p: 0.9,
897
- mode: 'primary',
898
- permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
899
- description:
900
- 'Router/coordinator agent for full-stack development with schema-driven architecture',
901
- prompt:
902
- '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.',
903
- },
904
- frontend: {
905
- temperature: 0.4,
906
- top_p: 0.9,
907
- mode: 'primary',
908
- permission: { edit: 'allow', bash: 'deny', webfetch: 'allow' },
909
- note: 'Bash access is denied for frontend persona to prevent shell command execution in UI environments. This restriction enforces security and architectural boundaries.',
910
- description:
911
- 'Builds Scandinavian, type-safe UIs with React, Tailwind, Shadcn.',
912
- prompt:
913
- '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.',
914
- },
915
- backend: {
916
- temperature: 0.3,
917
- top_p: 0.9,
918
- mode: 'primary',
919
- permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
920
- description:
921
- 'Functional, modular Koa + TypeScript services; schema-first with code quality focus.',
922
- prompt:
923
- '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.',
924
- },
925
- // Use centralized devops configuration with Helm guidelines
926
- devops: latestAgentConfig.devops || {
927
- temperature: 0.3,
928
- top_p: 0.8,
929
- mode: 'primary',
930
- permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
931
- description:
932
- 'Declarative GitOps infra with FluxCD, Kustomize, Helm, operators.',
933
- prompt:
934
- '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.',
935
- },
936
- app: {
937
- temperature: 0.4,
938
- top_p: 0.9,
939
- mode: 'primary',
940
- permission: { edit: 'allow', bash: 'deny', webfetch: 'allow' },
941
- note: 'Bash access is denied for app persona to prevent shell command execution in mobile/Expo environments. This restriction enforces security and architectural boundaries.',
942
- description:
943
- 'Expo + React Native apps; props-first, offline-aware, shared tokens.',
944
- prompt:
945
- '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.',
946
- },
947
- security: {
948
- temperature: 0.2,
949
- top_p: 0.8,
950
- mode: 'subagent',
951
- permission: { edit: 'deny', bash: 'allow', webfetch: 'allow' },
952
- description:
953
- 'Security specialist for pentesting, OWASP compliance, and vulnerability assessments.',
954
- prompt:
955
- '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.',
956
- },
957
- quality: {
958
- temperature: 0.1,
959
- top_p: 0.9,
960
- mode: 'subagent',
961
- permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
962
- description:
963
- 'Quality assurance specialist for testing, building, and complete PR management.',
964
- prompt:
965
- '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.',
966
- },
967
- },
968
- command: {
969
- fullstack: {
970
- description: 'Switch to Fullstack (router)',
971
- template: '{{input}}',
972
- agent: 'fullstack',
973
- },
974
- route: {
975
- description:
976
- 'Let Fullstack auto-route to the right persona based on files/intent',
977
- template: 'ROUTE {{input}}',
978
- agent: 'fullstack',
979
- subtask: true,
980
- },
981
- frontend: {
982
- description: 'Switch to Frontend persona',
983
- template: '{{input}}',
984
- agent: 'frontend',
985
- },
986
- backend: {
987
- description: 'Switch to Backend persona',
988
- template: '{{input}}',
989
- agent: 'backend',
990
- },
991
- devops: {
992
- description: 'Switch to DevOps persona',
993
- template: '{{input}}',
994
- agent: 'devops',
995
- },
996
- app: {
997
- description: 'Switch to App persona',
998
- template: '{{input}}',
999
- agent: 'app',
1000
- },
1001
- security: {
1002
- description:
1003
- 'Switch to Security persona for pentesting and OWASP compliance',
1004
- template: '{{input}}',
1005
- agent: 'security',
1006
- },
1007
- quality: {
1008
- description:
1009
- 'Switch to Quality agent for testing, building, and PR management',
1010
- template: '{{input}}',
1011
- agent: 'quality',
1012
- },
1013
- },
1014
- watcher: {
1015
- ignore: ['node_modules', 'dist', '.git', 'coverage'],
1016
- },
446
+ console.log(chalk.blue("📋 Current configuration:"));
447
+ if (currentConfig.model) {
448
+ console.log(chalk.dim(` Model: ${currentConfig.model}`));
1017
449
  }
1018
450
 
1019
- // Check if update is needed
1020
- const needsUpdate =
1021
- JSON.stringify(currentConfig) !== JSON.stringify(latestConfig)
451
+ const agentsDir = path.join(process.cwd(), ".opencode", "agents");
452
+ const templatesDir = getAgentTemplatesDir();
453
+ const templateFiles = fs.readdirSync(templatesDir).filter(f => f.endsWith(".md"));
1022
454
 
1023
- if (!needsUpdate && !options.force) {
1024
- console.log(chalk.green('✅ Already using the latest configuration!'))
1025
- return
455
+ // Check if agent definitions need updating
456
+ let agentsNeedUpdate = false;
457
+
458
+ for (const file of templateFiles) {
459
+ const src = path.join(templatesDir, file);
460
+ const dest = path.join(agentsDir, file);
461
+ if (!fs.existsSync(dest)) {
462
+ agentsNeedUpdate = true;
463
+ break;
464
+ }
465
+ const srcContent = fs.readFileSync(src, "utf8");
466
+ const destContent = fs.readFileSync(dest, "utf8");
467
+ if (srcContent !== destContent) {
468
+ agentsNeedUpdate = true;
469
+ break;
470
+ }
1026
471
  }
1027
472
 
1028
- if (needsUpdate) {
1029
- console.log(chalk.blue('\n🔄 Updates available:'))
473
+ // Check if opencode.json still has inline agent config (needs migration)
474
+ const needsMigration = !!currentConfig.agent;
1030
475
 
1031
- // Compare agents
1032
- const currentAgents = Object.keys(currentConfig.agent || {})
1033
- const latestAgents = Object.keys(latestConfig.agent)
1034
- const newAgents = latestAgents.filter(
1035
- (agent) => !currentAgents.includes(agent)
1036
- )
476
+ if (!agentsNeedUpdate && !needsMigration && !options.force) {
477
+ console.log(chalk.green("✅ Already using the latest configuration!"));
478
+ return;
479
+ }
1037
480
 
1038
- if (newAgents.length > 0) {
1039
- console.log(chalk.cyan(` • New agents: ${newAgents.join(', ')}`))
1040
- }
481
+ if (agentsNeedUpdate || needsMigration) {
482
+ console.log(chalk.blue("\n🔄 Updates available:"));
1041
483
 
1042
- // Check for quality agent specifically
1043
- if (!currentConfig.agent?.quality && latestConfig.agent.quality) {
1044
- console.log(
1045
- chalk.cyan(' • Quality subagent for testing and PR management')
1046
- )
484
+ if (needsMigration) {
485
+ console.log(chalk.cyan(" • Migrate agents from opencode.json to .opencode/agents/"));
1047
486
  }
1048
487
 
1049
- // Check for security subagent mode
1050
- if (currentConfig.agent?.security?.mode !== 'subagent') {
1051
- console.log(
1052
- chalk.cyan(' • Security agent converted to subagent (read-only)')
1053
- )
488
+ if (agentsNeedUpdate) {
489
+ console.log(chalk.cyan(" • Latest agent prompts and improvements"));
1054
490
  }
1055
-
1056
- // Check for plugin migration
1057
- if (!currentConfig.plugin) {
1058
- console.log(
1059
- chalk.cyan(' • Plugin-first auth (automatic token refresh + model discovery)')
1060
- )
1061
- }
1062
-
1063
- console.log(chalk.cyan(' • Latest agent prompts and improvements'))
1064
491
  }
1065
492
 
1066
493
  if (options.force) {
1067
- console.log(chalk.yellow('🔧 Force update requested'))
494
+ console.log(chalk.yellow("🔧 Force update requested"));
1068
495
  }
1069
496
 
1070
497
  if (!options.yes) {
1071
498
  console.log(
1072
- chalk.blue(
1073
- '\nThis will update your OpenCode configuration with the latest improvements.'
1074
- )
1075
- )
499
+ chalk.blue("\nThis will update your agent definitions and OpenCode configuration.")
500
+ );
1076
501
 
1077
- // Check if user has git for backup
1078
- const hasGitRepo = hasGit()
502
+ const hasGitRepo = hasGit();
1079
503
  if (!hasGitRepo) {
1080
- console.log(
1081
- chalk.yellow(
1082
- '⚠️ No .git repository detected - backup will be created'
1083
- )
1084
- )
504
+ console.log(chalk.yellow("⚠️ No .git repository detected - backup will be created"));
1085
505
  } else {
1086
- console.log(
1087
- chalk.green('✓ Git repository detected - changes are tracked')
1088
- )
506
+ console.log(chalk.green("✓ Git repository detected - changes are tracked"));
1089
507
  }
1090
508
  }
1091
509
 
1092
- // Ask user what they want to do
1093
- console.log(chalk.blue('\nChoose update strategy:'))
1094
- console.log(
1095
- chalk.cyan(
1096
- '1) Replace - Use latest configuration (your customizations will be lost)'
1097
- )
1098
- )
1099
- console.log(
1100
- chalk.cyan(
1101
- '2) Merge - Combine latest updates with your customizations (recommended)'
1102
- )
1103
- )
1104
-
1105
- let mergeChoice: 'replace' | 'merge' = 'merge'
1106
-
1107
- if (!options.yes) {
1108
- const choice = await askChoice(
1109
- '\nYour choice (1-2, default: 2): ',
1110
- ['replace', 'merge'],
1111
- 'merge'
1112
- )
1113
- mergeChoice = choice as 'replace' | 'merge'
1114
- }
1115
-
1116
- if (
1117
- await confirm(`\nProceed with ${mergeChoice}? (Y/n): `, options.yes)
1118
- ) {
510
+ if (await confirm("\nProceed with update? (Y/n): ", options.yes)) {
1119
511
  try {
1120
- let finalConfig: any
1121
- let backupPath: string | null = null
512
+ let backupPath: string | null = null;
1122
513
 
1123
- // Create backup if no git
1124
514
  if (!hasGit()) {
1125
- backupPath = `${configPath}.backup.${Date.now()}`
1126
- await writeFile(
1127
- backupPath,
1128
- JSON.stringify(currentConfig, null, 2)
1129
- )
515
+ backupPath = `${configPath}.backup.${Date.now()}`;
516
+ await writeFile(backupPath, JSON.stringify(currentConfig, null, 2));
1130
517
  console.log(
1131
- chalk.green(
1132
- `✓ Backed up current config to ${path.basename(backupPath)}`
1133
- )
1134
- )
518
+ chalk.green(`✓ Backed up current config to ${path.basename(backupPath)}`)
519
+ );
1135
520
  }
1136
521
 
1137
- if (mergeChoice === 'merge') {
1138
- // Merge configurations
1139
- finalConfig = await mergeConfigurations(
1140
- currentConfig,
1141
- latestConfig
1142
- )
1143
- console.log(
1144
- chalk.green('✓ Merged configurations with latest updates')
1145
- )
1146
- } else {
1147
- // Replace with latest
1148
- finalConfig = latestConfig
1149
- console.log(chalk.green('✓ Replaced with latest configuration'))
522
+ // Remove inline agent section from opencode.json if present
523
+ if (currentConfig.agent) {
524
+ delete currentConfig.agent;
525
+ await writeFile(configPath, JSON.stringify(currentConfig, null, 2));
526
+ console.log(chalk.green("✓ Removed inline agent config from opencode.json"));
1150
527
  }
1151
528
 
1152
- // Write final configuration
1153
- await writeFile(configPath, JSON.stringify(finalConfig, null, 2))
1154
- console.log(
1155
- chalk.green(
1156
- `✓ Updated opencode.json with ${mergeChoice} strategy`
1157
- )
1158
- )
529
+ // Sync agent markdown files from templates
530
+ fs.mkdirSync(agentsDir, { recursive: true });
531
+ let updatedCount = 0;
532
+ for (const file of templateFiles) {
533
+ const src = path.join(templatesDir, file);
534
+ const dest = path.join(agentsDir, file);
535
+ const agentName = path.basename(file, ".md");
536
+
537
+ if (
538
+ !fs.existsSync(dest) ||
539
+ fs.readFileSync(src, "utf8") !== fs.readFileSync(dest, "utf8")
540
+ ) {
541
+ fs.copyFileSync(src, dest);
542
+ updatedCount++;
543
+ console.log(chalk.cyan(` • Updated agent: ${agentName}`));
544
+ }
545
+ }
546
+
547
+ if (updatedCount > 0) {
548
+ console.log(chalk.green(`✓ Updated ${updatedCount} agent definition(s)`));
549
+ }
1159
550
 
1160
551
  // Update AGENTS.md if it doesn't exist
1161
- const agentsMdPath = path.join(process.cwd(), 'AGENTS.md')
552
+ const agentsMdPath = path.join(process.cwd(), "AGENTS.md");
1162
553
  if (!fs.existsSync(agentsMdPath)) {
1163
554
  const agentsMdContent = `# Berget Code Agents
1164
555
 
1165
556
  This document describes the specialized agents available in this project for use with OpenCode.
1166
557
 
558
+ Agents are defined as markdown files in \`.opencode/agents/\` with YAML frontmatter.
559
+
1167
560
  ## Available Agents
1168
561
 
1169
562
  ### Primary Agents
1170
563
 
1171
- #### fullstack
1172
- Router/coordinator agent for full-stack development with schema-driven architecture. Handles routing between different personas based on file paths and task requirements.
1173
-
1174
- **Use when:**
1175
- - Working across multiple parts of a monorepo
1176
- - Need to coordinate between frontend, backend, devops, and app
1177
- - Starting new projects and need to determine tech stack
1178
-
1179
- **Key features:**
1180
- - Schema-driven development (database → OpenAPI → types)
1181
- - Automatic routing to appropriate persona
1182
- - Tech stack discovery and recommendations
1183
-
1184
- #### frontend
1185
- Builds Scandinavian, type-safe UIs with React, Tailwind, and Shadcn.
1186
-
1187
- **Use when:**
1188
- - Working with React components (.tsx files)
1189
- - Frontend development in /apps/frontend
1190
- - UI/UX implementation
1191
-
1192
- **Key features:**
1193
- - Design system integration
1194
- - Semantic tokens and accessibility
1195
- - Props-first component architecture
1196
-
1197
- #### backend
1198
- Functional, modular Koa + TypeScript services with schema-first approach and code quality focus.
1199
-
1200
- **Use when:**
1201
- - Working with Koa routers and services
1202
- - Backend development in /services
1203
- - API development and database work
1204
-
1205
- **Key features:**
1206
- - Zod validation and OpenAPI generation
1207
- - Code quality and refactoring principles
1208
- - PR workflow integration
1209
-
1210
- #### devops
1211
- Declarative GitOps infrastructure with FluxCD, Kustomize, Helm, and operators.
1212
-
1213
- **Use when:**
1214
- - Working with Kubernetes manifests
1215
- - Infrastructure in /infra or /k8s
1216
- - CI/CD and deployment configurations
1217
-
1218
- **Key features:**
1219
- - GitOps workflows
1220
- - Operator-first approach
1221
- - SemVer with release candidates
1222
-
1223
- **Helm Values Configuration Process:**
1224
- 1. Documentation First Approach: Always fetch official documentation from Artifact Hub/GitHub for the specific chart version before writing values. Search Artifact Hub for exact chart version documentation, check the chart's GitHub repository for official docs and examples, verify the exact version being used in the deployment.
1225
- 2. Validation Requirements: Check for available validation schemas before committing YAML files. Use Helm's built-in validation tools (helm lint, helm template). Validate against JSON schema if available for the chart. Ensure YAML syntax correctness with linters.
1226
- 3. Standard Workflow: Identify chart name and exact version. Fetch official documentation from Artifact Hub/GitHub. Check for available schemas and validation tools. Write values according to official documentation. Validate against schema (if available). Test with helm template or helm lint. Commit validated YAML files.
1227
- 4. Quality Assurance: Never commit unvalidated Helm values. Use helm dependency update when adding new charts. Test rendering with helm template --dry-run before deployment. Document any custom values with comments referencing official docs.
1228
-
1229
- #### app
1230
- Expo + React Native applications with props-first architecture and offline awareness.
1231
-
1232
- **Use when:**
1233
- - Mobile app development with Expo
1234
- - React Native projects in /apps/app
1235
- - Cross-platform mobile development
1236
-
1237
- **Key features:**
1238
- - Shared design tokens with frontend
1239
- - Offline-first architecture
1240
- - Expo integration
564
+ | Agent | Description | Temperature |
565
+ |-------|-------------|-------------|
566
+ | fullstack | Router/coordinator for full-stack development | 0.3 |
567
+ | frontend | Scandinavian, type-safe UIs with React, Tailwind, Shadcn | 0.4 |
568
+ | backend | Functional, modular Koa + TypeScript services | 0.3 |
569
+ | devops | Declarative GitOps infra with FluxCD, Kustomize, Helm | 0.3 |
570
+ | app | Expo + React Native apps; props-first, offline-aware | 0.4 |
1241
571
 
1242
572
  ### Subagents
1243
573
 
1244
- #### security
1245
- Security specialist for penetration testing, OWASP compliance, and vulnerability assessments.
1246
-
1247
- **Use when:**
1248
- - Need security review of code changes
1249
- - OWASP Top 10 compliance checks
1250
- - Vulnerability assessments
1251
-
1252
- **Key features:**
1253
- - OWASP standards compliance
1254
- - Security best practices
1255
- - Actionable remediation strategies
1256
-
1257
- #### quality
1258
- Quality assurance specialist for testing, building, and PR management.
1259
-
1260
- **Use when:**
1261
- - Need to run test suites and build processes
1262
- - Creating or updating pull requests
1263
- - Monitoring GitHub for reviewer comments
1264
- - Ensuring code quality standards
1265
-
1266
- **Key features:**
1267
- - Comprehensive testing and building workflows
1268
- - PR creation and management
1269
- - GitHub integration for reviewer feedback
1270
- - CLI command expertise for quality assurance
574
+ | Agent | Description | Temperature |
575
+ |-------|-------------|-------------|
576
+ | security | Security specialist for pentesting and OWASP compliance | 0.2 |
577
+ | quality | QA specialist for testing, building, and PR management | 0.1 |
1271
578
 
1272
579
  ## Usage
1273
580
 
1274
- ### Switching Agents
1275
- Use the \`<tab>\` key to cycle through primary agents during a session.
1276
-
1277
- ### Manual Agent Selection
1278
- Use commands to switch to specific agents:
1279
- - \`/fullstack\` - Switch to Fullstack agent
1280
- - \`/frontend\` - Switch to Frontend agent
1281
- - \`/backend\` - Switch to Backend agent
1282
- - \`/devops\` - Switch to DevOps agent
1283
- - \`/app\` - Switch to App agent
1284
- - \`/quality\` - Switch to Quality agent for testing and PR management
1285
-
1286
- ### Using Subagents
1287
- Mention subagents with \`@\` symbol:
1288
- - \`@security review this authentication implementation\`
1289
- - \`@quality run tests and create PR for these changes\`
581
+ - **Tab** key to cycle between primary agents
582
+ - **@mention** to invoke subagents (e.g. \`@security review this code\`)
583
+ - \`/fullstack\`, \`/frontend\`, \`/backend\`, \`/devops\`, \`/app\` to switch agents
1290
584
 
1291
585
  ## Routing Rules
1292
586
 
1293
587
  The fullstack agent automatically routes tasks based on file patterns:
1294
588
 
1295
589
  - \`/apps/frontend\` or \`.tsx\` files → frontend
1296
- - \`/apps/app\` or Expo/React Native → app
590
+ - \`/apps/app\` or Expo/React Native → app
1297
591
  - \`/infra\`, \`/k8s\`, FluxCD, Helm → devops
1298
592
  - \`/services\`, Koa routers → backend
1299
593
 
1300
- ## Configuration
1301
-
1302
- All agents are configured in \`opencode.json\` with:
1303
- - Specialized prompts and temperature settings
1304
- - Appropriate tool permissions
1305
- - Model optimizations for their specific tasks
1306
-
1307
- ## Environment Setup
1308
-
1309
- Authentication is handled by the Berget plugin. Run \`/connect\` in OpenCode to authenticate.
1310
-
1311
- ## Workflow
594
+ ## Customization
1312
595
 
1313
- All agents follow these principles:
1314
- - Never work directly in main branch
1315
- - Follow branch strategy and commit conventions
1316
- - Create PRs for new functionality
1317
- - Run tests before committing
1318
- - Address reviewer feedback promptly
596
+ Edit the markdown files in \`.opencode/agents/\` to customize agent behavior.
597
+ See https://opencode.ai/docs/agents/ for available options.
1319
598
 
1320
599
  ---
1321
600
 
1322
601
  *Updated by berget code update*
1323
- `
602
+ `;
1324
603
 
1325
- await writeFile(agentsMdPath, agentsMdContent)
1326
- console.log(chalk.green('Updated AGENTS.md documentation'))
604
+ await writeFile(agentsMdPath, agentsMdContent);
605
+ console.log(chalk.green("Created AGENTS.md documentation"));
1327
606
  }
1328
607
 
1329
- console.log(chalk.green('\n✅ Update completed successfully!'))
1330
- console.log(chalk.blue('New features available:'))
1331
- console.log(
1332
- chalk.cyan(' • @quality subagent for testing and PR management')
1333
- )
1334
- console.log(
1335
- chalk.cyan(' • @security subagent for security reviews')
1336
- )
1337
- console.log(chalk.cyan(' • Improved agent prompts and routing'))
1338
- console.log(chalk.cyan(' • GLM-4.7 token optimizations'))
1339
- console.log(chalk.blue('\nTry these new commands:'))
1340
- console.log(chalk.cyan(' @quality run tests and create PR'))
1341
- console.log(chalk.cyan(' @security review this code'))
608
+ console.log(chalk.green("\n✅ Update completed successfully!"));
1342
609
  } catch (error) {
1343
- console.error(chalk.red('Failed to update configuration:'))
1344
- handleError('Update failed', error)
610
+ console.error(chalk.red("Failed to update configuration:"));
611
+ handleError("Update failed", error);
1345
612
 
1346
- // Restore from backup if update failed
1347
613
  try {
1348
- await writeFile(
1349
- configPath,
1350
- JSON.stringify(currentConfig, null, 2)
1351
- )
1352
- console.log(
1353
- chalk.yellow('📁 Restored original configuration from backup')
1354
- )
1355
- } catch (restoreError) {
1356
- console.error(chalk.red('Failed to restore backup:'))
614
+ await writeFile(configPath, JSON.stringify(currentConfig, null, 2));
615
+ console.log(chalk.yellow("📁 Restored original configuration from backup"));
616
+ } catch {
617
+ console.error(chalk.red("Failed to restore backup:"));
1357
618
  }
1358
619
  }
1359
620
  } else {
1360
- console.log(chalk.yellow('Update cancelled.'))
621
+ console.log(chalk.yellow("Update cancelled."));
1361
622
  }
1362
- } catch (error) {
1363
- handleError('Failed to update OpenCode configuration', error)
623
+ } catch {
624
+ console.error(chalk.red("Failed to update OpenCode configuration"));
1364
625
  }
1365
- })
626
+ });
1366
627
  }