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.
@@ -0,0 +1,1063 @@
1
+ /**
2
+ * Hybard AI Agent — Complete working agent with real LLM integration.
3
+ *
4
+ * Features:
5
+ * - Real LLM Provider Connection (Groq → Ollama → OpenRouter → OpenAI)
6
+ * - Codebase Understanding (search, find, understand code)
7
+ * - Project Detection System (scan, detect framework/language/deps)
8
+ * - Agent Loop (Think → Act → Observe → Evaluate → Fix)
9
+ * - Tool Calling (file ops, terminal, code search)
10
+ * - Knowledge Base awareness (35 domains)
11
+ * - Memory system (short-term, long-term)
12
+ * - Bengali + English multilingual support
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const { spawnSync } = require('child_process');
18
+ const { HybardAI } = require('./online');
19
+ const { ProjectAnalyzer } = require('./analyzer');
20
+
21
+ // ─── Knowledge Base Loader ──────────────────────────────
22
+
23
+ class KnowledgeBase {
24
+ constructor() {
25
+ this.kbPath = path.join(__dirname, '..', 'knowledge', 'KNOWLEDGE_BASE.md');
26
+ this.capabilitiesPath = path.join(__dirname, '..', 'knowledge', 'CAPABILITIES.md');
27
+ this.content = '';
28
+ this.capabilities = '';
29
+ this.load();
30
+ }
31
+
32
+ load() {
33
+ try {
34
+ if (fs.existsSync(this.kbPath)) {
35
+ this.content = fs.readFileSync(this.kbPath, 'utf8');
36
+ }
37
+ if (fs.existsSync(this.capabilitiesPath)) {
38
+ this.capabilities = fs.readFileSync(this.capabilitiesPath, 'utf8');
39
+ }
40
+ } catch (e) {
41
+ console.error('Failed to load knowledge base:', e.message);
42
+ }
43
+ }
44
+
45
+ getDomainKnowledge(domain) {
46
+ const regex = new RegExp(`## ${domain}[\\s\\S]*?(?=\\n## \\d|$)`, 'i');
47
+ const match = this.content.match(regex);
48
+ return match ? match[0] : null;
49
+ }
50
+
51
+ getCapabilitiesSummary() {
52
+ return this.capabilities.slice(0, 3000);
53
+ }
54
+ }
55
+
56
+ // ─── Memory System ──────────────────────────────────────
57
+
58
+ class Memory {
59
+ constructor() {
60
+ this.shortTerm = [];
61
+ this.longTerm = [];
62
+ this.projectMemory = [];
63
+ this.memoryDir = path.join(require('os').homedir(), '.hybard', 'memory');
64
+ this.loadLongTerm();
65
+ }
66
+
67
+ loadLongTerm() {
68
+ try {
69
+ const memoryFile = path.join(this.memoryDir, 'long-term.json');
70
+ if (fs.existsSync(memoryFile)) {
71
+ this.longTerm = JSON.parse(fs.readFileSync(memoryFile, 'utf8'));
72
+ }
73
+ } catch (e) {}
74
+ }
75
+
76
+ saveLongTerm() {
77
+ try {
78
+ if (!fs.existsSync(this.memoryDir)) {
79
+ fs.mkdirSync(this.memoryDir, { recursive: true });
80
+ }
81
+ fs.writeFileSync(
82
+ path.join(this.memoryDir, 'long-term.json'),
83
+ JSON.stringify(this.longTerm, null, 2)
84
+ );
85
+ } catch (e) {}
86
+ }
87
+
88
+ addShortTerm(role, content) {
89
+ this.shortTerm.push({ role, content, timestamp: Date.now() });
90
+ if (this.shortTerm.length > 20) {
91
+ this.shortTerm = this.shortTerm.slice(-20);
92
+ }
93
+ }
94
+
95
+ addLongTerm(content, type = 'fact') {
96
+ this.longTerm.push({ content, type, timestamp: Date.now() });
97
+ this.saveLongTerm();
98
+ }
99
+
100
+ addProjectMemory(content) {
101
+ this.projectMemory.push({ content, timestamp: Date.now() });
102
+ }
103
+
104
+ getConversationContext() {
105
+ return this.shortTerm.map(m => `${m.role}: ${m.content}`).join('\n');
106
+ }
107
+
108
+ searchLongTerm(query) {
109
+ const lower = query.toLowerCase();
110
+ return this.longTerm.filter(m => m.content.toLowerCase().includes(lower));
111
+ }
112
+ }
113
+
114
+ // ─── Project Detection System ───────────────────────────
115
+
116
+ class ProjectDetector {
117
+ constructor(rootDir = process.cwd()) {
118
+ this.rootDir = rootDir;
119
+ this.result = {
120
+ type: 'unknown',
121
+ languages: [],
122
+ frameworks: [],
123
+ dependencies: {},
124
+ configFiles: [],
125
+ entryPoints: [],
126
+ structure: {},
127
+ };
128
+ }
129
+
130
+ detect() {
131
+ this.scanConfigFiles();
132
+ this.detectLanguage();
133
+ this.detectFramework();
134
+ this.detectDependencies();
135
+ this.detectEntryPoints();
136
+ this.buildStructure();
137
+ return this.result;
138
+ }
139
+
140
+ scanConfigFiles() {
141
+ const configFiles = [
142
+ 'package.json', 'tsconfig.json', 'vite.config.js', 'vite.config.ts',
143
+ 'next.config.js', 'next.config.ts', 'nuxt.config.js',
144
+ 'requirements.txt', 'pyproject.toml', 'setup.py', 'Pipfile',
145
+ 'pubspec.yaml', 'Cargo.toml', 'go.mod', 'pom.xml', 'build.gradle',
146
+ 'Makefile', 'Dockerfile', 'docker-compose.yml', 'docker-compose.yaml',
147
+ '.eslintrc.js', '.prettierrc', 'tailwind.config.js', 'postcss.config.js',
148
+ 'manage.py', 'app.json', 'eas.json',
149
+ ];
150
+
151
+ for (const file of configFiles) {
152
+ const filePath = path.join(this.rootDir, file);
153
+ if (fs.existsSync(filePath)) {
154
+ this.result.configFiles.push(file);
155
+ }
156
+ }
157
+ }
158
+
159
+ detectLanguage() {
160
+ const langMap = {
161
+ '.js': 'JavaScript', '.jsx': 'React JSX', '.ts': 'TypeScript', '.tsx': 'React TSX',
162
+ '.py': 'Python', '.dart': 'Dart', '.rs': 'Rust', '.go': 'Go',
163
+ '.java': 'Java', '.kt': 'Kotlin', '.swift': 'Swift',
164
+ '.html': 'HTML', '.css': 'CSS', '.scss': 'SCSS',
165
+ '.sql': 'SQL', '.sh': 'Shell',
166
+ };
167
+
168
+ const counts = {};
169
+ const walk = (dir, depth = 0) => {
170
+ if (depth > 5) return;
171
+ try {
172
+ const items = fs.readdirSync(dir, { withFileTypes: true });
173
+ for (const item of items) {
174
+ if (item.isDirectory() && !item.name.startsWith('.') &&
175
+ item.name !== 'node_modules' && item.name !== '__pycache__') {
176
+ walk(path.join(dir, item.name), depth + 1);
177
+ } else if (item.isFile()) {
178
+ const ext = path.extname(item.name).toLowerCase();
179
+ if (langMap[ext]) {
180
+ counts[langMap[ext]] = (counts[langMap[ext]] || 0) + 1;
181
+ }
182
+ }
183
+ }
184
+ } catch (e) {}
185
+ };
186
+
187
+ walk(this.rootDir);
188
+ this.result.languages = Object.entries(counts)
189
+ .sort((a, b) => b[1] - a[1])
190
+ .map(([lang]) => lang);
191
+ }
192
+
193
+ detectFramework() {
194
+ const frameworks = [];
195
+
196
+ // Check package.json
197
+ const pkgPath = path.join(this.rootDir, 'package.json');
198
+ if (fs.existsSync(pkgPath)) {
199
+ try {
200
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
201
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
202
+ if (deps.react) frameworks.push('React');
203
+ if (deps.vue) frameworks.push('Vue');
204
+ if (deps.angular || deps['@angular/core']) frameworks.push('Angular');
205
+ if (deps.svelte) frameworks.push('Svelte');
206
+ if (deps.next) frameworks.push('Next.js');
207
+ if (deps.nuxt) frameworks.push('Nuxt');
208
+ if (deps.express) frameworks.push('Express');
209
+ if (deps.fastify) frameworks.push('Fastify');
210
+ if (deps.tailwindcss) frameworks.push('Tailwind CSS');
211
+ if (deps.typescript) frameworks.push('TypeScript');
212
+ if (deps.fastapi || deps['fastapi']) frameworks.push('FastAPI');
213
+ if (deps.flutter || deps.flutter_test) frameworks.push('Flutter');
214
+ } catch (e) {}
215
+ }
216
+
217
+ // Check requirements.txt
218
+ const reqPath = path.join(this.rootDir, 'requirements.txt');
219
+ if (fs.existsSync(reqPath)) {
220
+ const req = fs.readFileSync(reqPath, 'utf8');
221
+ if (req.includes('Django')) frameworks.push('Django');
222
+ if (req.includes('flask') || req.includes('Flask')) frameworks.push('Flask');
223
+ if (req.includes('fastapi') || req.includes('FastAPI')) frameworks.push('FastAPI');
224
+ if (req.includes('torch')) frameworks.push('PyTorch');
225
+ if (req.includes('tensorflow')) frameworks.push('TensorFlow');
226
+ }
227
+
228
+ // Check pubspec.yaml
229
+ if (fs.existsSync(path.join(this.rootDir, 'pubspec.yaml'))) {
230
+ frameworks.push('Flutter');
231
+ }
232
+
233
+ // Check manage.py
234
+ if (fs.existsSync(path.join(this.rootDir, 'manage.py'))) {
235
+ frameworks.push('Django');
236
+ }
237
+
238
+ this.result.frameworks = frameworks;
239
+ }
240
+
241
+ detectDependencies() {
242
+ const deps = {};
243
+
244
+ // Node.js dependencies
245
+ const pkgPath = path.join(this.rootDir, 'package.json');
246
+ if (fs.existsSync(pkgPath)) {
247
+ try {
248
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
249
+ deps.npm = {
250
+ dependencies: Object.keys(pkg.dependencies || {}),
251
+ devDependencies: Object.keys(pkg.devDependencies || {}),
252
+ };
253
+ } catch (e) {}
254
+ }
255
+
256
+ // Python dependencies
257
+ const reqPath = path.join(this.rootDir, 'requirements.txt');
258
+ if (fs.existsSync(reqPath)) {
259
+ const req = fs.readFileSync(reqPath, 'utf8');
260
+ deps.pip = req.split('\n').filter(l => l.trim() && !l.startsWith('#'));
261
+ }
262
+
263
+ this.result.dependencies = deps;
264
+ }
265
+
266
+ detectEntryPoints() {
267
+ const entryPoints = [];
268
+
269
+ // Common entry points
270
+ const commonEntries = [
271
+ 'src/index.js', 'src/index.ts', 'src/main.js', 'src/main.ts',
272
+ 'src/App.jsx', 'src/App.tsx', 'src/main.jsx', 'src/main.tsx',
273
+ 'index.js', 'index.ts', 'main.js', 'main.ts',
274
+ 'app.py', 'main.py', 'manage.py',
275
+ 'lib/main.dart', 'lib/main.dart',
276
+ 'server.js', 'server.ts',
277
+ 'src/server.js', 'src/server.ts',
278
+ ];
279
+
280
+ for (const entry of commonEntries) {
281
+ if (fs.existsSync(path.join(this.rootDir, entry))) {
282
+ entryPoints.push(entry);
283
+ }
284
+ }
285
+
286
+ this.result.entryPoints = entryPoints;
287
+ }
288
+
289
+ buildStructure() {
290
+ const structure = { dirs: [], files: [] };
291
+ const walk = (dir, prefix = '', depth = 0) => {
292
+ if (depth > 3) return;
293
+ try {
294
+ const items = fs.readdirSync(dir, { withFileTypes: true }).slice(0, 50);
295
+ for (const item of items) {
296
+ if (item.isDirectory() && !item.name.startsWith('.') &&
297
+ item.name !== 'node_modules' && item.name !== '__pycache__') {
298
+ structure.dirs.push(`${prefix}${item.name}/`);
299
+ walk(path.join(dir, item.name), `${prefix} `, depth + 1);
300
+ } else if (item.isFile()) {
301
+ structure.files.push(`${prefix}${item.name}`);
302
+ }
303
+ }
304
+ } catch (e) {}
305
+ };
306
+
307
+ walk(this.rootDir);
308
+ this.result.structure = structure;
309
+ }
310
+
311
+ getSummary() {
312
+ const r = this.result;
313
+ let summary = `Project Type: ${r.type}\n`;
314
+ summary += `Languages: ${r.languages.join(', ') || 'Unknown'}\n`;
315
+ summary += `Frameworks: ${r.frameworks.join(', ') || 'None detected'}\n`;
316
+ summary += `Config Files: ${r.configFiles.length}\n`;
317
+ summary += `Entry Points: ${r.entryPoints.join(', ') || 'None found'}\n`;
318
+ return summary;
319
+ }
320
+ }
321
+
322
+ // ─── Codebase Search Engine ─────────────────────────────
323
+
324
+ class CodebaseSearch {
325
+ constructor(rootDir = process.cwd()) {
326
+ this.rootDir = rootDir;
327
+ this.ignoreDirs = ['node_modules', '.git', '__pycache__', '.venv', 'venv',
328
+ '.idea', '.vscode', 'dist', 'build', '.next', 'coverage'];
329
+ this.ignoreFiles = ['.gitignore', '.env', 'package-lock.json', 'yarn.lock',
330
+ '*.pyc', '*.pyo', 'db.sqlite3'];
331
+ }
332
+
333
+ searchCode(query) {
334
+ const results = [];
335
+ const walk = (dir, depth = 0) => {
336
+ if (depth > 8) return;
337
+ try {
338
+ const items = fs.readdirSync(dir, { withFileTypes: true });
339
+ for (const item of items) {
340
+ if (item.isDirectory() && !this.ignoreDirs.includes(item.name)) {
341
+ walk(path.join(dir, item.name), depth + 1);
342
+ } else if (item.isFile() && !this.shouldIgnore(item.name)) {
343
+ this.searchInFile(path.join(dir, item.name), query, results);
344
+ }
345
+ }
346
+ } catch (e) {}
347
+ };
348
+
349
+ walk(this.rootDir);
350
+ return results;
351
+ }
352
+
353
+ searchInFile(filePath, query, results) {
354
+ try {
355
+ const content = fs.readFileSync(filePath, 'utf8');
356
+ const lines = content.split('\n');
357
+ const matches = [];
358
+
359
+ lines.forEach((line, index) => {
360
+ if (line.toLowerCase().includes(query.toLowerCase())) {
361
+ matches.push({
362
+ line: index + 1,
363
+ content: line.trim(),
364
+ });
365
+ }
366
+ });
367
+
368
+ if (matches.length > 0) {
369
+ results.push({
370
+ file: path.relative(this.rootDir, filePath),
371
+ matches,
372
+ score: matches.length,
373
+ });
374
+ }
375
+ } catch (e) {}
376
+ }
377
+
378
+ findFiles(pattern) {
379
+ const results = [];
380
+ const walk = (dir, depth = 0) => {
381
+ if (depth > 8) return;
382
+ try {
383
+ const items = fs.readdirSync(dir, { withFileTypes: true });
384
+ for (const item of items) {
385
+ if (item.isDirectory() && !this.ignoreDirs.includes(item.name)) {
386
+ walk(path.join(dir, item.name), depth + 1);
387
+ } else if (item.isFile() && item.name.includes(pattern)) {
388
+ results.push(path.relative(this.rootDir, path.join(dir, item.name)));
389
+ }
390
+ }
391
+ } catch (e) {}
392
+ };
393
+
394
+ walk(this.rootDir);
395
+ return results;
396
+ }
397
+
398
+ findFunctions(query) {
399
+ const results = [];
400
+ const functionPatterns = [
401
+ /function\s+(\w+)/g,
402
+ /const\s+(\w+)\s*=\s*(?:async\s+)?\(/g,
403
+ /def\s+(\w+)/g,
404
+ /class\s+(\w+)/g,
405
+ /export\s+(?:default\s+)?(?:function|const|class)\s+(\w+)/g,
406
+ ];
407
+
408
+ const walk = (dir, depth = 0) => {
409
+ if (depth > 8) return;
410
+ try {
411
+ const items = fs.readdirSync(dir, { withFileTypes: true });
412
+ for (const item of items) {
413
+ if (item.isDirectory() && !this.ignoreDirs.includes(item.name)) {
414
+ walk(path.join(dir, item.name), depth + 1);
415
+ } else if (item.isFile() && !this.shouldIgnore(item.name)) {
416
+ this.findInFile(path.join(dir, item.name), query, functionPatterns, results);
417
+ }
418
+ }
419
+ } catch (e) {}
420
+ };
421
+
422
+ walk(this.rootDir);
423
+ return results;
424
+ }
425
+
426
+ findInFile(filePath, query, patterns, results) {
427
+ try {
428
+ const content = fs.readFileSync(filePath, 'utf8');
429
+ const lines = content.split('\n');
430
+
431
+ lines.forEach((line, index) => {
432
+ if (line.toLowerCase().includes(query.toLowerCase())) {
433
+ for (const pattern of patterns) {
434
+ const match = line.match(pattern);
435
+ if (match) {
436
+ results.push({
437
+ file: path.relative(this.rootDir, filePath),
438
+ line: index + 1,
439
+ name: match[1] || match[0],
440
+ content: line.trim(),
441
+ });
442
+ }
443
+ }
444
+ }
445
+ });
446
+ } catch (e) {}
447
+ }
448
+
449
+ shouldIgnore(filename) {
450
+ return this.ignoreFiles.some(pattern => {
451
+ if (pattern.startsWith('*')) return filename.endsWith(pattern.slice(1));
452
+ return filename === pattern;
453
+ });
454
+ }
455
+
456
+ getContextForQuery(query) {
457
+ const codeResults = this.searchCode(query);
458
+ const fileResults = this.findFiles(query);
459
+ const funcResults = this.findFunctions(query);
460
+
461
+ let context = `Search Results for "${query}":\n\n`;
462
+
463
+ if (codeResults.length > 0) {
464
+ context += `Code Matches (${codeResults.length} files):\n`;
465
+ for (const result of codeResults.slice(0, 5)) {
466
+ context += ` ${result.file}:\n`;
467
+ for (const match of result.matches.slice(0, 3)) {
468
+ context += ` Line ${match.line}: ${match.content}\n`;
469
+ }
470
+ }
471
+ }
472
+
473
+ if (fileResults.length > 0) {
474
+ context += `\nFiles Found (${fileResults.length}):\n`;
475
+ for (const file of fileResults.slice(0, 10)) {
476
+ context += ` ${file}\n`;
477
+ }
478
+ }
479
+
480
+ if (funcResults.length > 0) {
481
+ context += `\nFunctions Found (${funcResults.length}):\n`;
482
+ for (const func of funcResults.slice(0, 10)) {
483
+ context += ` ${func.name} in ${func.file}:${func.line}\n`;
484
+ }
485
+ }
486
+
487
+ return context;
488
+ }
489
+ }
490
+
491
+ // ─── Tool System ────────────────────────────────────────
492
+
493
+ class ToolSystem {
494
+ constructor(rootDir = process.cwd()) {
495
+ this.rootDir = rootDir;
496
+ this.projectDetector = new ProjectDetector(rootDir);
497
+ this.codebaseSearch = new CodebaseSearch(rootDir);
498
+ this.projectInfo = null;
499
+
500
+ this.tools = {
501
+ read_file: this.readFile.bind(this),
502
+ write_file: this.writeFile.bind(this),
503
+ edit_file: this.editFile.bind(this),
504
+ delete_file: this.deleteFile.bind(this),
505
+ create_file: this.createFile.bind(this),
506
+ list_directory: this.listDirectory.bind(this),
507
+ search_files: this.searchFiles.bind(this),
508
+ search_text: this.searchText.bind(this),
509
+ search_code: this.searchCode.bind(this),
510
+ find_functions: this.findFunctions.bind(this),
511
+ run_command: this.runCommand.bind(this),
512
+ get_project_info: this.getProjectInfo.bind(this),
513
+ detect_project: this.detectProject.bind(this),
514
+ };
515
+ }
516
+
517
+ readFile(filePath) {
518
+ try {
519
+ const absPath = path.resolve(this.rootDir, filePath);
520
+ if (!fs.existsSync(absPath)) {
521
+ return { success: false, error: `File not found: ${filePath}` };
522
+ }
523
+ const content = fs.readFileSync(absPath, 'utf8');
524
+ return { success: true, content, path: absPath };
525
+ } catch (e) {
526
+ return { success: false, error: e.message };
527
+ }
528
+ }
529
+
530
+ writeFile(filePath, content) {
531
+ try {
532
+ const absPath = path.resolve(this.rootDir, filePath);
533
+ const dir = path.dirname(absPath);
534
+ if (!fs.existsSync(dir)) {
535
+ fs.mkdirSync(dir, { recursive: true });
536
+ }
537
+ fs.writeFileSync(absPath, content, 'utf8');
538
+ return { success: true, path: absPath };
539
+ } catch (e) {
540
+ return { success: false, error: e.message };
541
+ }
542
+ }
543
+
544
+ createFile(filePath, content) {
545
+ const absPath = path.resolve(this.rootDir, filePath);
546
+ if (fs.existsSync(absPath)) {
547
+ return { success: false, error: `File already exists: ${filePath}` };
548
+ }
549
+ return this.writeFile(filePath, content);
550
+ }
551
+
552
+ editFile(filePath, oldContent, newContent) {
553
+ try {
554
+ const absPath = path.resolve(this.rootDir, filePath);
555
+ if (!fs.existsSync(absPath)) {
556
+ return { success: false, error: `File not found: ${filePath}` };
557
+ }
558
+ let content = fs.readFileSync(absPath, 'utf8');
559
+ if (!content.includes(oldContent)) {
560
+ return { success: false, error: 'Old content not found in file' };
561
+ }
562
+ content = content.replace(oldContent, newContent);
563
+ fs.writeFileSync(absPath, content, 'utf8');
564
+ return { success: true, path: absPath };
565
+ } catch (e) {
566
+ return { success: false, error: e.message };
567
+ }
568
+ }
569
+
570
+ deleteFile(filePath) {
571
+ try {
572
+ const absPath = path.resolve(this.rootDir, filePath);
573
+ if (!fs.existsSync(absPath)) {
574
+ return { success: false, error: `File not found: ${filePath}` };
575
+ }
576
+ fs.unlinkSync(absPath);
577
+ return { success: true, path: absPath };
578
+ } catch (e) {
579
+ return { success: false, error: e.message };
580
+ }
581
+ }
582
+
583
+ listDirectory(dirPath = '.') {
584
+ try {
585
+ const absPath = path.resolve(this.rootDir, dirPath);
586
+ const items = fs.readdirSync(absPath, { withFileTypes: true });
587
+ const result = items.map(item => ({
588
+ name: item.name,
589
+ type: item.isDirectory() ? 'directory' : 'file',
590
+ path: path.join(absPath, item.name),
591
+ }));
592
+ return { success: true, items: result, path: absPath };
593
+ } catch (e) {
594
+ return { success: false, error: e.message };
595
+ }
596
+ }
597
+
598
+ searchFiles(pattern) {
599
+ try {
600
+ const results = this.codebaseSearch.findFiles(pattern);
601
+ return { success: true, results, count: results.length };
602
+ } catch (e) {
603
+ return { success: false, error: e.message };
604
+ }
605
+ }
606
+
607
+ searchText(searchText, filePath) {
608
+ try {
609
+ const absPath = path.resolve(this.rootDir, filePath);
610
+ if (!fs.existsSync(absPath)) {
611
+ return { success: false, error: `File not found: ${filePath}` };
612
+ }
613
+ const content = fs.readFileSync(absPath, 'utf8');
614
+ const lines = content.split('\n');
615
+ const matches = [];
616
+
617
+ lines.forEach((line, index) => {
618
+ if (line.toLowerCase().includes(searchText.toLowerCase())) {
619
+ matches.push({ line: index + 1, content: line.trim() });
620
+ }
621
+ });
622
+
623
+ return { success: true, matches, count: matches.length };
624
+ } catch (e) {
625
+ return { success: false, error: e.message };
626
+ }
627
+ }
628
+
629
+ searchCode(query) {
630
+ try {
631
+ const results = this.codebaseSearch.searchCode(query);
632
+ return { success: true, results, count: results.length };
633
+ } catch (e) {
634
+ return { success: false, error: e.message };
635
+ }
636
+ }
637
+
638
+ findFunctions(query) {
639
+ try {
640
+ const results = this.codebaseSearch.findFunctions(query);
641
+ return { success: true, results, count: results.length };
642
+ } catch (e) {
643
+ return { success: false, error: e.message };
644
+ }
645
+ }
646
+
647
+ runCommand(command, options = {}) {
648
+ try {
649
+ const result = spawnSync(command, {
650
+ shell: true,
651
+ encoding: 'utf8',
652
+ cwd: options.cwd || this.rootDir,
653
+ timeout: options.timeout || 30000,
654
+ });
655
+
656
+ return {
657
+ success: result.status === 0,
658
+ stdout: result.stdout,
659
+ stderr: result.stderr,
660
+ exitCode: result.status,
661
+ };
662
+ } catch (e) {
663
+ return { success: false, error: e.message };
664
+ }
665
+ }
666
+
667
+ getProjectInfo() {
668
+ if (!this.projectInfo) {
669
+ this.projectInfo = this.projectDetector.detect();
670
+ }
671
+ return this.projectInfo;
672
+ }
673
+
674
+ detectProject() {
675
+ this.projectInfo = this.projectDetector.detect();
676
+ return this.projectInfo;
677
+ }
678
+ }
679
+
680
+ // ─── Main Agent ─────────────────────────────────────────
681
+
682
+ class HybardAgent {
683
+ constructor(options = {}) {
684
+ this.ai = new HybardAI(options);
685
+ this.knowledge = new KnowledgeBase();
686
+ this.memory = new Memory();
687
+ this.tools = new ToolSystem(options.rootDir || process.cwd());
688
+ this.maxIterations = 10;
689
+ this.conversationHistory = [];
690
+
691
+ this.systemPrompt = this.buildSystemPrompt();
692
+ }
693
+
694
+ buildSystemPrompt() {
695
+ return `You are Hybard AI Agent — a full-stack AI coding assistant with real LLM integration.
696
+
697
+ IDENTITY:
698
+ - You are an expert coding agent with knowledge across 35 domains
699
+ - You can write, read, edit, and delete files
700
+ - You can execute terminal commands
701
+ - You can search and understand codebases
702
+ - You can build complete applications
703
+ - You support Bengali (বাংলা) and English
704
+
705
+ AGENT LOOP:
706
+ When given a task, follow this loop:
707
+ 1. THINK — Understand the request, plan your approach
708
+ 2. ACT — Execute tools (read files, write code, run commands)
709
+ 3. OBSERVE — Check results, read errors
710
+ 4. EVALUATE — Did it work? If yes, done. If no, fix.
711
+ 5. FIX — Generate a fix and retry
712
+
713
+ TOOL CALLING:
714
+ You can use these tools by outputting JSON in your response:
715
+ {"tool": "read_file", "path": "file.js"}
716
+ {"tool": "write_file", "path": "file.js", "content": "..."}
717
+ {"tool": "edit_file", "path": "file.js", "old": "...", "new": "..."}
718
+ {"tool": "run_command", "command": "npm install"}
719
+ {"tool": "list_directory", "path": "."}
720
+ {"tool": "search_files", "pattern": ".js"}
721
+ {"tool": "search_text", "text": "function", "file": "index.js"}
722
+ {"tool": "search_code", "query": "login"}
723
+ {"tool": "find_functions", "query": "authenticate"}
724
+ {"tool": "get_project_info"}
725
+ {"tool": "detect_project"}
726
+
727
+ CODEBASE UNDERSTANDING:
728
+ When asked about code, use these tools:
729
+ - search_code: Find code by keyword
730
+ - find_functions: Find function definitions
731
+ - read_file: Read specific files
732
+ - get_project_info: Understand project structure
733
+
734
+ PROJECT DETECTION:
735
+ Your agent automatically detects:
736
+ - Project type (Node, Python, React, Flutter, etc.)
737
+ - Languages used
738
+ - Frameworks and libraries
739
+ - Dependencies
740
+ - Entry points
741
+ - File structure
742
+
743
+ RESPONSE FORMAT:
744
+ - Always respond in the user's language
745
+ - Keep code comments in English
746
+ - Use markdown code blocks for code
747
+ - Be concise but thorough
748
+ - Always verify your work
749
+
750
+ IMPORTANT:
751
+ - Never hardcode secrets or API keys
752
+ - Always handle errors gracefully
753
+ - Write clean, readable code
754
+ - Test your code when possible`;
755
+ }
756
+
757
+ async chat(message) {
758
+ // Add to memory
759
+ this.memory.addShortTerm('user', message);
760
+ this.conversationHistory.push({ role: 'user', content: message });
761
+
762
+ // Build context
763
+ const context = this.buildContext(message);
764
+
765
+ // Call AI with real LLM
766
+ let response;
767
+ try {
768
+ response = await this.ai.chat(message, {
769
+ system: this.systemPrompt + '\n\n' + context,
770
+ });
771
+ } catch (err) {
772
+ console.error(`[LLM Error] ${err.message}`);
773
+ response = `[Error] Failed to get response from AI: ${err.message}`;
774
+ }
775
+
776
+ // Process response and execute tools
777
+ const result = await this.processResponse(response);
778
+
779
+ // Add to memory
780
+ this.memory.addShortTerm('assistant', result);
781
+ this.conversationHistory.push({ role: 'assistant', content: result });
782
+
783
+ return result;
784
+ }
785
+
786
+ buildContext(message) {
787
+ let context = '';
788
+
789
+ // Add project info
790
+ try {
791
+ const projectInfo = this.tools.getProjectInfo();
792
+ context += `\n\nPROJECT CONTEXT:\n`;
793
+ context += `Type: ${projectInfo.type}\n`;
794
+ context += `Languages: ${projectInfo.languages.join(', ')}\n`;
795
+ context += `Frameworks: ${projectInfo.frameworks.join(', ')}\n`;
796
+ context += `Entry Points: ${projectInfo.entryPoints.join(', ')}\n`;
797
+ context += `Config Files: ${projectInfo.configFiles.join(', ')}`;
798
+ } catch (e) {}
799
+
800
+ // Add codebase search if query looks like code-related
801
+ if (this.isCodeRelated(message)) {
802
+ try {
803
+ const searchContext = this.tools.codebaseSearch.getContextForQuery(message);
804
+ if (searchContext.length > 50) {
805
+ context += `\n\nCODEBASE SEARCH:\n${searchContext}`;
806
+ }
807
+ } catch (e) {}
808
+ }
809
+
810
+ // Add conversation history
811
+ if (this.conversationHistory.length > 0) {
812
+ context += '\n\nCONVERSATION HISTORY:\n';
813
+ context += this.conversationHistory.slice(-5).map(m =>
814
+ `${m.role}: ${m.content.slice(0, 200)}`
815
+ ).join('\n');
816
+ }
817
+
818
+ return context;
819
+ }
820
+
821
+ isCodeRelated(message) {
822
+ const codeKeywords = [
823
+ 'code', 'function', 'class', 'file', 'module', 'import', 'export',
824
+ 'component', 'hook', 'api', 'endpoint', 'route', 'controller',
825
+ 'model', 'view', 'service', 'auth', 'login', 'user', 'test',
826
+ 'build', 'deploy', 'fix', 'bug', 'error', 'debug',
827
+ ];
828
+ const lower = message.toLowerCase();
829
+ return codeKeywords.some(kw => lower.includes(kw));
830
+ }
831
+
832
+ async processResponse(response) {
833
+ let finalResponse = response;
834
+ let iterations = 0;
835
+
836
+ // Check for tool calls in response
837
+ const toolCallRegex = /\{"tool":\s*"[^"]+?"[^}]*\}/g;
838
+ const toolCalls = response.match(toolCallRegex);
839
+
840
+ if (toolCalls && toolCalls.length > 0) {
841
+ for (const toolCallStr of toolCalls) {
842
+ try {
843
+ const toolCall = JSON.parse(toolCallStr);
844
+ console.log(`[Tool] Executing: ${toolCall.tool}...`);
845
+
846
+ const result = this.executeTool(toolCall);
847
+
848
+ // Add tool result to context
849
+ finalResponse += `\n\nTool Result (${toolCall.tool}):\n${JSON.stringify(result, null, 2)}`;
850
+
851
+ // If tool failed, ask AI to fix
852
+ if (!result.success && iterations < this.maxIterations) {
853
+ const fixPrompt = `The tool call failed. Error: ${result.error}\n\nPlease fix the issue and try again.`;
854
+ const fixResponse = await this.ai.chat(fixPrompt, {
855
+ system: this.systemPrompt,
856
+ });
857
+ finalResponse += '\n\n' + fixResponse;
858
+ iterations++;
859
+ }
860
+ } catch (e) {
861
+ finalResponse += `\n\nTool Error: ${e.message}`;
862
+ }
863
+ }
864
+ }
865
+
866
+ return finalResponse;
867
+ }
868
+
869
+ executeTool(toolCall) {
870
+ const { tool, ...params } = toolCall;
871
+
872
+ if (!this.tools.tools[tool]) {
873
+ return { success: false, error: `Unknown tool: ${tool}` };
874
+ }
875
+
876
+ return this.tools.tools[tool](...Object.values(params));
877
+ }
878
+
879
+ // ─── Quick Methods ──────────────────────────────────
880
+
881
+ async generateCode(prompt, language = 'python') {
882
+ const system = `You are an expert ${language} programmer. Generate clean, working code.
883
+ Respond ONLY with the code in a markdown code block. No explanations unless asked.`;
884
+ return this.ai.chat(prompt, { system });
885
+ }
886
+
887
+ async fixCode(code, error = '') {
888
+ const system = `You are a debugging expert. Fix the given code.
889
+ Return ONLY the corrected code in a markdown code block.`;
890
+ const msg = error
891
+ ? `Fix this code. Error: ${error}\n\n\`\`\`\n${code}\n\`\`\``
892
+ : `Fix any issues in this code:\n\n\`\`\`\n${code}\n\`\`\``;
893
+ return this.ai.chat(msg, { system });
894
+ }
895
+
896
+ async explainCode(code) {
897
+ const system = `You are a code analysis expert. Explain the given code clearly and concisely.
898
+ Cover: what it does, how it works, key patterns, and potential issues.`;
899
+ return this.ai.chat(`Explain this code:\n\n\`\`\`\n${code}\n\`\`\``, { system });
900
+ }
901
+
902
+ async createProject(goal) {
903
+ const system = `You are a project scaffolding expert. Create project files for the given goal.
904
+ Return each file in this format:
905
+ ## filename.ext
906
+ \`\`\`language
907
+ file content here
908
+ \`\`\`
909
+ Include all necessary files (main code, config, tests, README).`;
910
+ return this.ai.chat(`Create a project scaffold for: ${goal}`, { system });
911
+ }
912
+
913
+ async writeCodeToFile(code, filePath) {
914
+ const result = this.tools.writeFile(filePath, code);
915
+ if (result.success) {
916
+ return `File created successfully: ${filePath}`;
917
+ } else {
918
+ return `Failed to create file: ${result.error}`;
919
+ }
920
+ }
921
+
922
+ async runCommand(command) {
923
+ const result = this.tools.runCommand(command);
924
+ if (result.success) {
925
+ return `Command executed successfully:\n${result.stdout}`;
926
+ } else {
927
+ return `Command failed:\n${result.stderr || result.error}`;
928
+ }
929
+ }
930
+
931
+ // ─── Interactive Chat ──────────────────────────────
932
+
933
+ async startChat() {
934
+ const readline = require('readline');
935
+ const rl = readline.createInterface({
936
+ input: process.stdin,
937
+ output: process.stdout,
938
+ prompt: 'Hybard > ',
939
+ });
940
+
941
+ console.log('\n╔════════════════════════════════════════════════════════════╗');
942
+ console.log('║ Hybard AI Agent — Full Stack ║');
943
+ console.log('║ ║');
944
+ console.log('║ Connected to Real LLM Provider ║');
945
+ console.log('║ Codebase Understanding Active ║');
946
+ console.log('║ Project Detection Active ║');
947
+ console.log('║ ║');
948
+ console.log('║ Commands: ║');
949
+ console.log('║ /help — Show capabilities ║');
950
+ console.log('║ /clear — Clear conversation ║');
951
+ console.log('║ /memory — Show memory ║');
952
+ console.log('║ /project — Show project info ║');
953
+ console.log('║ /search — Search codebase ║');
954
+ console.log('║ exit — Exit ║');
955
+ console.log('╚════════════════════════════════════════════════════════════╝\n');
956
+
957
+ // Show connection status
958
+ if (!this.ai.getApiKey()) {
959
+ console.log('⚠ No API key detected. Running in offline mode.');
960
+ } else {
961
+ console.log(`✓ Connected to ${this.ai.config.provider} (${this.ai.config.model})`);
962
+ }
963
+
964
+ // Show project info
965
+ try {
966
+ const info = this.tools.getProjectInfo();
967
+ console.log(`✓ Project detected: ${info.languages.join(', ')} with ${info.frameworks.join(', ')}`);
968
+ } catch (e) {
969
+ console.log('ℹ No project detected in current directory');
970
+ }
971
+
972
+ console.log('');
973
+
974
+ rl.prompt();
975
+
976
+ rl.on('line', async (line) => {
977
+ const text = line.trim();
978
+ if (!text) { rl.prompt(); return; }
979
+
980
+ if (text.toLowerCase() === 'exit' || text.toLowerCase() === 'quit') {
981
+ console.log('\nGoodbye!\n');
982
+ rl.close();
983
+ return;
984
+ }
985
+
986
+ if (text === '/help') {
987
+ console.log('\n' + this.knowledge.getCapabilitiesSummary());
988
+ rl.prompt();
989
+ return;
990
+ }
991
+
992
+ if (text === '/clear') {
993
+ this.conversationHistory = [];
994
+ this.memory.shortTerm = [];
995
+ console.log('Conversation cleared.\n');
996
+ rl.prompt();
997
+ return;
998
+ }
999
+
1000
+ if (text === '/memory') {
1001
+ console.log('\nShort-term memory:');
1002
+ this.memory.shortTerm.forEach(m => {
1003
+ console.log(` ${m.role}: ${m.content.slice(0, 100)}`);
1004
+ });
1005
+ console.log('\nLong-term memory:');
1006
+ this.memory.longTerm.forEach(m => {
1007
+ console.log(` [${m.type}] ${m.content.slice(0, 100)}`);
1008
+ });
1009
+ rl.prompt();
1010
+ return;
1011
+ }
1012
+
1013
+ if (text === '/project') {
1014
+ try {
1015
+ const info = this.tools.detectProject();
1016
+ console.log('\nProject Info:');
1017
+ console.log(` Type: ${info.type}`);
1018
+ console.log(` Languages: ${info.languages.join(', ')}`);
1019
+ console.log(` Frameworks: ${info.frameworks.join(', ')}`);
1020
+ console.log(` Entry Points: ${info.entryPoints.join(', ')}`);
1021
+ console.log(` Config Files: ${info.configFiles.join(', ')}`);
1022
+ } catch (e) {
1023
+ console.log('No project detected in current directory.');
1024
+ }
1025
+ rl.prompt();
1026
+ return;
1027
+ }
1028
+
1029
+ if (text.startsWith('/search ')) {
1030
+ const query = text.slice(8);
1031
+ console.log(`\nSearching codebase for "${query}"...`);
1032
+ const results = this.tools.codebaseSearch.searchCode(query);
1033
+ if (results.length === 0) {
1034
+ console.log('No results found.');
1035
+ } else {
1036
+ console.log(`Found ${results.length} files:\n`);
1037
+ for (const result of results.slice(0, 10)) {
1038
+ console.log(` ${result.file}:`);
1039
+ for (const match of result.matches.slice(0, 3)) {
1040
+ console.log(` Line ${match.line}: ${match.content}`);
1041
+ }
1042
+ }
1043
+ }
1044
+ rl.prompt();
1045
+ return;
1046
+ }
1047
+
1048
+ // Normal message — send to AI
1049
+ process.stdout.write('\nHybard > Thinking...\n');
1050
+ try {
1051
+ const response = await this.chat(text);
1052
+ console.log('\n' + response + '\n');
1053
+ } catch (err) {
1054
+ console.error(`\n[Error] ${err.message}\n`);
1055
+ }
1056
+ rl.prompt();
1057
+ });
1058
+
1059
+ rl.on('close', () => process.exit(0));
1060
+ }
1061
+ }
1062
+
1063
+ module.exports = { HybardAgent, KnowledgeBase, Memory, ToolSystem, ProjectDetector, CodebaseSearch };