berget 2.2.7 → 2.2.9

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 (130) hide show
  1. package/.github/workflows/publish.yml +6 -6
  2. package/.github/workflows/test.yml +1 -1
  3. package/.prettierrc +5 -3
  4. package/dist/index.js +24 -25
  5. package/dist/package.json +7 -3
  6. package/dist/src/agents/app.js +8 -8
  7. package/dist/src/agents/backend.js +3 -3
  8. package/dist/src/agents/devops.js +8 -8
  9. package/dist/src/agents/frontend.js +3 -3
  10. package/dist/src/agents/fullstack.js +3 -3
  11. package/dist/src/agents/index.js +18 -18
  12. package/dist/src/agents/quality.js +8 -8
  13. package/dist/src/agents/security.js +8 -8
  14. package/dist/src/client.js +115 -127
  15. package/dist/src/commands/api-keys.js +181 -202
  16. package/dist/src/commands/auth.js +16 -25
  17. package/dist/src/commands/autocomplete.js +8 -8
  18. package/dist/src/commands/billing.js +10 -19
  19. package/dist/src/commands/chat.js +139 -170
  20. package/dist/src/commands/clusters.js +21 -30
  21. package/dist/src/commands/code/__tests__/auth-sync.test.js +189 -186
  22. package/dist/src/commands/code/__tests__/fake-api-key-service.js +3 -13
  23. package/dist/src/commands/code/__tests__/fake-auth-service.js +21 -29
  24. package/dist/src/commands/code/__tests__/fake-command-runner.js +22 -33
  25. package/dist/src/commands/code/__tests__/fake-file-store.js +19 -41
  26. package/dist/src/commands/code/__tests__/fake-prompter.js +81 -97
  27. package/dist/src/commands/code/__tests__/setup-flow.test.js +295 -295
  28. package/dist/src/commands/code/adapters/clack-prompter.js +15 -32
  29. package/dist/src/commands/code/adapters/fs-file-store.js +25 -44
  30. package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -41
  31. package/dist/src/commands/code/auth-sync.js +215 -228
  32. package/dist/src/commands/code/errors.js +15 -12
  33. package/dist/src/commands/code/setup.js +390 -425
  34. package/dist/src/commands/code.js +279 -294
  35. package/dist/src/commands/index.js +5 -5
  36. package/dist/src/commands/models.js +16 -25
  37. package/dist/src/commands/users.js +9 -18
  38. package/dist/src/constants/command-structure.js +138 -138
  39. package/dist/src/services/api-key-service.js +132 -152
  40. package/dist/src/services/auth-service.js +81 -95
  41. package/dist/src/services/browser-auth.js +121 -131
  42. package/dist/src/services/chat-service.js +369 -386
  43. package/dist/src/services/cluster-service.js +47 -62
  44. package/dist/src/services/collaborator-service.js +9 -21
  45. package/dist/src/services/flux-service.js +13 -25
  46. package/dist/src/services/helm-service.js +9 -21
  47. package/dist/src/services/kubectl-service.js +15 -29
  48. package/dist/src/utils/config-checker.js +8 -8
  49. package/dist/src/utils/config-loader.js +109 -109
  50. package/dist/src/utils/default-api-key.js +129 -139
  51. package/dist/src/utils/env-manager.js +55 -66
  52. package/dist/src/utils/error-handler.js +62 -62
  53. package/dist/src/utils/logger.js +74 -67
  54. package/dist/src/utils/markdown-renderer.js +28 -28
  55. package/dist/src/utils/opencode-validator.js +67 -69
  56. package/dist/src/utils/token-manager.js +67 -65
  57. package/dist/tests/commands/chat.test.js +30 -39
  58. package/dist/tests/commands/code.test.js +186 -195
  59. package/dist/tests/utils/config-loader.test.js +107 -107
  60. package/dist/tests/utils/env-manager.test.js +81 -90
  61. package/dist/tests/utils/opencode-validator.test.js +42 -41
  62. package/dist/vitest.config.js +1 -1
  63. package/eslint.config.mjs +65 -30
  64. package/index.ts +30 -31
  65. package/package.json +7 -3
  66. package/src/agents/app.ts +9 -9
  67. package/src/agents/backend.ts +4 -4
  68. package/src/agents/devops.ts +9 -9
  69. package/src/agents/frontend.ts +4 -4
  70. package/src/agents/fullstack.ts +4 -4
  71. package/src/agents/index.ts +27 -25
  72. package/src/agents/quality.ts +9 -9
  73. package/src/agents/security.ts +9 -9
  74. package/src/agents/types.ts +10 -10
  75. package/src/client.ts +85 -77
  76. package/src/commands/api-keys.ts +180 -185
  77. package/src/commands/auth.ts +15 -14
  78. package/src/commands/autocomplete.ts +10 -10
  79. package/src/commands/billing.ts +13 -12
  80. package/src/commands/chat.ts +145 -142
  81. package/src/commands/clusters.ts +20 -19
  82. package/src/commands/code/__tests__/auth-sync.test.ts +176 -175
  83. package/src/commands/code/__tests__/fake-api-key-service.ts +2 -2
  84. package/src/commands/code/__tests__/fake-auth-service.ts +18 -18
  85. package/src/commands/code/__tests__/fake-command-runner.ts +28 -22
  86. package/src/commands/code/__tests__/fake-file-store.ts +15 -15
  87. package/src/commands/code/__tests__/fake-prompter.ts +86 -85
  88. package/src/commands/code/__tests__/setup-flow.test.ts +253 -251
  89. package/src/commands/code/adapters/clack-prompter.ts +32 -30
  90. package/src/commands/code/adapters/fs-file-store.ts +18 -17
  91. package/src/commands/code/adapters/spawn-command-runner.ts +20 -15
  92. package/src/commands/code/auth-sync.ts +210 -210
  93. package/src/commands/code/errors.ts +11 -11
  94. package/src/commands/code/ports/auth-services.ts +7 -7
  95. package/src/commands/code/ports/command-runner.ts +2 -2
  96. package/src/commands/code/ports/file-store.ts +3 -3
  97. package/src/commands/code/ports/prompter.ts +13 -13
  98. package/src/commands/code/setup.ts +408 -406
  99. package/src/commands/code.ts +288 -287
  100. package/src/commands/index.ts +11 -10
  101. package/src/commands/models.ts +19 -18
  102. package/src/commands/users.ts +11 -10
  103. package/src/constants/command-structure.ts +159 -159
  104. package/src/services/api-key-service.ts +85 -85
  105. package/src/services/auth-service.ts +55 -54
  106. package/src/services/browser-auth.ts +62 -62
  107. package/src/services/chat-service.ts +170 -171
  108. package/src/services/cluster-service.ts +28 -28
  109. package/src/services/collaborator-service.ts +6 -6
  110. package/src/services/flux-service.ts +17 -17
  111. package/src/services/helm-service.ts +11 -11
  112. package/src/services/kubectl-service.ts +12 -12
  113. package/src/types/api.d.ts +1933 -1933
  114. package/src/types/json.d.ts +1 -1
  115. package/src/utils/config-checker.ts +7 -7
  116. package/src/utils/config-loader.ts +130 -129
  117. package/src/utils/default-api-key.ts +81 -80
  118. package/src/utils/env-manager.ts +37 -37
  119. package/src/utils/error-handler.ts +64 -64
  120. package/src/utils/logger.ts +72 -66
  121. package/src/utils/markdown-renderer.ts +28 -28
  122. package/src/utils/opencode-validator.ts +72 -71
  123. package/src/utils/token-manager.ts +69 -68
  124. package/tests/commands/chat.test.ts +32 -31
  125. package/tests/commands/code.test.ts +182 -181
  126. package/tests/utils/config-loader.test.ts +111 -110
  127. package/tests/utils/env-manager.test.ts +83 -79
  128. package/tests/utils/opencode-validator.test.ts +43 -42
  129. package/tsconfig.json +2 -1
  130. package/vitest.config.ts +2 -2
