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.
Files changed (145) hide show
  1. package/.github/workflows/publish.yml +2 -2
  2. package/.github/workflows/test.yml +10 -4
  3. package/.husky/pre-commit +1 -0
  4. package/.prettierignore +15 -0
  5. package/.prettierrc +7 -3
  6. package/CONTRIBUTING.md +38 -0
  7. package/README.md +2 -148
  8. package/dist/index.js +10 -11
  9. package/dist/package.json +30 -2
  10. package/dist/src/agents/app.js +28 -0
  11. package/dist/src/agents/backend.js +25 -0
  12. package/dist/src/agents/devops.js +34 -0
  13. package/dist/src/agents/frontend.js +25 -0
  14. package/dist/src/agents/fullstack.js +25 -0
  15. package/dist/src/agents/index.js +61 -0
  16. package/dist/src/agents/quality.js +70 -0
  17. package/dist/src/agents/security.js +26 -0
  18. package/dist/src/agents/types.js +2 -0
  19. package/dist/src/client.js +97 -117
  20. package/dist/src/commands/api-keys.js +75 -90
  21. package/dist/src/commands/auth.js +7 -16
  22. package/dist/src/commands/autocomplete.js +1 -1
  23. package/dist/src/commands/billing.js +6 -17
  24. package/dist/src/commands/chat.js +68 -101
  25. package/dist/src/commands/clusters.js +9 -18
  26. package/dist/src/commands/code/__tests__/auth-sync.test.js +351 -0
  27. package/dist/src/commands/code/__tests__/fake-api-key-service.js +13 -0
  28. package/dist/src/commands/code/__tests__/fake-auth-service.js +47 -0
  29. package/dist/src/commands/code/__tests__/fake-command-runner.js +21 -34
  30. package/dist/src/commands/code/__tests__/fake-file-store.js +20 -33
  31. package/dist/src/commands/code/__tests__/fake-prompter.js +83 -57
  32. package/dist/src/commands/code/__tests__/setup-flow.test.js +359 -92
  33. package/dist/src/commands/code/adapters/clack-prompter.js +15 -22
  34. package/dist/src/commands/code/adapters/fs-file-store.js +26 -40
  35. package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -37
  36. package/dist/src/commands/code/auth-sync.js +270 -0
  37. package/dist/src/commands/code/errors.js +12 -9
  38. package/dist/src/commands/code/ports/auth-services.js +2 -0
  39. package/dist/src/commands/code/setup.js +387 -281
  40. package/dist/src/commands/code.js +205 -332
  41. package/dist/src/commands/index.js +5 -5
  42. package/dist/src/commands/models.js +6 -17
  43. package/dist/src/commands/users.js +5 -16
  44. package/dist/src/constants/command-structure.js +104 -104
  45. package/dist/src/services/api-key-service.js +132 -157
  46. package/dist/src/services/auth-service.js +89 -342
  47. package/dist/src/services/browser-auth.js +268 -0
  48. package/dist/src/services/chat-service.js +371 -401
  49. package/dist/src/services/cluster-service.js +47 -62
  50. package/dist/src/services/collaborator-service.js +10 -25
  51. package/dist/src/services/flux-service.js +14 -29
  52. package/dist/src/services/helm-service.js +10 -25
  53. package/dist/src/services/kubectl-service.js +16 -33
  54. package/dist/src/utils/config-checker.js +3 -3
  55. package/dist/src/utils/config-loader.js +95 -95
  56. package/dist/src/utils/default-api-key.js +124 -134
  57. package/dist/src/utils/env-manager.js +55 -66
  58. package/dist/src/utils/error-handler.js +20 -21
  59. package/dist/src/utils/logger.js +72 -65
  60. package/dist/src/utils/markdown-renderer.js +27 -27
  61. package/dist/src/utils/opencode-validator.js +63 -68
  62. package/dist/src/utils/token-manager.js +74 -45
  63. package/dist/tests/commands/chat.test.js +16 -25
  64. package/dist/tests/commands/code.test.js +95 -104
  65. package/dist/tests/utils/config-loader.test.js +48 -48
  66. package/dist/tests/utils/env-manager.test.js +43 -52
  67. package/dist/tests/utils/opencode-validator.test.js +22 -21
  68. package/dist/vitest.config.js +1 -1
  69. package/eslint.config.mjs +67 -0
  70. package/index.ts +35 -42
  71. package/package.json +30 -2
  72. package/src/agents/app.ts +27 -0
  73. package/src/agents/backend.ts +24 -0
  74. package/src/agents/devops.ts +33 -0
  75. package/src/agents/frontend.ts +24 -0
  76. package/src/agents/fullstack.ts +24 -0
  77. package/src/agents/index.ts +73 -0
  78. package/src/agents/quality.ts +69 -0
  79. package/src/agents/security.ts +26 -0
  80. package/src/agents/types.ts +17 -0
  81. package/src/client.ts +118 -152
  82. package/src/commands/api-keys.ts +241 -333
  83. package/src/commands/auth.ts +22 -27
  84. package/src/commands/autocomplete.ts +9 -9
  85. package/src/commands/billing.ts +20 -24
  86. package/src/commands/chat.ts +248 -338
  87. package/src/commands/clusters.ts +27 -26
  88. package/src/commands/code/__tests__/auth-sync.test.ts +482 -0
  89. package/src/commands/code/__tests__/fake-api-key-service.ts +13 -0
  90. package/src/commands/code/__tests__/fake-auth-service.ts +50 -0
  91. package/src/commands/code/__tests__/fake-command-runner.ts +45 -42
  92. package/src/commands/code/__tests__/fake-file-store.ts +32 -23
  93. package/src/commands/code/__tests__/fake-prompter.ts +116 -77
  94. package/src/commands/code/__tests__/setup-flow.test.ts +624 -268
  95. package/src/commands/code/adapters/clack-prompter.ts +53 -39
  96. package/src/commands/code/adapters/fs-file-store.ts +32 -27
  97. package/src/commands/code/adapters/spawn-command-runner.ts +38 -29
  98. package/src/commands/code/auth-sync.ts +329 -0
  99. package/src/commands/code/errors.ts +18 -18
  100. package/src/commands/code/ports/auth-services.ts +14 -0
  101. package/src/commands/code/ports/command-runner.ts +8 -4
  102. package/src/commands/code/ports/file-store.ts +5 -4
  103. package/src/commands/code/ports/prompter.ts +24 -18
  104. package/src/commands/code/setup.ts +570 -340
  105. package/src/commands/code.ts +338 -539
  106. package/src/commands/index.ts +20 -19
  107. package/src/commands/models.ts +28 -32
  108. package/src/commands/users.ts +15 -21
  109. package/src/constants/command-structure.ts +134 -157
  110. package/src/services/api-key-service.ts +105 -122
  111. package/src/services/auth-service.ts +99 -345
  112. package/src/services/browser-auth.ts +296 -0
  113. package/src/services/chat-service.ts +265 -299
  114. package/src/services/cluster-service.ts +42 -45
  115. package/src/services/collaborator-service.ts +14 -19
  116. package/src/services/flux-service.ts +23 -25
  117. package/src/services/helm-service.ts +19 -21
  118. package/src/services/kubectl-service.ts +17 -19
  119. package/src/types/api.d.ts +1905 -1907
  120. package/src/types/json.d.ts +2 -2
  121. package/src/utils/config-checker.ts +10 -10
  122. package/src/utils/config-loader.ts +162 -178
  123. package/src/utils/default-api-key.ts +114 -125
  124. package/src/utils/env-manager.ts +53 -57
  125. package/src/utils/error-handler.ts +61 -56
  126. package/src/utils/logger.ts +79 -73
  127. package/src/utils/markdown-renderer.ts +31 -31
  128. package/src/utils/opencode-validator.ts +85 -89
  129. package/src/utils/token-manager.ts +108 -87
  130. package/templates/agents/app.md +1 -0
  131. package/templates/agents/backend.md +1 -0
  132. package/templates/agents/devops.md +2 -0
  133. package/templates/agents/frontend.md +1 -0
  134. package/templates/agents/fullstack.md +1 -0
  135. package/templates/agents/quality.md +45 -40
  136. package/templates/agents/security.md +1 -0
  137. package/tests/commands/chat.test.ts +53 -62
  138. package/tests/commands/code.test.ts +265 -310
  139. package/tests/utils/config-loader.test.ts +189 -188
  140. package/tests/utils/env-manager.test.ts +110 -113
  141. package/tests/utils/opencode-validator.test.ts +52 -56
  142. package/tsconfig.json +4 -3
  143. package/vitest.config.ts +3 -3
  144. package/AGENTS.md +0 -374
  145. package/TODO.md +0 -19
