@stan-chen/simple-cli 0.2.1

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 (87) hide show
  1. package/README.md +287 -0
  2. package/dist/cli.d.ts +6 -0
  3. package/dist/cli.js +259 -0
  4. package/dist/commands/add.d.ts +9 -0
  5. package/dist/commands/add.js +50 -0
  6. package/dist/commands/git/commit.d.ts +12 -0
  7. package/dist/commands/git/commit.js +97 -0
  8. package/dist/commands/git/status.d.ts +6 -0
  9. package/dist/commands/git/status.js +42 -0
  10. package/dist/commands/index.d.ts +16 -0
  11. package/dist/commands/index.js +376 -0
  12. package/dist/commands/mcp/status.d.ts +6 -0
  13. package/dist/commands/mcp/status.js +31 -0
  14. package/dist/commands/swarm.d.ts +36 -0
  15. package/dist/commands/swarm.js +236 -0
  16. package/dist/commands.d.ts +32 -0
  17. package/dist/commands.js +427 -0
  18. package/dist/context.d.ts +116 -0
  19. package/dist/context.js +327 -0
  20. package/dist/index.d.ts +6 -0
  21. package/dist/index.js +109 -0
  22. package/dist/lib/agent.d.ts +98 -0
  23. package/dist/lib/agent.js +281 -0
  24. package/dist/lib/editor.d.ts +74 -0
  25. package/dist/lib/editor.js +441 -0
  26. package/dist/lib/git.d.ts +164 -0
  27. package/dist/lib/git.js +351 -0
  28. package/dist/lib/ui.d.ts +159 -0
  29. package/dist/lib/ui.js +252 -0
  30. package/dist/mcp/client.d.ts +22 -0
  31. package/dist/mcp/client.js +81 -0
  32. package/dist/mcp/manager.d.ts +186 -0
  33. package/dist/mcp/manager.js +442 -0
  34. package/dist/prompts/provider.d.ts +22 -0
  35. package/dist/prompts/provider.js +78 -0
  36. package/dist/providers/index.d.ts +15 -0
  37. package/dist/providers/index.js +82 -0
  38. package/dist/providers/multi.d.ts +11 -0
  39. package/dist/providers/multi.js +28 -0
  40. package/dist/registry.d.ts +24 -0
  41. package/dist/registry.js +379 -0
  42. package/dist/repoMap.d.ts +5 -0
  43. package/dist/repoMap.js +79 -0
  44. package/dist/router.d.ts +41 -0
  45. package/dist/router.js +108 -0
  46. package/dist/skills.d.ts +25 -0
  47. package/dist/skills.js +288 -0
  48. package/dist/swarm/coordinator.d.ts +86 -0
  49. package/dist/swarm/coordinator.js +257 -0
  50. package/dist/swarm/index.d.ts +28 -0
  51. package/dist/swarm/index.js +29 -0
  52. package/dist/swarm/task.d.ts +104 -0
  53. package/dist/swarm/task.js +221 -0
  54. package/dist/swarm/types.d.ts +132 -0
  55. package/dist/swarm/types.js +37 -0
  56. package/dist/swarm/worker.d.ts +107 -0
  57. package/dist/swarm/worker.js +299 -0
  58. package/dist/tools/analyzeFile.d.ts +16 -0
  59. package/dist/tools/analyzeFile.js +43 -0
  60. package/dist/tools/git.d.ts +40 -0
  61. package/dist/tools/git.js +236 -0
  62. package/dist/tools/glob.d.ts +34 -0
  63. package/dist/tools/glob.js +165 -0
  64. package/dist/tools/grep.d.ts +53 -0
  65. package/dist/tools/grep.js +296 -0
  66. package/dist/tools/linter.d.ts +35 -0
  67. package/dist/tools/linter.js +349 -0
  68. package/dist/tools/listDir.d.ts +29 -0
  69. package/dist/tools/listDir.js +50 -0
  70. package/dist/tools/memory.d.ts +34 -0
  71. package/dist/tools/memory.js +215 -0
  72. package/dist/tools/readFiles.d.ts +25 -0
  73. package/dist/tools/readFiles.js +31 -0
  74. package/dist/tools/reloadTools.d.ts +11 -0
  75. package/dist/tools/reloadTools.js +22 -0
  76. package/dist/tools/runCommand.d.ts +32 -0
  77. package/dist/tools/runCommand.js +79 -0
  78. package/dist/tools/scraper.d.ts +31 -0
  79. package/dist/tools/scraper.js +211 -0
  80. package/dist/tools/writeFiles.d.ts +63 -0
  81. package/dist/tools/writeFiles.js +87 -0
  82. package/dist/ui/server.d.ts +5 -0
  83. package/dist/ui/server.js +74 -0
  84. package/dist/watcher.d.ts +35 -0
  85. package/dist/watcher.js +164 -0
  86. package/docs/assets/logo.jpeg +0 -0
  87. package/package.json +78 -0
