@causyne/agent 1.10.20 → 1.10.21

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.
@@ -0,0 +1,139 @@
1
+ /*
2
+ * Copyright (c) 2025 Bytedance, Inc. and its affiliates.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ *
5
+ * Causyne Vision Desktop & Coordinate Grounding Tool Wrapper.
6
+ */
7
+ import { parseVisionGroundingPrediction, visionGroundingToDesktopAct, groundBoxCenter } from '../computerUse/visionGrounding.js';
8
+ import { desktopAct } from './desktopAct.js';
9
+ import { auditLog } from '../auditLog.js';
10
+ export const computerUse = {
11
+ schema: {
12
+ name: 'computer_use',
13
+ description: 'Ground and execute graphical computer-use actions (click coordinates, type text, send key presses, drag, wait) derived from vision-language models and coordinate grounding predictions. Automatically translates normalized bounding boxes and coordinates against current screen dimensions with safety boundary checks. Requires explicit approval.',
14
+ parameters: {
15
+ type: 'object',
16
+ properties: {
17
+ action: {
18
+ type: 'string',
19
+ enum: ['execute_prediction', 'click', 'type', 'key', 'drag', 'wait'],
20
+ description: 'Type of computer-use action to perform.'
21
+ },
22
+ prediction: {
23
+ type: 'string',
24
+ description: 'Raw prediction text from a vision-language model or coordinate grounding engine, e.g. "Thought: Click search bar. Action: click(start_box=\'(500, 200)\')".'
25
+ },
26
+ coordinates: {
27
+ type: 'array',
28
+ items: { type: 'number' },
29
+ description: 'Coordinates as [x, y] or bounding box [x1, y1, x2, y2] normalized (0-1000) or in screen pixels.'
30
+ },
31
+ text: {
32
+ type: 'string',
33
+ description: 'Text to type into the active or targeted element.'
34
+ },
35
+ key: {
36
+ type: 'string',
37
+ description: 'Key or hotkey to press, e.g. "return", "tab", "escape".'
38
+ },
39
+ seconds: {
40
+ type: 'number',
41
+ description: 'Seconds to wait (0.1 to 10).'
42
+ },
43
+ app: {
44
+ type: 'string',
45
+ description: 'Application to focus before executing actions, e.g. "Safari".'
46
+ },
47
+ screenContext: {
48
+ type: 'object',
49
+ properties: {
50
+ width: { type: 'number' },
51
+ height: { type: 'number' },
52
+ scaleFactor: { type: 'number' }
53
+ },
54
+ description: 'Current display dimensions and scale factor for coordinate grounding.'
55
+ }
56
+ }
57
+ }
58
+ },
59
+ async execute(args = {}, context = {}) {
60
+ const screen = args.screenContext || { width: 1920, height: 1080, scaleFactor: 1 };
61
+ const app = typeof args.app === 'string' ? args.app.trim() : '';
62
+ let desktopActions = [];
63
+ // Mode 1: Parse full model prediction from vision grounding engine
64
+ if (args.prediction || args.action === 'execute_prediction') {
65
+ const pred = args.prediction || '';
66
+ if (!pred.trim()) {
67
+ return { success: false, error: 'No prediction string provided for execute_prediction.' };
68
+ }
69
+ const parsed = parseVisionGroundingPrediction(pred, screen);
70
+ if (parsed.actions.length === 0) {
71
+ return { success: false, error: 'Could not parse any executable GUI actions from prediction string.' };
72
+ }
73
+ desktopActions = visionGroundingToDesktopAct(parsed.actions);
74
+ }
75
+ else {
76
+ // Mode 2: Direct action request
77
+ const actionType = args.action || 'click';
78
+ if (actionType === 'click') {
79
+ const coords = args.coordinates || [];
80
+ const point = groundBoxCenter(coords, screen);
81
+ if (!point)
82
+ return { success: false, error: 'Click requires valid coordinates [x, y].' };
83
+ desktopActions.push({ verb: 'clickxy', x: point.x, y: point.y });
84
+ }
85
+ else if (actionType === 'type') {
86
+ if (args.coordinates && args.coordinates.length >= 2) {
87
+ const point = groundBoxCenter(args.coordinates, screen);
88
+ if (point)
89
+ desktopActions.push({ verb: 'clickxy', x: point.x, y: point.y });
90
+ }
91
+ if (!args.text)
92
+ return { success: false, error: 'Type action requires "text" parameter.' };
93
+ desktopActions.push({ verb: 'type', text: args.text });
94
+ }
95
+ else if (actionType === 'key') {
96
+ if (!args.key)
97
+ return { success: false, error: 'Key action requires "key" parameter.' };
98
+ const keyMap = {
99
+ return: 36, enter: 36, tab: 48, space: 49, backspace: 51, escape: 53, esc: 53
100
+ };
101
+ const keyCode = keyMap[args.key.toLowerCase()];
102
+ if (keyCode !== undefined) {
103
+ desktopActions.push({ verb: 'key', keyCode });
104
+ }
105
+ else {
106
+ desktopActions.push({ verb: 'type', text: args.key });
107
+ }
108
+ }
109
+ else if (actionType === 'wait') {
110
+ desktopActions.push({ verb: 'wait', seconds: Math.min(10, Math.max(0.1, args.seconds || 1)) });
111
+ }
112
+ else {
113
+ return { success: false, error: `Unsupported computer_use action: ${actionType}` };
114
+ }
115
+ }
116
+ if (desktopActions.length === 0) {
117
+ return { success: false, error: 'No actions to execute.' };
118
+ }
119
+ if (desktopActions.length > 12) {
120
+ return { success: false, error: `Maximum 12 actions allowed per call; got ${desktopActions.length}.` };
121
+ }
122
+ auditLog.log({
123
+ event: 'computer_use_actuation',
124
+ app,
125
+ actionCount: desktopActions.length,
126
+ verbs: desktopActions.map((a) => a.verb)
127
+ });
128
+ const execResult = await desktopAct.execute({ app, actions: desktopActions }, context);
129
+ const finalRes = {
130
+ success: Boolean(execResult.success),
131
+ actions: desktopActions
132
+ };
133
+ if (execResult.output)
134
+ finalRes.output = execResult.output;
135
+ if (execResult.error)
136
+ finalRes.error = execResult.error;
137
+ return finalRes;
138
+ }
139
+ };
@@ -0,0 +1,147 @@
1
+ /*
2
+ * Copyright (c) 2025 Bytedance, Inc. and its affiliates.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ *
5
+ * Causyne Vision Desktop & Coordinate Grounding Tool Wrapper.
6
+ */
7
+
8
+ import { parseVisionGroundingPrediction, visionGroundingToDesktopAct, groundBoxCenter, type ScreenContext } from '../computerUse/visionGrounding.js';
9
+ import { desktopAct } from './desktopAct.js';
10
+ import { auditLog } from '../auditLog.js';
11
+
12
+ export interface ComputerUseArgs {
13
+ action?: 'execute_prediction' | 'click' | 'type' | 'key' | 'drag' | 'wait';
14
+ prediction?: string;
15
+ coordinates?: number[];
16
+ text?: string;
17
+ key?: string;
18
+ seconds?: number;
19
+ app?: string;
20
+ screenContext?: ScreenContext;
21
+ }
22
+
23
+ export const computerUse = {
24
+ schema: {
25
+ name: 'computer_use',
26
+ description: 'Ground and execute graphical computer-use actions (click coordinates, type text, send key presses, drag, wait) derived from vision-language models and coordinate grounding predictions. Automatically translates normalized bounding boxes and coordinates against current screen dimensions with safety boundary checks. Requires explicit approval.',
27
+ parameters: {
28
+ type: 'object',
29
+ properties: {
30
+ action: {
31
+ type: 'string',
32
+ enum: ['execute_prediction', 'click', 'type', 'key', 'drag', 'wait'],
33
+ description: 'Type of computer-use action to perform.'
34
+ },
35
+ prediction: {
36
+ type: 'string',
37
+ description: 'Raw prediction text from a vision-language model or coordinate grounding engine, e.g. "Thought: Click search bar. Action: click(start_box=\'(500, 200)\')".'
38
+ },
39
+ coordinates: {
40
+ type: 'array',
41
+ items: { type: 'number' },
42
+ description: 'Coordinates as [x, y] or bounding box [x1, y1, x2, y2] normalized (0-1000) or in screen pixels.'
43
+ },
44
+ text: {
45
+ type: 'string',
46
+ description: 'Text to type into the active or targeted element.'
47
+ },
48
+ key: {
49
+ type: 'string',
50
+ description: 'Key or hotkey to press, e.g. "return", "tab", "escape".'
51
+ },
52
+ seconds: {
53
+ type: 'number',
54
+ description: 'Seconds to wait (0.1 to 10).'
55
+ },
56
+ app: {
57
+ type: 'string',
58
+ description: 'Application to focus before executing actions, e.g. "Safari".'
59
+ },
60
+ screenContext: {
61
+ type: 'object',
62
+ properties: {
63
+ width: { type: 'number' },
64
+ height: { type: 'number' },
65
+ scaleFactor: { type: 'number' }
66
+ },
67
+ description: 'Current display dimensions and scale factor for coordinate grounding.'
68
+ }
69
+ }
70
+ }
71
+ },
72
+
73
+ async execute(args: ComputerUseArgs = {}, context: any = {}): Promise<{ success: boolean; output?: string; error?: string; actions?: any[] }> {
74
+ const screen: ScreenContext = args.screenContext || { width: 1920, height: 1080, scaleFactor: 1 };
75
+ const app = typeof args.app === 'string' ? args.app.trim() : '';
76
+
77
+ let desktopActions: any[] = [];
78
+
79
+ // Mode 1: Parse full model prediction from vision grounding engine
80
+ if (args.prediction || args.action === 'execute_prediction') {
81
+ const pred = args.prediction || '';
82
+ if (!pred.trim()) {
83
+ return { success: false, error: 'No prediction string provided for execute_prediction.' };
84
+ }
85
+ const parsed = parseVisionGroundingPrediction(pred, screen);
86
+ if (parsed.actions.length === 0) {
87
+ return { success: false, error: 'Could not parse any executable GUI actions from prediction string.' };
88
+ }
89
+ desktopActions = visionGroundingToDesktopAct(parsed.actions);
90
+ } else {
91
+ // Mode 2: Direct action request
92
+ const actionType = args.action || 'click';
93
+ if (actionType === 'click') {
94
+ const coords = args.coordinates || [];
95
+ const point = groundBoxCenter(coords, screen);
96
+ if (!point) return { success: false, error: 'Click requires valid coordinates [x, y].' };
97
+ desktopActions.push({ verb: 'clickxy', x: point.x, y: point.y });
98
+ } else if (actionType === 'type') {
99
+ if (args.coordinates && args.coordinates.length >= 2) {
100
+ const point = groundBoxCenter(args.coordinates, screen);
101
+ if (point) desktopActions.push({ verb: 'clickxy', x: point.x, y: point.y });
102
+ }
103
+ if (!args.text) return { success: false, error: 'Type action requires "text" parameter.' };
104
+ desktopActions.push({ verb: 'type', text: args.text });
105
+ } else if (actionType === 'key') {
106
+ if (!args.key) return { success: false, error: 'Key action requires "key" parameter.' };
107
+ const keyMap: Record<string, number> = {
108
+ return: 36, enter: 36, tab: 48, space: 49, backspace: 51, escape: 53, esc: 53
109
+ };
110
+ const keyCode = keyMap[args.key.toLowerCase()];
111
+ if (keyCode !== undefined) {
112
+ desktopActions.push({ verb: 'key', keyCode });
113
+ } else {
114
+ desktopActions.push({ verb: 'type', text: args.key });
115
+ }
116
+ } else if (actionType === 'wait') {
117
+ desktopActions.push({ verb: 'wait', seconds: Math.min(10, Math.max(0.1, args.seconds || 1)) });
118
+ } else {
119
+ return { success: false, error: `Unsupported computer_use action: ${actionType}` };
120
+ }
121
+ }
122
+
123
+ if (desktopActions.length === 0) {
124
+ return { success: false, error: 'No actions to execute.' };
125
+ }
126
+
127
+ if (desktopActions.length > 12) {
128
+ return { success: false, error: `Maximum 12 actions allowed per call; got ${desktopActions.length}.` };
129
+ }
130
+
131
+ auditLog.log({
132
+ event: 'computer_use_actuation',
133
+ app,
134
+ actionCount: desktopActions.length,
135
+ verbs: desktopActions.map((a) => a.verb)
136
+ });
137
+
138
+ const execResult = await desktopAct.execute({ app, actions: desktopActions }, context);
139
+ const finalRes: { success: boolean; output?: string; error?: string; actions?: any[] } = {
140
+ success: Boolean(execResult.success),
141
+ actions: desktopActions
142
+ };
143
+ if (execResult.output) finalRes.output = execResult.output;
144
+ if (execResult.error) finalRes.error = execResult.error;
145
+ return finalRes;
146
+ }
147
+ };
@@ -0,0 +1,10 @@
1
+ export const desktopAct: {
2
+ schema: Record<string, unknown>;
3
+ execute(args?: Record<string, unknown>, context?: Record<string, unknown>): Promise<{
4
+ success: boolean;
5
+ output?: string;
6
+ error?: string;
7
+ requiresConsent?: boolean;
8
+ useToolInstead?: string;
9
+ }>;
10
+ };
@@ -1,4 +1,6 @@
1
1
  import { creativeApps } from './creativeApps.js';
