Files
beardrive/web/docs/astro.config.mjs
T
4031495c81 feat(cli,docs): say that agent skills sync, and refuse ~/.claude as a mount root (BEA-117) (#138)
`.claude/skills/**` has always synced — deliberately, per the reservation
rule's own comment — but the only sentence saying so sits under the heading
"What beardrive does not sync". Nobody knows.

Track B, the one real bug: `bdrive init ~/.claude` was accepted. The
reserved-path rule matches ".claude/settings.json" on its directory segment,
so at that mount root the file is bare "settings.json" — reserved by nothing —
along with .credentials.json and every saved session under projects/. New
exported config.AgentConfigDir folds the keys of agentHookConfigs the way
ReservedDir folds (case, trailing dots), and init refuses before any network
call or file write. Only that direction leaks: a mount CONTAINING ~/.claude
still sees .claude/settings.json, reserved at any depth.

Track A, the content job: a README Features bullet stating the positive claim,
a 7th use-case page (plus its astro.config.mjs sidebar entry, without which it
is invisible), and a `skills` template appended last to the registry so `docs`
keeps the RECOMMENDED badge. The embed directive becomes `//go:embed all:files`
— a plain pattern drops dot-prefixed paths silently, so the template whose
whole payload is .claude/skills/<name>/SKILL.md would have shipped empty.

templates_test.go's every-directory-holds-a-file rule now marks ancestors, not
just the direct parent: skills is the first template more than one level deep,
and the rule was stricter than its own stated reason (an intermediate
directory on the way to a file is not empty).

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 05:04:12 +09:00

182 lines
8.1 KiB
JavaScript

// @ts-check
import { defineConfig } from "astro/config";
import starlight from "@astrojs/starlight";
import sitemap from "@astrojs/sitemap";
import llmsTxt from "starlight-llms-txt";
import { execFileSync } from "node:child_process";
// docs.beardrive.ai — the public product documentation.
//
// This is a standalone static site, deliberately NOT embedded into the Go
// binary the way the hub frontend (internal/webapp/static) and the cloud
// landing page (cloud/internal/landing/dist) are. Docs change far more often
// than the binary does, and a Pagefind search index has no business shipping
// inside every self-hoster's install. It deploys on its own, on push.
//
// It lives in the OSS repo because that's what it documents: the CLI, the sync
// model, self-hosting. "Edit this page" resolves to something an outside
// contributor can actually open a PR against.
// `lastmod` for the sitemap, from the commit that last touched each page.
//
// The tempting shortcut — stamp every URL with the build time — is worse than
// emitting nothing: a sitemap that reports the whole site changed on every
// deploy teaches Google to disregard its lastmod entirely, and freshness is
// most of what a sitemap is for on a docs site.
//
// Which is also why the shallow check exists. A CI checkout is depth-1 by
// default, and there `git log` attributes every file to the one commit it has
// — the same uniform lie in a different costume. No history, no lastmod.
// (A host that wants these dates must clone with full history:
// actions/checkout needs `fetch-depth: 0`.)
const lastmodBySource = (() => {
const git = (...args) =>
execFileSync("git", args, { cwd: import.meta.dirname, encoding: "utf8" }).trim();
try {
if (git("rev-parse", "--is-shallow-repository") === "true") return new Map();
// One `git log` for every page rather than one per page. Newest commit
// first, so the first time a path appears is its last modification.
const log = git("log", "--format=%cI", "--name-only", "--relative", "--", "src/content/docs");
const map = new Map();
let when = "";
for (const line of log.split("\n")) {
if (!line) continue;
else if (/^\d{4}-\d\d-\d\dT/.test(line)) when = line;
else if (!map.has(line)) map.set(line, when);
}
return map;
} catch {
return new Map(); // built from a tarball, or no git installed — not fatal
}
})();
/** `/reference/cli/` -> the date on `src/content/docs/reference/cli.md`. */
function lastmodFor(url) {
const slug = new URL(url).pathname.replace(/^\/|\/$/g, "") || "index";
for (const ext of [".md", ".mdx"]) {
const at = lastmodBySource.get(`src/content/docs/${slug}${ext}`);
if (at) return at;
}
}
export default defineConfig({
site: "https://docs.beardrive.ai",
// The docs were reorganized around the agent-first path; these URLs were
// public and indexed. Astro emits meta-refresh pages for static output —
// real 301s belong in the host config (see README, "Deploying").
redirects: {
"/start/install": "/manual/install/",
"/start/quickstart": "/manual/setup-by-hand/",
"/guides/connect-an-agent": "/start/setup/",
"/manual/skills-and-hooks": "/manual/hooks/",
},
integrations: [
// Starlight adds @astrojs/sitemap itself, but only when the config hasn't
// already — declaring it here replaces that default rather than doubling
// it, which is the supported way to reach these options. (Starlight's own
// version only sets `i18n`, and this site is single-language.)
sitemap({ serialize: (item) => ({ ...item, lastmod: lastmodFor(item.url) }) }),
starlight({
title: "BearDrive",
description:
"Google Drive for AI agents. One shared folder your whole team's agents read and write — real files, synced in seconds, with history, provenance, and share links.",
logo: { src: "./src/assets/bear.svg", alt: "BearDrive" },
customCss: ["./src/styles/tokens.gen.css", "./src/styles/custom.css"],
social: [
{
icon: "github",
label: "GitHub",
href: "https://github.com/runbear-io/beardrive",
},
],
editLink: {
baseUrl:
"https://github.com/runbear-io/beardrive/edit/main/web/docs/",
},
// Docs are the top AI-citation surface for a dev tool, so ship the
// machine-readable index too: /llms.txt and /llms-full.txt.
//
// Convention puts llms.txt at the ROOT domain, not a docs subdomain.
// beardrive.ai/llms.txt should redirect (or proxy) here — that lives in
// the cloud landing, and is the one cross-repo coordination point this
// split introduces.
plugins: [llmsTxt()],
sidebar: [
{
// The reading order IS the recommended path, and the recommended path
// is agent-first: nobody should meet `brew install` before they meet
// /beardrive:install. Everything CLI lives under "Manual setup",
// one click away and never on the critical path.
label: "Start here",
items: [
{ label: "What is BearDrive?", slug: "" },
{ label: "Set up with your agent", slug: "start/setup" },
{ label: "Your first hour", slug: "start/first-hour" },
],
},
{
// Guides are about working with agents — that's what the product is
// for. Command-by-command CLI detail belongs in Reference.
label: "Working with agents",
items: [
{ label: "Shared agent memory", slug: "guides/shared-agent-memory" },
{ label: "Artifacts and links", slug: "guides/agent-artifacts" },
{ label: "What agents read", slug: "guides/what-agents-read" },
{ label: "Scoping the folder", slug: "guides/scoping" },
],
},
{
// For people who would rather type it, and for machines with no agent
// on them. Same destination, more steps.
label: "Manual setup (optional)",
items: [
{ label: "Install the CLI", slug: "manual/install" },
{ label: "Set up by hand", slug: "manual/setup-by-hand" },
{ label: "Hooks in detail", slug: "manual/hooks" },
],
},
{
// Job-shaped titles, persona named in the description (which is also
// the search snippet and the llms.txt line). These pages ROUTE — the
// moment one starts teaching a feature, it links to the guide that
// owns it instead.
label: "Use cases",
items: [
{ label: "Share work across your team's agents", slug: "use-cases/team-artifacts" },
{ label: "Keep a wiki your agents maintain", slug: "use-cases/team-wiki" },
{ label: "Give your coding agents the business context", slug: "use-cases/business-context" },
{ label: "Turn a personal brain into a company brain", slug: "use-cases/company-brain" },
{ label: "Run a personal wiki, publish part of it", slug: "use-cases/personal-wiki" },
{ label: "Carry one context across agents and devices", slug: "use-cases/multi-device" },
{ label: "Give every agent on the team the same skills", slug: "use-cases/shared-skills" },
],
},
{
label: "Self-hosting",
items: [
{ label: "Run a hub", slug: "self-hosting/run-a-hub" },
{ label: "Authentication", slug: "self-hosting/authentication" },
{ label: "Database", slug: "self-hosting/database" },
],
},
{
label: "Reference",
items: [
{ label: "CLI", slug: "reference/cli" },
{ label: "Project files", slug: "reference/project-files" },
{ label: "Hub config", slug: "reference/hub-config" },
{ label: "Migrate between hubs", slug: "reference/migration" },
],
},
{
label: "Concepts",
items: [
{ label: "How sync works", slug: "concepts/how-it-works" },
{ label: "Project permissions", slug: "concepts/permissions" },
],
},
],
}),
],
});