refactorai-cli 0.2.9__tar.gz → 0.2.11__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 (32) hide show
  1. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/PKG-INFO +2 -2
  2. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/README.md +1 -1
  3. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/pyproject.toml +1 -1
  4. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/__init__.py +1 -1
  5. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/commands/run_cmds.py +65 -1
  6. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/control_plane.py +2 -1
  7. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/local_engine_runtime.py +69 -10
  8. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/runtime_manager.py +2 -1
  9. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli.egg-info/PKG-INFO +2 -2
  10. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/auth.py +0 -0
  11. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/client.py +0 -0
  12. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/commands/__init__.py +0 -0
  13. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/commands/auth_cmds.py +0 -0
  14. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/commands/engine_cmds.py +0 -0
  15. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/commands/model_cmds.py +0 -0
  16. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/commands/rules_cmds.py +0 -0
  17. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/commands/runtime_cmds.py +0 -0
  18. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/commands/runtime_proxy_cmds.py +0 -0
  19. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/commands/setup_cmds.py +0 -0
  20. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/credentials.py +0 -0
  21. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/local_constitution.py +0 -0
  22. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/local_paths.py +0 -0
  23. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/main.py +0 -0
  24. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/model_policy.py +0 -0
  25. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/settings.py +0 -0
  26. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli/setup_flow.py +0 -0
  27. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli.egg-info/SOURCES.txt +0 -0
  28. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli.egg-info/dependency_links.txt +0 -0
  29. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli.egg-info/entry_points.txt +0 -0
  30. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli.egg-info/requires.txt +0 -0
  31. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/refactorai_cli.egg-info/top_level.txt +0 -0
  32. {refactorai_cli-0.2.9 → refactorai_cli-0.2.11}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.2.9