@@ -1,73 +1,43 @@
1
- import { Command } from 'commander'
2
- import chalk from 'chalk'
3
- import { ApiKeyService, ApiKey } from '../services/api-key-service'
4
- import { handleError } from '../utils/error-handler'
5
- import { DefaultApiKeyManager } from '../utils/default-api-key'
1
+ import chalk from 'chalk';
2
+ import { Command } from 'commander';
6
3
 
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
- }
4
+ import { ApiKey, ApiKeyService } from '../services/api-key-service';
5
+ import { DefaultApiKeyManager } from '../utils/default-api-key';
6
+ import { handleError } from '../utils/error-handler';
35
7
 
36
8
  /**
37
9
  * Register API key commands
38
10
  */
39
11
  export function registerApiKeyCommands(program: Command): void {
40
- const apiKey = program
41
- .command(ApiKeyService.COMMAND_GROUP)
42
- .description('Manage API keys')
12
+ const apiKey = program.command(ApiKeyService.COMMAND_GROUP).description('Manage API keys');
43
13
 
44
14
  apiKey
45
15
  .command(ApiKeyService.COMMANDS.LIST)
46
16
  .description('List all API keys')
47
17
  .action(async () => {
48
18
  try {
49
- const apiKeyService = ApiKeyService.getInstance()
50
- const keys = await apiKeyService.list()
19
+ const apiKeyService = ApiKeyService.getInstance();
20
+ const keys = await apiKeyService.list();
51
21
 
52
22
  if (keys.length === 0) {
53
23
  console.log(
54
24
  chalk.yellow(
55
25
  'No API keys found. Create one with `berget api-key create --name <name>`',
56
26
  ),
57
- )
58
- return
27
+ );
28
+ return;
59
29
  }
60
30
 
61
- console.log(chalk.bold('🔑 Your API keys:'))
62
- console.log('')
31
+ console.log(chalk.bold('🔑 Your API keys:'));
32
+ console.log('');
63
33
 
64
34
  // 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
35
+ const idWidth = 10;
36
+ const nameWidth = 30;
37
+ const prefixWidth = 20;
38
+ const statusWidth = 12;
39
+ const createdWidth = 12;
40
+ const usedWidth = 15;
71
41
 
72
42
  console.log(
73
43
  chalk.dim('ID'.padEnd(idWidth)) +
@@ -75,61 +45,57 @@ export function registerApiKeyCommands(program: Command): void {
75
45
  chalk.dim('PREFIX'.padEnd(prefixWidth)) +
76
46
  chalk.dim('STATUS'.padEnd(statusWidth)) +
77
47
  chalk.dim('CREATED'.padEnd(createdWidth)) +
78
- chalk.dim('LAST USED')
79
- )
48
+ chalk.dim('LAST USED'),
49
+ );
80
50
 
81
- console.log(chalk.dim('─'.repeat(idWidth + nameWidth + prefixWidth + statusWidth + createdWidth + usedWidth + 5)))
51
+ console.log(
52
+ chalk.dim(
53
+ '─'.repeat(
54
+ idWidth + nameWidth + prefixWidth + statusWidth + createdWidth + usedWidth + 5,
55
+ ),
56
+ ),
57
+ );
82
58
 
83
59
  keys.forEach((key: ApiKey) => {
84
- const lastUsed = key.lastUsed
85
- ? formatLastUsed(key.lastUsed)
86
- : chalk.yellow('Never used')
87
- const status = key.active
88
- ? chalk.green('● Active')
89
- : chalk.red('● Inactive')
60
+ const lastUsed = key.lastUsed ? formatLastUsed(key.lastUsed) : chalk.yellow('Never used');
61
+ const status = key.active ? chalk.green('● Active') : chalk.red('● Inactive');
90
62
 
91
63
  // Show only first 8 characters of ID for easier reading
92
- const shortId = chalk.cyan(String(key.id).substring(0, 8))
64
+ const shortId = chalk.cyan(String(key.id).slice(0, 8));
93
65
 
94
66
  // 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)
67
+ const prefixString =
68
+ key.prefix.length > prefixWidth
69
+ ? key.prefix.slice(0, Math.max(0, prefixWidth - 3)) + '...'
70
+ : chalk.gray(key.prefix);
98
71
 
99
72
  // Truncate name if too long
100
- const nameStr = key.name.length > nameWidth
101
- ? key.name.substring(0, nameWidth - 3) + '...'
102
- : key.name
73
+ const nameString =
74
+ key.name.length > nameWidth
75
+ ? key.name.slice(0, Math.max(0, nameWidth - 3)) + '...'
76
+ : key.name;
103
77
 
104
78
  // Format created date
105
- const createdDate = formatDate(key.created)
79
+ const createdDate = formatDate(key.created);
106
80
 
107
81
  console.log(
108
82
  shortId.padEnd(idWidth) +
109
- nameStr.padEnd(nameWidth) +
110
- prefixStr.padEnd(prefixWidth) +
83
+ nameString.padEnd(nameWidth) +
84
+ prefixString.padEnd(prefixWidth) +
111
85
  status.padEnd(statusWidth) +
112
86
  createdDate.padEnd(createdWidth) +
113
- lastUsed
114
- )
115
- })
116
-
117
- console.log('')
118
- console.log(
119
- chalk.dim(
120
- 'Use `berget api-key create --name <name>` to create a new API key',
121
- ),
122
- )
123
- console.log(
124
- chalk.dim('Use `berget api-key delete <id>` to delete an API key'),
125
- )
126
- console.log(
127
- chalk.dim('Use `berget api-key rotate <id>` to rotate an API key'),
128
- )
87
+ lastUsed,
88
+ );
89
+ });
90
+
91
+ console.log('');
92
+ console.log(chalk.dim('Use `berget api-key create --name <name>` to create a new API key'));
93
+ console.log(chalk.dim('Use `berget api-key delete <id>` to delete an API key'));
94
+ console.log(chalk.dim('Use `berget api-key rotate <id>` to rotate an API key'));
129
95
  } catch (error) {
130
- handleError('Failed to list API keys', error)
96
+ handleError('Failed to list API keys', error);
131
97
  }
