refactorai-cli 0.2.24__tar.gz → 0.2.27__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 (32) hide show
  1. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/PKG-INFO +2 -2
  2. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/README.md +1 -1
  3. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/pyproject.toml +1 -1
  4. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/__init__.py +1 -1
  5. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/commands/setup_cmds.py +21 -0
  6. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/setup_flow.py +266 -1
  7. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli.egg-info/PKG-INFO +2 -2
  8. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/auth.py +0 -0
  9. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/client.py +0 -0
  10. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/commands/__init__.py +0 -0
  11. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/commands/auth_cmds.py +0 -0
  12. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/commands/engine_cmds.py +0 -0
  13. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/commands/model_cmds.py +0 -0
  14. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/commands/rules_cmds.py +0 -0
  15. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/commands/run_cmds.py +0 -0
  16. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/commands/runtime_cmds.py +0 -0
  17. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/commands/runtime_proxy_cmds.py +0 -0
  18. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/control_plane.py +0 -0
  19. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/credentials.py +0 -0
  20. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/local_constitution.py +0 -0
  21. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/local_engine_runtime.py +0 -0
  22. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/local_paths.py +0 -0
  23. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/main.py +0 -0
  24. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/model_policy.py +0 -0
  25. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/runtime_manager.py +0 -0
  26. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli/settings.py +0 -0
  27. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli.egg-info/SOURCES.txt +0 -0
  28. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli.egg-info/dependency_links.txt +0 -0
  29. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli.egg-info/entry_points.txt +0 -0
  30. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli.egg-info/requires.txt +0 -0
  31. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/refactorai_cli.egg-info/top_level.txt +0 -0
  32. {refactorai_cli-0.2.24 → refactorai_cli-0.2.27}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.2.24
