berget 2.2.6 → 2.2.8

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 (145) hide show
  1. package/.github/workflows/publish.yml +2 -2
  2. package/.github/workflows/test.yml +10 -4
  3. package/.husky/pre-commit +1 -0
  4. package/.prettierignore +15 -0
  5. package/.prettierrc +7 -3
  6. package/CONTRIBUTING.md +38 -0
  7. package/README.md +2 -148
  8. package/dist/index.js +10 -11
  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 +97 -117
  20. package/dist/src/commands/api-keys.js +75 -90
  21. package/dist/src/commands/auth.js +7 -16
  22. package/dist/src/commands/autocomplete.js +1 -1
  23. package/dist/src/commands/billing.js +6 -17
  24. package/dist/src/commands/chat.js +68 -101
  25. package/dist/src/commands/clusters.js +9 -18
  26. package/dist/src/commands/code/__tests__/auth-sync.test.js +351 -0
  27. package/dist/src/commands/code/__tests__/fake-api-key-service.js +13 -0
  28. package/dist/src/commands/code/__tests__/fake-auth-service.js +47 -0
  29. package/dist/src/commands/code/__tests__/fake-command-runner.js +21 -34
  30. package/dist/src/commands/code/__tests__/fake-file-store.js +20 -33
  31. package/dist/src/commands/code/__tests__/fake-prompter.js +83 -57
  32. package/dist/src/commands/code/__tests__/setup-flow.test.js +359 -92
  33. package/dist/src/commands/code/adapters/clack-prompter.js +15 -22
  34. package/dist/src/commands/code/adapters/fs-file-store.js +26 -40
  35. package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -37
  36. package/dist/src/commands/code/auth-sync.js +270 -0
  37. package/dist/src/commands/code/errors.js +12 -9
  38. package/dist/src/commands/code/ports/auth-services.js +2 -0
  39. package/dist/src/commands/code/setup.js +387 -281
  40. package/dist/src/commands/code.js +205 -332
  41. package/dist/src/commands/index.js +5 -5
  42. package/dist/src/commands/models.js +6 -17
  43. package/dist/src/commands/users.js +5 -16
  44. package/dist/src/constants/command-structure.js +104 -104
  45. package/dist/src/services/api-key-service.js +132 -157
  46. package/dist/src/services/auth-service.js +89 -342
  47. package/dist/src/services/browser-auth.js +268 -0
  48. package/dist/src/services/chat-service.js +371 -401
  49. package/dist/src/services/cluster-service.js +47 -62
  50. package/dist/src/services/collaborator-service.js +10 -25
  51. package/dist/src/services/flux-service.js +14 -29
  52. package/dist/src/services/helm-service.js +10 -25
  53. package/dist/src/services/kubectl-service.js +16 -33
  54. package/dist/src/utils/config-checker.js +3 -3
  55. package/dist/src/utils/config-loader.js +95 -95
  56. package/dist/src/utils/default-api-key.js +124 -134
  57. package/dist/src/utils/env-manager.js +55 -66
  58. package/dist/src/utils/error-handler.js +20 -21
  59. package/dist/src/utils/logger.js +72 -65
  60. package/dist/src/utils/markdown-renderer.js +27 -27
  61. package/dist/src/utils/opencode-validator.js +63 -68
  62. package/dist/src/utils/token-manager.js +74 -45
  63. package/dist/tests/commands/chat.test.js +16 -25
  64. package/dist/tests/commands/code.test.js +95 -104
  65. package/dist/tests/utils/config-loader.test.js +48 -48
  66. package/dist/tests/utils/env-manager.test.js +43 -52
  67. package/dist/tests/utils/opencode-validator.test.js +22 -21
  68. package/dist/vitest.config.js +1 -1
  69. package/eslint.config.mjs +67 -0
  70. package/index.ts +35 -42
  71. package/package.json +30 -2
  72. package/src/agents/app.ts +27 -0
  73. package/src/agents/backend.ts +24 -0
  74. package/src/agents/devops.ts +33 -0
  75. package/src/agents/frontend.ts +24 -0
  76. package/src/agents/fullstack.ts +24 -0
  77. package/src/agents/index.ts +73 -0
  78. package/src/agents/quality.ts +69 -0
  79. package/src/agents/security.ts +26 -0
  80. package/src/agents/types.ts +17 -0
  81. package/src/client.ts +118 -152
  82. package/src/commands/api-keys.ts +241 -333
  83. package/src/commands/auth.ts +22 -27
  84. package/src/commands/autocomplete.ts +9 -9
  85. package/src/commands/billing.ts +20 -24
  86. package/src/commands/chat.ts +248 -338
  87. package/src/commands/clusters.ts +27 -26
  88. package/src/commands/code/__tests__/auth-sync.test.ts +482 -0
  89. package/src/commands/code/__tests__/fake-api-key-service.ts +13 -0
  90. package/src/commands/code/__tests__/fake-auth-service.ts +50 -0
  91. package/src/commands/code/__tests__/fake-command-runner.ts +45 -42
  92. package/src/commands/code/__tests__/fake-file-store.ts +32 -23
  93. package/src/commands/code/__tests__/fake-prompter.ts +116 -77
  94. package/src/commands/code/__tests__/setup-flow.test.ts +624 -268
  95. package/src/commands/code/adapters/clack-prompter.ts +53 -39
  96. package/src/commands/code/adapters/fs-file-store.ts +32 -27
  97. package/src/commands/code/adapters/spawn-command-runner.ts +38 -29
  98. package/src/commands/code/auth-sync.ts +329 -0
  99. package/src/commands/code/errors.ts +18 -18
  100. package/src/commands/code/ports/auth-services.ts +14 -0
  101. package/src/commands/code/ports/command-runner.ts +8 -4
  102. package/src/commands/code/ports/file-store.ts +5 -4
  103. package/src/commands/code/ports/prompter.ts +24 -18
  104. package/src/commands/code/setup.ts +570 -340
  105. package/src/commands/code.ts +338 -539
  106. package/src/commands/index.ts +20 -19
  107. package/src/commands/models.ts +28 -32
  108. package/src/commands/users.ts +15 -21
  109. package/src/constants/command-structure.ts +134 -157
  110. package/src/services/api-key-service.ts +105 -122
  111. package/src/services/auth-service.ts +99 -345
  112. package/src/services/browser-auth.ts +296 -0
  113. package/src/services/chat-service.ts +265 -299
  114. package/src/services/cluster-service.ts +42 -45
  115. package/src/services/collaborator-service.ts +14 -19
  116. package/src/services/flux-service.ts +23 -25
  117. package/src/services/helm-service.ts +19 -21
  118. package/src/services/kubectl-service.ts +17 -19
  119. package/src/types/api.d.ts +1905 -1907
  120. package/src/types/json.d.ts +2 -2
  121. package/src/utils/config-checker.ts +10 -10
  122. package/src/utils/config-loader.ts +162 -178
  123. package/src/utils/default-api-key.ts +114 -125
  124. package/src/utils/env-manager.ts +53 -57
  125. package/src/utils/error-handler.ts +61 -56
  126. package/src/utils/logger.ts +79 -73
  127. package/src/utils/markdown-renderer.ts +31 -31
  128. package/src/utils/opencode-validator.ts +85 -89
  129. package/src/utils/token-manager.ts +108 -87
  130. package/templates/agents/app.md +1 -0
  131. package/templates/agents/backend.md +1 -0
  132. package/templates/agents/devops.md +2 -0
  133. package/templates/agents/frontend.md +1 -0
  134. package/templates/agents/fullstack.md +1 -0
  135. package/templates/agents/quality.md +45 -40
  136. package/templates/agents/security.md +1 -0
  137. package/tests/commands/chat.test.ts +53 -62
  138. package/tests/commands/code.test.ts +265 -310
  139. package/tests/utils/config-loader.test.ts +189 -188
  140. package/tests/utils/env-manager.test.ts +110 -113
  141. package/tests/utils/opencode-validator.test.ts +52 -56
  142. package/tsconfig.json +4 -3
  143. package/vitest.config.ts +3 -3
  144. package/AGENTS.md +0 -374
  145. package/TODO.md +0 -19
