distill
![]()
distill helps you turn repeated AI-agent work into reusable skills. It watches Claude/Codex sessions, proposes improvements, and lets you accept them with a quick review flow.
Install
# Homebrew (recommended)
# crates.io
The installed command is still distill.
Icon assets used by notifications and docs:
- SVG:
assets/icons/distill-icon.svg - PNG:
assets/icons/png/color/distill-color-256.png
Requirements
- Distill needs at least one supported agent CLI on
PATH:- Claude Code via
claude - Codex CLI via
codex
- Claude Code via
- Onboarding marks an agent as detected only when its CLI is discoverable on
PATH. - Scans require the configured
proposal_agentCLI to be installed and already authenticated. - Distill reads local session logs from:
- Claude:
~/.claude/projects/**/*.jsonl - Codex:
~/.codex/sessions/**/*.jsonl
- Claude:
- If a scan stalls because the upstream agent is slow, raise
DISTILL_AGENT_TIMEOUT_SECS(default: 900 seconds).
Quick Start
||
Local Commit Checks (Git + jj)
For this repo, run:
This configures:
- Git
pre-commithook (viacore.hooksPath) to run local checks beforegit commit - repo-local
jjaliases:jj safe-commit ...jj safe-describe ...
The shared check pipeline auto-applies format/fixes, then verifies tests:
Notes:
jjcurrently does not have native commit hooks, so the practical equivalent is using thesafe-*aliases for commit-time enforcement.- You can run the same pipeline anytime with
make local-checks.
Release Automation
Pushing to main triggers the release workflow. When the version in Cargo.toml has not been released yet, the workflow will:
- publishes release artifacts to GitHub Releases
- publishes the crate to crates.io as
distill-cli - updates the Homebrew formula in
nclandrei/homebrew-tap
The workflow creates the v<version> tag automatically. You do not need to push tags manually.
If the current version is already released, the workflow exits without publishing again.
For a retry of the current version, use GitHub Actions workflow_dispatch on the Release workflow.
Required GitHub Actions secrets:
CARGO_REGISTRY_TOKEN: crates.io API token with publish access fordistill-cliHOMEBREW_TAP_TOKEN: GitHub token with push access tonclandrei/homebrew-tap
Release process:
# bump Cargo.toml version first
Commands
| Command | Description |
|---|---|
distill |
Run onboarding on first run, otherwise print quick usage hints |
distill onboard |
Run onboarding TUI explicitly |
distill scan --now |
Run an immediate scan for skill proposals |
distill review |
Review pending proposals in a TUI (a/r/e/s/A) |
distill dedupe [--dry-run] |
Detect duplicate global skills and propose removals |
distill sync-agents ... |
Propose AGENTS.md updates from project evidence |
distill status |
Show config, pending proposals, existing skills |
distill watch --install |
Install scheduled scan (launchd/systemd) |
distill watch --uninstall |
Remove scheduled scan |
distill notify --check |
Check for pending proposals (used by shell hook) |
sync-agents examples:
distill sync-agents --projects /abs/repo --dry-rundistill sync-agents --projects /abs/repo1,/abs/repo2 --save-projectsdistill sync-agents --all-configureddistill sync-agents --list-configured
Notifications
notifications:terminal|native|both|nonenotification_icon:nullor absolute icon path- Terminal notifications show an inline icon when supported by the terminal:
- Ghostty/kitty-like terminals via kitty graphics protocol
- iTerm2 via OSC 1337
- Fallback is text-only
- Optional terminal controls:
DISTILL_TERMINAL_IMAGE=on|off(onby default; setoffto disable)DISTILL_TERMINAL_IMAGE_PROTOCOL=ansi|kitty|iterm|none
- If running inside tmux, enable passthrough for image rendering:
set -g allow-passthrough on. - In tmux sessions, distill auto-detects the attached terminal (
ghostty/kittyfirst, theniTerm) and falls back to text-only when passthrough is disabled. - SVG
notification_iconvalues are rasterized to PNG for terminal inline rendering. - On Linux native notifications,
notification_icon: nullfalls back to the built-in project icon automatically. - On macOS native notifications, distill tries
terminal-notifier -appIcon <icon>first and falls back to AppleScript notification if that path fails.
Platform Notes
- Scheduled scans use
launchdon macOS andsystemd --useron Linux. - Native notifications use
terminal-notifieron macOS andnotify-sendon Linux when available. - If those native notifier tools are missing, terminal notifications still work when
notificationsisterminalorboth.
For AI Agents
Use one-shot JSON modes to avoid TUI interaction.
Reference examples:
examples/onboarding.jsonexamples/review.json
1) Onboarding (non-interactive)
# edit onboarding.json
onboarding.json fields:
format_version(currently1)agents([{"name":"claude|codex","enabled":true|false}])scan_interval(daily|weekly|monthly)proposal_agent(claude|codex)shell(zsh|bash|fish|other)notifications(terminal|native|both|none)notification_icon(nullor absolute path)install_shell_hook(true|false)
2) Review (non-interactive)
# set decision for each proposal: accept | reject | skip
review.json behavior:
- Contains all pending proposals plus an editable
decisionfield - Missing decisions default to
skip - Applying decisions writes skills, logs history, and removes processed proposals
- Accepted skills are synced to:
~/.agents/skills/<skill-name>/SKILL.md(default shared target)~/.claude/skills/<skill-name>/SKILL.mdwhen Claude is enabled
3) Stdin/stdout mode
Both onboarding and review JSON flags accept - as path:
--write-json -writes JSON to stdout--apply-json -reads JSON from stdin