generated from coulomb/repo-seed
Updated repo onboarding
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -34,6 +36,8 @@ from api.schemas.managed_repo import (
|
||||
PendingInterfaceChange,
|
||||
RepoCreate,
|
||||
RepoDispatch,
|
||||
RepoOnboardRequest,
|
||||
RepoOnboardResult,
|
||||
RepoPathRegister,
|
||||
RepoRead,
|
||||
RepoScopeHealth,
|
||||
@@ -90,6 +94,77 @@ async def register_repo(
|
||||
return repo
|
||||
|
||||
|
||||
@router.post("/onboard", response_model=RepoOnboardResult)
|
||||
async def onboard_repo(body: RepoOnboardRequest) -> RepoOnboardResult:
|
||||
"""Run the local repo onboarding script for an accessible working copy.
|
||||
|
||||
The dashboard uses this for the "Add Repo" action. The path must be visible
|
||||
from the State Hub host, either as a local checkout or through an ops-bridge
|
||||
mounted/exposed working copy. Keep the API agent-profile based so future
|
||||
native coding agents can gain their own profiles without changing callers.
|
||||
"""
|
||||
project_path = Path(body.project_path).expanduser()
|
||||
if not project_path.exists() or not project_path.is_dir():
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"project_path is not an accessible directory: {body.project_path}",
|
||||
)
|
||||
if not (project_path / ".git").exists():
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"project_path does not look like a git working copy: {body.project_path}",
|
||||
)
|
||||
|
||||
script = Path(__file__).parent.parent.parent / "scripts" / "register_project.sh"
|
||||
cmd = ["bash", str(script), body.domain_slug, str(project_path)]
|
||||
if body.agent_profile == "codex":
|
||||
cmd.append("--codex")
|
||||
if body.additional:
|
||||
cmd.append("--additional")
|
||||
|
||||
env = {
|
||||
**os.environ,
|
||||
"API_BASE": settings.api_base,
|
||||
"CUSTODIAN_SKIP_SBOM_PROMPT": "true",
|
||||
}
|
||||
result = await asyncio.to_thread(
|
||||
subprocess.run,
|
||||
cmd,
|
||||
cwd=str(script.parent.parent),
|
||||
env=env,
|
||||
stdin=subprocess.DEVNULL,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=180,
|
||||
)
|
||||
stdout = result.stdout or ""
|
||||
stderr = result.stderr or ""
|
||||
if result.returncode != 0:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail={
|
||||
"message": "Repo onboarding failed.",
|
||||
"command": cmd,
|
||||
"stdout": stdout,
|
||||
"stderr": stderr,
|
||||
},
|
||||
)
|
||||
|
||||
repo_slug = None
|
||||
match = re.search(r"Repo slug:\s+([a-z0-9][a-z0-9-]*)", stdout)
|
||||
if match:
|
||||
repo_slug = match.group(1)
|
||||
|
||||
return RepoOnboardResult(
|
||||
ok=True,
|
||||
repo_slug=repo_slug,
|
||||
agent_profile=body.agent_profile,
|
||||
command=cmd,
|
||||
stdout=stdout,
|
||||
stderr=stderr,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/by-fingerprint", response_model=list[RepoRead])
|
||||
async def get_repo_by_fingerprint(
|
||||
hash: str,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
from typing import Any
|
||||
from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
@@ -32,6 +32,23 @@ class RepoPathRegister(BaseModel):
|
||||
path: str
|
||||
|
||||
|
||||
class RepoOnboardRequest(BaseModel):
|
||||
"""Start scripted onboarding for a working copy that is visible to State Hub."""
|
||||
domain_slug: str
|
||||
project_path: str
|
||||
agent_profile: Literal["claude-code", "codex"] = "codex"
|
||||
additional: bool = False
|
||||
|
||||
|
||||
class RepoOnboardResult(BaseModel):
|
||||
ok: bool
|
||||
repo_slug: str | None = None
|
||||
agent_profile: str
|
||||
command: list[str]
|
||||
stdout: str = ""
|
||||
stderr: str = ""
|
||||
|
||||
|
||||
class RepoRead(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
id: uuid.UUID
|
||||
|
||||
Reference in New Issue
Block a user