berget 2.2.6 → 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 +2 -2
- package/.github/workflows/test.yml +10 -4
- package/.husky/pre-commit +1 -0
- package/.prettierignore +15 -0
- package/.prettierrc +7 -3
- package/CONTRIBUTING.md +38 -0
- package/README.md +2 -148
- package/dist/index.js +10 -11
- package/dist/package.json +30 -2
- package/dist/src/agents/app.js +28 -0
- package/dist/src/agents/backend.js +25 -0
- package/dist/src/agents/devops.js +34 -0
- package/dist/src/agents/frontend.js +25 -0
- package/dist/src/agents/fullstack.js +25 -0
- package/dist/src/agents/index.js +61 -0
- package/dist/src/agents/quality.js +70 -0
- package/dist/src/agents/security.js +26 -0
- package/dist/src/agents/types.js +2 -0
- package/dist/src/client.js +97 -117
- package/dist/src/commands/api-keys.js +75 -90
- package/dist/src/commands/auth.js +7 -16
- package/dist/src/commands/autocomplete.js +1 -1
- package/dist/src/commands/billing.js +6 -17
- package/dist/src/commands/chat.js +68 -101
- package/dist/src/commands/clusters.js +9 -18
- package/dist/src/commands/code/__tests__/auth-sync.test.js +351 -0
- package/dist/src/commands/code/__tests__/fake-api-key-service.js +13 -0
- package/dist/src/commands/code/__tests__/fake-auth-service.js +47 -0
- package/dist/src/commands/code/__tests__/fake-command-runner.js +21 -34
- package/dist/src/commands/code/__tests__/fake-file-store.js +20 -33
- package/dist/src/commands/code/__tests__/fake-prompter.js +83 -57
- package/dist/src/commands/code/__tests__/setup-flow.test.js +359 -92
- package/dist/src/commands/code/adapters/clack-prompter.js +15 -22
- package/dist/src/commands/code/adapters/fs-file-store.js +26 -40
- package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -37
- package/dist/src/commands/code/auth-sync.js +270 -0
- package/dist/src/commands/code/errors.js +12 -9
- package/dist/src/commands/code/ports/auth-services.js +2 -0
- package/dist/src/commands/code/setup.js +387 -281
- package/dist/src/commands/code.js +205 -332
- package/dist/src/commands/index.js +5 -5
- package/dist/src/commands/models.js +6 -17
- package/dist/src/commands/users.js +5 -16
- package/dist/src/constants/command-structure.js +104 -104
- package/dist/src/services/api-key-service.js +132 -157
- package/dist/src/services/auth-service.js +89 -342
- package/dist/src/services/browser-auth.js +268 -0
- package/dist/src/services/chat-service.js +371 -401
- package/dist/src/services/cluster-service.js +47 -62
- package/dist/src/services/collaborator-service.js +10 -25
- package/dist/src/services/flux-service.js +14 -29
- package/dist/src/services/helm-service.js +10 -25
- package/dist/src/services/kubectl-service.js +16 -33
- package/dist/src/utils/config-checker.js +3 -3
- package/dist/src/utils/config-loader.js +95 -95
- package/dist/src/utils/default-api-key.js +124 -134
- package/dist/src/utils/env-manager.js +55 -66
- package/dist/src/utils/error-handler.js +20 -21
- package/dist/src/utils/logger.js +72 -65
- package/dist/src/utils/markdown-renderer.js +27 -27
- package/dist/src/utils/opencode-validator.js +63 -68
- package/dist/src/utils/token-manager.js +74 -45
- package/dist/tests/commands/chat.test.js +16 -25
- package/dist/tests/commands/code.test.js +95 -104
- package/dist/tests/utils/config-loader.test.js +48 -48
- package/dist/tests/utils/env-manager.test.js +43 -52
- package/dist/tests/utils/opencode-validator.test.js +22 -21
- package/dist/vitest.config.js +1 -1
- package/eslint.config.mjs +67 -0
- package/index.ts +35 -42
- package/package.json +30 -2
- package/src/agents/app.ts +27 -0
- package/src/agents/backend.ts +24 -0
- package/src/agents/devops.ts +33 -0
- package/src/agents/frontend.ts +24 -0
- package/src/agents/fullstack.ts +24 -0
- package/src/agents/index.ts +73 -0
- package/src/agents/quality.ts +69 -0
- package/src/agents/security.ts +26 -0
- package/src/agents/types.ts +17 -0
- package/src/client.ts +118 -152
- package/src/commands/api-keys.ts +241 -333
- package/src/commands/auth.ts +22 -27
- package/src/commands/autocomplete.ts +9 -9
- package/src/commands/billing.ts +20 -24
- package/src/commands/chat.ts +248 -338
- package/src/commands/clusters.ts +27 -26
- package/src/commands/code/__tests__/auth-sync.test.ts +482 -0
- package/src/commands/code/__tests__/fake-api-key-service.ts +13 -0
- package/src/commands/code/__tests__/fake-auth-service.ts +50 -0
- package/src/commands/code/__tests__/fake-command-runner.ts +45 -42
- package/src/commands/code/__tests__/fake-file-store.ts +32 -23
- package/src/commands/code/__tests__/fake-prompter.ts +116 -77
- package/src/commands/code/__tests__/setup-flow.test.ts +624 -268
- package/src/commands/code/adapters/clack-prompter.ts +53 -39
- package/src/commands/code/adapters/fs-file-store.ts +32 -27
- package/src/commands/code/adapters/spawn-command-runner.ts +38 -29
- package/src/commands/code/auth-sync.ts +329 -0
- package/src/commands/code/errors.ts +18 -18
- package/src/commands/code/ports/auth-services.ts +14 -0
- package/src/commands/code/ports/command-runner.ts +8 -4
- package/src/commands/code/ports/file-store.ts +5 -4
- package/src/commands/code/ports/prompter.ts +24 -18
- package/src/commands/code/setup.ts +570 -340
- package/src/commands/code.ts +338 -539
- package/src/commands/index.ts +20 -19
- package/src/commands/models.ts +28 -32
- package/src/commands/users.ts +15 -21
- package/src/constants/command-structure.ts +134 -157
- package/src/services/api-key-service.ts +105 -122
- package/src/services/auth-service.ts +99 -345
- package/src/services/browser-auth.ts +296 -0
- package/src/services/chat-service.ts +265 -299
- package/src/services/cluster-service.ts +42 -45
- package/src/services/collaborator-service.ts +14 -19
- package/src/services/flux-service.ts +23 -25
- package/src/services/helm-service.ts +19 -21
- package/src/services/kubectl-service.ts +17 -19
- package/src/types/api.d.ts +1905 -1907
- package/src/types/json.d.ts +2 -2
- package/src/utils/config-checker.ts +10 -10
- package/src/utils/config-loader.ts +162 -178
- package/src/utils/default-api-key.ts +114 -125
- package/src/utils/env-manager.ts +53 -57
- package/src/utils/error-handler.ts +61 -56
- package/src/utils/logger.ts +79 -73
- package/src/utils/markdown-renderer.ts +31 -31
- package/src/utils/opencode-validator.ts +85 -89
- package/src/utils/token-manager.ts +108 -87
- package/templates/agents/app.md +1 -0
- package/templates/agents/backend.md +1 -0
- package/templates/agents/devops.md +2 -0
- package/templates/agents/frontend.md +1 -0
- package/templates/agents/fullstack.md +1 -0
- package/templates/agents/quality.md +45 -40
- package/templates/agents/security.md +1 -0
- package/tests/commands/chat.test.ts +53 -62
- package/tests/commands/code.test.ts +265 -310
- package/tests/utils/config-loader.test.ts +189 -188
- package/tests/utils/env-manager.test.ts +110 -113
- package/tests/utils/opencode-validator.test.ts +52 -56
- package/tsconfig.json +4 -3
- package/vitest.config.ts +3 -3
- package/AGENTS.md +0 -374
- package/TODO.md +0 -19
|
@@ -1,199 +1,196 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { updateEnvFile, hasEnvKey } from '../../src/utils/env-manager'
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { writeFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
import {
|
|
7
|
+
hasEnvKey as hasEnvironmentKey,
|
|
8
|
+
updateEnvFile as updateEnvironmentFile,
|
|
9
|
+
} from '../../src/utils/env-manager';
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
vi.mock('fs');
|
|
12
|
+
vi.mock('fs/promises');
|
|
13
|
+
vi.mock('path');
|
|
14
|
+
|
|
15
|
+
const mockFs = vi.mocked(fs);
|
|
16
|
+
const mockWriteFile = vi.mocked(writeFile);
|
|
17
|
+
const mockPath = vi.mocked(path);
|
|
14
18
|
|
|
15
19
|
describe('env-manager', () => {
|
|
16
|
-
const
|
|
17
|
-
const testCwd = '/test'
|
|
20
|
+
const testEnvironmentPath = '/test/.env';
|
|
21
|
+
const testCwd = '/test';
|
|
18
22
|
|
|
19
23
|
beforeEach(() => {
|
|
20
|
-
vi.clearAllMocks()
|
|
21
|
-
mockPath.join.mockReturnValue(
|
|
22
|
-
vi.spyOn(process, 'cwd').mockReturnValue(testCwd)
|
|
23
|
-
})
|
|
24
|
+
vi.clearAllMocks();
|
|
25
|
+
mockPath.join.mockReturnValue(testEnvironmentPath);
|
|
26
|
+
vi.spyOn(process, 'cwd').mockReturnValue(testCwd);
|
|
27
|
+
});
|
|
24
28
|
|
|
25
29
|
afterEach(() => {
|
|
26
|
-
vi.restoreAllMocks()
|
|
27
|
-
})
|
|
30
|
+
vi.restoreAllMocks();
|
|
31
|
+
});
|
|
28
32
|
|
|
29
33
|
describe('updateEnvFile', () => {
|
|
30
34
|
it('should create a new .env file with the key when file does not exist', async () => {
|
|
31
|
-
mockFs.existsSync.mockReturnValue(false)
|
|
35
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
32
36
|
|
|
33
|
-
await
|
|
37
|
+
await updateEnvironmentFile({
|
|
38
|
+
comment: 'Test comment',
|
|
34
39
|
key: 'TEST_KEY',
|
|
35
40
|
value: 'test_value',
|
|
36
|
-
|
|
37
|
-
})
|
|
41
|
+
});
|
|
38
42
|
|
|
39
|
-
expect(mockFs.existsSync).toHaveBeenCalledWith(
|
|
43
|
+
expect(mockFs.existsSync).toHaveBeenCalledWith(testEnvironmentPath);
|
|
40
44
|
expect(mockWriteFile).toHaveBeenCalledWith(
|
|
41
|
-
|
|
45
|
+
testEnvironmentPath,
|
|
42
46
|
'# Test comment\nTEST_KEY=test_value\n',
|
|
43
|
-
)
|
|
44
|
-
})
|
|
47
|
+
);
|
|
48
|
+
});
|
|
45
49
|
|
|
46
50
|
it('should append to existing .env file when key does not exist', async () => {
|
|
47
|
-
const existingContent = 'EXISTING_KEY=existing_value\n'
|
|
48
|
-
mockFs.existsSync.mockReturnValue(true)
|
|
49
|
-
mockFs.readFileSync.mockReturnValue(existingContent)
|
|
51
|
+
const existingContent = 'EXISTING_KEY=existing_value\n';
|
|
52
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
53
|
+
mockFs.readFileSync.mockReturnValue(existingContent);
|
|
50
54
|
|
|
51
|
-
await
|
|
55
|
+
await updateEnvironmentFile({
|
|
56
|
+
comment: 'Test comment',
|
|
52
57
|
key: 'NEW_KEY',
|
|
53
58
|
value: 'new_value',
|
|
54
|
-
|
|
55
|
-
})
|
|
59
|
+
});
|
|
56
60
|
|
|
57
61
|
expect(mockWriteFile).toHaveBeenCalledWith(
|
|
58
|
-
|
|
62
|
+
testEnvironmentPath,
|
|
59
63
|
'EXISTING_KEY=existing_value\nNEW_KEY=new_value\n',
|
|
60
|
-
)
|
|
61
|
-
})
|
|
64
|
+
);
|
|
65
|
+
});
|
|
62
66
|
|
|
63
67
|
it('should not update when key already exists and force is false', async () => {
|
|
64
|
-
const existingContent =
|
|
65
|
-
|
|
66
|
-
mockFs.
|
|
67
|
-
mockFs.readFileSync.mockReturnValue(existingContent)
|
|
68
|
+
const existingContent = 'EXISTING_KEY=existing_value\nTEST_KEY=old_value\n';
|
|
69
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
70
|
+
mockFs.readFileSync.mockReturnValue(existingContent);
|
|
68
71
|
|
|
69
|
-
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
72
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
70
73
|
|
|
71
|
-
await
|
|
74
|
+
await updateEnvironmentFile({
|
|
72
75
|
key: 'TEST_KEY',
|
|
73
76
|
value: 'new_value',
|
|
74
|
-
})
|
|
77
|
+
});
|
|
75
78
|
|
|
76
79
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
77
|
-
expect.stringContaining(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
81
|
-
expect(mockWriteFile).not.toHaveBeenCalled()
|
|
80
|
+
expect.stringContaining('TEST_KEY already exists in .env - leaving unchanged'),
|
|
81
|
+
);
|
|
82
|
+
expect(mockWriteFile).not.toHaveBeenCalled();
|
|
82
83
|
|
|
83
|
-
consoleSpy.mockRestore()
|
|
84
|
-
})
|
|
84
|
+
consoleSpy.mockRestore();
|
|
85
|
+
});
|
|
85
86
|
|
|
86
87
|
it('should update existing key when force is true', async () => {
|
|
87
|
-
const existingContent =
|
|
88
|
-
|
|
89
|
-
mockFs.
|
|
90
|
-
mockFs.readFileSync.mockReturnValue(existingContent)
|
|
88
|
+
const existingContent = 'EXISTING_KEY=existing_value\nTEST_KEY=old_value\n';
|
|
89
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
90
|
+
mockFs.readFileSync.mockReturnValue(existingContent);
|
|
91
91
|
|
|
92
|
-
await
|
|
92
|
+
await updateEnvironmentFile({
|
|
93
|
+
force: true,
|
|
93
94
|
key: 'TEST_KEY',
|
|
94
95
|
value: 'new_value',
|
|
95
|
-
|
|
96
|
-
})
|
|
96
|
+
});
|
|
97
97
|
|
|
98
98
|
expect(mockWriteFile).toHaveBeenCalledWith(
|
|
99
|
-
|
|
99
|
+
testEnvironmentPath,
|
|
100
100
|
'EXISTING_KEY=existing_value\nTEST_KEY=new_value\n',
|
|
101
|
-
)
|
|
102
|
-
})
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
103
|
|
|
104
104
|
it('should handle complex values with quotes and special characters', async () => {
|
|
105
|
-
mockFs.existsSync.mockReturnValue(false)
|
|
105
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
106
106
|
|
|
107
|
-
await
|
|
107
|
+
await updateEnvironmentFile({
|
|
108
|
+
comment: 'Complex test',
|
|
108
109
|
key: 'COMPLEX_KEY',
|
|
109
110
|
value: 'value with "quotes" and $special',
|
|
110
|
-
|
|
111
|
-
})
|
|
111
|
+
});
|
|
112
112
|
|
|
113
113
|
expect(mockWriteFile).toHaveBeenCalledWith(
|
|
114
|
-
|
|
114
|
+
testEnvironmentPath,
|
|
115
115
|
'# Complex test\nCOMPLEX_KEY=value with "quotes" and $special\n',
|
|
116
|
-
)
|
|
117
|
-
})
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
118
|
|
|
119
119
|
it('should use custom env path when provided', async () => {
|
|
120
|
-
const customPath = '/custom/.env'
|
|
121
|
-
mockFs.existsSync.mockReturnValue(false)
|
|
120
|
+
const customPath = '/custom/.env';
|
|
121
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
122
122
|
|
|
123
|
-
await
|
|
123
|
+
await updateEnvironmentFile({
|
|
124
124
|
envPath: customPath,
|
|
125
125
|
key: 'TEST_KEY',
|
|
126
126
|
value: 'test_value',
|
|
127
|
-
})
|
|
127
|
+
});
|
|
128
128
|
|
|
129
|
-
expect(mockFs.existsSync).toHaveBeenCalledWith(customPath)
|
|
130
|
-
expect(mockWriteFile).toHaveBeenCalledWith(
|
|
131
|
-
|
|
132
|
-
'TEST_KEY=test_value\n',
|
|
133
|
-
)
|
|
134
|
-
})
|
|
129
|
+
expect(mockFs.existsSync).toHaveBeenCalledWith(customPath);
|
|
130
|
+
expect(mockWriteFile).toHaveBeenCalledWith(customPath, 'TEST_KEY=test_value\n');
|
|
131
|
+
});
|
|
135
132
|
|
|
136
133
|
it('should throw error when write fails', async () => {
|
|
137
|
-
mockFs.existsSync.mockReturnValue(false)
|
|
138
|
-
mockWriteFile.mockRejectedValue(new Error('Write error'))
|
|
134
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
135
|
+
mockWriteFile.mockRejectedValue(new Error('Write error'));
|
|
139
136
|
|
|
140
137
|
await expect(
|
|
141
|
-
|
|
138
|
+
updateEnvironmentFile({
|
|
142
139
|
key: 'TEST_KEY',
|
|
143
140
|
value: 'test_value',
|
|
144
141
|
}),
|
|
145
|
-
).rejects.toThrow('Write error')
|
|
146
|
-
})
|
|
147
|
-
})
|
|
142
|
+
).rejects.toThrow('Write error');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
148
145
|
|
|
149
146
|
describe('hasEnvKey', () => {
|
|
150
147
|
it('should return false when .env file does not exist', () => {
|
|
151
|
-
mockFs.existsSync.mockReturnValue(false)
|
|
148
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
152
149
|
|
|
153
|
-
const result =
|
|
150
|
+
const result = hasEnvironmentKey(testEnvironmentPath, 'TEST_KEY');
|
|
154
151
|
|
|
155
|
-
expect(result).toBe(false)
|
|
156
|
-
expect(mockFs.existsSync).toHaveBeenCalledWith(
|
|
157
|
-
expect(mockFs.readFileSync).not.toHaveBeenCalled()
|
|
158
|
-
})
|
|
152
|
+
expect(result).toBe(false);
|
|
153
|
+
expect(mockFs.existsSync).toHaveBeenCalledWith(testEnvironmentPath);
|
|
154
|
+
expect(mockFs.readFileSync).not.toHaveBeenCalled();
|
|
155
|
+
});
|
|
159
156
|
|
|
160
157
|
it('should return true when key exists in .env file', () => {
|
|
161
|
-
const existingContent = 'KEY1=value1\nTEST_KEY=test_value\nKEY2=value2\n'
|
|
162
|
-
mockFs.existsSync.mockReturnValue(true)
|
|
163
|
-
mockFs.readFileSync.mockReturnValue(existingContent)
|
|
158
|
+
const existingContent = 'KEY1=value1\nTEST_KEY=test_value\nKEY2=value2\n';
|
|
159
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
160
|
+
mockFs.readFileSync.mockReturnValue(existingContent);
|
|
164
161
|
|
|
165
|
-
const result =
|
|
162
|
+
const result = hasEnvironmentKey(testEnvironmentPath, 'TEST_KEY');
|
|
166
163
|
|
|
167
|
-
expect(result).toBe(true)
|
|
168
|
-
})
|
|
164
|
+
expect(result).toBe(true);
|
|
165
|
+
});
|
|
169
166
|
|
|
170
167
|
it('should return false when key does not exist in .env file', () => {
|
|
171
|
-
const existingContent = 'KEY1=value1\nKEY2=value2\n'
|
|
172
|
-
mockFs.existsSync.mockReturnValue(true)
|
|
173
|
-
mockFs.readFileSync.mockReturnValue(existingContent)
|
|
168
|
+
const existingContent = 'KEY1=value1\nKEY2=value2\n';
|
|
169
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
170
|
+
mockFs.readFileSync.mockReturnValue(existingContent);
|
|
174
171
|
|
|
175
|
-
const result =
|
|
172
|
+
const result = hasEnvironmentKey(testEnvironmentPath, 'TEST_KEY');
|
|
176
173
|
|
|
177
|
-
expect(result).toBe(false)
|
|
178
|
-
})
|
|
174
|
+
expect(result).toBe(false);
|
|
175
|
+
});
|
|
179
176
|
|
|
180
177
|
it('should return false when .env file is malformed', () => {
|
|
181
|
-
mockFs.existsSync.mockReturnValue(true)
|
|
178
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
182
179
|
mockFs.readFileSync.mockImplementation(() => {
|
|
183
|
-
throw new Error('Read error')
|
|
184
|
-
})
|
|
180
|
+
throw new Error('Read error');
|
|
181
|
+
});
|
|
185
182
|
|
|
186
|
-
const result =
|
|
183
|
+
const result = hasEnvironmentKey(testEnvironmentPath, 'TEST_KEY');
|
|
187
184
|
|
|
188
|
-
expect(result).toBe(false)
|
|
189
|
-
})
|
|
185
|
+
expect(result).toBe(false);
|
|
186
|
+
});
|
|
190
187
|
|
|
191
188
|
it('should use default path when not provided', () => {
|
|
192
|
-
mockFs.existsSync.mockReturnValue(false)
|
|
189
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
193
190
|
|
|
194
|
-
|
|
191
|
+
hasEnvironmentKey(undefined, 'TEST_KEY');
|
|
195
192
|
|
|
196
|
-
expect(mockFs.existsSync).toHaveBeenCalledWith(
|
|
197
|
-
})
|
|
198
|
-
})
|
|
199
|
-
})
|
|
193
|
+
expect(mockFs.existsSync).toHaveBeenCalledWith(testEnvironmentPath);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -1,119 +1,115 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from '../../src/utils/opencode-validator'
|
|
6
|
-
import { readFileSync } from 'fs'
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { fixOpenCodeConfig, validateOpenCodeConfig } from '../../src/utils/opencode-validator';
|
|
7
5
|
|
|
8
6
|
describe('OpenCode Validator', () => {
|
|
9
7
|
it('should validate a correct OpenCode configuration', () => {
|
|
10
8
|
const validConfig = {
|
|
11
9
|
$schema: 'https://opencode.ai/config.json',
|
|
12
|
-
username: 'test-user',
|
|
13
|
-
model: 'gpt-4',
|
|
14
10
|
agent: {
|
|
15
11
|
test: {
|
|
16
12
|
model: 'gpt-4',
|
|
17
|
-
temperature: 0.7,
|
|
18
|
-
prompt: 'Test agent',
|
|
19
13
|
permission: {
|
|
20
|
-
edit: 'allow',
|
|
21
14
|
bash: 'allow',
|
|
15
|
+
edit: 'allow',
|
|
22
16
|
webfetch: 'allow',
|
|
23
17
|
},
|
|
18
|
+
prompt: 'Test agent',
|
|
19
|
+
temperature: 0.7,
|
|
24
20
|
},
|
|
25
21
|
},
|
|
26
|
-
|
|
22
|
+
model: 'gpt-4',
|
|
23
|
+
username: 'test-user',
|
|
24
|
+
};
|
|
27
25
|
|
|
28
|
-
const result = validateOpenCodeConfig(validConfig)
|
|
29
|
-
expect(result.valid).toBe(true)
|
|
30
|
-
expect(result.errors).toBeUndefined()
|
|
31
|
-
})
|
|
26
|
+
const result = validateOpenCodeConfig(validConfig);
|
|
27
|
+
expect(result.valid).toBe(true);
|
|
28
|
+
expect(result.errors).toBeUndefined();
|
|
29
|
+
});
|
|
32
30
|
|
|
33
31
|
it('should reject invalid configuration', () => {
|
|
34
32
|
const invalidConfig = {
|
|
35
|
-
username: 123, // Should be string
|
|
36
|
-
model: 'gpt-4',
|
|
37
33
|
agent: {
|
|
38
34
|
test: {
|
|
39
35
|
model: 'gpt-4',
|
|
40
|
-
temperature: 'high', // Should be number
|
|
41
|
-
prompt: 'Test agent',
|
|
42
36
|
permission: {
|
|
43
|
-
edit: 'invalid', // Should be enum value
|
|
44
37
|
bash: 'allow',
|
|
38
|
+
edit: 'invalid', // Should be enum value
|
|
45
39
|
webfetch: 'allow',
|
|
46
40
|
},
|
|
41
|
+
prompt: 'Test agent',
|
|
42
|
+
temperature: 'high', // Should be number
|
|
47
43
|
},
|
|
48
44
|
},
|
|
49
|
-
|
|
45
|
+
model: 'gpt-4',
|
|
46
|
+
username: 123, // Should be string
|
|
47
|
+
};
|
|
50
48
|
|
|
51
|
-
const result = validateOpenCodeConfig(invalidConfig)
|
|
52
|
-
expect(result.valid).toBe(false)
|
|
53
|
-
expect(result.errors).toBeDefined()
|
|
54
|
-
expect(result.errors!.length).toBeGreaterThan(0)
|
|
55
|
-
})
|
|
49
|
+
const result = validateOpenCodeConfig(invalidConfig);
|
|
50
|
+
expect(result.valid).toBe(false);
|
|
51
|
+
expect(result.errors).toBeDefined();
|
|
52
|
+
expect(result.errors!.length).toBeGreaterThan(0);
|
|
53
|
+
});
|
|
56
54
|
|
|
57
55
|
it('should fix common configuration issues', () => {
|
|
58
56
|
const configWithIssues = {
|
|
59
|
-
username: 'test-user',
|
|
60
|
-
model: 'gpt-4',
|
|
61
|
-
tools: {
|
|
62
|
-
compact: { threshold: 80000 }, // Should be boolean
|
|
63
|
-
},
|
|
64
57
|
maxTokens: 4000, // Invalid property
|
|
58
|
+
model: 'gpt-4',
|
|
65
59
|
provider: {
|
|
66
60
|
berget: {
|
|
67
61
|
models: {
|
|
68
62
|
'test-model': {
|
|
69
|
-
name: 'Test Model',
|
|
70
|
-
maxTokens: 4000, // Should be moved to limit.context
|
|
71
63
|
contextWindow: 8000, // Should be moved to limit.context
|
|
64
|
+
maxTokens: 4000, // Should be moved to limit.context
|
|
65
|
+
name: 'Test Model',
|
|
72
66
|
},
|
|
73
67
|
},
|
|
74
68
|
},
|
|
75
69
|
},
|
|
76
|
-
|
|
70
|
+
tools: {
|
|
71
|
+
compact: { threshold: 80_000 }, // Should be boolean
|
|
72
|
+
},
|
|
73
|
+
username: 'test-user',
|
|
74
|
+
};
|
|
77
75
|
|
|
78
|
-
const fixed = fixOpenCodeConfig(configWithIssues)
|
|
76
|
+
const fixed = fixOpenCodeConfig(configWithIssues);
|
|
79
77
|
|
|
80
78
|
// tools.compact should be boolean
|
|
81
|
-
expect(typeof fixed.tools.compact).toBe('boolean')
|
|
79
|
+
expect(typeof fixed.tools.compact).toBe('boolean');
|
|
82
80
|
|
|
83
81
|
// maxTokens should be removed
|
|
84
|
-
expect(fixed.maxTokens).toBeUndefined()
|
|
82
|
+
expect(fixed.maxTokens).toBeUndefined();
|
|
85
83
|
|
|
86
84
|
// maxTokens and contextWindow should be moved to limit.context
|
|
87
|
-
expect(fixed.provider.berget.models['test-model'].limit).toBeDefined()
|
|
88
|
-
expect(fixed.provider.berget.models['test-model'].limit.context).toBe(8000)
|
|
89
|
-
expect(fixed.provider.berget.models['test-model'].maxTokens).toBeUndefined()
|
|
90
|
-
expect(
|
|
91
|
-
|
|
92
|
-
).toBeUndefined()
|
|
93
|
-
})
|
|
85
|
+
expect(fixed.provider.berget.models['test-model'].limit).toBeDefined();
|
|
86
|
+
expect(fixed.provider.berget.models['test-model'].limit.context).toBe(8000);
|
|
87
|
+
expect(fixed.provider.berget.models['test-model'].maxTokens).toBeUndefined();
|
|
88
|
+
expect(fixed.provider.berget.models['test-model'].contextWindow).toBeUndefined();
|
|
89
|
+
});
|
|
94
90
|
|
|
95
91
|
it('should validate the current opencode.json file', () => {
|
|
96
|
-
let currentConfig
|
|
92
|
+
let currentConfig;
|
|
97
93
|
try {
|
|
98
|
-
currentConfig = JSON.parse(readFileSync('opencode.json', 'utf8'))
|
|
94
|
+
currentConfig = JSON.parse(readFileSync('opencode.json', 'utf8'));
|
|
99
95
|
} catch (error) {
|
|
100
96
|
// Skip when opencode.json is not present (e.g. in CI or clean checkouts)
|
|
101
|
-
console.log('Skipping: opencode.json not found:', error)
|
|
102
|
-
return
|
|
97
|
+
console.log('Skipping: opencode.json not found:', error);
|
|
98
|
+
return;
|
|
103
99
|
}
|
|
104
100
|
|
|
105
101
|
// Apply fixes to handle common issues
|
|
106
|
-
const fixedConfig = fixOpenCodeConfig(currentConfig)
|
|
102
|
+
const fixedConfig = fixOpenCodeConfig(currentConfig);
|
|
107
103
|
|
|
108
104
|
// Validate the fixed config
|
|
109
|
-
const result = validateOpenCodeConfig(fixedConfig)
|
|
105
|
+
const result = validateOpenCodeConfig(fixedConfig);
|
|
110
106
|
|
|
111
107
|
// The fixed config should be valid according to the JSON Schema
|
|
112
|
-
expect(result.valid).toBe(true)
|
|
108
|
+
expect(result.valid).toBe(true);
|
|
113
109
|
|
|
114
110
|
if (!result.valid) {
|
|
115
|
-
console.log('Fixed opencode.json validation errors:')
|
|
116
|
-
result.errors
|
|
111
|
+
console.log('Fixed opencode.json validation errors:');
|
|
112
|
+
if (result.errors) for (const error of result.errors) console.log(` - ${error}`);
|
|
117
113
|
}
|
|
118
|
-
})
|
|
119
|
-
})
|
|
114
|
+
});
|
|
115
|
+
});
|
package/tsconfig.json
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
|
12
12
|
|
|
13
13
|
/* Language and Environment */
|
|
14
|
-
"target": "
|
|
14
|
+
"target": "ES2022",
|
|
15
|
+
"lib": ["ES2022"],
|
|
15
16
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
|
16
17
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
|
17
18
|
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
|
@@ -91,8 +92,8 @@
|
|
|
91
92
|
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
|
92
93
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
|
93
94
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
"noUnusedLocals": true /* Enable error reporting when local variables aren't read. */,
|
|
96
|
+
"noUnusedParameters": true /* Raise an error when a function parameter isn't read. */,
|
|
96
97
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
|
97
98
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
|
98
99
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|