第1回 |ターミナルで育つ相棒を作ろう。zsh製ミニ育成アプリ「Term-gotchi」開発記 | UNIX Cafe

* 当サイトでは、コンテンツの一部に広告を掲載しています。

System Note $ cat /proc/ai-disclosure

本記事の構成および論理分析にはAI(人工知能)を使用しています。情報の正確性は、システム管理者(UNIXユーザー)による手動検証済みです。

第1回 |ターミナルで育つ相棒を作ろう。zsh製ミニ育成アプリ「Term-gotchi」開発記 | UNIX Cafe

zsh の中で育つ小さな相棒アプリ Term-gotchi を作りました。普段のコマンド操作で経験値がたまり、世話もできるミニ育成アプリです。この記事では、安全なシェル設計を意識しながら、設計から実装、配布可能な形にまとめるまでの流れを紹介します。

ターミナルは、作業のための道具です。

でも毎日開いている場所だからこそ、少しだけ遊び心があると、驚くほど愛着が出ます。

今回作ったのは、zsh の中で育つ小さな相棒アプリ、Term-gotchi です。

lsgit など、いつものコマンドを打つたびに経験値がたまり、状態を見たり、ごはんをあげたり、ちょっと話しかけたりできます。しかも、普段のシェル環境を壊さないことを最優先にして、macOSLinuxzsh 上で安全に動くように設計しました。

この記事では、どういう発想でこのアプリを作ったのか、どんな順番で設計と実装を進めたのか、ターミナル初心者でも真似できる作り方の考え方、そして最後にどうやって配布可能な形まで整えたのかを、開発の流れに沿って紹介します。

自分も何かターミナルアプリを作ってみたい

そんな気持ちが少しでも湧いたら、この企画は成功です。

目次

まず作りたかったもの

最初に欲しかったのは、高機能なゲームではありませんでした。

欲しかったのは、毎日ターミナルを開くのが少し楽しくなること、コマンド操作に小さな達成感が乗ること、英語ベースの軽いメッセージで学習感も少し入ること、そして既存のシェル環境は絶対に壊さないことです。

その結果、最初の MVP では次のような仕様に絞りました。(ここでいう MVP は、まず動く最小版のことです)

  • zsh 専用
  • 状態は ~/.termgotchi/state.json に保存
  • tg_status で様子を見る
  • tg_feedtg_cleantg_talktg_train で世話をする
  • 普段のコマンド実行で経験値が増える
  • レベルが上がると egg -> sprout -> buddy と進化する

ここで大事なのは、最初から全部作ろうとしないことです。動く最小単位を決めると、設計も実装もかなり進めやすくなります。

最初にコードを書かず、設計から始めた理由

ターミナルアプリは、うまく作れば軽快ですが、雑に作るとすぐに危険になります。

例えば、.zshrc を壊してシェル起動時にエラーを出したり、preexec / precmd を乱暴に上書きして他の設定と衝突したり、state ファイル更新に失敗して JSON を壊したり、同じ hook が何度も登録されて経験値が二重に増えたりします。

そこで今回は、実装前に次の文書を用意しました。

  • docs/spec.md
  • docs/architecture.md
  • docs/implementation-plan.md
  • NEXT.md

役割はシンプルです。

  • spec.md: 何を作るか
  • architecture.md: どう配置し、どう安全に動かすか
  • implementation-plan.md: どの順番で作るか
  • NEXT.md: 次回どこから再開するか

個人開発でも、この 4 つがあるだけで驚くほど迷いが減ります。

安全性の方針を最初に決めた

今回のアプリでいちばん重視したのは、見た目やゲーム性よりも安全性でした。

最初に決めたルールは次の通りです。

1. データは ~/.termgotchi/ に閉じ込める

アプリの実行ファイルや state は、すべてホーム配下の専用ディレクトリに入れます。

~/.termgotchi/
  termgotchi.zsh
  state.json
  art/
  backup/

この形にしておくと、削除も簡単ですし、他の設定と混ざりません。

2. .zshrc には 1 行だけ追加する

追記するのは次の guarded source line だけです。

[[ -f "$HOME/.termgotchi/termgotchi.zsh" ]] && source "$HOME/.termgotchi/termgotchi.zsh" # termgotchi

1 行だけ、しかもコメント付き。これなら後から見つけやすく、アンインストールもしやすくなります。

3. preexec()precmd() を直接上書きしない

代わりに add-zsh-hook を使います。

add-zsh-hook preexec tg_on_command_start
add-zsh-hook precmd tg_on_command_finish

