[!NOTE] Status: experimental. The command definitions and shell integrations may change before 1.0.
The last line is not output. It is your next prompt, already filled in. jany prints one shell-quoted line on stdout, and the wrapper from jany --init zsh puts it on the command line. Read it, edit it if you like, press Enter.
|
jany generalises jind (jev × find) and jurl (jev × curl) into one binary. Each command is a definition directory — three files, no Rust — and adding a command is a job for an LLM skill, not a new crate.
How it works
- rules — the unambiguous shapes are decided by data in
schema.toml: paths, globs,8080:80,KEY=value,+7d,>10M, table words likefiles/delete/background. If every word resolves, jany never leaves your machine. - jev — anything left over goes to jev (TypeSafe System One, via OpenRouter) in one request: "what is the role of each word?" plus the extra questions the schema declares (which unit? at least or at most? a typo of which table word?). jev only picks from fixed choices and returns probabilities; it never generates the command.
- repair — fixes what jev cannot see word by word: attach
daysto7, letexceptclaim the names after it, pairfirst_name amandainto key and value. - assemble.sh — the command's own script (stdin JSON → stdout JSON) turns the role-tagged words into
argv. It also says how risky the result is. - output — the line goes to stdout, everything else to stderr. When the words can't be turned into a command (unresolved, or below a confidence floor), jany exits non-zero and puts
jany <command> --hinton the prompt instead, with the reason as a comment. Adangerousresult (find-delete) is previewed first with a read-only run. Anunsafeone (curlDELETE, docker--privileged) gets a one-line note.
One jev call is 200–700 ms and under $0.0001.
Setup
# Before the release, install from this checkout:
jany --init does three things: prints the wrapper function, installs the built-in definitions (find, curl, docker run) into ~/.config/jany/cmd/, and installs the /jany-register and /jany-update skills into ~/.agents/skills/ (linked from ~/.claude/skills/ and ~/.codex/skills/ when those exist). It never overwrites a definition that is already there. The skills are in English by default; jany --init zsh --locale ja installs the Japanese one (put the flag in your rc line, since --init rewrites the skill on every shell start).
In zsh the wrapper also shows a dim hint of what you can still say after jany <command> (the definition's [[placeholders]]), e.g. jany find src → <file|dir> <*.log> <older than N days> <delete|count>. It never calls jev. [suggest] enabled = false in ~/.config/jany/config.toml turns it off; JANY_SUGGEST=0 / 1 overrides that for one shell. bash and fish don't have it.
OPENROUTER_API_KEY in the environment takes precedence; a key saved by jind setup or jurl setup is picked up too. Tested on macOS with zsh.
Usage
jany <command> [words ...] [flags] [-- passthrough args]
| flag | |
|---|---|
--explain |
per-word role, confidence, and whether a rule or jev decided it (stderr) |
--no-jev |
offline only; unresolved words are an error |
--hint |
what you can say to <command> (its roles) and examples from its cases.toml (stderr) |
-- … |
passed through untouched (what that means is up to the command: find options, curl flags, the container command for docker run) |
jany's own actions are flags, so <command> is always the tool's name:
jany --list |
the definitions found, with an example each |
jany --test find |
run a definition's cases.toml (jev answers are mocked) |
/jany-register tar |
create and fill ~/.config/jany/cmd/tar/ with the agent skill |
jany --update |
after upgrading jany: replace the built-ins you have not edited, list what the others lack, and install the skills that are missing |
/jany-update tar |
add only what a definition lacks, with the agent skill; existing rules and cases stay |
jany --init zsh|bash|fish |
the wrapper, plus built-ins and the skill (--locale en|ja, default en) |
jany --setup |
save the API key |
Adding a command
A definition lives in ~/.config/jany/cmd/<name>[/<sub>]/:
| file | |
|---|---|
schema.toml |
roles (what jev may choose from), word tables, rules, questions for jev, repair steps, risk settings |
assemble.sh |
role-tagged tokens in, {argv, preview, risk, pipe, error} out; any language, bash + jq is enough |
cases.toml |
words → expected argv, with jev's answers written down; jany --test refuses answers to questions jany did not ask |
The skill reads a reference of every schema key and the two worked examples (find, curl) before writing. The rule of thumb from jind and jurl carries over: cover the 80 % you actually type, pass the rest through after --, and do not trust an assemble.sh that has no cases.
Updating definitions
A new jany can bring new definition features (such as [[placeholders]]) and updated built-ins, but jany --init never touches a definition that is already in place. After upgrading, run:
jany --update knows a built-in is unedited when every file matches a version jany has shipped (examples/released.txt). An edited built-in is left alone and listed, like your own definitions; /jany-update merges the new parts into it and keeps your edits. jany --update also installs /jany-update itself when it is missing (in the language of the installed /jany-register, or --locale en|ja); skill files already there are left alone.
Config (optional)
~/.config/jany/config.toml
[]
= "typesafe/jev-1.13"
= 0.5 # below this, no command: exit non-zero and offer `--hint`
[]
= false # no dim hint in zsh (JANY_SUGGEST=0/1 overrides it)
[] # overrides the schema's [defaults]
= "text/plain"
[]
= "~/Downloads"
Where it is weak
- The same weaknesses as jind and jurl: a bare word (
log,app) can be two roles at once, and jev is often only 0.6–0.9 sure. Write the unambiguous form (*.log) to skip jev. - Commands whose vocabulary does not close — arbitrary SQL, jq programs, ffmpeg filter graphs — are not a fit. Cover the common forms and pass the rest through.
- Every word, including ones the rules already decided, is sent to jev as context. Roles that may carry secrets (curl headers, docker
KEY=value) declare amaskso only a placeholder goes out.