+ {children}
+
+
+ );
+}
diff --git a/.docs/site/app/page.tsx b/.docs/site/app/page.tsx
new file mode 100644
index 0000000..858ea5c
--- /dev/null
+++ b/.docs/site/app/page.tsx
@@ -0,0 +1,38 @@
+import Link from "next/link";
+
+const title = "SQL Studio";
+const description = "Single binary, single command SQL database explorer for SQLite, PostgreSQL, MySQL, DuckDB, ClickHouse, MSSQL, Parquet, CSV, and more.";
+
+export default function HomePage() {
+ return (
+
+
+
+
+ Documentation
+
+
{title}
+
{description}
+
+ Author markdown in{" "}
+
+ docs/
+
+ . Everything under{" "}
+ /docs is synced from that folder.
+
+
+
+ Open documentation
+
+
+
+
+ );
+}
diff --git a/.docs/site/docs.config.tsx b/.docs/site/docs.config.tsx
new file mode 100644
index 0000000..d42e925
--- /dev/null
+++ b/.docs/site/docs.config.tsx
@@ -0,0 +1,28 @@
+import { defineDocs } from "@farming-labs/docs";
+import { colorful } from "@farming-labs/theme/colorful";
+
+export default defineDocs({
+ entry: "docs",
+ theme: colorful(),
+ ordering: [
+ {
+ "slug": "quickstart"
+ },
+ {
+ "slug": "installation"
+ },
+ {
+ "slug": "configuration"
+ },
+ {
+ "slug": "databases"
+ },
+ {
+ "slug": "features"
+ }
+ ],
+ metadata: {
+ titleTemplate: "%s – Docs",
+ description: "Managed by @farming-labs/docs Cloud",
+ },
+});
diff --git a/.docs/site/next-env.d.ts b/.docs/site/next-env.d.ts
new file mode 100644
index 0000000..9edff1c
--- /dev/null
+++ b/.docs/site/next-env.d.ts
@@ -0,0 +1,6 @@
+///
+///
+import "./.next/types/routes.d.ts";
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/.docs/site/next.config.ts b/.docs/site/next.config.ts
new file mode 100644
index 0000000..948bed4
--- /dev/null
+++ b/.docs/site/next.config.ts
@@ -0,0 +1,12 @@
+import { dirname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+import { withDocs } from "@farming-labs/next/config";
+
+const appDir = dirname(fileURLToPath(import.meta.url));
+const root = join(appDir, "../..");
+
+export default withDocs({
+ turbopack: {
+ root,
+ },
+});
diff --git a/.docs/site/package.json b/.docs/site/package.json
new file mode 100644
index 0000000..a3012c4
--- /dev/null
+++ b/.docs/site/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "docs-cloud-managed-runtime",
+ "private": true,
+ "packageManager": "pnpm@10.9.0",
+ "scripts": {
+ "sync:content": "node ./scripts/sync-managed-content.mjs",
+ "dev": "node ./scripts/sync-managed-content.mjs && next dev --turbopack",
+ "build": "node ./scripts/sync-managed-content.mjs && next build --turbopack",
+ "start": "node ./scripts/sync-managed-content.mjs && next start"
+ },
+ "dependencies": {
+ "@farming-labs/docs": "latest",
+ "@farming-labs/next": "latest",
+ "@farming-labs/theme": "latest",
+ "next": "16.2.3",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "zod": "^4.1.0"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4.1.18",
+ "@types/mdx": "^2.0.13",
+ "@types/node": "^22.10.0",
+ "@types/react": "^19.2.0",
+ "@types/react-dom": "^19.2.0",
+ "postcss": "^8.5.6",
+ "tailwindcss": "^4.1.18",
+ "typescript": "^5.9.3"
+ }
+}
diff --git a/.docs/site/postcss.config.mjs b/.docs/site/postcss.config.mjs
new file mode 100644
index 0000000..61e3684
--- /dev/null
+++ b/.docs/site/postcss.config.mjs
@@ -0,0 +1,7 @@
+const config = {
+ plugins: {
+ "@tailwindcss/postcss": {},
+ },
+};
+
+export default config;
diff --git a/.docs/site/scripts/sync-managed-content.mjs b/.docs/site/scripts/sync-managed-content.mjs
new file mode 100644
index 0000000..bd8e35b
--- /dev/null
+++ b/.docs/site/scripts/sync-managed-content.mjs
@@ -0,0 +1,159 @@
+import { cp, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
+import { basename, dirname, extname, join, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const runtimeRoot = resolve(fileURLToPath(new URL("..", import.meta.url)));
+const repoRoot = resolve(runtimeRoot, "../..");
+const authoredRoots = [
+ { source: resolve(repoRoot, "docs"), target: resolve(runtimeRoot, "app/docs") },
+ { source: resolve(repoRoot, "api-reference"), target: resolve(runtimeRoot, "app/docs/api") },
+];
+const ignoredDirectoryNames = new Set([
+ "node_modules",
+ ".next",
+ ".turbo",
+ ".vercel",
+ "dist",
+ "build",
+ "coverage",
+]);
+const ignoredFileNames = new Set([
+ "bun.lock",
+ "jsconfig.json",
+ "package-lock.json",
+ "package.json",
+ "pnpm-lock.yaml",
+ "tsconfig.json",
+ "yarn.lock",
+]);
+const staticAssetExtensions = new Set([
+ ".avif",
+ ".bmp",
+ ".csv",
+ ".gif",
+ ".ico",
+ ".jpeg",
+ ".jpg",
+ ".json",
+ ".mp4",
+ ".pdf",
+ ".png",
+ ".svg",
+ ".txt",
+ ".webm",
+ ".webp",
+ ".zip",
+]);
+const codeFenceLanguageAliases = new Map([
+ ["dotenv", "bash"],
+ ["env", "bash"],
+ ["shell", "bash"],
+]);
+
+function isMarkdownFile(path) {
+ return [".md", ".mdx"].includes(extname(path).toLowerCase());
+}
+
+function isStaticAssetFile(path) {
+ return staticAssetExtensions.has(extname(path).toLowerCase());
+}
+
+function shouldSkipDirectory(name) {
+ return name.startsWith(".") || ignoredDirectoryNames.has(name);
+}
+
+function shouldSkipFile(name) {
+ return name.startsWith(".") || ignoredFileNames.has(name);
+}
+
+function normalizeMarkdownContent(content) {
+ const fenceMarker = String.fromCharCode(96);
+ const codeFencePattern = new RegExp(
+ "(^|\\n)(" + fenceMarker + "{3,})([A-Za-z0-9_+.-]+)([^\\n" + fenceMarker + "]*)",
+ "g",
+ );
+
+ return content.replace(codeFencePattern, (match, prefix, fence, language, rest = "") => {
+ const normalizedLanguage = codeFenceLanguageAliases.get(language.toLowerCase());
+
+ if (!normalizedLanguage) {
+ return match;
+ }
+
+ return prefix + fence + normalizedLanguage + rest;
+ });
+}
+
+function targetPagePath(targetRoot, relativePath) {
+ const withoutExtension = relativePath.replace(/\.mdx?$/i, "");
+ const routeFileName = basename(withoutExtension).toLowerCase();
+ const isIndexPage = routeFileName === "index" || routeFileName === "page";
+ const targetDirectory = isIndexPage ? dirname(withoutExtension) : withoutExtension;
+ return join(targetRoot, targetDirectory === "." ? "" : targetDirectory, "page.mdx");
+}
+
+async function fileExists(path) {
+ try {
+ await readdir(path);
+ return true;
+ } catch {
+ try {
+ await readFile(path, "utf8");
+ return true;
+ } catch {
+ return false;
+ }
+ }
+}
+
+async function syncAuthoredRoot(sourceRoot, targetRoot) {
+ if (!(await fileExists(sourceRoot))) {
+ return;
+ }
+
+ const visit = async (currentSourceDirectory, relativeDirectory = "") => {
+ const entries = await readdir(currentSourceDirectory, { withFileTypes: true });
+
+ for (const entry of entries) {
+ if (entry.name.startsWith(".")) {
+ continue;
+ }
+
+ const sourcePath = join(currentSourceDirectory, entry.name);
+ const relativePath = relativeDirectory ? join(relativeDirectory, entry.name) : entry.name;
+
+ if (entry.isDirectory()) {
+ if (shouldSkipDirectory(entry.name)) {
+ continue;
+ }
+
+ await visit(sourcePath, relativePath);
+ continue;
+ }
+
+ if (!isMarkdownFile(entry.name)) {
+ if (shouldSkipFile(entry.name) || !isStaticAssetFile(entry.name)) {
+ continue;
+ }
+
+ const targetPath = join(targetRoot, relativePath);
+ await mkdir(dirname(targetPath), { recursive: true });
+ await cp(sourcePath, targetPath, { force: true });
+ continue;
+ }
+
+ const targetPath = targetPagePath(targetRoot, relativePath);
+ await mkdir(dirname(targetPath), { recursive: true });
+ await writeFile(targetPath, normalizeMarkdownContent(await readFile(sourcePath, "utf8")), "utf8");
+ }
+ };
+
+ await visit(sourceRoot);
+}
+
+await rm(resolve(runtimeRoot, "app/docs"), { recursive: true, force: true });
+await mkdir(resolve(runtimeRoot, "app/docs"), { recursive: true });
+
+for (const authoredRoot of authoredRoots) {
+ await syncAuthoredRoot(authoredRoot.source, authoredRoot.target);
+}
diff --git a/.docs/site/tsconfig.json b/.docs/site/tsconfig.json
new file mode 100644
index 0000000..247f602
--- /dev/null
+++ b/.docs/site/tsconfig.json
@@ -0,0 +1,42 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "baseUrl": ".",
+ "paths": {
+ "@/*": [
+ "./*"
+ ]
+ }
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..a928d4e
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,22 @@
+# Docs Maintenance Guide
+Use this file as the handoff checklist for future edits to this documentation PR.
+## Source Layout
+- The docs source lives in `docs/`.
+- `docs.json` is the Docs Cloud configuration for publishing, previews, and content roots.
+- The managed runtime lives in `.docs/site`; edit authored markdown at the repo root instead of generated runtime pages under `.docs/site/app/docs`.
+- Keep every page grounded in README content, package metadata, source exports, CLI help, environment examples, or existing docs.
+## Docs Routes
+- /docs - Introduction
+- /docs/installation - Installation
+- /docs/quickstart - Quickstart
+- /docs/configuration - Configuration
+- /docs/databases - Databases
+- /docs/features - Features
+## Editing Guidelines
+- Prefer reader-facing setup, usage, and troubleshooting notes over source inventories.
+- Do not add commands, flags, environment variables, routes, imports, or framework names unless they are present in the repository.
+- If you add or rename a page, keep its frontmatter title and description accurate and make sure the navigation ordering still includes it.
+- Avoid analyzer language such as generated from, source evidence, implementation map, source surface, or detected in files.
+## Verification
+- Build the docs site with `cd .docs/site && pnpm install && pnpm build` before handing off a docs PR.
+- Open `/docs` and at least one generated leaf page to confirm the sidebar and page content match the PR.
diff --git a/docs.json b/docs.json
new file mode 100644
index 0000000..efe3175
--- /dev/null
+++ b/docs.json
@@ -0,0 +1,26 @@
+{
+ "$schema": "https://docs.farming-labs.dev/schema/docs.json",
+ "version": 1,
+ "docs": {
+ "mode": "frameworkless",
+ "runtime": "nextjs",
+ "root": ".docs/site"
+ },
+ "content": {
+ "docsRoot": "docs",
+ "apiReferenceRoot": "api-reference",
+ "openapi": []
+ },
+ "cloud": {
+ "apiKey": {
+ "env": "DOCS_CLOUD_API_KEY"
+ },
+ "preview": {
+ "enabled": true
+ },
+ "publish": {
+ "mode": "draft-pr",
+ "baseBranch": "main"
+ }
+ }
+}
diff --git a/docs/configuration.mdx b/docs/configuration.mdx
new file mode 100644
index 0000000..d9dbe86
--- /dev/null
+++ b/docs/configuration.mdx
@@ -0,0 +1,63 @@
+---
+title: "Configuration"
+description: "Global CLI options, environment variables, and deployment configuration."
+order: 0
+---
+
+# Configuration
+
+All global options must be placed **before** the database subcommand. The general command shape is:
+
+```bash
+sql-studio [OPTIONS] [ARGS...]
+```
+
+Every option can also be set with an environment variable — useful when running SQL Studio inside Docker or a process manager without modifying the command line.
+
+## Global Options
+
+| Option | Short | Description | Default | Env Var |
+|--------|-------|-------------|---------|---------|
+| `--address` | `-a` | Address and port to bind to | `127.0.0.1:3030` | `ADDRESS` |
+| `--timeout` | `-t` | Timeout for queries from the query page | `5secs` | `TIMEOUT` |
+| `--base-path` | `-b` | Base URL path for the UI (e.g. `/sql-studio`) | _(none)_ | `BASE_PATH` |
+| `--no-browser` | | Don't open the URL in the system browser | `false` | `NO_BROWSER` |
+| `--no-shutdown` | | Don't show the shutdown button in the UI | `false` | `NO_SHUTDOWN` |
+
+## Timeout Format
+
+The `--timeout` option accepts human-readable durations. Valid examples: `5secs`, `30secs`, `1min`, `2min 30secs`. The timeout applies to queries executed from the Query Editor page — it does not affect schema or metadata fetches.
+
+## Binding to a Custom Address
+
+By default SQL Studio binds to `127.0.0.1:3030`, which is only reachable from the local machine. To expose it on your local network or inside a container:
+
+```bash
+sql-studio --address=0.0.0.0:8080 sqlite ./my-app.db
+```
+
+## Setting a Base Path
+
+If you're serving SQL Studio behind a reverse proxy under a sub-path, use `--base-path`:
+
+```bash
+sql-studio --base-path=/sql-studio postgres postgres://localhost:5432/mydb
+```
+
+The UI will then expect to be reached at `http://your-host/sql-studio`.
+
+## Running Headless
+
+For server or CI environments where no browser is available, suppress the automatic browser launch and disable the shutdown button:
+
+```bash
+sql-studio --no-browser --no-shutdown sqlite ./my-app.db
+```
+
+## Logging
+
+SQL Studio reads the standard `RUST_LOG` environment variable to control log verbosity. Set it before running the binary:
+
+```bash
+RUST_LOG=debug sql-studio sqlite ./my-app.db
+```
diff --git a/docs/databases.mdx b/docs/databases.mdx
new file mode 100644
index 0000000..e9ad4fe
--- /dev/null
+++ b/docs/databases.mdx
@@ -0,0 +1,38 @@
+---
+title: "Databases"
+description: "Overview of all databases supported by SQL Studio."
+order: 0
+---
+
+# Databases
+
+SQL Studio supports a wide range of SQL databases and local file formats. Each backend gets its own subcommand with arguments specific to that database.
+
+## Supported Backends
+
+| Database | Subcommand | Type |
+|----------|------------|------|
+| SQLite | `sqlite` | Local file |
+| libSQL | `libsql` | Remote server |
+| Local libSQL | `local-libsql` | Local file (libSQL driver) |
+| PostgreSQL | `postgres` | Remote server |
+| MySQL / MariaDB | `mysql` | Remote server |
+| DuckDB | `duckdb` | Local file |
+| Parquet | `parquet` | Local file |
+| CSV | `csv` | Local file |
+| ClickHouse | `clickhouse` | Remote server |
+| Microsoft SQL Server | `mssql` | Remote server |
+
+## General Syntax
+
+```bash
+sql-studio [OPTIONS] [ARGS...]
+```
+
+Global options like `--address`, `--timeout`, and `--base-path` go before the subcommand. See [Configuration](/docs/configuration) for the full option reference.
+
+## Platform Notes
+
+DuckDB, Parquet, and CSV support all depend on the DuckDB native library. These backends are not available in the musl (static Linux) build. Use the glibc Linux build, macOS, or Windows for those file types.
+
+SQL Studio uses `rustls` for TLS across all server-backed drivers, so no separate OpenSSL installation is required.
diff --git a/docs/features.mdx b/docs/features.mdx
new file mode 100644
index 0000000..f9f29b0
--- /dev/null
+++ b/docs/features.mdx
@@ -0,0 +1,17 @@
+---
+title: "Features"
+description: "Explore the SQL Studio user interface."
+order: 0
+---
+
+# Features
+
+SQL Studio provides four main UI pages, all accessible from the sidebar. Each page gives you a different lens on your database.
+
+The **Overview Dashboard** is the landing page — it shows database metadata, total table and index counts, and bar charts of row and column counts per table.
+
+The **Table Explorer** lets you click into any table to see its schema, creation SQL, and data rows with infinite scroll.
+
+The **Query Editor** gives you a full Monaco editor with SQL syntax highlighting and IntelliSense powered by your live database schema, so you can write and run arbitrary queries.
+
+The **ERD Viewer** renders an interactive entity-relationship diagram of your schema, with tables as draggable nodes and foreign-key relationships drawn as edges.
diff --git a/docs/index.mdx b/docs/index.mdx
new file mode 100644
index 0000000..b42b9ed
--- /dev/null
+++ b/docs/index.mdx
@@ -0,0 +1,19 @@
+---
+title: "Introduction"
+description: "Single binary, single command SQL database explorer for SQLite, PostgreSQL, MySQL, DuckDB, ClickHouse, MSSQL, Parquet, CSV, and more."
+order: 0
+---
+
+# Introduction
+
+SQL Studio is a single-binary, single-command web-based database explorer. Point it at any supported database and you instantly get a rich UI for browsing schemas, tables, and running queries — no configuration files, no installation wizard, no persistent server to manage.
+
+The binary embeds the entire frontend, so one command is all it takes:
+
+```bash
+sql-studio sqlite my-database.db
+```
+
+SQL Studio opens `http://127.0.0.1:3030` in your browser and serves the full UI from that moment on. It supports SQLite, libSQL (Turso), PostgreSQL, MySQL / MariaDB, DuckDB, ClickHouse, Microsoft SQL Server, Parquet, and CSV files — each with its own subcommand.
+
+If you're new, head to [Installation](/docs/installation) to get the binary onto your machine, then follow the [Quickstart](/docs/quickstart) to open your first database in under a minute. Once you're running, [Configuration](/docs/configuration) covers every flag and environment variable, [Databases](/docs/databases) documents each supported backend, and [Features](/docs/features) walks through the four UI pages.
diff --git a/docs/installation.mdx b/docs/installation.mdx
new file mode 100644
index 0000000..c69fbb5
--- /dev/null
+++ b/docs/installation.mdx
@@ -0,0 +1,72 @@
+---
+title: "Installation"
+description: "Install and configure SQL Studio."
+order: 10
+---
+
+# Installation
+
+SQL Studio is distributed as a self-contained binary. Pick the method that fits your environment.
+
+## Shell Script (macOS & Linux)
+
+The fastest path to a working install:
+
+```bash
+curl --proto '=https' --tlsv1.2 -LsSf https://github.com/frectonz/sql-studio/releases/download/0.1.51/sql-studio-installer.sh | sh
+```
+
+This downloads the latest release binary and places it on your `PATH`.
+
+## PowerShell (Windows)
+
+Open PowerShell and run the Windows installer published on the releases page. The exact command is listed on the [GitHub releases page](https://github.com/frectonz/sql-studio/releases).
+
+## Nix
+
+SQL Studio is available in Nixpkgs. Add it to your environment the usual way or run it ephemerally with `nix run`.
+
+## Docker
+
+A Docker image is published on Docker Hub as `frectonz/sql-studio`. The example below connects to a PostgreSQL database and exposes the UI on port `3030`:
+
+```bash
+docker run -p 3030:3030 frectonz/sql-studio /bin/sql-studio \
+ --no-browser \
+ --no-shutdown \
+ --address=0.0.0.0:3030 \
+ postgres \
+ postgres://localhost:5432/
+```
+
+When running in Docker you almost always want `--no-browser` (no desktop browser inside a container), `--no-shutdown` (keep the server alive), and `--address=0.0.0.0:3030` (bind to all interfaces so the host can reach the port).
+
+## Building from Source
+
+You need Rust and Node.js installed. The Rust binary statically embeds the compiled UI, so you must build the frontend first:
+
+```bash
+git clone git@github.com:frectonz/sql-studio.git
+cd sql-studio
+cd ui
+npm install
+npm run build
+cd ..
+cargo build --release
+```
+
+The compiled binary lands at `target/release/sql-studio`.
+
+## Updating
+
+If you installed via the shell script or PowerShell installer, re-run the same install command to pull the latest release.
+
+## Verifying the Install
+
+Run the built-in preview to confirm everything is working:
+
+```bash
+sql-studio sqlite preview
+```
+
+SQL Studio should open `http://127.0.0.1:3030` with a sample database loaded. If you see the Overview page, your install is good.
diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx
new file mode 100644
index 0000000..180a440
--- /dev/null
+++ b/docs/quickstart.mdx
@@ -0,0 +1,60 @@
+---
+title: "Quickstart"
+description: "Run SQL Studio for the first time."
+order: 20
+---
+
+# Quickstart
+
+SQL Studio ships with a built-in sample database. You can explore the full UI without pointing it at a real database first.
+
+## Try It Instantly
+
+Run this single command after installing the binary:
+
+```bash
+sql-studio sqlite preview
+```
+
+SQL Studio starts a local server, opens `http://127.0.0.1:3030` in your browser, and loads a sample SQLite database. You'll land on the Overview page showing metadata cards, statistics, and row-count bar charts.
+
+## Open Your Own SQLite File
+
+```bash
+sql-studio sqlite ./my-app.db
+```
+
+## Connect to PostgreSQL
+
+```bash
+sql-studio postgres postgres://user:password@localhost:5432/mydb
+```
+
+## Connect to MySQL
+
+```bash
+sql-studio mysql mysql://user:password@localhost:3306/mydb
+```
+
+## Open a CSV or Parquet File
+
+```bash
+sql-studio csv ./data.csv
+```
+
+```bash
+sql-studio parquet ./dataset.parquet
+```
+
+> **Note:** CSV and Parquet support requires the glibc Linux build, macOS, or Windows. They are not available in the musl (static Linux) build.
+
+## What You'll See
+
+Once SQL Studio opens, the sidebar gives you four pages to work with:
+
+- **Overview** — database metadata, version, size, and per-table statistics.
+- **Table Explorer** — browse every table's schema, row count, and data with infinite scroll.
+- **Query Editor** — write and run SQL against your database using Monaco IntelliSense.
+- **ERD Viewer** — interactive entity-relationship diagram showing tables and foreign-key relationships.
+
+From here, read [Configuration](/docs/configuration) to control the bind address, query timeout, or base path, and [Databases](/docs/databases) for the full argument reference for each supported backend.