matex-cli 1.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 (45) hide show
  1. package/README.md +261 -0
  2. package/USAGE_GUIDE.md +180 -0
  3. package/bin/matex +2 -0
  4. package/dist/api/client.d.ts +34 -0
  5. package/dist/api/client.d.ts.map +1 -0
  6. package/dist/api/client.js +99 -0
  7. package/dist/api/client.js.map +1 -0
  8. package/dist/commands/ask.d.ts +3 -0
  9. package/dist/commands/ask.d.ts.map +1 -0
  10. package/dist/commands/ask.js +63 -0
  11. package/dist/commands/ask.js.map +1 -0
  12. package/dist/commands/chat.d.ts +3 -0
  13. package/dist/commands/chat.d.ts.map +1 -0
  14. package/dist/commands/chat.js +95 -0
  15. package/dist/commands/chat.js.map +1 -0
  16. package/dist/commands/config.d.ts +3 -0
  17. package/dist/commands/config.d.ts.map +1 -0
  18. package/dist/commands/config.js +74 -0
  19. package/dist/commands/config.js.map +1 -0
  20. package/dist/commands/models.d.ts +3 -0
  21. package/dist/commands/models.d.ts.map +1 -0
  22. package/dist/commands/models.js +77 -0
  23. package/dist/commands/models.js.map +1 -0
  24. package/dist/index.d.ts +3 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +48 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/utils/config.d.ts +56 -0
  29. package/dist/utils/config.d.ts.map +1 -0
  30. package/dist/utils/config.js +135 -0
  31. package/dist/utils/config.js.map +1 -0
  32. package/dist/utils/spinner.d.ts +12 -0
  33. package/dist/utils/spinner.d.ts.map +1 -0
  34. package/dist/utils/spinner.js +58 -0
  35. package/dist/utils/spinner.js.map +1 -0
  36. package/package.json +44 -0
  37. package/src/api/client.ts +116 -0
  38. package/src/commands/ask.ts +62 -0
  39. package/src/commands/chat.ts +101 -0
  40. package/src/commands/config.ts +78 -0
  41. package/src/commands/models.ts +81 -0
  42. package/src/index.ts +50 -0
  43. package/src/utils/config.ts +120 -0
  44. package/src/utils/spinner.ts +57 -0
  45. package/tsconfig.json +27 -0
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "matex-cli",
3
+ "version": "1.0.0",
4
+ "description": "Official CLI tool for MATEX AI - Access powerful AI models from your terminal",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "matex": "bin/matex"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsc --watch",
12
+ "test": "jest",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "keywords": [
16
+ "ai",
17
+ "cli",
18
+ "matex",
19
+ "chatgpt",
20
+ "coding",
21
+ "assistant"
22
+ ],
23
+ "author": "Ajay Sharma <ajaypoudel47@gmail.com>",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "axios": "^1.6.0",
27
+ "chalk": "^4.1.2",
28
+ "commander": "^11.1.0",
29
+ "conf": "^11.0.2",
30
+ "inquirer": "^8.2.5",
31
+ "ora": "^5.4.1",
32
+ "dotenv": "^16.3.1"
33
+ },
34
+ "devDependencies": {
35
+ "@types/inquirer": "^9.0.7",
36
+ "@types/node": "^20.10.0",
37
+ "typescript": "^5.3.0",
38
+ "jest": "^29.7.0",
39
+ "@types/jest": "^29.5.0"
40
+ },
41
+ "engines": {
42
+ "node": ">=16.0.0"
43
+ }
44
+ }
@@ -0,0 +1,116 @@
1
+ import axios, { AxiosInstance } from 'axios';
2
+
3
+ export interface ChatMessage {
4
+ role: 'user' | 'assistant' | 'system';
5
+ content: string;
6
+ }
7
+
8
+ export interface ChatRequest {
9
+ messages: ChatMessage[];
10
+ model: string;
11
+ temperature?: number;
12
+ max_tokens?: number;
13
+ stream?: boolean;
14
+ }
15
+
16
+ export class MatexAPIClient {
17
+ private client: AxiosInstance;
18
+ private apiKey: string;
19
+ private baseURL: string;
20
+
21
+ constructor(apiKey: string, baseURL: string = 'https://matexai-backend-550499663766.us-central1.run.app') {
22
+ this.apiKey = apiKey;
23
+ this.baseURL = baseURL;
24
+
25
+ this.client = axios.create({
26
+ baseURL: this.baseURL,
27
+ headers: {
28
+ 'X-API-Key': this.apiKey,
29
+ 'Content-Type': 'application/json',
30
+ },
31
+ timeout: 60000, // 60 seconds
32
+ });
33
+ }
34
+
35
+ /**
36
+ * Send a chat request to the API
37
+ */
38
+ async chat(request: ChatRequest): Promise<string> {
39
+ try {
40
+ const response = await this.client.post('/api/v1/chat', {
41
+ ...request,
42
+ uid: 'cli-user', // Placeholder, backend uses API key for auth
43
+ });
44
+
45
+ // Handle streaming response
46
+ if (request.stream) {
47
+ return this.handleStreamingResponse(response.data);
48
+ }
49
+
50
+ return response.data;
51
+ } catch (error: any) {
52
+ if (error.response) {
53
+ throw new Error(`API Error: ${error.response.data.detail || error.response.statusText}`);
54
+ } else if (error.request) {
55
+ throw new Error('No response from server. Please check your internet connection.');
56
+ } else {
57
+ throw new Error(`Request failed: ${error.message}`);
58
+ }
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Handle Server-Sent Events (SSE) streaming response
64
+ */
65
+ private async handleStreamingResponse(data: any): Promise<string> {
66
+ let fullResponse = '';
67
+
68
+ // Parse SSE format: "data: {...}\n\n"
69
+ const lines = data.split('\n');
70
+
71
+ for (const line of lines) {
72
+ if (line.startsWith('data: ')) {
73
+ const jsonStr = line.substring(6);
74
+
75
+ if (jsonStr === '[DONE]') {
76
+ break;
77
+ }
78
+
79
+ try {
80
+ const parsed = JSON.parse(jsonStr);
81
+ if (parsed.content) {
82
+ fullResponse += parsed.content;
83
+ }
84
+ } catch (e) {
85
+ // Skip invalid JSON
86
+ }
87
+ }
88
+ }
89
+
90
+ return fullResponse;
91
+ }
92
+
93
+ /**
94
+ * List available API keys (requires Firebase token, not for CLI use)
95
+ */
96
+ async listAPIKeys(): Promise<any> {
97
+ const response = await this.client.get('/api/v1/api-keys/list');
98
+ return response.data;
99
+ }
100
+
101
+ /**
102
+ * Test API key validity
103
+ */
104
+ async testConnection(): Promise<boolean> {
105
+ try {
106
+ await this.chat({
107
+ messages: [{ role: 'user', content: 'test' }],
108
+ model: 'matexcodex',
109
+ max_tokens: 10,
110
+ });
111
+ return true;
112
+ } catch (error) {
113
+ return false;
114
+ }
115
+ }
116
+ }
@@ -0,0 +1,62 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { configManager } from '../utils/config';
4
+ import { MatexAPIClient } from '../api/client';
5
+ import { spinner } from '../utils/spinner';
6
+
7
+ export const askCommand = new Command('ask')
8
+ .description('Ask a single question to MATEX AI')
9
+ .argument('<question>', 'Your question')
10
+ .option('-m, --model <model>', 'AI model to use (matexcodex, matexai, elite, matexspirit)', configManager.getDefaultModel())
11
+ .option('-t, --temperature <number>', 'Temperature (0-1)', '0.7')
12
+ .option('--max-tokens <number>', 'Maximum tokens in response', '4000')
13
+ .action(async (question: string, options: any) => {
14
+ try {
15
+ // Check for API key
16
+ const apiKey = configManager.getAPIKey();
17
+ if (!apiKey) {
18
+ console.error(chalk.red('❌ No API key configured.'));
19
+ console.log(chalk.yellow('Run: matex config set-key <your-api-key>'));
20
+ process.exit(1);
21
+ }
22
+
23
+ // Create API client
24
+ const client = new MatexAPIClient(apiKey, configManager.getBaseURL());
25
+
26
+ // Show thinking indicator
27
+ spinner.start(`Thinking with ${options.model}...`);
28
+
29
+ // Send request
30
+ const response = await client.chat({
31
+ messages: [
32
+ { role: 'user', content: question }
33
+ ],
34
+ model: options.model,
35
+ temperature: parseFloat(options.temperature),
36
+ max_tokens: parseInt(options.maxTokens),
37
+ stream: false,
38
+ });
39
+
40
+ spinner.succeed('Response received!');
41
+
42
+ // Display response
43
+ console.log(chalk.cyan('\n💬 MATEX AI:\n'));
44
+ console.log(chalk.white(response));
45
+ console.log();
46
+
47
+ } catch (error: any) {
48
+ spinner.fail('Request failed');
49
+
50
+ if (error.message.includes('403')) {
51
+ console.error(chalk.red('\n❌ Invalid or revoked API key.'));
52
+ console.log(chalk.yellow('Please check your API key or generate a new one from the MATEX AI platform.'));
53
+ } else if (error.message.includes('429')) {
54
+ console.error(chalk.red('\n❌ Rate limit exceeded.'));
55
+ console.log(chalk.yellow('Please wait a moment before trying again.'));
56
+ } else {
57
+ console.error(chalk.red(`\n❌ Error: ${error.message}`));
58
+ }
59
+
60
+ process.exit(1);
61
+ }
62
+ });
@@ -0,0 +1,101 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import inquirer from 'inquirer';
4
+ import { configManager } from '../utils/config';
5
+ import { MatexAPIClient, ChatMessage } from '../api/client';
6
+ import { spinner } from '../utils/spinner';
7
+
8
+ export const chatCommand = new Command('chat')
9
+ .description('Start an interactive chat session with MATEX AI')
10
+ .option('-m, --model <model>', 'AI model to use (matexcodex, matexai, elite, matexspirit)', configManager.getDefaultModel())
11
+ .action(async (options: any) => {
12
+ try {
13
+ // Check for API key
14
+ const apiKey = configManager.getAPIKey();
15
+ if (!apiKey) {
16
+ console.error(chalk.red('❌ No API key configured.'));
17
+ console.log(chalk.yellow('Run: matex config set-key <your-api-key>'));
18
+ process.exit(1);
19
+ }
20
+
21
+ // Create API client
22
+ const client = new MatexAPIClient(apiKey, configManager.getBaseURL());
23
+
24
+ // Welcome message
25
+ console.log(chalk.bold.cyan('\n🤖 MATEX AI Interactive Chat'));
26
+ console.log(chalk.gray(`Model: ${options.model}`));
27
+ console.log(chalk.gray('Type "exit" or "quit" to end the session\n'));
28
+
29
+ // Conversation history
30
+ const messages: ChatMessage[] = [];
31
+
32
+ // Chat loop
33
+ while (true) {
34
+ // Get user input
35
+ const { userMessage } = await inquirer.prompt([
36
+ {
37
+ type: 'input',
38
+ name: 'userMessage',
39
+ message: chalk.green('You:'),
40
+ prefix: '',
41
+ },
42
+ ]);
43
+
44
+ // Check for exit
45
+ if (userMessage.toLowerCase() === 'exit' || userMessage.toLowerCase() === 'quit') {
46
+ console.log(chalk.cyan('\n👋 Goodbye!\n'));
47
+ break;
48
+ }
49
+
50
+ // Skip empty messages
51
+ if (!userMessage.trim()) {
52
+ continue;
53
+ }
54
+
55
+ // Add user message to history
56
+ messages.push({ role: 'user', content: userMessage });
57
+
58
+ // Show thinking indicator
59
+ spinner.start('Thinking...');
60
+
61
+ try {
62
+ // Send request
63
+ const response = await client.chat({
64
+ messages: messages,
65
+ model: options.model,
66
+ temperature: 0.7,
67
+ max_tokens: 4000,
68
+ stream: false,
69
+ });
70
+
71
+ spinner.stop();
72
+
73
+ // Add assistant response to history
74
+ messages.push({ role: 'assistant', content: response });
75
+
76
+ // Display response
77
+ console.log(chalk.cyan('AI:'), chalk.white(response));
78
+ console.log();
79
+
80
+ } catch (error: any) {
81
+ spinner.fail('Request failed');
82
+
83
+ if (error.message.includes('403')) {
84
+ console.error(chalk.red('❌ Invalid or revoked API key.'));
85
+ break;
86
+ } else if (error.message.includes('429')) {
87
+ console.error(chalk.red('❌ Rate limit exceeded. Please wait a moment.'));
88
+ } else {
89
+ console.error(chalk.red(`❌ Error: ${error.message}`));
90
+ }
91
+
92
+ // Remove failed message from history
93
+ messages.pop();
94
+ }
95
+ }
96
+
97
+ } catch (error: any) {
98
+ console.error(chalk.red(`\n❌ Chat session error: ${error.message}`));
99
+ process.exit(1);
100
+ }
101
+ });
@@ -0,0 +1,78 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { configManager } from '../utils/config';
4
+ import { spinner } from '../utils/spinner';
5
+
6
+ export const configCommand = new Command('config')
7
+ .description('Manage MATEX CLI configuration');
8
+
9
+ // Set API key
10
+ configCommand
11
+ .command('set-key <apiKey>')
12
+ .description('Set your MATEX AI API key')
13
+ .action((apiKey: string) => {
14
+ try {
15
+ // Validate API key format
16
+ if (!apiKey.startsWith('sk-matex-')) {
17
+ console.error(chalk.red('❌ Invalid API key format. API keys should start with "sk-matex-"'));
18
+ process.exit(1);
19
+ }
20
+
21
+ configManager.setAPIKey(apiKey);
22
+ console.log(chalk.green('✅ API key saved successfully!'));
23
+ console.log(chalk.gray('Your API key is stored securely in ~/.matex/config.json'));
24
+ console.log(chalk.cyan('\n💡 Try it out: matex ask "What is 2+2?"'));
25
+ } catch (error: any) {
26
+ console.error(chalk.red(`❌ Failed to save API key: ${error.message}`));
27
+ process.exit(1);
28
+ }
29
+ });
30
+
31
+ // Show configuration
32
+ configCommand
33
+ .command('show')
34
+ .description('Show current configuration')
35
+ .action(() => {
36
+ const config = configManager.getAll();
37
+
38
+ console.log(chalk.bold.cyan('\n📋 MATEX CLI Configuration\n'));
39
+
40
+ if (config.apiKey) {
41
+ const maskedKey = config.apiKey.substring(0, 12) + '...' + config.apiKey.substring(config.apiKey.length - 4);
42
+ console.log(chalk.white('API Key: ') + chalk.gray(maskedKey));
43
+ } else {
44
+ console.log(chalk.white('API Key: ') + chalk.red('Not set'));
45
+ }
46
+
47
+ console.log(chalk.white('Default Model: ') + chalk.cyan(config.defaultModel || 'matexcodex'));
48
+ console.log(chalk.white('Base URL: ') + chalk.gray(config.baseURL || 'https://matexai-backend-550499663766.us-central1.run.app'));
49
+
50
+ if (!config.apiKey) {
51
+ console.log(chalk.yellow('\n⚠️ No API key configured. Use "matex config set-key <your-api-key>" to get started.'));
52
+ }
53
+ });
54
+
55
+ // Clear configuration
56
+ configCommand
57
+ .command('clear')
58
+ .description('Clear all configuration')
59
+ .action(() => {
60
+ configManager.clear();
61
+ console.log(chalk.green('✅ Configuration cleared successfully!'));
62
+ });
63
+
64
+ // Set default model
65
+ configCommand
66
+ .command('set-model <model>')
67
+ .description('Set default AI model')
68
+ .action((model: string) => {
69
+ const validModels = ['matexcodex', 'matexai', 'elite', 'matexspirit'];
70
+
71
+ if (!validModels.includes(model.toLowerCase())) {
72
+ console.error(chalk.red(`❌ Invalid model. Choose from: ${validModels.join(', ')}`));
73
+ process.exit(1);
74
+ }
75
+
76
+ configManager.setDefaultModel(model.toLowerCase());
77
+ console.log(chalk.green(`✅ Default model set to: ${model}`));
78
+ });
@@ -0,0 +1,81 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { configManager } from '../utils/config';
4
+
5
+ const AVAILABLE_MODELS = [
6
+ {
7
+ name: 'matexcodex',
8
+ description: 'Best for coding, technical tasks, and development',
9
+ tier: 'Free (5 min/day), Starter (4M tokens/month), Pro Plus (unlimited)',
10
+ },
11
+ {
12
+ name: 'matexai',
13
+ description: 'General purpose AI for conversations and knowledge',
14
+ tier: 'Pro, Pro Plus',
15
+ },
16
+ {
17
+ name: 'elite',
18
+ description: 'Premium model with advanced capabilities',
19
+ tier: 'Elite Plan',
20
+ },
21
+ {
22
+ name: 'matexspirit',
23
+ description: 'Specialized model for creative and empathetic responses',
24
+ tier: 'Pro, Pro Plus',
25
+ },
26
+ ];
27
+
28
+ export const modelsCommand = new Command('models')
29
+ .description('Manage AI models');
30
+
31
+ // List available models
32
+ modelsCommand
33
+ .command('list')
34
+ .description('List all available AI models')
35
+ .action(() => {
36
+ console.log(chalk.bold.cyan('\n🤖 Available MATEX AI Models\n'));
37
+
38
+ AVAILABLE_MODELS.forEach((model) => {
39
+ console.log(chalk.bold.white(`${model.name}`));
40
+ console.log(chalk.gray(` ${model.description}`));
41
+ console.log(chalk.yellow(` Access: ${model.tier}`));
42
+ console.log();
43
+ });
44
+
45
+ const currentModel = configManager.getDefaultModel();
46
+ console.log(chalk.cyan(`Current default: ${currentModel}`));
47
+ });
48
+
49
+ // Set default model
50
+ modelsCommand
51
+ .command('set <model>')
52
+ .description('Set default AI model')
53
+ .action((model: string) => {
54
+ const validModels = AVAILABLE_MODELS.map(m => m.name);
55
+
56
+ if (!validModels.includes(model.toLowerCase())) {
57
+ console.error(chalk.red(`❌ Invalid model. Choose from: ${validModels.join(', ')}`));
58
+ process.exit(1);
59
+ }
60
+
61
+ configManager.setDefaultModel(model.toLowerCase());
62
+ console.log(chalk.green(`✅ Default model set to: ${model}`));
63
+ });
64
+
65
+ // Show current model
66
+ modelsCommand
67
+ .command('current')
68
+ .description('Show current default model')
69
+ .action(() => {
70
+ const currentModel = configManager.getDefaultModel();
71
+ const modelInfo = AVAILABLE_MODELS.find(m => m.name === currentModel);
72
+
73
+ console.log(chalk.bold.cyan('\n📌 Current Default Model\n'));
74
+ console.log(chalk.bold.white(currentModel));
75
+
76
+ if (modelInfo) {
77
+ console.log(chalk.gray(modelInfo.description));
78
+ console.log(chalk.yellow(`Access: ${modelInfo.tier}`));
79
+ }
80
+ console.log();
81
+ });
package/src/index.ts ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import { configCommand } from './commands/config';
6
+ import { askCommand } from './commands/ask';
7
+ import { chatCommand } from './commands/chat';
8
+ import { modelsCommand } from './commands/models';
9
+
10
+ const program = new Command();
11
+
12
+ program
13
+ .name('matex')
14
+ .description('Official CLI tool for MATEX AI - Access powerful AI models from your terminal')
15
+ .version('1.0.0');
16
+
17
+ // ASCII Art Banner
18
+ const banner = `
19
+ ${chalk.cyan('╔═══════════════════════════════════════╗')}
20
+ ${chalk.cyan('║')} ${chalk.bold.white('MATEX AI')} ${chalk.gray('- Terminal Edition')} ${chalk.cyan('║')}
21
+ ${chalk.cyan('╚═══════════════════════════════════════╝')}
22
+ `;
23
+
24
+ // Show banner on help
25
+ program.on('--help', () => {
26
+ console.log(banner);
27
+ console.log(chalk.gray('\nExamples:'));
28
+ console.log(chalk.white(' $ matex config set-key sk-matex-xxxxx'));
29
+ console.log(chalk.white(' $ matex ask "What is 2+2?"'));
30
+ console.log(chalk.white(' $ matex chat'));
31
+ console.log(chalk.white(' $ matex models list'));
32
+ console.log();
33
+ console.log(chalk.gray('Get your API key from: ') + chalk.cyan('https://matexai.space/platform'));
34
+ console.log();
35
+ });
36
+
37
+ // Add commands
38
+ program.addCommand(configCommand);
39
+ program.addCommand(askCommand);
40
+ program.addCommand(chatCommand);
41
+ program.addCommand(modelsCommand);
42
+
43
+ // Parse arguments
44
+ program.parse(process.argv);
45
+
46
+ // Show help if no command provided
47
+ if (!process.argv.slice(2).length) {
48
+ console.log(banner);
49
+ program.outputHelp();
50
+ }
@@ -0,0 +1,120 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+
5
+ export interface Config {
6
+ apiKey?: string;
7
+ defaultModel?: string;
8
+ baseURL?: string;
9
+ }
10
+
11
+ export class ConfigManager {
12
+ private configDir: string;
13
+ private configPath: string;
14
+
15
+ constructor() {
16
+ this.configDir = path.join(os.homedir(), '.matex');
17
+ this.configPath = path.join(this.configDir, 'config.json');
18
+
19
+ // Ensure config directory exists
20
+ if (!fs.existsSync(this.configDir)) {
21
+ fs.mkdirSync(this.configDir, { recursive: true });
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Load config from file
27
+ */
28
+ private loadConfig(): Config {
29
+ if (!fs.existsSync(this.configPath)) {
30
+ return {};
31
+ }
32
+
33
+ try {
34
+ const data = fs.readFileSync(this.configPath, 'utf-8');
35
+ return JSON.parse(data);
36
+ } catch (error) {
37
+ return {};
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Save config to file
43
+ */
44
+ private saveConfig(config: Config): void {
45
+ fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), 'utf-8');
46
+ }
47
+
48
+ /**
49
+ * Set API key
50
+ */
51
+ setAPIKey(apiKey: string): void {
52
+ const config = this.loadConfig();
53
+ config.apiKey = apiKey;
54
+ this.saveConfig(config);
55
+ }
56
+
57
+ /**
58
+ * Get API key
59
+ */
60
+ getAPIKey(): string | undefined {
61
+ return this.loadConfig().apiKey;
62
+ }
63
+
64
+ /**
65
+ * Set default model
66
+ */
67
+ setDefaultModel(model: string): void {
68
+ const config = this.loadConfig();
69
+ config.defaultModel = model;
70
+ this.saveConfig(config);
71
+ }
72
+
73
+ /**
74
+ * Get default model
75
+ */
76
+ getDefaultModel(): string {
77
+ return this.loadConfig().defaultModel || 'matexcodex';
78
+ }
79
+
80
+ /**
81
+ * Set base URL
82
+ */
83
+ setBaseURL(url: string): void {
84
+ const config = this.loadConfig();
85
+ config.baseURL = url;
86
+ this.saveConfig(config);
87
+ }
88
+
89
+ /**
90
+ * Get base URL
91
+ */
92
+ getBaseURL(): string {
93
+ return this.loadConfig().baseURL || 'https://matexai-backend-550499663766.us-central1.run.app';
94
+ }
95
+
96
+ /**
97
+ * Get all config
98
+ */
99
+ getAll(): Config {
100
+ return this.loadConfig();
101
+ }
102
+
103
+ /**
104
+ * Clear all config
105
+ */
106
+ clear(): void {
107
+ if (fs.existsSync(this.configPath)) {
108
+ fs.unlinkSync(this.configPath);
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Check if API key is set
114
+ */
115
+ hasAPIKey(): boolean {
116
+ return !!this.getAPIKey();
117
+ }
118
+ }
119
+
120
+ export const configManager = new ConfigManager();