berget 2.0.2 → 2.0.4
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/AGENTS.md +29 -0
- package/dist/index.js +2 -0
- package/dist/package.json +1 -1
- package/dist/src/commands/api-keys.js +55 -7
- package/dist/src/commands/code.js +103 -57
- package/dist/src/constants/command-structure.js +2 -0
- package/dist/src/services/api-key-service.js +64 -1
- package/dist/src/services/chat-service.js +14 -0
- package/dist/src/utils/config-loader.js +217 -0
- package/dist/tests/utils/config-loader.test.js +182 -0
- package/index.ts +19 -19
- package/opencode.json +1 -1
- package/package.json +1 -1
- package/src/commands/api-keys.ts +93 -8
- package/src/commands/code.ts +119 -58
- package/src/constants/command-structure.ts +2 -0
- package/src/services/api-key-service.ts +100 -2
- package/src/services/chat-service.ts +17 -0
- package/src/utils/config-loader.ts +261 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.getProviderModels = exports.getModelConfig = exports.getAllAgentConfigs = exports.getAgentConfig = exports.getConfigLoader = exports.ConfigLoader = void 0;
|
|
27
|
+
const fs = __importStar(require("fs"));
|
|
28
|
+
const path = __importStar(require("path"));
|
|
29
|
+
const logger_1 = require("./logger");
|
|
30
|
+
class ConfigLoader {
|
|
31
|
+
constructor(configPath) {
|
|
32
|
+
this.config = null;
|
|
33
|
+
// Default to opencode.json in current working directory
|
|
34
|
+
this.configPath = configPath || path.join(process.cwd(), 'opencode.json');
|
|
35
|
+
}
|
|
36
|
+
static getInstance(configPath) {
|
|
37
|
+
if (!ConfigLoader.instance) {
|
|
38
|
+
ConfigLoader.instance = new ConfigLoader(configPath);
|
|
39
|
+
}
|
|
40
|
+
return ConfigLoader.instance;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Load configuration from opencode.json
|
|
44
|
+
*/
|
|
45
|
+
loadConfig() {
|
|
46
|
+
if (this.config) {
|
|
47
|
+
return this.config;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
if (!fs.existsSync(this.configPath)) {
|
|
51
|
+
throw new Error(`Configuration file not found: ${this.configPath}`);
|
|
52
|
+
}
|
|
53
|
+
const configContent = fs.readFileSync(this.configPath, 'utf8');
|
|
54
|
+
this.config = JSON.parse(configContent);
|
|
55
|
+
logger_1.logger.debug(`Loaded configuration from ${this.configPath}`);
|
|
56
|
+
return this.config;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
logger_1.logger.error(`Failed to load configuration from ${this.configPath}:`, error);
|
|
60
|
+
throw new Error(`Failed to load configuration: ${error instanceof Error ? error.message : String(error)}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get agent configuration by name
|
|
65
|
+
*/
|
|
66
|
+
getAgentConfig(agentName) {
|
|
67
|
+
var _a;
|
|
68
|
+
const config = this.loadConfig();
|
|
69
|
+
return ((_a = config.agent) === null || _a === void 0 ? void 0 : _a[agentName]) || null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get all agent configurations
|
|
73
|
+
*/
|
|
74
|
+
getAllAgentConfigs() {
|
|
75
|
+
const config = this.loadConfig();
|
|
76
|
+
return config.agent || {};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get model configuration
|
|
80
|
+
*/
|
|
81
|
+
getModelConfig() {
|
|
82
|
+
const config = this.loadConfig();
|
|
83
|
+
// Extract from config or fall back to defaults
|
|
84
|
+
const primary = config.model || 'berget/deepseek-r1';
|
|
85
|
+
const small = config.small_model || 'berget/gpt-oss';
|
|
86
|
+
return { primary, small };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get provider model configuration
|
|
90
|
+
*/
|
|
91
|
+
getProviderModels() {
|
|
92
|
+
var _a, _b;
|
|
93
|
+
const config = this.loadConfig();
|
|
94
|
+
// Extract from provider configuration
|
|
95
|
+
if ((_b = (_a = config.provider) === null || _a === void 0 ? void 0 : _a.berget) === null || _b === void 0 ? void 0 : _b.models) {
|
|
96
|
+
return config.provider.berget.models;
|
|
97
|
+
}
|
|
98
|
+
// Fallback to defaults
|
|
99
|
+
return {
|
|
100
|
+
'deepseek-r1': {
|
|
101
|
+
name: 'GLM-4.6',
|
|
102
|
+
limit: { output: 4000, context: 90000 }
|
|
103
|
+
},
|
|
104
|
+
'gpt-oss': {
|
|
105
|
+
name: 'GPT-OSS',
|
|
106
|
+
limit: { output: 4000, context: 128000 }
|
|
107
|
+
},
|
|
108
|
+
'llama-8b': {
|
|
109
|
+
name: 'llama-3.1-8b',
|
|
110
|
+
limit: { output: 4000, context: 128000 }
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get command configurations
|
|
116
|
+
*/
|
|
117
|
+
getCommandConfigs() {
|
|
118
|
+
const config = this.loadConfig();
|
|
119
|
+
return config.command || {};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get watcher configuration
|
|
123
|
+
*/
|
|
124
|
+
getWatcherConfig() {
|
|
125
|
+
const config = this.loadConfig();
|
|
126
|
+
return config.watcher || { ignore: ['node_modules', 'dist', '.git', 'coverage'] };
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get provider configuration
|
|
130
|
+
*/
|
|
131
|
+
getProviderConfig() {
|
|
132
|
+
const config = this.loadConfig();
|
|
133
|
+
return config.provider || {};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Check if an agent exists
|
|
137
|
+
*/
|
|
138
|
+
hasAgent(agentName) {
|
|
139
|
+
return agentName in this.getAllAgentConfigs();
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get list of all available agent names
|
|
143
|
+
*/
|
|
144
|
+
getAgentNames() {
|
|
145
|
+
return Object.keys(this.getAllAgentConfigs());
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get list of primary agents (mode: 'primary')
|
|
149
|
+
*/
|
|
150
|
+
getPrimaryAgentNames() {
|
|
151
|
+
const agents = this.getAllAgentConfigs();
|
|
152
|
+
return Object.keys(agents).filter(name => agents[name].mode === 'primary');
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get list of subagents (mode: 'subagent')
|
|
156
|
+
*/
|
|
157
|
+
getSubagentNames() {
|
|
158
|
+
const agents = this.getAllAgentConfigs();
|
|
159
|
+
return Object.keys(agents).filter(name => agents[name].mode === 'subagent');
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Reload configuration from file
|
|
163
|
+
*/
|
|
164
|
+
reloadConfig() {
|
|
165
|
+
this.config = null;
|
|
166
|
+
return this.loadConfig();
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Set custom configuration path (for testing or different environments)
|
|
170
|
+
*/
|
|
171
|
+
setConfigPath(configPath) {
|
|
172
|
+
this.configPath = configPath;
|
|
173
|
+
this.config = null; // Force reload
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Get the current configuration path
|
|
177
|
+
*/
|
|
178
|
+
getConfigPath() {
|
|
179
|
+
return this.configPath;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
exports.ConfigLoader = ConfigLoader;
|
|
183
|
+
/**
|
|
184
|
+
* Convenience function to get the config loader instance
|
|
185
|
+
*/
|
|
186
|
+
function getConfigLoader(configPath) {
|
|
187
|
+
return ConfigLoader.getInstance(configPath);
|
|
188
|
+
}
|
|
189
|
+
exports.getConfigLoader = getConfigLoader;
|
|
190
|
+
/**
|
|
191
|
+
* Convenience function to get agent configuration
|
|
192
|
+
*/
|
|
193
|
+
function getAgentConfig(agentName, configPath) {
|
|
194
|
+
return getConfigLoader(configPath).getAgentConfig(agentName);
|
|
195
|
+
}
|
|
196
|
+
exports.getAgentConfig = getAgentConfig;
|
|
197
|
+
/**
|
|
198
|
+
* Convenience function to get all agent configurations
|
|
199
|
+
*/
|
|
200
|
+
function getAllAgentConfigs(configPath) {
|
|
201
|
+
return getConfigLoader(configPath).getAllAgentConfigs();
|
|
202
|
+
}
|
|
203
|
+
exports.getAllAgentConfigs = getAllAgentConfigs;
|
|
204
|
+
/**
|
|
205
|
+
* Convenience function to get model configuration
|
|
206
|
+
*/
|
|
207
|
+
function getModelConfig(configPath) {
|
|
208
|
+
return getConfigLoader(configPath).getModelConfig();
|
|
209
|
+
}
|
|
210
|
+
exports.getModelConfig = getModelConfig;
|
|
211
|
+
/**
|
|
212
|
+
* Convenience function to get provider models
|
|
213
|
+
*/
|
|
214
|
+
function getProviderModels(configPath) {
|
|
215
|
+
return getConfigLoader(configPath).getProviderModels();
|
|
216
|
+
}
|
|
217
|
+
exports.getProviderModels = getProviderModels;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
const vitest_1 = require("vitest");
|
|
27
|
+
const config_loader_1 = require("../../src/utils/config-loader");
|
|
28
|
+
const path = __importStar(require("path"));
|
|
29
|
+
const fs_1 = require("fs");
|
|
30
|
+
(0, vitest_1.describe)('ConfigLoader Singleton Pattern', () => {
|
|
31
|
+
const testConfigPath = path.join(process.cwd(), 'test-config.json');
|
|
32
|
+
const testConfigPath2 = path.join(process.cwd(), 'test-config2.json');
|
|
33
|
+
const testConfig = {
|
|
34
|
+
$schema: 'https://opencode.ai/config.json',
|
|
35
|
+
username: 'test-user',
|
|
36
|
+
model: 'test-model',
|
|
37
|
+
agent: {
|
|
38
|
+
test: {
|
|
39
|
+
model: 'test-model',
|
|
40
|
+
temperature: 0.5,
|
|
41
|
+
mode: 'primary',
|
|
42
|
+
permission: { edit: 'allow', bash: 'allow', webfetch: 'allow' },
|
|
43
|
+
description: 'Test agent',
|
|
44
|
+
prompt: 'Test prompt'
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
(0, vitest_1.beforeEach)(() => {
|
|
49
|
+
// Clean up any existing test files
|
|
50
|
+
if ((0, fs_1.existsSync)(testConfigPath))
|
|
51
|
+
(0, fs_1.unlinkSync)(testConfigPath);
|
|
52
|
+
if ((0, fs_1.existsSync)(testConfigPath2))
|
|
53
|
+
(0, fs_1.unlinkSync)(testConfigPath2);
|
|
54
|
+
config_loader_1.ConfigLoader.instance = null;
|
|
55
|
+
});
|
|
56
|
+
(0, vitest_1.afterEach)(() => {
|
|
57
|
+
// Clean up test files
|
|
58
|
+
if ((0, fs_1.existsSync)(testConfigPath))
|
|
59
|
+
(0, fs_1.unlinkSync)(testConfigPath);
|
|
60
|
+
if ((0, fs_1.existsSync)(testConfigPath2))
|
|
61
|
+
(0, fs_1.unlinkSync)(testConfigPath2);
|
|
62
|
+
config_loader_1.ConfigLoader.instance = null;
|
|
63
|
+
});
|
|
64
|
+
(0, vitest_1.it)('should create singleton instance on first call', () => {
|
|
65
|
+
const instance1 = config_loader_1.ConfigLoader.getInstance(testConfigPath);
|
|
66
|
+
const instance2 = config_loader_1.ConfigLoader.getInstance();
|
|
67
|
+
(0, vitest_1.expect)(instance1).toBe(instance2);
|
|
68
|
+
(0, vitest_1.expect)(instance1.getConfigPath()).toBe(testConfigPath);
|
|
69
|
+
});
|
|
70
|
+
(0, vitest_1.it)('should ignore configPath parameter after first instantiation', () => {
|
|
71
|
+
const instance1 = config_loader_1.ConfigLoader.getInstance(testConfigPath);
|
|
72
|
+
const instance2 = config_loader_1.ConfigLoader.getInstance(testConfigPath2); // Should be ignored
|
|
73
|
+
(0, vitest_1.expect)(instance1).toBe(instance2);
|
|
74
|
+
(0, vitest_1.expect)(instance1.getConfigPath()).toBe(testConfigPath); // Still uses first path
|
|
75
|
+
});
|
|
76
|
+
(0, vitest_1.it)('should load and cache configuration', () => {
|
|
77
|
+
(0, fs_1.writeFileSync)(testConfigPath, JSON.stringify(testConfig));
|
|
78
|
+
const loader = config_loader_1.ConfigLoader.getInstance(testConfigPath);
|
|
79
|
+
const config1 = loader.loadConfig();
|
|
80
|
+
const config2 = loader.loadConfig();
|
|
81
|
+
(0, vitest_1.expect)(config1).toBe(config2); // Same object reference (cached)
|
|
82
|
+
(0, vitest_1.expect)(config1.username).toBe('test-user');
|
|
83
|
+
});
|
|
84
|
+
(0, vitest_1.it)('should reload configuration when reloadConfig is called', () => {
|
|
85
|
+
(0, fs_1.writeFileSync)(testConfigPath, JSON.stringify(testConfig));
|
|
86
|
+
const loader = config_loader_1.ConfigLoader.getInstance(testConfigPath);
|
|
87
|
+
const config1 = loader.loadConfig();
|
|
88
|
+
// Modify file
|
|
89
|
+
const modifiedConfig = Object.assign(Object.assign({}, testConfig), { username: 'modified-user' });
|
|
90
|
+
(0, fs_1.writeFileSync)(testConfigPath, JSON.stringify(modifiedConfig));
|
|
91
|
+
const config2 = loader.loadConfig(); // Should still be cached
|
|
92
|
+
(0, vitest_1.expect)(config2).toBe(config1);
|
|
93
|
+
(0, vitest_1.expect)(config2.username).toBe('test-user');
|
|
94
|
+
const config3 = loader.reloadConfig(); // Should reload
|
|
95
|
+
(0, vitest_1.expect)(config3).not.toBe(config1);
|
|
96
|
+
(0, vitest_1.expect)(config3.username).toBe('modified-user');
|
|
97
|
+
});
|
|
98
|
+
(0, vitest_1.it)('should change config path and clear cache with setConfigPath', () => {
|
|
99
|
+
(0, fs_1.writeFileSync)(testConfigPath, JSON.stringify(testConfig));
|
|
100
|
+
(0, fs_1.writeFileSync)(testConfigPath2, JSON.stringify(Object.assign(Object.assign({}, testConfig), { username: 'user2' })));
|
|
101
|
+
const loader = config_loader_1.ConfigLoader.getInstance(testConfigPath);
|
|
102
|
+
const config1 = loader.loadConfig();
|
|
103
|
+
(0, vitest_1.expect)(config1.username).toBe('test-user');
|
|
104
|
+
loader.setConfigPath(testConfigPath2);
|
|
105
|
+
const config2 = loader.loadConfig();
|
|
106
|
+
(0, vitest_1.expect)(config2.username).toBe('user2');
|
|
107
|
+
(0, vitest_1.expect)(loader.getConfigPath()).toBe(testConfigPath2);
|
|
108
|
+
});
|
|
109
|
+
(0, vitest_1.it)('should throw error when config file does not exist', () => {
|
|
110
|
+
const loader = config_loader_1.ConfigLoader.getInstance('/nonexistent/config.json');
|
|
111
|
+
(0, vitest_1.expect)(() => loader.loadConfig()).toThrow('Configuration file not found');
|
|
112
|
+
});
|
|
113
|
+
(0, vitest_1.it)('should throw error when config file has invalid JSON', () => {
|
|
114
|
+
(0, fs_1.writeFileSync)(testConfigPath, 'invalid json content');
|
|
115
|
+
const loader = config_loader_1.ConfigLoader.getInstance(testConfigPath);
|
|
116
|
+
(0, vitest_1.expect)(() => loader.loadConfig()).toThrow('Invalid JSON in configuration file');
|
|
117
|
+
});
|
|
118
|
+
(0, vitest_1.it)('should provide fallback model configuration with warning', () => {
|
|
119
|
+
const minimalConfig = { username: 'test' }; // No model settings
|
|
120
|
+
(0, fs_1.writeFileSync)(testConfigPath, JSON.stringify(minimalConfig));
|
|
121
|
+
const loader = config_loader_1.ConfigLoader.getInstance(testConfigPath);
|
|
122
|
+
const modelConfig = loader.getModelConfig();
|
|
123
|
+
(0, vitest_1.expect)(modelConfig.primary).toBe('berget/deepseek-r1');
|
|
124
|
+
(0, vitest_1.expect)(modelConfig.small).toBe('berget/gpt-oss');
|
|
125
|
+
});
|
|
126
|
+
(0, vitest_1.it)('should provide fallback provider models with warning', () => {
|
|
127
|
+
const minimalConfig = { username: 'test' }; // No provider settings
|
|
128
|
+
(0, fs_1.writeFileSync)(testConfigPath, JSON.stringify(minimalConfig));
|
|
129
|
+
const loader = config_loader_1.ConfigLoader.getInstance(testConfigPath);
|
|
130
|
+
const providerModels = loader.getProviderModels();
|
|
131
|
+
(0, vitest_1.expect)(providerModels['deepseek-r1']).toBeDefined();
|
|
132
|
+
(0, vitest_1.expect)(providerModels['gpt-oss']).toBeDefined();
|
|
133
|
+
(0, vitest_1.expect)(providerModels['llama-8b']).toBeDefined();
|
|
134
|
+
});
|
|
135
|
+
(0, vitest_1.it)('should provide fallback watcher configuration with warning', () => {
|
|
136
|
+
const minimalConfig = { username: 'test' }; // No watcher settings
|
|
137
|
+
(0, fs_1.writeFileSync)(testConfigPath, JSON.stringify(minimalConfig));
|
|
138
|
+
const loader = config_loader_1.ConfigLoader.getInstance(testConfigPath);
|
|
139
|
+
const watcherConfig = loader.getWatcherConfig();
|
|
140
|
+
(0, vitest_1.expect)(watcherConfig.ignore).toContain('node_modules');
|
|
141
|
+
(0, vitest_1.expect)(watcherConfig.ignore).toContain('dist');
|
|
142
|
+
(0, vitest_1.expect)(watcherConfig.ignore).toContain('.git');
|
|
143
|
+
(0, vitest_1.expect)(watcherConfig.ignore).toContain('coverage');
|
|
144
|
+
});
|
|
145
|
+
(0, vitest_1.it)('should use actual configuration when provided', () => {
|
|
146
|
+
const fullConfig = Object.assign(Object.assign({}, testConfig), { model: 'custom-primary-model', small_model: 'custom-small-model', provider: {
|
|
147
|
+
berget: {
|
|
148
|
+
models: {
|
|
149
|
+
'custom-model': {
|
|
150
|
+
name: 'Custom Model',
|
|
151
|
+
limit: { output: 2000, context: 4000 }
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}, watcher: {
|
|
156
|
+
ignore: ['custom-ignore']
|
|
157
|
+
} });
|
|
158
|
+
(0, fs_1.writeFileSync)(testConfigPath, JSON.stringify(fullConfig));
|
|
159
|
+
const loader = config_loader_1.ConfigLoader.getInstance(testConfigPath);
|
|
160
|
+
const modelConfig = loader.getModelConfig();
|
|
161
|
+
(0, vitest_1.expect)(modelConfig.primary).toBe('custom-primary-model');
|
|
162
|
+
(0, vitest_1.expect)(modelConfig.small).toBe('custom-small-model');
|
|
163
|
+
const providerModels = loader.getProviderModels();
|
|
164
|
+
(0, vitest_1.expect)(providerModels['custom-model']).toBeDefined();
|
|
165
|
+
(0, vitest_1.expect)(providerModels['custom-model'].name).toBe('Custom Model');
|
|
166
|
+
const watcherConfig = loader.getWatcherConfig();
|
|
167
|
+
(0, vitest_1.expect)(watcherConfig.ignore).toEqual(['custom-ignore']);
|
|
168
|
+
});
|
|
169
|
+
(0, vitest_1.it)('should maintain consistent getter method behavior', () => {
|
|
170
|
+
(0, fs_1.writeFileSync)(testConfigPath, JSON.stringify(testConfig));
|
|
171
|
+
const loader = config_loader_1.ConfigLoader.getInstance(testConfigPath);
|
|
172
|
+
// All getter methods should use the same cached config
|
|
173
|
+
const agentConfig = loader.getAgentConfig('test');
|
|
174
|
+
const allAgents = loader.getAllAgentConfigs();
|
|
175
|
+
const commandConfigs = loader.getCommandConfigs();
|
|
176
|
+
const providerConfig = loader.getProviderConfig();
|
|
177
|
+
(0, vitest_1.expect)(agentConfig).toBeDefined();
|
|
178
|
+
(0, vitest_1.expect)(allAgents.test).toBeDefined();
|
|
179
|
+
(0, vitest_1.expect)(commandConfigs).toEqual({});
|
|
180
|
+
(0, vitest_1.expect)(providerConfig).toEqual({});
|
|
181
|
+
});
|
|
182
|
+
});
|
package/index.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { registerCommands } from './src/commands'
|
|
|
5
5
|
import { checkBergetConfig } from './src/utils/config-checker'
|
|
6
6
|
import chalk from 'chalk'
|
|
7
7
|
import { version } from './package.json'
|
|
8
|
+
process.env.DOTENV_CONFIG_OVERRIDE = 'true'
|
|
9
|
+
import 'dotenv/config'
|
|
8
10
|
|
|
9
11
|
// Set version and description
|
|
10
12
|
program
|
|
@@ -18,7 +20,7 @@ program
|
|
|
18
20
|
\\____/ \\___|_| \\__, |\\___|\\_\\_ \\_| |_/\\___/
|
|
19
21
|
__/ |
|
|
20
22
|
|___/ AI on European terms
|
|
21
|
-
Version: ${version}
|
|
23
|
+
Version: ${version}`
|
|
22
24
|
)
|
|
23
25
|
.version(version, '-v, --version')
|
|
24
26
|
.option('--local', 'Use local API endpoint (hidden)', false)
|
|
@@ -35,34 +37,32 @@ if (process.argv.length <= 2) {
|
|
|
35
37
|
console.log(chalk.blue('\nWelcome to the Berget CLI!'))
|
|
36
38
|
console.log(chalk.blue('Common commands:'))
|
|
37
39
|
console.log(
|
|
38
|
-
chalk.blue(` ${chalk.bold('berget auth login')} - Log in to Berget`)
|
|
40
|
+
chalk.blue(` ${chalk.bold('berget auth login')} - Log in to Berget`)
|
|
39
41
|
)
|
|
40
42
|
console.log(
|
|
41
43
|
chalk.blue(
|
|
42
|
-
` ${chalk.bold('berget models list')} - List available AI models
|
|
43
|
-
)
|
|
44
|
+
` ${chalk.bold('berget models list')} - List available AI models`
|
|
45
|
+
)
|
|
44
46
|
)
|
|
45
47
|
console.log(
|
|
46
48
|
chalk.blue(
|
|
47
|
-
` ${chalk.bold('berget chat run')} - Start a chat session
|
|
48
|
-
)
|
|
49
|
+
` ${chalk.bold('berget chat run')} - Start a chat session`
|
|
50
|
+
)
|
|
49
51
|
)
|
|
50
52
|
console.log(
|
|
51
53
|
chalk.blue(
|
|
52
54
|
` ${chalk.bold(
|
|
53
|
-
'berget code init'
|
|
54
|
-
)} - Initialize AI coding assistant
|
|
55
|
-
)
|
|
55
|
+
'berget code init'
|
|
56
|
+
)} - Initialize AI coding assistant`
|
|
57
|
+
)
|
|
56
58
|
)
|
|
57
59
|
console.log(
|
|
58
|
-
chalk.blue(
|
|
59
|
-
` ${chalk.bold('berget api-keys list')} - List your API keys`,
|
|
60
|
-
),
|
|
60
|
+
chalk.blue(` ${chalk.bold('berget api-keys list')} - List your API keys`)
|
|
61
61
|
)
|
|
62
62
|
console.log(
|
|
63
63
|
chalk.blue(
|
|
64
|
-
`\nRun ${chalk.bold('berget --help')} for a complete list of commands
|
|
65
|
-
)
|
|
64
|
+
`\nRun ${chalk.bold('berget --help')} for a complete list of commands.`
|
|
65
|
+
)
|
|
66
66
|
)
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -89,15 +89,15 @@ program.on('command:*', (operands) => {
|
|
|
89
89
|
console.log(
|
|
90
90
|
chalk.yellow(
|
|
91
91
|
`Did you mean? ${chalk.bold(
|
|
92
|
-
`berget ${commonMistakes[unknownCommand]}
|
|
93
|
-
)}
|
|
94
|
-
)
|
|
92
|
+
`berget ${commonMistakes[unknownCommand]}`
|
|
93
|
+
)}`
|
|
94
|
+
)
|
|
95
95
|
)
|
|
96
96
|
} else {
|
|
97
97
|
// Try to find similar commands
|
|
98
98
|
const availableCommands = program.commands.map((cmd) => cmd.name())
|
|
99
99
|
const similarCommands = availableCommands.filter(
|
|
100
|
-
(cmd) => cmd.includes(unknownCommand) || unknownCommand.includes(cmd)
|
|
100
|
+
(cmd) => cmd.includes(unknownCommand) || unknownCommand.includes(cmd)
|
|
101
101
|
)
|
|
102
102
|
|
|
103
103
|
if (similarCommands.length > 0) {
|
|
@@ -108,7 +108,7 @@ program.on('command:*', (operands) => {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
console.log(
|
|
111
|
-
chalk.blue('\nRun `berget --help` for a list of available commands.')
|
|
111
|
+
chalk.blue('\nRun `berget --help` for a list of available commands.')
|
|
112
112
|
)
|
|
113
113
|
}
|
|
114
114
|
|
package/opencode.json
CHANGED
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"webfetch": "allow"
|
|
59
59
|
},
|
|
60
60
|
"description": "Declarative GitOps infra with FluxCD, Kustomize, Helm, operators.",
|
|
61
|
-
"prompt": "You are Berget Code DevOps agent. Voice: Scandinavian calm—precise, concise, confident. Start simple: k8s/{deployment,service,ingress}. Add FluxCD sync to repo and image automation. Use Kustomize bases/overlays (staging, production). Add dependencies via Helm from upstream sources; prefer native operators when available (CloudNativePG, cert-manager, external-dns). SemVer with -rc tags keeps CI environments current. Observability with Prometheus/Grafana. No manual kubectl in production—Git is the source of truth."
|
|
61
|
+
"prompt": "You are Berget Code DevOps agent. Voice: Scandinavian calm—precise, concise, confident. Start simple: k8s/{deployment,service,ingress}. Add FluxCD sync to repo and image automation. Use Kustomize bases/overlays (staging, production). Add dependencies via Helm from upstream sources; prefer native operators when available (CloudNativePG, cert-manager, external-dns). SemVer with -rc tags keeps CI environments current. Observability with Prometheus/Grafana. No manual kubectl in production—Git is the source of truth.\n\nHelm Values Configuration Process:\n1. Documentation First Approach: Always fetch official documentation from Artifact Hub/GitHub for the specific chart version before writing values. Search Artifact Hub for exact chart version documentation, check the chart's GitHub repository for official docs and examples, verify the exact version being used in the deployment.\n2. Validation Requirements: Check for available validation schemas before committing YAML files. Use Helm's built-in validation tools (helm lint, helm template). Validate against JSON schema if available for the chart. Ensure YAML syntax correctness with linters.\n3. Standard Workflow: Identify chart name and exact version. Fetch official documentation from Artifact Hub/GitHub. Check for available schemas and validation tools. Write values according to official documentation. Validate against schema (if available). Test with helm template or helm lint. Commit validated YAML files.\n4. Quality Assurance: Never commit unvalidated Helm values. Use helm dependency update when adding new charts. Test rendering with helm template --dry-run before deployment. Document any custom values with comments referencing official docs."
|
|
62
62
|
},
|
|
63
63
|
"app": {
|
|
64
64
|
"model": "berget/deepseek-r1",
|
package/package.json
CHANGED
package/src/commands/api-keys.ts
CHANGED
|
@@ -34,7 +34,7 @@ export function registerApiKeyCommands(program: Command): void {
|
|
|
34
34
|
|
|
35
35
|
// Create a table-like format with headers
|
|
36
36
|
console.log(
|
|
37
|
-
chalk.dim('ID'.padEnd(
|
|
37
|
+
chalk.dim('ID (8)'.padEnd(8)) +
|
|
38
38
|
chalk.dim('NAME'.padEnd(25)) +
|
|
39
39
|
chalk.dim('PREFIX'.padEnd(12)) +
|
|
40
40
|
chalk.dim('STATUS'.padEnd(12)) +
|
|
@@ -52,6 +52,9 @@ export function registerApiKeyCommands(program: Command): void {
|
|
|
52
52
|
? chalk.green('● Active')
|
|
53
53
|
: chalk.red('● Inactive')
|
|
54
54
|
|
|
55
|
+
// Show only first 8 characters of ID for easier reading
|
|
56
|
+
const shortId = String(key.id).substring(0, 8)
|
|
57
|
+
|
|
55
58
|
// Format the prefix to ensure it's not too long
|
|
56
59
|
const prefixStr =
|
|
57
60
|
key.prefix.length > 12
|
|
@@ -59,7 +62,7 @@ export function registerApiKeyCommands(program: Command): void {
|
|
|
59
62
|
: key.prefix
|
|
60
63
|
|
|
61
64
|
console.log(
|
|
62
|
-
|
|
65
|
+
shortId.padEnd(8) +
|
|
63
66
|
key.name.padEnd(25) +
|
|
64
67
|
prefixStr.padEnd(15) +
|
|
65
68
|
status.padEnd(12) +
|
|
@@ -90,6 +93,7 @@ export function registerApiKeyCommands(program: Command): void {
|
|
|
90
93
|
.description('Create a new API key')
|
|
91
94
|
.option('--name <name>', 'Name of the API key')
|
|
92
95
|
.option('--description <description>', 'Description of the API key')
|
|
96
|
+
|
|
93
97
|
.action(async (options) => {
|
|
94
98
|
try {
|
|
95
99
|
if (!options.name) {
|
|
@@ -149,15 +153,96 @@ export function registerApiKeyCommands(program: Command): void {
|
|
|
149
153
|
apiKey
|
|
150
154
|
.command(ApiKeyService.COMMANDS.DELETE)
|
|
151
155
|
.description('Delete an API key')
|
|
152
|
-
.argument(
|
|
153
|
-
|
|
156
|
+
.argument(
|
|
157
|
+
'<identifier>',
|
|
158
|
+
'ID (first 8 chars), full ID, or name of the API key to delete',
|
|
159
|
+
)
|
|
160
|
+
.action(async (identifier) => {
|
|
154
161
|
try {
|
|
155
|
-
console.log(chalk.blue(`Deleting API key ${id}...`))
|
|
156
|
-
|
|
157
162
|
const apiKeyService = ApiKeyService.getInstance()
|
|
158
|
-
await apiKeyService.delete(id)
|
|
159
163
|
|
|
160
|
-
|
|
164
|
+
// First, get all API keys to find the matching one
|
|
165
|
+
const keys = await apiKeyService.list()
|
|
166
|
+
|
|
167
|
+
// Try to find the key by:
|
|
168
|
+
// 1. Full ID match
|
|
169
|
+
// 2. Short ID (first 8 chars) match
|
|
170
|
+
// 3. Exact name match
|
|
171
|
+
// 4. Partial name match
|
|
172
|
+
|
|
173
|
+
// Check for exact matches first (full ID or exact name)
|
|
174
|
+
let exactMatches = keys.filter(
|
|
175
|
+
(key) => String(key.id) === identifier || key.name === identifier,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
// If no exact matches, check for short ID matches
|
|
179
|
+
if (exactMatches.length === 0) {
|
|
180
|
+
exactMatches = keys.filter(
|
|
181
|
+
(key) => String(key.id).substring(0, 8) === identifier,
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// If still no matches, check for partial name matches
|
|
186
|
+
if (exactMatches.length === 0) {
|
|
187
|
+
exactMatches = keys.filter((key) =>
|
|
188
|
+
key.name.toLowerCase().includes(identifier.toLowerCase()),
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Handle multiple matches
|
|
193
|
+
if (exactMatches.length > 1) {
|
|
194
|
+
console.error(
|
|
195
|
+
chalk.red(
|
|
196
|
+
`Error: Multiple API keys found matching "${identifier}"`,
|
|
197
|
+
),
|
|
198
|
+
)
|
|
199
|
+
console.log('')
|
|
200
|
+
console.log('Please be more specific. Matching keys:')
|
|
201
|
+
exactMatches.forEach((key) => {
|
|
202
|
+
const shortId = String(key.id).substring(0, 8)
|
|
203
|
+
console.log(` ${shortId.padEnd(8)} ${key.name}`)
|
|
204
|
+
})
|
|
205
|
+
console.log('')
|
|
206
|
+
console.log(
|
|
207
|
+
'Use the first 8 characters of the ID to specify which key to delete.',
|
|
208
|
+
)
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Handle no matches
|
|
213
|
+
if (exactMatches.length === 0) {
|
|
214
|
+
console.error(
|
|
215
|
+
chalk.red(`Error: No API key found matching "${identifier}"`),
|
|
216
|
+
)
|
|
217
|
+
console.log('')
|
|
218
|
+
console.log('Available API keys:')
|
|
219
|
+
keys.forEach((key) => {
|
|
220
|
+
const shortId = String(key.id).substring(0, 8)
|
|
221
|
+
console.log(` ${shortId.padEnd(8)} ${key.name}`)
|
|
222
|
+
})
|
|
223
|
+
console.log('')
|
|
224
|
+
console.log(
|
|
225
|
+
'Use the first 8 characters of the ID, full ID, or name to delete.',
|
|
226
|
+
)
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const matchingKey = exactMatches[0]
|
|
231
|
+
|
|
232
|
+
const keyId = String(matchingKey.id)
|
|
233
|
+
const shortId = keyId.substring(0, 8)
|
|
234
|
+
|
|
235
|
+
console.log(
|
|
236
|
+
chalk.blue(`Deleting API key ${shortId} (${matchingKey.name})...`),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
await apiKeyService.delete(keyId)
|
|
240
|
+
|
|
241
|
+
console.log(
|
|
242
|
+
chalk.green(
|
|
243
|
+
`✓ API key ${shortId} (${matchingKey.name}) has been deleted`,
|
|
244
|
+
),
|
|
245
|
+
)
|
|
161
246
|
console.log('')
|
|
162
247
|
console.log(
|
|
163
248
|
chalk.dim(
|