3
+ Version: 0.2.27
4
4
  Summary: Local-first CLI for the refactor platform
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -53,6 +53,6 @@ python -m twine upload ./refactorai-cli/dist/*
53
53
  ## Install test (local)
54
54
 
55
55
  ```bash
56
- python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.24-py3-none-any.whl
56
+ python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.27-py3-none-any.whl
57
57
  refactor --version
58
58
  ```
@@ -42,6 +42,6 @@ python -m twine upload ./refactorai-cli/dist/*
42
42
  ## Install test (local)
43
43
 
44
44
  ```bash
45
- python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.24-py3-none-any.whl
45
+ python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.27-py3-none-any.whl
46
46
  refactor --version
47
47
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "refactorai-cli"
3
- version = "0.2.24"
3
+ version = "0.2.27"
4
4
  description = "Local-first CLI for the refactor platform"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -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.2.24"
8
+ __version__ = "0.2.27"
@@ -18,6 +18,19 @@ def _ask_approval(auto_approve: bool, prompt: str) -> bool:
18
18
  return True
19
19
 
20
20
 
21
+ def _applehv_fallback_applied() -> bool:
22
+ """Return True when S1 precheck applied Podman applehv fallback."""
23
+ payload = get_setup_diagnostics()
24
+ stages = payload.get("stages") or {}
25
+ s1 = stages.get("S1") or {}
26
+ if not isinstance(s1, dict):
27
+ return False
28
+ output = s1.get("output") or {}
29
+ if not isinstance(output, dict):
30
+ return False
31
+ return bool(output.get("podman_applehv_fallback_applied"))
32
+
33
+
21
34
  def setup(
22
35
  yes: bool = typer.Option(False, "--yes", help="Auto-approve setup actions."),
23
36
  resume: bool = typer.Option(False, "--resume", help="Resume from last incomplete stage."),
@@ -66,5 +79,13 @@ def setup(
66
79
  console.print("[green]Setup completed successfully.[/green]")
67
80
  if result.execution_backend:
68
81
  console.print(f"[bold]backend[/bold]: {result.execution_backend}")
82
+ if result.sandbox_ready is True:
83
+ runtime = result.sandbox_runtime or "podman"
84
+ console.print(f"[bold]sandbox[/bold]: ready ({runtime})")
85
+ elif result.sandbox_ready is False:
86
+ detail = result.sandbox_reason or "Podman missing or not ready."
87
+ console.print(f"[yellow]sandbox[/yellow]: not ready ({detail})")
88
+ if _applehv_fallback_applied():
89
+ console.print("[yellow]sandbox fallback[/yellow]: applied provider=applehv")
69
90
  console.print(f"[bold]completed stages[/bold]: {', '.join(result.completed_stages)}")
70
91
  console.print("[bold]next[/bold]: run `refactor init` and then `refactor review .`")
@@ -4,6 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  import json
6
6
  import shutil
7
+ import subprocess
8
+ import sys
7
9
  from dataclasses import dataclass
8
10
  from datetime import datetime, timezone
9
11
  from pathlib import Path
@@ -55,6 +57,9 @@ class SetupResult:
55
57
  completed_stages: list[str]
56
58
  last_error: str = ""
57
59
  execution_backend: str = ""
60
+ sandbox_ready: bool | None = None
61
+ sandbox_runtime: str = ""
62
+ sandbox_reason: str = ""
58
63
 
59
64
 
60
65
  def _now_iso() -> str:
@@ -147,19 +152,267 @@ def get_setup_diagnostics() -> dict:
147
152
  return {"state": state, "stages": stages}
148
153
 
149
154
 
155
+ def _host_runtime_install_plan(runtime: str) -> list[str]:
156
+ """Return host-level install commands for a missing runtime binary."""
157
+ rt = (runtime or "").strip().lower()
158
+ if rt != "podman":
159
+ return []
160
+ if shutil.which("apt-get"):
161
+ return ["sudo apt-get update", "sudo apt-get install -y podman"]
162
+ if shutil.which("dnf"):
163
+ return ["sudo dnf install -y podman"]
164
+ if shutil.which("yum"):
165
+ return ["sudo yum install -y podman"]
166
+ if shutil.which("apk"):
167
+ return ["sudo apk add --no-cache podman"]
168
+ if shutil.which("pacman"):
169
+ return ["sudo pacman -Sy --noconfirm podman"]
170
+ if shutil.which("brew"):
171
+ plan = ["brew install podman"]
172
+ if sys.platform == "darwin":
173
+ plan.extend(
174
+ [
175
+ "podman machine inspect >/dev/null 2>&1 || podman machine init",
176
+ "podman machine start",
177
+ ]
178
+ )
179
+ return plan
180
+ return []
181
+
182
+
183
+ def _host_runtime_repair_plan(runtime: str) -> list[str]:
184
+ """Return host-level repair commands for an installed runtime."""
185
+ rt = (runtime or "").strip().lower()
186
+ if rt != "podman":
187
+ return []
188
+ if sys.platform == "darwin":
189
+ return [
190
+ "podman machine inspect >/dev/null 2>&1 || podman machine init",
191
+ "podman machine start",
192
+ ]
193
+ return []
194
+
195
+
196
+ def _is_krunkit_missing_error(detail: str) -> bool:
197
+ """Detect Podman startup failures caused by missing krunkit on macOS."""
198
+ text = str(detail or "").strip().lower()
199
+ return "krunkit" in text and ("not found" in text or "executable file" in text)
200
+
201
+
202
+ def _podman_applehv_fallback_plan() -> list[str]:
203
+ """Return recovery commands to recreate Podman machine with applehv."""
204
+ if sys.platform != "darwin":
205
+ return []
206
+ return [
207
+ "podman machine stop >/dev/null 2>&1 || true",
208
+ "podman machine rm --force >/dev/null 2>&1 || true",
209
+ "podman machine init --provider applehv",
210
+ "podman machine start",
211
+ ]
212
+
213
+
214
+ def _run_host_plan(commands: list[str], emit: Callable[[str], None] | None = None) -> tuple[bool, str]:
215
+ """Run host-level setup commands sequentially."""
216
+ for command in commands:
217
+ if emit:
218
+ emit(f"Executing host command: {command}")
219
+ proc = subprocess.run(command, shell=True, capture_output=True, text=True)
220
+ if proc.returncode != 0:
221
+ detail = (proc.stderr or proc.stdout or "").strip()[:500]
222
+ return False, f"`{command}` -> {detail or 'unknown error'}"
223
+ return True, ""
224
+
225
+
226
+ def _podman_runtime_ready(runtime: str) -> tuple[bool, str]:
227
+ """Return whether Podman is installed and ready for container operations."""
228
+ try:
229
+ proc = subprocess.run([runtime, "info"], capture_output=True, text=True)
230
+ except OSError as exc:
231
+ return False, str(exc)
232
+ if proc.returncode == 0:
233
+ return True, ""
234
+ detail = (proc.stderr or proc.stdout or "").strip()[:500]
235
+ return False, detail or "Podman runtime is installed but not ready."
236
+
237
+
238
+ def _sandbox_summary_from_precheck() -> tuple[bool | None, str, str]:
239
+ """Read S1 output and derive sandbox readiness summary for CLI output."""
240
+ path = stage_output_path("S1")
241
+ if not path.is_file():
242
+ return None, "", ""
243
+ try:
244
+ payload = json.loads(path.read_text(encoding="utf-8"))
245
+ except (json.JSONDecodeError, OSError):
246
+ return None, "", ""
247
+ output = payload.get("output")
248
+ if not isinstance(output, dict):
249
+ return None, "", ""
250
+ ready_raw = output.get("podman_ready")
251
+ available_raw = output.get("podman_available")
252
+ sandbox_ready: bool | None = None
253
+ if isinstance(ready_raw, bool):
254
+ sandbox_ready = ready_raw
255
+ elif isinstance(available_raw, bool):
256
+ sandbox_ready = available_raw
257
+ reason = str(output.get("podman_ready_reason") or output.get("podman_reason") or "").strip()
258
+ runtime = "podman" if sandbox_ready is not None else ""
259
+ return sandbox_ready, runtime, reason
260
+
261
+
150
262
  def _stage_s1_precheck(_ask_approval: Callable[[str], bool], _emit: Callable[[str], None] | None = None) -> dict:
151
263
  profile = detect_machine_profile()
152
264
  runtime, reason = resolve_engine_runtime("podman")
265
+ podman_install_plan: list[str] = []
266
+ podman_install_approved = False
267
+ podman_install_attempted = False
268
+ podman_repair_plan: list[str] = []
269
+ podman_repair_approved = False
270
+ podman_repair_attempted = False
271
+ podman_applehv_fallback_plan: list[str] = []
272
+ podman_applehv_fallback_approved = False
273
+ podman_applehv_fallback_attempted = False
274
+ podman_applehv_fallback_applied = False
275
+ podman_applehv_fallback_error = ""
153
276
  disk = shutil.disk_usage(str(refactor_home()))
154
277
  blockers: list[str] = []
155
278
  if disk.free < 2 * 1024 * 1024 * 1024:
156
279
  blockers.append("Less than 2GB free disk available in REFACTOR_HOME filesystem.")
157
280
  if blockers:
158
281
  raise SetupError("Precheck failed: " + "; ".join(blockers))
282
+
283
+ def _attempt_applehv_fallback(failure_detail: str) -> bool:
284
+ nonlocal podman_applehv_fallback_plan
285
+ nonlocal podman_applehv_fallback_approved
286
+ nonlocal podman_applehv_fallback_attempted
287
+ nonlocal podman_applehv_fallback_applied
288
+ nonlocal podman_applehv_fallback_error
289
+ if not _is_krunkit_missing_error(failure_detail):
290
+ return False
291
+ if not podman_applehv_fallback_plan:
292
+ podman_applehv_fallback_plan = _podman_applehv_fallback_plan()
293
+ if not podman_applehv_fallback_plan:
294
+ return False
295
+ if _emit:
296
+ _emit(
297
+ "Detected Podman startup failure due to missing `krunkit`. "
298
+ "Offering Apple Hypervisor fallback..."
299
+ )
300
+ podman_applehv_fallback_approved = _ask_approval(
301
+ "Podman start failed because `krunkit` is missing. "
302
+ "Recreate Podman machine with `applehv` provider now? "
303
+ "This can remove existing Podman machine state."
304
+ )
305
+ if not podman_applehv_fallback_approved:
306
+ podman_applehv_fallback_error = "fallback_not_approved"
307
+ return False
308
+ podman_applehv_fallback_attempted = True
309
+ if _emit:
310
+ _emit("Applying Podman fallback with `applehv` provider...")
311
+ ok, fallback_detail = _run_host_plan(podman_applehv_fallback_plan, emit=_emit)
312
+ if not ok:
313
+ podman_applehv_fallback_error = fallback_detail
314
+ return False
315
+ podman_applehv_fallback_applied = True
316
+ podman_applehv_fallback_error = ""
317
+ return True
318
+
319
+ def _applehv_manual_hint() -> str:
320
+ if not podman_applehv_fallback_plan:
321
+ return ""
322
+ return " To recover on macOS, run: " + "; ".join(podman_applehv_fallback_plan) + "."
323
+
324
+ if not runtime:
325
+ podman_install_plan = _host_runtime_install_plan("podman")
326
+ if podman_install_plan:
327
+ podman_install_approved = _ask_approval(
328
+ "Install Podman host runtime now so sandboxed review can run?"
329
+ )
330
+ if podman_install_approved:
331
+ podman_install_attempted = True
332
+ if _emit:
333
+ _emit("Podman was not found. Running guided host install plan...")
334
+ ok, detail = _run_host_plan(podman_install_plan, emit=_emit)
335
+ if not ok:
336
+ if _attempt_applehv_fallback(detail):
337
+ runtime, reason = resolve_engine_runtime("podman")
338
+ if not runtime:
339
+ raise SetupError(
340
+ "Apple Hypervisor fallback completed but Podman is still unavailable. "
341
+ "Verify PATH and retry (`refactor doctor --sandbox`)."
342
+ )
343
+ else:
344
+ fallback_hint = _applehv_manual_hint()
345
+ raise SetupError(
346
+ "Podman host install failed: "
347
+ f"{detail}.{fallback_hint} Install manually and retry (`refactor doctor --sandbox`)."
348
+ )
349
+ if not runtime:
350
+ runtime, reason = resolve_engine_runtime("podman")
351
+ if not runtime:
352
+ raise SetupError(
353
+ "Podman install completed but runtime is still unavailable. "
354
+ "Verify PATH and retry (`refactor doctor --sandbox`)."
355
+ )
356
+ elif _emit:
357
+ _emit("Podman is missing and no automated install plan is available on this host.")
358
+
359
+ podman_ready = False
360
+ podman_ready_reason = reason
361
+ if runtime:
362
+ podman_ready, podman_ready_reason = _podman_runtime_ready(str(runtime))
363
+ if not podman_ready:
364
+ podman_repair_plan = _host_runtime_repair_plan(str(runtime))
365
+ if podman_repair_plan:
366
+ podman_repair_approved = _ask_approval(
367
+ "Podman is installed but not ready. Initialize/start Podman machine now?"
368
+ )
369
+ if podman_repair_approved:
370
+ podman_repair_attempted = True
371
+ if _emit:
372
+ _emit("Podman is installed but not ready. Running repair plan...")
373
+ ok, detail = _run_host_plan(podman_repair_plan, emit=_emit)
374
+ if not ok:
375
+ if not _attempt_applehv_fallback(detail):
376
+ raise SetupError(
377
+ "Podman runtime repair failed: "
378
+ f"{detail}.{_applehv_manual_hint()} Run `podman machine start` and retry."
379
+ )
380
+ podman_ready, podman_ready_reason = _podman_runtime_ready(str(runtime))
381
+ if not podman_ready:
382
+ raise SetupError(
383
+ "Podman is installed but still not ready after repair: "
384
+ f"{podman_ready_reason}"
385
+ )
386
+ if _emit:
387
+ if runtime and podman_ready:
388
+ _emit("Sandbox runtime precheck: Podman is ready.")
389
+ elif runtime:
390
+ _emit(
391
+ "Sandbox runtime precheck: Podman is installed but not ready. "
392
+ "Setup can continue, but `refactor review` may fail until Podman is started."
393
+ )
394
+ else:
395
+ _emit(
396
+ "Sandbox runtime precheck: Podman is not installed. "
397
+ "Setup can continue, but sandboxed review requires Podman."
398
+ )
159
399
  return {
160
400
  "profile": profile,
161
401
  "podman_available": bool(runtime),
162
402
  "podman_reason": reason,
403
+ "podman_ready": bool(runtime and podman_ready),
404
+ "podman_ready_reason": podman_ready_reason,
405
+ "podman_install_plan": podman_install_plan,
406
+ "podman_install_approved": podman_install_approved,
407
+ "podman_install_attempted": podman_install_attempted,
408
+ "podman_repair_plan": podman_repair_plan,
409
+ "podman_repair_approved": podman_repair_approved,
410
+ "podman_repair_attempted": podman_repair_attempted,
411
+ "podman_applehv_fallback_plan": podman_applehv_fallback_plan,
412
+ "podman_applehv_fallback_approved": podman_applehv_fallback_approved,
413
+ "podman_applehv_fallback_attempted": podman_applehv_fallback_attempted,
414
+ "podman_applehv_fallback_applied": podman_applehv_fallback_applied,
415
+ "podman_applehv_fallback_error": podman_applehv_fallback_error,
163
416
  "disk_free_mb": int(disk.free / (1024 * 1024)),
164
417
  }
165
418
 
@@ -399,7 +652,15 @@ def run_setup(
399
652
  elif resume:
400
653
  pending = [stage for stage in STAGE_IDS if stage not in completed]
401
654
  if not pending:
402
- return SetupResult(status="already_completed", completed_stages=completed, last_error="")
655
+ sandbox_ready, sandbox_runtime, sandbox_reason = _sandbox_summary_from_precheck()
656
+ return SetupResult(
657
+ status="already_completed",
658
+ completed_stages=completed,
659
+ last_error="",
660
+ sandbox_ready=sandbox_ready,
661
+ sandbox_runtime=sandbox_runtime,
662
+ sandbox_reason=sandbox_reason,
663
+ )
403
664
  start_index = STAGE_IDS.index(pending[0])
404
665
  else:
405
666
  start_index = 0
@@ -478,9 +739,13 @@ def run_setup(
478
739
  "last_error": "",
479
740
  }
480
741
  )
742
+ sandbox_ready, sandbox_runtime, sandbox_reason = _sandbox_summary_from_precheck()
481
743
  return SetupResult(
482
744
  status="completed",
483
745
  completed_stages=completed,
484
746
  last_error="",
485
747
  execution_backend=final_backend,
748
+ sandbox_ready=sandbox_ready,
749
+ sandbox_runtime=sandbox_runtime,
750
+ sandbox_reason=sandbox_reason,
486
751
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.2.24
3
+ Version: 0.2.27
4
4
  Summary: Local-first CLI for the refactor platform
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -53,6 +53,6 @@ python -m twine upload ./refactorai-cli/dist/*
53
53
  ## Install test (local)
54
54
 
55
55
  ```bash
56
- python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.24-py3-none-any.whl
56
+ python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.27-py3-none-any.whl
57
57
  refactor --version
58
58
  ```