berget 1.3.1 → 2.0.0

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 (67) hide show
  1. package/.env.example +5 -0
  2. package/.github/workflows/publish.yml +56 -0
  3. package/.github/workflows/test.yml +38 -0
  4. package/AGENTS.md +184 -0
  5. package/README.md +177 -38
  6. package/TODO.md +2 -0
  7. package/blog-post.md +176 -0
  8. package/dist/index.js +11 -8
  9. package/dist/package.json +14 -3
  10. package/dist/src/commands/api-keys.js +4 -2
  11. package/dist/src/commands/chat.js +182 -23
  12. package/dist/src/commands/code.js +1424 -0
  13. package/dist/src/commands/index.js +2 -0
  14. package/dist/src/constants/command-structure.js +12 -0
  15. package/dist/src/schemas/opencode-schema.json +1121 -0
  16. package/dist/src/services/chat-service.js +10 -10
  17. package/dist/src/services/cluster-service.js +1 -1
  18. package/dist/src/utils/default-api-key.js +2 -2
  19. package/dist/src/utils/env-manager.js +86 -0
  20. package/dist/src/utils/error-handler.js +10 -3
  21. package/dist/src/utils/markdown-renderer.js +4 -4
  22. package/dist/src/utils/opencode-validator.js +122 -0
  23. package/dist/src/utils/token-manager.js +2 -2
  24. package/dist/tests/commands/chat.test.js +109 -0
  25. package/dist/tests/commands/code.test.js +414 -0
  26. package/dist/tests/utils/env-manager.test.js +148 -0
  27. package/dist/tests/utils/opencode-validator.test.js +103 -0
  28. package/dist/vitest.config.js +9 -0
  29. package/index.ts +67 -32
  30. package/opencode.json +182 -0
  31. package/package.json +14 -3
  32. package/src/client.ts +20 -20
  33. package/src/commands/api-keys.ts +93 -60
  34. package/src/commands/auth.ts +4 -2
  35. package/src/commands/billing.ts +6 -3
  36. package/src/commands/chat.ts +291 -97
  37. package/src/commands/clusters.ts +2 -2
  38. package/src/commands/code.ts +1696 -0
  39. package/src/commands/index.ts +2 -0
  40. package/src/commands/models.ts +3 -3
  41. package/src/commands/users.ts +2 -2
  42. package/src/constants/command-structure.ts +112 -58
  43. package/src/schemas/opencode-schema.json +991 -0
  44. package/src/services/api-key-service.ts +1 -1
  45. package/src/services/auth-service.ts +27 -25
  46. package/src/services/chat-service.ts +37 -44
  47. package/src/services/cluster-service.ts +5 -5
  48. package/src/services/collaborator-service.ts +3 -3
  49. package/src/services/flux-service.ts +2 -2
  50. package/src/services/helm-service.ts +2 -2
  51. package/src/services/kubectl-service.ts +3 -6
  52. package/src/types/api.d.ts +1032 -1010
  53. package/src/types/json.d.ts +3 -3
  54. package/src/utils/default-api-key.ts +54 -42
  55. package/src/utils/env-manager.ts +98 -0
  56. package/src/utils/error-handler.ts +24 -15
  57. package/src/utils/logger.ts +12 -12
  58. package/src/utils/markdown-renderer.ts +18 -18
  59. package/src/utils/opencode-validator.ts +134 -0
  60. package/src/utils/token-manager.ts +35 -23
  61. package/tests/commands/chat.test.ts +129 -0
  62. package/tests/commands/code.test.ts +505 -0
  63. package/tests/utils/env-manager.test.ts +199 -0
  64. package/tests/utils/opencode-validator.test.ts +118 -0
  65. package/tsconfig.json +8 -8
  66. package/vitest.config.ts +8 -0
  67. package/-27b-it +0 -0
