Skip to content
Open
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
129 changes: 129 additions & 0 deletions better-agent-gateway/frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -387,3 +387,132 @@ header h1 {
.perm-error {
background: #fff5f5;
}

/* --- CursorConfig --- */

.cursor-config {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 1.25rem 1.5rem;
margin-bottom: 2rem;
}

.cursor-config h2 {
font-size: 1.1rem;
margin: 0 0 0.75rem;
}

.cursor-config-steps {
font-size: 0.9rem;
color: #495057;
margin: 0 0 1rem;
padding-left: 1.4rem;
line-height: 1.7;
}

.cursor-config-steps code {
background: #f0f0f0;
padding: 0.15em 0.4em;
border-radius: 3px;
font-size: 0.88em;
font-family: 'SF Mono', 'Fira Code', monospace;
}

.cursor-config-code {
background: #1e1e1e;
color: #d4d4d4;
border-radius: 6px;
padding: 1rem 1.25rem;
font-family: 'SF Mono', 'Fira Code', monospace;
font-size: 0.82rem;
line-height: 1.5;
overflow-x: auto;
margin: 0 0 1rem;
white-space: pre;
}

.cursor-config-code code {
background: none;
color: inherit;
font-size: inherit;
font-family: inherit;
padding: 0;
}

.cursor-config-actions {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}

.copy-btn {
padding: 0.4rem 1rem;
border: 1px solid #0d6efd;
background: #0d6efd;
color: #fff;
border-radius: 6px;
font-size: 0.85rem;
font-weight: 500;
cursor: pointer;
transition: background 0.15s;
}

.copy-btn:hover {
background: #0b5ed7;
}

.copy-btn.copied {
background: #155724;
border-color: #155724;
}

.cursor-config-validation {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.4rem;
}

.cursor-config-check {
display: flex;
align-items: baseline;
gap: 0.5rem;
padding: 0.4rem 0.75rem;
border-radius: 6px;
font-size: 0.88rem;
}

.cursor-config-check .check-icon {
font-weight: 700;
flex-shrink: 0;
width: 1em;
text-align: center;
}

.cursor-config-check .check-name {
font-weight: 600;
}

.cursor-config-check .check-message {
color: inherit;
opacity: 0.85;
font-size: 0.85em;
}

.cursor-config-check.pass {
background: #d4edda;
color: #155724;
}

.cursor-config-check.fail {
background: #f8d7da;
color: #721c24;
}

.cursor-config-check.info {
background: #f0f4ff;
color: #1a3a6b;
}
2 changes: 2 additions & 0 deletions better-agent-gateway/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ModelTable } from './components/ModelTable'
import { HealthBadge } from './components/HealthBadge'
import { UserInfo } from './components/UserInfo'
import { PermissionComparison } from './components/PermissionComparison'
import { CursorConfig } from './components/CursorConfig'
import './App.css'

function App() {
Expand All @@ -21,6 +22,7 @@ function App() {
</section>
<UserInfo />
<PermissionComparison />
<CursorConfig />
<ModelTable />
</main>
</div>
Expand Down
127 changes: 127 additions & 0 deletions better-agent-gateway/frontend/src/components/CursorConfig.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { useEffect, useState } from 'react'

interface CursorConfigResponse {
instructions: string
config: {
apiKey: string
baseUrl: string
models: string[]
}
notes: string[]
}

interface ValidationCheck {
name: string
status: 'pass' | 'fail' | 'info'
detail: string
}

interface ValidationResponse {
valid: boolean
checks: ValidationCheck[]
}

export function CursorConfig() {
const [data, setData] = useState<CursorConfigResponse | null>(null)
const [error, setError] = useState<string | null>(null)
const [copied, setCopied] = useState(false)
const [validation, setValidation] = useState<ValidationResponse | null>(null)
const [validating, setValidating] = useState(false)
const [validationError, setValidationError] = useState<string | null>(null)

useEffect(() => {
fetch('/api/v1/config/cursor')
.then((r) => {
if (!r.ok) throw new Error(`HTTP ${r.status}`)
return r.json()
})
.then(setData)
.catch((e) => setError(e.message))
}, [])

const handleCopy = () => {
if (!data) return
const text = JSON.stringify(data.config, null, 2)
navigator.clipboard.writeText(text).then(() => {
setCopied(true)
setTimeout(() => setCopied(false), 2000)
})
}

const handleValidate = () => {
setValidating(true)
setValidation(null)
setValidationError(null)
fetch('/api/v1/config/cursor/validate', { method: 'POST' })
.then((r) => {
if (!r.ok) throw new Error(`HTTP ${r.status}`)
return r.json()
})
.then(setValidation)
.catch((e) => setValidationError(e.message))
.finally(() => setValidating(false))
}

if (error) return <div className="error">Failed to load Cursor config: {error}</div>
if (!data) return <div>Loading Cursor config...</div>

const configJson = JSON.stringify(data.config, null, 2)

return (
<div className="cursor-config">
<h2>Cursor IDE Setup</h2>

<ol className="cursor-config-steps">
<li>Open Cursor Settings (<code>Cmd+,</code>)</li>
<li>Go to <strong>Models</strong> &gt; <strong>OpenAI API Compatible</strong></li>
<li>Paste the configuration below</li>
<li>Click <strong>Validate Connection</strong> to verify</li>
</ol>

<pre className="cursor-config-code"><code>{configJson}</code></pre>

<ul className="cursor-config-notes">
{data.notes.map((note, i) => (
<li key={i}>{note}</li>
))}
</ul>

<div className="cursor-config-actions">
<button
className={`copy-btn${copied ? ' copied' : ''}`}
onClick={handleCopy}
>
{copied ? 'Copied!' : 'Copy to Clipboard'}
</button>
<button
className="refresh-btn"
onClick={handleValidate}
disabled={validating}
>
{validating ? 'Validating…' : 'Validate Connection'}
</button>
</div>

{validationError && (
<div className="error">Validation failed: {validationError}</div>
)}

{validation && (
<ul className="cursor-config-validation">
{validation.checks.map((check, i) => {
const icon = check.status === 'pass' ? '✓' : check.status === 'fail' ? '✗' : 'ℹ'
return (
<li key={i} className={`cursor-config-check ${check.status}`}>
<span className="check-icon">{icon}</span>
<span className="check-name">{check.name}</span>
{check.detail && (
<span className="check-detail">{check.detail}</span>
)}
</li>
)
})}
</ul>
)}
</div>
)
}
2 changes: 2 additions & 0 deletions better-agent-gateway/server/routes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .audit_routes import router as audit_router
from .auth import router as auth_router
from .chat import router as chat_router
from .cursor_config import router as cursor_config_router
from .health import router as health_router
from .models import router as models_router
from .permissions import router as permissions_router
Expand All @@ -12,6 +13,7 @@
api_router.include_router(audit_router, tags=["audit"])
api_router.include_router(auth_router, tags=["auth"])
api_router.include_router(chat_router, tags=["chat"])
api_router.include_router(cursor_config_router, tags=["config"])
api_router.include_router(health_router, tags=["health"])
api_router.include_router(models_router, tags=["models"])
api_router.include_router(permissions_router, tags=["permissions"])
Expand Down
Loading