berget 2.2.7 → 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 +6 -6
- package/.github/workflows/test.yml +1 -1
- package/.prettierrc +5 -3
- package/dist/index.js +24 -25
- package/dist/package.json +5 -3
- package/dist/src/agents/app.js +8 -8
- package/dist/src/agents/backend.js +3 -3
- package/dist/src/agents/devops.js +8 -8
- package/dist/src/agents/frontend.js +3 -3
- package/dist/src/agents/fullstack.js +3 -3
- package/dist/src/agents/index.js +18 -18
- package/dist/src/agents/quality.js +8 -8
- package/dist/src/agents/security.js +8 -8
- package/dist/src/client.js +115 -127
- package/dist/src/commands/api-keys.js +195 -202
- package/dist/src/commands/auth.js +16 -25
- package/dist/src/commands/autocomplete.js +8 -8
- package/dist/src/commands/billing.js +10 -19
- package/dist/src/commands/chat.js +139 -170
- package/dist/src/commands/clusters.js +21 -30
- package/dist/src/commands/code/__tests__/auth-sync.test.js +189 -186
- package/dist/src/commands/code/__tests__/fake-api-key-service.js +3 -13
- package/dist/src/commands/code/__tests__/fake-auth-service.js +21 -29
- package/dist/src/commands/code/__tests__/fake-command-runner.js +22 -33
- package/dist/src/commands/code/__tests__/fake-file-store.js +19 -41
- package/dist/src/commands/code/__tests__/fake-prompter.js +81 -97
- package/dist/src/commands/code/__tests__/setup-flow.test.js +295 -295
- package/dist/src/commands/code/adapters/clack-prompter.js +15 -32
- package/dist/src/commands/code/adapters/fs-file-store.js +25 -44
- package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -41
- package/dist/src/commands/code/auth-sync.js +215 -228
- package/dist/src/commands/code/errors.js +15 -12
- package/dist/src/commands/code/setup.js +390 -425
- package/dist/src/commands/code.js +279 -294
- package/dist/src/commands/index.js +5 -5
- package/dist/src/commands/models.js +16 -25
- package/dist/src/commands/users.js +9 -18
- package/dist/src/constants/command-structure.js +138 -138
- package/dist/src/services/api-key-service.js +132 -152
- package/dist/src/services/auth-service.js +81 -95
- package/dist/src/services/browser-auth.js +121 -131
- package/dist/src/services/chat-service.js +369 -386
- package/dist/src/services/cluster-service.js +47 -62
- package/dist/src/services/collaborator-service.js +9 -21
- package/dist/src/services/flux-service.js +13 -25
- package/dist/src/services/helm-service.js +9 -21
- package/dist/src/services/kubectl-service.js +15 -29
- package/dist/src/utils/config-checker.js +7 -7
- package/dist/src/utils/config-loader.js +109 -109
- package/dist/src/utils/default-api-key.js +129 -139
- package/dist/src/utils/env-manager.js +55 -66
- package/dist/src/utils/error-handler.js +62 -62
- package/dist/src/utils/logger.js +74 -67
- package/dist/src/utils/markdown-renderer.js +28 -28
- package/dist/src/utils/opencode-validator.js +67 -69
- package/dist/src/utils/token-manager.js +67 -65
- package/dist/tests/commands/chat.test.js +30 -39
- package/dist/tests/commands/code.test.js +186 -195
- package/dist/tests/utils/config-loader.test.js +107 -107
- package/dist/tests/utils/env-manager.test.js +81 -90
- package/dist/tests/utils/opencode-validator.test.js +42 -41
- package/dist/vitest.config.js +1 -1
- package/eslint.config.mjs +50 -30
- package/index.ts +30 -31
- package/package.json +5 -3
- package/src/agents/app.ts +9 -9
- package/src/agents/backend.ts +4 -4
- package/src/agents/devops.ts +9 -9
- package/src/agents/frontend.ts +4 -4
- package/src/agents/fullstack.ts +4 -4
- package/src/agents/index.ts +27 -25
- package/src/agents/quality.ts +9 -9
- package/src/agents/security.ts +9 -9
- package/src/agents/types.ts +10 -10
- package/src/client.ts +85 -77
- package/src/commands/api-keys.ts +190 -185
- package/src/commands/auth.ts +15 -14
- package/src/commands/autocomplete.ts +10 -10
- package/src/commands/billing.ts +13 -12
- package/src/commands/chat.ts +145 -142
- package/src/commands/clusters.ts +20 -19
- package/src/commands/code/__tests__/auth-sync.test.ts +176 -175
- package/src/commands/code/__tests__/fake-api-key-service.ts +2 -2
- package/src/commands/code/__tests__/fake-auth-service.ts +18 -18
- package/src/commands/code/__tests__/fake-command-runner.ts +28 -22
- package/src/commands/code/__tests__/fake-file-store.ts +15 -15
- package/src/commands/code/__tests__/fake-prompter.ts +86 -85
- package/src/commands/code/__tests__/setup-flow.test.ts +253 -251
- package/src/commands/code/adapters/clack-prompter.ts +32 -30
- package/src/commands/code/adapters/fs-file-store.ts +18 -17
- package/src/commands/code/adapters/spawn-command-runner.ts +20 -15
- package/src/commands/code/auth-sync.ts +210 -210
- package/src/commands/code/errors.ts +11 -11
- package/src/commands/code/ports/auth-services.ts +7 -7
- package/src/commands/code/ports/command-runner.ts +2 -2
- package/src/commands/code/ports/file-store.ts +3 -3
- package/src/commands/code/ports/prompter.ts +13 -13
- package/src/commands/code/setup.ts +408 -406
- package/src/commands/code.ts +288 -287
- package/src/commands/index.ts +11 -10
- package/src/commands/models.ts +19 -18
- package/src/commands/users.ts +11 -10
- package/src/constants/command-structure.ts +159 -159
- package/src/services/api-key-service.ts +85 -85
- package/src/services/auth-service.ts +55 -54
- package/src/services/browser-auth.ts +62 -62
- package/src/services/chat-service.ts +169 -170
- package/src/services/cluster-service.ts +28 -28
- package/src/services/collaborator-service.ts +6 -6
- package/src/services/flux-service.ts +17 -17
- package/src/services/helm-service.ts +11 -11
- package/src/services/kubectl-service.ts +12 -12
- package/src/types/api.d.ts +1933 -1933
- package/src/types/json.d.ts +1 -1
- package/src/utils/config-checker.ts +6 -6
- package/src/utils/config-loader.ts +130 -129
- package/src/utils/default-api-key.ts +81 -80
- package/src/utils/env-manager.ts +37 -37
- package/src/utils/error-handler.ts +64 -64
- package/src/utils/logger.ts +72 -66
- package/src/utils/markdown-renderer.ts +28 -28
- package/src/utils/opencode-validator.ts +72 -71
- package/src/utils/token-manager.ts +69 -68
- package/tests/commands/chat.test.ts +32 -31
- package/tests/commands/code.test.ts +182 -181
- package/tests/utils/config-loader.test.ts +111 -110
- package/tests/utils/env-manager.test.ts +83 -79
- package/tests/utils/opencode-validator.test.ts +43 -42
- package/tsconfig.json +2 -1
- package/vitest.config.ts +2 -2
package/src/commands/code.ts
CHANGED
|
@@ -1,158 +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(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes" || answer === "");
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Get project name from current directory or package.json
|
|
46
|
-
*/
|
|
47
|
-
function getProjectName(): string {
|
|
48
|
-
try {
|
|
49
|
-
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
50
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
51
|
-
const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8");
|
|
52
|
-
const packageJson = JSON.parse(packageJsonContent);
|
|
53
|
-
return packageJson.name || path.basename(process.cwd());
|
|
54
|
-
}
|
|
55
|
-
} catch {
|
|
56
|
-
// Ignore error and fallback to directory name
|
|
57
|
-
}
|
|
58
|
-
return path.basename(process.cwd());
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Get the path to the bundled agent templates directory
|
|
63
|
-
*/
|
|
64
|
-
function getAgentTemplatesDir(): string {
|
|
65
|
-
return path.resolve(__dirname, "../../templates/agents");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Check if opencode is installed
|
|
70
|
-
*/
|
|
71
|
-
function checkOpencodeInstalled(): Promise<boolean> {
|
|
72
|
-
return new Promise(resolve => {
|
|
73
|
-
const child = spawn("which", ["opencode"], {
|
|
74
|
-
stdio: "pipe",
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
child.on("close", code => {
|
|
78
|
-
resolve(code === 0);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
child.on("error", () => {
|
|
82
|
-
resolve(false);
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Install opencode via npm
|
|
89
|
-
*/
|
|
90
|
-
async function installOpencode(): Promise<boolean> {
|
|
91
|
-
console.log(chalk.cyan("Installing OpenCode via npm..."));
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
await new Promise<void>((resolve, reject) => {
|
|
95
|
-
const install = spawn("npm", ["install", "-g", "opencode-ai@1.3"], {
|
|
96
|
-
stdio: "inherit",
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
install.on("close", code => {
|
|
100
|
-
if (code === 0) {
|
|
101
|
-
console.log(chalk.green("✓ OpenCode installed successfully!"));
|
|
102
|
-
resolve();
|
|
103
|
-
} else {
|
|
104
|
-
reject(new Error(`Installation failed with code ${code}`));
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
install.on("error", reject);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// Verify installation
|
|
112
|
-
const opencodeInstalled = await checkOpencodeInstalled();
|
|
113
|
-
if (!opencodeInstalled) {
|
|
114
|
-
console.log(chalk.yellow("Installation completed but opencode command not found."));
|
|
115
|
-
console.log(chalk.yellow("You may need to restart your terminal or check your PATH."));
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return true;
|
|
120
|
-
} catch (error) {
|
|
121
|
-
console.error(chalk.red("Failed to install OpenCode:"));
|
|
122
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
123
|
-
console.log(chalk.blue("\nAlternative installation methods:"));
|
|
124
|
-
console.log(chalk.blue(" curl -fsSL https://opencode.ai/install | sh"));
|
|
125
|
-
console.log(chalk.blue(" Or visit: https://opencode.ai/docs"));
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Ensure opencode is installed, offering to install if not
|
|
132
|
-
*/
|
|
133
|
-
async function ensureOpencodeInstalled(autoYes = false): Promise<boolean> {
|
|
134
|
-
let opencodeInstalled = await checkOpencodeInstalled();
|
|
135
|
-
if (!opencodeInstalled) {
|
|
136
|
-
if (!autoYes) {
|
|
137
|
-
console.log(chalk.red("OpenCode is not installed."));
|
|
138
|
-
console.log(chalk.blue("OpenCode is required for the AI coding assistant."));
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (await confirm("Would you like to install OpenCode automatically? (Y/n): ", autoYes)) {
|
|
142
|
-
opencodeInstalled = await installOpencode();
|
|
143
|
-
} else {
|
|
144
|
-
if (!autoYes) {
|
|
145
|
-
console.log(chalk.blue("\nInstallation cancelled."));
|
|
146
|
-
console.log(
|
|
147
|
-
chalk.blue("To install manually: curl -fsSL https://opencode.ai/install | bash")
|
|
148
|
-
);
|
|
149
|
-
console.log(chalk.blue("Or visit: https://opencode.ai/docs"));
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return opencodeInstalled;
|
|
155
|
-
}
|
|
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';
|
|
156
12
|
|
|
157
13
|
/**
|
|
158
14
|
* Register code commands
|
|
@@ -160,40 +16,40 @@ async function ensureOpencodeInstalled(autoYes = false): Promise<boolean> {
|
|
|
160
16
|
export function registerCodeCommands(program: Command): void {
|
|
161
17
|
const code = program
|
|
162
18
|
.command(COMMAND_GROUPS.CODE)
|
|
163
|
-
.description(
|
|
19
|
+
.description('AI-powered coding assistant with OpenCode');
|
|
164
20
|
|
|
165
21
|
if (process.env.BERGET_EXPERIMENTAL) {
|
|
166
22
|
code
|
|
167
|
-
.command(
|
|
168
|
-
.description(
|
|
23
|
+
.command('setup')
|
|
24
|
+
.description('Interactive setup for Berget AI coding tools')
|
|
169
25
|
.action(async () => {
|
|
170
26
|
try {
|
|
171
27
|
await runSetupCommand();
|
|
172
28
|
} catch (error) {
|
|
173
|
-
handleError(
|
|
29
|
+
handleError('Setup failed', error);
|
|
174
30
|
}
|
|
175
31
|
});
|
|
176
32
|
}
|
|
177
33
|
|
|
178
34
|
code
|
|
179
35
|
.command(SUBCOMMANDS.CODE.INIT)
|
|
180
|
-
.description(
|
|
181
|
-
.option(
|
|
182
|
-
.option(
|
|
183
|
-
.option(
|
|
184
|
-
.action(async options => {
|
|
36
|
+
.description('Initialize project for AI coding assistant')
|
|
37
|
+
.option('-n, --name <name>', 'Project name (defaults to directory name)')
|
|
38
|
+
.option('-f, --force', 'Overwrite existing configuration')
|
|
39
|
+
.option('-y, --yes', 'Automatically answer yes to all prompts (for automation)')
|
|
40
|
+
.action(async (options) => {
|
|
185
41
|
try {
|
|
186
42
|
const projectName = options.name || getProjectName();
|
|
187
|
-
const configPath = path.join(process.cwd(),
|
|
43
|
+
const configPath = path.join(process.cwd(), 'opencode.json');
|
|
188
44
|
|
|
189
45
|
// Check if already initialized
|
|
190
46
|
if (fs.existsSync(configPath) && !options.force) {
|
|
191
47
|
if (!options.yes) {
|
|
192
|
-
console.log(chalk.yellow(
|
|
48
|
+
console.log(chalk.yellow('Project already initialized for OpenCode.'));
|
|
193
49
|
console.log(chalk.dim(`Config file: ${configPath}`));
|
|
194
50
|
}
|
|
195
51
|
|
|
196
|
-
if (await confirm(
|
|
52
|
+
if (await confirm('Do you want to reinitialize? (Y/n): ', options.yes)) {
|
|
197
53
|
// Continue with reinitialization
|
|
198
54
|
} else {
|
|
199
55
|
return;
|
|
@@ -208,71 +64,71 @@ export function registerCodeCommands(program: Command): void {
|
|
|
208
64
|
console.log(chalk.cyan(`Initializing OpenCode for project: ${projectName}`));
|
|
209
65
|
|
|
210
66
|
const config = {
|
|
211
|
-
$schema:
|
|
212
|
-
plugin: [
|
|
67
|
+
$schema: 'https://opencode.ai/config.json',
|
|
68
|
+
plugin: ['@bergetai/opencode-auth@1.0.16'],
|
|
213
69
|
};
|
|
214
70
|
|
|
215
|
-
const agentsDir = path.join(process.cwd(),
|
|
71
|
+
const agentsDir = path.join(process.cwd(), '.opencode', 'agents');
|
|
216
72
|
const templatesDir = getAgentTemplatesDir();
|
|
217
73
|
|
|
218
74
|
if (!options.yes) {
|
|
219
|
-
console.log(chalk.blue(
|
|
75
|
+
console.log(chalk.blue('\nAbout to create configuration files:'));
|
|
220
76
|
console.log(chalk.dim(`Config: ${configPath}`));
|
|
221
77
|
console.log(chalk.dim(`Agents: ${agentsDir}/`));
|
|
222
|
-
console.log(chalk.dim(
|
|
78
|
+
console.log(chalk.dim('This will configure OpenCode with the Berget auth plugin.'));
|
|
223
79
|
}
|
|
224
80
|
|
|
225
|
-
if (await confirm(
|
|
81
|
+
if (await confirm('\nCreate configuration files? (Y/n): ', options.yes)) {
|
|
226
82
|
try {
|
|
227
83
|
await writeFile(configPath, JSON.stringify(config, null, 2));
|
|
228
|
-
console.log(chalk.green(
|
|
229
|
-
console.log(chalk.dim(
|
|
84
|
+
console.log(chalk.green('✓ Created opencode.json'));
|
|
85
|
+
console.log(chalk.dim(' Plugin: @bergetai/opencode-auth'));
|
|
230
86
|
|
|
231
87
|
fs.mkdirSync(agentsDir, { recursive: true });
|
|
232
|
-
const templateFiles = fs.readdirSync(templatesDir).filter(f => f.endsWith(
|
|
88
|
+
const templateFiles = fs.readdirSync(templatesDir).filter((f) => f.endsWith('.md'));
|
|
233
89
|
for (const file of templateFiles) {
|
|
234
|
-
const
|
|
235
|
-
const
|
|
236
|
-
fs.copyFileSync(
|
|
90
|
+
const source = path.join(templatesDir, file);
|
|
91
|
+
const destination = path.join(agentsDir, file);
|
|
92
|
+
fs.copyFileSync(source, destination);
|
|
237
93
|
}
|
|
238
94
|
console.log(
|
|
239
95
|
chalk.green(
|
|
240
|
-
`✓ Created ${templateFiles.length} agent definitions in .opencode/agents
|
|
241
|
-
)
|
|
96
|
+
`✓ Created ${templateFiles.length} agent definitions in .opencode/agents/`,
|
|
97
|
+
),
|
|
242
98
|
);
|
|
243
99
|
} catch (error) {
|
|
244
|
-
console.error(chalk.red(
|
|
245
|
-
handleError(
|
|
100
|
+
console.error(chalk.red('Failed to create config files:'));
|
|
101
|
+
handleError('Config file creation failed', error);
|
|
246
102
|
return;
|
|
247
103
|
}
|
|
248
104
|
} else {
|
|
249
|
-
console.log(chalk.yellow(
|
|
105
|
+
console.log(chalk.yellow('Configuration file creation cancelled.'));
|
|
250
106
|
return;
|
|
251
107
|
}
|
|
252
108
|
|
|
253
|
-
console.log(chalk.green(
|
|
254
|
-
console.log(chalk.blue(
|
|
255
|
-
console.log(chalk.cyan(
|
|
256
|
-
console.log(chalk.cyan(
|
|
257
|
-
console.log(chalk.cyan(
|
|
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:'));
|
|
258
114
|
console.log(chalk.dim(' • "Login with Berget" — Berget Code team members (SSO)'));
|
|
259
115
|
console.log(chalk.dim(' • "Enter API Key" — API key users (console.berget.ai)'));
|
|
260
116
|
} catch (error) {
|
|
261
|
-
handleError(
|
|
117
|
+
handleError('Failed to initialize project', error);
|
|
262
118
|
}
|
|
263
119
|
});
|
|
264
120
|
|
|
265
121
|
code
|
|
266
122
|
.command(SUBCOMMANDS.CODE.RUN)
|
|
267
|
-
.description(
|
|
268
|
-
.argument(
|
|
269
|
-
.option(
|
|
270
|
-
.option(
|
|
271
|
-
.option(
|
|
272
|
-
.option(
|
|
123
|
+
.description('Run AI coding assistant')
|
|
124
|
+
.argument('[prompt]', 'Prompt to send directly to OpenCode')
|
|
125
|
+
.option('-m, --model <model>', 'Model to use (overrides config)')
|
|
126
|
+
.option('-a, --analysis', 'Use fast analysis model for context building')
|
|
127
|
+
.option('--no-config', 'Run without loading project config')
|
|
128
|
+
.option('-y, --yes', 'Automatically answer yes to all prompts (for automation)')
|
|
273
129
|
.action(async (prompt: string, options: any) => {
|
|
274
130
|
try {
|
|
275
|
-
const configPath = path.join(process.cwd(),
|
|
131
|
+
const configPath = path.join(process.cwd(), 'opencode.json');
|
|
276
132
|
|
|
277
133
|
// Ensure opencode is installed
|
|
278
134
|
if (!(await ensureOpencodeInstalled(options.yes))) {
|
|
@@ -282,48 +138,48 @@ export function registerCodeCommands(program: Command): void {
|
|
|
282
138
|
let config: any = null;
|
|
283
139
|
if (!options.noConfig && fs.existsSync(configPath)) {
|
|
284
140
|
try {
|
|
285
|
-
const configContent = await readFile(configPath,
|
|
141
|
+
const configContent = await readFile(configPath, 'utf8');
|
|
286
142
|
config = JSON.parse(configContent);
|
|
287
143
|
console.log(chalk.dim(`Loaded config for project: ${config.projectName}`));
|
|
288
144
|
console.log(
|
|
289
|
-
chalk.dim(`Models: Analysis=${config.analysisModel}, Build=${config.buildModel}`)
|
|
145
|
+
chalk.dim(`Models: Analysis=${config.analysisModel}, Build=${config.buildModel}`),
|
|
290
146
|
);
|
|
291
147
|
} catch {
|
|
292
|
-
console.log(chalk.yellow(
|
|
148
|
+
console.log(chalk.yellow('Warning: Failed to load opencode.json'));
|
|
293
149
|
}
|
|
294
150
|
}
|
|
295
151
|
|
|
296
152
|
if (!config) {
|
|
297
|
-
console.log(chalk.yellow(
|
|
153
|
+
console.log(chalk.yellow('No project configuration found.'));
|
|
298
154
|
console.log(
|
|
299
155
|
chalk.blue(
|
|
300
|
-
`Run ${chalk.bold(`berget ${COMMAND_GROUPS.CODE} ${SUBCOMMANDS.CODE.INIT}`)} first
|
|
301
|
-
)
|
|
156
|
+
`Run ${chalk.bold(`berget ${COMMAND_GROUPS.CODE} ${SUBCOMMANDS.CODE.INIT}`)} first.`,
|
|
157
|
+
),
|
|
302
158
|
);
|
|
303
159
|
return;
|
|
304
160
|
}
|
|
305
161
|
|
|
306
162
|
// Prepare opencode command
|
|
307
|
-
const
|
|
308
|
-
const
|
|
163
|
+
const environment = { ...process.env };
|
|
164
|
+
const opencodeArguments: string[] = [];
|
|
309
165
|
|
|
310
166
|
// Read --stage and --local from root program options
|
|
311
167
|
// (these flags are registered at program level, not subcommand level)
|
|
312
|
-
const isStage = process.argv.includes(
|
|
313
|
-
const isLocal = process.argv.includes(
|
|
168
|
+
const isStage = process.argv.includes('--stage');
|
|
169
|
+
const isLocal = process.argv.includes('--local');
|
|
314
170
|
|
|
315
171
|
if (isStage) {
|
|
316
|
-
console.log(chalk.cyan(
|
|
317
|
-
|
|
318
|
-
|
|
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';
|
|
319
175
|
} else if (isLocal) {
|
|
320
|
-
console.log(chalk.cyan(
|
|
321
|
-
|
|
322
|
-
|
|
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';
|
|
323
179
|
}
|
|
324
180
|
|
|
325
181
|
if (prompt) {
|
|
326
|
-
|
|
182
|
+
opencodeArguments.push('run', prompt);
|
|
327
183
|
}
|
|
328
184
|
|
|
329
185
|
// Choose model based on analysis flag or override
|
|
@@ -333,101 +189,101 @@ export function registerCodeCommands(program: Command): void {
|
|
|
333
189
|
}
|
|
334
190
|
|
|
335
191
|
if (selectedModel) {
|
|
336
|
-
|
|
192
|
+
opencodeArguments.push('--model', selectedModel);
|
|
337
193
|
}
|
|
338
194
|
|
|
339
|
-
console.log(chalk.cyan(
|
|
195
|
+
console.log(chalk.cyan('Starting OpenCode...'));
|
|
340
196
|
|
|
341
197
|
// Spawn opencode process
|
|
342
|
-
const opencode = spawn(
|
|
343
|
-
|
|
344
|
-
|
|
198
|
+
const opencode = spawn('opencode', opencodeArguments, {
|
|
199
|
+
env: environment,
|
|
200
|
+
stdio: 'inherit',
|
|
345
201
|
});
|
|
346
202
|
|
|
347
|
-
opencode.on(
|
|
203
|
+
opencode.on('close', (code) => {
|
|
348
204
|
if (code !== 0) {
|
|
349
205
|
console.log(chalk.red(`OpenCode exited with code ${code}`));
|
|
350
206
|
}
|
|
351
207
|
});
|
|
352
208
|
|
|
353
|
-
opencode.on(
|
|
354
|
-
console.error(chalk.red(
|
|
209
|
+
opencode.on('error', (error) => {
|
|
210
|
+
console.error(chalk.red('Failed to start OpenCode:'));
|
|
355
211
|
console.error(error.message);
|
|
356
212
|
});
|
|
357
213
|
} catch (error) {
|
|
358
|
-
handleError(
|
|
214
|
+
handleError('Failed to run OpenCode', error);
|
|
359
215
|
}
|
|
360
216
|
});
|
|
361
217
|
|
|
362
218
|
code
|
|
363
219
|
.command(SUBCOMMANDS.CODE.SERVE)
|
|
364
|
-
.description(
|
|
365
|
-
.option(
|
|
366
|
-
.option(
|
|
367
|
-
.option(
|
|
368
|
-
.action(async options => {
|
|
220
|
+
.description('Start OpenCode web server')
|
|
221
|
+
.option('-p, --port <port>', 'Port to run the server on (default: 3000)')
|
|
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)')
|
|
224
|
+
.action(async (options) => {
|
|
369
225
|
try {
|
|
370
226
|
// Ensure opencode is installed
|
|
371
227
|
if (!(await ensureOpencodeInstalled(options.yes))) {
|
|
372
228
|
return;
|
|
373
229
|
}
|
|
374
230
|
|
|
375
|
-
console.log(chalk.cyan(
|
|
231
|
+
console.log(chalk.cyan('🚀 Starting OpenCode web server...'));
|
|
376
232
|
|
|
377
233
|
// Prepare opencode serve command
|
|
378
|
-
const
|
|
234
|
+
const serveArguments: string[] = ['serve'];
|
|
379
235
|
|
|
380
236
|
if (options.port) {
|
|
381
|
-
|
|
237
|
+
serveArguments.push('--port', options.port);
|
|
382
238
|
}
|
|
383
239
|
|
|
384
240
|
if (options.host) {
|
|
385
|
-
|
|
241
|
+
serveArguments.push('--host', options.host);
|
|
386
242
|
}
|
|
387
243
|
|
|
388
244
|
// Spawn opencode serve process
|
|
389
|
-
const opencode = spawn(
|
|
390
|
-
stdio:
|
|
245
|
+
const opencode = spawn('opencode', serveArguments, {
|
|
246
|
+
stdio: 'inherit',
|
|
391
247
|
});
|
|
392
248
|
|
|
393
|
-
opencode.on(
|
|
249
|
+
opencode.on('close', (code) => {
|
|
394
250
|
if (code !== 0) {
|
|
395
251
|
console.log(chalk.red(`OpenCode server exited with code ${code}`));
|
|
396
252
|
}
|
|
397
253
|
});
|
|
398
254
|
|
|
399
|
-
opencode.on(
|
|
400
|
-
console.error(chalk.red(
|
|
255
|
+
opencode.on('error', (error) => {
|
|
256
|
+
console.error(chalk.red('Failed to start OpenCode server:'));
|
|
401
257
|
console.error(error.message);
|
|
402
258
|
});
|
|
403
259
|
} catch (error) {
|
|
404
|
-
handleError(
|
|
260
|
+
handleError('Failed to start OpenCode server', error);
|
|
405
261
|
}
|
|
406
262
|
});
|
|
407
263
|
|
|
408
264
|
code
|
|
409
265
|
.command(SUBCOMMANDS.CODE.UPDATE)
|
|
410
|
-
.description(
|
|
411
|
-
.option(
|
|
412
|
-
.option(
|
|
413
|
-
.action(async options => {
|
|
266
|
+
.description('Update OpenCode and agents to latest versions')
|
|
267
|
+
.option('-f, --force', 'Force update even if already latest')
|
|
268
|
+
.option('-y, --yes', 'Automatically answer yes to all prompts (for automation)')
|
|
269
|
+
.action(async (options) => {
|
|
414
270
|
try {
|
|
415
|
-
console.log(chalk.cyan(
|
|
271
|
+
console.log(chalk.cyan('🔄 Updating OpenCode configuration...'));
|
|
416
272
|
|
|
417
273
|
// Ensure opencode is installed first
|
|
418
274
|
if (!(await ensureOpencodeInstalled(options.yes))) {
|
|
419
275
|
return;
|
|
420
276
|
}
|
|
421
277
|
|
|
422
|
-
const configPath = path.join(process.cwd(),
|
|
278
|
+
const configPath = path.join(process.cwd(), 'opencode.json');
|
|
423
279
|
|
|
424
280
|
// Check if project is initialized
|
|
425
281
|
if (!fs.existsSync(configPath)) {
|
|
426
|
-
console.log(chalk.red(
|
|
282
|
+
console.log(chalk.red('❌ No OpenCode configuration found.'));
|
|
427
283
|
console.log(
|
|
428
284
|
chalk.blue(
|
|
429
|
-
`Run ${chalk.bold(`berget ${COMMAND_GROUPS.CODE} ${SUBCOMMANDS.CODE.INIT}`)} first
|
|
430
|
-
)
|
|
285
|
+
`Run ${chalk.bold(`berget ${COMMAND_GROUPS.CODE} ${SUBCOMMANDS.CODE.INIT}`)} first.`,
|
|
286
|
+
),
|
|
431
287
|
);
|
|
432
288
|
return;
|
|
433
289
|
}
|
|
@@ -435,36 +291,36 @@ export function registerCodeCommands(program: Command): void {
|
|
|
435
291
|
// Read current configuration
|
|
436
292
|
let currentConfig: any;
|
|
437
293
|
try {
|
|
438
|
-
const configContent = await readFile(configPath,
|
|
294
|
+
const configContent = await readFile(configPath, 'utf8');
|
|
439
295
|
currentConfig = JSON.parse(configContent);
|
|
440
296
|
} catch (error) {
|
|
441
|
-
console.error(chalk.red(
|
|
442
|
-
handleError(
|
|
297
|
+
console.error(chalk.red('Failed to read current opencode.json:'));
|
|
298
|
+
handleError('Config read failed', error);
|
|
443
299
|
return;
|
|
444
300
|
}
|
|
445
301
|
|
|
446
|
-
console.log(chalk.blue(
|
|
302
|
+
console.log(chalk.blue('📋 Current configuration:'));
|
|
447
303
|
if (currentConfig.model) {
|
|
448
304
|
console.log(chalk.dim(` Model: ${currentConfig.model}`));
|
|
449
305
|
}
|
|
450
306
|
|
|
451
|
-
const agentsDir = path.join(process.cwd(),
|
|
307
|
+
const agentsDir = path.join(process.cwd(), '.opencode', 'agents');
|
|
452
308
|
const templatesDir = getAgentTemplatesDir();
|
|
453
|
-
const templateFiles = fs.readdirSync(templatesDir).filter(f => f.endsWith(
|
|
309
|
+
const templateFiles = fs.readdirSync(templatesDir).filter((f) => f.endsWith('.md'));
|
|
454
310
|
|
|
455
311
|
// Check if agent definitions need updating
|
|
456
312
|
let agentsNeedUpdate = false;
|
|
457
313
|
|
|
458
314
|
for (const file of templateFiles) {
|
|
459
|
-
const
|
|
460
|
-
const
|
|
461
|
-
if (!fs.existsSync(
|
|
315
|
+
const source = path.join(templatesDir, file);
|
|
316
|
+
const destination = path.join(agentsDir, file);
|
|
317
|
+
if (!fs.existsSync(destination)) {
|
|
462
318
|
agentsNeedUpdate = true;
|
|
463
319
|
break;
|
|
464
320
|
}
|
|
465
|
-
const
|
|
466
|
-
const
|
|
467
|
-
if (
|
|
321
|
+
const sourceContent = fs.readFileSync(source, 'utf8');
|
|
322
|
+
const destinationContent = fs.readFileSync(destination, 'utf8');
|
|
323
|
+
if (sourceContent !== destinationContent) {
|
|
468
324
|
agentsNeedUpdate = true;
|
|
469
325
|
break;
|
|
470
326
|
}
|
|
@@ -474,48 +330,48 @@ export function registerCodeCommands(program: Command): void {
|
|
|
474
330
|
const needsMigration = !!currentConfig.agent;
|
|
475
331
|
|
|
476
332
|
if (!agentsNeedUpdate && !needsMigration && !options.force) {
|
|
477
|
-
console.log(chalk.green(
|
|
333
|
+
console.log(chalk.green('✅ Already using the latest configuration!'));
|
|
478
334
|
return;
|
|
479
335
|
}
|
|
480
336
|
|
|
481
337
|
if (agentsNeedUpdate || needsMigration) {
|
|
482
|
-
console.log(chalk.blue(
|
|
338
|
+
console.log(chalk.blue('\n🔄 Updates available:'));
|
|
483
339
|
|
|
484
340
|
if (needsMigration) {
|
|
485
|
-
console.log(chalk.cyan(
|
|
341
|
+
console.log(chalk.cyan(' • Migrate agents from opencode.json to .opencode/agents/'));
|
|
486
342
|
}
|
|
487
343
|
|
|
488
344
|
if (agentsNeedUpdate) {
|
|
489
|
-
console.log(chalk.cyan(
|
|
345
|
+
console.log(chalk.cyan(' • Latest agent prompts and improvements'));
|
|
490
346
|
}
|
|
491
347
|
}
|
|
492
348
|
|
|
493
349
|
if (options.force) {
|
|
494
|
-
console.log(chalk.yellow(
|
|
350
|
+
console.log(chalk.yellow('🔧 Force update requested'));
|
|
495
351
|
}
|
|
496
352
|
|
|
497
353
|
if (!options.yes) {
|
|
498
354
|
console.log(
|
|
499
|
-
chalk.blue(
|
|
355
|
+
chalk.blue('\nThis will update your agent definitions and OpenCode configuration.'),
|
|
500
356
|
);
|
|
501
357
|
|
|
502
358
|
const hasGitRepo = hasGit();
|
|
503
|
-
if (
|
|
504
|
-
console.log(chalk.
|
|
359
|
+
if (hasGitRepo) {
|
|
360
|
+
console.log(chalk.green('✓ Git repository detected - changes are tracked'));
|
|
505
361
|
} else {
|
|
506
|
-
console.log(chalk.
|
|
362
|
+
console.log(chalk.yellow('⚠️ No .git repository detected - backup will be created'));
|
|
507
363
|
}
|
|
508
364
|
}
|
|
509
365
|
|
|
510
|
-
if (await confirm(
|
|
366
|
+
if (await confirm('\nProceed with update? (Y/n): ', options.yes)) {
|
|
511
367
|
try {
|
|
512
|
-
let backupPath:
|
|
368
|
+
let backupPath: null | string = null;
|
|
513
369
|
|
|
514
370
|
if (!hasGit()) {
|
|
515
371
|
backupPath = `${configPath}.backup.${Date.now()}`;
|
|
516
372
|
await writeFile(backupPath, JSON.stringify(currentConfig, null, 2));
|
|
517
373
|
console.log(
|
|
518
|
-
chalk.green(`✓ Backed up current config to ${path.basename(backupPath)}`)
|
|
374
|
+
chalk.green(`✓ Backed up current config to ${path.basename(backupPath)}`),
|
|
519
375
|
);
|
|
520
376
|
}
|
|
521
377
|
|
|
@@ -523,22 +379,22 @@ export function registerCodeCommands(program: Command): void {
|
|
|
523
379
|
if (currentConfig.agent) {
|
|
524
380
|
delete currentConfig.agent;
|
|
525
381
|
await writeFile(configPath, JSON.stringify(currentConfig, null, 2));
|
|
526
|
-
console.log(chalk.green(
|
|
382
|
+
console.log(chalk.green('✓ Removed inline agent config from opencode.json'));
|
|
527
383
|
}
|
|
528
384
|
|
|
529
385
|
// Sync agent markdown files from templates
|
|
530
386
|
fs.mkdirSync(agentsDir, { recursive: true });
|
|
531
387
|
let updatedCount = 0;
|
|
532
388
|
for (const file of templateFiles) {
|
|
533
|
-
const
|
|
534
|
-
const
|
|
535
|
-
const agentName = path.basename(file,
|
|
389
|
+
const source = path.join(templatesDir, file);
|
|
390
|
+
const destination = path.join(agentsDir, file);
|
|
391
|
+
const agentName = path.basename(file, '.md');
|
|
536
392
|
|
|
537
393
|
if (
|
|
538
|
-
!fs.existsSync(
|
|
539
|
-
fs.readFileSync(
|
|
394
|
+
!fs.existsSync(destination) ||
|
|
395
|
+
fs.readFileSync(source, 'utf8') !== fs.readFileSync(destination, 'utf8')
|
|
540
396
|
) {
|
|
541
|
-
fs.copyFileSync(
|
|
397
|
+
fs.copyFileSync(source, destination);
|
|
542
398
|
updatedCount++;
|
|
543
399
|
console.log(chalk.cyan(` • Updated agent: ${agentName}`));
|
|
544
400
|
}
|
|
@@ -549,7 +405,7 @@ export function registerCodeCommands(program: Command): void {
|
|
|
549
405
|
}
|
|
550
406
|
|
|
551
407
|
// Update AGENTS.md if it doesn't exist
|
|
552
|
-
const agentsMdPath = path.join(process.cwd(),
|
|
408
|
+
const agentsMdPath = path.join(process.cwd(), 'AGENTS.md');
|
|
553
409
|
if (!fs.existsSync(agentsMdPath)) {
|
|
554
410
|
const agentsMdContent = `# Berget Code Agents
|
|
555
411
|
|
|
@@ -602,26 +458,171 @@ See https://opencode.ai/docs/agents/ for available options.
|
|
|
602
458
|
`;
|
|
603
459
|
|
|
604
460
|
await writeFile(agentsMdPath, agentsMdContent);
|
|
605
|
-
console.log(chalk.green(
|
|
461
|
+
console.log(chalk.green('✓ Created AGENTS.md documentation'));
|
|
606
462
|
}
|
|
607
463
|
|
|
608
|
-
console.log(chalk.green(
|
|
464
|
+
console.log(chalk.green('\n✅ Update completed successfully!'));
|
|
609
465
|
} catch (error) {
|
|
610
|
-
console.error(chalk.red(
|
|
611
|
-
handleError(
|
|
466
|
+
console.error(chalk.red('Failed to update configuration:'));
|
|
467
|
+
handleError('Update failed', error);
|
|
612
468
|
|
|
613
469
|
try {
|
|
614
470
|
await writeFile(configPath, JSON.stringify(currentConfig, null, 2));
|
|
615
|
-
console.log(chalk.yellow(
|
|
471
|
+
console.log(chalk.yellow('📁 Restored original configuration from backup'));
|
|
616
472
|
} catch {
|
|
617
|
-
console.error(chalk.red(
|
|
473
|
+
console.error(chalk.red('Failed to restore backup:'));
|
|
618
474
|
}
|
|
619
475
|
}
|
|
620
476
|
} else {
|
|
621
|
-
console.log(chalk.yellow(
|
|
477
|
+
console.log(chalk.yellow('Update cancelled.'));
|
|
622
478
|
}
|
|
623
479
|
} catch {
|
|
624
|
-
console.error(chalk.red(
|
|
480
|
+
console.error(chalk.red('Failed to update OpenCode configuration'));
|
|
625
481
|
}
|
|
626
482
|
});
|
|
627
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'));
|
|
545
|
+
}
|
|
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
|
+
}
|
|
585
|
+
}
|
|
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
|
+
}
|