diff --git a/app/components/TalkThumbnail.tsx b/app/components/TalkThumbnail.tsx new file mode 100644 index 0000000..2007b7c --- /dev/null +++ b/app/components/TalkThumbnail.tsx @@ -0,0 +1,204 @@ +"use client" +import styled from "styled-components" + +// +// Types +// + +export type TalkThumbnailData = { + speakerName: string + talkTitle: string + profilePhotoUrl?: string +} + +export type TalkThumbnailProps = TalkThumbnailData & { + /** Width in pixels; height is derived for 16:9 (YouTube thumbnail). */ + width?: number + className?: string +} + +// +// Constants +// + +const ASPECT_RATIO = 16 / 9 +const DEFAULT_WIDTH = 640 +const BG_IMAGE_PATH = "/images/devx-thumbnail-bg.png" +const LOGO_IMAGE_PATH = "/images/sd-devx-brand.png" + +// +// Components +// + +/** + * Renders a DEVx-style talk video thumbnail matching the DEVxYouTubeThumbnail.svg + * template layout: talk title on the left, circular speaker photo on the right, + * DEVxSD branding at bottom-left, dark silk texture background. + * + * 16:9 aspect ratio (YouTube standard 1280x720). + */ +export function TalkThumbnail({ + speakerName, + talkTitle, + profilePhotoUrl, + width = DEFAULT_WIDTH, + className +}: TalkThumbnailProps) { + const height = Math.round(width / ASPECT_RATIO) + + // Layout proportions matching the template + const pad = width * 0.06 + const photoRadius = Math.round(height * 0.3) + const photoBackdropRadius = photoRadius + Math.round(width * 0.01) + const photoCx = width - pad - photoBackdropRadius + const photoCy = height * 0.44 + const titleX = pad + const photoLeftEdge = photoCx - photoBackdropRadius + const titleGap = width * 0.04 + const titleMaxWidth = photoLeftEdge - pad - titleGap + const titleFontSize = Math.round(width * 0.058) + const titleY = height * 0.38 + const logoWidth = Math.round(width * 0.18) + const logoHeight = Math.round(logoWidth * (582 / 2772)) + const logoX = pad + const logoY = height - pad - logoHeight + + const titleLines = wrapText(talkTitle || "Your Talk Title", titleMaxWidth, titleFontSize) + + return ( + + + + {profilePhotoUrl ? ( + + + + ) : null} + + + {/* Dark silk texture background */} + + + {/* Speaker photo circle with light backdrop — right side */} + {profilePhotoUrl ? ( + <> + + + + ) : ( + + )} + + {/* Talk title — large bold white, left side */} + + {titleLines.map((line, i) => ( + + {line} + + ))} + + + {/* DEVxSD branding — bottom left */} + + + + ) +} + +const Wrapper = styled.div<{ $width: number; $height: number }>` + width: ${(p) => p.$width}px; + max-width: 100%; + aspect-ratio: 16 / 9; + overflow: hidden; + border-radius: 0.5rem; + border: 1px solid rgba(255, 255, 255, 0.1); + background: #0a0a0a; + + svg { + display: block; + width: 100%; + height: auto; + } +` + +// +// Functions +// + +/** Wrap text into lines that fit within maxWidth, max 3 lines. */ +function wrapText(text: string, maxWidth: number, fontSize: number): string[] { + if (!text.trim()) return ["Your Talk Title"] + const words = text.trim().split(/\s+/) + const approxCharWidth = fontSize * 0.52 + const maxLines = 3 + const lines: string[] = [] + let current = "" + + for (const word of words) { + const candidate = current ? `${current} ${word}` : word + if (candidate.length * approxCharWidth <= maxWidth) { + current = candidate + } else { + if (current) lines.push(current) + if (lines.length >= maxLines) { + const last = lines[lines.length - 1] + if (last && word) { + lines[lines.length - 1] = last + "..." + } + return lines + } + current = word + } + } + if (current && lines.length < maxLines) lines.push(current) + return lines +} diff --git a/app/submit-talk/page.tsx b/app/submit-talk/page.tsx index 3849fac..4840601 100644 --- a/app/submit-talk/page.tsx +++ b/app/submit-talk/page.tsx @@ -11,6 +11,7 @@ import { TextareaInput } from "../components/TextareaInput" import { RadioInput } from "../components/RadioInput" import { PageContainer } from "../components/PageContainer" import { SuccessMessage as SuccessMessageComponent } from "../components/SuccessMessage" +import { TalkThumbnail } from "../components/TalkThumbnail" import Link from "next/link" export default function SubmitTalk() { @@ -23,6 +24,7 @@ export default function SubmitTalk() { const [userEmail, setUserEmail] = useState("") const [userFullName, setUserFullName] = useState("") const [userHandle, setUserHandle] = useState(null) + const [profilePhotoUrl, setProfilePhotoUrl] = useState(null) const [profileId, setProfileId] = useState(null) const [profilePhoneNumber, setProfilePhoneNumber] = useState(null) const [isEditingPhone, setIsEditingPhone] = useState(false) @@ -63,15 +65,16 @@ export default function SubmitTalk() { const { handle } = getProfileFromCache(user) setUserHandle(handle) - // Load profile to get full name, profile_id, and phone number + // Load profile to get full name, profile_id, phone number, and profile photo (for thumbnail) const { data: profile } = await supabaseClient .from("profiles") - .select("id, full_name, phone_number") + .select("id, full_name, phone_number, profile_photo") .eq("user_id", user.id) .single() if (profile) { setUserFullName(profile.full_name) + setProfilePhotoUrl(profile.profile_photo || null) setProfileId(profile.id) setProfilePhoneNumber(profile.phone_number) // If profile has phone number, use it; otherwise start with empty @@ -400,6 +403,26 @@ export default function SubmitTalk() { required /> + + + + This is how your talk could look as a YouTube thumbnail. Update your{" "} + {userHandle ? ( + nametag + ) : ( + nametag + )}{" "} + to change your name and photo. + + + + + @@ -579,6 +602,11 @@ const InfoLink = styled(Link)` } ` +const ThumbnailPreviewWrap = styled.div` + max-width: 100%; + margin-top: 0.5rem; +` + const Label = styled.label` font-size: 0.875rem; font-weight: 700; diff --git a/app/talk-thumbnail-gen/page.tsx b/app/talk-thumbnail-gen/page.tsx new file mode 100644 index 0000000..3a773cd --- /dev/null +++ b/app/talk-thumbnail-gen/page.tsx @@ -0,0 +1,501 @@ +"use client" +import styled from "styled-components" +import { useState, useRef, useCallback } from "react" +import { supabaseClient } from "../../lib/supabaseClient" +import { TalkThumbnail } from "../components/TalkThumbnail" +import { TextInput } from "../components/TextInput" +import { Button } from "../components/Button" +import { PotionBackground } from "../components/PotionBackground" +import { PageContainer } from "../components/PageContainer" + +// +// Constants +// + +const THUMBNAIL_WIDTH = 1280 +const THUMBNAIL_HEIGHT = 720 + +// +// Components +// + +export default function TalkThumbnailGen() { + const [talkTitle, setTalkTitle] = useState("") + const [handle, setHandle] = useState("") + const [photoUrl, setPhotoUrl] = useState(null) + const [photoSource, setPhotoSource] = useState<"none" | "upload" | "handle">("none") + const [handleLoading, setHandleLoading] = useState(false) + const [handleError, setHandleError] = useState(null) + const fileInputRef = useRef(null) + const svgContainerRef = useRef(null) + + const handleFileUpload = useCallback( + (e: React.ChangeEvent) => { + const file = e.target.files?.[0] + if (!file) return + + if (photoUrl && photoSource === "upload") { + URL.revokeObjectURL(photoUrl) + } + + const blobUrl = URL.createObjectURL(file) + setPhotoUrl(blobUrl) + setPhotoSource("upload") + setHandle("") + setHandleError(null) + }, + [photoUrl, photoSource] + ) + + const handleLookup = useCallback(async () => { + if (!handle.trim()) return + + setHandleLoading(true) + setHandleError(null) + + try { + const { data: profile, error } = await supabaseClient + .from("profiles") + .select("full_name, profile_photo") + .eq("handle", handle.trim().toLowerCase()) + .single() + + if (error || !profile) { + setHandleError(`No profile found for @${handle.trim()}`) + return + } + + if (profile.profile_photo) { + if (photoUrl && photoSource === "upload") { + URL.revokeObjectURL(photoUrl) + } + setPhotoUrl(profile.profile_photo) + setPhotoSource("handle") + } else { + setHandleError(`@${handle.trim()} has no profile photo`) + } + } catch { + setHandleError("Failed to look up profile") + } finally { + setHandleLoading(false) + } + }, [handle, photoUrl, photoSource]) + + const clearPhoto = useCallback(() => { + if (photoUrl && photoSource === "upload") { + URL.revokeObjectURL(photoUrl) + } + setPhotoUrl(null) + setPhotoSource("none") + if (fileInputRef.current) { + fileInputRef.current.value = "" + } + }, [photoUrl, photoSource]) + + const downloadRaster = useCallback( + async (format: "png" | "jpg") => { + const svgEl = svgContainerRef.current?.querySelector("svg") + if (!svgEl) return + + const svgString = await buildEmbeddedSvgString(svgEl) + const svgBlob = new Blob([svgString], { type: "image/svg+xml" }) + const svgBlobUrl = URL.createObjectURL(svgBlob) + + const img = new Image() + img.width = THUMBNAIL_WIDTH + img.height = THUMBNAIL_HEIGHT + + img.onload = () => { + const canvas = document.createElement("canvas") + canvas.width = THUMBNAIL_WIDTH + canvas.height = THUMBNAIL_HEIGHT + const ctx = canvas.getContext("2d") + if (!ctx) return + + // JPG has no transparency — fill white first + if (format === "jpg") { + ctx.fillStyle = "#000000" + ctx.fillRect(0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT) + } + + ctx.drawImage(img, 0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT) + URL.revokeObjectURL(svgBlobUrl) + + const mimeType = format === "jpg" ? "image/jpeg" : "image/png" + // Quality 0.92 for JPG keeps it well under 2 MB + const quality = format === "jpg" ? 0.92 : undefined + + canvas.toBlob( + (blob) => { + if (!blob) return + const url = URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = url + a.download = makeFilename(talkTitle, format) + a.click() + URL.revokeObjectURL(url) + }, + mimeType, + quality + ) + } + + img.src = svgBlobUrl + }, + [talkTitle] + ) + + return ( + <> + + + + + + Talk Thumbnail Generator + Create a YouTube thumbnail for your DEVx talk + + + + + setTalkTitle(e.target.value)} + placeholder="Enter your talk title" + /> + + + + + + + + + or + + + + + @ + setHandle(e.target.value)} + placeholder="username" + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault() + handleLookup() + } + }} + /> + + + {handleError && {handleError}} + + + + {photoUrl && ( + + + Photo loaded + {photoSource === "handle" ? ` from @${handle}` : " from upload"} + + + + )} + + + + + + + + + + + 1280 x 720 px · under 2 MB · YouTube ready + + + + ) +} + +// +// Styled Components +// + +const BackgroundContainer = styled.section` + background-color: #0a0a0a; + position: fixed; + height: 100vh; + width: 100vw; + top: 0; + left: 0; + z-index: -1; +` + +const WidePageContainer = styled(PageContainer)` + max-width: 960px; +` + +const Container = styled.main` + min-height: 100vh; + display: flex; + align-items: flex-start; + justify-content: center; + padding: 2rem 1rem; +` + +const Title = styled.h1` + font-size: 2rem; + font-weight: 700; + color: white; + margin: 0; + text-align: center; +` + +const Subtitle = styled.p` + color: rgba(255, 255, 255, 0.7); + margin: -1rem 0 0 0; + text-align: center; +` + +const FormSection = styled.div` + display: flex; + flex-direction: column; + gap: 1rem; + width: 100%; +` + +const PhotoRow = styled.div` + display: flex; + align-items: flex-end; + gap: 1.5rem; + width: 100%; + + & > div { + flex: 1; + } + + @media (max-width: 600px) { + flex-direction: column; + align-items: stretch; + } +` + +const PhotoStatusRow = styled.div` + display: flex; + align-items: center; + gap: 1rem; +` + +const Preview = styled.div` + width: 100%; + border-radius: 0.5rem; + overflow: hidden; + + & > div { + width: 100% !important; + } +` + +const Field = styled.div` + display: flex; + flex-direction: column; + gap: 0.375rem; +` + +const Label = styled.label` + font-size: 0.875rem; + font-weight: 700; + color: rgba(255, 255, 255, 0.9); +` + +const FileInput = styled.input` + padding: 0.5rem; + background-color: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 0.5rem; + color: white; + font-size: 0.875rem; + cursor: pointer; + + &::file-selector-button { + padding: 0.375rem 0.75rem; + margin-right: 0.75rem; + background-color: rgba(156, 163, 255, 0.2); + border: 1px solid rgba(156, 163, 255, 0.4); + border-radius: 0.375rem; + color: white; + cursor: pointer; + font-size: 0.8125rem; + transition: all 0.2s ease; + + &:hover { + background-color: rgba(156, 163, 255, 0.3); + } + } +` + +const HandleRow = styled.div` + display: flex; + align-items: center; + gap: 0.5rem; + + input { + flex: 1; + } +` + +const HandlePrefix = styled.span` + color: rgba(255, 255, 255, 0.5); + font-size: 1rem; + font-weight: 600; + flex-shrink: 0; +` + +const ErrorText = styled.p` + margin: 0; + color: #ff6b6b; + font-size: 0.8125rem; +` + +const PhotoStatus = styled.p` + margin: 0; + color: rgba(156, 163, 255, 0.9); + font-size: 0.8125rem; +` + +const OrDivider = styled.div` + text-align: center; + color: rgba(255, 255, 255, 0.4); + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.1em; + flex-shrink: 0; + padding-bottom: 0.5rem; + + @media (max-width: 600px) { + padding-bottom: 0; + } +` + +const DownloadRow = styled.div` + display: flex; + justify-content: center; + gap: 0.75rem; +` + +const SpecNote = styled.p` + margin: -1rem 0 0 0; + text-align: center; + color: rgba(255, 255, 255, 0.4); + font-size: 0.75rem; +` + +// +// Functions +// + +function makeFilename(title: string, ext: string): string { + const slug = (title || "talk-thumbnail") + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-|-$/g, "") + .slice(0, 50) + return `${slug}-thumbnail.${ext}` +} + +/** + * Convert an image URL (relative, absolute, or blob) to a base64 data URL + * by drawing it onto a temporary canvas. + */ +async function urlToDataUrl(src: string): Promise { + // Already a data URL — return as-is + if (src.startsWith("data:")) return src + + // Resolve relative paths to absolute + const resolved = src.startsWith("/") ? `${window.location.origin}${src}` : src + + const response = await fetch(resolved) + const blob = await response.blob() + + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.onloadend = () => resolve(reader.result as string) + reader.onerror = reject + reader.readAsDataURL(blob) + }) +} + +/** + * Clone the live SVG element, convert every `` to an + * embedded base64 data URL, and return the serialized SVG string. + * This makes the exported SVG/PNG fully self-contained. + */ +async function buildEmbeddedSvgString(svgEl: SVGSVGElement): Promise { + const clone = svgEl.cloneNode(true) as SVGSVGElement + const images = clone.querySelectorAll("image") + + await Promise.all( + Array.from(images).map(async (img) => { + const href = + img.getAttribute("href") || img.getAttributeNS("http://www.w3.org/1999/xlink", "href") + if (!href || href.startsWith("data:")) return + try { + const dataUrl = await urlToDataUrl(href) + img.setAttribute("href", dataUrl) + // Remove xlink:href if present to avoid duplicates + img.removeAttributeNS("http://www.w3.org/1999/xlink", "href") + } catch { + // If an image fails to convert, leave the original href + } + }) + ) + + const serializer = new XMLSerializer() + return serializer.serializeToString(clone) +} diff --git a/package.json b/package.json index d2a2fd2..c8c457d 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "dev": "next dev", + "dev:supabase": "supabase start", "build": "next build", "start": "next start", "lint": "next lint", diff --git a/public/images/devx-thumbnail-bg.png b/public/images/devx-thumbnail-bg.png new file mode 100644 index 0000000..d6b527d Binary files /dev/null and b/public/images/devx-thumbnail-bg.png differ diff --git a/public/slides/houdini-overview-and-custom-properties-dive-aj-caldwell/metadata.json b/public/slides/houdini-overview-and-custom-properties-dive-aj-caldwell/metadata.json index 9b4deeb..ba24890 100644 --- a/public/slides/houdini-overview-and-custom-properties-dive-aj-caldwell/metadata.json +++ b/public/slides/houdini-overview-and-custom-properties-dive-aj-caldwell/metadata.json @@ -1,6 +1,6 @@ { - "title": "Houdini Overview and Custom Properties Dive", - "description": "A lightning talk covering the Houdini initive and then narrowing in on how we can use the full power of CSS custom properties.", - "author": "AJ Caldwell", - "timestamp": "2026-01-23T00:00:00.000Z" + "title": "Houdini Overview and Custom Properties Dive", + "description": "A lightning talk covering the Houdini initive and then narrowing in on how we can use the full power of CSS custom properties.", + "author": "AJ Caldwell", + "timestamp": "2026-01-23T00:00:00.000Z" } diff --git a/public/slides/houdini-overview-and-custom-properties-dive-aj-caldwell/slides.html b/public/slides/houdini-overview-and-custom-properties-dive-aj-caldwell/slides.html index 2e2ce0a..1bff922 100644 --- a/public/slides/houdini-overview-and-custom-properties-dive-aj-caldwell/slides.html +++ b/public/slides/houdini-overview-and-custom-properties-dive-aj-caldwell/slides.html @@ -1,157 +1,181 @@ - + - - - - Houdini Overview and Custom Properties Dive - - - -
-
- Slide 1 -
-
- Slide 2 -
-
- Slide 3 -
-
- Slide 4 -
-
- Slide 5 -
-
- Slide 6 -
-
- Slide 7 -
-
- Slide 8 -
- -
- -
- 1 / 8 -
- -
-
- - - - \ No newline at end of file + + + + Houdini Overview and Custom Properties Dive + + + +
+
+ Slide 1 +
+
+ Slide 2 +
+
+ Slide 3 +
+
+ Slide 4 +
+
+ Slide 5 +
+
+ Slide 6 +
+
+ Slide 7 +
+
+ Slide 8 +
+ +
+ +
+ 1 / 8 +
+ +
+
+ + + + diff --git a/public/slides/opencode-ai-coding-agent-sam-holmes/slides.html b/public/slides/opencode-ai-coding-agent-sam-holmes/slides.html index 0c39d6b..b326b93 100644 --- a/public/slides/opencode-ai-coding-agent-sam-holmes/slides.html +++ b/public/slides/opencode-ai-coding-agent-sam-holmes/slides.html @@ -1,4 +1,816 @@ -Getting Started +
code { + min-width: 328px; + } + div#\:\$p > svg > foreignObject > section table { + border-collapse: collapse; + margin: 0 auto 15px; + } + div#\:\$p > svg > foreignObject > section table > tbody > tr > td, + div#\:\$p > svg > foreignObject > section table > tbody > tr > th, + div#\:\$p > svg > foreignObject > section table > thead > tr > td, + div#\:\$p > svg > foreignObject > section table > thead > tr > th { + padding: 0.15em 0.5em; + } + div#\:\$p > svg > foreignObject > section table > thead > tr > td, + div#\:\$p > svg > foreignObject > section table > thead > tr > th { + border-bottom: 3px solid; + } + div#\:\$p > svg > foreignObject > section table > tbody > tr:not(:last-child) > td, + div#\:\$p > svg > foreignObject > section table > tbody > tr:not(:last-child) > th { + border-bottom: 1px solid; + } + div#\:\$p > svg > foreignObject > section blockquote { + font-size: 90%; + line-height: 1.3; + padding: 0 2em; + position: relative; + z-index: 0; + } + div#\:\$p > svg > foreignObject > section blockquote:after, + div#\:\$p > svg > foreignObject > section blockquote:before { + content: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cGF0aCBkPSJNNDQgMTkuMyAzOC45NCAwQzguMTQgOS41OSAwIDQwLjA1IDAgNTQuODNWMTAwaDQxLjQ3VjU0LjgzaC0yM2MtLjA0LS4yOC4yNS0yNy42NiAyNS41My0zNS41M201NiAwTDk0Ljk0IDBDNjQuMTQgOS41OSA1NiA0MC4wNSA1NiA1NC44M1YxMDBoNDEuNDdWNTQuODNoLTIzYy0uMDQtLjI4LjI1LTI3LjY2IDI1LjUzLTM1LjUzIiBzdHlsZT0iZmlsbDojODg4O29wYWNpdHk6LjMzIi8+PC9zdmc+"); + height: auto; + pointer-events: none; + position: absolute; + width: 1em; + z-index: -1; + } + div#\:\$p > svg > foreignObject > section blockquote:before { + left: 0; + top: 0; + } + div#\:\$p > svg > foreignObject > section blockquote:after { + bottom: 0; + right: 0; + transform: rotate(180deg); + } + div#\:\$p > svg > foreignObject > section blockquote > :last-child { + margin-bottom: 0; + } + div#\:\$p > svg > foreignObject > section mark { + background: transparent; + color: var(--color-highlight); + } + div#\:\$p > svg > foreignObject > :where(section):not([\20 root]) { + --color-background: #110e0b; + --color-foreground: #ffffff; + --color-highlight: #ffffff; + --color-dimmed: #737373; + } + div#\:\$p > svg > foreignObject > section { + background: #110e0b; + font-family: "IBM Plex Mono", "SF Mono", "Menlo", "Monaco", "Consolas", monospace; + letter-spacing: 0.02em; + } + div#\:\$p > svg > foreignObject > section :is(h1, marp-h1) { + color: #ffffff; + font-weight: 600; + font-family: "IBM Plex Mono", monospace; + letter-spacing: 0.05em; + } + div#\:\$p > svg > foreignObject > section :is(h2, marp-h2) { + color: #ffffff; + font-weight: 500; + font-family: "IBM Plex Mono", monospace; + letter-spacing: 0.03em; + } + div#\:\$p > svg > foreignObject > section :is(h3, marp-h3) { + color: #a3a3a3; + font-weight: 500; + font-family: "IBM Plex Mono", monospace; + } + div#\:\$p > svg > foreignObject > section p { + color: #d4d4d4; + } + div#\:\$p > svg > foreignObject > section code { + background: #1a1714; + color: #ffffff; + padding: 0.2em 0.4em; + border-radius: 4px; + font-family: "IBM Plex Mono", monospace; + border: 1px solid #2a2420; + } + div#\:\$p > svg > foreignObject > section :is(pre, marp-pre) { + background: #0a0908; + border: 1px solid #2a2420; + border-radius: 8px; + padding: 1em; + } + div#\:\$p > svg > foreignObject > section :is(pre, marp-pre) code { + background: transparent; + color: #e5e5e5; + border: none; + } + div#\:\$p > svg > foreignObject > section a { + color: #ffffff; + text-decoration: underline; + text-underline-offset: 3px; + } + div#\:\$p > svg > foreignObject > section a:hover { + color: #a3a3a3; + } + div#\:\$p > svg > foreignObject > section strong { + color: #ffffff; + font-weight: 600; + } + div#\:\$p > svg > foreignObject > section ul, + div#\:\$p > svg > foreignObject > section ol { + text-align: left; + color: #d4d4d4; + } + div#\:\$p > svg > foreignObject > section li { + margin: 0.5em 0; + } + div#\:\$p > svg > foreignObject > section li::marker { + color: #525252; + } + div#\:\$p > svg > foreignObject > section blockquote { + border-left: 2px solid #4a4238; + padding-left: 1em; + color: #737373; + font-style: normal; + } + div#\:\$p > svg > foreignObject > section table { + border-collapse: collapse; + width: 100%; + } + div#\:\$p > svg > foreignObject > section th { + background: #0a0908; + color: #ffffff; + font-weight: 500; + border-bottom: 1px solid #2a2420; + } + div#\:\$p > svg > foreignObject > section td { + border-bottom: 1px solid #1a1714; + color: #d4d4d4; + } + div#\:\$p > svg > foreignObject > section .columns { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2em; + } + div#\:\$p > svg > foreignObject > section .highlight { + color: #ffffff; + font-weight: 600; + } + div#\:\$p > svg > foreignObject > section .dimmed { + color: #525252; + } + div#\:\$p > svg > foreignObject > section img { + border-radius: 8px; + } + div#\:\$p > svg > foreignObject > section::after { + color: #525252; + font-family: "IBM Plex Mono", monospace; + font-size: 0.6em; + } + div#\:\$p > svg > foreignObject > section::after { + --marpit-root-font-size: 0.6em; + } + div#\:\$p > svg > foreignObject > section[data-marpit-advanced-background="background"] { + columns: initial !important; + display: block !important; + padding: 0 !important; + } + div#\:\$p + > svg + > foreignObject + > section[data-marpit-advanced-background="background"]::before, + div#\:\$p + > svg + > foreignObject + > section[data-marpit-advanced-background="background"]::after, + div#\:\$p > svg > foreignObject > section[data-marpit-advanced-background="content"]::before, + div#\:\$p > svg > foreignObject > section[data-marpit-advanced-background="content"]::after { + display: none !important; + } + div#\:\$p + > svg + > foreignObject + > section[data-marpit-advanced-background="background"] + > div[data-marpit-advanced-background-container] { + all: initial; + display: flex; + flex-direction: row; + height: 100%; + overflow: hidden; + width: 100%; + } + div#\:\$p + > svg + > foreignObject + > section[data-marpit-advanced-background="background"] + > div[data-marpit-advanced-background-container][data-marpit-advanced-background-direction="vertical"] { + flex-direction: column; + } + div#\:\$p + > svg + > foreignObject + > section[data-marpit-advanced-background="background"][data-marpit-advanced-background-split] + > div[data-marpit-advanced-background-container] { + width: var(--marpit-advanced-background-split, 50%); + } + div#\:\$p + > svg + > foreignObject + > section[data-marpit-advanced-background="background"][data-marpit-advanced-background-split="right"] + > div[data-marpit-advanced-background-container] { + margin-left: calc(100% - var(--marpit-advanced-background-split, 50%)); + } + div#\:\$p + > svg + > foreignObject + > section[data-marpit-advanced-background="background"] + > div[data-marpit-advanced-background-container] + > figure { + all: initial; + background-position: center; + background-repeat: no-repeat; + background-size: cover; + flex: auto; + margin: 0; + } + div#\:\$p + > svg + > foreignObject + > section[data-marpit-advanced-background="background"] + > div[data-marpit-advanced-background-container] + > figure + > figcaption { + position: absolute; + border: 0; + clip: rect(0, 0, 0, 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + white-space: nowrap; + width: 1px; + } + div#\:\$p > svg > foreignObject > section[data-marpit-advanced-background="content"], + div#\:\$p > svg > foreignObject > section[data-marpit-advanced-background="pseudo"] { + background: transparent !important; + } + div#\:\$p > svg > foreignObject > section[data-marpit-advanced-background="pseudo"], + div#\:\$p > svg[data-marpit-svg] > foreignObject[data-marpit-advanced-background="pseudo"] { + pointer-events: none !important; + } + div#\:\$p > svg > foreignObject > section[data-marpit-advanced-background-split] { + width: 100%; + height: 100%; + } + + + +
+ +
+
+ +
-

