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
|
@@ -1,60 +1,62 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import type { ApiKeyServicePort, AuthServicePort } from '../ports/auth-services';
|
|
4
|
+
|
|
5
|
+
import { CancelledError, CommandFailedError, PrerequisiteError } from '../errors';
|
|
6
|
+
import { runSetup } from '../setup';
|
|
7
|
+
import { FakeApiKeyService } from './fake-api-key-service';
|
|
8
|
+
import { FakeAuthService } from './fake-auth-service';
|
|
9
|
+
import { FakeCommandRunner } from './fake-command-runner';
|
|
10
|
+
import { FakeFileStore } from './fake-file-store';
|
|
11
|
+
import { CANCEL, confirm, FakePrompter, multiselect, select } from './fake-prompter';
|
|
10
12
|
|
|
11
13
|
const makeDeps = (
|
|
12
|
-
overrides: Partial<Parameters<typeof runSetup>[0]> = {}
|
|
14
|
+
overrides: Partial<Parameters<typeof runSetup>[0]> = {},
|
|
13
15
|
): Parameters<typeof runSetup>[0] => {
|
|
14
16
|
return {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
apiKeyService:
|
|
18
|
+
(overrides.apiKeyService as ApiKeyServicePort) ?? new FakeApiKeyService('sk_ber_test'),
|
|
19
|
+
authService: (overrides.authService as AuthServicePort) ?? new FakeAuthService(false),
|
|
17
20
|
commands:
|
|
18
21
|
overrides.commands ??
|
|
19
22
|
new FakeCommandRunner()
|
|
20
|
-
.handle(
|
|
21
|
-
.handle(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
cwd: "/home/user/project",
|
|
23
|
+
.handle('opencode --version', 'mocked')
|
|
24
|
+
.handle('pi --version', 'mocked'),
|
|
25
|
+
cwd: '/home/user/project',
|
|
26
|
+
files: overrides.files ?? new FakeFileStore(),
|
|
27
|
+
homeDir: '/home/user',
|
|
28
|
+
prompter: overrides.prompter ?? new FakePrompter([]),
|
|
27
29
|
...Object.fromEntries(
|
|
28
30
|
Object.entries(overrides).filter(
|
|
29
31
|
([k]) =>
|
|
30
|
-
k !==
|
|
31
|
-
k !==
|
|
32
|
-
k !==
|
|
33
|
-
k !==
|
|
34
|
-
k !==
|
|
35
|
-
)
|
|
32
|
+
k !== 'prompter' &&
|
|
33
|
+
k !== 'files' &&
|
|
34
|
+
k !== 'commands' &&
|
|
35
|
+
k !== 'authService' &&
|
|
36
|
+
k !== 'apiKeyService',
|
|
37
|
+
),
|
|
36
38
|
),
|
|
37
39
|
};
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
function base64urlEncode(data: string): string {
|
|
41
|
-
return Buffer.from(data).toString(
|
|
43
|
+
return Buffer.from(data).toString('base64url');
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
function makeJwt(payload: Record<string, unknown>): string {
|
|
45
|
-
const header = base64urlEncode(JSON.stringify({ alg:
|
|
47
|
+
const header = base64urlEncode(JSON.stringify({ alg: 'none', typ: 'JWT' }));
|
|
46
48
|
const body = base64urlEncode(JSON.stringify(payload));
|
|
47
49
|
return `${header}.${body}.signature`;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
describe(
|
|
51
|
-
describe(
|
|
52
|
-
it(
|
|
52
|
+
describe('runSetup', () => {
|
|
53
|
+
describe('happy path', () => {
|
|
54
|
+
it('sets up opencode project without existing config', async () => {
|
|
53
55
|
const deps = makeDeps({
|
|
54
56
|
prompter: new FakePrompter([
|
|
55
|
-
select(
|
|
56
|
-
select(
|
|
57
|
-
confirm(true,
|
|
57
|
+
select('opencode'),
|
|
58
|
+
select('project'),
|
|
59
|
+
confirm(true, 'Create'), // Config write
|
|
58
60
|
multiselect([]), // No agents selected
|
|
59
61
|
]),
|
|
60
62
|
});
|
|
@@ -63,17 +65,17 @@ describe("runSetup", () => {
|
|
|
63
65
|
|
|
64
66
|
const files = deps.files as FakeFileStore;
|
|
65
67
|
const written = files.getWrittenFiles();
|
|
66
|
-
expect(written.has(
|
|
67
|
-
const config = JSON.parse(written.get(
|
|
68
|
-
expect(config.plugin).toContain(
|
|
68
|
+
expect(written.has('/home/user/project/opencode.json')).toBe(true);
|
|
69
|
+
const config = JSON.parse(written.get('/home/user/project/opencode.json')!);
|
|
70
|
+
expect(config.plugin).toContain('@bergetai/opencode-auth');
|
|
69
71
|
});
|
|
70
72
|
|
|
71
|
-
it(
|
|
73
|
+
it('sets up opencode globally without existing config', async () => {
|
|
72
74
|
const deps = makeDeps({
|
|
73
75
|
prompter: new FakePrompter([
|
|
74
|
-
select(
|
|
75
|
-
select(
|
|
76
|
-
confirm(true,
|
|
76
|
+
select('opencode'),
|
|
77
|
+
select('global'),
|
|
78
|
+
confirm(true, 'Create'), // Config write
|
|
77
79
|
multiselect([]), // No agents selected
|
|
78
80
|
]),
|
|
79
81
|
});
|
|
@@ -82,40 +84,40 @@ describe("runSetup", () => {
|
|
|
82
84
|
|
|
83
85
|
const files = deps.files as FakeFileStore;
|
|
84
86
|
const written = files.getWrittenFiles();
|
|
85
|
-
expect(written.has(
|
|
87
|
+
expect(written.has('/home/user/.config/opencode/opencode.json')).toBe(true);
|
|
86
88
|
});
|
|
87
89
|
|
|
88
|
-
it(
|
|
90
|
+
it('sets up pi project with fresh install', async () => {
|
|
89
91
|
const deps = makeDeps({
|
|
92
|
+
commands: new FakeCommandRunner()
|
|
93
|
+
.handle('pi --version', 'mocked') // For checkInstalled
|
|
94
|
+
.handle('pi install', ''), // For actual install
|
|
90
95
|
prompter: new FakePrompter([
|
|
91
|
-
select(
|
|
92
|
-
select(
|
|
93
|
-
select(
|
|
94
|
-
confirm(true,
|
|
96
|
+
select('pi'),
|
|
97
|
+
select('project'),
|
|
98
|
+
select('fullstack'), // Agent selection
|
|
99
|
+
confirm(true, 'Create'),
|
|
95
100
|
]),
|
|
96
|
-
commands: new FakeCommandRunner()
|
|
97
|
-
.handle("pi --version", "mocked") // For checkInstalled
|
|
98
|
-
.handle("pi install", ""), // For actual install
|
|
99
101
|
});
|
|
100
102
|
|
|
101
103
|
await runSetup(deps);
|
|
102
104
|
|
|
103
105
|
const commands = deps.commands as FakeCommandRunner;
|
|
104
106
|
expect(commands.calls.length).toBeGreaterThan(0);
|
|
105
|
-
const installCall = commands.calls.find(c => c.command ===
|
|
106
|
-
expect(installCall?.args).toContain(
|
|
107
|
+
const installCall = commands.calls.find((c) => c.command === 'pi');
|
|
108
|
+
expect(installCall?.args).toContain('npm:@bergetai/pi-provider');
|
|
107
109
|
});
|
|
108
110
|
|
|
109
|
-
it(
|
|
111
|
+
it('skips agent selection for pi project', async () => {
|
|
110
112
|
const deps = makeDeps({
|
|
113
|
+
commands: new FakeCommandRunner()
|
|
114
|
+
.handle('pi --version', 'mocked') // For checkInstalled
|
|
115
|
+
.handle('pi install', ''), // For actual install
|
|
111
116
|
prompter: new FakePrompter([
|
|
112
|
-
select(
|
|
113
|
-
select(
|
|
114
|
-
select(
|
|
117
|
+
select('pi'),
|
|
118
|
+
select('project'),
|
|
119
|
+
select('__skip__'), // Skip agent selection
|
|
115
120
|
]),
|
|
116
|
-
commands: new FakeCommandRunner()
|
|
117
|
-
.handle("pi --version", "mocked") // For checkInstalled
|
|
118
|
-
.handle("pi install", ""), // For actual install
|
|
119
121
|
});
|
|
120
122
|
|
|
121
123
|
await runSetup(deps);
|
|
@@ -124,16 +126,16 @@ describe("runSetup", () => {
|
|
|
124
126
|
const written = files.getWrittenFiles();
|
|
125
127
|
// Should not create any agent files
|
|
126
128
|
for (const path of written.keys()) {
|
|
127
|
-
expect(path).not.toContain(
|
|
129
|
+
expect(path).not.toContain('SYSTEM.md');
|
|
128
130
|
}
|
|
129
131
|
});
|
|
130
132
|
});
|
|
131
133
|
|
|
132
|
-
describe(
|
|
133
|
-
it(
|
|
134
|
+
describe('prerequisites', () => {
|
|
135
|
+
it('throws PrerequisiteError when opencode is not installed', async () => {
|
|
134
136
|
const deps = makeDeps({
|
|
135
|
-
prompter: new FakePrompter([select("opencode"), select("project")]),
|
|
136
137
|
commands: new FakeCommandRunner(),
|
|
138
|
+
prompter: new FakePrompter([select('opencode'), select('project')]),
|
|
137
139
|
});
|
|
138
140
|
|
|
139
141
|
// Simulate opencode not being installed
|
|
@@ -141,8 +143,8 @@ describe("runSetup", () => {
|
|
|
141
143
|
});
|
|
142
144
|
});
|
|
143
145
|
|
|
144
|
-
describe(
|
|
145
|
-
it(
|
|
146
|
+
describe('cancellation', () => {
|
|
147
|
+
it('throws CancelledError when user cancels at tool selection', async () => {
|
|
146
148
|
const deps = makeDeps({
|
|
147
149
|
prompter: new FakePrompter([select(CANCEL)]),
|
|
148
150
|
});
|
|
@@ -150,171 +152,171 @@ describe("runSetup", () => {
|
|
|
150
152
|
await expect(runSetup(deps)).rejects.toBeInstanceOf(CancelledError);
|
|
151
153
|
});
|
|
152
154
|
|
|
153
|
-
it(
|
|
155
|
+
it('throws CancelledError when user cancels at write confirmation', async () => {
|
|
154
156
|
const deps = makeDeps({
|
|
155
157
|
prompter: new FakePrompter([
|
|
156
|
-
select(
|
|
157
|
-
select(
|
|
158
|
-
confirm(false,
|
|
158
|
+
select('opencode'),
|
|
159
|
+
select('project'),
|
|
160
|
+
confirm(false, 'Create'),
|
|
159
161
|
]),
|
|
160
162
|
});
|
|
161
163
|
|
|
162
164
|
await expect(runSetup(deps)).rejects.toBeInstanceOf(CancelledError);
|
|
163
165
|
});
|
|
164
166
|
|
|
165
|
-
it(
|
|
167
|
+
it('throws CancelledError when user cancels at agent write confirmation (opencode)', async () => {
|
|
166
168
|
const deps = makeDeps({
|
|
167
169
|
prompter: new FakePrompter([
|
|
168
|
-
select(
|
|
169
|
-
select(
|
|
170
|
-
confirm(true,
|
|
171
|
-
multiselect([
|
|
172
|
-
confirm(false,
|
|
170
|
+
select('opencode'),
|
|
171
|
+
select('project'),
|
|
172
|
+
confirm(true, 'Create'),
|
|
173
|
+
multiselect(['backend', 'frontend']),
|
|
174
|
+
confirm(false, 'agent'),
|
|
173
175
|
]),
|
|
174
176
|
});
|
|
175
177
|
|
|
176
178
|
await expect(runSetup(deps)).rejects.toBeInstanceOf(CancelledError);
|
|
177
179
|
});
|
|
178
180
|
|
|
179
|
-
it(
|
|
181
|
+
it('throws CancelledError when user cancels at agent write confirmation (pi)', async () => {
|
|
180
182
|
const deps = makeDeps({
|
|
183
|
+
commands: new FakeCommandRunner().handle('pi --version', 'mocked').handle('pi install', ''),
|
|
181
184
|
prompter: new FakePrompter([
|
|
182
|
-
select(
|
|
183
|
-
select(
|
|
184
|
-
select(
|
|
185
|
+
select('pi'),
|
|
186
|
+
select('project'),
|
|
187
|
+
select('fullstack'),
|
|
185
188
|
confirm(false, /Create|Overwrite/),
|
|
186
189
|
]),
|
|
187
|
-
commands: new FakeCommandRunner().handle("pi --version", "mocked").handle("pi install", ""),
|
|
188
190
|
});
|
|
189
191
|
|
|
190
192
|
await expect(runSetup(deps)).rejects.toBeInstanceOf(CancelledError);
|
|
191
193
|
});
|
|
192
194
|
});
|
|
193
195
|
|
|
194
|
-
describe(
|
|
195
|
-
it(
|
|
196
|
+
describe('file operations', () => {
|
|
197
|
+
it('preserves existing configuration keys when updating', async () => {
|
|
196
198
|
const deps = makeDeps({
|
|
197
199
|
prompter: new FakePrompter([
|
|
198
|
-
select(
|
|
199
|
-
select(
|
|
200
|
-
confirm(true,
|
|
200
|
+
select('opencode'),
|
|
201
|
+
select('project'),
|
|
202
|
+
confirm(true, 'Write'),
|
|
201
203
|
multiselect([]),
|
|
202
204
|
]),
|
|
203
205
|
});
|
|
204
206
|
|
|
205
207
|
const files = deps.files as FakeFileStore;
|
|
206
208
|
files.seed(
|
|
207
|
-
|
|
209
|
+
'/home/user/project/opencode.json',
|
|
208
210
|
JSON.stringify({
|
|
209
|
-
customField:
|
|
210
|
-
plugin: [
|
|
211
|
-
})
|
|
211
|
+
customField: 'should-preserve',
|
|
212
|
+
plugin: ['other-plugin'],
|
|
213
|
+
}),
|
|
212
214
|
);
|
|
213
215
|
|
|
214
216
|
await runSetup(deps);
|
|
215
217
|
|
|
216
218
|
const written = files.getWrittenFiles();
|
|
217
|
-
const config = JSON.parse(written.get(
|
|
218
|
-
expect(config.customField).toBe(
|
|
219
|
-
expect(config.plugin).toContain(
|
|
220
|
-
expect(config.plugin).toContain(
|
|
219
|
+
const config = JSON.parse(written.get('/home/user/project/opencode.json')!);
|
|
220
|
+
expect(config.customField).toBe('should-preserve');
|
|
221
|
+
expect(config.plugin).toContain('other-plugin');
|
|
222
|
+
expect(config.plugin).toContain('@bergetai/opencode-auth');
|
|
221
223
|
});
|
|
222
224
|
|
|
223
|
-
it(
|
|
225
|
+
it('preserves jsonc comments when updating', async () => {
|
|
224
226
|
const deps = makeDeps({
|
|
225
227
|
prompter: new FakePrompter([
|
|
226
|
-
select(
|
|
227
|
-
select(
|
|
228
|
-
confirm(true,
|
|
228
|
+
select('opencode'),
|
|
229
|
+
select('project'),
|
|
230
|
+
confirm(true, 'Write'),
|
|
229
231
|
multiselect([]),
|
|
230
232
|
]),
|
|
231
233
|
});
|
|
232
234
|
|
|
233
235
|
const files = deps.files as FakeFileStore;
|
|
234
236
|
files.seed(
|
|
235
|
-
|
|
237
|
+
'/home/user/project/opencode.jsonc',
|
|
236
238
|
`{
|
|
237
239
|
// This is my custom config
|
|
238
240
|
"customField": "should-preserve",
|
|
239
241
|
/* block comment explaining plugin */
|
|
240
242
|
"plugin": ["other-plugin"]
|
|
241
|
-
}
|
|
243
|
+
}`,
|
|
242
244
|
);
|
|
243
245
|
|
|
244
246
|
await runSetup(deps);
|
|
245
247
|
|
|
246
248
|
const written = files.getWrittenFiles();
|
|
247
|
-
const content = written.get(
|
|
248
|
-
expect(content).toContain(
|
|
249
|
-
expect(content).toContain(
|
|
249
|
+
const content = written.get('/home/user/project/opencode.jsonc')!;
|
|
250
|
+
expect(content).toContain('// This is my custom config');
|
|
251
|
+
expect(content).toContain('/* block comment explaining plugin */');
|
|
250
252
|
expect(content).toContain('"customField": "should-preserve"');
|
|
251
|
-
expect(content).toContain(
|
|
253
|
+
expect(content).toContain('@bergetai/opencode-auth');
|
|
252
254
|
});
|
|
253
255
|
|
|
254
|
-
it(
|
|
256
|
+
it('shows no changes needed when config is already up to date', async () => {
|
|
255
257
|
const deps = makeDeps({
|
|
256
|
-
prompter: new FakePrompter([select(
|
|
258
|
+
prompter: new FakePrompter([select('opencode'), select('project'), multiselect([])]),
|
|
257
259
|
});
|
|
258
260
|
|
|
259
261
|
const files = deps.files as FakeFileStore;
|
|
260
262
|
// Already has the exact same plugin version
|
|
261
263
|
files.seed(
|
|
262
|
-
|
|
264
|
+
'/home/user/project/opencode.json',
|
|
263
265
|
JSON.stringify(
|
|
264
266
|
{
|
|
265
|
-
$schema:
|
|
266
|
-
plugin: [
|
|
267
|
+
$schema: 'https://opencode.ai/config.json',
|
|
268
|
+
plugin: ['@bergetai/opencode-auth'],
|
|
267
269
|
},
|
|
268
270
|
null,
|
|
269
|
-
2
|
|
270
|
-
) +
|
|
271
|
+
2,
|
|
272
|
+
) + '\n',
|
|
271
273
|
);
|
|
272
274
|
|
|
273
275
|
await runSetup(deps);
|
|
274
276
|
|
|
275
277
|
// Check that no write happened — content should be unchanged
|
|
276
278
|
const written = files.getWrittenFiles();
|
|
277
|
-
const content = written.get(
|
|
279
|
+
const content = written.get('/home/user/project/opencode.json')!;
|
|
278
280
|
const config = JSON.parse(content);
|
|
279
|
-
expect(config.plugin).toEqual([
|
|
280
|
-
expect(content).toContain(
|
|
281
|
+
expect(config.plugin).toEqual(['@bergetai/opencode-auth']);
|
|
282
|
+
expect(content).toContain('$schema');
|
|
281
283
|
});
|
|
282
284
|
|
|
283
|
-
it(
|
|
285
|
+
it('preserves existing Pi settings when setting defaultProvider', async () => {
|
|
284
286
|
const deps = makeDeps({
|
|
287
|
+
commands: new FakeCommandRunner().handle('pi --version', 'mocked').handle('pi install', ''),
|
|
285
288
|
prompter: new FakePrompter([
|
|
286
|
-
select(
|
|
287
|
-
select(
|
|
288
|
-
select(
|
|
289
|
-
confirm(true,
|
|
289
|
+
select('pi'),
|
|
290
|
+
select('project'),
|
|
291
|
+
select('fullstack'),
|
|
292
|
+
confirm(true, 'Create'),
|
|
290
293
|
]),
|
|
291
|
-
commands: new FakeCommandRunner().handle("pi --version", "mocked").handle("pi install", ""),
|
|
292
294
|
});
|
|
293
295
|
|
|
294
296
|
const files = deps.files as FakeFileStore;
|
|
295
297
|
files.seed(
|
|
296
|
-
|
|
298
|
+
'/home/user/project/.pi/settings.json',
|
|
297
299
|
JSON.stringify({
|
|
298
|
-
existingKey: "should-preserve",
|
|
299
300
|
anotherSetting: true,
|
|
300
|
-
|
|
301
|
+
existingKey: 'should-preserve',
|
|
302
|
+
}),
|
|
301
303
|
);
|
|
302
304
|
|
|
303
305
|
await runSetup(deps);
|
|
304
306
|
|
|
305
307
|
const written = files.getWrittenFiles();
|
|
306
|
-
const settings = JSON.parse(written.get(
|
|
307
|
-
expect(settings.existingKey).toBe(
|
|
308
|
+
const settings = JSON.parse(written.get('/home/user/project/.pi/settings.json')!);
|
|
309
|
+
expect(settings.existingKey).toBe('should-preserve');
|
|
308
310
|
expect(settings.anotherSetting).toBe(true);
|
|
309
|
-
expect(settings.defaultProvider).toBe(
|
|
311
|
+
expect(settings.defaultProvider).toBe('berget');
|
|
310
312
|
});
|
|
311
313
|
|
|
312
|
-
it(
|
|
314
|
+
it('creates parent directories when writing files', async () => {
|
|
313
315
|
const deps = makeDeps({
|
|
314
316
|
prompter: new FakePrompter([
|
|
315
|
-
select(
|
|
316
|
-
select(
|
|
317
|
-
confirm(true,
|
|
317
|
+
select('opencode'),
|
|
318
|
+
select('global'),
|
|
319
|
+
confirm(true, 'Create'),
|
|
318
320
|
multiselect([]),
|
|
319
321
|
]),
|
|
320
322
|
});
|
|
@@ -323,157 +325,157 @@ describe("runSetup", () => {
|
|
|
323
325
|
|
|
324
326
|
const files = deps.files as FakeFileStore;
|
|
325
327
|
const written = files.getWrittenFiles();
|
|
326
|
-
expect(written.has(
|
|
328
|
+
expect(written.has('/home/user/.config/opencode/opencode.json')).toBe(true);
|
|
327
329
|
});
|
|
328
330
|
});
|
|
329
331
|
|
|
330
|
-
describe(
|
|
331
|
-
it(
|
|
332
|
+
describe('command execution', () => {
|
|
333
|
+
it('passes arguments as array (no shell injection)', async () => {
|
|
332
334
|
const deps = makeDeps({
|
|
335
|
+
commands: new FakeCommandRunner().handle('pi --version', 'mocked').handle('pi install', ''),
|
|
333
336
|
prompter: new FakePrompter([
|
|
334
|
-
select(
|
|
335
|
-
select(
|
|
336
|
-
select(
|
|
337
|
-
confirm(true,
|
|
337
|
+
select('pi'),
|
|
338
|
+
select('project'),
|
|
339
|
+
select('fullstack'),
|
|
340
|
+
confirm(true, 'Create'),
|
|
338
341
|
]),
|
|
339
|
-
commands: new FakeCommandRunner().handle("pi --version", "mocked").handle("pi install", ""),
|
|
340
342
|
});
|
|
341
343
|
|
|
342
344
|
await runSetup(deps);
|
|
343
345
|
|
|
344
346
|
const commands = deps.commands as FakeCommandRunner;
|
|
345
|
-
const installCall = commands.calls.find(c => c.command ===
|
|
346
|
-
expect(installCall?.args).toContain(
|
|
347
|
-
expect(installCall?.args).toContain(
|
|
347
|
+
const installCall = commands.calls.find((c) => c.command === 'pi');
|
|
348
|
+
expect(installCall?.args).toContain('npm:@bergetai/pi-provider');
|
|
349
|
+
expect(installCall?.args).toContain('-l');
|
|
348
350
|
});
|
|
349
351
|
});
|
|
350
352
|
|
|
351
|
-
describe(
|
|
352
|
-
it(
|
|
353
|
+
describe('error handling', () => {
|
|
354
|
+
it('throws CommandFailedError when pi install fails', async () => {
|
|
353
355
|
const deps = makeDeps({
|
|
354
|
-
prompter: new FakePrompter([select("pi"), select("project")]),
|
|
355
356
|
commands: new FakeCommandRunner()
|
|
356
|
-
.handle(
|
|
357
|
-
.handle(
|
|
357
|
+
.handle('pi --version', 'mocked')
|
|
358
|
+
.handle('pi install', new Error('npm error')),
|
|
359
|
+
prompter: new FakePrompter([select('pi'), select('project')]),
|
|
358
360
|
});
|
|
359
361
|
|
|
360
362
|
await expect(runSetup(deps)).rejects.toBeInstanceOf(CommandFailedError);
|
|
361
363
|
});
|
|
362
364
|
});
|
|
363
365
|
|
|
364
|
-
describe(
|
|
365
|
-
it(
|
|
366
|
+
describe('auth integration', () => {
|
|
367
|
+
it('already authenticated shows simplified message', async () => {
|
|
366
368
|
const files = new FakeFileStore();
|
|
367
369
|
files.seed(
|
|
368
|
-
|
|
369
|
-
JSON.stringify({ berget: { type:
|
|
370
|
+
'/home/user/.local/share/opencode/auth.json',
|
|
371
|
+
JSON.stringify({ berget: { type: 'oauth' } }),
|
|
370
372
|
);
|
|
371
373
|
|
|
372
374
|
const deps = makeDeps({
|
|
375
|
+
files,
|
|
373
376
|
prompter: new FakePrompter([
|
|
374
|
-
select(
|
|
375
|
-
select(
|
|
376
|
-
select(
|
|
377
|
-
confirm(true,
|
|
377
|
+
select('opencode'),
|
|
378
|
+
select('project'),
|
|
379
|
+
select('keep'), // New: keep existing auth
|
|
380
|
+
confirm(true, 'Create'), // Config write
|
|
378
381
|
multiselect([]),
|
|
379
382
|
]),
|
|
380
|
-
files,
|
|
381
383
|
});
|
|
382
384
|
|
|
383
385
|
await runSetup(deps);
|
|
384
386
|
|
|
385
387
|
const prompter = deps.prompter as FakePrompter;
|
|
386
|
-
const notes = prompter.calls.filter(c => c.method ===
|
|
387
|
-
const lastNote = notes
|
|
388
|
-
expect(JSON.stringify(lastNote)).toContain(
|
|
389
|
-
expect(JSON.stringify(lastNote)).not.toContain(
|
|
388
|
+
const notes = prompter.calls.filter((c) => c.method === 'note');
|
|
389
|
+
const lastNote = notes.at(-1);
|
|
390
|
+
expect(JSON.stringify(lastNote)).toContain('Run: opencode');
|
|
391
|
+
expect(JSON.stringify(lastNote)).not.toContain('/connect');
|
|
390
392
|
});
|
|
391
393
|
|
|
392
|
-
it(
|
|
394
|
+
it('login failure shows manual auth instructions', async () => {
|
|
393
395
|
const deps = makeDeps({
|
|
394
|
-
prompter: new FakePrompter([
|
|
395
|
-
select("pi"),
|
|
396
|
-
select("project"),
|
|
397
|
-
select("fullstack"),
|
|
398
|
-
confirm(true, "Create"),
|
|
399
|
-
]),
|
|
400
|
-
commands: new FakeCommandRunner().handle("pi --version", "mocked").handle("pi install", ""),
|
|
401
396
|
authService: new FakeAuthService(false),
|
|
397
|
+
commands: new FakeCommandRunner().handle('pi --version', 'mocked').handle('pi install', ''),
|
|
402
398
|
files: new FakeFileStore(), // No pre-seeded auth → auth flow runs
|
|
399
|
+
prompter: new FakePrompter([
|
|
400
|
+
select('pi'),
|
|
401
|
+
select('project'),
|
|
402
|
+
select('fullstack'),
|
|
403
|
+
confirm(true, 'Create'),
|
|
404
|
+
]),
|
|
403
405
|
});
|
|
404
406
|
|
|
405
407
|
await runSetup(deps);
|
|
406
408
|
|
|
407
409
|
const prompter = deps.prompter as FakePrompter;
|
|
408
|
-
const notes = prompter.calls.filter(c => c.method ===
|
|
409
|
-
const lastNote = notes
|
|
410
|
-
expect(JSON.stringify(lastNote)).toContain(
|
|
410
|
+
const notes = prompter.calls.filter((c) => c.method === 'note');
|
|
411
|
+
const lastNote = notes.at(-1);
|
|
412
|
+
expect(JSON.stringify(lastNote)).toContain('/login');
|
|
411
413
|
});
|
|
412
414
|
|
|
413
|
-
it(
|
|
415
|
+
it('creates api key for pi when no seat', async () => {
|
|
414
416
|
const files = new FakeFileStore();
|
|
415
417
|
|
|
416
418
|
const deps = makeDeps({
|
|
419
|
+
authService: new FakeAuthService(true, false), // succeed, no seat
|
|
420
|
+
commands: new FakeCommandRunner().handle('pi --version', 'mocked').handle('pi install', ''),
|
|
421
|
+
files,
|
|
417
422
|
prompter: new FakePrompter([
|
|
418
|
-
select(
|
|
419
|
-
select(
|
|
423
|
+
select('pi'),
|
|
424
|
+
select('project'),
|
|
420
425
|
confirm(true), // API key creation prompt
|
|
421
|
-
select(
|
|
422
|
-
confirm(true,
|
|
426
|
+
select('fullstack'),
|
|
427
|
+
confirm(true, 'Create'),
|
|
423
428
|
]),
|
|
424
|
-
commands: new FakeCommandRunner().handle("pi --version", "mocked").handle("pi install", ""),
|
|
425
|
-
authService: new FakeAuthService(true, false), // succeed, no seat
|
|
426
|
-
files,
|
|
427
429
|
});
|
|
428
430
|
|
|
429
431
|
await runSetup(deps);
|
|
430
432
|
|
|
431
433
|
const written = files.getWrittenFiles();
|
|
432
|
-
expect(written.has(
|
|
433
|
-
const parsed = JSON.parse(written.get(
|
|
434
|
-
expect(parsed.berget.type).toBe(
|
|
434
|
+
expect(written.has('/home/user/.pi/agent/auth.json')).toBe(true);
|
|
435
|
+
const parsed = JSON.parse(written.get('/home/user/.pi/agent/auth.json')!);
|
|
436
|
+
expect(parsed.berget.type).toBe('api_key');
|
|
435
437
|
});
|
|
436
438
|
|
|
437
|
-
it(
|
|
439
|
+
it('uses subscription when berget_code_seat present', async () => {
|
|
438
440
|
const files = new FakeFileStore();
|
|
439
441
|
const farFuture = Math.floor(Date.now() / 1000) + 3600 * 24 * 365; // 1 year from now in seconds
|
|
440
442
|
files.seed(
|
|
441
|
-
|
|
443
|
+
'/home/user/.berget/auth.json',
|
|
442
444
|
JSON.stringify({
|
|
443
|
-
access_token: makeJwt({ realm_access: { roles: [
|
|
444
|
-
refresh_token: "ref",
|
|
445
|
+
access_token: makeJwt({ exp: farFuture, realm_access: { roles: ['berget_code_seat'] } }),
|
|
445
446
|
expires_at: farFuture * 1000,
|
|
446
|
-
|
|
447
|
+
refresh_token: 'ref',
|
|
448
|
+
}),
|
|
447
449
|
);
|
|
448
450
|
|
|
449
451
|
const deps = makeDeps({
|
|
452
|
+
files,
|
|
450
453
|
prompter: new FakePrompter([
|
|
451
|
-
select(
|
|
452
|
-
select(
|
|
453
|
-
select(
|
|
454
|
-
confirm(true,
|
|
454
|
+
select('opencode'),
|
|
455
|
+
select('project'),
|
|
456
|
+
select('subscription'),
|
|
457
|
+
confirm(true, 'Create'),
|
|
455
458
|
multiselect([]),
|
|
456
459
|
]),
|
|
457
|
-
files,
|
|
458
460
|
});
|
|
459
461
|
|
|
460
462
|
await runSetup(deps);
|
|
461
463
|
|
|
462
464
|
const written = files.getWrittenFiles();
|
|
463
|
-
const parsed = JSON.parse(written.get(
|
|
464
|
-
expect(parsed.berget.type).toBe(
|
|
465
|
+
const parsed = JSON.parse(written.get('/home/user/.local/share/opencode/auth.json')!);
|
|
466
|
+
expect(parsed.berget.type).toBe('oauth');
|
|
465
467
|
});
|
|
466
468
|
});
|
|
467
469
|
|
|
468
|
-
describe(
|
|
469
|
-
it(
|
|
470
|
+
describe('agent configuration', () => {
|
|
471
|
+
it('sets up multiple agents for opencode project', async () => {
|
|
470
472
|
const deps = makeDeps({
|
|
471
473
|
prompter: new FakePrompter([
|
|
472
|
-
select(
|
|
473
|
-
select(
|
|
474
|
-
confirm(true,
|
|
475
|
-
multiselect([
|
|
476
|
-
confirm(true,
|
|
474
|
+
select('opencode'),
|
|
475
|
+
select('project'),
|
|
476
|
+
confirm(true, 'Create'),
|
|
477
|
+
multiselect(['backend', 'frontend']),
|
|
478
|
+
confirm(true, 'agent'),
|
|
477
479
|
]),
|
|
478
480
|
});
|
|
479
481
|
|
|
@@ -481,16 +483,16 @@ describe("runSetup", () => {
|
|
|
481
483
|
|
|
482
484
|
const files = deps.files as FakeFileStore;
|
|
483
485
|
const written = files.getWrittenFiles();
|
|
484
|
-
expect(written.has(
|
|
485
|
-
expect(written.has(
|
|
486
|
+
expect(written.has('/home/user/project/.opencode/agents/backend.md')).toBe(true);
|
|
487
|
+
expect(written.has('/home/user/project/.opencode/agents/frontend.md')).toBe(true);
|
|
486
488
|
});
|
|
487
489
|
|
|
488
|
-
it(
|
|
490
|
+
it('sets up no agents for opencode when none selected', async () => {
|
|
489
491
|
const deps = makeDeps({
|
|
490
492
|
prompter: new FakePrompter([
|
|
491
|
-
select(
|
|
492
|
-
select(
|
|
493
|
-
confirm(true,
|
|
493
|
+
select('opencode'),
|
|
494
|
+
select('project'),
|
|
495
|
+
confirm(true, 'Create'),
|
|
494
496
|
multiselect([]),
|
|
495
497
|
]),
|
|
496
498
|
});
|
|
@@ -504,14 +506,14 @@ describe("runSetup", () => {
|
|
|
504
506
|
}
|
|
505
507
|
});
|
|
506
508
|
|
|
507
|
-
it(
|
|
509
|
+
it('sets up agent globally for opencode', async () => {
|
|
508
510
|
const deps = makeDeps({
|
|
509
511
|
prompter: new FakePrompter([
|
|
510
|
-
select(
|
|
511
|
-
select(
|
|
512
|
-
confirm(true,
|
|
513
|
-
multiselect([
|
|
514
|
-
confirm(true,
|
|
512
|
+
select('opencode'),
|
|
513
|
+
select('global'),
|
|
514
|
+
confirm(true, 'Create'),
|
|
515
|
+
multiselect(['fullstack']),
|
|
516
|
+
confirm(true, 'agent'),
|
|
515
517
|
]),
|
|
516
518
|
});
|
|
517
519
|
|
|
@@ -519,53 +521,53 @@ describe("runSetup", () => {
|
|
|
519
521
|
|
|
520
522
|
const files = deps.files as FakeFileStore;
|
|
521
523
|
const written = files.getWrittenFiles();
|
|
522
|
-
expect(written.has(
|
|
524
|
+
expect(written.has('/home/user/.config/opencode/agents/fullstack.md')).toBe(true);
|
|
523
525
|
});
|
|
524
526
|
|
|
525
|
-
it(
|
|
527
|
+
it('sets up agent for pi project', async () => {
|
|
526
528
|
const deps = makeDeps({
|
|
529
|
+
commands: new FakeCommandRunner().handle('pi --version', 'mocked').handle('pi install', ''),
|
|
527
530
|
prompter: new FakePrompter([
|
|
528
|
-
select(
|
|
529
|
-
select(
|
|
530
|
-
select(
|
|
531
|
-
confirm(true,
|
|
531
|
+
select('pi'),
|
|
532
|
+
select('project'),
|
|
533
|
+
select('fullstack'),
|
|
534
|
+
confirm(true, 'Create'),
|
|
532
535
|
]),
|
|
533
|
-
commands: new FakeCommandRunner().handle("pi --version", "mocked").handle("pi install", ""),
|
|
534
536
|
});
|
|
535
537
|
|
|
536
538
|
await runSetup(deps);
|
|
537
539
|
|
|
538
540
|
const files = deps.files as FakeFileStore;
|
|
539
541
|
const written = files.getWrittenFiles();
|
|
540
|
-
expect(written.has(
|
|
542
|
+
expect(written.has('/home/user/project/.pi/SYSTEM.md')).toBe(true);
|
|
541
543
|
});
|
|
542
544
|
|
|
543
|
-
it(
|
|
545
|
+
it('sets up agent for pi globally', async () => {
|
|
544
546
|
const deps = makeDeps({
|
|
547
|
+
commands: new FakeCommandRunner().handle('pi --version', 'mocked').handle('pi install', ''),
|
|
545
548
|
prompter: new FakePrompter([
|
|
546
|
-
select(
|
|
547
|
-
select(
|
|
548
|
-
select(
|
|
549
|
-
confirm(true,
|
|
549
|
+
select('pi'),
|
|
550
|
+
select('global'),
|
|
551
|
+
select('backend'),
|
|
552
|
+
confirm(true, 'Create'),
|
|
550
553
|
]),
|
|
551
|
-
commands: new FakeCommandRunner().handle("pi --version", "mocked").handle("pi install", ""),
|
|
552
554
|
});
|
|
553
555
|
|
|
554
556
|
await runSetup(deps);
|
|
555
557
|
|
|
556
558
|
const files = deps.files as FakeFileStore;
|
|
557
559
|
const written = files.getWrittenFiles();
|
|
558
|
-
expect(written.has(
|
|
560
|
+
expect(written.has('/home/user/.pi/agent/SYSTEM.md')).toBe(true);
|
|
559
561
|
});
|
|
560
562
|
|
|
561
|
-
it(
|
|
563
|
+
it('skips writing identical opencode agent files', async () => {
|
|
562
564
|
const deps = makeDeps({
|
|
563
565
|
prompter: new FakePrompter([
|
|
564
|
-
select(
|
|
565
|
-
select(
|
|
566
|
-
confirm(true,
|
|
567
|
-
multiselect([
|
|
568
|
-
confirm(true,
|
|
566
|
+
select('opencode'),
|
|
567
|
+
select('project'),
|
|
568
|
+
confirm(true, 'Create'),
|
|
569
|
+
multiselect(['backend', 'frontend']),
|
|
570
|
+
confirm(true, 'agent'),
|
|
569
571
|
]),
|
|
570
572
|
});
|
|
571
573
|
|
|
@@ -575,54 +577,54 @@ describe("runSetup", () => {
|
|
|
575
577
|
const files = deps.files as FakeFileStore;
|
|
576
578
|
const firstBackend = files
|
|
577
579
|
.getWrittenFiles()
|
|
578
|
-
.get(
|
|
580
|
+
.get('/home/user/project/.opencode/agents/backend.md');
|
|
579
581
|
const firstFrontend = files
|
|
580
582
|
.getWrittenFiles()
|
|
581
|
-
.get(
|
|
583
|
+
.get('/home/user/project/.opencode/agents/frontend.md');
|
|
582
584
|
|
|
583
585
|
// Second run with exact same content should not prompt for overwrite
|
|
584
586
|
const deps2 = makeDeps({
|
|
585
587
|
files,
|
|
586
588
|
prompter: new FakePrompter([
|
|
587
|
-
select(
|
|
588
|
-
select(
|
|
589
|
-
multiselect([
|
|
589
|
+
select('opencode'),
|
|
590
|
+
select('project'),
|
|
591
|
+
multiselect(['backend', 'frontend']),
|
|
590
592
|
]),
|
|
591
593
|
});
|
|
592
594
|
|
|
593
595
|
await runSetup(deps2);
|
|
594
596
|
|
|
595
597
|
// Content should be unchanged
|
|
596
|
-
expect(files.getWrittenFiles().get(
|
|
597
|
-
firstBackend
|
|
598
|
+
expect(files.getWrittenFiles().get('/home/user/project/.opencode/agents/backend.md')).toBe(
|
|
599
|
+
firstBackend,
|
|
598
600
|
);
|
|
599
|
-
expect(files.getWrittenFiles().get(
|
|
600
|
-
firstFrontend
|
|
601
|
+
expect(files.getWrittenFiles().get('/home/user/project/.opencode/agents/frontend.md')).toBe(
|
|
602
|
+
firstFrontend,
|
|
601
603
|
);
|
|
602
604
|
});
|
|
603
605
|
|
|
604
|
-
it(
|
|
606
|
+
it('overwrites pi SYSTEM.md when content differs', async () => {
|
|
605
607
|
const files = new FakeFileStore();
|
|
606
|
-
files.seed(
|
|
608
|
+
files.seed('/home/user/project/.pi/SYSTEM.md', 'old agent content');
|
|
607
609
|
|
|
608
610
|
const deps = makeDeps({
|
|
611
|
+
commands: new FakeCommandRunner().handle('pi --version', 'mocked').handle('pi install', ''),
|
|
612
|
+
files,
|
|
609
613
|
prompter: new FakePrompter([
|
|
610
|
-
select(
|
|
611
|
-
select(
|
|
612
|
-
select(
|
|
613
|
-
confirm(true,
|
|
614
|
+
select('pi'),
|
|
615
|
+
select('project'),
|
|
616
|
+
select('fullstack'),
|
|
617
|
+
confirm(true, 'Overwrite'),
|
|
614
618
|
]),
|
|
615
|
-
files,
|
|
616
|
-
commands: new FakeCommandRunner().handle("pi --version", "mocked").handle("pi install", ""),
|
|
617
619
|
});
|
|
618
620
|
|
|
619
621
|
await runSetup(deps);
|
|
620
622
|
|
|
621
623
|
const written = files.getWrittenFiles();
|
|
622
|
-
const content = written.get(
|
|
623
|
-
expect(content).not.toBe(
|
|
624
|
+
const content = written.get('/home/user/project/.pi/SYSTEM.md');
|
|
625
|
+
expect(content).not.toBe('old agent content');
|
|
624
626
|
// Pi doesn't use front matter, so check for system prompt content
|
|
625
|
-
expect(content).toContain(
|
|
627
|
+
expect(content).toContain('Fullstack Agent');
|
|
626
628
|
});
|
|
627
629
|
});
|
|
628
630
|
});
|