fluxloop-cli 0.3.4__tar.gz → 0.3.5__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 (70) hide show
  1. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/PKG-INFO +1 -1
  2. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/__init__.py +1 -1
  3. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/api_utils.py +32 -9
  4. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/auth.py +6 -13
  5. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/config.py +3 -8
  6. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/projects.py +8 -2
  7. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/sync.py +5 -13
  8. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/context_manager.py +3 -3
  9. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/templates.py +1 -2
  10. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli.egg-info/PKG-INFO +1 -1
  11. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/pyproject.toml +1 -1
  12. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/README.md +0 -0
  13. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/arg_binder.py +0 -0
  14. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/auth_manager.py +0 -0
  15. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/__init__.py +0 -0
  16. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/apikeys.py +0 -0
  17. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/bundles.py +0 -0
  18. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/context.py +0 -0
  19. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/criteria.py +0 -0
  20. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/data.py +0 -0
  21. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/evaluate.py +0 -0
  22. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/generate.py +0 -0
  23. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/init.py +0 -0
  24. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/inputs.py +0 -0
  25. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/local_context.py +0 -0
  26. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/personas.py +0 -0
  27. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/run.py +0 -0
  28. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/scenarios.py +0 -0
  29. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/status.py +0 -0
  30. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/commands/test.py +0 -0
  31. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/common_options.py +0 -0
  32. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/config_loader.py +0 -0
  33. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/config_schema.py +0 -0
  34. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/constants.py +0 -0
  35. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/conversation_supervisor.py +0 -0
  36. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/environment.py +0 -0
  37. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/http_client.py +0 -0
  38. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/input_generator.py +0 -0
  39. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/llm_generator.py +0 -0
  40. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/main.py +0 -0
  41. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/project_paths.py +0 -0
  42. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/runner.py +0 -0
  43. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/target_loader.py +0 -0
  44. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/testing/__init__.py +0 -0
  45. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/testing/pytest_plugin.py +0 -0
  46. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/testing/types.py +0 -0
  47. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/token_usage.py +0 -0
  48. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/turns.py +0 -0
  49. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli/validators.py +0 -0
  50. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli.egg-info/SOURCES.txt +0 -0
  51. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli.egg-info/dependency_links.txt +0 -0
  52. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli.egg-info/entry_points.txt +0 -0
  53. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli.egg-info/requires.txt +0 -0
  54. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/fluxloop_cli.egg-info/top_level.txt +0 -0
  55. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/setup.cfg +0 -0
  56. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/tests/test_analysis_recommendations.py +0 -0
  57. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/tests/test_arg_binder.py +0 -0
  58. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/tests/test_config_command.py +0 -0
  59. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/tests/test_conversation_supervisor.py +0 -0
  60. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/tests/test_evaluate_command.py +0 -0
  61. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/tests/test_evaluation_llm.py +0 -0
  62. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/tests/test_init_pytest_template.py +0 -0
  63. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/tests/test_input_generator.py +0 -0
  64. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/tests/test_prompt_library.py +0 -0
  65. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/tests/test_pytest_plugin.py +0 -0
  66. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/tests/test_run_command.py +0 -0
  67. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/tests/test_runner_multi_turn.py +0 -0
  68. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/tests/test_success_criteria.py +0 -0
  69. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/tests/test_target_loader.py +0 -0
  70. {fluxloop_cli-0.3.4 → fluxloop_cli-0.3.5}/tests/test_testing_types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fluxloop-cli
3
- Version: 0.3.4
3
+ Version: 0.3.5
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.3.4"
5
+ __version__ = "0.3.5"
6
6
 
7
7
  from .main import app
8
8
 
@@ -20,18 +20,41 @@ from rich.console import Console
20
20
  console = Console()
21
21
 
22
22
 
