refactorai-cli 0.7.16__tar.gz → 0.7.18__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 (54) hide show
  1. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/PKG-INFO +2 -2
  2. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/pyproject.toml +2 -2
  3. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/__init__.py +1 -1
  4. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/run_cmds.py +64 -0
  5. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/mode_sync.py +90 -19
  6. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli.egg-info/PKG-INFO +2 -2
  7. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli.egg-info/requires.txt +1 -1
  8. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/README.md +0 -0
  9. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/auth.py +0 -0
  10. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/client.py +0 -0
  11. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/cloud_rr.py +0 -0
  12. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/__init__.py +0 -0
  13. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/account_cmds.py +0 -0
  14. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/auth_cmds.py +0 -0
  15. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/branch_cmds.py +0 -0
  16. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/cloud_cmds.py +0 -0
  17. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/engine_cmds.py +0 -0
  18. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/hook_cmds.py +0 -0
  19. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/model_cmds.py +0 -0
  20. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/policy_cmds.py +0 -0
  21. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/pre_push_cmds.py +0 -0
  22. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/request_cmds.py +0 -0
  23. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/rules_cmds.py +0 -0
  24. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/runtime_cmds.py +0 -0
  25. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/runtime_proxy_cmds.py +0 -0
  26. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/setup_cmds.py +0 -0
  27. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/toolchains_cmds.py +0 -0
  28. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/watch_cmds.py +0 -0
  29. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commands/workspace_cmds.py +0 -0
  30. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commit_queue.py +0 -0
  31. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/commit_telemetry.py +0 -0
  32. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/control_plane.py +0 -0
  33. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/credentials.py +0 -0
  34. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/dotenv_loader.py +0 -0
  35. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/git_scope.py +0 -0
  36. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/local_constitution.py +0 -0
  37. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/local_engine_runtime.py +0 -0
  38. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/local_paths.py +0 -0
  39. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/main.py +0 -0
  40. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/model_policy.py +0 -0
  41. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/pre_push_gate.py +0 -0
  42. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/refactor_branch_store.py +0 -0
  43. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/review_runner.py +0 -0
  44. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/runtime_manager.py +0 -0
  45. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/settings.py +0 -0
  46. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/setup_flow.py +0 -0
  47. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/watch_ledger.py +0 -0
  48. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/watch_state.py +0 -0
  49. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli/watch_supervisor.py +0 -0
  50. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli.egg-info/SOURCES.txt +0 -0
  51. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli.egg-info/dependency_links.txt +0 -0
  52. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli.egg-info/entry_points.txt +0 -0
  53. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/refactorai_cli.egg-info/top_level.txt +0 -0
  54. {refactorai_cli-0.7.16 → refactorai_cli-0.7.18}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.7.16
3
+ Version: 0.7.18
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.2.23
11
+ Requires-Dist: refactorai-core>=3.2.24
12
12
 
13
13
  # refactorai-cli
14
14
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "refactorai-cli"
3
- version = "0.7.16"
3
+ version = "0.7.18"
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.2.23",
15
+ "refactorai-core>=3.2.24",
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.7.16"
8
+ __version__ = "0.7.18"
@@ -8,6 +8,7 @@ generation and application.
8
8
  from __future__ import annotations
9
9
 
10
10
  import hashlib
11
+ import importlib.util
11
12
  import os
12
13
  import json
13
14
  import re
@@ -669,6 +670,60 @@ def _guard_local_inference_ready(variant: str) -> None:
669
670
  )
670
671
 
671
672
 
