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