@@ -1,158 +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(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("AI-powered coding assistant with OpenCode");
19
+ .description('AI-powered coding assistant with OpenCode');
164
20
 
165
21
  if (process.env.BERGET_EXPERIMENTAL) {
166
22
  code
167
- .command("setup")
168
- .description("Interactive setup for Berget AI coding tools")
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("Setup failed", error);
29
+ handleError('Setup failed', error);
174
30
  }
175
31
  });
176
32
  }
177
33
 
178
34
  code
179
35
  .command(SUBCOMMANDS.CODE.INIT)
180
- .description("Initialize project for AI coding assistant")
181
- .option("-n, --name <name>", "Project name (defaults to directory name)")
182
- .option("-f, --force", "Overwrite existing configuration")
183
- .option("-y, --yes", "Automatically answer yes to all prompts (for automation)")
184
- .action(async options => {
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(), "opencode.json");
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("Project already initialized for OpenCode."));
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("Do you want to reinitialize? (Y/n): ", options.yes)) {
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: "https://opencode.ai/config.json",
212
- plugin: ["@bergetai/opencode-auth@1.0.16"],
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(), ".opencode", "agents");
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("\nAbout to create configuration files:"));
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("This will configure OpenCode with the Berget auth plugin."));
78
+ console.log(chalk.dim('This will configure OpenCode with the Berget auth plugin.'));
223
79
  }