132
- })
98
+ });
133
99
 
134
100
  apiKey
135
101
  .command(ApiKeyService.COMMANDS.CREATE)
@@ -140,72 +106,57 @@ export function registerApiKeyCommands(program: Command): void {
140
106
  .action(async (options) => {
141
107
  try {
142
108
  if (!options.name) {
143
- console.error(chalk.red('Error: --name is required'))
144
- console.log('')
145
- console.log(
146
- 'Usage: berget api-key create --name <name> [--description <description>]',
147
- )
148
- return
109
+ console.error(chalk.red('Error: --name is required'));
110
+ console.log('');
111
+ console.log('Usage: berget api-key create --name <name> [--description <description>]');
112
+ return;
149
113
  }
150
114
 
151
- console.log(chalk.blue('Creating API key...'))
115
+ console.log(chalk.blue('Creating API key...'));
152
116
 
153
- const apiKeyService = ApiKeyService.getInstance()
117
+ const apiKeyService = ApiKeyService.getInstance();
154
118
  const result = await apiKeyService.create({
155
- name: options.name,
156
119
  description: options.description,
157
- })
158
-
159
- console.log('')
160
- console.log(chalk.green('✓ API key created'))
161
- console.log('')
162
- console.log(chalk.bold('API key details:'))
163
- console.log('')
164
- console.log(`${chalk.dim('ID:')} ${result.id}`)
165
- console.log(`${chalk.dim('Name:')} ${result.name}`)
120
+ name: options.name,
121
+ });
122
+
123
+ console.log('');
124
+ console.log(chalk.green('✓ API key created'));
125
+ console.log('');
126
+ console.log(chalk.bold('API key details:'));
127
+ console.log('');
128
+ console.log(`${chalk.dim('ID:')} ${result.id}`);
129
+ console.log(`${chalk.dim('Name:')} ${result.name}`);
166
130
  if (result.description) {
167
- console.log(`${chalk.dim('Description:')} ${result.description}`)
131
+ console.log(`${chalk.dim('Description:')} ${result.description}`);
168
132
  }
133
+ console.log(`${chalk.dim('Created:')} ${new Date(result.created).toLocaleString()}`);
134
+ console.log('');
135
+ console.log(chalk.bold('API key:'));
136
+ console.log(chalk.cyan(result.key));
137
+ console.log('');
138
+ console.log(chalk.yellow('⚠️ IMPORTANT: Save this API key in a secure location.'));
139
+ console.log(chalk.yellow(' It will not be displayed again.'));
140
+
141
+ console.log('');
169
142
  console.log(
170
- `${chalk.dim('Created:')} ${new Date(
171
- result.created,
172
- ).toLocaleString()}`,
173
- )
174
- console.log('')
175
- console.log(chalk.bold('API key:'))
176
- console.log(chalk.cyan(result.key))
177
- console.log('')
178
- console.log(
179
- chalk.yellow(
180
- '⚠️ IMPORTANT: Save this API key in a secure location.',
181
- ),
182
- )
183
- console.log(chalk.yellow(' It will not be displayed again.'))
184
-
185
- console.log('')
186
- console.log(
187
- chalk.dim(
188
- 'Use this key in your applications to authenticate with the Berget API.',
189
- ),
190
- )
143
+ chalk.dim('Use this key in your applications to authenticate with the Berget API.'),
144
+ );
191
145
  } catch (error) {
192
- handleError('Failed to create API key', error)
146
+ handleError('Failed to create API key', error);
193
147
  }
