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