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,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 };