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/agent.js
ADDED
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
const { spawnSync } = require('child_process');
|
|
5
|
+
const { HybardAI } = require('./online');
|
|
6
|
+
|
|
7
|
+
const CONFIG_FILE = path.join(process.cwd(), 'hybard.config.json');
|
|
8
|
+
const DEFAULT_CONFIG = {
|
|
9
|
+
preferredLanguage: 'node',
|
|
10
|
+
includeTests: true,
|
|
11
|
+
codeStyle: 'simple',
|
|
12
|
+
useLocalModel: true,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const IGNORE_PATHS = ['node_modules', '.git', '__pycache__', '.vscode'];
|
|
16
|
+
const IGNORE_FILES = ['hybard_model.pth', 'dataset.txt'];
|
|
17
|
+
|
|
18
|
+
function loadConfig() {
|
|
19
|
+
if (!fs.existsSync(CONFIG_FILE)) return DEFAULT_CONFIG;
|
|
20
|
+
try {
|
|
21
|
+
const raw = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
22
|
+
const config = JSON.parse(raw);
|
|
23
|
+
return { ...DEFAULT_CONFIG, ...config };
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error(`Failed to read config ${CONFIG_FILE}: ${err.message}`);
|
|
26
|
+
return DEFAULT_CONFIG;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function saveConfig(config) {
|
|
31
|
+
safeWriteFile(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function askQuestion(message, defaultValue) {
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
37
|
+
const prompt = defaultValue ? `${message} [${defaultValue}]: ` : `${message}: `;
|
|
38
|
+
rl.question(prompt, (answer) => {
|
|
39
|
+
rl.close();
|
|
40
|
+
resolve(answer.trim() || defaultValue);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function promptInitConfig() {
|
|
46
|
+
console.log('Initializing Hybard config...');
|
|
47
|
+
const preferredLanguage = await askQuestion('Preferred project language (node/python/web)', 'node');
|
|
48
|
+
const includeTestsInput = await askQuestion('Include a test scaffold? (yes/no)', 'yes');
|
|
49
|
+
const codeStyle = await askQuestion('Preferred code style (simple/advanced)', 'simple');
|
|
50
|
+
const useLocalModelInput = await askQuestion('Use local model.py for repairs? (yes/no)', 'yes');
|
|
51
|
+
|
|
52
|
+
const includeTests = includeTestsInput.toLowerCase().startsWith('y');
|
|
53
|
+
const useLocalModel = useLocalModelInput.toLowerCase().startsWith('y');
|
|
54
|
+
const language = ['python', 'node', 'web'].includes(preferredLanguage.toLowerCase())
|
|
55
|
+
? preferredLanguage.toLowerCase()
|
|
56
|
+
: 'node';
|
|
57
|
+
|
|
58
|
+
const config = {
|
|
59
|
+
preferredLanguage: language,
|
|
60
|
+
includeTests,
|
|
61
|
+
codeStyle: codeStyle.toLowerCase() === 'advanced' ? 'advanced' : 'simple',
|
|
62
|
+
useLocalModel,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
saveConfig(config);
|
|
66
|
+
console.log(`Saved configuration to ${CONFIG_FILE}`);
|
|
67
|
+
return config;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function scanProjectFiles(dir, root = dir) {
|
|
71
|
+
const files = [];
|
|
72
|
+
try {
|
|
73
|
+
for (const name of fs.readdirSync(dir)) {
|
|
74
|
+
if (IGNORE_PATHS.includes(name) || name.startsWith('.')) continue;
|
|
75
|
+
const fullPath = path.join(dir, name);
|
|
76
|
+
const stat = fs.statSync(fullPath);
|
|
77
|
+
if (stat.isDirectory()) {
|
|
78
|
+
files.push(...scanProjectFiles(fullPath, root));
|
|
79
|
+
} else {
|
|
80
|
+
const relative = path.relative(root, fullPath);
|
|
81
|
+
if (IGNORE_FILES.includes(relative) || IGNORE_FILES.includes(name)) continue;
|
|
82
|
+
files.push(relative);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (err) {
|
|
86
|
+
// ignore unreadable directories
|
|
87
|
+
}
|
|
88
|
+
return files;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function detectProjectType(goal, config = DEFAULT_CONFIG) {
|
|
92
|
+
const text = goal.toLowerCase();
|
|
93
|
+
if (text.includes('flutter') || text.includes('dart')) return 'flutter';
|
|
94
|
+
if (text.includes('react') || text.includes('reactjs')) return 'react';
|
|
95
|
+
if (text.includes('landing page') || text.includes('landing') || text.includes('tailwind') || (text.includes('html') && text.includes('css')) || text.includes('website') || text.includes('frontend')) return 'html';
|
|
96
|
+
if (text.includes('django') || text.includes('dejano')) return 'django';
|
|
97
|
+
if (text.includes('python') || text.includes('flask')) return 'python';
|
|
98
|
+
if (text.includes('node') || text.includes('javascript') || text.includes('express') || text.includes('npm')) return 'node';
|
|
99
|
+
return config.preferredLanguage || 'node';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function createProject(goal, config) {
|
|
103
|
+
const type = detectProjectType(goal, config);
|
|
104
|
+
console.log(`Detected project type: ${type}`);
|
|
105
|
+
|
|
106
|
+
switch (type) {
|
|
107
|
+
case 'django':
|
|
108
|
+
createDjangoProject(goal, config);
|
|
109
|
+
break;
|
|
110
|
+
case 'python':
|
|
111
|
+
createPythonProject(goal, config);
|
|
112
|
+
break;
|
|
113
|
+
case 'flutter':
|
|
114
|
+
createFlutterProject(goal, config);
|
|
115
|
+
break;
|
|
116
|
+
case 'react':
|
|
117
|
+
createReactProject(goal, config);
|
|
118
|
+
break;
|
|
119
|
+
case 'html':
|
|
120
|
+
createHtmlCssProject(goal, config);
|
|
121
|
+
break;
|
|
122
|
+
case 'web':
|
|
123
|
+
createWebProject(goal, config);
|
|
124
|
+
break;
|
|
125
|
+
default:
|
|
126
|
+
createNodeProject(goal, config);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function safeWriteFile(filePath, content) {
|
|
132
|
+
const dir = path.dirname(filePath);
|
|
133
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
134
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
135
|
+
console.log(`Created ${path.relative(process.cwd(), filePath)}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function backupFile(filePath) {
|
|
139
|
+
const backupPath = `${filePath}.hybard.bak`;
|
|
140
|
+
if (!fs.existsSync(backupPath)) {
|
|
141
|
+
fs.copyFileSync(filePath, backupPath);
|
|
142
|
+
console.log(`Backed up ${path.relative(process.cwd(), filePath)} to ${path.relative(process.cwd(), backupPath)}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function commandExists(command) {
|
|
147
|
+
const check = process.platform === 'win32'
|
|
148
|
+
? spawnSync('where', [command], { encoding: 'utf8' })
|
|
149
|
+
: spawnSync('command', ['-v', command], { encoding: 'utf8' });
|
|
150
|
+
return check.status === 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function runCommand(command, args, options = {}) {
|
|
154
|
+
console.log(`Running: ${command} ${args.join(' ')}`);
|
|
155
|
+
const isWindows = process.platform === 'win32';
|
|
156
|
+
const result = spawnSync(command, args, {
|
|
157
|
+
encoding: 'utf8',
|
|
158
|
+
cwd: process.cwd(),
|
|
159
|
+
stdio: 'inherit',
|
|
160
|
+
shell: isWindows, // Use shell on Windows to find .cmd files
|
|
161
|
+
...options,
|
|
162
|
+
});
|
|
163
|
+
if (result.error) {
|
|
164
|
+
throw result.error;
|
|
165
|
+
}
|
|
166
|
+
if (result.status !== 0) {
|
|
167
|
+
throw new Error(`${command} exited with code ${result.status}`);
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function runLocalModel(prompt) {
|
|
173
|
+
const modelPath = path.join(__dirname, '..', 'model.py');
|
|
174
|
+
const pythonCmd = commandExists('python') ? 'python' : commandExists('python3') ? 'python3' : null;
|
|
175
|
+
if (!pythonCmd) {
|
|
176
|
+
throw new Error('Python is not available on PATH. Please install Python to run the local model.');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const result = spawnSync(pythonCmd, [modelPath, prompt], {
|
|
180
|
+
encoding: 'utf8',
|
|
181
|
+
cwd: process.cwd(),
|
|
182
|
+
maxBuffer: 1024 * 1024,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (result.error) {
|
|
186
|
+
throw result.error;
|
|
187
|
+
}
|
|
188
|
+
if (result.status !== 0) {
|
|
189
|
+
throw new Error(result.stderr || result.stdout || `model.py exited with code ${result.status}`);
|
|
190
|
+
}
|
|
191
|
+
return result.stdout;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function installDependencies() {
|
|
195
|
+
if (fs.existsSync(path.join(process.cwd(), 'package.json'))) {
|
|
196
|
+
console.log('Detected Node project. Installing npm dependencies...');
|
|
197
|
+
if (!commandExists('npm')) {
|
|
198
|
+
console.error('npm is not available on PATH. Please install Node.js and npm first.');
|
|
199
|
+
console.error('Then run `npm install -g .` from this repository to make `hybard` available globally.');
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// Use npm.cmd on Windows for proper execution
|
|
203
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
204
|
+
const result = spawnSync(npmCmd, ['install'], {
|
|
205
|
+
encoding: 'utf8',
|
|
206
|
+
cwd: process.cwd(),
|
|
207
|
+
stdio: 'inherit',
|
|
208
|
+
});
|
|
209
|
+
if (result.error) {
|
|
210
|
+
console.error(`Failed to run npm install: ${result.error.message}`);
|
|
211
|
+
} else if (result.status !== 0) {
|
|
212
|
+
console.error(`npm install exited with code ${result.status}`);
|
|
213
|
+
}
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (fs.existsSync(path.join(process.cwd(), 'pyproject.toml')) || fs.existsSync(path.join(process.cwd(), 'requirements.txt'))) {
|
|
218
|
+
console.log('Detected Python project. Installing pip dependencies...');
|
|
219
|
+
const pythonCmd = commandExists('python') ? 'python' : commandExists('python3') ? 'python3' : null;
|
|
220
|
+
if (!pythonCmd) {
|
|
221
|
+
console.error('Python is not available on PATH. Install Python before running dependency installation.');
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (fs.existsSync(path.join(process.cwd(), 'requirements.txt'))) {
|
|
226
|
+
runCommand(pythonCmd, ['-m', 'pip', 'install', '-r', 'requirements.txt']);
|
|
227
|
+
} else {
|
|
228
|
+
runCommand(pythonCmd, ['-m', 'pip', 'install', '.']);
|
|
229
|
+
}
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
console.log('No known dependency manifest found. Nothing to install.');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function getFileType(filePath) {
|
|
237
|
+
if (filePath.endsWith('.py')) return 'python';
|
|
238
|
+
if (filePath.endsWith('.js')) return 'javascript';
|
|
239
|
+
return 'text';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function runSyntaxCheck(filePath) {
|
|
243
|
+
const absPath = path.join(process.cwd(), filePath);
|
|
244
|
+
if (filePath.endsWith('.py')) {
|
|
245
|
+
const result = spawnSync('python', ['-m', 'py_compile', absPath], {
|
|
246
|
+
encoding: 'utf8',
|
|
247
|
+
maxBuffer: 1024 * 1024,
|
|
248
|
+
});
|
|
249
|
+
return {
|
|
250
|
+
file: filePath,
|
|
251
|
+
ok: result.status === 0,
|
|
252
|
+
error: result.stderr || result.stdout,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (filePath.endsWith('.js')) {
|
|
257
|
+
const result = spawnSync('node', ['--check', absPath], {
|
|
258
|
+
encoding: 'utf8',
|
|
259
|
+
maxBuffer: 1024 * 1024,
|
|
260
|
+
});
|
|
261
|
+
return {
|
|
262
|
+
file: filePath,
|
|
263
|
+
ok: result.status === 0,
|
|
264
|
+
error: result.stderr || result.stdout,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return { file: filePath, ok: true, error: '' };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function createNodeProject(goal, config) {
|
|
272
|
+
const pkg = {
|
|
273
|
+
name: path.basename(process.cwd()),
|
|
274
|
+
version: '0.1.0',
|
|
275
|
+
description: `Hybard-generated project for: ${goal}`,
|
|
276
|
+
main: 'src/index.js',
|
|
277
|
+
scripts: {
|
|
278
|
+
start: 'node src/index.js',
|
|
279
|
+
dev: 'node src/index.js',
|
|
280
|
+
},
|
|
281
|
+
license: 'MIT',
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
safeWriteFile(path.join(process.cwd(), 'package.json'), JSON.stringify(pkg, null, 2) + '\n');
|
|
285
|
+
safeWriteFile(path.join(process.cwd(), '.gitignore'), 'node_modules\n*.log\n*.hybard.bak\n');
|
|
286
|
+
safeWriteFile(path.join(process.cwd(), 'README.md'), `# ${pkg.name}\n\nGenerated by Hybard.\n\nTask: ${goal}\n`);
|
|
287
|
+
safeWriteFile(path.join(process.cwd(), 'src', 'index.js'), `const tasks = [];
|
|
288
|
+
|
|
289
|
+
function listTasks() {
|
|
290
|
+
console.log('Tasks:', tasks);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function addTask(title) {
|
|
294
|
+
tasks.push({ title, done: false });
|
|
295
|
+
console.log('Added task:', title);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
addTask('Example task');
|
|
299
|
+
listTasks();
|
|
300
|
+
`);
|
|
301
|
+
|
|
302
|
+
if (config.includeTests) {
|
|
303
|
+
safeWriteFile(path.join(process.cwd(), 'src', 'index.test.js'), `const { describe, it } = require('node:test');
|
|
304
|
+
|
|
305
|
+
describe('basic example', () => {
|
|
306
|
+
it('runs successfully', () => {
|
|
307
|
+
const value = 1 + 1;
|
|
308
|
+
if (value !== 2) throw new Error('Math failed');
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function createPythonProject(goal, config) {
|
|
316
|
+
safeWriteFile(path.join(process.cwd(), 'pyproject.toml'), `[build-system]\nrequires = ["setuptools>=42", "wheel"]\nbuild-backend = "setuptools.build_meta"\n\n[project]\nname = "${path.basename(process.cwd())}"\nversion = "0.1.0"\ndescription = "Hybard-generated project for: ${goal}"\nlicense = { file = "LICENSE" }\n\n[project.scripts]\nstart = "python main.py"\n`);
|
|
317
|
+
safeWriteFile(path.join(process.cwd(), 'README.md'), `# ${path.basename(process.cwd())}\n\nGenerated by Hybard.\n\nTask: ${goal}\n`);
|
|
318
|
+
safeWriteFile(path.join(process.cwd(), '.gitignore'), '__pycache__/\n*.pyc\n*.hybard.bak\n');
|
|
319
|
+
safeWriteFile(path.join(process.cwd(), 'main.py'), `def main():\n print('Hybard project created for: ${goal}')\n\nif __name__ == '__main__':\n main()\n`);
|
|
320
|
+
|
|
321
|
+
if (config.includeTests) {
|
|
322
|
+
safeWriteFile(path.join(process.cwd(), 'test_main.py'), `def test_main_runs():\n assert True\n`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function createDjangoProject(goal, config) {
|
|
327
|
+
const projectName = path.basename(process.cwd()).replace(/[-\s]/g, '_').toLowerCase() || 'hybard_django';
|
|
328
|
+
safeWriteFile(path.join(process.cwd(), 'requirements.txt'), 'Django>=4.2\n');
|
|
329
|
+
safeWriteFile(path.join(process.cwd(), '.gitignore'), '__pycache__/\n*.pyc\n*.db\n*.log\n*.hybard.bak\n');
|
|
330
|
+
safeWriteFile(path.join(process.cwd(), 'README.md'), `# ${projectName}\n\nGenerated by Hybard Django scaffold.\n\nTask: ${goal}\n`);
|
|
331
|
+
safeWriteFile(path.join(process.cwd(), 'manage.py'), `#!/usr/bin/env python\nimport os\nimport sys\n\nif __name__ == '__main__':\n os.environ.setdefault('DJANGO_SETTINGS_MODULE', '${projectName}.settings')\n try:\n from django.core.management import execute_from_command_line\n except ImportError as exc:\n raise ImportError('Django is not installed.') from exc\n execute_from_command_line(sys.argv)\n`);
|
|
332
|
+
safeWriteFile(path.join(process.cwd(), projectName, '__init__.py'), '');
|
|
333
|
+
safeWriteFile(path.join(process.cwd(), projectName, 'settings.py'), `from pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent.parent\n\nSECRET_KEY = 'hybard-django-secret-key'\nDEBUG = True\nALLOWED_HOSTS = []\n\nINSTALLED_APPS = [\n 'django.contrib.admin',\n 'django.contrib.auth',\n 'django.contrib.contenttypes',\n 'django.contrib.sessions',\n 'django.contrib.messages',\n 'django.contrib.staticfiles',\n 'app',\n]\n\nMIDDLEWARE = [\n 'django.middleware.security.SecurityMiddleware',\n 'django.contrib.sessions.middleware.SessionMiddleware',\n 'django.middleware.common.CommonMiddleware',\n 'django.middleware.csrf.CsrfViewMiddleware',\n 'django.contrib.auth.middleware.AuthenticationMiddleware',\n 'django.contrib.messages.middleware.MessageMiddleware',\n 'django.middleware.clickjacking.XFrameOptionsMiddleware',\n]\n\nROOT_URLCONF = '${projectName}.urls'\n\nTEMPLATES = [\n {\n 'BACKEND': 'django.template.backends.django.DjangoTemplates',\n 'DIRS': [BASE_DIR / 'templates'],\n 'APP_DIRS': True,\n 'OPTIONS': {\n 'context_processors': [\n 'django.template.context_processors.debug',\n 'django.template.context_processors.request',\n 'django.contrib.auth.context_processors.auth',\n 'django.contrib.messages.context_processors.messages',\n ],\n },\n },\n]\n\nWSGI_APPLICATION = '${projectName}.wsgi.application'\n\nDATABASES = {\n 'default': {\n 'ENGINE': 'django.db.backends.sqlite3',\n 'NAME': BASE_DIR / 'db.sqlite3',\n }\n}\n\nAUTH_PASSWORD_VALIDATORS = []\n\nLANGUAGE_CODE = 'en-us'\nTIME_ZONE = 'UTC'\nUSE_I18N = True\nUSE_TZ = True\n\nSTATIC_URL = '/static/'\n`);
|
|
334
|
+
safeWriteFile(path.join(process.cwd(), projectName, 'urls.py'), `from django.contrib import admin\nfrom django.urls import path, include\n\nurlpatterns = [\n path('admin/', admin.site.urls),\n path('', include('app.urls')),\n]\n`);
|
|
335
|
+
safeWriteFile(path.join(process.cwd(), projectName, 'wsgi.py'), `import os\n\nfrom django.core.wsgi import get_wsgi_application\n\nos.environ.setdefault('DJANGO_SETTINGS_MODULE', '${projectName}.settings')\n\napplication = get_wsgi_application()\n`);
|
|
336
|
+
safeWriteFile(path.join(process.cwd(), 'app', '__init__.py'), '');
|
|
337
|
+
safeWriteFile(path.join(process.cwd(), 'app', 'apps.py'), `from django.apps import AppConfig\n\nclass AppConfig(AppConfig):\n default_auto_field = 'django.db.models.BigAutoField'\n name = 'app'\n`);
|
|
338
|
+
safeWriteFile(path.join(process.cwd(), 'app', 'models.py'), 'from django.db import models\n\n# Create your models here.\n');
|
|
339
|
+
safeWriteFile(path.join(process.cwd(), 'app', 'views.py'), `from django.http import HttpResponse\n\ndef home(request):\n return HttpResponse('Hello from Hybard Django app!')\n`);
|
|
340
|
+
safeWriteFile(path.join(process.cwd(), 'app', 'urls.py'), `from django.urls import path\nfrom .views import home\n\nurlpatterns = [\n path('', home, name='home'),\n]\n`);
|
|
341
|
+
safeWriteFile(path.join(process.cwd(), 'templates', 'base.html'), `<!DOCTYPE html>\n<html lang="en">\n<head>\n <meta charset="UTF-8">\n <meta name="viewport" content="width=device-width, initial-scale=1.0">\n <title>${projectName}</title>\n</head>\n<body>\n <h1>Hybard Django App</h1>\n <p>${goal}</p>\n</body>\n</html>\n`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function createReactProject(goal, config) {
|
|
345
|
+
const pkg = {
|
|
346
|
+
name: path.basename(process.cwd()),
|
|
347
|
+
version: '0.1.0',
|
|
348
|
+
private: true,
|
|
349
|
+
description: `Hybard-generated React app for: ${goal}`,
|
|
350
|
+
scripts: {
|
|
351
|
+
dev: 'npx vite',
|
|
352
|
+
build: 'npx vite build',
|
|
353
|
+
preview: 'npx vite preview'
|
|
354
|
+
},
|
|
355
|
+
dependencies: {
|
|
356
|
+
react: '^18.2.0',
|
|
357
|
+
'react-dom': '^18.2.0'
|
|
358
|
+
},
|
|
359
|
+
devDependencies: {
|
|
360
|
+
vite: '^5.0.0',
|
|
361
|
+
'@vitejs/plugin-react': '^4.0.0'
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
safeWriteFile(path.join(process.cwd(), 'package.json'), JSON.stringify(pkg, null, 2) + '\n');
|
|
366
|
+
safeWriteFile(path.join(process.cwd(), '.gitignore'), 'node_modules\ndist\n*.log\n*.hybard.bak\n');
|
|
367
|
+
safeWriteFile(path.join(process.cwd(), 'README.md'), `# ${pkg.name}\n\nGenerated by Hybard React scaffold.\n\nTask: ${goal}\n`);
|
|
368
|
+
safeWriteFile(path.join(process.cwd(), 'index.html'), `<!DOCTYPE html>\n<html lang="en">\n<head>\n <meta charset="UTF-8">\n <meta name="viewport" content="width=device-width, initial-scale=1.0">\n <title>${pkg.name}</title>\n</head>\n<body>\n <div id="root"></div>\n <script type="module" src="src/main.jsx"></script>\n</body>\n</html>\n`);
|
|
369
|
+
safeWriteFile(path.join(process.cwd(), 'src', 'main.jsx'), `import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport App from './App';\nimport './style.css';\n\nReactDOM.createRoot(document.getElementById('root')).render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n`);
|
|
370
|
+
safeWriteFile(path.join(process.cwd(), 'src', 'App.jsx'), `export default function App() {\n return (\n <div className=\"app\">\n <h1>Hybard React App</h1>\n <p>${goal}</p>\n </div>\n );\n}\n`);
|
|
371
|
+
safeWriteFile(path.join(process.cwd(), 'src', 'style.css'), `body {\n font-family: Arial, sans-serif;\n margin: 0;\n padding: 2rem;\n background: #f5f7fb;\n}\n.app {\n max-width: 720px;\n margin: auto;\n background: white;\n border-radius: 16px;\n padding: 2rem;\n box-shadow: 0 10px 30px rgba(0,0,0,0.08);\n}\n`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function createFlutterProject(goal, config) {
|
|
375
|
+
safeWriteFile(path.join(process.cwd(), 'pubspec.yaml'), `name: ${path.basename(process.cwd())}\ndescription: A new Flutter project generated by Hybard.\npublish_to: 'none'\nversion: 0.1.0+1\nenvironment:\n sdk: '>=2.19.0 <3.0.0'\ndependencies:\n flutter:\n sdk: flutter\n cupertino_icons: ^1.0.5\n\ndev_dependencies:\n flutter_test:\n sdk: flutter\n\nflutter:\n uses-material-design: true\n`);
|
|
376
|
+
safeWriteFile(path.join(process.cwd(), '.gitignore'), '.dart_tool/\n.packages\n.buildlog\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.idea/\n.vs/\n*.iml\nbuild/\n.DS_Store\n');
|
|
377
|
+
safeWriteFile(path.join(process.cwd(), 'README.md'), `# ${path.basename(process.cwd())}\n\nGenerated by Hybard Flutter scaffold.\n\nTask: ${goal}\n`);
|
|
378
|
+
safeWriteFile(path.join(process.cwd(), 'lib', 'main.dart'), `import 'package:flutter/material.dart';\n\nvoid main() {\n runApp(const MyApp());\n}\n\nclass MyApp extends StatelessWidget {\n const MyApp({super.key});\n\n @override\n Widget build(BuildContext context) {\n return MaterialApp(\n title: '${path.basename(process.cwd())}',\n theme: ThemeData(primarySwatch: Colors.blue),\n home: const HomeScreen(),\n );\n }\n}\n\nclass HomeScreen extends StatelessWidget {\n const HomeScreen({super.key});\n\n @override\n Widget build(BuildContext context) {\n return Scaffold(\n appBar: AppBar(title: const Text('Hybard Flutter App')),\n body: const Center(\n child: Text('Generated app for: ${goal}'),\n ),\n );\n }\n}\n`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function createHtmlCssProject(goal, config) {
|
|
382
|
+
safeWriteFile(path.join(process.cwd(), 'index.html'), `<!DOCTYPE html>
|
|
383
|
+
<html lang="en">
|
|
384
|
+
<head>
|
|
385
|
+
<meta charset="UTF-8">
|
|
386
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
387
|
+
<title>${path.basename(process.cwd())}</title>
|
|
388
|
+
<link rel="stylesheet" href="style.css">
|
|
389
|
+
</head>
|
|
390
|
+
<body>
|
|
391
|
+
<header class="hero">
|
|
392
|
+
<div class="hero__content">
|
|
393
|
+
<p class="eyebrow">Landing page</p>
|
|
394
|
+
<h1>${path.basename(process.cwd())}</h1>
|
|
395
|
+
<p>${goal}</p>
|
|
396
|
+
<a class="cta" href="#features">Get started</a>
|
|
397
|
+
</div>
|
|
398
|
+
</header>
|
|
399
|
+
<main>
|
|
400
|
+
<section id="features" class="features">
|
|
401
|
+
<article>
|
|
402
|
+
<h2>Responsive design</h2>
|
|
403
|
+
<p>Built with HTML, CSS, and JavaScript for a polished landing page.</p>
|
|
404
|
+
</article>
|
|
405
|
+
<article>
|
|
406
|
+
<h2>Modern layout</h2>
|
|
407
|
+
<p>Clean, mobile-friendly sections with a strong first impression.</p>
|
|
408
|
+
</article>
|
|
409
|
+
<article>
|
|
410
|
+
<h2>Easy interaction</h2>
|
|
411
|
+
<p>Includes a CTA button and smooth scroll behavior.</p>
|
|
412
|
+
</article>
|
|
413
|
+
</section>
|
|
414
|
+
</main>
|
|
415
|
+
<footer>
|
|
416
|
+
<p>Generated by Hybard.</p>
|
|
417
|
+
</footer>
|
|
418
|
+
<script src="script.js"></script>
|
|
419
|
+
</body>
|
|
420
|
+
</html>
|
|
421
|
+
`);
|
|
422
|
+
safeWriteFile(path.join(process.cwd(), 'style.css'), `:root {
|
|
423
|
+
font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
424
|
+
background: #f8fafc;
|
|
425
|
+
color: #0f172a;
|
|
426
|
+
line-height: 1.6;
|
|
427
|
+
margin: 0;
|
|
428
|
+
}
|
|
429
|
+
*, *::before, *::after {
|
|
430
|
+
box-sizing: border-box;
|
|
431
|
+
}
|
|
432
|
+
body {
|
|
433
|
+
margin: 0;
|
|
434
|
+
}
|
|
435
|
+
.hero {
|
|
436
|
+
min-height: 100vh;
|
|
437
|
+
display: grid;
|
|
438
|
+
place-items: center;
|
|
439
|
+
padding: 4rem 1.5rem;
|
|
440
|
+
text-align: center;
|
|
441
|
+
background: linear-gradient(135deg, #2563eb 0%, #0ea5e9 100%);
|
|
442
|
+
color: white;
|
|
443
|
+
}
|
|
444
|
+
.hero__content {
|
|
445
|
+
max-width: 720px;
|
|
446
|
+
}
|
|
447
|
+
.eyebrow {
|
|
448
|
+
text-transform: uppercase;
|
|
449
|
+
letter-spacing: 0.2em;
|
|
450
|
+
opacity: 0.9;
|
|
451
|
+
margin-bottom: 1rem;
|
|
452
|
+
font-size: 0.85rem;
|
|
453
|
+
}
|
|
454
|
+
.hero h1 {
|
|
455
|
+
margin: 0;
|
|
456
|
+
font-size: clamp(3rem, 6vw, 4.5rem);
|
|
457
|
+
}
|
|
458
|
+
.hero p {
|
|
459
|
+
max-width: 38rem;
|
|
460
|
+
margin: 1.5rem auto 2rem;
|
|
461
|
+
opacity: 0.95;
|
|
462
|
+
font-size: 1.1rem;
|
|
463
|
+
}
|
|
464
|
+
.cta {
|
|
465
|
+
display: inline-block;
|
|
466
|
+
padding: 1rem 2rem;
|
|
467
|
+
border-radius: 999px;
|
|
468
|
+
background: white;
|
|
469
|
+
color: #0f172a;
|
|
470
|
+
font-weight: 700;
|
|
471
|
+
text-decoration: none;
|
|
472
|
+
}
|
|
473
|
+
.features {
|
|
474
|
+
display: grid;
|
|
475
|
+
gap: 1.5rem;
|
|
476
|
+
max-width: 80rem;
|
|
477
|
+
margin: 0 auto;
|
|
478
|
+
padding: 4rem 1.5rem;
|
|
479
|
+
}
|
|
480
|
+
.features article {
|
|
481
|
+
background: white;
|
|
482
|
+
border-radius: 1.5rem;
|
|
483
|
+
padding: 2rem;
|
|
484
|
+
box-shadow: 0 20px 60px rgba(15, 23, 42, 0.08);
|
|
485
|
+
}
|
|
486
|
+
footer {
|
|
487
|
+
text-align: center;
|
|
488
|
+
padding: 2rem 1rem;
|
|
489
|
+
color: #64748b;
|
|
490
|
+
}
|
|
491
|
+
`);
|
|
492
|
+
safeWriteFile(path.join(process.cwd(), 'script.js'), `const button = document.querySelector('.cta');
|
|
493
|
+
if (button) {
|
|
494
|
+
button.addEventListener('click', (event) => {
|
|
495
|
+
event.preventDefault();
|
|
496
|
+
document.querySelector('#features')?.scrollIntoView({ behavior: 'smooth' });
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
`);
|
|
500
|
+
safeWriteFile(path.join(process.cwd(), 'README.md'), `# ${path.basename(process.cwd())}
|
|
501
|
+
|
|
502
|
+
Generated by Hybard HTML landing page scaffold.
|
|
503
|
+
|
|
504
|
+
Task: ${goal}
|
|
505
|
+
`);
|
|
506
|
+
}
|
|
507
|
+
function createWebProject(goal, config) {
|
|
508
|
+
createNodeProject(goal, config);
|
|
509
|
+
safeWriteFile(path.join(process.cwd(), 'src', 'index.html'), `<!DOCTYPE html>\n<html lang="en">\n<head>\n <meta charset="UTF-8">\n <meta name="viewport" content="width=device-width, initial-scale=1.0">\n <title>${path.basename(process.cwd())}</title>\n</head>\n<body>\n <h1>Hybard Web Project</h1>\n <p>${goal}</p>\n <script src=\"index.js\"></script>\n</body>\n</html>\n`);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function describeWorkspace() {
|
|
513
|
+
const files = scanProjectFiles(process.cwd());
|
|
514
|
+
if (files.length === 0) return 'Workspace is empty.';
|
|
515
|
+
return `Workspace contains ${files.length} files:\n- ${files.slice(0, 20).join('\n- ')}${files.length > 20 ? '\n- ...' : ''}`;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async function initAgent() {
|
|
519
|
+
const config = await promptInitConfig();
|
|
520
|
+
console.log('Hybard is initialized.');
|
|
521
|
+
return config;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function getFixPrompt(filePath, content, error) {
|
|
525
|
+
const language = getFileType(filePath);
|
|
526
|
+
return `Repair the following ${language} file. Keep the file structure and fix the syntax errors or obvious issues. Return only the corrected file contents without extra explanation.\n\nFILE: ${filePath}\nERROR: ${error}\n\nCONTENT:\n${content}`;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function fixFile(filePath) {
|
|
530
|
+
const absPath = path.join(process.cwd(), filePath);
|
|
531
|
+
if (!fs.existsSync(absPath)) {
|
|
532
|
+
console.error(`File not found: ${filePath}`);
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
const current = fs.readFileSync(absPath, 'utf8');
|
|
536
|
+
const syntax = runSyntaxCheck(filePath);
|
|
537
|
+
if (syntax.ok) {
|
|
538
|
+
console.log(`No syntax errors detected in ${filePath}.`);
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
console.log(`Fixing ${filePath}...`);
|
|
543
|
+
backupFile(absPath);
|
|
544
|
+
const prompt = getFixPrompt(filePath, current, syntax.error);
|
|
545
|
+
let repair = '';
|
|
546
|
+
try {
|
|
547
|
+
repair = runLocalModel(prompt);
|
|
548
|
+
} catch (err) {
|
|
549
|
+
console.error(`Failed to generate repair for ${filePath}: ${err.message}`);
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (!repair.trim()) {
|
|
554
|
+
console.error(`No repair content returned for ${filePath}.`);
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
fs.writeFileSync(absPath, repair, 'utf8');
|
|
559
|
+
console.log(`Updated ${filePath} with repaired content.`);
|
|
560
|
+
const check = runSyntaxCheck(filePath);
|
|
561
|
+
if (!check.ok) {
|
|
562
|
+
console.error(`Repair did not fully fix ${filePath}: ${check.error}`);
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
console.log(`${filePath} is now syntactically valid.`);
|
|
566
|
+
return true;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function fixProject() {
|
|
570
|
+
const files = scanProjectFiles(process.cwd()).filter((file) => file.endsWith('.js') || file.endsWith('.py'));
|
|
571
|
+
if (files.length === 0) {
|
|
572
|
+
console.log('No JavaScript or Python files found to fix.');
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
let fixedCount = 0;
|
|
577
|
+
for (const file of files) {
|
|
578
|
+
const syntax = runSyntaxCheck(file);
|
|
579
|
+
if (!syntax.ok) {
|
|
580
|
+
if (fixFile(file)) fixedCount += 1;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (fixedCount === 0) {
|
|
585
|
+
console.log('No syntax issues were found or fixed.');
|
|
586
|
+
} else {
|
|
587
|
+
console.log(`Fixed ${fixedCount} file(s).`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function createPlan(goal) {
|
|
592
|
+
const prompt = `You are a coding assistant that writes a development plan. Provide a step-by-step plan for building: ${goal}. Return numbered steps.`;
|
|
593
|
+
const ai = new HybardAI();
|
|
594
|
+
const apiKey = ai.getApiKey();
|
|
595
|
+
|
|
596
|
+
if (apiKey) {
|
|
597
|
+
ai.chat(prompt, { system: 'You are an expert software architect. Create clear, actionable development plans with numbered steps.' })
|
|
598
|
+
.then(plan => {
|
|
599
|
+
console.log('--- Project plan ---');
|
|
600
|
+
console.log(plan);
|
|
601
|
+
})
|
|
602
|
+
.catch(err => {
|
|
603
|
+
console.error('Failed to generate a plan with AI:', err.message);
|
|
604
|
+
console.log('Trying local model...');
|
|
605
|
+
try {
|
|
606
|
+
const plan = runLocalModel(prompt);
|
|
607
|
+
console.log('--- Project plan (local) ---');
|
|
608
|
+
console.log(plan);
|
|
609
|
+
} catch (e2) {
|
|
610
|
+
console.error('Failed to run local model:', e2.message);
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
} else {
|
|
614
|
+
try {
|
|
615
|
+
const plan = runLocalModel(prompt);
|
|
616
|
+
console.log('--- Project plan ---');
|
|
617
|
+
console.log(plan);
|
|
618
|
+
} catch (err) {
|
|
619
|
+
console.error('Failed to generate a plan:', err.message);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function getHelpText() {
|
|
625
|
+
return `Hybard AI Agent commands:
|
|
626
|
+
|
|
627
|
+
hybard init Initialize Hybard configuration interactively
|
|
628
|
+
hybard create <goal> Create a new project scaffold for the given goal
|
|
629
|
+
hybard install Install project dependencies for the current workspace
|
|
630
|
+
hybard inspect Show workspace files and summary
|
|
631
|
+
hybard fix project Scan and repair JS/Python syntax issues
|
|
632
|
+
hybard fix <file> Repair a specific file if syntax errors are found
|
|
633
|
+
hybard plan <goal> Generate a step-by-step development plan
|
|
634
|
+
hybard help Show this help message
|
|
635
|
+
|
|
636
|
+
Examples:
|
|
637
|
+
hybard create "social media app"
|
|
638
|
+
hybard create "ecommerce website"
|
|
639
|
+
hybard create "chat application"
|
|
640
|
+
hybard create "python api backend"
|
|
641
|
+
hybard create "react dashboard app"
|
|
642
|
+
hybard create "flutter mobile app"
|
|
643
|
+
hybard create "html css javascript landing page"
|
|
644
|
+
hybard create "landing page with html css tailwind"
|
|
645
|
+
hybard create "django blog app"
|
|
646
|
+
hybard create "dejano ecommerce site"
|
|
647
|
+
`;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
async function runAgent(args) {
|
|
651
|
+
const goal = args.join(' ').trim();
|
|
652
|
+
const config = loadConfig();
|
|
653
|
+
|
|
654
|
+
if (!goal || goal === 'help' || goal === '--help' || goal === '-h') {
|
|
655
|
+
console.log(getHelpText());
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (goal === 'init') {
|
|
660
|
+
await initAgent();
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (goal.startsWith('create ') || goal.startsWith('scaffold ') || goal.startsWith('build ')) {
|
|
665
|
+
createProject(goal.replace(/^(create|scaffold|build)\s+/, ''), config);
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (goal === 'inspect current project' || goal === 'inspect') {
|
|
670
|
+
console.log(describeWorkspace());
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (goal === 'install') {
|
|
675
|
+
installDependencies();
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (goal === 'fix project') {
|
|
680
|
+
fixProject();
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (goal.startsWith('fix ')) {
|
|
685
|
+
const filePath = goal.replace(/^fix\s+/, '').trim();
|
|
686
|
+
fixFile(filePath);
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (goal.startsWith('plan ')) {
|
|
691
|
+
createPlan(goal.replace(/^plan\s+/, ''));
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
console.log('Scanning workspace and generating code...');
|
|
696
|
+
console.log(describeWorkspace());
|
|
697
|
+
|
|
698
|
+
const ai = new HybardAI();
|
|
699
|
+
const apiKey = ai.getApiKey();
|
|
700
|
+
|
|
701
|
+
if (apiKey) {
|
|
702
|
+
try {
|
|
703
|
+
const output = await ai.chat(goal);
|
|
704
|
+
console.log('\n--- AI Response ---\n');
|
|
705
|
+
console.log(output);
|
|
706
|
+
} catch (err) {
|
|
707
|
+
console.error(`\n[AI Error] ${err.message}\n`);
|
|
708
|
+
console.log('Falling back to local model...');
|
|
709
|
+
try {
|
|
710
|
+
const output = runLocalModel(goal);
|
|
711
|
+
console.log('--- Local Model output ---');
|
|
712
|
+
console.log(output);
|
|
713
|
+
} catch (e2) {
|
|
714
|
+
console.error('Failed to run local model:', e2.message);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
} else {
|
|
718
|
+
try {
|
|
719
|
+
const output = runLocalModel(goal);
|
|
720
|
+
console.log('--- Local Model output ---');
|
|
721
|
+
console.log(output);
|
|
722
|
+
} catch (err) {
|
|
723
|
+
console.error('Failed to run local model:', err.message);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
module.exports = {
|
|
729
|
+
runAgent,
|
|
730
|
+
};
|