berget 2.2.11 → 2.2.13
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/dist/package.json +1 -1
- package/dist/src/commands/code/__tests__/setup-flow.test.js +34 -34
- package/dist/src/commands/code/auth-sync.js +3 -3
- package/dist/src/commands/code/{setup.js → init.js} +17 -29
- package/dist/src/commands/code.js +5 -553
- package/dist/src/constants/command-structure.js +1 -9
- package/dist/src/services/auth-service.js +1 -1
- package/dist/tests/commands/code.test.js +4 -415
- package/package.json +1 -1
- package/src/commands/code/__tests__/setup-flow.test.ts +36 -36
- package/src/commands/code/auth-sync.ts +3 -3
- package/src/commands/code/{setup.ts → init.ts} +14 -26
- package/src/commands/code.ts +5 -608
- package/src/constants/command-structure.ts +1 -11
- package/src/services/auth-service.ts +1 -1
- package/tests/commands/code.test.ts +5 -483
- package/templates/agents/app.md +0 -23
- package/templates/agents/backend.md +0 -23
- package/templates/agents/devops.md +0 -30
- package/templates/agents/frontend.md +0 -25
- package/templates/agents/fullstack.md +0 -23
- package/templates/agents/quality.md +0 -69
- package/templates/agents/security.md +0 -21
|
@@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest';
|
|
|
3
3
|
import type { ApiKeyServicePort, AuthServicePort } from '../ports/auth-services';
|
|
4
4
|
|
|
5
5
|
import { CancelledError, CommandFailedError, PrerequisiteError } from '../errors';
|
|
6
|
-
import {
|
|
6
|
+
import { runInit } from '../init';
|
|
7
7
|
import { FakeApiKeyService } from './fake-api-key-service';
|
|
8
8
|
import { FakeAuthService } from './fake-auth-service';
|
|
9
9
|
import { FakeCommandRunner } from './fake-command-runner';
|
|
@@ -11,8 +11,8 @@ import { FakeFileStore } from './fake-file-store';
|
|
|
11
11
|
import { CANCEL, confirm, FakePrompter, multiselect, select } from './fake-prompter';
|
|
12
12
|
|
|
13
13
|
const makeDeps = (
|
|
14
|
-
overrides: Partial<Parameters<typeof
|
|
15
|
-
): Parameters<typeof
|
|
14
|
+
overrides: Partial<Parameters<typeof runInit>[0]> = {},
|
|
15
|
+
): Parameters<typeof runInit>[0] => {
|
|
16
16
|
return {
|
|
17
17
|
apiKeyService:
|
|
18
18
|
(overrides.apiKeyService as ApiKeyServicePort) ?? new FakeApiKeyService('sk_ber_test'),
|
|
@@ -49,7 +49,7 @@ function makeJwt(payload: Record<string, unknown>): string {
|
|
|
49
49
|
return `${header}.${body}.signature`;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
describe('
|
|
52
|
+
describe('runInit', () => {
|
|
53
53
|
describe('happy path', () => {
|
|
54
54
|
it('sets up opencode project without existing config', async () => {
|
|
55
55
|
const deps = makeDeps({
|
|
@@ -61,13 +61,13 @@ describe('runSetup', () => {
|
|
|
61
61
|
]),
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
await
|
|
64
|
+
await runInit(deps);
|
|
65
65
|
|
|
66
66
|
const files = deps.files as FakeFileStore;
|
|
67
67
|
const written = files.getWrittenFiles();
|
|
68
68
|
expect(written.has('/home/user/project/opencode.json')).toBe(true);
|
|
69
69
|
const config = JSON.parse(written.get('/home/user/project/opencode.json')!);
|
|
70
|
-
expect(config.plugin).toContain('@bergetai/opencode-auth');
|
|
70
|
+
expect(config.plugin).toContain('@bergetai/opencode-auth@1.0.22');
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
it('sets up opencode globally without existing config', async () => {
|
|
@@ -80,7 +80,7 @@ describe('runSetup', () => {
|
|
|
80
80
|
]),
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
await
|
|
83
|
+
await runInit(deps);
|
|
84
84
|
|
|
85
85
|
const files = deps.files as FakeFileStore;
|
|
86
86
|
const written = files.getWrittenFiles();
|
|
@@ -101,7 +101,7 @@ describe('runSetup', () => {
|
|
|
101
101
|
]),
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
-
await
|
|
104
|
+
await runInit(deps);
|
|
105
105
|
|
|
106
106
|
const commands = deps.commands as FakeCommandRunner;
|
|
107
107
|
expect(commands.calls.length).toBeGreaterThan(0);
|
|
@@ -121,7 +121,7 @@ describe('runSetup', () => {
|
|
|
121
121
|
]),
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
-
await
|
|
124
|
+
await runInit(deps);
|
|
125
125
|
|
|
126
126
|
const files = deps.files as FakeFileStore;
|
|
127
127
|
const written = files.getWrittenFiles();
|
|
@@ -140,7 +140,7 @@ describe('runSetup', () => {
|
|
|
140
140
|
});
|
|
141
141
|
|
|
142
142
|
// Simulate opencode not being installed
|
|
143
|
-
await expect(
|
|
143
|
+
await expect(runInit(deps)).rejects.toBeInstanceOf(PrerequisiteError);
|
|
144
144
|
});
|
|
145
145
|
});
|
|
146
146
|
|
|
@@ -150,7 +150,7 @@ describe('runSetup', () => {
|
|
|
150
150
|
prompter: new FakePrompter([select(CANCEL)]),
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
await expect(
|
|
153
|
+
await expect(runInit(deps)).rejects.toBeInstanceOf(CancelledError);
|
|
154
154
|
});
|
|
155
155
|
|
|
156
156
|
it('throws CancelledError when user cancels at write confirmation', async () => {
|
|
@@ -162,7 +162,7 @@ describe('runSetup', () => {
|
|
|
162
162
|
]),
|
|
163
163
|
});
|
|
164
164
|
|
|
165
|
-
await expect(
|
|
165
|
+
await expect(runInit(deps)).rejects.toBeInstanceOf(CancelledError);
|
|
166
166
|
});
|
|
167
167
|
|
|
168
168
|
it('throws CancelledError when user cancels at agent write confirmation (opencode)', async () => {
|
|
@@ -176,7 +176,7 @@ describe('runSetup', () => {
|
|
|
176
176
|
]),
|
|
177
177
|
});
|
|
178
178
|
|
|
179
|
-
await expect(
|
|
179
|
+
await expect(runInit(deps)).rejects.toBeInstanceOf(CancelledError);
|
|
180
180
|
});
|
|
181
181
|
|
|
182
182
|
it('throws CancelledError when user cancels at agent write confirmation (pi)', async () => {
|
|
@@ -191,7 +191,7 @@ describe('runSetup', () => {
|
|
|
191
191
|
]),
|
|
192
192
|
});
|
|
193
193
|
|
|
194
|
-
await expect(
|
|
194
|
+
await expect(runInit(deps)).rejects.toBeInstanceOf(CancelledError);
|
|
195
195
|
});
|
|
196
196
|
});
|
|
197
197
|
|
|
@@ -215,13 +215,13 @@ describe('runSetup', () => {
|
|
|
215
215
|
}),
|
|
216
216
|
);
|
|
217
217
|
|
|
218
|
-
await
|
|
218
|
+
await runInit(deps);
|
|
219
219
|
|
|
220
220
|
const written = files.getWrittenFiles();
|
|
221
221
|
const config = JSON.parse(written.get('/home/user/project/opencode.json')!);
|
|
222
222
|
expect(config.customField).toBe('should-preserve');
|
|
223
223
|
expect(config.plugin).toContain('other-plugin');
|
|
224
|
-
expect(config.plugin).toContain('@bergetai/opencode-auth');
|
|
224
|
+
expect(config.plugin).toContain('@bergetai/opencode-auth@1.0.22');
|
|
225
225
|
});
|
|
226
226
|
|
|
227
227
|
it('preserves jsonc comments when updating', async () => {
|
|
@@ -245,7 +245,7 @@ describe('runSetup', () => {
|
|
|
245
245
|
}`,
|
|
246
246
|
);
|
|
247
247
|
|
|
248
|
-
await
|
|
248
|
+
await runInit(deps);
|
|
249
249
|
|
|
250
250
|
const written = files.getWrittenFiles();
|
|
251
251
|
const content = written.get('/home/user/project/opencode.jsonc')!;
|
|
@@ -267,20 +267,20 @@ describe('runSetup', () => {
|
|
|
267
267
|
JSON.stringify(
|
|
268
268
|
{
|
|
269
269
|
$schema: 'https://opencode.ai/config.json',
|
|
270
|
-
plugin: ['@bergetai/opencode-auth'],
|
|
270
|
+
plugin: ['@bergetai/opencode-auth@1.0.22'],
|
|
271
271
|
},
|
|
272
272
|
null,
|
|
273
273
|
2,
|
|
274
274
|
) + '\n',
|
|
275
275
|
);
|
|
276
276
|
|
|
277
|
-
await
|
|
277
|
+
await runInit(deps);
|
|
278
278
|
|
|
279
279
|
// Check that no write happened — content should be unchanged
|
|
280
280
|
const written = files.getWrittenFiles();
|
|
281
281
|
const content = written.get('/home/user/project/opencode.json')!;
|
|
282
282
|
const config = JSON.parse(content);
|
|
283
|
-
expect(config.plugin).toEqual(['@bergetai/opencode-auth']);
|
|
283
|
+
expect(config.plugin).toEqual(['@bergetai/opencode-auth@1.0.22']);
|
|
284
284
|
expect(content).toContain('$schema');
|
|
285
285
|
});
|
|
286
286
|
|
|
@@ -305,7 +305,7 @@ describe('runSetup', () => {
|
|
|
305
305
|
}),
|
|
306
306
|
);
|
|
307
307
|
|
|
308
|
-
await
|
|
308
|
+
await runInit(deps);
|
|
309
309
|
|
|
310
310
|
const written = files.getWrittenFiles();
|
|
311
311
|
const settings = JSON.parse(written.get('/home/user/project/.pi/settings.json')!);
|
|
@@ -324,7 +324,7 @@ describe('runSetup', () => {
|
|
|
324
324
|
]),
|
|
325
325
|
});
|
|
326
326
|
|
|
327
|
-
await
|
|
327
|
+
await runInit(deps);
|
|
328
328
|
|
|
329
329
|
const files = deps.files as FakeFileStore;
|
|
330
330
|
const written = files.getWrittenFiles();
|
|
@@ -345,7 +345,7 @@ describe('runSetup', () => {
|
|
|
345
345
|
]),
|
|
346
346
|
});
|
|
347
347
|
|
|
348
|
-
await
|
|
348
|
+
await runInit(deps);
|
|
349
349
|
|
|
350
350
|
const commands = deps.commands as FakeCommandRunner;
|
|
351
351
|
const installCall = commands.calls.find((c) => c.command === 'pi');
|
|
@@ -363,7 +363,7 @@ describe('runSetup', () => {
|
|
|
363
363
|
prompter: new FakePrompter([select('pi'), select('project')]),
|
|
364
364
|
});
|
|
365
365
|
|
|
366
|
-
await expect(
|
|
366
|
+
await expect(runInit(deps)).rejects.toBeInstanceOf(CommandFailedError);
|
|
367
367
|
});
|
|
368
368
|
});
|
|
369
369
|
|
|
@@ -386,7 +386,7 @@ describe('runSetup', () => {
|
|
|
386
386
|
]),
|
|
387
387
|
});
|
|
388
388
|
|
|
389
|
-
await
|
|
389
|
+
await runInit(deps);
|
|
390
390
|
|
|
391
391
|
const prompter = deps.prompter as FakePrompter;
|
|
392
392
|
const notes = prompter.calls.filter((c) => c.method === 'note');
|
|
@@ -409,7 +409,7 @@ describe('runSetup', () => {
|
|
|
409
409
|
]),
|
|
410
410
|
});
|
|
411
411
|
|
|
412
|
-
await
|
|
412
|
+
await runInit(deps);
|
|
413
413
|
|
|
414
414
|
const prompter = deps.prompter as FakePrompter;
|
|
415
415
|
const notes = prompter.calls.filter((c) => c.method === 'note');
|
|
@@ -434,7 +434,7 @@ describe('runSetup', () => {
|
|
|
434
434
|
]),
|
|
435
435
|
});
|
|
436
436
|
|
|
437
|
-
await
|
|
437
|
+
await runInit(deps);
|
|
438
438
|
|
|
439
439
|
const written = files.getWrittenFiles();
|
|
440
440
|
expect(written.has('/home/user/.pi/agent/auth.json')).toBe(true);
|
|
@@ -465,7 +465,7 @@ describe('runSetup', () => {
|
|
|
465
465
|
]),
|
|
466
466
|
});
|
|
467
467
|
|
|
468
|
-
await
|
|
468
|
+
await runInit(deps);
|
|
469
469
|
|
|
470
470
|
const written = files.getWrittenFiles();
|
|
471
471
|
const parsed = JSON.parse(written.get('/home/user/.local/share/opencode/auth.json')!);
|
|
@@ -485,7 +485,7 @@ describe('runSetup', () => {
|
|
|
485
485
|
]),
|
|
486
486
|
});
|
|
487
487
|
|
|
488
|
-
await
|
|
488
|
+
await runInit(deps);
|
|
489
489
|
|
|
490
490
|
const files = deps.files as FakeFileStore;
|
|
491
491
|
const written = files.getWrittenFiles();
|
|
@@ -503,7 +503,7 @@ describe('runSetup', () => {
|
|
|
503
503
|
]),
|
|
504
504
|
});
|
|
505
505
|
|
|
506
|
-
await
|
|
506
|
+
await runInit(deps);
|
|
507
507
|
|
|
508
508
|
const files = deps.files as FakeFileStore;
|
|
509
509
|
const written = files.getWrittenFiles();
|
|
@@ -523,7 +523,7 @@ describe('runSetup', () => {
|
|
|
523
523
|
]),
|
|
524
524
|
});
|
|
525
525
|
|
|
526
|
-
await
|
|
526
|
+
await runInit(deps);
|
|
527
527
|
|
|
528
528
|
const files = deps.files as FakeFileStore;
|
|
529
529
|
const written = files.getWrittenFiles();
|
|
@@ -542,7 +542,7 @@ describe('runSetup', () => {
|
|
|
542
542
|
]),
|
|
543
543
|
});
|
|
544
544
|
|
|
545
|
-
await
|
|
545
|
+
await runInit(deps);
|
|
546
546
|
|
|
547
547
|
const files = deps.files as FakeFileStore;
|
|
548
548
|
const written = files.getWrittenFiles();
|
|
@@ -561,7 +561,7 @@ describe('runSetup', () => {
|
|
|
561
561
|
]),
|
|
562
562
|
});
|
|
563
563
|
|
|
564
|
-
await
|
|
564
|
+
await runInit(deps);
|
|
565
565
|
|
|
566
566
|
const files = deps.files as FakeFileStore;
|
|
567
567
|
const written = files.getWrittenFiles();
|
|
@@ -580,7 +580,7 @@ describe('runSetup', () => {
|
|
|
580
580
|
});
|
|
581
581
|
|
|
582
582
|
// First run writes the files
|
|
583
|
-
await
|
|
583
|
+
await runInit(deps);
|
|
584
584
|
|
|
585
585
|
const files = deps.files as FakeFileStore;
|
|
586
586
|
const firstBackend = files
|
|
@@ -601,7 +601,7 @@ describe('runSetup', () => {
|
|
|
601
601
|
]),
|
|
602
602
|
});
|
|
603
603
|
|
|
604
|
-
await
|
|
604
|
+
await runInit(deps2);
|
|
605
605
|
|
|
606
606
|
// Content should be unchanged
|
|
607
607
|
expect(files.getWrittenFiles().get('/home/user/project/.opencode/agents/backend.md')).toBe(
|
|
@@ -628,7 +628,7 @@ describe('runSetup', () => {
|
|
|
628
628
|
]),
|
|
629
629
|
});
|
|
630
630
|
|
|
631
|
-
await
|
|
631
|
+
await runInit(deps);
|
|
632
632
|
|
|
633
633
|
const written = files.getWrittenFiles();
|
|
634
634
|
const content = written.get('/home/user/project/.pi/SYSTEM.md');
|
|
@@ -66,7 +66,7 @@ export async function configureAuth(deps: AuthDeps, tool: 'opencode' | 'pi'): Pr
|
|
|
66
66
|
if (!loginResult.success) {
|
|
67
67
|
s.stop('Login failed.');
|
|
68
68
|
prompter.note(
|
|
69
|
-
`${loginResult.error || 'Login timed out or was cancelled.'}\n\nPlease run \`berget auth login\` manually, then run \`berget code
|
|
69
|
+
`${loginResult.error || 'Login timed out or was cancelled.'}\n\nPlease run \`berget auth login\` manually, then run \`berget code init\` again.`,
|
|
70
70
|
'Authentication Failed',
|
|
71
71
|
);
|
|
72
72
|
return { authenticated: false };
|
|
@@ -129,7 +129,7 @@ export async function configureAuth(deps: AuthDeps, tool: 'opencode' | 'pi'): Pr
|
|
|
129
129
|
s.start('Creating API key...');
|
|
130
130
|
try {
|
|
131
131
|
const { key } = await apiKeyService.create({
|
|
132
|
-
description: 'Created by berget code
|
|
132
|
+
description: 'Created by berget code init',
|
|
133
133
|
name: `${tool === 'opencode' ? 'OpenCode' : 'Pi'} (created by berget CLI)`,
|
|
134
134
|
});
|
|
135
135
|
await syncApiKeyToTool(files, homeDir, tool, key);
|
|
@@ -156,7 +156,7 @@ export async function configureAuth(deps: AuthDeps, tool: 'opencode' | 'pi'): Pr
|
|
|
156
156
|
s.start('Creating API key...');
|
|
157
157
|
try {
|
|
158
158
|
const { key } = await apiKeyService.create({
|
|
159
|
-
description: 'Created by berget code
|
|
159
|
+
description: 'Created by berget code init',
|
|
160
160
|
name: `${tool === 'opencode' ? 'OpenCode' : 'Pi'} (created by berget CLI)`,
|
|
161
161
|
});
|
|
162
162
|
await syncApiKeyToTool(files, homeDir, tool, key);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
1
2
|
import { applyEdits, modify, parse } from 'jsonc-parser';
|
|
2
3
|
import * as os from 'node:os';
|
|
3
4
|
|
|
@@ -15,7 +16,7 @@ import { SpawnCommandRunner } from './adapters/spawn-command-runner.js';
|
|
|
15
16
|
import { configureAuth } from './auth-sync.js';
|
|
16
17
|
import { CancelledError, CommandFailedError, PrerequisiteError } from './errors';
|
|
17
18
|
|
|
18
|
-
const OPENCODE_PLUGIN = '@bergetai/opencode-auth';
|
|
19
|
+
const OPENCODE_PLUGIN = '@bergetai/opencode-auth@1.0.22';
|
|
19
20
|
const PI_PROVIDER = 'npm:@bergetai/pi-provider';
|
|
20
21
|
const OPENCODE_PLUGIN_NAME = '@bergetai/opencode-auth';
|
|
21
22
|
const PI_PROVIDER_NAME = '@bergetai/pi-provider';
|
|
@@ -30,10 +31,14 @@ export interface WizardDeps {
|
|
|
30
31
|
prompter: Prompter;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
export async function
|
|
34
|
+
export async function runInit(deps: WizardDeps): Promise<void> {
|
|
34
35
|
const { apiKeyService, authService, commands, cwd, files, homeDir, prompter } = deps;
|
|
35
36
|
|
|
36
|
-
prompter.intro('
|
|
37
|
+
prompter.intro(`${chalk.bgGreen.black(' berget code ')}`);
|
|
38
|
+
prompter.note(
|
|
39
|
+
`Ask questions and report bugs on our GitHub repository:\n\n${chalk.cyan.underline('https://github.com/berget-ai/cli')}`,
|
|
40
|
+
'Need help?',
|
|
41
|
+
);
|
|
37
42
|
|
|
38
43
|
const ocState = await getOpencodeState(files, homeDir, cwd);
|
|
39
44
|
const piState = await getPiState(files, homeDir, cwd);
|
|
@@ -121,14 +126,14 @@ export async function runSetup(deps: WizardDeps): Promise<void> {
|
|
|
121
126
|
}
|
|
122
127
|
}
|
|
123
128
|
|
|
124
|
-
prompter.outro('
|
|
129
|
+
prompter.outro('Initialization complete!');
|
|
125
130
|
}
|
|
126
131
|
|
|
127
132
|
// ─── OpenCode ────────────────────────────────────────────────────────────────
|
|
128
133
|
|
|
129
|
-
export async function
|
|
134
|
+
export async function runInitCommand(): Promise<void> {
|
|
130
135
|
try {
|
|
131
|
-
await
|
|
136
|
+
await runInit({
|
|
132
137
|
apiKeyService: ApiKeyService.getInstance(),
|
|
133
138
|
authService: AuthService.getInstance(),
|
|
134
139
|
commands: new SpawnCommandRunner(),
|
|
@@ -154,24 +159,7 @@ export async function runSetupCommand(): Promise<void> {
|
|
|
154
159
|
}
|
|
155
160
|
}
|
|
156
161
|
|
|
157
|
-
// ───
|
|
158
|
-
|
|
159
|
-
function generateDiff(oldText: string, newText: string, filePath: string): string {
|
|
160
|
-
const oldLines = oldText.split('\n');
|
|
161
|
-
const newLines = newText.split('\n');
|
|
162
|
-
let result = `--- ${filePath}\n+++ ${filePath}\n`;
|
|
163
|
-
|
|
164
|
-
const maxLength = Math.max(oldLines.length, newLines.length);
|
|
165
|
-
for (let index = 0; index < maxLength; index++) {
|
|
166
|
-
const oldLine = oldLines[index];
|
|
167
|
-
const newLine = newLines[index];
|
|
168
|
-
if (oldLine !== newLine) {
|
|
169
|
-
if (oldLine !== undefined) result += `- ${oldLine}\n`;
|
|
170
|
-
if (newLine !== undefined) result += `+ ${newLine}\n`;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return result.trimEnd();
|
|
174
|
-
}
|
|
162
|
+
// ─── OpenCode Config Helpers ──────────────────────────────────────────────────
|
|
175
163
|
|
|
176
164
|
function generateModifiedContent(existingContent: null | string, configPath: string): string {
|
|
177
165
|
if (configPath.endsWith('.jsonc')) {
|
|
@@ -381,9 +369,9 @@ async function setupOpenCode(deps: {
|
|
|
381
369
|
}
|
|
382
370
|
|
|
383
371
|
if (existingContent) {
|
|
384
|
-
prompter.note(
|
|
372
|
+
prompter.note(`OpenCode config will be updated at:\n ${configPath}`, 'Config update');
|
|
385
373
|
} else {
|
|
386
|
-
prompter.note(`
|
|
374
|
+
prompter.note(`OpenCode config will be created at:\n ${configPath}`, 'Config update');
|
|
387
375
|
}
|
|
388
376
|
|
|
389
377
|
const shouldWrite = await prompter.confirm({
|