194
- })
148
+ });
195
149
 
196
150
  apiKey
197
151
  .command(ApiKeyService.COMMANDS.DELETE)
198
152
  .description('Delete an API key')
199
- .argument(
200
- '<identifier>',
201
- 'ID (first 8 chars), full ID, or name of the API key to delete',
202
- )
153
+ .argument('<identifier>', 'ID (first 8 chars), full ID, or name of the API key to delete')
203
154
  .action(async (identifier) => {
204
155
  try {
205
- const apiKeyService = ApiKeyService.getInstance()
156
+ const apiKeyService = ApiKeyService.getInstance();
206
157
 
207
158
  // First, get all API keys to find the matching one
208
- const keys = await apiKeyService.list()
159
+ const keys = await apiKeyService.list();
209
160
 
210
161
  // Try to find the key by:
211
162
  // 1. Full ID match
@@ -216,144 +167,102 @@ export function registerApiKeyCommands(program: Command): void {
216
167
  // Check for exact matches first (full ID or exact name)
217
168
  let exactMatches = keys.filter(
218
169
  (key) => String(key.id) === identifier || key.name === identifier,
219
- )
170
+ );
220
171
 
221
172
  // If no exact matches, check for short ID matches
222
173
  if (exactMatches.length === 0) {
223
- exactMatches = keys.filter(
224
- (key) => String(key.id).substring(0, 8) === identifier,
225
- )
174
+ exactMatches = keys.filter((key) => String(key.id).slice(0, 8) === identifier);
226
175
  }
227
176
 
228
177
  // If still no matches, check for partial name matches
229
178
  if (exactMatches.length === 0) {
230
179
  exactMatches = keys.filter((key) =>
231
180
  key.name.toLowerCase().includes(identifier.toLowerCase()),
232
- )
181
+ );
233
182
  }
234
183
 
235
184
  // Handle multiple matches
236
185
  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
186
+ console.error(chalk.red(`Error: Multiple API keys found matching "${identifier}"`));
187
+ console.log('');
188
+ console.log('Please be more specific. Matching keys:');
189
+ for (const key of exactMatches) {
190
+ const shortId = String(key.id).slice(0, 8);
191
+ console.log(` ${shortId.padEnd(8)} ${key.name}`);
192
+ }
193
+ console.log('');
194
+ console.log('Use the first 8 characters of the ID to specify which key to delete.');
195
+ return;
253
196
  }
254
197
 
255
198
  // Handle no matches
256
199
  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
200
+ console.error(chalk.red(`Error: No API key found matching "${identifier}"`));
201
+ console.log('');
202
+ console.log('Available API keys:');
203
+ for (const key of keys) {
204
+ const shortId = String(key.id).slice(0, 8);
205
+ console.log(` ${shortId.padEnd(8)} ${key.name}`);
206
+ }
207
+ console.log('');
208
+ console.log('Use the first 8 characters of the ID, full ID, or name to delete.');
209
+ return;
271
210
  }
