@claudiv/cli 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 +430 -0
- package/bin/claudiv.js +407 -0
- package/dist/claude-api.d.ts +20 -0
- package/dist/claude-api.js +117 -0
- package/dist/claude-cli.d.ts +18 -0
- package/dist/claude-cli.js +124 -0
- package/dist/claude-client.d.ts +16 -0
- package/dist/claude-client.js +44 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +67 -0
- package/dist/dev-server.d.ts +10 -0
- package/dist/dev-server.js +118 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +305 -0
- package/dist/updater.d.ts +29 -0
- package/dist/updater.js +79 -0
- package/dist/utils/logger.d.ts +11 -0
- package/dist/utils/logger.js +36 -0
- package/dist/watcher.d.ts +22 -0
- package/dist/watcher.js +66 -0
- package/package.json +69 -0
package/bin/claudiv.js
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Claudiv CLI — Universal Declarative Generation Platform
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx @claudiv/cli <command> [options]
|
|
8
|
+
* claudiv <command> [options]
|
|
9
|
+
*
|
|
10
|
+
* Commands:
|
|
11
|
+
* new <name> Create a new .cdml file
|
|
12
|
+
* gen <name> Generate code from .cdml file
|
|
13
|
+
* reverse <file> Reverse-engineer file to .cdml
|
|
14
|
+
* watch <name> Watch .cdml file for changes
|
|
15
|
+
* help Show this help message
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { spawn } from 'child_process';
|
|
19
|
+
import { existsSync, writeFileSync, readdirSync } from 'fs';
|
|
20
|
+
import { join, basename, extname } from 'path';
|
|
21
|
+
import { fileURLToPath } from 'url';
|
|
22
|
+
|
|
23
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
24
|
+
const VERSION = '0.1.0';
|
|
25
|
+
|
|
26
|
+
// ─── Parse Arguments ───────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
|
|
30
|
+
// Parse flags
|
|
31
|
+
const flags = {};
|
|
32
|
+
const positional = [];
|
|
33
|
+
for (let i = 0; i < args.length; i++) {
|
|
34
|
+
const arg = args[i];
|
|
35
|
+
if (arg === '-s' || arg === '--spec') {
|
|
36
|
+
flags.spec = args[++i];
|
|
37
|
+
} else if (arg === '-g' || arg === '--gen') {
|
|
38
|
+
flags.gen = true;
|
|
39
|
+
} else if (arg === '-t' || arg === '--target') {
|
|
40
|
+
flags.target = args[++i];
|
|
41
|
+
} else if (arg === '-f' || arg === '--framework') {
|
|
42
|
+
flags.framework = args[++i];
|
|
43
|
+
} else if (arg === '-w' || arg === '--watch') {
|
|
44
|
+
flags.watch = true;
|
|
45
|
+
} else if (arg === '-o' || arg === '--output') {
|
|
46
|
+
flags.output = args[++i];
|
|
47
|
+
} else if (arg === '--dry-run') {
|
|
48
|
+
flags.dryRun = true;
|
|
49
|
+
} else if (arg === '-v' || arg === '--version') {
|
|
50
|
+
console.log(`claudiv ${VERSION}`);
|
|
51
|
+
process.exit(0);
|
|
52
|
+
} else if (arg === '-h' || arg === '--help') {
|
|
53
|
+
showHelp();
|
|
54
|
+
process.exit(0);
|
|
55
|
+
} else if (!arg.startsWith('-')) {
|
|
56
|
+
positional.push(arg);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ─── Route Commands ────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
switch (positional[0]) {
|
|
63
|
+
case 'new':
|
|
64
|
+
cmdNew(positional[1], flags);
|
|
65
|
+
break;
|
|
66
|
+
case 'gen':
|
|
67
|
+
cmdGen(positional[1], flags);
|
|
68
|
+
break;
|
|
69
|
+
case 'reverse':
|
|
70
|
+
cmdReverse(positional[1], flags);
|
|
71
|
+
break;
|
|
72
|
+
case 'watch':
|
|
73
|
+
cmdWatch(positional[1], flags);
|
|
74
|
+
break;
|
|
75
|
+
case 'help':
|
|
76
|
+
showHelp();
|
|
77
|
+
break;
|
|
78
|
+
case undefined:
|
|
79
|
+
// No command: look for .cdml in current directory and watch
|
|
80
|
+
cmdDefault(flags);
|
|
81
|
+
break;
|
|
82
|
+
default:
|
|
83
|
+
// Check if it's a .cdml file path
|
|
84
|
+
if (positional[0].endsWith('.cdml')) {
|
|
85
|
+
cmdGen(positional[0], flags);
|
|
86
|
+
} else {
|
|
87
|
+
console.error(`Unknown command: ${positional[0]}`);
|
|
88
|
+
console.log('Run "claudiv help" for usage information.');
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ─── Commands ──────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* claudiv new <name> [-s|--spec '<xml>'] [-g|--gen] [-t|--target <lang>]
|
|
97
|
+
*/
|
|
98
|
+
function cmdNew(name, flags) {
|
|
99
|
+
if (!name) {
|
|
100
|
+
console.error('Usage: claudiv new <name> [options]');
|
|
101
|
+
console.log('');
|
|
102
|
+
console.log('Examples:');
|
|
103
|
+
console.log(' claudiv new myapp');
|
|
104
|
+
console.log(' claudiv new txt2img -s \'<txt2img lang="python" type="cli" gen="">...</txt2img>\'');
|
|
105
|
+
console.log(' claudiv new api -t python -f fastapi');
|
|
106
|
+
console.log(' claudiv new myapp -s \'<spec>\' -g');
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Strip .cdml extension if provided
|
|
111
|
+
const baseName = name.replace(/\.cdml$/, '');
|
|
112
|
+
const cdmlFile = `${baseName}.cdml`;
|
|
113
|
+
const cdmlPath = join(process.cwd(), cdmlFile);
|
|
114
|
+
|
|
115
|
+
if (existsSync(cdmlPath)) {
|
|
116
|
+
console.error(`File already exists: ${cdmlFile}`);
|
|
117
|
+
console.log(`Use "claudiv gen ${cdmlFile}" to generate from it.`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let content;
|
|
122
|
+
|
|
123
|
+
if (flags.spec) {
|
|
124
|
+
// Use provided spec
|
|
125
|
+
content = flags.spec;
|
|
126
|
+
} else {
|
|
127
|
+
// Generate default template
|
|
128
|
+
const target = flags.target || 'html';
|
|
129
|
+
const framework = flags.framework ? ` framework="${flags.framework}"` : '';
|
|
130
|
+
content = `<${baseName} target="${target}"${framework} gen>
|
|
131
|
+
<!-- Describe what you want here -->
|
|
132
|
+
</${baseName}>
|
|
133
|
+
`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
writeFileSync(cdmlPath, content, 'utf-8');
|
|
137
|
+
console.log(`Created ${cdmlFile}`);
|
|
138
|
+
|
|
139
|
+
if (flags.gen) {
|
|
140
|
+
console.log(`Generating from ${cdmlFile}...`);
|
|
141
|
+
startEngine(cdmlPath, flags);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* claudiv gen <name> [-t|--target <component>] [-w|--watch] [-o|--output <file>]
|
|
147
|
+
*
|
|
148
|
+
* Examples:
|
|
149
|
+
* claudiv gen myapp # generate all from myapp.cdml
|
|
150
|
+
* claudiv gen myapp -t config # generate config component only
|
|
151
|
+
* claudiv gen txt2img -t config # generate txt2img.config
|
|
152
|
+
* claudiv gen myapp.cdml # explicit .cdml path
|
|
153
|
+
* claudiv gen myapp -w # generate + watch for changes
|
|
154
|
+
* claudiv gen myapp -o output.py # generate to specific file
|
|
155
|
+
*/
|
|
156
|
+
function cmdGen(name, flags) {
|
|
157
|
+
if (!name) {
|
|
158
|
+
console.error('Usage: claudiv gen <name> [options]');
|
|
159
|
+
console.log('');
|
|
160
|
+
console.log('Examples:');
|
|
161
|
+
console.log(' claudiv gen myapp');
|
|
162
|
+
console.log(' claudiv gen myapp -t config');
|
|
163
|
+
console.log(' claudiv gen myapp -w');
|
|
164
|
+
console.log(' claudiv gen myapp -o output.py');
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Resolve .cdml file
|
|
169
|
+
const cdmlFile = name.endsWith('.cdml') ? name : `${name}.cdml`;
|
|
170
|
+
const cdmlPath = join(process.cwd(), cdmlFile);
|
|
171
|
+
|
|
172
|
+
if (!existsSync(cdmlPath)) {
|
|
173
|
+
console.error(`File not found: ${cdmlFile}`);
|
|
174
|
+
console.log(`Create it with: claudiv new ${name}`);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// If -t flag, it selects a component/target within the .cdml
|
|
179
|
+
if (flags.target) {
|
|
180
|
+
console.log(`Generating "${flags.target}" from ${cdmlFile}...`);
|
|
181
|
+
} else {
|
|
182
|
+
console.log(`Generating from ${cdmlFile}...`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
startEngine(cdmlPath, flags);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* claudiv reverse <file> [-o|--output <name.cdml>]
|
|
190
|
+
*
|
|
191
|
+
* Examples:
|
|
192
|
+
* claudiv reverse api.py # → api.cdml
|
|
193
|
+
* claudiv reverse Button.tsx # → Button.cdml
|
|
194
|
+
* claudiv reverse backup.sh # → backup.cdml
|
|
195
|
+
* claudiv reverse styles.css # → styles.cdml
|
|
196
|
+
* claudiv reverse api.py -o spec.cdml # → spec.cdml
|
|
197
|
+
*/
|
|
198
|
+
function cmdReverse(file, flags) {
|
|
199
|
+
if (!file) {
|
|
200
|
+
console.error('Usage: claudiv reverse <file> [options]');
|
|
201
|
+
console.log('');
|
|
202
|
+
console.log('Examples:');
|
|
203
|
+
console.log(' claudiv reverse api.py');
|
|
204
|
+
console.log(' claudiv reverse Button.tsx');
|
|
205
|
+
console.log(' claudiv reverse backup.sh');
|
|
206
|
+
console.log(' claudiv reverse api.py -o spec.cdml');
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const filePath = join(process.cwd(), file);
|
|
211
|
+
if (!existsSync(filePath)) {
|
|
212
|
+
console.error(`File not found: ${file}`);
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const base = basename(file, extname(file));
|
|
217
|
+
const outputFile = flags.output || `${base}.cdml`;
|
|
218
|
+
console.log(`Reverse engineering: ${file} → ${outputFile}`);
|
|
219
|
+
|
|
220
|
+
// TODO: Implement reverse generation via Claude
|
|
221
|
+
console.log('Reverse generation coming soon.');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* claudiv watch <name>
|
|
226
|
+
*
|
|
227
|
+
* Watch .cdml file for changes and regenerate automatically.
|
|
228
|
+
*/
|
|
229
|
+
function cmdWatch(name, flags) {
|
|
230
|
+
if (!name) {
|
|
231
|
+
console.error('Usage: claudiv watch <name>');
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
flags.watch = true;
|
|
236
|
+
cmdGen(name, flags);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Default: no command given. Look for .cdml files in cwd.
|
|
241
|
+
*/
|
|
242
|
+
function cmdDefault(flags) {
|
|
243
|
+
// Look for .cdml files in current directory
|
|
244
|
+
const files = readdirSync(process.cwd());
|
|
245
|
+
const cdmlFiles = files.filter(f => f.endsWith('.cdml'));
|
|
246
|
+
|
|
247
|
+
if (cdmlFiles.length === 0) {
|
|
248
|
+
console.log('Claudiv — Claude in a Div');
|
|
249
|
+
console.log('');
|
|
250
|
+
console.log('No .cdml files found. Get started:');
|
|
251
|
+
console.log('');
|
|
252
|
+
console.log(' claudiv new myapp # Create myapp.cdml');
|
|
253
|
+
console.log(' claudiv new myapp -t python # Python project');
|
|
254
|
+
console.log(' claudiv new myapp -s \'<app gen>...</app>\' # With inline spec');
|
|
255
|
+
console.log(' claudiv new myapp -s \'<app gen>...</app>\' -g # Create + generate');
|
|
256
|
+
console.log('');
|
|
257
|
+
console.log(' claudiv gen myapp # Generate from myapp.cdml');
|
|
258
|
+
console.log(' claudiv gen myapp -t config # Generate specific target');
|
|
259
|
+
console.log(' claudiv gen myapp -w # Watch mode');
|
|
260
|
+
console.log('');
|
|
261
|
+
console.log(' claudiv reverse api.py # Reverse → api.cdml');
|
|
262
|
+
console.log('');
|
|
263
|
+
console.log(' claudiv help # Full help');
|
|
264
|
+
process.exit(0);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (cdmlFiles.length === 1) {
|
|
268
|
+
console.log(`Found ${cdmlFiles[0]}, starting...`);
|
|
269
|
+
startEngine(join(process.cwd(), cdmlFiles[0]), flags);
|
|
270
|
+
} else {
|
|
271
|
+
console.log('Multiple .cdml files found:');
|
|
272
|
+
cdmlFiles.forEach((f, i) => console.log(` ${i + 1}. ${f}`));
|
|
273
|
+
console.log('');
|
|
274
|
+
console.log('Specify which file to use:');
|
|
275
|
+
console.log(` claudiv gen ${cdmlFiles[0]}`);
|
|
276
|
+
process.exit(0);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ─── Engine ────────────────────────────────────────────────────
|
|
281
|
+
|
|
282
|
+
function startEngine(cdmlPath, flags) {
|
|
283
|
+
const mode = process.env.MODE || 'cli';
|
|
284
|
+
const envVars = { ...process.env, MODE: mode };
|
|
285
|
+
|
|
286
|
+
if (flags.target) envVars.CLAUDIV_TARGET = flags.target;
|
|
287
|
+
if (flags.output) envVars.CLAUDIV_OUTPUT = flags.output;
|
|
288
|
+
if (flags.watch) envVars.CLAUDIV_WATCH = '1';
|
|
289
|
+
|
|
290
|
+
const editor = spawn('node', [join(__dirname, '../dist/index.js'), cdmlPath], {
|
|
291
|
+
stdio: 'inherit',
|
|
292
|
+
cwd: process.cwd(),
|
|
293
|
+
env: envVars,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
editor.on('exit', (exitCode) => {
|
|
297
|
+
process.exit(exitCode || 0);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
editor.on('error', (err) => {
|
|
301
|
+
console.error(`Failed to start: ${err.message}`);
|
|
302
|
+
process.exit(1);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ─── Help ──────────────────────────────────────────────────────
|
|
307
|
+
|
|
308
|
+
function showHelp() {
|
|
309
|
+
console.log(`
|
|
310
|
+
Claudiv ${VERSION} — Claude in a Div
|
|
311
|
+
Universal Declarative Generation Platform
|
|
312
|
+
|
|
313
|
+
USAGE
|
|
314
|
+
claudiv <command> [name] [options]
|
|
315
|
+
npx @claudiv/cli <command> [name] [options]
|
|
316
|
+
|
|
317
|
+
COMMANDS
|
|
318
|
+
new <name> Create a new .cdml file
|
|
319
|
+
gen <name> Generate code from .cdml file
|
|
320
|
+
reverse <file> Reverse-engineer existing file to .cdml
|
|
321
|
+
watch <name> Watch .cdml file and regenerate on changes
|
|
322
|
+
help Show this help message
|
|
323
|
+
|
|
324
|
+
OPTIONS
|
|
325
|
+
-s, --spec <xml> Inline .cdml content for 'new' command
|
|
326
|
+
-g, --gen Immediately generate after 'new'
|
|
327
|
+
-t, --target <name> Target component or language
|
|
328
|
+
-f, --framework <fw> Framework (fastapi, express, nextjs, etc.)
|
|
329
|
+
-o, --output <file> Output file path
|
|
330
|
+
-w, --watch Watch mode (regenerate on save)
|
|
331
|
+
--dry-run Preview without writing files
|
|
332
|
+
-v, --version Show version
|
|
333
|
+
-h, --help Show help
|
|
334
|
+
|
|
335
|
+
EXAMPLES
|
|
336
|
+
|
|
337
|
+
Getting Started:
|
|
338
|
+
claudiv new myapp Create myapp.cdml
|
|
339
|
+
claudiv new myapp -t python Create Python project spec
|
|
340
|
+
claudiv new myapp -g Create and generate immediately
|
|
341
|
+
|
|
342
|
+
Inline Spec:
|
|
343
|
+
claudiv new txt2img -s '<txt2img lang="python" type="cli" gen="">
|
|
344
|
+
<ai provider="openai" />
|
|
345
|
+
<config apikey organizationid />
|
|
346
|
+
<args input="text|file, size" output="filename, format" />
|
|
347
|
+
</txt2img>'
|
|
348
|
+
|
|
349
|
+
claudiv new txt2img -s '<txt2img lang="python" type="cli" gen="">
|
|
350
|
+
<ai provider="openai" />
|
|
351
|
+
<config apikey organizationid />
|
|
352
|
+
<args input="text|file, size" output="filename, format" />
|
|
353
|
+
</txt2img>' -g Create + generate immediately
|
|
354
|
+
|
|
355
|
+
Generate:
|
|
356
|
+
claudiv gen myapp Generate all from myapp.cdml
|
|
357
|
+
claudiv gen myapp.cdml Explicit .cdml path
|
|
358
|
+
claudiv gen txt2img -t config Generate only config component
|
|
359
|
+
claudiv gen myapp -w Watch mode
|
|
360
|
+
claudiv gen myapp -o output.py Custom output file
|
|
361
|
+
|
|
362
|
+
Component Target (-t):
|
|
363
|
+
claudiv gen txt2img -t config Generates txt2img.config
|
|
364
|
+
claudiv gen myapp -t api Generates myapp.api
|
|
365
|
+
claudiv gen myapp -t database Generates myapp.database
|
|
366
|
+
|
|
367
|
+
Reverse Engineering:
|
|
368
|
+
claudiv reverse api.py → api.cdml
|
|
369
|
+
claudiv reverse Button.tsx → Button.cdml
|
|
370
|
+
claudiv reverse backup.sh → backup.cdml
|
|
371
|
+
claudiv reverse styles.css → styles.cdml
|
|
372
|
+
claudiv reverse api.py -o spec.cdml Custom output name
|
|
373
|
+
|
|
374
|
+
Watch Mode:
|
|
375
|
+
claudiv watch myapp Watch myapp.cdml for changes
|
|
376
|
+
claudiv gen myapp -w Same as above
|
|
377
|
+
|
|
378
|
+
Run in Empty Folder:
|
|
379
|
+
npx @claudiv/cli new myapp -t python -g
|
|
380
|
+
|
|
381
|
+
FILE FORMAT
|
|
382
|
+
Input: <name>.cdml
|
|
383
|
+
Output: <name>.<ext> (based on target language)
|
|
384
|
+
|
|
385
|
+
Examples:
|
|
386
|
+
app.cdml → app.html (target=html)
|
|
387
|
+
api.cdml → api.py (target=python)
|
|
388
|
+
backup.cdml → backup.sh (target=bash)
|
|
389
|
+
deploy.cdml → deploy.yaml (target=kubernetes)
|
|
390
|
+
|
|
391
|
+
SPEC SYNTAX
|
|
392
|
+
<element-name [attributes] gen[="instructions"]>
|
|
393
|
+
Natural language description
|
|
394
|
+
</element-name>
|
|
395
|
+
|
|
396
|
+
Action attributes:
|
|
397
|
+
gen Generate new code
|
|
398
|
+
gen="..." Generate with specific instructions
|
|
399
|
+
retry Regenerate (not satisfied)
|
|
400
|
+
undo Revert to previous version
|
|
401
|
+
lock Protect from regeneration
|
|
402
|
+
unlock Allow regeneration of locked element
|
|
403
|
+
|
|
404
|
+
MORE INFO
|
|
405
|
+
https://github.com/claudiv-ai/claudiv
|
|
406
|
+
`);
|
|
407
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic API integration for direct API access
|
|
3
|
+
*/
|
|
4
|
+
import type { HierarchyContext } from '@claudiv/core';
|
|
5
|
+
export declare class ClaudeAPIClient {
|
|
6
|
+
private client;
|
|
7
|
+
constructor(apiKey: string);
|
|
8
|
+
/**
|
|
9
|
+
* Send prompt to Claude API and stream response
|
|
10
|
+
*/
|
|
11
|
+
sendPrompt(userMessage: string, context: HierarchyContext): AsyncGenerator<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Check if API is available
|
|
14
|
+
*/
|
|
15
|
+
checkAvailable(): Promise<boolean>;
|
|
16
|
+
/**
|
|
17
|
+
* Build system prompt with context
|
|
18
|
+
*/
|
|
19
|
+
private buildSystemPrompt;
|
|
20
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic API integration for direct API access
|
|
3
|
+
*/
|
|
4
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
5
|
+
import { logger } from './utils/logger.js';
|
|
6
|
+
import { buildPromptContext } from '@claudiv/core';
|
|
7
|
+
export class ClaudeAPIClient {
|
|
8
|
+
client;
|
|
9
|
+
constructor(apiKey) {
|
|
10
|
+
this.client = new Anthropic({
|
|
11
|
+
apiKey,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Send prompt to Claude API and stream response
|
|
16
|
+
*/
|
|
17
|
+
async *sendPrompt(userMessage, context) {
|
|
18
|
+
logger.processing('Sending request to Claude API...');
|
|
19
|
+
// Build system prompt with hierarchy context
|
|
20
|
+
const systemPrompt = this.buildSystemPrompt(context);
|
|
21
|
+
try {
|
|
22
|
+
// Stream from API
|
|
23
|
+
const stream = await this.client.messages.stream({
|
|
24
|
+
model: 'claude-opus-4-6',
|
|
25
|
+
max_tokens: 4096,
|
|
26
|
+
system: systemPrompt,
|
|
27
|
+
messages: [
|
|
28
|
+
{
|
|
29
|
+
role: 'user',
|
|
30
|
+
content: userMessage,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
let chunkCount = 0;
|
|
35
|
+
for await (const chunk of stream) {
|
|
36
|
+
if (chunk.type === 'content_block_delta' &&
|
|
37
|
+
chunk.delta.type === 'text_delta') {
|
|
38
|
+
chunkCount++;
|
|
39
|
+
yield chunk.delta.text;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
logger.debug(`Received ${chunkCount} chunks from API`);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
const err = error;
|
|
46
|
+
logger.error(`Claude API error: ${err.message}`);
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Check if API is available
|
|
52
|
+
*/
|
|
53
|
+
async checkAvailable() {
|
|
54
|
+
try {
|
|
55
|
+
// Test with a minimal request
|
|
56
|
+
await this.client.messages.create({
|
|
57
|
+
model: 'claude-opus-4-6',
|
|
58
|
+
max_tokens: 10,
|
|
59
|
+
messages: [{ role: 'user', content: 'test' }],
|
|
60
|
+
});
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
logger.error('Claude API not available');
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Build system prompt with context
|
|
70
|
+
*/
|
|
71
|
+
buildSystemPrompt(context) {
|
|
72
|
+
const contextStr = buildPromptContext(context);
|
|
73
|
+
return `You are an AI assistant helping generate HTML/CSS code from natural language requests.
|
|
74
|
+
|
|
75
|
+
${contextStr}
|
|
76
|
+
|
|
77
|
+
**CRITICAL IMPLEMENTATION REQUIREMENTS:**
|
|
78
|
+
⚠️ NEVER use placeholder comments like "<!-- Child components render here -->" or "<!-- TODO: implement X -->"
|
|
79
|
+
⚠️ If the user request lists NESTED COMPONENTS TO IMPLEMENT, you MUST generate complete, working HTML/CSS for EVERY single one
|
|
80
|
+
⚠️ Each nested component specification MUST result in actual HTML elements with proper styling, NOT comments
|
|
81
|
+
⚠️ If you see a list like "1. <nav-menu>", "2. <pages>", etc., these are REQUIRED components that need full implementations
|
|
82
|
+
|
|
83
|
+
**Instructions:**
|
|
84
|
+
1. Structure your response as XML elements (NOT plain text)
|
|
85
|
+
2. ${context.existingCode ? 'Modify ONLY what the user requested, keeping everything else exactly the same' : 'Generate the necessary HTML/CSS code appropriate for this context'}
|
|
86
|
+
3. Consider the hierarchy - you're working on: ${context.elementPath}
|
|
87
|
+
4. If referenced elements are provided above, use their definitions to inform your implementation
|
|
88
|
+
5. If the user request contains a section "NESTED COMPONENTS TO IMPLEMENT", you MUST implement EACH ONE with complete HTML/CSS:
|
|
89
|
+
- Create actual HTML elements for each component
|
|
90
|
+
- Add proper CSS styling for each component
|
|
91
|
+
- Use the attributes as specifications for styling and behavior
|
|
92
|
+
- Use the content as guidance for what to include
|
|
93
|
+
- Do NOT use placeholder comments - implement the actual functionality
|
|
94
|
+
- **CRITICAL EXCEPTION - LOCKED COMPONENTS**:
|
|
95
|
+
- Components marked with "[LOCKED - DO NOT REGENERATE]" are already implemented
|
|
96
|
+
- You MUST preserve locked components exactly as they are
|
|
97
|
+
- Do NOT regenerate, modify, or replace locked components
|
|
98
|
+
- Include them as-is or reference them in your generated code
|
|
99
|
+
- Only implement components that are NOT marked as locked
|
|
100
|
+
|
|
101
|
+
**Response Format (use semantic XML tags, NOT plain text):**
|
|
102
|
+
<changes>Brief description of what was created/modified</changes>
|
|
103
|
+
<details>
|
|
104
|
+
<tag-name>Specific detail about the implementation</tag-name>
|
|
105
|
+
<another-tag>Another detail</another-tag>
|
|
106
|
+
</details>
|
|
107
|
+
<summary>Overall summary</summary>
|
|
108
|
+
|
|
109
|
+
IMPORTANT:
|
|
110
|
+
- Use semantic tag names that describe the content (e.g., <styling>, <background>, <effect>, <feature>, etc.)
|
|
111
|
+
- DO NOT use plain text or bullet points
|
|
112
|
+
- Structure your response with nested XML elements
|
|
113
|
+
- Still include code blocks with \`\`\`html and \`\`\`css for implementation
|
|
114
|
+
- EVERY nested component must be fully implemented, not just mentioned in comments
|
|
115
|
+
`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code CLI integration via subprocess
|
|
3
|
+
*/
|
|
4
|
+
import type { HierarchyContext } from '@claudiv/core';
|
|
5
|
+
export declare class ClaudeCLIClient {
|
|
6
|
+
/**
|
|
7
|
+
* Send prompt to Claude Code CLI and stream response
|
|
8
|
+
*/
|
|
9
|
+
sendPrompt(userMessage: string, context: HierarchyContext): AsyncGenerator<string>;
|
|
10
|
+
/**
|
|
11
|
+
* Check if Claude CLI is installed
|
|
12
|
+
*/
|
|
13
|
+
checkAvailable(): Promise<boolean>;
|
|
14
|
+
/**
|
|
15
|
+
* Build prompt with hierarchy context
|
|
16
|
+
*/
|
|
17
|
+
private buildPrompt;
|
|
18
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code CLI integration via subprocess
|
|
3
|
+
*/
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import { logger } from './utils/logger.js';
|
|
6
|
+
import { buildPromptContext } from '@claudiv/core';
|
|
7
|
+
export class ClaudeCLIClient {
|
|
8
|
+
/**
|
|
9
|
+
* Send prompt to Claude Code CLI and stream response
|
|
10
|
+
*/
|
|
11
|
+
async *sendPrompt(userMessage, context) {
|
|
12
|
+
logger.processing('Sending request to Claude CLI...');
|
|
13
|
+
// Build full prompt with context
|
|
14
|
+
const fullPrompt = this.buildPrompt(userMessage, context);
|
|
15
|
+
// Spawn Claude process with --print for non-interactive mode
|
|
16
|
+
// Unset CLAUDECODE to allow nested invocation
|
|
17
|
+
const claude = spawn('claude', ['--print', fullPrompt], {
|
|
18
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
19
|
+
env: {
|
|
20
|
+
...process.env,
|
|
21
|
+
CLAUDECODE: '', // Unset to allow nested sessions
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
let hasOutput = false;
|
|
25
|
+
// Stream stdout chunks
|
|
26
|
+
for await (const chunk of claude.stdout) {
|
|
27
|
+
hasOutput = true;
|
|
28
|
+
yield chunk.toString('utf-8');
|
|
29
|
+
}
|
|
30
|
+
// Collect errors
|
|
31
|
+
let errorOutput = '';
|
|
32
|
+
claude.stderr.on('data', (data) => {
|
|
33
|
+
errorOutput += data.toString();
|
|
34
|
+
});
|
|
35
|
+
// Wait for process to complete
|
|
36
|
+
const exitCode = await new Promise((resolve) => {
|
|
37
|
+
claude.on('close', resolve);
|
|
38
|
+
});
|
|
39
|
+
if (exitCode !== 0) {
|
|
40
|
+
logger.error(`Claude CLI exited with code ${exitCode}`);
|
|
41
|
+
if (errorOutput) {
|
|
42
|
+
logger.error(`Error output: ${errorOutput}`);
|
|
43
|
+
}
|
|
44
|
+
throw new Error(`Claude CLI failed with exit code ${exitCode}`);
|
|
45
|
+
}
|
|
46
|
+
if (!hasOutput) {
|
|
47
|
+
logger.warn('No output received from Claude CLI');
|
|
48
|
+
}
|
|
49
|
+
logger.debug('Claude CLI request completed');
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Check if Claude CLI is installed
|
|
53
|
+
*/
|
|
54
|
+
async checkAvailable() {
|
|
55
|
+
try {
|
|
56
|
+
const check = spawn('claude', ['--version'], {
|
|
57
|
+
env: {
|
|
58
|
+
...process.env,
|
|
59
|
+
CLAUDECODE: '', // Unset to allow check
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
const exitCode = await new Promise((resolve) => {
|
|
63
|
+
check.on('close', resolve);
|
|
64
|
+
check.on('error', () => resolve(null));
|
|
65
|
+
});
|
|
66
|
+
return exitCode === 0;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Build prompt with hierarchy context
|
|
74
|
+
*/
|
|
75
|
+
buildPrompt(userMessage, context) {
|
|
76
|
+
const contextStr = buildPromptContext(context);
|
|
77
|
+
return `You are an AI assistant helping generate HTML/CSS code from natural language requests.
|
|
78
|
+
|
|
79
|
+
${contextStr}
|
|
80
|
+
|
|
81
|
+
**User Request:** ${userMessage}
|
|
82
|
+
|
|
83
|
+
**CRITICAL IMPLEMENTATION REQUIREMENTS:**
|
|
84
|
+
⚠️ NEVER use placeholder comments like "<!-- Child components render here -->" or "<!-- TODO: implement X -->"
|
|
85
|
+
⚠️ If the user request lists NESTED COMPONENTS TO IMPLEMENT, you MUST generate complete, working HTML/CSS for EVERY single one
|
|
86
|
+
⚠️ Each nested component specification MUST result in actual HTML elements with proper styling, NOT comments
|
|
87
|
+
⚠️ If you see a list like "1. <nav-menu>", "2. <pages>", etc., these are REQUIRED components that need full implementations
|
|
88
|
+
|
|
89
|
+
**Instructions:**
|
|
90
|
+
1. Structure your response as XML elements (NOT plain text)
|
|
91
|
+
2. ${context.existingCode ? 'Modify ONLY what the user requested, keeping everything else exactly the same' : 'Generate the necessary HTML/CSS code appropriate for this context'}
|
|
92
|
+
3. Consider the hierarchy - you're working on: ${context.elementPath}
|
|
93
|
+
4. If referenced elements are provided above, use their definitions to inform your implementation
|
|
94
|
+
5. If the user request contains a section "NESTED COMPONENTS TO IMPLEMENT", you MUST implement EACH ONE with complete HTML/CSS:
|
|
95
|
+
- Create actual HTML elements for each component
|
|
96
|
+
- Add proper CSS styling for each component
|
|
97
|
+
- Use the attributes as specifications for styling and behavior
|
|
98
|
+
- Use the content as guidance for what to include
|
|
99
|
+
- Do NOT use placeholder comments - implement the actual functionality
|
|
100
|
+
- **CRITICAL EXCEPTION - LOCKED COMPONENTS**:
|
|
101
|
+
- Components marked with "[LOCKED - DO NOT REGENERATE]" are already implemented
|
|
102
|
+
- You MUST preserve locked components exactly as they are
|
|
103
|
+
- Do NOT regenerate, modify, or replace locked components
|
|
104
|
+
- Include them as-is or reference them in your generated code
|
|
105
|
+
- Only implement components that are NOT marked as locked
|
|
106
|
+
|
|
107
|
+
**Response Format (use semantic XML tags, NOT plain text):**
|
|
108
|
+
<changes>Brief description of what was created/modified</changes>
|
|
109
|
+
<details>
|
|
110
|
+
<tag-name>Specific detail about the implementation</tag-name>
|
|
111
|
+
<another-tag>Another detail</another-tag>
|
|
112
|
+
</details>
|
|
113
|
+
<summary>Overall summary</summary>
|
|
114
|
+
|
|
115
|
+
IMPORTANT:
|
|
116
|
+
- Use semantic tag names that describe the content (e.g., <styling>, <background>, <effect>, <feature>, etc.)
|
|
117
|
+
- DO NOT use plain text or bullet points
|
|
118
|
+
- Structure your response with nested XML elements
|
|
119
|
+
- Still include code blocks with \`\`\`html and \`\`\`css for implementation
|
|
120
|
+
- EVERY nested component must be fully implemented, not just mentioned in comments
|
|
121
|
+
|
|
122
|
+
Please respond now.`;
|
|
123
|
+
}
|
|
124
|
+
}
|