Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
- run: pnpm install --frozen-lockfile
- run: pnpm check
- run: pnpm build
- run: pnpm check:projects
- id: preview
uses: cloudflare/wrangler-action@v4
with:
Expand Down
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: "Development, content authoring, and deployment workflow for dbtlr.com."
---

# dbtlr.com

Personal site for [dbtlr.com](https://dbtlr.com) — a calm, typography-led dark-mode
Expand Down Expand Up @@ -69,24 +73,29 @@ feature branch → PR to main → preview deploy → merge to main → p
```

- **PR previews** (`.github/workflows/preview.yml`): every PR against `main` runs
`pnpm check` + `pnpm build`, then `wrangler versions upload` publishes an ephemeral
`pnpm check`, `pnpm build`, and `pnpm check:projects`. Then
`wrangler versions upload` publishes an ephemeral
version of the `dbtlr-com` Worker **without** touching production. The preview URL is
posted (and kept updated) as a comment on the PR.
- **Production** (`.github/workflows/deploy.yml`): pushing to `main` runs the same
checks and `wrangler deploy`, publishing the `dbtlr-com` Worker that serves
- **Production** (`.github/workflows/deploy.yml`): pushing to `main` runs
`pnpm check` and `pnpm build`. Then `wrangler deploy` publishes the
`dbtlr-com` Worker that serves
[dbtlr.com](https://dbtlr.com).

Both workflows use Node 24 + pnpm and require `CLOUDFLARE_API_TOKEN` and
`CLOUDFLARE_ACCOUNT_ID` repository secrets.

## Checks

Run before pushing — these are exactly what CI runs:
Run before pushing:

```sh
pnpm check && pnpm build
pnpm check && pnpm build && pnpm check:projects
```

The project check inspects built pages and search. It verifies the current roster,
the retained Saga retirement page, and the removal of fixed version badges.

## Project structure

```
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"build": "astro build",
"check": "astro check",
"preview": "astro preview",
"sync:writing": "node scripts/sync-writing.ts"
"sync:writing": "node scripts/sync-writing.ts",
"check:projects": "node scripts/check-projects.mjs"
},
"dependencies": {
"@astrojs/rss": "^4.0.18",
Expand Down
8 changes: 8 additions & 0 deletions public/projects/artifacts.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/projects/loomcli.ico
Binary file not shown.
7 changes: 7 additions & 0 deletions public/projects/mimir.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions public/projects/norn.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions public/projects/sources.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"norn.svg": "https://github.com/dbtlr/norn-next/blob/ed25f3b1ac9884103649a1ce049451eeaa09d212/favicon.svg",
"mimir.svg": "https://github.com/dbtlr/mimir/blob/fd47e707c4961a87e6a143953d35eb4e7c98f62f/favicon.svg",
"loomcli.ico": "https://github.com/dbtlr/loomcli/blob/bc6381aae2cdee08542218a799c6a015c939c25a/favicon.ico",
"artifacts.svg": "https://github.com/dbtlr/artifacts/blob/4b8408602b74e152c0afa2eaf1d3abc93cdc9ba3/favicon.svg"
}
47 changes: 47 additions & 0 deletions scripts/check-projects.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';

