5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

国産CLIライブラリ「gunshi」を触ってみた感想

Last updated at Posted at 2025-12-25

この記事は、「デジタル創作サークルUniProject Advent Calendar 2025」のシリーズ16、11日目の記事です。
(このアドカレでは初参加ですがQiita上でシリーズ16なんて文字初めて見ました...)
今回は、国産CLIライブラリであるgunshiが個人的にアツいと感じたので紹介します。

gunshiとは?

gunshiは、kazupon氏 が開発している国産のCLIライブラリです。

Node.jsで扱えるCLIライブラリの例として「Commander.js」や「Yargs」などが有名です。
gunshiは今年リリースされたばかりのライブラリですが、急成長を遂げています。
star-history-20251225.png
gunshiの主な特徴に、

  • TypeScriptのフルサポート(型宣言+型安全)
  • 遅延コマンドのサポート
  • プラグインによる機能拡張のサポート
  • i18nによる国際化のサポート
  • LLMの支援開発キット
  • アップデートの頻度が高い

が挙げられます。

gunshiを用いたCLIの構築

お好きなパッケージマネージャでインストールします。私はBunが好きなのでBunを使います。

bun add gunshi

また、Claude CodeやCursorなどのAIによるコーディングアシスタント向けのドキュメントが整備されています。

bunx @gunshi/docs

これを実行することでインストールされているgunshiのバージョンに合わせたドキュメントとgunshi APIに参照させるためのSkillsが配置されます。既にgunshiがインストールされていた場合は実行したタイミングで最新バージョンに更新されます。
簡単なCLIを作ってみましょう。
適当にindex.tsを作成して次のコードをコピーして実行してみます。
コードはgunshiのドキュメントに記載のコードを一部改変しています。

index.ts
import { cli, define } from 'gunshi'

const command = define({
  name: 'greeter',
  description: 'A simple greeting CLI',
  args: {
    name: {
      type: 'string',
      short: 'n',
      description: 'Name to greet'
    },
    uppercase: {
      type: 'boolean',
      short: 'u',
      description: 'Convert greeting to uppercase'
    }
  },
  run: ctx => {
    const { name = 'World', uppercase } = ctx.values
    let greeting = `Hello, ${name}!`

    if (uppercase) {
      greeting = greeting.toUpperCase()
    }

    console.log(greeting)
  }
})

await cli(process.argv.slice(2), command)

基本的には、コマンドを定義するdefine()と実際にコマンドを実行するcli()に分けて記述します。
そして、

bun index.ts --name hogehoge --uppercase

と実行すると、HELLO, HOGEHOGE!と出力されます。

サブコマンドの作成

次にサブコマンドを作成してみましょう。次のコードをコピーして実行してみてください。

index.ts
import { cli, define } from 'gunshi'

// Define type-safe sub-commands
const createCommand = define({
  name: 'create',
  description: 'Create a new resource',
  args: {
    name: { type: 'string', short: 'n', required: true }
  },
  run: ctx => {
    // ctx.values is fully typed
    console.log(`Creating resource: ${ctx.values.name}`)
  }
})

const listCommand = define({
  name: 'list',
  description: 'List all resources',
  run: () => {
    console.log('Listing all resources...')
  }
})

// Define the main command
const mainCommand = define({
  name: 'manage',
  description: 'Manage resources',
  run: ctx => {
    // This runs when no sub-command is provided
    console.log('Available commands: create, list')
    console.log('Run "manage --help" for more information')
  }
})

// Run the CLI with composable sub-commands
await cli(process.argv.slice(2), mainCommand, {
  name: 'my-app',
  version: '1.0.0',
  subCommands: {
    create: createCommand,
    list: listCommand
  }
})

gunshiでは特に設定せずとも自動的にヘルプページを生成してくれます。そのため、上のコードを実行したときにOptionsとして-hまたは--helpをつけてみるとヘルプページが表示されます。
もちろん、サブコマンドをファイルベースにして管理することもできます。