-

Presented by Sam Holmes

-
-
+

+

Presented by Sam Holmes

+
+
+
-

What is OpenCode?

-

An open source AI coding agent available as:

-
    -
  • Terminal-based interface (TUI)
  • -
  • Desktop app
  • -
  • IDE extension
  • -
-
-

Work with any LLM provider on your projects

-
-
-
+

What is OpenCode?

+

An open source AI coding agent available as:

+
    +
  • Terminal-based interface (TUI)
  • +
  • Desktop app
  • +
  • IDE extension
  • +
+
+

Work with any LLM provider on your projects

+
+
+
+
What is OpenCode? font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="3" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Why OpenCode?

-
    -
  • Open Source - Community-driven development
  • -
  • Provider Agnostic - Any LLM provider
  • -
  • Customizable - Themes, keybinds, agents
  • -
  • Extensible - MCP servers & plugins
  • -
  • Polished - High-quality TUI compared to other tools
  • -
-
-
+

Why OpenCode?

+
    +
  • Open Source - Community-driven development
  • +
  • Provider Agnostic - Any LLM provider
  • +
  • Customizable - Themes, keybinds, agents
  • +
  • Extensible - MCP servers & plugins
  • +
  • Polished - High-quality TUI compared to other tools
  • +
+
+
+
Why OpenCode? font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="4" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Getting Started