const root = new URL('../dist/', import.meta.url);
const read = (path) => readFile(new URL(path, root), 'utf8');
const current = ['norn', 'mimir', 'loomcli', 'artifacts', 'skald'];
const search = JSON.parse(await read('search.json'));
assert.deepEqual(search.filter((item) => item.type === 'project').map((item) => item.href),
current.map((slug) => `/projects/${slug}/`), 'Search must list current projects in portfolio order');
for (const path of ['index.html', 'projects/index.html', 'about/index.html']) {
const html = await read(path);
assert.ok(!html.includes('/projects/saga/'), `${path} must not promote retired Saga`);
}
for (const [path, expected] of [
['index.html', ['norn', 'mimir', 'loomcli']],
['projects/index.html', current],
]) {
const html = await read(path);
const rows = [...html.matchAll(/<a\b[^>]*class="group [^"]*"[^>]*href="([^"]+)"/g)]
.map((match) => match[1]);
assert.deepEqual(rows, expected.map((slug) => `/projects/${slug}/`),
`${path} must show exactly its intended project rows`);
}
const saga = await read('projects/saga/index.html');
assert.match(saga, /Retired/);
assert.match(saga, /no longer under development/);
assert.ok(!saga.includes('code-file'), 'The retirement page must not offer a terminal demo');
for (const slug of [...current, 'saga']) {
const html = await read(`projects/${slug}/index.html`);
assert.ok(!/\bv\d+\.\d+/.test(html), `${slug} must not publish a fixed version badge`);
}
const mimir = await read('projects/mimir/index.html');
assert.match(mimir, /Markdown is the system of record/);
assert.ok(!mimir.includes('SQLite'), 'Mimir must not describe the retired SQLite backend');

for (const [slug, extension] of [['norn', 'svg'], ['mimir', 'svg'], ['loomcli', 'ico'], ['artifacts', 'svg']]) {
const icon = `/projects/${slug}.${extension}`;
for (const page of [`projects/${slug}/index.html`, 'projects/index.html',
...(['norn', 'mimir', 'loomcli'].includes(slug) ? ['index.html'] : [])]) {
const html = await read(page);
assert.ok(html.includes(`src="${icon}"`), `${page} must display the ${slug} icon`);
}
const bytes = await readFile(new URL(icon.slice(1), root));
assert.ok(bytes.length > 0, `${icon} must be served locally`);
}

console.log('Built project pages and search satisfy the current/retired content contract.');
5 changes: 3 additions & 2 deletions src/content.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ const projects = defineCollection({
description: z.string(),
short: z.string(),
lang: z.string(),
status: z.string(),
version: z.string(),
icon: z.string().optional(),
status: z.enum(['active', 'early', 'pre-alpha', 'retired']),
license: z.string().optional(),
order: z.number(),
tagline: z.string(),
Expand All @@ -31,6 +31,7 @@ const projects = defineCollection({
github: z.string().optional(),
docs: z.string().optional(),
changelog: z.string().optional(),
releases: z.string().optional(),
})
.optional(),
facts: z.array(
Expand Down
39 changes: 39 additions & 0 deletions src/content/projects/artifacts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
name: Artifacts
icon: /projects/artifacts.svg
blurb: Persistent links for agent-generated documents, images, and PDFs.
description: An MCP server and web interface that turn agent-generated documents, images, and PDFs into persistent links. Unversioned pre-alpha software for trusted networks.
short: share agent output
lang: TypeScript
status: pre-alpha
license: MIT
order: 4
tagline: Give agent output a link you can return to.
links:
github: https://github.com/dbtlr/artifacts
facts:
- label: Surfaces
value: MCP · web
- label: Content
value: Documents · images · PDFs
- label: Maturity
value: Pre-alpha
demo: |
# Connect to a local Artifacts server
codex mcp add artifacts --url http://localhost:4242/mcp
---

Artifacts turns agent-generated Markdown, HTML, text, images, and PDFs into persistent links. An MCP server lets agents create and update content, and a small web interface makes that content readable and discoverable.

## Why it is useful

A report or mock is easier to review when it has a stable address. Artifacts gives the result its own page, and updates preserve the same URL.

## What it does

- Publishes documents, supported images, and PDFs through MCP.
- Preserves a link when its content changes.
- Groups related output in collections.
- Provides a web interface for reading and finding results.

Artifacts is unversioned pre-alpha software for trusted networks. It has no built-in authentication and is not a public hosting service.
46 changes: 46 additions & 0 deletions src/content/projects/loomcli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
name: Loom
icon: /projects/loomcli.ico
blurb: A batteries-included, strongly typed CLI framework. Beautiful for humans and agent-native by default.
description: A batteries-included, strongly typed framework for building CLI applications that are beautiful for humans and agent-native by default.
short: human and agent-native CLIs
lang: TypeScript
status: early
order: 3
tagline: A batteries-included, strongly typed framework for building CLI applications that are beautiful for humans and agent-native by default.
links:
github: https://github.com/dbtlr/loomcli
docs: https://github.com/dbtlr/loomcli/blob/main/docs/core.md
releases: https://github.com/dbtlr/loomcli/releases
facts:
- label: Type
value: CLI application framework
- label: Validation
value: Standard Schema
- label: Maturity
value: Early development
demo: |
# From the Loom repository
pnpm install --frozen-lockfile
pnpm build
node examples/textstat/dist/src/main.js --metric words README.md
---

## Why it exists

A CLI is an interface for people and, increasingly, for agents. Both deserve a deliberate design: an application that feels good to use in a terminal and gives an agent a clear, dependable way to work.

Loom’s north star is to make that the default. A batteries-included framework should bring the pieces of a complete CLI application together, with strong types connecting its declarations, inputs, and actions. Human experience and agent interaction belong in the framework’s foundations, so application authors can focus on what their tools do.

## What it does today

Loom is in early development. Its TypeScript core establishes the typed command model that the broader vision builds on:


- Routes named commands under application-wide options.
- Derives argument and option types from declarations.
- Accepts Standard Schema validators without a dedicated adapter.
- Keeps passthrough arguments separate from parsed inputs.
- Provides shared output and failure behavior.

The textstat and jsonkit examples demonstrate the current scope.
40 changes: 20 additions & 20 deletions src/content/projects/mimir.md
Original file line number Diff line number Diff line change
@@ -1,45 +1,45 @@
---
name: mimir
blurb: Local-first work-state tracker — one query layer, four surfaces, SQLite as the source of truth.
description: A local-first task and work-state tracker. SQLite is the source of truth; rollups and dependency predicates are derived live, never stored. One core, four surfaces.
short: work tracker
icon: /projects/mimir.svg
blurb: Local work state in a Norn-managed Markdown vault, with queues and status derived when queried.
description: A local-first work-state engine over a Norn-managed, git-backed Markdown vault. The CLI, MCP server, HTTP API, and operator console share one work model.
short: work state
lang: TypeScript
status: active
version: v0.8
license: MIT
order: 2
tagline: One core query layer over four surfaces — a CLI for humans, an MCP server for agents, an HTTP API, and a web console — all speaking the same verbs over a single SQLite store. Work state stays the source of truth, without a sync problem.
tagline: Local work state, with the next action derived from the facts.
links:
github: https://github.com/dbtlr/mimir
changelog: https://github.com/dbtlr/mimir/blob/main/CHANGELOG.md
releases: https://github.com/dbtlr/mimir/releases
facts:
- label: Surfaces
value: CLI · MCP · HTTP · console
- label: Store
value: SQLite, rollups derived
value: Norn-managed Markdown
- label: Scope
value: Single operator
demo: |
curl -fsSL https://raw.githubusercontent.com/dbtlr/mimir/main/install.sh | sh
mimir next # ready tasks, in rank order
mimir create task "wire the API" --parent MMR-2 --priority p1
mimir start MMR-3 && mimir done MMR-3
mimir overview # current direction and active work
mimir next # ready tasks, in rank order
mimir list # the live work queue
---

## Why it exists
Mimir keeps projects, tasks, dependencies, decisions, and work products in a Norn-managed, git-backed Markdown vault. The CLI, MCP server, HTTP API, and operator console share the same work model.

Task state is ephemeral and fast-changing, but a Markdown vault is optimized for durable, slow-changing knowledge. Storing work state as notes makes convention the only enforcement layer — and convention always decays: statuses drift, stale tasks pile up, rollups get hand-maintained. mimir moves work state into a structured SQLite store so the substrate fits the job. Markdown becomes a projection, not the source.
## Why it exists

mimir exists because work state changes faster than knowledge. It keeps tasks, blockers, rank, and history in a structured local store, while Markdown remains a projection for humans.
Long-running work needs a reliable answer to what is active, what is blocked, and what comes next. Mimir gives those questions a structured model while keeping the underlying records inspectable as Markdown.

## What it does

- **Models work as a tree** — project → initiative → phase → task — with two status axes per task: a lifecycle and a hold overlay.
- **Derives, never stores** — `ready`, `blocked`, `stale`, and every rollup are computed live, so there's no cache to fall out of sync.
- **One query surface, composable** — `next`, `list`, `get`, `status` with AND-composed filters and pipe-safe output formats.
- **Four transports, one core** — a CLI, an MCP server for agents, an HTTP API, and an installable web console.
- **Append-only history** — abandoned work stays abandoned; nothing is deleted.
- Organizes projects, initiatives, phases, and tasks.
- Derives queues, status rollups, and blockers when queried.
- Preserves work products as Artifacts and temporary context as Scratchpads.
- Tracks new ideas and cross-project requests as Seeds.
- Gives agents session context and operators a console across projects.

## Derive, don't store
## Store facts, derive the view

The spine running through every design decision: mimir stores work-state facts, never derived state or a consumer's semantics. Caching a rollup would reintroduce the very sync problem the tool exists to remove — so "what's next," "what's blocked," and "what's stale" are answered at read time. Rank, the one true ordering, beats every priority heuristic, because sometimes you place a P2 ahead of a P0 for a reason the system can't see.
Markdown is the system of record. Norn owns validated access to the vault. Mimir derives what is ready, awaiting, or stale from the stored facts, so there is no second project-status record to synchronize.
7 changes: 6 additions & 1 deletion src/content/projects/norn.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
---
name: norn
icon: /projects/norn.svg
blurb: Your Markdown vault as a deterministic graph — query, validate, and repair from the shell.
description: A Rust CLI and MCP server that turns a Markdown vault into a deterministic graph you can query, validate, and repair — for shells, scripts, and coding agents.
short: vault tooling
lang: Rust
status: active
version: v0.38
license: MIT
order: 1
tagline: Obsidian gives you a GUI over your Markdown vault. norn gives you the same vault as a deterministic graph — one humans and agents can both query, validate, and repair from the command line.
links:
releases: https://github.com/dbtlr/norn/releases
github: https://github.com/dbtlr/norn
changelog: https://github.com/dbtlr/norn/blob/main/CHANGELOG.md
facts:
Expand Down Expand Up @@ -43,3 +44,7 @@ The thesis is a chain — rules make the vault consistent, consistency makes que
## Plan, then apply

Mutation is never a black box. norn produces a schema-versioned, inspectable repair plan and only changes files when you apply it. The division of labor is deliberate: the agent decides, norn enumerates. No model sits in the loop rewriting prose — which is exactly what makes the result predictable enough to trust.

## Current development

The released tool is available from the [norn repository](https://github.com/dbtlr/norn). The next implementation is under development in [norn-next](https://github.com/dbtlr/norn-next) and has no releases yet.
Loading