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.
- package/.github/workflows/publish.yml +2 -2
- package/.github/workflows/test.yml +10 -4
- package/.husky/pre-commit +1 -0
- package/.prettierignore +15 -0
- package/.prettierrc +7 -3
- package/CONTRIBUTING.md +38 -0
- package/README.md +2 -148
- package/dist/index.js +10 -11
- package/dist/package.json +30 -2
- package/dist/src/agents/app.js +28 -0
- package/dist/src/agents/backend.js +25 -0
- package/dist/src/agents/devops.js +34 -0
- package/dist/src/agents/frontend.js +25 -0
- package/dist/src/agents/fullstack.js +25 -0
- package/dist/src/agents/index.js +61 -0
- package/dist/src/agents/quality.js +70 -0
- package/dist/src/agents/security.js +26 -0
- package/dist/src/agents/types.js +2 -0
- package/dist/src/client.js +97 -117
- package/dist/src/commands/api-keys.js +75 -90
- package/dist/src/commands/auth.js +7 -16
- package/dist/src/commands/autocomplete.js +1 -1
- package/dist/src/commands/billing.js +6 -17
- package/dist/src/commands/chat.js +68 -101
- package/dist/src/commands/clusters.js +9 -18
- package/dist/src/commands/code/__tests__/auth-sync.test.js +351 -0
- package/dist/src/commands/code/__tests__/fake-api-key-service.js +13 -0
- package/dist/src/commands/code/__tests__/fake-auth-service.js +47 -0
- package/dist/src/commands/code/__tests__/fake-command-runner.js +21 -34
- package/dist/src/commands/code/__tests__/fake-file-store.js +20 -33
- package/dist/src/commands/code/__tests__/fake-prompter.js +83 -57
- package/dist/src/commands/code/__tests__/setup-flow.test.js +359 -92
- package/dist/src/commands/code/adapters/clack-prompter.js +15 -22
- package/dist/src/commands/code/adapters/fs-file-store.js +26 -40
- package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -37
- package/dist/src/commands/code/auth-sync.js +270 -0
- package/dist/src/commands/code/errors.js +12 -9
- package/dist/src/commands/code/ports/auth-services.js +2 -0
- package/dist/src/commands/code/setup.js +387 -281
- package/dist/src/commands/code.js +205 -332
- package/dist/src/commands/index.js +5 -5
- package/dist/src/commands/models.js +6 -17
- package/dist/src/commands/users.js +5 -16
- package/dist/src/constants/command-structure.js +104 -104
- package/dist/src/services/api-key-service.js +132 -157
- package/dist/src/services/auth-service.js +89 -342
- package/dist/src/services/browser-auth.js +268 -0
- package/dist/src/services/chat-service.js +371 -401
- package/dist/src/services/cluster-service.js +47 -62
- package/dist/src/services/collaborator-service.js +10 -25
- package/dist/src/services/flux-service.js +14 -29
- package/dist/src/services/helm-service.js +10 -25
- package/dist/src/services/kubectl-service.js +16 -33
- package/dist/src/utils/config-checker.js +3 -3
- package/dist/src/utils/config-loader.js +95 -95
- package/dist/src/utils/default-api-key.js +124 -134
- package/dist/src/utils/env-manager.js +55 -66
- package/dist/src/utils/error-handler.js +20 -21
- package/dist/src/utils/logger.js +72 -65
- package/dist/src/utils/markdown-renderer.js +27 -27
- package/dist/src/utils/opencode-validator.js +63 -68
- package/dist/src/utils/token-manager.js +74 -45
- package/dist/tests/commands/chat.test.js +16 -25
- package/dist/tests/commands/code.test.js +95 -104
- package/dist/tests/utils/config-loader.test.js +48 -48
- package/dist/tests/utils/env-manager.test.js +43 -52
- package/dist/tests/utils/opencode-validator.test.js +22 -21
- package/dist/vitest.config.js +1 -1
- package/eslint.config.mjs +67 -0
- package/index.ts +35 -42
- package/package.json +30 -2
- package/src/agents/app.ts +27 -0
- package/src/agents/backend.ts +24 -0
- package/src/agents/devops.ts +33 -0
- package/src/agents/frontend.ts +24 -0
- package/src/agents/fullstack.ts +24 -0
- package/src/agents/index.ts +73 -0
- package/src/agents/quality.ts +69 -0
- package/src/agents/security.ts +26 -0
- package/src/agents/types.ts +17 -0
- package/src/client.ts +118 -152
- package/src/commands/api-keys.ts +241 -333
- package/src/commands/auth.ts +22 -27
- package/src/commands/autocomplete.ts +9 -9
- package/src/commands/billing.ts +20 -24
- package/src/commands/chat.ts +248 -338
- package/src/commands/clusters.ts +27 -26
- package/src/commands/code/__tests__/auth-sync.test.ts +482 -0
- package/src/commands/code/__tests__/fake-api-key-service.ts +13 -0
- package/src/commands/code/__tests__/fake-auth-service.ts +50 -0
- package/src/commands/code/__tests__/fake-command-runner.ts +45 -42
- package/src/commands/code/__tests__/fake-file-store.ts +32 -23
- package/src/commands/code/__tests__/fake-prompter.ts +116 -77
- package/src/commands/code/__tests__/setup-flow.test.ts +624 -268
- package/src/commands/code/adapters/clack-prompter.ts +53 -39
- package/src/commands/code/adapters/fs-file-store.ts +32 -27
- package/src/commands/code/adapters/spawn-command-runner.ts +38 -29
- package/src/commands/code/auth-sync.ts +329 -0
- package/src/commands/code/errors.ts +18 -18
- package/src/commands/code/ports/auth-services.ts +14 -0
- package/src/commands/code/ports/command-runner.ts +8 -4
- package/src/commands/code/ports/file-store.ts +5 -4
- package/src/commands/code/ports/prompter.ts +24 -18
- package/src/commands/code/setup.ts +570 -340
- package/src/commands/code.ts +338 -539
- package/src/commands/index.ts +20 -19
- package/src/commands/models.ts +28 -32
- package/src/commands/users.ts +15 -21
- package/src/constants/command-structure.ts +134 -157
- package/src/services/api-key-service.ts +105 -122
- package/src/services/auth-service.ts +99 -345
- package/src/services/browser-auth.ts +296 -0
- package/src/services/chat-service.ts +265 -299
- package/src/services/cluster-service.ts +42 -45
- package/src/services/collaborator-service.ts +14 -19
- package/src/services/flux-service.ts +23 -25
- package/src/services/helm-service.ts +19 -21
- package/src/services/kubectl-service.ts +17 -19
- package/src/types/api.d.ts +1905 -1907
- package/src/types/json.d.ts +2 -2
- package/src/utils/config-checker.ts +10 -10
- package/src/utils/config-loader.ts +162 -178
- package/src/utils/default-api-key.ts +114 -125
- package/src/utils/env-manager.ts +53 -57
- package/src/utils/error-handler.ts +61 -56
- package/src/utils/logger.ts +79 -73
- package/src/utils/markdown-renderer.ts +31 -31
- package/src/utils/opencode-validator.ts +85 -89
- package/src/utils/token-manager.ts +108 -87
- package/templates/agents/app.md +1 -0
- package/templates/agents/backend.md +1 -0
- package/templates/agents/devops.md +2 -0
- package/templates/agents/frontend.md +1 -0
- package/templates/agents/fullstack.md +1 -0
- package/templates/agents/quality.md +45 -40
- package/templates/agents/security.md +1 -0
- package/tests/commands/chat.test.ts +53 -62
- package/tests/commands/code.test.ts +265 -310
- package/tests/utils/config-loader.test.ts +189 -188
- package/tests/utils/env-manager.test.ts +110 -113
- package/tests/utils/opencode-validator.test.ts +52 -56
- package/tsconfig.json +4 -3
- package/vitest.config.ts +3 -3
- package/AGENTS.md +0 -374
- package/TODO.md +0 -19
package/src/commands/code.ts
CHANGED
|
@@ -1,288 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
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
|
-
|
|
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
|
|
380
|
-
const
|
|
381
|
-
fs.copyFileSync(
|
|
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
|
-
|
|
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
|
-
|
|
454
|
-
|
|
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
|
|
462
|
-
const
|
|
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
|
-
|
|
472
|
-
|
|
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
|
-
|
|
476
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
198
|
+
const opencode = spawn('opencode', opencodeArguments, {
|
|
199
|
+
env: environment,
|
|
497
200
|
stdio: 'inherit',
|
|
498
|
-
|
|
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
|
-
|
|
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
|
|
234
|
+
const serveArguments: string[] = ['serve'];
|
|
539
235
|
|
|
540
236
|
if (options.port) {
|
|
541
|
-
|
|
237
|
+
serveArguments.push('--port', options.port);
|
|
542
238
|
}
|
|
543
239
|
|
|
544
240
|
if (options.host) {
|
|
545
|
-
|
|
241
|
+
serveArguments.push('--host', options.host);
|
|
546
242
|
}
|
|
547
243
|
|
|
548
244
|
// Spawn opencode serve process
|
|
549
|
-
const opencode = spawn('opencode',
|
|
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
|
-
|
|
594
|
-
|
|
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
|
|
635
|
-
const
|
|
636
|
-
if (!fs.existsSync(
|
|
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
|
|
641
|
-
const
|
|
642
|
-
if (
|
|
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
|
-
|
|
678
|
-
)
|
|
679
|
-
)
|
|
355
|
+
chalk.blue('\nThis will update your agent definitions and OpenCode configuration.'),
|
|
356
|
+
);
|
|
680
357
|
|
|
681
|
-
const hasGitRepo = hasGit()
|
|
682
|
-
if (
|
|
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:
|
|
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
|
-
|
|
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
|
|
728
|
-
const
|
|
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(
|
|
733
|
-
fs.readFileSync(
|
|
394
|
+
!fs.existsSync(destination) ||
|
|
395
|
+
fs.readFileSync(source, 'utf8') !== fs.readFileSync(destination, 'utf8')
|
|
734
396
|
) {
|
|
735
|
-
fs.copyFileSync(
|
|
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
|
-
|
|
812
|
-
|
|
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
|
|
825
|
-
|
|
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
|
+
}
|