file-handler
One on-disk convention for every skill in this repo:
.skillz/
└── <type>/
└── <slug>
<type> groups artifacts by purpose (note, plan, draft, log,
…). <slug> is the filename. Both are validated — no path separators,
no .., no leading dots — so a skill can hand a user-supplied slug
straight through without sanitizing.
How to use this skill
When another skill needs to save or load a working file, shell out to
scripts/skillz.sh. Three subcommands cover the surface area:
# write content to .skillz/<type>/<slug>
scripts/skillz.sh save_file note todo "things to do"
# write stdin (preferred for multi-line content)
some_command | scripts/skillz.sh save_file plan release-cut
# read it back
scripts/skillz.sh get_file note todo
# search by title and body, optionally narrowed to a type
scripts/skillz.sh search_files todo
scripts/skillz.sh search_files release --type plan
save_file echoes the resolved path on success so callers can quote
it back to the user. get_file and search_files exit 1 when the
target / matches don't exist, so a skill can branch on ||.
Storage root
By default the tree lives at $PWD/.skillz. Override with SKILLZ_DIR
when you want a session-scoped or repo-scoped location:
SKILLZ_DIR="$HOME/.local/share/skillz" scripts/skillz.sh save_file note hello "hi"
Add .skillz/ to the repo's .gitignore if these artifacts shouldn't
be committed.
Conventions for callers
- Pick a type once and reuse it. Don't invent a new
<type>per artifact — types are buckets (note,plan,draft,log,transcript). Slugs are the unique part. - Slugs are ASCII filename-safe. Allowed:
[A-Za-z0-9._-](letters, digits, dot, underscore, hyphen — kebab-case is the preferred style, but dots for extensions and uppercase are accepted). Anything else gets rejected with exit 2. - No extension is required, but adding one (
.md,.txt,.json) helps when a user opens the file directly. - Prefer stdin over the inline content arg for anything multi-line — quoting gets fragile fast.
- Don't write through this skill for tracked source files. This is for skill scratch space, not the project tree.
Subcommand reference
save_file <type> <slug> [content]
Writes to .skillz/<type>/<slug>. If content is omitted, reads from
stdin. Parent directories are created on demand. Prints the resolved
path. Exit 2 on bad args.
get_file <type> <slug>
Prints .skillz/<type>/<slug> to stdout. Exit 1 if the file is
missing, exit 2 on bad args.
search_files <query> [--type <type>]
Two-pass search:
- Titles — case-insensitive
find -iname "*<query>*"across the storage root (or one type if--typeis supplied). - Bodies —
grep -rInfor the same query against the same scope. Binary files are skipped.
Output is sectioned (## titles then ## bodies); body hits are in
the standard <path>:<line>:<text> form so editors can jump to them.
Exit 1 when neither pass finds anything.
Why a single skill for this
Without a shared convention, each skill that needs to persist working
state ends up inventing its own (~/.cache/<skill>/, /tmp/<skill>-*,
a hidden file in $PWD, …). That fragments where users go to find
their stuff and makes cross-skill workflows harder ("the plan we drafted
last turn"). Centralizing on .skillz/<type>/<slug> plus three verbs
keeps the rest of the toolkit tiny.
When NOT to use this skill
- Application code, configs, or anything tracked in git. Edit those in place — this skill is for skill scratch space.
- Secrets.
.skillz/is plaintext on local disk. Use a secret manager (1Password, Bitwarden, age) instead. - Cross-machine sync. Files live on the local filesystem only. If you need them elsewhere, copy them out by hand or commit deliberately.
- Large binaries. The body-grep pass skips binary files, but the storage layout isn't tuned for big artifacts.
Gotchas
SKILLZ_DIRis captured at invocation, not export-once. Each shell-out re-resolves it, so changing the env between calls changes the storage root.- Slug validation rejects spaces. Pass
release-cut, notrelease cut. The regex is documented above. - Body grep uses fixed-string matching (
grep -F). Special characters (.,*,[) are matched literally. That's deliberate — users searching forv1.2.3get exactly what they typed, not a regex. - No locking. Two skills writing the same
<type>/<slug>concurrently will race; last writer wins. Use distinct slugs for parallel work.