272
211
 
273
- const matchingKey = exactMatches[0]
212
+ const matchingKey = exactMatches[0];
274
213
 
275
- const keyId = String(matchingKey.id)
276
- const shortId = keyId.substring(0, 8)
214
+ const keyId = String(matchingKey.id);
215
+ const shortId = keyId.slice(0, 8);
277
216
 
278
- console.log(
279
- chalk.blue(`Deleting API key ${shortId} (${matchingKey.name})...`),
280
- )
217
+ console.log(chalk.blue(`Deleting API key ${shortId} (${matchingKey.name})...`));
281
218
 
282
- await apiKeyService.delete(keyId)
219
+ await apiKeyService.delete(keyId);
283
220
 
221
+ console.log(chalk.green(`✓ API key ${shortId} (${matchingKey.name}) has been deleted`));
222
+ console.log('');
284
223
  console.log(
285
- chalk.green(
286
- `✓ API key ${shortId} (${matchingKey.name}) has been deleted`,
287
- ),
288
- )
289
- console.log('')
290
- console.log(
291
- chalk.dim(
292
- 'Applications using this key will no longer be able to authenticate.',
293
- ),
294
- )
295
- console.log(
296
- chalk.dim(
297
- 'Use `berget api-key list` to see your remaining API keys.',
298
- ),
299
- )
224
+ chalk.dim('Applications using this key will no longer be able to authenticate.'),
225
+ );
226
+ console.log(chalk.dim('Use `berget api-key list` to see your remaining API keys.'));
300
227
  } catch (error) {
301
- handleError('Failed to delete API key', error)
228
+ handleError('Failed to delete API key', error);
302
229
  }
303
- })
230
+ });
304
231
 
305
232
  apiKey
306
233
  .command(ApiKeyService.COMMANDS.ROTATE)
307
- .description(
308
- 'Rotate an API key (creates a new one and invalidates the old one)',
309
- )
234
+ .description('Rotate an API key (creates a new one and invalidates the old one)')
310
235
  .argument('<id>', 'ID of the API key to rotate')
