Briefkit

README

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

NeedBriefkit behavior
Fast local reportRun briefkit dev /path/to/report and get a live site
Portable sourceUse README.md, index.md, or pages/*.md
Full componentsUse index.mdx or pages/*.mdx
Scannable tablesMarkdown tables are automatically styled and width-normalized
Decision artifactsUse callouts, dense tables, sidebars, and heading TOCs
Throwaway outputBuild 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
CommandUse it forOutput
devPrimary authoring workflowLive Astro dev server
buildSaved static artifact{report-folder}/brief/ by default
publishBuild and upload a static briefHosted URL from your publish server
unpublishDelete a hosted brief by URL or slugRemoved static brief
publish-configSave 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

VariableRequiredDefaultMeaning
BRIEFKIT_DOMAINNohttp://localhost:8080Public base URL returned after publish
BRIEFKIT_API_KEYSYesnoneComma-separated valid API keys
BRIEFKIT_DEFAULT_DURATIONNo3moUsed when the client omits --duration
BRIEFKIT_DEFAULT_INDEXEDNotrueWhether published briefs appear on the homepage when the client does not override indexing
BRIEFKIT_STORAGE_DIRNo/briefsContainer storage path
BRIEFKIT_MAX_BODY_BYTESNo52428800Maximum 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.md to /;
  • use the first # Heading as 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>

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 fileRoute
index.mdx/
index.md/
README.mdx/
README.md/
pages/details.mdx/details/
pages/details.md/details/

Rules:

  • pages in config controls page order.
  • Unlisted pages are included alphabetically after configured pages.
  • hidden: true frontmatter keeps a page out of render / nav.
  • layout: none renders page content without the Briefkit shell.
  • layout: custom with customLayout lets 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.

ComponentUse it for
ReportLayoutShared shell used automatically by the CLI
CalloutVerdicts, notes, caveats, warnings, source notes
TooltipInline explanations for jargon and acronyms
BriefTableExplicit 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';

Use only the sections that fit the task.

  1. Verdict
  2. Hard facts
  3. Good fit / bad fit
  4. Comparison matrix
  5. Score matrix
  6. System-by-system analysis
  7. Risk register
  8. Source audit / caveats
  9. Decision rules
  10. 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