refactorai-cli 0.5.0__tar.gz → 0.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/PKG-INFO +1 -1
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/pyproject.toml +1 -1
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/__init__.py +1 -1
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/client.py +5 -4
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commands/run_cmds.py +34 -12
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commands/watch_cmds.py +9 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/watch_state.py +46 -7
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli.egg-info/PKG-INFO +1 -1
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/README.md +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/auth.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/cloud_rr.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commands/__init__.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commands/account_cmds.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commands/auth_cmds.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commands/branch_cmds.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commands/cloud_cmds.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commands/engine_cmds.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commands/hook_cmds.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commands/model_cmds.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commands/pre_push_cmds.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commands/request_cmds.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commands/rules_cmds.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commands/runtime_cmds.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commands/runtime_proxy_cmds.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commands/setup_cmds.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commands/toolchains_cmds.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commit_queue.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/commit_telemetry.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/control_plane.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/credentials.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/dotenv_loader.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/git_scope.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/local_constitution.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/local_engine_runtime.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/local_paths.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/main.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/model_policy.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/pre_push_gate.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/refactor_branch_store.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/review_runner.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/runtime_manager.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/settings.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/setup_flow.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli/watch_supervisor.py +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli.egg-info/SOURCES.txt +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli.egg-info/dependency_links.txt +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli.egg-info/entry_points.txt +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli.egg-info/requires.txt +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/refactorai_cli.egg-info/top_level.txt +0 -0
- {refactorai_cli-0.5.0 → refactorai_cli-0.5.2}/setup.cfg +0 -0
|
@@ -48,7 +48,6 @@ class PlatformClient:
|
|
|
48
48
|
def create_project(
|
|
49
49
|
self,
|
|
50
50
|
developer_key: str,
|
|
51
|
-
account_slug: str,
|
|
52
51
|
name: str,
|
|
53
52
|
*,
|
|
54
53
|
idempotency_key: str,
|
|
@@ -57,10 +56,12 @@ class PlatformClient:
|
|
|
57
56
|
visibility: str = "private",
|
|
58
57
|
repo_url: str | None = None,
|
|
59
58
|
) -> dict:
|
|
60
|
-
"""
|
|
59
|
+
"""Provision a project in the developer key's account.
|
|
61
60
|
|
|
62
61
|
Used by ``refactor init`` to auto-provision a cloud project for the local
|
|
63
|
-
commit loop.
|
|
62
|
+
commit loop. This targets the developer-key authenticated ``/v1/projects/
|
|
63
|
+
provision`` endpoint (the JWT-only account endpoints do not accept a
|
|
64
|
+
developer key). ``repo_url`` is optional and can be attached later from the
|
|
64
65
|
web UI. Raises ``PlatformError`` on any rejection or transport failure.
|
|
65
66
|
"""
|
|
66
67
|
body: dict = {
|
|
@@ -73,7 +74,7 @@ class PlatformClient:
|
|
|
73
74
|
body["repo_url"] = repo_url
|
|
74
75
|
try:
|
|
75
76
|
response = httpx.post(
|
|
76
|
-
f"{self.base_url}/v1/
|
|
77
|
+
f"{self.base_url}/v1/projects/provision",
|
|
77
78
|
headers={
|
|
78
79
|
"Authorization": f"Bearer {developer_key}",
|
|
79
80
|
"Content-Type": "application/json",
|
|
@@ -1065,25 +1065,35 @@ def init(
|
|
|
1065
1065
|
# Authenticate and provision the cloud project BEFORE writing any files, so a
|
|
1066
1066
|
# failure leaves the working tree untouched (fail-fast, no partial state).
|
|
1067
1067
|
auth_ctx = _ensure_init_auth(project_root)
|
|
1068
|
-
account_slug = auth_ctx.account_slug or load_credentials().get("active_account_slug") or ""
|
|
1069
|
-
if not account_slug:
|
|
1070
|
-
console.print(
|
|
1071
|
-
"[red]Could not resolve your account.[/red] Run `refactor login` and retry."
|
|
1072
|
-
)
|
|
1073
|
-
raise typer.Exit(code=1)
|
|
1074
|
-
|
|
1075
1068
|
project_name = project_root.name or "refactor-project"
|
|
1076
1069
|
client = PlatformClient()
|
|
1077
1070
|
try:
|
|
1078
1071
|
created = client.create_project(
|
|
1079
1072
|
auth_ctx.key.key,
|
|
1080
|
-
account_slug,
|
|
1081
1073
|
project_name,
|
|
1082
1074
|
idempotency_key=str(uuid.uuid4()),
|
|
1083
1075
|
)
|
|
1084
1076
|
except PlatformError as exc:
|
|
1085
|
-
|
|
1086
|
-
|
|
1077
|
+
# A rejected key mid-flow (revoked/rotated) is recoverable in a terminal:
|
|
1078
|
+
# ask for an updated key and retry once rather than dead-ending.
|
|
1079
|
+
if getattr(exc, "status_code", None) == 401 and sys.stdin.isatty():
|
|
1080
|
+
console.print(
|
|
1081
|
+
f"[yellow]{exc}.[/yellow] Enter an updated developer key to continue."
|
|
1082
|
+
)
|
|
1083
|
+
auth_ctx = _prompt_developer_key(project_root)
|
|
1084
|
+
try:
|
|
1085
|
+
created = client.create_project(
|
|
1086
|
+
auth_ctx.key.key,
|
|
1087
|
+
project_name,
|
|
1088
|
+
idempotency_key=str(uuid.uuid4()),
|
|
1089
|
+
)
|
|
1090
|
+
except PlatformError as retry_exc:
|
|
1091
|
+
console.print(f"[red]Could not create cloud project:[/red] {retry_exc}")
|
|
1092
|
+
raise typer.Exit(code=1) from retry_exc
|
|
1093
|
+
else:
|
|
1094
|
+
console.print(f"[red]Could not create cloud project:[/red] {exc}")
|
|
1095
|
+
raise typer.Exit(code=1) from exc
|
|
1096
|
+
account_slug = auth_ctx.account_slug or load_credentials().get("active_account_slug") or ""
|
|
1087
1097
|
project_id = str(created.get("id") or "").strip()
|
|
1088
1098
|
if not project_id:
|
|
1089
1099
|
console.print("[red]Cloud project creation returned no id.[/red] Please retry.")
|
|
@@ -1098,9 +1108,10 @@ def init(
|
|
|
1098
1108
|
record = register_project(project_root, constitution_hash=constitution.content_hash)
|
|
1099
1109
|
console.print(f"[green]Created[/green] {consti_target}")
|
|
1100
1110
|
console.print(f"[green]Created[/green] {config_target}")
|
|
1111
|
+
account_note = f" (account {account_slug})" if account_slug else ""
|
|
1101
1112
|
console.print(
|
|
1102
|
-
f"[green]Linked[/green] to cloud project [bold]{project_name}[/bold]
|
|
1103
|
-
f"
|
|
1113
|
+
f"[green]Linked[/green] to cloud project [bold]{project_name}[/bold]"
|
|
1114
|
+
f"{account_note}. The project id is saved in {CONFIG_FILENAME}; "
|
|
1104
1115
|
"rename the project or attach a repository anytime from the web UI."
|
|
1105
1116
|
)
|
|
1106
1117
|
if config_profile == "local_server":
|
|
@@ -1145,6 +1156,17 @@ def _ensure_init_auth(project_root: Path) -> AuthContext:
|
|
|
1145
1156
|
raise typer.Exit(code=1)
|
|
1146
1157
|
|
|
1147
1158
|
console.print("[bold]Sign in to Refactor[/bold] to create your cloud project.")
|
|
1159
|
+
return _prompt_developer_key(project_root)
|
|
1160
|
+
|
|
1161
|
+
|
|
1162
|
+
def _prompt_developer_key(project_root: Path) -> AuthContext:
|
|
1163
|
+
"""Interactively prompt for a developer key, validate it, and persist it.
|
|
1164
|
+
|
|
1165
|
+
Shared by the initial `init` login and the retry path when the platform
|
|
1166
|
+
rejects a key mid-flow (e.g. it was revoked). Returns a fresh, validated
|
|
1167
|
+
``AuthContext``. Non-interactive callers must guard on ``sys.stdin.isatty()``
|
|
1168
|
+
before calling this.
|
|
1169
|
+
"""
|
|
1148
1170
|
console.print(
|
|
1149
1171
|
f"[dim]No account yet? Create one and generate a developer key at "
|
|
1150
1172
|
f"{platform_url()}, then paste it below.[/dim]"
|
|
@@ -688,6 +688,15 @@ def _dispatch_key(live, project_root: Path, key: str, state: WatchState) -> None
|
|
|
688
688
|
f"Press [{key}] again.[/dim]"
|
|
689
689
|
)
|
|
690
690
|
return
|
|
691
|
+
if not state.has_pending_work():
|
|
692
|
+
# Nothing anywhere needs review/merge: this is the idle steady
|
|
693
|
+
# state, so guide toward the next commit rather than implying the
|
|
694
|
+
# user picked the wrong node.
|
|
695
|
+
console.print(
|
|
696
|
+
"[dim]watch: all commits are reviewed and resolved — "
|
|
697
|
+
"watching for the next commit.[/dim]"
|
|
698
|
+
)
|
|
699
|
+
return
|
|
691
700
|
console.print(
|
|
692
701
|
f"[dim]watch: selected {node.short_sha} ({node.branch or 'detached'} / "
|
|
693
702
|
f"phase={node.phase}, requests={node.rr_count}) has nothing to "
|
|
@@ -199,6 +199,27 @@ class WatchState:
|
|
|
199
199
|
and node.sha not in self.forced_review
|
|
200
200
|
)
|
|
201
201
|
|
|
202
|
+
def has_pending_work(self) -> bool:
|
|
203
|
+
"""True when any node still needs attention (review, resolve, or merge).
|
|
204
|
+
|
|
205
|
+
Drives the terminal's idle vs. active affordances: when this is ``False``
|
|
206
|
+
every commit is reviewed with no open requests and nothing to finalize,
|
|
207
|
+
so the footer should say "watching for the next commit" rather than
|
|
208
|
+
advertising review/merge hotkeys the user can't act on.
|
|
209
|
+
"""
|
|
210
|
+
for node in self._nodes.values():
|
|
211
|
+
if node.state in (QUEUED, REVIEWING):
|
|
212
|
+
return True
|
|
213
|
+
if (
|
|
214
|
+
node.state == REVIEWED
|
|
215
|
+
and node.rr_count > 0
|
|
216
|
+
and (node.phase == PHASE_REVIEW or node.sha in self.forced_review)
|
|
217
|
+
):
|
|
218
|
+
return True
|
|
219
|
+
if node.phase == PHASE_FINALIZE and node.sha not in self.forced_review:
|
|
220
|
+
return True
|
|
221
|
+
return False
|
|
222
|
+
|
|
202
223
|
def has(self, sha: str) -> bool:
|
|
203
224
|
return sha in self._nodes
|
|
204
225
|
|
|
@@ -522,6 +543,7 @@ def _render_review_view(state: WatchState, *, now: datetime | None = None, width
|
|
|
522
543
|
lines.append(_with_pad(row))
|
|
523
544
|
lines.append(_with_pad(Text.from_markup("[cyan]+[/cyan]")))
|
|
524
545
|
lines.append(_with_pad(Text("")))
|
|
546
|
+
pending = state.has_pending_work()
|
|
525
547
|
selected = state.selected()
|
|
526
548
|
if selected is not None:
|
|
527
549
|
context = Text.from_markup("[bold bright_cyan]selected[/bold bright_cyan]: ")
|
|
@@ -531,15 +553,32 @@ def _render_review_view(state: WatchState, *, now: datetime | None = None, width
|
|
|
531
553
|
context.append(f" requests={selected.rr_count}", style="dim")
|
|
532
554
|
lines.append(_with_pad(context))
|
|
533
555
|
lines.append(_with_pad(Text("")))
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
556
|
+
if pending:
|
|
557
|
+
lines.append(
|
|
558
|
+
_with_pad(
|
|
559
|
+
Text.from_markup(
|
|
560
|
+
"[dim]\\[↑/↓ or j/k][/dim] select [bold]\\[r][/bold] refactor "
|
|
561
|
+
"[bold]\\[f][/bold] fresh review [bold]\\[d][/bold] diff "
|
|
562
|
+
"[bold]\\[q][/bold] quit"
|
|
563
|
+
)
|
|
564
|
+
)
|
|
565
|
+
)
|
|
566
|
+
else:
|
|
567
|
+
# Everything is reviewed and resolved: switch to an idle affordance so the
|
|
568
|
+
# user isn't offered review/merge keys that would no-op (docs/41 Part F).
|
|
569
|
+
lines.append(
|
|
570
|
+
_with_pad(
|
|
571
|
+
Text.from_markup("[green]✓ all commits reviewed[/green] — [dim]watching for the next commit[/dim]")
|
|
572
|
+
)
|
|
573
|
+
)
|
|
574
|
+
lines.append(
|
|
575
|
+
_with_pad(
|
|
576
|
+
Text.from_markup(
|
|
577
|
+
"[dim]\\[↑/↓ or j/k][/dim] select [bold]\\[d][/bold] diff "
|
|
578
|
+
"[bold]\\[q][/bold] quit"
|
|
579
|
+
)
|
|
540
580
|
)
|
|
541
581
|
)
|
|
542
|
-
)
|
|
543
582
|
return Group(*lines)
|
|
544
583
|
|
|
545
584
|
|
|
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
|
|
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
|