berget 2.2.10 → 2.2.12
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 +47 -37
- package/dist/src/commands/code/auth-sync.js +3 -3
- package/dist/src/commands/code/{setup.js → init.js} +73 -87
- 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 +49 -39
- package/src/commands/code/auth-sync.ts +3 -3
- package/src/commands/code/{setup.ts → init.ts} +82 -97
- package/src/commands/code/ports/prompter.ts +1 -0
- 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.21');
|
|
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();
|
|
@@ -95,12 +95,13 @@ describe('runSetup', () => {
|
|
|
95
95
|
prompter: new FakePrompter([
|
|
96
96
|
select('pi'),
|
|
97
97
|
select('project'),
|
|
98
|
+
confirm(true, 'Set up an agent for Pi?'),
|
|
98
99
|
select('fullstack'), // Agent selection
|
|
99
100
|
confirm(true, 'Create'),
|
|
100
101
|
]),
|
|
101
102
|
});
|
|
102
103
|
|
|
103
|
-
await
|
|
104
|
+
await runInit(deps);
|
|
104
105
|
|
|
105
106
|
const commands = deps.commands as FakeCommandRunner;
|
|
106
107
|
expect(commands.calls.length).toBeGreaterThan(0);
|
|
@@ -116,11 +117,11 @@ describe('runSetup', () => {
|
|
|
116
117
|
prompter: new FakePrompter([
|
|
117
118
|
select('pi'),
|
|
118
119
|
select('project'),
|
|
119
|
-
|
|
120
|
+
confirm(false, 'Set up an agent for Pi?'),
|
|
120
121
|
]),
|
|
121
122
|
});
|
|
122
123
|
|
|
123
|
-
await
|
|
124
|
+
await runInit(deps);
|
|
124
125
|
|
|
125
126
|
const files = deps.files as FakeFileStore;
|
|
126
127
|
const written = files.getWrittenFiles();
|
|
@@ -139,7 +140,7 @@ describe('runSetup', () => {
|
|
|
139
140
|
});
|
|
140
141
|
|
|
141
142
|
// Simulate opencode not being installed
|
|
142
|
-
await expect(
|
|
143
|
+
await expect(runInit(deps)).rejects.toBeInstanceOf(PrerequisiteError);
|
|
143
144
|
});
|
|
144
145
|
});
|
|
145
146
|
|
|
@@ -149,7 +150,7 @@ describe('runSetup', () => {
|
|
|
149
150
|
prompter: new FakePrompter([select(CANCEL)]),
|
|
150
151
|
});
|
|
151
152
|
|
|
152
|
-
await expect(
|
|
153
|
+
await expect(runInit(deps)).rejects.toBeInstanceOf(CancelledError);
|
|
153
154
|
});
|
|
154
155
|
|
|
155
156
|
it('throws CancelledError when user cancels at write confirmation', async () => {
|
|
@@ -161,7 +162,7 @@ describe('runSetup', () => {
|
|
|
161
162
|
]),
|
|
162
163
|
});
|
|
163
164
|
|
|
164
|
-
await expect(
|
|
165
|
+
await expect(runInit(deps)).rejects.toBeInstanceOf(CancelledError);
|
|
165
166
|
});
|
|
166
167
|
|
|
167
168
|
it('throws CancelledError when user cancels at agent write confirmation (opencode)', async () => {
|
|
@@ -175,7 +176,7 @@ describe('runSetup', () => {
|
|
|
175
176
|
]),
|
|
176
177
|
});
|
|
177
178
|
|
|
178
|
-
await expect(
|
|
179
|
+
await expect(runInit(deps)).rejects.toBeInstanceOf(CancelledError);
|
|
179
180
|
});
|
|
180
181
|
|
|
181
182
|
it('throws CancelledError when user cancels at agent write confirmation (pi)', async () => {
|
|
@@ -184,12 +185,13 @@ describe('runSetup', () => {
|
|
|
184
185
|
prompter: new FakePrompter([
|
|
185
186
|
select('pi'),
|
|
186
187
|
select('project'),
|
|
188
|
+
confirm(true, 'Set up an agent for Pi?'),
|
|
187
189
|
select('fullstack'),
|
|
188
190
|
confirm(false, /Create|Overwrite/),
|
|
189
191
|
]),
|
|
190
192
|
});
|
|
191
193
|
|
|
192
|
-
await expect(
|
|
194
|
+
await expect(runInit(deps)).rejects.toBeInstanceOf(CancelledError);
|
|
193
195
|
});
|
|
194
196
|
});
|
|
195
197
|
|
|
@@ -213,13 +215,13 @@ describe('runSetup', () => {
|
|
|
213
215
|
}),
|
|
214
216
|
);
|
|
215
217
|
|
|
216
|
-
await
|
|
218
|
+
await runInit(deps);
|
|
217
219
|
|
|
218
220
|
const written = files.getWrittenFiles();
|
|
219
221
|
const config = JSON.parse(written.get('/home/user/project/opencode.json')!);
|
|
220
222
|
expect(config.customField).toBe('should-preserve');
|
|
221
223
|
expect(config.plugin).toContain('other-plugin');
|
|
222
|
-
expect(config.plugin).toContain('@bergetai/opencode-auth');
|
|
224
|
+
expect(config.plugin).toContain('@bergetai/opencode-auth@1.0.21');
|
|
223
225
|
});
|
|
224
226
|
|
|
225
227
|
it('preserves jsonc comments when updating', async () => {
|
|
@@ -243,7 +245,7 @@ describe('runSetup', () => {
|
|
|
243
245
|
}`,
|
|
244
246
|
);
|
|
245
247
|
|
|
246
|
-
await
|
|
248
|
+
await runInit(deps);
|
|
247
249
|
|
|
248
250
|
const written = files.getWrittenFiles();
|
|
249
251
|
const content = written.get('/home/user/project/opencode.jsonc')!;
|
|
@@ -265,20 +267,20 @@ describe('runSetup', () => {
|
|
|
265
267
|
JSON.stringify(
|
|
266
268
|
{
|
|
267
269
|
$schema: 'https://opencode.ai/config.json',
|
|
268
|
-
plugin: ['@bergetai/opencode-auth'],
|
|
270
|
+
plugin: ['@bergetai/opencode-auth@1.0.21'],
|
|
269
271
|
},
|
|
270
272
|
null,
|
|
271
273
|
2,
|
|
272
274
|
) + '\n',
|
|
273
275
|
);
|
|
274
276
|
|
|
275
|
-
await
|
|
277
|
+
await runInit(deps);
|
|
276
278
|
|
|
277
279
|
// Check that no write happened — content should be unchanged
|
|
278
280
|
const written = files.getWrittenFiles();
|
|
279
281
|
const content = written.get('/home/user/project/opencode.json')!;
|
|
280
282
|
const config = JSON.parse(content);
|
|
281
|
-
expect(config.plugin).toEqual(['@bergetai/opencode-auth']);
|
|
283
|
+
expect(config.plugin).toEqual(['@bergetai/opencode-auth@1.0.21']);
|
|
282
284
|
expect(content).toContain('$schema');
|
|
283
285
|
});
|
|
284
286
|
|
|
@@ -288,6 +290,7 @@ describe('runSetup', () => {
|
|
|
288
290
|
prompter: new FakePrompter([
|
|
289
291
|
select('pi'),
|
|
290
292
|
select('project'),
|
|
293
|
+
confirm(true, 'Set up an agent for Pi?'),
|
|
291
294
|
select('fullstack'),
|
|
292
295
|
confirm(true, 'Create'),
|
|
293
296
|
]),
|
|
@@ -302,7 +305,7 @@ describe('runSetup', () => {
|
|
|
302
305
|
}),
|
|
303
306
|
);
|
|
304
307
|
|
|
305
|
-
await
|
|
308
|
+
await runInit(deps);
|
|
306
309
|
|
|
307
310
|
const written = files.getWrittenFiles();
|
|
308
311
|
const settings = JSON.parse(written.get('/home/user/project/.pi/settings.json')!);
|
|
@@ -321,7 +324,7 @@ describe('runSetup', () => {
|
|
|
321
324
|
]),
|
|
322
325
|
});
|
|
323
326
|
|
|
324
|
-
await
|
|
327
|
+
await runInit(deps);
|
|
325
328
|
|
|
326
329
|
const files = deps.files as FakeFileStore;
|
|
327
330
|
const written = files.getWrittenFiles();
|
|
@@ -336,12 +339,13 @@ describe('runSetup', () => {
|
|
|
336
339
|
prompter: new FakePrompter([
|
|
337
340
|
select('pi'),
|
|
338
341
|
select('project'),
|
|
342
|
+
confirm(true, 'Set up an agent for Pi?'),
|
|
339
343
|
select('fullstack'),
|
|
340
344
|
confirm(true, 'Create'),
|
|
341
345
|
]),
|
|
342
346
|
});
|
|
343
347
|
|
|
344
|
-
await
|
|
348
|
+
await runInit(deps);
|
|
345
349
|
|
|
346
350
|
const commands = deps.commands as FakeCommandRunner;
|
|
347
351
|
const installCall = commands.calls.find((c) => c.command === 'pi');
|
|
@@ -359,7 +363,7 @@ describe('runSetup', () => {
|
|
|
359
363
|
prompter: new FakePrompter([select('pi'), select('project')]),
|
|
360
364
|
});
|
|
361
365
|
|
|
362
|
-
await expect(
|
|
366
|
+
await expect(runInit(deps)).rejects.toBeInstanceOf(CommandFailedError);
|
|
363
367
|
});
|
|
364
368
|
});
|
|
365
369
|
|
|
@@ -382,7 +386,7 @@ describe('runSetup', () => {
|
|
|
382
386
|
]),
|
|
383
387
|
});
|
|
384
388
|
|
|
385
|
-
await
|
|
389
|
+
await runInit(deps);
|
|
386
390
|
|
|
387
391
|
const prompter = deps.prompter as FakePrompter;
|
|
388
392
|
const notes = prompter.calls.filter((c) => c.method === 'note');
|
|
@@ -399,12 +403,13 @@ describe('runSetup', () => {
|
|
|
399
403
|
prompter: new FakePrompter([
|
|
400
404
|
select('pi'),
|
|
401
405
|
select('project'),
|
|
406
|
+
confirm(true, 'Set up an agent for Pi?'),
|
|
402
407
|
select('fullstack'),
|
|
403
408
|
confirm(true, 'Create'),
|
|
404
409
|
]),
|
|
405
410
|
});
|
|
406
411
|
|
|
407
|
-
await
|
|
412
|
+
await runInit(deps);
|
|
408
413
|
|
|
409
414
|
const prompter = deps.prompter as FakePrompter;
|
|
410
415
|
const notes = prompter.calls.filter((c) => c.method === 'note');
|
|
@@ -423,12 +428,13 @@ describe('runSetup', () => {
|
|
|
423
428
|
select('pi'),
|
|
424
429
|
select('project'),
|
|
425
430
|
confirm(true), // API key creation prompt
|
|
431
|
+
confirm(true, 'Set up an agent for Pi?'),
|
|
426
432
|
select('fullstack'),
|
|
427
433
|
confirm(true, 'Create'),
|
|
428
434
|
]),
|
|
429
435
|
});
|
|
430
436
|
|
|
431
|
-
await
|
|
437
|
+
await runInit(deps);
|
|
432
438
|
|
|
433
439
|
const written = files.getWrittenFiles();
|
|
434
440
|
expect(written.has('/home/user/.pi/agent/auth.json')).toBe(true);
|
|
@@ -459,7 +465,7 @@ describe('runSetup', () => {
|
|
|
459
465
|
]),
|
|
460
466
|
});
|
|
461
467
|
|
|
462
|
-
await
|
|
468
|
+
await runInit(deps);
|
|
463
469
|
|
|
464
470
|
const written = files.getWrittenFiles();
|
|
465
471
|
const parsed = JSON.parse(written.get('/home/user/.local/share/opencode/auth.json')!);
|
|
@@ -479,7 +485,7 @@ describe('runSetup', () => {
|
|
|
479
485
|
]),
|
|
480
486
|
});
|
|
481
487
|
|
|
482
|
-
await
|
|
488
|
+
await runInit(deps);
|
|
483
489
|
|
|
484
490
|
const files = deps.files as FakeFileStore;
|
|
485
491
|
const written = files.getWrittenFiles();
|
|
@@ -497,7 +503,7 @@ describe('runSetup', () => {
|
|
|
497
503
|
]),
|
|
498
504
|
});
|
|
499
505
|
|
|
500
|
-
await
|
|
506
|
+
await runInit(deps);
|
|
501
507
|
|
|
502
508
|
const files = deps.files as FakeFileStore;
|
|
503
509
|
const written = files.getWrittenFiles();
|
|
@@ -517,7 +523,7 @@ describe('runSetup', () => {
|
|
|
517
523
|
]),
|
|
518
524
|
});
|
|
519
525
|
|
|
520
|
-
await
|
|
526
|
+
await runInit(deps);
|
|
521
527
|
|
|
522
528
|
const files = deps.files as FakeFileStore;
|
|
523
529
|
const written = files.getWrittenFiles();
|
|
@@ -530,12 +536,13 @@ describe('runSetup', () => {
|
|
|
530
536
|
prompter: new FakePrompter([
|
|
531
537
|
select('pi'),
|
|
532
538
|
select('project'),
|
|
539
|
+
confirm(true, 'Set up an agent for Pi?'),
|
|
533
540
|
select('fullstack'),
|
|
534
541
|
confirm(true, 'Create'),
|
|
535
542
|
]),
|
|
536
543
|
});
|
|
537
544
|
|
|
538
|
-
await
|
|
545
|
+
await runInit(deps);
|
|
539
546
|
|
|
540
547
|
const files = deps.files as FakeFileStore;
|
|
541
548
|
const written = files.getWrittenFiles();
|
|
@@ -548,12 +555,13 @@ describe('runSetup', () => {
|
|
|
548
555
|
prompter: new FakePrompter([
|
|
549
556
|
select('pi'),
|
|
550
557
|
select('global'),
|
|
558
|
+
confirm(true, 'Set up an agent for Pi?'),
|
|
551
559
|
select('backend'),
|
|
552
560
|
confirm(true, 'Create'),
|
|
553
561
|
]),
|
|
554
562
|
});
|
|
555
563
|
|
|
556
|
-
await
|
|
564
|
+
await runInit(deps);
|
|
557
565
|
|
|
558
566
|
const files = deps.files as FakeFileStore;
|
|
559
567
|
const written = files.getWrittenFiles();
|
|
@@ -572,7 +580,7 @@ describe('runSetup', () => {
|
|
|
572
580
|
});
|
|
573
581
|
|
|
574
582
|
// First run writes the files
|
|
575
|
-
await
|
|
583
|
+
await runInit(deps);
|
|
576
584
|
|
|
577
585
|
const files = deps.files as FakeFileStore;
|
|
578
586
|
const firstBackend = files
|
|
@@ -582,17 +590,18 @@ describe('runSetup', () => {
|
|
|
582
590
|
.getWrittenFiles()
|
|
583
591
|
.get('/home/user/project/.opencode/agents/frontend.md');
|
|
584
592
|
|
|
585
|
-
// Second run with exact same content should
|
|
593
|
+
// Second run with exact same content should prompt for confirmation
|
|
586
594
|
const deps2 = makeDeps({
|
|
587
595
|
files,
|
|
588
596
|
prompter: new FakePrompter([
|
|
589
597
|
select('opencode'),
|
|
590
598
|
select('project'),
|
|
591
599
|
multiselect(['backend', 'frontend']),
|
|
600
|
+
confirm(true, 'Write'),
|
|
592
601
|
]),
|
|
593
602
|
});
|
|
594
603
|
|
|
595
|
-
await
|
|
604
|
+
await runInit(deps2);
|
|
596
605
|
|
|
597
606
|
// Content should be unchanged
|
|
598
607
|
expect(files.getWrittenFiles().get('/home/user/project/.opencode/agents/backend.md')).toBe(
|
|
@@ -613,12 +622,13 @@ describe('runSetup', () => {
|
|
|
613
622
|
prompter: new FakePrompter([
|
|
614
623
|
select('pi'),
|
|
615
624
|
select('project'),
|
|
625
|
+
confirm(true, 'Set up an agent for Pi?'),
|
|
616
626
|
select('fullstack'),
|
|
617
|
-
confirm(true, '
|
|
627
|
+
confirm(true, 'SYSTEM.md already exists'),
|
|
618
628
|
]),
|
|
619
629
|
});
|
|
620
630
|
|
|
621
|
-
await
|
|
631
|
+
await runInit(deps);
|
|
622
632
|
|
|
623
633
|
const written = files.getWrittenFiles();
|
|
624
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);
|