@@ -1,288 +1,14 @@
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
-
12
- /**
13
- * Check if current directory has git
14
- */
15
- function hasGit(): boolean {
16
- try {
17
- return fs.existsSync(path.join(process.cwd(), '.git'))
18
- } catch {
19
- return false
20
- }
21
- }
22
-
23
- /**
24
- * Helper function to get user confirmation
25
- */
26
- async function confirm(question: string, autoYes = false): Promise<boolean> {
27
- if (autoYes) {
28
- return true
29
- }
30
-
31
- return new Promise((resolve) => {
32
- const rl = readline.createInterface({
33
- input: process.stdin,
34
- output: process.stdout,
35
- })
36
-
37
- rl.question(question, (answer) => {
38
- rl.close()
39
- resolve(
40
- answer.toLowerCase() === 'y' ||
41
- answer.toLowerCase() === 'yes' ||
42
- answer === ''
43
- )
44
- })
45
- })
46
- }
47
-
48
-
49
- /**
50
- * Helper function to get user input
51
- */
52
- async function getInput(
53
- question: string,
54
- defaultValue: string,
55
- autoYes = false
56
- ): Promise<string> {
57
- if (autoYes) {
58
- return defaultValue
59
- }
60
-
61
- const rl = readline.createInterface({
62
- input: process.stdin,
63
- output: process.stdout,
64
- })
65
-
66
- return new Promise<string>((resolve) => {
67
- rl.question(question, (answer) => {
68
- rl.close()
69
- resolve(answer.trim() || defaultValue)
70
- })
71
- })
72
- }
73
-
74
- /**
75
- * Get project name from current directory or package.json
76
- */
77
- function getProjectName(): string {
78
- try {
79
- const packageJsonPath = path.join(process.cwd(), 'package.json')
80
- if (fs.existsSync(packageJsonPath)) {
81
- const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8')
82
- const packageJson = JSON.parse(packageJsonContent)
83
- return packageJson.name || path.basename(process.cwd())
84
- }
85
- } catch (error) {
86
- // Ignore error and fallback to directory name
87
- }
88
- return path.basename(process.cwd())
89
- }
90
-
91
- /**
92
- * Get the path to the bundled agent templates directory
93
- */
94
- function getAgentTemplatesDir(): string {
95
- return path.resolve(__dirname, '../../templates/agents')
96
- }
97
-
98
- /**
99
- * Parse a markdown agent file with YAML frontmatter into an agent config object
100
- */
101
- function parseAgentMarkdown(content: string): Record<string, any> {
102
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/)
103
- if (!frontmatterMatch) {
104
- throw new Error('Invalid agent markdown: missing frontmatter')
105
- }
106
-
107
- const yamlStr = frontmatterMatch[1]
108
- const promptBody = frontmatterMatch[2].trim()
109
-
110
- const config: Record<string, any> = { prompt: promptBody }
111
-
112
- for (const line of yamlStr.split('\n')) {
113
- const trimmed = line.trim()
114
- if (!trimmed || trimmed.startsWith('#')) continue
115
-
116
- const colonIdx = trimmed.indexOf(':')
117
- if (colonIdx === -1) continue
118
-
119
- const key = trimmed.substring(0, colonIdx).trim()
120
- const value = trimmed.substring(colonIdx + 1).trim()
121
-
122
- if (key === 'permission') continue
123
-
124
- if (value === 'true') {
125
- config[key] = true
126
- } else if (value === 'false') {
127
- config[key] = false
128
- } else if (!isNaN(Number(value)) && value !== '') {
129
- config[key] = Number(value)
130
- } else {
131
- config[key] = value
132
- }
133
- }
134
-
135
- const permission: Record<string, string> = {}
136
- const permMatch = yamlStr.match(/permission:\s*\n((?:\s+\w+:.*\n?)*)/)
137
- if (permMatch) {
138
- for (const permLine of permMatch[1].split('\n')) {
139
- const permTrimmed = permLine.trim()
140
- if (!permTrimmed) continue
141
- const permColonIdx = permTrimmed.indexOf(':')
142
- if (permColonIdx === -1) continue
143
- const permKey = permTrimmed.substring(0, permColonIdx).trim()
144
- const permValue = permTrimmed.substring(permColonIdx + 1).trim()
145
- if (permKey && permValue) {
146
- permission[permKey] = permValue
147
- }
148
- }
149
- }
150
- if (Object.keys(permission).length > 0) {
151
- config.permission = permission
152
- }
153
-
154
- return config
155
- }
156
-
157
- /**
158
- * Load the latest agent configuration from bundled markdown templates
159
- */
160
- async function loadLatestAgentConfig(): Promise<any> {
161
- const templatesDir = getAgentTemplatesDir()
162
- const agents: Record<string, any> = {}
163
-
164
- const files = fs.readdirSync(templatesDir).filter((f) => f.endsWith('.md'))
165
-
166
- for (const file of files) {
167
- const agentName = path.basename(file, '.md')
168
- const filePath = path.join(templatesDir, file)
169
- const content = fs.readFileSync(filePath, 'utf8')
170
-
171
- try {
172
- agents[agentName] = parseAgentMarkdown(content)
173
- } catch (error) {
174
- console.warn(
175
- chalk.yellow(`Warning: Failed to parse agent template ${file}: ${error}`)
176
- )
177
- }
178
- }
179
-
180
- return agents
181
- }
182
-
183
- /**
184
- * Check if opencode is installed
185
- */
186
- function checkOpencodeInstalled(): Promise<boolean> {
187
- return new Promise((resolve) => {
188
- const child = spawn('which', ['opencode'], {
189
- stdio: 'pipe',
190
- })
191
-
192
- child.on('close', (code) => {
193
- resolve(code === 0)
194
- })
195
-
196
- child.on('error', () => {
197
- resolve(false)
198
- })
199
- })
200
- }
201
-
202
- /**
203
- * Install opencode via npm
204
- */
205
- async function installOpencode(): Promise<boolean> {
206
- console.log(chalk.cyan('Installing OpenCode via npm...'))
207
-
208
- try {
209
- await new Promise<void>((resolve, reject) => {
210
- const install = spawn('npm', ['install', '-g', 'opencode-ai@1.3'], {
211
- stdio: 'inherit',
212
- })
213
-
214
- install.on('close', (code) => {
215
- if (code === 0) {
216
- console.log(chalk.green('✓ OpenCode installed successfully!'))
217
- resolve()
218
- } else {
219
- reject(new Error(`Installation failed with code ${code}`))
220
- }
221
- })
222
-
223
- install.on('error', reject)
224
- })
225
-
226
- // Verify installation
227
- const opencodeInstalled = await checkOpencodeInstalled()
228
- if (!opencodeInstalled) {
229
- console.log(
230
- chalk.yellow('Installation completed but opencode command not found.')
231
- )
232
- console.log(
233
- chalk.yellow(
234
- 'You may need to restart your terminal or check your PATH.'
235
- )
236
- )
237
- return false
238
- }
239
-
240
- return true
241
- } catch (error) {
242
- console.error(chalk.red('Failed to install OpenCode:'))
243
- console.error(error instanceof Error ? error.message : String(error))
244
- console.log(chalk.blue('\nAlternative installation methods:'))
245
- console.log(chalk.blue(' curl -fsSL https://opencode.ai/install | sh'))
246
- console.log(chalk.blue(' Or visit: https://opencode.ai/docs'))
247
- return false
248
- }
249
- }
250
-
251
- /**
252
- * Ensure opencode is installed, offering to install if not
253
- */
254
- async function ensureOpencodeInstalled(autoYes = false): Promise<boolean> {
255
- let opencodeInstalled = await checkOpencodeInstalled()
256
- if (!opencodeInstalled) {
257
- if (!autoYes) {
258
- console.log(chalk.red('OpenCode is not installed.'))
259
- console.log(
260
- chalk.blue('OpenCode is required for the AI coding assistant.')
261
- )
262
- }
263
-
264
- if (
265
- await confirm(
266
- 'Would you like to install OpenCode automatically? (Y/n): ',
267
- autoYes
268
- )
269
- ) {
270
- opencodeInstalled = await installOpencode()
271
- } else {
272
- if (!autoYes) {
273
- console.log(chalk.blue('\nInstallation cancelled.'))
274
- console.log(
275
- chalk.blue(
276
- 'To install manually: curl -fsSL https://opencode.ai/install | bash'
277
- )
278
- )
279
- console.log(chalk.blue('Or visit: https://opencode.ai/docs'))
280
- }
281
- }
282
- }
283
-
284
- return opencodeInstalled
285
- }
1
+ import chalk from 'chalk';
2
+ import { Command } from 'commander';
3
+ import { spawn } from 'node:child_process';
4
+ import * as fs from 'node:fs';
5
+ import { readFile, writeFile } from 'node:fs/promises';
6
+ import path from 'node:path';
7
+ import readline from 'node:readline';
8
+
9
+ import { COMMAND_GROUPS, SUBCOMMANDS } from '../constants/command-structure';
10
+ import { handleError } from '../utils/error-handler';
11
+ import { runSetupCommand } from './code/setup';
286
12
 
