skema-core 0.1.0 → 0.1.2

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/cli.js CHANGED
@@ -3,35 +3,1198 @@
3
3
 
4
4
  var fs = require('fs');
5
5
  var path = require('path');
6
+ require('dotenv/config');
7
+ var ws = require('ws');
8
+ var child_process = require('child_process');
9
+ var generativeAi = require('@google/generative-ai');
6
10
 
7
- var ROUTE_CONTENT = `export { POST, DELETE } from 'skema-core/server';
8
- `;
11
+ function _interopNamespace(e) {
12
+ if (e && e.__esModule) return e;
13
+ var n = Object.create(null);
14
+ if (e) {
15
+ Object.keys(e).forEach(function (k) {
16
+ if (k !== 'default') {
17
+ var d = Object.getOwnPropertyDescriptor(e, k);
18
+ Object.defineProperty(n, k, d.get ? d : {
19
+ enumerable: true,
20
+ get: function () { return e[k]; }
21
+ });
22
+ }
23
+ });
24
+ }
25
+ n.default = e;
26
+ return Object.freeze(n);
27
+ }
28
+
29
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
30
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
31
+
32
+ var __defProp = Object.defineProperty;
33
+ var __getOwnPropNames = Object.getOwnPropertyNames;
34
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
35
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
36
+ }) : x)(function(x) {
37
+ if (typeof require !== "undefined") return require.apply(this, arguments);
38
+ throw Error('Dynamic require of "' + x + '" is not supported');
39
+ });
40
+ var __esm = (fn, res) => function __init() {
41
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
42
+ };
43
+ var __export = (target, all) => {
44
+ for (var name in all)
45
+ __defProp(target, name, { get: all[name], enumerable: true });
46
+ };
47
+
48
+ // src/cli/init.ts
49
+ var init_exports = {};
50
+ __export(init_exports, {
51
+ init: () => init
52
+ });
53
+ function detectFramework(cwd) {
54
+ const packageJsonPath = path.join(cwd, "package.json");
55
+ if (!fs.existsSync(packageJsonPath)) return "unknown";
56
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
57
+ const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
58
+ if (deps["next"]) return "nextjs";
59
+ if (deps["vite"]) return "vite";
60
+ if (deps["react-scripts"]) return "cra";
61
+ if (deps["@remix-run/react"]) return "remix";
62
+ return "unknown";
63
+ }
64
+ function setupNextjs(cwd) {
65
+ const configFiles = ["next.config.js", "next.config.mjs", "next.config.ts"];
66
+ let configPath = null;
67
+ for (const file of configFiles) {
68
+ if (fs.existsSync(path.join(cwd, file))) {
69
+ configPath = path.join(cwd, file);
70
+ break;
71
+ }
72
+ }
73
+ if (configPath) {
74
+ let content = fs.readFileSync(configPath, "utf-8");
75
+ let modified = false;
76
+ if (!content.includes("reactStrictMode")) {
77
+ content = content.replace(
78
+ /(const\s+\w+\s*=\s*\{)/,
79
+ "$1\n reactStrictMode: false, // Required for tldraw"
80
+ );
81
+ modified = true;
82
+ } else if (content.includes("reactStrictMode: true")) {
83
+ content = content.replace(
84
+ "reactStrictMode: true",
85
+ "reactStrictMode: false // Required for tldraw"
86
+ );
87
+ modified = true;
88
+ }
89
+ if (!content.includes("transpilePackages")) {
90
+ content = content.replace(
91
+ /(const\s+\w+\s*=\s*\{)/,
92
+ "$1\n transpilePackages: ['skema-core'],"
93
+ );
94
+ modified = true;
95
+ } else if (!content.includes("'skema-core'") && !content.includes('"skema-core"')) {
96
+ content = content.replace(
97
+ /transpilePackages:\s*\[/,
98
+ "transpilePackages: ['skema-core', "
99
+ );
100
+ modified = true;
101
+ }
102
+ if (modified) {
103
+ fs.writeFileSync(configPath, content);
104
+ console.log(` \u2713 Updated ${configPath.replace(cwd, ".")}`);
105
+ return true;
106
+ } else {
107
+ console.log(` \u2713 ${configPath.replace(cwd, ".")} already configured`);
108
+ return false;
109
+ }
110
+ } else {
111
+ fs.writeFileSync(
112
+ path.join(cwd, "next.config.js"),
113
+ `/** @type {import('next').NextConfig} */
114
+ const nextConfig = {
115
+ reactStrictMode: false, // Required for tldraw
116
+ transpilePackages: ['skema-core'],
117
+ };
118
+
119
+ module.exports = nextConfig;
120
+ `
121
+ );
122
+ console.log(" \u2713 Created next.config.js");
123
+ return true;
124
+ }
125
+ }
126
+ function removeStrictModeWrapper(cwd) {
127
+ const entryFiles = [
128
+ "src/main.tsx",
129
+ "src/main.jsx",
130
+ "src/index.tsx",
131
+ "src/index.jsx"
132
+ ];
133
+ for (const file of entryFiles) {
134
+ const filePath = path.join(cwd, file);
135
+ if (!fs.existsSync(filePath)) continue;
136
+ let content = fs.readFileSync(filePath, "utf-8");
137
+ if (!content.includes("StrictMode")) {
138
+ console.log(` \u2713 No StrictMode wrapper found in ${file}`);
139
+ return false;
140
+ }
141
+ const patterns = [
142
+ // <React.StrictMode>...</React.StrictMode>
143
+ /<React\.StrictMode>\s*([\s\S]*?)\s*<\/React\.StrictMode>/g,
144
+ // <StrictMode>...</StrictMode>
145
+ /<StrictMode>\s*([\s\S]*?)\s*<\/StrictMode>/g
146
+ ];
147
+ let modified = false;
148
+ for (const pattern of patterns) {
149
+ if (pattern.test(content)) {
150
+ content = content.replace(pattern, "$1");
151
+ modified = true;
152
+ }
153
+ }
154
+ if (modified && !content.includes("StrictMode")) {
155
+ content = content.replace(/,?\s*StrictMode\s*,?/g, (match) => {
156
+ return match.includes(",") ? ", " : "";
157
+ });
158
+ content = content.replace(/import\s*{\s*}\s*from\s*['"]react['"]\s*;?\n?/g, "");
159
+ content = content.replace(/import\s*{\s*,\s*/g, "import { ");
160
+ content = content.replace(/,\s*}\s*from/g, " } from");
161
+ }
162
+ if (modified) {
163
+ fs.writeFileSync(filePath, content);
164
+ console.log(` \u2713 Removed StrictMode wrapper from ${file}`);
165
+ console.log(" (Required for tldraw compatibility)");
166
+ return true;
167
+ }
168
+ }
169
+ return false;
170
+ }
9
171
  function init() {
10
172
  const cwd = process.cwd();
11
- const appDir = fs.existsSync(path.join(cwd, "app")) ? path.join(cwd, "app") : fs.existsSync(path.join(cwd, "src/app")) ? path.join(cwd, "src/app") : null;
12
- if (!appDir) {
13
- console.error("\u274C Could not find app/ or src/app/ directory.");
14
- console.error(" Make sure you run this from a Next.js App Router project root.");
15
- process.exit(1);
173
+ let modified = false;
174
+ console.log("");
175
+ console.log(" Skema Init");
176
+ console.log("");
177
+ const framework = detectFramework(cwd);
178
+ console.log(` Detected: ${framework === "unknown" ? "React app" : framework}`);
179
+ console.log("");
180
+ switch (framework) {
181
+ case "nextjs":
182
+ modified = setupNextjs(cwd) || modified;
183
+ break;
184
+ case "vite":
185
+ case "cra":
186
+ case "remix":
187
+ case "unknown":
188
+ modified = removeStrictModeWrapper(cwd) || modified;
189
+ break;
16
190
  }
17
- const apiDir = path.join(appDir, "api", "gemini");
18
- const routePath = path.join(apiDir, "route.ts");
19
- if (fs.existsSync(routePath)) {
20
- console.log("\u2713 API route already exists at", routePath.replace(cwd, "."));
21
- return;
191
+ console.log("");
192
+ if (modified) {
193
+ console.log(" Skema configured successfully!");
194
+ } else {
195
+ console.log(" Skema ready to use!");
22
196
  }
23
- if (!fs.existsSync(apiDir)) {
24
- fs.mkdirSync(apiDir, { recursive: true });
25
- console.log("\u2713 Created", apiDir.replace(cwd, "."));
197
+ console.log("");
198
+ console.log(" Usage:");
199
+ console.log("");
200
+ console.log(' import { Skema } from "skema-core";');
201
+ console.log("");
202
+ if (framework === "nextjs") {
203
+ console.log(" // In your layout.tsx or page");
204
+ console.log(' {process.env.NODE_ENV === "development" && <Skema />}');
205
+ } else {
206
+ console.log(" // In your App.tsx or main entry");
207
+ console.log(" {import.meta.env.DEV && <Skema />}");
26
208
  }
27
- fs.writeFileSync(routePath, ROUTE_CONTENT);
28
- console.log("\u2713 Created", routePath.replace(cwd, "."));
29
209
  console.log("");
30
- console.log("\u{1F389} Skema is ready! The Gemini CLI integration is now set up.");
210
+ console.log(" Start the daemon:");
211
+ console.log("");
212
+ console.log(" npx skema-serve");
213
+ console.log("");
214
+ console.log(" Then press \u2318\u21E7E (Cmd+Shift+E) to toggle Skema");
31
215
  console.log("");
32
- console.log("Usage:");
33
- console.log(" 1. Add <Skema /> to your page");
34
- console.log(" 2. Press Cmd+Shift+E to toggle the overlay");
35
- console.log(" 3. Annotate elements and Gemini CLI will make the changes");
36
216
  }
37
- init();
217
+ var init_init = __esm({
218
+ "src/cli/init.ts"() {
219
+ if (__require.main === module) {
220
+ init();
221
+ }
222
+ }
223
+ });
224
+ var PROVIDERS = {
225
+ gemini: {
226
+ command: "gemini",
227
+ buildArgs: (prompt, options) => {
228
+ const args2 = ["-p", prompt];
229
+ if (options?.yolo !== false) args2.push("--yolo");
230
+ args2.push("--output-format", "stream-json");
231
+ if (options?.model) args2.push("-m", options.model);
232
+ else args2.push("-m", "gemini-2.5-flash");
233
+ return args2;
234
+ },
235
+ parseOutput: (line) => {
236
+ try {
237
+ const parsed = JSON.parse(line);
238
+ return {
239
+ type: mapGeminiEventType(parsed.type),
240
+ content: parsed.content || parsed.message,
241
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
242
+ provider: "gemini",
243
+ raw: parsed
244
+ };
245
+ } catch {
246
+ return {
247
+ type: "text",
248
+ content: line,
249
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
250
+ provider: "gemini"
251
+ };
252
+ }
253
+ }
254
+ },
255
+ claude: {
256
+ command: "claude",
257
+ buildArgs: (prompt, options) => {
258
+ const args2 = ["-p", prompt, "--output-format", "stream-json"];
259
+ if (options?.yolo !== false) args2.push("--dangerously-skip-permissions");
260
+ if (options?.model) args2.push("--model", options.model);
261
+ return args2;
262
+ },
263
+ parseOutput: (line) => {
264
+ try {
265
+ const parsed = JSON.parse(line);
266
+ return {
267
+ type: mapClaudeEventType(parsed.type),
268
+ content: parsed.content || extractClaudeContent(parsed),
269
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
270
+ provider: "claude",
271
+ raw: parsed
272
+ };
273
+ } catch {
274
+ return {
275
+ type: "text",
276
+ content: line,
277
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
278
+ provider: "claude"
279
+ };
280
+ }
281
+ }
282
+ }
283
+ };
284
+ function mapGeminiEventType(type) {
285
+ switch (type) {
286
+ case "init":
287
+ return "init";
288
+ case "message":
289
+ return "text";
290
+ case "tool_use":
291
+ return "tool_use";
292
+ case "tool_result":
293
+ return "tool_result";
294
+ case "error":
295
+ return "error";
296
+ case "done":
297
+ case "result":
298
+ return "done";
299
+ default:
300
+ return "text";
301
+ }
302
+ }
303
+ function mapClaudeEventType(type) {
304
+ switch (type) {
305
+ case "system":
306
+ return "init";
307
+ case "assistant":
308
+ case "text":
309
+ return "text";
310
+ case "tool_use":
311
+ return "tool_use";
312
+ case "tool_result":
313
+ return "tool_result";
314
+ case "error":
315
+ return "error";
316
+ case "result":
317
+ return "done";
318
+ default:
319
+ return "text";
320
+ }
321
+ }
322
+ function extractClaudeContent(parsed) {
323
+ if (typeof parsed.content === "string") return parsed.content;
324
+ if (parsed.message && typeof parsed.message === "object") {
325
+ const msg = parsed.message;
326
+ if (typeof msg.content === "string") return msg.content;
327
+ }
328
+ return void 0;
329
+ }
330
+ function spawnAICLI(prompt, config) {
331
+ const spec = PROVIDERS[config.provider];
332
+ const args2 = spec.buildArgs(prompt, { model: config.model });
333
+ const cwd = config.cwd || process.cwd();
334
+ console.log(`[Skema] Spawning ${config.provider}: ${spec.command} ${args2.join(" ")}`);
335
+ const child = child_process.spawn(spec.command, args2, {
336
+ cwd,
337
+ env: process.env
338
+ });
339
+ const events = {
340
+ [Symbol.asyncIterator]() {
341
+ let buffer = "";
342
+ let done = false;
343
+ const queue = [];
344
+ let resolveNext = null;
345
+ const pushEvent = (event) => {
346
+ if (resolveNext) {
347
+ resolveNext({ value: event, done: false });
348
+ resolveNext = null;
349
+ } else {
350
+ queue.push(event);
351
+ }
352
+ };
353
+ child.stdout?.on("data", (data) => {
354
+ buffer += data.toString();
355
+ const lines = buffer.split("\n");
356
+ buffer = lines.pop() || "";
357
+ for (const line of lines) {
358
+ if (line.trim()) {
359
+ const event = spec.parseOutput(line.trim());
360
+ if (event) pushEvent(event);
361
+ }
362
+ }
363
+ });
364
+ child.stderr?.on("data", (data) => {
365
+ const content = data.toString().trim();
366
+ if (content) {
367
+ pushEvent({
368
+ type: "error",
369
+ content,
370
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
371
+ provider: config.provider
372
+ });
373
+ }
374
+ });
375
+ child.on("close", (code) => {
376
+ if (buffer.trim()) {
377
+ const event = spec.parseOutput(buffer.trim());
378
+ if (event) pushEvent(event);
379
+ }
380
+ pushEvent({
381
+ type: "done",
382
+ content: `Process exited with code ${code}`,
383
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
384
+ provider: config.provider
385
+ });
386
+ done = true;
387
+ if (resolveNext) {
388
+ resolveNext({ value: void 0, done: true });
389
+ }
390
+ });
391
+ child.on("error", (err) => {
392
+ pushEvent({
393
+ type: "error",
394
+ content: `Failed to spawn ${config.provider}: ${err.message}`,
395
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
396
+ provider: config.provider
397
+ });
398
+ done = true;
399
+ if (resolveNext) {
400
+ resolveNext({ value: void 0, done: true });
401
+ }
402
+ });
403
+ return {
404
+ next() {
405
+ if (queue.length > 0) {
406
+ return Promise.resolve({ value: queue.shift(), done: false });
407
+ }
408
+ if (done) {
409
+ return Promise.resolve({ value: void 0, done: true });
410
+ }
411
+ return new Promise((resolve) => {
412
+ resolveNext = resolve;
413
+ });
414
+ }
415
+ };
416
+ }
417
+ };
418
+ return { process: child, events };
419
+ }
420
+ function isProviderAvailable(provider) {
421
+ const spec = PROVIDERS[provider];
422
+ try {
423
+ const { execSync: execSync3 } = __require("child_process");
424
+ execSync3(`which ${spec.command}`, { stdio: "ignore" });
425
+ return true;
426
+ } catch {
427
+ return false;
428
+ }
429
+ }
430
+ function getAvailableProviders() {
431
+ return ["gemini", "claude"].filter(isProviderAvailable);
432
+ }
433
+
434
+ // src/lib/utils.ts
435
+ function getGridCellReference(x, y, gridSize = 100) {
436
+ const col = Math.floor(x / gridSize);
437
+ const row = Math.floor(y / gridSize);
438
+ const colLabel = String.fromCharCode(65 + col);
439
+ return `${colLabel}${row}`;
440
+ }
441
+
442
+ // src/server/prompts.ts
443
+ var CRITICAL_RULES = `CRITICAL RULES:
444
+ - Do NOT create new files. Only edit existing files.
445
+ - Do NOT modify the import { SkemaWrapper } from "@/components/skema-wrapper" line.
446
+ - Ensure all JSX tags are properly closed - every <tag> needs a matching </tag>.
447
+ - Do NOT run any shell commands. No npm, no git, no lint, no build commands. Just edit files.
448
+ - STOP immediately after making the file changes. Do not verify, do not run tests, do not check status.`;
449
+ var JSX_VALIDATION_RULE = `JSX SYNTAX: Every opening tag (<div>, <a>, <span>, <button>) MUST have a matching closing tag. Self-closing tags (<img />, <input />, <br />) must end with />.`;
450
+ function buildFastDomSelectionPrompt(input) {
451
+ const { comment, selector, text, tagName } = input;
452
+ const parts = [];
453
+ if (tagName) {
454
+ parts.push(`<${tagName.toLowerCase()}>`);
455
+ }
456
+ if (selector) {
457
+ parts.push(`selector: ${selector}`);
458
+ }
459
+ if (text) {
460
+ parts.push(`text: "${text.slice(0, 50)}"`);
461
+ }
462
+ const target = parts.length > 0 ? ` (${parts.join(" | ")})` : "";
463
+ return `${comment}${target}. Make the change directly, no explanation needed. ${CRITICAL_RULES} ${JSX_VALIDATION_RULE}`;
464
+ }
465
+ function buildDetailedDomSelectionPrompt(input) {
466
+ const { comment, tagName, selector, text, elementPath, cssClasses, attributes, elements } = input;
467
+ let prompt = `Make this code change: "${comment || "No specific comment provided"}"
468
+
469
+ ## Target Element
470
+ - Tag: <${tagName?.toLowerCase() || "unknown"}>`;
471
+ if (selector) {
472
+ prompt += `
473
+ - Selector: ${selector}`;
474
+ }
475
+ if (elementPath) {
476
+ prompt += `
477
+ - DOM Path: ${elementPath}`;
478
+ }
479
+ if (cssClasses) {
480
+ prompt += `
481
+ - CSS Classes: ${cssClasses}`;
482
+ }
483
+ if (attributes && Object.keys(attributes).length > 0) {
484
+ const attrStr = Object.entries(attributes).map(([k, v]) => `${k}="${v}"`).join(", ");
485
+ prompt += `
486
+ - Attributes: ${attrStr}`;
487
+ }
488
+ if (text) {
489
+ prompt += `
490
+ - Text Content: "${text.slice(0, 150)}"`;
491
+ }
492
+ if (elements && elements.length > 1) {
493
+ prompt += `
494
+
495
+ ## Multi-Selection (${elements.length} elements)`;
496
+ elements.slice(0, 5).forEach((el, i) => {
497
+ prompt += `
498
+ ${i + 1}. <${el.tagName}> ${el.selector}`;
499
+ if (el.text) prompt += ` - "${el.text.slice(0, 50)}"`;
500
+ });
501
+ if (elements.length > 5) {
502
+ prompt += `
503
+ ... and ${elements.length - 5} more`;
504
+ }
505
+ }
506
+ prompt += `
507
+
508
+ Make minimal changes. ${CRITICAL_RULES} ${JSX_VALIDATION_RULE}`;
509
+ return prompt;
510
+ }
511
+ function buildGesturePrompt(input) {
512
+ const { comment, gesture, boundingBox } = input;
513
+ return `Make this code change: "${comment || "No specific comment provided"}"
514
+
515
+ Element: gesture: ${gesture || "unknown"} at (${boundingBox?.x || 0}, ${boundingBox?.y || 0})
516
+
517
+ Make minimal changes. ${CRITICAL_RULES} ${JSX_VALIDATION_RULE}`;
518
+ }
519
+ function buildDrawingToCodePrompt(input) {
520
+ const {
521
+ comment = "Create a component based on this drawing",
522
+ boundingBox,
523
+ extractedText,
524
+ gridConfig,
525
+ viewport,
526
+ nearbyElements = [],
527
+ visionDescription
528
+ } = input;
529
+ const gridSize = gridConfig?.size || 100;
530
+ let gridCellRef = "";
531
+ if (boundingBox) {
532
+ gridCellRef = getGridCellReference(boundingBox.x, boundingBox.y, gridSize);
533
+ }
534
+ const positionContext = buildPositionContext(boundingBox, viewport, gridCellRef);
535
+ const nearbyContext = buildNearbyElementsContext(nearbyElements);
536
+ const textContext = extractedText?.trim() ? `
537
+ **Text found in drawing (use as reference if hard to read):**
538
+ ${extractedText}` : "";
539
+ const imageNote = buildImageNote(!!input.drawingImage, visionDescription);
540
+ return `Your task is to interpret a user's sketch/wireframe and turn it into code that is integrated in the codebase.
541
+
542
+ ## User's Request
543
+ "${comment}"
544
+
545
+ ## Drawing Context
546
+ ${positionContext}${textContext}${nearbyContext}${imageNote}
547
+
548
+ ## Your Process
549
+ 1. **Analyze the Sketch:** Understand the visual intent\u2014what UI component does the user want?
550
+ 2. **Interpret, Don't Transcribe:** Elevate the low-fidelity drawing into a high-fidelity component. Choose appropriate spacing, colors, and typography that match modern design standards.
551
+ 3. **Infer Missing Details:** If something is underspecified, use your expertise to make the best choice. An informed decision is better than an incomplete component.
552
+
553
+ ## Implementation Guidelines
554
+ ${DRAWING_IMPLEMENTATION_GUIDELINES}
555
+
556
+ Make the changes directly. Insert the UI elements inline at the appropriate location in the page. No explanation needed.
557
+
558
+ ## Error Prevention Rules
559
+ ${DRAWING_ERROR_PREVENTION_RULES}`;
560
+ }
561
+ var DRAWING_IMPLEMENTATION_GUIDELINES = `- **CRITICAL: DO NOT CREATE ANY NEW FILES. NEVER CREATE NEW FILES. You must ONLY edit existing files.**
562
+ - Add your code directly inline within the existing JSX of the page file - do NOT create separate component files.
563
+ - DO NOT run any shell commands (no npm, git, lint, build, or verification commands). Just edit files and STOP.
564
+ - After making your file edits, you are DONE. Do not run any follow-up commands or checks.
565
+ - Write the UI as inline JSX elements (divs, sections, etc.) directly in the return statement - NOT as a separate component definition
566
+ - Use Tailwind CSS classes for styling (the project uses Tailwind)
567
+ - Do NOT use hardcoded pixel positions or absolute coordinates - integrate naturally with existing page flow
568
+ - Use flexbox, grid, or relative positioning to place the component appropriately
569
+ - If the sketch shows:
570
+ - **Rectangle/box:** Card, container, button, or input field depending on context
571
+ - **Text elements:** Headings, paragraphs, or labels with appropriate hierarchy
572
+ - **Form layout:** Input fields with labels, proper spacing
573
+ - **Icons/shapes:** Use appropriate icons from lucide-react or inline SVGs (but DO NOT add new imports mid-file)
574
+ - **Navigation:** Nav links, menus, or breadcrumbs
575
+ - **Lists:** Ordered/unordered lists or grid layouts
576
+ - Make the UI fit naturally with the existing page design
577
+ - Style it nicely and according to the existing codebase
578
+ - Use semantic HTML and ARIA attributes where appropriate
579
+ - **NEVER add import statements inside JSX or in the middle of a file - all imports must be at the very top of the file**
580
+ - If you need a new import, add it at the TOP of the file with the other imports, then use it in the JSX below`;
581
+ var DRAWING_ERROR_PREVENTION_RULES = `1. **NEVER CREATE NEW FILES** - Do NOT create new component files, utility files, or any other files. Write everything inline in the existing file
582
+ 2. You do not need to update package.json or anything, just add / edit the react component.
583
+ 3. Do NOT add import statements in the middle of the file or inside JSX - imports go ONLY at the top
584
+ 4. Do NOT modify the import { SkemaWrapper } from "@/components/skema-wrapper" line or the SkemaWrapper component itself
585
+ 5. If you need something that requires an import and it's not already imported, either use an alternative that doesn't need an import, or add the import at the very TOP of the file with the other imports
586
+ 6. DONT MAKE ANY CHANGES THAT WOULD RESULT IN A Build Error
587
+ 7. **JSX SYNTAX VALIDATION** - ALWAYS ensure every JSX tag is properly closed. Every opening tag like <div>, <a>, <span>, <button> MUST have a matching closing tag </div>, </a>, </span>, </button>. Self-closing tags like <img />, <input />, <br /> must end with />. Before finishing, mentally verify all tag pairs are balanced.`;
588
+ var IMAGE_ANALYSIS_PROMPT = `
589
+ Analyze this UI wireframe sketch in some detail (not too long) for a front-end developer.
590
+
591
+ Describe every element, layout, spacing, icons, and text you see.
592
+ Focus on whats apparent, don't overthink it.
593
+ Mention relative positions and hierarchy.
594
+ Be distinct about what is drawn vs what might be background.
595
+
596
+ Do NOT focus on exact pixel coordinates or absolute positions - describe layouts
597
+ in terms of relative positioning (left/right/top/bottom, centered, evenly spaced, etc.).
598
+
599
+ It is expected that they will be rough draft's / hand-drawn things. Interpret the drawing and its goals as best as you can.
600
+ DO NOT MENTION THAT THINGS HAVE "Hand-sketched" or "Hand-drawn" vibes. Make assumptions of what they were trying to do.
601
+ Just FYI, this gets passed onto a generator to generate the actual code of modern UI componenents.
602
+ `.trim();
603
+ function buildPositionContext(bbox, viewport, gridCellRef) {
604
+ if (!bbox) return "";
605
+ if (viewport) {
606
+ const relX = (bbox.x / viewport.width * 100).toFixed(1);
607
+ const relY = (bbox.y / viewport.height * 100).toFixed(1);
608
+ let context = `**Drawing Location:** Approximately ${relX}% from left, ${relY}% from top of viewport`;
609
+ if (gridCellRef) {
610
+ context += ` (grid cell ${gridCellRef})`;
611
+ }
612
+ return context;
613
+ }
614
+ return `**Drawing Area:** ${Math.round(bbox.width)}\xD7${Math.round(bbox.height)}px`;
615
+ }
616
+ function buildNearbyElementsContext(nearbyElements) {
617
+ if (nearbyElements.length === 0) return "";
618
+ const elementList = nearbyElements.slice(0, 5).map((el) => {
619
+ let desc = `- <${el.tagName.toLowerCase()}>`;
620
+ if (el.text) desc += `: "${el.text.slice(0, 50)}"`;
621
+ if (el.className) desc += ` (class: ${el.className.slice(0, 50)})`;
622
+ desc += ` (${el.selector})`;
623
+ return desc;
624
+ }).join("\n");
625
+ return `
626
+ **Nearby DOM Elements (for placement reference):**
627
+ ${elementList}`;
628
+ }
629
+ function buildImageNote(hasImage, visionDescription) {
630
+ let note = hasImage ? "\n**[Drawing image provided as base64 PNG with labeled grid overlay]**" : "";
631
+ if (visionDescription) {
632
+ note += `
633
+
634
+ ## Visual Analysis of Drawing
635
+ ${visionDescription}`;
636
+ }
637
+ return note;
638
+ }
639
+ function buildPromptFromAnnotation(annotation, _projectContext, options) {
640
+ const fastMode = options?.fastMode ?? true;
641
+ if (annotation.type === "drawing") {
642
+ const drawingAnnotation = annotation;
643
+ return buildDrawingToCodePrompt({
644
+ comment: drawingAnnotation.comment || "Create a component based on this drawing",
645
+ boundingBox: drawingAnnotation.boundingBox,
646
+ drawingSvg: drawingAnnotation.drawingSvg,
647
+ drawingImage: drawingAnnotation.drawingImage,
648
+ extractedText: drawingAnnotation.extractedText,
649
+ gridConfig: drawingAnnotation.gridConfig,
650
+ viewport: drawingAnnotation.viewport,
651
+ projectStyles: drawingAnnotation.projectStyles,
652
+ nearbyElements: drawingAnnotation.nearbyElements,
653
+ visionDescription: options?.visionDescription
654
+ });
655
+ }
656
+ if (annotation.type === "gesture") {
657
+ const gestureAnnotation = annotation;
658
+ return buildGesturePrompt({
659
+ comment: annotation.comment || "No specific comment provided",
660
+ gesture: gestureAnnotation.gesture,
661
+ boundingBox: gestureAnnotation.boundingBox
662
+ });
663
+ }
664
+ const domAnnotation = annotation;
665
+ if (fastMode) {
666
+ return buildFastDomSelectionPrompt({
667
+ comment: annotation.comment || "No specific comment provided",
668
+ selector: domAnnotation.selector,
669
+ text: domAnnotation.text,
670
+ tagName: domAnnotation.tagName
671
+ });
672
+ }
673
+ return buildDetailedDomSelectionPrompt({
674
+ comment: annotation.comment || "No specific comment provided",
675
+ tagName: domAnnotation.tagName,
676
+ selector: domAnnotation.selector,
677
+ text: domAnnotation.text,
678
+ elementPath: domAnnotation.elementPath,
679
+ cssClasses: domAnnotation.cssClasses,
680
+ attributes: domAnnotation.attributes,
681
+ elements: domAnnotation.elements
682
+ });
683
+ }
684
+ async function analyzeWithGemini(base64Image, apiKey, model = "gemini-2.5-flash") {
685
+ try {
686
+ const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
687
+ const visionModel = genAI.getGenerativeModel({ model });
688
+ const cleanBase64 = base64Image.replace(/^data:image\/\w+;base64,/, "");
689
+ const result = await visionModel.generateContent([
690
+ IMAGE_ANALYSIS_PROMPT,
691
+ {
692
+ inlineData: {
693
+ data: cleanBase64,
694
+ mimeType: "image/png"
695
+ }
696
+ }
697
+ ]);
698
+ const response = await result.response;
699
+ const text = response.text();
700
+ return {
701
+ success: true,
702
+ description: text,
703
+ provider: "gemini"
704
+ };
705
+ } catch (error) {
706
+ const message = error instanceof Error ? error.message : String(error);
707
+ console.error("[Vision] Gemini analysis failed:", message);
708
+ return {
709
+ success: false,
710
+ description: "",
711
+ provider: "gemini",
712
+ error: message
713
+ };
714
+ }
715
+ }
716
+ async function analyzeWithClaude(base64Image, apiKey, model = "claude-sonnet-4-20250514") {
717
+ try {
718
+ const Anthropic = (await import('@anthropic-ai/sdk')).default;
719
+ const client = new Anthropic({ apiKey });
720
+ const cleanBase64 = base64Image.replace(/^data:image\/\w+;base64,/, "");
721
+ const response = await client.messages.create({
722
+ model,
723
+ max_tokens: 1024,
724
+ messages: [
725
+ {
726
+ role: "user",
727
+ content: [
728
+ {
729
+ type: "image",
730
+ source: {
731
+ type: "base64",
732
+ media_type: "image/png",
733
+ data: cleanBase64
734
+ }
735
+ },
736
+ {
737
+ type: "text",
738
+ text: IMAGE_ANALYSIS_PROMPT
739
+ }
740
+ ]
741
+ }
742
+ ]
743
+ });
744
+ const textContent = response.content.find((c) => c.type === "text");
745
+ const description = textContent && "text" in textContent ? textContent.text : "";
746
+ return {
747
+ success: true,
748
+ description,
749
+ provider: "claude"
750
+ };
751
+ } catch (error) {
752
+ const message = error instanceof Error ? error.message : String(error);
753
+ console.error("[Vision] Claude analysis failed:", message);
754
+ return {
755
+ success: false,
756
+ description: "",
757
+ provider: "claude",
758
+ error: message
759
+ };
760
+ }
761
+ }
762
+ async function analyzeImage(base64Image, config) {
763
+ const { provider } = config;
764
+ let apiKey = config.apiKey;
765
+ if (!apiKey) {
766
+ apiKey = provider === "gemini" ? process.env.GEMINI_API_KEY : process.env.ANTHROPIC_API_KEY;
767
+ }
768
+ if (!apiKey) {
769
+ return {
770
+ success: false,
771
+ description: "",
772
+ provider,
773
+ error: `No API key found for ${provider} vision. Set ${provider === "gemini" ? "GEMINI_API_KEY" : "ANTHROPIC_API_KEY"} environment variable.`
774
+ };
775
+ }
776
+ console.log(`[Vision] Analyzing image with ${provider}...`);
777
+ if (provider === "gemini") {
778
+ return analyzeWithGemini(base64Image, apiKey, config.model);
779
+ } else {
780
+ return analyzeWithClaude(base64Image, apiKey, config.model);
781
+ }
782
+ }
783
+ function isVisionAvailable(provider) {
784
+ {
785
+ return !!process.env.GEMINI_API_KEY;
786
+ }
787
+ }
788
+
789
+ // src/server/daemon.ts
790
+ var currentProvider = "gemini";
791
+ var workingDirectory = process.cwd();
792
+ var annotationSnapshots2 = /* @__PURE__ */ new Map();
793
+ function createSnapshot2(annotationId) {
794
+ try {
795
+ const stashRef = child_process.execSync("git stash create", { cwd: workingDirectory, encoding: "utf-8" }).trim();
796
+ if (stashRef) {
797
+ annotationSnapshots2.set(annotationId, stashRef);
798
+ console.log(`[Daemon] Created snapshot ${stashRef.slice(0, 7)} for ${annotationId}`);
799
+ return stashRef;
800
+ }
801
+ const headRef = child_process.execSync("git rev-parse HEAD", { cwd: workingDirectory, encoding: "utf-8" }).trim();
802
+ annotationSnapshots2.set(annotationId, headRef);
803
+ return headRef;
804
+ } catch (error) {
805
+ console.error("[Daemon] Failed to create snapshot:", error);
806
+ return null;
807
+ }
808
+ }
809
+ function revertSnapshot(annotationId) {
810
+ const snapshotRef = annotationSnapshots2.get(annotationId);
811
+ if (!snapshotRef) {
812
+ return { success: false, message: `No snapshot found for ${annotationId}` };
813
+ }
814
+ try {
815
+ const changedFiles = child_process.execSync(`git diff --name-only ${snapshotRef}`, {
816
+ cwd: workingDirectory,
817
+ encoding: "utf-8"
818
+ }).trim().split("\n").filter(Boolean);
819
+ if (changedFiles.length === 0) {
820
+ annotationSnapshots2.delete(annotationId);
821
+ return { success: true, message: "No changes to revert" };
822
+ }
823
+ for (const file of changedFiles) {
824
+ try {
825
+ child_process.execSync(`git checkout ${snapshotRef} -- "${file}"`, { cwd: workingDirectory });
826
+ } catch {
827
+ }
828
+ }
829
+ annotationSnapshots2.delete(annotationId);
830
+ return { success: true, message: `Reverted ${changedFiles.length} file(s)` };
831
+ } catch (error) {
832
+ return { success: false, message: String(error) };
833
+ }
834
+ }
835
+ var handlers = {
836
+ // -------------------------------------------------------------------------
837
+ // Provider Management
838
+ // -------------------------------------------------------------------------
839
+ "get-provider": async (msg) => {
840
+ return {
841
+ id: msg.id,
842
+ type: "provider",
843
+ provider: currentProvider,
844
+ available: getAvailableProviders()
845
+ };
846
+ },
847
+ "set-provider": async (msg) => {
848
+ const newProvider = msg.provider;
849
+ if (!["gemini", "claude"].includes(newProvider)) {
850
+ return { id: msg.id, type: "error", error: `Invalid provider: ${newProvider}` };
851
+ }
852
+ if (!isProviderAvailable(newProvider)) {
853
+ return {
854
+ id: msg.id,
855
+ type: "error",
856
+ error: `Provider "${newProvider}" is not installed. Run: ${newProvider === "gemini" ? "npm install -g @anthropic-ai/gemini-cli" : "npm install -g @anthropic-ai/claude-code"}`
857
+ };
858
+ }
859
+ currentProvider = newProvider;
860
+ console.log(`[Daemon] Switched to provider: ${currentProvider}`);
861
+ return { id: msg.id, type: "provider-changed", provider: currentProvider };
862
+ },
863
+ // -------------------------------------------------------------------------
864
+ // AI Generation (streaming)
865
+ // -------------------------------------------------------------------------
866
+ generate: async (msg, ws) => {
867
+ const annotation = msg.annotation;
868
+ const projectContext = msg.projectContext;
869
+ const annotationId = annotation.id || `temp-${Date.now()}`;
870
+ createSnapshot2(annotationId);
871
+ let visionDescription = "";
872
+ const drawingAnnotation = annotation;
873
+ if (annotation.type === "drawing" && drawingAnnotation.drawingImage) {
874
+ if (isVisionAvailable()) {
875
+ sendMessage(ws, {
876
+ id: msg.id,
877
+ type: "ai-event",
878
+ event: {
879
+ type: "text",
880
+ content: `[Analyzing drawing with Gemini vision...]`,
881
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
882
+ provider: currentProvider
883
+ }
884
+ });
885
+ const visionResult = await analyzeImage(drawingAnnotation.drawingImage, {
886
+ provider: "gemini"
887
+ });
888
+ if (visionResult.success) {
889
+ visionDescription = visionResult.description;
890
+ sendMessage(ws, {
891
+ id: msg.id,
892
+ type: "ai-event",
893
+ event: {
894
+ type: "text",
895
+ content: `[Vision analysis complete]
896
+ ${visionDescription}`,
897
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
898
+ provider: currentProvider
899
+ }
900
+ });
901
+ } else {
902
+ sendMessage(ws, {
903
+ id: msg.id,
904
+ type: "ai-event",
905
+ event: {
906
+ type: "error",
907
+ content: `Vision analysis failed: ${visionResult.error}`,
908
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
909
+ provider: currentProvider
910
+ }
911
+ });
912
+ }
913
+ } else {
914
+ sendMessage(ws, {
915
+ id: msg.id,
916
+ type: "ai-event",
917
+ event: {
918
+ type: "text",
919
+ content: `[Vision not available - set GEMINI_API_KEY for image analysis]`,
920
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
921
+ provider: currentProvider
922
+ }
923
+ });
924
+ }
925
+ }
926
+ const prompt = buildPromptFromAnnotation(annotation, projectContext, {
927
+ fastMode: msg.fastMode === true,
928
+ // Default to detailed mode (false) unless explicitly set to true
929
+ visionDescription
930
+ });
931
+ sendMessage(ws, {
932
+ id: msg.id,
933
+ type: "ai-event",
934
+ event: {
935
+ type: "debug",
936
+ content: prompt,
937
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
938
+ provider: currentProvider
939
+ }
940
+ });
941
+ const config = {
942
+ provider: currentProvider,
943
+ cwd: workingDirectory,
944
+ model: msg.model
945
+ };
946
+ const { events } = spawnAICLI(prompt, config);
947
+ for await (const event of events) {
948
+ sendMessage(ws, {
949
+ id: msg.id,
950
+ type: "ai-event",
951
+ event,
952
+ annotationId
953
+ });
954
+ if (event.type === "done") {
955
+ sendMessage(ws, {
956
+ id: msg.id,
957
+ type: "generate-complete",
958
+ success: true,
959
+ annotationId,
960
+ provider: currentProvider
961
+ });
962
+ break;
963
+ }
964
+ }
965
+ },
966
+ // -------------------------------------------------------------------------
967
+ // Undo/Revert
968
+ // -------------------------------------------------------------------------
969
+ revert: async (msg) => {
970
+ const annotationId = msg.annotationId;
971
+ if (!annotationId) {
972
+ return { id: msg.id, type: "error", error: "Missing annotationId" };
973
+ }
974
+ const result = revertSnapshot(annotationId);
975
+ return { id: msg.id, type: "revert-result", ...result };
976
+ },
977
+ // -------------------------------------------------------------------------
978
+ // File Operations
979
+ // -------------------------------------------------------------------------
980
+ "read-file": async (msg) => {
981
+ const filePath = msg.path;
982
+ const absolutePath = path__namespace.isAbsolute(filePath) ? filePath : path__namespace.join(workingDirectory, filePath);
983
+ try {
984
+ const content = fs__namespace.readFileSync(absolutePath, "utf-8");
985
+ return { id: msg.id, type: "file-content", path: filePath, content };
986
+ } catch (error) {
987
+ return { id: msg.id, type: "error", error: `Failed to read file: ${error}` };
988
+ }
989
+ },
990
+ "write-file": async (msg) => {
991
+ const filePath = msg.path;
992
+ const content = msg.content;
993
+ const absolutePath = path__namespace.isAbsolute(filePath) ? filePath : path__namespace.join(workingDirectory, filePath);
994
+ try {
995
+ fs__namespace.mkdirSync(path__namespace.dirname(absolutePath), { recursive: true });
996
+ fs__namespace.writeFileSync(absolutePath, content, "utf-8");
997
+ console.log(`[Daemon] Wrote file: ${filePath}`);
998
+ return { id: msg.id, type: "write-success", path: filePath };
999
+ } catch (error) {
1000
+ return { id: msg.id, type: "error", error: `Failed to write file: ${error}` };
1001
+ }
1002
+ },
1003
+ "list-files": async (msg) => {
1004
+ const dirPath = msg.path || ".";
1005
+ const absolutePath = path__namespace.isAbsolute(dirPath) ? dirPath : path__namespace.join(workingDirectory, dirPath);
1006
+ try {
1007
+ const entries = fs__namespace.readdirSync(absolutePath, { withFileTypes: true });
1008
+ const files = entries.map((entry) => ({
1009
+ name: entry.name,
1010
+ isDirectory: entry.isDirectory()
1011
+ }));
1012
+ return { id: msg.id, type: "file-list", path: dirPath, files };
1013
+ } catch (error) {
1014
+ return { id: msg.id, type: "error", error: `Failed to list files: ${error}` };
1015
+ }
1016
+ },
1017
+ // -------------------------------------------------------------------------
1018
+ // Command Execution
1019
+ // -------------------------------------------------------------------------
1020
+ "run-command": async (msg, ws) => {
1021
+ const command2 = msg.command;
1022
+ if (!command2) {
1023
+ return { id: msg.id, type: "error", error: "Missing command" };
1024
+ }
1025
+ console.log(`[Daemon] Running command: ${command2}`);
1026
+ return new Promise((resolve) => {
1027
+ child_process.exec(command2, { cwd: workingDirectory }, (error, stdout, stderr) => {
1028
+ resolve({
1029
+ id: msg.id,
1030
+ type: "command-result",
1031
+ stdout,
1032
+ stderr,
1033
+ exitCode: error ? error.code : 0
1034
+ });
1035
+ });
1036
+ });
1037
+ },
1038
+ // -------------------------------------------------------------------------
1039
+ // Status
1040
+ // -------------------------------------------------------------------------
1041
+ ping: async (msg) => {
1042
+ return {
1043
+ id: msg.id,
1044
+ type: "pong",
1045
+ provider: currentProvider,
1046
+ cwd: workingDirectory,
1047
+ availableProviders: getAvailableProviders()
1048
+ };
1049
+ }
1050
+ };
1051
+ function sendMessage(ws$1, message) {
1052
+ if (ws$1.readyState === ws.WebSocket.OPEN) {
1053
+ ws$1.send(JSON.stringify(message));
1054
+ }
1055
+ }
1056
+ function handleConnection(ws) {
1057
+ console.log("[Daemon] Client connected");
1058
+ sendMessage(ws, {
1059
+ type: "connected",
1060
+ provider: currentProvider,
1061
+ cwd: workingDirectory,
1062
+ availableProviders: getAvailableProviders()
1063
+ });
1064
+ ws.on("message", async (data) => {
1065
+ let msg;
1066
+ try {
1067
+ msg = JSON.parse(data.toString());
1068
+ } catch {
1069
+ sendMessage(ws, { type: "error", error: "Invalid JSON" });
1070
+ return;
1071
+ }
1072
+ const handler = handlers[msg.type];
1073
+ if (!handler) {
1074
+ sendMessage(ws, { id: msg.id, type: "error", error: `Unknown message type: ${msg.type}` });
1075
+ return;
1076
+ }
1077
+ try {
1078
+ const response = await handler(msg, ws);
1079
+ if (response) {
1080
+ sendMessage(ws, response);
1081
+ }
1082
+ } catch (error) {
1083
+ sendMessage(ws, {
1084
+ id: msg.id,
1085
+ type: "error",
1086
+ error: `Handler error: ${error}`
1087
+ });
1088
+ }
1089
+ });
1090
+ ws.on("close", () => {
1091
+ console.log("[Daemon] Client disconnected");
1092
+ });
1093
+ ws.on("error", (error) => {
1094
+ console.error("[Daemon] WebSocket error:", error);
1095
+ });
1096
+ }
1097
+ function startDaemon(config = {}) {
1098
+ const port = config.port ?? 9999;
1099
+ workingDirectory = config.cwd ?? process.cwd();
1100
+ currentProvider = config.defaultProvider ?? "gemini";
1101
+ if (!isProviderAvailable(currentProvider)) {
1102
+ const available = getAvailableProviders();
1103
+ if (available.length > 0) {
1104
+ console.log(`[Daemon] ${currentProvider} not found, falling back to ${available[0]}`);
1105
+ currentProvider = available[0];
1106
+ } else {
1107
+ console.warn("[Daemon] Warning: No AI providers found. Install gemini or claude CLI.");
1108
+ }
1109
+ }
1110
+ const wss = new ws.WebSocketServer({ port });
1111
+ wss.on("connection", handleConnection);
1112
+ wss.on("listening", () => {
1113
+ console.log("");
1114
+ console.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
1115
+ console.log(" \u2502 \u2502");
1116
+ console.log(` \u2502 \u{1F3A8} Skema Daemon running on ws://localhost:${port} \u2502`);
1117
+ console.log(" \u2502 \u2502");
1118
+ console.log(` \u2502 Provider: ${currentProvider.padEnd(35)}\u2502`);
1119
+ console.log(` \u2502 Directory: ${workingDirectory.slice(-33).padEnd(34)}\u2502`);
1120
+ console.log(" \u2502 \u2502");
1121
+ console.log(" \u2502 Waiting for browser connections... \u2502");
1122
+ console.log(" \u2502 \u2502");
1123
+ console.log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
1124
+ console.log("");
1125
+ });
1126
+ wss.on("error", (error) => {
1127
+ if (error.code === "EADDRINUSE") {
1128
+ console.error(`[Daemon] Port ${port} is already in use. Is another Skema daemon running?`);
1129
+ } else {
1130
+ console.error("[Daemon] Server error:", error);
1131
+ }
1132
+ });
1133
+ return {
1134
+ port,
1135
+ close: () => {
1136
+ wss.close();
1137
+ console.log("[Daemon] Server stopped");
1138
+ }
1139
+ };
1140
+ }
1141
+
1142
+ // src/cli/index.ts
1143
+ var args = process.argv.slice(2);
1144
+ var command = args[0];
1145
+ function printHelp() {
1146
+ console.log("");
1147
+ console.log(" Skema - Drawing-based website development");
1148
+ console.log("");
1149
+ console.log(" Usage:");
1150
+ console.log(" npx skema-core Start the daemon (default)");
1151
+ console.log(" npx skema-core init Configure your project");
1152
+ console.log(" npx skema-core help Show this help");
1153
+ console.log("");
1154
+ console.log(" Options (for daemon):");
1155
+ console.log(" -p, --port <port> Port number (default: 9999)");
1156
+ console.log(" -d, --dir <path> Working directory");
1157
+ console.log(" --provider <name> Default AI provider (gemini|claude)");
1158
+ console.log("");
1159
+ console.log(" Examples:");
1160
+ console.log(" npx skema-core");
1161
+ console.log(" npx skema-core --port 8080");
1162
+ console.log(" npx skema-core init");
1163
+ console.log("");
1164
+ console.log(' Note: After installing skema-core, you can also use "skema" directly.');
1165
+ console.log("");
1166
+ }
1167
+ function parseArgs(args2) {
1168
+ const config = {};
1169
+ for (let i = 0; i < args2.length; i++) {
1170
+ const arg = args2[i];
1171
+ const next = args2[i + 1];
1172
+ if (arg === "-p" || arg === "--port") {
1173
+ config.port = parseInt(next, 10);
1174
+ i++;
1175
+ } else if (arg === "-d" || arg === "--dir") {
1176
+ config.cwd = next;
1177
+ i++;
1178
+ } else if (arg === "--provider") {
1179
+ config.defaultProvider = next;
1180
+ i++;
1181
+ }
1182
+ }
1183
+ return config;
1184
+ }
1185
+ async function runInit() {
1186
+ await Promise.resolve().then(() => (init_init(), init_exports));
1187
+ }
1188
+ function runDaemon(args2) {
1189
+ const config = parseArgs(args2);
1190
+ startDaemon(config);
1191
+ }
1192
+ if (command === "help" || command === "-h" || command === "--help") {
1193
+ printHelp();
1194
+ } else if (command === "init") {
1195
+ runInit();
1196
+ } else if (command === "serve") {
1197
+ runDaemon(args.slice(1));
1198
+ } else {
1199
+ runDaemon(args);
1200
+ }