@claudiv/cli 0.1.0 → 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/config.js CHANGED
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import dotenv from 'dotenv';
5
5
  import { existsSync, readdirSync } from 'fs';
6
- import { join } from 'path';
6
+ import { join, isAbsolute } from 'path';
7
7
  import { logger } from './utils/logger.js';
8
8
  // Load .env file
9
9
  dotenv.config();
@@ -32,9 +32,10 @@ export function loadConfig() {
32
32
  const cliFile = process.argv[2];
33
33
  let specFile;
34
34
  if (cliFile) {
35
- specFile = join(process.cwd(), cliFile);
35
+ // If path is already absolute, use it directly; otherwise join with cwd
36
+ specFile = isAbsolute(cliFile) ? cliFile : join(process.cwd(), cliFile);
36
37
  if (!existsSync(specFile)) {
37
- logger.error(`File not found: ${cliFile}`);
38
+ logger.error(`File not found: ${specFile}`);
38
39
  process.exit(1);
39
40
  }
40
41
  }
@@ -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
  }
@@ -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: true, // Open browser automatically
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 as the root
26
- const outputFile = 'app.html'; // TODO: Make configurable
27
- if (req.url === '/' || req.url === '/index.html') {
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
- // Optionally: show progress
132
- if (process.env.DEBUG) {
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
- logger.success('Received response from Claude');
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 spec.code.html`;
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudiv/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "Claude in a Div — Universal Declarative Generation Platform",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",