-
-
+

Getting Started

+
+
+
Getting Started font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="5" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Installation

-
# Install script (easiest)
-curl -fsSL https://opencode.ai/install | bash
+"
+						lang="en-US"
+						class="invert"
+						data-marpit-pagination="5"
+						style="
+							--class: invert;
+							--paginate: true;
+							--theme: uncover;
+							--style: @import
+								url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap");
+						"
+						data-marpit-pagination-total="36"
+					>
+						

Installation

+
+							# Install script (easiest)
+								curl -fsSL https://opencode.ai/install | bash
 
-# Or via package managers
-brew install anomalyco/tap/opencode  # macOS/Linux
-npm install -g opencode-ai           # Node.js
-choco install opencode               # Windows
-
-
-
# Or via package managers + brew install anomalyco/tap/opencode + # macOS/Linux npm install -g opencode-ai + # Node.js choco install opencode + # Windows + + +
+
+
Installation font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="6" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Configuration

-

Connect to your LLM provider:

-
/connect
-
-

Choose from:

-
    -
  • OpenCode Zen - Curated models (recommended for beginners)
  • -
  • Anthropic, OpenAI, Google, and more
  • -
-
-
+

Configuration

+

Connect to your LLM provider:

+
/connect 
+

Choose from:

+
    +
  • OpenCode Zen - Curated models (recommended for beginners)
  • +
  • Anthropic, OpenAI, Google, and more
  • +