3
+ Version: 0.2.11
4
4
  Summary: Local-first CLI for the refactor platform
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -53,6 +53,6 @@ python -m twine upload ./refactorai-cli/dist/*
53
53
  ## Install test (local)
54
54
 
55
55
  ```bash
56
- python -m pip install ./refactorai-cli/dist/refactorai_cli-0.1.0-py3-none-any.whl
56
+ python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.11-py3-none-any.whl
57
57
  refactor --version
58
58
  ```
@@ -42,6 +42,6 @@ python -m twine upload ./refactorai-cli/dist/*
42
42
  ## Install test (local)
43
43
 
44
44
  ```bash
45
- python -m pip install ./refactorai-cli/dist/refactorai_cli-0.1.0-py3-none-any.whl
45
+ python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.11-py3-none-any.whl
46
46
  refactor --version
47
47
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "refactorai-cli"
3
- version = "0.2.9"
3
+ version = "0.2.11"
4
4
  description = "Local-first CLI for the refactor platform"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -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.2.9"
8
+ __version__ = "0.2.11"
@@ -8,6 +8,7 @@ generation and application.
8
8
  from __future__ import annotations
9
9
 
10
10
  import os
11
+ import json
11
12
  import re
12
13
  import shlex
13
14
  import shutil
@@ -92,10 +93,62 @@ from refactor_core.store import (
92
93
  from refactor_core.testcmd import resolve_test_command
93
94
 
94
95
  from refactorai_cli.auth import AuthError, ensure_authenticated
96
+ from refactorai_cli.local_engine_runtime import DEFAULT_ENGINE_PORT, read_engine_state
95
97
  from refactorai_cli.settings import platform_url
98
+ from refactorai_cli.setup_flow import (
99
+ BACKEND_BYOK,
100
+ BACKEND_LOCAL_SERVER,
101
+ read_setup_state,
102
+ stage_output_path,
103
+ )
96
104
 
97
105
  console = Console()
98
106
 
107
+
108
+ def _read_stage_output(stage_id: str) -> dict:
109
+ path = stage_output_path(stage_id)
110
+ if not path.is_file():
111
+ return {}
112
+ try:
113
+ payload = json.loads(path.read_text(encoding="utf-8"))
114
+ except (json.JSONDecodeError, OSError):
115
+ return {}
116
+ output = payload.get("output")
117
+ return output if isinstance(output, dict) else {}
118
+
119
+
120
+ def _setup_recommended_model_id() -> str:
121
+ s5_output = _read_stage_output("S5")
122
+ model_id = str(s5_output.get("recommended_model_id") or "").strip()
123
+ return model_id or "qwen2.5-coder:1.5b-instruct"
124
+
125
+
126
+ def _setup_engine_base_url() -> str:
127
+ state = read_engine_state()
128
+ try:
129
+ port = int(state.get("port", DEFAULT_ENGINE_PORT))
130
+ except (TypeError, ValueError):
131
+ port = DEFAULT_ENGINE_PORT
132
+ return f"http://127.0.0.1:{port}/v1"
133
+
134
+
135
+ def _default_config_for_init() -> tuple[str, str]:
136
+ setup_state = read_setup_state()
137
+ backend = str(setup_state.get("execution_backend") or "").strip().lower()
138
+ if backend == BACKEND_LOCAL_SERVER:
139
+ model_id = _setup_recommended_model_id()
140
+ base_url = _setup_engine_base_url()
141
+ config_text = DEFAULT_CONFIG.replace("provider: heuristic", "provider: ollama", 1)
142
+ config_text = config_text.replace(
143
+ "model_id: heuristic-local-v1",
144
+ f"model_id: {model_id}\nbase_url: {base_url}",
145
+ 1,
146
+ )
147
+ return config_text, "local_server"
148
+ if backend == BACKEND_BYOK:
149
+ return DEFAULT_CONFIG, "byok"
150
+ return DEFAULT_CONFIG, "default"
151
+
99
152
  _SEVERITY_STYLE = {
100
153
  "critical": "bold red",
101
154
  "high": "red",
@@ -603,12 +656,23 @@ def init(
603
656
  )
604
657
  raise typer.Exit(code=1)
605
658
  consti_target.write_text(DEFAULT_CONSTITUTION, encoding="utf-8")
606
- config_target.write_text(DEFAULT_CONFIG, encoding="utf-8")
659
+ config_text, config_profile = _default_config_for_init()
660
+ config_target.write_text(config_text, encoding="utf-8")
607
661
 
608
662
  constitution = load_constitution(consti_target)
609
663
  record = register_project(project_root, constitution_hash=constitution.content_hash)
610
664
  console.print(f"[green]Created[/green] {consti_target}")
611
665
  console.print(f"[green]Created[/green] {config_target}")
666
+ if config_profile == "local_server":
667
+ console.print(
668
+ "[green]Configured[/green] refactor.config for local server setup "
669
+ "(provider=ollama with setup-recommended model)."
670
+ )
671
+ elif config_profile == "byok":
672
+ console.print(
673
+ "[green]Configured[/green] refactor.config for BYOK setup "
674
+ "(provider=heuristic; uncomment a provider block to use your key)."
675
+ )
612
676
  console.print(
613
677
  f"[green]Registered[/green] project in central store: {project_store_dir(project_root)}"
614
678
  )
@@ -12,6 +12,7 @@ from pathlib import Path
12
12
 
13
13
  import httpx
14
14
 
15
+ from refactorai_cli import __version__
15
16
  from refactorai_cli.local_paths import ensure_dir, refactor_home
16
17
 
17
18
  from refactorai_cli.credentials import resolve_developer_key
@@ -115,7 +116,7 @@ def read_cached_lease() -> LeaseContext | None:
115
116
  return _from_cached(payload)
116
117
 
117
118
 
118
- def request_lease(*, timeout: float = 20.0, cli_version: str = "0.1.0") -> LeaseContext:
119
+ def request_lease(*, timeout: float = 20.0, cli_version: str = __version__) -> LeaseContext:
119
120
  resolved = resolve_developer_key()
120
121
  if not resolved:
121
122
  raise RuntimeError(
@@ -10,6 +10,9 @@ import json
10
10
  import re
11
11
  import shutil
12
12
  import subprocess
13
+ import time
14
+ import urllib.error
15
+ import urllib.request
13
16
  from datetime import datetime, timezone
14
17
  from pathlib import Path
15
18
 
@@ -18,6 +21,8 @@ from refactorai_cli.local_paths import ensure_dir, refactor_home
18
21
  DEFAULT_ENGINE_IMAGE = "docker.io/ollama/ollama:latest"
19
22
  DEFAULT_ENGINE_CONTAINER = "refactor-engine"
20
23
  DEFAULT_ENGINE_PORT = 17777
24
+ # Ollama serves inside the container on 11434.
25
+ ENGINE_API_PORT = 11434
21
26
  DEFAULT_ENGINE_PROFILE = "cpu-small"
22
27
  ENGINE_DIR = "engine"
23
28
  ENGINE_STATE_FILE = "engine.json"
@@ -105,12 +110,56 @@ def _running(runtime: str, name: str) -> bool:
105
110
  return proc.returncode == 0 and proc.stdout.strip().lower() == "true"
106
111
 
107
112
 
108
- def _health(runtime: str, name: str) -> bool:
113
+ def _http_health(host_port: int, *, retries: int = 8, delay_s: float = 0.25) -> bool:
114
+ url = f"http://127.0.0.1:{int(host_port)}/api/tags"
115
+ for attempt in range(max(1, int(retries))):
116
+ try:
117
+ with urllib.request.urlopen(url, timeout=2.0) as response:
118
+ code = int(getattr(response, "status", 0) or 0)
119
+ if 200 <= code < 500:
120
+ return True
121
+ except (urllib.error.URLError, OSError, ValueError):
122
+ if attempt + 1 < retries:
123
+ time.sleep(delay_s)
124
+ return False
125
+
126
+
127
+ def _health(runtime: str, name: str, *, host_port: int | None = None) -> bool:
109
128
  has_binary = _run([runtime, "exec", name, "sh", "-lc", "command -v ollama >/dev/null 2>&1"])
110
129
  if has_binary.returncode != 0:
111
130
  return False
112
131
  proc = _run([runtime, "exec", name, "sh", "-lc", "ollama list >/dev/null 2>&1"])
113
- return proc.returncode == 0
132
+ if proc.returncode != 0:
133
+ return False
134
+ if host_port is None:
135
+ return True
136
+ return _http_health(int(host_port))
137
+
138
+
139
+ def _published_host_port(runtime: str, name: str, container_port: int = ENGINE_API_PORT) -> int | None:
140
+ proc = _run([runtime, "port", name, str(int(container_port))])
141
+ if proc.returncode != 0:
142
+ return None
143
+ line = (proc.stdout or "").strip().splitlines()
144
+ if not line:
145
+ return None
146
+ candidate = line[0].strip()
147
+ # Typical outputs:
148
+ # - "127.0.0.1:17777"
149
+ # - "[::1]:17777"
150
+ # - "0.0.0.0:17777"
151
+ if ":" not in candidate:
152
+ return None
153
+ port_text = candidate.rsplit(":", 1)[-1].strip()
154
+ try:
155
+ return int(port_text)
156
+ except (TypeError, ValueError):
157
+ return None
158
+
159
+
160
+ def _port_mapping_matches(runtime: str, name: str, host_port: int) -> bool:
161
+ published = _published_host_port(runtime, name, ENGINE_API_PORT)
162
+ return published == int(host_port)
114
163
 
115
164
 
116
165
  def _run_args_for_image(image: str) -> list[str]:
@@ -131,11 +180,17 @@ def ensure_engine_up(
131
180
  port: int = DEFAULT_ENGINE_PORT,
132
181
  rebuild: bool = False,
133
182
  ) -> tuple[bool, str, dict]:
183
+ resolved_port = int(port)
134
184
  models_dir = ensure_dir(refactor_home() / ENGINE_MODELS_DIR)
135
185
  cache_dir = ensure_dir(refactor_home() / ENGINE_CACHE_DIR)
136
- if rebuild and _exists(runtime, container_name):
186
+ exists = _exists(runtime, container_name)
187
+ if rebuild and exists:
137
188
  _run([runtime, "rm", "-f", container_name])
138
- if not _exists(runtime, container_name):
189
+ exists = False
190
+ if exists and not _port_mapping_matches(runtime, container_name, resolved_port):
191
+ _run([runtime, "rm", "-f", container_name])
192
+ exists = False
193
+ if not exists:
139
194
  command_args = _run_args_for_image(image)
140
195
  proc = _run(
141
196
  [
@@ -145,7 +200,7 @@ def ensure_engine_up(
145
200
  "--name",
146
201
  container_name,
147
202
  "-p",
148
- f"127.0.0.1:{int(port)}:{int(port)}",
203
+ f"127.0.0.1:{resolved_port}:{ENGINE_API_PORT}",
149
204
  "-v",
150
205
  f"{models_dir}:/root/.ollama/models:Z",
151
206
  "-v",
@@ -162,16 +217,19 @@ def ensure_engine_up(
162
217
  if proc.returncode != 0:
163
218
  detail = (proc.stderr or proc.stdout or "").strip()
164
219
  return False, f"Failed to start engine container: {detail}", {}
220
+ status = "ready" if _health(runtime, container_name, host_port=resolved_port) else "degraded"
165
221
  state = {
166
222
  "runtime": runtime,
167
223
  "container_name": container_name,
168
224
  "image": image,
169
- "port": int(port),
225
+ "port": resolved_port,
170
226
  "profile": profile,
171
- "status": "ready" if _health(runtime, container_name) else "degraded",
227
+ "status": status,
172
228
  }
173
229
  write_engine_state(state)
174
- return True, "Engine ready.", state
230
+ if status == "ready":
231
+ return True, "Engine ready.", state
232
+ return False, "Engine container is running but local API health checks failed.", state
175
233
 
176
234
 
177
235
  def stop_engine(*, runtime: str, container_name: str = DEFAULT_ENGINE_CONTAINER) -> tuple[bool, str]:
@@ -192,6 +250,7 @@ def stop_engine(*, runtime: str, container_name: str = DEFAULT_ENGINE_CONTAINER)
192
250
  def engine_status(*, runtime: str | None = None, container_name: str = DEFAULT_ENGINE_CONTAINER) -> dict:
193
251
  state = read_engine_state()
194
252
  resolved_runtime = runtime or str(state.get("runtime") or "podman")
253
+ resolved_port = int(state.get("port", DEFAULT_ENGINE_PORT))
195
254
  exists = False
196
255
  running = False
197
256
  healthy = False
@@ -199,7 +258,7 @@ def engine_status(*, runtime: str | None = None, container_name: str = DEFAULT_E
199
258
  try:
200
259
  exists = _exists(resolved_runtime, container_name)
201
260
  running = _running(resolved_runtime, container_name) if exists else False
202
- healthy = _health(resolved_runtime, container_name) if running else False
261
+ healthy = _health(resolved_runtime, container_name, host_port=resolved_port) if running else False
203
262
  if running and healthy:
204
263
  status = "ready"
205
264
  elif running and not healthy:
@@ -212,7 +271,7 @@ def engine_status(*, runtime: str | None = None, container_name: str = DEFAULT_E
212
271
  "runtime": resolved_runtime,
213
272
  "container_name": container_name,
214
273
  "image": state.get("image", DEFAULT_ENGINE_IMAGE),
215
- "port": int(state.get("port", DEFAULT_ENGINE_PORT)),
274
+ "port": resolved_port,
216
275
  "profile": state.get("profile", DEFAULT_ENGINE_PROFILE),
217
276
  "status": status,
218
277
  "exists": exists,
@@ -17,6 +17,7 @@ from pathlib import Path
17
17
 
18
18
  import httpx
19
19
 
20
+ from refactorai_cli import __version__
20
21
  from refactorai_cli.local_paths import ensure_dir, refactor_home
21
22
 
22
23
  from refactorai_cli.control_plane import ensure_lease
@@ -129,7 +130,7 @@ def resolve_runtime_manifest(*, channel: str = "stable", timeout: float = 20.0)
129
130
  os_name = str(platform.system() or "linux").lower()
130
131
  arch = str(platform.machine() or "x86_64").lower()
131
132
  payload = {
132
- "cli_version": "0.1.0",
133
+ "cli_version": __version__,
133
134
  "os": os_name,
134
135
  "arch": arch,
135
136
  "channel": channel,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.2.9
3
+ Version: 0.2.11
4
4
  Summary: Local-first CLI for the refactor platform
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -53,6 +53,6 @@ python -m twine upload ./refactorai-cli/dist/*
53
53
  ## Install test (local)
54
54
 
55
55
  ```bash
56
- python -m pip install ./refactorai-cli/dist/refactorai_cli-0.1.0-py3-none-any.whl
56
+ python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.11-py3-none-any.whl
57
57
  refactor --version
58
58
  ```