camo-cli 2.0.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 (44) hide show
  1. package/README.md +184 -0
  2. package/dist/agent.js +977 -0
  3. package/dist/art.js +33 -0
  4. package/dist/components/App.js +71 -0
  5. package/dist/components/Chat.js +509 -0
  6. package/dist/components/HITLConfirmation.js +89 -0
  7. package/dist/components/ModelSelector.js +100 -0
  8. package/dist/components/SetupScreen.js +43 -0
  9. package/dist/config/constants.js +58 -0
  10. package/dist/config/prompts.js +98 -0
  11. package/dist/config/store.js +5 -0
  12. package/dist/core/AgentLoop.js +159 -0
  13. package/dist/hooks/useAutocomplete.js +52 -0
  14. package/dist/hooks/useKeyboard.js +73 -0
  15. package/dist/index.js +31 -0
  16. package/dist/mcp.js +95 -0
  17. package/dist/memory/MemoryManager.js +228 -0
  18. package/dist/providers/index.js +85 -0
  19. package/dist/providers/registry.js +121 -0
  20. package/dist/providers/types.js +5 -0
  21. package/dist/theme.js +45 -0
  22. package/dist/tools/FileTools.js +88 -0
  23. package/dist/tools/MemoryTools.js +53 -0
  24. package/dist/tools/SearchTools.js +45 -0
  25. package/dist/tools/ShellTools.js +40 -0
  26. package/dist/tools/TaskTools.js +52 -0
  27. package/dist/tools/ToolDefinitions.js +102 -0
  28. package/dist/tools/ToolRegistry.js +30 -0
  29. package/dist/types/Agent.js +6 -0
  30. package/dist/types/ink.js +1 -0
  31. package/dist/types/message.js +1 -0
  32. package/dist/types/ui.js +1 -0
  33. package/dist/utils/CriticAgent.js +88 -0
  34. package/dist/utils/DecisionLogger.js +156 -0
  35. package/dist/utils/MessageHistory.js +55 -0
  36. package/dist/utils/PermissionManager.js +253 -0
  37. package/dist/utils/SessionManager.js +180 -0
  38. package/dist/utils/TaskState.js +108 -0
  39. package/dist/utils/debug.js +35 -0
  40. package/dist/utils/execAsync.js +3 -0
  41. package/dist/utils/retry.js +50 -0
  42. package/dist/utils/tokenCounter.js +24 -0
  43. package/dist/utils/uiFormatter.js +106 -0
  44. package/package.json +92 -0
