berget 2.2.7 → 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 (130) hide show
  1. package/.github/workflows/publish.yml +6 -6
  2. package/.github/workflows/test.yml +1 -1
  3. package/.prettierrc +5 -3
  4. package/dist/index.js +24 -25
  5. package/dist/package.json +5 -3
  6. package/dist/src/agents/app.js +8 -8
  7. package/dist/src/agents/backend.js +3 -3
  8. package/dist/src/agents/devops.js +8 -8
  9. package/dist/src/agents/frontend.js +3 -3
  10. package/dist/src/agents/fullstack.js +3 -3
  11. package/dist/src/agents/index.js +18 -18
  12. package/dist/src/agents/quality.js +8 -8
  13. package/dist/src/agents/security.js +8 -8
  14. package/dist/src/client.js +115 -127
  15. package/dist/src/commands/api-keys.js +195 -202
  16. package/dist/src/commands/auth.js +16 -25
  17. package/dist/src/commands/autocomplete.js +8 -8
  18. package/dist/src/commands/billing.js +10 -19
  19. package/dist/src/commands/chat.js +139 -170
  20. package/dist/src/commands/clusters.js +21 -30
  21. package/dist/src/commands/code/__tests__/auth-sync.test.js +189 -186
  22. package/dist/src/commands/code/__tests__/fake-api-key-service.js +3 -13
  23. package/dist/src/commands/code/__tests__/fake-auth-service.js +21 -29
  24. package/dist/src/commands/code/__tests__/fake-command-runner.js +22 -33
  25. package/dist/src/commands/code/__tests__/fake-file-store.js +19 -41
  26. package/dist/src/commands/code/__tests__/fake-prompter.js +81 -97
  27. package/dist/src/commands/code/__tests__/setup-flow.test.js +295 -295
  28. package/dist/src/commands/code/adapters/clack-prompter.js +15 -32
  29. package/dist/src/commands/code/adapters/fs-file-store.js +25 -44
  30. package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -41
  31. package/dist/src/commands/code/auth-sync.js +215 -228
  32. package/dist/src/commands/code/errors.js +15 -12
  33. package/dist/src/commands/code/setup.js +390 -425
  34. package/dist/src/commands/code.js +279 -294
  35. package/dist/src/commands/index.js +5 -5
  36. package/dist/src/commands/models.js +16 -25
  37. package/dist/src/commands/users.js +9 -18
  38. package/dist/src/constants/command-structure.js +138 -138
  39. package/dist/src/services/api-key-service.js +132 -152
  40. package/dist/src/services/auth-service.js +81 -95
  41. package/dist/src/services/browser-auth.js +121 -131
  42. package/dist/src/services/chat-service.js +369 -386
  43. package/dist/src/services/cluster-service.js +47 -62
  44. package/dist/src/services/collaborator-service.js +9 -21
  45. package/dist/src/services/flux-service.js +13 -25
  46. package/dist/src/services/helm-service.js +9 -21
  47. package/dist/src/services/kubectl-service.js +15 -29
  48. package/dist/src/utils/config-checker.js +7 -7
  49. package/dist/src/utils/config-loader.js +109 -109
  50. package/dist/src/utils/default-api-key.js +129 -139
  51. package/dist/src/utils/env-manager.js +55 -66
  52. package/dist/src/utils/error-handler.js +62 -62
  53. package/dist/src/utils/logger.js +74 -67
  54. package/dist/src/utils/markdown-renderer.js +28 -28
  55. package/dist/src/utils/opencode-validator.js +67 -69
  56. package/dist/src/utils/token-manager.js +67 -65
  57. package/dist/tests/commands/chat.test.js +30 -39
  58. package/dist/tests/commands/code.test.js +186 -195
  59. package/dist/tests/utils/config-loader.test.js +107 -107
  60. package/dist/tests/utils/env-manager.test.js +81 -90
  61. package/dist/tests/utils/opencode-validator.test.js +42 -41
  62. package/dist/vitest.config.js +1 -1
  63. package/eslint.config.mjs +50 -30
  64. package/index.ts +30 -31
  65. package/package.json +5 -3
  66. package/src/agents/app.ts +9 -9
  67. package/src/agents/backend.ts +4 -4
  68. package/src/agents/devops.ts +9 -9
  69. package/src/agents/frontend.ts +4 -4
  70. package/src/agents/fullstack.ts +4 -4
  71. package/src/agents/index.ts +27 -25
  72. package/src/agents/quality.ts +9 -9
  73. package/src/agents/security.ts +9 -9
  74. package/src/agents/types.ts +10 -10
  75. package/src/client.ts +85 -77
  76. package/src/commands/api-keys.ts +190 -185
  77. package/src/commands/auth.ts +15 -14
  78. package/src/commands/autocomplete.ts +10 -10
  79. package/src/commands/billing.ts +13 -12
  80. package/src/commands/chat.ts +145 -142
  81. package/src/commands/clusters.ts +20 -19
  82. package/src/commands/code/__tests__/auth-sync.test.ts +176 -175
  83. package/src/commands/code/__tests__/fake-api-key-service.ts +2 -2
  84. package/src/commands/code/__tests__/fake-auth-service.ts +18 -18
  85. package/src/commands/code/__tests__/fake-command-runner.ts +28 -22
  86. package/src/commands/code/__tests__/fake-file-store.ts +15 -15
  87. package/src/commands/code/__tests__/fake-prompter.ts +86 -85
  88. package/src/commands/code/__tests__/setup-flow.test.ts +253 -251
  89. package/src/commands/code/adapters/clack-prompter.ts +32 -30
  90. package/src/commands/code/adapters/fs-file-store.ts +18 -17
  91. package/src/commands/code/adapters/spawn-command-runner.ts +20 -15
  92. package/src/commands/code/auth-sync.ts +210 -210
  93. package/src/commands/code/errors.ts +11 -11
  94. package/src/commands/code/ports/auth-services.ts +7 -7
  95. package/src/commands/code/ports/command-runner.ts +2 -2
  96. package/src/commands/code/ports/file-store.ts +3 -3
  97. package/src/commands/code/ports/prompter.ts +13 -13
  98. package/src/commands/code/setup.ts +408 -406
  99. package/src/commands/code.ts +288 -287
  100. package/src/commands/index.ts +11 -10
  101. package/src/commands/models.ts +19 -18
  102. package/src/commands/users.ts +11 -10
  103. package/src/constants/command-structure.ts +159 -159
  104. package/src/services/api-key-service.ts +85 -85
  105. package/src/services/auth-service.ts +55 -54
  106. package/src/services/browser-auth.ts +62 -62
  107. package/src/services/chat-service.ts +169 -170
  108. package/src/services/cluster-service.ts +28 -28
  109. package/src/services/collaborator-service.ts +6 -6
  110. package/src/services/flux-service.ts +17 -17
  111. package/src/services/helm-service.ts +11 -11
  112. package/src/services/kubectl-service.ts +12 -12
  113. package/src/types/api.d.ts +1933 -1933
  114. package/src/types/json.d.ts +1 -1
  115. package/src/utils/config-checker.ts +6 -6
  116. package/src/utils/config-loader.ts +130 -129
  117. package/src/utils/default-api-key.ts +81 -80
  118. package/src/utils/env-manager.ts +37 -37
  119. package/src/utils/error-handler.ts +64 -64
  120. package/src/utils/logger.ts +72 -66
  121. package/src/utils/markdown-renderer.ts +28 -28
  122. package/src/utils/opencode-validator.ts +72 -71
  123. package/src/utils/token-manager.ts +69 -68
  124. package/tests/commands/chat.test.ts +32 -31
  125. package/tests/commands/code.test.ts +182 -181
  126. package/tests/utils/config-loader.test.ts +111 -110
  127. package/tests/utils/env-manager.test.ts +83 -79
  128. package/tests/utils/opencode-validator.test.ts +43 -42
  129. package/tsconfig.json +2 -1
  130. package/vitest.config.ts +2 -2
