Skillshub unjs-citty

ALWAYS use when writing code importing \"citty\". Consult for debugging, best practices, or modifying citty.

install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/harlan-zw/skilld/unjs-citty" ~/.claude/skills/comeonoliver-skillshub-unjs-citty && rm -rf "$T"
manifest: skills/harlan-zw/skilld/unjs-citty/SKILL.md
source content

unjs/citty
citty

Version: 0.2.1 (yesterday) Tags: latest: 0.2.1 (yesterday)

References: package.jsonREADMEGitHub IssuesReleases

Search

Use

npx -y skilld search
instead of grepping
.skilld/
directories — hybrid semantic + keyword search across all indexed docs, issues, and releases.

npx -y skilld search "query" -p citty
npx -y skilld search "issues:error handling" -p citty
npx -y skilld search "releases:deprecated" -p citty

Filters:

docs:
,
issues:
,
releases:
prefix narrows by source type.

API Changes

⚠️ ESM-only — v0.2.0 ships ESM only,

require('citty')
no longer works source

⚠️

node:util.parseArgs
internally — v0.2.0 replaced custom parser with Node.js native
util.parseArgs
, edge cases around arg parsing may differ from v0.1.x source

⚠️ Optional args type

T | undefined
— v0.2.0 improved type inference: args without
required: true
or
default
now correctly type as
T | undefined
instead of
T
source

⚠️

--no-
negation conditionally printed — v0.2.0 only shows
--no-<flag>
in usage when
negativeDescription
is set; previously always shown source

type: "enum"
— new arg type in v0.2.0, requires
options: string[]
array. Typed as union of options values source

args: {
  color: {
    type: "enum",
    options: ["red", "blue", "green"] as const,
    description: "Pick a color",
  },
}
// args.color typed as "red" | "blue" | "green" | undefined

meta.hidden
— v0.2.0, hides a subcommand from usage/help output source

negativeDescription
— v0.2.0, on boolean args, sets description for the
--no-<flag>
variant in usage source

cleanup
hook — v0.1.4, runs after
run()
completes (mirror of
setup
) source

createMain(cmd)
— v0.1.4, returns a reusable
(opts?) => Promise<void>
wrapper around
runMain
source

--version
flag — v0.1.4, auto-handled when
meta.version
is set source

runMain({ showUsage })
— v0.1.5, accepts custom
showUsage
function to override default help rendering source

⚠️

--no-
propagation fix — v0.2.1,
--no-<flag>
now correctly negates aliases too (was broken in v0.2.0) source

Best Practices

✅ Use

setup
and
cleanup
hooks for lifecycle management — undocumented in README but fully supported;
cleanup
runs in
finally
block so it executes even on errors source

defineCommand({
  args: { db: { type: "string", default: "mydb" } },
  async setup({ args }) { await connectDb(args.db) },
  async cleanup() { await disconnectDb() },
  async run({ args }) { /* db is connected */ },
})

✅ Use

enum
type with
options
for constrained values — validates input and shows allowed values in usage/error messages (v0.2.0+) source

args: {
  format: {
    type: "enum",
    options: ["json", "yaml", "toml"],
    default: "json",
    description: "Output format",
  },
}

✅ Use

meta.hidden: true
to hide subcommands from usage output — keeps internal/debug commands accessible but invisible (v0.2.0+) source

subCommands: {
  debug: () => defineCommand({ meta: { name: "debug", hidden: true }, run() {} }),
}

✅ Make

subCommands
values lazy via arrow functions — citty resolves them with
resolveValue()
, enabling code-splitting and faster startup source

subCommands: {
  deploy: () => import("./commands/deploy").then(m => m.default),
  build: () => import("./commands/build").then(m => m.default),
}

✅ Use

negativeDescription
on boolean args that default to
true
— citty auto-generates
--no-*
flags with separate help text (v0.2.0+) source

args: {
  color: {
    type: "boolean",
    default: true,
    description: "Colorize output",
    negativeDescription: "Disable colored output",
  },
}

✅ Pass custom

showUsage
to
runMain
for branded help screens — citty calls your function instead of the built-in one for
--help
and error display source

runMain(cmd, {
  showUsage: async (cmd, parent) => {
    console.log(await renderUsage(cmd, parent))
    console.log("\nDocs: https://example.com/docs")
  },
})

✅ Arg names auto-alias between camelCase and kebab-case — defining

outputDir
auto-creates
--output-dir
and vice versa; don't add redundant aliases source

--version
only works as the sole argument — citty checks
rawArgs.length === 1 && rawArgs[0] === "--version"
, so
--version --verbose
won't trigger it; set
meta.version
on the root command source

✅ Use

runCommand
over
runMain
for programmatic invocation —
runMain
calls
process.exit(1)
on errors and handles
--help
/
--version
;
runCommand
returns
{ result }
and lets errors propagate source

const { result } = await runCommand(cmd, { rawArgs: ["build", "--prod"] })

✅ Avoid positional args on commands with subcommands — if a positional value matches a subcommand name, citty routes to the subcommand instead of using it as the arg value source