Briefkit
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.
What it does
| 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/ |
Install
From npm
npm install -g briefkit
Then run:
briefkit dev /path/to/report
briefkit build /path/to/report
briefkit publish /path/to/report
From this repository
git clone https://github.com/taylorcjensen/briefkit.git
cd briefkit
npm install
npm link
Then run:
briefkit dev examples/readme-report
briefkit build examples/basic-report
Without linking:
npm run briefkit -- dev examples/readme-report
npm run briefkit -- build examples/basic-report
Commands
briefkit 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.
Publishing briefs
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.
Run the publish server
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:latest
Docker 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-server
The GitHub Actions workflow publishes multi-architecture images to ghcr.io/taylorcjensen/briefkit/publish-server on main, releases, and manual dispatches.
Server configuration
| 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.
Configure the CLI
Save the target and API key once:
briefkit publish-config set --target https://briefs.example.com --api-key key-one
If 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.
Publish and unpublish
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.
Quick start: plain Markdown
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-brief
Briefkit 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.
Full-featured MDX report
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>
Report folder shape
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.
Components
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
<Callout type="warning" title="Main caveat">
This recommendation depends on the source data being current.
</Callout>
Types:
note | tip | info | warning | danger
Tooltip
<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.
BriefTable
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.
Data and local imports
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 confidence
import 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';
Recommended report sections
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
Example site
This README is the example report. Build it with:
npm run briefkit -- build . --out ./dist
The generated site includes the README at / and the design notes at /design/.
Design notes
The detailed design record is included as a second Briefkit page: Design notes.
License
MIT