@claudiv/cli 0.1.2 ā 0.1.3
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/dist/dev-server.d.ts +9 -0
- package/dist/dev-server.js +33 -7
- package/dist/index.js +21 -17
- package/dist/updater.d.ts +3 -3
- package/dist/updater.js +7 -7
- package/package.json +1 -1
package/dist/dev-server.d.ts
CHANGED
|
@@ -4,7 +4,16 @@
|
|
|
4
4
|
export declare class DevServer {
|
|
5
5
|
private server;
|
|
6
6
|
private port;
|
|
7
|
+
private outputFile;
|
|
8
|
+
/**
|
|
9
|
+
* Set the output file to serve (e.g., 'test.html', 'myapp.html')
|
|
10
|
+
*/
|
|
11
|
+
setOutputFile(filename: string): void;
|
|
7
12
|
start(): Promise<void>;
|
|
8
13
|
stop(): Promise<void>;
|
|
9
14
|
getPort(): number;
|
|
15
|
+
/**
|
|
16
|
+
* Open the browser to the specific file
|
|
17
|
+
*/
|
|
18
|
+
openInBrowser(filename: string): Promise<void>;
|
|
10
19
|
}
|
package/dist/dev-server.js
CHANGED
|
@@ -7,6 +7,13 @@ import { logger } from './utils/logger.js';
|
|
|
7
7
|
export class DevServer {
|
|
8
8
|
server = null;
|
|
9
9
|
port = 30004;
|
|
10
|
+
outputFile = 'app.html'; // Default, can be configured
|
|
11
|
+
/**
|
|
12
|
+
* Set the output file to serve (e.g., 'test.html', 'myapp.html')
|
|
13
|
+
*/
|
|
14
|
+
setOutputFile(filename) {
|
|
15
|
+
this.outputFile = filename;
|
|
16
|
+
}
|
|
10
17
|
async start() {
|
|
11
18
|
try {
|
|
12
19
|
// Create Vite server
|
|
@@ -15,20 +22,21 @@ export class DevServer {
|
|
|
15
22
|
server: {
|
|
16
23
|
port: this.port,
|
|
17
24
|
strictPort: false, // Auto-increment if port is busy
|
|
18
|
-
open:
|
|
25
|
+
open: false, // Don't auto-open on start (we'll open after first generation)
|
|
19
26
|
},
|
|
20
27
|
plugins: [
|
|
21
28
|
{
|
|
22
29
|
name: 'spec-code-server',
|
|
23
|
-
configureServer(server) {
|
|
30
|
+
configureServer: (server) => {
|
|
31
|
+
const self = this;
|
|
24
32
|
server.middlewares.use(async (req, res, next) => {
|
|
25
|
-
// Serve generated code
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
28
|
-
if (existsSync(outputFile)) {
|
|
33
|
+
// Serve generated code at its specific path
|
|
34
|
+
const requestPath = req.url?.split('?')[0]; // Remove query params
|
|
35
|
+
if (requestPath === '/' || requestPath === '/index.html' || requestPath === `/${self.outputFile}`) {
|
|
36
|
+
if (existsSync(self.outputFile)) {
|
|
29
37
|
// Read and serve the file directly
|
|
30
38
|
const { readFile } = await import('fs/promises');
|
|
31
|
-
const content = await readFile(outputFile, 'utf-8');
|
|
39
|
+
const content = await readFile(self.outputFile, 'utf-8');
|
|
32
40
|
res.statusCode = 200;
|
|
33
41
|
res.setHeader('Content-Type', 'text/html');
|
|
34
42
|
res.end(content);
|
|
@@ -115,4 +123,22 @@ export class DevServer {
|
|
|
115
123
|
getPort() {
|
|
116
124
|
return this.port;
|
|
117
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Open the browser to the specific file
|
|
128
|
+
*/
|
|
129
|
+
async openInBrowser(filename) {
|
|
130
|
+
const url = `http://localhost:${this.port}/${filename}`;
|
|
131
|
+
try {
|
|
132
|
+
// Use platform-specific command to open browser
|
|
133
|
+
const { spawn } = await import('child_process');
|
|
134
|
+
const command = process.platform === 'darwin' ? 'open' :
|
|
135
|
+
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
136
|
+
spawn(command, [url], { detached: true, stdio: 'ignore' }).unref();
|
|
137
|
+
logger.info(`Opened ${filename} in browser`);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
// If command fails, just log the URL
|
|
141
|
+
logger.info(`View at: ${url}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
118
144
|
}
|
package/dist/index.js
CHANGED
|
@@ -26,10 +26,14 @@ async function main() {
|
|
|
26
26
|
// For now, generation happens on-demand through chat patterns
|
|
27
27
|
// Create file watcher
|
|
28
28
|
const watcher = new SpecFileWatcher(config);
|
|
29
|
+
// Start Vite dev server
|
|
30
|
+
const devServer = new DevServer();
|
|
31
|
+
await devServer.start();
|
|
32
|
+
logger.info('');
|
|
29
33
|
// Handle file changes
|
|
30
34
|
watcher.on('change', async (filePath) => {
|
|
31
35
|
try {
|
|
32
|
-
await processSpecFile(filePath, config, claudeClient, watcher);
|
|
36
|
+
await processSpecFile(filePath, config, claudeClient, watcher, devServer);
|
|
33
37
|
}
|
|
34
38
|
catch (error) {
|
|
35
39
|
const err = error;
|
|
@@ -46,10 +50,6 @@ async function main() {
|
|
|
46
50
|
logger.info('š” Example: <create-button gen>Make a blue button</create-button>');
|
|
47
51
|
logger.info('š” Example: <my-button color="blue" size="large" gen />');
|
|
48
52
|
logger.info('');
|
|
49
|
-
// Start Vite dev server
|
|
50
|
-
const devServer = new DevServer();
|
|
51
|
-
await devServer.start();
|
|
52
|
-
logger.info('');
|
|
53
53
|
// Handle graceful shutdown
|
|
54
54
|
const shutdown = async () => {
|
|
55
55
|
logger.info('\nš Shutting down...');
|
|
@@ -63,7 +63,7 @@ async function main() {
|
|
|
63
63
|
/**
|
|
64
64
|
* Process .cdml file for chat patterns
|
|
65
65
|
*/
|
|
66
|
-
async function processSpecFile(filePath, config, claudeClient, watcher) {
|
|
66
|
+
async function processSpecFile(filePath, config, claudeClient, watcher, devServer) {
|
|
67
67
|
// Skip if we're updating the file ourselves
|
|
68
68
|
if (watcher.isCurrentlyUpdating()) {
|
|
69
69
|
logger.debug('Skipping processing (internal update in progress)');
|
|
@@ -81,13 +81,13 @@ async function processSpecFile(filePath, config, claudeClient, watcher) {
|
|
|
81
81
|
logger.info(`Found ${parsed.chatPatterns.length} chat pattern(s) to process`);
|
|
82
82
|
// Process each chat pattern
|
|
83
83
|
for (const pattern of parsed.chatPatterns) {
|
|
84
|
-
await processChatPattern(pattern, config, claudeClient, watcher, parsed.dom);
|
|
84
|
+
await processChatPattern(pattern, config, claudeClient, watcher, parsed.dom, devServer);
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
/**
|
|
88
88
|
* Process a single chat pattern
|
|
89
89
|
*/
|
|
90
|
-
async function processChatPattern(pattern, config, claudeClient, watcher,
|
|
90
|
+
async function processChatPattern(pattern, config, claudeClient, watcher, $, devServer) {
|
|
91
91
|
const { action, element, elementName, specAttributes, userMessage, context, elementPath } = pattern;
|
|
92
92
|
// Build display message
|
|
93
93
|
const attrPreview = Object.entries(specAttributes).length > 0
|
|
@@ -126,25 +126,24 @@ async function processChatPattern(pattern, config, claudeClient, watcher, $) {
|
|
|
126
126
|
// Accumulate full response
|
|
127
127
|
let fullResponse = '';
|
|
128
128
|
// Send to Claude and stream response
|
|
129
|
+
logger.info('Claude response:');
|
|
129
130
|
for await (const chunk of claudeClient.sendPrompt(fullPrompt, context)) {
|
|
130
131
|
fullResponse += chunk;
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
process.stdout.write('.');
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
if (process.env.DEBUG) {
|
|
137
|
-
process.stdout.write('\n');
|
|
132
|
+
// Stream output to console so user can see progress
|
|
133
|
+
process.stdout.write(chunk);
|
|
138
134
|
}
|
|
139
|
-
|
|
135
|
+
process.stdout.write('\n\n');
|
|
136
|
+
logger.success('ā Response complete');
|
|
140
137
|
// Check if response contains code blocks
|
|
141
138
|
const codeBlocks = extractCodeBlocks(fullResponse);
|
|
142
139
|
const hasCode = codeBlocks.length > 0;
|
|
143
140
|
// Generate code file using universal generator (if there's code)
|
|
141
|
+
let outputFileName;
|
|
144
142
|
if (hasCode) {
|
|
145
143
|
const generated = await generateCode(fullResponse, pattern, context);
|
|
146
144
|
// Determine output file path
|
|
147
145
|
const outputFile = config.specFile.replace('.cdml', generated.fileExtension);
|
|
146
|
+
outputFileName = outputFile.split('/').pop();
|
|
148
147
|
// Write generated code
|
|
149
148
|
await writeFile(outputFile, generated.code, 'utf-8');
|
|
150
149
|
// Make executable if it's a script
|
|
@@ -153,11 +152,16 @@ async function processChatPattern(pattern, config, claudeClient, watcher, $) {
|
|
|
153
152
|
logger.debug(`Made ${outputFile} executable`);
|
|
154
153
|
}
|
|
155
154
|
logger.success(`Generated ${pattern.target} code ā ${outputFile}`);
|
|
155
|
+
// Set output file on dev server and open in browser (for HTML files)
|
|
156
|
+
if (outputFileName && generated.fileExtension === '.html') {
|
|
157
|
+
devServer.setOutputFile(outputFileName);
|
|
158
|
+
await devServer.openInBrowser(outputFileName);
|
|
159
|
+
}
|
|
156
160
|
}
|
|
157
161
|
// Update spec.html: remove action attribute and add <ai> child with response
|
|
158
162
|
watcher.setUpdating(true);
|
|
159
163
|
try {
|
|
160
|
-
await updateSpecWithResponse($, element, action, fullResponse, config.specFile, hasCode);
|
|
164
|
+
await updateSpecWithResponse($, element, action, fullResponse, config.specFile, hasCode, outputFileName);
|
|
161
165
|
}
|
|
162
166
|
finally {
|
|
163
167
|
watcher.setUpdating(false);
|
package/dist/updater.d.ts
CHANGED
|
@@ -6,11 +6,11 @@ import type { Element } from 'domhandler';
|
|
|
6
6
|
/**
|
|
7
7
|
* Strip code blocks from response and replace with reference
|
|
8
8
|
*/
|
|
9
|
-
export declare function stripCodeBlocks(response: string, hasCode: boolean): string;
|
|
9
|
+
export declare function stripCodeBlocks(response: string, hasCode: boolean, outputFileName?: string): string;
|
|
10
10
|
/**
|
|
11
11
|
* Update element with AI response: remove action attribute and add/update <ai> child
|
|
12
12
|
*/
|
|
13
|
-
export declare function updateElementWithResponse($: CheerioAPI, element: Element, action: 'gen' | 'retry' | 'undo', response: string, hasCode?: boolean): void;
|
|
13
|
+
export declare function updateElementWithResponse($: CheerioAPI, element: Element, action: 'gen' | 'retry' | 'undo', response: string, hasCode?: boolean, outputFileName?: string): void;
|
|
14
14
|
/**
|
|
15
15
|
* Serialize cheerio DOM back to HTML string
|
|
16
16
|
*/
|
|
@@ -26,4 +26,4 @@ export declare function sleep(ms: number): Promise<void>;
|
|
|
26
26
|
/**
|
|
27
27
|
* Complete update flow: remove action attribute, add AI response, serialize, write file
|
|
28
28
|
*/
|
|
29
|
-
export declare function updateSpecWithResponse($: CheerioAPI, element: Element, action: 'gen' | 'retry' | 'undo', response: string, filePath: string, hasCode?: boolean): Promise<void>;
|
|
29
|
+
export declare function updateSpecWithResponse($: CheerioAPI, element: Element, action: 'gen' | 'retry' | 'undo', response: string, filePath: string, hasCode?: boolean, outputFileName?: string): Promise<void>;
|
package/dist/updater.js
CHANGED
|
@@ -6,25 +6,25 @@ import { logger } from './utils/logger.js';
|
|
|
6
6
|
/**
|
|
7
7
|
* Strip code blocks from response and replace with reference
|
|
8
8
|
*/
|
|
9
|
-
export function stripCodeBlocks(response, hasCode) {
|
|
9
|
+
export function stripCodeBlocks(response, hasCode, outputFileName) {
|
|
10
10
|
// Remove code blocks (```...```)
|
|
11
11
|
const withoutCodeBlocks = response.replace(/```[\s\S]*?```/g, '').trim();
|
|
12
12
|
// If there was code, add a reference
|
|
13
|
-
if (hasCode) {
|
|
14
|
-
return `${withoutCodeBlocks}\n\nā Implementation in
|
|
13
|
+
if (hasCode && outputFileName) {
|
|
14
|
+
return `${withoutCodeBlocks}\n\nā Implementation in ${outputFileName}`;
|
|
15
15
|
}
|
|
16
16
|
return withoutCodeBlocks;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* Update element with AI response: remove action attribute and add/update <ai> child
|
|
20
20
|
*/
|
|
21
|
-
export function updateElementWithResponse($, element, action, response, hasCode = false) {
|
|
21
|
+
export function updateElementWithResponse($, element, action, response, hasCode = false, outputFileName) {
|
|
22
22
|
const $element = $(element);
|
|
23
23
|
// 1. Remove the action attribute (gen/retry/undo)
|
|
24
24
|
$element.removeAttr(action);
|
|
25
25
|
logger.debug(`Removed '${action}' attribute from element`);
|
|
26
26
|
// 2. Strip code blocks and add reference if code was generated
|
|
27
|
-
const cleanResponse = stripCodeBlocks(response, hasCode);
|
|
27
|
+
const cleanResponse = stripCodeBlocks(response, hasCode, outputFileName);
|
|
28
28
|
// 3. Add or append <ai> child element with response
|
|
29
29
|
// Create new <ai> element
|
|
30
30
|
const aiElement = $('<ai></ai>');
|
|
@@ -66,9 +66,9 @@ export function sleep(ms) {
|
|
|
66
66
|
/**
|
|
67
67
|
* Complete update flow: remove action attribute, add AI response, serialize, write file
|
|
68
68
|
*/
|
|
69
|
-
export async function updateSpecWithResponse($, element, action, response, filePath, hasCode = false) {
|
|
69
|
+
export async function updateSpecWithResponse($, element, action, response, filePath, hasCode = false, outputFileName) {
|
|
70
70
|
// Update the element: remove action attribute and add <ai> child
|
|
71
|
-
updateElementWithResponse($, element, action, response, hasCode);
|
|
71
|
+
updateElementWithResponse($, element, action, response, hasCode, outputFileName);
|
|
72
72
|
// Serialize back to HTML
|
|
73
73
|
const updatedHTML = await serializeToHTML($);
|
|
74
74
|
// Write to file
|