これで既存のシェル設定との衝突をかなり減らせます。

4. state 更新は temp file + mv

JSON を直接上書きすると、途中失敗で壊れる可能性があります。そのため、一時ファイルに書き、jq empty で検証し、最後に mv で置き換える流れにしました。

この考え方は、初心者がスクリプトを書くときにもかなり重要です。

実装は「インストール」から始めた

いきなりゲーム部分から作りたくなりますが、今回は逆です。まず作ったのは install.zsh でした。

理由は簡単で、動く場所を先に整えた方が、その後の開発が圧倒的に楽だからです。

install.zsh の役割は次の通りです。

  • jq の存在確認
  • ~/.termgotchi/ の作成
  • termgotchi.zshart/ のコピー
  • state.json の初期化
  • .zshrc に 1 行だけ追記
  • 既存 state.json が壊れていれば backup/ に退避して再生成

この段階で、もうアプリの器はできています。

最初の到達点は tg_status

次に作ったのが termgotchi.zsh の最初の runnable 版です。

ここでの目標は明快でした。インストールしたあとに tg_status が動くことです。

実装したのは主に次の関数です。

  • tg_now
  • tg_require_dependencies
  • tg_require_state
  • tg_load_state
  • tg_render_ascii
  • tg_get_status_message
  • tg_status

最初に表示だけ作ると、state の構造が見えます。そして、いまどこまで完成しているかが目に見えるので、開発のモチベーションも上がります。

世話コマンドを足して、アプリらしさを出した

状態表示だけだと、まだ読み取り専用です。そこで次に追加したのが、tg_feedtg_cleantg_talktg_train です。

それぞれの役割は次の通りです。

  • tg_feed: hunger と mood を上げる
  • tg_clean: health と mood を上げる
  • tg_talk: 今の状態に応じた短いメッセージを返す
  • tg_train: XP と vocab を上げる

ここで重要だったのは、1つのコマンドが state をどう更新するかを明確にすることでした。

例えば tg_feed なら、hunger が 95 以上なら拒否し、それ以外は +20、mood は +3、値は 0..100 に clamp する、というようにルールを先に固定しています。

ルールが曖昧だと、後から表示と保存ロジックがズレて壊れやすくなります。

普段のコマンドで成長する仕組みを入れた

ここからが Term-gotchi の中核です。通常コマンドで経験値が増える仕組みを、preexecprecmd の hook で実装しました。

流れはこうです。

  1. preexec で実行前のコマンド名を取得
  2. precmd で終了後に経験値や回数を更新

経験値ルールはシンプルです。

  • ls, cd, pwd: +1
  • git, vi, vim, nvim, code: +2
  • make, npm, pnpm, yarn, cargo: +3
  • その他の通常コマンド: +1

さらに、初めて出てきたコマンド名には +2 のボーナスがあります。

これだけでも、いつもの作業にちょっとした意味が生まれます。

ただし、全部のコマンドを数えるわけではない

ここはかなり慎重に作りました。例えば次のようなものは、作業進捗というより shell 設定や内部操作です。

  • source
  • .
  • alias
  • setopt
  • export
  • history
  • tg_*

こうしたものは passive XP の対象外にしています。

一方で、次のような wrapper は中身を見ます。

  • command ls
  • builtin pwd
  • noglob ls

つまり、「wrapper 自体ではなく、その後ろの実コマンドを数える」という整理です。

ただし sudo は、安全に中身を解釈しづらいので今は特別扱いしていません。

こういう境界整理は、一見地味ですが、実際の使い勝手にはかなり効きます。

レベルアップと進化を入れる

経験値が増えるだけだと、数字の変化にとどまります。そこで次に入れたのが、レベルアップと進化です。

今回のルールは次の通りです。

  • egg -> sprout when level >= 2
  • sprout -> buddy when level >= 3 and unique_commands.length >= 10

つまり、単に同じコマンドを打ち続けるだけでなく、いろいろなコマンドを使うほど成長しやすい設計です。

このあたりは、ターミナルを日常的に使う人にとって気持ちいい設計ポイントです。

idle decay で「放っておくと少しさびしい」を入れた

育成アプリらしさを出すために、最小限の idle decay も入れました。

ルールは次のような段階式です。

  • 6 時間以上: hunger -10
  • 12 時間以上: hunger -20, mood -5
  • 24 時間以上: hunger -30, mood -10

ただし、ここでも安全性を優先して、次の制限を入れました。

  • tg_status では decay を適用しない
  • health は減らさない
  • 同じ idle window で何度も減衰しないよう last_decay_at を使う