@@ -256,26 +256,26 @@ class ChatService {
256
256
  */
257
257
  handleStreamingResponse(options, headers) {
258
258
  return __awaiter(this, void 0, void 0, function* () {
259
- logger_1.logger.debug('Handling streaming response');
260
- // Create URL with query parameters
261
- const url = new URL(`${client_1.API_BASE_URL}/v1/chat/completions`);
262
- // Debug the headers and options
263
- logger_1.logger.debug('Streaming headers:');
264
- logger_1.logger.debug(JSON.stringify(headers, null, 2));
265
- logger_1.logger.debug('Streaming options:');
266
- logger_1.logger.debug(JSON.stringify(Object.assign(Object.assign({}, options), { onChunk: options.onChunk ? 'function present' : 'no function' }), null, 2));
259
+ // Use the same base URL as the client
260
+ const baseUrl = process.env.API_BASE_URL || 'https://api.berget.ai';
261
+ const url = new URL(`${baseUrl}/v1/chat/completions`);
267
262
  try {
263
+ logger_1.logger.debug(`Making streaming request to: ${url.toString()}`);
264
+ logger_1.logger.debug(`Headers:`, JSON.stringify(headers, null, 2));
265
+ logger_1.logger.debug(`Body:`, JSON.stringify(options, null, 2));
268
266
  // Make fetch request directly to handle streaming
269
267
  const response = yield fetch(url.toString(), {
270
268
  method: 'POST',
271
269
  headers: Object.assign({ 'Content-Type': 'application/json', Accept: 'text/event-stream' }, headers),
272
270
  body: JSON.stringify(options),
273
271
  });
272
+ logger_1.logger.debug(`Response status: ${response.status}`);
273
+ logger_1.logger.debug(`Response headers:`, JSON.stringify(Object.fromEntries(response.headers.entries()), null, 2));
274
274
  if (!response.ok) {
275
275
  const errorText = yield response.text();
276
276
  logger_1.logger.error(`Stream request failed: ${response.status} ${response.statusText}`);
277
- logger_1.logger.debug(`Error response: ${errorText}`);
278
- throw new Error(`Stream request failed: ${response.status} ${response.statusText}`);
277
+ logger_1.logger.error(`Error response: ${errorText}`);
278
+ throw new Error(`Stream request failed: ${response.status} ${response.statusText} - ${errorText}`);
279
279
  }
280
280
  if (!response.body) {
281
281
  throw new Error('No response body received');
@@ -74,7 +74,7 @@ class ClusterService {
74
74
  // This is a placeholder since the API doesn't have a specific endpoint
75
75
  // In a real implementation, this would call a specific endpoint
76
76
  const clusters = yield this.list();
77
- return clusters.find(cluster => cluster.id === clusterId) || null;
77
+ return clusters.find((cluster) => cluster.id === clusterId) || null;
78
78
  }
79
79
  catch (error) {
80
80
  console.error('Failed to describe cluster:', error);
@@ -179,7 +179,7 @@ class DefaultApiKeyManager {
179
179
  // Create readline interface for user input
180
180
  const rl = readline_1.default.createInterface({
181
181
  input: process.stdin,
182
- output: process.stdout
182
+ output: process.stdout,
183
183
  });
184
184
  // Prompt for selection
185
185
  const selection = yield new Promise((resolve) => {
@@ -202,7 +202,7 @@ class DefaultApiKeyManager {
202
202
  // Create a new API key with the selected name
203
203
  const newKey = yield apiKeyService.create({
204
204
  name: `CLI Default (copy of ${selectedKey.name})`,
205
- description: 'Created automatically by the Berget CLI for default use'
205
+ description: 'Created automatically by the Berget CLI for default use',
206
206
  });
207
207
  // Save the new key as default
208
208
  this.setDefaultApiKey(newKey.id.toString(), newKey.name, newKey.key.substring(0, 8), // Use first 8 chars as prefix
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.hasEnvKey = exports.updateEnvFile = void 0;
16
+ const fs_1 = __importDefault(require("fs"));
17
+ const path_1 = __importDefault(require("path"));
18
+ const promises_1 = require("fs/promises");
19
+ const chalk_1 = __importDefault(require("chalk"));
20
+ const dotenv_1 = __importDefault(require("dotenv"));
21
+ /**
22
+ * Safely updates .env file without overwriting existing keys
23
+ * Uses dotenv for proper parsing and formatting
24
+ */
25
+ function updateEnvFile(options) {
26
+ return __awaiter(this, void 0, void 0, function* () {
27
+ const { envPath = path_1.default.join(process.cwd(), '.env'), key, value, comment, force = false, } = options;
28
+ try {
29
+ let existingContent = '';
30
+ let parsed = {};
31
+ // Read existing .env file if it exists
32
+ if (fs_1.default.existsSync(envPath)) {
33
+ existingContent = fs_1.default.readFileSync(envPath, 'utf8');
34
+ parsed = dotenv_1.default.parse(existingContent);
35
+ }
36
+ // Check if key already exists and we're not forcing
37
+ if (parsed[key] && !force) {
38
+ console.log(chalk_1.default.yellow(`⚠ ${key} already exists in .env - leaving unchanged`));
39
+ return false;
40
+ }
41
+ // Update the parsed object
42
+ parsed[key] = value;
43
+ // Generate new .env content
44
+ let newContent = '';
45
+ // Add comment at the top if this is a new file
46
+ if (!existingContent && comment) {
47
+ newContent += `# ${comment}\n`;
48
+ }
49
+ // Convert parsed object back to .env format
50
+ for (const [envKey, envValue] of Object.entries(parsed)) {
51
+ newContent += `${envKey}=${envValue}\n`;
52
+ }
53
+ // Write the updated content
54
+ yield (0, promises_1.writeFile)(envPath, newContent.trim() + '\n');
55
+ if (existingContent) {
56
+ console.log(chalk_1.default.green(`✓ Updated .env with ${key}`));
57
+ }
58
+ else {
59
+ console.log(chalk_1.default.green(`✓ Created .env with ${key}`));
60
+ }
61
+ return true;
62
+ }
63
+ catch (error) {
64
+ console.error(chalk_1.default.red(`Failed to update .env file:`));
65
+ throw error;
66
+ }
67
+ });
68
+ }
69
+ exports.updateEnvFile = updateEnvFile;
70
+ /**
71
+ * Checks if a .env file exists and contains a specific key
72
+ */
73
+ function hasEnvKey(envPath = path_1.default.join(process.cwd(), '.env'), key) {
74
+ if (!fs_1.default.existsSync(envPath)) {
75
+ return false;
76
+ }
77
+ try {
78
+ const content = fs_1.default.readFileSync(envPath, 'utf8');
79
+ const parsed = dotenv_1.default.parse(content);
80
+ return key in parsed;
81
+ }
82
+ catch (_a) {
83
+ return false;
84
+ }
85
+ }
86
+ exports.hasEnvKey = hasEnvKey;
@@ -35,9 +35,16 @@ function handleError(message, error) {
35
35
  console.error(chalk_1.default.dim(`Details: ${error.message}`));
36
36
  }
37
37
  // Check for authentication errors
38
- if ((typeof error === 'string' && (error.includes('Unauthorized') || error.includes('Authentication failed'))) ||
39
- (error && error.message && (error.message.includes('Unauthorized') || error.message.includes('Authentication failed'))) ||
40
- (error && error.code && (error.code === 401 || error.code === 'AUTH_FAILED'))) {
38
+ if ((typeof error === 'string' &&
39
+ (error.includes('Unauthorized') ||
40
+ error.includes('Authentication failed'))) ||
41
+ (error &&
42
+ error.message &&
43
+ (error.message.includes('Unauthorized') ||
44
+ error.message.includes('Authentication failed'))) ||
45
+ (error &&
46
+ error.code &&
47
+ (error.code === 401 || error.code === 'AUTH_FAILED'))) {
41
48
  console.error(chalk_1.default.yellow('\nYou need to be logged in to use this command.'));
42
49
  console.error(chalk_1.default.yellow('Run `berget auth login` to authenticate.'));
43
50
  }
@@ -23,8 +23,8 @@ marked_1.marked.setOptions({
23
23
  // Adjust the width to fit the terminal
24
24
  width: process.stdout.columns || 80,
25
25
  // Customize code block rendering
26
- codespan: chalk_1.default.cyan
27
- })
26
+ codespan: chalk_1.default.cyan,
27
+ }),
28
28
  });
29
29
  /**
30
30
  * Render markdown text to terminal-friendly formatted text
@@ -66,8 +66,8 @@ function containsMarkdown(text) {
66
66
  /^\s*>\s+/m, // Blockquotes
67
67
  /\|.*\|.*\|/, // Tables
68
68
  /^---+$/m, // Horizontal rules
69
- /^===+$/m // Alternative headers
69
+ /^===+$/m, // Alternative headers
70
70
  ];
71
- return markdownPatterns.some(pattern => pattern.test(text));
71
+ return markdownPatterns.some((pattern) => pattern.test(text));
72
72
  }
73
73
  exports.containsMarkdown = containsMarkdown;
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.fixOpenCodeConfig = exports.validateOpenCodeConfig = void 0;
7
+ const ajv_1 = __importDefault(require("ajv"));
8
+ const ajv_formats_1 = __importDefault(require("ajv-formats"));
9
+ const fs_1 = require("fs");
10
+ const path_1 = require("path");
11
+ const path_2 = require("path");
12
+ // Load the official OpenCode JSON Schema
13
+ const __dirname = (0, path_2.dirname)(__filename);
14
+ const schemaPath = (0, path_1.join)(__dirname, '..', 'schemas', 'opencode-schema.json');
15
+ let ajv;
16
+ let openCodeSchema;
17
+ let validateFunction;
18
+ try {
19
+ const schemaContent = (0, fs_1.readFileSync)(schemaPath, 'utf-8');
20
+ openCodeSchema = JSON.parse(schemaContent);
21
+ // Initialize AJV with formats and options
22
+ ajv = new ajv_1.default({
23
+ allErrors: true,
24
+ verbose: true,
25
+ strict: false,
26
+ allowUnionTypes: true,
27
+ removeAdditional: false,
28
+ });
29
+ // Add JSON Schema formats
30
+ (0, ajv_formats_1.default)(ajv);
31
+ // Compile the schema
32
+ validateFunction = ajv.compile(openCodeSchema);
33
+ }
34
+ catch (error) {
35
+ console.error('Failed to load OpenCode schema:', error);
36
+ throw new Error('Could not initialize OpenCode validator');
37
+ }
38
+ /**
39
+ * Validate OpenCode configuration against the official JSON Schema
40
+ */
41
+ function validateOpenCodeConfig(config) {
42
+ var _a;
43
+ try {
44
+ if (!validateFunction) {
45
+ return { valid: false, errors: ['Schema validator not initialized'] };
46
+ }
47
+ const isValid = validateFunction(config);
48
+ if (isValid) {
49
+ return { valid: true };
50
+ }
51
+ else {
52
+ const errors = ((_a = validateFunction.errors) === null || _a === void 0 ? void 0 : _a.map((err) => {
53
+ const path = err.instancePath || err.schemaPath || 'root';
54
+ const message = err.message || 'Unknown error';
55
+ return `${path}: ${message}`;
56
+ })) || ['Unknown validation error'];
57
+ return { valid: false, errors };
58
+ }
59
+ }
60
+ catch (error) {
61
+ console.error('Validation error:', error);
62
+ return { valid: false, errors: ['Validation process failed'] };
63
+ }
64
+ }
65
+ exports.validateOpenCodeConfig = validateOpenCodeConfig;
66
+ /**
67
+ * Fix common OpenCode configuration issues
68
+ */
69
+ function fixOpenCodeConfig(config) {
70
+ const fixed = Object.assign({}, config);
71
+ // Fix tools.compact - should be boolean, not object
72
+ if (fixed.tools && typeof fixed.tools.compact === 'object') {
73
+ console.warn('⚠️ Converting tools.compact from object to boolean');
74
+ // If it has properties, assume it should be enabled
75
+ fixed.tools.compact = true;
76
+ }
77
+ // Remove invalid properties
78
+ const invalidProps = ['maxTokens', 'contextWindow'];
79
+ invalidProps.forEach((prop) => {
80
+ if (fixed[prop] !== undefined) {
81
+ console.warn(`⚠️ Removing invalid property: ${prop}`);
82
+ delete fixed[prop];
83
+ }
84
+ });
85
+ // Fix provider models with invalid properties
86
+ if (fixed.provider) {
87
+ Object.values(fixed.provider).forEach((provider) => {
88
+ if (provider === null || provider === void 0 ? void 0 : provider.models) {
89
+ Object.values(provider.models).forEach((model) => {
90
+ if (model && typeof model === 'object') {
91
+ // Move maxTokens/contextWindow to proper structure if needed
92
+ if (model.maxTokens || model.contextWindow) {
93
+ if (!model.limit)
94
+ model.limit = {};
95
+ // Use the larger of maxTokens/contextWindow for context
96
+ const contextValues = [
97
+ model.maxTokens,
98
+ model.contextWindow,
99
+ ].filter(Boolean);
100
+ if (contextValues.length > 0) {
101
+ const newContext = Math.max(...contextValues);
102
+ if (!model.limit.context || newContext > model.limit.context) {
103
+ model.limit.context = newContext;
104
+ }
105
+ }
106
+ // Set a reasonable default for output if not present
107
+ // (typically 1/4 to 1/8 of context window)
108
+ if (!model.limit.output && model.limit.context) {
109
+ model.limit.output = Math.floor(model.limit.context / 4);
110
+ }
111
+ delete model.maxTokens;
112
+ delete model.contextWindow;
113
+ console.warn('⚠️ Moved maxTokens/contextWindow to limit.context/output');
114
+ }
115
+ }
116
+ });
117
+ }
118
+ });
119
+ }
120
+ return fixed;
121
+ }
122
+ exports.fixOpenCodeConfig = fixOpenCodeConfig;
@@ -135,7 +135,7 @@ class TokenManager {
135
135
  this.tokenData = {
136
136
  access_token: accessToken,
137
137
  refresh_token: refreshToken,
138
- expires_at: Date.now() + (expiresIn * 1000)
138
+ expires_at: Date.now() + expiresIn * 1000,
139
139
  };
140
140
  this.saveToken();
141
141
  }
@@ -148,7 +148,7 @@ class TokenManager {
148
148
  if (!this.tokenData)
149
149
  return;
150
150
  this.tokenData.access_token = accessToken;
151
- this.tokenData.expires_at = Date.now() + (expiresIn * 1000);
151
+ this.tokenData.expires_at = Date.now() + expiresIn * 1000;
152
152
  this.saveToken();
153
153
  }
154
154
  /**
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const vitest_1 = require("vitest");
13
+ const commander_1 = require("commander");
14
+ const chat_1 = require("../../src/commands/chat");
15
+ const chat_service_1 = require("../../src/services/chat-service");
16
+ const default_api_key_1 = require("../../src/utils/default-api-key");
17
+ // Mock dependencies
18
+ vitest_1.vi.mock('../../src/services/chat-service');
19
+ vitest_1.vi.mock('../../src/utils/default-api-key');
20
+ vitest_1.vi.mock('readline', () => ({
21
+ createInterface: vitest_1.vi.fn(() => ({
22
+ question: vitest_1.vi.fn(),
23
+ close: vitest_1.vi.fn(),
24
+ })),
25
+ }));
26
+ (0, vitest_1.describe)('Chat Commands', () => {
27
+ let program;
28
+ let mockChatService;
29
+ let mockDefaultApiKeyManager;
30
+ (0, vitest_1.beforeEach)(() => {
31
+ program = new commander_1.Command();
32
+ // Mock ChatService
33
+ mockChatService = {
34
+ createCompletion: vitest_1.vi.fn(),
35
+ listModels: vitest_1.vi.fn(),
36
+ };
37
+ vitest_1.vi.mocked(chat_service_1.ChatService.getInstance).mockReturnValue(mockChatService);
38
+ // Mock DefaultApiKeyManager
39
+ mockDefaultApiKeyManager = {
40
+ getDefaultApiKeyData: vitest_1.vi.fn(),
41
+ promptForDefaultApiKey: vitest_1.vi.fn(),
42
+ };
43
+ vitest_1.vi.mocked(default_api_key_1.DefaultApiKeyManager.getInstance).mockReturnValue(mockDefaultApiKeyManager);
44
+ (0, chat_1.registerChatCommands)(program);
45
+ });
46
+ (0, vitest_1.afterEach)(() => {
47
+ vitest_1.vi.clearAllMocks();
48
+ });
49
+ (0, vitest_1.describe)('chat run command', () => {
50
+ (0, vitest_1.it)('should use openai/gpt-oss as default model', () => {
51
+ const chatCommand = program.commands.find((cmd) => cmd.name() === 'chat');
52
+ const runCommand = chatCommand === null || chatCommand === void 0 ? void 0 : chatCommand.commands.find((cmd) => cmd.name() === 'run');
53
+ (0, vitest_1.expect)(runCommand).toBeDefined();
54
+ // Check the help text which contains the default model
55
+ const helpText = runCommand === null || runCommand === void 0 ? void 0 : runCommand.helpInformation();
56
+ (0, vitest_1.expect)(helpText).toContain('openai/gpt-oss');
57
+ });
58
+ (0, vitest_1.it)('should have streaming enabled by default', () => {
59
+ const chatCommand = program.commands.find((cmd) => cmd.name() === 'chat');
60
+ const runCommand = chatCommand === null || chatCommand === void 0 ? void 0 : chatCommand.commands.find((cmd) => cmd.name() === 'run');
61
+ (0, vitest_1.expect)(runCommand).toBeDefined();
62
+ // Check that the option is --no-stream (meaning streaming is default)
63
+ const streamOption = runCommand === null || runCommand === void 0 ? void 0 : runCommand.options.find((opt) => opt.long === '--no-stream');
64
+ (0, vitest_1.expect)(streamOption).toBeDefined();
65
+ (0, vitest_1.expect)(streamOption === null || streamOption === void 0 ? void 0 : streamOption.description).toContain('Disable streaming');
66
+ });
67
+ (0, vitest_1.it)('should create completion with correct default options', () => __awaiter(void 0, void 0, void 0, function* () {
68
+ // Mock API key
69
+ process.env.BERGET_API_KEY = 'test-key';
70
+ // Mock successful completion
71
+ mockChatService.createCompletion.mockResolvedValue({
72
+ choices: [
73
+ {
74
+ message: { content: 'Test response' },
75
+ },
76
+ ],
77
+ });
78
+ // This would normally test the actual command execution
79
+ // but since it involves readline interaction, we just verify
80
+ // that the service would be called with correct defaults
81
+ (0, vitest_1.expect)(mockChatService.createCompletion).not.toHaveBeenCalled();
82
+ // Clean up
83
+ delete process.env.BERGET_API_KEY;
84
+ }));
85
+ });
86
+ (0, vitest_1.describe)('chat list command', () => {
87
+ (0, vitest_1.it)('should list available models', () => __awaiter(void 0, void 0, void 0, function* () {
88
+ const mockModels = {
89
+ data: [
90
+ {
91
+ id: 'gpt-oss',
92
+ owned_by: 'openai',
93
+ active: true,
94
+ capabilities: {
95
+ vision: false,
96
+ function_calling: true,
97
+ json_mode: true,
98
+ },
99
+ },
100
+ ],
101
+ };
102
+ mockChatService.listModels.mockResolvedValue(mockModels);
103
+ const chatCommand = program.commands.find((cmd) => cmd.name() === 'chat');
104
+ const listCommand = chatCommand === null || chatCommand === void 0 ? void 0 : chatCommand.commands.find((cmd) => cmd.name() === 'list');
105
+ (0, vitest_1.expect)(listCommand).toBeDefined();
106
+ (0, vitest_1.expect)(listCommand === null || listCommand === void 0 ? void 0 : listCommand.description()).toBe('List available chat models');
107
+ }));
108
+ });
109
+ });