224
80
 
225
- if (await confirm("\nCreate configuration files? (Y/n): ", options.yes)) {
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("✓ Created opencode.json"));
229
- console.log(chalk.dim(" Plugin: @bergetai/opencode-auth"));
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(".md"));
88
+ const templateFiles = fs.readdirSync(templatesDir).filter((f) => f.endsWith('.md'));
233
89
  for (const file of templateFiles) {
234
- const src = path.join(templatesDir, file);
235
- const dest = path.join(agentsDir, file);
236
- fs.copyFileSync(src, dest);
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("Failed to create config files:"));
245
- handleError("Config file creation failed", error);
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("Configuration file creation cancelled."));
105
+ console.log(chalk.yellow('Configuration file creation cancelled.'));
250
106
  return;
251
107
  }
252
108
 
253
- console.log(chalk.green("\n✅ Project initialized successfully!"));
254
- console.log(chalk.blue("\nNext steps:"));
255
- console.log(chalk.cyan(" 1. Run: opencode"));
256
- console.log(chalk.cyan(" 2. Type: /connect"));
257
- console.log(chalk.cyan(" 3. Choose your auth method:"));
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("Failed to initialize project", error);
117
+ handleError('Failed to initialize project', error);
262
118
  }
263
119
  });
264
120
 
265
121
  code
266
122
  .command(SUBCOMMANDS.CODE.RUN)
267
- .description("Run AI coding assistant")
268
- .argument("[prompt]", "Prompt to send directly to OpenCode")
269
- .option("-m, --model <model>", "Model to use (overrides config)")
270
- .option("-a, --analysis", "Use fast analysis model for context building")
271
- .option("--no-config", "Run without loading project config")
272
- .option("-y, --yes", "Automatically answer yes to all prompts (for automation)")
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(), "opencode.json");
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, "utf8");
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("Warning: Failed to load opencode.json"));
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("No project configuration found."));
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 env = { ...process.env };
308
- const opencodeArgs: string[] = [];
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("--stage");
313
- const isLocal = process.argv.includes("--local");
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("Using Berget stage environment"));
317
- env.BERGET_API_URL = "https://api.stage.berget.ai";
318
- env.BERGET_INFERENCE_URL = "https://api.stage.berget.ai/v1";
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("Using local development environment"));
321
- env.BERGET_API_URL = "http://localhost:3000";
322
- 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';
323
179
  }
324
180
 
325
181
  if (prompt) {
326
- opencodeArgs.push("run", prompt);
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
- opencodeArgs.push("--model", selectedModel);
192
+ opencodeArguments.push('--model', selectedModel);
337
193
  }
338
194
 
339
- console.log(chalk.cyan("Starting OpenCode..."));
195
+ console.log(chalk.cyan('Starting OpenCode...'));
340
196
 
341
197
  // Spawn opencode process