23
- def resolve_api_url(override: Optional[str]) -> str:
23
+ PRODUCTION_API_URL = "https://api.fluxloop.ai"
24
+ STAGING_API_URL = "https://staging.api.fluxloop.ai"
25
+
26
+
27
+ def _read_project_json_api_url() -> Optional[str]:
28
+ """Read api_url from .fluxloop/project.json if it exists."""
29
+ try:
30
+ from .context_manager import load_project_connection
31
+
32
+ conn = load_project_connection()
33
+ if conn and conn.api_url:
34
+ return conn.api_url
35
+ except Exception:
36
+ pass
37
+ return None
38
+
39
+
40
+ def resolve_api_url(
41
+ override: Optional[str] = None,
42
+ *,
43
+ staging: bool = False,
44
+ ) -> str:
24
45
  """
25
- Resolve API URL from override or environment variables.
46
+ Resolve API URL with a single, unified priority chain.
26
47
 
27
48
  Priority:
28
- 1. Override parameter
29
- 2. FLUXLOOP_API_URL
30
- 3. FLUXLOOP_SYNC_URL
31
- 4. Default: https://api.fluxloop.ai
49
+ 1. ``override`` parameter (--api-url)
50
+ 2. FLUXLOOP_API_URL environment variable
51
+ 3. .fluxloop/project.json ``api_url``
52
+ 4. ``--staging`` flag → staging URL
53
+ 5. Hardcoded production URL (final fallback)
32
54
 
33
55
  Args:
34
- override: Optional URL override.
56
+ override: Optional URL override (--api-url).
57
+ staging: When True, use staging URL as fallback instead of production.
35
58
 
36
59
  Returns:
37
60
  Resolved API URL (trailing slash removed).
@@ -39,8 +62,8 @@ def resolve_api_url(override: Optional[str]) -> str:
39
62
  url = (
40
63
  override
41
64
  or os.getenv("FLUXLOOP_API_URL")
42
- or os.getenv("FLUXLOOP_SYNC_URL")
43
- or "https://api.fluxloop.ai"
65
+ or _read_project_json_api_url()
66
+ or (STAGING_API_URL if staging else PRODUCTION_API_URL)
44
67
  )
45
68
  return url.rstrip("/")
46
69
 
@@ -16,6 +16,7 @@ from rich.panel import Panel
16
16
  from rich.spinner import Spinner
17
17
  from rich.live import Live
18
18
 
19
+ from ..api_utils import resolve_api_url
19
20
  from ..auth_manager import (
20
21
  start_device_code_flow,
21
22
  poll_device_code,
@@ -35,17 +36,6 @@ app = typer.Typer(help="Manage FluxLoop authentication")
35
36
  console = Console()
36
37
 
37
38
 
38
- def _resolve_api_url(override: Optional[str]) -> str:
39
- """Resolve API URL from override or environment variables."""
40
- url = (
41
- override
42
- or os.getenv("FLUXLOOP_API_URL")
43
- or os.getenv("FLUXLOOP_SYNC_URL")
44
- or "https://api.fluxloop.ai"
45
- )
46
- return url.rstrip("/")
47
-
48
-
49
39
  def _is_agent_environment() -> bool:
50
40
  """Detect if running in an agent/headless environment."""
51
41
  # Check common agent environment indicators
@@ -63,6 +53,9 @@ def login(
63
53
  api_url: Optional[str] = typer.Option(
64
54
  None, "--api-url", help="FluxLoop API base URL"
65
55
  ),
56
+ staging: bool = typer.Option(
57
+ False, "--staging", help="Use staging API (staging.api.fluxloop.ai)"
58
+ ),
66
59
  force: bool = typer.Option(
67
60
  False, "--force", "-f", help="Force re-login even if already logged in"
68
61
  ),
@@ -87,7 +80,7 @@ def login(
87
80
  unless --force is specified. This makes the command idempotent and
88
81
  safe to call from automated scripts or AI agents.
89
82
  """
90
- api_url = _resolve_api_url(api_url)
83
+ api_url = resolve_api_url(api_url, staging=staging)
91
84
 
92
85
  # Check if already logged in (idempotent behavior)
93
86
  if not force:
@@ -290,7 +283,7 @@ def status():
290
283
  If token is expired or about to expire, automatically attempts to refresh.
291
284
  Only suggests re-login if refresh fails.
