refactorai-cli 0.2.29__tar.gz → 0.3.0__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 (33) hide show
  1. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/PKG-INFO +15 -8
  2. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/README.md +13 -7
  3. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/pyproject.toml +5 -1
  4. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/__init__.py +1 -1
  5. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/cloud_cmds.py +2 -1
  6. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/run_cmds.py +215 -87
  7. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/runtime_proxy_cmds.py +2 -2
  8. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli.egg-info/PKG-INFO +15 -8
  9. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli.egg-info/requires.txt +1 -0
  10. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/auth.py +0 -0
  11. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/client.py +0 -0
  12. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/__init__.py +0 -0
  13. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/auth_cmds.py +0 -0
  14. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/engine_cmds.py +0 -0
  15. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/model_cmds.py +0 -0
  16. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/rules_cmds.py +0 -0
  17. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/runtime_cmds.py +0 -0
  18. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/setup_cmds.py +0 -0
  19. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/control_plane.py +0 -0
  20. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/credentials.py +0 -0
  21. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/local_constitution.py +0 -0
  22. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/local_engine_runtime.py +0 -0
  23. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/local_paths.py +0 -0
  24. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/main.py +0 -0
  25. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/model_policy.py +0 -0
  26. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/runtime_manager.py +0 -0
  27. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/settings.py +0 -0
  28. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/setup_flow.py +0 -0
  29. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli.egg-info/SOURCES.txt +0 -0
  30. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli.egg-info/dependency_links.txt +0 -0
  31. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli.egg-info/entry_points.txt +0 -0
  32. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli.egg-info/top_level.txt +0 -0
  33. {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.2.29
3
+ Version: 0.3.0
4
4
  Summary: Local-first CLI for the refactor platform
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -8,6 +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.0.0
11
12
 
12
13
  # refactorai-cli
13
14
 
@@ -53,28 +54,34 @@ python -m twine upload ./refactorai-cli/dist/*
53
54
  ## Install test (local)
54
55
 
55
56
  ```bash
56
- python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.29-py3-none-any.whl
57
+ python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.0-py3-none-any.whl
57
58
  refactor --version
58
59
  ```
59
60
 
60
61
  ## Cloud BYOK setup (bring your own provider key)
61
62
 
62
63
  Run cloud inference with your own provider credentials (no local runtime
63
- required). The secret is read from your environment and encrypted server-side;
64
- it is never written to disk or printed.
64
+ required). Preferred path is env-key in project config (`provider_key:
65
+ ${ENV_VAR}`). Platform-stored credentials (`credential_ref`) are optional
66
+ fallback.
65
67
 
66
68
  ```bash
67
69
  # 1. Authenticate (developer key with the run:byok entitlement).
68
70
  refactor login
69
71
 
70
- # 2. Register your provider secret from an env var.
72
+ # 2A. Preferred: set BYOK directly from project env in refactor.config:
73
+ #
74
+ # mode: cloud_byok
75
+ # provider: openai
76
+ # model_id: gpt-4.1-mini
77
+ # provider_key: ${OPENAI_API_KEY}
78
+ #
79
+ # 2B. Optional fallback: register a platform-stored credential from env.
71
80
  export OPENAI_API_KEY=sk-...
72
81
  refactor cloud credentials set --provider openai --from-env OPENAI_API_KEY --label "my key"
73
82
  # -> prints a credential_ref, e.g. cred_01J...
74
83
 
75
- # 3. Point your project at cloud_byok in refactor.config:
76
- # mode: cloud_byok
77
- # provider: openai
84
+ # 3. If you use platform-stored fallback, set credential_ref in config:
78
85
  # credential_ref: cred_01J...
79
86
 
80
87
  # 4. Verify readiness, then run.
@@ -42,28 +42,34 @@ 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.29-py3-none-any.whl
45
+ python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.0-py3-none-any.whl
46
46
  refactor --version
47
47
  ```
48
48
 
49
49
  ## Cloud BYOK setup (bring your own provider key)
50
50
 
51
51
  Run cloud inference with your own provider credentials (no local runtime
52
- required). The secret is read from your environment and encrypted server-side;
53
- it is never written to disk or printed.
52
+ required). Preferred path is env-key in project config (`provider_key:
53
+ ${ENV_VAR}`). Platform-stored credentials (`credential_ref`) are optional
54
+ fallback.
54
55
 
55
56
  ```bash
56
57
  # 1. Authenticate (developer key with the run:byok entitlement).
57
58
  refactor login
58
59
 
59
- # 2. Register your provider secret from an env var.
60
+ # 2A. Preferred: set BYOK directly from project env in refactor.config:
61
+ #
62
+ # mode: cloud_byok
63
+ # provider: openai
64
+ # model_id: gpt-4.1-mini
65
+ # provider_key: ${OPENAI_API_KEY}
66
+ #
67
+ # 2B. Optional fallback: register a platform-stored credential from env.
60
68
  export OPENAI_API_KEY=sk-...
61
69
  refactor cloud credentials set --provider openai --from-env OPENAI_API_KEY --label "my key"
62
70
  # -> prints a credential_ref, e.g. cred_01J...
63
71
 
64
- # 3. Point your project at cloud_byok in refactor.config:
65
- # mode: cloud_byok
66
- # provider: openai
72
+ # 3. If you use platform-stored fallback, set credential_ref in config:
67
73
  # credential_ref: cred_01J...
68
74
 
69
75
  # 4. Verify readiness, then run.
@@ -1,14 +1,18 @@
1
1
  [project]
2
2
  name = "refactorai-cli"
3
- version = "0.2.29"
3
+ version = "0.3.0"
4
4
  description = "Local-first CLI for the refactor platform"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
7
+ # R21 core split: the CLI depends on the PUBLIC client core only. The proprietary
8
+ # engine (refactor-core-engine) is never installed on a developer's machine via
9
+ # pip; local mode runs the engine from the downloaded runtime binary instead.
7
10
  dependencies = [
8
11
  "typer>=0.12.0",
9
12
  "httpx>=0.27.0",
10
13
  "rich>=13.7.0",
11
14
  "PyYAML>=6.0.1",
15
+ "refactorai-core>=3.0.0",
12
16
  ]
13
17
 
14
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.2.29"
8
+ __version__ = "0.3.0"
@@ -114,7 +114,8 @@ def credentials_set(
114
114
  f"[green]Credential registered[/green] for provider [bold]{data.get('provider', provider)}[/bold].\n"
115
115
  f" credential_ref: [bold]{data.get('credential_ref', '?')}[/bold]\n"
116
116
  f" masked_secret: {data.get('masked_secret', '****')}\n"
117
- "Add the credential_ref to refactor.config under `credential_ref:` to use cloud_byok."
117
+ "Use this as an optional cloud_byok fallback (`credential_ref:`). "
118
+ "Preferred path is env-key in project config (`provider_key: ${ENV_VAR}`)."
118
119
  )
119
120
 
120
121
 
@@ -52,9 +52,6 @@ from refactor_core.apply import (
52
52
  )
53
53
  from refactor_core.budgeting import compute_budget
54
54
  from refactor_core.capabilities import resolve_capabilities
55
- from refactor_core.execution import resolve_mode, run_with_mode
56
- from refactor_core.execution.rr_executor import run_refactor_requests
57
- from refactor_core.execution.structural_ops import structural_compatibility_matrix
58
55
  from refactor_core.gate import evaluate_gate
59
56
  from refactor_core.refactor_requests import (
60
57
  DEFERRED,
@@ -70,30 +67,10 @@ from refactor_core.refactor_requests import (
70
67
  from refactor_core.refactor_ops import remediation_hint
71
68
  from refactor_core.indexing import index_target
72
69
  from refactor_core.models import Finding, GeneratedTest, PatchGroup, RunArtifact
73
- from refactor_core.pipeline import build_refactor_prompt, build_review_prompt
74
- from refactor_core.providers import get_provider, resolve_provider_name, supported_providers
75
70
  from refactor_core.rules import annotate_documents_with_rules, filter_documents_by_rules
76
71
  from refactor_core.security import merge_refactor_findings, suppress_findings_for_rr
77
72
  from refactor_core.security.semgrep_runner import EMBEDDED_SEMGREP_SENTINEL
78
73
  from refactor_core.security.suppression import SecuritySuppression
79
- from refactor_core.sandbox_runtime import (
80
- DEFAULT_IMAGE as SANDBOX_DEFAULT_IMAGE,
81
- DEFAULT_SHELL as SANDBOX_DEFAULT_SHELL,
82
- attach_shell as attach_sandbox_shell,
83
- command_exists_in_container,
84
- command_exists_in_sandbox,
85
- detect_installer_in_container,
86
- detect_installer_in_sandbox,
87
- ensure_sandbox,
88
- exec_in_container,
89
- exec_in_container_stream,
90
- exec_in_sandbox,
91
- exec_in_sandbox_stream,
92
- remove_sandbox_container,
93
- sandbox_name,
94
- resolve_runtime,
95
- stop_sandbox,
96
- )
97
74
  from refactor_core.store import (
98
75
  append_sandbox_install_history,
99
76
  clear_sandbox_state,
@@ -125,6 +102,27 @@ from refactorai_cli.setup_flow import (
125
102
 
126
103
  console = Console()
127
104
 
105
+ # Local mirrors of engine-only sandbox defaults (R21 core split). These are used
106
+ # as Typer option defaults, which are evaluated at import time, so they cannot be
107
+ # lazy-imported from the engine-only ``refactor_core.enginecore.sandbox_runtime`` module.
108
+ # They mirror ``sandbox_runtime.DEFAULT_IMAGE`` / ``DEFAULT_SHELL`` and are only
109
+ # meaningful in local mode (where the engine is present via the runtime binary).
110
+ SANDBOX_DEFAULT_IMAGE = "docker.io/library/alpine:3.20"
111
+ SANDBOX_DEFAULT_SHELL = "/bin/sh"
112
+
113
+
114
+ def _sandbox_runtime():
115
+ """Lazily import the engine-only sandbox runtime module (local mode only).
116
+
117
+ Cloud modes never touch the sandbox; this import is therefore reached only on
118
+ the local execution path, where the engine is present (inside the runtime
119
+ binary). A client-only install that never runs local commands never imports
120
+ it, keeping engine code off the developer's disk (R21 core split).
121
+ """
122
+ from refactor_core.enginecore import sandbox_runtime as _sr
123
+
124
+ return _sr
125
+
128
126
 
129
127
  def _read_stage_output(stage_id: str) -> dict:
130
128
  path = stage_output_path(stage_id)
@@ -167,7 +165,14 @@ def _default_config_for_init() -> tuple[str, str]:
167
165
  )
168
166
  return config_text, "local_server"
169
167
  if backend == BACKEND_BYOK:
170
- return DEFAULT_CONFIG, "byok"
168
+ config_text = DEFAULT_CONFIG.replace("mode: local", "mode: cloud_byok", 1)
169
+ config_text = config_text.replace("provider: heuristic", "provider: openai", 1)
170
+ config_text = config_text.replace(
171
+ "model_id: heuristic-local-v1",
172
+ "model_id: gpt-4.1-mini\nprovider_key: ${OPENAI_API_KEY}\n# credential_ref: cred_01J...",
173
+ 1,
174
+ )
175
+ return config_text, "byok"
171
176
  return DEFAULT_CONFIG, "default"
172
177
 
173
178
  _SEVERITY_STYLE = {
@@ -631,14 +636,14 @@ def _cloud_inference(
631
636
  documents, _scope_meta = filter_documents_by_rules(documents, project_root=Path(project_root))
632
637
  rule_meta = annotate_documents_with_rules(documents, project_root=Path(project_root))
633
638
 
634
- if stage == "review":
635
- system_prompt, user_prompt = build_review_prompt(constitution, documents, max_alerts)
636
- else:
637
- # Feed prior review findings into the refactor prompt so cloud `code`
638
- # generates patches targeting the same issues (parity with local RR).
639
- system_prompt, user_prompt = build_refactor_prompt(
640
- constitution, documents, list(findings or []), max_alerts
641
- )
639
+ # Server-side prompt building (R21): send raw indexed documents + the
640
+ # constitution body and let the platform build the prompts. The proprietary
641
+ # prompt engineering stays off the developer's machine. Prior review findings
642
+ # are forwarded for the refactor stage so cloud `code` targets the same
643
+ # issues (parity with local RR).
644
+ request_findings = [
645
+ f.model_dump() if hasattr(f, "model_dump") else dict(f) for f in (findings or [])
646
+ ]
642
647
 
643
648
  execution_mode = _cloud_execution_mode(constitution) or "cloud_managed"
644
649
  auth_ctx = ensure_authenticated(project_root)
@@ -646,23 +651,37 @@ def _cloud_inference(
646
651
  "stage": stage,
647
652
  "execution_mode": execution_mode,
648
653
  "constitution_hash": constitution.content_hash,
649
- "system_prompt": system_prompt,
650
- "user_prompt": user_prompt,
654
+ "documents": documents,
655
+ "constitution_body": getattr(constitution, "body", "") or "",
656
+ "findings": request_findings if stage != "review" else [],
651
657
  "max_alerts": max_alerts,
652
658
  "model_hint": model_hint or str(constitution.get_setting("model_id", "managed-default") or "managed-default"),
653
659
  }
654
660
 
655
661
  if execution_mode == "cloud_byok":
656
- credential_ref = str(constitution.get_setting("credential_ref", "") or "").strip()
657
662
  provider_name = str(constitution.get_setting("provider", "") or "").strip()
658
- if not credential_ref:
663
+ provider_key = str(constitution.get_setting("provider_key", "") or "").strip()
664
+ credential_ref = str(constitution.get_setting("credential_ref", "") or "").strip()
665
+
666
+ # Env-key-first BYOK path: when provider_key is configured (typically via
667
+ # ${ENV_VAR} interpolation), prefer it over credential_ref.
668
+ if provider_key:
669
+ if not provider_name or provider_name.lower() == "cloud":
670
+ raise RuntimeError(
671
+ "cloud_byok with provider_key requires an explicit provider "
672
+ "(e.g. provider: openai)."
673
+ )
674
+ payload["provider_key"] = provider_key
675
+ payload["provider"] = provider_name
676
+ elif credential_ref:
677
+ payload["credential_ref"] = credential_ref
678
+ if provider_name and provider_name.lower() != "cloud":
679
+ payload["provider"] = provider_name
680
+ else:
659
681
  raise RuntimeError(
660
- "cloud_byok requires a `credential_ref` in refactor.config. "
661
- "Register one with `refactor cloud credentials set` and copy its ref."
682
+ "cloud_byok requires either `provider_key` (preferred env-key path) "
683
+ "or `credential_ref` (platform-stored credential path) in refactor.config."
662
684
  )
663
- payload["credential_ref"] = credential_ref
664
- if provider_name and provider_name.lower() != "cloud":
665
- payload["provider"] = provider_name
666
685
 
667
686
  idempotency_key = _stable_idempotency_key(
668
687
  account_id=auth_ctx.account_id,
@@ -911,7 +930,7 @@ def init(
911
930
  elif config_profile == "byok":
912
931
  console.print(
913
932
  "[green]Configured[/green] refactor.config for BYOK setup "
914
- "(provider=heuristic; uncomment a provider block to use your key)."
933
+ "(mode=cloud_byok, env-key-first via provider_key; credential_ref is optional fallback)."
915
934
  )
916
935
  console.print(
917
936
  f"[green]Registered[/green] project in central store: {project_store_dir(project_root)}"
@@ -1166,6 +1185,15 @@ def _sandbox_preflight(
1166
1185
  workdir: str | None = None,
1167
1186
  ) -> tuple[bool, str]:
1168
1187
  """Run sandbox preflight and optionally perform approved install plan."""
1188
+ _sr = _sandbox_runtime()
1189
+ command_exists_in_container = _sr.command_exists_in_container
1190
+ command_exists_in_sandbox = _sr.command_exists_in_sandbox
1191
+ exec_in_container = _sr.exec_in_container
1192
+ exec_in_container_stream = _sr.exec_in_container_stream
1193
+ exec_in_sandbox = _sr.exec_in_sandbox
1194
+ exec_in_sandbox_stream = _sr.exec_in_sandbox_stream
1195
+ detect_installer_in_container = _sr.detect_installer_in_container
1196
+ detect_installer_in_sandbox = _sr.detect_installer_in_sandbox
1169
1197
 
1170
1198
  has_container_ctx = bool(runtime and container_name)
1171
1199
  resolved_workdir = str(workdir or "/workspace")
@@ -1465,6 +1493,10 @@ def _ensure_sandbox_ready(
1465
1493
  continue_on_preflight_failure: bool = False,
1466
1494
  ) -> dict | None:
1467
1495
  """Ensure sandbox lifecycle + preflight for stage execution."""
1496
+ _sr = _sandbox_runtime()
1497
+ resolve_runtime = _sr.resolve_runtime
1498
+ sandbox_name = _sr.sandbox_name
1499
+ ensure_sandbox = _sr.ensure_sandbox
1468
1500
  sandbox_cfg = _sandbox_settings(project_config)
1469
1501
  lifecycle = _sandbox_lifecycle(project_config)
1470
1502
  ephemeral = lifecycle == "per_run" and stage in {"review", "code"}
@@ -1533,6 +1565,9 @@ def _teardown_sandbox_context(project_root: Path, ctx: dict | None) -> None:
1533
1565
  """Clean up ephemeral per-run sandboxes."""
1534
1566
  if not ctx or not ctx.get("ephemeral"):
1535
1567
  return
1568
+ _sr = _sandbox_runtime()
1569
+ remove_sandbox_container = _sr.remove_sandbox_container
1570
+ sandbox_name = _sr.sandbox_name
1536
1571
  runtime = str(ctx.get("runtime") or "")
1537
1572
  name = str(ctx.get("container_name") or "")
1538
1573
  ok, msg = remove_sandbox_container(runtime=runtime, container_name=name)
@@ -1567,6 +1602,10 @@ def start(
1567
1602
  project_root, _constitution, project_config = _load_project()
1568
1603
  register_project(project_root)
1569
1604
 
1605
+ _sr = _sandbox_runtime()
1606
+ resolve_runtime = _sr.resolve_runtime
1607
+ ensure_sandbox = _sr.ensure_sandbox
1608
+ attach_sandbox_shell = _sr.attach_shell
1570
1609
  sandbox_cfg = _sandbox_settings(project_config)
1571
1610
  preferred_runtime = str(runtime or sandbox_cfg.get("runtime", "podman") or "podman")
1572
1611
  resolved_runtime, reason = resolve_runtime(preferred_runtime)
@@ -1642,7 +1681,7 @@ def stop() -> None:
1642
1681
  "Switch to a valid directory and retry."
1643
1682
  )
1644
1683
  raise typer.Exit(code=1) from None
1645
- ok, message = stop_sandbox(project_root=project_root)
1684
+ ok, message = _sandbox_runtime().stop_sandbox(project_root=project_root)
1646
1685
  if ok:
1647
1686
  console.print(f"[green]{message}[/green]")
1648
1687
  return
@@ -1665,7 +1704,7 @@ def shell(
1665
1704
  if not sys.stdin.isatty():
1666
1705
  console.print("[yellow]Non-interactive terminal; cannot attach shell.[/yellow]")
1667
1706
  raise typer.Exit(code=1)
1668
- ok, message = attach_sandbox_shell(project_root=project_root, shell_path=shell_path)
1707
+ ok, message = _sandbox_runtime().attach_shell(project_root=project_root, shell_path=shell_path)
1669
1708
  if ok:
1670
1709
  return
1671
1710
  console.print(f"[red]Sandbox shell failed:[/red] {message}")
@@ -1777,6 +1816,9 @@ def review(
1777
1816
  model_hint=provider,
1778
1817
  )
1779
1818
  else:
1819
+ from refactor_core.execution.orchestrator import run_with_mode
1820
+ from refactor_core.providers import get_provider
1821
+
1780
1822
  selected_provider = get_provider(
1781
1823
  constitution, project_root=project_root, provider_name=provider
1782
1824
  )
@@ -1993,6 +2035,8 @@ def code(
1993
2035
  )
1994
2036
  selected_provider = None
1995
2037
  if not cloud:
2038
+ from refactor_core.providers import get_provider
2039
+
1996
2040
  selected_provider = get_provider(
1997
2041
  constitution, project_root=project_root, provider_name=provider
1998
2042
  )
@@ -2015,6 +2059,8 @@ def code(
2015
2059
  )
2016
2060
  cloud_review_findings = list(review_artifact.findings or [])
2017
2061
  else:
2062
+ from refactor_core.execution.orchestrator import run_with_mode
2063
+
2018
2064
  with console.status("[cyan]review[/cyan] analyzing...", spinner="dots") as status:
2019
2065
  review_progress = _agent_progress_logger(
2020
2066
  "review", status=status, verbose_steps=True
@@ -2138,6 +2184,8 @@ def code(
2138
2184
  )
2139
2185
  return
2140
2186
 
2187
+ from refactor_core.execution.rr_executor import run_refactor_requests
2188
+
2141
2189
  with console.status("[cyan]code[/cyan] running...", spinner="dots") as status:
2142
2190
  rr_config = (
2143
2191
  dict(getattr(project_config, "settings", {}) or {})
@@ -2214,6 +2262,9 @@ def requests(
2214
2262
  if not deferred_rrs:
2215
2263
  console.print("[dim]No deferred requests to retry.[/dim]")
2216
2264
  else:
2265
+ from refactor_core.execution.rr_executor import run_refactor_requests
2266
+ from refactor_core.providers import get_provider
2267
+
2217
2268
  selected_provider = get_provider(
2218
2269
  constitution, project_root=project_root, provider_name=provider
2219
2270
  )
@@ -3065,60 +3116,123 @@ def _cloud_doctor(
3065
3116
  console.print("[dim]Run `refactor login` or set REFACTOR_API_KEY.[/dim]")
3066
3117
  raise typer.Exit(code=1) from exc
3067
3118
 
3068
- # BYOK requires a credential_ref in config plus a matching server-side
3069
- # credential owned by this account.
3119
+ # BYOK supports two secret sources:
3120
+ # 1) provider_key (preferred env-key path), or
3121
+ # 2) credential_ref (platform-stored secret fallback).
3070
3122
  credential_ref = ""
3123
+ provider_key = ""
3124
+ provider_name = ""
3071
3125
  if execution_mode == "cloud_byok":
3126
+ provider_name = str(constitution.get_setting("provider", "") or "").strip()
3127
+ provider_key = str(constitution.get_setting("provider_key", "") or "").strip()
3072
3128
  credential_ref = str(constitution.get_setting("credential_ref", "") or "").strip()
3073
- if not credential_ref:
3129
+ if not provider_name or provider_name.lower() == "cloud":
3074
3130
  console.print(
3075
- "[red]cloud_byok[/red]: no `credential_ref` in refactor.config. "
3076
- "Register one with `refactor cloud credentials set` and add its ref."
3131
+ "[red]cloud_byok[/red]: set an explicit `provider` "
3132
+ "(for example `provider: openai`)."
3133
+ )
3134
+ raise typer.Exit(code=1)
3135
+ console.print(f"[bold]provider[/bold]: {provider_name}")
3136
+ if provider_key:
3137
+ console.print(
3138
+ "[green]provider_key[/green]: set via config/env interpolation "
3139
+ "(preferred BYOK path)."
3140
+ )
3141
+ elif credential_ref:
3142
+ console.print(f"[green]credential_ref[/green]: {credential_ref}")
3143
+ else:
3144
+ console.print(
3145
+ "[red]cloud_byok[/red]: set either `provider_key` (preferred) "
3146
+ "or `credential_ref` in refactor.config."
3077
3147
  )
3078
3148
  raise typer.Exit(code=1)
3079
- console.print(f"[green]credential_ref[/green]: {credential_ref}")
3080
- provider_name = str(constitution.get_setting("provider", "") or "").strip()
3081
- if provider_name and provider_name.lower() != "cloud":
3082
- console.print(f"[bold]provider[/bold]: {provider_name}")
3083
3149
 
3084
3150
  if skip_ping:
3085
3151
  console.print("[dim]Skipping cloud endpoint reachability check (--skip-ping).[/dim]")
3086
3152
  return
3087
3153
 
3088
- # Verify the BYOK credential exists and is owned by this account, and that
3089
- # the credential endpoint is reachable.
3154
+ # Verify BYOK readiness and endpoint reachability.
3090
3155
  if execution_mode == "cloud_byok":
3091
- try:
3092
- response = httpx.get(
3093
- f"{platform_url()}/v1/inference/credentials",
3094
- headers={"Authorization": f"Bearer {auth_ctx.key.key}"},
3095
- timeout=20.0,
3096
- )
3097
- except httpx.HTTPError as exc:
3098
- console.print(f"[red]cloud endpoint[/red]: unreachable ({exc}).")
3099
- raise typer.Exit(code=1) from exc
3100
- if response.status_code == 402:
3101
- console.print(
3102
- "[red]entitlement[/red]: BYOK not enabled for this account (missing `run:byok`). "
3103
- "Contact your admin to enable BYOK cloud execution."
3104
- )
3105
- raise typer.Exit(code=1)
3106
- if response.status_code == 401:
3107
- console.print("[red]developer key[/red]: unauthorized (invalid or revoked). Run `refactor login`.")
3108
- raise typer.Exit(code=1)
3109
- if response.status_code >= 400:
3110
- console.print(f"[red]cloud endpoint[/red]: credential check failed ({response.status_code}).")
3111
- raise typer.Exit(code=1)
3112
- data = response.json() if response.content else {}
3113
- refs = {str(row.get("credential_ref", "")) for row in (data.get("credentials", []) or [])}
3114
- if credential_ref not in refs:
3115
- console.print(
3116
- f"[red]credential[/red]: `{credential_ref}` not found for this account. "
3117
- "Check `refactor cloud credentials list`."
3118
- )
3119
- raise typer.Exit(code=1)
3120
- console.print("[green]credential[/green]: found and owned by this account.")
3121
- console.print("[green]cloud endpoint[/green]: reachable.")
3156
+ if provider_key:
3157
+ # Env-key path: perform a lightweight inference ping so entitlement +
3158
+ # provider auth are validated together.
3159
+ try:
3160
+ response = httpx.post(
3161
+ f"{platform_url()}/v1/inference/refactor",
3162
+ headers={
3163
+ "Authorization": f"Bearer {auth_ctx.key.key}",
3164
+ "Content-Type": "application/json",
3165
+ "Idempotency-Key": f"doctor-cloud-byok-{uuid.uuid4().hex}",
3166
+ },
3167
+ json={
3168
+ "stage": "review",
3169
+ "execution_mode": "cloud_byok",
3170
+ "provider": provider_name,
3171
+ "provider_key": provider_key,
3172
+ "constitution_hash": "doctor",
3173
+ "system_prompt": "doctor ping",
3174
+ "user_prompt": "doctor ping",
3175
+ "max_alerts": 1,
3176
+ },
3177
+ timeout=20.0,
3178
+ )
3179
+ except httpx.HTTPError as exc:
3180
+ console.print(f"[red]cloud endpoint[/red]: unreachable ({exc}).")
3181
+ raise typer.Exit(code=1) from exc
3182
+ if response.status_code == 402:
3183
+ console.print(
3184
+ "[red]entitlement[/red]: BYOK not enabled for this account (missing `run:byok`). "
3185
+ "Contact your admin to enable BYOK cloud execution."
3186
+ )
3187
+ raise typer.Exit(code=1)
3188
+ if response.status_code == 401:
3189
+ console.print("[red]developer key[/red]: unauthorized (invalid or revoked). Run `refactor login`.")
3190
+ raise typer.Exit(code=1)
3191
+ if response.status_code >= 400:
3192
+ detail = ""
3193
+ try:
3194
+ detail = str((response.json() or {}).get("detail", ""))
3195
+ except Exception:
3196
+ detail = response.text[:200]
3197
+ console.print(
3198
+ f"[red]cloud BYOK ping failed[/red] ({response.status_code})"
3199
+ + (f": {detail}" if detail else ".")
3200
+ )
3201
+ raise typer.Exit(code=1)
3202
+ console.print("[green]cloud endpoint[/green]: reachable.")
3203
+ console.print("[green]BYOK provider auth[/green]: OK")
3204
+ else:
3205
+ try:
3206
+ response = httpx.get(
3207
+ f"{platform_url()}/v1/inference/credentials",
3208
+ headers={"Authorization": f"Bearer {auth_ctx.key.key}"},
3209
+ timeout=20.0,
3210
+ )
3211
+ except httpx.HTTPError as exc:
3212
+ console.print(f"[red]cloud endpoint[/red]: unreachable ({exc}).")
3213
+ raise typer.Exit(code=1) from exc
3214
+ if response.status_code == 402:
3215
+ console.print(
3216
+ "[red]entitlement[/red]: BYOK not enabled for this account (missing `run:byok`). "
3217
+ "Contact your admin to enable BYOK cloud execution."
3218
+ )
3219
+ raise typer.Exit(code=1)
3220
+ if response.status_code == 401:
3221
+ console.print("[red]developer key[/red]: unauthorized (invalid or revoked). Run `refactor login`.")
3222
+ raise typer.Exit(code=1)
3223
+ if response.status_code >= 400:
3224
+ console.print(f"[red]cloud endpoint[/red]: credential check failed ({response.status_code}).")
3225
+ raise typer.Exit(code=1)
3226
+ data = response.json() if response.content else {}
3227
+ refs = {str(row.get("credential_ref", "")) for row in (data.get("credentials", []) or [])}
3228
+ if credential_ref not in refs:
3229
+ console.print(
3230
+ f"[red]credential[/red]: `{credential_ref}` not found for this account. "
3231
+ "Check `refactor cloud credentials list`."
3232
+ )
3233
+ raise typer.Exit(code=1)
3234
+ console.print("[green]credential[/green]: found and owned by this account.")
3235
+ console.print("[green]cloud endpoint[/green]: reachable.")
3122
3236
  else:
3123
3237
  console.print(
3124
3238
  "[dim]cloud_managed uses platform-managed provider credentials; "
@@ -3170,6 +3284,16 @@ def doctor(
3170
3284
  )
3171
3285
  return
3172
3286
 
3287
+ # Local-mode doctor uses engine-only provider/execution helpers. These are
3288
+ # lazily imported here so a client-only install (cloud modes) never requires
3289
+ # the engine on disk (R21 core split); local mode has the engine via binary.
3290
+ from refactor_core.execution.modes import resolve_mode
3291
+ from refactor_core.providers import (
3292
+ get_provider,
3293
+ resolve_provider_name,
3294
+ supported_providers,
3295
+ )
3296
+
3173
3297
  mode = str(constitution.get_setting("mode", "local") or "local")
3174
3298
  provider_name = resolve_provider_name(constitution, provider_name=provider)
3175
3299
 
@@ -3315,7 +3439,7 @@ def _report_sandbox(project_root: Path, config) -> None:
3315
3439
  enabled = True
3316
3440
  runtime_pref = str(sandbox_cfg.get("runtime", "podman") or "podman")
3317
3441
  image = str(sandbox_cfg.get("image") or SANDBOX_DEFAULT_IMAGE)
3318
- resolved_runtime, reason = resolve_runtime(runtime_pref)
3442
+ resolved_runtime, reason = _sandbox_runtime().resolve_runtime(runtime_pref)
3319
3443
 
3320
3444
  console.print(f"[bold]project[/bold]: {project_root}")
3321
3445
  console.print(f"[bold]sandbox enabled[/bold]: {enabled} [dim](mandatory for local runs)[/dim]")
@@ -3365,6 +3489,10 @@ def _report_sandbox(project_root: Path, config) -> None:
3365
3489
 
3366
3490
 
3367
3491
  def _report_structural_readiness(project_root: Path, config) -> None:
3492
+ # Engine-only structural ops (local doctor diagnostic); lazy so client-only
3493
+ # installs never require the engine on disk (R21 core split).
3494
+ from refactor_core.execution.structural_ops import structural_compatibility_matrix
3495
+
3368
3496
  settings = dict(getattr(config, "settings", {}) or {})
3369
3497
  refactor_cfg = settings.get("refactor", {})
3370
3498
  if not isinstance(refactor_cfg, dict):
@@ -142,8 +142,8 @@ def _exec_runtime(command_name: str, passthrough_args: list[str]) -> None:
142
142
 
143
143
 
144
144
  def init(ctx: typer.Context) -> None:
145
- """Initialize project state via proprietary runtime."""
146
- _exec_runtime("init", list(ctx.args))
145
+ """Initialize project config locally (no runtime required)."""
146
+ _delegate_to_run_cmds("init", list(ctx.args))
147
147
 
148
148
 
149
149
  def start(ctx: typer.Context) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.2.29
3
+ Version: 0.3.0
4
4
  Summary: Local-first CLI for the refactor platform
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -8,6 +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.0.0
11
12
 
12
13
  # refactorai-cli
13
14
 
@@ -53,28 +54,34 @@ python -m twine upload ./refactorai-cli/dist/*
53
54
  ## Install test (local)
54
55
 
55
56
  ```bash
56
- python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.29-py3-none-any.whl
57
+ python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.0-py3-none-any.whl
57
58
  refactor --version
58
59
  ```
59
60
 
60
61
  ## Cloud BYOK setup (bring your own provider key)
61
62
 
62
63
  Run cloud inference with your own provider credentials (no local runtime
63
- required). The secret is read from your environment and encrypted server-side;
64
- it is never written to disk or printed.
64
+ required). Preferred path is env-key in project config (`provider_key:
65
+ ${ENV_VAR}`). Platform-stored credentials (`credential_ref`) are optional
66
+ fallback.
65
67
 
66
68
  ```bash
67
69
  # 1. Authenticate (developer key with the run:byok entitlement).
68
70
  refactor login
69
71
 
70
- # 2. Register your provider secret from an env var.
72
+ # 2A. Preferred: set BYOK directly from project env in refactor.config:
73
+ #
74
+ # mode: cloud_byok
75
+ # provider: openai
76
+ # model_id: gpt-4.1-mini
77
+ # provider_key: ${OPENAI_API_KEY}
78
+ #
79
+ # 2B. Optional fallback: register a platform-stored credential from env.
71
80
  export OPENAI_API_KEY=sk-...
72
81
  refactor cloud credentials set --provider openai --from-env OPENAI_API_KEY --label "my key"
73
82
  # -> prints a credential_ref, e.g. cred_01J...
74
83
 
75
- # 3. Point your project at cloud_byok in refactor.config:
76
- # mode: cloud_byok
77
- # provider: openai
84
+ # 3. If you use platform-stored fallback, set credential_ref in config:
78
85
  # credential_ref: cred_01J...
79
86
 
80
87
  # 4. Verify readiness, then run.
@@ -2,3 +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.0.0