◉ANTIGRAVITY LABEN
記事一覧/AIツール
⚙ AIツール/2026-10-15中級

agy の headless モードに日本語の校閲を任せる 50 行のラッパー

Antigravity CLI(agy)の headless モードを、日本語の校閲用シェルスクリプトに包みます。stdin の渡し方、終了コード 0 でも応答が空になる場合、引数の長さ上限、2027 年 1 月の単価改定を含めた費用の見積もりまで、手元で確かめた順に書き残します。

Antigravity CLI38agy5headless5シェルスクリプト3費用見積もり

別のモデルに日本語の校閲を任せる、という記事を読んだあと、同じことを自分の環境でも試してみたくなりました。書いた本人のクセは、書いた本人のモデルには見えにくいものです。その発想がとても腑に落ちたからです。

ところが、いざ agy -p を呼ぶスクリプトを書き始めると、細かな疑問が次々に出てきました。文章をパイプで渡せるのか、失敗したときの終了コードはどうなるのか、そもそも 1 回いくらかかるのか。個人開発で 1 本のスクリプトに頼る以上、この三つは先に決めておきたいところです。

最初にお伝えしておきます。私が手元で確かめたのは、シェル側の分岐です。実機の agy には接続しておらず、引数を受け取って終了コードを変える偽の agy で、ラッパーの挙動を一つずつ検証しました。agy 本体の仕様は、公式の Headless mode のページに書かれていることだけを根拠にしています。

公式ドキュメントで確かめた headless の約束事

headless モード(print モード)は、1 つのプロンプトを実行して応答を書き出し、終了します。ラッパーに関係する部分だけを抜き出します。

  • プロンプトは -p、--print、--prompt のどれかで渡します。応答は stdout、診断メッセージは stderr に出ます
  • 認証は保存済みの資格情報を使います。先に対話モードの agy を一度起動しておく必要があります。未認証のまま端末なしで実行すると、固まらずに authentication required で終了します
  • モデルは --model <slug> で指定します。使える slug は agy models で一覧できます。headless では別モデルへ黙って切り替わることはなく、未知の slug は非 0 で終了します
  • 承認が必要な操作は、承認を得られないため soft-deny になります。実行は続き、終了コードは 0 で、stderr に通知が出ます
  • 待ち時間の上限は --print-timeout(既定は 5 分)です

そして、見落としやすい点が一つあります。公式のページには、プレーンテキストを -p へパイプで流し込む方法が書かれていません。--input-format stream-json は標準入力を読みますが、JSON を 1 行ずつ渡す別の仕組みです。そこで、標準入力はラッパー側で読み、プロンプトの文字列に結合して引数として渡すことにしました。

50 行のラッパー

校閲の依頼文は「直す箇所を 3 点(元の文、直し案、理由)で最大 5 件」に絞っています。校閲役に書き直しまで任せると、元の文章の声が失われるためです。

#!/usr/bin/env bash
# 日本語の文章を agy の headless モードで別モデルに校閲させる小さなラッパー
set -euo pipefail
 
MODEL="${JA_REVIEW_MODEL:-gemini-3.8-flash-medium}"
LIMIT="${JA_REVIEW_TIMEOUT:-3m}"
 
if [ "$#" -gt 0 ]; then
  TEXT="$*"
elif [ ! -t 0 ]; then
  TEXT="$(cat)"
else
  echo "使い方: ja-review.sh '文章'  または  cat draft.md | ja-review.sh" >&2
  exit 64
fi
 
if [ -z "${TEXT//[[:space:]]/}" ]; then
  echo "入力が空です" >&2
  exit 64
fi
 
# 引数1個の上限(Linux は 128KB)に近づく前に止める
if [ "$(printf '%s' "$TEXT" | wc -c)" -gt 100000 ]; then
  echo "入力が長すぎます(100,000 バイト超)。章ごとに分けてください" >&2
  exit 65
fi
 
PROMPT="次の日本語を校閲してください。直す箇所を「元の文 → 直し案 → 理由」の3点で最大5件。事実の追加はしないでください。
 
