Package Exports
- gunshi
- gunshi/context
- gunshi/package.json
- gunshi/renderer
Readme
đ¯ Gunshi
Gunshi is a modern javascript command-line library
[!TIP] gunshi (čģå¸Ģ) is a position in ancient Japanese samurai battle in which a samurai devised strategies and gave orders. That name is inspired by the word "command".
⨠Features
Gunshi is designed to simplify the creation of modern command-line interfaces:
- đ Simple: Run the commands with a simple API.
- đĄī¸ Type Safe: Arguments parsing and options value resolution type-safely by args-tokens
- âī¸ Declarative configuration: Configure the command modules declaratively.
- đ§Š Composable: Sub-commands that can be composed with modularized commands.
- âŗ Lazy & Async: Command modules lazy loading and asynchronously executing.
- đ Auto usage generation: Automatic usage message generation with modularized commands.
- đ¨ Custom usage generation: Usage message generation customizable.
- đ Internationalization: I18n out of the box and locale resource lazy loading.
đŋ Installation
đĸ Node
# npm
npm install --save gunshi
## pnpm
pnpm add gunshi
## yarn
yarn add gunshiđĻ Deno
deno add jsr:@kazupon/gunshiđĨ Bun
bun add gunshiđ Usage
đ Simple API
Gunshi has a simple API that is a facade:
import { cli } from 'gunshi'
// Run a simple command
cli(process.argv.slice(2), () => {
console.log('Hello from Gunshi!')
})đĄī¸ Type-Safe Arguments
Gunshi provides type-safe argument parsing with TypeScript:
import { cli } from 'gunshi'
import type { ArgOptions, Command, CommandContext } from 'gunshi'
// Define interfaces for options and values
interface UserOptions extends ArgOptions {
name: { type: 'string'; short: 'n' }
age: { type: 'number'; short: 'a'; default: number }
verbose: { type: 'boolean'; short: 'v' }
}
interface UserValues {
name?: string
age: number
verbose?: boolean
}
// Create a type-safe command
const command: Command<UserOptions> = {
name: 'type-safe',
options: {
name: { type: 'string', short: 'n' },
age: { type: 'number', short: 'a', default: 25 },
verbose: { type: 'boolean', short: 'v' }
},
run: (ctx: CommandContext<UserOptions, UserValues>) => {
const { name, age, verbose } = ctx.values
console.log(`Hello, ${name || 'World'}! You are ${age} years old.`)
}
}
await cli(process.argv.slice(2), command)For more detailed examples, check out the playground/type-safe in the repository.
âī¸ Declarative Configuration
Configure commands declaratively:
import { cli } from 'gunshi'
// Define a command with declarative configuration
const command = {
name: 'greet',
description: 'A greeting command',
options: {
name: { type: 'string', short: 'n' },
greeting: { type: 'string', short: 'g', default: 'Hello' },
times: { type: 'number', short: 't', default: 1 }
},
usage: {
options: {
name: 'Name to greet',
greeting: 'Greeting to use (default: "Hello")',
times: 'Number of times to repeat the greeting (default: 1)'
}
},
run: ctx => {
const { name = 'World', greeting, times } = ctx.values
for (let i = 0; i < times; i++) {
console.log(`${greeting}, ${name}!`)
}
}
}
cli(process.argv.slice(2), command, {
name: 'my-app',
version: '1.0.0',
description: 'My CLI application'
})For more detailed examples, check out the playground/declarative in the repository.
đ§Š Composable Sub-commands
Create a CLI with composable sub-commands:
import { cli } from 'gunshi'
// Define sub-commands
const createCommand = {
name: 'create',
description: 'Create a new resource',
options: {
name: { type: 'string', short: 'n' }
},
run: ctx => {
console.log(`Creating resource: ${ctx.values.name}`)
}
}
const listCommand = {
name: 'list',
description: 'List all resources',
run: () => {
console.log('Listing all resources...')
}
}
// Create a Map of sub-commands
const subCommands = new Map()
subCommands.set('create', createCommand)
subCommands.set('list', listCommand)
// Define the main command
const mainCommand = {
name: 'resource-manager',
description: 'Manage resources',
run: () => {
console.log('Use one of the sub-commands: create, list')
}
}
// Run the CLI with composable sub-commands
cli(process.argv.slice(2), mainCommand, {
name: 'my-app',
version: '1.0.0',
subCommands
})For more detailed examples, check out the playground/composable in the repository.
âŗ Lazy & Async Command Loading
Load commands lazily and execute them asynchronously:
import { cli } from 'gunshi'
// Define a command that will be loaded lazily
const lazyCommand = async () => {
// Simulate async loading
await new Promise(resolve => setTimeout(resolve, 1000))
// Return the actual command
return {
name: 'lazy',
description: 'A command that is loaded lazily',
run: async ctx => {
// Async execution
await new Promise(resolve => setTimeout(resolve, 500))
console.log('Command executed!')
}
}
}
// Create a Map of sub-commands with lazy-loaded commands
const subCommands = new Map()
subCommands.set('lazy', lazyCommand)
// Run the CLI with lazy-loaded commands
cli(
process.argv.slice(2),
{ name: 'main', run: () => {} },
{
name: 'my-app',
subCommands
}
)For more detailed examples, check out the playground/lazy-async in the repository.
đ Auto Usage Generation
Gunshi automatically generates usage information:
import { cli } from 'gunshi'
const command = {
name: 'app',
description: 'My application',
options: {
path: { type: 'string', short: 'p' },
recursive: { type: 'boolean', short: 'r' },
operation: { type: 'string', short: 'o', required: true }
},
usage: {
options: {
path: 'File or directory path',
recursive: 'Operate recursively on directories',
operation: 'Operation to perform (list, copy, move, delete)'
},
examples: '# Example\n$ my-app --operation list --path ./src'
},
run: ctx => {
// Command implementation
}
}
// Run with --help to see the automatically generated usage information
cli(process.argv.slice(2), command, {
name: 'my-app',
version: '1.0.0'
})For more detailed examples, check out the playground/auto-usage in the repository.
đ¨ Custom Usage Generation
Customize the usage message generation:
import { cli } from 'gunshi'
// Custom header renderer
const customHeaderRenderer = ctx => {
return Promise.resolve(`
âââââââââââââââââââââââââ
â ${ctx.env.name.toUpperCase()} â
âââââââââââââââââââââââââ
${ctx.env.description}
Version: ${ctx.env.version}
`)
}
// Custom usage renderer
const customUsageRenderer = ctx => {
const lines = []
lines.push('USAGE:')
lines.push(` $ ${ctx.env.name} [options]`)
lines.push('')
lines.push('OPTIONS:')
for (const [key, option] of Object.entries(ctx.options || {})) {
const shortFlag = option.short ? `-${option.short}, ` : ' '
lines.push(` ${shortFlag}--${key.padEnd(10)} ${ctx.translation(key)}`)
}
return Promise.resolve(lines.join('\n'))
}
// Run with custom renderers
cli(
process.argv.slice(2),
{ name: 'app', run: () => {} },
{
name: 'my-app',
version: '1.0.0',
description: 'My application',
renderHeader: customHeaderRenderer,
renderUsage: customUsageRenderer
}
)For more detailed examples, check out the playground/custom-usage in the repository.
đ Internationalization
Support internationalization:
import { cli } from 'gunshi'
import enUS from './locales/en-US.json' with { type: 'json' }
const command = {
name: 'greeter',
options: {
name: { type: 'string', short: 'n' },
formal: { type: 'boolean', short: 'f' }
},
// Resource fetcher for translations
resource: async ctx => {
if (ctx.locale.toString() === 'ja-JP') {
const resource = await import('./locales/ja-JP.json', { with: { type: 'json' } })
return resource.default
}
// Default to English
return enUS
},
run: ctx => {
const { name = 'World', formal } = ctx.values
const greeting = formal ? ctx.translation('formal') : ctx.translation('informal')
console.log(`${greeting}, ${name}!`)
}
}
// Run with locale support
cli(process.argv.slice(2), command, {
name: 'my-app',
version: '1.0.0',
// Set the locale via an environment variable
// If Node v21 or later is used, you can use the built-in `navigator.language` instead)
locale: new Intl.Locale(process.env.MY_LOCALE || 'en-US')
})For more detailed examples, check out the playground/i18n in the repository.
đââī¸ Showcases
- pnpmc: PNPM Catalogs Tooling
đ Contributing guidelines
If you are interested in contributing to gunshi, I highly recommend checking out the contributing guidelines here. You'll find all the relevant information such as how to make a PR, how to setup development) etc., there.
đ Credits
This project is inspired by:
citty, created by UnJS team and contributors- cline and claude 3.7 sonnet, examples and docs is generated
Thank you!