Read this README as a Briefkit report
Briefkit turns Markdown or MDX folders into local, static, decision-oriented report websites.
It is built for agent-authored briefs: research summaries, comparisons, evidence packs, audits, risk registers, status reports, and one-off decision pages. The source stays simple and portable. Briefkit supplies the shared shell: layout, sidebar navigation, heading TOC, dark/light/auto theme, callouts, tooltips, and table normalization.
Briefkit is not an app framework, dashboard backend, CMS, or structured-data prison. The goal is to keep nuanced analysis easy to write while making the result readable enough to hand to a human.
| Need | Briefkit behavior |
|---|---|
| Fast local report | Run briefkit dev /path/to/report and get a live site |
| Portable source | Use README.md, index.md, or pages/*.md |
| Full components | Use index.mdx or pages/*.mdx |
| Scannable tables | Markdown tables are automatically styled and width-normalized |
| Decision artifacts | Use callouts, dense tables, sidebars, and heading TOCs |
| Throwaway output | Build static files to {report-folder}/brief/ |
npm install -g briefkitThen run:
briefkit dev /path/to/report
briefkit build /path/to/report
briefkit publish /path/to/reportgit clone https://github.com/taylorcjensen/briefkit.git
cd briefkit
npm install
npm linkThen run:
briefkit dev examples/readme-report
briefkit build examples/basic-reportWithout linking:
npm run briefkit -- dev examples/readme-report
npm run briefkit -- build examples/basic-reportbriefkit dev [report-dir] [--port 4311] [--no-open] [--color-mode auto|light|dark]
briefkit build [report-dir] [--out ./brief] [--color-mode auto|light|dark]
briefkit publish [report-dir] [--duration 90d|3mo|1y|forever] [--indexed|--no-indexed] [--target https://briefs.example.com] [--api-key KEY]
briefkit unpublish <url-or-slug> [--target https://briefs.example.com] [--api-key KEY]
briefkit publish-config set --target https://briefs.example.com --api-key KEY| Command | Use it for | Output |
|---|---|---|
dev |
Primary authoring workflow | Live Astro dev server |
build |
Saved static artifact | {report-folder}/brief/ by default |
publish |
Build and upload a static brief | Hosted URL from your publish server |
unpublish |
Delete a hosted brief by URL or slug | Removed static brief |
publish-config |
Save a default publish target and API key | ~/.config/briefkit/publish.json |
briefkit dev opens the browser by default. Agent workflows should not open a separate browser tab after running it. Use --no-open only when you explicitly do not want a browser opened.
Build/dev generated Astro workspaces live under the OS temp directory. The report folder only needs to contain your source files and optional build output.
Briefkit includes a small Docker publish server in publish-server/. The CLI builds a report, uploads the generated static files, and the server stores them under /briefs.
Published URLs use the configured public domain and the report title slug:
{configured-domain}/{article-title-slug}/
Duplicate titles receive numeric suffixes: article-title-slug-2, article-title-slug-3, and so on.
Use the published multi-architecture image:
docker run -d \
--name briefkit-publish \
--restart unless-stopped \
-p 8080:8080 \
-v /path/on/host/briefs:/briefs \
-e BRIEFKIT_DOMAIN=https://briefs.example.com \
-e BRIEFKIT_API_KEYS=key-one,key-two \
-e BRIEFKIT_DEFAULT_DURATION=3mo \
-e BRIEFKIT_DEFAULT_INDEXED=true \
ghcr.io/taylorcjensen/briefkit/publish-server:latestDocker Compose example:
services:
briefkit-publish:
image: ghcr.io/taylorcjensen/briefkit/publish-server:latest
container_name: briefkit-publish
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- /path/on/host/briefs:/briefs
environment:
BRIEFKIT_DOMAIN: https://briefs.example.com
BRIEFKIT_API_KEYS: key-one,key-two
BRIEFKIT_DEFAULT_DURATION: 3mo
BRIEFKIT_DEFAULT_INDEXED: "true"Put a reverse proxy in front of the container if the public domain should terminate TLS or listen on ports 80/443. The container itself listens on port 8080.
Or build the image locally:
docker build -t briefkit-publish-server ./publish-serverThe GitHub Actions workflow publishes multi-architecture images to ghcr.io/taylorcjensen/briefkit/publish-server on main, releases, and manual dispatches.
| Variable | Required | Default | Meaning |
|---|---|---|---|
BRIEFKIT_DOMAIN |
No | http://localhost:8080 |
Public base URL returned after publish |
BRIEFKIT_API_KEYS |
Yes | none | Comma-separated valid API keys |
BRIEFKIT_DEFAULT_DURATION |
No | 3mo |
Used when the client omits --duration |
BRIEFKIT_DEFAULT_INDEXED |
No | true |
Whether published briefs appear on the homepage when the client does not override indexing |
BRIEFKIT_STORAGE_DIR |
No | /briefs |
Container storage path |
BRIEFKIT_MAX_BODY_BYTES |
No | 52428800 |
Maximum JSON upload size |
BRIEFKIT_API_KEYS is a comma-separated allowlist. There are no user accounts; use separate keys if you want to track or rotate access by person or machine.
The storage directory should be a bind mount or Docker volume if you want briefs to survive container replacement. The image declares /briefs; you decide what host path or volume maps there.
The server homepage at / lists non-expired indexed briefs, sorted newest first. Use BRIEFKIT_DEFAULT_INDEXED=false if briefs should be hidden from the homepage unless the client opts in.
Save the target and API key once:
briefkit publish-config set --target https://briefs.example.com --api-key key-oneIf the key contains spaces or shell metacharacters, quote it:
briefkit publish-config set --target https://briefs.example.com --api-key 'key with special chars'Use space-separated options. Briefkit does not currently support --api-key=value or --target=value.
The config is stored at:
~/.config/briefkit/publish.json
You can also pass --target and --api-key directly to publish or unpublish to override the saved config.
briefkit publish /path/to/report
briefkit publish /path/to/report --duration 30d
briefkit publish /path/to/report --duration forever
briefkit publish /path/to/report --indexed
briefkit publish /path/to/report --no-indexed
briefkit unpublish article-title-slug
briefkit unpublish https://briefs.example.com/article-title-slug/Durations support d, w, mo, y, and forever. If --duration is omitted, the server decides using BRIEFKIT_DEFAULT_DURATION; if that is also unset, the server uses 3mo.
Indexing controls whether a brief appears on the server homepage. If neither --indexed nor --no-indexed is supplied, the server decides using BRIEFKIT_DEFAULT_INDEXED; if that is also unset, the server indexes briefs.
Expired briefs return 404 and are deleted by the server cleanup loop. Briefs set to forever can be removed with briefkit unpublish.
A folder with only README.md is a valid Briefkit report.
mkdir /tmp/vendor-brief
cat > /tmp/vendor-brief/README.md <<'EOF'
# Vendor Decision Brief
## Verdict
Pick Option A if you need the lowest maintenance path.
## Comparison
| Option | Best for | Caveat |
|---|---|---|
| A | Fast decisions | Less customizable |
| B | Deep analysis | More work |
## Decision rule
Choose B only if the extra analysis time changes the decision.
EOF
briefkit dev /tmp/vendor-briefBriefkit will:
- route
README.mdto/; - use the first
# Headingas the page title; - avoid rendering that title twice;
- add sidebar navigation from page headings;
- upgrade Markdown tables with Briefkit table styling and content-aware widths.
Use Markdown when the report should stay generic and portable.
Use MDX when you need Briefkit components, imported data, or local components.
my-report/
briefkit.config.js
index.mdx
pages/details.mdx
data/risks.yaml
public/source.pdf
briefkit.config.js:
export default {
title: 'Vendor Decision Brief',
author: 'Ariadne',
pages: [
'index.mdx',
{ file: 'pages/details.mdx', title: 'Details', route: '/details/' },
],
};index.mdx:
---
title: Vendor Decision Brief
---
import { Callout, BriefTable, Tooltip } from 'briefkit';
import risks from '@report/data/risks.yaml';
<Callout type="info" title="Verdict">
Pick Option A unless the extra evidence from Option B would change the decision.
</Callout>
## Risk register
<BriefTable
caption="Risk register"
columns={[
{ key: 'risk', label: 'Risk' },
{ key: 'warning', label: 'Warning sign' },
{ key: 'mitigation', label: 'Mitigation' },
]}
rows={risks}
stickyHeader
stickyFirstColumn
/>
Use a <Tooltip term="throwaway report">purpose-built static site for one decision or research result.</Tooltip>Minimum Markdown report:
my-report/
README.md
Minimum MDX report:
my-report/
index.mdx
Full shape:
my-report/
README.md
index.mdx
pages/**/*.{md,mdx}
components/**/*.{astro,tsx,jsx}
data/**/*.{json,yaml,yml,csv}
public/**/*
briefkit.config.{ts,js,mjs}
Root page priority:
index.mdx > index.md > README.mdx > README.md
Routes:
| Source file | Route |
|---|---|
index.mdx |
/ |
index.md |
/ |
README.mdx |
/ |
README.md |
/ |
pages/details.mdx |
/details/ |
pages/details.md |
/details/ |
Rules:
pagesin config controls page order.- Unlisted pages are included alphabetically after configured pages.
hidden: truefrontmatter keeps a page out of render/nav.layout: nonerenders page content without the Briefkit shell.layout: customwithcustomLayoutlets a page use a local layout.- Files in
public/are copied to the static output and can be linked from/file-name.ext.
Briefkit v0 intentionally keeps the primitive set small.
| Component | Use it for |
|---|---|
ReportLayout |
Shared shell used automatically by the CLI |
Callout |
Verdicts, notes, caveats, warnings, source notes |
Tooltip |
Inline explanations for jargon and acronyms |
BriefTable |
Explicit tables with captions, sticky columns, or data rows |
<Callout type="warning" title="Main caveat">
This recommendation depends on the source data being current.
</Callout>Types:
note | tip | info | warning | danger
<Tooltip term="ETL">Extract, transform, load: a pipeline that moves and reshapes data.</Tooltip>Tooltip text should explain practical effect, not merely expand an acronym.
Inline Markdown table:
<BriefTable caption="Decision matrix" stickyHeader stickyFirstColumn>
| Option | Best for | Caveat |
|---|---|---|
| A | Fast decisions | Less customizable |
| B | Deep analysis | More work |
</BriefTable>Data-driven table:
import rows from '@report/data/risks.yaml';
<BriefTable
caption="Risk register"
columns={[
{ key: 'risk', label: 'Risk' },
{ key: 'warning', label: 'Warning sign' },
{ key: 'mitigation', label: 'Mitigation' },
]}
rows={rows}
stickyHeader
stickyFirstColumn
/>Ordinary Markdown tables are enhanced automatically. Use BriefTable when you need a caption, sticky first column, data rows, or explicit control.
YAML files can be imported directly from MDX:
# data/risks.yaml
- risk: Source conflict
warning: Official docs and local notes disagree
mitigation: Preserve both claims and mark confidenceimport risks from '@report/data/risks.yaml';Use @report for imports rooted at the report folder:
import CustomMatrix from '@report/components/CustomMatrix.astro';
import facts from '@report/data/facts.yaml';Use only the sections that fit the task.
- Verdict
- Hard facts
- Good fit / bad fit
- Comparison matrix
- Score matrix
- System-by-system analysis
- Risk register
- Source audit / caveats
- Decision rules
- Next steps
This README is the example report. Build it with:
npm run briefkit -- build . --out ./distThe generated site includes the README at / and the design notes at /design/.
The detailed design record is included as a second Briefkit page: Design notes.
MIT