Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,19 @@ GITPILOT_GITHUB_TOKEN=
# Alternative PAT variable name (either works)
# GITHUB_TOKEN=

# Option 3: OAuth App (Advanced)
# Option 3: OAuth App (Advanced — "Continue with GitHub" Web Flow)
# Create OAuth App at: https://github.com/settings/developers
# NOTE: The Web Flow only activates when GITHUB_CLIENT_SECRET is set. Without it
# the app automatically uses the Device Flow (no secret / no callback URL).
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GITHUB_REDIRECT_URI=http://localhost:8000/api/auth/callback
# Browser callback after authorizing on GitHub. This must point at the FRONTEND
# /auth page (which exchanges the code), NOT the API. When left blank it
# defaults to "{GITPILOT_PUBLIC_BASE_URL}/auth". Register this exact URL as the
# OAuth/GitHub App "Authorization callback URL".
# Local dev: http://localhost:5173/auth
# Production: https://gitpilot.ruslanmv.com/auth
GITHUB_REDIRECT_URI=

# Priority: GitHub App > OAuth > PAT
# For quickstart: Use PAT
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- **Coder API** (`POST /repair` + `GET /repair/health`), bearer-token gated (`GITPILOT_API_TOKEN`), mounted into the main app — turns a repair-plan into a dry-run patch preview for SelfRepair / matrix-maintainer over HTTPS.
- **Runtime-aware onboarding** — `/api/status` now reports `workspace.runtime` (`cloud`/`local`); in a cloud workspace the empty state guides you to connect GitHub and pick a repository, while a local install offers Folder / Local Git paths with GitHub optional.
- **Account-first auth across split deployments** — portable `X-GitPilot-Session` token (cross-origin Vercel↔HF), a dedicated email-verification screen, a Settings → Account tab (update name, change password, delete account), and "GitHub not linked" now shows a calm Connect prompt instead of a repo-fetch error.

### Changed — `make run` now starts the MCP Context Forge stack by default

Expand Down
71 changes: 65 additions & 6 deletions frontend/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ import {
SessionsTab,
AdvancedTab,
SandboxTab,
AccountTab,
} from "./components/AdminTabs";
import { apiUrl, safeFetchJSON, fetchStatus } from "./utils/api.js";
import { apiUrl, safeFetchJSON, fetchStatus, getAuthHeaders, clearSessionToken, startSession } from "./utils/api.js";
import { initApp } from "./utils/appInit.js";

function makeRepoKey(repo) {
Expand Down Expand Up @@ -267,7 +268,7 @@ export default function App() {
...(token ? { Authorization: `Bearer ${token}` } : {}),
};

const res = await fetch("/api/sessions", {
const res = await fetch(apiUrl("/api/sessions"), {
method: "POST",
headers,
body: JSON.stringify({
Expand Down Expand Up @@ -317,7 +318,7 @@ export default function App() {
...(token ? { Authorization: `Bearer ${token}` } : {}),
};

const res = await fetch("/api/sessions", {
const res = await fetch(apiUrl("/api/sessions"), {
method: "POST",
headers,
body: JSON.stringify({
Expand Down Expand Up @@ -857,6 +858,7 @@ export default function App() {
const me = await safeFetchJSON(apiUrl("/api/account/me"), {
method: "GET",
credentials: "include",
headers: getAuthHeaders(), // X-GitPilot-Session survives cross-origin
timeout: 8000,
});
if (me && me.id) {
Expand Down Expand Up @@ -895,13 +897,38 @@ export default function App() {
};

const handleLogout = () => {
// Best-effort backend logout (clears the session cookie); ignore failures.
try {
fetch(apiUrl("/api/account/logout"), {
method: "POST",
credentials: "include",
headers: getAuthHeaders(),
}).catch(() => {});
} catch { /* ignore */ }
localStorage.removeItem("github_token");
localStorage.removeItem("github_user");
clearSessionToken();
setIsAuthenticated(false);
setUserInfo(null);
clearAllContext();
};

// In-workspace "Connect GitHub": let an already-signed-in account user run
// the GitHub device flow to link their repos, then return to the workspace.
// This renders even when authenticated (unlike the sign-in screen below).
if (typeof window !== "undefined") {
const qs = new URLSearchParams(window.location.search);
if (window.location.pathname.startsWith("/auth") && qs.get("connect") === "github") {
return (
<AuthPage
onAuthenticated={handleAuthenticated}
backendReady={!!startupStatusSnapshot}
connectMode
/>
);
}
}

// Public pages must render instantly — never gated behind backend startup.
// (A marketing landing page that shows "Connecting to backend…" is bad for
// users and for SEO crawlers.) Only a returning GitHub-token session, which
Expand Down Expand Up @@ -1037,7 +1064,18 @@ export default function App() {
{!sidebarCollapsed && (
<>
{!hasContext && (
<RepoSelector onSelect={(r) => addRepoToContext(r)} />
<RepoSelector
onSelect={(r) => addRepoToContext(r)}
workspace={startupStatusSnapshot?.workspace}
onOpenLocal={async (payload) => {
const result = await startSession(payload);
setActiveSessionId(result.session_id);
setSessionRefreshNonce((n) => n + 1);
setActivePage("workspace");
showToast?.("Workspace ready", "Local project opened.");
return result;
}}
/>
)}

{repo && (
Expand All @@ -1058,6 +1096,10 @@ export default function App() {
<UserMenu
userInfo={userInfo}
sidebarCollapsed={sidebarCollapsed}
onOpenAccount={() => {
setActivePage("admin");
setAdminTab("account");
}}
onOpenSettings={() => {
setActivePage("admin");
setAdminTab("advanced");
Expand All @@ -1071,9 +1113,20 @@ export default function App() {

<main className="workspace">
{activePage === "admin" && (
<div style={{ padding: "24px", maxWidth: "960px", margin: "0 auto" }}>
<div
style={{
flex: 1,
minHeight: 0,
overflowY: "auto",
padding: "24px",
maxWidth: "960px",
width: "100%",
margin: "0 auto",
boxSizing: "border-box",
}}
>
<div style={{ display: "flex", gap: "8px", marginBottom: "24px", flexWrap: "wrap" }}>
{["overview", "providers", "workspace-modes", "integrations", "mcp-servers", "sandbox", "sessions", "skills", "security", "advanced"].map((tab) => (
{["overview", "account", "providers", "workspace-modes", "integrations", "mcp-servers", "sandbox", "sessions", "skills", "security", "advanced"].map((tab) => (
<button
key={tab}
onClick={() => setAdminTab(tab)}
Expand Down Expand Up @@ -1164,6 +1217,10 @@ export default function App() {
</div>
)}

{adminTab === "account" && (
<AccountTab onLogout={handleLogout} />
)}

{adminTab === "providers" && (
<div>
<h3 style={{ marginBottom: "16px" }}>AI Providers</h3>
Expand All @@ -1174,6 +1231,7 @@ export default function App() {
{adminTab === "workspace-modes" && (
<WorkspaceModesTab
showToast={showToast}
runtime={startupStatusSnapshot?.workspace?.runtime}
onSessionStarted={(result) => {
setActiveSessionId(result.session_id);
setSessionRefreshNonce((n) => n + 1);
Expand Down Expand Up @@ -1248,6 +1306,7 @@ export default function App() {
onBranchChange={handleBranchChange}
pulseNonce={pulseNonce}
lastExecution={lastExecution}
runtime={startupStatusSnapshot?.workspace?.runtime}
onSettingsClick={() => setSettingsOpen(true)}
/>
</aside>
Expand Down
Loading
Loading