292
285
  """
293
- api_url = _resolve_api_url(None)
286
+ api_url = resolve_api_url()
294
287
  token = load_auth_token()
295
288
 
296
289
  if not token:
@@ -191,8 +191,7 @@ def env(
191
191
  loaded_paths = []
192
192
 
193
193
  env_vars = [
194
- ("FLUXLOOP_SYNC_URL", "Sync API base URL", "https://api.fluxloop.ai"),
195
- ("FLUXLOOP_API_KEY", "API key for authentication", None),
194
+ ("FLUXLOOP_API_URL", "API base URL override (optional, project.json is primary)", None),
196
195
  ("FLUXLOOP_SYNC_API_KEY", "Sync API key for uploads", None),
197
196
  ("FLUXLOOP_ENABLED", "Enable/disable tracing", "true"),
198
197
  ("FLUXLOOP_DEBUG", "Enable debug mode", "false"),
@@ -327,13 +326,12 @@ def set_llm(
327
326
  @app.command()
328
327
  def set_sync_key(
329
328
  api_key: str = typer.Argument(..., help="Sync API key"),
330
- api_url: Optional[str] = typer.Option(None, "--url", help="Sync API base URL"),
331
329
  overwrite_env: bool = typer.Option(False, "--overwrite-env", help="Overwrite existing key in .env"),
332
330
  env_file: Path = typer.Option(Path(".env"), "--env-file", help="Path to environment file"),
333
331
  project: Optional[str] = typer.Option(None, "--project", help="Project name under the FluxLoop root"),
334
332
  root: Path = typer.Option(Path(DEFAULT_ROOT_DIR_NAME), "--root", help="FluxLoop root directory"),
335
333
  ):
336
- """Save Sync API credentials to .env."""
334
+ """Save Sync API key to .env."""
337
335
 
338
336
  env_path = resolve_env_path(env_file, project, root)
339
337
  env_contents: Dict[str, str] = {}
@@ -351,12 +349,9 @@ def set_sync_key(
351
349
  else:
352
350
  env_contents["FLUXLOOP_SYNC_API_KEY"] = api_key
353
351
 
354
- if api_url:
355
- env_contents["FLUXLOOP_SYNC_URL"] = api_url
356
-
357
352
  env_lines = [f"{key}={value}" for key, value in env_contents.items()]
358
353
  env_path.write_text("\n".join(env_lines) + "\n")
359
- console.print(f"[green]✓[/green] Saved sync credentials to {env_path}")
354
+ console.print(f"[green]✓[/green] Saved sync API key to {env_path}")
360
355
 
361
356
 
362
357
  @app.command()
@@ -120,6 +120,9 @@ def create_project(
120
120
  api_url: Optional[str] = typer.Option(
121
121
  None, "--api-url", help="FluxLoop API base URL"
122
122
  ),
123
+ staging: bool = typer.Option(
124
+ False, "--staging", help="Use staging API (staging.api.fluxloop.ai)"
125
+ ),
123
126
  select: bool = typer.Option(
124
127
  True, "--select/--no-select", help="Automatically select the created project"
125
128
  ),
@@ -127,7 +130,7 @@ def create_project(
127
130
  """
128
131
  Create a new Web Project.
129
132
  """
130
- api_url = resolve_api_url(api_url)
133
+ api_url = resolve_api_url(api_url, staging=staging)
131
134
 
132
135
  try:
133
136
  client = create_authenticated_client(api_url, use_jwt=True)
@@ -165,6 +168,9 @@ def select_project(
165
168
  api_url: Optional[str] = typer.Option(
166
169
  None, "--api-url", help="FluxLoop API base URL"
167
170
  ),
171
+ staging: bool = typer.Option(
172
+ False, "--staging", help="Use staging API (staging.api.fluxloop.ai)"
173
+ ),
168
174
  ):
169
175
  """
170
176
  Select a Web Project for this workspace.
