Filter
zalando/skipper
Create or modify code in the filters package and all its sub-folders
This skill should be used when the user asks to "create a filter", "write a tokf filter", "add a filter for <tool", "how do I filter output", or needs guidance on tokf filter step types, templates…
$ npx skills add mpecan/tokf --skill tokf-filter -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install mpecan/tokf tokf-filter --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/mpecan/tokf.git skills-src && mkdir -p .claude/skills && cp -r skills-src/crates/tokf-cli/skills/tokf-filter .claude/skills/tokf-filter && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "tokf-filter" agent skill from https://github.com/mpecan/tokf/tree/main/crates/tokf-cli/skills/tokf-filter into .claude/skills/tokf-filter/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "tokf-filter", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/mpecan/tokf/tree/main/crates/tokf-cli/skills/tokf-filterType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add mpecan/tokf --skill tokf-filter -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install mpecan/tokf tokf-filter --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/mpecan/tokf.git skills-src && mkdir -p .agents/skills && cp -r skills-src/crates/tokf-cli/skills/tokf-filter .agents/skills/tokf-filter && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "tokf-filter" agent skill from https://github.com/mpecan/tokf/tree/main/crates/tokf-cli/skills/tokf-filter into .agents/skills/tokf-filter/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "tokf-filter", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add mpecan/tokf --skill tokf-filter -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install mpecan/tokf tokf-filter --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/mpecan/tokf.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/crates/tokf-cli/skills/tokf-filter .cursor/skills/tokf-filter && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "tokf-filter" agent skill from https://github.com/mpecan/tokf/tree/main/crates/tokf-cli/skills/tokf-filter into .cursor/skills/tokf-filter/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "tokf-filter", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/mpecan/tokf.git --path crates/tokf-cli/skills/tokf-filter--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add mpecan/tokf --skill tokf-filter -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install mpecan/tokf tokf-filter --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/mpecan/tokf.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/crates/tokf-cli/skills/tokf-filter .gemini/skills/tokf-filter && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "tokf-filter" agent skill from https://github.com/mpecan/tokf/tree/main/crates/tokf-cli/skills/tokf-filter into .gemini/skills/tokf-filter/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "tokf-filter", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install mpecan/tokf tokf-filterInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add mpecan/tokf --skill tokf-filter -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/mpecan/tokf.git skills-src && mkdir -p .github/skills && cp -r skills-src/crates/tokf-cli/skills/tokf-filter .github/skills/tokf-filter && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "tokf-filter" agent skill from https://github.com/mpecan/tokf/tree/main/crates/tokf-cli/skills/tokf-filter into .github/skills/tokf-filter/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "tokf-filter", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add mpecan/tokf --skill tokf-filter -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install mpecan/tokf tokf-filter --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/mpecan/tokf.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/crates/tokf-cli/skills/tokf-filter .opencode/skills/tokf-filter && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "tokf-filter" agent skill from https://github.com/mpecan/tokf/tree/main/crates/tokf-cli/skills/tokf-filter into .opencode/skills/tokf-filter/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "tokf-filter", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
tokf-filterThis skill should be used when the user asks to "create a filter", "write a tokf filter", "add a filter for <tool", "how do I filter output", or needs guidance on tokf filter step types, templates…
Tokf Filter is an agent skill from mpecan/tokf. This skill should be used when the user asks to "create a filter", "write a tokf filter", "add a filter for <tool", "how do I filter output", or needs guidance on tokf filter step types, templates, pipes, or placement conventions.
Its SKILL.md is about 9.9k tokens, which your agent loads only when the skill is triggered. The skill folder holds 3 other files, including reference files (for example `references/step-reference.md`).
The repository describes itself as: Config-driven CLI tool that compresses command output before it reaches an LLM context. The licence is MIT.
5 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 87c93d9. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
gitnpmkubectldockerghcargoFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use git, npm, kubectl, docker and gh, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Tokf Filter loads about 9.9k tokens when it runs, and up to ~20k if it reads all its reference files. Until then it costs about 61 tokens; SKILL.md has 3,552 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from mpecan/tokf at commit 87c93d9, republished under its MIT licence (© mpecan). 3,552 words, ~9,874 tokens.
.claude/skills/tokf-filter/SKILL.md (or your agent's skills folder). This skill also uses 2 other files; get the full folder from GitHub.You are an expert at writing tokf filter files. tokf is a config-driven CLI that compresses command output before it reaches an LLM context. Filters are TOML files that define how to process a command's output.
When the user asks you to create or modify a filter, follow this guide exactly. Produce valid, idiomatic TOML that matches the schema described below.
A filter file is a TOML file that describes:
command)Filters live in three places, searched in priority order:
.tokf/filters/ — project-local (repo-level overrides)~/.config/tokf/filters/ — user-level overridesFirst match wins. Use tokf which "cargo test" to see which filter would activate for a given command.
Steps execute in this fixed order — do not rearrange them:
match_output — whole-output substring checks; if matched, short-circuits the entire pipeline and emits immediately[[replace]] — per-line regex transforms applied to every line, in array orderstrip_ansi / trim_lines — per-line cleanup (ANSI stripping, whitespace trimming)skip / keep — line-level filtering (drop or retain lines by regex)dedup / dedup_window — collapse duplicate consecutive lineslua_script — Luau escape hatch; runs after dedup, before JSON/section/parse[json] — JSON extraction via JSONPath; when configured, replaces section/parse/chunk[[section]] OR [parse] — structured extraction (these are mutually exclusive; section is a state machine, parse is a declarative grouper). Skipped when [json] is configured.[[chunk]] — block-based structured extraction with per-block aggregation, grouping, and tree output (runs on raw output, alongside sections). Skipped when [json] is configured.[on_success] or [on_failure] depending on exit code[fallback] — if neither on_success nor on_failure produced outputstrip_empty_lines / collapse_empty_lines — post-processing cleanup on the final outputWithin [on_success] and [on_failure], fields are processed as:
head / tail → trim linesskip / extract → further filteraggregate → reduce collected sectionsoutput → final template render| Field | Type | Default | Description |
|---|---|---|---|
command | string or array of strings | required | Command pattern(s) to match. Supports * wildcard. |
run | string | (same as command) | Override the actual command executed. Use {args} to forward arguments. |
match_output | array of tables | [] | Whole-output checks. Short-circuit on first match. |
[[replace]] | array of tables | [] | Per-line regex replacements, in order. |
skip | array of strings (regex) | [] | Drop lines matching any regex. |
keep | array of strings (regex) | [] | Retain only lines matching any regex. (Inverse of skip.) |
dedup | bool | false | Collapse consecutive identical lines. |
dedup_window | integer | 0 (off) | Dedup within a sliding window of N lines. |
strip_ansi | bool | false | Strip ANSI escape sequences before skip/keep. |
trim_lines | bool | false | Trim leading/trailing whitespace from each line. |
lua_script | table | (absent) | Luau escape hatch. |
[json] | table | (absent) | JSON extraction via JSONPath. When configured, replaces [[section]]/[parse]/[[chunk]]. |
[[section]] | array of tables | [] | State-machine section collectors. |
[[chunk]] | array of tables | [] | Block-based structured extraction with per-block aggregation and grouping. |
[parse] | table | (absent) | Declarative structured parser (branch + group). |
[on_success] | table | (absent) | Output branch for exit code 0. |
[on_failure] | table | (absent) | Output branch for non-zero exit. |
[output] | table | (absent) | Top-level output template (used by [parse]). |
[fallback] | table | (absent) | Fallback when no branch matched. |
strip_empty_lines | bool | false | Remove all blank lines from the final output. |
collapse_empty_lines | bool | false | Collapse consecutive blank lines into one. |
show_history_hint | bool | false | Append a hint line after filtered output pointing to the full output in history. |
[[variant]] | array of tables | [] | Context-aware delegation to specialized child filters. |
match_output — Whole-Output Short-CircuitCheck the entire raw output for a substring. If matched, emit a fixed string and stop — no further processing.
match_output = [
{ contains = "Everything up-to-date", output = "ok (up-to-date)" },
{ contains = "rejected", output = "✗ push rejected (try pulling first)" },
]contains: literal substring to search for (case-sensitive)output: string to emit if matched{line_containing} template variable: the first line that contains the substringmatch_output = [
{ contains = "error", output = "Error on: {line_containing}" },
]When to use: for well-known one-liner outcomes that make the rest of filtering irrelevant (e.g., "already up to date", "nothing to push", "authentication failed").
[[replace]] — Per-Line Regex TransformsApplied to every line, in array order, before skip/keep. Use to reformat noisy lines.
[[replace]]
pattern = '^(\S+)\s+\S+\s+(\S+)\s+(\S+)'
output = "{1}: {2} → {3}"
[[replace]]
pattern = '^\s+Compiling (\S+) v(\S+)'
output = "compiling {1}@{2}"pattern: Rust regex (RE2 syntax, no lookaheads)output: template with {1}, {2}, … for capture groups; {0} is the full matchWhen to use: when a line contains useful information but in a verbose format — reformat it rather than dropping it.
skip / keep — Line Filteringskip drops lines matching any regex. keep retains only lines matching any regex. They compose:
skip = [
"^\\s*Compiling ",
"^\\s*Downloading ",
"^\\s*$",
]
keep = ["^error", "^warning"][[replace]]skip is checked first, then keepWhen to use: skip for removing known noise patterns; keep for allow-listing (e.g., keep only lines that start with error or warning).
Also available inside [on_success] and [on_failure] for branch-level filtering.
dedup / dedup_window — Deduplicationdedup = true # collapse consecutive identical lines
dedup_window = 10 # dedup within a 10-line sliding windowdedup = true: removes consecutive duplicate lines (like uniq)dedup_window = N: deduplicates within a sliding window of N lines (catches near-consecutive repeats)When to use: for commands that emit repetitive progress lines (e.g., npm install printing the same package multiple times, spinner frames, repeated warnings).
lua_script — Luau Escape HatchFor logic that pure TOML cannot express: numeric math, multi-line lookahead, conditional branching.
[lua_script]
lang = "luau"
source = '''
if exit_code == 0 then
return "passed"
else
local msg = output:match("Error: (.+)") or "unknown error"
return "FAILED: " .. msg
end
'''Or load the script from an external file:
[lua_script]
lang = "luau"
file = "scripts/my-filter.luau"The file path resolves relative to the current working directory. Exactly one of source or file must be set.
Globals available:
output (string): the full output after skip/keep/dedupexit_code (integer): the command's exit codeargs (table of strings): the arguments passed to the commandReturn semantics:
nil → fall through to [[section]] / [parse] / [on_success] / [on_failure]Sandbox: io, os, and package are blocked. No filesystem or network access. Standard math/string/table libraries are available.
When to use: only when no TOML step can express the logic. Most filters do not need this. Consider it after exhausting match_output, skip/keep, [[replace]], [[section]], and [parse].
[json] — JSON Extraction via JSONPathFor commands that produce JSON output (e.g. kubectl get pods -o json, gh api, docker inspect). Extracts values using JSONPath (RFC 9535) queries and produces template variables and structured collections.
[json]
[[json.extract]]
path = "$.items[*]"
as = "pods"
[[json.extract.fields]]
field = "metadata.name"
as = "name"
[[json.extract.fields]]
field = "status.phase"
as = "phase"
[on_success]
output = "Pods ({pods_count}):\n{pods | each: \" {name}: {phase}\" | join: \"\\n\"}"[[json.extract]] fields:
| Field | Type | Required | Description |
|---|---|---|---|
path | string | yes | JSONPath expression (RFC 9535), e.g. "$.items[*]", "$.version" |
as | string | yes | Variable name to bind the result to |
fields | array of tables | no | Sub-field extraction for each matched object |
[[json.extract.fields]] fields:
| Field | Type | Required | Description |
|---|---|---|---|
field | string | yes | Dot-separated path within each object (e.g. "metadata.name", "containers.0.name"). Not JSONPath — uses simple dot-notation. Supports numeric array indices. |
as | string | yes | Variable name for the extracted value |
Result mapping:
vars["as_name"] = string_value (no count, no chunk)ChunkData::Flat collection + {as_name_count} variablefields → top-level scalars auto-flattened into chunk itemsfields → named fields extracted per itemPipeline behavior: when [json] is configured, [[section]], [parse], and [[chunk]] are skipped. JSON replaces line-based structural processing. Extracted vars and chunks flow into [on_success]/[on_failure] template rendering.
Error handling: invalid JSON input → extraction skipped, pipeline falls back to raw output (templates are not rendered). Invalid JSONPath → rule silently skipped, other rules still run. Empty array with fields → emits {as_name_count} = "0".
When to use: when the command produces structured JSON output and you need to extract specific fields. Prefer this over [parse] + skip/keep for JSON-native commands.
[[section]] — State-Machine Section CollectorThe most powerful step. Defines a state machine that collects lines into named variables as it scans top-to-bottom.
[[section]]
name = "failures"
enter = "^failures:$" # regex: start collecting when this matches
exit = "^failures:$" # regex: stop collecting when this matches (after start)
split_on = "^\\s*$" # regex: split collected lines into blocks on blank lines
collect_as = "failure_blocks"
[[section]]
name = "summary"
match = "^test result:" # regex: collect only lines matching this (no enter/exit)
collect_as = "summary_lines"Fields:
| Field | Required | Description |
|---|---|---|
name | yes | Identifier for this section (used in error messages) |
enter | no | Regex to start collecting (state transitions to "inside") |
exit | no | Regex to stop collecting (state transitions to "outside") |
match | no | Collect any line matching this regex, without enter/exit state |
split_on | no | Split collected lines into blocks when this regex matches |
collect_as | yes | Variable name to bind the result to |
Accessing collected variables in templates:
| Expression | Type | Description |
|---|---|---|
{name} | string | Full collected text joined with newlines |
{name.lines} | collection | Individual lines as a list |
{name.blocks} | collection | Blocks split by split_on |
{name.count} | integer | Number of blocks (or lines if no split_on) |
When to use: when the output has distinct sections with clear start/end markers — test failure blocks, error sections, file change groups.
[[chunk]] — Block-Based Structured ExtractionChunks split raw output into repeating structural blocks (e.g., per-crate test suites in a Cargo workspace), extract structured data per-block, and produce named collections for template rendering. Like sections, chunks operate on the raw (unfiltered) command output — skip/keep patterns do not affect chunk processing.
[[chunk]]
split_on = "^\\s*Running " # regex marking the start of each chunk
include_split_line = true # include the splitting line in the chunk (default: true)
collect_as = "suites_detail" # name for the structured collection
group_by = "crate_name" # merge chunks sharing this field value
children_as = "children" # preserve original items as nested collection
[chunk.extract]
pattern = 'unittests.+deps/([\w_-]+)-' # extract a field from the header line
as = "crate_name"
carry_forward = true # inherit value from previous chunk when pattern doesn't match
[[chunk.body_extract]]
pattern = 'Running\s+(.+?)\s+\('
as = "suite_name"
[[chunk.aggregate]]
pattern = '(\d+) passed' # aggregates run within each chunk's lines
sum = "passed"
[[chunk.aggregate]]
pattern = '^test result:'
count_as = "suite_count"Fields:
| Field | Type | Required | Description |
|---|---|---|---|
split_on | string (regex) | yes | Regex marking the start of each chunk |
include_split_line | bool | no | Whether the splitting line is part of the chunk (default: true) |
collect_as | string | yes | Name for the resulting structured collection |
extract | table | no | Extract a named field from the header line (pattern + as) |
body_extract | array of tables | no | Extract fields from body lines (pattern + as, first match wins) |
aggregate | array of tables | no | Per-chunk aggregation rules (pattern + sum/count_as) |
group_by | string | no | Merge chunks sharing the same field value, summing numeric fields |
children_as | string | no | When set with group_by, preserve original items as a nested collection |
carry_forward (on extract or body_extract): when a chunk's pattern doesn't match, inherit the value from the most recent chunk that did. Useful when boundary markers (like Running unittests) identify a group, and subsequent chunks should inherit that identity.
Structured collections in templates: each item has named fields accessible in each pipes:
[on_success]
output = """\
{suites_detail | each: " {crate_name}: {passed} passed ({suite_count} suites)" | join: "\\n"}"""Tree output with children_as: groups preserve their child items for nested template rendering:
[on_success]
output = """\
{suites_detail | each: " {crate_name}: {passed} passed\\n{children | each: \" {suite_name}: {passed} passed\" | join: \"\\n\"}" | join: "\\n"}"""When to use: when output contains repeating structural blocks with per-block data you want to aggregate and display. Common for workspace build tools (Cargo, Gradle, Nx) where output is organized by sub-project.
[parse] — Declarative Structured ParserAlternative to [[section]] for commands with table-like output. Declaratively extracts a header field and groups remaining lines.
[parse]
branch = { line = 1, pattern = '## (\S+?)(?:\.\.\.(\S+))?(?:\s+\[(.+)\])?$', output = "{1}" }
[parse.group]
key = { pattern = '^(.{2}) ', output = "{1}" }
labels = { "M " = "modified", "??" = "untracked", "D " = "deleted" }
[output]
format = """
{branch}{tracking_info}
{group_counts}"""
group_counts_format = " {label}: {count}"
empty = "clean — nothing to commit"[parse] fields:
| Field | Description |
|---|---|
branch | Extract a single value from a specific line (line, pattern, output) |
[parse.group] | Group remaining lines by a key pattern |
[parse.group] fields:
| Field | Description |
|---|---|
key | { pattern, output } — extract the grouping key from each line |
labels | Map from raw key string to human-readable label |
[output] fields (used with [parse]):
| Field | Description |
|---|---|
format | Template string for the overall output |
group_counts_format | Template for each group entry: {label}, {count} |
empty | String to emit when no lines were grouped |
When to use: for commands like git status, docker ps, kubectl get — table-formatted output where you want to extract a header and count/group rows.
[on_success] / [on_failure] — Exit Code BranchesThese branches run after all top-level steps. They have their own sub-fields:
[on_success]
output = "ok ✓ {2}" # template; collected variables are available
head = 20 # keep first N lines
tail = 10 # keep last N lines
skip = ["^\\s*$"] # additional line filtering
extract = { pattern = '(\S+)\s*->\s*(\S+)', output = "ok ✓ {2}" }
# Singular form (one rule):
aggregate = { from = "summary_lines", pattern = 'ok\. (\d+) passed', sum = "passed", count_as = "suites" }
# Plural form (multiple rules):
# [[on_success.aggregates]]
# from = "summary_lines"
# pattern = 'ok\. (\d+) passed'
# sum = "passed"
# count_as = "suites"
#
# [[on_success.aggregates]]
# from = "summary_lines"
# pattern = '(\d+) failed'
# sum = "failed"
[on_failure]
tail = 10
output = "FAILED: {summary_lines | join: \"\\n\"}"Branch sub-fields:
| Field | Description |
|---|---|
output | Template string for the output. Has access to all [[section]] and [[chunk]] variables. {output} = the filtered output text. |
head | Keep first N lines of filtered output |
tail | Keep last N lines of filtered output |
skip | Array of regexes to filter output lines within this branch |
extract | { pattern, output } — find first match, render template with capture groups |
aggregate | Reduce collected section lines into numeric summaries (singular form) |
aggregates | Array of aggregate rules (plural form — use [[on_success.aggregates]]) |
aggregate / aggregates fields:
| Field | Description |
|---|---|
from | Variable name (a collect_as result from [[section]]) |
pattern | Regex with one capture group to extract a number |
sum | Variable name to bind the sum to |
count_as | Variable name to bind the count (number of lines matched) to |
Both singular aggregate and plural aggregates can be used together — they are merged at runtime.
When to use: Always. Every filter should have at least one of [on_success] or [on_failure]. Use [on_success] to produce a clean summary. Use [on_failure] to show enough context to diagnose the issue.
[fallback] — Last ResortEmits output when neither [on_success] nor [on_failure] produced anything.
[fallback]
tail = 5When to use: as a safety net when you have complex branching logic. Ensures tokf never silently swallows output.
[[variant]] — Context-Aware Filter DelegationSome commands are wrappers around different underlying tools (e.g. npm test may run Jest, Vitest, or Mocha). A parent filter can declare [[variant]] entries that delegate to specialized child filters based on project context.
command = ["npm test", "pnpm test", "yarn test"]
strip_ansi = true
skip = ["^> ", "^\\s*npm (warn|notice|WARN|verbose|info|timing|error|ERR)"]
[on_success]
output = "{output}"
[on_failure]
tail = 20
[[variant]]
name = "vitest"
detect.files = ["vitest.config.ts", "vitest.config.js", "vitest.config.mts"]
filter = "npm/test-vitest"
[[variant]]
name = "jest"
detect.files = ["jest.config.js", "jest.config.ts", "jest.config.json"]
filter = "npm/test-jest"Fields:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Human-readable identifier for this variant |
detect.files | array of strings | no | File paths to check in CWD (pre-execution detection) |
detect.output_pattern | string (regex) | no | Regex to match against command output (post-execution fallback) |
filter | string | yes | Filter to delegate to (relative path without .toml, e.g. "npm/test-vitest") |
Two-phase detection:
At least one of detect.files or detect.output_pattern must be set.
Behavior:
skip, on_success, etc.) apply as the fallbackfilter field references another filter by its discovery name (e.g. "npm/test-vitest" maps to filters/npm/test-vitest.toml)TOML ordering: [[variant]] entries must appear after all top-level fields (skip, [on_success], etc.) because TOML array-of-tables sections capture subsequent keys.
When to use: when a single command pattern maps to different underlying tools that produce fundamentally different output formats. Create a parent filter with a generic fallback, then create specialized child filters for each tool.
Output templates support pipe chains: {var | pipe | pipe: "arg"}.
| Pipe | Input → Output | Description |
|---|---|---|
lines | Str → Collection | Split string on newlines into a list |
join: "sep" | Collection → Str | Join list items with separator string |
each: "tmpl" | Collection → Collection | Map each item through a sub-template; {value} = item, {index} = 1-based index. For structured collections (from chunks), all named fields are also available (e.g. {crate_name}, {passed}). |
keep: "re" | Collection → Collection | Retain items matching the regex |
where: "re" | Collection → Collection | Alias for keep: |
truncate: N | Str → Str | Truncate to N characters, appending … |
Examples:
Filter a multi-line output variable to only error lines:
[on_failure]
output = "{output | lines | keep: \"^error\" | join: \"\\n\"}"For each collected block, show only > (pointer) and E (assertion) lines:
[on_failure]
output = "{failure_blocks | each: \"{value | lines | keep: \\\"^[>E] \\\"}\" | join: \"\\n\"}"Truncate long lines and number them:
[on_failure]
output = "{summary_lines | each: \"{index}. {value | truncate: 120}\" | join: \"\\n\"}"File naming:
filters/<tool>/<subcommand>.toml for two-word commands: filters/git/push.toml for git pushfilters/<tool>.toml for single-word commands: filters/pytest.toml for pytestfilters/npm/run.toml with command = "npm run *" in the TOMLPlacement:
| Location | Purpose |
|---|---|
.tokf/filters/ | Project-local override (committed to the repo) |
~/.config/tokf/filters/ | User-level override (your personal filters) |
filters/ in the tokf source repo | Built-in library (requires a tokf release) |
When creating a filter for a user's project, default to .tokf/filters/ unless they specify otherwise.
Command field:
command = "git push" matches git push and git push origin maincommand = "npm run *" matches npm run dev, npm run build, etc.command = ["cargo test", "cargo t"] matches either formFollow these steps when asked to create a filter:
Ask the user to provide (or capture) example output from the command. If they don't have it, generate a plausible example based on the tool's known output format. Look for:
| Level | When to use | Steps to use |
|---|---|---|
| Level 1 (simple) | Command produces one-liner outcomes | match_output, skip, extract |
| Level 2 (structured) | Table-like output needing grouping | [parse] + [output] |
| Level 2J (JSON) | Command produces JSON output | [json] + on_success/on_failure templates |
| Level 3 (stateful) | Multi-section output with nested structure | [[section]] + aggregate + pipes |
| Level 4 (chunked) | Repeating blocks with per-block aggregation (workspaces) | [[chunk]] + [[section]] + aggregates + tree templates |
Start at the lowest level that handles the use case. Don't reach for [[section]] when skip + extract suffices.
command to match the command patternmatch_output for well-known short-circuit cases (empty output, auth failure, "already done")skip to drop noise lines (progress, compile output, blank lines)[[replace]] to reformat noisy-but-useful lines[[section]] or [parse] if you need structured extraction[on_success] with the desired output format[on_failure] with enough context to diagnose (tail = 20 is a safe default)[fallback] with tail = 5 as a safety net for complex filtersCreate a <stem>_test/ directory adjacent to the filter TOML and add at least one test case per meaningful outcome (success, failure, edge cases):
filters/mytool/
mysubcmd.toml ← filter config
mysubcmd_test/ ← test suite
success.toml
failure.tomlEach test case is a TOML file:
name = "success shows one clean line"
fixture = "tests/fixtures/mytool_success.txt" # path relative to this file, then CWD
exit_code = 0
[[expect]]
equals = "ok ✓"
[[expect]]
not_contains = "noise"Or with an inline fixture (no file needed):
name = "known error message"
inline = "Error: connection refused\n"
exit_code = 1
[[expect]]
contains = "connection refused"Run the suite:
tokf verify mytool/mysubcmd # run one suite
tokf verify # run all suitesFor quick one-off testing without creating test files:
tokf apply filters/mytool/mysubcmd.toml tests/fixtures/mytool_output.txt --exit-code 0.tokf/filters/mytool/mysubcmd.toml.tokf/filters/mytool.toml.tokf/filters/mytool/run.toml with command = "mytool run *"git push (Level 1 — match_output + extract)Goal: 15 lines of push noise → "ok ✓ main" (or failure message).
# filters/git/push.toml — Level 1
# Raw output: 15 lines of object counting, compression, "remote:" lines
# Filtered (success): "ok ✓ main"
# Filtered (up-to-date): "ok (up-to-date)"
# Filtered (rejected): "✗ push rejected (try pulling first)"
command = "git push"
# Check full output for well-known outcomes before any processing
match_output = [
{ contains = "Everything up-to-date", output = "ok (up-to-date)" },
{ contains = "rejected", output = "✗ push rejected (try pulling first)" },
]
[on_success]
# Drop all the noise lines
skip = [
"^Enumerating objects:",
"^Counting objects:",
"^Delta compression",
"^Compressing objects:",
"^Writing objects:",
"^Total \\d+",
"^remote:",
"^To ",
]
# Extract the branch name from the ref update line: "abc1234..def5678 main -> main"
extract = { pattern = '(\S+)\s*->\s*(\S+)', output = "ok ✓ {2}" }
[on_failure]
tail = 10Key decisions:
match_output handles the two most common "instant" outcomesextract captures the branch name from the ref update linetail = 10 on failure gives enough context without overwhelminggit status (Level 2 — parse + group)Goal: 30+ lines of verbose status → branch name + grouped file counts.
# filters/git/status.toml — Level 2
# Raw output: 30+ lines with hints, file paths, status codes
# Filtered: "main [ahead 2]\n modified: 3\n untracked: 2"
command = "git status"
# Override: use porcelain format for reliable machine parsing
run = "git status --porcelain -b"
match_output = [
{ contains = "not a git repository", output = "Not a git repository" },
]
[parse]
# First line: "## main...origin/main [ahead 2]"
# Extract: branch name, upstream, ahead/behind info
branch = { line = 1, pattern = '## (\S+?)(?:\.\.\.(\S+))?(?:\s+\[(.+)\])?$', output = "{1}" }
[parse.group]
# Group remaining lines by their two-character status code
key = { pattern = '^(.{2}) ', output = "{1}" }
labels = {
"M " = "modified",
" M" = "modified (unstaged)",
"MM" = "modified (staged+unstaged)",
"A " = "added",
"??" = "untracked",
"D " = "deleted",
" D" = "deleted (unstaged)",
"R " = "renamed",
"UU" = "conflict",
"AM" = "added+modified"
}
[output]
format = """
{branch}{tracking_info}
{group_counts}"""
group_counts_format = " {label}: {count}"
empty = "clean — nothing to commit"Key decisions:
run overrides to porcelain format — machine-readable is easier to parse[parse] extracts the branch header line declaratively[parse.group] groups by status code without needing [[section]][output] uses built-in {group_counts} variable populated by the parsercargo test (Level 4 — section + chunk + aggregates + tree)Goal: 200+ lines with compile noise, per-test "ok" lines, failure blocks → per-crate tree summary on pass, structured failure report on fail.
# filters/cargo/test.toml — Level 4
# Raw output: 200+ lines
# Filtered (pass): "✓ cargo test: 1279 passed, 0 failed, 119 ignored (42 suites)"
# with per-crate tree breakdown showing individual test suites
# Filtered (fail): failure details + summary
command = "cargo test"
strip_ansi = true
# Drop all the noise
skip = [
"^\\s*Compiling ",
"^\\s*Downloading ",
"^\\s*Downloaded ",
"^\\s*Finished ",
"^\\s*Locking ",
"^running \\d+ tests?$",
"^test .+ \\.\\.\\. ok$", # individual passing tests
"^\\s*$",
"^\\s*Doc-tests ",
]
# State machine: collect the "failures:" section into blocks split by blank lines
[[section]]
name = "failures"
enter = "^failures:$"
exit = "^failures:$"
split_on = "^\\s*$"
collect_as = "failure_blocks"
# Collect "test result: ok/FAILED" summary lines (one per test suite)
[[section]]
name = "summary"
match = "^test result:"
collect_as = "summary_lines"
# Chunk processing: per-crate breakdown from "Running" headers.
# "unittests" lines define crate boundaries; integration test suites
# inherit the crate name via carry_forward.
[[chunk]]
split_on = "^\\s*Running "
include_split_line = true
collect_as = "suites_detail"
group_by = "crate_name"
children_as = "children"
[chunk.extract]
pattern = 'unittests.+deps/([\w_-]+)-'
as = "crate_name"
carry_forward = true
[[chunk.body_extract]]
pattern = 'Running\s+(.+?)\s+\('
as = "suite_name"
[[chunk.aggregate]]
pattern = '(\d+) passed'
sum = "passed"
[[chunk.aggregate]]
pattern = '(\d+) failed'
sum = "failed"
[[chunk.aggregate]]
pattern = '(\d+) ignored'
sum = "ignored"
[[chunk.aggregate]]
pattern = '^test result:'
count_as = "suite_count"
# Success: aggregate summaries + per-crate tree breakdown
[on_success]
output = "✓ cargo test: {passed} passed, {failed} failed, {ignored} ignored ({suites} suites)\n{suites_detail | each: \" {crate_name}: {passed} passed ({suite_count} suites)\\n{children | each: \\\" {suite_name}: {passed} passed\\\" | join: \\\"\\\\n\\\"}\" | join: \"\\n\"}"
[[on_success.aggregates]]
from = "summary_lines"
pattern = 'ok\. (\d+) passed'
sum = "passed"
count_as = "suites"
[[on_success.aggregates]]
from = "summary_lines"
pattern = '(\d+) failed'
sum = "failed"
[[on_success.aggregates]]
from = "summary_lines"
pattern = '(\d+) ignored'
sum = "ignored"
# Failure: show failure details + summary
[on_failure]
output = "✗ cargo test: {passed} passed, {failed} failed ({suites} suites)\n\nFAILURES ({failure_blocks.count}):\n{failure_blocks | each: \"\\n── {index}. ──\\n{value}\" | join: \"\\n\"}\n\n{summary_lines | join: \"\\n\"}"
[[on_failure.aggregates]]
from = "summary_lines"
pattern = '(\d+) passed'
sum = "passed"
count_as = "suites"
[[on_failure.aggregates]]
from = "summary_lines"
pattern = '(\d+) failed'
sum = "failed"
[fallback]
tail = 5Key decisions:
skip removes all per-test "ok" lines — only failures and summaries remain[[section]] collectors handle failure blocks and summary lines[[chunk]] splits on Running headers, extracts crate names from unittests linescarry_forward = true makes integration test suites inherit the crate name from the preceding unit test suitechildren_as = "children" preserves per-suite detail within each crate group[[on_success.aggregates]] (plural) sums passed/failed/ignored across all suite summary lineseach pipes produce tree output: crate → suites[fallback] catches edge cases (compile errors with no test output)Every filter in the standard library has a <stem>_test/ directory with declarative test cases. When writing or modifying a filter, write test cases alongside it.
name = "success output is a single clean line" # required, human-readable
fixture = "tests/fixtures/cargo_build_success.txt" # path to raw output file
# inline = "some raw output\nline two" # alternative: inline fixture
exit_code = 0 # optional, default 0
args = [] # optional, forwarded to filter
[[expect]]
equals = "ok ✓" # exact match
[[expect]]
not_contains = "Compiling" # noise must be gone| Field | Description |
|---|---|
equals | Output exactly equals this string |
contains | Output contains this substring |
not_contains | Output does not contain this substring |
starts_with | Output starts with this string |
ends_with | Output ends with this string |
line_count | Output has exactly N non-empty lines |
matches | Output matches this regex |
not_matches | Output does not match this regex |
Every [[expect]] entry checks one assertion. A test case with multiple [[expect]] entries must pass all of them. A test case with no [[expect]] entries is an error.
For every filter, write at least:
match_output branch (e.g., "up-to-date", "rejected")filters/
git/
push.toml ← filter config
push_test/ ← test suite (identified by _test suffix)
success.toml
up_to_date.toml
rejected.toml
failure.tomlThe _test suffix makes suite directories immediately identifiable in file listings and distinguishes them from filter category directories.
Don't use keep when skip is enough. keep is an allow-list — it drops everything that doesn't match. Use it only when you want to radically filter to a specific type of line.
Escape backslashes in TOML strings. In regular strings, \\d means literal \d in the regex. In TOML raw strings ('...'), backslashes are literal. Use raw strings for complex patterns.
match_output is a short-circuit. If it matches, nothing else runs. Don't put it at the end expecting it to be a fallback — it runs first.
[[section]], [parse], and [json] are mutually exclusive in practice. [json] replaces [[section]]/[parse]/[[chunk]] — when [json] is configured, those line-based steps are skipped. Use [json] for JSON output, [[section]]/[parse]/[[chunk]] for line-based output.
{output} in branch templates is the filtered output text (after skip/keep/replace/dedup), not the raw command output.
Pipe chains need careful quoting. When nesting templates inside each:, escape inner quotes: {each: "{value | lines | keep: \\\"^error\\\"}"}.
Don't skip the [fallback]. Complex filters with [[section]] can produce empty output if sections don't match. Always add [fallback] tail = 5 as a safety net.
Test with realistic fixture data. A filter that works on a trimmed example may miss edge cases. Use real command output saved to a .txt fixture file.
Before writing a full filter, consider whether a generic command would be sufficient. tokf provides three built-in subcommands that work on any command without a TOML filter:
| Command | Use case | Example |
|---|---|---|
tokf err <cmd> | Extract errors/warnings | tokf err mix compile |
tokf test <cmd> | Extract test failures | tokf test ctest --output-on-failure |
tokf summary <cmd> | Heuristic summary | tokf summary terraform plan |
To make generic commands trigger automatically via the hook, add rewrite rules to .tokf/rewrites.toml:
# Build tools without dedicated filters
[[rewrite]]
match = "^mix compile"
replace = "tokf err {0}"
# Test runners without dedicated filters
[[rewrite]]
match = "^mix test"
replace = "tokf test {0}"
# Long output without dedicated filters
[[rewrite]]
match = "^terraform plan"
replace = "tokf summary {0}"Important: Only add rewrite rules for commands that don't already have a filter. Check with tokf which "<command>" first. Commands with dedicated filters produce better output through tokf run.
© mpecan, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 2 other files (references) in crates/tokf-cli/skills/tokf-filter of mpecan/tokf.
Open the folder on GitHubat commit 87c93d9
Tokf Filter next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Tokf Filter this skillmpecan/tokf | 199 | — | ~9.9k | Automated safety check: Pass | MIT | |
| Filterzalando/skipper | 3.3k | — | ~527 | Automated safety check: Pass | MIT | |
| Django Filter Benchmarksaleor/saleor | 23k | — | ~2.3k | Automated safety check: Pass | BSD-3-Clause | |
| Filteringhashgraph-online/awesome-codex-plugins | 1.3k | — | ~1.5k | Automated safety check: Pass | Apache-2.0 | |
| Recipe Create Gmail Filtergoogleworkspace/cli | 31k | — | ~260 | Automated safety check: Pass | Apache-2.0 | |
| Modifying Taxonomic FilterPostHog/posthog | 40k | — | ~2.8k | Automated safety check: Pass | Custom licence |
zalando/skipper
Create or modify code in the filters package and all its sub-folders
saleor/saleor
Benchmarks Django ORM filters in Saleor by generating bulk data, extracting the SQL and running EXPLAIN ANALYZE to check index usage.
hashgraph-online/awesome-codex-plugins
A skill your agent uses when the user needs to filter data — whether in a structured query, a metric aggregation, or an attribute expression.
googleworkspace/cli
Create a Gmail filter to automatically label, star, or categorize incoming messages.
PostHog/posthog
Guides safe changes to the TaxonomicFilter, PostHog's picker for events, actions, properties, cohorts, and more.
microsoft/testfx
Reference data for test filter syntax across all platform and framework combinations: VSTest --filter expressions, MTP filters for MSTest/NUnit/xUnit v3/TUnit, and VSTest-to-MTP filter translation.
mpecan/tokf
Find missed token savings in Claude Code sessions and create filters for unfiltered commands
mpecan/tokf
Compress verbose CLI output with tokf before returning results.
mpecan/tokf
Find missed token savings by scanning AI coding session files for commands that ran without tokf filtering.
This skill should be used when the user asks to "create a filter", "write a tokf filter", "add a filter for <tool", "how do I filter output", or needs guidance on tokf filter step types, templates…. Tokf Filter is an agent skill from mpecan/tokf. This skill should be used when the user asks to "create a filter", "write a tokf filter", "add a filter for <tool", "how do I filter output", or needs guidance on tokf filter step types, templates, pipes, or placement conventions.
Tokf Filter fits situations like: asks to create a filter; write a tokf filter; add a filter for <tool; how do I filter output.
Run `npx skills add mpecan/tokf --skill tokf-filter -a claude-code`. Or copy the skill folder (crates/tokf-cli/skills/tokf-filter in mpecan/tokf) into .claude/skills/tokf-filter in your project. Claude Code loads it when a task matches its description.
Run `npx skills add mpecan/tokf --skill tokf-filter -a codex`. Or copy the skill folder (crates/tokf-cli/skills/tokf-filter in mpecan/tokf) into .agents/skills/tokf-filter in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add mpecan/tokf --skill tokf-filter -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/tokf-filter, .gemini/skills/tokf-filter, .github/skills/tokf-filter and .opencode/skills/tokf-filter in your project.
Going by SKILL.md and its folder, Tokf Filter needs the command-line tools its instructions call (git, npm, kubectl, docker, gh and cargo). Our summary lists: Node.js; Docker.
SKILL.md contains no URLs. Its commands use git, npm, docker and gh, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Tokf Filter is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 9.9k tokens (SKILL.md is roughly 39k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 9.8k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with Tokf Filter: Filter (zalando/skipper, 3.3k stars), Django Filter Benchmark (saleor/saleor, 23k stars), Filtering (hashgraph-online/awesome-codex-plugins, 1.3k stars) and Recipe Create Gmail Filter (googleworkspace/cli, 31k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
mpecan (a GitHub user) maintains it in mpecan/tokf, which has 199 GitHub stars. The repository holds 4 skills in this directory. The repository was last updated on October 9, 2026.
Source: mpecan/tokf on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.