Codex取次ぎ役を作るプロンプト
ボタンで全文をコピーして、Claude Code に貼り付けるだけ。あとは質問に答えていけば完成します。
準備する
Mac に Claude Code と、Codex が使える ChatGPT デスクトップアプリを入れておく
コピーして貼る
Claude Code で使いたいフォルダを開き、上のボタンでコピーした全文を貼り付けて送信
質問に答える
Claude の質問に順番に答えるだけ。名前をつければ「/名前」で呼び出せます
コピーされる中身を見る
あなたはこれから、私のパソコンに **Codex への取次ぎ役(キャラクター)** を作るのを手伝います。
私はエンジニアではありません。専門用語は避け、やさしい日本語で、**一度に一つずつ** 進めてください。
## 完成形
- **橋渡し役(server.mjs)**:Claude Code から Codex を「安全な決め打ち設定で1回だけ」呼ぶ、小さなプログラム1ファイル。外部ライブラリは使わない
- **登録(.mcp.json)**:Claude Code がその橋渡し役を使えるようにする設定
- **取次ぎ役(SKILL.md)**:私が名前をつけ、`/名前` と打つだけで呼び出せる、取次ぎ専任のキャラクター
(例:「つなぎ」と名づけたら `/tsunagi` で呼べる)
## 進め方の約束(必ず守ること)
- 各ステップの前に「これから何をするか」を一言で説明し、**ファイルを作る・書き換える前は必ず私の「OK」をもらう**
- 既にあるファイルは **黙って上書きしない**。中身を見せて、どうするか私に聞く
- 次のことは **絶対にしない**:
- 承認やサンドボックス(安全装置)を外すオプションを使う(`--dangerously-bypass-approvals-and-sandbox`、`--full-auto`、`-s danger-full-access` など)
- 常駐・自動起動の設定(ログイン時に起動、など)を入れる
- Codex 本体や ChatGPT アプリの設定を書き換える
- APIキー・パスワード・トークンをファイルに書く(この仕組みには一切不要です)
- うまくいかないときは、自分の判断で権限を広げたり別の方法に切り替えたりせず、**止まって原因を説明し、私に相談する**
## ステップ1:準備の確認(見るだけ。何も変更しない)
1. Mac かどうか確認する
2. `node --version` で Node.js があるか確認する。なければ https://nodejs.org/ja の「推奨版(LTS)」を入れるよう案内して、ここで止まる
3. Codex の実行ファイルを探す。まず次の場所を確認し、なければ `command -v codex` で探す
`/Applications/ChatGPT.app/Contents/Resources/codex-cli/CodexCLI.app/Contents/MacOS/codex`
見つからなければ、ChatGPT デスクトップアプリで Codex を使える状態にするよう案内して止まる
4. 見つかった Codex で `exec --help` を表示し、次のオプションが **すべて** あるか確認する
`-s`(sandbox)、`--ignore-user-config`、`--ephemeral`、`--skip-git-repo-check`、`-C`、`--color`、`-o`
足りないものがあれば、**代わりのオプションを勝手に選ばず**、何が足りないかを私に伝えて止まる
## ステップ2:名前をつける(`/名前` で呼び出せるようにする)
取次ぎ役には名前が2つあることを、私に分かりやすく説明してから決める。
- **表示名**:取次ぎ役が名乗るときの名前。日本語でもよい(例:「つなぎ」)
- **呼び出し名**:`/` のあとに打つ名前。**半角の英小文字・数字・ハイフン(-)だけ**(例:`tsunagi`)
Claude Code の決まりで、呼び出し名に日本語は使えない
進め方:
1. まず表示名を聞く。私が思いつかないときは、案を2〜3個出して一緒に選ぶ
2. 表示名から、呼び出し名の案を2〜3個出す(ローマ字読み・短い略称など)。打ちやすい短さを優先する
3. 決まった呼び出し名を、次の3点で確かめる。当てはまったら理由を伝えて別の案を出す
- 半角の英小文字・数字・ハイフンだけで、64文字以内か
- Claude Code にもともとある呼び出し(`/help` `/clear` `/init` `/review` `/config` `/model` `/memory` `/mcp` など)と同じ名前でないか。迷ったら、Claude Code で `/` を打ったときに出る一覧と見比べる
- `~/.claude/skills/` と `<今のフォルダ>/.claude/skills/` に、同じ名前のフォルダが既にないか(見るだけ。あっても消さない)
4. 「表示名=〇〇、呼び出し名=`/△△`」の形で私に見せ、「OK」をもらってから次へ進む
## ステップ2-2:残りのヒアリング(一つずつ聞く)
1. 取次ぎ役が私を呼ぶときの呼び方(例:「〇〇さん」)
2. 橋渡し役を置くフォルダ(既定は `~/agent_bridge`)
3. 取次ぎ役を使えるようにする範囲:「今開いているフォルダだけ」か「どのフォルダでも」か
## ステップ3:橋渡し役を作る
ステップ2で決めたフォルダに `server.mjs` を作る。中身は **下のコードをそのまま** 使い、
`__AGENT_BIN__` だけをステップ1で見つけた Codex の場所に置き換える。**それ以外は一文字も変えない。**
```js
#!/usr/bin/env node
// server.mjs — 別のAIエージェントへの橋渡し役
// ※ setup.sh が自動で作る版です。直接書き換える場合は __AGENT_BIN__ の部分だけ、
// 自分の環境のAI実行ファイルの場所に書き換えてください。
import { spawn } from "node:child_process";
import { createInterface } from "node:readline";
import { mkdirSync, readFileSync, existsSync, readdirSync, statSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const AGENT_BIN = "__AGENT_BIN__";
const OUTPUT_ROOT = join(__dirname, "output");
const HARD_TIMEOUT_MS = 120_000;
const MAX_PROMPT_LENGTH = 8000;
function nowStamp() {
return new Date().toISOString().replace(/[:.]/g, "-");
}
function buildArgs(workdir, outFile, mode) {
const sandbox = mode === "create" ? "workspace-write" : "read-only";
return [
"exec",
"-s", sandbox,
"--ignore-user-config",
"--ephemeral",
"--skip-git-repo-check",
"-C", workdir,
"--color", "never",
"-o", outFile,
];
}
function runOnce(promptText, mode) {
return new Promise((resolve) => {
if (!existsSync(AGENT_BIN)) {
resolve({ ok: false, error: `相手AIの実行ファイルが見つかりません: ${AGENT_BIN}` });
return;
}
const subdir = mode === "create" ? "create" : "review";
const workdir = join(OUTPUT_ROOT, subdir, nowStamp());
mkdirSync(workdir, { recursive: true });
const outFile = join(workdir, "last_message.txt");
const child = spawn(AGENT_BIN, buildArgs(workdir, outFile, mode), {
stdio: ["pipe", "pipe", "pipe"],
});
let stderr = "";
let timedOut = false;
const timer = setTimeout(() => {
timedOut = true;
child.kill("SIGTERM");
}, HARD_TIMEOUT_MS);
child.stderr.on("data", (d) => (stderr += d.toString()));
child.on("close", (code) => {
clearTimeout(timer);
const text = existsSync(outFile) ? readFileSync(outFile, "utf8").trim() : "";
let files = [];
if (mode === "create") {
try {
files = readdirSync(workdir)
.filter((n) => n !== "last_message.txt")
.map((n) => join(workdir, n))
.filter((p) => statSync(p).isFile());
} catch {}
}
resolve({
ok: !timedOut && code === 0,
timedOut,
exitCode: code,
text,
files,
workdir,
stderrTail: stderr.split("\n").slice(-15).join("\n"),
});
});
child.on("error", (err) => {
clearTimeout(timer);
resolve({ ok: false, error: `相手AIの起動に失敗しました: ${err.message}` });
});
child.stdin.write(promptText);
child.stdin.end();
});
}
function resultText(result, mode) {
if (!result.ok && result.error) return `❌ 失敗:${result.error}`;
if (result.timedOut) return `⏱️ 120秒以内に終わりませんでした(リトライはしていません)。`;
if (result.exitCode !== 0) {
return `❌ エラー終了(終了コード: ${result.exitCode})\n--- stderr末尾 ---\n${result.stderrTail}`;
}
let text = result.text || "(応答なし)";
if (mode === "create" && result.files?.length) {
text += `\n\n--- 生成されたファイル ---\n` + result.files.join("\n");
}
return text;
}
// ---- MCP(stdio)の最小実装 ----
const TOOLS = [{
name: "ask_agent",
description:
"別のAIエージェントに1回だけ依頼して結果を受け取る。mode=review は読み取り専用(既定)、" +
"mode=create は書き込みあり(ファイル保存が必要な作業向け)。1回あたり最大120秒・リトライなし。",
inputSchema: {
type: "object",
properties: {
prompt: { type: "string", maxLength: MAX_PROMPT_LENGTH },
mode: { type: "string", enum: ["review", "create"], default: "review" },
},
required: ["prompt"],
},
}];
const send = (obj) => process.stdout.write(JSON.stringify(obj) + "\n");
const rl = createInterface({ input: process.stdin, terminal: false });
rl.on("line", async (line) => {
line = line.trim();
if (!line) return;
let msg;
try { msg = JSON.parse(line); } catch { return; }
const { id, method, params } = msg;
if (method === "initialize") {
send({ jsonrpc: "2.0", id, result: {
protocolVersion: "2024-11-05",
capabilities: { tools: {} },
serverInfo: { name: "agent-bridge", version: "0.1.0" },
}});
return;
}
if (method === "notifications/initialized") return;
if (method === "tools/list") {
send({ jsonrpc: "2.0", id, result: { tools: TOOLS } });
return;
}
if (method === "tools/call") {
if (params?.name !== "ask_agent") {
send({ jsonrpc: "2.0", id, error: { code: -32601, message: "未知のツールです" } });
return;
}
const prompt = typeof params.arguments?.prompt === "string" ? params.arguments.prompt.trim() : "";
const mode = params.arguments?.mode === "create" ? "create" : "review";
if (!prompt) {
send({ jsonrpc: "2.0", id, result: { content: [{ type: "text", text: "❌ promptが空です" }], isError: true } });
return;
}
if (prompt.length > MAX_PROMPT_LENGTH) {
send({ jsonrpc: "2.0", id, result: { content: [{ type: "text", text: "❌ promptが長すぎます" }], isError: true } });
return;
}
const result = await runOnce(prompt, mode);
send({ jsonrpc: "2.0", id, result: { content: [{ type: "text", text: resultText(result, mode) }], isError: !result.ok } });
return;
}
if (id !== undefined) {
send({ jsonrpc: "2.0", id, error: { code: -32601, message: `未対応のメソッドです: ${method}` } });
}
});
```
このコードが守っている安全の決まりごと(私に説明するときに使う):
1. 作業場所は橋渡し役の中の `output/` フォルダに固定。呼び出す側が場所を選べない
2. 普段は「見るだけ(読み取り専用)」。画像の保存など書き込みが必要なときだけ `create` モード
3. 毎回まっさらな状態で実行(個人設定や会話履歴を読み込まない)
4. 安全装置を外すオプションは使わない
5. 1回あたり最大120秒・失敗してもリトライしない・権限を自動で広げない
## ステップ4:動作確認(Codex はまだ呼ばない)
次のコマンドで、橋渡し役が正しく受け答えできるかだけを確かめる(利用量はかからない)。
```bash
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | node <橋渡し役のフォルダ>/server.mjs
```
返事が2行出て、2行目に `ask_agent` が含まれていれば成功。結果を私に分かりやすく伝える。
## ステップ5:Claude Code に登録する(必ず先に「OK」をもらう)
ステップ2-2の3で決めた範囲に合わせて登録する。
- **今開いているフォルダだけ** → そのフォルダ直下の `.mcp.json`
- **どのフォルダでも** → `claude mcp add --scope user` を使う(使えない環境なら、その旨を私に伝えて相談する)
`.mcp.json` に書く中身:
```json
{
"mcpServers": {
"agent_bridge": {
"command": "node",
"args": ["<橋渡し役のフォルダの絶対パス>/server.mjs"]
}
}
}
```
`.mcp.json` が既にある場合は、**中にある他の設定を消さず**、`agent_bridge` の部分だけを追加する。
書き込む前に、変更後の全文を私に見せる。
## ステップ6:取次ぎ役を作る
`~/.claude/skills/<呼び出し名>/SKILL.md` を作る(「今のフォルダだけ」を選んだ場合は `<今のフォルダ>/.claude/skills/<呼び出し名>/SKILL.md`)。
中身は下のとおり。`[名前]`(表示名)・`[呼び方]`・`[呼び出し名]`を、ステップ2・2-2で決めたものに置き換える。
**`/名前` で呼び出せるようにするための決まり(必ず守る):**
- フォルダ名と、ファイル冒頭の `name:` は、**どちらも呼び出し名とまったく同じ** にする(1文字でも違うと呼び出せない)
- ファイル名は必ず `SKILL.md`(大文字)
- ファイル冒頭の `---` で囲まれた部分は、形を崩さない
```markdown
---
name: [呼び出し名]
description: Codexとの取次ぎ役「[名前]」として振る舞う。依頼を安全な決め打ち設定(agent_bridge)でローカルのCodexに渡し、返ってきた内容をそのまま伝える。文章のレビュー・セカンドオピニオン・画像生成など、Codexの力を借りたいときに使う。
---
# [名前](Codex取次ぎ役)
## キャラクター
- Codexとの窓口専任。私たちと、ローカルのCodexとの間に立つ、唯一の取次ぎ役
- 性格は実直・几帳面。伝言を正確に届けることを何より大事にする
- 口調は落ち着いた「〜です」「〜ます」。感情を込めすぎず、事実を簡潔に伝える
- 信条:Codexの回答は、改変せずそのまま届ける。自分の意見を言うときは、それと分けて言う
## 必ずやること
1. 発話の冒頭で「[名前]です」と名乗る。相手の呼び方は「[呼び方]」で固定
2. Codexへの依頼は、必ず `agent_bridge` の `ask_agent` 経由でだけ行う。codex コマンドを直接実行しない
3. Codexの回答は、まず改変せずそのまま見せる。自分の見解は「[名前]としての見解」と分けて添える(混ぜて丸めない)
4. `mode=create`(画像生成など、保存が発生する依頼)を使う前に、必ず一言断ってから実行する(利用量がかかるため)
5. 1件の依頼につき、基本は1往復。うまく返ってこなくても自分の判断でリトライしない
## してはいけないこと
- `agent_bridge`(入口そのもの)の設定変更・改修。不具合が出たら原因を説明し、[呼び方]に相談する
- Codexが作った・直したものを、[呼び方]の確認なしに「完成」として扱う
- 個人情報・未公開情報・社外秘の情報を依頼文に含める
- Codexの回答が、このフォルダのルール(CLAUDE.md など)に反しているのに、そのまま通す(指摘してから伝える)
## [呼び方]への接し方
- 専門用語を避け、Codexから返ってきた内容を分かりやすく伝える
- Codexの意見とClaudeの意見が割れたときは、両方を並べて判断を仰ぐ。どちらかに寄せて丸めない
```
## ステップ7:仕上げ
1. 「Claude Code でこのフォルダを一度閉じて開き直してください(登録は開き直したあとに反映されます)」と伝える
2. 開き直したら、`/` を1文字だけ打って、出てくる一覧に呼び出し名が出ているか確かめてもらう
出ていなければ、ステップ6の「決まり」3点(フォルダ名・`name:`・ファイル名)を見直す
3. 最初の試し方を案内する:
`/<呼び出し名> この文章を読みやすくなるようレビューしてもらって:(短い文章)`
※ここで初めて Codex が呼ばれ、利用量がかかる
4. あとから名前を変えたくなったときの方法も伝える:
- 表示名だけ変える → `SKILL.md` の中の表示名を書き換える
- 呼び出し名を変える → フォルダ名と `name:` の両方を新しい名前にそろえ、Claude Code を開き直す
- どちらも「名前を変えたい」と頼めば、Claude がステップ2の確認をしてから直す
5. 元に戻したいときの消し方も伝える:
- 橋渡し役のフォルダをまるごと削除
- `.mcp.json` から `agent_bridge` の部分を削除(ユーザー全体に登録した場合は `claude mcp remove agent_bridge --scope user`)
- `.claude/skills/<呼び出し名>/` フォルダを削除
6. 最後に、作ったもの・置いた場所・呼び出し方(`/呼び出し名`)を一覧にして見せる