+
+
+
Configuration font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="7" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Initialize Your Project

-
cd /path/to/project
-opencode
-
-

Then run:

-
/init
-
-

Creates an AGENTS.md file with project context

-
-
+

Initialize Your Project

+
+							cd /path/to/project opencode
+							
+						
+

Then run:

+
/init 
+

Creates an AGENTS.md file with project context

+
+
+
Initialize Your Project font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="8" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Core Features

-
-
+

Core Features

+
+
+
Core Features font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="9" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

File References

-

Use @ to reference files in your prompts:

-
How is auth handled in @src/api/auth.ts?
-
-

Fuzzy file search built-in

-
-
+

File References

+

Use @ to reference files in your prompts:

+
+							How is auth handled in @src/api/auth.ts? 
+						
+

Fuzzy file search built-in

+
+
+
File References font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="10" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Undo & Redo

-

Made a mistake? No problem:

-
/undo    # Revert changes + restore files
-/redo    # Restore undone changes
-
-

Uses Git under the hood for file restoration

-
-
+

Undo & Redo

+

Made a mistake? No problem:

+
+							/undo # Revert changes + restore files /redo # Restore undone changes 
+						
+

Uses Git under the hood for file restoration

+
+
+
Undo & Redo font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="11" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Shell Commands

-

