Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

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.

docs.tsxBlame1601 lines · 3 contributors
e0e4219Claude1/**
2 * /docs — expanded documentation site.
3 *
4 * Routes:
5 * GET /docs — landing page with links to all sections
6 * GET /docs/getting-started — push your first repo in 60 seconds
7 * GET /docs/workflow-yaml — workflow YAML syntax reference
8 * GET /docs/mcp-server — MCP server setup + tool catalogue
9 * GET /docs/api — REST API reference
10 * GET /docs/agents — agent publishing guide
11 *
12 * Uses softAuth so the nav bar renders with the signed-in user's session
13 * cookie when present; the pages are reachable without auth.
14 */
15
16import { Hono } from "hono";
17import { Layout } from "../views/layout";
18import { softAuth } from "../middleware/auth";
19import type { AuthEnv } from "../middleware/auth";
20
21const docs = new Hono<AuthEnv>();
22docs.use("*", softAuth);
23
24// ─── SHARED STYLES ──────────────────────────────────────────────────────────
25const docsStyles = `
26 /* ─── Layout ─── */
27 .docs-wrap {
28 max-width: 1320px;
29 margin: 0 auto;
30 padding: var(--space-6) var(--space-4);
31 display: grid;
32 grid-template-columns: 240px 1fr;
33 gap: var(--space-6);
34 align-items: start;
35 }
8102dd4ccantynz-alt36 /* A 1fr grid track defaults to min-width:auto, so it grows to fit its
37 widest child — a long unwrapped line inside .docs-content pre stretched
38 the whole page to ~3400px and overflow-x:auto on the pre never got a
39 chance to kick in. min-width:0 lets the track shrink so the pre
40 scrolls internally instead. */
41 .docs-content { min-width: 0; }
e0e4219Claude42 @media (max-width: 800px) {
43 .docs-wrap { grid-template-columns: 1fr; }
44 .docs-sidebar { display: none; }
45 }
46
47 /* ─── Sidebar ─── */
48 .docs-sidebar {
49 position: sticky;
50 top: calc(var(--header-h, 56px) + var(--space-4));
51 background: var(--bg-elevated);
52 border: 1px solid var(--border);
53 border-radius: 14px;
54 padding: var(--space-4);
55 }
56 .docs-sidebar-label {
57 font-size: 11px;
58 font-weight: 700;
59 letter-spacing: 0.08em;
60 text-transform: uppercase;
61 color: var(--accent);
62 margin-bottom: var(--space-3);
63 padding-bottom: 8px;
64 border-bottom: 1px solid var(--border);
65 }
66 .docs-sidebar-nav {
67 display: flex;
68 flex-direction: column;
69 gap: 2px;
70 }
71 .docs-sidebar-nav a {
72 display: block;
73 padding: 7px 10px;
74 font-size: 13.5px;
75 color: var(--text);
76 text-decoration: none;
77 border-radius: 8px;
78 transition: background 120ms ease, color 120ms ease;
79 }
80 .docs-sidebar-nav a:hover {
6fd5915Claude81 background: rgba(91,110,232,0.08);
e0e4219Claude82 color: var(--text-strong);
83 }
84 .docs-sidebar-nav a.docs-nav-active {
6fd5915Claude85 background: rgba(91,110,232,0.14);
e0e4219Claude86 color: #c4b6ff;
87 font-weight: 600;
88 }
89
90 /* ─── Hero (landing page only) ─── */
91 .docs-hero {
92 position: relative;
93 margin-bottom: var(--space-5);
94 padding: var(--space-5) var(--space-6);
95 background: var(--bg-elevated);
96 border: 1px solid var(--border);
97 border-radius: 16px;
98 overflow: hidden;
99 }
100 .docs-hero::before {
101 content: '';
102 position: absolute;
103 top: 0; left: 0; right: 0;
104 height: 2px;
6fd5915Claude105 background: linear-gradient(90deg, transparent 0%, #5b6ee8 30%, #5f8fa0 70%, transparent 100%);
e0e4219Claude106 opacity: 0.75;
107 }
108 .docs-hero-orb {
109 position: absolute;
110 inset: -20% -10% auto auto;
111 width: 320px; height: 320px;
6fd5915Claude112 background: radial-gradient(circle, rgba(91,110,232,0.18), rgba(95,143,160,0.09) 45%, transparent 70%);
e0e4219Claude113 filter: blur(80px);
114 opacity: 0.7;
115 pointer-events: none;
116 }
117 .docs-hero-inner { position: relative; z-index: 1; max-width: 640px; }
118 .docs-hero-eyebrow {
119 font-size: 11px;
120 font-weight: 700;
121 letter-spacing: 0.08em;
122 text-transform: uppercase;
123 color: var(--accent);
124 margin-bottom: var(--space-2);
125 }
126 .docs-hero-title {
127 font-size: clamp(26px, 3.5vw, 38px);
128 font-family: var(--font-display);
129 font-weight: 800;
130 letter-spacing: -0.025em;
131 margin: 0 0 var(--space-3);
132 color: var(--text-strong);
133 }
134 .docs-hero-title .docs-gradient {
6fd5915Claude135 background-image: linear-gradient(135deg, #5b6ee8 0%, #5b6ee8 50%, #5f8fa0 100%);
e0e4219Claude136 -webkit-background-clip: text;
137 background-clip: text;
138 -webkit-text-fill-color: transparent;
139 color: transparent;
140 }
141 .docs-hero-sub {
142 font-size: 15px;
143 color: var(--text-muted);
144 margin: 0;
145 line-height: 1.55;
146 }
147
148 /* ─── Card grid (landing) ─── */
149 .docs-card-grid {
150 display: grid;
151 grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
152 gap: var(--space-4);
153 margin-bottom: var(--space-5);
154 }
155 .docs-card {
156 background: var(--bg-elevated);
157 border: 1px solid var(--border);
158 border-radius: 14px;
159 padding: var(--space-4) var(--space-5);
160 text-decoration: none;
161 transition: border-color 150ms ease, box-shadow 150ms ease;
162 display: block;
163 }
164 .docs-card:hover {
6fd5915Claude165 border-color: rgba(91,110,232,0.45);
166 box-shadow: 0 4px 24px -8px rgba(91,110,232,0.25);
e0e4219Claude167 }
168 .docs-card-icon {
169 font-size: 24px;
170 margin-bottom: var(--space-2);
171 }
172 .docs-card-title {
173 font-size: 15px;
174 font-weight: 700;
175 color: var(--text-strong);
176 margin: 0 0 6px;
177 }
178 .docs-card-desc {
179 font-size: 13px;
180 color: var(--text-muted);
181 line-height: 1.5;
182 margin: 0;
183 }
184
185 /* ─── Main content area ─── */
186 .docs-content h1 {
187 font-family: var(--font-display);
188 font-size: clamp(22px, 3vw, 30px);
189 font-weight: 800;
190 letter-spacing: -0.022em;
191 color: var(--text-strong);
192 margin: 0 0 var(--space-2);
193 }
194 .docs-content .docs-page-sub {
195 font-size: 14.5px;
196 color: var(--text-muted);
197 margin: 0 0 var(--space-5);
198 line-height: 1.55;
199 }
200 .docs-content h2 {
201 font-family: var(--font-display);
202 font-size: 18px;
203 font-weight: 700;
204 letter-spacing: -0.015em;
205 color: var(--text-strong);
206 margin: var(--space-6) 0 var(--space-3);
207 padding-top: var(--space-4);
208 border-top: 1px solid var(--border);
209 scroll-margin-top: calc(var(--header-h, 56px) + var(--space-3));
210 }
211 .docs-content h2:first-of-type { border-top: none; margin-top: 0; padding-top: 0; }
212 .docs-content h3 {
213 font-size: 15px;
214 font-weight: 700;
215 color: var(--text-strong);
216 margin: var(--space-4) 0 var(--space-2);
217 }
218 .docs-content p {
219 font-size: 14px;
220 line-height: 1.65;
221 color: var(--text);
222 margin: 0 0 var(--space-3);
223 }
224 .docs-content ul, .docs-content ol {
225 font-size: 14px;
226 line-height: 1.65;
227 color: var(--text);
228 margin: 0 0 var(--space-3);
229 padding-left: var(--space-4);
230 }
231 .docs-content li { margin-bottom: 6px; }
232 .docs-content code {
233 font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
234 font-size: 12.5px;
235 padding: 1px 5px;
236 border-radius: 4px;
6fd5915Claude237 background: rgba(91,110,232,0.10);
e0e4219Claude238 color: #c4b6ff;
239 }
240 .docs-content pre {
241 margin: 0 0 var(--space-3);
242 padding: 16px 18px;
243 background: #0a0c14;
244 border: 1px solid var(--border);
245 border-radius: 10px;
246 font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
247 font-size: 13px;
248 line-height: 1.6;
249 color: #d6d8e3;
250 overflow-x: auto;
251 }
252 .docs-content pre code {
253 background: none;
254 padding: 0;
255 color: inherit;
256 font-size: inherit;
257 }
258 .docs-content a { color: var(--accent); }
259 .docs-content table {
260 width: 100%;
261 border-collapse: collapse;
262 font-size: 13.5px;
263 margin: 0 0 var(--space-4);
264 }
265 .docs-content th {
266 text-align: left;
267 padding: 8px 12px;
268 background: var(--bg-elevated);
269 border: 1px solid var(--border);
270 font-weight: 600;
271 color: var(--text-strong);
272 }
273 .docs-content td {
274 padding: 8px 12px;
275 border: 1px solid var(--border);
276 color: var(--text);
277 vertical-align: top;
278 }
279 .docs-content td code { font-size: 12px; }
280
281 /* ─── Page nav (prev/next) ─── */
282 .docs-page-nav {
283 display: flex;
284 justify-content: space-between;
285 gap: var(--space-4);
286 margin-top: var(--space-6);
287 padding-top: var(--space-4);
288 border-top: 1px solid var(--border);
289 }
290 .docs-page-nav-item {
291 display: flex;
292 flex-direction: column;
293 gap: 4px;
294 text-decoration: none;
295 padding: 12px 16px;
296 background: var(--bg-elevated);
297 border: 1px solid var(--border);
298 border-radius: 10px;
299 transition: border-color 150ms ease;
300 max-width: 220px;
301 flex: 1;
302 }
6fd5915Claude303 .docs-page-nav-item:hover { border-color: rgba(91,110,232,0.45); }
e0e4219Claude304 .docs-page-nav-item--next { text-align: right; margin-left: auto; }
305 .docs-page-nav-label {
306 font-size: 11px;
307 font-weight: 600;
308 letter-spacing: 0.07em;
309 text-transform: uppercase;
310 color: var(--text-muted);
311 }
312 .docs-page-nav-title {
313 font-size: 13.5px;
314 font-weight: 600;
315 color: var(--text-strong);
316 }
317
318 /* ─── Edit link ─── */
319 .docs-edit-link {
320 display: inline-flex;
321 align-items: center;
322 gap: 6px;
323 font-size: 12px;
324 color: var(--text-muted);
325 text-decoration: none;
326 margin-top: var(--space-5);
327 padding: 5px 10px;
328 border: 1px solid var(--border);
329 border-radius: 8px;
330 transition: color 120ms ease, border-color 120ms ease;
331 }
332 .docs-edit-link:hover {
333 color: var(--text);
6fd5915Claude334 border-color: rgba(91,110,232,0.35);
e0e4219Claude335 }
336`;
337
338// ─── SIDEBAR NAV (shared) ─────────────────────────────────────────────────
339type DocSection = { href: string; label: string };
340
341const NAV_SECTIONS: DocSection[] = [
342 { href: "/docs", label: "Overview" },
343 { href: "/docs/getting-started", label: "Getting started" },
344 { href: "/docs/workflow-yaml", label: "Workflow YAML" },
345 { href: "/docs/mcp-server", label: "MCP server" },
346 { href: "/docs/api", label: "API reference" },
347 { href: "/docs/agents", label: "Agent publishing" },
348];
349
350function Sidebar({ current }: { current: string }) {
351 return (
352 <aside class="docs-sidebar">
353 <div class="docs-sidebar-label">Documentation</div>
354 <nav class="docs-sidebar-nav" aria-label="Docs sections">
355 {NAV_SECTIONS.map((s) => (
356 <a
357 href={s.href}
358 class={s.href === current ? "docs-nav-active" : ""}
359 aria-current={s.href === current ? "page" : undefined}
360 >
361 {s.label}
362 </a>
363 ))}
364 </nav>
365 </aside>
366 );
367}
368
369function PageNav({
370 prev,
371 next,
372}: {
373 prev?: DocSection;
374 next?: DocSection;
375}) {
376 return (
377 <div class="docs-page-nav">
378 {prev ? (
379 <a href={prev.href} class="docs-page-nav-item">
380 <span class="docs-page-nav-label">&#8592; Previous</span>
381 <span class="docs-page-nav-title">{prev.label}</span>
382 </a>
383 ) : (
384 <span />
385 )}
386 {next ? (
387 <a href={next.href} class="docs-page-nav-item docs-page-nav-item--next">
388 <span class="docs-page-nav-label">Next &#8594;</span>
389 <span class="docs-page-nav-title">{next.label}</span>
390 </a>
391 ) : null}
392 </div>
393 );
394}
395
396function EditLink({ path }: { path: string }) {
397 return (
398 <a
399 href={`/ccantynz/Gluecron.com/blob/main/${path}`}
400 class="docs-edit-link"
401 title="Edit this page in the source repo"
402 >
403 <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">
404 <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
405 <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
406 </svg>
407 Edit this page
408 </a>
409 );
410}
411
412// ─── LANDING (/docs) ──────────────────────────────────────────────────────
413
414docs.get("/docs", (c) => {
415 const user = c.get("user");
416 return c.html(
417 <Layout title="Docs — gluecron" user={user} description="Gluecron documentation — getting started, workflow YAML, MCP server, REST API, and agent publishing.">
418 <style dangerouslySetInnerHTML={{ __html: docsStyles }} />
419 <div class="docs-wrap">
420 <Sidebar current="/docs" />
421 <div class="docs-content">
422 {/* Hero */}
423 <div class="docs-hero">
424 <div class="docs-hero-orb" aria-hidden="true" />
425 <div class="docs-hero-inner">
426 <div class="docs-hero-eyebrow">Documentation</div>
427 <h1 class="docs-hero-title">
428 Build on <span class="docs-gradient">gluecron</span>
429 </h1>
430 <p class="docs-hero-sub">
431 Everything you need to host code, automate CI, talk to your
432 repos from Claude, and publish agents to the marketplace.
433 </p>
434 </div>
435 </div>
436
437 {/* Card grid */}
438 <div class="docs-card-grid">
439 <a href="/docs/getting-started" class="docs-card">
440 <div class="docs-card-icon">&#9654;</div>
441 <div class="docs-card-title">Getting started</div>
442 <p class="docs-card-desc">
443 Create an account, create a repo, and push your first commit —
444 in under 60 seconds.
445 </p>
446 </a>
447 <a href="/docs/workflow-yaml" class="docs-card">
448 <div class="docs-card-icon">&#9881;</div>
449 <div class="docs-card-title">Workflow YAML</div>
450 <p class="docs-card-desc">
451 Triggers, steps, env vars, secrets, matrix builds, and the
452 cache action — full syntax reference.
453 </p>
454 </a>
455 <a href="/docs/mcp-server" class="docs-card">
456 <div class="docs-card-icon">&#128279;</div>
457 <div class="docs-card-title">MCP server</div>
458 <p class="docs-card-desc">
459 Add the Gluecron MCP config to Claude Code and get 15+ tools
460 for driving repos from AI sessions.
461 </p>
462 </a>
463 <a href="/docs/api" class="docs-card">
464 <div class="docs-card-icon">&#128196;</div>
465 <div class="docs-card-title">API reference</div>
466 <p class="docs-card-desc">
467 Bearer-token auth, repos, issues, PRs, webhooks, and rate
468 limits. Core endpoints with request/response shapes.
469 </p>
470 </a>
471 <a href="/docs/agents" class="docs-card">
472 <div class="docs-card-icon">&#129302;</div>
473 <div class="docs-card-title">Agent publishing</div>
474 <p class="docs-card-desc">
475 Write an agent, test it locally, publish to the marketplace,
476 and earn 70% of every sale.
477 </p>
478 </a>
479 </div>
480
481 <EditLink path="src/routes/docs.tsx" />
482 </div>
483 </div>
484 </Layout>
485 );
486});
487
488// ─── GETTING STARTED (/docs/getting-started) ─────────────────────────────
489
490docs.get("/docs/getting-started", (c) => {
491 const user = c.get("user");
492 const idx = NAV_SECTIONS.findIndex((s) => s.href === "/docs/getting-started");
493 const prev = NAV_SECTIONS[idx - 1];
494 const next = NAV_SECTIONS[idx + 1];
495
496 return c.html(
497 <Layout title="Getting started — Docs — gluecron" user={user} description="Push your first repo to Gluecron in under 60 seconds.">
498 <style dangerouslySetInnerHTML={{ __html: docsStyles }} />
499 <div class="docs-wrap">
500 <Sidebar current="/docs/getting-started" />
501 <div class="docs-content">
502 <h1>Getting started</h1>
503 <p class="docs-page-sub">
504 Push your first repo to Gluecron in under 60 seconds. No special
505 tooling required — just git.
506 </p>
507
508 <h2 id="create-account">1. Create an account</h2>
509 <p>
510 Head to <a href="/register">/register</a>, pick a username and
511 password. Your username becomes part of every repo URL:
512 <code>gluecron.com/&lt;username&gt;/&lt;repo&gt;</code>.
513 </p>
514 <p>
515 Verify your email when the one-time link arrives. Verified
516 addresses receive issue, PR, and gate-run notifications.
517 </p>
518
519 <h2 id="create-repo">2. Create your first repo</h2>
520 <p>
521 From the dashboard hit <strong>+ New</strong>, or visit{" "}
522 <a href="/new">/new</a>. Choose:
523 </p>
524 <ul>
525 <li>
526 <strong>Name</strong> — lowercase, hyphens OK, no spaces.
527 </li>
528 <li>
529 <strong>Visibility</strong> — public (anyone can clone) or
530 private (only you and collaborators).
531 </li>
532 <li>
533 <strong>Initialize with README</strong> — tick this so you get
534 a default branch immediately.
535 </li>
536 </ul>
537
538 <h2 id="push-existing">3. Push an existing local repo</h2>
539 <p>
540 Add gluecron as a remote and push your default branch. Replace
541 <code>you</code> and <code>my-project</code> with your actual
542 username and repo name.
543 </p>
544 <pre><code>{`# Add the remote
545git remote add origin https://gluecron.com/you/my-project.git
546
547# Push (first time — track the upstream branch)
548git push -u origin main`}</code></pre>
549 <p>
550 Git will prompt for your username and password. Use your
551 Gluecron password, or better yet a{" "}
552 <a href="/settings/tokens">personal access token</a> (tokens
553 start with <code>glc_</code> and never expire until revoked).
554 </p>
555
556 <h2 id="clone">4. Clone an existing repo</h2>
557 <pre><code>{`git clone https://gluecron.com/you/my-project.git
558cd my-project`}</code></pre>
559
560 <h2 id="ssh">5. Switch to SSH (recommended)</h2>
561 <p>
562 HTTPS is fine for one-off clones. For daily use, SSH is smoother —
563 no password prompts.
564 </p>
565 <ol>
566 <li>
567 Copy your public key:{" "}
568 <code>cat ~/.ssh/id_ed25519.pub</code>
569 </li>
570 <li>
571 Paste it at{" "}
572 <a href="/settings/keys">/settings/keys</a> and save.
573 </li>
574 <li>
575 Re-clone with the SSH URL:
576 <pre><code>{`git clone git@gluecron.com:you/my-project.git`}</code></pre>
577 </li>
578 </ol>
579
580 <h2 id="import">6. Import from GitHub</h2>
581 <p>
582 Already have repos on GitHub? Visit{" "}
583 <a href="/import">/import</a>, paste the GitHub URL (public or
584 private), and Gluecron mirrors the full history, branches, and tags
585 in one shot. Subsequent pushes go to Gluecron directly.
586 </p>
587
588 <h2 id="next">What happens after push?</h2>
589 <p>
590 Every push to the default branch triggers the{" "}
591 <code>post-receive</code> hook:
592 </p>
593 <ul>
594 <li>
595 <strong>GateTest scan</strong> — checks the diff for leaked
596 secrets, dependency advisories, and policy violations. Results
597 appear on the commit page within seconds.
598 </li>
599 <li>
600 <strong>AI review</strong> — if the push targets an open PR,
601 the AI reviewer comments on changed files automatically.
602 </li>
603 <li>
604 <strong>Workflow runs</strong> — any{" "}
605 <code>.gluecron/workflows/*.yml</code> files with a{" "}
606 <code>push</code> trigger fire immediately.
607 </li>
608 <li>
609 <strong>Webhooks</strong> — registered webhook URLs receive a{" "}
610 <code>push</code> event payload within ~1 second.
611 </li>
612 </ul>
613
614 <PageNav prev={prev} next={next} />
615 <EditLink path="src/routes/docs.tsx" />
616 </div>
617 </div>
618 </Layout>
619 );
620});
621
622// ─── WORKFLOW YAML (/docs/workflow-yaml) ──────────────────────────────────
623
624docs.get("/docs/workflow-yaml", (c) => {
625 const user = c.get("user");
626 const idx = NAV_SECTIONS.findIndex((s) => s.href === "/docs/workflow-yaml");
627 const prev = NAV_SECTIONS[idx - 1];
628 const next = NAV_SECTIONS[idx + 1];
629
630 return c.html(
631 <Layout title="Workflow YAML — Docs — gluecron" user={user} description="Gluecron workflow YAML syntax reference — triggers, steps, env vars, secrets, matrix builds.">
632 <style dangerouslySetInnerHTML={{ __html: docsStyles }} />
633 <div class="docs-wrap">
634 <Sidebar current="/docs/workflow-yaml" />
635 <div class="docs-content">
636 <h1>Workflow YAML syntax</h1>
637 <p class="docs-page-sub">
638 Workflows live in <code>.gluecron/workflows/*.yml</code> at the
639 repo root. Gluecron runs them on the same node that handles your
640 pushes — no external scheduler required.
641 </p>
642
643 <h2 id="minimal">Minimal example</h2>
644 <pre><code>{`name: CI
645
646on:
647 push:
648 branches: [main]
649
650jobs:
651 test:
652 runs-on: ubuntu-latest
653 steps:
654 - uses: actions/checkout@v4
655 - run: bun install --frozen-lockfile
656 - run: bun test`}</code></pre>
657
658 <h2 id="triggers">Triggers</h2>
659 <p>
660 The <code>on:</code> key controls when a workflow fires. Multiple
661 triggers can be combined in a single file.
662 </p>
663
664 <h3 id="push-trigger">push</h3>
665 <pre><code>{`on:
666 push:
667 branches: [main, "release/**"]
668 # Optional — only run when these paths change:
669 paths:
670 - "src/**"
671 - "package.json"`}</code></pre>
672
673 <h3 id="pr-trigger">pull_request</h3>
674 <pre><code>{`on:
675 pull_request:
676 types: [opened, synchronize, reopened]
677 branches: [main]`}</code></pre>
678
679 <h3 id="schedule-trigger">schedule (cron)</h3>
680 <p>
681 Drop a cron expression in the <code>schedule</code> array. The
682 Gluecron autopilot ticker fires it from the same node — no
683 external scheduler needed.
684 </p>
685 <pre><code>{`on:
686 schedule:
687 # Every day at 06:00 UTC
688 - cron: "0 6 * * *"
689 # Every Monday at 09:00 UTC
690 - cron: "0 9 * * 1"`}</code></pre>
691
692 <h3 id="manual-trigger">workflow_dispatch (manual)</h3>
693 <pre><code>{`on:
694 workflow_dispatch:
695 inputs:
696 environment:
697 description: "Target environment"
698 required: true
699 default: staging
700 type: choice
701 options: [staging, production]`}</code></pre>
702
703 <h2 id="jobs">Jobs</h2>
704 <p>
705 Each job runs in a fresh container. Jobs within the same workflow
706 run in parallel by default; use <code>needs:</code> to sequence
707 them.
708 </p>
709 <pre><code>{`jobs:
710 build:
711 runs-on: ubuntu-latest
712 steps:
713 - uses: actions/checkout@v4
714 - run: bun run build
715
716 deploy:
717 runs-on: ubuntu-latest
718 needs: build # waits for build to succeed
719 if: github.ref == 'refs/heads/main'
720 steps:
721 - run: echo "Deploying…"`}</code></pre>
722
723 <h2 id="steps">Steps</h2>
724
725 <h3>run — shell commands</h3>
726 <pre><code>{`steps:
727 - name: Install deps
728 run: bun install --frozen-lockfile
729
730 - name: Multi-line script
731 run: |
732 bun run lint
733 bun run typecheck
734 bun test --reporter=verbose`}</code></pre>
735
736 <h3>uses — reusable actions</h3>
737 <pre><code>{`steps:
738 - uses: actions/checkout@v4 # check out the repo
739
740 - uses: oven-sh/setup-bun@v2 # install Bun
741 with:
742 bun-version: latest
743
744 - uses: actions/cache@v4 # cache restore/save
745 with:
746 path: ~/.bun/install/cache
747 key: \${{ runner.os }}-bun-\${{ hashFiles('**/bun.lockb') }}`}</code></pre>
748
749 <h2 id="env">Environment variables</h2>
750 <p>
751 Set env vars at the workflow, job, or step level. Step-level
752 values override job-level which override workflow-level.
753 </p>
754 <pre><code>{`env:
755 NODE_ENV: test
756 LOG_LEVEL: debug
757
758jobs:
759 test:
760 runs-on: ubuntu-latest
761 env:
762 DATABASE_URL: postgresql://localhost/testdb
763 steps:
764 - run: bun test
765 env:
766 VERBOSE: "1" # step-level override`}</code></pre>
767
768 <h2 id="secrets">Secrets</h2>
769 <p>
770 Secrets are stored encrypted at the repo or org level and injected
771 at runtime. They never appear in logs — values are masked
772 automatically.
773 </p>
774 <pre><code>{`steps:
775 - name: Deploy
776 run: curl -X POST \$DEPLOY_WEBHOOK
777 env:
778 DEPLOY_WEBHOOK: \${{ secrets.DEPLOY_WEBHOOK_URL }}`}</code></pre>
779 <p>
780 Manage repo secrets at{" "}
781 <code>/:owner/:repo/settings/secrets</code>. Org-level secrets
782 live at <code>/orgs/:slug/settings/secrets</code> and are shared
783 across all repos in the org.
784 </p>
785
786 <h2 id="matrix">Matrix builds</h2>
787 <p>
788 Run the same job across multiple configurations in parallel.
789 </p>
790 <pre><code>{`jobs:
791 test:
792 runs-on: ubuntu-latest
793 strategy:
794 matrix:
795 node: [18, 20, 22]
796 os: [ubuntu-latest, macos-latest]
797 fail-fast: false # don't cancel others on first failure
798 steps:
799 - uses: actions/checkout@v4
800 - run: node --version # uses matrix.node implicitly via setup-node
801 - run: bun test`}</code></pre>
802
803 <h2 id="cache">Cache action</h2>
804 <pre><code>{`# Bun package cache — speeds up installs by ~80% on warm hits
805- uses: actions/cache@v4
806 id: bun-cache
807 with:
808 path: ~/.bun/install/cache
809 key: \${{ runner.os }}-bun-\${{ hashFiles('**/bun.lockb') }}
810 restore-keys: |
811 \${{ runner.os }}-bun-
812
813- run: bun install --frozen-lockfile`}</code></pre>
814
815 <h2 id="expressions">Expressions</h2>
816 <pre><code>{`# Context access
817\${{ github.actor }} # username that triggered the run
818\${{ github.ref }} # refs/heads/main
819\${{ github.sha }} # full commit SHA
820\${{ runner.os }} # Linux | macOS | Windows
821\${{ job.status }} # success | failure | cancelled
822
823# Functions
824\${{ hashFiles('**/bun.lockb') }}
825\${{ contains(github.ref, 'release') }}
826\${{ startsWith(github.ref, 'refs/tags/') }}`}</code></pre>
827
828 <h2 id="complete-example">Complete CI + deploy example</h2>
829 <pre><code>{`# .gluecron/workflows/ci.yml
830name: CI / Deploy
831
832on:
833 push:
834 branches: [main]
835 pull_request:
836 branches: [main]
837
838jobs:
839 ci:
840 runs-on: ubuntu-latest
841 steps:
842 - uses: actions/checkout@v4
843
844 - uses: actions/cache@v4
845 with:
846 path: ~/.bun/install/cache
847 key: \${{ runner.os }}-bun-\${{ hashFiles('**/bun.lockb') }}
848
849 - uses: oven-sh/setup-bun@v2
850 with:
851 bun-version: latest
852
853 - run: bun install --frozen-lockfile
854 - run: bun run typecheck
855 - run: bun test
856
857 deploy:
858 runs-on: ubuntu-latest
859 needs: ci
860 if: github.ref == 'refs/heads/main'
861 steps:
862 - uses: actions/checkout@v4
863 - run: bun run build
864 - name: Deploy to Fly.io
865 run: flyctl deploy --remote-only
866 env:
867 FLY_API_TOKEN: \${{ secrets.FLY_API_TOKEN }}`}</code></pre>
868
869 <PageNav prev={prev} next={next} />
870 <EditLink path="src/routes/docs.tsx" />
871 </div>
872 </div>
873 </Layout>
874 );
875});
876
877// ─── MCP SERVER (/docs/mcp-server) ───────────────────────────────────────
878
879docs.get("/docs/mcp-server", (c) => {
880 const user = c.get("user");
881 const idx = NAV_SECTIONS.findIndex((s) => s.href === "/docs/mcp-server");
882 const prev = NAV_SECTIONS[idx - 1];
883 const next = NAV_SECTIONS[idx + 1];
884
885 return c.html(
886 <Layout title="MCP server — Docs — gluecron" user={user} description="Set up the Gluecron MCP server in Claude Code and drive your repos from AI sessions.">
887 <style dangerouslySetInnerHTML={{ __html: docsStyles }} />
888 <div class="docs-wrap">
889 <Sidebar current="/docs/mcp-server" />
890 <div class="docs-content">
891 <h1>MCP server</h1>
892 <p class="docs-page-sub">
893 Gluecron ships a Model Context Protocol (MCP) server at{" "}
894 <code>POST /mcp</code>. Add it to Claude Code and you can create
895 issues, open PRs, merge pull requests, and more — all from an AI
896 session, with full gate enforcement on every write.
897 </p>
898
899 <h2 id="install">1. Install Claude Code</h2>
900 <p>
901 If you don't have Claude Code yet, install it from{" "}
902 <a href="https://claude.ai/code" target="_blank" rel="noopener">claude.ai/code</a>{" "}
903 or via npm:
904 </p>
905 <pre><code>{`npm install -g @anthropic-ai/claude-code`}</code></pre>
906
907 <h2 id="generate-token">2. Generate a personal access token</h2>
908 <p>
909 MCP write tools require authentication. Create a token at{" "}
910 <a href="/settings/tokens">/settings/tokens</a> — choose the{" "}
911 <strong>admin</strong> scope so merge and close operations are
912 allowed. The token is shown once; copy it immediately. Tokens
913 start with <code>glc_</code>.
914 </p>
915
916 <h2 id="add-config">3. Add the MCP config</h2>
917 <p>
78ede81ccanty labs918 Create an <code>.mcp.json</code> file at the root of your project
919 (this is the file Claude Code actually loads MCP servers from, on
920 both the CLI and web/cloud sessions):
e0e4219Claude921 </p>
922 <pre><code>{`{
923 "mcpServers": {
924 "gluecron": {
925 "type": "http",
926 "url": "https://gluecron.com/mcp",
927 "headers": {
928 "Authorization": "Bearer glc_your_token_here"
929 }
930 }
931 }
932}`}</code></pre>
933 <p>
934 If you self-host Gluecron, replace <code>gluecron.com</code> with
935 your instance URL.
936 </p>
937 <p>
938 Alternatively, one-click setup is available at{" "}
939 <a href="/connect/claude">/connect/claude</a> — it generates the
940 JSON snippet with your token pre-filled.
941 </p>
942
943 <h2 id="verify">4. Verify the connection</h2>
944 <p>Start Claude Code in your project directory:</p>
945 <pre><code>{`claude
946
947# In the session, ask:
948# "List open PRs on my-org/my-repo"
949# Claude will call gluecron_list_prs and return the results.`}</code></pre>
950
951 <h2 id="tools">Available tools</h2>
952 <p>
953 The MCP server exposes the following tools. Read-only tools work
954 without authentication; write tools require a token with the
955 appropriate scope.
956 </p>
957
958 <h3>Read-only tools</h3>
959 <table>
960 <thead>
961 <tr>
962 <th>Tool</th>
963 <th>Description</th>
964 </tr>
965 </thead>
966 <tbody>
967 <tr>
968 <td><code>gluecron_repo_search</code></td>
969 <td>Search public repos by keyword. Returns up to 20 results.</td>
970 </tr>
971 <tr>
972 <td><code>gluecron_repo_read_file</code></td>
973 <td>Read a single file from a public repo at a given ref (branch / tag / commit).</td>
974 </tr>
975 <tr>
976 <td><code>gluecron_repo_list_issues</code></td>
977 <td>List open issues for a public repo. Returns up to 50 ordered by most-recent.</td>
978 </tr>
979 <tr>
980 <td><code>gluecron_repo_explain_codebase</code></td>
981 <td>Return the cached AI "explain this codebase" Markdown for a public repo.</td>
982 </tr>
983 <tr>
984 <td><code>gluecron_repo_health</code></td>
985 <td>Compute the health report for a public repo: overall score (0-100), letter grade, per-category breakdown, and actionable insights.</td>
986 </tr>
987 </tbody>
988 </table>
989
990 <h3>Write tools (require auth)</h3>
991 <table>
992 <thead>
993 <tr>
994 <th>Tool</th>
995 <th>Description</th>
996 </tr>
997 </thead>
998 <tbody>
999 <tr>
1000 <td><code>gluecron_create_issue</code></td>
1001 <td>Create a new issue. Returns <code>{"{number, url}"}</code>.</td>
1002 </tr>
1003 <tr>
1004 <td><code>gluecron_comment_issue</code></td>
1005 <td>Add a comment to an existing issue. Returns <code>{"{commentId}"}</code>.</td>
1006 </tr>
1007 <tr>
1008 <td><code>gluecron_close_issue</code></td>
1009 <td>Close an open issue (idempotent). Returns <code>{"{state}"}</code>.</td>
1010 </tr>
1011 <tr>
1012 <td><code>gluecron_reopen_issue</code></td>
1013 <td>Reopen a closed issue (idempotent). Returns <code>{"{state}"}</code>.</td>
1014 </tr>
1015 <tr>
1016 <td><code>gluecron_create_pr</code></td>
1017 <td>Open a new pull request. Returns <code>{"{number, url}"}</code>.</td>
1018 </tr>
1019 <tr>
1020 <td><code>gluecron_get_pr</code></td>
1021 <td>Fetch full PR detail (title, body, state, branches, draft, author).</td>
1022 </tr>
1023 <tr>
1024 <td><code>gluecron_list_prs</code></td>
1025 <td>List PRs filtered by state (open|closed|merged|all). Returns up to 50 rows.</td>
1026 </tr>
1027 <tr>
1028 <td><code>gluecron_comment_pr</code></td>
1029 <td>Add a comment to a pull request. Returns <code>{"{commentId}"}</code>.</td>
1030 </tr>
1031 <tr>
1032 <td><code>gluecron_merge_pr</code></td>
1033 <td>Merge an open PR. Enforces gate checks, branch-protection rules, and the pre-merge risk score. Returns <code>{"{merged, sha?, reason?, riskScore?}"}</code>.</td>
1034 </tr>
1035 <tr>
1036 <td><code>gluecron_close_pr</code></td>
1037 <td>Close a PR without merging (idempotent). Returns <code>{"{state}"}</code>.</td>
1038 </tr>
1039 </tbody>
1040 </table>
1041
1042 <h2 id="example-session">Example Claude session</h2>
1043 <pre><code>{`# Open an issue
1044User: "Open an issue on my-org/api titled 'Rate limiter not resetting'"
1045Claude: [calls gluecron_create_issue]
1046 Created issue #42: /my-org/api/issues/42
1047
1048# Create a PR from a feature branch
1049User: "Open a PR from feat/rate-limit-fix into main"
1050Claude: [calls gluecron_create_pr with head_branch=feat/rate-limit-fix]
1051 Opened PR #17: /my-org/api/pulls/17
1052
1053# Merge after review
1054User: "Merge PR #17 — I've reviewed it"
1055Claude: [calls gluecron_merge_pr with number=17]
1056 Merged PR #17. SHA: a3f9c2d`}</code></pre>
1057
1058 <h2 id="gate-enforcement">Gate enforcement on merge</h2>
1059 <p>
1060 <code>gluecron_merge_pr</code> enforces the same checks as the
1061 web UI merge button:
1062 </p>
1063 <ul>
1064 <li>PR must not be a draft.</li>
1065 <li>Head branch ref must resolve.</li>
1066 <li>
1067 GateTest hard gates must pass (secrets scan, dependency
1068 advisories, policy violations).
1069 </li>
1070 <li>Branch-protection rules must be satisfied.</li>
1071 <li>
1072 If the pre-merge risk score is <em>critical</em>, the tool
1073 returns <code>merged: false</code> with a prompt to re-call
1074 with <code>confirm_high_risk: true</code>.
1075 </li>
1076 </ul>
1077
1078 <h2 id="dxt">One-click Claude Desktop extension</h2>
1079 <p>
1080 Download the <code>.dxt</code> extension at{" "}
1081 <a href="/gluecron.dxt">/gluecron.dxt</a>. Open it in Claude
1082 Desktop — it installs the MCP config automatically. No JSON
1083 editing needed.
1084 </p>
1085
1086 <PageNav prev={prev} next={next} />
1087 <EditLink path="src/routes/docs.tsx" />
1088 </div>
1089 </div>
1090 </Layout>
1091 );
1092});
1093
1094// ─── API REFERENCE (/docs/api) ────────────────────────────────────────────
1095
1096docs.get("/docs/api", (c) => {
1097 const user = c.get("user");
1098 const idx = NAV_SECTIONS.findIndex((s) => s.href === "/docs/api");
1099 const prev = NAV_SECTIONS[idx - 1];
1100 const next = NAV_SECTIONS[idx + 1];
1101
1102 return c.html(
1103 <Layout title="API reference — Docs — gluecron" user={user} description="Gluecron REST API reference — authentication, repos, issues, PRs, webhooks, rate limits.">
1104 <style dangerouslySetInnerHTML={{ __html: docsStyles }} />
1105 <div class="docs-wrap">
1106 <Sidebar current="/docs/api" />
1107 <div class="docs-content">
1108 <h1>API reference</h1>
1109 <p class="docs-page-sub">
1110 Gluecron exposes a REST API at <code>/api/v2/</code>. All
1111 endpoints return JSON. Authenticated endpoints require a{" "}
1112 <code>Bearer</code> token in the <code>Authorization</code> header.
1113 </p>
1114
1115 <h2 id="auth">Authentication</h2>
1116 <p>
1117 Generate a personal access token at{" "}
1118 <a href="/settings/tokens">/settings/tokens</a>. Tokens start
1119 with <code>glc_</code> and are hashed (SHA-256) before storage —
1120 the plaintext is shown exactly once.
1121 </p>
1122 <pre><code>{`curl -H "Authorization: Bearer glc_your_token_here" \\
1123 https://gluecron.com/api/v2/repos`}</code></pre>
1124 <p>
1125 Tokens can also authenticate <code>git</code> over HTTPS —
1126 use the token as the password with any username.
1127 </p>
1128
1129 <h2 id="rate-limits">Rate limits</h2>
1130 <table>
1131 <thead>
1132 <tr>
1133 <th>Surface</th>
1134 <th>Limit</th>
1135 <th>Window</th>
1136 </tr>
1137 </thead>
1138 <tbody>
1139 <tr>
1140 <td>API (anonymous)</td>
1141 <td>1 000 requests</td>
1142 <td>60 seconds</td>
1143 </tr>
1144 <tr>
1145 <td>API (authenticated)</td>
1146 <td>4 000 requests</td>
1147 <td>60 seconds</td>
1148 </tr>
1149 <tr>
1150 <td>AI commit message</td>
1151 <td>60 requests</td>
1152 <td>60 seconds per token</td>
1153 </tr>
1154 <tr>
1155 <td>Login / register</td>
1156 <td>10–20 requests</td>
1157 <td>60 seconds per IP</td>
1158 </tr>
1159 </tbody>
1160 </table>
1161 <p>
1162 When a limit is exceeded the server returns{" "}
1163 <code>429 Too Many Requests</code> with a{" "}
1164 <code>Retry-After</code> header.
1165 </p>
1166
1167 <h2 id="repos">Repositories</h2>
1168
1169 <h3>List your repos</h3>
1170 <pre><code>{`GET /api/v2/repos
1171
1172Response 200:
1173[
1174 {
1175 "id": "...",
1176 "name": "my-project",
1177 "fullName": "you/my-project",
1178 "description": "...",
1179 "isPrivate": false,
1180 "defaultBranch": "main",
1181 "starCount": 12,
1182 "forkCount": 3,
1183 "issueCount": 5,
1184 "createdAt": "2025-01-15T10:00:00Z",
1185 "updatedAt": "2025-06-01T14:30:00Z"
1186 }
1187]`}</code></pre>
1188
1189 <h3>Create a repo</h3>
1190 <pre><code>{`POST /api/v2/repos
1191Content-Type: application/json
1192
1193{
1194 "name": "my-project",
1195 "description": "Optional description",
1196 "isPrivate": false,
1197 "initReadme": true,
1198 "defaultBranch": "main"
1199}
1200
1201Response 201:
1202{ "id": "...", "name": "my-project", "fullName": "you/my-project" }`}</code></pre>
1203
1204 <h3>Get a repo</h3>
1205 <pre><code>{`GET /api/v2/repos/:owner/:repo
1206
1207Response 200:
1208{ /* same shape as list item */ }`}</code></pre>
1209
1210 <h3>Delete a repo</h3>
1211 <pre><code>{`DELETE /api/v2/repos/:owner/:repo
1212
1213Response 204 No Content`}</code></pre>
1214
1215 <h2 id="issues">Issues</h2>
1216
1217 <h3>List issues</h3>
1218 <pre><code>{`GET /api/v2/repos/:owner/:repo/issues?state=open&limit=25
1219
1220Response 200:
1221[
1222 {
1223 "number": 42,
1224 "title": "Bug in auth flow",
1225 "body": "...",
1226 "state": "open",
1227 "author": "kit",
1228 "createdAt": "2025-05-20T08:00:00Z"
1229 }
1230]`}</code></pre>
1231
1232 <h3>Create an issue</h3>
1233 <pre><code>{`POST /api/v2/repos/:owner/:repo/issues
1234Content-Type: application/json
1235
1236{
1237 "title": "Bug in auth flow",
1238 "body": "Steps to reproduce: ..."
1239}
1240
1241Response 201:
1242{ "number": 42, "url": "/you/my-project/issues/42" }`}</code></pre>
1243
1244 <h3>Close / reopen an issue</h3>
1245 <pre><code>{`PATCH /api/v2/repos/:owner/:repo/issues/:number
1246Content-Type: application/json
1247
1248{ "state": "closed" } // or "open"
1249
1250Response 200:
1251{ "state": "closed" }`}</code></pre>
1252
1253 <h2 id="pull-requests">Pull requests</h2>
1254
1255 <h3>List PRs</h3>
1256 <pre><code>{`GET /api/v2/repos/:owner/:repo/pulls?state=open
1257
1258Response 200:
1259[
1260 {
1261 "number": 17,
1262 "title": "Add rate limiter",
1263 "state": "open",
1264 "baseBranch": "main",
1265 "headBranch": "feat/rate-limit",
1266 "isDraft": false,
1267 "author": "kit",
1268 "createdAt": "2025-06-01T09:00:00Z"
1269 }
1270]`}</code></pre>
1271
1272 <h3>Create a PR</h3>
1273 <pre><code>{`POST /api/v2/repos/:owner/:repo/pulls
1274Content-Type: application/json
1275
1276{
1277 "title": "Add rate limiter",
1278 "body": "## Summary\n- Implements token-bucket rate limiter\n\n## Test plan\n- [ ] Run bun test",
1279 "headBranch": "feat/rate-limit",
1280 "baseBranch": "main"
1281}
1282
1283Response 201:
1284{ "number": 17, "url": "/you/my-project/pulls/17" }`}</code></pre>
1285
1286 <h3>Merge a PR</h3>
1287 <pre><code>{`POST /api/v2/repos/:owner/:repo/pulls/:number/merge
1288
1289Response 200:
1290{ "merged": true, "sha": "a3f9c2d..." }
1291
1292// If gate checks fail:
1293Response 422:
1294{ "merged": false, "reason": "GateTest: leaked secret in src/config.ts" }`}</code></pre>
1295
1296 <h2 id="webhooks">Webhooks</h2>
1297
1298 <h3>Register a webhook</h3>
1299 <pre><code>{`POST /api/v2/repos/:owner/:repo/webhooks
1300Content-Type: application/json
1301
1302{
1303 "url": "https://example.com/hooks/gluecron",
1304 "secret": "mysecret",
1305 "events": ["push", "pull_request", "issues", "star"]
1306}
1307
1308Response 201:
1309{ "id": "...", "url": "https://example.com/hooks/gluecron" }`}</code></pre>
1310
1311 <h3>Webhook payload shape</h3>
1312 <pre><code>{`// push event
1313{
1314 "event": "push",
1315 "repo": { "owner": "you", "name": "my-project" },
1316 "ref": "refs/heads/main",
1317 "before": "<sha>",
1318 "after": "<sha>",
1319 "commits": [
1320 {
1321 "id": "<sha>",
1322 "message": "feat: add rate limiter",
1323 "author": { "name": "Kit", "email": "kit@example.com" }
1324 }
1325 ],
1326 "sender": { "username": "kit" }
1327}
1328
1329// Signature header (verify with HMAC-SHA256 over raw body):
1330X-Gluecron-Signature: sha256=<hex>`}</code></pre>
1331
1332 <h2 id="ai-endpoints">AI endpoints</h2>
1333
1334 <h3>Generate a commit message</h3>
1335 <pre><code>{`POST /api/v2/ai/commit-message
1336Content-Type: application/json
1337
1338{ "diff": "<git diff output>" }
1339
1340Response 200:
1341{
1342 "message": "feat(auth): add token-bucket rate limiter\\n\\nPrevents brute-force login attempts..."
1343}`}</code></pre>
1344
1345 <h3>Semantic code search</h3>
1346 <pre><code>{`GET /api/v2/repos/:owner/:repo/semantic-search?q=password+hashing&limit=10
1347
1348Response 200:
1349[
1350 {
1351 "path": "src/lib/auth.ts",
1352 "startLine": 12,
1353 "endLine": 28,
1354 "snippet": "...",
1355 "score": 0.94
1356 }
1357]`}</code></pre>
1358
1359 <PageNav prev={prev} next={next} />
1360 <EditLink path="src/routes/docs.tsx" />
1361 </div>
1362 </div>
1363 </Layout>
1364 );
1365});
1366
1367// ─── AGENT PUBLISHING (/docs/agents) ─────────────────────────────────────
1368
1369docs.get("/docs/agents", (c) => {
1370 const user = c.get("user");
1371 const idx = NAV_SECTIONS.findIndex((s) => s.href === "/docs/agents");
1372 const prev = NAV_SECTIONS[idx - 1];
1373 const next = NAV_SECTIONS[idx + 1];
1374
1375 return c.html(
1376 <Layout title="Agent publishing — Docs — gluecron" user={user} description="Write, test, and publish an AI agent to the Gluecron marketplace. Earn 70% of every sale.">
1377 <style dangerouslySetInnerHTML={{ __html: docsStyles }} />
1378 <div class="docs-wrap">
1379 <Sidebar current="/docs/agents" />
1380 <div class="docs-content">
1381 <h1>Agent publishing guide</h1>
1382 <p class="docs-page-sub">
1383 Build an AI agent that drives Gluecron repos, publish it to the
1384 marketplace, and earn <strong>70% revenue share</strong> on every
1385 sale. Gluecron keeps 30% to cover infrastructure and fraud
1386 prevention.
1387 </p>
1388
1389 <h2 id="overview">What is an agent?</h2>
1390 <p>
1391 A Gluecron agent is a JSON manifest that describes an AI tool-use
1392 loop. It declares:
1393 </p>
1394 <ul>
1395 <li>A name, description, and icon.</li>
1396 <li>
1397 Which MCP tools the agent is allowed to call (scoped to the
1398 token provided by the buyer).
1399 </li>
1400 <li>
1401 A system prompt that instructs Claude how to use those tools to
1402 accomplish a goal.
1403 </li>
1404 <li>
1405 Optional input fields the user fills in before the agent runs.
1406 </li>
1407 </ul>
1408 <p>
1409 Example agents: "Daily standup writer", "Auto-triage new issues",
1410 "Dependency upgrade PR opener", "Release notes drafter".
1411 </p>
1412
1413 <h2 id="write">1. Write your agent</h2>
1414 <p>
1415 Create an <code>agent.json</code> in any repo:
1416 </p>
1417 <pre><code>{`{
1418 "name": "standup-writer",
1419 "displayName": "Daily Standup Writer",
1420 "description": "Reads yesterday's pushes and open PRs, then writes a concise standup summary.",
1421 "icon": "https://example.com/standup-icon.png",
1422 "version": "1.0.0",
1423 "tools": [
1424 "gluecron_repo_list_issues",
1425 "gluecron_list_prs",
1426 "gluecron_repo_read_file"
1427 ],
1428 "inputs": [
1429 {
1430 "key": "owner",
1431 "label": "GitHub/Gluecron owner",
1432 "type": "string",
1433 "required": true
1434 },
1435 {
1436 "key": "repo",
1437 "label": "Repository",
1438 "type": "string",
1439 "required": true
1440 }
1441 ],
1442 "systemPrompt": "You are a standup writer. Given the open PRs and recent issues on the target repo, write a concise standup update in bullet-point form: what was done yesterday, what is planned today, and any blockers.",
1443 "pricing": {
1444 "model": "per_run",
1445 "price_usd": 0.10
1446 }
1447}`}</code></pre>
1448
1449 <h2 id="test">2. Test locally</h2>
1450 <p>
1451 Use the Gluecron CLI to run your agent against a repo before
1452 publishing:
1453 </p>
1454 <pre><code>{`# Install the CLI
1455bun add -g @gluecron/cli
1456
1457# Run the agent locally (no marketplace, no billing)
1458gluecron agent run ./agent.json \\
1459 --input owner=my-org \\
1460 --input repo=api \\
1461 --token glc_your_dev_token`}</code></pre>
1462 <p>
1463 The CLI streams each tool call and its result to stdout so you
1464 can see exactly what Claude is doing. Errors in the system prompt
1465 or missing tool permissions surface here before any buyer sees
1466 them.
1467 </p>
1468
1469 <h3>Testing checklist</h3>
1470 <ul>
1471 <li>
1472 Run against a repo with open PRs and issues to verify the
1473 agent handles non-empty state.
1474 </li>
1475 <li>Run against a brand-new empty repo (edge case).</li>
1476 <li>
1477 Confirm the agent only calls the tools it declared in{" "}
1478 <code>"tools"</code> — the runtime rejects undeclared tool
1479 calls.
1480 </li>
1481 <li>
1482 Test with a read-only token to verify the agent degrades
1483 gracefully when write tools are unavailable.
1484 </li>
1485 </ul>
1486
1487 <h2 id="publish">3. Publish to the marketplace</h2>
1488 <p>
1489 Once you are happy with your agent, push <code>agent.json</code>{" "}
1490 to a public repo and submit via the marketplace form:
1491 </p>
1492 <ol>
1493 <li>
1494 Visit{" "}
1495 <a href="/marketplace/agents/new">/marketplace/agents/new</a>.
1496 </li>
1497 <li>
1498 Paste the URL of your public repo (e.g.{" "}
1499 <code>https://gluecron.com/you/standup-writer</code>).
1500 </li>
1501 <li>
1502 Set a price — per-run, monthly subscription, or free.
1503 Minimum per-run price is <strong>$0.01</strong>.
1504 </li>
1505 <li>
1506 Submit for review. The Gluecron team checks that the agent
1507 manifest is valid, the system prompt doesn't exfiltrate data,
1508 and the declared tool scope is appropriate. Reviews typically
1509 complete within <strong>48 hours</strong>.
1510 </li>
1511 </ol>
1512
1513 <h2 id="revenue">Revenue share</h2>
1514 <table>
1515 <thead>
1516 <tr>
1517 <th>Party</th>
1518 <th>Share</th>
1519 </tr>
1520 </thead>
1521 <tbody>
1522 <tr>
1523 <td>Agent author (you)</td>
1524 <td><strong>70%</strong></td>
1525 </tr>
1526 <tr>
1527 <td>Gluecron</td>
1528 <td>30% (infrastructure + payment processing + fraud prevention)</td>
1529 </tr>
1530 </tbody>
1531 </table>
1532 <p>
1533 Payouts are processed monthly via Stripe to the bank account or
1534 PayPal address on file in your{" "}
1535 <a href="/settings">account settings</a>. Minimum payout
1536 threshold is <strong>$10</strong>.
1537 </p>
1538
1539 <h2 id="versioning">Versioning and updates</h2>
1540 <p>
1541 Bump <code>"version"</code> in <code>agent.json</code>, push,
1542 and submit an update from the marketplace dashboard. Buyers on
1543 a subscription get the new version automatically on their next
1544 run. Per-run buyers always get the latest approved version.
1545 </p>
1546 <p>
1547 Breaking changes (removing tools, changing input keys) require a
1548 major version bump and a migration note in the changelog field.
1549 </p>
1550
1551 <h2 id="guidelines">Marketplace guidelines</h2>
1552 <ul>
1553 <li>
1554 Agents must not exfiltrate repo content to external URLs not
1555 disclosed in the description.
1556 </li>
1557 <li>
1558 Agents must not call write tools without clear disclosure in
1559 the description (e.g. "This agent opens PRs automatically").
1560 </li>
1561 <li>
1562 System prompts are public — do not embed secrets or proprietary
1563 data in them.
1564 </li>
1565 <li>
1566 Agents that repeatedly fail (error rate &gt; 20% over 7 days)
1567 are automatically delisted pending author review.
1568 </li>
1569 </ul>
1570
1571 <h2 id="example-agent">Full example: dependency upgrade agent</h2>
1572 <pre><code>{`{
1573 "name": "dep-upgrader",
1574 "displayName": "Dependency Upgrade PR",
1575 "description": "Checks for outdated npm/bun dependencies and opens a PR with the bumped package.json.",
1576 "version": "1.0.0",
1577 "tools": [
1578 "gluecron_repo_read_file",
1579 "gluecron_create_pr",
1580 "gluecron_comment_pr"
1581 ],
1582 "inputs": [
1583 { "key": "owner", "label": "Owner", "type": "string", "required": true },
1584 { "key": "repo", "label": "Repo", "type": "string", "required": true }
1585 ],
1586 "systemPrompt": "You are a dependency upgrade bot. 1) Read package.json from the repo. 2) Identify any dependencies pinned to a major version older than the current latest on npm. 3) Produce an updated package.json with the bumped versions. 4) Open a PR titled 'chore(deps): bump outdated dependencies' with the updated file as the diff. 5) Comment on the PR with a table of what changed.",
1587 "pricing": {
1588 "model": "per_run",
1589 "price_usd": 0.25
1590 }
1591}`}</code></pre>
1592
1593 <PageNav prev={prev} next={next} />
1594 <EditLink path="src/routes/docs.tsx" />
1595 </div>
1596 </div>
1597 </Layout>
1598 );
1599});
1600
1601export default docs;