配布用プロンプト

Codex取次ぎ役を作るプロンプト

ボタンで全文をコピーして、Claude Code に貼り付けるだけ。あとは質問に答えていけば完成します。

全 10,539 文字
  1. 準備する

    Mac に Claude Code と、Codex が使える ChatGPT デスクトップアプリを入れておく

  2. コピーして貼る

    Claude Code で使いたいフォルダを開き、上のボタンでコピーした全文を貼り付けて送信

  3. 質問に答える

    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. 最後に、作ったもの・置いた場所・呼び出し方(`/呼び出し名`)を一覧にして見せる