Run commands directly with !:

-
!npm test
-!git status
-!ls -la
-
-

Output is added to the conversation context

-
-
+

Shell Commands

+

Run commands directly with !:

+
+							!npm test !git status !ls -la 
+						
+

Output is added to the conversation context

+
+
+
Shell Commands font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="12" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Agents

-
-
+

Agents

+
+
+
Agents font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="13" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Built-in Agents

-

Primary Agents (Tab to switch):

-
    -
  • Build - Full development access
  • -
  • Plan - Read-only analysis
  • -
-

Subagents (@ to invoke):

-
    -
  • General - Multi-step research tasks
  • -
  • Explore - Fast codebase exploration
  • -
-
-
+

Built-in Agents

+

Primary Agents (Tab to switch):

+
    +
  • Build - Full development access
  • +
  • Plan - Read-only analysis
  • +
+

Subagents (@ to invoke):

+
    +
  • General - Multi-step research tasks
  • +
  • Explore - Fast codebase exploration
  • +
+
+
+
Built-in Agents font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="14" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Custom Agents

-

Create specialized agents in ~/.config/opencode/agent/:

-
---
-description: Reviews code for security issues
-mode: subagent
-tools:
-  write: false
-  edit: false
----
+"
+						lang="en-US"
+						class="invert"
+						data-marpit-pagination="14"
+						style="
+							--class: invert;
+							--paginate: true;
+							--theme: uncover;
+							--style: @import
+								url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap");
+						"
+						data-marpit-pagination-total="36"
+					>
+						

