refactorai-cli 0.1.0__tar.gz → 0.2.2__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 (34) hide show
  1. {refactorai_cli-0.1.0 → refactorai_cli-0.2.2}/PKG-INFO +2 -3
  2. {refactorai_cli-0.1.0 → refactorai_cli-0.2.2}/README.md +1 -1
  3. {refactorai_cli-0.1.0 → refactorai_cli-0.2.2}/pyproject.toml +3 -4
  4. {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/__init__.py +1 -1
  5. {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/auth.py +3 -3
  6. {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/client.py +1 -1
  7. {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/auth_cmds.py +4 -4
  8. {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/engine_cmds.py +2 -2
  9. {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/model_cmds.py +2 -2
  10. {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/run_cmds.py +2 -2
  11. {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/runtime_cmds.py +4 -4
  12. {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/setup_cmds.py +1 -1
  13. {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/control_plane.py +3 -3
  14. {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/credentials.py +3 -3
  15. refactorai_cli-0.2.2/refactorai_cli/local_constitution.py +76 -0
  16. refactorai_cli-0.2.2/refactorai_cli/local_engine_runtime.py +243 -0
  17. refactorai_cli-0.2.2/refactorai_cli/local_paths.py +40 -0
  18. {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/main.py +3 -20
  19. {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/model_policy.py +1 -1
  20. {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/runtime_manager.py +21 -17
  21. {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/setup_flow.py +6 -6
  22. {refactorai_cli-0.1.0 → refactorai_cli-0.2.2}/refactorai_cli.egg-info/PKG-INFO +2 -3
  23. refactorai_cli-0.2.2/refactorai_cli.egg-info/SOURCES.txt +29 -0
  24. refactorai_cli-0.2.2/refactorai_cli.egg-info/entry_points.txt +2 -0
  25. {refactorai_cli-0.1.0 → refactorai_cli-0.2.2}/refactorai_cli.egg-info/requires.txt +0 -1
  26. refactorai_cli-0.2.2/refactorai_cli.egg-info/top_level.txt +1 -0
  27. refactorai_cli-0.1.0/refactorai_cli.egg-info/SOURCES.txt +0 -26
  28. refactorai_cli-0.1.0/refactorai_cli.egg-info/entry_points.txt +0 -2
  29. refactorai_cli-0.1.0/refactorai_cli.egg-info/top_level.txt +0 -1
  30. {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/__init__.py +0 -0
  31. {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/rules_cmds.py +0 -0
  32. {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/settings.py +0 -0
  33. {refactorai_cli-0.1.0 → refactorai_cli-0.2.2}/refactorai_cli.egg-info/dependency_links.txt +0 -0
  34. {refactorai_cli-0.1.0 → refactorai_cli-0.2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.1.0
3
+ Version: 0.2.2
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,6 @@ 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: refactor-core>=0.1.0
12
11
 
13
12
  # refactorai-cli
14
13
 
@@ -16,7 +15,7 @@ Public CLI package for Refactor.
16
15
 
17
16
  - PyPI package name: `refactorai-cli`
18
17
  - Installed command: `refactor`
19
- - Python module package: `refactor_cli`
18
+ - Python module package: `refactorai_cli`
20
19
 
21
20
  ## Local development install
22
21
 
@@ -4,7 +4,7 @@ Public CLI package for Refactor.
4
4
 
5
5
  - PyPI package name: `refactorai-cli`
6
6
  - Installed command: `refactor`
7
- - Python module package: `refactor_cli`
7
+ - Python module package: `refactorai_cli`
8
8
 
9
9
  ## Local development install
10
10
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "refactorai-cli"
3
- version = "0.1.0"
3
+ version = "0.2.2"
4
4
  description = "Local-first CLI for the refactor platform"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -9,15 +9,14 @@ dependencies = [
9
9
  "httpx>=0.27.0",
10
10
  "rich>=13.7.0",
11
11
  "PyYAML>=6.0.1",
12
- "refactor-core>=0.1.0",
13
12
  ]
14
13
 
15
14
  [project.scripts]
16
- refactor = "refactor_cli.main:app"
15
+ refactor = "refactorai_cli.main:app"
17
16
 
18
17
  [build-system]
19
18
  requires = ["setuptools>=68", "wheel"]
20
19
  build-backend = "setuptools.build_meta"
21
20
 
22
21
  [tool.setuptools]
23
- packages = ["refactor_cli", "refactor_cli.commands"]
22
+ packages = ["refactorai_cli", "refactorai_cli.commands"]
@@ -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.1.0"
8
+ __version__ = "0.2.2"
@@ -11,10 +11,10 @@ from dataclasses import dataclass
11
11
  from datetime import datetime, timezone
12
12
  from pathlib import Path
13
13
 
14
- from refactor_core.paths import ensure_dir, validation_cache_dir
14
+ from refactorai_cli.local_paths import ensure_dir, validation_cache_dir
15
15
 
16
- from refactor_cli.client import PlatformClient, PlatformError
17
- from refactor_cli.credentials import ResolvedKey, resolve_developer_key
16
+ from refactorai_cli.client import PlatformClient, PlatformError
17
+ from refactorai_cli.credentials import ResolvedKey, resolve_developer_key
18
18
 
19
19
 
20
20
  class AuthError(RuntimeError):
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import httpx
6
6
 
7
- from refactor_cli.settings import platform_url
7
+ from refactorai_cli.settings import platform_url
8
8
 
9
9
 
10
10
  class PlatformError(RuntimeError):
@@ -5,10 +5,10 @@ from __future__ import annotations
5
5
  import typer
6
6
  from rich.console import Console
7
7
 
8
- from refactor_cli.auth import AuthError, ensure_authenticated
9
- from refactor_cli.client import PlatformClient, PlatformError
10
- from refactor_cli.credentials import load_credentials, save_credentials
11
- from refactor_cli.settings import mask_key, platform_url
8
+ from refactorai_cli.auth import AuthError, ensure_authenticated
9
+ from refactorai_cli.client import PlatformClient, PlatformError
10
+ from refactorai_cli.credentials import load_credentials, save_credentials
11
+ from refactorai_cli.settings import mask_key, platform_url
12
12
 
13
13
  console = Console()
14
14
 
@@ -7,7 +7,7 @@ import os
7
7
  import typer
8
8
  from rich.console import Console
9
9
 
10
- from refactor_core.engine_runtime import (
10
+ from refactorai_cli.local_engine_runtime import (
11
11
  DEFAULT_ENGINE_CONTAINER,
12
12
  DEFAULT_ENGINE_IMAGE,
13
13
  DEFAULT_ENGINE_PORT,
@@ -20,7 +20,7 @@ from refactor_core.engine_runtime import (
20
20
  run_engine_logs,
21
21
  stop_engine,
22
22
  )
23
- from refactor_cli.model_policy import detect_machine_profile, evaluate_model, get_policy_bundle
23
+ from refactorai_cli.model_policy import detect_machine_profile, evaluate_model, get_policy_bundle
24
24
 
25
25
  console = Console()
26
26
  app = typer.Typer(help="Manage the persistent local refactor engine.")
@@ -6,9 +6,9 @@ import typer
6
6
  from rich.console import Console
7
7
  from rich.table import Table
8
8
 
9
- from refactor_core.engine_runtime import DEFAULT_ENGINE_CONTAINER, probe_model, read_engine_state, resolve_runtime
9
+ from refactorai_cli.local_engine_runtime import DEFAULT_ENGINE_CONTAINER, probe_model, read_engine_state, resolve_runtime
10
10
 
11
- from refactor_cli.model_policy import (
11
+ from refactorai_cli.model_policy import (
12
12
  detect_machine_profile,
13
13
  evaluate_model,
14
14
  get_policy_bundle,
@@ -88,8 +88,8 @@ from refactor_core.store import (
88
88
  )
89
89
  from refactor_core.testcmd import resolve_test_command
90
90
 
91
- from refactor_cli.auth import AuthError, ensure_authenticated
92
- from refactor_cli.settings import platform_url
91
+ from refactorai_cli.auth import AuthError, ensure_authenticated
92
+ from refactorai_cli.settings import platform_url
93
93
 
94
94
  console = Console()
95
95
 
@@ -5,9 +5,9 @@ from __future__ import annotations
5
5
  import typer
6
6
  from rich.console import Console
7
7
 
8
- from refactor_cli.auth import AuthError, ensure_authenticated
9
- from refactor_cli.control_plane import ensure_lease, heartbeat, read_cached_lease, resolve_policy
10
- from refactor_cli.runtime_manager import (
8
+ from refactorai_cli.auth import AuthError, ensure_authenticated
9
+ from refactorai_cli.control_plane import ensure_lease, heartbeat, read_cached_lease, resolve_policy
10
+ from refactorai_cli.runtime_manager import (
11
11
  activate_runtime,
12
12
  download_artifact,
13
13
  resolve_runtime_manifest,
@@ -68,7 +68,7 @@ def update(
68
68
  # Verification happens in activate_runtime; dry-run still checks
69
69
  # integrity by attempting activation in-memory style via duplicate call.
70
70
  # We use a no-op by verifying digest directly in manager on activation.
71
- from refactor_cli.runtime_manager import verify_sha256 # local import to keep module surface small
71
+ from refactorai_cli.runtime_manager import verify_sha256 # local import to keep module surface small
72
72
 
73
73
  ok = verify_sha256(artifact, manifest.sha256)
74
74
  if not ok:
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import typer
6
6
  from rich.console import Console
7
7
 
8
- from refactor_cli.setup_flow import STAGE_IDS, SetupError, get_setup_diagnostics, run_setup
8
+ from refactorai_cli.setup_flow import STAGE_IDS, SetupError, get_setup_diagnostics, run_setup
9
9
 
10
10
  console = Console()
11
11
 
@@ -12,10 +12,10 @@ from pathlib import Path
12
12
 
13
13
  import httpx
14
14
 
15
- from refactor_core.paths import ensure_dir, refactor_home
15
+ from refactorai_cli.local_paths import ensure_dir, refactor_home
16
16
 
17
- from refactor_cli.credentials import resolve_developer_key
18
- from refactor_cli.settings import platform_url, policy_signing_key
17
+ from refactorai_cli.credentials import resolve_developer_key
18
+ from refactorai_cli.settings import platform_url, policy_signing_key
19
19
 
20
20
  LEASE_DIR = "leases"
21
21
  LEASE_FILE = "current.json"
@@ -13,10 +13,10 @@ import os
13
13
  from dataclasses import dataclass
14
14
  from pathlib import Path
15
15
 
16
- from refactor_core.constitution import find_config, load_config
17
- from refactor_core.paths import credentials_path, ensure_dir, refactor_home
16
+ from refactorai_cli.local_constitution import find_config, load_config
17
+ from refactorai_cli.local_paths import credentials_path, ensure_dir, refactor_home
18
18
 
19
- from refactor_cli.settings import env_api_key
19
+ from refactorai_cli.settings import env_api_key
20
20
 
21
21
 
22
22
  @dataclass
@@ -0,0 +1,76 @@
1
+ """Lightweight config discovery/parsing helpers for the CLI package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import re
7
+ from dataclasses import dataclass, field
8
+ from pathlib import Path
9
+
10
+ import yaml
11
+
12
+ CONFIG_FILENAME = "refactor.config"
13
+ _ENV_TOKEN = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}")
14
+
15
+
16
+ @dataclass
17
+ class RefactorConfig:
18
+ settings: dict = field(default_factory=dict)
19
+ raw: str = ""
20
+ path: Path | None = None
21
+
22
+ def get_setting(self, key: str, default=None):
23
+ return self.settings.get(key, default)
24
+
25
+
26
+ def _interpolate_env(value):
27
+ if isinstance(value, str):
28
+ return _ENV_TOKEN.sub(lambda m: os.environ.get(m.group(1), ""), value)
29
+ if isinstance(value, dict):
30
+ return {k: _interpolate_env(v) for k, v in value.items()}
31
+ if isinstance(value, list):
32
+ return [_interpolate_env(v) for v in value]
33
+ return value
34
+
35
+
36
+ def _search_root(start: Path | None = None) -> Path:
37
+ if start is not None:
38
+ try:
39
+ return Path(start).resolve()
40
+ except OSError:
41
+ return Path.home().resolve()
42
+ try:
43
+ return Path.cwd().resolve()
44
+ except OSError:
45
+ return Path.home().resolve()
46
+
47
+
48
+ def find_config(start: Path | None = None) -> Path | None:
49
+ current = _search_root(start)
50
+ for candidate in [current, *current.parents]:
51
+ config = candidate / CONFIG_FILENAME
52
+ if config.is_file():
53
+ return config
54
+ return None
55
+
56
+
57
+ def parse_config(text: str, *, path: Path | None = None) -> RefactorConfig:
58
+ normalized = text.replace("\r\n", "\n")
59
+ try:
60
+ loaded = yaml.safe_load(normalized)
61
+ except yaml.YAMLError:
62
+ loaded = {}
63
+ if isinstance(loaded, dict):
64
+ if isinstance(loaded.get("settings"), dict):
65
+ settings = dict(loaded.get("settings") or {})
66
+ else:
67
+ settings = dict(loaded)
68
+ else:
69
+ settings = {}
70
+ settings = _interpolate_env(settings)
71
+ return RefactorConfig(settings=settings, raw=text, path=path)
72
+
73
+
74
+ def load_config(path: Path) -> RefactorConfig:
75
+ text = Path(path).read_text(encoding="utf-8")
76
+ return parse_config(text, path=Path(path))
@@ -0,0 +1,243 @@
1
+ """Local engine runtime helpers shipped with the CLI package.
2
+
3
+ These are copied from the shared runtime helpers so setup/engine/model commands
4
+ remain usable without a separate ``refactor-core`` package dependency.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import shutil
11
+ import subprocess
12
+ from datetime import datetime, timezone
13
+ from pathlib import Path
14
+
15
+ from refactorai_cli.local_paths import ensure_dir, refactor_home
16
+
17
+ DEFAULT_ENGINE_IMAGE = "docker.io/library/alpine:3.20"
18
+ DEFAULT_ENGINE_CONTAINER = "refactor-engine"
19
+ DEFAULT_ENGINE_PORT = 17777
20
+ DEFAULT_ENGINE_PROFILE = "cpu-small"
21
+ ENGINE_DIR = "engine"
22
+ ENGINE_STATE_FILE = "engine.json"
23
+ ENGINE_MODELS_DIR = "models"
24
+ ENGINE_CACHE_DIR = "cache/ollama"
25
+
26
+
27
+ def _now_iso() -> str:
28
+ return datetime.now(timezone.utc).isoformat()
29
+
30
+
31
+ def engine_store_dir() -> Path:
32
+ return refactor_home() / ENGINE_DIR
33
+
34
+
35
+ def engine_state_path() -> Path:
36
+ return engine_store_dir() / ENGINE_STATE_FILE
37
+
38
+
39
+ def _default_state() -> dict:
40
+ return {
41
+ "runtime": "podman",
42
+ "container_name": DEFAULT_ENGINE_CONTAINER,
43
+ "image": DEFAULT_ENGINE_IMAGE,
44
+ "port": DEFAULT_ENGINE_PORT,
45
+ "profile": DEFAULT_ENGINE_PROFILE,
46
+ "status": "down",
47
+ "updated_at": "",
48
+ }
49
+
50
+
51
+ def read_engine_state() -> dict:
52
+ path = engine_state_path()
53
+ if not path.is_file():
54
+ return _default_state()
55
+ try:
56
+ data = json.loads(path.read_text(encoding="utf-8"))
57
+ except (json.JSONDecodeError, OSError):
58
+ return _default_state()
59
+ out = _default_state()
60
+ out.update({k: data.get(k, v) for k, v in out.items()})
61
+ return out
62
+
63
+
64
+ def write_engine_state(state: dict) -> None:
65
+ ensure_dir(engine_store_dir())
66
+ merged = _default_state()
67
+ merged.update(state or {})
68
+ merged["updated_at"] = _now_iso()
69
+ path = engine_state_path()
70
+ tmp = path.with_suffix(path.suffix + ".tmp")
71
+ tmp.write_text(json.dumps(merged, indent=2), encoding="utf-8")
72
+ tmp.replace(path)
73
+
74
+
75
+ def resolve_runtime(preferred: str = "podman") -> tuple[str | None, str]:
76
+ pref = (preferred or "podman").strip().lower()
77
+ candidates: list[str] = ["podman"] if pref in {"", "auto", "podman"} else [pref]
78
+ for runtime in candidates:
79
+ if shutil.which(runtime):
80
+ return runtime, ""
81
+ return None, "Podman not found. Install podman and retry (`refactor engine up`)."
82
+
83
+
84
+ def _run(cmd: list[str]) -> subprocess.CompletedProcess:
85
+ return subprocess.run(cmd, capture_output=True, text=True)
86
+
87
+
88
+ def _exists(runtime: str, name: str) -> bool:
89
+ proc = _run([runtime, "container", "exists", name])
90
+ return proc.returncode == 0
91
+
92
+
93
+ def _running(runtime: str, name: str) -> bool:
94
+ proc = _run([runtime, "inspect", "-f", "{{.State.Running}}", name])
95
+ return proc.returncode == 0 and proc.stdout.strip().lower() == "true"
96
+
97
+
98
+ def _health(runtime: str, name: str) -> bool:
99
+ proc = _run([runtime, "exec", name, "sh", "-lc", "command -v ollama >/dev/null 2>&1"])
100
+ return proc.returncode == 0
101
+
102
+
103
+ def ensure_engine_up(
104
+ *,
105
+ runtime: str,
106
+ image: str = DEFAULT_ENGINE_IMAGE,
107
+ container_name: str = DEFAULT_ENGINE_CONTAINER,
108
+ profile: str = DEFAULT_ENGINE_PROFILE,
109
+ port: int = DEFAULT_ENGINE_PORT,
110
+ rebuild: bool = False,
111
+ ) -> tuple[bool, str, dict]:
112
+ models_dir = ensure_dir(refactor_home() / ENGINE_MODELS_DIR)
113
+ cache_dir = ensure_dir(refactor_home() / ENGINE_CACHE_DIR)
114
+ if rebuild and _exists(runtime, container_name):
115
+ _run([runtime, "rm", "-f", container_name])
116
+ if not _exists(runtime, container_name):
117
+ proc = _run(
118
+ [
119
+ runtime,
120
+ "run",
121
+ "-d",
122
+ "--name",
123
+ container_name,
124
+ "-p",
125
+ f"127.0.0.1:{int(port)}:{int(port)}",
126
+ "-v",
127
+ f"{models_dir}:/root/.ollama/models:Z",
128
+ "-v",
129
+ f"{cache_dir}:/root/.cache/ollama:Z",
130
+ image,
131
+ "sleep",
132
+ "infinity",
133
+ ]
134
+ )
135
+ if proc.returncode != 0:
136
+ detail = (proc.stderr or proc.stdout or "").strip()
137
+ return False, f"Failed to create engine container: {detail}", {}
138
+ elif not _running(runtime, container_name):
139
+ proc = _run([runtime, "start", container_name])
140
+ if proc.returncode != 0:
141
+ detail = (proc.stderr or proc.stdout or "").strip()
142
+ return False, f"Failed to start engine container: {detail}", {}
143
+ state = {
144
+ "runtime": runtime,
145
+ "container_name": container_name,
146
+ "image": image,
147
+ "port": int(port),
148
+ "profile": profile,
149
+ "status": "ready" if _health(runtime, container_name) else "degraded",
150
+ }
151
+ write_engine_state(state)
152
+ return True, "Engine ready.", state
153
+
154
+
155
+ def stop_engine(*, runtime: str, container_name: str = DEFAULT_ENGINE_CONTAINER) -> tuple[bool, str]:
156
+ if not _exists(runtime, container_name):
157
+ write_engine_state({"runtime": runtime, "container_name": container_name, "status": "down"})
158
+ return False, "Engine container does not exist."
159
+ if not _running(runtime, container_name):
160
+ write_engine_state({"runtime": runtime, "container_name": container_name, "status": "down"})
161
+ return True, "Engine already stopped."
162
+ proc = _run([runtime, "stop", container_name])
163
+ if proc.returncode != 0:
164
+ detail = (proc.stderr or proc.stdout or "").strip()
165
+ return False, f"Failed to stop engine: {detail}"
166
+ write_engine_state({"runtime": runtime, "container_name": container_name, "status": "down"})
167
+ return True, "Engine stopped."
168
+
169
+
170
+ def engine_status(*, runtime: str | None = None, container_name: str = DEFAULT_ENGINE_CONTAINER) -> dict:
171
+ state = read_engine_state()
172
+ resolved_runtime = runtime or str(state.get("runtime") or "podman")
173
+ exists = False
174
+ running = False
175
+ healthy = False
176
+ status = "error"
177
+ try:
178
+ exists = _exists(resolved_runtime, container_name)
179
+ running = _running(resolved_runtime, container_name) if exists else False
180
+ healthy = _health(resolved_runtime, container_name) if running else False
181
+ if running and healthy:
182
+ status = "ready"
183
+ elif running and not healthy:
184
+ status = "degraded"
185
+ else:
186
+ status = "down"
187
+ except Exception:
188
+ status = "error"
189
+ merged = {
190
+ "runtime": resolved_runtime,
191
+ "container_name": container_name,
192
+ "image": state.get("image", DEFAULT_ENGINE_IMAGE),
193
+ "port": int(state.get("port", DEFAULT_ENGINE_PORT)),
194
+ "profile": state.get("profile", DEFAULT_ENGINE_PROFILE),
195
+ "status": status,
196
+ "exists": exists,
197
+ "running": running,
198
+ "healthy": healthy,
199
+ "updated_at": state.get("updated_at", ""),
200
+ }
201
+ return merged
202
+
203
+
204
+ def run_engine_logs(*, runtime: str, container_name: str, follow: bool = False, tail: int = 200) -> int:
205
+ cmd = [runtime, "logs", "--tail", str(max(1, int(tail)))]
206
+ if follow:
207
+ cmd.append("-f")
208
+ cmd.append(container_name)
209
+ return subprocess.call(cmd)
210
+
211
+
212
+ def pull_model(*, runtime: str, container_name: str, model_id: str) -> tuple[bool, str]:
213
+ if not model_id.strip():
214
+ return False, "Model id is required."
215
+ if not _exists(runtime, container_name):
216
+ return False, "Engine container does not exist. Run `refactor engine up` first."
217
+ if not _running(runtime, container_name):
218
+ started = _run([runtime, "start", container_name])
219
+ if started.returncode != 0:
220
+ detail = (started.stderr or started.stdout or "").strip()
221
+ return False, f"Failed to start engine container: {detail}"
222
+ probe = _run([runtime, "exec", container_name, "sh", "-lc", "command -v ollama >/dev/null 2>&1"])
223
+ if probe.returncode != 0:
224
+ return False, "Engine image does not include `ollama`; use a model-capable engine image."
225
+ proc = _run([runtime, "exec", container_name, "sh", "-lc", f"ollama pull {model_id}"])
226
+ text = (proc.stdout or proc.stderr or "").strip()
227
+ if proc.returncode != 0:
228
+ return False, text or "Model pull failed."
229
+ return True, text or "Model pull complete."
230
+
231
+
232
+ def probe_model(*, runtime: str, container_name: str, model_id: str) -> tuple[bool, str]:
233
+ if not model_id.strip():
234
+ return False, "Model id is required."
235
+ if not _exists(runtime, container_name):
236
+ return False, "Engine container does not exist. Run `refactor engine up` first."
237
+ if not _running(runtime, container_name):
238
+ return False, "Engine container is not running. Run `refactor engine up` first."
239
+ proc = _run([runtime, "exec", container_name, "sh", "-lc", f"ollama show {model_id}"])
240
+ text = (proc.stdout or proc.stderr or "").strip()
241
+ if proc.returncode != 0:
242
+ return False, text or "model not found in engine"
243
+ return True, "model is available in engine"
@@ -0,0 +1,40 @@
1
+ """Local path helpers for the standalone CLI package.
2
+
3
+ These mirror the path helpers that were previously imported from ``refactor_core``
4
+ so the public ``refactorai-cli`` package can install without requiring a separate
5
+ ``refactor-core`` wheel on PyPI.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ from pathlib import Path
12
+
13
+
14
+ def refactor_home() -> Path:
15
+ override = os.environ.get("REFACTOR_HOME")
16
+ if override:
17
+ return Path(override).expanduser()
18
+ return Path.home() / ".refactor"
19
+
20
+
21
+ def encode_project_path(project_root: Path | str) -> str:
22
+ resolved = str(Path(project_root).resolve())
23
+ return resolved.lstrip("/").replace("/", "-") or "root"
24
+
25
+
26
+ def ensure_dir(path: Path, *, mode: int = 0o700) -> Path:
27
+ path.mkdir(parents=True, exist_ok=True, mode=mode)
28
+ return path
29
+
30
+
31
+ def credentials_path() -> Path:
32
+ return refactor_home() / "credentials.json"
33
+
34
+
35
+ def validation_cache_dir() -> Path:
36
+ return refactor_home() / "cache" / "validation"
37
+
38
+
39
+ def project_store_dir(project_root: Path | str) -> Path:
40
+ return refactor_home() / "projects" / encode_project_path(project_root)
@@ -4,8 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  import typer
6
6
 
7
- from refactor_cli import __version__
8
- from refactor_cli.commands import auth_cmds, engine_cmds, model_cmds, rules_cmds, run_cmds, runtime_cmds, setup_cmds
7
+ from refactorai_cli import __version__
8
+ from refactorai_cli.commands import auth_cmds, engine_cmds, model_cmds, runtime_cmds, setup_cmds
9
9
 
10
10
  app = typer.Typer(
11
11
  name="refactor",
@@ -39,26 +39,9 @@ def main(
39
39
  app.command()(auth_cmds.login)
40
40
  app.command()(auth_cmds.whoami)
41
41
 
42
- # Project / run surface (R7.0 scaffolding; auth-guarded where required)
43
- app.command()(run_cmds.init)
44
- app.command()(run_cmds.start)
45
- app.command()(run_cmds.stop)
46
- app.command()(run_cmds.shell)
47
- app.command()(run_cmds.review)
48
- app.command()(run_cmds.code)
49
- app.command()(run_cmds.requests)
50
- app.command()(run_cmds.apply)
51
- app.command()(run_cmds.revert)
52
- app.command()(run_cmds.status)
53
- app.command()(run_cmds.diff)
54
- app.command()(run_cmds.gc)
55
- app.command()(run_cmds.check)
56
- app.command()(run_cmds.doctor)
57
- app.command()(run_cmds.config)
58
42
  app.command()(setup_cmds.setup)
59
43
 
60
- # Rule policy introspection (R12)
61
- app.add_typer(rules_cmds.app, name="rules")
44
+ # Runtime/engine/model operations.
62
45
  app.add_typer(runtime_cmds.app, name="runtime")
63
46
  app.add_typer(engine_cmds.app, name="engine")
64
47
  app.add_typer(model_cmds.app, name="model")
@@ -7,7 +7,7 @@ import shutil
7
7
  import subprocess
8
8
  from dataclasses import dataclass
9
9
 
10
- from refactor_cli.control_plane import resolve_policy
10
+ from refactorai_cli.control_plane import resolve_policy
11
11
 
12
12
 
13
13
  @dataclass
@@ -17,11 +17,11 @@ from pathlib import Path
17
17
 
18
18
  import httpx
19
19
 
20
- from refactor_core.paths import ensure_dir, refactor_home
20
+ from refactorai_cli.local_paths import ensure_dir, refactor_home
21
21
 
22
- from refactor_cli.control_plane import ensure_lease
23
- from refactor_cli.credentials import resolve_developer_key
24
- from refactor_cli.settings import platform_url
22
+ from refactorai_cli.control_plane import ensure_lease
23
+ from refactorai_cli.credentials import resolve_developer_key
24
+ from refactorai_cli.settings import platform_url
25
25
 
26
26
  RUNTIME_DIR = "runtime"
27
27
  VERSIONS_DIR = "versions"
@@ -125,18 +125,7 @@ def parse_manifest(payload: dict) -> RuntimeManifest:
125
125
 
126
126
 
127
127
  def resolve_runtime_manifest(*, channel: str = "stable", timeout: float = 20.0) -> RuntimeManifest:
128
- auth_header = ""
129
- try:
130
- lease = ensure_lease()
131
- auth_header = f"Bearer {lease.token}"
132
- except RuntimeError:
133
- resolved = resolve_developer_key()
134
- if not resolved:
135
- raise RuntimeError(
136
- "No developer key configured. Run `refactor login`, set REFACTOR_API_KEY, "
137
- "or add developer_key to refactor.config."
138
- )
139
- auth_header = f"Bearer {resolved.key}"
128
+ auth_header = _runtime_auth_header()
140
129
  os_name = str(platform.system() or "linux").lower()
141
130
  arch = str(platform.machine() or "x86_64").lower()
142
131
  payload = {
@@ -163,8 +152,9 @@ def resolve_runtime_manifest(*, channel: str = "stable", timeout: float = 20.0)
163
152
 
164
153
 
165
154
  def download_artifact(url: str, *, timeout: float = 60.0) -> bytes:
155
+ auth_header = _runtime_auth_header()
166
156
  try:
167
- response = httpx.get(url, timeout=timeout)
157
+ response = httpx.get(url, headers={"Authorization": auth_header}, timeout=timeout)
168
158
  except httpx.HTTPError as exc:
169
159
  raise RuntimeError(f"Could not download runtime artifact: {exc}") from exc
170
160
  if response.status_code >= 400:
@@ -172,6 +162,20 @@ def download_artifact(url: str, *, timeout: float = 60.0) -> bytes:
172
162
  return response.content
173
163
 
174
164
 
165
+ def _runtime_auth_header() -> str:
166
+ try:
167
+ lease = ensure_lease()
168
+ return f"Bearer {lease.token}"
169
+ except RuntimeError:
170
+ resolved = resolve_developer_key()
171
+ if not resolved:
172
+ raise RuntimeError(
173
+ "No developer key configured. Run `refactor login`, set REFACTOR_API_KEY, "
174
+ "or add developer_key to refactor.config."
175
+ )
176
+ return f"Bearer {resolved.key}"
177
+
178
+
175
179
  def activate_runtime(manifest: RuntimeManifest, artifact: bytes, *, channel: str = "stable") -> Path:
176
180
  if not verify_sha256(artifact, manifest.sha256):
177
181
  raise RuntimeError("Runtime artifact checksum verification failed")
@@ -9,19 +9,19 @@ from datetime import datetime, timezone
9
9
  from pathlib import Path
10
10
  from typing import Callable
11
11
 
12
- from refactor_core.engine_runtime import (
12
+ from refactorai_cli.local_engine_runtime import (
13
13
  DEFAULT_ENGINE_CONTAINER,
14
14
  engine_status,
15
15
  ensure_engine_up,
16
16
  pull_model,
17
17
  resolve_runtime as resolve_engine_runtime,
18
18
  )
19
- from refactor_core.paths import ensure_dir, refactor_home
19
+ from refactorai_cli.local_paths import ensure_dir, refactor_home
20
20
 
21
- from refactor_cli.auth import ensure_authenticated
22
- from refactor_cli.control_plane import ensure_lease, heartbeat, resolve_policy
23
- from refactor_cli.model_policy import detect_machine_profile, evaluate_model, recommended_model_id
24
- from refactor_cli.runtime_manager import activate_runtime, download_artifact, resolve_runtime_manifest, runtime_status
21
+ from refactorai_cli.auth import ensure_authenticated
22
+ from refactorai_cli.control_plane import ensure_lease, heartbeat, resolve_policy
23
+ from refactorai_cli.model_policy import detect_machine_profile, evaluate_model, recommended_model_id
24
+ from refactorai_cli.runtime_manager import activate_runtime, download_artifact, resolve_runtime_manifest, runtime_status
25
25
 
26
26
  SETUP_DIR = "setup"
27
27
  STATE_FILE = "state.json"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.1.0
3
+ Version: 0.2.2
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,6 @@ 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: refactor-core>=0.1.0
12
11
 
13
12
  # refactorai-cli
14
13
 
@@ -16,7 +15,7 @@ Public CLI package for Refactor.
16
15
 
17
16
  - PyPI package name: `refactorai-cli`
18
17
  - Installed command: `refactor`
19
- - Python module package: `refactor_cli`
18
+ - Python module package: `refactorai_cli`
20
19
 
21
20
  ## Local development install
22
21
 
@@ -0,0 +1,29 @@
1
+ README.md
2
+ pyproject.toml
3
+ refactorai_cli/__init__.py
4
+ refactorai_cli/auth.py
5
+ refactorai_cli/client.py
6
+ refactorai_cli/control_plane.py
7
+ refactorai_cli/credentials.py
8
+ refactorai_cli/local_constitution.py
9
+ refactorai_cli/local_engine_runtime.py
10
+ refactorai_cli/local_paths.py
11
+ refactorai_cli/main.py
12
+ refactorai_cli/model_policy.py
13
+ refactorai_cli/runtime_manager.py
14
+ refactorai_cli/settings.py
15
+ refactorai_cli/setup_flow.py
16
+ refactorai_cli.egg-info/PKG-INFO
17
+ refactorai_cli.egg-info/SOURCES.txt
18
+ refactorai_cli.egg-info/dependency_links.txt
19
+ refactorai_cli.egg-info/entry_points.txt
20
+ refactorai_cli.egg-info/requires.txt
21
+ refactorai_cli.egg-info/top_level.txt
22
+ refactorai_cli/commands/__init__.py
23
+ refactorai_cli/commands/auth_cmds.py
24
+ refactorai_cli/commands/engine_cmds.py
25
+ refactorai_cli/commands/model_cmds.py
26
+ refactorai_cli/commands/rules_cmds.py
27
+ refactorai_cli/commands/run_cmds.py
28
+ refactorai_cli/commands/runtime_cmds.py
29
+ refactorai_cli/commands/setup_cmds.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ refactor = refactorai_cli.main:app
@@ -2,4 +2,3 @@ typer>=0.12.0
2
2
  httpx>=0.27.0
3
3
  rich>=13.7.0
4
4
  PyYAML>=6.0.1
5
- refactor-core>=0.1.0
@@ -0,0 +1 @@
1
+ refactorai_cli
@@ -1,26 +0,0 @@
1
- README.md
2
- pyproject.toml
3
- refactor_cli/__init__.py
4
- refactor_cli/auth.py
5
- refactor_cli/client.py
6
- refactor_cli/control_plane.py
7
- refactor_cli/credentials.py
8
- refactor_cli/main.py
9
- refactor_cli/model_policy.py
10
- refactor_cli/runtime_manager.py
11
- refactor_cli/settings.py
12
- refactor_cli/setup_flow.py
13
- refactor_cli/commands/__init__.py
14
- refactor_cli/commands/auth_cmds.py
15
- refactor_cli/commands/engine_cmds.py
16
- refactor_cli/commands/model_cmds.py
17
- refactor_cli/commands/rules_cmds.py
18
- refactor_cli/commands/run_cmds.py
19
- refactor_cli/commands/runtime_cmds.py
20
- refactor_cli/commands/setup_cmds.py
21
- refactorai_cli.egg-info/PKG-INFO
22
- refactorai_cli.egg-info/SOURCES.txt
23
- refactorai_cli.egg-info/dependency_links.txt
24
- refactorai_cli.egg-info/entry_points.txt
25
- refactorai_cli.egg-info/requires.txt
26
- refactorai_cli.egg-info/top_level.txt
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- refactor = refactor_cli.main:app
@@ -1 +0,0 @@
1
- refactor_cli
File without changes