cursorflow 1.2.0__py3-none-any.whl → 1.3.0__py3-none-any.whl

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.
cursorflow/cli.py CHANGED
@@ -124,6 +124,218 @@ def test(test_name, base_url, actions, logs, config, verbose):
124
124
  console.print(traceback.format_exc())
125
125
  raise
126
126
 
127
+ @main.command()
128
+ @click.argument('mockup_url', required=True)
129
+ @click.option('--base-url', '-u', default='http://localhost:3000',
130
+ help='Base URL of work-in-progress implementation')
131
+ @click.option('--mockup-actions', '-ma',
132
+ help='JSON file with actions to perform on mockup, or inline JSON string')
133
+ @click.option('--implementation-actions', '-ia',
134
+ help='JSON file with actions to perform on implementation, or inline JSON string')
135
+ @click.option('--viewports', '-v',
136
+ help='JSON array of viewports to test: [{"width": 1440, "height": 900, "name": "desktop"}]')
137
+ @click.option('--diff-threshold', '-t', type=float, default=0.1,
138
+ help='Visual difference threshold (0.0-1.0)')
139
+ @click.option('--output', '-o', default='mockup_comparison_results.json',
140
+ help='Output file for comparison results')
141
+ @click.option('--verbose', is_flag=True,
142
+ help='Verbose output')
143
+ def compare_mockup(mockup_url, base_url, mockup_actions, implementation_actions, viewports, diff_threshold, output, verbose):
144
+ """Compare mockup design to work-in-progress implementation"""
145
+
146
+ console.print(f"🎨 Comparing mockup [blue]{mockup_url}[/blue] to implementation [blue]{base_url}[/blue]")
147
+
148
+ # Parse actions
149
+ def parse_actions(actions_input):
150
+ if not actions_input:
151
+ return None
152
+
153
+ if actions_input.startswith('[') or actions_input.startswith('{'):
154
+ return json.loads(actions_input)
155
+ else:
156
+ with open(actions_input, 'r') as f:
157
+ return json.load(f)
158
+
159
+ try:
160
+ mockup_actions_parsed = parse_actions(mockup_actions)
161
+ implementation_actions_parsed = parse_actions(implementation_actions)
162
+
163
+ # Parse viewports
164
+ viewports_parsed = None
165
+ if viewports:
166
+ if viewports.startswith('['):
167
+ viewports_parsed = json.loads(viewports)
168
+ else:
169
+ with open(viewports, 'r') as f:
170
+ viewports_parsed = json.load(f)
171
+
172
+ # Build comparison config
173
+ comparison_config = {
174
+ "diff_threshold": diff_threshold
175
+ }
176
+ if viewports_parsed:
177
+ comparison_config["viewports"] = viewports_parsed
178
+
179
+ except Exception as e:
180
+ console.print(f"[red]Error parsing input parameters: {e}[/red]")
181
+ return
182
+
183
+ # Initialize CursorFlow
184
+ try:
185
+ from .core.cursorflow import CursorFlow
186
+ flow = CursorFlow(
187
+ base_url=base_url,
188
+ log_config={'source': 'local', 'paths': ['logs/app.log']},
189
+ browser_config={'headless': True}
190
+ )
191
+ except Exception as e:
192
+ console.print(f"[red]Error initializing CursorFlow: {e}[/red]")
193
+ return
194
+
195
+ # Execute mockup comparison
196
+ try:
197
+ console.print("🚀 Starting mockup comparison...")
198
+ results = asyncio.run(flow.compare_mockup_to_implementation(
199
+ mockup_url=mockup_url,
200
+ mockup_actions=mockup_actions_parsed,
201
+ implementation_actions=implementation_actions_parsed,
202
+ comparison_config=comparison_config
203
+ ))
204
+
205
+ if "error" in results:
206
+ console.print(f"[red]❌ Comparison failed: {results['error']}[/red]")
207
+ return
208
+
209
+ # Display results summary
210
+ summary = results.get('summary', {})
211
+ console.print(f"✅ Comparison completed: {results.get('comparison_id', 'unknown')}")
212
+ console.print(f"📊 Average similarity: [bold]{summary.get('average_similarity', 0)}%[/bold]")
213
+ console.print(f"📱 Viewports tested: {summary.get('viewports_tested', 0)}")
214
+
215
+ # Show recommendations
216
+ recommendations = results.get('recommendations', [])
217
+ if recommendations:
218
+ console.print(f"💡 Recommendations: {len(recommendations)} improvements suggested")
219
+ for i, rec in enumerate(recommendations[:3]): # Show first 3
220
+ console.print(f" {i+1}. {rec.get('description', 'No description')}")
221
+
222
+ # Save results
223
+ with open(output, 'w') as f:
224
+ json.dump(results, f, indent=2, default=str)
225
+
226
+ console.print(f"💾 Full results saved to: [cyan]{output}[/cyan]")
227
+ console.print(f"📁 Visual diffs stored in: [cyan].cursorflow/artifacts/[/cyan]")
228
+
229
+ except Exception as e:
230
+ console.print(f"[red]❌ Comparison failed: {e}[/red]")
231
+ if verbose:
232
+ import traceback
233
+ console.print(traceback.format_exc())
234
+ raise
235
+
236
+ @main.command()
237
+ @click.argument('mockup_url', required=True)
238
+ @click.option('--base-url', '-u', default='http://localhost:3000',
239
+ help='Base URL of work-in-progress implementation')
240
+ @click.option('--css-improvements', '-c', required=True,
241
+ help='JSON file with CSS improvements to test, or inline JSON string')
242
+ @click.option('--base-actions', '-a',
243
+ help='JSON file with base actions to perform before each test')
244
+ @click.option('--diff-threshold', '-t', type=float, default=0.1,
245
+ help='Visual difference threshold (0.0-1.0)')
246
+ @click.option('--output', '-o', default='mockup_iteration_results.json',
247
+ help='Output file for iteration results')
248
+ @click.option('--verbose', is_flag=True,
249
+ help='Verbose output')
250
+ def iterate_mockup(mockup_url, base_url, css_improvements, base_actions, diff_threshold, output, verbose):
251
+ """Iteratively improve implementation to match mockup design"""
252
+
253
+ console.print(f"🔄 Iterating on [blue]{base_url}[/blue] to match [blue]{mockup_url}[/blue]")
254
+
255
+ # Parse CSS improvements
256
+ def parse_json_input(input_str):
257
+ if not input_str:
258
+ return None
259
+
260
+ if input_str.startswith('[') or input_str.startswith('{'):
261
+ return json.loads(input_str)
262
+ else:
263
+ with open(input_str, 'r') as f:
264
+ return json.load(f)
265
+
266
+ try:
267
+ css_improvements_parsed = parse_json_input(css_improvements)
268
+ base_actions_parsed = parse_json_input(base_actions)
269
+
270
+ if not css_improvements_parsed:
271
+ console.print("[red]Error: CSS improvements are required[/red]")
272
+ return
273
+
274
+ comparison_config = {"diff_threshold": diff_threshold}
275
+
276
+ except Exception as e:
277
+ console.print(f"[red]Error parsing input parameters: {e}[/red]")
278
+ return
279
+
280
+ # Initialize CursorFlow
281
+ try:
282
+ from .core.cursorflow import CursorFlow
283
+ flow = CursorFlow(
284
+ base_url=base_url,
285
+ log_config={'source': 'local', 'paths': ['logs/app.log']},
286
+ browser_config={'headless': True}
287
+ )
288
+ except Exception as e:
289
+ console.print(f"[red]Error initializing CursorFlow: {e}[/red]")
290
+ return
291
+
292
+ # Execute iterative mockup matching
293
+ try:
294
+ console.print(f"🚀 Starting iterative matching with {len(css_improvements_parsed)} CSS improvements...")
295
+ results = asyncio.run(flow.iterative_mockup_matching(
296
+ mockup_url=mockup_url,
297
+ css_improvements=css_improvements_parsed,
298
+ base_actions=base_actions_parsed,
299
+ comparison_config=comparison_config
300
+ ))
301
+
302
+ if "error" in results:
303
+ console.print(f"[red]❌ Iteration failed: {results['error']}[/red]")
304
+ return
305
+
306
+ # Display results summary
307
+ summary = results.get('summary', {})
308
+ console.print(f"✅ Iteration completed: {results.get('session_id', 'unknown')}")
309
+ console.print(f"📊 Total improvement: [bold]{summary.get('total_improvement', 0)}%[/bold]")
310
+ console.print(f"🔄 Successful iterations: {summary.get('successful_iterations', 0)}/{summary.get('total_iterations', 0)}")
311
+
312
+ # Show best iteration
313
+ best_iteration = results.get('best_iteration')
314
+ if best_iteration:
315
+ console.print(f"🏆 Best iteration: {best_iteration.get('css_change', {}).get('name', 'unnamed')}")
316
+ console.print(f" Similarity achieved: {best_iteration.get('similarity_achieved', 0)}%")
317
+
318
+ # Show final recommendations
319
+ recommendations = results.get('final_recommendations', [])
320
+ if recommendations:
321
+ console.print(f"💡 Final recommendations: {len(recommendations)} actions suggested")
322
+ for i, rec in enumerate(recommendations[:3]):
323
+ console.print(f" {i+1}. {rec.get('description', 'No description')}")
324
+
325
+ # Save results
326
+ with open(output, 'w') as f:
327
+ json.dump(results, f, indent=2, default=str)
328
+
329
+ console.print(f"💾 Full results saved to: [cyan]{output}[/cyan]")
330
+ console.print(f"📁 Iteration progress stored in: [cyan].cursorflow/artifacts/[/cyan]")
331
+
332
+ except Exception as e:
333
+ console.print(f"[red]❌ Iteration failed: {e}[/red]")
334
+ if verbose:
335
+ import traceback
336
+ console.print(traceback.format_exc())
337
+ raise
338
+
127
339
  @main.command()
128
340
  @click.option('--project-path', '-p', default='.',
129
341
  help='Project directory path')