673
+ # Subpackages that ship ONLY in the private engine wheel (R21 core split) and are
674
+ # intentionally absent from a public pip/pipx install of ``refactorai-core``. The
675
+ # engine is proprietary: it runs on the Refactor server (server-engine variants)
676
+ # or inside the separately downloaded runtime artifact (local_model /
677
+ # local_heuristic, dispatched via ``runtime_proxy_cmds._exec_runtime``). It is
678
+ # never a client dependency, so ``run_cmds`` must never partially import it.
679
+ _ENGINE_ONLY_ROOT = "refactor_core.execution"
680
+
681
+
682
+ def _engine_importable() -> bool:
683
+ """True when the proprietary local engine is importable in this process.
684
+
685
+ Present only in monorepo/source checkouts (where the engine tree exists) --
686
+ never in a public client install, which excludes the engine subpackages from
687
+ the wheel. Used as a capability gate so the client never *attempts* an engine
688
+ import (which would both crash with a raw ``ModuleNotFoundError`` and, in a
689
+ source checkout, execute proprietary code on the developer's machine).
690
+ """
691
+ try:
692
+ return importlib.util.find_spec(_ENGINE_ONLY_ROOT) is not None
693
+ except (ImportError, ValueError):
694
+ return False
695
+
696
+
697
+ def _guard_local_engine(constitution) -> None:
698
+ """Refuse to run the local (in-process) engine path on a client install.
699
+
700
+ ``run_cmds`` serves the server-engine variants (``local_byok`` /
701
+ ``local_managed`` / ``cloud_*``); the router
702
+ (``runtime_proxy_cmds._dispatch``) sends host-engine variants
703
+ (``local_model`` / ``local_heuristic``) to the downloaded runtime artifact.
704
+ A local-engine branch here is therefore only valid in a source checkout where
705
+ the engine is present. When it is not importable, fail with an actionable
706
+ routing/mode message *before* importing anything -- never a raw
707
+ ``ModuleNotFoundError`` and never a partial import of proprietary code.
708
+ """
709
+ if _engine_importable():
710
+ return
711
+ variant = _execution_variant(constitution)
712
+ console.print(
713
+ "[red]This command reached the local engine path, which is not part of "
714
+ "the public install.[/red]\n"
715
+ f"Execution variant: [bold]{variant}[/bold]\n"
716
+ "The refactor engine is proprietary and never installed on the "
717
+ "developer machine: server-engine modes (local_byok / local_managed / "
718
+ "cloud_byok / cloud_managed) run it remotely, and local_model / "
719
+ "local_heuristic run it via the downloaded runtime artifact.\n"
720
+ "Check the project's execution mode in refactor.config or the web UI, "
721
+ "then rerun. If the mode is already a server-engine mode, this is a "
722
+ "routing bug (the run should never have taken the local engine path)."
723
+ )
724
+ raise typer.Exit(code=1)
725
+
726
+
672
727
  def _documents_digest(documents: list | None) -> str:
