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.tsxBlame529 lines · 1 contributor
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;
58 background: linear-gradient(90deg, transparent 0%, #8c6dff 30%, #36c5d6 70%, transparent 100%);
59 pointer-events: none;
60 }
61 .cg-hero-orb {
62 position: absolute;
63 inset: -20% -5% auto auto;
64 width: 420px; height: 420px;
65 background: radial-gradient(circle, rgba(140,109,255,0.20), rgba(54,197,214,0.10) 45%, transparent 70%);
66 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;
87 border: 1px solid rgba(140,109,255,0.35);
88 background: rgba(140,109,255,0.10);
89 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 {
106 background-image: linear-gradient(135deg, #a48bff 0%, #8c6dff 50%, #36c5d6 100%);
107 -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;
124 border: 1px solid rgba(140,109,255,0.45);
125 background: linear-gradient(135deg, rgba(140,109,255,0.18), rgba(54,197,214,0.12));
126 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 {
133 border-color: rgba(140,109,255,0.65);
134 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%;
166 background: linear-gradient(135deg, rgba(140,109,255,0.22), rgba(54,197,214,0.16));
167 border: 1px solid rgba(140,109,255,0.40);
168 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: {
325 transport: "http",
326 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>
412 <h2 class="cg-step-title">Configure MCP in .claude/settings.json</h2>
413 </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
417 PRs, and query your repos directly. Paste the snippet below into{" "}
418 <code>.claude/settings.json</code> (or{" "}
419 <code>~/.claude/settings.json</code> for global config), replacing{" "}
420 <code>YOUR_TOKEN_HERE</code> with the token from Step 1.
421 </p>
422 <div class="cg-code-wrap">
423 <pre id="cg-mcp-json" class="cg-code">{mcpJsonSnippet}</pre>
424 <button type="button" class="cg-copy-btn" data-cg-copy="cg-mcp-json">
425 Copy
426 </button>
427 </div>
428 </div>
429 </section>
430
431 {/* Step 4 */}
432 <section class="cg-step">
433 <div class="cg-step-head">
434 <span class="cg-step-num">4</span>
435 <h2 class="cg-step-title">Push</h2>
436 </div>
437 <div class="cg-step-body">
438 <p class="cg-step-desc">
a2b3e99Claude439 Push your branch. CI gates fire immediately and a draft PR is
440 auto-created if you're pushing a feature branch.
f5b9ef5Claude441 </p>
442 <div class="cg-code-wrap">
443 <pre id="cg-push" class="cg-code">git push gluecron main</pre>
444 <button type="button" class="cg-copy-btn" data-cg-copy="cg-push">
445 Copy
446 </button>
447 </div>
a2b3e99Claude448 <p class="cg-step-desc" style="margin-top: var(--space-3);">
449 For feature branches, auto-create a draft PR in one call:
450 </p>
451 <div class="cg-code-wrap">
452 <pre id="cg-auto-pr" class="cg-code">{`curl -X POST ${host}/api/claude/push \\
453 -H "Authorization: Bearer $GLUECRON_TOKEN" \\
454 -H "Content-Type: application/json" \\
455 -d '{"repoName":"REPO","branch":"my-feature"}'`}</pre>
456 <button type="button" class="cg-copy-btn" data-cg-copy="cg-auto-pr">
457 Copy
458 </button>
459 </div>
f5b9ef5Claude460 <p class="cg-step-desc" style="margin-top: var(--space-3); margin-bottom: 0;">
a2b3e99Claude461 The response includes a <code>pushWatchUrl</code> — open it to watch gates
462 and deployment live. Claude AI review lands within seconds.
f5b9ef5Claude463 </p>
464 </div>
465 </section>
466 </div>
467
468 {/* ─── Why Gluecron ─── */}
469 <section class="cg-why">
470 <h2 class="cg-why-title">Why Gluecron?</h2>
471 <div class="cg-why-grid">
472 <div class="cg-why-card">
473 <span class="cg-why-card-icon" aria-hidden="true">⚡</span>
474 <h3 class="cg-why-card-title">Lightning-fast CI</h3>
475 <p class="cg-why-card-desc">
476 Gate runs fire in parallel the moment code lands. Type-check,
477 lint, secret-scan, and test in under 30 seconds on most repos.
478 </p>
479 </div>
480 <div class="cg-why-card">
481 <span class="cg-why-card-icon" aria-hidden="true">🤖</span>
482 <h3 class="cg-why-card-title">AI review on every PR</h3>
483 <p class="cg-why-card-desc">
484 Claude reads the diff, flags bugs, suggests improvements, and
485 auto-approves or blocks merge — before a human even looks.
486 </p>
487 </div>
488 <div class="cg-why-card">
489 <span class="cg-why-card-icon" aria-hidden="true">🔧</span>
490 <h3 class="cg-why-card-title">15 MCP tools built-in</h3>
491 <p class="cg-why-card-desc">
492 Claude Code gets native read/write tools: search code, open
493 issues, create PRs, merge branches — all from the chat window.
494 </p>
495 </div>
496 <div class="cg-why-card">
497 <span class="cg-why-card-icon" aria-hidden="true">🔒</span>
498 <h3 class="cg-why-card-title">Secret scanning</h3>
499 <p class="cg-why-card-desc">
500 Every push is scanned for leaked credentials. A flagged push is
501 blocked at the gate — not just flagged after the fact.
502 </p>
503 </div>
504 <div class="cg-why-card">
505 <span class="cg-why-card-icon" aria-hidden="true">🌐</span>
506 <h3 class="cg-why-card-title">Self-hostable</h3>
507 <p class="cg-why-card-desc">
508 Run Gluecron on your own infra with a single Docker image. All
509 AI features work with your own Anthropic key.
510 </p>
511 </div>
512 </div>
513 </section>
514
515 {/* ─── CTA footer ─── */}
516 {!user && (
517 <p style="text-align: center; color: var(--text-muted); font-size: 14px; margin-top: var(--space-4);">
518 <a href="/register" class="cg-link">Create a free account</a> to get
519 started, or{" "}
520 <a href="/login" class="cg-link">sign in</a> if you already have one.
521 </p>
522 )}
523 </div>
524 <script dangerouslySetInnerHTML={{ __html: clientScript(username) }} />
525 </Layout>
526 );
527});
528
529export default connectRoutes;