Custom Agents

+

Create specialized agents in ~/.config/opencode/agent/:

+
+							--- description: Reviews code for security issues mode: subagent tools: write:
+								false
+								 edit: false ---
 
-You are a security auditor. Focus on:
-- Input validation
-- Authentication flaws
-- Data exposure risks
-
-
-
- Input validation + - Authentication flaws + - Data exposure risks + + +
+
+
Custom Agents font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="15" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Agent Permissions

-

Fine-grained control:

-
{
-  "agent": {
-    "plan": {
-      "permission": {
-        "edit": "deny",
-        "bash": "ask"
-      }
-    }
-  }
-}
-
-

allow | ask | deny

-
-
+

Agent Permissions

+

Fine-grained control:

+
+							{
+								"agent": {
+								"plan": {
+								"permission": {
+								"edit":
+								"deny",
+								"bash":
+								"ask"
+								}
+								}
+								}
+								}
+							
+						
+

allow | ask | deny

+
+
+
Agent Permissions font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="16" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

MCP Servers

-

Model Context Protocol

-
-
+

MCP Servers

+

Model Context Protocol

+
+
+
MCP Servers font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="17" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

What is MCP?

-

Add external tools to OpenCode:

-
    -
  • Local - Run commands on your machine
  • -
  • Remote - Connect to web services
  • -
-

Tools become available to the LLM automatically

-
-
+

What is MCP?

+

Add external tools to OpenCode:

+
    +
  • Local - Run commands on your machine
  • +
  • Remote - Connect to web services
  • +
+

Tools become available to the LLM automatically

+
+
+
What is MCP? font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="18" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Remote MCP Example

-
{
-  "mcp": {
-    "sentry": {
-      "type": "remote",
-      "url": "https://mcp.sentry.dev/mcp"
-    }
-  }
-}
-
-

Auth: opencode mcp auth sentry

-
-
+

Remote MCP Example

+
+							{
+								"mcp": {
+								"sentry": {
+								"type":
+								"remote",
+								"url":
+								"https://mcp.sentry.dev/mcp"
+								}
+								}
+								}
+							
+						
+

Auth: opencode mcp auth sentry

+
+
+
Remote MCP Example font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="19" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Local MCP Example

-
{
-  "mcp": {
-    "my-tools": {
-      "type": "local",
-      "command": ["npx", "-y", "my-mcp-server"],
-      "environment": {
-        "API_KEY": "{env:MY_API_KEY}"
-      }
-    }
-  }
-}
-
-
-
+

Local MCP Example

+
+							{
+								"mcp": {
+								"my-tools": {
+								"type":
+								"local",
+								"command": ["npx",
+								"-y",
+								"my-mcp-server"],
+								"environment": {
+								"API_KEY":
+								"{env:MY_API_KEY}"
+								}
+								}
+								}
+								}
+							
+						
+
+
+
Local MCP Example font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="20" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Commands & Keybinds

-
-
+

Commands & Keybinds

+
+
+
Commands & Keybinds font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="21" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Essential Commands

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CommandDescription
/helpShow help dialog
/initCreate AGENTS.md
/newStart new session
/sessionsList all sessions
/shareShare conversation
/compactSummarize session
-
-
+

Essential Commands

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CommandDescription
/helpShow help dialog
/initCreate AGENTS.md
/newStart new session
/sessionsList all sessions
/shareShare conversation
/compactSummarize session
+
+
+
Essential Commands font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="22" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Default Keybinds

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyAction
TabSwitch agent mode
ctrl+x hHelp
ctrl+x nNew session
ctrl+x uUndo
ctrl+x rRedo
ctrl+x sShare
-
-
+

Default Keybinds

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyAction
TabSwitch agent mode
ctrl+x hHelp
ctrl+x nNew session
ctrl+x uUndo
ctrl+x rRedo
ctrl+x sShare
+
+
+
Default Keybinds font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="23" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Customization

-
-
+

Customization

+
+
+
Customization font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="24" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Themes

-
/themes
-
-

Or configure in opencode.json:

-
{
-  "theme": "opencode"
-}
-
-
-
+

Themes

+
/themes 
+

Or configure in opencode.json:

+
+							{
+								"theme":
+								"opencode"
+								}
+							
+						
+
+
+
Themes font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="25" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Configuration File

-

~/.config/opencode/opencode.json:

-
{
-  "$schema": "https://opencode.ai/config.json",
-  "provider": {
-    "default": "anthropic"
-  },
-  "model": {
-    "default": "anthropic/claude-sonnet-4"
-  }
-}
-
-
-
+

Configuration File

+

~/.config/opencode/opencode.json:

+
+							{
+								"$schema":
+								"https://opencode.ai/config.json",
+								"provider": {
+								"default":
+								"anthropic"
+								},
+								"model": {
+								"default":
+								"anthropic/claude-sonnet-4"
+								}
+								}
+							
+						
+
+
+
Configuration File font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="26" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Project Rules

-

Create .opencode/AGENTS.md or AGENTS.md:

-
# Project Guidelines
+"
+						lang="en-US"
+						class="invert"
+						data-marpit-pagination="26"
+						style="
+							--class: invert;
+							--paginate: true;
+							--theme: uncover;
+							--style: @import
+								url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap");
+						"
+						data-marpit-pagination-total="36"
+					>
+						

Project Rules

+

Create .opencode/AGENTS.md or AGENTS.md:

+
+							# Project Guidelines
 
-- Use TypeScript for all new files
-- Follow existing code patterns
-- Run tests before committing
-
-
-
- Use TypeScript for all new files + - Follow existing code patterns + - Run tests before committing + + +
+
+
Project Rules font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="27" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Sharing & Collaboration

-
-
+

Sharing & Collaboration

+
+
+
Sharing & Collaboration font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="28" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Share Conversations

-
/share
-
-

Creates a shareable link:
-https://opencode.ai/s/abc123

-

Unshare with /unshare

-
-
+

Share Conversations

+
/share 
+

Creates a shareable link:
https://opencode.ai/s/abc123

