Markdown → executable TypeScript pipelines. Deno runtime. Literate programming.
Pipe — A sequence of functions extracted from code blocks in a Markdown file
→ outputs a runnable TypeScript module in .pd/
Step — A single function from a fenced code block (ts/js). Properties:
code, name, funcName, range, inList, config, sourceMap
Input — Data object passed through the pipeline. Each step mutates input.*
and passes it forward.
| File | Purpose |
|---|---|
deps.ts |
Central dependencies → exports std, md, pd, esbuild |
mdToPipe.ts |
Markdown → Pipe object. Pipeline: parseMarkdown → findRanges → findPipeName → findSteps → mergeMetaConfig → setupChecks. Captures sourceMap line numbers for lossless reconstruction. |
rangeFinder.ts |
Identifies token ranges (code blocks, headings, lists, meta blocks). Exports: rangeFinder, Tag, TokenType |
pdBuild.ts |
Build orchestrator. Pipeline: parseMdFiles → mergeParentDirConfig → writePipeDir → writePipeJson → writePipeMd → transformMdFiles → writeDefaultGeneratedTemplates → writeUserTemplates → maybeExportPipe → report |
pipeToScript.ts |
Pipe → TypeScript. Pipeline: extractImportsFromSteps → sanitizeStepNames → stepsToFunctions → scriptTemplate |
pipeToMarkdown.ts |
Pipe JSON → Markdown. Two modes: lossless (splice changes into rawSource via sourceMap) and lossy (reconstruct from fields). Used by pd sync and LLM actions. |
extractSteps.ts |
Step extraction logic: parseStepIndices, buildExtractedPipe, performExtraction. Used by pd extract CLI and dashboard API. |
exportPipe.ts |
Bundles pipes via esbuild when pipe.config.build defined |
defaultTemplateFiles.ts |
Generates: test.ts, cli.ts, server.ts, devServer.ts, worker.ts, trace.ts, deno.json, replEval.ts. Handles @pkg/* aliases for installed packages. |
stringTemplates.ts |
Template strings for generated files (~450 lines) |
pdUtils.ts |
Utilities: sanitizeString, fileName, fileDir |
pdConfig.ts |
Config from deno.json "pipedown" property (primary) or config.json (fallback). Exports: readPipedownConfig, writePipedownConfig |
packageManifest.ts |
pipedown.json manifest: validateManifest, readManifest, resolvePackageFiles, resolveBuildArtifacts |
pipedown.d.ts |
Types: Token, Step, Pipe, PipeConfig, Input, BuildInput |
| Command | Usage | Purpose |
|---|---|---|
pd build |
pd build [--debug] [--json] |
Build all .md → .pd/ |
pd run |
pd run <pipe.md> [--input '{}'] [-- flags] |
Execute a pipeline |
pd serve |
pd serve <pipe.md> [--port N] [--dev] |
HTTP server. --dev launches dashboard with hot reload |
pd test |
pd test [-u] |
Snapshot test all pipes |
pd sync |
pd sync <pipe> [--dry-run] |
Regenerate .md from .pd/index.json |
pd extract |
pd extract <file.md> <indices> <name> |
Extract steps into sub-pipe. Indices: 2, 2-5, 1,3,5, 0,2-4,6 |
pd llm |
pd llm <file> <step> <instruction> |
LLM-assisted code generation |
pd watch |
pd watch |
File watcher with auto-rebuild |
pd repl |
pd repl |
Interactive REPL with all pipes loaded |
pd list |
pd list |
List available pipes |
pd clean |
pd clean |
Remove .pd/ directory |
pd inspect |
pd inspect <pipe> |
Inspect pipe structure |
pd pack |
pd pack |
Package pipe for distribution |
pd install |
pd install <pkg> |
Install a pipedown package |
pd run-step |
pd run-step <pipe> <step> |
Run individual step |
pd run-with |
pd run-with <wrapper> <pipe> <input> |
Run with template wrapper |
Three pages served by buildandserve.ts:
/— Home: local pipes, inline markdown editing, LLM actions, step extraction/projects— Multi-project explorer with new project/pipe creation/traces— Execution trace viewer
Frontend: Mithril.js SPA in pdCli/frontend/ with shared/, home/,
projects/, traces/ directories. Convention: window.pd for shared utils,
window.PD for per-page namespace.
llmCommand.ts exports callLLM() (uses llm CLI with claude-sonnet-4.6) and
getPipedownSystemPrompt() (reads LLM.md from disk, cached). Dashboard actions:
description, schema, tests, step-title, step-description, step-code.
pd.process(funcs, input, opts)
- Takes array of async stage functions
- Each receives
(input, opts)—optsis the Pipe instance - Each mutates or returns input
- Sequential execution
- Errors →
input.errors[](pipeline continues)
Guards evaluated in order:
only— Execute this step exclusively, skip all othersstop— Halt processing after this step index- Error checking — Short-circuit if prior steps failed
not— Skip if JSON-pointer condition is truthycheck/and/or— Boolean gate logicroutes— URL-pattern matching (server mode)methods— HTTP method filtering (GET, POST, etc.)
Supported languages: ts, js, javascript, typescript Meta languages:
json, yaml, yml Step naming: From preceding heading (## Step Name) or
anonymous{index} Config blocks: JSON/YAML code blocks → deep merged into
pipe.config
Conditional execution — List items before code block:
check:,if:,when:— JSON pointer conditions (OR logic)or:,and:,not:— Logical operatorsroute:— URL pattern matching (URLPattern API)method:— HTTP method filtering (GET, POST, etc.)type:— Response content-type shorthand (html, json, xml, css, js, stream)stop:,only:,flags:— Flow controlmock:— Test mocking
Values are JSON pointers: /points/add
type— START | END | TEXTtag— CODE_BLOCK | HEADING | LIST | ITEMcontent— Text contentlevel— Heading level (1-6)language— Code block language
code— Extracted coderange— [startIndex, endIndex] in token arrayname— From headingfuncName— Sanitized identifierinList— Boolean (conditional execution context)config.checks— JSON pointers to evaluateconfig.routes— Route patternsconfig.methods— HTTP methodsconfig.contentType— Response content-typeconfig.or/and/not— Logical conditionssourceMap—{ headingLine, codeStartLine, codeEndLine }(0-indexed line numbers for lossless reconstruction)originalCode/originalName/originalDescription— Values at parse time (mutation detection)
name— From H1 headingcleanName— Sanitizedsteps— Step[]mdPath— Source .md pathdir— Output in .pd/rawSource— Original markdown (for lossless reconstruction)pipeDescription— Prose below H1originalPipeDescription— At parse timeconfig.inputs— Test inputsconfig.templates— Custom templatesconfig.build— esbuild config (BuildOptions[])config.skip/exclude— Regex patterns
.pd/
├── {pipeName}/
│ ├── index.ts # Executable pipe
│ ├── index.json # Raw Pipe object (the neutral interchange format)
│ ├── index.md # Source copy
│ ├── test.ts # Snapshot test file
│ ├── cli.ts # CLI runner
│ ├── server.ts # HTTP server
│ ├── devServer.ts # Dev server with hot reload + tracing
│ ├── worker.ts # Service worker
│ └── trace.ts # Execution tracing
├── deno.json # Import map (pipes + @pkg/* aliases)
└── replEval.ts # REPL helper
Markdown ──pd build──▶ index.json ──pd sync──▶ Markdown
(human) (LLM) (human)
Lossless mode (when pipe.rawSource + step.sourceMap available):
- Splices changes into original source at precise line ranges
- Preserves original formatting, DSL directives, blockquotes
- Detects mutations via
original*fields
Lossy mode (fallback):
- Reconstructs from structured fields only
- Clean, predictable markdown
| Package | Version | Use |
|---|---|---|
@std/collections |
1.1.6 | deepMerge |
@std/fs |
1.0.23 | exists, walk |
@std/path |
1.1.4 | basename, dirname, join, parse, relative, globToRegExp |
@std/fmt |
1.0.9 | colors |
@std/http |
1.0.25 | serveFile |
@std/async |
1.2.0 | debounce |
@std/cli |
1.0.28 | parseArgs |
@cliffy/keycode |
1.0.0 | Keycode parsing (REPL) |
markdown-it |
14.1.1 | Markdown parser |
@pd/pdpipe |
0.2.2 | Pipeline processor |
@pd/pointers |
0.1.1 | JSON pointer ops ($p) |
esbuild |
0.25+ | Bundler |
Library: @pd/pointers (wraps jsonpointer)
$p.get(input, '/points/add')$p.set(input, '/points/amount', 10)$p.compile('/ranges/codeBlocks').get(input)
Used in conditional checks to test input values and in step code for nested access.
- Directory:
test/ - Test files:
extractSteps_test.ts,mdToPipe_test.ts,pipeToMarkdown_test.ts,pipeToScript_test.ts,pdBuild_test.ts,rangeFinder_test.ts,pdUtils_test.ts - Inputs: Define in JSON code blocks →
pipe.config.inputs - Snapshots:
@std/testing/snapshot
pdBuild()
→ walk .md files (respects .gitignore, skip/exclude patterns)
→ mdToPipe() per file
→ parseMarkdown → findRanges → findSteps (with sourceMap)
→ mergeParentDirConfig() (deno.json "pipedown" + config.json)
→ pipeToScript() → index.ts
→ defaultTemplateFiles() → supporting files
→ maybeExportPipe() → esbuild bundles (if config.build)
Patterns:
- Pipeline functions:
(input, opts) => void | input - Error handling: Push to
input.errors[], don't throw - Async: All pipeline functions can be async
- Config: deno.json
"pipedown"property preferred over config.json
Gotchas:
- Ranges are token array indices, not line numbers
- Step names sanitized via
sanitizeString() - Parent
config.json/ deno.json files merge intopipe.config - sourceMap line numbers are 0-indexed
rawSourceis set during mdToPipe for lossless reconstructionbuildPipeContextHeaderandbuildContextPromptare async (read LLM.md from disk)