「ゲームらしさ」と「壊れにくさ」のバランスを取るのが、この段階のポイントでした。

アンインストールまで作ると、安心して試せる

地味ですが、かなり重要だったのが uninstall.zsh です。

これがあることで、次の撤去が簡単にできます。

  • .zshrc から Term-gotchi の 1 行だけ外す
  • ~/.termgotchi/ を削除する

新しいものを試したい人ほど、「ちゃんと戻せるのか?」を気にします。

そのため、アンインストール手順まで含めて完成させるのは、配布可能なアプリにするうえでとても大切です。

さらに、他のマシンに持っていけるようにした

開発が一段落したあと、配布用ディレクトリも作りました。

構成はこんな感じです。

dist/termgotchi-portable/
  README.md
  install.zsh
  uninstall.zsh
  termgotchi.zsh
  art/
  docs/

さらに、dist/termgotchi-portable.zipdist/termgotchi-portable.tar.gz も作成して、他の Mac や PC にそのまま渡せる形にしました。

ここまでやると、「個人の作業メモ」ではなく、ちゃんとした小さなアプリになります。

実際にやってよかったこと

今回の開発で、特によかったのは次の 5 つです。

1. 最初に仕様を書いたこと

何を作るかが最初に固まっていたので、途中で迷いにくくなりました。

2. 安全性を先に決めたこと

シェルまわりは後から安全に直す方が大変です。最初に制約を決めたのが効きました。

3. install → status → care → passive growth の順に積んだこと

機能の依存関係に沿って進めたので、毎回「動く区切り」がありました。

4. 隔離した HOME で検証したこと

本物の ~/.zshrc を汚さずに検証できたので、安心して試せました。

5. 配布と撤去まで含めて作ったこと

使う人の目線に立つと、導入と削除のしやすさは機能そのものと同じくらい大事です。

ターミナル初心者に伝えたいこと

「ターミナルで何か作る」と聞くと、難しそうに感じるかもしれません。でも実際には、今回の Term-gotchi も最初から複雑だったわけではありません。

最初はただ、ファイルを 1 つ置く、状態を JSON で持つ、コマンドを 1 つ作る、それを少しずつ増やす、という積み重ねです。

ターミナルアプリの面白さは、GUI よりも部品が少なく、構造が見えやすいところにあります。

だからこそ、シェル、ファイル、JSON、フック、インストーラのような UNIX 的な考え方を学ぶには、とても良い題材です。

もし「自分も作ってみたい」と思ったなら、次のようなものから始めるのがおすすめです。

  • 今日の作業回数を数えるスクリプト
  • よく使うコマンドを集計するツール
  • シェル起動時に一言出すだけの小さな相棒
  • JSON を使ったミニ日記ツール

最初は小さくても良いのです。小さいものでも毎日使っていると、愛情が生まれます。

ちょっとした作品を完成させた気分です。

まとめ

Term-gotchi は、見た目は小さな遊びですが、中身はかなりまじめに作っています。

  • .zshrc に 1 行だけ追加する
  • state は安全に保存する
  • hook は衝突しにくい形で登録する
  • 導入も削除も簡単にする
  • 他のマシンにも持っていけるようにする

こうした積み重ねで、日常に気軽に入れられるターミナルアプリになりました。

もしこの記事を読んで、「ターミナルって、作業だけの場所じゃないかもしれない」と思ってもらえたらうれしいです。

そして、次はぜひ、自分だけの小さなターミナルアプリを作ってみてください。最初の 1 本は、思っているよりずっと簡単に、そして楽しみながら作れます。

次回は、成長記録を足していきます

Term-gotchi は、zsh の中で育つ小さな相棒として動き始めました。

次回は、ここに成長記録と分岐進化を追加します。育ったあとに何が起きたのかを見返せるようにして、ただのミニアプリから、長く触りたくなる道具へ育てていきます。

👉 第2回「育てて終わりにしない。Term-gotchi に成長記録を足した開発記」へ進む

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

のいのアバター のい UNIX Cafe マスター

Macintosh Color Classicから始まった旅は、長いWindows時代を経て、Windows10のサポート終了をきっかけにUNIXの世界へ戻ってきました。UNIX Cafeでは、UNIX・Linux・そしてMacな世界を、むずかしい言葉を使わず、物語のように書いています。プログラミングは、アイデアをコンピューターに伝えるための言葉です。簡単な単語と文法を覚えれば、誰でもコマンドを使えます。ぜひ一度、やさしいプログラミングの世界をのぞいてみてください。

目次