refactorai-cli 0.3.9__tar.gz → 0.3.12__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 (36) hide show
  1. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/PKG-INFO +2 -2
  2. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/pyproject.toml +2 -2
  3. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/__init__.py +1 -1
  4. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/cloud_rr.py +153 -16
  5. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/commands/run_cmds.py +7 -6
  6. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli.egg-info/PKG-INFO +2 -2
  7. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli.egg-info/requires.txt +1 -1
  8. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/README.md +0 -0
  9. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/auth.py +0 -0
  10. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/client.py +0 -0
  11. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/commands/__init__.py +0 -0
  12. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/commands/account_cmds.py +0 -0
  13. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/commands/auth_cmds.py +0 -0
  14. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/commands/cloud_cmds.py +0 -0
  15. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/commands/engine_cmds.py +0 -0
  16. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/commands/model_cmds.py +0 -0
  17. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/commands/rules_cmds.py +0 -0
  18. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/commands/runtime_cmds.py +0 -0
  19. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/commands/runtime_proxy_cmds.py +0 -0
  20. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/commands/setup_cmds.py +0 -0
  21. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/commands/toolchains_cmds.py +0 -0
  22. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/control_plane.py +0 -0
  23. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/credentials.py +0 -0
  24. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/local_constitution.py +0 -0
  25. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/local_engine_runtime.py +0 -0
  26. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/local_paths.py +0 -0
  27. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/main.py +0 -0
  28. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/model_policy.py +0 -0
  29. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/runtime_manager.py +0 -0
  30. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/settings.py +0 -0
  31. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli/setup_flow.py +0 -0
  32. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli.egg-info/SOURCES.txt +0 -0
  33. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli.egg-info/dependency_links.txt +0 -0
  34. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli.egg-info/entry_points.txt +0 -0
  35. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/refactorai_cli.egg-info/top_level.txt +0 -0
  36. {refactorai_cli-0.3.9 → refactorai_cli-0.3.12}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.3.9
3
+ Version: 0.3.12
4
4
  Summary: Local-first CLI for the refactor platform
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -8,7 +8,7 @@ Requires-Dist: typer>=0.12.0
8
8
  Requires-Dist: httpx>=0.27.0
9
9
  Requires-Dist: rich>=13.7.0
10
10
  Requires-Dist: PyYAML>=6.0.1
11
- Requires-Dist: refactorai-core>=3.1.0
11
+ Requires-Dist: refactorai-core>=3.1.1
12
12
 
13
13
  # refactorai-cli
14
14
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "refactorai-cli"
3
- version = "0.3.9"
3
+ version = "0.3.12"
4
4
  description = "Local-first CLI for the refactor platform"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -12,7 +12,7 @@ dependencies = [
12
12
  "httpx>=0.27.0",
13
13
  "rich>=13.7.0",
14
14
  "PyYAML>=6.0.1",
15
- "refactorai-core>=3.1.0",
15
+ "refactorai-core>=3.1.1",
16
16
  ]
17
17
 
18
18
  [project.scripts]
@@ -5,4 +5,4 @@ the shared `refactor_core` pipeline from a project folder while staying
5
5
  authenticated to the hosted platform via a developer key.
6
6
  """
7
7
 
8
- __version__ = "0.3.9"
8
+ __version__ = "0.3.12"
@@ -22,6 +22,8 @@ this design fixes. ``tests/test_cloud_rr_client_boundary.py`` enforces this.
22
22
 
23
23
  from __future__ import annotations
24
24
 
25
+ from collections import deque
26
+ import contextlib
25
27
  import hashlib
26
28
  import re
27
29
  import shlex
@@ -135,6 +137,55 @@ def _run_test(
135
137
  return False, (proc.stdout or proc.stderr or "")[-500:]
136
138
 
137
139
 
140
+ def _run_command_with_progress(
141
+ project_root: Path,
142
+ command: str,
143
+ *,
144
+ env: dict | None = None,
145
+ progress_cb: Callable[[dict], None] | None = None,
146
+ label: str = "",
147
+ timeout: int = 1800,
148
+ ) -> tuple[bool, str]:
149
+ """Run a host command and stream output lines as progress events.
150
+
151
+ Used for long-running dependency setup steps (e.g. ``npm install``) so the
152
+ CLI does not appear stuck while the process is active.
153
+ """
154
+ try:
155
+ proc = subprocess.Popen(
156
+ command,
157
+ shell=True,
158
+ cwd=str(project_root),
159
+ stdout=subprocess.PIPE,
160
+ stderr=subprocess.STDOUT,
161
+ text=True,
162
+ bufsize=1,
163
+ env=env,
164
+ )
165
+ except (subprocess.SubprocessError, OSError) as exc:
166
+ return False, f"command error: {exc}"
167
+
168
+ tail: deque[str] = deque(maxlen=30)
169
+ try:
170
+ if proc.stdout is not None:
171
+ for raw in iter(proc.stdout.readline, ""):
172
+ line = raw.rstrip()
173
+ if not line:
174
+ continue
175
+ tail.append(line)
176
+ detail = f"{label}: {line}" if label else line
177
+ _emit(progress_cb, phase="rr", event="toolchain_provisioning", detail=detail[:240])
178
+ code = proc.wait(timeout=timeout)
179
+ except subprocess.TimeoutExpired:
180
+ proc.kill()
181
+ with contextlib.suppress(Exception):
182
+ proc.wait(timeout=2)
183
+ return False, f"command timed out after {timeout}s: {command}"
184
+ if code == 0:
185
+ return True, "ok"
186
+ return False, ("\n".join(tail))[-700:]
187
+
188
+
138
189
  def _leading_binary(command: str | None) -> str | None:
139
190
  """Return the leading executable of a simple command, or None.
