CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
api-docs.tsx
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| 45e31d0 | 1 | /** |
| 2 | * API Documentation — interactive docs page. | |
| 93812e4 | 3 | * |
| 4 | * 2026 polish: scoped `.apidocs-*` class system gives every endpoint group | |
| 5 | * its own card, every endpoint its own row with a color-coded method pill, | |
| 6 | * and every code example a solid-white spec block with a Copy button | |
| 7 | * (recipe lifted from `build-agent-spec.tsx`). Nothing outside `.apidocs-*` | |
| 8 | * is touched and the route surface is unchanged. | |
| 45e31d0 | 9 | */ |
| 10 | ||
| 11 | import { Hono } from "hono"; | |
| 12 | import { Layout } from "../views/layout"; | |
| 13 | import { softAuth } from "../middleware/auth"; | |
| 14 | import type { AuthEnv } from "../middleware/auth"; | |
| 15 | ||
| 16 | const apiDocs = new Hono<AuthEnv>(); | |
| 17 | ||
| 93812e4 | 18 | // ─── Scoped CSS (.apidocs-*) ──────────────────────────────────────────────── |
| 19 | const apiDocsStyles = ` | |
| eed4684 | 20 | .apidocs-wrap { max-width: 1680px; margin: 0 auto; padding: var(--space-5) var(--space-4) var(--space-8); } |
| 93812e4 | 21 | |
| 22 | /* ─── Header ─── */ | |
| 23 | .apidocs-head { margin-bottom: var(--space-5); } | |
| 24 | .apidocs-eyebrow { | |
| 25 | display: inline-flex; | |
| 26 | align-items: center; | |
| 27 | gap: 8px; | |
| 28 | text-transform: uppercase; | |
| 29 | font-family: var(--font-mono); | |
| 30 | font-size: 11px; | |
| 31 | letter-spacing: 0.16em; | |
| 32 | color: var(--text-muted); | |
| 33 | font-weight: 600; | |
| 34 | margin-bottom: 10px; | |
| 35 | } | |
| 36 | .apidocs-eyebrow-dot { | |
| 37 | width: 8px; height: 8px; | |
| 38 | border-radius: 9999px; | |
| 39 | background: linear-gradient(135deg, #8c6dff, #36c5d6); | |
| 40 | box-shadow: 0 0 0 3px rgba(140,109,255,0.18); | |
| 41 | } | |
| 42 | .apidocs-title { | |
| 43 | font-family: var(--font-display); | |
| 44 | font-size: clamp(26px, 3.6vw, 40px); | |
| 45 | font-weight: 800; | |
| 46 | letter-spacing: -0.028em; | |
| 47 | line-height: 1.05; | |
| 48 | margin: 0 0 6px; | |
| 49 | color: var(--text-strong); | |
| 50 | } | |
| 51 | .apidocs-title-grad { | |
| 52 | background-image: linear-gradient(135deg, #a48bff 0%, #8c6dff 50%, #36c5d6 100%); | |
| 53 | -webkit-background-clip: text; | |
| 54 | background-clip: text; | |
| 55 | -webkit-text-fill-color: transparent; | |
| 56 | color: transparent; | |
| 57 | } | |
| 58 | .apidocs-sub { | |
| 59 | margin: 0; | |
| 60 | font-size: 14.5px; | |
| 61 | color: var(--text-muted); | |
| 62 | line-height: 1.5; | |
| 63 | max-width: 720px; | |
| 64 | } | |
| 65 | .apidocs-sub a { color: var(--accent); text-decoration: none; } | |
| 66 | .apidocs-sub a:hover { text-decoration: underline; } | |
| 67 | ||
| 68 | /* ─── Section card per endpoint group ─── */ | |
| 69 | .apidocs-section { | |
| 70 | margin-bottom: var(--space-5); | |
| 71 | background: var(--bg-elevated); | |
| 72 | border: 1px solid var(--border); | |
| 73 | border-radius: 14px; | |
| 74 | overflow: hidden; | |
| 75 | position: relative; | |
| 76 | } | |
| 77 | .apidocs-section::before { | |
| 78 | content: ''; | |
| 79 | position: absolute; | |
| 80 | top: 0; left: 0; right: 0; | |
| 81 | height: 2px; | |
| 82 | background: linear-gradient(90deg, transparent 0%, #8c6dff 30%, #36c5d6 70%, transparent 100%); | |
| 83 | opacity: 0.55; | |
| 84 | pointer-events: none; | |
| 85 | } | |
| 86 | .apidocs-section-head { | |
| 87 | padding: var(--space-4) var(--space-5) var(--space-3); | |
| 88 | border-bottom: 1px solid var(--border); | |
| 89 | } | |
| 90 | .apidocs-section-title { | |
| 91 | margin: 0; | |
| 92 | font-family: var(--font-display); | |
| 93 | font-size: 17px; | |
| 94 | font-weight: 700; | |
| 95 | letter-spacing: -0.018em; | |
| 96 | color: var(--text-strong); | |
| 97 | } | |
| 98 | .apidocs-section-sub { | |
| 99 | margin: 6px 0 0; | |
| 100 | font-size: 12.5px; | |
| 101 | color: var(--text-muted); | |
| 102 | line-height: 1.45; | |
| 103 | } | |
| 104 | .apidocs-section-sub code, | |
| 105 | .apidocs-sub code { | |
| 106 | font-family: var(--font-mono); | |
| 107 | font-size: 12px; | |
| 108 | background: rgba(255,255,255,0.04); | |
| 109 | padding: 1px 6px; | |
| 110 | border-radius: 4px; | |
| 111 | } | |
| 112 | .apidocs-section-body { | |
| 113 | padding: var(--space-3) var(--space-5) var(--space-4); | |
| 114 | display: flex; | |
| 115 | flex-direction: column; | |
| 116 | gap: 10px; | |
| 117 | } | |
| 118 | ||
| 119 | /* ─── Per-endpoint card ─── */ | |
| 120 | .apidocs-endpoint { | |
| 121 | display: grid; | |
| 122 | grid-template-columns: 64px 1fr auto; | |
| 123 | column-gap: 14px; | |
| 124 | row-gap: 6px; | |
| 125 | align-items: start; | |
| 126 | padding: 12px 14px; | |
| 127 | background: rgba(255,255,255,0.018); | |
| 128 | border: 1px solid var(--border); | |
| 129 | border-radius: 11px; | |
| 130 | transition: border-color 120ms ease, background 120ms ease; | |
| 131 | } | |
| 132 | .apidocs-endpoint:hover { | |
| 133 | border-color: var(--border-strong); | |
| 134 | background: rgba(255,255,255,0.03); | |
| 135 | } | |
| 136 | @media (max-width: 720px) { | |
| 137 | .apidocs-endpoint { | |
| 138 | grid-template-columns: 1fr; | |
| 139 | grid-template-areas: "method" "path" "desc" "badges"; | |
| 140 | } | |
| 141 | .apidocs-method { grid-area: method; justify-self: start; } | |
| 142 | .apidocs-path { grid-area: path; } | |
| 143 | .apidocs-desc { grid-area: desc; } | |
| 144 | .apidocs-badges { grid-area: badges; justify-self: start; } | |
| 145 | } | |
| 146 | .apidocs-method { | |
| 147 | display: inline-flex; | |
| 148 | align-items: center; | |
| 149 | justify-content: center; | |
| 150 | padding: 3px 8px; | |
| 151 | min-width: 56px; | |
| 152 | font-family: var(--font-mono); | |
| 153 | font-size: 11px; | |
| 154 | font-weight: 700; | |
| 155 | letter-spacing: 0.04em; | |
| 156 | border-radius: 6px; | |
| 157 | text-align: center; | |
| 158 | text-transform: uppercase; | |
| 159 | line-height: 1.4; | |
| 160 | } | |
| 161 | .apidocs-method.m-get { | |
| 162 | background: rgba(52,211,153,0.14); | |
| 163 | color: #6ee7b7; | |
| 164 | box-shadow: inset 0 0 0 1px rgba(52,211,153,0.32); | |
| 165 | } | |
| 166 | .apidocs-method.m-post { | |
| 167 | background: rgba(140,109,255,0.16); | |
| 168 | color: #c4b5fd; | |
| 169 | box-shadow: inset 0 0 0 1px rgba(140,109,255,0.32); | |
| 170 | } | |
| 171 | .apidocs-method.m-put, | |
| 172 | .apidocs-method.m-patch { | |
| 173 | background: rgba(251,191,36,0.14); | |
| 174 | color: #fde68a; | |
| 175 | box-shadow: inset 0 0 0 1px rgba(251,191,36,0.32); | |
| 176 | } | |
| 177 | .apidocs-method.m-delete { | |
| 178 | background: rgba(248,113,113,0.14); | |
| 179 | color: #fecaca; | |
| 180 | box-shadow: inset 0 0 0 1px rgba(248,113,113,0.32); | |
| 181 | } | |
| 182 | .apidocs-path { | |
| 183 | font-family: var(--font-mono); | |
| 184 | font-size: 13px; | |
| 185 | color: #e9e2ff; | |
| 186 | word-break: break-all; | |
| 187 | line-height: 1.5; | |
| 188 | } | |
| 189 | .apidocs-desc { | |
| 190 | grid-column: 2 / 3; | |
| 191 | font-size: 12.5px; | |
| 192 | color: var(--text-muted); | |
| 193 | line-height: 1.5; | |
| 194 | } | |
| 195 | .apidocs-desc code { | |
| 196 | font-family: var(--font-mono); | |
| 197 | font-size: 11.5px; | |
| 198 | background: rgba(255,255,255,0.04); | |
| 199 | padding: 1px 5px; | |
| 200 | border-radius: 4px; | |
| 201 | } | |
| 202 | .apidocs-params { | |
| 203 | display: block; | |
| 204 | margin-top: 4px; | |
| 205 | font-size: 11.5px; | |
| 206 | color: var(--text-muted); | |
| 207 | } | |
| 208 | .apidocs-badges { | |
| 209 | display: inline-flex; | |
| 210 | flex-direction: column; | |
| 211 | gap: 4px; | |
| 212 | align-items: flex-end; | |
| 213 | } | |
| 214 | @media (max-width: 720px) { | |
| 215 | .apidocs-badges { flex-direction: row; flex-wrap: wrap; } | |
| 216 | } | |
| 217 | .apidocs-badge { | |
| 218 | display: inline-flex; | |
| 219 | align-items: center; | |
| 220 | gap: 4px; | |
| 221 | padding: 2px 8px; | |
| 222 | border-radius: 9999px; | |
| 223 | font-size: 10.5px; | |
| 224 | font-weight: 600; | |
| 225 | letter-spacing: 0.04em; | |
| 226 | text-transform: uppercase; | |
| 227 | line-height: 1.4; | |
| 228 | } | |
| 229 | .apidocs-badge.is-auth { | |
| 230 | background: rgba(54,197,214,0.14); | |
| 231 | color: #67e8f9; | |
| 232 | box-shadow: inset 0 0 0 1px rgba(54,197,214,0.32); | |
| 233 | } | |
| 234 | .apidocs-badge.is-scope { | |
| 235 | background: rgba(52,211,153,0.14); | |
| 236 | color: #6ee7b7; | |
| 237 | box-shadow: inset 0 0 0 1px rgba(52,211,153,0.32); | |
| 238 | } | |
| 239 | .apidocs-badge .dot { width: 5px; height: 5px; border-radius: 9999px; background: currentColor; } | |
| 240 | ||
| 241 | /* ─── Rate-limit table ─── */ | |
| 242 | .apidocs-table-wrap { | |
| 243 | margin: 0; | |
| 244 | border: 1px solid var(--border); | |
| 245 | border-radius: 11px; | |
| 246 | overflow: hidden; | |
| 247 | } | |
| 248 | .apidocs-table { | |
| 249 | width: 100%; | |
| 250 | border-collapse: collapse; | |
| 251 | font-size: 13px; | |
| 252 | } | |
| 253 | .apidocs-table th { | |
| 254 | text-align: left; | |
| 255 | padding: 10px 14px; | |
| 256 | font-size: 11px; | |
| 257 | font-weight: 600; | |
| 258 | text-transform: uppercase; | |
| 259 | letter-spacing: 0.06em; | |
| 260 | color: var(--text-muted); | |
| 261 | background: rgba(255,255,255,0.025); | |
| 262 | border-bottom: 1px solid var(--border); | |
| 263 | } | |
| 264 | .apidocs-table td { | |
| 265 | padding: 10px 14px; | |
| 266 | color: var(--text); | |
| 267 | border-bottom: 1px solid var(--border); | |
| 268 | font-variant-numeric: tabular-nums; | |
| 269 | } | |
| 270 | .apidocs-table tr:last-child td { border-bottom: none; } | |
| 271 | ||
| 272 | /* ─── Solid-white spec block for examples (build-agent-spec recipe) ─── */ | |
| 273 | .apidocs-spec { | |
| 274 | background: #ffffff; | |
| 275 | color: #0a0a0a; | |
| 276 | border: 1px solid #e5e7eb; | |
| 277 | border-radius: 12px; | |
| 278 | overflow: hidden; | |
| 279 | box-shadow: 0 1px 0 rgba(255,255,255,0.04), 0 6px 20px rgba(0,0,0,0.16); | |
| 280 | } | |
| 281 | .apidocs-spec-head { | |
| 282 | display: flex; | |
| 283 | align-items: center; | |
| 284 | justify-content: space-between; | |
| 285 | gap: 12px; | |
| 286 | padding: 10px 14px; | |
| 287 | background: #f9fafb; | |
| 288 | border-bottom: 1px solid #e5e7eb; | |
| 289 | flex-wrap: wrap; | |
| 290 | } | |
| 291 | .apidocs-spec-title { | |
| 292 | display: flex; | |
| 293 | align-items: center; | |
| 294 | gap: 9px; | |
| 295 | margin: 0; | |
| 296 | font-family: var(--font-display, system-ui, sans-serif); | |
| 297 | font-size: 13px; | |
| 298 | font-weight: 700; | |
| 299 | color: #111827; | |
| 300 | letter-spacing: -0.005em; | |
| 301 | } | |
| 302 | .apidocs-spec-dot { | |
| 303 | width: 7px; height: 7px; | |
| 304 | border-radius: 9999px; | |
| 305 | background: linear-gradient(135deg, #8c6dff, #36c5d6); | |
| 306 | box-shadow: 0 0 0 3px rgba(140,109,255,0.18); | |
| 307 | } | |
| 308 | .apidocs-spec-copy { | |
| 309 | display: inline-flex; | |
| 310 | align-items: center; | |
| 311 | gap: 6px; | |
| 312 | padding: 5px 11px; | |
| 313 | font-size: 12px; | |
| 314 | font-weight: 600; | |
| 315 | color: #111827; | |
| 316 | background: #ffffff; | |
| 317 | border: 1px solid #d1d5db; | |
| 318 | border-radius: 7px; | |
| 319 | cursor: pointer; | |
| 320 | font-family: inherit; | |
| 321 | transition: background 120ms ease, border-color 120ms ease, color 120ms ease; | |
| 322 | } | |
| 323 | .apidocs-spec-copy:hover { | |
| 324 | background: #f3f4f6; | |
| 325 | border-color: #9ca3af; | |
| 326 | } | |
| 327 | .apidocs-spec-copy.is-copied { | |
| 328 | background: #ecfdf5; | |
| 329 | border-color: #6ee7b7; | |
| 330 | color: #047857; | |
| 331 | } | |
| 332 | .apidocs-spec-copy svg { display: block; } | |
| 333 | .apidocs-spec-pre { | |
| 334 | margin: 0; | |
| 335 | padding: 16px 18px; | |
| 336 | font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, monospace); | |
| 337 | font-size: 12.5px; | |
| 338 | line-height: 1.65; | |
| 339 | color: #0a0a0a; | |
| 340 | background: #ffffff; | |
| 341 | white-space: pre; | |
| 342 | overflow-x: auto; | |
| 343 | tab-size: 2; | |
| 344 | } | |
| 345 | ||
| 346 | /* ─── Footer / kbd ─── */ | |
| 347 | .apidocs-foot { | |
| 348 | margin-top: var(--space-5); | |
| 349 | padding: var(--space-4); | |
| 350 | text-align: center; | |
| 351 | color: var(--text-muted); | |
| 352 | font-size: 13px; | |
| 353 | border: 1px dashed var(--border); | |
| 354 | border-radius: 12px; | |
| 355 | } | |
| 356 | .apidocs-foot code { | |
| 357 | font-family: var(--font-mono); | |
| 358 | font-size: 12px; | |
| 359 | background: rgba(255,255,255,0.04); | |
| 360 | padding: 1px 6px; | |
| 361 | border-radius: 4px; | |
| 362 | color: var(--text-strong); | |
| 363 | } | |
| 364 | .apidocs-kbd { | |
| 365 | display: inline-block; | |
| 366 | padding: 2px 6px; | |
| 367 | font-family: var(--font-mono); | |
| 368 | font-size: 11px; | |
| 369 | color: var(--text-strong); | |
| 370 | background: rgba(255,255,255,0.04); | |
| 371 | border: 1px solid var(--border-strong); | |
| 372 | border-bottom-width: 2px; | |
| 373 | border-radius: 4px; | |
| 374 | } | |
| 375 | `; | |
| 376 | ||
| 45e31d0 | 377 | apiDocs.get("/api/docs", softAuth, (c) => { |
| 378 | const user = c.get("user"); | |
| 379 | ||
| 380 | return c.html( | |
| 381 | <Layout title="API Documentation" user={user}> | |
| 93812e4 | 382 | <div class="apidocs-wrap"> |
| 383 | <header class="apidocs-head"> | |
| 384 | <div class="apidocs-eyebrow"> | |
| 385 | <span class="apidocs-eyebrow-dot" aria-hidden="true" /> | |
| 386 | REST API · v2 · Public reference | |
| 387 | </div> | |
| 388 | <h1 class="apidocs-title"> | |
| 389 | <span class="apidocs-title-grad">gluecron API.</span> | |
| 390 | </h1> | |
| 391 | <p class="apidocs-sub"> | |
| 392 | Complete REST surface for programmatic access to repositories, | |
| 393 | issues, pull requests, and more. Shapes match GitHub REST v3 — a | |
| 394 | base-URL swap reuses most existing integrations. Build a token at{" "} | |
| 395 | <a href="/settings/tokens">/settings/tokens</a>. | |
| 396 | </p> | |
| 397 | </header> | |
| 45e31d0 | 398 | |
| 399 | <ApiSection | |
| 400 | title="Authentication" | |
| 401 | description="All API requests require authentication via a personal access token." | |
| 402 | > | |
| 403 | <CodeExample | |
| 93812e4 | 404 | id="auth" |
| 45e31d0 | 405 | title="Using a Bearer token" |
| 406 | code={`curl -H "Authorization: Bearer glue_your_token_here" \\ | |
| 407 | https://gluecron.com/api/v2/user`} | |
| 408 | /> | |
| 93812e4 | 409 | <p class="apidocs-section-sub"> |
| 410 | Create a token at <a href="/settings/tokens" style="color:var(--accent);text-decoration:none">/settings/tokens</a>. Tokens support scopes: <code>repo</code>, <code>user</code>, <code>admin</code>. | |
| 45e31d0 | 411 | </p> |
| 412 | </ApiSection> | |
| 413 | ||
| 414 | <ApiSection title="Rate Limits" description="Rate limits are applied per IP address."> | |
| 415 | <EndpointTable | |
| 416 | rows={[ | |
| 417 | ["API routes", "100 req/min"], | |
| 418 | ["Search", "30 req/min"], | |
| 419 | ["Authentication", "10 req/min"], | |
| 420 | ["Git operations", "60 req/min"], | |
| 421 | ]} | |
| 422 | headers={["Scope", "Limit"]} | |
| 423 | /> | |
| 93812e4 | 424 | <p class="apidocs-section-sub" style="margin-top:10px"> |
| 425 | Rate-limit info is included in response headers: <code>X-RateLimit-Limit</code>, <code>X-RateLimit-Remaining</code>, <code>X-RateLimit-Reset</code>. | |
| 45e31d0 | 426 | </p> |
| 427 | </ApiSection> | |
| 428 | ||
| 429 | <ApiSection title="Users"> | |
| 430 | <Endpoint method="GET" path="/api/v2/user" description="Get authenticated user" auth /> | |
| 431 | <Endpoint method="GET" path="/api/v2/users/:username" description="Get user by username" /> | |
| 432 | <Endpoint method="PATCH" path="/api/v2/user" description="Update profile (displayName, bio, avatarUrl)" auth scope="user" /> | |
| 433 | </ApiSection> | |
| 434 | ||
| 435 | <ApiSection title="Repositories"> | |
| 436 | <Endpoint method="GET" path="/api/v2/users/:username/repos" description="List user repositories" params="sort=updated|stars|name" /> | |
| 437 | <Endpoint method="POST" path="/api/v2/repos" description="Create repository" auth scope="repo" /> | |
| 438 | <Endpoint method="GET" path="/api/v2/repos/:owner/:repo" description="Get repository details" /> | |
| 439 | <Endpoint method="PATCH" path="/api/v2/repos/:owner/:repo" description="Update repository (description, visibility)" auth scope="repo" /> | |
| 440 | <Endpoint method="DELETE" path="/api/v2/repos/:owner/:repo" description="Delete repository" auth scope="admin" /> | |
| 441 | </ApiSection> | |
| 442 | ||
| 443 | <ApiSection title="Branches"> | |
| 444 | <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/branches" description="List all branches" /> | |
| 445 | </ApiSection> | |
| 446 | ||
| 447 | <ApiSection title="Commits"> | |
| 448 | <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/commits" description="List commits" params="ref, limit, offset" /> | |
| 449 | <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/commits/:sha" description="Get commit with diff" /> | |
| 450 | </ApiSection> | |
| 451 | ||
| 452 | <ApiSection title="File Contents"> | |
| 453 | <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/tree/:ref" description="Get file tree at ref" params="path" /> | |
| 454 | <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/contents/:path" description="Get file contents" params="ref" /> | |
| 455 | </ApiSection> | |
| 456 | ||
| 457 | <ApiSection title="Issues"> | |
| 458 | <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/issues" description="List issues" params="state=open|closed, limit" /> | |
| 459 | <Endpoint method="POST" path="/api/v2/repos/:owner/:repo/issues" description="Create issue" auth scope="repo" /> | |
| 460 | <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/issues/:number" description="Get issue with comments" /> | |
| 461 | <Endpoint method="PATCH" path="/api/v2/repos/:owner/:repo/issues/:number" description="Update issue (title, body, state)" auth scope="repo" /> | |
| 462 | <Endpoint method="POST" path="/api/v2/repos/:owner/:repo/issues/:number/comments" description="Add comment to issue" auth scope="repo" /> | |
| 463 | </ApiSection> | |
| 464 | ||
| 465 | <ApiSection title="Pull Requests"> | |
| 466 | <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/pulls" description="List pull requests" params="state=open|closed|merged" /> | |
| 467 | <Endpoint method="POST" path="/api/v2/repos/:owner/:repo/pulls" description="Create pull request" auth scope="repo" /> | |
| 468 | <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/pulls/:number" description="Get PR with comments" /> | |
| 469 | </ApiSection> | |
| 470 | ||
| 471 | <ApiSection title="Stars"> | |
| 472 | <Endpoint method="PUT" path="/api/v2/repos/:owner/:repo/star" description="Star a repository" auth /> | |
| 473 | <Endpoint method="DELETE" path="/api/v2/repos/:owner/:repo/star" description="Unstar a repository" auth /> | |
| 474 | </ApiSection> | |
| 475 | ||
| 476 | <ApiSection title="Labels"> | |
| 477 | <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/labels" description="List labels" /> | |
| 478 | <Endpoint method="POST" path="/api/v2/repos/:owner/:repo/labels" description="Create label" auth scope="repo" /> | |
| 479 | </ApiSection> | |
| 480 | ||
| 481 | <ApiSection title="Search"> | |
| 482 | <Endpoint method="GET" path="/api/v2/search/repos" description="Search repositories" params="q (required), sort, limit" /> | |
| 483 | <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/search/code" description="Search code in repository" params="q (required)" /> | |
| 484 | </ApiSection> | |
| 485 | ||
| 486 | <ApiSection title="Topics"> | |
| 487 | <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/topics" description="Get repository topics" /> | |
| 488 | <Endpoint method="PUT" path="/api/v2/repos/:owner/:repo/topics" description="Set repository topics" auth scope="repo" /> | |
| 489 | </ApiSection> | |
| 490 | ||
| 491 | <ApiSection title="Webhooks"> | |
| 492 | <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/webhooks" description="List webhooks" auth scope="repo" /> | |
| 493 | <Endpoint method="POST" path="/api/v2/repos/:owner/:repo/webhooks" description="Create webhook" auth scope="admin" /> | |
| 494 | </ApiSection> | |
| 495 | ||
| 496 | <ApiSection title="Activity Feed"> | |
| 497 | <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/activity" description="Get activity feed" params="limit" /> | |
| 498 | </ApiSection> | |
| 499 | ||
| 500 | <ApiSection title="Status Checks (CI Integration)"> | |
| 501 | <Endpoint method="POST" path="/api/v2/repos/:owner/:repo/statuses/:sha" description="Create status check" auth scope="repo" /> | |
| 502 | <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/statuses/:sha" description="Get status checks for commit" /> | |
| 503 | <CodeExample | |
| 93812e4 | 504 | id="status-ci" |
| 45e31d0 | 505 | title="Report CI status" |
| 506 | code={`curl -X POST -H "Authorization: Bearer glue_xxx" \\ | |
| 507 | -H "Content-Type: application/json" \\ | |
| 508 | -d '{"context":"ci/build","state":"success","targetUrl":"https://ci.example.com/build/123"}' \\ | |
| 509 | https://gluecron.com/api/v2/repos/user/repo/statuses/abc123`} | |
| 510 | /> | |
| 511 | </ApiSection> | |
| 512 | ||
| 93812e4 | 513 | <div class="apidocs-foot"> |
| 514 | API index: <code>GET /api/v2</code> returns a machine-readable endpoint listing. | |
| 515 | Press <span class="apidocs-kbd">?</span> for keyboard shortcuts. | |
| 45e31d0 | 516 | </div> |
| 517 | </div> | |
| 93812e4 | 518 | <style dangerouslySetInnerHTML={{ __html: apiDocsStyles }} /> |
| 519 | <script | |
| 520 | dangerouslySetInnerHTML={{ | |
| 521 | __html: ` | |
| 522 | (function(){ | |
| 523 | var btns = document.querySelectorAll('[data-apidocs-copy]'); | |
| 524 | btns.forEach(function(btn){ | |
| 525 | var pre = btn.parentNode && btn.parentNode.parentNode | |
| 526 | ? btn.parentNode.parentNode.querySelector('[data-apidocs-pre]') | |
| 527 | : null; | |
| 528 | var label = btn.querySelector('[data-apidocs-copy-label]'); | |
| 529 | if (!pre || !label) return; | |
| 530 | btn.addEventListener('click', function(){ | |
| 531 | var text = pre.textContent || ''; | |
| 532 | var done = function(){ | |
| 533 | btn.classList.add('is-copied'); | |
| 534 | label.textContent = 'Copied'; | |
| 535 | setTimeout(function(){ | |
| 536 | btn.classList.remove('is-copied'); | |
| 537 | label.textContent = 'Copy'; | |
| 538 | }, 1800); | |
| 539 | }; | |
| 540 | if (navigator.clipboard && navigator.clipboard.writeText) { | |
| 541 | navigator.clipboard.writeText(text).then(done).catch(function(){ | |
| 542 | var ta = document.createElement('textarea'); | |
| 543 | ta.value = text; ta.style.position='fixed'; ta.style.left='-9999px'; | |
| 544 | document.body.appendChild(ta); ta.select(); | |
| 545 | try { document.execCommand('copy'); done(); } catch(e){} | |
| 546 | document.body.removeChild(ta); | |
| 547 | }); | |
| 548 | } else { | |
| 549 | var ta = document.createElement('textarea'); | |
| 550 | ta.value = text; ta.style.position='fixed'; ta.style.left='-9999px'; | |
| 551 | document.body.appendChild(ta); ta.select(); | |
| 552 | try { document.execCommand('copy'); done(); } catch(e){} | |
| 553 | document.body.removeChild(ta); | |
| 554 | } | |
| 555 | }); | |
| 556 | }); | |
| 557 | })(); | |
| 558 | `, | |
| 559 | }} | |
| 560 | /> | |
| 45e31d0 | 561 | </Layout> |
| 562 | ); | |
| 563 | }); | |
| 564 | ||
| 565 | // ─── Documentation Components ──────────────────────────────────────────── | |
| 566 | ||
| 567 | const ApiSection = ({ | |
| 568 | title, | |
| 569 | description, | |
| 570 | children, | |
| 571 | }: { | |
| 572 | title: string; | |
| 573 | description?: string; | |
| 574 | children: any; | |
| 575 | }) => ( | |
| 93812e4 | 576 | <section class="apidocs-section"> |
| 577 | <header class="apidocs-section-head"> | |
| 578 | <h2 class="apidocs-section-title">{title}</h2> | |
| 579 | {description && ( | |
| 580 | <p class="apidocs-section-sub">{description}</p> | |
| 581 | )} | |
| 582 | </header> | |
| 583 | <div class="apidocs-section-body">{children}</div> | |
| 584 | </section> | |
| 45e31d0 | 585 | ); |
| 586 | ||
| 587 | const Endpoint = ({ | |
| 588 | method, | |
| 589 | path, | |
| 590 | description, | |
| 591 | params, | |
| 592 | auth, | |
| 593 | scope, | |
| 594 | }: { | |
| 595 | method: string; | |
| 596 | path: string; | |
| 597 | description: string; | |
| 598 | params?: string; | |
| 599 | auth?: boolean; | |
| 600 | scope?: string; | |
| 601 | }) => { | |
| 93812e4 | 602 | const methodClass = |
| 603 | method === "GET" ? "m-get" : | |
| 604 | method === "POST" ? "m-post" : | |
| 605 | method === "PUT" ? "m-put" : | |
| 606 | method === "PATCH" ? "m-patch" : | |
| 607 | method === "DELETE" ? "m-delete" : "m-get"; | |
| 45e31d0 | 608 | |
| 609 | return ( | |
| 93812e4 | 610 | <div class="apidocs-endpoint"> |
| 611 | <span class={`apidocs-method ${methodClass}`}>{method}</span> | |
| 612 | <code class="apidocs-path">{path}</code> | |
| 613 | <span class="apidocs-badges"> | |
| 45e31d0 | 614 | {auth && ( |
| 93812e4 | 615 | <span class="apidocs-badge is-auth"> |
| 616 | <span class="dot" aria-hidden="true" /> | |
| 617 | Auth | |
| 45e31d0 | 618 | </span> |
| 619 | )} | |
| 620 | {scope && ( | |
| 93812e4 | 621 | <span class="apidocs-badge is-scope"> |
| 622 | <span class="dot" aria-hidden="true" /> | |
| 45e31d0 | 623 | {scope} |
| 624 | </span> | |
| 625 | )} | |
| 626 | </span> | |
| 93812e4 | 627 | <span class="apidocs-desc"> |
| 628 | {description} | |
| 629 | {params && ( | |
| 630 | <span class="apidocs-params"> | |
| 631 | Params: <code>{params}</code> | |
| 632 | </span> | |
| 633 | )} | |
| 634 | </span> | |
| 45e31d0 | 635 | </div> |
| 636 | ); | |
| 637 | }; | |
| 638 | ||
| 93812e4 | 639 | const CodeExample = ({ |
| 640 | id, | |
| 641 | title, | |
| 642 | code, | |
| 643 | }: { | |
| 644 | id?: string; | |
| 645 | title: string; | |
| 646 | code: string; | |
| 647 | }) => ( | |
| 648 | <div class="apidocs-spec" aria-labelledby={id ? `apidocs-spec-${id}` : undefined}> | |
| 649 | <header class="apidocs-spec-head"> | |
| 650 | <p class="apidocs-spec-title" id={id ? `apidocs-spec-${id}` : undefined}> | |
| 651 | <span class="apidocs-spec-dot" aria-hidden="true" /> | |
| 652 | {title} | |
| 653 | </p> | |
| 45e31d0 | 654 | <button |
| 655 | type="button" | |
| 93812e4 | 656 | class="apidocs-spec-copy" |
| 657 | data-apidocs-copy | |
| 658 | aria-label={`Copy ${title} example to clipboard`} | |
| 45e31d0 | 659 | > |
| 93812e4 | 660 | <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> |
| 661 | <rect x="9" y="9" width="13" height="13" rx="2" ry="2" /> | |
| 662 | <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" /> | |
| 663 | </svg> | |
| 664 | <span data-apidocs-copy-label>Copy</span> | |
| 45e31d0 | 665 | </button> |
| 93812e4 | 666 | </header> |
| 667 | <pre class="apidocs-spec-pre" data-apidocs-pre>{code}</pre> | |
| 45e31d0 | 668 | </div> |
| 669 | ); | |
| 670 | ||
| 671 | const EndpointTable = ({ | |
| 672 | headers, | |
| 673 | rows, | |
| 674 | }: { | |
| 675 | headers: string[]; | |
| 676 | rows: string[][]; | |
| 677 | }) => ( | |
| 93812e4 | 678 | <div class="apidocs-table-wrap"> |
| 679 | <table class="apidocs-table"> | |
| 680 | <thead> | |
| 45e31d0 | 681 | <tr> |
| 93812e4 | 682 | {headers.map((h) => ( |
| 683 | <th>{h}</th> | |
| 45e31d0 | 684 | ))} |
| 685 | </tr> | |
| 93812e4 | 686 | </thead> |
| 687 | <tbody> | |
| 688 | {rows.map((row) => ( | |
| 689 | <tr> | |
| 690 | {row.map((cell) => ( | |
| 691 | <td>{cell}</td> | |
| 692 | ))} | |
| 693 | </tr> | |
| 694 | ))} | |
| 695 | </tbody> | |
| 696 | </table> | |
| 697 | </div> | |
| 45e31d0 | 698 | ); |
| 699 | ||
| 700 | export default apiDocs; |