287
13
  /**
288
14
  * Register code commands
@@ -290,7 +16,7 @@ async function ensureOpencodeInstalled(autoYes = false): Promise<boolean> {
290
16
  export function registerCodeCommands(program: Command): void {
291
17
  const code = program
292
18
  .command(COMMAND_GROUPS.CODE)
293
- .description('AI-powered coding assistant with OpenCode')
19
+ .description('AI-powered coding assistant with OpenCode');
294
20
 
295
21
  if (process.env.BERGET_EXPERIMENTAL) {
296
22
  code
@@ -298,11 +24,11 @@ export function registerCodeCommands(program: Command): void {
298
24
  .description('Interactive setup for Berget AI coding tools')
299
25
  .action(async () => {
300
26
  try {
301
- await runSetupCommand()
27
+ await runSetupCommand();
302
28
  } catch (error) {
303
- handleError('Setup failed', error)
29
+ handleError('Setup failed', error);
304
30
  }
305
- })
31
+ });
306
32
  }
307
33
 
308
34
  code
@@ -310,102 +36,87 @@ export function registerCodeCommands(program: Command): void {
310
36
  .description('Initialize project for AI coding assistant')
311
37
  .option('-n, --name <name>', 'Project name (defaults to directory name)')
312
38
  .option('-f, --force', 'Overwrite existing configuration')
313
- .option(
314
- '-y, --yes',
315
- 'Automatically answer yes to all prompts (for automation)'
316
- )
39
+ .option('-y, --yes', 'Automatically answer yes to all prompts (for automation)')
317
40
  .action(async (options) => {
318
41
  try {
319
- const projectName = options.name || getProjectName()
320
- const configPath = path.join(process.cwd(), 'opencode.json')
42
+ const projectName = options.name || getProjectName();
43
+ const configPath = path.join(process.cwd(), 'opencode.json');
321
44
 
322
45
  // Check if already initialized
323
46
  if (fs.existsSync(configPath) && !options.force) {
324
47
  if (!options.yes) {
325
- console.log(
326
- chalk.yellow('Project already initialized for OpenCode.')
327
- )
328
- console.log(chalk.dim(`Config file: ${configPath}`))
48
+ console.log(chalk.yellow('Project already initialized for OpenCode.'));
49
+ console.log(chalk.dim(`Config file: ${configPath}`));
329
50
  }
330
51
 
331
- if (
332
- await confirm('Do you want to reinitialize? (Y/n): ', options.yes)
333
- ) {
52
+ if (await confirm('Do you want to reinitialize? (Y/n): ', options.yes)) {
334
53
  // Continue with reinitialization
335
54
  } else {
336
- return
55
+ return;
337
56
  }
338
57
  }
339
58
 
340
59
  // Ensure opencode is installed
341
60
  if (!(await ensureOpencodeInstalled(options.yes))) {
342
- return
61
+ return;
343
62
  }
344
63
 
345
- console.log(
346
- chalk.cyan(`Initializing OpenCode for project: ${projectName}`)
347
- )
64
+ console.log(chalk.cyan(`Initializing OpenCode for project: ${projectName}`));
348
65
 
349
66
  const config = {
350
67
  $schema: 'https://opencode.ai/config.json',
351
68
  plugin: ['@bergetai/opencode-auth@1.0.16'],
352
- }
69
+ };
353
70
 
354
- const agentsDir = path.join(process.cwd(), '.opencode', 'agents')
355
- const templatesDir = getAgentTemplatesDir()
71
+ const agentsDir = path.join(process.cwd(), '.opencode', 'agents');
72
+ const templatesDir = getAgentTemplatesDir();
356
73
 
357
74
  if (!options.yes) {
358
- console.log(chalk.blue('\nAbout to create configuration files:'))
359
- console.log(chalk.dim(`Config: ${configPath}`))
360
- console.log(chalk.dim(`Agents: ${agentsDir}/`))
361
- console.log(
362
- chalk.dim('This will configure OpenCode with the Berget auth plugin.')
363
- )
75
+ console.log(chalk.blue('\nAbout to create configuration files:'));
76
+ console.log(chalk.dim(`Config: ${configPath}`));
77
+ console.log(chalk.dim(`Agents: ${agentsDir}/`));
78
+ console.log(chalk.dim('This will configure OpenCode with the Berget auth plugin.'));
364
79
  }
365
80
 
366
- if (
367
- await confirm('\nCreate configuration files? (Y/n): ', options.yes)
368
- ) {
81
+ if (await confirm('\nCreate configuration files? (Y/n): ', options.yes)) {
369
82
  try {
370
- await writeFile(configPath, JSON.stringify(config, null, 2))
371
- console.log(chalk.green('✓ Created opencode.json'))
372
- console.log(chalk.dim(' Plugin: @bergetai/opencode-auth'))
373
-
374
- fs.mkdirSync(agentsDir, { recursive: true })
375
- const templateFiles = fs
376
- .readdirSync(templatesDir)
377
- .filter((f) => f.endsWith('.md'))
83
+ await writeFile(configPath, JSON.stringify(config, null, 2));
84
+ console.log(chalk.green('✓ Created opencode.json'));
85
+ console.log(chalk.dim(' Plugin: @bergetai/opencode-auth'));
86
+
87
+ fs.mkdirSync(agentsDir, { recursive: true });
88
+ const templateFiles = fs.readdirSync(templatesDir).filter((f) => f.endsWith('.md'));
378
89
  for (const file of templateFiles) {
379
- const src = path.join(templatesDir, file)
380
- const dest = path.join(agentsDir, file)
381
- fs.copyFileSync(src, dest)
90
+ const source = path.join(templatesDir, file);
91
+ const destination = path.join(agentsDir, file);
92
+ fs.copyFileSync(source, destination);
382
93
  }
383
94
  console.log(
384
95
  chalk.green(
385
- `✓ Created ${templateFiles.length} agent definitions in .opencode/agents/`
386
- )
387
- )
96
+ `✓ Created ${templateFiles.length} agent definitions in .opencode/agents/`,
97
+ ),
98
+ );
388
99
  } catch (error) {
389
- console.error(chalk.red('Failed to create config files:'))
390
- handleError('Config file creation failed', error)
391
- return
100
+ console.error(chalk.red('Failed to create config files:'));
101
+ handleError('Config file creation failed', error);
102
+ return;
392
103
  }
393
104
  } else {
394
- console.log(chalk.yellow('Configuration file creation cancelled.'))
395
- return
105
+ console.log(chalk.yellow('Configuration file creation cancelled.'));
106
+ return;
396
107
  }
397
108
 
398
- console.log(chalk.green('\n✅ Project initialized successfully!'))
399
- console.log(chalk.blue('\nNext steps:'))
400
- console.log(chalk.cyan(' 1. Run: opencode'))
401
- console.log(chalk.cyan(' 2. Type: /connect'))
402
- console.log(chalk.cyan(' 3. Choose your auth method:'))
403
- console.log(chalk.dim(' • "Login with Berget" — Berget Code team members (SSO)'))
404
- console.log(chalk.dim(' • "Enter API Key" — API key users (console.berget.ai)'))
109
+ console.log(chalk.green('\n✅ Project initialized successfully!'));
110
+ console.log(chalk.blue('\nNext steps:'));
111
+ console.log(chalk.cyan(' 1. Run: opencode'));
112
+ console.log(chalk.cyan(' 2. Type: /connect'));
113
+ console.log(chalk.cyan(' 3. Choose your auth method:'));
114
+ console.log(chalk.dim(' • "Login with Berget" — Berget Code team members (SSO)'));
115
+ console.log(chalk.dim(' • "Enter API Key" — API key users (console.berget.ai)'));
405
116
  } catch (error) {
406
- handleError('Failed to initialize project', error)
117
+ handleError('Failed to initialize project', error);
407
118
  }
408
- })
119
+ });
409
120
 
410
121
  code
411
122
  .command(SUBCOMMANDS.CODE.RUN)
@@ -414,338 +125,287 @@ export function registerCodeCommands(program: Command): void {
414
125
  .option('-m, --model <model>', 'Model to use (overrides config)')
415
126
  .option('-a, --analysis', 'Use fast analysis model for context building')
416
127
  .option('--no-config', 'Run without loading project config')
417
- .option(
418
- '-y, --yes',
419
- 'Automatically answer yes to all prompts (for automation)'
420
- )
128
+ .option('-y, --yes', 'Automatically answer yes to all prompts (for automation)')
421
129
  .action(async (prompt: string, options: any) => {
422
130
  try {
423
- const configPath = path.join(process.cwd(), 'opencode.json')
131
+ const configPath = path.join(process.cwd(), 'opencode.json');
424
132
 
425
133
  // Ensure opencode is installed
426
134
  if (!(await ensureOpencodeInstalled(options.yes))) {
427
- return
135
+ return;
428
136
  }
429
137
 
430
- let config: any = null
138
+ let config: any = null;
431
139
  if (!options.noConfig && fs.existsSync(configPath)) {
432
140
  try {
433
- const configContent = await readFile(configPath, 'utf8')
434
- config = JSON.parse(configContent)
435
- console.log(
436
- chalk.dim(`Loaded config for project: ${config.projectName}`)
437
- )
141
+ const configContent = await readFile(configPath, 'utf8');
142
+ config = JSON.parse(configContent);
143
+ console.log(chalk.dim(`Loaded config for project: ${config.projectName}`));
438
144
  console.log(
439
- chalk.dim(
440
- `Models: Analysis=${config.analysisModel}, Build=${config.buildModel}`
441
- )
442
- )
443
- } catch (error) {
444
- console.log(chalk.yellow('Warning: Failed to load opencode.json'))
145
+ chalk.dim(`Models: Analysis=${config.analysisModel}, Build=${config.buildModel}`),
146
+ );
147
+ } catch {
148
+ console.log(chalk.yellow('Warning: Failed to load opencode.json'));
445
149
  }
446
150
  }
447
151
 
448
152
  if (!config) {
449
- console.log(chalk.yellow('No project configuration found.'))
153
+ console.log(chalk.yellow('No project configuration found.'));
450
154
  console.log(
451
155
  chalk.blue(
452
- `Run ${chalk.bold(
453
- `berget ${COMMAND_GROUPS.CODE} ${SUBCOMMANDS.CODE.INIT}`
454
- )} first.`
455
- )
456
- )
457
- return
156
+ `Run ${chalk.bold(`berget ${COMMAND_GROUPS.CODE} ${SUBCOMMANDS.CODE.INIT}`)} first.`,
157
+ ),
158
+ );
159
+ return;
458
160
  }
459
161
 
460
162
  // Prepare opencode command
461
- const env = { ...process.env }
462
- const opencodeArgs: string[] = []
163
+ const environment = { ...process.env };
164
+ const opencodeArguments: string[] = [];
463
165
 
464
166
  // Read --stage and --local from root program options
465
167
  // (these flags are registered at program level, not subcommand level)
466
- const isStage = process.argv.includes('--stage')
467
- const isLocal = process.argv.includes('--local')
168
+ const isStage = process.argv.includes('--stage');
169
+ const isLocal = process.argv.includes('--local');
468
170
 
469
171
  if (isStage) {
470
- console.log(chalk.cyan('Using Berget stage environment'))
471
- env.BERGET_API_URL = 'https://api.stage.berget.ai'
472
- env.BERGET_INFERENCE_URL = 'https://api.stage.berget.ai/v1'
172
+ console.log(chalk.cyan('Using Berget stage environment'));
173
+ environment.BERGET_API_URL = 'https://api.stage.berget.ai';
174
+ environment.BERGET_INFERENCE_URL = 'https://api.stage.berget.ai/v1';
473
175
  } else if (isLocal) {
474
- console.log(chalk.cyan('Using local development environment'))
475
- env.BERGET_API_URL = 'http://localhost:3000'
476
- env.BERGET_INFERENCE_URL = 'http://localhost:3000/v1'
176
+ console.log(chalk.cyan('Using local development environment'));
177
+ environment.BERGET_API_URL = 'http://localhost:3000';
178
+ environment.BERGET_INFERENCE_URL = 'http://localhost:3000/v1';
477
179
  }
478
180
 
479
181
  if (prompt) {
480
- opencodeArgs.push('run', prompt)
182
+ opencodeArguments.push('run', prompt);
481
183
  }
482
184
 
483
185
  // Choose model based on analysis flag or override
484
- let selectedModel = options.model || config.buildModel
186
+ let selectedModel = options.model || config.buildModel;
485
187
  if (options.analysis && !options.model) {
486
- selectedModel = config.analysisModel
188
+ selectedModel = config.analysisModel;
487
189
  }
488
190
 
489
191
  if (selectedModel) {
490
- opencodeArgs.push('--model', selectedModel)
192
+ opencodeArguments.push('--model', selectedModel);
491
193
  }
492
194
 
493
- console.log(chalk.cyan('Starting OpenCode...'))
195
+ console.log(chalk.cyan('Starting OpenCode...'));
494
196
 
495
197
  // Spawn opencode process
496
- const opencode = spawn('opencode', opencodeArgs, {
198
+ const opencode = spawn('opencode', opencodeArguments, {
199
+ env: environment,
497
200
  stdio: 'inherit',
498
- env: env,
499
- })
201
+ });
500
202
 
501
203
  opencode.on('close', (code) => {
502
204
  if (code !== 0) {
503
- console.log(chalk.red(`OpenCode exited with code ${code}`))
205
+ console.log(chalk.red(`OpenCode exited with code ${code}`));
504
206
  }
505
- })
207
+ });
506
208
 
507
209
  opencode.on('error', (error) => {
508
- console.error(chalk.red('Failed to start OpenCode:'))
509
- console.error(error.message)
510
- })
210
+ console.error(chalk.red('Failed to start OpenCode:'));
211
+ console.error(error.message);
212
+ });
511
213
  } catch (error) {
512
- handleError('Failed to run OpenCode', error)
214
+ handleError('Failed to run OpenCode', error);
513
215
  }
514
- })
216
+ });
515
217
 
516
218
  code
517
219
  .command(SUBCOMMANDS.CODE.SERVE)
518
220
  .description('Start OpenCode web server')
519
221
  .option('-p, --port <port>', 'Port to run the server on (default: 3000)')
520
- .option(
521
- '-h, --host <host>',
522
- 'Host to bind the server to (default: localhost)'
523
- )
524
- .option(
525
- '-y, --yes',
526
- 'Automatically answer yes to all prompts (for automation)'
527
- )
222
+ .option('-h, --host <host>', 'Host to bind the server to (default: localhost)')
223
+ .option('-y, --yes', 'Automatically answer yes to all prompts (for automation)')
528
224
  .action(async (options) => {
529
225
  try {
530
226
  // Ensure opencode is installed
531
227
  if (!(await ensureOpencodeInstalled(options.yes))) {
532
- return
228
+ return;
533
229
  }
534
230
 
535
- console.log(chalk.cyan('🚀 Starting OpenCode web server...'))
231
+ console.log(chalk.cyan('🚀 Starting OpenCode web server...'));
536
232
 
537
233
  // Prepare opencode serve command
538
- const serveArgs: string[] = ['serve']
234
+ const serveArguments: string[] = ['serve'];
539
235
 
540
236
  if (options.port) {
541
- serveArgs.push('--port', options.port)
237
+ serveArguments.push('--port', options.port);
542
238
  }
543
239
 
544
240
  if (options.host) {
545
- serveArgs.push('--host', options.host)
241
+ serveArguments.push('--host', options.host);
546
242
  }
547
243
 
548
244
  // Spawn opencode serve process
549
- const opencode = spawn('opencode', serveArgs, {
245
+ const opencode = spawn('opencode', serveArguments, {
550
246
  stdio: 'inherit',
551
- })
247
+ });
552
248
 
553
249
  opencode.on('close', (code) => {
554
250
  if (code !== 0) {
555
- console.log(chalk.red(`OpenCode server exited with code ${code}`))
251
+ console.log(chalk.red(`OpenCode server exited with code ${code}`));
556
252
  }
557
- })
253
+ });
558
254
 
559
255
  opencode.on('error', (error) => {
560
- console.error(chalk.red('Failed to start OpenCode server:'))
561
- console.error(error.message)
562
- })
256
+ console.error(chalk.red('Failed to start OpenCode server:'));
257
+ console.error(error.message);
258
+ });
563
259
  } catch (error) {
564
- handleError('Failed to start OpenCode server', error)
260
+ handleError('Failed to start OpenCode server', error);
565
261
  }
566
- })
262
+ });
567
263
 
568
264
  code
569
265
  .command(SUBCOMMANDS.CODE.UPDATE)
570
266
  .description('Update OpenCode and agents to latest versions')
571
267
  .option('-f, --force', 'Force update even if already latest')
572
- .option(
573
- '-y, --yes',
574
- 'Automatically answer yes to all prompts (for automation)'
575
- )
268
+ .option('-y, --yes', 'Automatically answer yes to all prompts (for automation)')
576
269
  .action(async (options) => {
577
270
  try {
578
- console.log(chalk.cyan('🔄 Updating OpenCode configuration...'))
271
+ console.log(chalk.cyan('🔄 Updating OpenCode configuration...'));
579
272
 
580
273
  // Ensure opencode is installed first
581
274
  if (!(await ensureOpencodeInstalled(options.yes))) {
582
- return
275
+ return;
583
276
  }
584
277
 
585
- const configPath = path.join(process.cwd(), 'opencode.json')
278
+ const configPath = path.join(process.cwd(), 'opencode.json');
586
279
 
587
280
  // Check if project is initialized
588
281
  if (!fs.existsSync(configPath)) {
589
- console.log(chalk.red('❌ No OpenCode configuration found.'))
282
+ console.log(chalk.red('❌ No OpenCode configuration found.'));
590
283
  console.log(
591
284
  chalk.blue(
592
- `Run ${chalk.bold(
593
- `berget ${COMMAND_GROUPS.CODE} ${SUBCOMMANDS.CODE.INIT}`
594
- )} first.`
595
- )
596
- )
597
- return
285
+ `Run ${chalk.bold(`berget ${COMMAND_GROUPS.CODE} ${SUBCOMMANDS.CODE.INIT}`)} first.`,
286
+ ),
287
+ );
288
+ return;
598
289
  }
599
290
 
600
291
  // Read current configuration
601
- let currentConfig: any
292
+ let currentConfig: any;
602
293
  try {
603
- const configContent = await readFile(configPath, 'utf8')
604
- currentConfig = JSON.parse(configContent)
294
+ const configContent = await readFile(configPath, 'utf8');
295
+ currentConfig = JSON.parse(configContent);
605
296
  } catch (error) {
606
- console.error(chalk.red('Failed to read current opencode.json:'))
607
- handleError('Config read failed', error)
608
- return
297
+ console.error(chalk.red('Failed to read current opencode.json:'));
298
+ handleError('Config read failed', error);
299
+ return;
609
300
  }
610
301
 
611
- console.log(chalk.blue('📋 Current configuration:'))
302
+ console.log(chalk.blue('📋 Current configuration:'));
612
303
  if (currentConfig.model) {
613
- console.log(chalk.dim(` Model: ${currentConfig.model}`))
304
+ console.log(chalk.dim(` Model: ${currentConfig.model}`));
614
305
  }
615
306
 
616
- const agentsDir = path.join(process.cwd(), '.opencode', 'agents')
617
- const templatesDir = getAgentTemplatesDir()
618
- const templateFiles = fs
619
- .readdirSync(templatesDir)
620
- .filter((f) => f.endsWith('.md'))
621
-
622
- const latestConfig = {
623
- $schema: 'https://opencode.ai/config.json',
624
- plugin: ['@bergetai/opencode-auth@1.0.16'],
625
- }
307
+ const agentsDir = path.join(process.cwd(), '.opencode', 'agents');
308
+ const templatesDir = getAgentTemplatesDir();
309
+ const templateFiles = fs.readdirSync(templatesDir).filter((f) => f.endsWith('.md'));
626
310
 
627
311
  // Check if agent definitions need updating
628
- let agentsNeedUpdate = false
629
- const existingAgentFiles = fs.existsSync(agentsDir)
630
- ? fs.readdirSync(agentsDir).filter((f) => f.endsWith('.md'))
631
- : []
312
+ let agentsNeedUpdate = false;
632
313
 
633
314
  for (const file of templateFiles) {
634
- const src = path.join(templatesDir, file)
635
- const dest = path.join(agentsDir, file)
636
- if (!fs.existsSync(dest)) {
637
- agentsNeedUpdate = true
638
- break
315
+ const source = path.join(templatesDir, file);
316
+ const destination = path.join(agentsDir, file);
317
+ if (!fs.existsSync(destination)) {
318
+ agentsNeedUpdate = true;
319
+ break;
639
320
  }
640
- const srcContent = fs.readFileSync(src, 'utf8')
641
- const destContent = fs.readFileSync(dest, 'utf8')
642
- if (srcContent !== destContent) {
643
- agentsNeedUpdate = true
644
- break
321
+ const sourceContent = fs.readFileSync(source, 'utf8');
322
+ const destinationContent = fs.readFileSync(destination, 'utf8');
323
+ if (sourceContent !== destinationContent) {
324
+ agentsNeedUpdate = true;
325
+ break;
645
326
  }
646
327
  }
647
328
 
648
329
  // Check if opencode.json still has inline agent config (needs migration)
649
- const needsMigration = !!currentConfig.agent
330
+ const needsMigration = !!currentConfig.agent;
650
331
 
651
332
  if (!agentsNeedUpdate && !needsMigration && !options.force) {
652
- console.log(chalk.green('✅ Already using the latest configuration!'))
653
- return
333
+ console.log(chalk.green('✅ Already using the latest configuration!'));
334
+ return;
654
335
  }
655
336
 
656
337
  if (agentsNeedUpdate || needsMigration) {
657
- console.log(chalk.blue('\n🔄 Updates available:'))
338
+ console.log(chalk.blue('\n🔄 Updates available:'));
658
339
 
659
340
  if (needsMigration) {
660
- console.log(
661
- chalk.cyan(' • Migrate agents from opencode.json to .opencode/agents/')
662
- )
341
+ console.log(chalk.cyan(' • Migrate agents from opencode.json to .opencode/agents/'));
663
342
  }
664
343
 
665
344
  if (agentsNeedUpdate) {
666
- console.log(chalk.cyan(' • Latest agent prompts and improvements'))
345
+ console.log(chalk.cyan(' • Latest agent prompts and improvements'));
667
346
  }
668
347
  }
669
348
 
670
349
  if (options.force) {
671
- console.log(chalk.yellow('🔧 Force update requested'))
350
+ console.log(chalk.yellow('🔧 Force update requested'));
672
351
  }
673
352
 
674
353
  if (!options.yes) {
675
354
  console.log(
676
- chalk.blue(
677
- '\nThis will update your agent definitions and OpenCode configuration.'
678
- )
679
- )
355
+ chalk.blue('\nThis will update your agent definitions and OpenCode configuration.'),
356
+ );
680
357
 
681
- const hasGitRepo = hasGit()
682
- if (!hasGitRepo) {
683
- console.log(
684
- chalk.yellow(
685
- '⚠️ No .git repository detected - backup will be created'
686
- )
687
- )
358
+ const hasGitRepo = hasGit();
359
+ if (hasGitRepo) {
360
+ console.log(chalk.green('✓ Git repository detected - changes are tracked'));
688
361
  } else {
689
- console.log(
690
- chalk.green('✓ Git repository detected - changes are tracked')
691
- )
362
+ console.log(chalk.yellow('⚠️ No .git repository detected - backup will be created'));
692
363
  }
693
364
  }
694
365
 
695
- if (
696
- await confirm('\nProceed with update? (Y/n): ', options.yes)
697
- ) {
366
+ if (await confirm('\nProceed with update? (Y/n): ', options.yes)) {
698
367
  try {
699
- let backupPath: string | null = null
368
+ let backupPath: null | string = null;
700
369
 
701
370
  if (!hasGit()) {
702
- backupPath = `${configPath}.backup.${Date.now()}`
703
- await writeFile(
704
- backupPath,
705
- JSON.stringify(currentConfig, null, 2)
706
- )
371
+ backupPath = `${configPath}.backup.${Date.now()}`;
372
+ await writeFile(backupPath, JSON.stringify(currentConfig, null, 2));
707
373
  console.log(
708
- chalk.green(
709
- `✓ Backed up current config to ${path.basename(backupPath)}`
710
- )
711
- )
374
+ chalk.green(`✓ Backed up current config to ${path.basename(backupPath)}`),
375
+ );
712
376
  }
713
377
 
714
378
  // Remove inline agent section from opencode.json if present
715
379
  if (currentConfig.agent) {
716
- delete currentConfig.agent
717
- await writeFile(configPath, JSON.stringify(currentConfig, null, 2))
718
- console.log(
719
- chalk.green('✓ Removed inline agent config from opencode.json')
720
- )
380
+ delete currentConfig.agent;
381
+ await writeFile(configPath, JSON.stringify(currentConfig, null, 2));
382
+ console.log(chalk.green('✓ Removed inline agent config from opencode.json'));
721
383
  }
722
384
 
723
385
  // Sync agent markdown files from templates
724
- fs.mkdirSync(agentsDir, { recursive: true })
725
- let updatedCount = 0
386
+ fs.mkdirSync(agentsDir, { recursive: true });
387
+ let updatedCount = 0;
726
388
  for (const file of templateFiles) {
727
- const src = path.join(templatesDir, file)
728
- const dest = path.join(agentsDir, file)
729
- const agentName = path.basename(file, '.md')
389
+ const source = path.join(templatesDir, file);
390
+ const destination = path.join(agentsDir, file);
391
+ const agentName = path.basename(file, '.md');
730
392
 
731
393
  if (
732
- !fs.existsSync(dest) ||
733
- fs.readFileSync(src, 'utf8') !== fs.readFileSync(dest, 'utf8')
394
+ !fs.existsSync(destination) ||
395
+ fs.readFileSync(source, 'utf8') !== fs.readFileSync(destination, 'utf8')
734
396
  ) {
735
- fs.copyFileSync(src, dest)
736
- updatedCount++
737
- console.log(chalk.cyan(` • Updated agent: ${agentName}`))
397
+ fs.copyFileSync(source, destination);
398
+ updatedCount++;
399
+ console.log(chalk.cyan(` • Updated agent: ${agentName}`));
738
400
  }
739
401
  }
740
402
 
741
403
  if (updatedCount > 0) {
742
- console.log(
743
- chalk.green(`✓ Updated ${updatedCount} agent definition(s)`)
744
- )
404
+ console.log(chalk.green(`✓ Updated ${updatedCount} agent definition(s)`));
745
405
  }
746
406
 
747
407
  // Update AGENTS.md if it doesn't exist
748
- const agentsMdPath = path.join(process.cwd(), 'AGENTS.md')
408
+ const agentsMdPath = path.join(process.cwd(), 'AGENTS.md');
749
409
  if (!fs.existsSync(agentsMdPath)) {
750
410
  const agentsMdContent = `# Berget Code Agents
751
411
 
@@ -795,35 +455,174 @@ See https://opencode.ai/docs/agents/ for available options.
795
455
  ---
796
456
 
797
457
  *Updated by berget code update*
798
- `
458
+ `;
799
459
 
800
- await writeFile(agentsMdPath, agentsMdContent)
801
- console.log(chalk.green('✓ Created AGENTS.md documentation'))
460
+ await writeFile(agentsMdPath, agentsMdContent);
461
+ console.log(chalk.green('✓ Created AGENTS.md documentation'));
802
462
  }
803
463
 
804
- console.log(chalk.green('\n✅ Update completed successfully!'))
464
+ console.log(chalk.green('\n✅ Update completed successfully!'));
805
465
  } catch (error) {
806
- console.error(chalk.red('Failed to update configuration:'))
807
- handleError('Update failed', error)
466
+ console.error(chalk.red('Failed to update configuration:'));
467
+ handleError('Update failed', error);
808
468
 
809
469
  try {
810
- await writeFile(
811
- configPath,
812
- JSON.stringify(currentConfig, null, 2)
813
- )
814
- console.log(
815
- chalk.yellow('📁 Restored original configuration from backup')
816
- )
817
- } catch (restoreError) {
818
- console.error(chalk.red('Failed to restore backup:'))
470
+ await writeFile(configPath, JSON.stringify(currentConfig, null, 2));
471
+ console.log(chalk.yellow('📁 Restored original configuration from backup'));
472
+ } catch {
473
+ console.error(chalk.red('Failed to restore backup:'));
819
474
  }
820
475
  }
821
476
  } else {
822
- console.log(chalk.yellow('Update cancelled.'))
477
+ console.log(chalk.yellow('Update cancelled.'));
823
478
  }
824
- } catch (error) {
825
- handleError('Failed to update OpenCode configuration', error)
479
+ } catch {
480
+ console.error(chalk.red('Failed to update OpenCode configuration'));
481
+ }
482
+ });
483
+ }
484
+
485
+ /**
486
+ * Check if opencode is installed
487
+ */
488
+ function checkOpencodeInstalled(): Promise<boolean> {
489
+ return new Promise((resolve) => {
490
+ const child = spawn('which', ['opencode'], {
491
+ stdio: 'pipe',
492
+ });
493
+
494
+ child.on('close', (code) => {
495
+ resolve(code === 0);
496
+ });
497
+
498
+ child.on('error', () => {
499
+ resolve(false);
500
+ });
501
+ });
502
+ }
503
+
504
+ /**
505
+ * Helper function to get user confirmation
506
+ */
507
+ async function confirm(question: string, autoYes = false): Promise<boolean> {
508
+ if (autoYes) {
509
+ return true;
510
+ }
511
+
512
+ return new Promise((resolve) => {
513
+ const rl = readline.createInterface({
514
+ input: process.stdin,
515
+ output: process.stdout,
516
+ });
517
+
518
+ rl.question(question, (answer) => {
519
+ rl.close();
520
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes' || answer === '');
521
+ });
522
+ });
523
+ }
524
+
525
+ /**
526
+ * Ensure opencode is installed, offering to install if not
527
+ */
528
+ async function ensureOpencodeInstalled(autoYes = false): Promise<boolean> {
529
+ let opencodeInstalled = await checkOpencodeInstalled();
530
+ if (!opencodeInstalled) {
531
+ if (!autoYes) {
532
+ console.log(chalk.red('OpenCode is not installed.'));
533
+ console.log(chalk.blue('OpenCode is required for the AI coding assistant.'));
534
+ }
535
+
536
+ if (await confirm('Would you like to install OpenCode automatically? (Y/n): ', autoYes)) {
537
+ opencodeInstalled = await installOpencode();
538
+ } else {
539
+ if (!autoYes) {
540
+ console.log(chalk.blue('\nInstallation cancelled.'));
541
+ console.log(
542
+ chalk.blue('To install manually: curl -fsSL https://opencode.ai/install | bash'),
543
+ );
544
+ console.log(chalk.blue('Or visit: https://opencode.ai/docs'));
826
545
  }
827
- })
546
+ }
547
+ }
548
+
549
+ return opencodeInstalled;
550
+ }
551
+
552
+ /**
553
+ * Get the path to the bundled agent templates directory
554
+ */
555
+ function getAgentTemplatesDir(): string {
556
+ return path.resolve(__dirname, '../../templates/agents');
557
+ }
558
+
559
+ /**
560
+ * Get project name from current directory or package.json
561
+ */
562
+ function getProjectName(): string {
563
+ try {
564
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
565
+ if (fs.existsSync(packageJsonPath)) {
566
+ const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8');
567
+ const packageJson = JSON.parse(packageJsonContent);
568
+ return packageJson.name || path.basename(process.cwd());
569
+ }
570
+ } catch {
571
+ // Ignore error and fallback to directory name
572
+ }
573
+ return path.basename(process.cwd());
574
+ }
575
+
576
+ /**
577
+ * Check if current directory has git
578
+ */
579
+ function hasGit(): boolean {
580
+ try {
581
+ return fs.existsSync(path.join(process.cwd(), '.git'));
582
+ } catch {
583
+ return false;
584
+ }
828
585
  }
