refactorai-cli 0.2.29__tar.gz → 0.3.0__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.29 → refactorai_cli-0.3.0}/PKG-INFO +15 -8
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/README.md +13 -7
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/pyproject.toml +5 -1
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/__init__.py +1 -1
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/cloud_cmds.py +2 -1
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/run_cmds.py +215 -87
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/runtime_proxy_cmds.py +2 -2
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli.egg-info/PKG-INFO +15 -8
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli.egg-info/requires.txt +1 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/auth.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/client.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/__init__.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/auth_cmds.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/engine_cmds.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/model_cmds.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/rules_cmds.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/runtime_cmds.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/setup_cmds.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/control_plane.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/credentials.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/local_constitution.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/local_engine_runtime.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/local_paths.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/main.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/model_policy.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/runtime_manager.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/settings.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/setup_flow.py +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli.egg-info/SOURCES.txt +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli.egg-info/dependency_links.txt +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli.egg-info/entry_points.txt +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli.egg-info/top_level.txt +0 -0
- {refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/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.3.0
|
|
4
4
|
Summary: Local-first CLI for the refactor platform
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -8,6 +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.0.0
|
|
11
12
|
|
|
12
13
|
# refactorai-cli
|
|
13
14
|
|
|
@@ -53,28 +54,34 @@ python -m twine upload ./refactorai-cli/dist/*
|
|
|
53
54
|
## Install test (local)
|
|
54
55
|
|
|
55
56
|
```bash
|
|
56
|
-
python -m pip install ./refactorai-cli/dist/refactorai_cli-0.
|
|
57
|
+
python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.0-py3-none-any.whl
|
|
57
58
|
refactor --version
|
|
58
59
|
```
|
|
59
60
|
|
|
60
61
|
## Cloud BYOK setup (bring your own provider key)
|
|
61
62
|
|
|
62
63
|
Run cloud inference with your own provider credentials (no local runtime
|
|
63
|
-
required).
|
|
64
|
-
|
|
64
|
+
required). Preferred path is env-key in project config (`provider_key:
|
|
65
|
+
${ENV_VAR}`). Platform-stored credentials (`credential_ref`) are optional
|
|
66
|
+
fallback.
|
|
65
67
|
|
|
66
68
|
```bash
|
|
67
69
|
# 1. Authenticate (developer key with the run:byok entitlement).
|
|
68
70
|
refactor login
|
|
69
71
|
|
|
70
|
-
#
|
|
72
|
+
# 2A. Preferred: set BYOK directly from project env in refactor.config:
|
|
73
|
+
#
|
|
74
|
+
# mode: cloud_byok
|
|
75
|
+
# provider: openai
|
|
76
|
+
# model_id: gpt-4.1-mini
|
|
77
|
+
# provider_key: ${OPENAI_API_KEY}
|
|
78
|
+
#
|
|
79
|
+
# 2B. Optional fallback: register a platform-stored credential from env.
|
|
71
80
|
export OPENAI_API_KEY=sk-...
|
|
72
81
|
refactor cloud credentials set --provider openai --from-env OPENAI_API_KEY --label "my key"
|
|
73
82
|
# -> prints a credential_ref, e.g. cred_01J...
|
|
74
83
|
|
|
75
|
-
# 3.
|
|
76
|
-
# mode: cloud_byok
|
|
77
|
-
# provider: openai
|
|
84
|
+
# 3. If you use platform-stored fallback, set credential_ref in config:
|
|
78
85
|
# credential_ref: cred_01J...
|
|
79
86
|
|
|
80
87
|
# 4. Verify readiness, then run.
|
|
@@ -42,28 +42,34 @@ 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.
|
|
45
|
+
python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.0-py3-none-any.whl
|
|
46
46
|
refactor --version
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
## Cloud BYOK setup (bring your own provider key)
|
|
50
50
|
|
|
51
51
|
Run cloud inference with your own provider credentials (no local runtime
|
|
52
|
-
required).
|
|
53
|
-
|
|
52
|
+
required). Preferred path is env-key in project config (`provider_key:
|
|
53
|
+
${ENV_VAR}`). Platform-stored credentials (`credential_ref`) are optional
|
|
54
|
+
fallback.
|
|
54
55
|
|
|
55
56
|
```bash
|
|
56
57
|
# 1. Authenticate (developer key with the run:byok entitlement).
|
|
57
58
|
refactor login
|
|
58
59
|
|
|
59
|
-
#
|
|
60
|
+
# 2A. Preferred: set BYOK directly from project env in refactor.config:
|
|
61
|
+
#
|
|
62
|
+
# mode: cloud_byok
|
|
63
|
+
# provider: openai
|
|
64
|
+
# model_id: gpt-4.1-mini
|
|
65
|
+
# provider_key: ${OPENAI_API_KEY}
|
|
66
|
+
#
|
|
67
|
+
# 2B. Optional fallback: register a platform-stored credential from env.
|
|
60
68
|
export OPENAI_API_KEY=sk-...
|
|
61
69
|
refactor cloud credentials set --provider openai --from-env OPENAI_API_KEY --label "my key"
|
|
62
70
|
# -> prints a credential_ref, e.g. cred_01J...
|
|
63
71
|
|
|
64
|
-
# 3.
|
|
65
|
-
# mode: cloud_byok
|
|
66
|
-
# provider: openai
|
|
72
|
+
# 3. If you use platform-stored fallback, set credential_ref in config:
|
|
67
73
|
# credential_ref: cred_01J...
|
|
68
74
|
|
|
69
75
|
# 4. Verify readiness, then run.
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "refactorai-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Local-first CLI for the refactor platform"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
7
|
+
# R21 core split: the CLI depends on the PUBLIC client core only. The proprietary
|
|
8
|
+
# engine (refactor-core-engine) is never installed on a developer's machine via
|
|
9
|
+
# pip; local mode runs the engine from the downloaded runtime binary instead.
|
|
7
10
|
dependencies = [
|
|
8
11
|
"typer>=0.12.0",
|
|
9
12
|
"httpx>=0.27.0",
|
|
10
13
|
"rich>=13.7.0",
|
|
11
14
|
"PyYAML>=6.0.1",
|
|
15
|
+
"refactorai-core>=3.0.0",
|
|
12
16
|
]
|
|
13
17
|
|
|
14
18
|
[project.scripts]
|
|
@@ -114,7 +114,8 @@ def credentials_set(
|
|
|
114
114
|
f"[green]Credential registered[/green] for provider [bold]{data.get('provider', provider)}[/bold].\n"
|
|
115
115
|
f" credential_ref: [bold]{data.get('credential_ref', '?')}[/bold]\n"
|
|
116
116
|
f" masked_secret: {data.get('masked_secret', '****')}\n"
|
|
117
|
-
"
|
|
117
|
+
"Use this as an optional cloud_byok fallback (`credential_ref:`). "
|
|
118
|
+
"Preferred path is env-key in project config (`provider_key: ${ENV_VAR}`)."
|
|
118
119
|
)
|
|
119
120
|
|
|
120
121
|
|
|
@@ -52,9 +52,6 @@ from refactor_core.apply import (
|
|
|
52
52
|
)
|
|
53
53
|
from refactor_core.budgeting import compute_budget
|
|
54
54
|
from refactor_core.capabilities import resolve_capabilities
|
|
55
|
-
from refactor_core.execution import resolve_mode, run_with_mode
|
|
56
|
-
from refactor_core.execution.rr_executor import run_refactor_requests
|
|
57
|
-
from refactor_core.execution.structural_ops import structural_compatibility_matrix
|
|
58
55
|
from refactor_core.gate import evaluate_gate
|
|
59
56
|
from refactor_core.refactor_requests import (
|
|
60
57
|
DEFERRED,
|
|
@@ -70,30 +67,10 @@ from refactor_core.refactor_requests import (
|
|
|
70
67
|
from refactor_core.refactor_ops import remediation_hint
|
|
71
68
|
from refactor_core.indexing import index_target
|
|
72
69
|
from refactor_core.models import Finding, GeneratedTest, PatchGroup, RunArtifact
|
|
73
|
-
from refactor_core.pipeline import build_refactor_prompt, build_review_prompt
|
|
74
|
-
from refactor_core.providers import get_provider, resolve_provider_name, supported_providers
|
|
75
70
|
from refactor_core.rules import annotate_documents_with_rules, filter_documents_by_rules
|
|
76
71
|
from refactor_core.security import merge_refactor_findings, suppress_findings_for_rr
|
|
77
72
|
from refactor_core.security.semgrep_runner import EMBEDDED_SEMGREP_SENTINEL
|
|
78
73
|
from refactor_core.security.suppression import SecuritySuppression
|
|
79
|
-
from refactor_core.sandbox_runtime import (
|
|
80
|
-
DEFAULT_IMAGE as SANDBOX_DEFAULT_IMAGE,
|
|
81
|
-
DEFAULT_SHELL as SANDBOX_DEFAULT_SHELL,
|
|
82
|
-
attach_shell as attach_sandbox_shell,
|
|
83
|
-
command_exists_in_container,
|
|
84
|
-
command_exists_in_sandbox,
|
|
85
|
-
detect_installer_in_container,
|
|
86
|
-
detect_installer_in_sandbox,
|
|
87
|
-
ensure_sandbox,
|
|
88
|
-
exec_in_container,
|
|
89
|
-
exec_in_container_stream,
|
|
90
|
-
exec_in_sandbox,
|
|
91
|
-
exec_in_sandbox_stream,
|
|
92
|
-
remove_sandbox_container,
|
|
93
|
-
sandbox_name,
|
|
94
|
-
resolve_runtime,
|
|
95
|
-
stop_sandbox,
|
|
96
|
-
)
|
|
97
74
|
from refactor_core.store import (
|
|
98
75
|
append_sandbox_install_history,
|
|
99
76
|
clear_sandbox_state,
|
|
@@ -125,6 +102,27 @@ from refactorai_cli.setup_flow import (
|
|
|
125
102
|
|
|
126
103
|
console = Console()
|
|
127
104
|
|
|
105
|
+
# Local mirrors of engine-only sandbox defaults (R21 core split). These are used
|
|
106
|
+
# as Typer option defaults, which are evaluated at import time, so they cannot be
|
|
107
|
+
# lazy-imported from the engine-only ``refactor_core.enginecore.sandbox_runtime`` module.
|
|
108
|
+
# They mirror ``sandbox_runtime.DEFAULT_IMAGE`` / ``DEFAULT_SHELL`` and are only
|
|
109
|
+
# meaningful in local mode (where the engine is present via the runtime binary).
|
|
110
|
+
SANDBOX_DEFAULT_IMAGE = "docker.io/library/alpine:3.20"
|
|
111
|
+
SANDBOX_DEFAULT_SHELL = "/bin/sh"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _sandbox_runtime():
|
|
115
|
+
"""Lazily import the engine-only sandbox runtime module (local mode only).
|
|
116
|
+
|
|
117
|
+
Cloud modes never touch the sandbox; this import is therefore reached only on
|
|
118
|
+
the local execution path, where the engine is present (inside the runtime
|
|
119
|
+
binary). A client-only install that never runs local commands never imports
|
|
120
|
+
it, keeping engine code off the developer's disk (R21 core split).
|
|
121
|
+
"""
|
|
122
|
+
from refactor_core.enginecore import sandbox_runtime as _sr
|
|
123
|
+
|
|
124
|
+
return _sr
|
|
125
|
+
|
|
128
126
|
|
|
129
127
|
def _read_stage_output(stage_id: str) -> dict:
|
|
130
128
|
path = stage_output_path(stage_id)
|
|
@@ -167,7 +165,14 @@ def _default_config_for_init() -> tuple[str, str]:
|
|
|
167
165
|
)
|
|
168
166
|
return config_text, "local_server"
|
|
169
167
|
if backend == BACKEND_BYOK:
|
|
170
|
-
|
|
168
|
+
config_text = DEFAULT_CONFIG.replace("mode: local", "mode: cloud_byok", 1)
|
|
169
|
+
config_text = config_text.replace("provider: heuristic", "provider: openai", 1)
|
|
170
|
+
config_text = config_text.replace(
|
|
171
|
+
"model_id: heuristic-local-v1",
|
|
172
|
+
"model_id: gpt-4.1-mini\nprovider_key: ${OPENAI_API_KEY}\n# credential_ref: cred_01J...",
|
|
173
|
+
1,
|
|
174
|
+
)
|
|
175
|
+
return config_text, "byok"
|
|
171
176
|
return DEFAULT_CONFIG, "default"
|
|
172
177
|
|
|
173
178
|
_SEVERITY_STYLE = {
|
|
@@ -631,14 +636,14 @@ def _cloud_inference(
|
|
|
631
636
|
documents, _scope_meta = filter_documents_by_rules(documents, project_root=Path(project_root))
|
|
632
637
|
rule_meta = annotate_documents_with_rules(documents, project_root=Path(project_root))
|
|
633
638
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
639
|
+
# Server-side prompt building (R21): send raw indexed documents + the
|
|
640
|
+
# constitution body and let the platform build the prompts. The proprietary
|
|
641
|
+
# prompt engineering stays off the developer's machine. Prior review findings
|
|
642
|
+
# are forwarded for the refactor stage so cloud `code` targets the same
|
|
643
|
+
# issues (parity with local RR).
|
|
644
|
+
request_findings = [
|
|
645
|
+
f.model_dump() if hasattr(f, "model_dump") else dict(f) for f in (findings or [])
|
|
646
|
+
]
|
|
642
647
|
|
|
643
648
|
execution_mode = _cloud_execution_mode(constitution) or "cloud_managed"
|
|
644
649
|
auth_ctx = ensure_authenticated(project_root)
|
|
@@ -646,23 +651,37 @@ def _cloud_inference(
|
|
|
646
651
|
"stage": stage,
|
|
647
652
|
"execution_mode": execution_mode,
|
|
648
653
|
"constitution_hash": constitution.content_hash,
|
|
649
|
-
"
|
|
650
|
-
"
|
|
654
|
+
"documents": documents,
|
|
655
|
+
"constitution_body": getattr(constitution, "body", "") or "",
|
|
656
|
+
"findings": request_findings if stage != "review" else [],
|
|
651
657
|
"max_alerts": max_alerts,
|
|
652
658
|
"model_hint": model_hint or str(constitution.get_setting("model_id", "managed-default") or "managed-default"),
|
|
653
659
|
}
|
|
654
660
|
|
|
655
661
|
if execution_mode == "cloud_byok":
|
|
656
|
-
credential_ref = str(constitution.get_setting("credential_ref", "") or "").strip()
|
|
657
662
|
provider_name = str(constitution.get_setting("provider", "") or "").strip()
|
|
658
|
-
|
|
663
|
+
provider_key = str(constitution.get_setting("provider_key", "") or "").strip()
|
|
664
|
+
credential_ref = str(constitution.get_setting("credential_ref", "") or "").strip()
|
|
665
|
+
|
|
666
|
+
# Env-key-first BYOK path: when provider_key is configured (typically via
|
|
667
|
+
# ${ENV_VAR} interpolation), prefer it over credential_ref.
|
|
668
|
+
if provider_key:
|
|
669
|
+
if not provider_name or provider_name.lower() == "cloud":
|
|
670
|
+
raise RuntimeError(
|
|
671
|
+
"cloud_byok with provider_key requires an explicit provider "
|
|
672
|
+
"(e.g. provider: openai)."
|
|
673
|
+
)
|
|
674
|
+
payload["provider_key"] = provider_key
|
|
675
|
+
payload["provider"] = provider_name
|
|
676
|
+
elif credential_ref:
|
|
677
|
+
payload["credential_ref"] = credential_ref
|
|
678
|
+
if provider_name and provider_name.lower() != "cloud":
|
|
679
|
+
payload["provider"] = provider_name
|
|
680
|
+
else:
|
|
659
681
|
raise RuntimeError(
|
|
660
|
-
"cloud_byok requires
|
|
661
|
-
"
|
|
682
|
+
"cloud_byok requires either `provider_key` (preferred env-key path) "
|
|
683
|
+
"or `credential_ref` (platform-stored credential path) in refactor.config."
|
|
662
684
|
)
|
|
663
|
-
payload["credential_ref"] = credential_ref
|
|
664
|
-
if provider_name and provider_name.lower() != "cloud":
|
|
665
|
-
payload["provider"] = provider_name
|
|
666
685
|
|
|
667
686
|
idempotency_key = _stable_idempotency_key(
|
|
668
687
|
account_id=auth_ctx.account_id,
|
|
@@ -911,7 +930,7 @@ def init(
|
|
|
911
930
|
elif config_profile == "byok":
|
|
912
931
|
console.print(
|
|
913
932
|
"[green]Configured[/green] refactor.config for BYOK setup "
|
|
914
|
-
"(
|
|
933
|
+
"(mode=cloud_byok, env-key-first via provider_key; credential_ref is optional fallback)."
|
|
915
934
|
)
|
|
916
935
|
console.print(
|
|
917
936
|
f"[green]Registered[/green] project in central store: {project_store_dir(project_root)}"
|
|
@@ -1166,6 +1185,15 @@ def _sandbox_preflight(
|
|
|
1166
1185
|
workdir: str | None = None,
|
|
1167
1186
|
) -> tuple[bool, str]:
|
|
1168
1187
|
"""Run sandbox preflight and optionally perform approved install plan."""
|
|
1188
|
+
_sr = _sandbox_runtime()
|
|
1189
|
+
command_exists_in_container = _sr.command_exists_in_container
|
|
1190
|
+
command_exists_in_sandbox = _sr.command_exists_in_sandbox
|
|
1191
|
+
exec_in_container = _sr.exec_in_container
|
|
1192
|
+
exec_in_container_stream = _sr.exec_in_container_stream
|
|
1193
|
+
exec_in_sandbox = _sr.exec_in_sandbox
|
|
1194
|
+
exec_in_sandbox_stream = _sr.exec_in_sandbox_stream
|
|
1195
|
+
detect_installer_in_container = _sr.detect_installer_in_container
|
|
1196
|
+
detect_installer_in_sandbox = _sr.detect_installer_in_sandbox
|
|
1169
1197
|
|
|
1170
1198
|
has_container_ctx = bool(runtime and container_name)
|
|
1171
1199
|
resolved_workdir = str(workdir or "/workspace")
|
|
@@ -1465,6 +1493,10 @@ def _ensure_sandbox_ready(
|
|
|
1465
1493
|
continue_on_preflight_failure: bool = False,
|
|
1466
1494
|
) -> dict | None:
|
|
1467
1495
|
"""Ensure sandbox lifecycle + preflight for stage execution."""
|
|
1496
|
+
_sr = _sandbox_runtime()
|
|
1497
|
+
resolve_runtime = _sr.resolve_runtime
|
|
1498
|
+
sandbox_name = _sr.sandbox_name
|
|
1499
|
+
ensure_sandbox = _sr.ensure_sandbox
|
|
1468
1500
|
sandbox_cfg = _sandbox_settings(project_config)
|
|
1469
1501
|
lifecycle = _sandbox_lifecycle(project_config)
|
|
1470
1502
|
ephemeral = lifecycle == "per_run" and stage in {"review", "code"}
|
|
@@ -1533,6 +1565,9 @@ def _teardown_sandbox_context(project_root: Path, ctx: dict | None) -> None:
|
|
|
1533
1565
|
"""Clean up ephemeral per-run sandboxes."""
|
|
1534
1566
|
if not ctx or not ctx.get("ephemeral"):
|
|
1535
1567
|
return
|
|
1568
|
+
_sr = _sandbox_runtime()
|
|
1569
|
+
remove_sandbox_container = _sr.remove_sandbox_container
|
|
1570
|
+
sandbox_name = _sr.sandbox_name
|
|
1536
1571
|
runtime = str(ctx.get("runtime") or "")
|
|
1537
1572
|
name = str(ctx.get("container_name") or "")
|
|
1538
1573
|
ok, msg = remove_sandbox_container(runtime=runtime, container_name=name)
|
|
@@ -1567,6 +1602,10 @@ def start(
|
|
|
1567
1602
|
project_root, _constitution, project_config = _load_project()
|
|
1568
1603
|
register_project(project_root)
|
|
1569
1604
|
|
|
1605
|
+
_sr = _sandbox_runtime()
|
|
1606
|
+
resolve_runtime = _sr.resolve_runtime
|
|
1607
|
+
ensure_sandbox = _sr.ensure_sandbox
|
|
1608
|
+
attach_sandbox_shell = _sr.attach_shell
|
|
1570
1609
|
sandbox_cfg = _sandbox_settings(project_config)
|
|
1571
1610
|
preferred_runtime = str(runtime or sandbox_cfg.get("runtime", "podman") or "podman")
|
|
1572
1611
|
resolved_runtime, reason = resolve_runtime(preferred_runtime)
|
|
@@ -1642,7 +1681,7 @@ def stop() -> None:
|
|
|
1642
1681
|
"Switch to a valid directory and retry."
|
|
1643
1682
|
)
|
|
1644
1683
|
raise typer.Exit(code=1) from None
|
|
1645
|
-
ok, message = stop_sandbox(project_root=project_root)
|
|
1684
|
+
ok, message = _sandbox_runtime().stop_sandbox(project_root=project_root)
|
|
1646
1685
|
if ok:
|
|
1647
1686
|
console.print(f"[green]{message}[/green]")
|
|
1648
1687
|
return
|
|
@@ -1665,7 +1704,7 @@ def shell(
|
|
|
1665
1704
|
if not sys.stdin.isatty():
|
|
1666
1705
|
console.print("[yellow]Non-interactive terminal; cannot attach shell.[/yellow]")
|
|
1667
1706
|
raise typer.Exit(code=1)
|
|
1668
|
-
ok, message =
|
|
1707
|
+
ok, message = _sandbox_runtime().attach_shell(project_root=project_root, shell_path=shell_path)
|
|
1669
1708
|
if ok:
|
|
1670
1709
|
return
|
|
1671
1710
|
console.print(f"[red]Sandbox shell failed:[/red] {message}")
|
|
@@ -1777,6 +1816,9 @@ def review(
|
|
|
1777
1816
|
model_hint=provider,
|
|
1778
1817
|
)
|
|
1779
1818
|
else:
|
|
1819
|
+
from refactor_core.execution.orchestrator import run_with_mode
|
|
1820
|
+
from refactor_core.providers import get_provider
|
|
1821
|
+
|
|
1780
1822
|
selected_provider = get_provider(
|
|
1781
1823
|
constitution, project_root=project_root, provider_name=provider
|
|
1782
1824
|
)
|
|
@@ -1993,6 +2035,8 @@ def code(
|
|
|
1993
2035
|
)
|
|
1994
2036
|
selected_provider = None
|
|
1995
2037
|
if not cloud:
|
|
2038
|
+
from refactor_core.providers import get_provider
|
|
2039
|
+
|
|
1996
2040
|
selected_provider = get_provider(
|
|
1997
2041
|
constitution, project_root=project_root, provider_name=provider
|
|
1998
2042
|
)
|
|
@@ -2015,6 +2059,8 @@ def code(
|
|
|
2015
2059
|
)
|
|
2016
2060
|
cloud_review_findings = list(review_artifact.findings or [])
|
|
2017
2061
|
else:
|
|
2062
|
+
from refactor_core.execution.orchestrator import run_with_mode
|
|
2063
|
+
|
|
2018
2064
|
with console.status("[cyan]review[/cyan] analyzing...", spinner="dots") as status:
|
|
2019
2065
|
review_progress = _agent_progress_logger(
|
|
2020
2066
|
"review", status=status, verbose_steps=True
|
|
@@ -2138,6 +2184,8 @@ def code(
|
|
|
2138
2184
|
)
|
|
2139
2185
|
return
|
|
2140
2186
|
|
|
2187
|
+
from refactor_core.execution.rr_executor import run_refactor_requests
|
|
2188
|
+
|
|
2141
2189
|
with console.status("[cyan]code[/cyan] running...", spinner="dots") as status:
|
|
2142
2190
|
rr_config = (
|
|
2143
2191
|
dict(getattr(project_config, "settings", {}) or {})
|
|
@@ -2214,6 +2262,9 @@ def requests(
|
|
|
2214
2262
|
if not deferred_rrs:
|
|
2215
2263
|
console.print("[dim]No deferred requests to retry.[/dim]")
|
|
2216
2264
|
else:
|
|
2265
|
+
from refactor_core.execution.rr_executor import run_refactor_requests
|
|
2266
|
+
from refactor_core.providers import get_provider
|
|
2267
|
+
|
|
2217
2268
|
selected_provider = get_provider(
|
|
2218
2269
|
constitution, project_root=project_root, provider_name=provider
|
|
2219
2270
|
)
|
|
@@ -3065,60 +3116,123 @@ def _cloud_doctor(
|
|
|
3065
3116
|
console.print("[dim]Run `refactor login` or set REFACTOR_API_KEY.[/dim]")
|
|
3066
3117
|
raise typer.Exit(code=1) from exc
|
|
3067
3118
|
|
|
3068
|
-
# BYOK
|
|
3069
|
-
#
|
|
3119
|
+
# BYOK supports two secret sources:
|
|
3120
|
+
# 1) provider_key (preferred env-key path), or
|
|
3121
|
+
# 2) credential_ref (platform-stored secret fallback).
|
|
3070
3122
|
credential_ref = ""
|
|
3123
|
+
provider_key = ""
|
|
3124
|
+
provider_name = ""
|
|
3071
3125
|
if execution_mode == "cloud_byok":
|
|
3126
|
+
provider_name = str(constitution.get_setting("provider", "") or "").strip()
|
|
3127
|
+
provider_key = str(constitution.get_setting("provider_key", "") or "").strip()
|
|
3072
3128
|
credential_ref = str(constitution.get_setting("credential_ref", "") or "").strip()
|
|
3073
|
-
if not
|
|
3129
|
+
if not provider_name or provider_name.lower() == "cloud":
|
|
3074
3130
|
console.print(
|
|
3075
|
-
"[red]cloud_byok[/red]:
|
|
3076
|
-
"
|
|
3131
|
+
"[red]cloud_byok[/red]: set an explicit `provider` "
|
|
3132
|
+
"(for example `provider: openai`)."
|
|
3133
|
+
)
|
|
3134
|
+
raise typer.Exit(code=1)
|
|
3135
|
+
console.print(f"[bold]provider[/bold]: {provider_name}")
|
|
3136
|
+
if provider_key:
|
|
3137
|
+
console.print(
|
|
3138
|
+
"[green]provider_key[/green]: set via config/env interpolation "
|
|
3139
|
+
"(preferred BYOK path)."
|
|
3140
|
+
)
|
|
3141
|
+
elif credential_ref:
|
|
3142
|
+
console.print(f"[green]credential_ref[/green]: {credential_ref}")
|
|
3143
|
+
else:
|
|
3144
|
+
console.print(
|
|
3145
|
+
"[red]cloud_byok[/red]: set either `provider_key` (preferred) "
|
|
3146
|
+
"or `credential_ref` in refactor.config."
|
|
3077
3147
|
)
|
|
3078
3148
|
raise typer.Exit(code=1)
|
|
3079
|
-
console.print(f"[green]credential_ref[/green]: {credential_ref}")
|
|
3080
|
-
provider_name = str(constitution.get_setting("provider", "") or "").strip()
|
|
3081
|
-
if provider_name and provider_name.lower() != "cloud":
|
|
3082
|
-
console.print(f"[bold]provider[/bold]: {provider_name}")
|
|
3083
3149
|
|
|
3084
3150
|
if skip_ping:
|
|
3085
3151
|
console.print("[dim]Skipping cloud endpoint reachability check (--skip-ping).[/dim]")
|
|
3086
3152
|
return
|
|
3087
3153
|
|
|
3088
|
-
# Verify
|
|
3089
|
-
# the credential endpoint is reachable.
|
|
3154
|
+
# Verify BYOK readiness and endpoint reachability.
|
|
3090
3155
|
if execution_mode == "cloud_byok":
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3156
|
+
if provider_key:
|
|
3157
|
+
# Env-key path: perform a lightweight inference ping so entitlement +
|
|
3158
|
+
# provider auth are validated together.
|
|
3159
|
+
try:
|
|
3160
|
+
response = httpx.post(
|
|
3161
|
+
f"{platform_url()}/v1/inference/refactor",
|
|
3162
|
+
headers={
|
|
3163
|
+
"Authorization": f"Bearer {auth_ctx.key.key}",
|
|
3164
|
+
"Content-Type": "application/json",
|
|
3165
|
+
"Idempotency-Key": f"doctor-cloud-byok-{uuid.uuid4().hex}",
|
|
3166
|
+
},
|
|
3167
|
+
json={
|
|
3168
|
+
"stage": "review",
|
|
3169
|
+
"execution_mode": "cloud_byok",
|
|
3170
|
+
"provider": provider_name,
|
|
3171
|
+
"provider_key": provider_key,
|
|
3172
|
+
"constitution_hash": "doctor",
|
|
3173
|
+
"system_prompt": "doctor ping",
|
|
3174
|
+
"user_prompt": "doctor ping",
|
|
3175
|
+
"max_alerts": 1,
|
|
3176
|
+
},
|
|
3177
|
+
timeout=20.0,
|
|
3178
|
+
)
|
|
3179
|
+
except httpx.HTTPError as exc:
|
|
3180
|
+
console.print(f"[red]cloud endpoint[/red]: unreachable ({exc}).")
|
|
3181
|
+
raise typer.Exit(code=1) from exc
|
|
3182
|
+
if response.status_code == 402:
|
|
3183
|
+
console.print(
|
|
3184
|
+
"[red]entitlement[/red]: BYOK not enabled for this account (missing `run:byok`). "
|
|
3185
|
+
"Contact your admin to enable BYOK cloud execution."
|
|
3186
|
+
)
|
|
3187
|
+
raise typer.Exit(code=1)
|
|
3188
|
+
if response.status_code == 401:
|
|
3189
|
+
console.print("[red]developer key[/red]: unauthorized (invalid or revoked). Run `refactor login`.")
|
|
3190
|
+
raise typer.Exit(code=1)
|
|
3191
|
+
if response.status_code >= 400:
|
|
3192
|
+
detail = ""
|
|
3193
|
+
try:
|
|
3194
|
+
detail = str((response.json() or {}).get("detail", ""))
|
|
3195
|
+
except Exception:
|
|
3196
|
+
detail = response.text[:200]
|
|
3197
|
+
console.print(
|
|
3198
|
+
f"[red]cloud BYOK ping failed[/red] ({response.status_code})"
|
|
3199
|
+
+ (f": {detail}" if detail else ".")
|
|
3200
|
+
)
|
|
3201
|
+
raise typer.Exit(code=1)
|
|
3202
|
+
console.print("[green]cloud endpoint[/green]: reachable.")
|
|
3203
|
+
console.print("[green]BYOK provider auth[/green]: OK")
|
|
3204
|
+
else:
|
|
3205
|
+
try:
|
|
3206
|
+
response = httpx.get(
|
|
3207
|
+
f"{platform_url()}/v1/inference/credentials",
|
|
3208
|
+
headers={"Authorization": f"Bearer {auth_ctx.key.key}"},
|
|
3209
|
+
timeout=20.0,
|
|
3210
|
+
)
|
|
3211
|
+
except httpx.HTTPError as exc:
|
|
3212
|
+
console.print(f"[red]cloud endpoint[/red]: unreachable ({exc}).")
|
|
3213
|
+
raise typer.Exit(code=1) from exc
|
|
3214
|
+
if response.status_code == 402:
|
|
3215
|
+
console.print(
|
|
3216
|
+
"[red]entitlement[/red]: BYOK not enabled for this account (missing `run:byok`). "
|
|
3217
|
+
"Contact your admin to enable BYOK cloud execution."
|
|
3218
|
+
)
|
|
3219
|
+
raise typer.Exit(code=1)
|
|
3220
|
+
if response.status_code == 401:
|
|
3221
|
+
console.print("[red]developer key[/red]: unauthorized (invalid or revoked). Run `refactor login`.")
|
|
3222
|
+
raise typer.Exit(code=1)
|
|
3223
|
+
if response.status_code >= 400:
|
|
3224
|
+
console.print(f"[red]cloud endpoint[/red]: credential check failed ({response.status_code}).")
|
|
3225
|
+
raise typer.Exit(code=1)
|
|
3226
|
+
data = response.json() if response.content else {}
|
|
3227
|
+
refs = {str(row.get("credential_ref", "")) for row in (data.get("credentials", []) or [])}
|
|
3228
|
+
if credential_ref not in refs:
|
|
3229
|
+
console.print(
|
|
3230
|
+
f"[red]credential[/red]: `{credential_ref}` not found for this account. "
|
|
3231
|
+
"Check `refactor cloud credentials list`."
|
|
3232
|
+
)
|
|
3233
|
+
raise typer.Exit(code=1)
|
|
3234
|
+
console.print("[green]credential[/green]: found and owned by this account.")
|
|
3235
|
+
console.print("[green]cloud endpoint[/green]: reachable.")
|
|
3122
3236
|
else:
|
|
3123
3237
|
console.print(
|
|
3124
3238
|
"[dim]cloud_managed uses platform-managed provider credentials; "
|
|
@@ -3170,6 +3284,16 @@ def doctor(
|
|
|
3170
3284
|
)
|
|
3171
3285
|
return
|
|
3172
3286
|
|
|
3287
|
+
# Local-mode doctor uses engine-only provider/execution helpers. These are
|
|
3288
|
+
# lazily imported here so a client-only install (cloud modes) never requires
|
|
3289
|
+
# the engine on disk (R21 core split); local mode has the engine via binary.
|
|
3290
|
+
from refactor_core.execution.modes import resolve_mode
|
|
3291
|
+
from refactor_core.providers import (
|
|
3292
|
+
get_provider,
|
|
3293
|
+
resolve_provider_name,
|
|
3294
|
+
supported_providers,
|
|
3295
|
+
)
|
|
3296
|
+
|
|
3173
3297
|
mode = str(constitution.get_setting("mode", "local") or "local")
|
|
3174
3298
|
provider_name = resolve_provider_name(constitution, provider_name=provider)
|
|
3175
3299
|
|
|
@@ -3315,7 +3439,7 @@ def _report_sandbox(project_root: Path, config) -> None:
|
|
|
3315
3439
|
enabled = True
|
|
3316
3440
|
runtime_pref = str(sandbox_cfg.get("runtime", "podman") or "podman")
|
|
3317
3441
|
image = str(sandbox_cfg.get("image") or SANDBOX_DEFAULT_IMAGE)
|
|
3318
|
-
resolved_runtime, reason = resolve_runtime(runtime_pref)
|
|
3442
|
+
resolved_runtime, reason = _sandbox_runtime().resolve_runtime(runtime_pref)
|
|
3319
3443
|
|
|
3320
3444
|
console.print(f"[bold]project[/bold]: {project_root}")
|
|
3321
3445
|
console.print(f"[bold]sandbox enabled[/bold]: {enabled} [dim](mandatory for local runs)[/dim]")
|
|
@@ -3365,6 +3489,10 @@ def _report_sandbox(project_root: Path, config) -> None:
|
|
|
3365
3489
|
|
|
3366
3490
|
|
|
3367
3491
|
def _report_structural_readiness(project_root: Path, config) -> None:
|
|
3492
|
+
# Engine-only structural ops (local doctor diagnostic); lazy so client-only
|
|
3493
|
+
# installs never require the engine on disk (R21 core split).
|
|
3494
|
+
from refactor_core.execution.structural_ops import structural_compatibility_matrix
|
|
3495
|
+
|
|
3368
3496
|
settings = dict(getattr(config, "settings", {}) or {})
|
|
3369
3497
|
refactor_cfg = settings.get("refactor", {})
|
|
3370
3498
|
if not isinstance(refactor_cfg, dict):
|
{refactorai_cli-0.2.29 → refactorai_cli-0.3.0}/refactorai_cli/commands/runtime_proxy_cmds.py
RENAMED
|
@@ -142,8 +142,8 @@ def _exec_runtime(command_name: str, passthrough_args: list[str]) -> None:
|
|
|
142
142
|
|
|
143
143
|
|
|
144
144
|
def init(ctx: typer.Context) -> None:
|
|
145
|
-
"""Initialize project
|
|
146
|
-
|
|
145
|
+
"""Initialize project config locally (no runtime required)."""
|
|
146
|
+
_delegate_to_run_cmds("init", list(ctx.args))
|
|
147
147
|
|
|
148
148
|
|
|
149
149
|
def start(ctx: typer.Context) -> None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: refactorai-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Local-first CLI for the refactor platform
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -8,6 +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.0.0
|
|
11
12
|
|
|
12
13
|
# refactorai-cli
|
|
13
14
|
|
|
@@ -53,28 +54,34 @@ python -m twine upload ./refactorai-cli/dist/*
|
|
|
53
54
|
## Install test (local)
|
|
54
55
|
|
|
55
56
|
```bash
|
|
56
|
-
python -m pip install ./refactorai-cli/dist/refactorai_cli-0.
|
|
57
|
+
python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.0-py3-none-any.whl
|
|
57
58
|
refactor --version
|
|
58
59
|
```
|
|
59
60
|
|
|
60
61
|
## Cloud BYOK setup (bring your own provider key)
|
|
61
62
|
|
|
62
63
|
Run cloud inference with your own provider credentials (no local runtime
|
|
63
|
-
required).
|
|
64
|
-
|
|
64
|
+
required). Preferred path is env-key in project config (`provider_key:
|
|
65
|
+
${ENV_VAR}`). Platform-stored credentials (`credential_ref`) are optional
|
|
66
|
+
fallback.
|
|
65
67
|
|
|
66
68
|
```bash
|
|
67
69
|
# 1. Authenticate (developer key with the run:byok entitlement).
|
|
68
70
|
refactor login
|
|
69
71
|
|
|
70
|
-
#
|
|
72
|
+
# 2A. Preferred: set BYOK directly from project env in refactor.config:
|
|
73
|
+
#
|
|
74
|
+
# mode: cloud_byok
|
|
75
|
+
# provider: openai
|
|
76
|
+
# model_id: gpt-4.1-mini
|
|
77
|
+
# provider_key: ${OPENAI_API_KEY}
|
|
78
|
+
#
|
|
79
|
+
# 2B. Optional fallback: register a platform-stored credential from env.
|
|
71
80
|
export OPENAI_API_KEY=sk-...
|
|
72
81
|
refactor cloud credentials set --provider openai --from-env OPENAI_API_KEY --label "my key"
|
|
73
82
|
# -> prints a credential_ref, e.g. cred_01J...
|
|
74
83
|
|
|
75
|
-
# 3.
|
|
76
|
-
# mode: cloud_byok
|
|
77
|
-
# provider: openai
|
|
84
|
+
# 3. If you use platform-stored fallback, set credential_ref in config:
|
|
78
85
|
# credential_ref: cred_01J...
|
|
79
86
|
|
|
80
87
|
# 4. Verify readiness, then run.
|
|
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
|