はじめに
ねこ宿研究所の技術発信を始めるにあたり、CodexにQiita CLIの環境構築から原稿作成、公開後確認まで手伝ってもらいました。
実際の分担は次のとおりです。
| 工程 | 担当 |
|---|---|
| 既存環境の調査 | Codex |
| Docker構成の作成と検証 | Codex |
| Qiitaのトークン発行・ログイン | 人 |
| 原稿作成と機密情報検査 | Codex |
| ブラウザでのプレビュー確認 | 人 |
| 対象記事だけを公開 | Codex |
| 公開APIとHTTPによる確認 | Codex |
ポイントは、認証情報をCodexとのチャットへ貼らず、外部公開の直前に人が内容を確認することです。
この記事では、実際に使ったファイルとコマンド、途中で詰まった点、CodexへCLIを渡すときに決めておくとよい境界をまとめます。
なぜQiita CLIをDockerで動かしたのか
最初にCodexがローカル環境を調べると、ホストのNode.jsはv20.11.1でした。一方、その時点で使ったQiita CLI 1.10.0には、より新しいNode.jsが必要でした。
ホスト全体のNode.jsを更新すると、別プロジェクトへ影響する可能性があります。そこでQiita記事の管理を独立したディレクトリへ分け、Node.js 22.22.1をDocker内に固定しました。
neko-yado-qiita/
├── Dockerfile
├── compose.yaml
├── package.json
├── qiita.config.json
├── README.md
└── public/
├── ai-wordpress-wp-cli-guardrails.md
└── codex-qiita-cli-docker-workflow.md
これにより、Codexはプロジェクト内の同じコマンドだけを使えばよく、ホスト側のNode.jsバージョンへ依存しなくなりました。
Dockerfile
実際のDockerfileは次のような構成です。
FROM node:22.22.1-bookworm-slim
WORKDIR /app
COPY package.json ./
RUN npm install --no-audit --no-fund \
&& npm cache clean --force \
&& mkdir -p /home/node/.config/qiita-cli \
&& chown -R node:node /home/node/.config
USER node
WORKDIR /workspace
ENTRYPOINT ["/app/node_modules/.bin/qiita"]
CMD ["help"]
package.jsonでは、公式パッケージを固定しました。
{
"name": "neko-yado-qiita",
"private": true,
"dependencies": {
"@qiita/qiita-cli": "1.10.0"
}
}
USER nodeにより、Qiita CLIをrootでは実行しません。認証情報の保存先も先に作り、nodeユーザーが書き込めるようにしています。
最初はENTRYPOINTで失敗した
初版では次のENTRYPOINTにしていました。
ENTRYPOINT ["npx", "--no-install", "qiita"]
しかし、依存パッケージは/app/node_modulesへインストールし、実行時のカレントディレクトリは/workspaceです。npxが実行ファイルを見つけられず、次のエラーになりました。
npm error could not determine executable to run
そこで、インストール済みバイナリの絶対パスをENTRYPOINTへ指定しました。
ENTRYPOINT ["/app/node_modules/.bin/qiita"]
修正後、Codexがコンテナ内でバージョンを確認しました。
docker compose run --rm qiita version
1.10.0
AIにCLIを使わせる場合、コマンドの入口と作業ディレクトリを固定すると、毎回の指示が短くなり、環境差による失敗も追いやすくなります。
Composeで認証情報と原稿を分ける
Composeは次のようにしました。
services:
qiita:
build:
context: .
init: true
stdin_open: true
tty: true
volumes:
- .:/workspace
- qiita-credentials:/home/node/.config/qiita-cli
ports:
- "127.0.0.1:8888:8888"
volumes:
qiita-credentials:
原稿と設定はホスト側のプロジェクトへ保存します。一方、Qiitaの認証情報は名前付きボリュームqiita-credentialsへ分離します。
この構成では、アクセストークンを次の場所へ入れません。
- Markdown原稿
.env- Git管理対象ファイル
- Codexとのチャット
- シェルのコマンド引数
Qiita CLIが必要とするときだけ、コンテナから名前付きボリュームを読みます。
ログインは人が行う
イメージを作った後、ログインだけは人がターミナルで実行しました。
docker compose build
docker compose run --rm qiita login
表示された案内に従って、Qiitaで発行したトークンをCLIへ直接入力します。トークンをCodexへ渡す必要はありません。
ログイン後、Codexからは次のような認証済み操作を実行できます。
docker compose run --rm qiita version
docker compose run --rm qiita new article-name
docker compose run --rm --service-ports qiita preview
docker compose run --rm qiita publish article-name
認証情報そのものを見せることと、認証済みCLIの限定されたコマンドを使わせることは分けて考えられます。
qiita initが作った自動公開workflowを削除した
Codexが次を実行して初期設定を生成しました。
docker compose run --rm qiita init
qiita.config.jsonに加えて、GitHubへのpushで記事を公開するworkflowも生成されました。
今回は「人がプレビューを確認した後、指定した1記事だけを公開する」という方針です。pushを公開承認としては扱わないため、自動生成されたworkflowを削除しました。
さらに、通常の投稿では次を使いません。
docker compose run --rm qiita publish --all
代わりに記事名を指定します。
docker compose run --rm qiita publish ai-wordpress-wp-cli-guardrails
CLIを導入した直後は「動くこと」だけでなく、暗黙に追加された自動化がないかも確認したほうが安全です。
原稿作成から公開までの会話
実際には、次の流れで進めました。
1. Codexが実装を調査する
最初の記事は、ねこ宿研究所で実際に使っているWordPress運用ラッパーを題材にしました。Codexがローカルのシェルスクリプトや設計資料を読み、実装と記事の説明が一致するように原稿を作成しました。
架空の成功談にはせず、実装が厳密な許可リストではなく、高危険度コマンドを拒否する方式である点も明記しました。
2. 公開してはいけない情報を検査する
投稿前にCodexが次を確認しました。
- 本番ホスト名
- SSHユーザー名
- サーバー内部パス
- 資格情報の値
- Markdownのコードフェンス
- Qiita front matter
- 参考リンクのHTTP応答
技術記事では、コードを正しくするだけでなく、実環境の情報をどこまで匿名化するかもレビュー対象になります。
3. 人がプレビューする
プレビューは次のコマンドで起動しました。
docker compose run --rm --service-ports qiita preview
ブラウザでhttp://localhost:8888を開き、人が表示と内容を確認します。
この段階では、まだQiitaへ公開していません。人が「確認できました」と返した後にだけ、Codexが投稿コマンドを実行しました。
4. Codexが対象記事だけを投稿する
docker compose run --rm qiita publish ai-wordpress-wp-cli-guardrails
Qiita CLIは成功時に記事IDを返し、Markdownのfront matterへidとupdated_atを書き戻しました。
5. 公開結果を別経路で確認する
コマンドの終了コード0だけで完了とはしませんでした。CodexがQiita APIから公開記事を取得し、次を再確認しました。
- 記事ID
- タイトル
private: false- タグ
- 公開URL
- 作成日時と更新日時
最後に公開URLがHTTP 200で応答することも確認しました。
「投稿コマンドが成功した」と「読者が公開記事を取得できる」は別の確認です。
Codexへ依頼するときの例
最初から公開まで一度に任せるのではなく、停止地点を明示すると扱いやすくなります。
このディレクトリでDocker版Qiita CLIを構築してください。
- 公式Qiita CLIを使用する
- Node.jsのバージョンを固定する
- 認証情報はGit管理対象やチャットへ出さない
- 記事はpublic/*.mdで管理する
- publish --allとpush時の自動公開は使わない
- 原稿完成後はプレビューで止める
- 私が確認した後、指定した記事だけを公開する
- 公開後はAPIとHTTP応答を確認する
OpenAIの公式ユースケースでも、Codexが繰り返し使うCLIには、予測可能な出力、認証チェック、draft-before-write、書き込み操作の承認境界を持たせる考え方が紹介されています。
今回分けてよかった境界
Codexへ任せたこと
- 必要バージョンの調査
- DockerfileとComposeの作成
- CLIの初期化
- エラーの診断と修正
- 原稿作成
- 静的検査
- 指定記事の投稿
- 公開後確認
人が行ったこと
- Qiitaトークンの発行
- CLIへのログイン
- ブラウザ上のプレビュー確認
- 公開してよいという最終判断
認証操作をすべて人に残す必要がある、という意味ではありません。CIで公開する運用も可能です。ただし、誰がどのイベントを公開承認とみなすかは、最初に決める必要があります。
今回の小規模な運用では、「プレビューを見た人の明示的な返答」を公開承認にしました。
まとめ
CodexからQiita CLIを使う環境を作り、実際に記事を公開できました。
うまく機能したのは、次の点です。
- Qiita用ディレクトリとDocker環境を独立させる
- CLIとNode.jsのバージョンを固定する
- 認証情報を名前付きボリュームへ分離する
-
publish --allやpush時の自動公開を使わない - 人のプレビュー確認を公開前の停止地点にする
- 公開後にAPIとHTTPで結果を再確認する
Codexに外部サービスのCLIを渡すと、環境構築から検証まで一つの会話で進められます。ただし、便利さを活かすには「どのコマンドを使えるか」だけでなく、「どの時点で人が確認するか」「秘密情報をどこへ置くか」「成功をどう再確認するか」まで設計するのが大切でした。
前回公開した実例は、AIに本番WordPressを操作させるため、WP-CLIの実行経路を4層に分けたです。