311
236
  .action(async (id) => {
312
237
  try {
313
- console.log(chalk.blue(`Rotating API key ${id}...`))
314
- console.log(
315
- chalk.dim('This will invalidate the old key and generate a new one.'),
316
- )
317
-
318
- const apiKeyService = ApiKeyService.getInstance()
319
- const result = await apiKeyService.rotate(id)
320
-
321
- console.log('')
322
- console.log(chalk.green(' API key rotated'))
323
- console.log('')
324
- console.log(chalk.bold('New API key details:'))
325
- console.log('')
326
- console.log(`${chalk.dim('ID:')} ${result.id}`)
327
- console.log(`${chalk.dim('Name:')} ${result.name}`)
238
+ console.log(chalk.blue(`Rotating API key ${id}...`));
239
+ console.log(chalk.dim('This will invalidate the old key and generate a new one.'));
240
+
241
+ const apiKeyService = ApiKeyService.getInstance();
242
+ const result = await apiKeyService.rotate(id);
243
+
244
+ console.log('');
245
+ console.log(chalk.green('✓ API key rotated'));
246
+ console.log('');
247
+ console.log(chalk.bold('New API key details:'));
248
+ console.log('');
249
+ console.log(`${chalk.dim('ID:')} ${result.id}`);
250
+ console.log(`${chalk.dim('Name:')} ${result.name}`);
328
251
  if (result.description) {
329
- console.log(`${chalk.dim('Description:')} ${result.description}`)
252
+ console.log(`${chalk.dim('Description:')} ${result.description}`);
330
253
  }
331
- console.log(
332
- `${chalk.dim('Created:')} ${new Date(
333
- result.created,
334
- ).toLocaleString()}`,
335
- )
336
- console.log('')
337
- console.log(chalk.bold('New API key:'))
338
- console.log(chalk.cyan(result.key))
339
- console.log('')
340
- console.log(
341
- chalk.yellow(
342
- '⚠️ IMPORTANT: Update your applications with this new API key.',
343
- ),
344
- )
345
- console.log(
346
- chalk.yellow(
347
- ' The old key has been invalidated and will no longer work.',
348
- ),
349
- )
350
- console.log(
351
- chalk.yellow(' This new key will not be displayed again.'),
352
- )
254
+ console.log(`${chalk.dim('Created:')} ${new Date(result.created).toLocaleString()}`);
255
+ console.log('');
256
+ console.log(chalk.bold('New API key:'));
257
+ console.log(chalk.cyan(result.key));
258
+ console.log('');
259
+ console.log(chalk.yellow('⚠️ IMPORTANT: Update your applications with this new API key.'));
260
+ console.log(chalk.yellow(' The old key has been invalidated and will no longer work.'));
261
+ console.log(chalk.yellow(' This new key will not be displayed again.'));
353
262
  } catch (error) {
354
- handleError('Failed to rotate API key', error)
263
+ handleError('Failed to rotate API key', error);
355
264
  }
356
- })
265
+ });
357
266
 
358
267
  apiKey
359
268
  .command(ApiKeyService.COMMANDS.DESCRIBE)