${TEXT}"
 
ERR="$(mktemp)"
trap 'rm -f "$ERR"' EXIT
 
OUT="$(agy -p "$PROMPT" --model "$MODEL" --print-timeout "$LIMIT" 2>"$ERR")" || {
  echo "agy が失敗しました(終了コード $?)" >&2
  cat "$ERR" >&2
  exit 1
}
 
# 終了コード 0 でも、承認待ちで道具が soft-deny されると本文が空になりうる
if [ -z "${OUT//[[:space:]]/}" ]; then
  echo "応答が空です。stderr:" >&2
  cat "$ERR" >&2
  exit 3
fi
 
printf '%s\n' "$OUT"
[ -s "$ERR" ] && { echo "--- stderr ---" >&2; cat "$ERR" >&2; }
exit 0

モデルの slug gemini-3.8-flash-medium は、公式ページの --model の例に載っているものです。環境によって使える slug は違うので、先に agy models で確認してください。

使い方は 2 通りです。

./ja-review.sh "校閲したい一文"
cat draft.md | ./ja-review.sh
JA_REVIEW_MODEL=別のslug ./ja-review.sh < draft.md

終了コード 0 なのに、応答が空になる

このラッパーでいちばん手間をかけたのは、OUT が空かどうかを調べる判定です。

公式の説明では、承認を得られない操作は soft-deny になり、実行は続いて終了コードは 0 のままです。つまり、agy が成功したように見えても、stdout が空という状態があり得ます。終了コードだけを見るスクリプトは、何も言われないまま空の結果を受け取ることになります。

偽の agy で再現すると、終了コード 0・stdout 空・stderr に通知、という組み合わせになりました。ラッパーはこれを終了コード 3 として扱い、stderr をそのまま見せます。終了コード 0 を信じすぎていたと気づいたのは、この空応答を再現したときでした。

状況偽 agy の挙動ラッパーの終了コード
正常本文を出力して 00
入力が空agy を呼ばない64
入力が 100,000 バイト超agy を呼ばない65
agy が非 0 で終了(未知の slug など)stderr に理由を出して 11
終了コード 0 で stdout が空stderr に soft-deny の通知3

校閲だけなら、ファイルの書き込みもシェル実行も要りません。あえて権限を広げず、空応答を検出して気づく設計にしています。--dangerously-skip-permissions で押し切ると、校閲のはずが文章ファイルを書き換える余地が生まれるからです。

長い原稿で「Argument list too long」

標準入力を読んで引数に結合する方式には、弱点があります。最初の版では、約 15 万バイト(全角 5 万字)の原稿を流すと、偽の agy の起動時点でこう止まりました。

agy が失敗しました(終了コード 126)
./ja-review.sh: line 29: .../bin/agy: Argument list too long

約 9 万バイト(全角 3 万字)では通ったので、Linux で引数 1 個の上限が約 128KB であることと合っています。そこで、100,000 バイトを超えたら agy を呼ばずに終了コード 65 で止める確認を足しました。長い原稿は、章ごとに分けて渡してください。

もう一つ、Zenn の記事によると、-p は次のトークンを引数として取るため、-p= の形で渡すほうが安全だそうです。今回のラッパーは、依頼文が必ず日本語の「次の」から始まるので、文章の先頭が - でも引数と取り違えられません。依頼文の頭を固定にしたのは、この点も理由でした。

費用を先に見積もっておく

校閲は何度も回すので、1 回の単価が小さくても、続けるうちに気になってきます。Gemini API の料金ページ(2026-10-15 に確認)では、gemini-3.8-flash の標準料金は次のとおりです。

期間入力(100 万トークンあたり)出力(思考を含む)
現行$0.75$3.75
2027 年 1 月 1 日以降$1.50$7.50

ページには、この単価が 2027 年 1 月 1 日に倍になると書かれていました。見積もり用のスクリプトを用意します。

"""校閲1回あたりの API 料金を見積もる。単価は 100万トークンあたりの USD。
2026-10-15 に公式の料金ページで確認した gemini-3.8-flash の標準料金。"""
import sys
 