140
191
 
@@ -193,6 +244,21 @@ def _managed_toolchain_enabled(config: dict) -> bool:
193
244
  return True
194
245
 
195
246
 
247
+ def _detected_is_provisionable(command: str | None) -> bool:
248
+ """True when ``command``'s leading binary maps to a managed runtime.
249
+
250
+ Lets the resolver keep a detected command (e.g. ``npm test``) whose runtime
251
+ is missing from the host PATH but which the managed toolchain can download,
252
+ instead of discarding it and deferring the run.
253
+ """
254
+ from refactor_core.host_toolchain import binary_to_runtime
255
+
256
+ binary = _leading_binary(command)
257
+ if not binary:
258
+ return False
259
+ return binary_to_runtime(binary) is not None
260
+
261
+
196
262
  def _provision_toolchain(
197
263
  project_root: Path,
198
264
  test_command: str | None,
@@ -251,16 +317,72 @@ def _restore_dependencies(
251
317
  present. Failures are non-fatal: the test run surfaces the real error.
252
318
  """
253
319
  if runtime == "node" and (project_root / "package.json").is_file():
254
- _emit(progress_cb, phase="rr", event="toolchain_provisioning", detail="npm install")
255
- _run_test(project_root, "npm install", env=env)
320
+ _emit(
321
+ progress_cb,
322
+ phase="rr",
323
+ event="toolchain_provisioning",
324
+ detail="step 1/1: npm install (streaming output below)",
325
+ )
326
+ ok, detail = _run_command_with_progress(
327
+ project_root,
328
+ "npm install",
329
+ env=env,
330
+ progress_cb=progress_cb,
331
+ label="npm",
332
+ timeout=1800,
333
+ )
334
+ if not ok:
335
+ _emit(
336
+ progress_cb,
337
+ phase="rr",
338
+ event="toolchain_provisioning",
339
+ detail=f"npm install finished with errors: {detail}",
340
+ )
256
341
  elif runtime == "python":
257
342
  if (project_root / "requirements.txt").is_file():
258
- _emit(progress_cb, phase="rr", event="toolchain_provisioning",
259
- detail="pip install -r requirements.txt")
260
- _run_test(project_root, "python -m pip install -r requirements.txt", env=env)
343
+ _emit(
344
+ progress_cb,
345
+ phase="rr",
346
+ event="toolchain_provisioning",
347
+ detail="step 1/1: pip install -r requirements.txt (streaming output below)",
348
+ )
349
+ ok, detail = _run_command_with_progress(
350
+ project_root,
351
+ "python -m pip install -r requirements.txt",
352
+ env=env,
353
+ progress_cb=progress_cb,
354
+ label="pip",
355
+ timeout=1800,
356
+ )
357
+ if not ok:
358
+ _emit(
359
+ progress_cb,
360
+ phase="rr",
361
+ event="toolchain_provisioning",
362
+ detail=f"pip install finished with errors: {detail}",
363
+ )
261
364
  elif (project_root / "pyproject.toml").is_file() or (project_root / "setup.py").is_file():
262
- _emit(progress_cb, phase="rr", event="toolchain_provisioning", detail="pip install -e .")
263
- _run_test(project_root, "python -m pip install -e .", env=env)
365
+ _emit(
366
+ progress_cb,
367
+ phase="rr",
368
+ event="toolchain_provisioning",
369
+ detail="step 1/1: pip install -e . (streaming output below)",
370
+ )
371
+ ok, detail = _run_command_with_progress(
372
+ project_root,
373
+ "python -m pip install -e .",
374
+ env=env,
375
+ progress_cb=progress_cb,
376
+ label="pip",
377
+ timeout=1800,
378
+ )
379
+ if not ok:
380
+ _emit(
381
+ progress_cb,
382
+ phase="rr",
383
+ event="toolchain_provisioning",
384
+ detail=f"pip install finished with errors: {detail}",
385
+ )
264
386
 
265
387
 
266
388
  def _collect_test_manifests(
@@ -396,13 +518,22 @@ def _resolve_cloud_test_command(
396
518
  "toolchain or update verification.tests.command."
397
519
  )
398
520
 
399
- # 2) Root-marker / monorepo detection: only usable if runnable on the host.
521
+ # 2) Root-marker / monorepo detection. Usable if runnable on the host OR if
522
+ # its runtime can be provisioned by the managed toolchain (the caller then
523
+ # downloads it before the baseline). Only when neither holds do we fall
524
+ # through to server discovery, so we never lock onto a command that is
525
+ # truly un-runnable (e.g. a Tier-3 binary with no managed runtime).
400
526
  detected = resolve_test_command(project_root, config) or discover_test_command(project_root)
401
527
  if detected:
402
528
  if _client_preflight_command(project_root, detected):
403
529
  return detected, f"auto-detected test command: {detected}"
404
- # Detected but not runnable on the host -> fall through to server
405
- # discovery rather than committing to an un-runnable command.
530
+ if _managed_toolchain_enabled(config) and _detected_is_provisionable(detected):
531
+ return detected, (
532
+ f"auto-detected test command: {detected} "
533
+ "(runtime not on host PATH; will provision a managed toolchain)"
534
+ )
535
+ # Detected but neither runnable nor provisionable -> fall through to
536
+ # server discovery rather than committing to an un-runnable command.
406
537
  detected_note = (
407
538
  f"auto-detected test command '{detected}' but its toolchain was not "
408
539
  "found on the host PATH; trying server discovery."
@@ -545,6 +676,18 @@ def run_cloud_refactor_requests(
545
676
  apply_byok_payload=apply_byok_payload,
546
677
  )
547
678
 
679
+ if requests is None:
680
+ requests = pending_requests(project_root)
681
+
682
+ _emit(
683
+ progress_cb,
684
+ phase="rr",
685
+ event="run_start",
686
+ total_requests=len(requests),
687
+ test_command=test_command or "",
688
+ execution_env="cloud",
689
+ )
690
+
548
691
  # Managed host toolchain: when the resolved command's runtime is missing from
549
692
  # the host PATH, download a pinned runtime into the central store and run
550
693
  # tests with it (no container, no system changes). ``toolchain_env`` is None
@@ -557,18 +700,12 @@ def run_cloud_refactor_requests(
557
700
  progress_cb=progress_cb,
558
701
  )
559
702
 
560
- if requests is None:
561
- requests = pending_requests(project_root)
562
-
563
703
  run_store = create_run(project_root)
564
704
  run_id = run_store.run_id
565
705
  resolved_model = model_hint or str(
566
706
  constitution.get_setting("model_id", "managed-default") or "managed-default"
567
707
  )
568
708
 
569
- _emit(progress_cb, phase="rr", event="run_start", total_requests=len(requests),
570
- test_command=test_command or "", execution_env="cloud")
571
-
572
709
  outcomes: list[dict] = []
573
710
  counts = {"applied": 0, "denied": 0, "failed": 0, "deferred": 0}
574
711
  notes: list[str] = []
@@ -464,8 +464,10 @@ def _toolchain_consent_prompt(*, auto_approve: bool) -> Callable[[dict], bool]:
464
464
  """First-run consent for downloading a managed language runtime.
465
465
 
466
466
  Managed toolchains are downloaded into the central store (``~/.refactor``),
467
- never system paths, so this is low-risk; we still confirm once per run before
468
- fetching. ``--auto-approve`` and non-interactive terminals proceed silently.
467
+ never system paths. To avoid an apparent "stuck" run when Rich status
468
+ spinners are active, interactive runs default to approve after showing an
469
+ informational message (no blocking prompt). ``--auto-approve`` and
470
+ non-interactive terminals proceed silently.
469
471
  """
470
472
 
471
473
  def _ask(info: dict) -> bool:
@@ -474,11 +476,10 @@ def _toolchain_consent_prompt(*, auto_approve: bool) -> Callable[[dict], bool]:
474
476
  if auto_approve or not sys.stdin.isatty():
475
477
  return True
476
478
  console.print(
477
- f"[dim]'{binary}' was not found on your PATH. refactor can download a "
478
- f"pinned {runtime} runtime into ~/.refactor (no system changes, no container).[/dim]"
479
+ f"[dim]{binary} not found on your PATH. "
480
+ f"Refactor is downloading a pinned {runtime} runtime for testing.[/dim]"
479
481
  )
480
- raw = typer.prompt("Download managed toolchain? (Y/n)", default="Y")
481
- return raw.strip().lower() in {"", "y", "yes"}
482
+ return True
482
483
 
483
484
  return _ask
484
485
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.3.9
3
+ Version: 0.3.12
4
4
  Summary: Local-first CLI for the refactor platform
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -8,7 +8,7 @@ Requires-Dist: typer>=0.12.0
8
8
  Requires-Dist: httpx>=0.27.0
9
9
  Requires-Dist: rich>=13.7.0
10
10
  Requires-Dist: PyYAML>=6.0.1
11
- Requires-Dist: refactorai-core>=3.1.0
11
+ Requires-Dist: refactorai-core>=3.1.1
12
12
 
13
13
  # refactorai-cli
14
14
 
@@ -2,4 +2,4 @@ typer>=0.12.0
2
2
  httpx>=0.27.0
3
3
  rich>=13.7.0
4
4
  PyYAML>=6.0.1
5
- refactorai-core>=3.1.0
5
+ refactorai-core>=3.1.1