@@ -361,69 +270,59 @@ export function registerApiKeyCommands(program: Command): void {
361
270
  .argument('<id>', 'ID of the API key')
362
271
  .option('--start <date>', 'Start date (YYYY-MM-DD)')
363
272
  .option('--end <date>', 'End date (YYYY-MM-DD)')
364
- .action(async (id, options) => {
273
+ .action(async (id, _options) => {
365
274
  try {
366
- console.log(
367
- chalk.blue(`Fetching usage statistics for API key ${id}...`),
368
- )
275
+ console.log(chalk.blue(`Fetching usage statistics for API key ${id}...`));
369
276
 
370
- const apiKeyService = ApiKeyService.getInstance()
371
- const usage = await apiKeyService.describe(id)
277
+ const apiKeyService = ApiKeyService.getInstance();
278
+ const usage = await apiKeyService.describe(id);
372
279
 
373
- console.log('')
374
- console.log(
375
- chalk.bold(`Usage statistics for API key: ${usage.name} (${id})`),
376
- )
377
- console.log('')
280
+ console.log('');
281
+ console.log(chalk.bold(`Usage statistics for API key: ${usage.name} (${id})`));
282
+ console.log('');
378
283
 
379
284
  // Period information
380
- console.log(
381
- chalk.dim(`Period: ${usage.period.start} to ${usage.period.end}`),
382
- )
383
- console.log('')
285
+ console.log(chalk.dim(`Period: ${usage.period.start} to ${usage.period.end}`));
286
+ console.log('');
384
287
 
385
288
  // Request statistics
386
- console.log(chalk.bold('Request statistics:'))
387
- console.log(
388
- `Total requests: ${chalk.cyan(usage.requests.total.toLocaleString())}`,
389
- )
289
+ console.log(chalk.bold('Request statistics:'));
290
+ console.log(`Total requests: ${chalk.cyan(usage.requests.total.toLocaleString())}`);
390
291
 
391
292
  // Daily breakdown if available
392
293
  if (usage.requests.daily && usage.requests.daily.length > 0) {
393
- console.log('')
394
- console.log(chalk.bold('Daily breakdown:'))
395
- console.log(chalk.dim('─'.repeat(30)))
396
- console.log(chalk.dim('DATE'.padEnd(12) + 'REQUESTS'))
397
-
398
- usage.requests.daily.forEach(
399
- (day: { date: string; count: number }) => {
400
- console.log(`${day.date.padEnd(12)}${day.count.toLocaleString()}`)
401
- },
402
- )
294
+ console.log('');
295
+ console.log(chalk.bold('Daily breakdown:'));
296
+ console.log(chalk.dim('─'.repeat(30)));
297
+ console.log(chalk.dim('DATE'.padEnd(12) + 'REQUESTS'));
298
+
299
+ usage.requests.daily.forEach((day: { count: number; date: string }) => {
300
+ console.log(`${day.date.padEnd(12)}${day.count.toLocaleString()}`);
301
+ });
403
302
  }
404
303
 
405
304
  // Model usage if available
406
305
  if (usage.models && usage.models.length > 0) {
407
- console.log('')
408
- console.log(chalk.bold('Model usage:'))
409
- console.log(chalk.dim('─'.repeat(70)))
306
+ console.log('');
307
+ console.log(chalk.bold('Model usage:'));
308
+ console.log(chalk.dim('─'.repeat(70)));
410
309
  console.log(
411
310
  chalk.dim('MODEL'.padEnd(20)) +
412
311
  chalk.dim('REQUESTS'.padEnd(10)) +
413
312
  chalk.dim('INPUT'.padEnd(12)) +
414
313
  chalk.dim('OUTPUT'.padEnd(12)) +
415
314
  chalk.dim('TOTAL TOKENS'),
416
- )
315
+ );
417
316
 
418
317
  usage.models.forEach(
419
318
  (model: {
420
- name: string
421
- requests: number
319
+ name: string;
320
+ requests: number;
422
321
  tokens: {
423
- input: number
424
- output: number
425
- total: number
426
- }
322
+ input: number;
323
+ output: number;
324
+ total: number;
325
+ };
427
326
  }) => {
428
327
  console.log(
429
328
  model.name.padEnd(20) +
@@ -431,21 +330,19 @@ export function registerApiKeyCommands(program: Command): void {
431
330
  model.tokens.input.toLocaleString().padEnd(12) +
432
331
  model.tokens.output.toLocaleString().padEnd(12) +
433
332
  model.tokens.total.toLocaleString(),
434
- )
333
+ );
435
334
  },
436
- )
335
+ );
437
336
  }
438
337
 
439
- console.log('')
338
+ console.log('');
440
339
  console.log(
441
- chalk.dim(
442
- 'Use these statistics to understand your API usage and optimize your costs.',
443
- ),
444
- )
340
+ chalk.dim('Use these statistics to understand your API usage and optimize your costs.'),
341
+ );
445
342
  } catch (error) {
446
- handleError('Failed to get API key usage', error)
343
+ handleError('Failed to get API key usage', error);
447
344
  }
448
- })
345
+ });
449
346
 
450
347
  apiKey
451
348
  .command(ApiKeyService.COMMANDS.SET_DEFAULT)
@@ -453,83 +350,94 @@ export function registerApiKeyCommands(program: Command): void {
453
350
  .argument('<id>', 'ID of the API key to set as default')
454
351
  .action(async (id) => {
455
352
  try {
456
- const apiKeyService = ApiKeyService.getInstance()
457
- const keys = await apiKeyService.list()
458
- const selectedKey = keys.find((key) => key.id.toString() === id)
353
+ const apiKeyService = ApiKeyService.getInstance();
354
+ const keys = await apiKeyService.list();
355
+ const selectedKey = keys.find((key) => key.id.toString() === id);
459
356
 
460
357
  if (!selectedKey) {
461
- console.error(chalk.red(`Error: API key with ID ${id} not found`))
462
- return
358
+ console.error(chalk.red(`Error: API key with ID ${id} not found`));
359
+ return;
463
360
  }
464
361
 
465
362
  // Save the default API key
466
- const defaultApiKeyManager = DefaultApiKeyManager.getInstance()
363
+ const defaultApiKeyManager = DefaultApiKeyManager.getInstance();
467
364
 
468
365
  // We need to rotate the key to get the actual key value
469
- const rotatedKey = await apiKeyService.rotate(id)
366
+ const rotatedKey = await apiKeyService.rotate(id);
470
367
 
471
368
  defaultApiKeyManager.setDefaultApiKey(
472
369
  id,
473
370
  selectedKey.name,
474
371
  selectedKey.prefix,
475
372
  rotatedKey.key,
476
- )
373
+ );
477
374
 
478
375
  console.log(
479
- chalk.green(
480
- `✓ API key "${selectedKey.name}" set as default for chat commands`,
481
- ),
482
- )
483
- console.log('')
484
- console.log(
485
- chalk.dim(
486
- 'This API key will be used by default when running chat commands',
487
- ),
488
- )
489
- console.log(
490
- chalk.dim(
491
- 'You can override it with --api-key or --api-key-id options',
492
- ),
493
- )
376
+ chalk.green(`✓ API key "${selectedKey.name}" set as default for chat commands`),
377
+ );
378
+ console.log('');
379
+ console.log(chalk.dim('This API key will be used by default when running chat commands'));
380
+ console.log(chalk.dim('You can override it with --api-key or --api-key-id options'));
494
381
  } catch (error) {
495
- handleError('Failed to set default API key', error)
382
+ handleError('Failed to set default API key', error);
496
383
  }
497
- })
384
+ });
498
385
 
