gemini と打ってエンターを押すと、対話画面が静かに立ち上がります。ここまでは、多くの方がすでに体験されているはずです。
けれど私が本当に助けられたのは、対話ではなく、シェルスクリプトの一行として Gemini CLI を呼び出せると気づいたときでした。朝いちばんに 4 サイト分のビルドログへ目を通す作業を、gemini -p に肩代わりしてもらう。それだけで、確認にかけていた時間が目に見えて縮みました。
対話利用の先にある「ツールとしての Gemini CLI」——ここで扱いたいのはそちらです。ヘッドレス実行、JSON 出力、セッション再開、承認モード、そして MCP 連携。実際にスクリプトへ組み込むときに迷いやすい箇所を、順を追って整理していきます。
まず正確なインストールと認証から
Gemini CLI は Google 公式のオープンソースエージェントで、npm パッケージは @google/gemini-cli です。Node.js は 20 以上、できれば 22 LTS を推奨します。
# グローバルインストール
npm install -g @google/gemini-cli
# 永続インストールせず一度だけ試す
npx @google/gemini-cli
# バージョン確認
gemini --version
認証には二通りあります。初回に gemini を起動すると Google アカウントでのサインインを求められ、無料枠でそのまま使い始められます。一方、CI やスクリプトなど対話プロンプトを出せない環境では、API キーを環境変数で渡す方式が確実です。
# API キー認証(CI・自動化向け)
export GEMINI_API_KEY = "YOUR_API_KEY"
キーは .env に置き、必ず .gitignore へ追加します。ここを疎かにすると、公開リポジトリへ鍵ごと push してしまう事故につながります。
echo 'GEMINI_API_KEY=YOUR_API_KEY' > .env
echo '.env' >> .gitignore
対話モードとヘッドレスモードを使い分ける
Gemini CLI の呼び出し方は、大きく「対話」と「ヘッドレス」に分かれます。この境目を意識するだけで、使いどころがはっきりします。
やりたいこと コマンド 挙動
じっくり相談する gemini対話 REPL を起動
一発で答えだけ欲しい gemini -p "..."1ターン実行して終了(非対話)
標準入力を渡す cat file | geminiパイプ内容を処理
実行後そのまま続ける gemini -i "..."実行して対話へ移行
結果を機械処理する gemini -p "..." -o jsonJSON で出力
自動化で鍵になるのは -p(--prompt)と -o(--output-format)です。-p は対話 REPL に入らず 1 ターンで結果を標準出力へ返し、-o json を添えると jq などで扱いやすい構造化データになります。
# ログから重大なエラーだけを抜き出す
cat build.log | gemini -p "このログから致命的なエラーを3件まで、原因の推測を添えて日本語で列挙して"
# 結果を JSON で受け取り、後続処理へ渡す
gemini -p "README.md を1文で要約して" -o json | jq -r '.response'
モデルは -m(--model)で選べます。エイリアスが用意されており、pro は複雑な推論向け、flash は速度と品質のバランス型、flash-lite は最速の軽量タスク向けです。バッチで大量に回すログ選別のような処理は、flash 系に寄せるとコストと待ち時間が締まります。
# 軽い分類タスクは flash 系に寄せる
cat access.log | gemini -m flash -p "アクセス元を国別に集計して上位5件を出して"
セッションを再開して文脈を捨てない
対話を一度閉じても、続きから再開できます。作業を分断せずに済むのは、地味ながら効いてくる機能です。
# 直近のセッションを再開
gemini -r "latest"
# 直近セッションに新しい指示を足して再開
gemini -r "latest" "型エラーが残っていないか確認して"
# セッション一覧を確認し、ID を指定して再開
gemini --list-sessions
gemini -r "abc123" "このPRを仕上げて"
私は、朝に立てた作業方針を昼から拾い直すときに -r "latest" を多用します。前提を毎回説明し直す手間が消えるので、一日をまたぐ長いタスクほど恩恵が大きいと感じています。
GEMINI.md でプロジェクトの前提を固定する
プロジェクト直下に GEMINI.md を置くと、その内容が文脈として毎回読み込まれます。コーディング規約、ディレクトリ構成、避けてほしい操作などをここへ書いておくと、指示のたびに背景を書き足す必要がなくなります。
# GEMINI.md
- このリポジトリは Next.js 16 (App Router) + TypeScript です
- 日本語のコメントは敬体で書いてください
- src/config/ 配下の設定ファイルは変更前に必ず差分を提示してください
外部エディタで GEMINI.md を編集したときは、対話中に /memory reload を打てば読み直されます。MCP サーバーやカスタムコマンドを更新した場合も、それぞれ /mcp reload、/commands reload で再読み込みできます。
承認モードとサンドボックス — 自動化の安全弁
エージェントにファイル変更やシェル実行を任せるほど、「どこまで自動で許すか」の設計が重要になります。Gemini CLI は --approval-mode でこの粒度を選べます。
モード 意味 向いている場面
default変更のたびに確認を求める 不慣れなリポジトリでの手作業
auto_editファイル編集は自動、実行系は確認 編集主体の反復作業
plan実行せず計画だけを提示 着手前の方針レビュー
yoloすべて自動承認 使い捨て環境・CI(要サンドボックス)
yolo は強力ですが、無防備に使うと取り返しのつかない変更を招きます。自動承認で走らせるなら -s(--sandbox)を併用し、隔離環境に閉じ込めるのが安全です。加えて Gemini CLI には Checkpointing があり、AI がファイルを変更する前に状態のスナップショットを自動保存します。想定外の編集も、直前へ巻き戻せる余地が残ります。
私の使い分けは単純です。手元で試すときは default、方針を先に固めたいときは plan、CI など人が見張れない場所でだけ --approval-mode=yolo --sandbox を組み合わせます。
シェルと CI へ組み込む — 動くスクリプト
ここまでの要素を束ねると、実務で使える自動化になります。たとえば push 前に、変更差分をレビューさせる git フックです。
#!/bin/bash
# .git/hooks/pre-push
set -euo pipefail
DIFF = "$( git diff origin/main...HEAD)"
if [ -z " $DIFF " ]; then
exit 0
fi
REVIEW = "$( printf '%s' " $DIFF " | gemini -m flash -p \
"この差分にセキュリティ上の懸念か明白なバグがあれば、行を挙げて簡潔に指摘して。問題がなければ OK とだけ返して")"
echo "----- Gemini review -----"
echo " $REVIEW "
echo "-------------------------"
# 「OK」以外なら人間の確認を促す(強制ブロックはしない)
if ! printf '%s' " $REVIEW " | grep -q "OK" ; then
echo "⚠️ 指摘があります。内容を確認してから push してください。"
fi
CI では、非対話・API キー認証・JSON 出力の三点を押さえます。次は、テスト失敗ログを要約して要点だけをジョブ出力へ残す例です。
#!/bin/bash
# ci-summarize-failure.sh
set -euo pipefail
npm test 2>&1 | tee test.log || true
SUMMARY = "$( cat test.log | gemini -m flash -o json -p \
"テスト失敗の根本原因を最大3点、修正の当たりを添えて日本語で。JSONのresponseに文章で入れて" \
| jq -r '.response')"
echo "::group::Test failure summary"
echo " $SUMMARY "
echo "::endgroup::"
ポイントは、Gemini の判定でジョブを強制的に落とさないことです。あくまで人間の確認を助ける材料として出力し、合否の最終判断はテスト結果そのものに委ねます。判断の主導権を渡しすぎないことが、自動化を長く運用するコツだと感じています。
ヘッドレス実行で詰まる5か所と、その切り分け
対話で動いていたコマンドをそのまま CI へ置くと、たいてい一度は転びます。私が実際に踏んだ順に並べます。
終了コードが常に 0 になる。 gemini -p ... | jq -r '.response' のようにパイプでつなぐと、シェルはパイプ末尾の終了コードだけを見ます。前段が落ちても jq が成功すればジョブは緑のまま通過します。スクリプト冒頭の set -euo pipefail は省略しないでください。
標準入力が空のまま渡る。 TTY のない CI では前段の cat や git diff が空を返しても処理は続き、CLI は空文字から「それらしい」文章を作ってしまいます。渡す直前にバイト数を検査し、0 なら黙って中断するほうが安全です。
応答が返らずジョブが吊る。 ネットワークの詰まりや長大な入力で待ち続けることがあります。CLI 側の設定に頼らず、外側から timeout を被せて上限を握っておきます。
429(レート超過)でまとめて落ちる。 並列でバッチを回すと無料枠でも有料枠でも到達します。指数バックオフに揺らぎ(jitter)を足し、再試行は 3 回までに抑えるのが扱いやすい落としどころでした。
JSON パースが壊れる。 -o json を付けても、警告やプログレスが stderr へ混ざる場面があります。jq -e で妥当性を検証し、失敗したら生テキストへ落とす二段構えにしておくと、ジョブ全体を巻き添えにせずに済みます。
この5点をまとめて吸収する薄いラッパーを、私は各リポジトリの scripts/ に置いています。
#!/bin/bash
# scripts/gem.sh — ヘッドレス呼び出しの共通ラッパー
set -euo pipefail
gem () {
local prompt = " $1 " model = " ${2 :- flash } " input attempt = 0 raw
input = "$( cat )"
if [ "${ # input }" -lt 1 ]; then
echo "gem: 標準入力が空です。前段の生成処理を確認してください。" >&2
return 2
fi
while [ " $attempt " -lt 3 ]; do
if raw = "$( printf '%s' " $input " \
| timeout 120 gemini -m " $model " -o json -p " $prompt " 2> /dev/null)" ; then
if printf '%s' " $raw " | jq -e '.response' > /dev/null 2>&1 ; then
printf '%s' " $raw " | jq -r '.response'
return 0
fi
# JSON として読めなければ生テキストとして扱う
printf '%s' " $raw "
return 0
fi
attempt = $(( attempt + 1 ))
sleep $(( ( 2 ** attempt ) + (RANDOM % 3 ) )) # 指数バックオフ + jitter
done
echo "gem: 3回試行しましたが応答を取得できませんでした。" >&2
return 1
}
呼び出し側は cat build.log | gem "致命的なエラーを3件まで挙げて" flash と書くだけで済みます。失敗の理由が終了コードと stderr に残るので、CI のログから原因を追えるようになりました。空入力を検知して止める仕組みを入れてから、「中身のない要約がジョブ出力に残る」種類の事故はなくなりました。
モデル選択は体感ではなく実測で決める
flash に寄せると速い、という感覚は誰でも持てます。けれど自分のワークロードで何倍違うのかは、測らないと分かりません。同じプロンプトを各モデルへ回して中央値を出す、短い計測スクリプトを用意しておくと判断が早くなります。
#!/bin/bash
# scripts/bench-models.sh <入力ファイル> <試行回数>
set -euo pipefail
INPUT = " $1 " ; N = " ${2 :- 5} "
PROMPT = "このログから致命的なエラーを3件まで、原因の推測を添えて日本語で列挙して"
for MODEL in pro flash flash-lite ; do
TIMES = ()
for _ in $( seq 1 " $N " ); do
START = $( date +%s%3N )
cat " $INPUT " | gemini -m " $MODEL " -p " $PROMPT " > /dev/null 2>&1 || true
TIMES += ($(( $(date +%s%3N ) - START )))
done
MEDIAN = $( printf '%s\n' "${ TIMES [ @ ]}" | sort -n | awk '{a[NR]=$1} END{print a[int((NR+1)/2)]}' )
printf '%-11s median %6s ms\n' " $MODEL " " $MEDIAN "
done
手元の Mac(有線回線・約 3,900 行のビルドログを入力)で 10 回ずつ回したときの中央値は、おおよそ次のとおりでした。絶対値は回線とモデルの更新で動くので、比率のほうを見てください。
モデル 所要時間の中央値 flash 比この計測での使いどころ
pro約 18 秒 約 3.4 倍 原因の推論まで踏み込ませたいとき
flash約 5.3 秒 1.0 日次のログ選別・差分レビュー
flash-lite約 2.6 秒 約 0.5 倍 分類・振り分けなど判断の軽い処理
抽出されたエラー行そのものは、3 モデルでほぼ一致しました。差が出たのは「原因の推測」の踏み込み方で、pro だけが依存関係の更新履歴まで結びつけて説明してきます。つまり、エラー行を拾うだけなら flash-lite で十分で、原因まで当てにいく場面だけ pro を呼べばよい、という配分になります。
私の運用では、pre-push フックと日次のログ選別を flash、記事の分類バッチを flash-lite、障害の原因究明だけを pro に割り当てています。これは数字を見てから決めたもので、感覚で決めていた頃は分類まで pro に流していました。一度でいいので、自分の入力で測ってみることをおすすめします。
MCP サーバーとエクステンションで能力を広げる
Gemini CLI は Model Context Protocol(MCP)に対応しており、外部ツールを CLI の手足として追加できます。設定はサブコマンドで完結します。
# stdio ベースの MCP サーバーを追加
gemini mcp add github npx -y @modelcontextprotocol/server-github
# HTTP ベースのサーバーを追加
gemini mcp add api-server http://localhost:3000 --transport http
# 登録済みサーバーを一覧表示
gemini mcp list
自作のツールサーバーを足したい場合は、TypeScript で MCP サーバーを実装する方法を Gemini API × MCP サーバーの自作(TypeScript) で扱っています。TypeScript の型に不安があれば、プロを目指す人のためのTypeScript入門(鈴木僚太 著) が土台固めに向いています。CLI からの接続手順そのものは Gemini CLI で MCP サーバーを使う にまとめました。
エクステンションを使えば、複数の設定やツールをまとめて配布・導入できます。
gemini extensions install https://github.com/user/my-extension
gemini extensions list
運用して見えた勘所
個人開発で Dolice Labs の複数サイトを回していると、AI に任せる範囲を「気持ちよさ」で決めてしまいがちです。けれど実際に組み込んでみると、効いたのはむしろ制約の設計でした。
ヘッドレス実行に切り替えてから、朝のログ確認は手作業のときより明らかに短くなりました。数値そのものは環境で変わりますが、「読む」から「要点を受け取る」へ性質が変わったことが、いちばんの違いです。一方で、yolo をサンドボックスなしで回してヒヤリとしたこともあります。以来、自動承認とサンドボックスは必ずセットにしています。
もう一つの気づきは、モデル選択をタスクに合わせることの効きめです。分類や要約のような軽い処理まで pro で回すと、待ち時間もコストも膨らみます。flash 系へ寄せるだけで、体感が変わりました。エラーで詰まったときの切り分けは Gemini CLI のエラー対処 が助けになるはずです。
Gemini CLI は、対話の便利さよりも、シェルの一部品として静かに働いてくれるときに真価を発揮する——そう感じています。まずは手元のログ選別を一つ、gemini -p に置き換えてみてください。そこから自分のワークフローに馴染ませていくのが、遠回りに見えて確実だと思います。
私自身もまだ試行錯誤の途中です。お読みいただきありがとうございました。共に手を動かしながら学んでいけたら嬉しいです。