refactorai-cli 0.7.12__tar.gz → 0.7.13__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.7.12 → refactorai_cli-0.7.13}/PKG-INFO +51 -2
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/README.md +49 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/pyproject.toml +2 -2
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/__init__.py +1 -1
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/run_cmds.py +22 -10
- refactorai_cli-0.7.13/refactorai_cli/mode_sync.py +171 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/setup_flow.py +19 -35
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli.egg-info/PKG-INFO +51 -2
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli.egg-info/requires.txt +1 -1
- refactorai_cli-0.7.12/refactorai_cli/mode_sync.py +0 -140
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/auth.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/client.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/cloud_rr.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/__init__.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/account_cmds.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/auth_cmds.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/branch_cmds.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/cloud_cmds.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/engine_cmds.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/hook_cmds.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/model_cmds.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/policy_cmds.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/pre_push_cmds.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/request_cmds.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/rules_cmds.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/runtime_cmds.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/runtime_proxy_cmds.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/setup_cmds.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/toolchains_cmds.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/watch_cmds.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/workspace_cmds.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commit_queue.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commit_telemetry.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/control_plane.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/credentials.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/dotenv_loader.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/git_scope.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/local_constitution.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/local_engine_runtime.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/local_paths.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/main.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/model_policy.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/pre_push_gate.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/refactor_branch_store.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/review_runner.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/runtime_manager.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/settings.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/watch_ledger.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/watch_state.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/watch_supervisor.py +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli.egg-info/SOURCES.txt +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli.egg-info/dependency_links.txt +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli.egg-info/entry_points.txt +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli.egg-info/top_level.txt +0 -0
- {refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: refactorai-cli
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.13
|
|
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,7 @@ 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: refactorai-core>=3.2.
|
|
11
|
+
Requires-Dist: refactorai-core>=3.2.23
|
|
12
12
|
|
|
13
13
|
# refactorai-cli
|
|
14
14
|
|
|
@@ -66,6 +66,55 @@ the managed host toolchain when no container runtime is available. `refactor
|
|
|
66
66
|
doctor` reports the selected environment (and `refactor doctor --sandbox` shows
|
|
67
67
|
its full state), so a run and diagnostics always agree.
|
|
68
68
|
|
|
69
|
+
## Customizing refactor.config
|
|
70
|
+
|
|
71
|
+
`refactor init` writes a **bare-minimum** `refactor.config`: just `version`,
|
|
72
|
+
`project_id`, and a CLI-managed block seeded to `local_heuristic`. A normal
|
|
73
|
+
developer never edits this file by hand — pick the mode (and provider keys, if
|
|
74
|
+
any) in the web UI or via `refactor setup --mode <variant>`.
|
|
75
|
+
|
|
76
|
+
### The managed block (single source of truth for the mode)
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
# >>> refactor managed (synced from server; do not edit) >>>
|
|
80
|
+
execution_variant: cloud_byok
|
|
81
|
+
provider: openai
|
|
82
|
+
model_id: gpt-4.1-mini
|
|
83
|
+
credential_ref: cred_01J...
|
|
84
|
+
# <<< refactor managed <<<
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- Owned by the CLI. `refactor setup` (terminal) writes it immediately; a change
|
|
88
|
+
made in the web UI is pulled into this block on the next `refactor doctor` /
|
|
89
|
+
`refactor watch` / `refactor setup` (**server-wins**).
|
|
90
|
+
- Only these keys live here: `execution_variant`, `provider`, `model_id`,
|
|
91
|
+
`base_url` (local_model), `credential_ref`. **Secrets are never written** —
|
|
92
|
+
keep provider keys in the environment and reference them as `${ENV_VAR}`.
|
|
93
|
+
|
|
94
|
+
### What each mode needs
|
|
95
|
+
|
|
96
|
+
| variant | you provide |
|
|
97
|
+
| ---------------- | ------------------------------------------------------- |
|
|
98
|
+
| `local_heuristic`| nothing |
|
|
99
|
+
| `local_model` | nothing (setup writes the Ollama provider/model block) |
|
|
100
|
+
| `local_byok` | provider API key in the env (`${VAR}`); provider/model via UI/setup |
|
|
101
|
+
| `local_managed` | nothing |
|
|
102
|
+
| `cloud_byok` | provider API key in env (or a UI credential); provider/model via UI/setup |
|
|
103
|
+
| `cloud_managed` | nothing |
|
|
104
|
+
|
|
105
|
+
### Advanced knobs and precedence
|
|
106
|
+
|
|
107
|
+
Advanced settings (`sandbox`, `verification`, `intelligence`, `compliance`,
|
|
108
|
+
`watch`, `timeout`, `exclude`, `model_context_overrides`, …) are **not** written
|
|
109
|
+
by `init`; add only the ones you need anywhere **outside** the managed block.
|
|
110
|
+
`refactor init --full` writes the fully-annotated template with every knob if you
|
|
111
|
+
prefer to start from that.
|
|
112
|
+
|
|
113
|
+
Resolution precedence: CLI flags → environment (`provider_key`,
|
|
114
|
+
`REFACTOR_PROVIDER`) → the synced managed block → code defaults. A key you place
|
|
115
|
+
outside the managed block (later in the file) also takes precedence over the
|
|
116
|
+
block, so power users can pin values while normal syncing continues.
|
|
117
|
+
|
|
69
118
|
## Local development install
|
|
70
119
|
|
|
71
120
|
From repository root:
|
|
@@ -54,6 +54,55 @@ the managed host toolchain when no container runtime is available. `refactor
|
|
|
54
54
|
doctor` reports the selected environment (and `refactor doctor --sandbox` shows
|
|
55
55
|
its full state), so a run and diagnostics always agree.
|
|
56
56
|
|
|
57
|
+
## Customizing refactor.config
|
|
58
|
+
|
|
59
|
+
`refactor init` writes a **bare-minimum** `refactor.config`: just `version`,
|
|
60
|
+
`project_id`, and a CLI-managed block seeded to `local_heuristic`. A normal
|
|
61
|
+
developer never edits this file by hand — pick the mode (and provider keys, if
|
|
62
|
+
any) in the web UI or via `refactor setup --mode <variant>`.
|
|
63
|
+
|
|
64
|
+
### The managed block (single source of truth for the mode)
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
# >>> refactor managed (synced from server; do not edit) >>>
|
|
68
|
+
execution_variant: cloud_byok
|
|
69
|
+
provider: openai
|
|
70
|
+
model_id: gpt-4.1-mini
|
|
71
|
+
credential_ref: cred_01J...
|
|
72
|
+
# <<< refactor managed <<<
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- Owned by the CLI. `refactor setup` (terminal) writes it immediately; a change
|
|
76
|
+
made in the web UI is pulled into this block on the next `refactor doctor` /
|
|
77
|
+
`refactor watch` / `refactor setup` (**server-wins**).
|
|
78
|
+
- Only these keys live here: `execution_variant`, `provider`, `model_id`,
|
|
79
|
+
`base_url` (local_model), `credential_ref`. **Secrets are never written** —
|
|
80
|
+
keep provider keys in the environment and reference them as `${ENV_VAR}`.
|
|
81
|
+
|
|
82
|
+
### What each mode needs
|
|
83
|
+
|
|
84
|
+
| variant | you provide |
|
|
85
|
+
| ---------------- | ------------------------------------------------------- |
|
|
86
|
+
| `local_heuristic`| nothing |
|
|
87
|
+
| `local_model` | nothing (setup writes the Ollama provider/model block) |
|
|
88
|
+
| `local_byok` | provider API key in the env (`${VAR}`); provider/model via UI/setup |
|
|
89
|
+
| `local_managed` | nothing |
|
|
90
|
+
| `cloud_byok` | provider API key in env (or a UI credential); provider/model via UI/setup |
|
|
91
|
+
| `cloud_managed` | nothing |
|
|
92
|
+
|
|
93
|
+
### Advanced knobs and precedence
|
|
94
|
+
|
|
95
|
+
Advanced settings (`sandbox`, `verification`, `intelligence`, `compliance`,
|
|
96
|
+
`watch`, `timeout`, `exclude`, `model_context_overrides`, …) are **not** written
|
|
97
|
+
by `init`; add only the ones you need anywhere **outside** the managed block.
|
|
98
|
+
`refactor init --full` writes the fully-annotated template with every knob if you
|
|
99
|
+
prefer to start from that.
|
|
100
|
+
|
|
101
|
+
Resolution precedence: CLI flags → environment (`provider_key`,
|
|
102
|
+
`REFACTOR_PROVIDER`) → the synced managed block → code defaults. A key you place
|
|
103
|
+
outside the managed block (later in the file) also takes precedence over the
|
|
104
|
+
block, so power users can pin values while normal syncing continues.
|
|
105
|
+
|
|
57
106
|
## Local development install
|
|
58
107
|
|
|
59
108
|
From repository root:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "refactorai-cli"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.13"
|
|
4
4
|
description = "Local-first CLI for the refactor platform"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -12,7 +12,7 @@ dependencies = [
|
|
|
12
12
|
"httpx>=0.27.0",
|
|
13
13
|
"rich>=13.7.0",
|
|
14
14
|
"PyYAML>=6.0.1",
|
|
15
|
-
"refactorai-core>=3.2.
|
|
15
|
+
"refactorai-core>=3.2.23",
|
|
16
16
|
]
|
|
17
17
|
|
|
18
18
|
[project.scripts]
|
|
@@ -30,6 +30,7 @@ from refactor_core.constitution import (
|
|
|
30
30
|
CONSTITUTION_FILENAME,
|
|
31
31
|
DEFAULT_CONFIG,
|
|
32
32
|
DEFAULT_CONSTITUTION,
|
|
33
|
+
MINIMAL_CONFIG,
|
|
33
34
|
find_project_files,
|
|
34
35
|
has_raw_secret_literal,
|
|
35
36
|
load_config,
|
|
@@ -158,17 +159,22 @@ def _sandbox_runtime():
|
|
|
158
159
|
return _sr
|
|
159
160
|
|
|
160
161
|
|
|
161
|
-
def _default_config_for_init() -> tuple[str, str]:
|
|
162
|
-
"""Return the config `init` writes
|
|
162
|
+
def _default_config_for_init(full: bool = False) -> tuple[str, str]:
|
|
163
|
+
"""Return the config `init` writes.
|
|
163
164
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
--mode
|
|
167
|
-
``
|
|
168
|
-
|
|
169
|
-
|
|
165
|
+
By default this is the bare-minimum template (docs/46): ``version`` +
|
|
166
|
+
the CLI-managed block seeded to ``local_heuristic``. A normal developer never
|
|
167
|
+
hand-edits it -- they pick a mode in the web UI or via ``refactor setup
|
|
168
|
+
--mode`` and the managed block is filled in by the CLI. ``--full`` writes the
|
|
169
|
+
fully-annotated template with every advanced knob for power users.
|
|
170
|
+
|
|
171
|
+
Mode selection is a *setup*-time concern: ``init`` never derives the mode
|
|
172
|
+
from prior setup-backend state (that legacy coupling produced configs whose
|
|
173
|
+
variant disagreed with the server default and is intentionally removed).
|
|
170
174
|
"""
|
|
171
|
-
|
|
175
|
+
if full:
|
|
176
|
+
return DEFAULT_CONFIG, "full"
|
|
177
|
+
return MINIMAL_CONFIG, "default"
|
|
172
178
|
|
|
173
179
|
_SEVERITY_STYLE = {
|
|
174
180
|
"critical": "bold red",
|
|
@@ -1356,6 +1362,12 @@ def init(
|
|
|
1356
1362
|
"--force",
|
|
1357
1363
|
help=f"Overwrite existing {CONSTITUTION_FILENAME} and {CONFIG_FILENAME}.",
|
|
1358
1364
|
),
|
|
1365
|
+
full: bool = typer.Option(
|
|
1366
|
+
False,
|
|
1367
|
+
"--full",
|
|
1368
|
+
hidden=True,
|
|
1369
|
+
help="Write the fully-annotated refactor.config template (power users).",
|
|
1370
|
+
),
|
|
1359
1371
|
) -> None:
|
|
1360
1372
|
"""Create `refactor.consti` + `refactor.config` and register the project.
|
|
1361
1373
|
|
|
@@ -1422,7 +1434,7 @@ def init(
|
|
|
1422
1434
|
raise typer.Exit(code=1)
|
|
1423
1435
|
|
|
1424
1436
|
consti_target.write_text(DEFAULT_CONSTITUTION, encoding="utf-8")
|
|
1425
|
-
config_text, _config_profile = _default_config_for_init()
|
|
1437
|
+
config_text, _config_profile = _default_config_for_init(full=full)
|
|
1426
1438
|
config_text = _inject_project_id(config_text, project_id)
|
|
1427
1439
|
config_target.write_text(config_text, encoding="utf-8")
|
|
1428
1440
|
gitignore_updated = _ensure_refactor_gitignore(project_root)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Execution-mode sync between the server project and local ``refactor.config``.
|
|
2
|
+
|
|
3
|
+
The web UI sets a project's ``execution_mode`` on the server (JWT). The CLI
|
|
4
|
+
authenticates with a developer key and treats the server as the source of truth
|
|
5
|
+
(server-wins): ``setup`` / ``doctor`` / ``watch`` pull the server mode and write
|
|
6
|
+
it into the local ``refactor.config`` so a UI change reflects in terminal runs.
|
|
7
|
+
``refactor setup --mode`` also pushes the chosen variant up so both agree.
|
|
8
|
+
|
|
9
|
+
All operations are best-effort: offline / unauthenticated / unbound projects are
|
|
10
|
+
a silent no-op so they never break the surrounding command.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Callable
|
|
17
|
+
|
|
18
|
+
from refactor_core.constitution import (
|
|
19
|
+
CONFIG_FILENAME,
|
|
20
|
+
MANAGED_KEYS,
|
|
21
|
+
find_project_files,
|
|
22
|
+
load_config,
|
|
23
|
+
upsert_managed_block,
|
|
24
|
+
)
|
|
25
|
+
from refactor_core.execution_variant import normalize_execution_variant
|
|
26
|
+
from refactor_core.store import resolve_project_id
|
|
27
|
+
|
|
28
|
+
from refactorai_cli.client import PlatformClient
|
|
29
|
+
from refactorai_cli.credentials import resolve_developer_key
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _local_config_path(project_root: Path) -> Path | None:
|
|
33
|
+
files = find_project_files(project_root)
|
|
34
|
+
if files:
|
|
35
|
+
return Path(files[1])
|
|
36
|
+
candidate = Path(project_root) / CONFIG_FILENAME
|
|
37
|
+
return candidate if candidate.is_file() else None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _current_managed_values(project_root: Path) -> dict:
|
|
41
|
+
"""Current values of the managed keys as parsed from refactor.config."""
|
|
42
|
+
config_path = _local_config_path(project_root)
|
|
43
|
+
if config_path is None:
|
|
44
|
+
return {}
|
|
45
|
+
try:
|
|
46
|
+
settings = getattr(load_config(config_path), "settings", {}) or {}
|
|
47
|
+
except Exception:
|
|
48
|
+
return {}
|
|
49
|
+
values: dict = {}
|
|
50
|
+
for key in MANAGED_KEYS:
|
|
51
|
+
raw = settings.get(key)
|
|
52
|
+
if raw is None:
|
|
53
|
+
continue
|
|
54
|
+
text = str(raw).strip()
|
|
55
|
+
if text:
|
|
56
|
+
values[key] = text
|
|
57
|
+
return values
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _desired_managed_fields(record: dict, current: dict, server_variant: str) -> dict:
|
|
61
|
+
"""Compute the managed-block fields from the server record (server-wins).
|
|
62
|
+
|
|
63
|
+
When the server carries a remote provider (BYOK/managed set from the UI), the
|
|
64
|
+
provider/model/credential come from the server and any local ``base_url`` is
|
|
65
|
+
dropped. Otherwise (heuristic / terminal-set local_model, where the server has
|
|
66
|
+
no provider) the local provider/model/base_url are preserved and only the
|
|
67
|
+
variant is reconciled.
|
|
68
|
+
"""
|
|
69
|
+
provider = str(record.get("inference_provider") or "").strip()
|
|
70
|
+
model_id = str(record.get("model_id") or "").strip()
|
|
71
|
+
credential_ref = str(record.get("provider_credential_ref") or "").strip()
|
|
72
|
+
|
|
73
|
+
fields: dict = {"execution_variant": server_variant}
|
|
74
|
+
if provider:
|
|
75
|
+
fields["provider"] = provider
|
|
76
|
+
if model_id:
|
|
77
|
+
fields["model_id"] = model_id
|
|
78
|
+
if credential_ref:
|
|
79
|
+
fields["credential_ref"] = credential_ref
|
|
80
|
+
else:
|
|
81
|
+
for key in ("provider", "model_id", "base_url", "credential_ref"):
|
|
82
|
+
if current.get(key):
|
|
83
|
+
fields[key] = current[key]
|
|
84
|
+
return fields
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def sync_execution_variant_from_server(
|
|
88
|
+
project_root: Path,
|
|
89
|
+
*,
|
|
90
|
+
client: PlatformClient | None = None,
|
|
91
|
+
emit: Callable[[str], None] | None = None,
|
|
92
|
+
) -> str | None:
|
|
93
|
+
"""Pull the server project's execution settings into the managed config block.
|
|
94
|
+
|
|
95
|
+
Server-wins for the managed keys (execution_variant / provider / model_id /
|
|
96
|
+
credential_ref). Secrets (``provider_key``) are never written -- they stay in
|
|
97
|
+
the environment. Returns the applied variant when the local config changed,
|
|
98
|
+
else ``None`` (already in sync, offline, unauthenticated, or unbound project).
|
|
99
|
+
"""
|
|
100
|
+
try:
|
|
101
|
+
project_id = resolve_project_id(project_root)
|
|
102
|
+
if not project_id:
|
|
103
|
+
return None
|
|
104
|
+
resolved = resolve_developer_key(project_root)
|
|
105
|
+
if not resolved:
|
|
106
|
+
return None
|
|
107
|
+
client = client or PlatformClient()
|
|
108
|
+
record = client.get_project_execution_mode(resolved.key, project_id)
|
|
109
|
+
except Exception:
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
server_variant = normalize_execution_variant((record or {}).get("execution_mode"))
|
|
113
|
+
if not server_variant:
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
config_path = _local_config_path(project_root)
|
|
117
|
+
if config_path is None:
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
current = _current_managed_values(project_root)
|
|
121
|
+
desired = _desired_managed_fields(record or {}, current, server_variant)
|
|
122
|
+
|
|
123
|
+
# No-op when the managed keys already match (compare only managed keys).
|
|
124
|
+
current_managed = {k: current.get(k) for k in MANAGED_KEYS if current.get(k)}
|
|
125
|
+
if desired == current_managed:
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
text = config_path.read_text(encoding="utf-8")
|
|
130
|
+
new_text = upsert_managed_block(text, desired)
|
|
131
|
+
if not new_text.endswith("\n"):
|
|
132
|
+
new_text += "\n"
|
|
133
|
+
if new_text == text:
|
|
134
|
+
return None
|
|
135
|
+
config_path.write_text(new_text, encoding="utf-8")
|
|
136
|
+
except OSError:
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
if emit:
|
|
140
|
+
emit(f"Synced execution_variant from server: {server_variant}")
|
|
141
|
+
return server_variant
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def push_execution_variant_to_server(
|
|
145
|
+
project_root: Path,
|
|
146
|
+
variant: str,
|
|
147
|
+
*,
|
|
148
|
+
client: PlatformClient | None = None,
|
|
149
|
+
emit: Callable[[str], None] | None = None,
|
|
150
|
+
) -> bool:
|
|
151
|
+
"""Push a locally chosen execution variant to the server project.
|
|
152
|
+
|
|
153
|
+
Best-effort; returns ``True`` on a successful push, else ``False``.
|
|
154
|
+
"""
|
|
155
|
+
normalized = normalize_execution_variant(variant)
|
|
156
|
+
if not normalized:
|
|
157
|
+
return False
|
|
158
|
+
try:
|
|
159
|
+
project_id = resolve_project_id(project_root)
|
|
160
|
+
if not project_id:
|
|
161
|
+
return False
|
|
162
|
+
resolved = resolve_developer_key(project_root)
|
|
163
|
+
if not resolved:
|
|
164
|
+
return False
|
|
165
|
+
client = client or PlatformClient()
|
|
166
|
+
client.set_project_execution_mode(resolved.key, project_id, normalized)
|
|
167
|
+
except Exception:
|
|
168
|
+
return False
|
|
169
|
+
if emit:
|
|
170
|
+
emit(f"Pushed execution_variant to server: {normalized}")
|
|
171
|
+
return True
|
|
@@ -598,38 +598,30 @@ def _local_model_provider_values() -> tuple[str, str]:
|
|
|
598
598
|
return model_id, f"http://127.0.0.1:{port}/v1"
|
|
599
599
|
|
|
600
600
|
|
|
601
|
-
def
|
|
602
|
-
"""
|
|
603
|
-
|
|
604
|
-
Only rewrites when the untouched heuristic default is still present, so a
|
|
605
|
-
developer who already configured a provider is never overwritten.
|
|
606
|
-
"""
|
|
601
|
+
def _local_model_managed_fields() -> dict:
|
|
602
|
+
"""Managed-block fields for ``local_model`` (Ollama provider + model/base_url)."""
|
|
607
603
|
model_id, base_url = _local_model_provider_values()
|
|
608
|
-
return
|
|
609
|
-
"
|
|
610
|
-
"provider:
|
|
611
|
-
"model_id:
|
|
612
|
-
"
|
|
613
|
-
|
|
614
|
-
f"model_id: {model_id}\n"
|
|
615
|
-
f"base_url: {base_url}",
|
|
616
|
-
1,
|
|
617
|
-
)
|
|
604
|
+
return {
|
|
605
|
+
"execution_variant": LOCAL_MODEL,
|
|
606
|
+
"provider": "ollama",
|
|
607
|
+
"model_id": model_id,
|
|
608
|
+
"base_url": base_url,
|
|
609
|
+
}
|
|
618
610
|
|
|
619
611
|
|
|
620
612
|
def _persist_variant_to_project_config(variant: str, emit: Callable[[str], None] | None = None) -> None:
|
|
621
|
-
"""Write the chosen ``execution_variant`` into the
|
|
613
|
+
"""Write the chosen ``execution_variant`` into the CLI-managed config block.
|
|
622
614
|
|
|
623
615
|
Keeps a UI-selected or terminal-selected mode as the single source of truth
|
|
624
616
|
for subsequent runs (doctor/review/code read it from config). For
|
|
625
|
-
``local_model``
|
|
626
|
-
|
|
627
|
-
|
|
617
|
+
``local_model`` the block also carries the local Ollama provider/model/base_url
|
|
618
|
+
(docs/46: local_model provider config is a setup concern, not an init concern).
|
|
619
|
+
Best-effort: a missing config or write error never fails setup.
|
|
628
620
|
"""
|
|
629
621
|
if not variant:
|
|
630
622
|
return
|
|
631
623
|
try:
|
|
632
|
-
from refactor_core.constitution import find_project_files
|
|
624
|
+
from refactor_core.constitution import find_project_files, upsert_managed_block
|
|
633
625
|
|
|
634
626
|
files = find_project_files()
|
|
635
627
|
if not files:
|
|
@@ -637,20 +629,12 @@ def _persist_variant_to_project_config(variant: str, emit: Callable[[str], None]
|
|
|
637
629
|
_consti_path, config_path = files
|
|
638
630
|
config_path = Path(config_path)
|
|
639
631
|
text = config_path.read_text(encoding="utf-8")
|
|
640
|
-
if variant == LOCAL_MODEL:
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
if line.strip().startswith("execution_variant:"):
|
|
647
|
-
if line.strip() != new_line:
|
|
648
|
-
lines[idx] = new_line
|
|
649
|
-
replaced = True
|
|
650
|
-
break
|
|
651
|
-
if not replaced:
|
|
652
|
-
lines.append(new_line)
|
|
653
|
-
config_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
632
|
+
fields = _local_model_managed_fields() if variant == LOCAL_MODEL else {"execution_variant": variant}
|
|
633
|
+
new_text = upsert_managed_block(text, fields)
|
|
634
|
+
if not new_text.endswith("\n"):
|
|
635
|
+
new_text += "\n"
|
|
636
|
+
if new_text != text:
|
|
637
|
+
config_path.write_text(new_text, encoding="utf-8")
|
|
654
638
|
if emit:
|
|
655
639
|
emit(f"Recorded execution_variant: {variant} in refactor.config.")
|
|
656
640
|
except Exception:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: refactorai-cli
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.13
|
|
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,7 @@ 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: refactorai-core>=3.2.
|
|
11
|
+
Requires-Dist: refactorai-core>=3.2.23
|
|
12
12
|
|
|
13
13
|
# refactorai-cli
|
|
14
14
|
|
|
@@ -66,6 +66,55 @@ the managed host toolchain when no container runtime is available. `refactor
|
|
|
66
66
|
doctor` reports the selected environment (and `refactor doctor --sandbox` shows
|
|
67
67
|
its full state), so a run and diagnostics always agree.
|
|
68
68
|
|
|
69
|
+
## Customizing refactor.config
|
|
70
|
+
|
|
71
|
+
`refactor init` writes a **bare-minimum** `refactor.config`: just `version`,
|
|
72
|
+
`project_id`, and a CLI-managed block seeded to `local_heuristic`. A normal
|
|
73
|
+
developer never edits this file by hand — pick the mode (and provider keys, if
|
|
74
|
+
any) in the web UI or via `refactor setup --mode <variant>`.
|
|
75
|
+
|
|
76
|
+
### The managed block (single source of truth for the mode)
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
# >>> refactor managed (synced from server; do not edit) >>>
|
|
80
|
+
execution_variant: cloud_byok
|
|
81
|
+
provider: openai
|
|
82
|
+
model_id: gpt-4.1-mini
|
|
83
|
+
credential_ref: cred_01J...
|
|
84
|
+
# <<< refactor managed <<<
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- Owned by the CLI. `refactor setup` (terminal) writes it immediately; a change
|
|
88
|
+
made in the web UI is pulled into this block on the next `refactor doctor` /
|
|
89
|
+
`refactor watch` / `refactor setup` (**server-wins**).
|
|
90
|
+
- Only these keys live here: `execution_variant`, `provider`, `model_id`,
|
|
91
|
+
`base_url` (local_model), `credential_ref`. **Secrets are never written** —
|
|
92
|
+
keep provider keys in the environment and reference them as `${ENV_VAR}`.
|
|
93
|
+
|
|
94
|
+
### What each mode needs
|
|
95
|
+
|
|
96
|
+
| variant | you provide |
|
|
97
|
+
| ---------------- | ------------------------------------------------------- |
|
|
98
|
+
| `local_heuristic`| nothing |
|
|
99
|
+
| `local_model` | nothing (setup writes the Ollama provider/model block) |
|
|
100
|
+
| `local_byok` | provider API key in the env (`${VAR}`); provider/model via UI/setup |
|
|
101
|
+
| `local_managed` | nothing |
|
|
102
|
+
| `cloud_byok` | provider API key in env (or a UI credential); provider/model via UI/setup |
|
|
103
|
+
| `cloud_managed` | nothing |
|
|
104
|
+
|
|
105
|
+
### Advanced knobs and precedence
|
|
106
|
+
|
|
107
|
+
Advanced settings (`sandbox`, `verification`, `intelligence`, `compliance`,
|
|
108
|
+
`watch`, `timeout`, `exclude`, `model_context_overrides`, …) are **not** written
|
|
109
|
+
by `init`; add only the ones you need anywhere **outside** the managed block.
|
|
110
|
+
`refactor init --full` writes the fully-annotated template with every knob if you
|
|
111
|
+
prefer to start from that.
|
|
112
|
+
|
|
113
|
+
Resolution precedence: CLI flags → environment (`provider_key`,
|
|
114
|
+
`REFACTOR_PROVIDER`) → the synced managed block → code defaults. A key you place
|
|
115
|
+
outside the managed block (later in the file) also takes precedence over the
|
|
116
|
+
block, so power users can pin values while normal syncing continues.
|
|
117
|
+
|
|
69
118
|
## Local development install
|
|
70
119
|
|
|
71
120
|
From repository root:
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
"""Execution-mode sync between the server project and local ``refactor.config``.
|
|
2
|
-
|
|
3
|
-
The web UI sets a project's ``execution_mode`` on the server (JWT). The CLI
|
|
4
|
-
authenticates with a developer key and treats the server as the source of truth
|
|
5
|
-
(server-wins): ``setup`` / ``doctor`` / ``watch`` pull the server mode and write
|
|
6
|
-
it into the local ``refactor.config`` so a UI change reflects in terminal runs.
|
|
7
|
-
``refactor setup --mode`` also pushes the chosen variant up so both agree.
|
|
8
|
-
|
|
9
|
-
All operations are best-effort: offline / unauthenticated / unbound projects are
|
|
10
|
-
a silent no-op so they never break the surrounding command.
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
from __future__ import annotations
|
|
14
|
-
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
from typing import Callable
|
|
17
|
-
|
|
18
|
-
from refactor_core.constitution import CONFIG_FILENAME, find_project_files
|
|
19
|
-
from refactor_core.execution_variant import (
|
|
20
|
-
normalize_execution_variant,
|
|
21
|
-
resolve_execution_variant,
|
|
22
|
-
)
|
|
23
|
-
from refactor_core.store import resolve_project_id
|
|
24
|
-
|
|
25
|
-
from refactorai_cli.client import PlatformClient
|
|
26
|
-
from refactorai_cli.credentials import resolve_developer_key
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def _local_config_path(project_root: Path) -> Path | None:
|
|
30
|
-
files = find_project_files(project_root)
|
|
31
|
-
if files:
|
|
32
|
-
return Path(files[1])
|
|
33
|
-
candidate = Path(project_root) / CONFIG_FILENAME
|
|
34
|
-
return candidate if candidate.is_file() else None
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _read_local_variant(project_root: Path) -> str:
|
|
38
|
-
files = find_project_files(project_root)
|
|
39
|
-
if not files:
|
|
40
|
-
return ""
|
|
41
|
-
try:
|
|
42
|
-
from refactor_core.constitution import load_constitution
|
|
43
|
-
|
|
44
|
-
constitution = load_constitution(files[0])
|
|
45
|
-
return resolve_execution_variant(getattr(constitution, "settings", {}) or {})
|
|
46
|
-
except Exception:
|
|
47
|
-
return ""
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _write_variant_to_config(config_path: Path, variant: str) -> bool:
|
|
51
|
-
"""Replace (or append) the ``execution_variant`` line in refactor.config."""
|
|
52
|
-
try:
|
|
53
|
-
text = config_path.read_text(encoding="utf-8")
|
|
54
|
-
except OSError:
|
|
55
|
-
return False
|
|
56
|
-
lines = text.splitlines()
|
|
57
|
-
new_line = f"execution_variant: {variant}"
|
|
58
|
-
replaced = False
|
|
59
|
-
for idx, line in enumerate(lines):
|
|
60
|
-
if line.strip().startswith("execution_variant:"):
|
|
61
|
-
if line.strip() == new_line:
|
|
62
|
-
return False
|
|
63
|
-
lines[idx] = new_line
|
|
64
|
-
replaced = True
|
|
65
|
-
break
|
|
66
|
-
if not replaced:
|
|
67
|
-
lines.append(new_line)
|
|
68
|
-
try:
|
|
69
|
-
config_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
70
|
-
except OSError:
|
|
71
|
-
return False
|
|
72
|
-
return True
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def sync_execution_variant_from_server(
|
|
76
|
-
project_root: Path,
|
|
77
|
-
*,
|
|
78
|
-
client: PlatformClient | None = None,
|
|
79
|
-
emit: Callable[[str], None] | None = None,
|
|
80
|
-
) -> str | None:
|
|
81
|
-
"""Pull the server project's execution mode into local config (server-wins).
|
|
82
|
-
|
|
83
|
-
Returns the newly applied variant when the local config was updated, else
|
|
84
|
-
``None`` (already in sync, offline, unauthenticated, or unbound project).
|
|
85
|
-
"""
|
|
86
|
-
try:
|
|
87
|
-
project_id = resolve_project_id(project_root)
|
|
88
|
-
if not project_id:
|
|
89
|
-
return None
|
|
90
|
-
resolved = resolve_developer_key(project_root)
|
|
91
|
-
if not resolved:
|
|
92
|
-
return None
|
|
93
|
-
client = client or PlatformClient()
|
|
94
|
-
record = client.get_project_execution_mode(resolved.key, project_id)
|
|
95
|
-
except Exception:
|
|
96
|
-
return None
|
|
97
|
-
|
|
98
|
-
server_variant = normalize_execution_variant((record or {}).get("execution_mode"))
|
|
99
|
-
if not server_variant:
|
|
100
|
-
return None
|
|
101
|
-
if server_variant == _read_local_variant(project_root):
|
|
102
|
-
return None
|
|
103
|
-
config_path = _local_config_path(project_root)
|
|
104
|
-
if config_path is None:
|
|
105
|
-
return None
|
|
106
|
-
if _write_variant_to_config(config_path, server_variant):
|
|
107
|
-
if emit:
|
|
108
|
-
emit(f"Synced execution_variant from server: {server_variant}")
|
|
109
|
-
return server_variant
|
|
110
|
-
return None
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def push_execution_variant_to_server(
|
|
114
|
-
project_root: Path,
|
|
115
|
-
variant: str,
|
|
116
|
-
*,
|
|
117
|
-
client: PlatformClient | None = None,
|
|
118
|
-
emit: Callable[[str], None] | None = None,
|
|
119
|
-
) -> bool:
|
|
120
|
-
"""Push a locally chosen execution variant to the server project.
|
|
121
|
-
|
|
122
|
-
Best-effort; returns ``True`` on a successful push, else ``False``.
|
|
123
|
-
"""
|
|
124
|
-
normalized = normalize_execution_variant(variant)
|
|
125
|
-
if not normalized:
|
|
126
|
-
return False
|
|
127
|
-
try:
|
|
128
|
-
project_id = resolve_project_id(project_root)
|
|
129
|
-
if not project_id:
|
|
130
|
-
return False
|
|
131
|
-
resolved = resolve_developer_key(project_root)
|
|
132
|
-
if not resolved:
|
|
133
|
-
return False
|
|
134
|
-
client = client or PlatformClient()
|
|
135
|
-
client.set_project_execution_mode(resolved.key, project_id, normalized)
|
|
136
|
-
except Exception:
|
|
137
|
-
return False
|
|
138
|
-
if emit:
|
|
139
|
-
emit(f"Pushed execution_variant to server: {normalized}")
|
|
140
|
-
return True
|
|
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
|
{refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli/commands/runtime_proxy_cmds.py
RENAMED
|
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
|
|
File without changes
|
{refactorai_cli-0.7.12 → refactorai_cli-0.7.13}/refactorai_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|