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

connect.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.

connect.tsxBlame530 lines · 2 contributors
f5b9ef5Claude1/**
2 * Connect Page — /connect/claude onboarding for Claude Code sessions.
3 *
4 * Routes
5 * GET /connect/claude — Beautiful onboarding page. If the user is logged
6 * in, their username is pre-filled in code snippets.
7 *
8 * NOTE: The existing /connect/claude route in connect-claude.tsx is
9 * auth-gated (requireAuth). This file intentionally lives at the same path
10 * but is mounted AFTER connect-claude.tsx in app.tsx — it will never win the
11 * route match while connect-claude.tsx is registered first. That's by design:
12 * logged-in users hit the rich interactive page; unauthenticated visitors
13 * should instead be redirected to login by connect-claude.tsx's requireAuth.
14 *
15 * This file provides a standalone public-friendly version of the onboarding
16 * guide at a slightly different path to avoid any conflict:
17 * GET /connect/claude-guide — public, no auth required
18 *
19 * Both views reference the same instructions; the guide version simply shows
20 * USERNAME as a placeholder when no session is present.
21 */
22
23import { Hono } from "hono";
24import { Layout } from "../views/layout";
25import { softAuth } from "../middleware/auth";
26import type { AuthEnv } from "../middleware/auth";
27import { config } from "../lib/config";
28
29const connectRoutes = new Hono<AuthEnv>();
30
31// Apply softAuth so we can read the user (if any) for pre-filling snippets.
32connectRoutes.use("/connect/claude-guide", softAuth);
33
34// ─── Scoped CSS ─────────────────────────────────────────────────────────────
35const styles = `
36 /* All classes prefixed with .cg- (claude-guide) to avoid bleed */
37 .cg-wrap {
38 max-width: 860px;
39 margin: 0 auto;
40 padding: var(--space-5) var(--space-4) var(--space-6);
41 }
42
43 /* ─── Hero ─── */
44 .cg-hero {
45 position: relative;
46 margin-bottom: var(--space-6);
47 padding: var(--space-5) var(--space-6);
48 background: var(--bg-elevated);
49 border: 1px solid var(--border);
50 border-radius: 18px;
51 overflow: hidden;
52 }
53 .cg-hero::before {
54 content: '';
55 position: absolute;
56 top: 0; left: 0; right: 0;
57 height: 2px;
6fd5915Claude58 background: linear-gradient(90deg, transparent 0%, #5b6ee8 30%, #5f8fa0 70%, transparent 100%);
f5b9ef5Claude59 pointer-events: none;
60 }
61 .cg-hero-orb {
62 position: absolute;
63 inset: -20% -5% auto auto;
64 width: 420px; height: 420px;
6fd5915Claude65 background: radial-gradient(circle, rgba(91,110,232,0.20), rgba(95,143,160,0.10) 45%, transparent 70%);
f5b9ef5Claude66 filter: blur(80px);
67 opacity: 0.6;
68 pointer-events: none;
69 animation: cgOrbPulse 18s ease-in-out infinite;
70 }
71 @keyframes cgOrbPulse {
72 0%, 100% { transform: scale(1) translate(0, 0); opacity: 0.5; }
73 50% { transform: scale(1.1) translate(-10px, 6px); opacity: 0.8; }
74 }
75 @media (prefers-reduced-motion: reduce) {
76 .cg-hero-orb { animation: none; }
77 }
78 .cg-hero-inner {
79 position: relative;
80 z-index: 1;
81 max-width: 640px;
82 }
83 .cg-hero-tag {
84 display: inline-block;
85 padding: 3px 10px;
86 border-radius: 999px;
6fd5915Claude87 border: 1px solid rgba(91,110,232,0.35);
88 background: rgba(91,110,232,0.10);
f5b9ef5Claude89 color: #c5b3ff;
90 font-size: 12px;
91 font-weight: 600;
92 letter-spacing: 0.04em;
93 text-transform: uppercase;
94 margin-bottom: var(--space-3);
95 }
96 .cg-hero-title {
97 font-size: clamp(28px, 4.5vw, 44px);
98 font-family: var(--font-display);
99 font-weight: 800;
100 letter-spacing: -0.03em;
101 line-height: 1.06;
102 margin: 0 0 var(--space-3);
103 color: var(--text-strong);
104 }
105 .cg-hero-title .gradient {
6fd5915Claude106 background-image: linear-gradient(135deg, #5b6ee8 0%, #5b6ee8 50%, #5f8fa0 100%);
f5b9ef5Claude107 -webkit-background-clip: text;
108 background-clip: text;
109 -webkit-text-fill-color: transparent;
110 color: transparent;
111 }
112 .cg-hero-sub {
113 font-size: 15px;
114 color: var(--text-muted);
115 margin: 0 0 var(--space-4);
116 line-height: 1.55;
117 }
118 .cg-hero-cta {
119 display: inline-flex;
120 align-items: center;
121 gap: 8px;
122 padding: 11px 20px;
123 border-radius: 10px;
6fd5915Claude124 border: 1px solid rgba(91,110,232,0.45);
125 background: linear-gradient(135deg, rgba(91,110,232,0.18), rgba(95,143,160,0.12));
f5b9ef5Claude126 color: var(--text-strong);
127 font-size: 14px;
128 font-weight: 600;
129 text-decoration: none;
130 transition: border-color 150ms ease, background 150ms ease, transform 150ms ease;
131 }
132 .cg-hero-cta:hover {
6fd5915Claude133 border-color: rgba(91,110,232,0.65);
f5b9ef5Claude134 transform: translateY(-1px);
135 text-decoration: none;
136 }
137
138 /* ─── Steps ─── */
139 .cg-steps {
140 display: flex;
141 flex-direction: column;
142 gap: var(--space-4);
143 margin-bottom: var(--space-6);
144 }
145 .cg-step {
146 position: relative;
147 background: var(--bg-elevated);
148 border: 1px solid var(--border);
149 border-radius: 14px;
150 overflow: hidden;
151 }
152 .cg-step-head {
153 display: flex;
154 align-items: center;
155 gap: 14px;
156 padding: var(--space-4) var(--space-5);
157 border-bottom: 1px solid var(--border-subtle);
158 }
159 .cg-step-num {
160 display: inline-flex;
161 align-items: center;
162 justify-content: center;
163 flex-shrink: 0;
164 width: 30px; height: 30px;
165 border-radius: 50%;
6fd5915Claude166 background: linear-gradient(135deg, rgba(91,110,232,0.22), rgba(95,143,160,0.16));
167 border: 1px solid rgba(91,110,232,0.40);
f5b9ef5Claude168 color: #c5b3ff;
169 font-family: var(--font-display);
170 font-weight: 700;
171 font-size: 14px;
172 }
173 .cg-step-title {
174 font-family: var(--font-display);
175 font-size: 16px;
176 font-weight: 700;
177 letter-spacing: -0.01em;
178 color: var(--text-strong);
179 margin: 0;
180 }
181 .cg-step-body {
182 padding: var(--space-4) var(--space-5);
183 }
184 .cg-step-desc {
185 font-size: 14px;
186 color: var(--text-muted);
187 margin: 0 0 var(--space-3);
188 line-height: 1.55;
189 }
190
191 /* ─── Code blocks ─── */
192 .cg-code-wrap {
193 position: relative;
194 margin-bottom: var(--space-3);
195 }
196 .cg-code {
197 display: block;
198 background: var(--bg-tertiary, #0d1018);
199 border: 1px solid var(--border-subtle);
200 border-radius: 8px;
201 padding: 14px 16px;
202 font-family: var(--font-mono);
203 font-size: 12.5px;
204 color: var(--text-strong);
205 overflow-x: auto;
206 white-space: pre;
207 line-height: 1.6;
208 margin: 0;
209 }
210 .cg-copy-btn {
211 appearance: none;
212 position: absolute;
213 top: 8px; right: 8px;
214 border: 1px solid var(--border-subtle);
215 background: var(--bg-elevated);
216 color: var(--text-muted);
217 padding: 4px 10px;
218 border-radius: 6px;
219 font-size: 11.5px;
220 font-family: inherit;
221 cursor: pointer;
222 transition: color 120ms ease, border-color 120ms ease;
223 }
224 .cg-copy-btn:hover {
225 color: var(--text-strong);
226 border-color: var(--border-strong);
227 }
228 .cg-link {
229 color: var(--accent);
230 text-decoration: none;
231 font-weight: 500;
232 }
233 .cg-link:hover { text-decoration: underline; }
234
235 /* ─── Why Gluecron section ─── */
236 .cg-why {
237 background: var(--bg-elevated);
238 border: 1px solid var(--border);
239 border-radius: 14px;
240 padding: var(--space-5);
241 margin-bottom: var(--space-4);
242 }
243 .cg-why-title {
244 font-family: var(--font-display);
245 font-size: 18px;
246 font-weight: 800;
247 letter-spacing: -0.015em;
248 color: var(--text-strong);
249 margin: 0 0 var(--space-4);
250 }
251 .cg-why-grid {
252 display: grid;
253 grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
254 gap: var(--space-3);
255 }
256 .cg-why-card {
257 padding: var(--space-3) var(--space-4);
258 background: var(--bg-secondary);
259 border: 1px solid var(--border-subtle);
260 border-radius: 10px;
261 display: flex;
262 flex-direction: column;
263 gap: 6px;
264 }
265 .cg-why-card-icon {
266 font-size: 20px;
267 line-height: 1;
268 }
269 .cg-why-card-title {
270 font-weight: 700;
271 font-size: 13.5px;
272 color: var(--text-strong);
273 margin: 0;
274 }
275 .cg-why-card-desc {
276 font-size: 12.5px;
277 color: var(--text-muted);
278 margin: 0;
279 line-height: 1.5;
280 }
281
282 @media (max-width: 600px) {
283 .cg-hero { padding: var(--space-4); }
284 .cg-step-body, .cg-step-head { padding: var(--space-3) var(--space-4); }
285 .cg-why-grid { grid-template-columns: 1fr; }
286 }
287`;
288
289// ─── Client-side copy + snippet fill ──────────────────────────────────────
290function clientScript(username: string) {
291 return `
292(function() {
293 // Copy-to-clipboard
294 document.querySelectorAll('[data-cg-copy]').forEach(function(btn) {
295 btn.addEventListener('click', function() {
296 var targetId = btn.getAttribute('data-cg-copy');
297 var el = document.getElementById(targetId);
298 if (!el) return;
299 var text = el.textContent || '';
300 if (navigator.clipboard && navigator.clipboard.writeText) {
301 navigator.clipboard.writeText(text).then(function() {
302 var prev = btn.textContent;
303 btn.textContent = 'Copied!';
304 setTimeout(function() { btn.textContent = prev; }, 1500);
305 }).catch(function() {});
306 }
307 });
308 });
309})();
310`;
311}
312
313// ─── GET /connect/claude-guide ─────────────────────────────────────────────
314
315connectRoutes.get("/connect/claude-guide", async (c) => {
316 const user = c.get("user") ?? null;
317 const username = user?.username ?? "USERNAME";
318 const host = config.appBaseUrl || "https://gluecron.com";
319
320 const remoteSnippet = `git remote add gluecron ${host}/${username}/REPO.git`;
321 const mcpJsonSnippet = JSON.stringify(
322 {
323 mcpServers: {
324 gluecron: {
78ede81ccanty labs325 type: "http",
f5b9ef5Claude326 url: `${host}/mcp`,
327 headers: {
328 Authorization: "Bearer YOUR_TOKEN_HERE",
329 },
330 },
331 },
332 },
333 null,
334 2
335 );
336
337 return c.html(
338 <Layout
339 title="Connect Claude Code to Gluecron"
340 user={user}
341 description="Push from any Claude Code session in 60 seconds. Zero config."
342 >
343 <style dangerouslySetInnerHTML={{ __html: styles }} />
344 <div class="cg-wrap">
345
346 {/* ─── Hero ─── */}
347 <section class="cg-hero">
348 <div class="cg-hero-orb" aria-hidden="true" />
349 <div class="cg-hero-inner">
350 <span class="cg-hero-tag">Claude Code Integration</span>
351 <h1 class="cg-hero-title">
352 Connect <span class="gradient">Claude Code</span> to Gluecron
353 </h1>
354 <p class="cg-hero-sub">
355 Push from any Claude session in 60 seconds. Your repos get
356 lightning-fast CI, AI review on every PR, and full MCP tool
357 access — no extra configuration needed.
358 </p>
359 <a href="/settings/tokens" class="cg-hero-cta">
360 Create an access token →
361 </a>
362 </div>
363 </section>
364
365 {/* ─── Steps ─── */}
366 <div class="cg-steps">
367
368 {/* Step 1 */}
369 <section class="cg-step">
370 <div class="cg-step-head">
371 <span class="cg-step-num">1</span>
372 <h2 class="cg-step-title">Create an access token</h2>
373 </div>
374 <div class="cg-step-body">
375 <p class="cg-step-desc">
376 Head to{" "}
377 <a href="/settings/tokens" class="cg-link">
378 /settings/tokens
379 </a>{" "}
380 and generate a new personal access token with{" "}
381 <code>repo</code> scope (or <code>admin,repo,user</code> for
382 full MCP write access). Copy the token — it's shown once.
383 </p>
384 </div>
385 </section>
386
387 {/* Step 2 */}
388 <section class="cg-step">
389 <div class="cg-step-head">
390 <span class="cg-step-num">2</span>
391 <h2 class="cg-step-title">Add the remote</h2>
392 </div>
393 <div class="cg-step-body">
394 <p class="cg-step-desc">
395 In your project directory, add Gluecron as a git remote.
396 Replace <code>REPO</code> with your repository name — it will
397 be created automatically on the first push.
398 </p>
399 <div class="cg-code-wrap">
400 <pre id="cg-remote" class="cg-code">{remoteSnippet}</pre>
401 <button type="button" class="cg-copy-btn" data-cg-copy="cg-remote">
402 Copy
403 </button>
404 </div>
405 </div>
406 </section>
407
408 {/* Step 3 */}
409 <section class="cg-step">
410 <div class="cg-step-head">
411 <span class="cg-step-num">3</span>
78ede81ccanty labs412 <h2 class="cg-step-title">Configure MCP in .mcp.json</h2>
f5b9ef5Claude413 </div>
414 <div class="cg-step-body">
415 <p class="cg-step-desc">
416 Add the Gluecron MCP server so Claude Code can open issues, file
78ede81ccanty labs417 PRs, and query your repos directly. Paste the snippet below into
418 an <code>.mcp.json</code> file at your project root (the file
419 Claude Code actually loads MCP servers from, on both the CLI and
420 web/cloud sessions), replacing{" "}
f5b9ef5Claude421 <code>YOUR_TOKEN_HERE</code> with the token from Step 1.
422 </p>
423 <div class="cg-code-wrap">
424 <pre id="cg-mcp-json" class="cg-code">{mcpJsonSnippet}</pre>
425 <button type="button" class="cg-copy-btn" data-cg-copy="cg-mcp-json">
426 Copy
427 </button>
428 </div>
429 </div>
430 </section>
431
432 {/* Step 4 */}
433 <section class="cg-step">
434 <div class="cg-step-head">
435 <span class="cg-step-num">4</span>
436 <h2 class="cg-step-title">Push</h2>
437 </div>
438 <div class="cg-step-body">
439 <p class="cg-step-desc">
a2b3e99Claude440 Push your branch. CI gates fire immediately and a draft PR is
441 auto-created if you're pushing a feature branch.
f5b9ef5Claude442 </p>
443 <div class="cg-code-wrap">
444 <pre id="cg-push" class="cg-code">git push gluecron main</pre>
445 <button type="button" class="cg-copy-btn" data-cg-copy="cg-push">
446 Copy
447 </button>
448 </div>
a2b3e99Claude449 <p class="cg-step-desc" style="margin-top: var(--space-3);">
450 For feature branches, auto-create a draft PR in one call:
451 </p>
452 <div class="cg-code-wrap">
453 <pre id="cg-auto-pr" class="cg-code">{`curl -X POST ${host}/api/claude/push \\
454 -H "Authorization: Bearer $GLUECRON_TOKEN" \\
455 -H "Content-Type: application/json" \\
456 -d '{"repoName":"REPO","branch":"my-feature"}'`}</pre>
457 <button type="button" class="cg-copy-btn" data-cg-copy="cg-auto-pr">
458 Copy
459 </button>
460 </div>
f5b9ef5Claude461 <p class="cg-step-desc" style="margin-top: var(--space-3); margin-bottom: 0;">
a2b3e99Claude462 The response includes a <code>pushWatchUrl</code> — open it to watch gates
463 and deployment live. Claude AI review lands within seconds.
f5b9ef5Claude464 </p>
465 </div>
466 </section>
467 </div>
468
469 {/* ─── Why Gluecron ─── */}
470 <section class="cg-why">
471 <h2 class="cg-why-title">Why Gluecron?</h2>
472 <div class="cg-why-grid">
473 <div class="cg-why-card">
474 <span class="cg-why-card-icon" aria-hidden="true">⚡</span>
475 <h3 class="cg-why-card-title">Lightning-fast CI</h3>
476 <p class="cg-why-card-desc">
477 Gate runs fire in parallel the moment code lands. Type-check,
478 lint, secret-scan, and test in under 30 seconds on most repos.
479 </p>
480 </div>
481 <div class="cg-why-card">
482 <span class="cg-why-card-icon" aria-hidden="true">🤖</span>
483 <h3 class="cg-why-card-title">AI review on every PR</h3>
484 <p class="cg-why-card-desc">
485 Claude reads the diff, flags bugs, suggests improvements, and
486 auto-approves or blocks merge — before a human even looks.
487 </p>
488 </div>
489 <div class="cg-why-card">
490 <span class="cg-why-card-icon" aria-hidden="true">🔧</span>
491 <h3 class="cg-why-card-title">15 MCP tools built-in</h3>
492 <p class="cg-why-card-desc">
493 Claude Code gets native read/write tools: search code, open
494 issues, create PRs, merge branches — all from the chat window.
495 </p>
496 </div>
497 <div class="cg-why-card">
498 <span class="cg-why-card-icon" aria-hidden="true">🔒</span>
499 <h3 class="cg-why-card-title">Secret scanning</h3>
500 <p class="cg-why-card-desc">
501 Every push is scanned for leaked credentials. A flagged push is
502 blocked at the gate — not just flagged after the fact.
503 </p>
504 </div>
505 <div class="cg-why-card">
506 <span class="cg-why-card-icon" aria-hidden="true">🌐</span>
507 <h3 class="cg-why-card-title">Self-hostable</h3>
508 <p class="cg-why-card-desc">
509 Run Gluecron on your own infra with a single Docker image. All
510 AI features work with your own Anthropic key.
511 </p>
512 </div>
513 </div>
514 </section>
515
516 {/* ─── CTA footer ─── */}
517 {!user && (
518 <p style="text-align: center; color: var(--text-muted); font-size: 14px; margin-top: var(--space-4);">
519 <a href="/register" class="cg-link">Create a free account</a> to get
520 started, or{" "}
521 <a href="/login" class="cg-link">sign in</a> if you already have one.
522 </p>
523 )}
524 </div>
525 <script dangerouslySetInnerHTML={{ __html: clientScript(username) }} />
526 </Layout>
527 );
528});
529
530export default connectRoutes;