create.ts
export default define({
  name: 'create',
  description: 'Create a new resource',
  args: {
    name: { type: 'string', short: 'n', required: true }
  },
  run: ctx => {
    // ctx.values is fully typed
    console.log(`Creating resource: ${ctx.values.name}`)
  }
})
index.ts
import { cli } from 'gunshi'
import create from './commands/create'

const main = define({
  name: 'manage',
  description: 'Manage resources',
  run: ctx => {
    // This runs when no sub-command is provided
    console.log('Available commands: create, list')
    console.log('Run "manage --help" for more information')
  }
})

await cli(process.argv.slice(2), main, {
  name: 'resource-manager',
  version: '1.0.0',
  subCommands: {
    create
  }
})

こんな感じでサブコマンドをdefine()で定義し、エントリーポイントのsubCommandsで登録するだけです。サブコマンドが増えたときは次のようにまとめて登録することもできます。

index.ts
const subCommands = {
  add: addCommand,
  info: infoCommand,
  install: installCommand,
  link: linkCommand,
  list: listCommand,
  remove: removeCommand,
  scan: scanCommand,
  search: searchCommand,
  update: updateCommand,
  upgrade: upgradeCommand,
};

await cli(process.argv.slice(2), main, {
  name: 'resource-manager',
  version: '1.0.0',
  subCommands
})

ヘルプページなどのカスタマイズ

gunshiにはカスタムレンダリングという機能があり、cli()部でカスタマイズできます。
cli()部でカスタマイズするとCLI全体が、各コマンドのdefine()部でカスタマイズするとそのコマンド内をカスタマイズできるようになります。CLI全体か各コマンドか自由にカスタマイズできるのは開発者にとってはデザインの幅が広がりますね。
今回は、cli()部で紹介していきます。

index.ts
await cli(process.argv.slice(2), main, {
  name: 'resource-manager',
  version: '1.0.0',
  subCommands,
  // ヘッダーレンダリングのカスタマイズ
  renderHeader: async(ctx) => {
      // TODO: Customize Header
  },
  // バリデーションエラーのカスタマイズ
  renderValidationErrors: async(_ctx, error) => {
      // TODO: Customize Validation Error
  },
  // ヘルプページのUsageのカスタマイズ
  renderUsage: async(ctx) => {
      // TODO: Customize Usage
  }
})

このように、自在にカスタマイズができるようになっています。

便利な点

便利な点としては、

  • カスタマイズ性が高い
  • サブコマンド分けが楽、遅延付きサブコマンドも生成できる
  • context7などのMCPを使わなくても簡単にLLMにドキュメントを参照させられる
  • プラグインによるエコシステム
  • 開発が頻繁

が便利だと感じました。特に公式プラグインでi18nによる国際化をCLIでできるのは珍しいですね。
また、1カ月たたないうちにマイナーバージョンがいくつか上がってたりと開発頻度が高いので新しい機能や不具合の修正等の対応を早く受けられるのはうれしいです。

不便な点

逆に不便な点としては、

  • LLMが一部プラグインシステムを読み間違えることが多い
  • サブコマンドのサブのコマンドが正式にサポートされていない

といったところです。
プラグインシステムでは、@gunshi/plugin-global@gunshi/plugin-rendererといった一部の公式プラグインがgunshi上に組み込まれているということを認識できず、これらのプラグインを新規追加してしまうといったことが発生しました。@gunshi/docsによってドキュメントが生成されていてもGemini3 ProやClaude Opus 4.5が間違えることが多かったのでどのように組み込まれているかわかりやすいほうがいいのかなと感じました。
また、サブコマンドのサブのコマンド、例えば anything repo addのようにrepoがサブコマンドでaddがrepoのサブコマンドとなっている場合にgunshi自体がこの記法をサポートしていないためこちらでバリデーションなりしないといけないです。他ライブラリがどうなっているかわかりませんが、こういった使い方をすることがあったので要望として今出しておきます。

終わりに

gunshiはまだv1にもなっていないライブラリですが、Commander.jsなどほかの著名なライブラリの代替になれるライブラリだと思っています。
もし気になっている方がいれば使って見ませんか?
私が開発しているCLIツールもgunshiを使用していますのでどのように使っているか気になる方がいましたら是非覗いてみてください!(オープンソースです)

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?