hybard-agent 0.1.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.
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/bin/.cmd +0 -0
- package/bin/agent.js +673 -0
- package/bin/cli.js +326 -0
- package/bin/hybard.cmd +2 -0
- package/knowledge/BENGALI_GUIDE.md +436 -0
- package/knowledge/CAPABILITIES.md +448 -0
- package/knowledge/INDEX.md +204 -0
- package/knowledge/KNOWLEDGE_BASE.md +1174 -0
- package/knowledge/README.md +97 -0
- package/knowledge/SYSTEM_PROMPT.md +310 -0
- package/lib/agent.js +730 -0
- package/lib/analyzer.js +330 -0
- package/lib/coding-agent.js +87 -0
- package/lib/coding-model.js +585 -0
- package/lib/engine.js +591 -0
- package/lib/hybard-agent.js +1063 -0
- package/lib/main.dart +32 -0
- package/lib/models.js +357 -0
- package/lib/online.js +654 -0
- package/lib/server.js +278 -0
- package/lib/ui.js +96 -0
- package/package.json +50 -0
package/lib/main.dart
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
void main() {
|
|
4
|
+
runApp(const MyApp());
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
class MyApp extends StatelessWidget {
|
|
8
|
+
const MyApp({super.key});
|
|
9
|
+
|
|
10
|
+
@override
|
|
11
|
+
Widget build(BuildContext context) {
|
|
12
|
+
return MaterialApp(
|
|
13
|
+
title: 'hybard-agent',
|
|
14
|
+
theme: ThemeData(primarySwatch: Colors.blue),
|
|
15
|
+
home: const HomeScreen(),
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class HomeScreen extends StatelessWidget {
|
|
21
|
+
const HomeScreen({super.key});
|
|
22
|
+
|
|
23
|
+
@override
|
|
24
|
+
Widget build(BuildContext context) {
|
|
25
|
+
return Scaffold(
|
|
26
|
+
appBar: AppBar(title: const Text('Hybard Flutter App')),
|
|
27
|
+
body: const Center(
|
|
28
|
+
child: Text('Generated app for: flutter mobile app --port 7862'),
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
package/lib/models.js
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybard Model Router — Multi-provider, multi-model AI routing.
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - Groq (free, fast — llama, mixtral, gemma)
|
|
6
|
+
* - Ollama (local — llama3.2, codellama, deepseek-coder, etc.)
|
|
7
|
+
* - OpenRouter (many models — Claude, Gemini, Llama, etc.)
|
|
8
|
+
* - OpenAI (GPT-4, GPT-3.5)
|
|
9
|
+
* - Custom endpoints (any OpenAI-compatible)
|
|
10
|
+
*
|
|
11
|
+
* Features:
|
|
12
|
+
* - Per-user model selection
|
|
13
|
+
* - Fallback chain (if one provider fails, try next)
|
|
14
|
+
* - Token counting & usage tracking
|
|
15
|
+
* - Model capabilities detection
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* const router = new ModelRouter();
|
|
19
|
+
* const response = await router.chat('write fibonacci', { provider: 'groq' });
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const https = require('https');
|
|
23
|
+
const http = require('http');
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
|
|
27
|
+
// ─── Provider Definitions ────────────────────────────────
|
|
28
|
+
|
|
29
|
+
const PROVIDERS = {
|
|
30
|
+
groq: {
|
|
31
|
+
name: 'Groq',
|
|
32
|
+
baseUrl: 'https://api.groq.com/openai/v1',
|
|
33
|
+
apiKey: process.env.GROQ_API_KEY || '',
|
|
34
|
+
models: {
|
|
35
|
+
'llama-3.3-70b-versatile': { name: 'Llama 3.3 70B', size: '70B', coding: true, speed: 'fast' },
|
|
36
|
+
'llama-3.1-8b-instant': { name: 'Llama 3.1 8B Instant', size: '8B', coding: true, speed: 'very-fast' },
|
|
37
|
+
'mixtral-8x7b-32768': { name: 'Mixtral 8x7B', size: '8x7B', coding: true, speed: 'fast' },
|
|
38
|
+
'gemma2-9b-it': { name: 'Gemma 2 9B', size: '9B', coding: false, speed: 'fast' },
|
|
39
|
+
},
|
|
40
|
+
defaultModel: 'llama-3.3-70b-versatile',
|
|
41
|
+
free: true,
|
|
42
|
+
},
|
|
43
|
+
ollama: {
|
|
44
|
+
name: 'Ollama (Local)',
|
|
45
|
+
baseUrl: 'http://localhost:11434',
|
|
46
|
+
apiKey: 'ollama',
|
|
47
|
+
models: {
|
|
48
|
+
'llama3.2': { name: 'Llama 3.2', size: '3.2B', coding: true, speed: 'very-fast' },
|
|
49
|
+
'codellama': { name: 'Code Llama', size: '7B', coding: true, speed: 'fast', codingSpecialist: true },
|
|
50
|
+
'deepseek-coder': { name: 'DeepSeek Coder', size: '7B', coding: true, speed: 'fast', codingSpecialist: true },
|
|
51
|
+
'llama3.1:8b': { name: 'Llama 3.1 8B', size: '8B', coding: true, speed: 'fast' },
|
|
52
|
+
'qwen2.5-coder': { name: 'Qwen 2.5 Coder', size: '7B', coding: true, speed: 'fast', codingSpecialist: true },
|
|
53
|
+
'phi3': { name: 'Phi-3', size: '3.8B', coding: true, speed: 'very-fast' },
|
|
54
|
+
'mistral': { name: 'Mistral', size: '7B', coding: true, speed: 'fast' },
|
|
55
|
+
},
|
|
56
|
+
defaultModel: 'llama3.2',
|
|
57
|
+
free: true,
|
|
58
|
+
local: true,
|
|
59
|
+
},
|
|
60
|
+
openrouter: {
|
|
61
|
+
name: 'OpenRouter',
|
|
62
|
+
baseUrl: 'https://openrouter.ai/api/v1',
|
|
63
|
+
apiKey: process.env.OPENROUTER_API_KEY || '',
|
|
64
|
+
models: {
|
|
65
|
+
'meta-llama/llama-3.3-70b-instruct': { name: 'Llama 3.3 70B', size: '70B', coding: true },
|
|
66
|
+
'anthropic/claude-3.5-sonnet': { name: 'Claude 3.5 Sonnet', size: 'large', coding: true },
|
|
67
|
+
'google/gemini-pro-1.5': { name: 'Gemini Pro 1.5', size: 'large', coding: true },
|
|
68
|
+
'deepseek/deepseek-chat': { name: 'DeepSeek Chat', size: 'large', coding: true },
|
|
69
|
+
},
|
|
70
|
+
defaultModel: 'meta-llama/llama-3.3-70b-instruct',
|
|
71
|
+
free: false,
|
|
72
|
+
},
|
|
73
|
+
openai: {
|
|
74
|
+
name: 'OpenAI',
|
|
75
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
76
|
+
apiKey: process.env.OPENAI_API_KEY || '',
|
|
77
|
+
models: {
|
|
78
|
+
'gpt-4o': { name: 'GPT-4o', size: 'large', coding: true },
|
|
79
|
+
'gpt-4o-mini': { name: 'GPT-4o Mini', size: 'medium', coding: true },
|
|
80
|
+
'gpt-3.5-turbo': { name: 'GPT-3.5 Turbo', size: 'small', coding: true },
|
|
81
|
+
},
|
|
82
|
+
defaultModel: 'gpt-4o',
|
|
83
|
+
free: false,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// ─── Model Router Class ──────────────────────────────────
|
|
88
|
+
|
|
89
|
+
class ModelRouter {
|
|
90
|
+
constructor(options = {}) {
|
|
91
|
+
this.configDir = options.configDir || path.join(require('os').homedir(), '.hybard');
|
|
92
|
+
this.configFile = path.join(this.configDir, 'models.json');
|
|
93
|
+
this.config = this._loadConfig();
|
|
94
|
+
this.providers = { ...PROVIDERS };
|
|
95
|
+
|
|
96
|
+
// Load .env
|
|
97
|
+
this._loadEnv();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
_loadEnv() {
|
|
101
|
+
const envPath = path.join(__dirname, '..', '.env');
|
|
102
|
+
if (fs.existsSync(envPath)) {
|
|
103
|
+
const envContent = fs.readFileSync(envPath, 'utf8');
|
|
104
|
+
for (const line of envContent.split('\n')) {
|
|
105
|
+
const trimmed = line.trim();
|
|
106
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
107
|
+
const eqIdx = trimmed.indexOf('=');
|
|
108
|
+
if (eqIdx > 0) {
|
|
109
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
110
|
+
const val = trimmed.slice(eqIdx + 1).trim();
|
|
111
|
+
if (!process.env[key]) process.env[key] = val;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Update provider API keys from env
|
|
115
|
+
if (process.env.GROQ_API_KEY) this.providers.groq.apiKey = process.env.GROQ_API_KEY;
|
|
116
|
+
if (process.env.OPENROUTER_API_KEY) this.providers.openrouter.apiKey = process.env.OPENROUTER_API_KEY;
|
|
117
|
+
if (process.env.OPENAI_API_KEY) this.providers.openai.apiKey = process.env.OPENAI_API_KEY;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_loadConfig() {
|
|
122
|
+
try {
|
|
123
|
+
if (fs.existsSync(this.configFile)) {
|
|
124
|
+
return JSON.parse(fs.readFileSync(this.configFile, 'utf8'));
|
|
125
|
+
}
|
|
126
|
+
} catch (e) {}
|
|
127
|
+
return {
|
|
128
|
+
defaultProvider: 'groq',
|
|
129
|
+
defaultModel: 'llama-3.3-70b-versatile',
|
|
130
|
+
fallbackChain: ['groq', 'ollama'],
|
|
131
|
+
maxTokens: 2048,
|
|
132
|
+
temperature: 0.7,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
saveConfig(updates = {}) {
|
|
137
|
+
this.config = { ...this.config, ...updates };
|
|
138
|
+
if (!fs.existsSync(this.configDir)) fs.mkdirSync(this.configDir, { recursive: true });
|
|
139
|
+
fs.writeFileSync(this.configFile, JSON.stringify(this.config, null, 2));
|
|
140
|
+
return this.config;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ─── Provider Info ──────────────────────────────────
|
|
144
|
+
|
|
145
|
+
listProviders() {
|
|
146
|
+
return Object.entries(this.providers).map(([key, prov]) => ({
|
|
147
|
+
id: key,
|
|
148
|
+
name: prov.name,
|
|
149
|
+
free: prov.free || false,
|
|
150
|
+
local: prov.local || false,
|
|
151
|
+
hasApiKey: !!(prov.apiKey),
|
|
152
|
+
modelCount: Object.keys(prov.models).length,
|
|
153
|
+
isDefault: this.config.defaultProvider === key,
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
listModels(provider = null) {
|
|
158
|
+
const results = [];
|
|
159
|
+
const providers = provider ? { [provider]: this.providers[provider] } : this.providers;
|
|
160
|
+
|
|
161
|
+
for (const [provId, prov] of Object.entries(providers)) {
|
|
162
|
+
if (!prov) continue;
|
|
163
|
+
for (const [modelId, model] of Object.entries(prov.models)) {
|
|
164
|
+
results.push({
|
|
165
|
+
provider: provId,
|
|
166
|
+
providerName: prov.name,
|
|
167
|
+
modelId,
|
|
168
|
+
...model,
|
|
169
|
+
isDefault: this.config.defaultModel === modelId,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return results;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
getCodingModels() {
|
|
177
|
+
return this.listModels().filter(m => m.coding);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
getCodingSpecialists() {
|
|
181
|
+
return this.listModels().filter(m => m.codingSpecialist);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ─── Chat ───────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
async chat(message, options = {}) {
|
|
187
|
+
const provider = options.provider || this.config.defaultProvider;
|
|
188
|
+
const model = options.model || this.config.defaultModel;
|
|
189
|
+
const prov = this.providers[provider];
|
|
190
|
+
|
|
191
|
+
if (!prov) throw new Error(`Unknown provider: ${provider}`);
|
|
192
|
+
if (!prov.apiKey && provider !== 'ollama') {
|
|
193
|
+
throw new Error(`No API key for ${provider}. Set it in .env or config.`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Try primary provider
|
|
197
|
+
try {
|
|
198
|
+
return await this._callProvider(provider, message, options);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
// Fallback chain
|
|
201
|
+
const fallbacks = this.config.fallbackChain.filter(p => p !== provider);
|
|
202
|
+
for (const fb of fallbacks) {
|
|
203
|
+
try {
|
|
204
|
+
console.log(`[Fallback] ${provider} failed, trying ${fb}...`);
|
|
205
|
+
return await this._callProvider(fb, message, options);
|
|
206
|
+
} catch (e2) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
throw err;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async _callProvider(provider, message, options = {}) {
|
|
215
|
+
const prov = this.providers[provider];
|
|
216
|
+
const model = options.model || prov.defaultModel;
|
|
217
|
+
|
|
218
|
+
// Use native API for Ollama
|
|
219
|
+
if (provider === 'ollama') {
|
|
220
|
+
return this._callOllama(message, model, options);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// OpenAI-compatible API for others
|
|
224
|
+
return this._callOpenAICompatible(prov, model, message, options);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async _callOllama(message, model, options = {}) {
|
|
228
|
+
const body = JSON.stringify({
|
|
229
|
+
model,
|
|
230
|
+
prompt: message,
|
|
231
|
+
stream: false,
|
|
232
|
+
options: {
|
|
233
|
+
num_predict: options.maxTokens || this.config.maxTokens,
|
|
234
|
+
temperature: options.temperature ?? this.config.temperature,
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
return new Promise((resolve, reject) => {
|
|
239
|
+
const req = http.request(
|
|
240
|
+
{
|
|
241
|
+
hostname: 'localhost',
|
|
242
|
+
port: 11434,
|
|
243
|
+
path: '/api/generate',
|
|
244
|
+
method: 'POST',
|
|
245
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
|
|
246
|
+
timeout: 300000,
|
|
247
|
+
},
|
|
248
|
+
(res) => {
|
|
249
|
+
let data = '';
|
|
250
|
+
res.on('data', (chunk) => (data += chunk));
|
|
251
|
+
res.on('end', () => {
|
|
252
|
+
try {
|
|
253
|
+
const json = JSON.parse(data);
|
|
254
|
+
if (json.error) reject(new Error(json.error));
|
|
255
|
+
else resolve(json.response || '');
|
|
256
|
+
} catch (e) {
|
|
257
|
+
reject(new Error(`Ollama parse error: ${data.slice(0, 200)}`));
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
req.on('error', reject);
|
|
263
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Ollama timeout — model may be loading. Try again in a moment.')); });
|
|
264
|
+
req.write(body);
|
|
265
|
+
req.end();
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async _callOpenAICompatible(prov, model, message, options = {}) {
|
|
270
|
+
const messages = [
|
|
271
|
+
{ role: 'system', content: options.system || 'You are a coding assistant.' },
|
|
272
|
+
{ role: 'user', content: message },
|
|
273
|
+
];
|
|
274
|
+
|
|
275
|
+
const body = JSON.stringify({
|
|
276
|
+
model,
|
|
277
|
+
messages,
|
|
278
|
+
max_tokens: options.maxTokens || this.config.maxTokens,
|
|
279
|
+
temperature: options.temperature ?? this.config.temperature,
|
|
280
|
+
stream: false,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const baseUrl = new URL(prov.baseUrl);
|
|
284
|
+
const isHttps = baseUrl.protocol === 'https:';
|
|
285
|
+
const transport = isHttps ? https : http;
|
|
286
|
+
|
|
287
|
+
return new Promise((resolve, reject) => {
|
|
288
|
+
const req = transport.request(
|
|
289
|
+
{
|
|
290
|
+
hostname: baseUrl.hostname,
|
|
291
|
+
port: baseUrl.port || (isHttps ? 443 : 80),
|
|
292
|
+
path: `${baseUrl.pathname}/chat/completions`.replace(/\/+/g, '/'),
|
|
293
|
+
method: 'POST',
|
|
294
|
+
headers: {
|
|
295
|
+
'Content-Type': 'application/json',
|
|
296
|
+
'Authorization': `Bearer ${prov.apiKey}`,
|
|
297
|
+
'Content-Length': Buffer.byteLength(body),
|
|
298
|
+
},
|
|
299
|
+
timeout: 60000,
|
|
300
|
+
},
|
|
301
|
+
(res) => {
|
|
302
|
+
let data = '';
|
|
303
|
+
res.on('data', (chunk) => (data += chunk));
|
|
304
|
+
res.on('end', () => {
|
|
305
|
+
try {
|
|
306
|
+
const json = JSON.parse(data);
|
|
307
|
+
if (json.error) reject(new Error(json.error.message || JSON.stringify(json.error)));
|
|
308
|
+
else resolve(json.choices?.[0]?.message?.content || '');
|
|
309
|
+
} catch (e) {
|
|
310
|
+
reject(new Error(`Parse error: ${data.slice(0, 200)}`));
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
req.on('error', reject);
|
|
316
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout')); });
|
|
317
|
+
req.write(body);
|
|
318
|
+
req.end();
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ─── Quick helpers ──────────────────────────────────
|
|
323
|
+
|
|
324
|
+
async generateCode(prompt, language = 'python', options = {}) {
|
|
325
|
+
const system = `You are an expert ${language} programmer. Generate clean, working code.
|
|
326
|
+
Respond ONLY with the code in a markdown code block. No explanations unless asked.`;
|
|
327
|
+
return this.chat(prompt, { ...options, system });
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async explainCode(code, options = {}) {
|
|
331
|
+
const system = `You are a code analysis expert. Explain the given code clearly and concisely.
|
|
332
|
+
Cover: what it does, how it works, key patterns, and potential issues.`;
|
|
333
|
+
return this.chat(`Explain this code:\n\n\`\`\`\n${code}\n\`\`\``, { ...options, system });
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async fixCode(code, error = '', options = {}) {
|
|
337
|
+
const system = `You are a debugging expert. Fix the given code.
|
|
338
|
+
Return ONLY the corrected code in a markdown code block.`;
|
|
339
|
+
const msg = error
|
|
340
|
+
? `Fix this code. Error: ${error}\n\n\`\`\`\n${code}\n\`\`\``
|
|
341
|
+
: `Fix any issues in this code:\n\n\`\`\`\n${code}\n\`\`\``;
|
|
342
|
+
return this.chat(msg, { ...options, system });
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async scaffold(goal, options = {}) {
|
|
346
|
+
const system = `You are a project scaffolding expert. Create project files for the given goal.
|
|
347
|
+
Return each file in this format:
|
|
348
|
+
## filename.ext
|
|
349
|
+
\`\`\`language
|
|
350
|
+
file content here
|
|
351
|
+
\`\`\`
|
|
352
|
+
Include all necessary files (main code, config, tests, README).`;
|
|
353
|
+
return this.chat(`Create a project scaffold for: ${goal}`, { ...options, system });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
module.exports = { ModelRouter, PROVIDERS };
|