2
+ import { computerUse } from './computerUse.js';
3
+ import { uiForge } from './uiForge.js';
2
4
  import { sysRun, sysRunBg } from './sysRun.js';
3
5
  import { fsRead } from './fsRead.js';
4
6
  import { fsWrite } from './fsWrite.js';
@@ -120,7 +122,8 @@ const TOOL_MODULES = [
120
122
  gameAssetHarvester, gameAudioSynth, gameVfxEmitterGen,
121
123
  gameShaderForge, gameSceneBuilder, gameEntitySpawner,
122
124
  gameTilemapGenerator, gameUiHudBuilder, gameEconomyBalancer,
123
- gameDataForge, gameWebRuntimeInspector, gameCanvasAudit, readNativeSkill, createSkill, creativeApps
125
+ gameDataForge, gameWebRuntimeInspector, gameCanvasAudit, readNativeSkill, createSkill, creativeApps,
126
+ computerUse, uiForge
124
127
  ];
125
128
 
126
129
  // Base static definitions (backwards compatible). Order is preserved from the
@@ -0,0 +1,132 @@
1
+ /*
2
+ * Copyright (c) 2011-2024 Thesys Inc.
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * Causyne UI Forge Tool Wrapper.
6
+ */
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { generateHtmlComponent, generatePreviewHtml, generateReactComponent } from '../uiEngine/uiForge.js';
10
+ import { resolveUserPath } from '../utils/paths.js';
11
+ export const uiForge = {
12
+ schema: {
13
+ name: 'ui_forge',
14
+ description: 'Causyne UI Forge: Generate production-grade accessible, responsive UI components (cards, dashboards, forms, tables) and design systems. Emits clean semantic HTML/CSS, React+Tailwind, or Svelte/Vue with a self-contained live preview.',
15
+ parameters: {
16
+ type: 'object',
17
+ properties: {
18
+ framework: {
19
+ type: 'string',
20
+ enum: ['html-css', 'react', 'vue', 'svelte'],
21
+ description: 'Target frontend framework. Defaults to "html-css" (zero-dependency semantic HTML + Vanilla CSS).'
22
+ },
23
+ spec: {
24
+ type: 'object',
25
+ properties: {
26
+ name: { type: 'string', description: 'Component PascalCase name, e.g. AnalyticsCard.' },
27
+ type: {
28
+ type: 'string',
29
+ enum: ['card', 'navbar', 'form', 'dashboard-stats', 'table', 'modal', 'custom'],
30
+ description: 'Pattern type.'
31
+ },
32
+ title: { type: 'string', description: 'Header or component title.' },
33
+ description: { type: 'string', description: 'Subtitle or descriptive text.' },
34
+ theme: { type: 'string', enum: ['dark', 'light'], description: 'Theme palette (dark or light).' },
35
+ items: {
36
+ type: 'array',
37
+ items: {
38
+ type: 'object',
39
+ properties: {
40
+ id: { type: 'string' },
41
+ label: { type: 'string' },
42
+ value: { type: ['string', 'number'] },
43
+ description: { type: 'string' }
44
+ },
45
+ required: ['label']
46
+ },
47
+ description: 'List of data items, metrics, fields or elements.'
48
+ },
49
+ actions: {
50
+ type: 'array',
51
+ items: {
52
+ type: 'object',
53
+ properties: {
54
+ label: { type: 'string' },
55
+ action: { type: 'string' },
56
+ primary: { type: 'boolean' }
57
+ },
58
+ required: ['label', 'action']
59
+ },
60
+ description: 'Interactive button actions.'
61
+ }
62
+ },
63
+ required: ['type']
64
+ },
65
+ outputPath: {
66
+ type: 'string',
67
+ description: 'Optional file path to write the generated component and preview directly to workspace, e.g. "src/components/AnalyticsCard.html".'
68
+ }
69
+ },
70
+ required: ['spec']
71
+ }
72
+ },
73
+ async execute(args = {}) {
74
+ if (!args.spec || !args.spec.type) {
75
+ return { success: false, error: 'UI Forge specification with a valid component "type" is required.' };
76
+ }
77
+ const framework = args.framework || 'html-css';
78
+ const spec = args.spec;
79
+ let code = '';
80
+ let css = '';
81
+ let previewHtml = '';
82
+ if (framework === 'react') {
83
+ code = generateReactComponent(spec);
84
+ const htmlComp = generateHtmlComponent(spec);
85
+ css = htmlComp.css;
86
+ previewHtml = generatePreviewHtml(htmlComp.html, htmlComp.css, spec.theme || 'dark');
87
+ }
88
+ else {
89
+ const htmlComp = generateHtmlComponent(spec);
90
+ code = htmlComp.html;
91
+ css = htmlComp.css;
92
+ previewHtml = generatePreviewHtml(htmlComp.html, htmlComp.css, spec.theme || 'dark');
93
+ }
94
+ let writtenFiles = [];
95
+ if (args.outputPath) {
96
+ try {
97
+ const resolved = resolveUserPath(args.outputPath);
98
+ const dir = path.dirname(resolved);
99
+ if (!fs.existsSync(dir))
100
+ fs.mkdirSync(dir, { recursive: true });
101
+ fs.writeFileSync(resolved, code, 'utf8');
102
+ writtenFiles.push(resolved);
103
+ const ext = path.extname(resolved);
104
+ const previewFile = path.join(dir, `${path.basename(resolved, ext)}.preview.html`);
105
+ fs.writeFileSync(previewFile, previewHtml, 'utf8');
106
+ writtenFiles.push(previewFile);
107
+ }
108
+ catch (err) {
109
+ return { success: false, error: `Failed to write component files: ${err.message}` };
110
+ }
111
+ }
112
+ const reportLines = [
113
+ `Generated ${spec.type} component using Causyne UI Forge (${framework}).`,
114
+ `Theme: ${spec.theme || 'dark'} | Items: ${(spec.items || []).length} | Actions: ${(spec.actions || []).length}`
115
+ ];
116
+ if (writtenFiles.length > 0) {
117
+ reportLines.push(`Saved component file: ${writtenFiles[0]}`);
118
+ reportLines.push(`Saved preview file: ${writtenFiles[1]}`);
119
+ }
120
+ else {
121
+ reportLines.push('', '--- Component Code ---', code);
122
+ }
123
+ const res = {
124
+ success: true,
125
+ output: reportLines.join('\n'),
126
+ code
127
+ };
128
+ if (writtenFiles[1])
129
+ res.previewPath = writtenFiles[1];
130
+ return res;
131
+ }
132
+ };
@@ -0,0 +1,145 @@
1
+ /*
2
+ * Copyright (c) 2011-2024 Thesys Inc.
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * Causyne UI Forge Tool Wrapper.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { generateHtmlComponent, generatePreviewHtml, generateReactComponent, type UIComponentSpec, type UIFramework } from '../uiEngine/uiForge.js';
11
+ import { resolveUserPath } from '../utils/paths.js';
12
+
13
+ export interface UiForgeArgs {
14
+ framework?: UIFramework;
15
+ spec?: UIComponentSpec;
16
+ outputPath?: string;
17
+ }
18
+
19
+ export const uiForge = {
20
+ schema: {
21
+ name: 'ui_forge',
22
+ description: 'Causyne UI Forge: Generate production-grade accessible, responsive UI components (cards, dashboards, forms, tables) and design systems. Emits clean semantic HTML/CSS, React+Tailwind, or Svelte/Vue with a self-contained live preview.',
23
+ parameters: {
24
+ type: 'object',
25
+ properties: {
26
+ framework: {
27
+ type: 'string',
28
+ enum: ['html-css', 'react', 'vue', 'svelte'],
29
+ description: 'Target frontend framework. Defaults to "html-css" (zero-dependency semantic HTML + Vanilla CSS).'
30
+ },
31
+ spec: {
32
+ type: 'object',
33
+ properties: {
34
+ name: { type: 'string', description: 'Component PascalCase name, e.g. AnalyticsCard.' },
35
+ type: {
36
+ type: 'string',
37
+ enum: ['card', 'navbar', 'form', 'dashboard-stats', 'table', 'modal', 'custom'],
38
+ description: 'Pattern type.'
39
+ },
40
+ title: { type: 'string', description: 'Header or component title.' },
41
+ description: { type: 'string', description: 'Subtitle or descriptive text.' },
42
+ theme: { type: 'string', enum: ['dark', 'light'], description: 'Theme palette (dark or light).' },
43
+ items: {
44
+ type: 'array',
45
+ items: {
46
+ type: 'object',
47
+ properties: {
48
+ id: { type: 'string' },
49
+ label: { type: 'string' },
50
+ value: { type: ['string', 'number'] },
51
+ description: { type: 'string' }
52
+ },
53
+ required: ['label']
54
+ },
55
+ description: 'List of data items, metrics, fields or elements.'
56
+ },
57
+ actions: {
58
+ type: 'array',
59
+ items: {
60
+ type: 'object',
61
+ properties: {
62
+ label: { type: 'string' },
63
+ action: { type: 'string' },
64
+ primary: { type: 'boolean' }
65
+ },
66
+ required: ['label', 'action']
67
+ },
68
+ description: 'Interactive button actions.'
69
+ }
70
+ },
71
+ required: ['type']
72
+ },
73
+ outputPath: {
74
+ type: 'string',
75
+ description: 'Optional file path to write the generated component and preview directly to workspace, e.g. "src/components/AnalyticsCard.html".'
76
+ }
77
+ },
78
+ required: ['spec']
79
+ }
80
+ },
81
+
82
+ async execute(args: UiForgeArgs = {}): Promise<{ success: boolean; output?: string; code?: string; previewPath?: string; error?: string }> {
83
+ if (!args.spec || !args.spec.type) {
84
+ return { success: false, error: 'UI Forge specification with a valid component "type" is required.' };
85
+ }
86
+
87
+ const framework: UIFramework = args.framework || 'html-css';
88
+ const spec = args.spec;
89
+
90
+ let code = '';
91
+ let css = '';
92
+ let previewHtml = '';
93
+
94
+ if (framework === 'react') {
95
+ code = generateReactComponent(spec);
96
+ const htmlComp = generateHtmlComponent(spec);
97
+ css = htmlComp.css;
98
+ previewHtml = generatePreviewHtml(htmlComp.html, htmlComp.css, spec.theme || 'dark');
99
+ } else {
100
+ const htmlComp = generateHtmlComponent(spec);
101
+ code = htmlComp.html;
102
+ css = htmlComp.css;
103
+ previewHtml = generatePreviewHtml(htmlComp.html, htmlComp.css, spec.theme || 'dark');
104
+ }
105
+
106
+ let writtenFiles: string[] = [];
107
+ if (args.outputPath) {
108
+ try {
109
+ const resolved = resolveUserPath(args.outputPath);
110
+ const dir = path.dirname(resolved);
111
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
112
+
113
+ fs.writeFileSync(resolved, code, 'utf8');
114
+ writtenFiles.push(resolved);
115
+
116
+ const ext = path.extname(resolved);
117
+ const previewFile = path.join(dir, `${path.basename(resolved, ext)}.preview.html`);
118
+ fs.writeFileSync(previewFile, previewHtml, 'utf8');
119
+ writtenFiles.push(previewFile);
120
+ } catch (err: any) {
121
+ return { success: false, error: `Failed to write component files: ${err.message}` };
122
+ }
123
+ }
124
+
125
+ const reportLines = [
126
+ `Generated ${spec.type} component using Causyne UI Forge (${framework}).`,
127
+ `Theme: ${spec.theme || 'dark'} | Items: ${(spec.items || []).length} | Actions: ${(spec.actions || []).length}`
128
+ ];
129
+
130
+ if (writtenFiles.length > 0) {
131
+ reportLines.push(`Saved component file: ${writtenFiles[0]}`);
132
+ reportLines.push(`Saved preview file: ${writtenFiles[1]}`);
133
+ } else {
134
+ reportLines.push('', '--- Component Code ---', code);
135
+ }
136
+
137
+ const res: { success: boolean; output?: string; code?: string; previewPath?: string; error?: string } = {
138
+ success: true,
139
+ output: reportLines.join('\n'),
140
+ code
141
+ };
142
+ if (writtenFiles[1]) res.previewPath = writtenFiles[1];
143
+ return res;
144
+ }
145
+ };