@@ -0,0 +1,73 @@
1
+ import { useState, useCallback } from 'react';
2
+ export const useKeyboard = () => {
3
+ const [input, setInput] = useState('');
4
+ const [cursorPos, setCursorPos] = useState(0);
5
+ const [history, setHistory] = useState([]);
6
+ const [historyIndex, setHistoryIndex] = useState(-1);
7
+ const handleKey = useCallback((str, key) => {
8
+ // Submit
9
+ if (key.return) {
10
+ if (input.trim()) {
11
+ setHistory(prev => [...prev, input]);
12
+ // Process command here
13
+ setInput('');
14
+ setCursorPos(0);
15
+ setHistoryIndex(-1);
16
+ }
17
+ return { shouldSubmit: true, value: input };
18
+ }
19
+ // History navigation
20
+ if (key.upArrow && history.length > 0) {
21
+ const newIndex = Math.min(historyIndex + 1, history.length - 1);
22
+ setHistoryIndex(newIndex);
23
+ const historyValue = history[history.length - 1 - newIndex];
24
+ setInput(historyValue);
25
+ setCursorPos(historyValue.length);
26
+ return { shouldSubmit: false };
27
+ }
28
+ if (key.downArrow && historyIndex >= 0) {
29
+ const newIndex = historyIndex - 1;
30
+ if (newIndex < 0) {
31
+ setHistoryIndex(-1);
32
+ setInput('');
33
+ setCursorPos(0);
34
+ }
35
+ else {
36
+ setHistoryIndex(newIndex);
37
+ const historyValue = history[history.length - 1 - newIndex];
38
+ setInput(historyValue);
39
+ setCursorPos(historyValue.length);
40
+ }
41
+ return { shouldSubmit: false };
42
+ }
43
+ // Cursor movement
44
+ if (key.leftArrow) {
45
+ setCursorPos(Math.max(0, cursorPos - 1));
46
+ return { shouldSubmit: false };
47
+ }
48
+ if (key.rightArrow) {
49
+ setCursorPos(Math.min(input.length, cursorPos + 1));
50
+ return { shouldSubmit: false };
51
+ }
52
+ // Backspace
53
+ if (key.backspace && cursorPos > 0) {
54
+ const newInput = input.slice(0, cursorPos - 1) + input.slice(cursorPos);
55
+ setInput(newInput);
56
+ setCursorPos(cursorPos - 1);
57
+ return { shouldSubmit: false };
58
+ }
59
+ // Tab for autocomplete
60
+ if (key.tab) {
61
+ return { shouldSubmit: false, shouldAutocomplete: true };
62
+ }
63
+ // Regular input
64
+ if (str && !key.ctrl && !key.meta) {
65
+ const newInput = input.slice(0, cursorPos) + str + input.slice(cursorPos);
66
+ setInput(newInput);
67
+ setCursorPos(cursorPos + str.length);
68
+ return { shouldSubmit: false };
69
+ }
70
+ return { shouldSubmit: false };
71
+ }, [input, cursorPos, history, historyIndex]);
72
+ return { input, cursorPos, handleKey, setInput, setCursorPos };
73
+ };
package/dist/index.js ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import React from 'react';
4
+ import { render } from 'ink';
5
+ import { App } from './components/App.js';
6
+ // Setup CLI program
7
+ const program = new Command();
8
+ program
9
+ .name('camo')
10
+ .description('🦎 CAMO - Elite Autonomous CLI Agent')
11
+ .version('2.0.0')
12
+ .option('-v, --verbose', 'Enable verbose output')
13
+ .argument('[initialInput]', 'Initial prompt to start with')
14
+ .action(async (initialInput, options) => {
15
+ // If we have an initial input, we can pass it to the App.
16
+ // Ideally, if it's a one-shot command, we might run headless, but user said "Start-Flow... Man landet direkt im Chat-Interface".
17
+ // So even with args, we might open the UI and fill the input?
18
+ // User didn't specify headless behavior in refactor. I'll focus on interactive UI.
19
+ // Start Ink with custom exit handling
20
+ const app = React.createElement(App, {
21
+ initialInput: initialInput,
22
+ verbose: options.verbose
23
+ });
24
+ // Render with exitOnCtrlC disabled (we handle it ourselves)
25
+ const { unmount, waitUntilExit } = render(app, {
26
+ exitOnCtrlC: false
27
+ });
28
+ // Wait for app to exit gracefully
29
+ await waitUntilExit();
30
+ });
31
+ program.parse();
package/dist/mcp.js ADDED
@@ -0,0 +1,95 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import { z } from "zod";
4
+ import * as clack from '@clack/prompts';
5
+ import chalk from 'chalk';
6
+ export class McpManager {
7
+ constructor() {
8
+ this.clients = new Map();
9
+ this.activeTools = new Map();
10
+ }
11
+ async connect(configs) {
12
+ for (const config of configs) {
13
+ try {
14
+ const transport = new StdioClientTransport({
15
+ command: config.command,
16
+ args: config.args,
17
+ });
18
+ const client = new Client({
19
+ name: "camo-client",
20
+ version: "1.0.0",
21
+ }, {
22
+ capabilities: {},
23
+ });
24
+ await client.connect(transport);
25
+ this.clients.set(config.name, client);
26
+ }
27
+ catch (error) {
28
+ console.error(chalk.red(`Failed to connect to MCP server '${config.name}': ${error.message}`));
29
+ }
30
+ }
31
+ }
32
+ getActiveServerCount() {
33
+ return this.clients.size;
34
+ }
35
+ async getTools() {
36
+ const aiSdkTools = {};
37
+ this.activeTools.clear();
38
+ for (const [serverName, client] of this.clients.entries()) {
39
+ try {
40
+ const result = await client.listTools();
41
+ for (const tool of result.tools) {
42
+ // Prefix tool name to avoid collisions
43
+ const uniqueToolName = `${serverName}_${tool.name}`;
44
+ this.activeTools.set(uniqueToolName, { serverName, toolName: tool.name });
45
+ // We wrap the execution to include a security check
46
+ aiSdkTools[uniqueToolName] = {
47
+ description: `${tool.description || ''} (Provided by MCP Server: ${serverName}) \nArguments Schema: ${JSON.stringify(tool.inputSchema)}`,
48
+ // We use z.any() here but the description contains the schema for the LLM to follow.
49
+ // This prevents complex runtime Zod conversion issues.
50
+ inputSchema: z.object({}).passthrough().describe("JSON arguments matching the schema provided in description"),
51
+ execute: async (args) => {
52
+ return await this.executeToolWithSecurity(uniqueToolName, args);
53
+ },
54
+ };
55
+ }
56
+ }
57
+ catch (error) {
58
+ console.error(chalk.yellow(`Failed to fetch tools from '${serverName}': ${error.message}`));
59
+ }
60
+ }
61
+ return aiSdkTools;
62
+ }
63
+ async executeToolWithSecurity(uniqueToolName, args) {
64
+ const info = this.activeTools.get(uniqueToolName);
65
+ if (!info) {
66
+ return { error: `Tool ${uniqueToolName} not found` };
67
+ }
68
+ const { serverName, toolName } = info;
69
+ console.log(chalk.yellow(`\n[MCP SECURITY] Server '${serverName}' wants to execute tool '${toolName}'`));
70
+ console.log(chalk.white(`Arguments: ${JSON.stringify(args, null, 2)}`));
71
+ const approved = await clack.confirm({
72
+ message: 'Allow execution?',
73
+ });
74
+ if (!approved || clack.isCancel(approved)) {
75
+ return { error: 'User denied execution' };
76
+ }
77
+ const client = this.clients.get(serverName);
78
+ if (!client)
79
+ return { error: 'Client disconnected' };
80
+ try {
81
+ const result = await client.callTool({
82
+ name: toolName,
83
+ arguments: args,
84
+ });
85
+ // Convert MCP result to simple text/json for AI SDK
86
+ const toolResult = result;
87
+ return {
88
+ result: toolResult.content.map((c) => c.type === 'text' ? c.text : '[Binary/Image]').join('\n')
89
+ };
90
+ }
91
+ catch (error) {
92
+ return { error: `MCP Execution Error: ${error.message}` };
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,228 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ /**
4
+ * MemoryManager - Handles persistent working memory in .camo directory
5
+ */
6
+ export class MemoryManager {
7
+ constructor(projectRoot) {
8
+ this.camoDir = path.join(projectRoot, '.camo');
9
+ this.planPath = path.join(this.camoDir, 'plan.md');
10
+ this.contextPath = path.join(this.camoDir, 'context.md');
11
+ this.sessionPath = path.join(this.camoDir, 'session.json');
12
+ }
13
+ static getInstance(projectRoot = process.cwd()) {
14
+ if (!MemoryManager.instance) {
15
+ MemoryManager.instance = new MemoryManager(projectRoot);
16
+ }
17
+ return MemoryManager.instance;
18
+ }
19
+ /**
20
+ * Initialize .camo directory and files if they don't exist
21
+ */
22
+ async initialize() {
23
+ try {
24
+ // Create .camo directory
25
+ await fs.mkdir(this.camoDir, { recursive: true });
26
+ // Initialize plan.md if not exists
27
+ try {
28
+ await fs.access(this.planPath);
29
+ }
30
+ catch {
31
+ await fs.writeFile(this.planPath, this.getDefaultPlan(), 'utf-8');
32
+ }
33
+ // Initialize context.md if not exists
34
+ try {
35
+ await fs.access(this.contextPath);
36
+ }
37
+ catch {
38
+ await fs.writeFile(this.contextPath, this.getDefaultContext(), 'utf-8');
39
+ }
40
+ // Initialize session.json if not exists
41
+ try {
42
+ await fs.access(this.sessionPath);
43
+ }
44
+ catch {
45
+ const defaultSession = {
46
+ totalTokens: 0,
47
+ lastCheckpoint: new Date().toISOString(),
48
+ sessionCount: 0
49
+ };
50
+ await fs.writeFile(this.sessionPath, JSON.stringify(defaultSession, null, 2), 'utf-8');
51
+ }
52
+ // Add .camo to .gitignore if .gitignore exists
53
+ await this.addToGitignore();
54
+ }
55
+ catch (e) {
56
+ console.error('Failed to initialize .camo directory:', e.message);
57
+ }
58
+ }
59
+ /**
60
+ * Add .camo/ to .gitignore if not already present
61
+ */
62
+ async addToGitignore() {
63
+ const gitignorePath = path.join(path.dirname(this.camoDir), '.gitignore');
64
+ try {
65
+ let gitignoreContent = '';
66
+ try {
67
+ gitignoreContent = await fs.readFile(gitignorePath, 'utf-8');
68
+ }
69
+ catch {
70
+ // .gitignore doesn't exist, create it
71
+ }
72
+ if (!gitignoreContent.includes('.camo')) {
73
+ const newContent = gitignoreContent + (gitignoreContent.endsWith('\n') ? '' : '\n') + '.camo/\n';
74
+ await fs.writeFile(gitignorePath, newContent, 'utf-8');
75
+ }
76
+ }
77
+ catch (e) {
78
+ // Ignore errors - .gitignore is optional
79
+ }
80
+ }
81
+ /**
82
+ * Load plan.md content
83
+ */
84
+ async loadPlan() {
85
+ try {
86
+ return await fs.readFile(this.planPath, 'utf-8');
87
+ }
88
+ catch {
89
+ return '';
90
+ }
91
+ }
92
+ /**
93
+ * Save plan.md content
94
+ */
95
+ async savePlan(content) {
96
+ await fs.writeFile(this.planPath, content, 'utf-8');
97
+ }
98
+ /**
99
+ * Update task status in plan.md
100
+ */
101
+ async updateTaskStatus(taskId, status) {
102
+ const plan = await this.loadPlan();
103
+ const statusChar = status === 'complete' ? 'x' : status === 'in_progress' ? '/' : ' ';
104
+ // Replace task status (simple regex-based approach)
105
+ const updatedPlan = plan.replace(new RegExp(`- \\[.\\] (.*)<!-- id: ${taskId} -->`, 'g'), `- [${statusChar}] $1<!-- id: ${taskId} -->`);
106
+ await this.savePlan(updatedPlan);
107
+ }
108
+ /**
109
+ * Load context.md content
110
+ */
111
+ async loadContext() {
112
+ try {
113
+ return await fs.readFile(this.contextPath, 'utf-8');
114
+ }
115
+ catch {
116
+ return '';
117
+ }
118
+ }
119
+ /**
120
+ * Add or update a fact in context.md
121
+ */
122
+ async updateContext(category, content) {
123
+ let context = await this.loadContext();
124
+ // Check if category already exists
125
+ const categoryHeader = `## ${category}`;
126
+ if (context.includes(categoryHeader)) {
127
+ // Append to existing category
128
+ const lines = context.split('\n');
129
+ const categoryIndex = lines.findIndex(l => l.startsWith(categoryHeader));
130
+ // Find next category or end
131
+ let nextCategoryIndex = lines.length;
132
+ for (let i = categoryIndex + 1; i < lines.length; i++) {
133
+ if (lines[i].startsWith('## ')) {
134
+ nextCategoryIndex = i;
135
+ break;
136
+ }
137
+ }
138
+ // Insert before next category
139
+ lines.splice(nextCategoryIndex, 0, `- ${content}`);
140
+ context = lines.join('\n');
141
+ }
142
+ else {
143
+ // Add new category
144
+ context += `\n${categoryHeader}\n- ${content}\n`;
145
+ }
146
+ await fs.writeFile(this.contextPath, context, 'utf-8');
147
+ }
148
+ /**
149
+ * Load session.json data
150
+ */
151
+ async loadSession() {
152
+ try {
153
+ const content = await fs.readFile(this.sessionPath, 'utf-8');
154
+ return JSON.parse(content);
155
+ }
156
+ catch {
157
+ return {
158
+ totalTokens: 0,
159
+ lastCheckpoint: new Date().toISOString(),
160
+ sessionCount: 0
161
+ };
162
+ }
163
+ }
164
+ /**
165
+ * Save session.json data
166
+ */
167
+ async saveSession(data) {
168
+ const current = await this.loadSession();
169
+ const updated = { ...current, ...data };
170
+ await fs.writeFile(this.sessionPath, JSON.stringify(updated, null, 2), 'utf-8');
171
+ }
172
+ /**
173
+ * Increment session count
174
+ */
175
+ async incrementSession() {
176
+ const session = await this.loadSession();
177
+ await this.saveSession({
178
+ sessionCount: session.sessionCount + 1,
179
+ lastCheckpoint: new Date().toISOString()
180
+ });
181
+ }
182
+ /**
183
+ * Get pending tasks from plan.md
184
+ */
185
+ async getPendingTasks() {
186
+ const plan = await this.loadPlan();
187
+ const lines = plan.split('\n');
188
+ const pending = [];
189
+ for (const line of lines) {
190
+ if (line.match(/- \[ \]/)) {
191
+ // Extract task text (remove checkbox)
192
+ const task = line.replace(/- \[ \]/, '').trim();
193
+ pending.push(task);
194
+ }
195
+ }
196
+ return pending;
197
+ }
198
+ /**
199
+ * Default plan.md template
200
+ */
201
+ getDefaultPlan() {
202
+ return `# Current Plan
203
+
204
+ ## Active Tasks
205
+ <!-- Add tasks here using: - [ ] Task description -->
206
+
207
+ ## Completed
208
+ <!-- Completed tasks will be moved here -->
209
+ `;
210
+ }
211
+ /**
212
+ * Default context.md template
213
+ */
214
+ getDefaultContext() {
215
+ return `# Project Context
216
+
217
+ ## Architecture
218
+ <!-- Learned architectural decisions go here -->
219
+
220
+ ## Key Files
221
+ <!-- Important file locations -->
222
+
223
+ ## Decisions
224
+ <!-- Technical decisions and rationale -->
225
+ `;
226
+ }
227
+ }
228
+ export default MemoryManager;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Provider Manager
3
+ * Central management for AI providers
4
+ */
5
+ import { DEFAULT_PROVIDERS } from './registry.js';
6
+ export class ProviderManager {
7
+ constructor(config) {
8
+ this.config = config;
9
+ }
10
+ getProviders() {
11
+ const providers = this.config.get('providers', {});
12
+ const merged = {};
13
+ // Merge defaults
14
+ Object.entries(DEFAULT_PROVIDERS).forEach(([key, defaultProvider]) => {
15
+ // Get API key from root config if it existed there (legacy) or provider config
16
+ // We'll primarily look in the provider config object now, or mapped keys in App.tsx logic used config.get('googleApiKey').
17
+ // To align with App.tsx which uses `googleApiKey` etc at root:
18
+ let apiKey = '';
19
+ if (key === 'google')
20
+ apiKey = this.config.get('googleApiKey');
21
+ if (key === 'anthropic')
22
+ apiKey = this.config.get('anthropicApiKey');
23
+ if (key === 'openai')
24
+ apiKey = this.config.get('openaiApiKey');
25
+ if (key === 'openrouter')
26
+ apiKey = this.config.get('openrouterApiKey');
27
+ if (key === 'custom')
28
+ apiKey = this.config.get('customApiKey');
29
+ merged[key] = {
30
+ ...defaultProvider,
31
+ apiKey: apiKey || '',
32
+ isActive: !!apiKey
33
+ };
34
+ });
35
+ // Custom Base URL handling
36
+ if (merged['custom']) {
37
+ merged['custom'].baseURL = this.config.get('customBaseUrl') || '';
38
+ }
39
+ return merged;
40
+ }
41
+ getActiveSelection() {
42
+ const activeProvider = this.config.get('activeProvider'); // defaults?
43
+ const activeModel = this.config.get('activeModel');
44
+ // If explicitly set and valid, use it
45
+ if (activeProvider && activeModel) {
46
+ const providers = this.getProviders();
47
+ if (providers[activeProvider]) {
48
+ const model = providers[activeProvider].models.find(m => m.id === activeModel);
49
+ if (model)
50
+ return { provider: activeProvider, model };
51
+ }
52
+ }
53
+ return this.getDefaultSelection();
54
+ }
55
+ getDefaultSelection() {
56
+ const providers = this.getProviders();
57
+ // Priority: Google first (as requested)
58
+ const priorityOrder = ['google', 'anthropic', 'openai', 'openrouter', 'custom'];
59
+ for (const key of priorityOrder) {
60
+ const p = providers[key];
61
+ if (p?.isActive && p.models.length > 0) {
62
+ return { provider: key, model: p.models[0] };
63
+ }
64
+ }
65
+ return null; // No active provider with key
66
+ }
67
+ async createModel(selection) {
68
+ const { createGoogleGenerativeAI } = await import('@ai-sdk/google');
69
+ const activeSelection = selection || this.getActiveSelection();
70
+ if (!activeSelection)
71
+ throw new Error('No active provider configured.');
72
+ const providers = this.getProviders();
73
+ const provider = providers[activeSelection.provider];
74
+ if (!provider.apiKey)
75
+ throw new Error(`Missing API Key for ${provider.name}`);
76
+ const modelId = activeSelection.model.id;
77
+ // Only Google is supported
78
+ if (provider.type === 'google') {
79
+ return createGoogleGenerativeAI({ apiKey: provider.apiKey })(modelId);
80
+ }
81
+ throw new Error(`Only Google provider is supported. Got: ${provider.type}`);
82
+ }
83
+ }
84
+ export * from './types.js';
85
+ export * from './registry.js';
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Provider Registry
3
+ * Pre-configured providers and their available models
4
+ */
5
+ // Anthropic Models
6
+ const anthropicModels = [
7
+ {
8
+ id: 'claude-3-5-sonnet-latest',
9
+ name: 'Claude 3.5 Sonnet',
10
+ contextWindow: 200000,
11
+ supportsTools: true,
12
+ description: 'Most capable model for coding tasks'
13
+ },
14
+ {
15
+ id: 'claude-3-5-haiku-latest',
16
+ name: 'Claude 3.5 Haiku',
17
+ contextWindow: 200000,
18
+ supportsTools: true,
19
+ description: 'Fast and efficient for quick tasks'
20
+ }
21
+ ];
22
+ // OpenAI Models
23
+ const openaiModels = [
24
+ {
25
+ id: 'gpt-4o',
26
+ name: 'GPT-4o',
27
+ contextWindow: 128000,
28
+ supportsTools: true,
29
+ description: 'Most capable GPT-4 model'
30
+ },
31
+ {
32
+ id: 'gpt-4o-mini',
33
+ name: 'GPT-4o Mini',
34
+ contextWindow: 128000,
35
+ supportsTools: true,
36
+ description: 'Faster and more affordable'
37
+ },
38
+ {
39
+ id: 'o1',
40
+ name: 'O1',
41
+ contextWindow: 200000,
42
+ supportsTools: false,
43
+ description: 'Advanced reasoning model'
44
+ }
45
+ ];
46
+ // Google Models
47
+ const googleModels = [
48
+ {
49
+ id: 'gemini-2.0-flash',
50
+ name: 'Gemini 2.0 Flash',
51
+ contextWindow: 1000000,
52
+ supportsTools: true,
53
+ description: 'Fast with massive context window (Default)'
54
+ }
55
+ ];
56
+ // OpenRouter Models (Example)
57
+ const openRouterModels = [
58
+ {
59
+ id: 'google/gemini-2.0-flash-exp:free',
60
+ name: 'Gemini 2.0 Flash (Free)',
61
+ contextWindow: 1000000,
62
+ supportsTools: true,
63
+ description: 'Free tier via OpenRouter'
64
+ },
65
+ {
66
+ id: 'anthropic/claude-3.5-sonnet',
67
+ name: 'Claude 3.5 Sonnet (OR)',
68
+ contextWindow: 200000,
69
+ supportsTools: true,
70
+ description: 'Via OpenRouter'
71
+ }
72
+ ];
73
+ // Custom/Self-hosted Models
74
+ const customModels = [
75
+ {
76
+ id: 'custom',
77
+ name: 'Custom Model',
78
+ contextWindow: 4000,
79
+ supportsTools: true,
80
+ description: 'Custom API endpoint'
81
+ }
82
+ ];
83
+ /**
84
+ * Default provider configurations
85
+ */
86
+ export const DEFAULT_PROVIDERS = {
87
+ google: {
88
+ name: 'Google',
89
+ type: 'google',
90
+ models: googleModels
91
+ },
92
+ anthropic: {
93
+ name: 'Anthropic',
94
+ type: 'anthropic',
95
+ models: anthropicModels
96
+ },
97
+ openai: {
98
+ name: 'OpenAI',
99
+ type: 'openai',
100
+ models: openaiModels
101
+ },
102
+ openrouter: {
103
+ name: 'OpenRouter',
104
+ type: 'openrouter',
105
+ models: openRouterModels
106
+ },
107
+ custom: {
108
+ name: 'Custom',
109
+ type: 'custom',
110
+ models: customModels
111
+ }
112
+ };
113
+ export function findModel(modelId) {
114
+ for (const [providerKey, provider] of Object.entries(DEFAULT_PROVIDERS)) {
115
+ const model = provider.models.find(m => m.id === modelId);
116
+ if (model) {
117
+ return { provider: providerKey, model };
118
+ }
119
+ }
120
+ return null;
121
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Provider System Types
3
+ * Defines the structure for AI providers and models
4
+ */
5
+ export {};
package/dist/theme.js ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Minimalist Design System
3
+ * Black/White/Gray color palette with high contrast
4
+ */
5
+ export const theme = {
6
+ colors: {
7
+ white: '#FFFFFF',
8
+ lightGray: '#D3D3D3',
9
+ mediumGray: '#808080',
10
+ darkGray: '#333333',
11
+ black: '#000000',
12
+ accent: '#FFFFFF', // For selection/active elements
13
+ success: '#00FF00',
14
+ error: '#FF0000',
15
+ warning: '#FFFF00',
16
+ },
17
+ textColors: {
18
+ primary: 'white',
19
+ secondary: 'lightGray',
20
+ muted: 'mediumGray',
21
+ inverse: 'black',
22
+ },
23
+ backgrounds: {
24
+ default: 'black',
25
+ selected: 'white',
26
+ muted: 'darkGray',
27
+ },
28
+ spacing: {
29
+ xs: 1,
30
+ sm: 2,
31
+ md: 4,
32
+ lg: 8,
33
+ },
34
+ borders: {
35
+ light: '─',
36
+ heavy: '━',
37
+ corner: '┬',
38
+ },
39
+ };
40
+ // Prefix symbols for messages
41
+ export const messagePrefixes = {
42
+ user: '> ',
43
+ assistant: '- ',
44
+ system: '~ ',
45
+ };