PRICES = {
    "now":        {"input": 0.75, "output": 3.75},  # ~2026-12-31
    "2027-01-01": {"input": 1.50, "output": 7.50},  # 2027-01-01 以降
}
 
def cost(in_tok: int, out_tok: int, period: str) -> float:
    p = PRICES[period]
    return in_tok / 1_000_000 * p["input"] + out_tok / 1_000_000 * p["output"]
 
def main() -> None:
    in_tok = int(sys.argv[1]) if len(sys.argv) > 1 else 3000
    out_tok = int(sys.argv[2]) if len(sys.argv) > 2 else 1200
    runs = int(sys.argv[3]) if len(sys.argv) > 3 else 100
    for period in PRICES:
        one = cost(in_tok, out_tok, period)
        out_share = (out_tok * PRICES[period]["output"]) / (one * 1_000_000)
        print(f"{period:>10}: 1回 ${one:.4f} / {runs}回 ${one*runs:.2f} (出力が占める割合 {out_share:.0%})")
 
if __name__ == "__main__":
    main()

入力 3,000 トークン、出力 1,200 トークン、月 100 回という仮定で実行すると、次の結果でした。トークン数は私の仮定で、実測値ではありません。

       now: 1回 $0.0067 / 100回 $0.68 (出力が占める割合 67%)
2027-01-01: 1回 $0.0135 / 100回 $1.35 (出力が占める割合 67%)

意外だったのは、入力より出力のほうが費用を支配する点です。入力 3,000 に対して出力は 1,200 と少ないのに、単価が 5 倍なので、費用の 67% は出力が占めます。同じ入力のまま出力を 300 に絞ると、1 回 $0.0034 まで下がります(出力の割合は 33%)。依頼文で「最大 5 件」と件数を決めたのは、文章の質のためだけでなく、費用の面でも効いていました。

ただし、この計算が当てはまるのは API キー経由の従量課金 です。サブスクリプション経由で agy を使う場合に、この単価がそのまま適用されるかは、私は確認できていません。どちらで使うかを決めてから読んでいただければと思います。

つまずきそうな点

  • 先に対話モードの agy を一度起動しておかないと、認証エラーで終了します
  • 未知の slug は非 0 で終了します。ラッパーはその理由を stderr からそのまま見せます
  • --disable-slash-commands は、今回確認した公式ページに記載がありませんでした。使うなら、手元で agy --help を見てからにしてください
  • モデルの slug と単価は動きます。この記事の数字は 2026-10-15 時点のものです

次のアクション

終了コード 0 は、成功の証拠にはなりません。出力が空なら、失敗として扱います。 この線引きだけは、ほかのスクリプトにも持ち込むようにしています。

まずは、手元の agy models で slug を一つ選び、短い一文で ja-review.sh を動かしてみていただければ幸いです。そこで空応答が出たら、stderr の通知が最初の手がかりになります。

参考にした情報

シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

⚙ AIツール2026-09-15
役割で分けるのをやめた日 — Antigravity CLI と Claude Code の預け先を決める3つの問い
無人で回していた処理が承認待ちのまま黙って止まりました。二つのエージェント CLI を得意分野で分けるのをやめ、承認の境界で振り分け直すまでの記録と、判断に使っている3つの問いを書き残します。
⚙ AIツール2026-09-11
AGY_CLI_HIDE_LOGO から始める、狭い端末とスクリーンリーダーと録画のための Antigravity CLI 設定
ロゴアート、メールアドレス、長すぎる出力、勝手に走る自動コピー。Antigravity CLI の見た目を抑える環境変数と設定を、狭い端末・スクリーンリーダー・画面録画という三つの場面ごとに並べました。
⚙ AIツール2026-07-26
Antigravity CLI の /effort をタスク種別で振り分ける — 6週間の実測と、効かなかった領域
Antigravity CLI v1.1.5 で加わった /effort を、タスク種別ごとに自動で振り分ける仕組みを作り、6週間分の実行ログを集計しました。効いた領域と、上げるほど結果が悪くなった領域を実測値とともに整理しています。