@@ -1,4 +1,4 @@
1
- declare module "*.json" {
1
+ declare module '*.json' {
2
2
  const value: any;
3
3
  export = value;
4
4
  }
@@ -1,20 +1,20 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
3
 
4
4
  /**
5
5
  * Check for .bergetconfig file and handle cluster switching
6
6
  */
7
7
  export function checkBergetConfig(): void {
8
- const configPath = path.join(process.cwd(), ".bergetconfig");
8
+ const configPath = path.join(process.cwd(), '.bergetconfig');
9
9
  if (fs.existsSync(configPath)) {
10
10
  try {
11
- const config = fs.readFileSync(configPath, "utf8");
11
+ const config = fs.readFileSync(configPath, 'utf8');
12
12
  const match = config.match(/cluster:\s*(.+)/);
13
13
  if (match && match[1]) {
14
14
  const clusterName = match[1].trim();
15
15
  console.log(`🔄 Berget: Switched to cluster "${clusterName}"`);
16
- console.log("✓ kubectl config updated");
17
- console.log("");
16
+ console.log('✓ kubectl config updated');
17
+ console.log('');
18
18
  }
19
19
  } catch {
20
20
  // Silently ignore errors reading config
@@ -1,6 +1,7 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import { logger } from "./logger";
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+
4
+ import { logger } from './logger';
4
5
 
5
6
  /**
6
7
  * Centralized agent configuration loader
@@ -8,18 +9,18 @@ import { logger } from "./logger";
8
9
  */
9
10
 
10
11
  export interface AgentConfig {
12
+ description?: string;
13
+ mode: 'primary' | 'subagent';
11
14
  model: string;
12
- temperature: number;
13
- top_p: number;
14
- mode: "primary" | "subagent";
15
+ note?: string;
15
16
  permission: {
16
- edit: "allow" | "deny";
17
- bash: "allow" | "deny";
18
- webfetch: "allow" | "deny";
17
+ bash: 'allow' | 'deny';
18
+ edit: 'allow' | 'deny';
19
+ webfetch: 'allow' | 'deny';
19
20
  };
20
- description?: string;
21
21
  prompt?: string;
22
- note?: string;
22
+ temperature: number;
23
+ top_p: number;
23
24
  }
24
25
 
25
26
  export interface ModelConfig {
@@ -27,47 +28,40 @@ export interface ModelConfig {
27
28
  small: string;
28
29
  }
29
30
 
31
+ export interface OpenCodeConfig {
32
+ $schema?: string;
33
+ agent?: Record<string, AgentConfig>;
34
+ autoupdate?: boolean;
35
+ command?: Record<string, any>;
36
+ model?: string;
37
+ provider?: Record<string, any>;
38
+ share?: string;
39
+ small_model?: string;
40
+ theme?: string;
41
+ username?: string;
42
+ watcher?: Record<string, any>;
43
+ }
44
+
30
45
  export interface ProviderModelConfig {
31
- name: string;
32
46
  limit: {
33
- output: number;
34
47
  context: number;
48
+ output: number;
35
49
  };
36
50
  modalities?: {
37
51
  input: string[];
38
52
  output: string[];
39
53
  };
40
- }
41
-
42
- export interface OpenCodeConfig {
43
- $schema?: string;
44
- username?: string;
45
- theme?: string;
46
- share?: string;
47
- autoupdate?: boolean;
48
- model?: string;
49
- small_model?: string;
50
- agent?: Record<string, AgentConfig>;
51
- command?: Record<string, any>;
52
- watcher?: Record<string, any>;
53
- provider?: Record<string, any>;
54
+ name: string;
54
55
  }
55
56
 
56
57
  export class ConfigLoader {
57
58
  private static instance: ConfigLoader;
58
- private config: OpenCodeConfig | null = null;
59
+ private config: null | OpenCodeConfig = null;
59
60
  private configPath: string;
60
61
 
61
62
  private constructor(configPath?: string) {
62
63
  // Default to opencode.json in current working directory
63
- this.configPath = configPath || path.join(process.cwd(), "opencode.json");
64
- }
65
-
66
- public static getInstance(configPath?: string): ConfigLoader {
67
- if (!ConfigLoader.instance) {
68
- ConfigLoader.instance = new ConfigLoader(configPath);
69
- }
70
- return ConfigLoader.instance;
64
+ this.configPath = configPath || path.join(process.cwd(), 'opencode.json');
71
65
  }
72
66
 
73
67
  /**
@@ -77,30 +71,11 @@ export class ConfigLoader {
77
71
  ConfigLoader.instance = null as any;
78
72
  }
79
73
 
80
- /**
81
- * Load configuration from opencode.json
82
- */
83
- public loadConfig(): OpenCodeConfig {
84
- if (this.config) {
85
- return this.config;
86
- }
87
-
88
- try {
89
- if (!fs.existsSync(this.configPath)) {
90
- throw new Error(`Configuration file not found: ${this.configPath}`);
91
- }
92
-
93
- const configContent = fs.readFileSync(this.configPath, "utf8");
94
- this.config = JSON.parse(configContent) as OpenCodeConfig;
95
-
96
- logger.debug(`Loaded configuration from ${this.configPath}`);
97
- return this.config;
98
- } catch (error) {
99
- logger.error(`Failed to load configuration from ${this.configPath}:`, error);
100
- throw new Error(
101
- `Failed to load configuration: ${error instanceof Error ? error.message : String(error)}`
102
- );
74
+ public static getInstance(configPath?: string): ConfigLoader {
75
+ if (!ConfigLoader.instance) {
76
+ ConfigLoader.instance = new ConfigLoader(configPath);
103
77
  }
78
+ return ConfigLoader.instance;
104
79
  }
105
80
 
106
81
  /**
@@ -116,6 +91,13 @@ export class ConfigLoader {
116
91
  }
117
92
  }
118
93
 
94
+ /**
95
+ * Get list of all available agent names
96
+ */
97
+ public getAgentNames(): string[] {
98
+ return Object.keys(this.getAllAgentConfigs());
99
+ }
100
+
119
101
  /**
120
102
  * Get all agent configurations
121
103
  */
@@ -129,6 +111,26 @@ export class ConfigLoader {
129
111
  }
130
112
  }
131
113
 
114
+ /**
115
+ * Get command configurations
116
+ */
117
+ public getCommandConfigs(): Record<string, any> {
118
+ try {
119
+ const config = this.loadConfig();
120
+ return config.command || {};
121
+ } catch {
122
+ // Config file doesn't exist, return empty object
123
+ return {};
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Get the current configuration path
129
+ */
130
+ public getConfigPath(): string {
131
+ return this.configPath;
132
+ }
133
+
132
134
  /**
133
135
  * Get model configuration
134
136
  */
@@ -137,19 +139,40 @@ export class ConfigLoader {
137
139
  const config = this.loadConfig();
138
140
 
139
141
  // Extract from config or fall back to defaults
140
- const primary = config.model || "berget/glm-4.7";
141
- const small = config.small_model || "berget/gpt-oss";
142
+ const primary = config.model || 'berget/glm-4.7';
143
+ const small = config.small_model || 'berget/gpt-oss';
142
144
 
143
145
  return { primary, small };
144
146
  } catch {
145
147
  // Fallback to defaults when no config exists (init scenario)
146
148
  return {
147
- primary: "berget/glm-4.7",
148
- small: "berget/gpt-oss",
149
+ primary: 'berget/glm-4.7',
150
+ small: 'berget/gpt-oss',
149
151
  };
150
152
  }
151
153
  }
152
154
 
155
+ /**
156
+ * Get list of primary agents (mode: 'primary')
157
+ */
158
+ public getPrimaryAgentNames(): string[] {
159
+ const agents = this.getAllAgentConfigs();
160
+ return Object.keys(agents).filter((name) => agents[name].mode === 'primary');
161
+ }
162
+
163
+ /**
164
+ * Get provider configuration
165
+ */
166
+ public getProviderConfig(): Record<string, any> {
167
+ try {
168
+ const config = this.loadConfig();
169
+ return config.provider || {};
170
+ } catch {
171
+ // Config file doesn't exist, return empty object
172
+ return {};
173
+ }
174
+ }
175
+
153
176
  /**
154
177
  * Get provider model configuration
155
178
  */
@@ -167,36 +190,31 @@ export class ConfigLoader {
167
190
 
168
191
  // Fallback to defaults
169
192
  return {
170
- "glm-4.7": {
171
- name: "GLM-4.7",
172
- limit: { output: 4000, context: 90000 },
193
+ 'glm-4.7': {
194
+ limit: { context: 90_000, output: 4000 },
195
+ name: 'GLM-4.7',
173
196
  },
174
- "gpt-oss": {
175
- name: "GPT-OSS",
176
- limit: { output: 4000, context: 128000 },
197
+ 'gpt-oss': {
198
+ limit: { context: 128_000, output: 4000 },
177
199
  modalities: {
178
- input: ["text", "image"],
179
- output: ["text"],
200
+ input: ['text', 'image'],
201
+ output: ['text'],
180
202
  },
203
+ name: 'GPT-OSS',
181
204
  },
182
- "llama-8b": {
183
- name: "llama-3.1-8b",
184
- limit: { output: 4000, context: 128000 },
205
+ 'llama-8b': {
206
+ limit: { context: 128_000, output: 4000 },
207
+ name: 'llama-3.1-8b',
185
208
  },
186
209
  };
187
210
  }
188
211
 
189
212
  /**
190
- * Get command configurations
213
+ * Get list of subagents (mode: 'subagent')
191
214
  */
192
- public getCommandConfigs(): Record<string, any> {
193
- try {
194
- const config = this.loadConfig();
195
- return config.command || {};
196
- } catch {
197
- // Config file doesn't exist, return empty object
198
- return {};
199
- }
215
+ public getSubagentNames(): string[] {
216
+ const agents = this.getAllAgentConfigs();
217
+ return Object.keys(agents).filter((name) => agents[name].mode === 'subagent');
200
218
  }
201
219
 
202
220
  /**
@@ -207,25 +225,12 @@ export class ConfigLoader {
207
225
  const config = this.loadConfig();
208
226
  return (
209
227
  config.watcher || {
210
- ignore: ["node_modules", "dist", ".git", "coverage"],
228
+ ignore: ['node_modules', 'dist', '.git', 'coverage'],
211
229
  }
212
230
  );
213
231
  } catch {
214
232
  // Config file doesn't exist, return default watcher config
215
- return { ignore: ["node_modules", "dist", ".git", "coverage"] };
216
- }
217
- }
218
-
219
- /**
220
- * Get provider configuration
221
- */
222
- public getProviderConfig(): Record<string, any> {
223
- try {
224
- const config = this.loadConfig();
225
- return config.provider || {};
226
- } catch {
227
- // Config file doesn't exist, return empty object
228
- return {};
233
+ return { ignore: ['node_modules', 'dist', '.git', 'coverage'] };
229
234
  }
230
235
  }
231
236
 
@@ -237,26 +242,29 @@ export class ConfigLoader {
237
242
  }
238
243
 
239
244
  /**
240
- * Get list of all available agent names
245
+ * Load configuration from opencode.json
241
246
  */
242
- public getAgentNames(): string[] {
243
- return Object.keys(this.getAllAgentConfigs());
244
- }
247
+ public loadConfig(): OpenCodeConfig {
248
+ if (this.config) {
249
+ return this.config;
250
+ }
245
251
 
246
- /**
247
- * Get list of primary agents (mode: 'primary')
248
- */
249
- public getPrimaryAgentNames(): string[] {
250
- const agents = this.getAllAgentConfigs();
251
- return Object.keys(agents).filter(name => agents[name].mode === "primary");
252
- }
252
+ try {
253
+ if (!fs.existsSync(this.configPath)) {
254
+ throw new Error(`Configuration file not found: ${this.configPath}`);
255
+ }
253
256
 
254
- /**
255
- * Get list of subagents (mode: 'subagent')
256
- */
257
- public getSubagentNames(): string[] {
258
- const agents = this.getAllAgentConfigs();
259
- return Object.keys(agents).filter(name => agents[name].mode === "subagent");
257
+ const configContent = fs.readFileSync(this.configPath, 'utf8');
258
+ this.config = JSON.parse(configContent) as OpenCodeConfig;
259
+
260
+ logger.debug(`Loaded configuration from ${this.configPath}`);
261
+ return this.config;
262
+ } catch (error) {
263
+ logger.error(`Failed to load configuration from ${this.configPath}:`, error);
264
+ throw new Error(
265
+ `Failed to load configuration: ${error instanceof Error ? error.message : String(error)}`,
266
+ );
267
+ }
260
268
  }
261
269
 
262
270
  /**
@@ -274,20 +282,6 @@ export class ConfigLoader {
274
282
  this.configPath = configPath;
275
283
  this.config = null; // Force reload
276
284
  }
277
-
278
- /**
279
- * Get the current configuration path
280
- */
281
- public getConfigPath(): string {
282
- return this.configPath;
283
- }
284
- }
285
-
286
- /**
287
- * Convenience function to get the config loader instance
288
- */
289
- export function getConfigLoader(configPath?: string): ConfigLoader {
290
- return ConfigLoader.getInstance(configPath);
291
285
  }
292
286
 
293
287
  /**
@@ -304,6 +298,13 @@ export function getAllAgentConfigs(configPath?: string): Record<string, AgentCon
304
298
  return getConfigLoader(configPath).getAllAgentConfigs();
305
299
  }
306
300
 
301
+ /**
302
+ * Convenience function to get the config loader instance
303
+ */
304
+ export function getConfigLoader(configPath?: string): ConfigLoader {
305
+ return ConfigLoader.getInstance(configPath);
306
+ }
307
+
307
308
  /**
308
309
  * Convenience function to get model configuration
309
310
  */
@@ -1,15 +1,16 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import * as os from "os";
4
- import { ApiKeyService } from "../services/api-key-service";
5
- import readline from "readline";
6
- import { logger } from "./logger";
1
+ import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import readline from 'node:readline';
5
+
6
+ import { ApiKeyService } from '../services/api-key-service';
7
+ import { logger } from './logger';
7
8
 
8
9
  interface DefaultApiKeyData {
9
10
  id: string;
11
+ key: string;
10
12
  name: string;
11
13
  prefix: string;
12
- key: string;
13
14
  }
14
15
 
15
16
  /**
@@ -22,11 +23,11 @@ export class DefaultApiKeyManager {
22
23
 
23
24
  private constructor() {
24
25
  // Set up config file path in user's home directory
25
- const bergetDir = path.join(os.homedir(), ".berget");
26
+ const bergetDir = path.join(os.homedir(), '.berget');
26
27
  if (!fs.existsSync(bergetDir)) {
27
28
  fs.mkdirSync(bergetDir, { recursive: true });
28
29
  }
29
- this.configFilePath = path.join(bergetDir, "default-api-key.json");
30
+ this.configFilePath = path.join(bergetDir, 'default-api-key.json');
30
31
  this.loadConfig();
31
32
  }
32
33
 
@@ -38,52 +39,17 @@ export class DefaultApiKeyManager {
38
39
  }
39
40
 
40
41
  /**
41
- * Load default API key from file
42
- */
43
- private loadConfig(): void {
44
- try {
45
- if (fs.existsSync(this.configFilePath)) {
46
- const data = fs.readFileSync(this.configFilePath, "utf8");
47
- this.defaultApiKey = JSON.parse(data);
48
- }
49
- } catch {
50
- logger.debug("Failed to load default API key configuration");
51
- this.defaultApiKey = null;
52
- }
53
- }
54
-
55
- /**
56
- * Save default API key to file
57
- */
58
- private saveConfig(): void {
59
- try {
60
- if (this.defaultApiKey) {
61
- fs.writeFileSync(this.configFilePath, JSON.stringify(this.defaultApiKey, null, 2));
62
- // Set file permissions to be readable only by the owner
63
- fs.chmodSync(this.configFilePath, 0o600);
64
- } else {
65
- // If default API key is null, remove the file
66
- if (fs.existsSync(this.configFilePath)) {
67
- fs.unlinkSync(this.configFilePath);
68
- }
69
- }
70
- } catch {
71
- logger.debug("Failed to save default API key configuration");
72
- }
73
- }
74
-
75
- /**
76
- * Set the default API key
42
+ * Clear the default API key
77
43
  */
78
- public setDefaultApiKey(id: string, name: string, prefix: string, key: string): void {
79
- this.defaultApiKey = { id, name, prefix, key };
44
+ public clearDefaultApiKey(): void {
45
+ this.defaultApiKey = null;
80
46
  this.saveConfig();
81
47
  }
82
48
 
83
49
  /**
84
50
  * Get the default API key string
85
51
  */
86
- public getDefaultApiKey(): string | null {
52
+ public getDefaultApiKey(): null | string {
87
53
  return this.defaultApiKey?.key || null;
88
54
  }
89
55
 
@@ -94,43 +60,35 @@ export class DefaultApiKeyManager {
94
60
  return this.defaultApiKey;
95
61
  }
96
62
 
97
- /**
98
- * Clear the default API key
99
- */
100
- public clearDefaultApiKey(): void {
101
- this.defaultApiKey = null;
102
- this.saveConfig();
103
- }
104
-
105
63
  /**
106
64
  * Prompts the user to select a default API key if none is set
107
65
  * @returns The selected API key or null if none was selected
108
66
  */
109
- public async promptForDefaultApiKey(): Promise<string | null> {
67
+ public async promptForDefaultApiKey(): Promise<null | string> {
110
68
  try {
111
- logger.debug("promptForDefaultApiKey called");
69
+ logger.debug('promptForDefaultApiKey called');
112
70
 
113
71
  // If we already have a default API key, return it
114
72
  if (this.defaultApiKey) {
115
- logger.debug("Using existing default API key");
73
+ logger.debug('Using existing default API key');
116
74
  return this.defaultApiKey.key;
117
75
  }
118
76
 
119
- logger.debug("No default API key found, getting ApiKeyService");
77
+ logger.debug('No default API key found, getting ApiKeyService');
120
78
 
121
79
  const apiKeyService = ApiKeyService.getInstance();
122
80
 
123
81
  // Get all API keys
124
82
  let apiKeys;
125
83
  try {
126
- logger.debug("Calling apiKeyService.list()");
84
+ logger.debug('Calling apiKeyService.list()');
127
85
 
128
86
  apiKeys = await apiKeyService.list();
129
87
 
130
88
  logger.debug(`Got ${apiKeys ? apiKeys.length : 0} API keys`);
131
89
 
132
90
  if (!apiKeys || apiKeys.length === 0) {
133
- logger.warn("No API keys found. Create one with:");
91
+ logger.warn('No API keys found. Create one with:');
134
92
  logger.info(' berget api-keys create --name "My Key"');
135
93
  return null;
136
94
  }
@@ -138,14 +96,14 @@ export class DefaultApiKeyManager {
138
96
  // Check if this is an authentication error
139
97
  const errorMessage = error instanceof Error ? error.message : String(error);
140
98
  const isAuthError =
141
- errorMessage.includes("Unauthorized") ||
142
- errorMessage.includes("Authentication failed") ||
143
- errorMessage.includes("AUTH_FAILED");
99
+ errorMessage.includes('Unauthorized') ||
100
+ errorMessage.includes('Authentication failed') ||
101
+ errorMessage.includes('AUTH_FAILED');
144
102
 
145
103
  if (isAuthError) {
146
- logger.warn("Authentication required. Please run `berget auth login` first.");
104
+ logger.warn('Authentication required. Please run `berget auth login` first.');
147
105
  } else {
148
- logger.error("Error fetching API keys:");
106
+ logger.error('Error fetching API keys:');
149
107
  if (error instanceof Error) {
150
108
  logger.error(error.message);
151
109
  logger.debug(`API key list error: ${error.message}`);
@@ -155,12 +113,12 @@ export class DefaultApiKeyManager {
155
113
  return null;
156
114
  }
157
115
 
158
- logger.info("Select an API key to use as default:");
116
+ logger.info('Select an API key to use as default:');
159
117
 
160
118
  // Display available API keys
161
- apiKeys.forEach((key, index) => {
119
+ for (const [index, key] of apiKeys.entries()) {
162
120
  logger.log(` ${index + 1}. ${key.name} (${key.prefix}...)`);
163
- });
121
+ }
164
122
 
165
123
  // Create readline interface for user input
166
124
  const rl = readline.createInterface({
@@ -169,20 +127,20 @@ export class DefaultApiKeyManager {
169
127
  });
170
128
 
171
129
  // Prompt for selection
172
- const selection = await new Promise<number>(resolve => {
173
- rl.question("Enter number (or press Enter to cancel): ", answer => {
130
+ const selection = await new Promise<number>((resolve) => {
131
+ rl.question('Enter number (or press Enter to cancel): ', (answer) => {
174
132
  rl.close();
175
- const num = parseInt(answer.trim(), 10);
176
- if (isNaN(num) || num < 1 || num > apiKeys.length) {
133
+ const number_ = Number.parseInt(answer.trim(), 10);
134
+ if (isNaN(number_) || number_ < 1 || number_ > apiKeys.length) {
177
135
  resolve(-1); // Invalid selection
178
136
  } else {
179
- resolve(num - 1); // Convert to zero-based index
137
+ resolve(number_ - 1); // Convert to zero-based index
180
138
  }
181
139
  });
182
140
  });
183
141
 
184
142
  if (selection === -1) {
185
- logger.warn("No API key selected");
143
+ logger.warn('No API key selected');
186
144
  return null;
187
145
  }
188
146
 
@@ -190,23 +148,66 @@ export class DefaultApiKeyManager {
190
148
 
191
149
  // Create a new API key with the selected name
192
150
  const newKey = await apiKeyService.create({
151
+ description: 'Created automatically by the Berget CLI for default use',
193
152
  name: `CLI Default (copy of ${selectedKey.name})`,
194
- description: "Created automatically by the Berget CLI for default use",
195
153
  });
196
154
 
197
155
  // Save the new key as default
198
156
  this.setDefaultApiKey(
199
157
  newKey.id.toString(),
200
158
  newKey.name,
201
- newKey.key.substring(0, 8), // Use first 8 chars as prefix
202
- newKey.key
159
+ newKey.key.slice(0, 8), // Use first 8 chars as prefix
160
+ newKey.key,
203
161
  );
204
162
 
205
163
  logger.success(`✓ Default API key set to: ${newKey.name}`);
206
164
  return newKey.key;
207
165
  } catch (error) {
208
- logger.error("Failed to set default API key:", error);
166
+ logger.error('Failed to set default API key:', error);
209
167
  return null;
210
168
  }
211
169
  }
170
+
171
+ /**
172
+ * Set the default API key
173
+ */
174
+ public setDefaultApiKey(id: string, name: string, prefix: string, key: string): void {
175
+ this.defaultApiKey = { id, key, name, prefix };
176
+ this.saveConfig();
177
+ }
178
+
179
+ /**
180
+ * Load default API key from file
181
+ */
182
+ private loadConfig(): void {
183
+ try {
184
+ if (fs.existsSync(this.configFilePath)) {
185
+ const data = fs.readFileSync(this.configFilePath, 'utf8');
186
+ this.defaultApiKey = JSON.parse(data);
187
+ }
188
+ } catch {
189
+ logger.debug('Failed to load default API key configuration');
190
+ this.defaultApiKey = null;
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Save default API key to file
196
+ */
197
+ private saveConfig(): void {
198
+ try {
199
+ if (this.defaultApiKey) {
200
+ fs.writeFileSync(this.configFilePath, JSON.stringify(this.defaultApiKey, null, 2));
201
+ // Set file permissions to be readable only by the owner
202
+ fs.chmodSync(this.configFilePath, 0o600);
203
+ } else {
204
+ // If default API key is null, remove the file
205
+ if (fs.existsSync(this.configFilePath)) {
206
+ fs.unlinkSync(this.configFilePath);
207
+ }
208
+ }
209
+ } catch {
210
+ logger.debug('Failed to save default API key configuration');
211
+ }
212
+ }
212
213
  }