fluxloop-cli 0.2.28__tar.gz → 0.2.29__tar.gz

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.
Files changed (65) hide show
  1. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/PKG-INFO +1 -1
  2. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/__init__.py +1 -1
  3. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/run.py +113 -133
  4. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli.egg-info/PKG-INFO +1 -1
  5. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/pyproject.toml +1 -1
  6. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/README.md +0 -0
  7. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/arg_binder.py +0 -0
  8. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/__init__.py +0 -0
  9. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/config.py +0 -0
  10. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/doctor.py +0 -0
  11. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/evaluate.py +0 -0
  12. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/generate.py +0 -0
  13. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/init.py +0 -0
  14. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/parse.py +0 -0
  15. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/record.py +0 -0
  16. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/status.py +0 -0
  17. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/config_loader.py +0 -0
  18. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/config_schema.py +0 -0
  19. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/constants.py +0 -0
  20. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/conversation_supervisor.py +0 -0
  21. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/environment.py +0 -0
  22. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/__init__.py +0 -0
  23. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/artifacts.py +0 -0
  24. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/config.py +0 -0
  25. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/engine/__init__.py +0 -0
  26. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/engine/analysis.py +0 -0
  27. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/engine/core.py +0 -0
  28. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/engine/reporting/__init__.py +0 -0
  29. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/engine/reporting/html.py +0 -0
  30. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/engine/reporting/markdown.py +0 -0
  31. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/engine/success.py +0 -0
  32. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/llm.py +0 -0
  33. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/prompts/__init__.py +0 -0
  34. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/prompts/base.py +0 -0
  35. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/prompts/information_completeness.py +0 -0
  36. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/prompts/intent_recognition.py +0 -0
  37. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/prompts/response_clarity.py +0 -0
  38. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/prompts/response_consistency.py +0 -0
  39. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/rules.py +0 -0
  40. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/input_generator.py +0 -0
  41. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/llm_generator.py +0 -0
  42. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/main.py +0 -0
  43. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/project_paths.py +0 -0
  44. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/runner.py +0 -0
  45. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/target_loader.py +0 -0
  46. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/templates.py +0 -0
  47. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/validators.py +0 -0
  48. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli.egg-info/SOURCES.txt +0 -0
  49. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli.egg-info/dependency_links.txt +0 -0
  50. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli.egg-info/entry_points.txt +0 -0
  51. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli.egg-info/requires.txt +0 -0
  52. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli.egg-info/top_level.txt +0 -0
  53. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/setup.cfg +0 -0
  54. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_analysis_recommendations.py +0 -0
  55. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_arg_binder.py +0 -0
  56. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_config_command.py +0 -0
  57. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_conversation_supervisor.py +0 -0
  58. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_evaluate_command.py +0 -0
  59. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_evaluation_llm.py +0 -0
  60. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_input_generator.py +0 -0
  61. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_prompt_library.py +0 -0
  62. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_run_command.py +0 -0
  63. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_runner_multi_turn.py +0 -0
  64. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_success_criteria.py +0 -0
  65. {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_target_loader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fluxloop-cli
3
- Version: 0.2.28
3
+ Version: 0.2.29
4
4
  Summary: FluxLoop CLI for running agent simulations
5
5
  Author-email: FluxLoop Team <team@fluxloop.dev>
6
6
  License: Apache-2.0
@@ -2,7 +2,7 @@
2
2
  FluxLoop CLI - Command-line interface for running agent simulations.
3
3
  """
4
4
 
5
- __version__ = "0.2.28"
5
+ __version__ = "0.2.29"
6
6
 
7
7
  from .main import app
8
8
 
@@ -3,10 +3,9 @@ Run command for executing experiments and simulations.
3
3
  """
4
4
 
5
5
  import asyncio
6
- import json
7
6
  import sys
8
7
  from pathlib import Path
9
- from typing import Optional
8
+ from typing import Callable, Optional
10
9
 
11
10
  import typer
12
11
  from rich.console import Console
@@ -28,16 +27,6 @@ app = typer.Typer()
28
27
  console = Console()
29
28
 
30
29
 
31
- def _emit_progress_event(event: str, **payload: object) -> None:
32
- """Emit a structured progress event to stderr for machine consumption."""
33
- data = {"event": event, **payload}
34
- try:
35
- print(json.dumps(data, default=str), file=sys.stderr, flush=True)
36
- except Exception:
37
- # Fall back to repr if serialization fails
38
- print({"event": event, **{k: repr(v) for k, v in payload.items()}}, file=sys.stderr, flush=True)
39
-
40
-
41
30
  @app.command()
42
31
  def experiment(
43
32
  config_file: Path = typer.Option(
@@ -124,6 +113,11 @@ def experiment(
124
113
  "--dry-run",
125
114
  help="Show what would be run without executing",
126
115
  ),
116
+ display: bool = typer.Option(
117
+ True,
118
+ "--display/--no-display",
119
+ help="Show rich console output (disable for plain log streaming)",
120
+ ),
127
121
  ):
128
122
  """
129
123
  Run an experiment based on configuration file.
@@ -239,19 +233,8 @@ def experiment(
239
233
 
240
234
  console.print(summary)
241
235
 
242
- _emit_progress_event(
243
- "experiment_summary",
244
- name=config.name,
245
- iterations=config.iterations,
246
- personas=len(config.personas),
247
- total_runs=total_runs,
248
- multi_turn_enabled=bool(config.multi_turn and config.multi_turn.enabled),
249
- output_dir=config.output_directory,
250
- )
251
-
252
236
  if dry_run:
253
237
  console.print("\n[yellow]Dry run mode - no execution will occur[/yellow]")
254
- _emit_progress_event("experiment_status", status="dry_run", name=config.name)
255
238
  return
256
239
 
257
240
  # Confirm execution
@@ -265,128 +248,134 @@ def experiment(
265
248
  f"\nThis will execute {total_runs} runs."
266
249
  )
267
250
 
268
- _emit_progress_event(
269
- "experiment_ready",
270
- name=config.name,
271
- total_runs=total_runs,
272
- )
273
-
274
251
  if not typer.confirm("Continue?"):
275
- _emit_progress_event("experiment_status", status="aborted", reason="user_declined", name=config.name)
276
252
  raise typer.Abort()
277
253
 
278
254
  # Create runner
279
255
  # Run experiment with progress tracking
280
256
  console.print("\n[bold green]▶️ Starting experiment...[/bold green]\n")
281
257
 
282
- _emit_progress_event("experiment_started", name=config.name, total_runs=total_runs)
283
-
284
- with Progress(
285
- SpinnerColumn(),
286
- TextColumn("[progress.description]{task.description}"),
287
- BarColumn(),
288
- TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
289
- TimeRemainingColumn(),
290
- TextColumn("({task.completed} of {task.total})"),
291
- console=console,
292
- ) as progress:
293
- # Create main task
294
- main_task = progress.add_task(
295
- f"Running {config.name}",
296
- total=total_runs,
297
- )
298
- multi_turn_total = (
299
- config.multi_turn.max_turns
300
- if config.multi_turn and config.multi_turn.max_turns
301
- else None
302
- )
303
- turn_task = progress.add_task(
304
- "[cyan]Turn 0/0",
305
- total=multi_turn_total or 1,
306
- visible=False,
307
- )
258
+ def _execute_with_callbacks(
259
+ progress_callback: Optional[Callable[[], None]],
260
+ turn_progress_callback: Optional[Callable[[int, int, Optional[str]], None]],
261
+ ):
262
+ try:
263
+ return asyncio.run(
264
+ runner.run_experiment(
265
+ progress_callback=progress_callback,
266
+ turn_progress_callback=turn_progress_callback,
267
+ )
268
+ )
269
+ except KeyboardInterrupt:
270
+ console.print("\n[yellow]Experiment interrupted by user[/yellow]")
271
+ raise typer.Exit(1)
272
+ except Exception as e:
273
+ console.print(f"\n[red]Error during experiment:[/red] {e}")
274
+ if "--debug" in sys.argv:
275
+ console.print_exception()
276
+ raise typer.Exit(1)
308
277
 
309
- def _progress_callback():
310
- progress.advance(main_task)
311
- task = progress.tasks[main_task]
312
- _emit_progress_event(
313
- "run_progress",
314
- completed=int(task.completed),
315
- total=int(task.total or 0),
316
- description=task.description,
317
- experiment=config.name,
278
+ if display and console.is_terminal:
279
+ with Progress(
280
+ SpinnerColumn(),
281
+ TextColumn("[progress.description]{task.description}"),
282
+ BarColumn(),
283
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
284
+ TimeRemainingColumn(),
285
+ TextColumn("({task.completed} of {task.total})"),
286
+ console=console,
287
+ ) as progress:
288
+ main_task = progress.add_task(
289
+ f"Running {config.name}",
290
+ total=total_runs,
291
+ )
292
+ multi_turn_total = (
293
+ config.multi_turn.max_turns
294
+ if config.multi_turn and config.multi_turn.max_turns
295
+ else None
296
+ )
297
+ turn_task = progress.add_task(
298
+ "[cyan]Turn 0/0",
299
+ total=multi_turn_total or 1,
300
+ visible=False,
318
301
  )
319
302
 
320
- def _turn_progress_callback(
321
- current_turn: int,
322
- total_turns: int,
323
- message: Optional[str] = None,
324
- ) -> None:
325
- total = total_turns or 1
326
- clamped_turn = max(0, current_turn)
327
- if message is None:
328
- completed = min(clamped_turn, total)
329
- description = f"[cyan]Turn {min(clamped_turn, total)}/{total}: complete"
303
+ def _progress_callback() -> None:
304
+ progress.advance(main_task)
305
+
306
+ def _turn_progress_callback(
307
+ current_turn: int,
308
+ total_turns: int,
309
+ message: Optional[str] = None,
310
+ ) -> None:
311
+ total = total_turns or 1
312
+ clamped_turn = max(0, current_turn)
313
+ if message is None:
314
+ completed = min(clamped_turn, total)
315
+ description = f"[cyan]Turn {min(clamped_turn, total)}/{total}: complete"
316
+ progress.update(
317
+ turn_task,
318
+ total=total,
319
+ completed=completed,
320
+ description=description,
321
+ visible=False,
322
+ )
323
+ return
324
+
325
+ if not progress.tasks[turn_task].visible:
326
+ progress.update(turn_task, visible=True)
327
+
328
+ preview = message.replace("\n", " ")
329
+ if len(preview) > 40:
330
+ preview = preview[:37] + "..."
331
+
332
+ completed = max(0, min(clamped_turn - 1, total))
333
+ description = f"[cyan]Turn {min(clamped_turn, total)}/{total}: {preview}"
330
334
  progress.update(
331
335
  turn_task,
332
336
  total=total,
333
337
  completed=completed,
334
338
  description=description,
335
- visible=False,
336
339
  )
337
- _emit_progress_event(
338
- "turn_progress",
339
- state="complete",
340
- current=min(clamped_turn, total),
341
- total=total,
342
- experiment=config.name,
343
- )
344
- return
345
-
346
- if not progress.tasks[turn_task].visible:
347
- progress.update(turn_task, visible=True)
348
-
349
- preview = message.replace("\n", " ")
350
- if len(preview) > 40:
351
- preview = preview[:37] + "..."
352
340
 
353
- completed = max(0, min(clamped_turn - 1, total))
354
- description = f"[cyan]Turn {min(clamped_turn, total)}/{total}: {preview}"
341
+ results = _execute_with_callbacks(_progress_callback, _turn_progress_callback)
342
+ else:
343
+ completed_runs = 0
355
344
 
356
- progress.update(
357
- turn_task,
358
- total=total,
359
- completed=completed,
360
- description=description,
361
- )
362
- _emit_progress_event(
363
- "turn_progress",
364
- state="in_progress",
365
- current=clamped_turn,
366
- total=total,
367
- preview=preview,
368
- experiment=config.name,
345
+ def _progress_callback() -> None:
346
+ nonlocal completed_runs
347
+ completed_runs += 1
348
+ print(
349
+ f"[fluxloop] run progress {completed_runs}/{total_runs}",
350
+ flush=True,
369
351
  )
370
352
 
371
- # Run experiment
372
- try:
373
- results = asyncio.run(
374
- runner.run_experiment(
375
- progress_callback=_progress_callback,
376
- turn_progress_callback=_turn_progress_callback,
353
+ def _turn_progress_callback(
354
+ current_turn: int,
355
+ total_turns: int,
356
+ message: Optional[str] = None,
357
+ ) -> None:
358
+ total = total_turns or 1
359
+ clamped_turn = max(0, current_turn)
360
+ if message:
361
+ preview = message.replace("\n", " ")
362
+ if len(preview) > 80:
363
+ preview = preview[:77] + "..."
364
+ print(
365
+ f"[fluxloop] turn progress {min(clamped_turn, total)}/{total}: {preview}",
366
+ flush=True,
377
367
  )
378
- )
379
- except KeyboardInterrupt:
380
- console.print("\n[yellow]Experiment interrupted by user[/yellow]")
381
- _emit_progress_event("experiment_status", status="interrupted", name=config.name)
382
- raise typer.Exit(1)
383
- except Exception as e:
384
- console.print(f"\n[red]Error during experiment:[/red] {e}")
385
- # Debug mode - show full traceback
386
- if "--debug" in sys.argv:
387
- console.print_exception()
388
- _emit_progress_event("experiment_status", status="error", error=str(e), name=config.name)
389
- raise typer.Exit(1)
368
+ else:
369
+ print(
370
+ f"[fluxloop] turn progress {min(clamped_turn, total)}/{total}: complete",
371
+ flush=True,
372
+ )
373
+
374
+ print(
375
+ f"[fluxloop] running {config.name} ({total_runs} total runs)",
376
+ flush=True,
377
+ )
378
+ results = _execute_with_callbacks(_progress_callback, _turn_progress_callback)
390
379
 
391
380
  config.set_resolved_input_count(results.get("input_count", config.get_input_count()))
392
381
 
@@ -399,15 +388,6 @@ def experiment(
399
388
  )
400
389
 
401
390
  _display_results(results)
402
- _emit_progress_event(
403
- "experiment_status",
404
- status="completed",
405
- name=config.name,
406
- total_runs=results.get("total_runs", total_runs),
407
- successful=results.get("successful"),
408
- failed=results.get("failed"),
409
- success_rate=results.get("success_rate"),
410
- )
411
391
 
412
392
 
413
393
  @app.command()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fluxloop-cli
3
- Version: 0.2.28
3
+ Version: 0.2.29
4
4
  Summary: FluxLoop CLI for running agent simulations
5
5
  Author-email: FluxLoop Team <team@fluxloop.dev>
6
6
  License: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fluxloop-cli"
7
- version = "0.2.28"
7
+ version = "0.2.29"
8
8
  description = "FluxLoop CLI for running agent simulations"
9
9
  authors = [
10
10
  {name = "FluxLoop Team", email = "team@fluxloop.dev"}
File without changes
File without changes