refactorai-cli 0.2.3__tar.gz → 0.2.5__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.3 → refactorai_cli-0.2.5}/PKG-INFO +1 -1
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/pyproject.toml +1 -1
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/__init__.py +1 -1
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/commands/setup_cmds.py +1 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/local_engine_runtime.py +27 -5
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/setup_flow.py +63 -9
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli.egg-info/PKG-INFO +1 -1
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/README.md +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/auth.py +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/client.py +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/commands/__init__.py +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/commands/auth_cmds.py +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/commands/engine_cmds.py +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/commands/model_cmds.py +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/commands/rules_cmds.py +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/commands/run_cmds.py +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/commands/runtime_cmds.py +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/control_plane.py +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/credentials.py +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/local_constitution.py +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/local_paths.py +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/main.py +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/model_policy.py +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/runtime_manager.py +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli/settings.py +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli.egg-info/SOURCES.txt +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli.egg-info/dependency_links.txt +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli.egg-info/entry_points.txt +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli.egg-info/requires.txt +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/refactorai_cli.egg-info/top_level.txt +0 -0
- {refactorai_cli-0.2.3 → refactorai_cli-0.2.5}/setup.cfg +0 -0
|
@@ -53,6 +53,7 @@ def setup(
|
|
|
53
53
|
resume=resume,
|
|
54
54
|
from_stage=from_stage,
|
|
55
55
|
ask_approval=lambda prompt: _ask_approval(yes, prompt),
|
|
56
|
+
on_progress=lambda message: console.print(f"[cyan]{message}[/cyan]"),
|
|
56
57
|
)
|
|
57
58
|
except SetupError as exc:
|
|
58
59
|
console.print(f"[red]Setup failed:[/red] {exc}")
|
|
@@ -7,6 +7,7 @@ remain usable without a separate ``refactor-core`` package dependency.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import json
|
|
10
|
+
import re
|
|
10
11
|
import shutil
|
|
11
12
|
import subprocess
|
|
12
13
|
from datetime import datetime, timezone
|
|
@@ -14,7 +15,7 @@ from pathlib import Path
|
|
|
14
15
|
|
|
15
16
|
from refactorai_cli.local_paths import ensure_dir, refactor_home
|
|
16
17
|
|
|
17
|
-
DEFAULT_ENGINE_IMAGE = "docker.io/
|
|
18
|
+
DEFAULT_ENGINE_IMAGE = "docker.io/ollama/ollama:latest"
|
|
18
19
|
DEFAULT_ENGINE_CONTAINER = "refactor-engine"
|
|
19
20
|
DEFAULT_ENGINE_PORT = 17777
|
|
20
21
|
DEFAULT_ENGINE_PROFILE = "cpu-small"
|
|
@@ -22,6 +23,8 @@ ENGINE_DIR = "engine"
|
|
|
22
23
|
ENGINE_STATE_FILE = "engine.json"
|
|
23
24
|
ENGINE_MODELS_DIR = "models"
|
|
24
25
|
ENGINE_CACHE_DIR = "cache/ollama"
|
|
26
|
+
_ANSI_RE = re.compile(r"\x1b\[[0-9;?]*[A-Za-z]")
|
|
27
|
+
_CONTROL_RE = re.compile(r"[\x00-\x08\x0b-\x1f\x7f]")
|
|
25
28
|
|
|
26
29
|
|
|
27
30
|
def _now_iso() -> str:
|
|
@@ -85,6 +88,13 @@ def _run(cmd: list[str]) -> subprocess.CompletedProcess:
|
|
|
85
88
|
return subprocess.run(cmd, capture_output=True, text=True)
|
|
86
89
|
|
|
87
90
|
|
|
91
|
+
def _clean_output(text: str) -> str:
|
|
92
|
+
cleaned = _ANSI_RE.sub("", text or "")
|
|
93
|
+
cleaned = cleaned.replace("\r", "\n")
|
|
94
|
+
cleaned = _CONTROL_RE.sub("", cleaned)
|
|
95
|
+
return "\n".join(line.strip() for line in cleaned.splitlines() if line.strip())
|
|
96
|
+
|
|
97
|
+
|
|
88
98
|
def _exists(runtime: str, name: str) -> bool:
|
|
89
99
|
proc = _run([runtime, "container", "exists", name])
|
|
90
100
|
return proc.returncode == 0
|
|
@@ -96,10 +106,22 @@ def _running(runtime: str, name: str) -> bool:
|
|
|
96
106
|
|
|
97
107
|
|
|
98
108
|
def _health(runtime: str, name: str) -> bool:
|
|
99
|
-
|
|
109
|
+
has_binary = _run([runtime, "exec", name, "sh", "-lc", "command -v ollama >/dev/null 2>&1"])
|
|
110
|
+
if has_binary.returncode != 0:
|
|
111
|
+
return False
|
|
112
|
+
proc = _run([runtime, "exec", name, "sh", "-lc", "ollama list >/dev/null 2>&1"])
|
|
100
113
|
return proc.returncode == 0
|
|
101
114
|
|
|
102
115
|
|
|
116
|
+
def _run_args_for_image(image: str) -> list[str]:
|
|
117
|
+
normalized = str(image or "").strip().lower()
|
|
118
|
+
if "ollama/ollama" in normalized:
|
|
119
|
+
# Let the container run its default command (ollama serve), which keeps
|
|
120
|
+
# the model API available for pull/show operations.
|
|
121
|
+
return []
|
|
122
|
+
return ["sleep", "infinity"]
|
|
123
|
+
|
|
124
|
+
|
|
103
125
|
def ensure_engine_up(
|
|
104
126
|
*,
|
|
105
127
|
runtime: str,
|
|
@@ -114,6 +136,7 @@ def ensure_engine_up(
|
|
|
114
136
|
if rebuild and _exists(runtime, container_name):
|
|
115
137
|
_run([runtime, "rm", "-f", container_name])
|
|
116
138
|
if not _exists(runtime, container_name):
|
|
139
|
+
command_args = _run_args_for_image(image)
|
|
117
140
|
proc = _run(
|
|
118
141
|
[
|
|
119
142
|
runtime,
|
|
@@ -128,8 +151,7 @@ def ensure_engine_up(
|
|
|
128
151
|
"-v",
|
|
129
152
|
f"{cache_dir}:/root/.cache/ollama:Z",
|
|
130
153
|
image,
|
|
131
|
-
|
|
132
|
-
"infinity",
|
|
154
|
+
*command_args,
|
|
133
155
|
]
|
|
134
156
|
)
|
|
135
157
|
if proc.returncode != 0:
|
|
@@ -223,7 +245,7 @@ def pull_model(*, runtime: str, container_name: str, model_id: str) -> tuple[boo
|
|
|
223
245
|
if probe.returncode != 0:
|
|
224
246
|
return False, "Engine image does not include `ollama`; use a model-capable engine image."
|
|
225
247
|
proc = _run([runtime, "exec", container_name, "sh", "-lc", f"ollama pull {model_id}"])
|
|
226
|
-
text = (proc.stdout or proc.stderr or "")
|
|
248
|
+
text = _clean_output(proc.stdout or proc.stderr or "")
|
|
227
249
|
if proc.returncode != 0:
|
|
228
250
|
return False, text or "Model pull failed."
|
|
229
251
|
return True, text or "Model pull complete."
|
|
@@ -7,10 +7,12 @@ import shutil
|
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from datetime import datetime, timezone
|
|
9
9
|
from pathlib import Path
|
|
10
|
+
from inspect import signature
|
|
10
11
|
from typing import Callable
|
|
11
12
|
|
|
12
13
|
from refactorai_cli.local_engine_runtime import (
|
|
13
14
|
DEFAULT_ENGINE_CONTAINER,
|
|
15
|
+
DEFAULT_ENGINE_IMAGE,
|
|
14
16
|
engine_status,
|
|
15
17
|
ensure_engine_up,
|
|
16
18
|
pull_model,
|
|
@@ -141,7 +143,7 @@ def get_setup_diagnostics() -> dict:
|
|
|
141
143
|
return {"state": state, "stages": stages}
|
|
142
144
|
|
|
143
145
|
|
|
144
|
-
def _stage_s1_precheck(_ask_approval: Callable[[str], bool]) -> dict:
|
|
146
|
+
def _stage_s1_precheck(_ask_approval: Callable[[str], bool], _emit: Callable[[str], None] | None = None) -> dict:
|
|
145
147
|
profile = detect_machine_profile()
|
|
146
148
|
runtime, reason = resolve_engine_runtime("podman")
|
|
147
149
|
disk = shutil.disk_usage(str(refactor_home()))
|
|
@@ -158,7 +160,7 @@ def _stage_s1_precheck(_ask_approval: Callable[[str], bool]) -> dict:
|
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
|
|
161
|
-
def _stage_s2_auth(_ask_approval: Callable[[str], bool]) -> dict:
|
|
163
|
+
def _stage_s2_auth(_ask_approval: Callable[[str], bool], _emit: Callable[[str], None] | None = None) -> dict:
|
|
162
164
|
auth = ensure_authenticated(force_remote=True)
|
|
163
165
|
lease = ensure_lease(force_refresh=True)
|
|
164
166
|
return {
|
|
@@ -170,11 +172,17 @@ def _stage_s2_auth(_ask_approval: Callable[[str], bool]) -> dict:
|
|
|
170
172
|
}
|
|
171
173
|
|
|
172
174
|
|
|
173
|
-
def _stage_s3_runtime(ask_approval: Callable[[str], bool]) -> dict:
|
|
175
|
+
def _stage_s3_runtime(ask_approval: Callable[[str], bool], emit: Callable[[str], None] | None = None) -> dict:
|
|
176
|
+
if emit:
|
|
177
|
+
emit("Resolving runtime manifest from control plane...")
|
|
174
178
|
manifest = resolve_runtime_manifest(channel="stable")
|
|
175
179
|
if not ask_approval(f"Install runtime artifact version {manifest.runtime_version}?"):
|
|
176
180
|
raise SetupError("Runtime installation was not approved.")
|
|
181
|
+
if emit:
|
|
182
|
+
emit(f"Downloading runtime artifact {manifest.runtime_version}...")
|
|
177
183
|
artifact = download_artifact(manifest.artifact_url)
|
|
184
|
+
if emit:
|
|
185
|
+
emit("Verifying and activating runtime artifact...")
|
|
178
186
|
path = activate_runtime(manifest, artifact, channel="stable")
|
|
179
187
|
status = runtime_status()
|
|
180
188
|
return {
|
|
@@ -199,7 +207,9 @@ def _selected_backend() -> str:
|
|
|
199
207
|
return backend
|
|
200
208
|
|
|
201
209
|
|
|
202
|
-
def _stage_s4_backend_choice(
|
|
210
|
+
def _stage_s4_backend_choice(
|
|
211
|
+
ask_approval: Callable[[str], bool], _emit: Callable[[str], None] | None = None
|
|
212
|
+
) -> dict:
|
|
203
213
|
if ask_approval("Install local refactor-server and local model now?"):
|
|
204
214
|
backend = BACKEND_LOCAL_SERVER
|
|
205
215
|
else:
|
|
@@ -210,7 +220,7 @@ def _stage_s4_backend_choice(ask_approval: Callable[[str], bool]) -> dict:
|
|
|
210
220
|
}
|
|
211
221
|
|
|
212
222
|
|
|
213
|
-
def _stage_s5_backend_bootstrap(ask_approval: Callable[[str], bool]) -> dict:
|
|
223
|
+
def _stage_s5_backend_bootstrap(ask_approval: Callable[[str], bool], emit: Callable[[str], None] | None = None) -> dict:
|
|
214
224
|
backend = _selected_backend()
|
|
215
225
|
if backend == BACKEND_BYOK:
|
|
216
226
|
return {
|
|
@@ -221,16 +231,39 @@ def _stage_s5_backend_bootstrap(ask_approval: Callable[[str], bool]) -> dict:
|
|
|
221
231
|
}
|
|
222
232
|
if not ask_approval("Install/start local refactor-server and bootstrap model now?"):
|
|
223
233
|
raise SetupError("Local server bootstrap was not approved.")
|
|
234
|
+
if emit:
|
|
235
|
+
emit("Checking local engine runtime availability...")
|
|
224
236
|
runtime, reason = resolve_engine_runtime("podman")
|
|
225
237
|
if not runtime:
|
|
226
238
|
raise SetupError(reason or "Engine runtime unavailable.")
|
|
239
|
+
if emit:
|
|
240
|
+
emit("Starting local refactor-engine container...")
|
|
227
241
|
ok, message, state = ensure_engine_up(runtime=runtime)
|
|
228
242
|
if not ok:
|
|
229
243
|
raise SetupError(f"Local server bootstrap failed: {message}")
|
|
230
244
|
estatus = engine_status(runtime=runtime, container_name=DEFAULT_ENGINE_CONTAINER)
|
|
231
245
|
if str(estatus.get("status") or "").lower() != "ready":
|
|
232
|
-
|
|
246
|
+
# If a stale container from older installs exists (for example an image
|
|
247
|
+
# without ollama), rebuild once with the default engine image.
|
|
248
|
+
if emit:
|
|
249
|
+
emit("Detected non-ready engine container. Rebuilding once with default model runtime image...")
|
|
250
|
+
ok, message, _state = ensure_engine_up(
|
|
251
|
+
runtime=runtime,
|
|
252
|
+
image=DEFAULT_ENGINE_IMAGE,
|
|
253
|
+
container_name=DEFAULT_ENGINE_CONTAINER,
|
|
254
|
+
rebuild=True,
|
|
255
|
+
)
|
|
256
|
+
if not ok:
|
|
257
|
+
raise SetupError(f"Local server bootstrap failed: {message}")
|
|
258
|
+
estatus = engine_status(runtime=runtime, container_name=DEFAULT_ENGINE_CONTAINER)
|
|
259
|
+
if str(estatus.get("status") or "").lower() != "ready":
|
|
260
|
+
raise SetupError(
|
|
261
|
+
f"Local server is not ready (status={estatus.get('status')}). "
|
|
262
|
+
"Run `refactor engine status` and retry from S5."
|
|
263
|
+
)
|
|
233
264
|
|
|
265
|
+
if emit:
|
|
266
|
+
emit("Resolving signed model policy...")
|
|
234
267
|
policy = resolve_policy(force_refresh=False)
|
|
235
268
|
profile = detect_machine_profile()
|
|
236
269
|
model_id = recommended_model_id(policy, profile=str(profile["profile"]))
|
|
@@ -245,6 +278,11 @@ def _stage_s5_backend_bootstrap(ask_approval: Callable[[str], bool]) -> dict:
|
|
|
245
278
|
runtime, reason = resolve_engine_runtime("podman")
|
|
246
279
|
if not runtime:
|
|
247
280
|
raise SetupError(reason or "Engine runtime unavailable.")
|
|
281
|
+
if emit:
|
|
282
|
+
emit(
|
|
283
|
+
f"Pulling recommended model '{model_id}' in local engine "
|
|
284
|
+
"(this can take several minutes on first download)..."
|
|
285
|
+
)
|
|
248
286
|
ok, message = pull_model(runtime=runtime, container_name=DEFAULT_ENGINE_CONTAINER, model_id=model_id)
|
|
249
287
|
if not ok:
|
|
250
288
|
raise SetupError(f"Model pull failed: {message}")
|
|
@@ -257,7 +295,7 @@ def _stage_s5_backend_bootstrap(ask_approval: Callable[[str], bool]) -> dict:
|
|
|
257
295
|
}
|
|
258
296
|
|
|
259
297
|
|
|
260
|
-
def _stage_s6_validate(_ask_approval: Callable[[str], bool]) -> dict:
|
|
298
|
+
def _stage_s6_validate(_ask_approval: Callable[[str], bool], _emit: Callable[[str], None] | None = None) -> dict:
|
|
261
299
|
backend = _selected_backend()
|
|
262
300
|
runtime_status_data = runtime_status()
|
|
263
301
|
if not runtime_status_data.get("active_version") or not runtime_status_data.get("active_artifact_exists"):
|
|
@@ -293,7 +331,7 @@ def _stage_s6_validate(_ask_approval: Callable[[str], bool]) -> dict:
|
|
|
293
331
|
}
|
|
294
332
|
|
|
295
333
|
|
|
296
|
-
STAGE_HANDLERS: dict[str, Callable[
|
|
334
|
+
STAGE_HANDLERS: dict[str, Callable[..., dict]] = {
|
|
297
335
|
"S1": _stage_s1_precheck,
|
|
298
336
|
"S2": _stage_s2_auth,
|
|
299
337
|
"S3": _stage_s3_runtime,
|
|
@@ -303,12 +341,24 @@ STAGE_HANDLERS: dict[str, Callable[[Callable[[str], bool]], dict]] = {
|
|
|
303
341
|
}
|
|
304
342
|
|
|
305
343
|
|
|
344
|
+
def _invoke_stage_handler(
|
|
345
|
+
handler: Callable,
|
|
346
|
+
ask_approval: Callable[[str], bool],
|
|
347
|
+
on_progress: Callable[[str], None] | None,
|
|
348
|
+
) -> dict:
|
|
349
|
+
param_names = list(signature(handler).parameters.keys())
|
|
350
|
+
if any(name in {"emit", "_emit", "on_progress"} for name in param_names):
|
|
351
|
+
return handler(ask_approval, on_progress)
|
|
352
|
+
return handler(ask_approval)
|
|
353
|
+
|
|
354
|
+
|
|
306
355
|
def run_setup(
|
|
307
356
|
*,
|
|
308
357
|
auto_approve: bool = False,
|
|
309
358
|
resume: bool = False,
|
|
310
359
|
from_stage: str | None = None,
|
|
311
360
|
ask_approval: Callable[[str], bool] | None = None,
|
|
361
|
+
on_progress: Callable[[str], None] | None = None,
|
|
312
362
|
) -> SetupResult:
|
|
313
363
|
if ask_approval is None:
|
|
314
364
|
ask_approval = lambda _prompt: bool(auto_approve)
|
|
@@ -344,6 +394,8 @@ def run_setup(
|
|
|
344
394
|
}
|
|
345
395
|
)
|
|
346
396
|
for stage_id in STAGE_IDS[start_index:]:
|
|
397
|
+
if on_progress:
|
|
398
|
+
on_progress(f"Running stage {stage_id} ({STAGE_NAMES.get(stage_id, stage_id)})...")
|
|
347
399
|
write_setup_state(
|
|
348
400
|
{
|
|
349
401
|
"status": "in_progress",
|
|
@@ -355,7 +407,7 @@ def run_setup(
|
|
|
355
407
|
)
|
|
356
408
|
handler = STAGE_HANDLERS[stage_id]
|
|
357
409
|
try:
|
|
358
|
-
output = handler
|
|
410
|
+
output = _invoke_stage_handler(handler, ask_approval, on_progress)
|
|
359
411
|
except SetupError as exc:
|
|
360
412
|
write_setup_state(
|
|
361
413
|
{
|
|
@@ -394,6 +446,8 @@ def run_setup(
|
|
|
394
446
|
write_stage_output(stage_id, output)
|
|
395
447
|
if stage_id not in completed:
|
|
396
448
|
completed.append(stage_id)
|
|
449
|
+
if on_progress:
|
|
450
|
+
on_progress(f"Completed stage {stage_id}.")
|
|
397
451
|
final_backend = _normalize_backend(read_setup_state().get("execution_backend"))
|
|
398
452
|
write_setup_state(
|
|
399
453
|
{
|
|
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
|
|
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
|