@@ -176,7 +182,7 @@ def select_project(
176
182
  Use this to connect your local workspace to a Web Project before
177
183
  creating scenarios with 'fluxloop init scenario'.
178
184
  """
179
- api_url = resolve_api_url(api_url)
185
+ api_url = resolve_api_url(api_url, staging=staging)
180
186
 
181
187
  try:
182
188
  # Verify project exists by fetching it
@@ -16,6 +16,7 @@ import typer
16
16
  import yaml
17
17
  from rich.console import Console
18
18
 
19
+ from ..api_utils import resolve_api_url
19
20
  from ..config_loader import load_experiment_config
20
21
  from ..constants import DEFAULT_CONFIG_PATH, FLUXLOOP_DIR_NAME, SCENARIOS_DIR_NAME
21
22
  from ..context_manager import get_current_web_project_id, get_current_scenario
@@ -98,15 +99,6 @@ def _load_env(scenario: Optional[str], base_dir: Optional[Path] = None) -> None:
98
99
  load_env_chain(source_dir, refresh_config=False)
99
100
 
100
101
 
101
- def _resolve_api_url(override: Optional[str]) -> str:
102
- url = (
103
- override
104
- or os.getenv("FLUXLOOP_SYNC_URL")
105
- or "https://api.fluxloop.ai"
106
- )
107
- return url.rstrip("/")
108
-
109
-
110
102
  def _resolve_api_key(override: Optional[str]) -> str:
111
103
  key = (
112
104
  override
@@ -165,7 +157,7 @@ def precreate_runs(
165
157
  Precreate runs via /api/sync/upload so streaming can reference existing runs.
166
158
  """
167
159
  _load_env(scenario, base_dir)
168
- api_url = _resolve_api_url(api_url)
160
+ api_url = resolve_api_url(api_url)
169
161
  api_key = _resolve_api_key(api_key)
170
162
  scenario_root = _resolve_scenario_dir(scenario, base_dir)
171
163
  sync_state = _read_sync_state(scenario_root)
@@ -229,7 +221,7 @@ def stream_turn(
229
221
  Stream a single turn to the sync API (best-effort).
230
222
  """
231
223
  try:
232
- api_url = _resolve_api_url(api_url)
224
+ api_url = resolve_api_url(api_url)
233
225
  api_key = _resolve_api_key(api_key)
234
226
  run_id = turn.get("run_id")
235
227
  turn_id = turn.get("turn_id")
@@ -543,7 +535,7 @@ def pull(
543
535
  console.print(f"[dim]Using scenario: {scenario}[/dim]")
544
536
 
545
537
  _load_env(scenario)
546
- api_url = _resolve_api_url(api_url)
538
+ api_url = resolve_api_url(api_url)
547
539
  api_key = _resolve_api_key(api_key)
548
540
 
549
541
  if not project_id:
@@ -661,7 +653,7 @@ def upload(
661
653
  console.print(f"[dim]Using scenario: {scenario}[/dim]")
662
654
 
663
655
  _load_env(scenario)
664
- api_url = _resolve_api_url(api_url)
656
+ api_url = resolve_api_url(api_url)
665
657
  api_key = _resolve_api_key(api_key)
666
658
 
667
659
  scenario_root = _resolve_scenario_dir(scenario)
@@ -40,7 +40,7 @@ class WebProjectConnection:
40
40
  """
41
41
  project_id: str
42
42
  project_name: str
43
- api_url: str = "https://api.fluxloop.ai"
43
+ api_url: str = ""
44
44
  connected_at: Optional[str] = None
45
45
 
46
46
  def to_dict(self) -> dict:
@@ -56,7 +56,7 @@ class WebProjectConnection:
56
56
  return cls(
57
57
  project_id=data["project_id"],
58
58
  project_name=data["project_name"],
59
- api_url=data.get("api_url", "https://api.fluxloop.ai"),
59
+ api_url=data.get("api_url", ""),
60
60
  connected_at=data.get("connected_at"),
61
61
  )
62
62
 
@@ -336,7 +336,7 @@ def save_project_connection(connection: WebProjectConnection, base_dir: Optional
336
336
  def select_web_project(
337
337
  project_id: str,
338
338
  project_name: str,
339
- api_url: str = "https://api.fluxloop.ai",
339
+ api_url: str = "",
340
340
  base_dir: Optional[Path] = None
341
341
  ) -> WebProjectConnection:
342
342
  """Select a Web Project for this workspace."""
@@ -336,9 +336,8 @@ def create_env_file() -> str:
336
336
  return dedent(
337
337
  """
338
338
  # FluxLoop Configuration
339
- FLUXLOOP_SYNC_URL=https://api.fluxloop.ai
339
+ # API URL is managed via .fluxloop/project.json (set by 'fluxloop projects select')
340
340
  FLUXLOOP_SYNC_API_KEY=your-api-key-here
341
- # Legacy alias (still supported): FLUXLOOP_API_KEY
342
341
  FLUXLOOP_ENABLED=true
343
342
  FLUXLOOP_DEBUG=false
344
343
  FLUXLOOP_SAMPLE_RATE=1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fluxloop-cli
3
- Version: 0.3.4
3
+ Version: 0.3.5
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.3.4"
7
+ version = "0.3.5"
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