Skit
A terminal script manager and launcher — Put any script, prompt, or program in one library; skit turns its inputs and flags into a TUI menu, and remembers inputs you used last time. With Agent Skill so agents reuse scripts they wrote.
npx -y skills add t41372/skit --skill skitAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
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).
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
16.5 KB, as published. Nobody here has run it
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
--jsonpayloads are the stable contract. - Before an entry's first run,
--dry-runit and show the user the exact command. - Never add, remove, or overwrite library entries without asking the user first.
Propose
skit addwhen you've written something reusable; don't add it silently. - Pass
--no-inputon everyskit runandskit add. It guarantees those never block on a prompt; if information is missing, skit fails fast with a named error instead.skit removeconfirms 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 # every entry: name, kind, description
skit show <name> # one entry: parameters, types, defaults, presets
skit list --json # same data, machine-readable (only if you're scripting over it)
skit show <name> --json # full parameter schema as 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 # defaults + the user's last-used values
skit run <name> --set width=1200 --set fmt=png --no-input
skit run <name> -p <preset> --no-input # a saved preset
skit run <name> --no-input -- --verbose input.txt # raw args to the script's own parser
skit run <name> --set width=800 --dry-run --no-input # print the command, run nothing
skit run <name> --raw --no-input # escape hatch: run the stored copy as-is (not prompt/command; see below)
skit run <name> --forget-args --no-input # erase the remembered extra args first, then run
--set NAME=VALUEworks 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'sdelivers_emptyinshow --json.truemeans an empty string is genuinely delivered as the value;falsemeans 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--modelalike (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-runshows exactly what would happen. --rawruns 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--rawis 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 # script text on stdin
skit add - --kind shell -n backup -d "Nightly dump" --no-input # stdin + forced kind (any interpreted kind)
skit add path/to/script.py --ref --no-input # reference the original file, don't copy
skit add path/to/tool --kind shell --name tool -d "Cleanup helper" --no-input # force the kind
skit add --cmd 'ffmpeg -i {input} -vf scale={width}:-1 {output}' --name scale-video --no-input
- Give every entry a
-ddescription —skit listis 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--exefor 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:
# /// script
# requires-python = ">=3.12"
# dependencies = ["requests>=2,<3"]
# ///
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 /--settargets:
skit params <name> # see managed + detected-but-unmanaged parameters
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 WIDTHrewrites a bareWIDTH=800constant 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 # run in: origin | store | invoke | an /absolute/path
skit params <name> --interpreter zsh # pin the interpreter/runtime (interpreted kinds only)
skit params <name> --interpreter '' # clear the interpreter pin (automatic detection)
skit params <name> --template 'ffmpeg -i {input} {output}' # command kind: rewrite the template ({holes} re-read)
--workdirsets 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.--interpreterpins 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> --jsonreports the entry'sinterpreter.--templaterewrites acommandentry'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 # create without running
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 # kind inferred; detected {{holes}} managed
skit add notes/spec.md --prompt --no-input # bare .md needs the explicit flag
skit add - --prompt --name review -d "Review a file" --no-input # body on stdin
skit add notes/review.prompt.md --runner claude --no-input # pin the agent up front
skit add big.prompt.md --prompt --no-interpolate --no-input # no insertion at all
skit show <name> --json # adds "runner", "runners_available", "interpolate"
skit run <name> --runner claude --set target=src/app.py --no-input
skit params <name> --runner claude # pin the agent
skit params <name> --runner '' # clear the pin (ask on the run form)
skit params <name> --add extra_hole # manage a {{hole}} typed into the body later
skit params <name> --rm noise # unmanage a false positive (it stays verbatim text)
skit params <name> --no-interpolate # switch insertion off; --interpolate turns it back on
- A run needs a runner. Resolution is
--runner> the entry's pin > exit 126 — never a guess (interactive terminals get asked;--no-inputnever does). Checkrunners_availableinskit show <name> --jsonbefore picking. - Prompts contain code snippets, so a detected
{{hole}}can be a false positive: unmanaged holes pass through to the agent verbatim,--rmunmanages, and--no-interpolateswitches the whole entry to verbatim delivery.skit params <name> --jsonlistsplaceholders(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) —
--addthe ones you need, or add with--no-interpolate. - Runners are user-editable config.
skit runner list --jsonshows 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 # [{"name": …, "argv": […]}]
skit runner add mycli -- mycli run {{prompt}} # each word = one argument, no shell
skit runner add mycli --force -- mycli run --model opus {{prompt}} # --force replaces an existing runner (edit)
skit runner remove mycli -y # confirms without -y, like skit remove
- 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 # health: uv, location, drift/missing, needs_missing, launch_blocked,
# mirror {enabled + stored URLs; an axis applies iff enabled and its URL is set}
skit rename <name> <new-name> # rename; presets, remembered values and history follow
skit describe <name> "Nightly dump" # set/replace the description (an empty string clears it)
skit remove <name> -y # remove an entry (the user's original file is never deleted) — ask first
skit edit <name> # open the stored source in the user's editor
skit config js.runner deno # pin the JS/TS runner (default: auto — deno > bun > node)
skit config shell.bash_path /path # where bash lives on Windows (POSIX auto-detects)
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.