@@ -0,0 +1,442 @@
1
+ /**
2
+ * MCP Client Manager - Manages multiple MCP server connections
3
+ * Supports Composio, custom MCP servers, and dynamic tool discovery
4
+ * Based on GeminiCLI's mcp-client.ts patterns
5
+ */
6
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
7
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
8
+ import { z } from 'zod';
9
+ import { existsSync, readFileSync } from 'fs';
10
+ import { join } from 'path';
11
+ // MCP Server Configuration Schema
12
+ export const MCPServerConfigSchema = z.object({
13
+ name: z.string(),
14
+ command: z.string().optional(),
15
+ args: z.array(z.string()).optional(),
16
+ env: z.record(z.string()).optional(),
17
+ url: z.string().optional(),
18
+ type: z.enum(['stdio', 'sse', 'http']).optional(),
19
+ timeout: z.number().optional(),
20
+ trust: z.enum(['full', 'partial', 'none']).optional(),
21
+ enabled: z.boolean().optional().default(true),
22
+ });
23
+ // Server Status
24
+ export var MCPServerStatus;
25
+ (function (MCPServerStatus) {
26
+ MCPServerStatus["DISCONNECTED"] = "disconnected";
27
+ MCPServerStatus["CONNECTING"] = "connecting";
28
+ MCPServerStatus["CONNECTED"] = "connected";
29
+ MCPServerStatus["ERROR"] = "error";
30
+ })(MCPServerStatus || (MCPServerStatus = {}));
31
+ /**
32
+ * MCP Client Manager
33
+ * Manages connections to multiple MCP servers and aggregates their tools
34
+ */
35
+ export class MCPManager {
36
+ servers = new Map();
37
+ onStatusChange;
38
+ constructor(options) {
39
+ this.onStatusChange = options?.onStatusChange;
40
+ }
41
+ /**
42
+ * Load MCP configuration from all available sources
43
+ */
44
+ async loadConfig(configPath) {
45
+ const allConfigs = [];
46
+ const sources = [
47
+ { path: configPath, type: 'file' },
48
+ { path: join(process.cwd(), 'mcp.json'), type: 'file' },
49
+ { path: join(process.cwd(), '.mcp.json'), type: 'file' },
50
+ { path: join(process.cwd(), 'mcp'), type: 'dir' },
51
+ { path: join(process.env.HOME || '', '.config', 'simplecli', 'mcp.json'), type: 'file' },
52
+ { path: join(process.env.APPDATA || '', 'simplecli', 'mcp.json'), type: 'file' },
53
+ ].filter(s => s.path && existsSync(s.path));
54
+ for (const source of sources) {
55
+ try {
56
+ if (source.type === 'file') {
57
+ const content = readFileSync(source.path, 'utf-8');
58
+ allConfigs.push(...this.parseConfigContent(content));
59
+ }
60
+ else if (source.type === 'dir') {
61
+ // Scan directory for .json files
62
+ const { readdir } = await import('fs/promises');
63
+ const files = await readdir(source.path);
64
+ for (const file of files) {
65
+ if (file.endsWith('.json')) {
66
+ const content = readFileSync(join(source.path, file), 'utf-8');
67
+ allConfigs.push(...this.parseConfigContent(content));
68
+ }
69
+ }
70
+ }
71
+ }
72
+ catch (e) {
73
+ if (process.env.DEBUG)
74
+ console.error(`Failed to load MCP source ${source.path}:`, e);
75
+ }
76
+ }
77
+ // De-duplicate by name, preferring earlier (local) configs
78
+ const unique = new Map();
79
+ for (const config of allConfigs) {
80
+ if (!unique.has(config.name)) {
81
+ unique.set(config.name, config);
82
+ }
83
+ }
84
+ return Array.from(unique.values()).filter(s => s.enabled !== false);
85
+ }
86
+ /**
87
+ * Parse MCP config content which can be in multiple formats
88
+ */
89
+ parseConfigContent(content) {
90
+ try {
91
+ const config = JSON.parse(content);
92
+ // Handle { servers: [...] } format
93
+ if (Array.isArray(config.servers)) {
94
+ return config.servers;
95
+ }
96
+ // Handle { mcpServers: { name: config } } format (Claude-style)
97
+ if (config.mcpServers) {
98
+ return Object.entries(config.mcpServers).map(([name, cfg]) => ({
99
+ name,
100
+ ...cfg
101
+ }));
102
+ }
103
+ // Handle single server config
104
+ if (config.name && (config.command || config.url)) {
105
+ return [config];
106
+ }
107
+ }
108
+ catch (e) {
109
+ if (process.env.DEBUG)
110
+ console.error('Failed to parse MCP content:', e);
111
+ }
112
+ return [];
113
+ }
114
+ /**
115
+ * Connect to an MCP server
116
+ */
117
+ async connect(config) {
118
+ const serverName = config.name;
119
+ if (this.servers.has(serverName)) {
120
+ await this.disconnect(serverName);
121
+ }
122
+ const state = {
123
+ client: null,
124
+ transport: null,
125
+ status: MCPServerStatus.CONNECTING,
126
+ config,
127
+ tools: [],
128
+ resources: [],
129
+ prompts: [],
130
+ };
131
+ this.servers.set(serverName, state);
132
+ this.updateStatus(serverName, MCPServerStatus.CONNECTING);
133
+ try {
134
+ const client = new Client({ name: 'simplecli', version: '0.2.1' }, { capabilities: {} });
135
+ let transport;
136
+ if (config.command) {
137
+ // Stdio transport
138
+ transport = new StdioClientTransport({
139
+ command: config.command,
140
+ args: config.args || [],
141
+ env: { ...process.env, ...(config.env || {}) },
142
+ });
143
+ }
144
+ else if (config.url) {
145
+ // For URL-based transports, we'd need additional SDK imports
146
+ // For now, throw an error suggesting stdio
147
+ throw new Error('URL-based MCP transports require additional configuration. Use stdio transport with command.');
148
+ }
149
+ else {
150
+ throw new Error(`Invalid MCP server config for ${serverName}: missing command or url`);
151
+ }
152
+ await client.connect(transport);
153
+ state.client = client;
154
+ state.transport = transport;
155
+ state.status = MCPServerStatus.CONNECTED;
156
+ // Discover tools, resources, and prompts
157
+ await this.discover(serverName);
158
+ this.updateStatus(serverName, MCPServerStatus.CONNECTED);
159
+ console.log(`✓ Connected to MCP server: ${serverName}`);
160
+ }
161
+ catch (error) {
162
+ state.status = MCPServerStatus.ERROR;
163
+ state.error = error instanceof Error ? error.message : String(error);
164
+ this.updateStatus(serverName, MCPServerStatus.ERROR);
165
+ console.error(`✗ Failed to connect to MCP server ${serverName}:`, state.error);
166
+ }
167
+ }
168
+ /**
169
+ * Connect to all configured servers
170
+ */
171
+ async connectAll(configs) {
172
+ const serverConfigs = configs || await this.loadConfig();
173
+ await Promise.all(serverConfigs.map(config => this.connect(config).catch(e => {
174
+ console.error(`Failed to connect to ${config.name}:`, e);
175
+ })));
176
+ }
177
+ /**
178
+ * Disconnect from an MCP server
179
+ */
180
+ async disconnect(serverName) {
181
+ const state = this.servers.get(serverName);
182
+ if (!state)
183
+ return;
184
+ try {
185
+ if (state.transport) {
186
+ await state.transport.close();
187
+ }
188
+ if (state.client) {
189
+ await state.client.close();
190
+ }
191
+ }
192
+ catch (e) {
193
+ // Ignore close errors
194
+ }
195
+ this.servers.delete(serverName);
196
+ this.updateStatus(serverName, MCPServerStatus.DISCONNECTED);
197
+ }
198
+ /**
199
+ * Disconnect from all servers
200
+ */
201
+ async disconnectAll() {
202
+ await Promise.all(Array.from(this.servers.keys()).map(name => this.disconnect(name)));
203
+ }
204
+ /**
205
+ * Discover tools, resources, and prompts from a server
206
+ */
207
+ async discover(serverName) {
208
+ const state = this.servers.get(serverName);
209
+ if (!state?.client)
210
+ return;
211
+ const client = state.client;
212
+ // Discover tools
213
+ try {
214
+ const toolsResult = await client.listTools();
215
+ state.tools = toolsResult.tools.map(tool => ({
216
+ name: tool.name,
217
+ description: tool.description || '',
218
+ serverName,
219
+ inputSchema: tool.inputSchema || {},
220
+ execute: async (args) => {
221
+ const result = await client.callTool({ name: tool.name, arguments: args });
222
+ return result;
223
+ },
224
+ }));
225
+ }
226
+ catch {
227
+ // Server may not support tools
228
+ }
229
+ // Discover resources
230
+ try {
231
+ const resourcesResult = await client.listResources();
232
+ state.resources = resourcesResult.resources.map(resource => ({
233
+ uri: resource.uri,
234
+ name: resource.name,
235
+ description: resource.description,
236
+ mimeType: resource.mimeType,
237
+ serverName,
238
+ }));
239
+ }
240
+ catch {
241
+ // Server may not support resources
242
+ }
243
+ // Discover prompts
244
+ try {
245
+ const promptsResult = await client.listPrompts();
246
+ state.prompts = promptsResult.prompts.map(prompt => ({
247
+ name: prompt.name,
248
+ description: prompt.description,
249
+ serverName,
250
+ arguments: prompt.arguments,
251
+ invoke: async (params) => {
252
+ const stringParams = {};
253
+ for (const [k, v] of Object.entries(params)) {
254
+ stringParams[k] = String(v);
255
+ }
256
+ const result = await client.getPrompt({ name: prompt.name, arguments: stringParams });
257
+ return {
258
+ messages: result.messages.map(m => ({
259
+ role: m.role,
260
+ content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
261
+ })),
262
+ };
263
+ },
264
+ }));
265
+ }
266
+ catch {
267
+ // Server may not support prompts
268
+ }
269
+ }
270
+ /**
271
+ * Get all discovered tools from all connected servers
272
+ */
273
+ getAllTools() {
274
+ const tools = [];
275
+ for (const state of this.servers.values()) {
276
+ if (state.status === MCPServerStatus.CONNECTED) {
277
+ tools.push(...state.tools);
278
+ }
279
+ }
280
+ return tools;
281
+ }
282
+ /**
283
+ * Get all discovered resources from all connected servers
284
+ */
285
+ getAllResources() {
286
+ const resources = [];
287
+ for (const state of this.servers.values()) {
288
+ if (state.status === MCPServerStatus.CONNECTED) {
289
+ resources.push(...state.resources);
290
+ }
291
+ }
292
+ return resources;
293
+ }
294
+ /**
295
+ * Get all discovered prompts from all connected servers
296
+ */
297
+ getAllPrompts() {
298
+ const prompts = [];
299
+ for (const state of this.servers.values()) {
300
+ if (state.status === MCPServerStatus.CONNECTED) {
301
+ prompts.push(...state.prompts);
302
+ }
303
+ }
304
+ return prompts;
305
+ }
306
+ /**
307
+ * Get a specific tool by name
308
+ */
309
+ getTool(name) {
310
+ return this.getAllTools().find(t => t.name === name);
311
+ }
312
+ /**
313
+ * Execute an MCP tool
314
+ */
315
+ async executeTool(name, args) {
316
+ const tool = this.getTool(name);
317
+ if (!tool) {
318
+ throw new Error(`MCP tool not found: ${name}`);
319
+ }
320
+ return tool.execute(args);
321
+ }
322
+ /**
323
+ * Read an MCP resource
324
+ */
325
+ async readResource(uri) {
326
+ // Find which server has this resource
327
+ for (const state of this.servers.values()) {
328
+ if (state.status !== MCPServerStatus.CONNECTED || !state.client)
329
+ continue;
330
+ const resource = state.resources.find(r => r.uri === uri);
331
+ if (resource) {
332
+ const result = await state.client.readResource({ uri });
333
+ return result;
334
+ }
335
+ }
336
+ throw new Error(`Resource not found: ${uri}`);
337
+ }
338
+ /**
339
+ * Invoke an MCP prompt
340
+ */
341
+ async invokePrompt(name, params) {
342
+ const prompt = this.getAllPrompts().find(p => p.name === name);
343
+ if (!prompt) {
344
+ throw new Error(`MCP prompt not found: ${name}`);
345
+ }
346
+ return prompt.invoke(params);
347
+ }
348
+ /**
349
+ * Get server status
350
+ */
351
+ getServerStatus(serverName) {
352
+ return this.servers.get(serverName)?.status || MCPServerStatus.DISCONNECTED;
353
+ }
354
+ /**
355
+ * Get all server statuses
356
+ */
357
+ getAllServerStatuses() {
358
+ const statuses = new Map();
359
+ for (const [name, state] of this.servers) {
360
+ statuses.set(name, state.status);
361
+ }
362
+ return statuses;
363
+ }
364
+ updateStatus(serverName, status) {
365
+ this.onStatusChange?.(serverName, status);
366
+ }
367
+ }
368
+ /**
369
+ * Create Composio MCP configuration
370
+ * Composio provides pre-built integrations for 250+ tools
371
+ */
372
+ export function createComposioConfig(apiKey) {
373
+ return {
374
+ name: 'composio',
375
+ command: 'npx',
376
+ args: ['-y', 'composio-core', 'mcp'],
377
+ env: apiKey ? { COMPOSIO_API_KEY: apiKey } : {},
378
+ trust: 'full',
379
+ enabled: true,
380
+ };
381
+ }
382
+ /**
383
+ * Create filesystem MCP configuration
384
+ */
385
+ export function createFilesystemConfig(allowedPaths) {
386
+ return {
387
+ name: 'filesystem',
388
+ command: 'npx',
389
+ args: ['-y', '@modelcontextprotocol/server-filesystem', ...allowedPaths],
390
+ trust: 'full',
391
+ enabled: true,
392
+ };
393
+ }
394
+ /**
395
+ * Create GitHub MCP configuration
396
+ */
397
+ export function createGitHubConfig(token) {
398
+ return {
399
+ name: 'github',
400
+ command: 'npx',
401
+ args: ['-y', '@modelcontextprotocol/server-github'],
402
+ env: token ? { GITHUB_PERSONAL_ACCESS_TOKEN: token } : {},
403
+ trust: 'full',
404
+ enabled: true,
405
+ };
406
+ }
407
+ /**
408
+ * Create memory/context MCP configuration
409
+ */
410
+ export function createMemoryConfig() {
411
+ return {
412
+ name: 'memory',
413
+ command: 'npx',
414
+ args: ['-y', '@modelcontextprotocol/server-memory'],
415
+ trust: 'full',
416
+ enabled: true,
417
+ };
418
+ }
419
+ /**
420
+ * Create Brave Search MCP configuration
421
+ */
422
+ export function createBraveSearchConfig(apiKey) {
423
+ return {
424
+ name: 'brave-search',
425
+ command: 'npx',
426
+ args: ['-y', '@modelcontextprotocol/server-brave-search'],
427
+ env: apiKey ? { BRAVE_API_KEY: apiKey } : {},
428
+ trust: 'full',
429
+ enabled: true,
430
+ };
431
+ }
432
+ // Singleton instance
433
+ let mcpManager = null;
434
+ /**
435
+ * Get or create the global MCP manager instance
436
+ */
437
+ export function getMCPManager() {
438
+ if (!mcpManager) {
439
+ mcpManager = new MCPManager();
440
+ }
441
+ return mcpManager;
442
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Prompt Provider - Manages composition of system prompts
3
+ * Supports built-in defaults, project-specific overrides, and modular personas.
4
+ */
5
+ export interface PromptOptions {
6
+ cwd: string;
7
+ skillPrompt?: string;
8
+ }
9
+ export declare class PromptProvider {
10
+ /**
11
+ * Builds the full system prompt by composing multiple layers:
12
+ * 1. Built-in system instructions (src/prompts/defaults)
13
+ * 2. Global user overrides (~/.simple/prompts)
14
+ * 3. Project-specific prompts (.simple/prompts)
15
+ * 4. Current skill instructions
16
+ * 5. Local project rules (AGENT.md)
17
+ */
18
+ getSystemPrompt(options: PromptOptions): Promise<string>;
19
+ private loadFromDirectory;
20
+ private loadProjectRules;
21
+ }
22
+ export declare function getPromptProvider(): PromptProvider;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Prompt Provider - Manages composition of system prompts
3
+ * Supports built-in defaults, project-specific overrides, and modular personas.
4
+ */
5
+ import { readFileSync, existsSync, readdirSync } from 'fs';
6
+ import { join, dirname } from 'path';
7
+ import { fileURLToPath } from 'url';
8
+ import { homedir } from 'os';
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+ export class PromptProvider {
12
+ /**
13
+ * Builds the full system prompt by composing multiple layers:
14
+ * 1. Built-in system instructions (src/prompts/defaults)
15
+ * 2. Global user overrides (~/.simple/prompts)
16
+ * 3. Project-specific prompts (.simple/prompts)
17
+ * 4. Current skill instructions
18
+ * 5. Local project rules (AGENT.md)
19
+ */
20
+ async getSystemPrompt(options) {
21
+ const parts = [];
22
+ // 1. Built-in defaults (Hardcoded in the package)
23
+ const builtInDir = join(__dirname, 'defaults');
24
+ if (existsSync(builtInDir)) {
25
+ parts.push(...this.loadFromDirectory(builtInDir));
26
+ }
27
+ // 2. Global user rules (~/.simple/AGENT.md)
28
+ const globalRulesPath = join(homedir(), '.simple', 'AGENT.md');
29
+ if (existsSync(globalRulesPath)) {
30
+ parts.push('\n## Global Rules\n' + readFileSync(globalRulesPath, 'utf-8'));
31
+ }
32
+ // 3. Project rules (AGENT.md)
33
+ const rules = this.loadProjectRules(options.cwd);
34
+ if (rules) {
35
+ parts.push('\n## Project Rules\n' + rules);
36
+ }
37
+ // 4. Skill-specific prompt (Dynamic based on @skill)
38
+ if (options.skillPrompt) {
39
+ parts.push('\n## Skill Context: ' + options.skillPrompt);
40
+ }
41
+ return parts.join('\n\n').trim();
42
+ }
43
+ loadFromDirectory(dir) {
44
+ try {
45
+ return readdirSync(dir)
46
+ .filter(f => f.endsWith('.md') || f.endsWith('.mdc'))
47
+ .sort() // Ensure deterministic order
48
+ .map(f => readFileSync(join(dir, f), 'utf-8'));
49
+ }
50
+ catch {
51
+ return [];
52
+ }
53
+ }
54
+ loadProjectRules(cwd) {
55
+ const commonPaths = [
56
+ '.simple/AGENT.md',
57
+ '.agent/AGENT.md',
58
+ 'AGENT.md',
59
+ '.agent.md',
60
+ '.cursorrules',
61
+ '.aider/agent.md'
62
+ ];
63
+ for (const p of commonPaths) {
64
+ const fullPath = join(cwd, p);
65
+ if (existsSync(fullPath)) {
66
+ return readFileSync(fullPath, 'utf-8');
67
+ }
68
+ }
69
+ return null;
70
+ }
71
+ }
72
+ let provider = null;
73
+ export function getPromptProvider() {
74
+ if (!provider) {
75
+ provider = new PromptProvider();
76
+ }
77
+ return provider;
78
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Provider Bridge: Unified LLM interface via OpenAI SDK
3
+ * Supports OpenAI, DeepSeek, Groq, and other OpenAI-compatible endpoints.
4
+ */
5
+ export interface Message {
6
+ role: string;
7
+ content: string;
8
+ }
9
+ export interface Provider {
10
+ name: string;
11
+ model: string;
12
+ generateResponse: (systemPrompt: string, messages: Message[]) => Promise<string>;
13
+ }
14
+ export declare const createProviderForModel: (model: string) => Provider;
15
+ export declare const createProvider: () => Provider;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Provider Bridge: Unified LLM interface via OpenAI SDK
3
+ * Supports OpenAI, DeepSeek, Groq, and other OpenAI-compatible endpoints.
4
+ */
5
+ import OpenAI from 'openai';
6
+ const getProviderConfig = () => {
7
+ // 1. OpenAI (Default)
8
+ if (process.env.OPENAI_API_KEY) {
9
+ return {
10
+ apiKey: process.env.OPENAI_API_KEY,
11
+ model: process.env.OPENAI_MODEL || 'gpt-4o'
12
+ };
13
+ }
14
+ // 2. DeepSeek
15
+ if (process.env.DEEPSEEK_API_KEY) {
16
+ return {
17
+ apiKey: process.env.DEEPSEEK_API_KEY,
18
+ baseURL: 'https://api.deepseek.com/v1',
19
+ model: process.env.DEEPSEEK_MODEL || 'deepseek-chat'
20
+ };
21
+ }
22
+ // 3. Groq
23
+ if (process.env.GROQ_API_KEY) {
24
+ return {
25
+ apiKey: process.env.GROQ_API_KEY,
26
+ baseURL: 'https://api.groq.com/openai/v1',
27
+ model: process.env.GROQ_MODEL || 'llama3-70b-8192'
28
+ };
29
+ }
30
+ // 4. Mistral
31
+ if (process.env.MISTRAL_API_KEY) {
32
+ return {
33
+ apiKey: process.env.MISTRAL_API_KEY,
34
+ baseURL: 'https://api.mistral.ai/v1',
35
+ model: process.env.MISTRAL_MODEL || 'mistral-large-latest'
36
+ };
37
+ }
38
+ throw new Error('No supported API key found (OPENAI_API_KEY, DEEPSEEK_API_KEY, GROQ_API_KEY, MISTRAL_API_KEY)');
39
+ };
40
+ export const createProviderForModel = (model) => {
41
+ // Quick heuristic to determine provider for specific model overrides
42
+ // logic can be improved, but this assumes the environment variables set the *default* linkage
43
+ // If a specific model is requested (e.g. for MoE), we try to route it.
44
+ let config = getProviderConfig();
45
+ // Override config if model implies a different provider?
46
+ // For the sake of "Simple-CLI", we assume the default connected provider serves the requested model
47
+ // or we just use OpenAI SDK's flexibility.
48
+ if (model.includes('gpt'))
49
+ config = { ...config, apiKey: process.env.OPENAI_API_KEY, baseURL: undefined };
50
+ else if (model.includes('deepseek'))
51
+ config = { ...config, apiKey: process.env.DEEPSEEK_API_KEY, baseURL: 'https://api.deepseek.com/v1' };
52
+ if (!config.apiKey)
53
+ throw new Error(`Cannot route for model ${model} - missing API key`);
54
+ const client = new OpenAI({
55
+ apiKey: config.apiKey,
56
+ baseURL: config.baseURL
57
+ });
58
+ return {
59
+ name: 'openai-compatible',
60
+ model,
61
+ generateResponse: async (systemPrompt, messages) => {
62
+ try {
63
+ const response = await client.chat.completions.create({
64
+ model: model,
65
+ messages: [
66
+ { role: 'system', content: systemPrompt },
67
+ ...messages.map(m => ({ role: m.role, content: m.content }))
68
+ ]
69
+ });
70
+ return response.choices[0]?.message?.content || '';
71
+ }
72
+ catch (e) {
73
+ return `Error calling LLM: ${e instanceof Error ? e.message : e}`; // Fail gracefully
74
+ }
75
+ }
76
+ };
77
+ };
78
+ export const createProvider = () => {
79
+ const config = getProviderConfig();
80
+ console.log(`🤖 Using model: ${config.model}`);
81
+ return createProviderForModel(config.model);
82
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Multi-Provider: Manages multiple LLM models for MoE routing via LiteLLM
3
+ * Each tier can use a different model from any provider
4
+ */
5
+ import { type Provider, type Message } from './index.js';
6
+ import type { Tier, TierConfig } from '../router.js';
7
+ export interface MultiProvider {
8
+ getProvider: (tier: Tier) => Provider;
9
+ generateWithTier: (tier: Tier, systemPrompt: string, messages: Message[]) => Promise<string>;
10
+ }
11
+ export declare const createMultiProvider: (tierConfigs: Map<Tier, TierConfig>) => MultiProvider;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Multi-Provider: Manages multiple LLM models for MoE routing via LiteLLM
3
+ * Each tier can use a different model from any provider
4
+ */
5
+ import { createProviderForModel } from './index.js';
6
+ // Create multi-provider system using LiteLLM
7
+ export const createMultiProvider = (tierConfigs) => {
8
+ const providerCache = new Map();
9
+ const getProvider = (tier) => {
10
+ const cached = providerCache.get(tier);
11
+ if (cached)
12
+ return cached;
13
+ const config = tierConfigs.get(tier);
14
+ if (!config) {
15
+ throw new Error(`No configuration for tier ${tier}`);
16
+ }
17
+ const provider = createProviderForModel(config.model);
18
+ providerCache.set(tier, provider);
19
+ return provider;
20
+ };
21
+ return {
22
+ getProvider,
23
+ generateWithTier: async (tier, systemPrompt, messages) => {
24
+ const provider = getProvider(tier);
25
+ return provider.generateResponse(systemPrompt, messages);
26
+ }
27
+ };
28
+ };