673
728
  """Stable digest of the exact documents sent to the platform.
674
729
 
@@ -3298,6 +3353,7 @@ def review(
3298
3353
  if scope and scope.get("summary"):
3299
3354
  artifact.notes.append(f"Commit-scoped review intent: {scope['summary']}")
3300
3355
  else:
3356
+ _guard_local_engine(constitution)
3301
3357
  from refactor_core.execution.orchestrator import run_with_mode
3302
3358
  from refactor_core.providers import get_provider
3303
3359
 
@@ -3337,6 +3393,10 @@ def review(
3337
3393
  "[yellow]review note:[/yellow] sandbox preflight issue detected. "
3338
3394
  "Review continued; address sandbox install/toolchain incompatibilities for best results."
3339
3395
  )
3396
+ except typer.Exit:
3397
+ # Clean, already-reported exits (e.g. the local-engine routing guard)
3398
+ # must propagate as-is, not be re-wrapped into a generic failure.
3399
+ raise
3340
3400
  except Exception as exc: # provider/transport errors should not crash the CLI
3341
3401
  console.print(f"[red]Review failed:[/red] {_sanitize_runtime_error_message(exc)}")
3342
3402
  raise typer.Exit(code=1) from exc
@@ -3663,6 +3723,7 @@ def code(
3663
3723
  )
3664
3724
  selected_provider = None
3665
3725
  if not engine_remote:
3726
+ _guard_local_engine(constitution)
3666
3727
  from refactor_core.providers import get_provider
3667
3728
 
3668
3729
  _guard_local_inference_ready(_execution_variant(constitution))
@@ -3748,6 +3809,7 @@ def code(
3748
3809
  )
3749
3810
  cloud_review_findings = list(review_artifact.findings or [])
3750
3811
  else:
3812
+ _guard_local_engine(constitution)
3751
3813
  from refactor_core.execution.orchestrator import run_with_mode
3752
3814
 
3753
3815
  with console.status("[cyan]review[/cyan] analyzing...", spinner="dots") as status:
@@ -3857,6 +3919,7 @@ def code(
3857
3919
  else:
3858
3920
  # Local mode is unchanged: dispatch to the local executor with the
3859
3921
  # local provider and no cloud hooks.
3922
+ _guard_local_engine(constitution)
3860
3923
  from refactor_core.execution.rr_executor import run_refactor_requests
3861
3924
 
3862
3925
  with console.status("[cyan]code[/cyan] running...", spinner="dots") as status:
@@ -3999,6 +4062,7 @@ def requests(
3999
4062
  if not deferred_rrs:
4000
4063
  console.print("[dim]No deferred requests to retry.[/dim]")
4001
4064
  else:
4065
+ _guard_local_engine(constitution)
4002
4066
  from refactor_core.execution.rr_executor import run_refactor_requests
4003
4067
  from refactor_core.providers import get_provider
4004
4068
 
@@ -17,9 +17,10 @@ from typing import Callable
17
17
 
18
18
  from refactor_core.constitution import (
19
19
  CONFIG_FILENAME,
20
+ MANAGED_BLOCK_BEGIN,
21
+ MANAGED_BLOCK_END,
20
22
  MANAGED_KEYS,
21
23
  find_project_files,
22
- load_config,
23
24
  upsert_managed_block,
24
25
  )
25
26
  from refactor_core.execution_variant import normalize_execution_variant
@@ -38,33 +39,90 @@ def _local_config_path(project_root: Path) -> Path | None:
38
39
 
39
40
 
40
41
  def _current_managed_values(project_root: Path) -> dict:
41
- """Current values of the managed keys as parsed from refactor.config."""
42
+ """Current values from the raw managed block (no env interpolation)."""
42
43
  config_path = _local_config_path(project_root)
43
44
  if config_path is None:
44
45
  return {}
45
46
  try:
46
- settings = getattr(load_config(config_path), "settings", {}) or {}
47
- except Exception:
47
+ text = config_path.read_text(encoding="utf-8")
48
+ except OSError:
49
+ return {}
50
+ begin = text.find(MANAGED_BLOCK_BEGIN)
51
+ if begin == -1:
52
+ return {}
53
+ end = text.find(MANAGED_BLOCK_END, begin)
54
+ if end == -1:
48
55
  return {}
56
+ body = text[begin + len(MANAGED_BLOCK_BEGIN) : end]
49
57
  values: dict = {}
50
- for key in MANAGED_KEYS:
51
- raw = settings.get(key)
52
- if raw is None:
58
+ for line in body.splitlines():
59
+ stripped = line.strip()
60
+ if not stripped or stripped.startswith("#") or ":" not in stripped:
53
61
  continue
54
- text = str(raw).strip()
55
- if text:
56
- values[key] = text
62
+ key, raw = stripped.split(":", 1)
63
+ key = key.strip()
64
+ if key not in MANAGED_KEYS:
65
+ continue
66
+ value = raw.strip()
67
+ if value:
68
+ values[key] = value
57
69
  return values
58
70
 
59
71
 
72
+ _LOCAL_BYOK_ENV_DEFAULTS: dict[str, dict[str, str]] = {
73
+ "openai": {
74
+ "provider_key": "${OPENAI_API_KEY}",
75
+ "model_id": "${OPENAI_MODEL_ID}",
76
+ "base_url": "${OPENAI_BASE_URL}",
77
+ },
78
+ "anthropic": {
79
+ "provider_key": "${ANTHROPIC_API_KEY}",
80
+ "model_id": "${ANTHROPIC_MODEL_ID}",
81
+ },
82
+ "openrouter": {
83
+ "provider_key": "${OPENROUTER_API_KEY}",
84
+ "model_id": "${OPENROUTER_MODEL_ID}",
85
+ "base_url": "${OPENROUTER_BASE_URL}",
86
+ },
87
+ "nvidia": {
88
+ "provider_key": "${NVIDIA_API_KEY}",
89
+ "model_id": "${NVIDIA_MODEL_ID}",
90
+ "base_url": "${NVIDIA_BASE_URL}",
91
+ },
92
+ "azure_openai": {
93
+ "provider_key": "${AZURE_OPENAI_API_KEY}",
94
+ "model_id": "${AZURE_OPENAI_DEPLOYMENT}",
95
+ "base_url": "${AZURE_OPENAI_ENDPOINT}",
96
+ "api_version": "${AZURE_OPENAI_API_VERSION}",
97
+ },
98
+ "vertex_ai": {
99
+ "provider_key": "${GOOGLE_OAUTH_ACCESS_TOKEN}",
100
+ "model_id": "${VERTEX_MODEL_ID}",
101
+ "project": "${GOOGLE_CLOUD_PROJECT}",
102
+ "region": "${GOOGLE_CLOUD_LOCATION}",
103
+ },
104
+ "bedrock": {
105
+ "provider_key": "${AWS_BEARER_TOKEN_BEDROCK}",
106
+ "model_id": "${BEDROCK_MODEL_ID}",
107
+ "region": "${AWS_REGION}",
108
+ },
109
+ "ollama": {
110
+ "model_id": "${OLLAMA_MODEL_ID}",
111
+ "base_url": "${OLLAMA_BASE_URL}",
112
+ },
113
+ }
114
+
115
+
60
116
  def _desired_managed_fields(record: dict, current: dict, server_variant: str) -> dict:
61
117
  """Compute the managed-block fields from the server record (server-wins).
