berget 2.0.3 → 2.0.5
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 +119 -24
- package/dist/src/commands/chat.js +6 -6
- 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/utils/config-loader.js +217 -0
- package/dist/src/utils/error-handler.js +98 -22
- 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 +156 -28
- package/src/commands/chat.ts +6 -6
- 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/utils/config-loader.ts +261 -0
- package/src/utils/error-handler.ts +120 -23
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
|
@@ -4,6 +4,35 @@ import { ApiKeyService, ApiKey } from '../services/api-key-service'
|
|
|
4
4
|
import { handleError } from '../utils/error-handler'
|
|
5
5
|
import { DefaultApiKeyManager } from '../utils/default-api-key'
|
|
6
6
|
|
|
7
|
+
// Helper functions for better date formatting
|
|
8
|
+
function formatDate(dateString: string): string {
|
|
9
|
+
const date = new Date(dateString)
|
|
10
|
+
const now = new Date()
|
|
11
|
+
const diffTime = Math.abs(now.getTime() - date.getTime())
|
|
12
|
+
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
|
13
|
+
|
|
14
|
+
if (diffDays === 0) return chalk.green('Today')
|
|
15
|
+
if (diffDays === 1) return chalk.yellow('Yesterday')
|
|
16
|
+
if (diffDays < 7) return chalk.yellow(`${diffDays} days ago`)
|
|
17
|
+
if (diffDays < 30) return chalk.blue(`${Math.floor(diffDays / 7)} weeks ago`)
|
|
18
|
+
if (diffDays < 365) return chalk.magenta(`${Math.floor(diffDays / 30)} months ago`)
|
|
19
|
+
return chalk.gray(`${Math.floor(diffDays / 365)} years ago`)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function formatLastUsed(dateString: string): string {
|
|
23
|
+
const date = new Date(dateString)
|
|
24
|
+
const now = new Date()
|
|
25
|
+
const diffTime = Math.abs(now.getTime() - date.getTime())
|
|
26
|
+
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
|
27
|
+
|
|
28
|
+
if (diffDays === 0) return chalk.green('Today')
|
|
29
|
+
if (diffDays === 1) return chalk.yellow('Yesterday')
|
|
30
|
+
if (diffDays < 7) return chalk.yellow(`${diffDays} days ago`)
|
|
31
|
+
if (diffDays < 30) return chalk.blue(`${Math.floor(diffDays / 7)} weeks ago`)
|
|
32
|
+
if (diffDays < 365) return chalk.magenta(`${Math.floor(diffDays / 30)} months ago`)
|
|
33
|
+
return chalk.gray(`${Math.floor(diffDays / 365)} years ago`)
|
|
34
|
+
}
|
|
35
|
+
|
|
7
36
|
/**
|
|
8
37
|
* Register API key commands
|
|
9
38
|
*/
|
|
@@ -29,42 +58,59 @@ export function registerApiKeyCommands(program: Command): void {
|
|
|
29
58
|
return
|
|
30
59
|
}
|
|
31
60
|
|
|
32
|
-
console.log(chalk.bold('Your API keys:'))
|
|
61
|
+
console.log(chalk.bold('🔑 Your API keys:'))
|
|
33
62
|
console.log('')
|
|
34
63
|
|
|
35
|
-
// Create a table
|
|
64
|
+
// Create a more readable table with better spacing and colors
|
|
65
|
+
const idWidth = 10
|
|
66
|
+
const nameWidth = 30
|
|
67
|
+
const prefixWidth = 20
|
|
68
|
+
const statusWidth = 12
|
|
69
|
+
const createdWidth = 12
|
|
70
|
+
const usedWidth = 15
|
|
71
|
+
|
|
36
72
|
console.log(
|
|
37
|
-
chalk.dim('ID'.padEnd(
|
|
38
|
-
chalk.dim('NAME'.padEnd(
|
|
39
|
-
chalk.dim('PREFIX'.padEnd(
|
|
40
|
-
chalk.dim('STATUS'.padEnd(
|
|
41
|
-
chalk.dim('CREATED'.padEnd(
|
|
42
|
-
chalk.dim('LAST USED')
|
|
73
|
+
chalk.dim('ID'.padEnd(idWidth)) +
|
|
74
|
+
chalk.dim('NAME'.padEnd(nameWidth)) +
|
|
75
|
+
chalk.dim('PREFIX'.padEnd(prefixWidth)) +
|
|
76
|
+
chalk.dim('STATUS'.padEnd(statusWidth)) +
|
|
77
|
+
chalk.dim('CREATED'.padEnd(createdWidth)) +
|
|
78
|
+
chalk.dim('LAST USED')
|
|
43
79
|
)
|
|
44
80
|
|
|
45
|
-
console.log(chalk.dim('─'.repeat(
|
|
81
|
+
console.log(chalk.dim('─'.repeat(idWidth + nameWidth + prefixWidth + statusWidth + createdWidth + usedWidth + 5)))
|
|
46
82
|
|
|
47
83
|
keys.forEach((key: ApiKey) => {
|
|
48
84
|
const lastUsed = key.lastUsed
|
|
49
|
-
? key.lastUsed
|
|
50
|
-
: 'Never'
|
|
85
|
+
? formatLastUsed(key.lastUsed)
|
|
86
|
+
: chalk.yellow('Never used')
|
|
51
87
|
const status = key.active
|
|
52
88
|
? chalk.green('● Active')
|
|
53
89
|
: chalk.red('● Inactive')
|
|
54
90
|
|
|
55
|
-
//
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
91
|
+
// Show only first 8 characters of ID for easier reading
|
|
92
|
+
const shortId = chalk.cyan(String(key.id).substring(0, 8))
|
|
93
|
+
|
|
94
|
+
// Format the prefix with better truncation
|
|
95
|
+
const prefixStr = key.prefix.length > prefixWidth
|
|
96
|
+
? key.prefix.substring(0, prefixWidth - 3) + '...'
|
|
97
|
+
: chalk.gray(key.prefix)
|
|
98
|
+
|
|
99
|
+
// Truncate name if too long
|
|
100
|
+
const nameStr = key.name.length > nameWidth
|
|
101
|
+
? key.name.substring(0, nameWidth - 3) + '...'
|
|
102
|
+
: key.name
|
|
103
|
+
|
|
104
|
+
// Format created date
|
|
105
|
+
const createdDate = formatDate(key.created)
|
|
60
106
|
|
|
61
107
|
console.log(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
prefixStr.padEnd(
|
|
65
|
-
status.padEnd(
|
|
66
|
-
|
|
67
|
-
lastUsed
|
|
108
|
+
shortId.padEnd(idWidth) +
|
|
109
|
+
nameStr.padEnd(nameWidth) +
|
|
110
|
+
prefixStr.padEnd(prefixWidth) +
|
|
111
|
+
status.padEnd(statusWidth) +
|
|
112
|
+
createdDate.padEnd(createdWidth) +
|
|
113
|
+
lastUsed
|
|
68
114
|
)
|
|
69
115
|
})
|
|
70
116
|
|
|
@@ -90,6 +136,7 @@ export function registerApiKeyCommands(program: Command): void {
|
|
|
90
136
|
.description('Create a new API key')
|
|
91
137
|
.option('--name <name>', 'Name of the API key')
|
|
92
138
|
.option('--description <description>', 'Description of the API key')
|
|
139
|
+
|
|
93
140
|
.action(async (options) => {
|
|
94
141
|
try {
|
|
95
142
|
if (!options.name) {
|
|
@@ -149,15 +196,96 @@ export function registerApiKeyCommands(program: Command): void {
|
|
|
149
196
|
apiKey
|
|
150
197
|
.command(ApiKeyService.COMMANDS.DELETE)
|
|
151
198
|
.description('Delete an API key')
|
|
152
|
-
.argument(
|
|
153
|
-
|
|
199
|
+
.argument(
|
|
200
|
+
'<identifier>',
|
|
201
|
+
'ID (first 8 chars), full ID, or name of the API key to delete',
|
|
202
|
+
)
|
|
203
|
+
.action(async (identifier) => {
|
|
154
204
|
try {
|
|
155
|
-
console.log(chalk.blue(`Deleting API key ${id}...`))
|
|
156
|
-
|
|
157
205
|
const apiKeyService = ApiKeyService.getInstance()
|
|
158
|
-
await apiKeyService.delete(id)
|
|
159
206
|
|
|
160
|
-
|
|
207
|
+
// First, get all API keys to find the matching one
|
|
208
|
+
const keys = await apiKeyService.list()
|
|
209
|
+
|
|
210
|
+
// Try to find the key by:
|
|
211
|
+
// 1. Full ID match
|
|
212
|
+
// 2. Short ID (first 8 chars) match
|
|
213
|
+
// 3. Exact name match
|
|
214
|
+
// 4. Partial name match
|
|
215
|
+
|
|
216
|
+
// Check for exact matches first (full ID or exact name)
|
|
217
|
+
let exactMatches = keys.filter(
|
|
218
|
+
(key) => String(key.id) === identifier || key.name === identifier,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
// If no exact matches, check for short ID matches
|
|
222
|
+
if (exactMatches.length === 0) {
|
|
223
|
+
exactMatches = keys.filter(
|
|
224
|
+
(key) => String(key.id).substring(0, 8) === identifier,
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// If still no matches, check for partial name matches
|
|
229
|
+
if (exactMatches.length === 0) {
|
|
230
|
+
exactMatches = keys.filter((key) =>
|
|
231
|
+
key.name.toLowerCase().includes(identifier.toLowerCase()),
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Handle multiple matches
|
|
236
|
+
if (exactMatches.length > 1) {
|
|
237
|
+
console.error(
|
|
238
|
+
chalk.red(
|
|
239
|
+
`Error: Multiple API keys found matching "${identifier}"`,
|
|
240
|
+
),
|
|
241
|
+
)
|
|
242
|
+
console.log('')
|
|
243
|
+
console.log('Please be more specific. Matching keys:')
|
|
244
|
+
exactMatches.forEach((key) => {
|
|
245
|
+
const shortId = String(key.id).substring(0, 8)
|
|
246
|
+
console.log(` ${shortId.padEnd(8)} ${key.name}`)
|
|
247
|
+
})
|
|
248
|
+
console.log('')
|
|
249
|
+
console.log(
|
|
250
|
+
'Use the first 8 characters of the ID to specify which key to delete.',
|
|
251
|
+
)
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Handle no matches
|
|
256
|
+
if (exactMatches.length === 0) {
|
|
257
|
+
console.error(
|
|
258
|
+
chalk.red(`Error: No API key found matching "${identifier}"`),
|
|
259
|
+
)
|
|
260
|
+
console.log('')
|
|
261
|
+
console.log('Available API keys:')
|
|
262
|
+
keys.forEach((key) => {
|
|
263
|
+
const shortId = String(key.id).substring(0, 8)
|
|
264
|
+
console.log(` ${shortId.padEnd(8)} ${key.name}`)
|
|
265
|
+
})
|
|
266
|
+
console.log('')
|
|
267
|
+
console.log(
|
|
268
|
+
'Use the first 8 characters of the ID, full ID, or name to delete.',
|
|
269
|
+
)
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const matchingKey = exactMatches[0]
|
|
274
|
+
|
|
275
|
+
const keyId = String(matchingKey.id)
|
|
276
|
+
const shortId = keyId.substring(0, 8)
|
|
277
|
+
|
|
278
|
+
console.log(
|
|
279
|
+
chalk.blue(`Deleting API key ${shortId} (${matchingKey.name})...`),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
await apiKeyService.delete(keyId)
|
|
283
|
+
|
|
284
|
+
console.log(
|
|
285
|
+
chalk.green(
|
|
286
|
+
`✓ API key ${shortId} (${matchingKey.name}) has been deleted`,
|
|
287
|
+
),
|
|
288
|
+
)
|
|
161
289
|
console.log('')
|
|
162
290
|
console.log(
|
|
163
291
|
chalk.dim(
|
package/src/commands/chat.ts
CHANGED
|
@@ -41,11 +41,11 @@ export function registerChatCommands(program: Command): void {
|
|
|
41
41
|
chat
|
|
42
42
|
.command(SUBCOMMANDS.CHAT.RUN)
|
|
43
43
|
.description('Run a chat session with a specified model')
|
|
44
|
-
.argument('[model]', 'Model to use (default: openai/gpt-oss)')
|
|
45
44
|
.argument('[message]', 'Message to send directly (skips interactive mode)')
|
|
46
|
-
|
|
45
|
+
.option('-m, --model <model>', 'Model to use (default: deepseek-r1)')
|
|
46
|
+
.option('--no-reasoning', 'Disable reasoning mode (adds </think> to messages)')
|
|
47
47
|
.option('-t, --temperature <temp>', 'Temperature (0-1)', parseFloat)
|
|
48
|
-
.option('
|
|
48
|
+
.option('--max-tokens <tokens>', 'Maximum tokens to generate', parseInt)
|
|
49
49
|
.option('-k, --api-key <key>', 'API key to use for this chat session')
|
|
50
50
|
.option(
|
|
51
51
|
'--api-key-id <id>',
|
|
@@ -55,7 +55,7 @@ export function registerChatCommands(program: Command): void {
|
|
|
55
55
|
'--no-stream',
|
|
56
56
|
'Disable streaming (streaming is enabled by default)',
|
|
57
57
|
)
|
|
58
|
-
.action(async (
|
|
58
|
+
.action(async (message, options) => {
|
|
59
59
|
try {
|
|
60
60
|
const chatService = ChatService.getInstance()
|
|
61
61
|
|
|
@@ -279,7 +279,7 @@ export function registerChatCommands(program: Command): void {
|
|
|
279
279
|
try {
|
|
280
280
|
// Call the API
|
|
281
281
|
const completionOptions: ChatCompletionOptions = {
|
|
282
|
-
model: model || 'openai/gpt-oss',
|
|
282
|
+
model: options.model || 'openai/gpt-oss',
|
|
283
283
|
messages: messages,
|
|
284
284
|
temperature:
|
|
285
285
|
options.temperature !== undefined ? options.temperature : 0.7,
|
|
@@ -415,7 +415,7 @@ export function registerChatCommands(program: Command): void {
|
|
|
415
415
|
try {
|
|
416
416
|
// Call the API
|
|
417
417
|
const completionOptions: ChatCompletionOptions = {
|
|
418
|
-
model: model || 'openai/gpt-oss',
|
|
418
|
+
model: options.model || 'openai/gpt-oss',
|
|
419
419
|
messages: messages,
|
|
420
420
|
temperature:
|
|
421
421
|
options.temperature !== undefined ? options.temperature : 0.7,
|