A no-nonsense, zero-dependency CLI framework for developers who prefer their tools lean, typed, and composable. Climonad won’t hold your hand—but it won’t get in your way, either.
Most CLI frameworks overpromise and underdeliver. Climonad does neither. It gives you a clean, type-safe foundation to build powerful CLIs your way—without the bloat or boilerplate.
Functional where it counts. Testable by default. Easy to start, endlessly flexible. No fluff. No footguns.
Built for developers who want complete control over their CLI tools. It avoids magic, embraces type safety, and scales from tiny scripts to full-featured CLIs without dragging opinions along.
Get rolling fast without hidden complexity. Define what you need, skip what you don’t.
// cli.ts
import { createCLI } from "climonad"
export const cli = createCLI({
name: "example-cli",
description: "An example CLI application",
})
// flags.ts
import { bool } from "climonad"
export const helpFlag = bool({
name: "help",
description: "Display help information",
default: false,
aliases: ["h"],
})
export const verboseFlag = bool({
name: "verbose",
description: "Verbose mode",
default: false,
aliases: ["v"],
})
// command.ts
import { cli } from "./cli"
export const runCmd = cli.cmd({
name: "run",
description: "Run the application",
action: async (args) => {
console.log("Running the application with args:", args)
},
})
export const testCmd = runCmd.cmd({
name: "test",
description: "Test the application",
action: async (args) => {
console.log("Testing the application with args:", args)
},
})
// index.ts
import { cli } from "./cli"
import "./command"
import { helpFlag, verboseFlag } from "./flags"
cli.use(helpFlag, verboseFlag)
cli.run(process.argv.slice(2)).catch((error) => {
console.error("Error running CLI:", error)
process.exit(1)
})
Climonad doesn’t guess what your help experience should look like. If users ask for help and you haven’t defined how to show it, that’s on purpose. You’re in charge of what’s printed.
// help.ts
import { CLIHelpConstructor } from "climonad"
export const helpReporter = ({ commands, flags, root }: CLIHelpConstructor) => {
console.log(`\n${root.name} - ${root.description}\n`)
if (commands.length > 0) {
console.log("Commands:")
for (const cmd of commands) {
console.log(` ${cmd.name} - ${cmd.description}`)
}
}
if (flags.length > 0) {
console.log("\nFlags:")
for (const flag of flags) {
console.log(` --${flag.name} - ${flag.description}`)
}
}
console.log("")
}
// cli.ts
import { createCLI } from "climonad"
import { helpReporter } from "./help"
export const cli = createCLI({
name: "example-cli",
description: "An example CLI application",
help: true,
helpReporter,
})
Want --assist
instead of --help
? Easy:
help: "assist"
Now your users run:
example-cli --assist
Warning
If you don't supply a helpReporter
, help output won't be shown—even if help
is enabled.
Don’t let unhandled errors ruin the CLI experience. Climonad lets you define responses for common CLI mistakes so your tool speaks your language—not ours.
// errors.ts
import { CLIErrorHandler, ErrorCodes } from "climonad"
export const errorHandler = new CLIErrorHandler<ErrorCodes>({
TOKEN_NOT_FOUND: (token, nodes) => {
const suggestions = nodes
?.filter((node) => node.name.startsWith(token))
.map((node) => node.name)
.join(", ")
return `Unknown token \"${token}\". Did you mean: ${suggestions}?`
},
REQ_FLAG_MISSING: (flagName) => `Oops! The flag \"--${flagName}\" is required.`,
})
// cli.ts
import { createCLI } from "climonad"
import { errorHandler } from "./errors"
export const cli = createCLI({
name: "example-cli",
description: "An example CLI application",
errorHandler,
})
Custom error handlers shine when:
- You want error messages in a specific tone or language
- Your CLI targets less technical users
- You want to plug in logging or telemetry
Note
Unhandled errors fall back to default Climonad messaging.
Most CLI frameworks stop at subcommands. Climonad goes further.
Commands can nest indefinitely, allowing you to structure large command trees in clean, composable layers. Whether you’re building a microservice runner, deployment pipeline, or multi-utility toolkit, you won’t hit a wall.
const root = createCLI({ name: "cli" })
const user = root.cmd({
name: "user",
description: "Manage users",
})
const create = user.cmd({
name: "create",
description: "Create a user",
action: () => {
console.log("User created!")
},
})
const admin = create.cmd({
name: "admin",
description: "Create an admin user",
action: () => {
console.log("Admin user created!")
},
})
Your CLI can now handle:
cli user create admin
Climonad doesn't impose limits on depth or nesting, so you can model your CLI the way your users think—not the way your framework dictates.
If you want to create reusable validation logic for flags, createPreset
is your tool.
Important
Climonad doesn't yet support positional arguments or lists out of the box, but presets give you composable power now.
// presets.ts
import { CLIDefinition, CLIEntryPreset } from "climonad"
import { CLI } from "climonad"
import { createPreset } from "climonad"
export function hex(config: CLIDefinition<string>): CLIEntryPreset<string> {
return createPreset("flag", config, (input) => {
const hexRegex = /^#?[0-9A-Fa-f]+$/
return hexRegex.test(input) ? CLI.Ok(input) : CLI.Error(null)
})
}
export const colorFlag = hex({
name: "color",
description: "Set the color",
default: "#000000",
required: true,
aliases: ["c"],
})
Presets are the preferred way to express custom validations currently.
Caution
Climonad is optimized, but no abstraction is free. Here's where performance could take a hit:
- Deep Command Nesting – While supported, very deep command trees can add processing overhead.
- Large Token Sets – Matching against many tokens might introduce delays.
- Complex Requirements – Intricate validation logic may slow down command resolution.
Tip
Climonad is built with fast initialization and predictable performance in mind:
- Time Complexity: O(1) to O(n) depending on input size.
- Space Complexity: Linear with respect to commands and flags.
- Runtime: Constant-time lookups and efficient traversal.
It's designed for real-world use—fast enough for scripts, structured enough for full CLI suites.
Whether it’s a sharp bug fix, a novel feature idea, or just feedback on the philosophy—PRs and issues welcome. Check the Contributing Guide before you dive in.
To report vulnerabilities or sensitive issues, please read our Security Policy.
Released under the MIT License.