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.
- {refactorai_cli-0.1.0 → refactorai_cli-0.2.2}/PKG-INFO +2 -3
- {refactorai_cli-0.1.0 → refactorai_cli-0.2.2}/README.md +1 -1
- {refactorai_cli-0.1.0 → refactorai_cli-0.2.2}/pyproject.toml +3 -4
- {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/__init__.py +1 -1
- {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/auth.py +3 -3
- {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/client.py +1 -1
- {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/auth_cmds.py +4 -4
- {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/engine_cmds.py +2 -2
- {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/model_cmds.py +2 -2
- {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/run_cmds.py +2 -2
- {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/runtime_cmds.py +4 -4
- {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/setup_cmds.py +1 -1
- {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/control_plane.py +3 -3
- {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/credentials.py +3 -3
- refactorai_cli-0.2.2/refactorai_cli/local_constitution.py +76 -0
- refactorai_cli-0.2.2/refactorai_cli/local_engine_runtime.py +243 -0
- refactorai_cli-0.2.2/refactorai_cli/local_paths.py +40 -0
- {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/main.py +3 -20
- {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/model_policy.py +1 -1
- {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/runtime_manager.py +21 -17
- {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/setup_flow.py +6 -6
- {refactorai_cli-0.1.0 → refactorai_cli-0.2.2}/refactorai_cli.egg-info/PKG-INFO +2 -3
- refactorai_cli-0.2.2/refactorai_cli.egg-info/SOURCES.txt +29 -0
- refactorai_cli-0.2.2/refactorai_cli.egg-info/entry_points.txt +2 -0
- {refactorai_cli-0.1.0 → refactorai_cli-0.2.2}/refactorai_cli.egg-info/requires.txt +0 -1
- refactorai_cli-0.2.2/refactorai_cli.egg-info/top_level.txt +1 -0
- refactorai_cli-0.1.0/refactorai_cli.egg-info/SOURCES.txt +0 -26
- refactorai_cli-0.1.0/refactorai_cli.egg-info/entry_points.txt +0 -2
- refactorai_cli-0.1.0/refactorai_cli.egg-info/top_level.txt +0 -1
- {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/__init__.py +0 -0
- {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/rules_cmds.py +0 -0
- {refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/settings.py +0 -0
- {refactorai_cli-0.1.0 → refactorai_cli-0.2.2}/refactorai_cli.egg-info/dependency_links.txt +0 -0
- {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.
|
|
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: `
|
|
18
|
+
- Python module package: `refactorai_cli`
|
|
20
19
|
|
|
21
20
|
## Local development install
|
|
22
21
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "refactorai-cli"
|
|
3
|
-
version = "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 = "
|
|
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 = ["
|
|
22
|
+
packages = ["refactorai_cli", "refactorai_cli.commands"]
|
|
@@ -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
|
|
14
|
+
from refactorai_cli.local_paths import ensure_dir, validation_cache_dir
|
|
15
15
|
|
|
16
|
-
from
|
|
17
|
-
from
|
|
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):
|
{refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/auth_cmds.py
RENAMED
|
@@ -5,10 +5,10 @@ from __future__ import annotations
|
|
|
5
5
|
import typer
|
|
6
6
|
from rich.console import Console
|
|
7
7
|
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
from
|
|
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
|
|
{refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/engine_cmds.py
RENAMED
|
@@ -7,7 +7,7 @@ import os
|
|
|
7
7
|
import typer
|
|
8
8
|
from rich.console import Console
|
|
9
9
|
|
|
10
|
-
from
|
|
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
|
|
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.")
|
{refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/model_cmds.py
RENAMED
|
@@ -6,9 +6,9 @@ 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
|
-
from
|
|
11
|
+
from refactorai_cli.model_policy import (
|
|
12
12
|
detect_machine_profile,
|
|
13
13
|
evaluate_model,
|
|
14
14
|
get_policy_bundle,
|
{refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/run_cmds.py
RENAMED
|
@@ -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
|
|
92
|
-
from
|
|
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
|
|
{refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/runtime_cmds.py
RENAMED
|
@@ -5,9 +5,9 @@ from __future__ import annotations
|
|
|
5
5
|
import typer
|
|
6
6
|
from rich.console import Console
|
|
7
7
|
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
from
|
|
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
|
|
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:
|
{refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/setup_cmds.py
RENAMED
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import typer
|
|
6
6
|
from rich.console import Console
|
|
7
7
|
|
|
8
|
-
from
|
|
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
|
|
15
|
+
from refactorai_cli.local_paths import ensure_dir, refactor_home
|
|
16
16
|
|
|
17
|
-
from
|
|
18
|
-
from
|
|
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
|
|
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
|
-
from
|
|
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
|
|
8
|
-
from
|
|
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
|
-
#
|
|
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")
|
{refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/runtime_manager.py
RENAMED
|
@@ -17,11 +17,11 @@ 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
|
-
from
|
|
23
|
-
from
|
|
24
|
-
from
|
|
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
|
|
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
|
-
from
|
|
22
|
-
from
|
|
23
|
-
from
|
|
24
|
-
from
|
|
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.
|
|
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: `
|
|
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 @@
|
|
|
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 +0,0 @@
|
|
|
1
|
-
refactor_cli
|
{refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/__init__.py
RENAMED
|
File without changes
|
{refactorai_cli-0.1.0/refactor_cli → refactorai_cli-0.2.2/refactorai_cli}/commands/rules_cmds.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|