CLI
SkillDev toolsApply when writing or changing anything run from a command line with arguments, a package's `bin`, a `scripts` tool behind a pnpm script, a plugin's script verbs, or when reading `process.argv`. Esposter's command lines, every one a citty command, so parsing, `--help`, usage and errors come from one library rather than a hand-rolled parser per tool.
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; ahel provides instructions and does not run this skill.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the CLI skill
What this skill tells your AI
The instructions your AI receives, as published by esposter/esposter in .agents/skills/cli/SKILL.md and read by ahel’s review.
A command line here is a citty command: citty reads the arguments, renders --help and the usage line, rejects a missing required argument, and turns a thrown error into a message and exit code 1. A tool that parses its own arguments rebuilds all of that, differently each time, and none of it is tested.
Settled — do not re-propose
- A hand-written usage string,
--helpflag or argument parser,node:util'sparseArgsincluded. Each drifts from the arguments it describes, since nothing reads the string against the code, andparseArgsstops at parsing: no subcommands, no usage, no help. citty renders the usage from the sameargsthe command parses, andno-restricted-importsrefusesnode:util'sparseArgs. - An interface restating a command's arguments beside the command.
defineCommandinfers the parsed arguments from the object written inline in it, so a second declaration only has to be kept in step by hand. - A module-scope
const <name>Args = { … } as consttypedCommandDef<typeof <name>Args>. UnderisolatedDeclarationsthe constant then needs an explicit type of its own, which is the restated interface again. - Exporting a command as
CommandDef.CommandDefwithout arguments isCommandDef<ArgsDef>, and a command with its own arguments is not assignable to it (itsruntakes narrower arguments);CommandDef<any>isno-explicit-any. The export is typedSubCommandsDef[string], citty's own type for a subcommand.
Rules
process.argvis read by citty alone;no-restricted-propertiesbans it everywhere else. A disable names the forcing agent that reads argv without citty (a test runner's own subcommand, the host re-launching its own entry).- The entry file runs one command and holds nothing else:
await runMain(<tool>Command). A tool with one command defines it inline there, itsrunthe work; a tool with several has a root command whosesubCommandsmap each name to its own file. - One subcommand per file,
<name>Command.ts, in the tool'scommandsfolder (services/cli/commands/in a package,scripts/src/services/<tool>/commands/in the scripts package), exported asexport const <name>Command: SubCommandsDef[string] = defineCommand({ … }), and the root typedCommandDef. A command parses and calls: its work is a service beside it, which is what a test reaches. - Arguments are written inline in
defineCommand, each with adescription, and the command with ametaholding itsnameanddescription: they are the help. A set of arguments several commands share is one exported constant named…Args, typed from citty's own argument types (Record<"x" | "y", PositionalArgDef & { required: true }>), and spread in. - Positional arguments are read in the order they are declared, so the objects inside a
defineCommandcall and a shared…Argsconstant are exempt fromperfectionist/sort-objects(packages/configuration/eslint/plugins/perfectionist.js), and are written in the order the command line takes them. - Arguments are declared by kind: a
positionalfor what the command acts on, abooleanfor a switch (--no-<name>negates one defaulting to true), anenumfor a closed set, and astringfor the rest. An enum'soptionsisObject.values(<Enum>)written in place, which types the parsed value as the enum with its default; areadonlylist spread in ([...BackendTypes]) is inferred as a readonly tuple underdefineCommand'sconstparameter, and the parsed value loses its default and reads as possiblyundefined. A choice that is not what the command acts on is an option (--motion entry), never an optional positional before a list, which citty cannot tell apart from the list. - citty has no number type, so a numeric argument is a
string, its default a string (default: "8"), converted withNumberwhererunhands it on. A comma-separated list of numbers in the scripts package goes throughparseNumbers(scripts/src/services/shared/parseNumbers.ts), which rejects an empty or non-numeric entry and a count other than the one given rather than lettingNumberread it as 0 or NaN. - A list of any length is the positionals after the declared ones,
args._.slice(<declared count>), sinceargs._holds every positional; what follows--is inargs._too, passed on untouched by a command forwarding a command line of its own. - A launch another program makes, carrying no subcommand, is served from the root's
setup, which runs before dispatch: citty throws on a first positional it does not know, so a link a platform starts the program with, or a child that re-runs the executable with a path first, is recognised there and served before dispatch is reached (packages/agent-console-server/src/services/cli/commands/agentConsoleServerCommand.ts). - A tool that prints in the reader's language passes
runMainits ownshowUsage, printing its localized usage and exiting, since citty prints the usage before an English error and exits 1 after it; exiting insideshowUsageanswers every unknown or missing command in the reader's language (packages/genshin-persona/scripts/genshin.ts). - A failure throws (
InvalidOperationError), andrunMainprints it and exits 1;process.exitCodeis set only for a result that is not an error but still fails, such as a check reporting problems. - A command's behaviour is tested through its root,
runCommand(<tool>Command, { rawArgs: [<name>, …] })from citty, the way a shell calls it.
Signals
- GitHub stars
- 23
- Forks
- 3
- Last commit
- Oct 2026
Advanced
- Item type
- skill
- Key
cli-esposter- Source
- github.com/esposter/esposter
Related picks
Skill · thedaviddias
The pick for JavaScriptmodern-javascript-patterns
Skill · wshobson
The pick for JavaScriptsetup-ts-deep-modules
Skill · mattpocock
The pick for TypeScripttypescript-pro
Skill · jeffallan
The pick for TypeScriptnodejs-backend-patterns
Skill · wshobson
The pick for Noderun-node-tests
Skill · hiroro-work
The pick for Node