62
118
 
63
119
  When the server carries a remote provider (BYOK/managed set from the UI), the
64
- provider/model/credential come from the server and any local ``base_url`` is
65
- dropped. Otherwise (heuristic / terminal-set local_model, where the server has
66
- no provider) the local provider/model/base_url are preserved and only the
67
- variant is reconciled.
120
+ provider/model/credential come from the server. Provider-local extras
121
+ (``provider_key`` env token, region/project/base_url/api_version) are preserved
122
+ from the local managed block so sync does not strip local BYOK readiness.
123
+ Otherwise (heuristic / terminal-set local_model, where the server has no
124
+ provider) local provider-scoped values are preserved and only the variant is
125
+ reconciled.
68
126
  """
69
127
  provider = str(record.get("inference_provider") or "").strip()
70
128
  model_id = str(record.get("model_id") or "").strip()
@@ -77,8 +135,20 @@ def _desired_managed_fields(record: dict, current: dict, server_variant: str) ->
77
135
  fields["model_id"] = model_id
78
136
  if credential_ref:
79
137
  fields["credential_ref"] = credential_ref
138
+ # Preserve local provider-scoped values when server record doesn't carry
139
+ # them. This keeps local BYOK config/env wiring stable across syncs.
140
+ for key in ("model_id", "provider_key", "base_url", "region", "project", "api_version"):
141
+ if key not in fields and current.get(key):
142
+ fields[key] = current[key]
143
+ # Local BYOK should be immediately runnable from env after sync. Seed
144
+ # provider-specific env-token placeholders when missing.
145
+ if server_variant == "local_byok":
146
+ defaults = _LOCAL_BYOK_ENV_DEFAULTS.get(provider, {})
147
+ for key, value in defaults.items():
148
+ if not str(fields.get(key) or "").strip():
149
+ fields[key] = value
80
150
  else:
81
- for key in ("provider", "model_id", "base_url", "credential_ref"):
151
+ for key in ("provider", "model_id", "provider_key", "base_url", "region", "project", "api_version", "credential_ref"):
82
152
  if current.get(key):
83
153
  fields[key] = current[key]
84
154
  return fields
@@ -92,10 +162,11 @@ def sync_execution_variant_from_server(
92
162
  ) -> str | None:
93
163
  """Pull the server project's execution settings into the managed config block.
94
164
 
95
- Server-wins for the managed keys (execution_variant / provider / model_id /
96
- credential_ref). Secrets (``provider_key``) are never written -- they stay in
97
- the environment. Returns the applied variant when the local config changed,
98
- else ``None`` (already in sync, offline, unauthenticated, or unbound project).
165
+ Server-wins for mode/provider identity, while preserving provider-local env
166
+ wiring in the managed block. ``provider_key`` is written only as an env token
167
+ placeholder (for example ``${OPENAI_API_KEY}``) and never as a raw secret.
168
+ Returns the applied variant when the local config changed, else ``None``
169
+ (already in sync, offline, unauthenticated, or unbound project).
99
170
  """
100
171
  try:
101
172
  project_id = resolve_project_id(project_root)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.7.16
3
+ Version: 0.7.18
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.2.23
11
+ Requires-Dist: refactorai-core>=3.2.24
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.2.23
5
+ refactorai-core>=3.2.24