本記事の構成および論理分析にはAI(人工知能)を使用しています。情報の正確性は、システム管理者(UNIXユーザー)による手動検証済みです。
第1回 |ターミナルで育つ相棒を作ろう。zsh製ミニ育成アプリ「Term-gotchi」開発記 | UNIX Cafe

zsh の中で育つ小さな相棒アプリ Term-gotchi を作りました。普段のコマンド操作で経験値がたまり、世話もできるミニ育成アプリです。この記事では、安全なシェル設計を意識しながら、設計から実装、配布可能な形にまとめるまでの流れを紹介します。
ターミナルは、作業のための道具です。
でも毎日開いている場所だからこそ、少しだけ遊び心があると、驚くほど愛着が出ます。
今回作ったのは、zsh の中で育つ小さな相棒アプリ、Term-gotchi です。
ls や git など、いつものコマンドを打つたびに経験値がたまり、状態を見たり、ごはんをあげたり、ちょっと話しかけたりできます。しかも、普段のシェル環境を壊さないことを最優先にして、macOS や Linux の zsh 上で安全に動くように設計しました。
この記事では、どういう発想でこのアプリを作ったのか、どんな順番で設計と実装を進めたのか、ターミナル初心者でも真似できる作り方の考え方、そして最後にどうやって配布可能な形まで整えたのかを、開発の流れに沿って紹介します。
「自分も何かターミナルアプリを作ってみたい」
そんな気持ちが少しでも湧いたら、この企画は成功です。
まず作りたかったもの
最初に欲しかったのは、高機能なゲームではありませんでした。
欲しかったのは、毎日ターミナルを開くのが少し楽しくなること、コマンド操作に小さな達成感が乗ること、英語ベースの軽いメッセージで学習感も少し入ること、そして既存のシェル環境は絶対に壊さないことです。
その結果、最初の MVP では次のような仕様に絞りました。(ここでいう MVP は、まず動く最小版のことです)
zsh専用- 状態は
~/.termgotchi/state.jsonに保存 tg_statusで様子を見るtg_feed、tg_clean、tg_talk、tg_trainで世話をする- 普段のコマンド実行で経験値が増える
- レベルが上がると
egg -> sprout -> buddyと進化する
ここで大事なのは、最初から全部作ろうとしないことです。動く最小単位を決めると、設計も実装もかなり進めやすくなります。
最初にコードを書かず、設計から始めた理由
ターミナルアプリは、うまく作れば軽快ですが、雑に作るとすぐに危険になります。
例えば、.zshrc を壊してシェル起動時にエラーを出したり、preexec / precmd を乱暴に上書きして他の設定と衝突したり、state ファイル更新に失敗して JSON を壊したり、同じ hook が何度も登録されて経験値が二重に増えたりします。
そこで今回は、実装前に次の文書を用意しました。
docs/spec.mddocs/architecture.mddocs/implementation-plan.mdNEXT.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" # termgotchi1 行だけ、しかもコメント付き。これなら後から見つけやすく、アンインストールもしやすくなります。
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.zshとart/のコピーstate.jsonの初期化.zshrcに 1 行だけ追記- 既存
state.jsonが壊れていればbackup/に退避して再生成
この段階で、もうアプリの器はできています。
最初の到達点は tg_status
次に作ったのが termgotchi.zsh の最初の runnable 版です。
ここでの目標は明快でした。インストールしたあとに tg_status が動くことです。
実装したのは主に次の関数です。
tg_nowtg_require_dependenciestg_require_statetg_load_statetg_render_asciitg_get_status_messagetg_status
最初に表示だけ作ると、state の構造が見えます。そして、いまどこまで完成しているかが目に見えるので、開発のモチベーションも上がります。
世話コマンドを足して、アプリらしさを出した
状態表示だけだと、まだ読み取り専用です。そこで次に追加したのが、tg_feed、tg_clean、tg_talk、tg_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 の中核です。通常コマンドで経験値が増える仕組みを、preexec と precmd の hook で実装しました。
流れはこうです。
preexecで実行前のコマンド名を取得precmdで終了後に経験値や回数を更新
経験値ルールはシンプルです。
ls,cd,pwd:+1git,vi,vim,nvim,code:+2make,npm,pnpm,yarn,cargo:+3- その他の通常コマンド:
+1
さらに、初めて出てきたコマンド名には +2 のボーナスがあります。
これだけでも、いつもの作業にちょっとした意味が生まれます。
ただし、全部のコマンドを数えるわけではない
ここはかなり慎重に作りました。例えば次のようなものは、作業進捗というより shell 設定や内部操作です。
source.aliassetoptexporthistorytg_*
こうしたものは passive XP の対象外にしています。
一方で、次のような wrapper は中身を見ます。
command lsbuiltin pwdnoglob ls
つまり、「wrapper 自体ではなく、その後ろの実コマンドを数える」という整理です。
ただし sudo は、安全に中身を解釈しづらいので今は特別扱いしていません。
こういう境界整理は、一見地味ですが、実際の使い勝手にはかなり効きます。
レベルアップと進化を入れる
経験値が増えるだけだと、数字の変化にとどまります。そこで次に入れたのが、レベルアップと進化です。
今回のルールは次の通りです。
egg -> sproutwhenlevel >= 2sprout -> buddywhenlevel >= 3andunique_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.zip と dist/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 に成長記録を足した開発記」へ進む





