| name | skit |
| description | Run, inspect, and manage entries in the user's skit library — their personal hub, manager, and launcher for scripts in many languages (Python, shell, JS/TS, and more), executables, command templates, and AI-agent prompts, with typed parameter forms, saved presets, and per-entry launch policy. Use when the user asks to run/list/add their scripts or prompts, mentions skit or "my script", or before writing a new one-off script (the library may already have one that does the job). |
| license | MIT |
| compatibility | Requires the skit CLI on PATH (install with `uv tool install skit-cli`) |
skit — the user's entry library
skit stores the user's scripts in one searchable library and makes them runnable
without remembering flags. It runs many languages — Python, shell (bash/sh/zsh),
JS/TS, fish, PowerShell, Ruby/Perl/Lua/R — plus executables, command templates, and
prompts (parameterized text fired at an AI-agent CLI such as claude or codex).
For every entry it knows the parameter schema (extracted statically — flags,
prompts, and marked constants all become typed fields), remembers last-used values,
and saves named presets. Python runs through uv run --script in an isolated
environment (dependencies declared per script, PEP 723); JS/TS scripts get per-script
npm dependencies (declared with skit deps, installed next to the stored copy on
first run); other languages run through their own interpreter or runner. The library
is the user's curated space: treat it like their dotfiles.
Ground rules
- Check the library before writing a new script. If the user asks for something
a saved script already does, run that instead of regenerating it.
- Trust exit codes, never output text. skit's human output is localized (English,
繁體中文, 简体中文, …) — string-matching it will break on other machines. The exit
code and
--json payloads are the stable contract.
- Before an entry's first run,
--dry-run it and show the user the exact command.
- Never add, remove, or overwrite library entries without asking the user first.
Propose
skit add when you've written something reusable; don't add it silently.
- Pass
--no-input on every skit run and skit add. It guarantees those never
block on a prompt; if information is missing, skit fails fast with a named error
instead. skit remove confirms instead of taking --no-input — pass -y. The
read commands (list, show, params, …) never prompt and don't take the flag.
Discover entries
skit list
skit show <name>
skit list --json
skit show <name> --json
show is the map: each field's type (str/int/float/bool/choice/path), required,
default, choices, and source — where the value goes: flag (passed as a real
CLI flag), inject (a managed constant or prompt answer, rewritten into a temporary
copy at run time), env (delivered as an environment variable), or placeholder
(fills a registered command template or prompt body). The top-level param_origin
(declared/reader/managed/command/none) says where the whole schema came
from. degraded_reason non-empty means skit could not model the script's own parser
(subcommands, dynamic args) — pass arguments through after -- instead. needs lists
the external commands the entry requires on PATH.
Run entries
skit run <name> --no-input
skit run <name> --set width=1200 --set fmt=png --no-input
skit run <name> -p <preset> --no-input
skit run <name> --no-input -- --verbose input.txt
skit run <name> --set width=800 --dry-run --no-input
skit run <name> --raw --no-input
skit run <name> --forget-args --no-input
--set NAME=VALUE works for every field kind (flags, injected constants, template
placeholders). Unknown names are rejected with the valid list (exit 2); values are
validated against the field's type (exit 125 on mismatch). Values may use tokens —
{cwd}, {today}, {now}, {env:VAR}, a leading ~ — and multi-value fields
also expand globs.
--set NAME= (an empty value): check the field's delivers_empty in
show --json. true means an empty string is genuinely delivered as the value;
false means empty = "leave it unset — the script's own default applies".
- Unset fields fall back to: preset > last-used value > the field's declared default.
A required field with no value fails fast (exit 125) rather than prompting.
- Reuse warning: with no
-- args given, any run reuses the last run's extra args
— a script's argv, a prompt's or command's agent/command flags like --model alike (it
says so on stderr). Pass your own -- args, use --raw (which never replays old
arguments), or --forget-args (erases the remembered tail up front,
then runs) when you need a clean slate; --dry-run shows exactly what would happen.
--raw runs a script's stored copy as-is, skipping the parameter form and injection.
It applies to every kind EXCEPT prompt and command, whose placeholders ARE the
artifact — there is no "as-is" without them, so --raw is refused there (exit 2). (exe
and the interpreted kinds run as-is exactly like python.) It also refuses
--set/-p/--save-preset.
- Secrets: prefer wiring them to environment variables (see below) over
--set.
Secret values never persist to disk and are masked as ••• in dry-run output.
Exit codes (docker convention)
When the entry's target process actually ran, its exit code passes through untouched
(even if it exits 125–127 — check stderr when in doubt). When it never launched:
| code | meaning |
|---|
| 2 | usage error (bad flags, unknown --set name, unknown preset) |
| 125 | skit-side failure: missing/invalid parameter value, drift, launch failure |
| 126 | target exists but is not executable |
| 127 | no such entry in the library (or launch target missing) |
| 130 | user cancelled the interactive form |
Add entries to the library
Always confirm with the user before adding. From a file or stdin:
skit add path/to/script.py --name resize -d "Resize images to a target width" --no-input
skit add path/to/backup.sh --name backup -d "Nightly database dump" --no-input
skit add - --name fetch-report -d "Pull the weekly report" --no-input
skit add - --kind shell -n backup -d "Nightly dump" --no-input
skit add path/to/script.py --ref --no-input
skit add path/to/tool --kind shell --name tool -d "Cleanup helper" --no-input
skit add --cmd 'ffmpeg -i {input} -vf scale={width}:-1 {output}' --name scale-video --no-input
- Give every entry a
-d description — skit list is the discovery surface.
- The kind is inferred from the extension or shebang. Force it with
--kind
(python, shell, js, ts, fish, powershell, ruby, …) for an extensionless
file, or --exe for a program.
- Two kinds of dependency. Python packages go in the script itself (PEP 723 inline
metadata) and skit resolves them through uv at run time, nothing installs globally:
Record them afterwards with skit deps <name> --dep "requests>=2,<3". JS/TS
packages are declared the same way (skit deps <name> --dep "chalk@^5"; skit add
suggests them from the script's own imports) and installed into a per-script
node_modules next to the stored copy on first run — copy-mode entries only, since
a reference entry runs from its own project. JS/TS installs never run package
lifecycle scripts (npm and bun get --ignore-scripts; deno skips them by default), so
a package that requires its postinstall step won't work. When deno is the resolved
runner, skit invokes it with --allow-all — scripts are not sandboxed. External
commands a script of any kind expects on PATH (a shell script needing jq, say)
are needs, checked before every run: skit deps <name> --need jq --need ffmpeg.
- Under
--no-input, detected parameters are NOT auto-managed. For a Python or
shell script that reads constants, prompts, or ${VAR:-} env-defaults (no
argparse/getopts), bring its parameters under management so they become form fields
/ --set targets:
skit params <name>
skit params <name> --manage WIDTH --manage CITY
skit params <name> --secret API_KEY --env-source API_KEY=OPENAI_API_KEY
With --env-source, the secret is read from the environment at run time — the
value never appears in commands, files, or output. Scripts that parse their own CLI
(Python argparse/click/typer, shell getopts, JS util.parseArgs, fish argparse,
PowerShell param()) need no management — skit reads them statically. When the
parse can't be read statically (a dynamic optstring, docopt/fire), the run form
falls back to an extra-arguments field, and managed constants add fields
alongside it rather than replacing anything.
- Declared parameters (for exe, command, and reader-less kinds like ruby/perl/lua/r)
are defined by hand on the entry, then behave like any other field:
skit params <name> --add OUTPUT --type OUTPUT=str --deliver OUTPUT=flag --flag OUTPUT=--out
skit params <name> --add FORMAT --choices FORMAT=png,jpg,webp --default FORMAT=png --optional FORMAT
skit params <name> --rm OUTPUT
--deliver picks how the value reaches the program: flag (exe), env (any kind),
or placeholder (command templates and prompt bodies).
- Shell only:
skit params <name> --normalize WIDTH rewrites a bare WIDTH=800
constant into the ${WIDTH:-800} idiom in skit's stored copy (never the user's
original), so the value is delivered as an environment variable rather than by
rewriting a temporary copy. Opt-in, and the one edit skit ever makes to script text.
Where and how an entry runs
Launch policy lives on the entry and is editable without remove + re-add:
skit params <name> --workdir origin
skit params <name> --interpreter zsh
skit params <name> --interpreter ''
skit params <name> --template 'ffmpeg -i {input} {output}'
--workdir sets where the process starts: origin (the source file's folder),
store (skit's stored copy), invoke (where skit was run from), or an absolute path.
--interpreter pins the binary an interpreted entry (shell/js/ts/fish/…) launches
with; refused on python and prompt kinds — they don't run through a pinnable
interpreter. skit show <name> --json reports the entry's interpreter.
--template rewrites a command entry's program and re-reads its {placeholders};
refused on any other kind, and an empty template is refused.
Presets
Named value sets per entry, ideal for recurring jobs:
skit run <name> --set a=1 --set b=2 --save-preset nightly --dry-run --no-input
skit preset list <name> --json
skit run <name> -p nightly --no-input
skit preset delete <name> nightly
Prompts & runners
A prompt entry is a reusable, parameterized piece of text for an AI coding agent:
{{placeholder}} holes (double braces, identifier names) become form fields / --set
targets, and the rendered text is handed to a runner — a configured agent CLI
(claude, codex, opencode, amp, antigravity, copilot, cursor, pi are preconfigured).
There are NO escape sequences: single-brace text (JSON, ${VAR}, f-strings) is never a
placeholder, and an unmanaged {{hole}} passes through byte-identical — skit only ever
touches the spans that are managed. The rendered prompt travels as ONE process argument,
never through a shell. Pi is the declared exception to byte-identical delivery: if its
parser would treat the opening text as an option, file, or package command, skit warns
and prepends one newline before continuing, including under --no-input.
skit add notes/review.prompt.md --no-input
skit add notes/spec.md --prompt --no-input
skit add - --prompt --name review -d "Review a file" --no-input
skit add notes/review.prompt.md --runner claude --no-input
skit add big.prompt.md --prompt --no-interpolate --no-input
skit show <name> --json
skit run <name> --runner claude --set target=src/app.py --no-input
skit params <name> --runner claude
skit params <name> --runner ''
skit params <name> --add extra_hole
skit params <name> --rm noise
skit params <name> --no-interpolate
- A run needs a runner. Resolution is
--runner > the entry's pin > exit 126 —
never a guess (interactive terminals get asked; --no-input never does). Check
runners_available in skit show <name> --json before picking.
- Prompts contain code snippets, so a detected
{{hole}} can be a false positive:
unmanaged holes pass through to the agent verbatim, --rm unmanages, and
--no-interpolate switches the whole entry to verbatim delivery. skit params <name> --json lists placeholders (managed), unmanaged, runner, interpolate.
- Flood guard: past 30 detected holes an add manages NOTHING automatically (the
prompt clearly wasn't written for insertion; stderr says so) —
--add the ones you
need, or add with --no-interpolate.
- Runners are user-editable config.
skit runner list --json shows them; register a
custom tool with the argv after --, {{prompt}} marking where the rendered text
lands (exactly one slot, never the first word; single-brace text is literal):
skit runner list --json
skit runner add mycli -- mycli run {{prompt}}
skit runner add mycli --force -- mycli run --model opus {{prompt}}
skit runner remove mycli -y
- The agent's own per-run flags pass through after
--:
skit run <name> --set a=1 --no-input -- --model opus.
- Prompts are NOT a secrets channel: whatever the rendered prompt contains lands in
the receiving agent's own session logs. skit still never persists a secret-marked
value, but confidentiality ends at the runner boundary.
Maintenance
skit doctor --json
skit rename <name> <new-name>
skit describe <name> "Nightly dump"
skit remove <name> -y
skit edit <name>
skit config js.runner deno
skit config shell.bash_path /path
doctor --json adds launch_blocked — a {name: reason} map of entries whose run
would refuse to start (uninstalled interpreter/JS runtime, a pinned agent binary that
is gone, a vanished working directory) even though their target file is present.
Note: doctor's EXIT CODE reflects uv availability only — per-entry warnings (missing
targets, drift, launch_blocked) do not change it. Read --json for health, never the
exit code.
If show/run reports drift (the source changed and its managed parameter
definitions no longer match), skit params <name> --resync refreshes them.