829
586
 
587
+ /**
588
+ * Install opencode via npm
589
+ */
590
+ async function installOpencode(): Promise<boolean> {
591
+ console.log(chalk.cyan('Installing OpenCode via npm...'));
592
+
593
+ try {
594
+ await new Promise<void>((resolve, reject) => {
595
+ const install = spawn('npm', ['install', '-g', 'opencode-ai@1.3'], {
596
+ stdio: 'inherit',
597
+ });
598
+
599
+ install.on('close', (code) => {
600
+ if (code === 0) {
601
+ console.log(chalk.green('✓ OpenCode installed successfully!'));
602
+ resolve();
603
+ } else {
604
+ reject(new Error(`Installation failed with code ${code}`));
605
+ }
606
+ });
607
+
608
+ install.on('error', reject);
609
+ });
610
+
611
+ // Verify installation
612
+ const opencodeInstalled = await checkOpencodeInstalled();
613
+ if (!opencodeInstalled) {
614
+ console.log(chalk.yellow('Installation completed but opencode command not found.'));
615
+ console.log(chalk.yellow('You may need to restart your terminal or check your PATH.'));
616
+ return false;
617
+ }
618
+
619
+ return true;
620
+ } catch (error) {
621
+ console.error(chalk.red('Failed to install OpenCode:'));
622
+ console.error(error instanceof Error ? error.message : String(error));
623
+ console.log(chalk.blue('\nAlternative installation methods:'));
624
+ console.log(chalk.blue(' curl -fsSL https://opencode.ai/install | sh'));
625
+ console.log(chalk.blue(' Or visit: https://opencode.ai/docs'));
626
+ return false;
627
+ }
628
+ }