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/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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "berget",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "berget": "dist/index.js"
@@ -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-like format with headers
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(10)) +
38
- chalk.dim('NAME'.padEnd(25)) +
39
- chalk.dim('PREFIX'.padEnd(12)) +
40
- chalk.dim('STATUS'.padEnd(12)) +
41
- chalk.dim('CREATED'.padEnd(12)) +
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(85)))
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.substring(0, 10)
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
- // Format the prefix to ensure it's not too long
56
- const prefixStr =
57
- key.prefix.length > 12
58
- ? key.prefix.substring(0, 12) + '...'
59
- : key.prefix
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
- String(key.id).padEnd(10) +
63
- key.name.padEnd(25) +
64
- prefixStr.padEnd(15) +
65
- status.padEnd(12) +
66
- key.created.substring(0, 10).padEnd(12) +
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('<id>', 'ID of the API key to delete')
153
- .action(async (id) => {
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
- console.log(chalk.green(`✓ API key ${id} has been deleted`))
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(
@@ -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
- .option('-s, --system <message>', 'System message')
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('-m, --max-tokens <tokens>', 'Maximum tokens to generate', parseInt)
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 (model, message, options) => {
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,