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/analyzer.js
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybard Project Analyzer — Scans and understands project structure.
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Detects project type (Node, Python, React, Flutter, Django, etc.)
|
|
6
|
+
* - Reads key files (package.json, requirements.txt, etc.)
|
|
7
|
+
* - Analyzes code patterns and conventions
|
|
8
|
+
* - Generates context-aware code suggestions
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const analyzer = new ProjectAnalyzer();
|
|
12
|
+
* const context = analyzer.analyze('/path/to/project');
|
|
13
|
+
* const suggestion = await analyzer.suggest('add user authentication');
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
const IGNORE_DIRS = [
|
|
20
|
+
'node_modules', '.git', '__pycache__', '.venv', 'venv', 'env',
|
|
21
|
+
'.idea', '.vscode', 'dist', 'build', '.next', '.nuxt',
|
|
22
|
+
'coverage', '.mypy_cache', '.pytest_cache', 'dart_tool',
|
|
23
|
+
'.gradle', 'android/.gradle', 'Pods', '.pub-cache',
|
|
24
|
+
'.dart_tool', '.packages', 'ios/Pods',
|
|
25
|
+
'.fvm', 'flutter-plugins', 'flutter-plugins-dependencies',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const IGNORE_FILES = [
|
|
29
|
+
'.gitignore', '.env', '.env.local', 'package-lock.json',
|
|
30
|
+
'yarn.lock', 'pnpm-lock.yaml', '*.pyc', '*.pyo',
|
|
31
|
+
'hybard_model.pth', 'dataset.txt', 'db.sqlite3',
|
|
32
|
+
'*.lock', '*.lockfile', 'pubspec.lock',
|
|
33
|
+
'build.gradle', 'settings.gradle', 'gradle.properties',
|
|
34
|
+
'Podfile.lock', '*.g.dart', '*.freezed.dart',
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// Source file extensions to prioritize
|
|
38
|
+
const SOURCE_EXTENSIONS = [
|
|
39
|
+
'.dart', '.py', '.js', '.ts', '.jsx', '.tsx',
|
|
40
|
+
'.html', '.css', '.scss', '.json', '.yaml', '.yml',
|
|
41
|
+
'.go', '.rs', '.java', '.kt', '.swift',
|
|
42
|
+
'.sql', '.sh', '.md',
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const CONFIG_FILES = [
|
|
46
|
+
'pubspec.yaml', 'package.json', 'requirements.txt', 'pyproject.toml',
|
|
47
|
+
'setup.py', 'Pipfile', 'Cargo.toml', 'go.mod', 'pom.xml',
|
|
48
|
+
'build.gradle', 'settings.gradle', 'Makefile', 'Dockerfile',
|
|
49
|
+
'docker-compose.yml', 'docker-compose.yaml',
|
|
50
|
+
'tsconfig.json', 'vite.config.js', 'vite.config.ts',
|
|
51
|
+
'next.config.js', 'next.config.ts', 'nuxt.config.js',
|
|
52
|
+
'tailwind.config.js', 'postcss.config.js',
|
|
53
|
+
'app.json', 'app.config.js', 'eas.json',
|
|
54
|
+
'README.md',
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const PROJECT_MARKERS = {
|
|
58
|
+
node: ['package.json'],
|
|
59
|
+
python: ['requirements.txt', 'pyproject.toml', 'setup.py', 'Pipfile'],
|
|
60
|
+
django: ['manage.py'],
|
|
61
|
+
react: ['next.config.js', 'vite.config.js', 'vite.config.ts'],
|
|
62
|
+
flutter: ['pubspec.yaml'],
|
|
63
|
+
rust: ['Cargo.toml'],
|
|
64
|
+
go: ['go.mod'],
|
|
65
|
+
java: ['pom.xml', 'build.gradle'],
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
class ProjectAnalyzer {
|
|
69
|
+
constructor(rootDir = process.cwd()) {
|
|
70
|
+
this.rootDir = rootDir;
|
|
71
|
+
this.projectType = null;
|
|
72
|
+
this.files = [];
|
|
73
|
+
this.keyFiles = {};
|
|
74
|
+
this.structure = {};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Full project analysis — scans files, detects type, reads key configs.
|
|
79
|
+
*/
|
|
80
|
+
analyze() {
|
|
81
|
+
this.files = this._scanFiles(this.rootDir);
|
|
82
|
+
this.projectType = this._detectProjectType();
|
|
83
|
+
this.keyFiles = this._readKeyFiles();
|
|
84
|
+
this.structure = this._buildStructure();
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
rootDir: this.rootDir,
|
|
88
|
+
projectType: this.projectType,
|
|
89
|
+
fileCount: this.files.length,
|
|
90
|
+
structure: this.structure,
|
|
91
|
+
keyFiles: this.keyFiles,
|
|
92
|
+
languages: this._detectLanguages(),
|
|
93
|
+
frameworks: this._detectFrameworks(),
|
|
94
|
+
summary: this._generateSummary(),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get context string for AI — includes project structure and key file contents.
|
|
100
|
+
*/
|
|
101
|
+
getContextForAI(maxTokens = 1000) {
|
|
102
|
+
const analysis = this.analyze();
|
|
103
|
+
let context = `Project: ${path.basename(this.rootDir)}\n`;
|
|
104
|
+
context += `Type: ${analysis.projectType}\n`;
|
|
105
|
+
context += `Files: ${analysis.fileCount}\n`;
|
|
106
|
+
context += `Languages: ${analysis.languages.join(', ')}\n\n`;
|
|
107
|
+
|
|
108
|
+
// Only include first 3 source files
|
|
109
|
+
const files = Object.entries(analysis.keyFiles).slice(0, 3);
|
|
110
|
+
for (const [name, content] of files) {
|
|
111
|
+
const truncated = content.slice(0, 200);
|
|
112
|
+
context += `--- ${name} ---\n${truncated}\n\n`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return context;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Generate a code suggestion that fits the existing project.
|
|
120
|
+
*/
|
|
121
|
+
async suggest(feature, modelRouter = null) {
|
|
122
|
+
const context = this.getContextForAI();
|
|
123
|
+
const prompt = `Based on this project context, ${feature}.
|
|
124
|
+
|
|
125
|
+
Project Context:
|
|
126
|
+
${context}
|
|
127
|
+
|
|
128
|
+
Generate code that:
|
|
129
|
+
1. Follows the existing code style and patterns
|
|
130
|
+
2. Uses the same frameworks and libraries
|
|
131
|
+
3. Fits naturally into the project structure
|
|
132
|
+
4. Includes proper imports and dependencies`;
|
|
133
|
+
|
|
134
|
+
if (modelRouter) {
|
|
135
|
+
return modelRouter.chat(prompt, {
|
|
136
|
+
system: 'You are a coding assistant that generates code fitting the user\'s existing project. Analyze the project context carefully and produce code that matches the style, patterns, and frameworks used.',
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return prompt; // Return the prompt if no model router
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ─── Internal Methods ───────────────────────────────
|
|
144
|
+
|
|
145
|
+
_scanFiles(dir, root = dir, depth = 0) {
|
|
146
|
+
const files = [];
|
|
147
|
+
if (depth > 10) return files; // prevent infinite recursion
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
for (const name of fs.readdirSync(dir)) {
|
|
151
|
+
if (IGNORE_DIRS.includes(name) || name.startsWith('.')) continue;
|
|
152
|
+
|
|
153
|
+
const fullPath = path.join(dir, name);
|
|
154
|
+
const stat = fs.statSync(fullPath);
|
|
155
|
+
|
|
156
|
+
if (stat.isDirectory()) {
|
|
157
|
+
files.push(...this._scanFiles(fullPath, root, depth + 1));
|
|
158
|
+
} else {
|
|
159
|
+
const relative = path.relative(root, fullPath);
|
|
160
|
+
if (!IGNORE_FILES.some(pattern => {
|
|
161
|
+
if (pattern.startsWith('*')) return relative.endsWith(pattern.slice(1));
|
|
162
|
+
return relative === pattern;
|
|
163
|
+
})) {
|
|
164
|
+
files.push(relative);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} catch (e) {}
|
|
169
|
+
return files.sort();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
_detectProjectType() {
|
|
173
|
+
for (const [type, markers] of Object.entries(PROJECT_MARKERS)) {
|
|
174
|
+
for (const marker of markers) {
|
|
175
|
+
if (this.files.some(f => f === marker || f.endsWith('/' + marker))) {
|
|
176
|
+
return type;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return 'unknown';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
_readKeyFiles() {
|
|
184
|
+
const keyFiles = {};
|
|
185
|
+
|
|
186
|
+
// Read config files
|
|
187
|
+
for (const file of CONFIG_FILES) {
|
|
188
|
+
const filePath = path.join(this.rootDir, file);
|
|
189
|
+
if (fs.existsSync(filePath)) {
|
|
190
|
+
try {
|
|
191
|
+
keyFiles[file] = fs.readFileSync(filePath, 'utf8').slice(0, 1500);
|
|
192
|
+
} catch (e) {}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Read source files (prioritize .dart, .py, .js, .ts, .tsx)
|
|
197
|
+
const sourceFiles = this.files
|
|
198
|
+
.filter(f => SOURCE_EXTENSIONS.some(ext => f.endsWith(ext)))
|
|
199
|
+
.sort((a, b) => {
|
|
200
|
+
// Prioritize main entry files
|
|
201
|
+
const aMain = a.includes('main.dart') || a.includes('main.py') || a.includes('index.js') || a.includes('App.');
|
|
202
|
+
const bMain = b.includes('main.dart') || b.includes('main.py') || b.includes('index.js') || b.includes('App.');
|
|
203
|
+
if (aMain && !bMain) return -1;
|
|
204
|
+
if (!aMain && bMain) return 1;
|
|
205
|
+
return 0;
|
|
206
|
+
})
|
|
207
|
+
.slice(0, 20); // Read up to 20 source files
|
|
208
|
+
|
|
209
|
+
for (const file of sourceFiles) {
|
|
210
|
+
const filePath = path.join(this.rootDir, file);
|
|
211
|
+
try {
|
|
212
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
213
|
+
keyFiles[file] = content.slice(0, 2000); // Up to 2000 chars per file
|
|
214
|
+
} catch (e) {}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return keyFiles;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
_buildStructure() {
|
|
221
|
+
const tree = this._buildTree(this.files);
|
|
222
|
+
return { tree, flat: this.files };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
_buildTree(files) {
|
|
226
|
+
const lines = [];
|
|
227
|
+
const dirs = new Set();
|
|
228
|
+
|
|
229
|
+
for (const file of files) {
|
|
230
|
+
const parts = file.split(path.sep);
|
|
231
|
+
// Show directory structure
|
|
232
|
+
for (let i = 1; i < parts.length; i++) {
|
|
233
|
+
const dir = parts.slice(0, i).join('/');
|
|
234
|
+
if (!dirs.has(dir)) {
|
|
235
|
+
dirs.add(dir);
|
|
236
|
+
const indent = ' '.repeat(i - 1);
|
|
237
|
+
lines.push(`${indent}${parts[i - 1]}/`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// Show file
|
|
241
|
+
const indent = ' '.repeat(Math.max(0, parts.length - 1));
|
|
242
|
+
lines.push(`${indent}${parts[parts.length - 1]}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return lines.slice(0, 100).join('\n');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
_detectLanguages() {
|
|
249
|
+
const langMap = {
|
|
250
|
+
'.js': 'JavaScript', '.jsx': 'React JSX', '.ts': 'TypeScript', '.tsx': 'React TSX',
|
|
251
|
+
'.py': 'Python', '.dart': 'Dart', '.rs': 'Rust', '.go': 'Go',
|
|
252
|
+
'.java': 'Java', '.kt': 'Kotlin', '.swift': 'Swift',
|
|
253
|
+
'.html': 'HTML', '.css': 'CSS', '.scss': 'SCSS',
|
|
254
|
+
'.json': 'JSON', '.yaml': 'YAML', '.yml': 'YAML',
|
|
255
|
+
'.sql': 'SQL', '.sh': 'Shell', '.bash': 'Shell',
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const counts = {};
|
|
259
|
+
for (const file of this.files) {
|
|
260
|
+
const ext = path.extname(file).toLowerCase();
|
|
261
|
+
if (langMap[ext]) {
|
|
262
|
+
counts[langMap[ext]] = (counts[langMap[ext]] || 0) + 1;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return Object.entries(counts)
|
|
267
|
+
.sort((a, b) => b[1] - a[1])
|
|
268
|
+
.map(([lang]) => lang);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
_detectFrameworks() {
|
|
272
|
+
const frameworks = [];
|
|
273
|
+
|
|
274
|
+
// Check package.json — read full file for JSON parsing
|
|
275
|
+
const pkgPath = path.join(this.rootDir, 'package.json');
|
|
276
|
+
if (fs.existsSync(pkgPath)) {
|
|
277
|
+
try {
|
|
278
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
279
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
280
|
+
if (deps.react) frameworks.push('React');
|
|
281
|
+
if (deps.vue) frameworks.push('Vue');
|
|
282
|
+
if (deps.angular || deps['@angular/core']) frameworks.push('Angular');
|
|
283
|
+
if (deps.svelte) frameworks.push('Svelte');
|
|
284
|
+
if (deps.next) frameworks.push('Next.js');
|
|
285
|
+
if (deps.nuxt) frameworks.push('Nuxt');
|
|
286
|
+
if (deps.express) frameworks.push('Express');
|
|
287
|
+
if (deps.fastify) frameworks.push('Fastify');
|
|
288
|
+
if (deps.tailwindcss) frameworks.push('Tailwind CSS');
|
|
289
|
+
if (deps.typescript) frameworks.push('TypeScript');
|
|
290
|
+
} catch (e) {}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Check requirements.txt
|
|
294
|
+
if (this.keyFiles['requirements.txt']) {
|
|
295
|
+
const req = this.keyFiles['requirements.txt'];
|
|
296
|
+
if (req.includes('Django')) frameworks.push('Django');
|
|
297
|
+
if (req.includes('flask') || req.includes('Flask')) frameworks.push('Flask');
|
|
298
|
+
if (req.includes('fastapi') || req.includes('FastAPI')) frameworks.push('FastAPI');
|
|
299
|
+
if (req.includes('torch')) frameworks.push('PyTorch');
|
|
300
|
+
if (req.includes('tensorflow')) frameworks.push('TensorFlow');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Check pubspec.yaml
|
|
304
|
+
if (this.keyFiles['pubspec.yaml']) {
|
|
305
|
+
frameworks.push('Flutter');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Check manage.py
|
|
309
|
+
if (this.files.includes('manage.py')) {
|
|
310
|
+
if (!frameworks.includes('Django')) frameworks.push('Django');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return frameworks;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
_generateSummary() {
|
|
317
|
+
const langs = this._detectLanguages();
|
|
318
|
+
const frameworks = this._detectFrameworks();
|
|
319
|
+
const type = this.projectType;
|
|
320
|
+
|
|
321
|
+
let summary = `${type.charAt(0).toUpperCase() + type.slice(1)} project`;
|
|
322
|
+
if (langs.length > 0) summary += ` using ${langs.slice(0, 3).join(', ')}`;
|
|
323
|
+
if (frameworks.length > 0) summary += ` with ${frameworks.join(', ')}`;
|
|
324
|
+
summary += ` (${this.files.length} files)`;
|
|
325
|
+
|
|
326
|
+
return summary;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
module.exports = { ProjectAnalyzer };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybard Coding Agent — Simple, fast AI coding assistant.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { HybardAI } = require('./online');
|
|
8
|
+
|
|
9
|
+
class CodingAgent {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.ai = new HybardAI(options);
|
|
12
|
+
this.configDir = options.configDir || path.join(require('os').homedir(), '.hybard');
|
|
13
|
+
this.configFile = path.join(this.configDir, 'agent.json');
|
|
14
|
+
this.config = this._loadConfig();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_loadConfig() {
|
|
18
|
+
try {
|
|
19
|
+
if (fs.existsSync(this.configFile)) {
|
|
20
|
+
return JSON.parse(fs.readFileSync(this.configFile, 'utf8'));
|
|
21
|
+
}
|
|
22
|
+
} catch (e) {}
|
|
23
|
+
return {
|
|
24
|
+
defaultProvider: 'groq',
|
|
25
|
+
defaultModel: 'llama-3.3-70b-versatile',
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
init() {
|
|
30
|
+
return { projectType: 'unknown', summary: 'Project loaded' };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async chat(message, options = {}) {
|
|
34
|
+
const provider = options.provider || this.config.defaultProvider;
|
|
35
|
+
const model = options.model || this.config.defaultModel;
|
|
36
|
+
const system = 'You are Hybard AI, an expert coding assistant. Generate complete, working code.';
|
|
37
|
+
return this.ai.chat(message, { system, provider, model, ...options });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
extractCodeBlocks(response) {
|
|
41
|
+
const blocks = [];
|
|
42
|
+
const regex = /```(\w*)\n([\s\S]*?)```/g;
|
|
43
|
+
let match;
|
|
44
|
+
while ((match = regex.exec(response)) !== null) {
|
|
45
|
+
const ext = { python: 'py', javascript: 'js', typescript: 'ts', html: 'html', css: 'css' };
|
|
46
|
+
blocks.push({ filename: `code.${ext[match[1]] || 'txt'}`, code: match[2].trim() });
|
|
47
|
+
}
|
|
48
|
+
return blocks;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
writeCodeBlocks(blocks, targetDir = null) {
|
|
52
|
+
const dir = targetDir || process.cwd();
|
|
53
|
+
const written = [];
|
|
54
|
+
for (const block of blocks) {
|
|
55
|
+
const filePath = path.join(dir, block.filename);
|
|
56
|
+
const fileDir = path.dirname(filePath);
|
|
57
|
+
if (!fs.existsSync(fileDir)) fs.mkdirSync(fileDir, { recursive: true });
|
|
58
|
+
fs.writeFileSync(filePath, block.code, 'utf8');
|
|
59
|
+
written.push(filePath);
|
|
60
|
+
}
|
|
61
|
+
return written;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getModels() {
|
|
65
|
+
const { ModelRouter } = require('./models');
|
|
66
|
+
return new ModelRouter().listModels();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
setModel(provider, model) {
|
|
70
|
+
this.config.defaultProvider = provider;
|
|
71
|
+
this.config.defaultModel = model;
|
|
72
|
+
if (!fs.existsSync(this.configDir)) fs.mkdirSync(this.configDir, { recursive: true });
|
|
73
|
+
fs.writeFileSync(this.configFile, JSON.stringify(this.config, null, 2));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
clearHistory() {}
|
|
77
|
+
|
|
78
|
+
getModelInfo() {
|
|
79
|
+
return {
|
|
80
|
+
provider: this.config.defaultProvider,
|
|
81
|
+
model: this.config.defaultModel,
|
|
82
|
+
projectSummary: 'Project loaded',
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = { CodingAgent };
|