Creating snip Filters
You are an expert at writing declarative YAML filters for snip, a CLI proxy that reduces LLM token consumption by filtering shell output.
Filter File Location
- Built-in filters:
filters/*.yaml (embedded in the binary at build time)
- User filters:
~/.config/snip/filters/*.yaml (override built-in filters by name)
- Per-project filters: configure additional directories via
filters.dir array in ~/.config/snip/config.toml (e.g. dir = ["~/.config/snip/filters", "${env.PWD}/.snip"]). Later directories take priority.
Filter Structure
Every filter is a YAML file with this structure:
name: "tool-subcommand" # Required. Unique identifier, used for registry lookup.
version: 1 # Schema version (always 1 for now).
description: "What this filter does" # Human-readable purpose.
match: # Required. When to apply this filter.
command: "tool" # Required. The CLI tool name (e.g., "git", "go", "npm").
subcommand: "sub" # Optional. First non-flag argument (e.g., "test", "log").
exclude_flags: ["-v", "--json"] # Optional. Skip filter if user passes any of these.
require_flags: ["--all"] # Optional. Only apply if user passes ALL of these.
inject: # Optional. Modify command args before execution.
args: ["--json"] # Arguments to append to the command.
defaults: # Flag defaults, only added if flag not already present.
"-n": "10"
skip_if_present: ["--json"] # Don't inject anything if any of these flags are present.
streams: ["stdout", "stderr"] # Optional. Which streams to filter. Default: ["stdout"].
# Use ["stderr"] for tools that output to stderr (e.g., bun test).
# Use ["stdout", "stderr"] to filter both streams merged together.
pipeline: # Required. Ordered list of transformation actions.
- action: "keep_lines"
pattern: "\\S"
- action: "head"
n: 20
on_error: "passthrough" # What to do if the pipeline fails: "passthrough" or "empty".
Match Rules
command is matched exactly against the first token of the shell command.
subcommand is matched against the first non-flag argument.
- Flag matching uses prefix matching:
"-v" matches both -v and -verbose.
- Registry lookup is O(1) by key
"command" or "command:subcommand".
Inject Behavior
- Injected
args are inserted before any -- separator, otherwise appended.
defaults only apply if their flag key is not already present in the user's args.
- If any flag in
skip_if_present is found, the entire inject block is skipped.
The 16 Pipeline Actions
Line Filtering
| Action |
Params |
Description |
keep_lines |
pattern (regex) |
Keep only lines matching the pattern |
remove_lines |
pattern (regex) |
Remove lines matching the pattern |
head |
n (int, default 10), overflow_msg (string, default "+{remaining} more lines") |
Keep first N lines |
tail |
n (int, default 10) |
Keep last N lines |
dedup |
normalize ([]string of regexes to strip before comparing), top (int, 0=all) |
Deduplicate lines, output "text (xN)" for repeats |
Line Transformation
| Action |
Params |
Description |
truncate_lines |
max (int, default 80), ellipsis (string, default "...") |
Truncate long lines |
strip_ansi |
(none) |
Remove ANSI escape codes |
compact_path |
(none) |
Remove directory prefixes from file paths |
Extraction & Grouping
| Action |
Params |
Description |
regex_extract |
pattern (regex with capture groups), format (string using $0, $1, $2...) |
Extract data via regex capture groups |
group_by |
pattern (regex with capture group), format (template, default "{{.Key}}: {{.Count}}"), top (int) |
Group lines by capture group, count occurrences |
aggregate |
patterns (map of name->regex), format (Go template) |
Count matches for named patterns across all input |
state_machine |
states (map of state definitions with keep, until, next) |
Stateful line filtering with transitions |
JSON Processing
| Action |
Params |
Description |
json_extract |
fields ([]string), format (template, optional) |
Extract fields from JSON input |
json_schema |
max_depth (int, default 3) |
Output JSON type schema |
ndjson_stream |
group_by (string field name), format (template with .Key, .Count, .Events) |
Process newline-delimited JSON |
Formatting
| Action |
Params |
Description |
format_template |
template (Go text/template, required) |
Format output using Go template |
Template Data for format_template
The template receives:
{{.lines}} - all current lines joined with newlines
{{.count}} - number of lines
{{.groups}} - map from group_by action (if used earlier in pipeline)
{{.stats}} - map from aggregate action (if used earlier in pipeline)
Metadata Flow Between Actions
group_by sets metadata "groups" (map[string]int)
aggregate sets metadata "stats" (map[string]int)
format_template can access both via {{.groups}} and {{.stats}}
- All other actions pass metadata through unchanged
Design Principles
- Start with
keep_lines pattern "\\S" to strip blank lines early.
- Use
inject to request machine-readable output (e.g., --json, --porcelain) then filter that structured data.
- Respect user intent: use
exclude_flags to skip filtering when the user explicitly requests a different format.
- Always set
on_error: "passthrough" so raw output is returned if filtering fails.
- Chain actions from broad to specific: filter noise first, then extract, then format.
- Keep output minimal but useful: the goal is 60-90% token reduction while preserving actionable information.
Examples
Simple: remove noise lines
name: "npm-install"
version: 1
description: "Condensed npm install output"
match:
command: "npm"
subcommand: "install"
pipeline:
- action: "remove_lines"
pattern: "^(npm warn|npm notice)"
- action: "keep_lines"
pattern: "\\S"
- action: "aggregate"
patterns:
added: "^added "
removed: "^removed "
up_to_date: "up to date"
format: "{{if gt .up_to_date 0}}up to date{{else}}{{.added}} added, {{.removed}} removed{{end}}"
on_error: "passthrough"
Intermediate: inject flags + extract structured data
name: "go-test"
version: 1
description: "Condensed go test output with pass/fail summary"
match:
command: "go"
subcommand: "test"
exclude_flags: ["-json", "-v", "-bench", "-run"]
inject:
args: ["-json"]
skip_if_present: ["-json", "-v", "-bench"]
pipeline:
- action: "keep_lines"
pattern: "\\S"
- action: "keep_lines"
pattern: "\"Test\":\""
- action: "keep_lines"
pattern: "\"Action\":\"(pass|fail)\""
- action: "aggregate"
patterns:
passed: '"Action":"pass"'
failed: '"Action":"fail"'
format: "{{if and (eq .passed 0) (eq .failed 0)}}No tests found{{else}}{{.passed}} passed, {{.failed}} failed{{end}}"
on_error: "passthrough"
Advanced: state machine for multi-section output
name: "cargo-test"
version: 1
description: "Condensed cargo test output"
match:
command: "cargo"
subcommand: "test"
pipeline:
- action: "remove_lines"
pattern: "^\\s*(Compiling|Downloading|Downloaded|Updating|Running|Executable)"
- action: "keep_lines"
pattern: "\\S"
- action: "state_machine"
states:
start:
keep: "^(test |running |test result)"
until: "^failures"
next: "failures"
failures:
keep: "."
until: "^$"
next: "done"
- action: "aggregate"
patterns:
pass: "\\.\\.\\. ok$"
fail: "\\.\\.\\. FAILED$"
ignored: "\\.\\.\\. ignored$"
- action: "format_template"
template: "{{.lines}}"
on_error: "passthrough"
Workflow to Create a New Filter
- Identify the command and its typical verbose output.
- Run the command and capture raw output to understand the structure.
- Decide what to keep: what information does the LLM actually need?
- Check if the tool has a machine-readable flag (--json, --porcelain, etc.) that would make filtering easier -- use
inject if so.
- Write the pipeline: strip blanks, filter/extract, aggregate, format.
- Test the filter by placing it in
~/.config/snip/filters/ and running the command through snip.
- To contribute: add the YAML to
filters/ in the repo and submit a PR.
Source: edouard-claude/snip — distributed by TomeVault.
1---2name: snip3description: You are an expert at writing declarative YAML filters for **snip**, a CLI proxy that reduces LLM token consumption by filtering shell output. Use when this capability is needed.4---5# Creating snip Filters67You are an expert at writing declarative YAML filters for **snip**, a CLI proxy that reduces LLM token consumption by filtering shell output.89## Filter File Location1011- **Built-in filters**: `filters/*.yaml` (embedded in the binary at build time)12- **User filters**: `~/.config/snip/filters/*.yaml` (override built-in filters by name)13- **Per-project filters**: configure additional directories via `filters.dir` array in `~/.config/snip/config.toml` (e.g. `dir = ["~/.config/snip/filters", "${env.PWD}/.snip"]`). Later directories take priority.1415## Filter Structure1617Every filter is a YAML file with this structure:1819```yaml20name: "tool-subcommand" # Required. Unique identifier, used for registry lookup.21version: 1 # Schema version (always 1 for now).22description: "What this filter does" # Human-readable purpose.2324match: # Required. When to apply this filter.25 command: "tool" # Required. The CLI tool name (e.g., "git", "go", "npm").26 subcommand: "sub" # Optional. First non-flag argument (e.g., "test", "log").27 exclude_flags: ["-v", "--json"] # Optional. Skip filter if user passes any of these.28 require_flags: ["--all"] # Optional. Only apply if user passes ALL of these.2930inject: # Optional. Modify command args before execution.31 args: ["--json"] # Arguments to append to the command.32 defaults: # Flag defaults, only added if flag not already present.33 "-n": "10"34 skip_if_present: ["--json"] # Don't inject anything if any of these flags are present.3536streams: ["stdout", "stderr"] # Optional. Which streams to filter. Default: ["stdout"].37 # Use ["stderr"] for tools that output to stderr (e.g., bun test).38 # Use ["stdout", "stderr"] to filter both streams merged together.3940pipeline: # Required. Ordered list of transformation actions.41 - action: "keep_lines"42 pattern: "\\S"43 - action: "head"44 n: 204546on_error: "passthrough" # What to do if the pipeline fails: "passthrough" or "empty".47```4849## Match Rules5051- `command` is matched exactly against the first token of the shell command.52- `subcommand` is matched against the first non-flag argument.53- Flag matching uses **prefix matching**: `"-v"` matches both `-v` and `-verbose`.54- Registry lookup is O(1) by key `"command"` or `"command:subcommand"`.5556## Inject Behavior5758- Injected `args` are inserted before any `--` separator, otherwise appended.59- `defaults` only apply if their flag key is not already present in the user's args.60- If any flag in `skip_if_present` is found, the entire inject block is skipped.6162## The 16 Pipeline Actions6364### Line Filtering6566| Action | Params | Description |67|--------|--------|-------------|68| `keep_lines` | `pattern` (regex) | Keep only lines matching the pattern |69| `remove_lines` | `pattern` (regex) | Remove lines matching the pattern |70| `head` | `n` (int, default 10), `overflow_msg` (string, default "+{remaining} more lines") | Keep first N lines |71| `tail` | `n` (int, default 10) | Keep last N lines |72| `dedup` | `normalize` ([]string of regexes to strip before comparing), `top` (int, 0=all) | Deduplicate lines, output "text (xN)" for repeats |7374### Line Transformation7576| Action | Params | Description |77|--------|--------|-------------|78| `truncate_lines` | `max` (int, default 80), `ellipsis` (string, default "...") | Truncate long lines |79| `strip_ansi` | (none) | Remove ANSI escape codes |80| `compact_path` | (none) | Remove directory prefixes from file paths |8182### Extraction & Grouping8384| Action | Params | Description |85|--------|--------|-------------|86| `regex_extract` | `pattern` (regex with capture groups), `format` (string using $0, $1, $2...) | Extract data via regex capture groups |87| `group_by` | `pattern` (regex with capture group), `format` (template, default "{{.Key}}: {{.Count}}"), `top` (int) | Group lines by capture group, count occurrences |88| `aggregate` | `patterns` (map of name->regex), `format` (Go template) | Count matches for named patterns across all input |89| `state_machine` | `states` (map of state definitions with `keep`, `until`, `next`) | Stateful line filtering with transitions |9091### JSON Processing9293| Action | Params | Description |94|--------|--------|-------------|95| `json_extract` | `fields` ([]string), `format` (template, optional) | Extract fields from JSON input |96| `json_schema` | `max_depth` (int, default 3) | Output JSON type schema |97| `ndjson_stream` | `group_by` (string field name), `format` (template with .Key, .Count, .Events) | Process newline-delimited JSON |9899### Formatting100101| Action | Params | Description |102|--------|--------|-------------|103| `format_template` | `template` (Go text/template, required) | Format output using Go template |104105### Template Data for `format_template`106107The template receives:108- `{{.lines}}` - all current lines joined with newlines109- `{{.count}}` - number of lines110- `{{.groups}}` - map from `group_by` action (if used earlier in pipeline)111- `{{.stats}}` - map from `aggregate` action (if used earlier in pipeline)112113### Metadata Flow Between Actions114115- `group_by` sets metadata `"groups"` (map[string]int)116- `aggregate` sets metadata `"stats"` (map[string]int)117- `format_template` can access both via `{{.groups}}` and `{{.stats}}`118- All other actions pass metadata through unchanged119120## Design Principles1211221. **Start with `keep_lines` pattern `"\\S"`** to strip blank lines early.1232. **Use `inject` to request machine-readable output** (e.g., `--json`, `--porcelain`) then filter that structured data.1243. **Respect user intent**: use `exclude_flags` to skip filtering when the user explicitly requests a different format.1254. **Always set `on_error: "passthrough"`** so raw output is returned if filtering fails.1265. **Chain actions from broad to specific**: filter noise first, then extract, then format.1276. **Keep output minimal but useful**: the goal is 60-90% token reduction while preserving actionable information.128129## Examples130131### Simple: remove noise lines132133```yaml134name: "npm-install"135version: 1136description: "Condensed npm install output"137match:138 command: "npm"139 subcommand: "install"140pipeline:141 - action: "remove_lines"142 pattern: "^(npm warn|npm notice)"143 - action: "keep_lines"144 pattern: "\\S"145 - action: "aggregate"146 patterns:147 added: "^added "148 removed: "^removed "149 up_to_date: "up to date"150 format: "{{if gt .up_to_date 0}}up to date{{else}}{{.added}} added, {{.removed}} removed{{end}}"151on_error: "passthrough"152```153154### Intermediate: inject flags + extract structured data155156```yaml157name: "go-test"158version: 1159description: "Condensed go test output with pass/fail summary"160match:161 command: "go"162 subcommand: "test"163 exclude_flags: ["-json", "-v", "-bench", "-run"]164inject:165 args: ["-json"]166 skip_if_present: ["-json", "-v", "-bench"]167pipeline:168 - action: "keep_lines"169 pattern: "\\S"170 - action: "keep_lines"171 pattern: "\"Test\":\""172 - action: "keep_lines"173 pattern: "\"Action\":\"(pass|fail)\""174 - action: "aggregate"175 patterns:176 passed: '"Action":"pass"'177 failed: '"Action":"fail"'178 format: "{{if and (eq .passed 0) (eq .failed 0)}}No tests found{{else}}{{.passed}} passed, {{.failed}} failed{{end}}"179on_error: "passthrough"180```181182### Advanced: state machine for multi-section output183184```yaml185name: "cargo-test"186version: 1187description: "Condensed cargo test output"188match:189 command: "cargo"190 subcommand: "test"191pipeline:192 - action: "remove_lines"193 pattern: "^\\s*(Compiling|Downloading|Downloaded|Updating|Running|Executable)"194 - action: "keep_lines"195 pattern: "\\S"196 - action: "state_machine"197 states:198 start:199 keep: "^(test |running |test result)"200 until: "^failures"201 next: "failures"202 failures:203 keep: "."204 until: "^$"205 next: "done"206 - action: "aggregate"207 patterns:208 pass: "\\.\\.\\. ok$"209 fail: "\\.\\.\\. FAILED$"210 ignored: "\\.\\.\\. ignored$"211 - action: "format_template"212 template: "{{.lines}}"213on_error: "passthrough"214```215216## Workflow to Create a New Filter2172181. **Identify the command** and its typical verbose output.2192. **Run the command** and capture raw output to understand the structure.2203. **Decide what to keep**: what information does the LLM actually need?2214. **Check if the tool has a machine-readable flag** (--json, --porcelain, etc.) that would make filtering easier -- use `inject` if so.2225. **Write the pipeline**: strip blanks, filter/extract, aggregate, format.2236. **Test the filter** by placing it in `~/.config/snip/filters/` and running the command through snip.2247. **To contribute**: add the YAML to `filters/` in the repo and submit a PR.225226---227> Source: [edouard-claude/snip](https://github.com/edouard-claude/snip) — distributed by [TomeVault](https://tomevault.io).228<!-- tomevault:4.0:skill_md:2026-06-17 -->