499
386
  apiKey
500
387
  .command(ApiKeyService.COMMANDS.GET_DEFAULT)
501
388
  .description('Show the current default API key')
502
389
  .action(() => {
503
390
  try {
504
- const defaultApiKeyManager = DefaultApiKeyManager.getInstance()
505
- const defaultApiKeyData = defaultApiKeyManager.getDefaultApiKeyData()
391
+ const defaultApiKeyManager = DefaultApiKeyManager.getInstance();
392
+ const defaultApiKeyData = defaultApiKeyManager.getDefaultApiKeyData();
506
393
 
507
394
  if (!defaultApiKeyData) {
508
- console.log(chalk.yellow('No default API key set'))
509
- console.log('')
510
- console.log('To set a default API key, run:')
511
- console.log(chalk.cyan(' berget api-keys set-default <id>'))
512
- return
395
+ console.log(chalk.yellow('No default API key set'));
396
+ console.log('');
397
+ console.log('To set a default API key, run:');
398
+ console.log(chalk.cyan(' berget api-keys set-default <id>'));
399
+ return;
513
400
  }
514
401
 
515
- console.log(chalk.bold('Default API key:'))
516
- console.log('')
517
- console.log(`${chalk.dim('ID:')} ${defaultApiKeyData.id}`)
518
- console.log(`${chalk.dim('Name:')} ${defaultApiKeyData.name}`)
519
- console.log(`${chalk.dim('Prefix:')} ${defaultApiKeyData.prefix}`)
520
- console.log('')
521
- console.log(
522
- chalk.dim(
523
- 'This API key will be used by default when running chat commands',
524
- ),
525
- )
526
- console.log(
527
- chalk.dim(
528
- 'You can override it with --api-key or --api-key-id options',
529
- ),
530
- )
402
+ console.log(chalk.bold('Default API key:'));
403
+ console.log('');
404
+ console.log(`${chalk.dim('ID:')} ${defaultApiKeyData.id}`);
405
+ console.log(`${chalk.dim('Name:')} ${defaultApiKeyData.name}`);
406
+ console.log(`${chalk.dim('Prefix:')} ${defaultApiKeyData.prefix}`);
407
+ console.log('');
408
+ console.log(chalk.dim('This API key will be used by default when running chat commands'));
409
+ console.log(chalk.dim('You can override it with --api-key or --api-key-id options'));
531
410
  } catch (error) {
532
- handleError('Failed to get default API key', error)
411
+ handleError('Failed to get default API key', error);
533
412
  }
534
- })
413
+ });
414
+ }
415
+
416
+ // Helper functions for better date formatting
417
+ function formatDate(dateString: string): string {
418
+ const date = new Date(dateString);
419
+ const now = new Date();
420
+ const diffTime = Math.abs(now.getTime() - date.getTime());
421
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
422
+
423
+ if (diffDays === 0) return chalk.green('Today');
424
+ if (diffDays === 1) return chalk.yellow('Yesterday');
425
+ if (diffDays < 7) return chalk.yellow(`${diffDays} days ago`);
426
+ if (diffDays < 30) return chalk.blue(`${Math.floor(diffDays / 7)} weeks ago`);
427
+ if (diffDays < 365) return chalk.magenta(`${Math.floor(diffDays / 30)} months ago`);
428
+ return chalk.gray(`${Math.floor(diffDays / 365)} years ago`);
429
+ }
430
+
431
+ function formatLastUsed(dateString: string): string {
432
+ const date = new Date(dateString);
433
+ const now = new Date();
434
+ const diffTime = Math.abs(now.getTime() - date.getTime());
435
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
436
+
437
+ if (diffDays === 0) return chalk.green('Today');
438
+ if (diffDays === 1) return chalk.yellow('Yesterday');
439
+ if (diffDays < 7) return chalk.yellow(`${diffDays} days ago`);
440
+ if (diffDays < 30) return chalk.blue(`${Math.floor(diffDays / 7)} weeks ago`);
441
+ if (diffDays < 365) return chalk.magenta(`${Math.floor(diffDays / 30)} months ago`);
442
+ return chalk.gray(`${Math.floor(diffDays / 365)} years ago`);
535
443
  }