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,28 +1,28 @@
1
- import Ajv from "ajv";
2
- import addFormats from "ajv-formats";
3
- import { readFileSync } from "fs";
4
- import { join } from "path";
5
- import { dirname } from "path";
1
+ import Ajv from 'ajv';
2
+ import addFormats from 'ajv-formats';
3
+ import { readFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { dirname } from 'node:path';
6
6
 
7
7
  // Load the official OpenCode JSON Schema
8
8
  const __dirname = dirname(__filename);
9
- const schemaPath = join(__dirname, "..", "schemas", "opencode-schema.json");
9
+ const schemaPath = join(__dirname, '..', 'schemas', 'opencode-schema.json');
10
10
 
11
11
  let ajv: Ajv;
12
12
  let openCodeSchema: any;
13
13
  let validateFunction: any;
14
14
 
15
15
  try {
16
- const schemaContent = readFileSync(schemaPath, "utf-8");
16
+ const schemaContent = readFileSync(schemaPath, 'utf-8');
17
17
  openCodeSchema = JSON.parse(schemaContent);
18
18
 
19
19
  // Initialize AJV with formats and options
20
20
  ajv = new Ajv({
21
21
  allErrors: true,
22
- verbose: true,
23
- strict: false,
24
22
  allowUnionTypes: true,
25
23
  removeAdditional: false,
24
+ strict: false,
25
+ verbose: true,
26
26
  });
27
27
 
28
28
  // Add JSON Schema formats
@@ -31,43 +31,12 @@ try {
31
31
  // Compile the schema
32
32
  validateFunction = ajv.compile(openCodeSchema);
33
33
  } catch (error) {
34
- console.error("Failed to load OpenCode schema:", error);
35
- throw new Error("Could not initialize OpenCode validator");
34
+ console.error('Failed to load OpenCode schema:', error);
35
+ throw new Error('Could not initialize OpenCode validator');
36
36
  }
37
37
 
38
38
  export type OpenCodeConfig = any;
39
39
 
40
- /**
41
- * Validate OpenCode configuration against the official JSON Schema
42
- */
43
- export function validateOpenCodeConfig(config: any): {
44
- valid: boolean;
45
- errors?: string[];
46
- } {
47
- try {
48
- if (!validateFunction) {
49
- return { valid: false, errors: ["Schema validator not initialized"] };
50
- }
51
-
52
- const isValid = validateFunction(config);
53
-
54
- if (isValid) {
55
- return { valid: true };
56
- } else {
57
- const errors = validateFunction.errors?.map((err: any) => {
58
- const path = err.instancePath || err.schemaPath || "root";
59
- const message = err.message || "Unknown error";
60
- return `${path}: ${message}`;
61
- }) || ["Unknown validation error"];
62
-
63
- return { valid: false, errors };
64
- }
65
- } catch (error) {
66
- console.error("Validation error:", error);
67
- return { valid: false, errors: ["Validation process failed"] };
68
- }
69
- }
70
-
71
40
  /**
72
41
  * Fix common OpenCode configuration issues
73
42
  */
@@ -75,50 +44,51 @@ export function fixOpenCodeConfig(config: any): OpenCodeConfig {
75
44
  const fixed = { ...config };
76
45
 
77
46
  // Fix tools.compact - should be boolean, not object
78
- if (fixed.tools && typeof fixed.tools.compact === "object") {
79
- console.warn("⚠️ Converting tools.compact from object to boolean");
47
+ if (fixed.tools && typeof fixed.tools.compact === 'object') {
48
+ console.warn('⚠️ Converting tools.compact from object to boolean');
80
49
  // If it has properties, assume it should be enabled
81
50
  fixed.tools.compact = true;
82
51
  }
83
52
 
84
53
  // Remove invalid properties
85
- const invalidProps = ["maxTokens", "contextWindow"];
86
- invalidProps.forEach(prop => {
87
- if (fixed[prop] !== undefined) {
88
- console.warn(`⚠️ Removing invalid property: ${prop}`);
89
- delete fixed[prop];
54
+ const invalidProperties = ['maxTokens', 'contextWindow'];
55
+ for (const property of invalidProperties) {
56
+ if (fixed[property] !== undefined) {
57
+ console.warn(`⚠️ Removing invalid property: ${property}`);
58
+ delete fixed[property];
90
59
  }
91
- });
60
+ }
92
61
 
93
62
  // Fix provider models with invalid properties
94
63
  if (fixed.provider) {
95
64
  Object.values(fixed.provider).forEach((provider: any) => {
96
65
  if (provider?.models) {
97
66
  Object.values(provider.models).forEach((model: any) => {
98
- if (model && typeof model === "object") {
99
- // Move maxTokens/contextWindow to proper structure if needed
100
- if (model.maxTokens || model.contextWindow) {
101
- if (!model.limit) model.limit = {};
102
-
103
- // Use the larger of maxTokens/contextWindow for context
104
- const contextValues = [model.maxTokens, model.contextWindow].filter(Boolean);
105
- if (contextValues.length > 0) {
106
- const newContext = Math.max(...contextValues);
107
- if (!model.limit.context || newContext > model.limit.context) {
108
- model.limit.context = newContext;
109
- }
110
- }
111
-
112
- // Set a reasonable default for output if not present
113
- // (typically 1/4 to 1/8 of context window)
114
- if (!model.limit.output && model.limit.context) {
115
- model.limit.output = Math.floor(model.limit.context / 4);
67
+ if (
68
+ model &&
69
+ typeof model === 'object' && // Move maxTokens/contextWindow to proper structure if needed
70
+ (model.maxTokens || model.contextWindow)
71
+ ) {
72
+ if (!model.limit) model.limit = {};
73
+
74
+ // Use the larger of maxTokens/contextWindow for context
75
+ const contextValues = [model.maxTokens, model.contextWindow].filter(Boolean);
76
+ if (contextValues.length > 0) {
77
+ const newContext = Math.max(...contextValues);
78
+ if (!model.limit.context || newContext > model.limit.context) {
79
+ model.limit.context = newContext;
116
80
  }
81
+ }
117
82
 
118
- delete model.maxTokens;
119
- delete model.contextWindow;
120
- console.warn("⚠️ Moved maxTokens/contextWindow to limit.context/output");
83
+ // Set a reasonable default for output if not present
84
+ // (typically 1/4 to 1/8 of context window)
85
+ if (!model.limit.output && model.limit.context) {
86
+ model.limit.output = Math.floor(model.limit.context / 4);
121
87
  }
88
+
89
+ delete model.maxTokens;
90
+ delete model.contextWindow;
91
+ console.warn('⚠️ Moved maxTokens/contextWindow to limit.context/output');
122
92
  }
123
93
  });
124
94
  }
@@ -127,3 +97,34 @@ export function fixOpenCodeConfig(config: any): OpenCodeConfig {
127
97
 
128
98
  return fixed;
129
99
  }
100
+
101
+ /**
102
+ * Validate OpenCode configuration against the official JSON Schema
103
+ */
104
+ export function validateOpenCodeConfig(config: any): {
105
+ errors?: string[];
106
+ valid: boolean;
107
+ } {
108
+ try {
109
+ if (!validateFunction) {
110
+ return { errors: ['Schema validator not initialized'], valid: false };
111
+ }
112
+
113
+ const isValid = validateFunction(config);
114
+
115
+ if (isValid) {
116
+ return { valid: true };
117
+ } else {
118
+ const errors = validateFunction.errors?.map((error: any) => {
119
+ const path = error.instancePath || error.schemaPath || 'root';
120
+ const message = error.message || 'Unknown error';
121
+ return `${path}: ${message}`;
122
+ }) || ['Unknown validation error'];
123
+
124
+ return { errors, valid: false };
125
+ }
126
+ } catch (error) {
127
+ console.error('Validation error:', error);
128
+ return { errors: ['Validation process failed'], valid: false };
129
+ }
130
+ }
@@ -1,32 +1,13 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import * as os from "os";
4
- 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
+
5
+ import { logger } from './logger';
5
6
 
6
7
  interface TokenData {
7
8
  access_token: string;
8
- refresh_token: string;
9
9
  expires_at: number; // timestamp in milliseconds
10
- }
11
-
12
- /**
13
- * Extract the expiration time from a JWT token
14
- * @param accessToken The JWT access token
15
- * @returns The expiration timestamp in milliseconds, or 0 if invalid
16
- */
17
- function extractJwtExpiresAt(accessToken: string): number {
18
- try {
19
- const parts = accessToken.split(".");
20
- if (parts.length !== 3) return 0;
21
- const payload = Buffer.from(parts[1], "base64url").toString("utf8");
22
- const decoded = JSON.parse(payload);
23
- if (typeof decoded.exp === "number") {
24
- return decoded.exp * 1000; // JWT exp is in seconds, convert to milliseconds
25
- }
26
- } catch {
27
- // If decoding fails, return 0 (treated as expired)
28
- }
29
- return 0;
10
+ refresh_token: string;
30
11
  }
31
12
 
32
13
  /**
@@ -34,16 +15,16 @@ function extractJwtExpiresAt(accessToken: string): number {
34
15
  */
35
16
  export class TokenManager {
36
17
  private static instance: TokenManager;
18
+ private tokenData: null | TokenData = null;
37
19
  private tokenFilePath: string;
38
- private tokenData: TokenData | null = null;
39
20
 
40
21
  private constructor() {
41
22
  // Set up token file path in user's home directory
42
- const bergetDir = path.join(os.homedir(), ".berget");
23
+ const bergetDir = path.join(os.homedir(), '.berget');
43
24
  if (!fs.existsSync(bergetDir)) {
44
25
  fs.mkdirSync(bergetDir, { recursive: true });
45
26
  }
46
- this.tokenFilePath = path.join(bergetDir, "auth.json");
27
+ this.tokenFilePath = path.join(bergetDir, 'auth.json');
47
28
  this.loadToken();
48
29
  }
49
30
 
@@ -55,45 +36,18 @@ export class TokenManager {
55
36
  }
56
37
 
57
38
  /**
58
- * Load token data from file
59
- */
60
- private loadToken(): void {
61
- try {
62
- if (fs.existsSync(this.tokenFilePath)) {
63
- const data = fs.readFileSync(this.tokenFilePath, "utf8");
64
- this.tokenData = JSON.parse(data);
65
- }
66
- } catch {
67
- logger.error("Failed to load authentication token");
68
- this.tokenData = null;
69
- }
70
- }
71
-
72
- /**
73
- * Save token data to file
39
+ * Clear all token data
74
40
  */
75
- private saveToken(): void {
76
- try {
77
- if (this.tokenData) {
78
- fs.writeFileSync(this.tokenFilePath, JSON.stringify(this.tokenData, null, 2));
79
- // Set file permissions to be readable only by the owner
80
- fs.chmodSync(this.tokenFilePath, 0o600);
81
- } else {
82
- // If token data is null, remove the file
83
- if (fs.existsSync(this.tokenFilePath)) {
84
- fs.unlinkSync(this.tokenFilePath);
85
- }
86
- }
87
- } catch {
88
- logger.error("Failed to save authentication token");
89
- }
41
+ public clearTokens(): void {
42
+ this.tokenData = null;
43
+ this.saveToken();
90
44
  }
91
45
 
92
46
  /**
93
47
  * Get the current access token
94
48
  * @returns The access token or null if not available
95
49
  */
96
- public getAccessToken(): string | null {
50
+ public getAccessToken(): null | string {
97
51
  if (!this.tokenData) return null;
98
52
  return this.tokenData.access_token;
99
53
  }
@@ -102,7 +56,7 @@ export class TokenManager {
102
56
  * Get the refresh token
103
57
  * @returns The refresh token or null if not available
104
58
  */
105
- public getRefreshToken(): string | null {
59
+ public getRefreshToken(): null | string {
106
60
  if (!this.tokenData) return null;
107
61
  return this.tokenData.refresh_token;
108
62
  }
@@ -129,7 +83,7 @@ export class TokenManager {
129
83
 
130
84
  if (isExpired) {
131
85
  logger.debug(
132
- `Token expired or expiring soon. Current time: ${new Date().toISOString()}, Expiry: ${new Date(expiresAt).toISOString()}`
86
+ `Token expired or expiring soon. Current time: ${new Date().toISOString()}, Expiry: ${new Date(expiresAt).toISOString()}`,
133
87
  );
134
88
  }
135
89
 
@@ -137,7 +91,7 @@ export class TokenManager {
137
91
  } catch (error) {
138
92
  // If there's any error checking expiration, assume token is expired
139
93
  logger.error(
140
- `Error checking token expiration: ${error instanceof Error ? error.message : String(error)}`
94
+ `Error checking token expiration: ${error instanceof Error ? error.message : String(error)}`,
141
95
  );
142
96
  return true;
143
97
  }
@@ -156,8 +110,8 @@ export class TokenManager {
156
110
 
157
111
  this.tokenData = {
158
112
  access_token: accessToken,
159
- refresh_token: refreshToken,
160
113
  expires_at: expiresAt,
114
+ refresh_token: refreshToken,
161
115
  };
162
116
  this.saveToken();
163
117
  }
@@ -180,10 +134,57 @@ export class TokenManager {
180
134
  }
181
135
 
182
136
  /**
183
- * Clear all token data
137
+ * Load token data from file
184
138
  */
185
- public clearTokens(): void {
186
- this.tokenData = null;
187
- this.saveToken();
139
+ private loadToken(): void {
140
+ try {
141
+ if (fs.existsSync(this.tokenFilePath)) {
142
+ const data = fs.readFileSync(this.tokenFilePath, 'utf8');
143
+ this.tokenData = JSON.parse(data);
144
+ }
145
+ } catch {
146
+ logger.error('Failed to load authentication token');
147
+ this.tokenData = null;
148
+ }
188
149
  }
150
+
151
+ /**
152
+ * Save token data to file
153
+ */
154
+ private saveToken(): void {
155
+ try {
156
+ if (this.tokenData) {
157
+ fs.writeFileSync(this.tokenFilePath, JSON.stringify(this.tokenData, null, 2));
158
+ // Set file permissions to be readable only by the owner
159
+ fs.chmodSync(this.tokenFilePath, 0o600);
160
+ } else {
161
+ // If token data is null, remove the file
162
+ if (fs.existsSync(this.tokenFilePath)) {
163
+ fs.unlinkSync(this.tokenFilePath);
164
+ }
165
+ }
166
+ } catch {
167
+ logger.error('Failed to save authentication token');
168
+ }
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Extract the expiration time from a JWT token
174
+ * @param accessToken The JWT access token
175
+ * @returns The expiration timestamp in milliseconds, or 0 if invalid
176
+ */
177
+ function extractJwtExpiresAt(accessToken: string): number {
178
+ try {
179
+ const parts = accessToken.split('.');
180
+ if (parts.length !== 3) return 0;
181
+ const payload = Buffer.from(parts[1], 'base64url').toString('utf8');
182
+ const decoded = JSON.parse(payload);
183
+ if (typeof decoded.exp === 'number') {
184
+ return decoded.exp * 1000; // JWT exp is in seconds, convert to milliseconds
185
+ }
186
+ } catch {
187
+ // If decoding fails, return 0 (treated as expired)
188
+ }
189
+ return 0;
189
190
  }
@@ -1,20 +1,21 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
- import { Command } from "commander";
3
- import { registerChatCommands } from "../../src/commands/chat";
4
- import { ChatService } from "../../src/services/chat-service";
5
- import { DefaultApiKeyManager } from "../../src/utils/default-api-key";
1
+ import { Command } from 'commander';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { registerChatCommands } from '../../src/commands/chat';
5
+ import { ChatService } from '../../src/services/chat-service';
6
+ import { DefaultApiKeyManager } from '../../src/utils/default-api-key';
6
7
 
7
8
  // Mock dependencies
8
- vi.mock("../../src/services/chat-service");
9
- vi.mock("../../src/utils/default-api-key");
10
- vi.mock("readline", () => ({
9
+ vi.mock('../../src/services/chat-service');
10
+ vi.mock('../../src/utils/default-api-key');
11
+ vi.mock('readline', () => ({
11
12
  createInterface: vi.fn(() => ({
12
- question: vi.fn(),
13
13
  close: vi.fn(),
14
+ question: vi.fn(),
14
15
  })),
15
16
  }));
16
17
 
17
- describe("Chat Commands", () => {
18
+ describe('Chat Commands', () => {
18
19
  let program: Command;
19
20
  let mockChatService: any;
20
21
  let mockDefaultApiKeyManager: any;
@@ -43,39 +44,39 @@ describe("Chat Commands", () => {
43
44
  vi.clearAllMocks();
44
45
  });
45
46
 
46
- describe("chat run command", () => {
47
- it("should use berget/glm-4.7 as default model", () => {
48
- const chatCommand = program.commands.find(cmd => cmd.name() === "chat");
49
- const runCommand = chatCommand?.commands.find(cmd => cmd.name() === "run");
47
+ describe('chat run command', () => {
48
+ it('should use berget/glm-4.7 as default model', () => {
49
+ const chatCommand = program.commands.find((cmd) => cmd.name() === 'chat');
50
+ const runCommand = chatCommand?.commands.find((cmd) => cmd.name() === 'run');
50
51
 
51
52
  expect(runCommand).toBeDefined();
52
53
 
53
54
  // Check the help text which contains the default model
54
55
  const helpText = runCommand?.helpInformation();
55
- expect(helpText).toContain("glm-4.7");
56
+ expect(helpText).toContain('glm-4.7');
56
57
  });
57
58
 
58
- it("should have streaming enabled by default", () => {
59
- const chatCommand = program.commands.find(cmd => cmd.name() === "chat");
60
- const runCommand = chatCommand?.commands.find(cmd => cmd.name() === "run");
59
+ it('should have streaming enabled by default', () => {
60
+ const chatCommand = program.commands.find((cmd) => cmd.name() === 'chat');
61
+ const runCommand = chatCommand?.commands.find((cmd) => cmd.name() === 'run');
61
62
 
62
63
  expect(runCommand).toBeDefined();
63
64
 
64
65
  // Check that the option is --no-stream (meaning streaming is default)
65
- const streamOption = runCommand?.options.find(opt => opt.long === "--no-stream");
66
+ const streamOption = runCommand?.options.find((opt) => opt.long === '--no-stream');
66
67
  expect(streamOption).toBeDefined();
67
- expect(streamOption?.description).toContain("Disable streaming");
68
+ expect(streamOption?.description).toContain('Disable streaming');
68
69
  });
69
70
 
70
- it("should create completion with correct default options", async () => {
71
+ it('should create completion with correct default options', async () => {
71
72
  // Mock API key
72
- process.env.BERGET_API_KEY = "test-key";
73
+ process.env.BERGET_API_KEY = 'test-key';
73
74
 
74
75
  // Mock successful completion
75
76
  mockChatService.createCompletion.mockResolvedValue({
76
77
  choices: [
77
78
  {
78
- message: { content: "Test response" },
79
+ message: { content: 'Test response' },
79
80
  },
80
81
  ],
81
82
  });
@@ -90,30 +91,30 @@ describe("Chat Commands", () => {
90
91
  });
91
92
  });
92
93
 
93
- describe("chat list command", () => {
94
- it("should list available models", async () => {
94
+ describe('chat list command', () => {
95
+ it('should list available models', async () => {
95
96
  const mockModels = {
96
97
  data: [
97
98
  {
98
- id: "gpt-oss",
99
- owned_by: "openai",
100
99
  active: true,
101
100
  capabilities: {
102
- vision: false,
103
101
  function_calling: true,
104
102
  json_mode: true,
103
+ vision: false,
105
104
  },
105
+ id: 'gpt-oss',
106
+ owned_by: 'openai',
106
107
  },
107
108
  ],
108
109
  };
109
110
 
110
111
  mockChatService.listModels.mockResolvedValue(mockModels);
111
112
 
112
- const chatCommand = program.commands.find(cmd => cmd.name() === "chat");
113
- const listCommand = chatCommand?.commands.find(cmd => cmd.name() === "list");
113
+ const chatCommand = program.commands.find((cmd) => cmd.name() === 'chat');
114
+ const listCommand = chatCommand?.commands.find((cmd) => cmd.name() === 'list');
114
115
 
115
116
  expect(listCommand).toBeDefined();
116
- expect(listCommand?.description()).toBe("List available chat models");
117
+ expect(listCommand?.description()).toBe('List available chat models');
117
118
  });
118
119
  });
119
120
  });