fix(gitea-bot): add write:organization scope so bot can create repos

Without this the bot PAT 403s on POST /orgs/{org}/repos, which is
the single most important operation — creating new project repos
inside the workspace's Gitea org.

Made-with: Cursor
This commit is contained in:
2026-04-21 11:05:55 -07:00
parent d9d3514647
commit 6f79a88abd
66 changed files with 2088 additions and 1713 deletions

View File

@@ -3,6 +3,7 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { JM } from "./modal-theme";
import { SetupHeader, FieldLabel, TextInput, PrimaryButton, type SetupProps } from "./setup-shared";
const HOSTING_OPTIONS = [
@@ -70,7 +71,7 @@ export function MigrateSetup({ workspace, onClose, onBack }: SetupProps) {
};
return (
<div style={{ padding: "32px 36px 36px" }}>
<div style={{ padding: 28 }}>
<SetupHeader
icon="⇢" label="Migrate Product" tagline="Move an existing product"
accent="#4a2a5a" onBack={onBack} onClose={onClose}
@@ -86,7 +87,7 @@ export function MigrateSetup({ workspace, onClose, onBack }: SetupProps) {
<FieldLabel>
Repository URL{" "}
<span style={{ color: "#b5b0a6", fontWeight: 400 }}>(recommended)</span>
<span style={{ color: JM.muted, fontWeight: 400 }}>(recommended)</span>
</FieldLabel>
<TextInput
value={repoUrl}
@@ -96,7 +97,7 @@ export function MigrateSetup({ workspace, onClose, onBack }: SetupProps) {
<FieldLabel>
Live URL{" "}
<span style={{ color: "#b5b0a6", fontWeight: 400 }}>(optional)</span>
<span style={{ color: JM.muted, fontWeight: 400 }}>(optional)</span>
</FieldLabel>
<TextInput
value={liveUrl}
@@ -111,14 +112,16 @@ export function MigrateSetup({ workspace, onClose, onBack }: SetupProps) {
value={hosting}
onChange={e => setHosting(e.target.value)}
style={{
width: "100%", padding: "11px 14px", marginBottom: 16,
borderRadius: 8, border: "1px solid #e0dcd4",
background: "#faf8f5", fontSize: "0.88rem",
fontFamily: "var(--font-inter), ui-sans-serif, sans-serif", color: hosting ? "#1a1a1a" : "#a09a90",
width: "100%", padding: "10px 13px", marginBottom: 16,
borderRadius: 8, border: `1px solid ${JM.border}`,
background: JM.inputBg, fontSize: 13,
fontFamily: JM.fontSans, color: hosting ? JM.ink : JM.muted,
outline: "none", boxSizing: "border-box", appearance: "none",
backgroundImage: `url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23a09a90' strokeWidth='1.5' strokeLinecap='round' strokeLinejoin='round'/%3E%3C/svg%3E")`,
backgroundImage: `url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%239CA3AF' strokeWidth='1.5' strokeLinecap='round' strokeLinejoin='round'/%3E%3C/svg%3E")`,
backgroundRepeat: "no-repeat", backgroundPosition: "right 12px center",
}}
onFocus={e => (e.currentTarget.style.borderColor = JM.indigo)}
onBlur={e => (e.currentTarget.style.borderColor = JM.border)}
>
{HOSTING_OPTIONS.map(o => (
<option key={o.value} value={o.value}>{o.label}</option>
@@ -127,7 +130,7 @@ export function MigrateSetup({ workspace, onClose, onBack }: SetupProps) {
</div>
<div>
<FieldLabel>
PAT{" "}<span style={{ color: "#b5b0a6", fontWeight: 400 }}>(private repos)</span>
PAT{" "}<span style={{ color: JM.muted, fontWeight: 400 }}>(private repos)</span>
</FieldLabel>
<input
type="password"
@@ -135,20 +138,24 @@ export function MigrateSetup({ workspace, onClose, onBack }: SetupProps) {
onChange={e => setPat(e.target.value)}
placeholder="ghp_…"
style={{
width: "100%", padding: "11px 14px", marginBottom: 16,
borderRadius: 8, border: "1px solid #e0dcd4",
background: "#faf8f5", fontSize: "0.9rem",
fontFamily: "var(--font-inter), ui-sans-serif, sans-serif", color: "#1a1a1a",
width: "100%", padding: "10px 13px", marginBottom: 16,
borderRadius: 8, border: `1px solid ${JM.border}`,
background: JM.inputBg, fontSize: 14,
fontFamily: JM.fontSans, color: JM.ink,
outline: "none", boxSizing: "border-box",
}}
onFocus={e => (e.currentTarget.style.borderColor = "#1a1a1a")}
onBlur={e => (e.currentTarget.style.borderColor = "#e0dcd4")}
onFocus={e => (e.currentTarget.style.borderColor = JM.indigo)}
onBlur={e => (e.currentTarget.style.borderColor = JM.border)}
/>
</div>
</div>
<div style={{ fontSize: "0.75rem", color: "#a09a90", marginBottom: 20, lineHeight: 1.5, padding: "12px 14px", background: "#faf8f5", borderRadius: 8, border: "1px solid #f0ece4" }}>
<strong style={{ color: "#4a2a5a" }}>Non-destructive.</strong> Vibn builds a full audit and migration plan. Your existing product stays live throughout the entire migration process.
<div style={{
fontSize: 12, color: JM.mid, marginBottom: 20, lineHeight: 1.5,
padding: "12px 14px", background: JM.cream, borderRadius: 8,
border: `1px solid ${JM.border}`, fontFamily: JM.fontSans,
}}>
<strong style={{ color: "#5B21B6" }}>Non-destructive.</strong> Vibn builds a full audit and migration plan. Your existing product stays live throughout the entire migration process.
</div>
<PrimaryButton onClick={handleCreate} disabled={!canCreate} loading={loading}>