+

Unshare with /unshare

+
+
+
Share Conversations font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="29" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Export Conversations

-
/export
-
-

Opens conversation as Markdown in your editor

-

Set your editor:

-
export EDITOR="code --wait"
-
-
-
+

Export Conversations

+
/export 
+

Opens conversation as Markdown in your editor

+

Set your editor:

+
+							export EDITOR="code --wait"
+							
+						
+
+
+
Export Conversations font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="30" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Pro Tips

-
-
+

Pro Tips

+
+
+
Pro Tips font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="31" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Effective Prompting

-
    -
  1. Be specific - Reference files with @
  2. -
  3. Provide context - Explain the "why"
  4. -
  5. Use images - Drag & drop into terminal
  6. -
  7. Iterate - Plan first, then build
  8. -
-
-
+

Effective Prompting

+
    +
  1. Be specific - Reference files with @
  2. +
  3. Provide context - Explain the "why"
  4. +
  5. Use images - Drag & drop into terminal
  6. +
  7. Iterate - Plan first, then build
  8. +
+
+
+
Effective Prompting font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="32" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Workflow Example

-
<Tab to Plan mode>
-
-When a user deletes a note, flag it as deleted.
-Create a screen showing recently deleted notes.
-Allow undelete or permanent delete.
-
-<Review plan, provide feedback>
-
-<Tab to Build mode>
-
-Sounds good! Make the changes.
-
-
-
+

Workflow Example

+
+							<Tab to Plan mode> When a user deletes a note, flag it as deleted. Create a
+								screen showing recently deleted notes. Allow undelete or permanent delete.
+								<Review plan, provide feedback> <Tab to Build mode> Sounds good! Make
+								the changes.
+							
+						
+
+
+
Workflow Example font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="33" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Best Practices

-
    -
  • Commit AGENTS.md to your repo
  • -
  • Use Plan mode for complex features
  • -
  • Run /compact to reduce context size
  • -
  • Create custom agents for repetitive tasks
  • -
-
-
+

Best Practices

+
    +
  • Commit AGENTS.md to your repo
  • +
  • Use Plan mode for complex features
  • +
  • Run /compact to reduce context size
  • +
  • Create custom agents for repetitive tasks
  • +
+
+
+
Best Practices font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="34" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Demo

-
-
+

Demo

+
+
+
Demo font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="35" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

One More Thing...

-
-
+

One More Thing...

+
+
+
One More Thing... font-family: 'IBM Plex Mono', monospace; font-size: 0.6em; } -" lang="en-US" class="invert" data-marpit-pagination="36" style="--class:invert;--paginate:true;--theme:uncover;--style:@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap') -;" data-marpit-pagination-total="36"> -

Thank You!

-

Questions?

-
-

- Welcome everyone -- Today we're walking through OpenCode -- Open source AI coding agent -- Show you how to get started and key features

- OpenCode is an AI coding assistant -- Multiple interfaces: terminal, desktop, IDE -- Works with ANY LLM provider - not locked in

Why OpenCode? -- Fully open source on GitHub -- Use Claude, GPT, Gemini, local models - your choice -- Everything is customizable (and if it isn't it is cause it's open source) -- Extend with MCP protocol for external tools -- Actually polished and stable - Best terminal experience of any AI coding tool

- Let's dive into setup -- Very quick to get running

- One-liner install script is easiest -- Also available via Homebrew, npm, Chocolatey -- Cross-platform: Mac, Linux, Windows -- Takes about 30 seconds

- First thing: connect to a provider -- /connect command in the TUI -- OpenCode Zen is their managed service - easy start -- Or bring your own API keys -- Supports all major providers

- Navigate to your project -- Run opencode to start the TUI -- /init analyzes your codebase -- Creates AGENTS.md with project context -- Commit this file to your repo -- Helps the AI understand your project

- Now let's look at the key features -- Things you'll use every day

- @ symbol triggers file search -- Fuzzy matching - don't need exact path -- File content automatically added to context -- Great for asking questions about specific code

- AI made wrong changes? Just /undo -- Reverts conversation AND file changes -- Uses Git internally for file restoration -- Project must be a Git repo -- Can undo multiple times -- /redo brings changes back

- Bang prefix runs shell commands -- Output goes into conversation -- AI can see the results -- Great for running tests, checking status -- Stays in context for follow-up questions

- Agents are a powerful feature -- Specialized AI assistants for different tasks

- Two types: Primary and Subagents -- Build mode: full access, can edit files -- Plan mode: read-only, just analyzes and suggests -- Tab key switches between them -- Subagents invoked with @ mention -- General for complex research -- Explore for quick codebase navigation

- Create your own agents -- Markdown files with frontmatter config -- Define what tools they can access -- Write custom system prompts -- Example: security auditor, code reviewer -- Disable write/edit for read-only agents

- Three permission levels -- Allow: just do it -- Ask: prompt before executing -- Deny: completely blocked -- Configure per-agent -- Great for safety guardrails

- MCP extends OpenCode with external tools -- Standard protocol for AI tool integration

- Model Context Protocol -- Two types: local and remote -- Local: run commands, scripts -- Remote: connect to APIs, services -- Once configured, AI can use them -- No extra prompting needed

- Remote MCP connects to web services -- Example: Sentry for error tracking -- Add to opencode.json config -- OAuth authentication supported -- Run mcp auth command to connect -- Then ask AI about your Sentry issues

- Local MCP runs on your machine -- Specify command to start server -- Pass environment variables -- Can use env: syntax for secrets -- Many community MCP servers available

- Quick reference for common commands -- Keyboard shortcuts for power users

- /help shows all available commands -- /init creates project context file -- /new starts fresh conversation -- /sessions to resume previous work -- /share creates shareable link -- /compact summarizes to reduce context

- Tab switches Build/Plan mode -- ctrl+x is the leader key -- All shortcuts start with ctrl+x -- Fully customizable in config -- h for help, n for new, u for undo -- Learn these for faster workflow

- OpenCode is highly customizable -- Make it your own

- Multiple built-in themes -- /themes command to browse -- Set in config for persistence -- Community themes available too

- Main config in ~/.config/opencode/ -- JSON with schema for autocomplete -- Set default provider and model -- Configure everything here -- Keybinds, themes, agents, MCP servers

- Project-specific instructions -- Goes in root or .opencode folder -- AI reads this for context -- Define coding standards -- Specify conventions -- Commit to your repo

- Share your AI conversations -- Great for team collaboration

- /share creates public link -- Anyone with link can view -- Great for code reviews -- Share debugging sessions -- /unshare to revoke access -- Not shared by default

