diff --git a/docs/docent-rust-demo.mp4 b/docs/docent-rust-demo.mp4 new file mode 100644 index 0000000..bc1189c Binary files /dev/null and b/docs/docent-rust-demo.mp4 differ diff --git a/src/components/DocentInstall/index.tsx b/src/components/DocentInstall/index.tsx index c086758..0dacde9 100644 --- a/src/components/DocentInstall/index.tsx +++ b/src/components/DocentInstall/index.tsx @@ -6,20 +6,38 @@ type Shell = 'unix' | 'windows'; // smartloop.ai/install* redirect (Cloudflare) to the scripts in the docent // repo, so the one-liners always hand out the current script. The deploy also // copies them into the site as a fallback (.github/workflows/deploy-pages.yml). -const installCommands: Record = { +export const installCommands: Record = { unix: 'curl -fsSL https://smartloop.ai/install | sh', windows: 'irm https://smartloop.ai/install.ps1 | iex', }; -// `location` tags the copy event in analytics (homepage vs /docent). -export default function DocentInstall({ location = 'home' }: { location?: string }): ReactNode { - const [shell, setShell] = useState('unix'); - const [copied, setCopied] = useState(false); +function CopyIcon({ done }: { done: boolean }): ReactNode { + const props = { width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round' as const, strokeLinejoin: 'round' as const, 'aria-hidden': true }; + return done + ? + : ; +} - // Windows visitors land on the PowerShell line; everyone else gets curl. +// The visitor's shell: Windows gets the PowerShell line, everyone else curl. +// Client Hints first (Chromium), then the legacy fields; macOS and Linux +// share the curl line, so only Windows needs spotting. Renders as unix on the +// server and switches after hydration. +export function useInstallShell(): Shell { + const [shell, setShell] = useState('unix'); useEffect(() => { - if (/win/i.test(navigator.platform) || /windows/i.test(navigator.userAgent)) setShell('windows'); + const nav = navigator as Navigator & { userAgentData?: { platform?: string } }; + const platform = nav.userAgentData?.platform || nav.platform || ''; + if (/^win/i.test(platform) || /windows/i.test(nav.userAgent)) setShell('windows'); }, []); + return shell; +} + +// The install line for the visitor's platform, with a copy button. No +// platform switch: the shell is detected. `location` tags the copy event in +// analytics (homepage vs /docent); `variant="hero"` centres it. +export default function DocentInstall({ location = 'home', variant = 'card' }: { location?: string; variant?: 'card' | 'hero' }): ReactNode { + const shell = useInstallShell(); + const [copied, setCopied] = useState(false); const copy = async () => { try { @@ -33,24 +51,18 @@ export default function DocentInstall({ location = 'home' }: { location?: string }; return ( -
-
- {([['unix', 'macOS · Linux'], ['windows', 'Windows']] as const).map(([key, label]) => ( - - ))} -
+
{shell === 'windows' ? '>' : '$'}{installCommands[shell]} - +
); diff --git a/src/components/DocentInstall/styles.module.css b/src/components/DocentInstall/styles.module.css index 354e527..a26949a 100644 --- a/src/components/DocentInstall/styles.module.css +++ b/src/components/DocentInstall/styles.module.css @@ -1,68 +1,38 @@ -/* The Docent install one-liner, with a tab per shell and a copy button. - Drawn as one of the site's soft cards: rounded, lightly lifted, with the - shell choice as a segmented pill rather than a ruled tab strip. */ +/* The Docent install one-liner for the visitor's platform: one command bar + with an icon-only copy button, so the command is what reads first. + Left-aligned by default (it sits in a text column on the homepage); + the hero variant centres it. */ .install { display: flex; flex-direction: column; + align-items: flex-start; + gap: 12px; width: 100%; - max-width: 520px; + max-width: 560px; min-width: 0; box-sizing: border-box; - border: 1px solid var(--sl-line); - border-radius: 14px; - background: var(--sl-panel); - box-shadow: 0 1px 2px rgba(10, 10, 20, 0.04), 0 12px 40px -12px rgba(10, 10, 20, 0.12); - overflow: hidden; -} - -[data-theme='dark'] .install { - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 12px 40px -12px rgba(0, 0, 0, 0.6); -} - -.installTabs { - display: flex; - align-self: flex-start; - gap: 2px; - margin: 10px 10px 0; - padding: 3px; - border-radius: 10px; - background: var(--sl-ground); - border: 1px solid var(--sl-line); -} - -.installTab { - background: none; - border: none; - border-radius: 7px; - padding: 5px 12px; - font-family: var(--sl-display); - font-size: 0.8rem; - font-weight: 500; - color: var(--sl-dim); - cursor: pointer; - transition: background 0.18s, color 0.18s, box-shadow 0.18s; -} - -.installTab:hover { - color: var(--sl-white); } -.installTabActive { - color: var(--sl-white); - background: var(--sl-panel); - box-shadow: 0 1px 2px rgba(10, 10, 20, 0.08); +.hero { + max-width: 600px; + align-items: center; } .installRow { display: flex; align-items: center; - gap: 10px; - margin: 10px; - padding: 8px 8px 8px 14px; + align-self: stretch; + gap: 14px; min-width: 0; - border-radius: 10px; - background: var(--sl-ground); + padding: 10px 10px 10px 20px; + border-radius: 14px; border: 1px solid var(--sl-line); + background: var(--sl-panel); + box-shadow: 0 1px 2px rgba(10, 10, 20, 0.04), 0 12px 40px -12px rgba(10, 10, 20, 0.12); +} + +[data-theme='dark'] .installRow { + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 12px 40px -12px rgba(0, 0, 0, 0.6); } .installRow code { @@ -70,14 +40,20 @@ min-width: 0; overflow-x: auto; white-space: nowrap; + scrollbar-width: none; background: none; border: none; padding: 0; + text-align: left; font-family: var(--sl-mono); - font-size: 0.8rem; + font-size: 0.88rem; color: var(--sl-white); } +.hero .installRow code { + font-size: 0.92rem; +} + .installRow code span { color: var(--sl-accent-text); margin-right: 10px; @@ -86,19 +62,28 @@ .copy { flex: none; - background: var(--sl-accent); - color: var(--sl-on-accent); - border: none; + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + padding: 0; border-radius: 8px; - padding: 7px 14px; - font-family: var(--sl-display); - font-size: 0.8rem; - font-weight: 500; + border: 1px solid var(--sl-line); + background: var(--sl-ground); + color: var(--sl-dim); cursor: pointer; - box-shadow: 0 6px 18px -8px rgba(236, 72, 153, 0.7); - transition: background 0.18s; + transition: border-color 0.18s, color 0.18s; } .copy:hover { - background: var(--sl-accent-soft); + border-color: var(--sl-accent); + color: var(--sl-accent-text); +} + +@media (max-width: 640px) { + .installRow code, + .hero .installRow code { + font-size: 0.78rem; + } } diff --git a/src/components/Footer/index.tsx b/src/components/Footer/index.tsx index 4ce5add..be17366 100644 --- a/src/components/Footer/index.tsx +++ b/src/components/Footer/index.tsx @@ -7,6 +7,7 @@ export default function Footer() { Made in San Francisco, CA
Docs + Partners Terms Privacy
  • Get started
  • +
  • Docent
  • Docs
  • -
  • Partners
diff --git a/src/pages/docent.module.css b/src/pages/docent.module.css new file mode 100644 index 0000000..e8632bd --- /dev/null +++ b/src/pages/docent.module.css @@ -0,0 +1,154 @@ +/* /docent only: tweaks on top of home.module.css. */ + +/* Three steps, three columns (the homepage grid is four wide). */ +.steps { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +/* The thing you type at each step: one line, scrolls rather than wraps. */ +.stepCode { + display: block; + margin: 10px 0 12px; + padding: 10px 12px; + overflow-x: auto; + white-space: nowrap; + scrollbar-width: none; + border: 1px solid var(--sl-line); + border-radius: 10px; + background: var(--sl-panel); + font-family: var(--sl-mono); + font-size: 0.76rem; + color: var(--sl-white); +} + +/* The page ends here, so leave room above the footer. */ +.lastSection { + padding-bottom: 112px; +} + +@media (max-width: 640px) { + .steps { + grid-template-columns: 1fr; + row-gap: 32px; + } + + .lastSection { + padding-bottom: 72px; + } +} + +/* ── how it works: five stages as rows, title left and detail right, + so each has room to read (five columns were cramped). ── */ +.loop { + list-style: none; + margin: 36px 0 0; + padding: 0; + border-top: 1px solid var(--sl-line); +} + +.loop li { + display: grid; + grid-template-columns: 48px minmax(0, 1fr) minmax(0, 1.6fr); + gap: 24px; + align-items: baseline; + padding: 22px 0; + border-bottom: 1px solid var(--sl-line); +} + +.loopNum { + font-family: var(--sl-mono); + font-size: 0.72rem; + color: var(--sl-accent-text); +} + +.loop h3, +.loop p { + margin: 0; +} + +/* ── languages table ── */ +.table { + margin-top: 36px; + border: 1px solid var(--sl-line); + border-radius: 14px; + overflow: hidden; + background: var(--sl-panel); +} + +.tableHead, +.tableRow { + display: grid; + grid-template-columns: 1.1fr 1.4fr 1fr; + gap: 16px; + align-items: center; + padding: 12px 20px; +} + +.tableHead { + font-family: var(--sl-mono); + font-size: 0.68rem; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--sl-dim); + border-bottom: 1px solid var(--sl-line); +} + +.tableRow + .tableRow { + border-top: 1px solid var(--sl-line); +} + +.tableRow span { + color: var(--sl-white); + font-size: 0.92rem; +} + +.tableRow code { + justify-self: start; + font-size: 0.8rem; +} + +/* ── model / platform cards ── */ +.cards { + margin-top: 36px; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 20px; +} + +.card { + padding: 22px; + border: 1px solid var(--sl-line); + border-radius: 14px; + background: var(--sl-panel); +} + +.card h3 { + margin-top: 0; +} + +@media (max-width: 900px) { + .cards { + grid-template-columns: 1fr; + } +} + +@media (max-width: 640px) { + .loop li { + grid-template-columns: 36px minmax(0, 1fr); + row-gap: 8px; + } + + .loop li p { + grid-column: 2; + } + + .tableHead, + .tableRow { + grid-template-columns: 1fr; + gap: 4px; + } + + .tableHead { + display: none; + } +} diff --git a/src/pages/docent.tsx b/src/pages/docent.tsx index f223855..0345b2b 100644 --- a/src/pages/docent.tsx +++ b/src/pages/docent.tsx @@ -4,8 +4,9 @@ import Head from '@docusaurus/Head'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import Header from '../components/Header'; import Footer from '../components/Footer'; -import DocentInstall from '../components/DocentInstall'; +import DocentInstall, { installCommands, useInstallShell } from '../components/DocentInstall'; import styles from './home.module.css'; +import local from './docent.module.css'; // smartloop.ai/docent: the page to link from posts and launches, so docent // traffic shows up as its own page in analytics (with any utm_* tags on the @@ -21,12 +22,93 @@ const points: [string, string][] = [ ['Open models.', 'No vendor lock-in: pick the model, and run it locally so your code stays private.'], ]; -const steps: [string, string][] = [ - ['Install', 'Run the one-liner above. It puts docent on your PATH.'], - ['Open a project', 'cd into a repo and run docent. It downloads the models it needs the first time.'], - ['Ask for a change', 'Describe what you want. docent writes it, builds it and fixes what breaks.'], +// What you type at each step, then what docent does with it. +const steps: { title: string; code: string; desc: string }[] = [ + { + title: 'Install', + code: installCommands.unix, // swapped for the visitor's shell below + desc: 'One line puts docent on your PATH. macOS, Linux and Windows.', + }, + { + title: 'Open a project', + code: 'cd my-app && docent', + desc: 'The first run fetches what it needs. Type /models to pick an open model, like MiniMax M3.', + }, + { + title: 'Ask for a change', + code: '> add a CLI that asks for a name and greets it', + desc: 'docent writes the diff, builds it, fixes what breaks, and asks before it runs anything.', + }, ]; +// ── Long-form content: the detail people (and search engines) look for. +// Every claim here comes from the docent README and the coding docs. + +// What happens on each prompt, in order. +const loop: [string, string][] = [ + ['It reads the project.', 'The file tree, the files your prompt is about and the git state (branch, remotes, what changed), plus your OS, shell, package managers and toolchains, so it answers from your code and uses brew on a Mac and apt-get on Ubuntu.'], + ['You see diffs as they stream.', 'Each file shows as numbered hunks, additions in green and removals in red, and is written into the folder as soon as it is done. Changes to files that already exist ask first.'], + ['It checks the build and fixes it.', 'A fast check for the kind of project runs after every change. Errors go back to the model, round after round, until the check passes.'], + ['Commands wait for your OK.', 'Commits, installs and test runs come back as shell commands. Enter runs them, Tab approves the rest of the session, Esc skips. Their output goes back to the agent, so a failing test gets fixed next.'], + ['Long runs go to the background.', 'Dev servers and watchers keep running under the prompt while you keep chatting. /tasks shows them and /stop ends them.'], +]; + +// Language → the language server and the build check docent uses. +const stack: [string, string, string][] = [ + ['Rust', 'rust-analyzer', 'cargo check'], + ['Go', 'gopls', 'go build'], + ['TypeScript · JavaScript', 'typescript-language-server', 'tsc --noEmit'], + ['Python', 'pyright', 'py_compile'], + ['C · C++', 'clangd', 'your build'], + ['Shell scripts', '—', 'bash -n'], +]; + +const beyond: [string, string][] = [ + ['Chat with your files', 'Drop a PDF, Office file, CSV, text file or image on the terminal and ask about it. Documents are indexed on your machine.'], + ['Web search', 'Sign in with /login for AI search, so answers can cite current docs and library APIs, with their sources.'], + ['MCP servers', 'Connect tools with /mcp add and docent can call them while it works.'], + ['Scriptable', 'Pipe a plain answer into your scripts with --plain, or run docent over SSH.'], +]; + +const faqs: { q: string; a: string }[] = [ + { + q: 'What is docent?', + a: 'docent is an open-source AI coding agent for the terminal, made by Smartloop. Open it in a project, ask for a change, and it writes the code as diffs, checks it with your language servers and build, fixes its own errors, and runs commands like commits and tests once you approve them. It also answers questions about your documents and the web.', + }, + { + q: 'Is docent free and open source?', + a: 'Yes. docent is MIT licensed, with the source on GitHub, and it is free to use with no account. Signing in is optional: it unlocks AI web search and the cloud models, which draw on a monthly allowance.', + }, + { + q: 'Which models does docent use?', + a: 'Open models. Small ones such as Qwen, Gemma and Ministral run on your own machine, and larger open models such as MiniMax M3, GLM and the bigger Qwen models are available in the cloud when you sign in. Pick and switch models with /models; the one that suits your machine is marked.', + }, + { + q: 'Does my code leave my machine?', + a: 'Not with local models: docent runs the model and its agent on your computer, with GPU acceleration through Metal on macOS and Vulkan on Linux and Windows. Cloud models are marked in /models, and web search sends only the search query, never your files.', + }, + { + q: 'Which languages does docent support?', + a: 'Any language it can read, with built-in checks for Rust (cargo check), Go (go build), TypeScript and JavaScript (tsc --noEmit), Python (py_compile) and shell scripts (bash -n). It also uses your language servers, such as rust-analyzer, gopls, typescript-language-server, pyright and clangd, to catch errors.', + }, + { + q: 'Which platforms does it run on?', + a: 'macOS on Apple Silicon (signed and notarized), Linux on x86_64 and arm64, and Windows on x86_64 (signed). Install with one command: curl -fsSL https://smartloop.ai/install | sh on macOS and Linux, or irm https://smartloop.ai/install.ps1 | iex in PowerShell.', + }, + { + q: 'Will it run commands without asking?', + a: 'No. Files it creates are written straight away, but changes to existing files ask first, and every shell command (a commit, an install, a test run) waits for you to press Enter. Press Tab to approve the rest of a session, or Esc to skip.', + }, +]; + +function GitHubIcon(): ReactNode { + return ( + + ); +} + function Check(): ReactNode { return ( @@ -37,6 +119,7 @@ function Check(): ReactNode { export default function Docent(): ReactNode { const { siteConfig } = useDocusaurusContext(); + const shell = useInstallShell(); // Latest Docent CLI version, baked in at build time (docusaurus.config.ts). const docentVersion = siteConfig.customFields?.docentVersion as string | null | undefined; @@ -62,6 +145,16 @@ export default function Docent(): ReactNode { author: { '@type': 'Organization', name: 'Smartloop Inc.', url: 'https://smartloop.ai' }, }; + const faqJsonLd = { + '@context': 'https://schema.org', + '@type': 'FAQPage', + mainEntity: faqs.map(({ q, a }) => ({ + '@type': 'Question', + name: q, + acceptedAnswer: { '@type': 'Answer', text: a }, + })), + }; + return (
@@ -71,6 +164,7 @@ export default function Docent(): ReactNode { +
@@ -86,67 +180,190 @@ export default function Docent(): ReactNode {

{DESCRIPTION}

- -
- GitHub - full reference -
+

free · macOS, Linux, Windows · no account

+ {/* Secondary on purpose: the install line is the call to action. */} +

+ + + + + full reference +

+ {/* ── Demo: install to a running Rust program, narrated ── */}
-
-
-

What it does

-

Ask for a change. Get a build that passes.

-
    - {points.map(([title, desc]) => ( -
  • {title} {desc}
  • - ))} -
+
+ {/* The recording carries its own window and desktop, so it + gets no frame of ours on top. It plays on request, muted by + default; the narration is there if you unmute. */} +
+
-
-
- - docent - local +

+ install → pick an open model → a Rust program that builds and runs · 1:33 +

+
+
+ +
+
+

What it does

+

Ask for a change. Get a build that passes.

+
    + {points.map(([title, desc]) => ( +
  • {title} {desc}
  • + ))} +
+
+
+ + {/* ── Each prompt, step by step ─────────────────── */} +
+
+

How it works

+

What happens when you ask for a change.

+
    + {loop.map(([title, desc], i) => ( +
  1. + {String(i + 1).padStart(2, '0')} +

    {title}

    +

    {desc}

    +
  2. + ))} +
+

+ More in the coding guide. +

+
+
+ + {/* ── Languages ─────────────────────────────────── */} +
+
+

Works with your stack

+

Checked by the tools you already use.

+

+ docent reads code in any language. For these it also knows the language server and the build check, so errors are caught and fixed before you see them. +

+
+
+ Language + Language server + Build check
- {/* One recording per site theme; CSS hides the other. */} - {(['light', 'dark'] as const).map((theme) => ( -
+ {/* ── Models and platforms ──────────────────────── */}
-

Get started

-

Three steps, then you're coding.

-
    - {steps.map(([title, desc], i) => ( +

    Open models

    +

    Your model, your machine.

    +
    +
    +

    Local, on your GPU

    +

    + Small open models such as Qwen, Gemma and Ministral run on your computer, through Metal on macOS and Vulkan on Linux and Windows. Your code stays on the machine. +

    +
    +
    +

    Bigger open models, when you need them

    +

    + Sign in to add larger open models such as MiniMax M3, GLM and the bigger Qwen models. Switch any time with /models; no vendor lock-in. +

    +
    +
    +

    macOS, Linux, Windows

    +

    + Signed builds for Apple Silicon Macs, Linux on x86_64 and arm64, and Windows on x86_64. One command installs it; no account needed. +

    +
    +
    +
+
+ + {/* ── Beyond code ───────────────────────────────── */} +
+
+

More than code

+

An agent harness, not just an autocomplete.

+
    + {beyond.map(([title, desc]) => ( +
  • {title}. {desc}
  • + ))} +
+

+ Every command and shortcut is in the command-line reference; see also documents and MCP servers. +

+
+
+ +
+
+

Get started

+

From install to a passing build.

+

Three things to type. docent does the rest.

+
    + {steps.map(({ title, code, desc }, i) => (
  1. {String(i + 1).padStart(2, '0')}

    {title}

    + {i === 0 ? installCommands[shell] : code}

    {desc}

  2. ))}
+ + {/* ── FAQ ───────────────────────────────────────── */} +
+
+
+

FAQ

+

Questions, answered.

+
+
+ {faqs.map(({ q, a }) => ( +
+ {q} +

{a}

+
+ ))} +
+
+
+ +
-
); diff --git a/static/img/docent-rust-demo-poster.jpg b/static/img/docent-rust-demo-poster.jpg new file mode 100644 index 0000000..42fb899 Binary files /dev/null and b/static/img/docent-rust-demo-poster.jpg differ