342
- const opencode = spawn("opencode", opencodeArgs, {
343
- stdio: "inherit",
344
- env: env,
198
+ const opencode = spawn('opencode', opencodeArguments, {
199
+ env: environment,
200
+ stdio: 'inherit',
345
201
  });
346
202
 
347
- opencode.on("close", code => {
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("error", error => {
354
- console.error(chalk.red("Failed to start OpenCode:"));
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("Failed to run OpenCode", error);
214
+ handleError('Failed to run OpenCode', error);
359
215
  }
360
216
  });
361
217
 
362
218
  code
363
219
  .command(SUBCOMMANDS.CODE.SERVE)
364
- .description("Start OpenCode web server")
365
- .option("-p, --port <port>", "Port to run the server on (default: 3000)")
366
- .option("-h, --host <host>", "Host to bind the server to (default: localhost)")
367
- .option("-y, --yes", "Automatically answer yes to all prompts (for automation)")
368
- .action(async options => {
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("🚀 Starting OpenCode web server..."));
231
+ console.log(chalk.cyan('🚀 Starting OpenCode web server...'));
376
232
 
377
233
  // Prepare opencode serve command
378
- const serveArgs: string[] = ["serve"];
234
+ const serveArguments: string[] = ['serve'];
379
235
 
380
236
  if (options.port) {
381
- serveArgs.push("--port", options.port);
237
+ serveArguments.push('--port', options.port);
382
238
  }
383
239
 
384
240
  if (options.host) {
385
- serveArgs.push("--host", options.host);
241
+ serveArguments.push('--host', options.host);
386
242
  }
387
243
 
388
244
  // Spawn opencode serve process
389
- const opencode = spawn("opencode", serveArgs, {
390
- stdio: "inherit",
245
+ const opencode = spawn('opencode', serveArguments, {
246
+ stdio: 'inherit',
391
247
  });
392
248
 
393
- opencode.on("close", code => {
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("error", error => {
400
- console.error(chalk.red("Failed to start OpenCode server:"));
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("Failed to start OpenCode server", error);
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("Update OpenCode and agents to latest versions")
411
- .option("-f, --force", "Force update even if already latest")
412
- .option("-y, --yes", "Automatically answer yes to all prompts (for automation)")
413
- .action(async options => {
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("🔄 Updating OpenCode configuration..."));
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(), "opencode.json");
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("❌ No OpenCode configuration found."));
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, "utf8");
294
+ const configContent = await readFile(configPath, 'utf8');
439
295
  currentConfig = JSON.parse(configContent);
440
296
  } catch (error) {
441
- console.error(chalk.red("Failed to read current opencode.json:"));
442
- handleError("Config read failed", error);
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("📋 Current configuration:"));
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(), ".opencode", "agents");
307
+ const agentsDir = path.join(process.cwd(), '.opencode', 'agents');
452
308
  const templatesDir = getAgentTemplatesDir();
453
- const templateFiles = fs.readdirSync(templatesDir).filter(f => f.endsWith(".md"));
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 src = path.join(templatesDir, file);
460
- const dest = path.join(agentsDir, file);
461
- if (!fs.existsSync(dest)) {
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 srcContent = fs.readFileSync(src, "utf8");
466
- const destContent = fs.readFileSync(dest, "utf8");
467
- if (srcContent !== destContent) {
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("✅ Already using the latest configuration!"));
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("\n🔄 Updates available:"));
338
+ console.log(chalk.blue('\n🔄 Updates available:'));
483
339
 
484
340
  if (needsMigration) {
485
- console.log(chalk.cyan(" • Migrate agents from opencode.json to .opencode/agents/"));
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(" • Latest agent prompts and improvements"));
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("🔧 Force update requested"));
350
+ console.log(chalk.yellow('🔧 Force update requested'));
495
351
  }
496
352
 
497
353
  if (!options.yes) {
498
354
  console.log(
499
- chalk.blue("\nThis will update your agent definitions and OpenCode configuration.")
355
+ chalk.blue('\nThis will update your agent definitions and OpenCode configuration.'),
500
356
  );
501
357
 
502
358
  const hasGitRepo = hasGit();
503
- if (!hasGitRepo) {
504
- console.log(chalk.yellow("⚠️ No .git repository detected - backup will be created"));
359
+ if (hasGitRepo) {
360
+ console.log(chalk.green('✓ Git repository detected - changes are tracked'));
505
361
  } else {
506
- console.log(chalk.green("✓ Git repository detected - changes are tracked"));
362
+ console.log(chalk.yellow('⚠️ No .git repository detected - backup will be created'));
507
363
  }
508
364
  }
509
365
 
510
- if (await confirm("\nProceed with update? (Y/n): ", options.yes)) {
366
+ if (await confirm('\nProceed with update? (Y/n): ', options.yes)) {
511
367
  try {
512
- let backupPath: string | null = null;
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("✓ Removed inline agent config from opencode.json"));
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 src = path.join(templatesDir, file);
534
- const dest = path.join(agentsDir, file);
535
- 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');
536
392
 
537
393
  if (
538
- !fs.existsSync(dest) ||
539
- fs.readFileSync(src, "utf8") !== fs.readFileSync(dest, "utf8")
394
+ !fs.existsSync(destination) ||
395
+ fs.readFileSync(source, 'utf8') !== fs.readFileSync(destination, 'utf8')
540
396
  ) {
541
- fs.copyFileSync(src, dest);
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(), "AGENTS.md");
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("✓ Created AGENTS.md documentation"));
461
+ console.log(chalk.green('✓ Created AGENTS.md documentation'));
606
462
  }
607
463
 
608
- console.log(chalk.green("\n✅ Update completed successfully!"));
464
+ console.log(chalk.green('\n✅ Update completed successfully!'));
609
465
  } catch (error) {
610
- console.error(chalk.red("Failed to update configuration:"));
611
- handleError("Update failed", error);
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("📁 Restored original configuration from backup"));
471
+ console.log(chalk.yellow('📁 Restored original configuration from backup'));
616
472
  } catch {
617
- console.error(chalk.red("Failed to restore backup:"));
473
+ console.error(chalk.red('Failed to restore backup:'));
618
474
  }
619
475
  }
620
476
  } else {
621
- console.log(chalk.yellow("Update cancelled."));
477
+ console.log(chalk.yellow('Update cancelled.'));
622
478
  }
623
479
  } catch {
624
- console.error(chalk.red("Failed to update OpenCode configuration"));
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
+ }