- Export to Markdown file -- Opens in your configured editor -- Set EDITOR environment variable -- Use --wait flag for GUI editors -- Good for documentation

- Tips from power users -- Get the most out of OpenCode

- Reference specific files with @ -- Give context: why you need this -- Can drag images into terminal -- Start in Plan mode, review, then Build -- Treat it like a junior developer

- Real workflow example -- Start in Plan mode -- Describe the feature clearly -- Review the suggested approach -- Give feedback, iterate -- Switch to Build when ready -- Confirm to implement

- AGENTS.md should be in version control -- Plan mode prevents accidental changes -- /compact when context gets too long -- Custom agents save time on repeated work

- Live demonstration -- Show actual usage -- Walk through a real task

- One more exciting thing to share -- [Add your surprise content here]

- Thanks for attending -- Install command on screen -- Happy to answer questions -- Resources: opencode.ai/docs -- GitHub: github.com/anomalyco/opencode -- Discord: opencode.ai/discord

\ No newline at end of file +" + lang="en-US" + class="invert" + data-marpit-pagination="36" + style=" + --class: invert; + --paginate: true; + --theme: uncover; + --style: @import + url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap"); + " + data-marpit-pagination-total="36" + > +

Thank You!

+

Questions?

+ + + + +
+

+ - Welcome everyone - Today we're walking through OpenCode - Open source AI coding agent - + Show you how to get started and key features +

+
+
+

+ - OpenCode is an AI coding assistant - Multiple interfaces: terminal, desktop, IDE - Works + with ANY LLM provider - not locked in +

+
+
+

+ Why OpenCode? - Fully open source on GitHub - Use Claude, GPT, Gemini, local models - your + choice - Everything is customizable (and if it isn't it is cause it's open source) - Extend + with MCP protocol for external tools - Actually polished and stable - Best terminal + experience of any AI coding tool +

+
+
+

- Let's dive into setup - Very quick to get running

+
+
+

+ - One-liner install script is easiest - Also available via Homebrew, npm, Chocolatey - + Cross-platform: Mac, Linux, Windows - Takes about 30 seconds +

+
+
+

+ - First thing: connect to a provider - /connect command in the TUI - OpenCode Zen is their + managed service - easy start - Or bring your own API keys - Supports all major providers +

+
+
+

+ - Navigate to your project - Run opencode to start the TUI - /init analyzes your codebase - + Creates AGENTS.md with project context - Commit this file to your repo - Helps the AI + understand your project +

+
+
+

- Now let's look at the key features - Things you'll use every day

+
+
+

+ - @ symbol triggers file search - Fuzzy matching - don't need exact path - File content + automatically added to context - Great for asking questions about specific code +

+
+
+

+ - AI made wrong changes? Just /undo - Reverts conversation AND file changes - Uses Git + internally for file restoration - Project must be a Git repo - Can undo multiple times - + /redo brings changes back +

+
+
+

+ - Bang prefix runs shell commands - Output goes into conversation - AI can see the results - + Great for running tests, checking status - Stays in context for follow-up questions +

+
+
+

- Agents are a powerful feature - Specialized AI assistants for different tasks

+
+
+

+ - Two types: Primary and Subagents - Build mode: full access, can edit files - Plan mode: + read-only, just analyzes and suggests - Tab key switches between them - Subagents invoked + with @ mention - General for complex research - Explore for quick codebase navigation +

+
+
+

+ - Create your own agents - Markdown files with frontmatter config - Define what tools they + can access - Write custom system prompts - Example: security auditor, code reviewer - + Disable write/edit for read-only agents +

+
+
+

+ - Three permission levels - Allow: just do it - Ask: prompt before executing - Deny: + completely blocked - Configure per-agent - Great for safety guardrails +

+
+
+

- MCP extends OpenCode with external tools - Standard protocol for AI tool integration

+
+
+

+ - Model Context Protocol - Two types: local and remote - Local: run commands, scripts - + Remote: connect to APIs, services - Once configured, AI can use them - No extra prompting + needed +

+
+
+

+ - Remote MCP connects to web services - Example: Sentry for error tracking - Add to + opencode.json config - OAuth authentication supported - Run mcp auth command to connect - + Then ask AI about your Sentry issues +

+
+
+

+ - Local MCP runs on your machine - Specify command to start server - Pass environment + variables - Can use env: syntax for secrets - Many community MCP servers available +

+
+
+

- Quick reference for common commands - Keyboard shortcuts for power users

+
+
+

+ - /help shows all available commands - /init creates project context file - /new starts + fresh conversation - /sessions to resume previous work - /share creates shareable link - + /compact summarizes to reduce context +

+
+
+

+ - Tab switches Build/Plan mode - ctrl+x is the leader key - All shortcuts start with ctrl+x + - Fully customizable in config - h for help, n for new, u for undo - Learn these for faster + workflow +

+
+
+

- OpenCode is highly customizable - Make it your own

+
+
+

+ - Multiple built-in themes - /themes command to browse - Set in config for persistence - + Community themes available too +

+
+
+

+ - Main config in ~/.config/opencode/ - JSON with schema for autocomplete - Set default + provider and model - Configure everything here - Keybinds, themes, agents, MCP servers +

+
+
+

+ - Project-specific instructions - Goes in root or .opencode folder - AI reads this for + context - Define coding standards - Specify conventions - Commit to your repo +

+
+
+

- Share your AI conversations - Great for team collaboration

+
+
+

+ - /share creates public link - Anyone with link can view - Great for code reviews - Share + debugging sessions - /unshare to revoke access - Not shared by default +

+
+
+

+ - Export to Markdown file - Opens in your configured editor - Set EDITOR environment + variable - Use --wait flag for GUI editors - Good for documentation +

+
+
+

- Tips from power users - Get the most out of OpenCode

+
+
+

+ - Reference specific files with @ - Give context: why you need this - Can drag images into + terminal - Start in Plan mode, review, then Build - Treat it like a junior developer +

+
+
+

+ - Real workflow example - Start in Plan mode - Describe the feature clearly - Review the + suggested approach - Give feedback, iterate - Switch to Build when ready - Confirm to + implement +

+
+
+

+ - AGENTS.md should be in version control - Plan mode prevents accidental changes - /compact + when context gets too long - Custom agents save time on repeated work +

+
+
+

- Live demonstration - Show actual usage - Walk through a real task

+
+
+

- One more exciting thing to share - [Add your surprise content here]

+
+
+

+ - Thanks for attending - Install command on screen - Happy to answer questions - Resources: + opencode.ai/docs - GitHub: github.com/anomalyco/opencode - Discord: opencode.ai/discord +

+
+ + + +