意思決定を委ねる:CLIツール「should-i」
はじめに
「今日のランチは何にしよう?」「新しいライブラリを試すべきか?」日常の小さな決断から技術選定まで、私たちは日々無数の選択を迫られています。そんな時、宇宙に判断を委ねてみるのはいかがでしょうか。
今回は、yesno.wtf APIを使って意思決定をサポートするCLIツール「should-i」をRustで開発しました。この記事では、その実装過程と技術的なポイントを紹介します。
should-iとは
should-iは、質問を投げかけるとYES/NO/MAYBEで答えてくれるシンプルなCLIツールです。回答と一緒にGIF画像のURLも返してくれるので、視覚的にも楽しめます。
使い方

--openオプションでGIFをブラウザ表示
--openオプションを付けると、回答のGIF画像を自動的にブラウザで開いてくれます:
$ should-i "take a coffee break" --open
🎲 Asking the universe...
✅ YES! Do it! 🎉
🖼️ https://yesno.wtf/assets/yes/2-5df1b403f2654fa77559af1bf2332d7a.gif
# ブラウザでランダムにGIFを自動的に開きます
技術スタック
このプロジェクトでは以下のクレートを使用しました:
- reqwest: HTTP通信を担当
- tokio: 非同期ランタイム
- serde/serde_json: JSONのシリアライズ・デシリアライズ
- clap: CLIの引数パーサー
- anyhow: エラーハンドリング
実装のポイント
1. API通信の実装
yesno.wtf APIはシンプルなRESTful APIで、レスポンスは以下のような構造です:
{
"answer": "yes",
"forced": false,
"image": "https://yesno.wtf/assets/yes/2.gif"
}
これをRustの構造体にマッピングします:
#[derive(Deserialize)]
struct YesNoResponse {
answer: String,
image: String,
}
reqwestを使った非同期通信は、シンプルかつ型安全に記述できます:
let response = client
.get("https://yesno.wtf/api")
.send()
.await?
.json::()
.await?;
2. CLIインターフェースの設計
clapを使うことで、直感的なCLIを簡単に実装できます。derivマクロを活用することで、構造体定義から自動的にパーサーを生成できるのがRustらしい特徴です:
#[derive(Parser)]
#[command(name = "should-i")]
#[command(about = "A CLI tool to help you make decisions")]
struct Cli {
/// The question you want to ask
question: String,
/// Open the GIF image in your browser
#[arg(short, long)]
open: bool,
}
3. ユーザー体験の向上
単に結果を表示するだけでなく、絵文字を使って視覚的に分かりやすい出力を心がけました:
match response.answer.as_str() {
"yes" => println!("✅ YES! Do it! 🎉"),
"no" => println!("❌ NO! Don't do it! 🚫"),
"maybe" => println!("🤔 MAYBE... You decide! 🎲"),
_ => println!("🤷 Unknown response"),
}
また、--openオプションでブラウザを自動的に開く機能も実装しました:
if cli.open {
if let Err(e) = webbrowser::open(&response.image) {
eprintln!("⚠️ Failed to open browser: {}", e);
}
}
エラーハンドリング
Rustの強力な型システムを活かし、anyhowクレートを使って適切なエラーハンドリングを実装しました:
async fn fetch_decision() -> anyhow::Result {
let client = reqwest::Client::new();
let response = client
.get("https://yesno.wtf/api")
.send()
.await
.context("Failed to connect to yesno.wtf API")?
.json::()
.await
.context("Failed to parse API response")?;
Ok(response)
}
context()メソッドを使うことで、エラーが発生した際に具体的な状況を伝えられます。
まとめ
should-iは、シンプルながらもRustのCLIツール開発の基本的な要素を詰め込んだプロジェクトです:
- HTTP通信と非同期処理
- JSONのパース
- CLIの引数処理
- エラーハンドリング
- クロスプラットフォーム対応
「宇宙に判断を委ねる」という遊び心のあるコンセプトですが、実装を通じてRustの実践的な技術を学べました。
迷った時は、ぜひshould-iに聞いてみてください!
$ should-i "write more Rust code"
🎲 Asking the universe...
✅ YES! Do it! 🎉
インストール方法
should-iはcrates.ioで公開されており、cargoを使って簡単にインストールできます:
$ cargo install should-i
インストール後は、すぐに使い始められます:
$ should-i "try this new library"
Discussion