refactorai-cli 0.2.0__tar.gz → 0.2.3__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.
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/PKG-INFO +5 -2
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/README.md +4 -0
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/pyproject.toml +1 -2
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli/__init__.py +1 -1
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli/auth.py +1 -1
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli/commands/engine_cmds.py +1 -1
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli/commands/model_cmds.py +1 -1
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli/control_plane.py +1 -1
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli/credentials.py +2 -2
- refactorai_cli-0.2.3/refactorai_cli/local_constitution.py +76 -0
- refactorai_cli-0.2.3/refactorai_cli/local_engine_runtime.py +243 -0
- refactorai_cli-0.2.3/refactorai_cli/local_paths.py +40 -0
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli/main.py +2 -19
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli/runtime_manager.py +1 -1
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli/settings.py +1 -1
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli/setup_flow.py +2 -2
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli.egg-info/PKG-INFO +5 -2
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli.egg-info/SOURCES.txt +3 -0
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli.egg-info/requires.txt +0 -1
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli/client.py +0 -0
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli/commands/__init__.py +0 -0
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli/commands/auth_cmds.py +0 -0
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli/commands/rules_cmds.py +0 -0
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli/commands/run_cmds.py +0 -0
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli/commands/runtime_cmds.py +0 -0
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli/commands/setup_cmds.py +0 -0
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli/model_policy.py +0 -0
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli.egg-info/dependency_links.txt +0 -0
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli.egg-info/entry_points.txt +0 -0
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/refactorai_cli.egg-info/top_level.txt +0 -0
- {refactorai_cli-0.2.0 → refactorai_cli-0.2.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: refactorai-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
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
|
|
|
@@ -18,6 +17,10 @@ Public CLI package for Refactor.
|
|
|
18
17
|
- Installed command: `refactor`
|
|
19
18
|
- Python module package: `refactorai_cli`
|
|
20
19
|
|
|
20
|
+
By default, the CLI targets `https://api.refactorai.codes`.
|
|
21
|
+
Use `REFACTOR_PLATFORM_URL` only when you need to override the control-plane URL
|
|
22
|
+
(for self-hosted or local development environments).
|
|
23
|
+
|
|
21
24
|
## Local development install
|
|
22
25
|
|
|
23
26
|
From repository root:
|
|
@@ -6,6 +6,10 @@ Public CLI package for Refactor.
|
|
|
6
6
|
- Installed command: `refactor`
|
|
7
7
|
- Python module package: `refactorai_cli`
|
|
8
8
|
|
|
9
|
+
By default, the CLI targets `https://api.refactorai.codes`.
|
|
10
|
+
Use `REFACTOR_PLATFORM_URL` only when you need to override the control-plane URL
|
|
11
|
+
(for self-hosted or local development environments).
|
|
12
|
+
|
|
9
13
|
## Local development install
|
|
10
14
|
|
|
11
15
|
From repository root:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "refactorai-cli"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.3"
|
|
4
4
|
description = "Local-first CLI for the refactor platform"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -9,7 +9,6 @@ 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]
|
|
@@ -11,7 +11,7 @@ from dataclasses import dataclass
|
|
|
11
11
|
from datetime import datetime, timezone
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
|
-
from
|
|
14
|
+
from refactorai_cli.local_paths import ensure_dir, validation_cache_dir
|
|
15
15
|
|
|
16
16
|
from refactorai_cli.client import PlatformClient, PlatformError
|
|
17
17
|
from refactorai_cli.credentials import ResolvedKey, resolve_developer_key
|
|
@@ -6,7 +6,7 @@ import typer
|
|
|
6
6
|
from rich.console import Console
|
|
7
7
|
from rich.table import Table
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from refactorai_cli.local_engine_runtime import DEFAULT_ENGINE_CONTAINER, probe_model, read_engine_state, resolve_runtime
|
|
10
10
|
|
|
11
11
|
from refactorai_cli.model_policy import (
|
|
12
12
|
detect_machine_profile,
|
|
@@ -12,7 +12,7 @@ from pathlib import Path
|
|
|
12
12
|
|
|
13
13
|
import httpx
|
|
14
14
|
|
|
15
|
-
from
|
|
15
|
+
from refactorai_cli.local_paths import ensure_dir, refactor_home
|
|
16
16
|
|
|
17
17
|
from refactorai_cli.credentials import resolve_developer_key
|
|
18
18
|
from refactorai_cli.settings import platform_url, policy_signing_key
|
|
@@ -13,8 +13,8 @@ import os
|
|
|
13
13
|
from dataclasses import dataclass
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
|
-
from
|
|
17
|
-
from
|
|
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
19
|
from refactorai_cli.settings import env_api_key
|
|
20
20
|
|
|
@@ -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)
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import typer
|
|
6
6
|
|
|
7
7
|
from refactorai_cli import __version__
|
|
8
|
-
from refactorai_cli.commands import auth_cmds, engine_cmds, model_cmds,
|
|
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
|
-
#
|
|
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")
|
|
@@ -17,7 +17,7 @@ from pathlib import Path
|
|
|
17
17
|
|
|
18
18
|
import httpx
|
|
19
19
|
|
|
20
|
-
from
|
|
20
|
+
from refactorai_cli.local_paths import ensure_dir, refactor_home
|
|
21
21
|
|
|
22
22
|
from refactorai_cli.control_plane import ensure_lease
|
|
23
23
|
from refactorai_cli.credentials import resolve_developer_key
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
|
|
7
|
-
DEFAULT_PLATFORM_URL = "
|
|
7
|
+
DEFAULT_PLATFORM_URL = "https://api.refactorai.codes"
|
|
8
8
|
ENV_API_KEY = "REFACTOR_API_KEY"
|
|
9
9
|
ENV_PLATFORM_URL = "REFACTOR_PLATFORM_URL"
|
|
10
10
|
ENV_POLICY_SIGNING_KEY = "REFACTOR_POLICY_SIGNING_KEY"
|
|
@@ -9,14 +9,14 @@ from datetime import datetime, timezone
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Callable
|
|
11
11
|
|
|
12
|
-
from
|
|
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
|
|
19
|
+
from refactorai_cli.local_paths import ensure_dir, refactor_home
|
|
20
20
|
|
|
21
21
|
from refactorai_cli.auth import ensure_authenticated
|
|
22
22
|
from refactorai_cli.control_plane import ensure_lease, heartbeat, resolve_policy
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: refactorai-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
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
|
|
|
@@ -18,6 +17,10 @@ Public CLI package for Refactor.
|
|
|
18
17
|
- Installed command: `refactor`
|
|
19
18
|
- Python module package: `refactorai_cli`
|
|
20
19
|
|
|
20
|
+
By default, the CLI targets `https://api.refactorai.codes`.
|
|
21
|
+
Use `REFACTOR_PLATFORM_URL` only when you need to override the control-plane URL
|
|
22
|
+
(for self-hosted or local development environments).
|
|
23
|
+
|
|
21
24
|
## Local development install
|
|
22
25
|
|
|
23
26
|
From repository root:
|
|
@@ -5,6 +5,9 @@ refactorai_cli/auth.py
|
|
|
5
5
|
refactorai_cli/client.py
|
|
6
6
|
refactorai_cli/control_plane.py
|
|
7
7
|
refactorai_cli/credentials.py
|
|
8
|
+
refactorai_cli/local_constitution.py
|
|
9
|
+
refactorai_cli/local_engine_runtime.py
|
|
10
|
+
refactorai_cli/local_paths.py
|
|
8
11
|
refactorai_cli/main.py
|
|
9
12
|
refactorai_cli/model_policy.py
|
|
10
13
|
refactorai_cli/runtime_manager.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|