Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions mureo/_data/web/auth_wizards.js
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,18 @@
if (isHosted) {
wrap.innerHTML =
"<h3>" + MUREO.t("wizard.provider_banner." + platform) + "</h3>";
// Codex has no claude.ai account connector, so Meta's hosted MCP
// can't be wired at all — there are no connector steps to show.
// Surface the "not available, native stays" note and let the user
// proceed; mureo-native Meta is never disabled here.
if (state.host === "codex") {
const note = document.createElement("p");
note.className = "dashboard-provider-hosted-note";
note.textContent = MUREO.t("dashboard.provider_codex_hosted_na_note");
note.setAttribute("data-i18n", "dashboard.provider_codex_hosted_na_note");
wrap.appendChild(note);
return wrap;
}
if (!showManualSetup()) onComplete();
return wrap;
}
Expand Down
6 changes: 6 additions & 0 deletions mureo/_data/web/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@
? "wizard.host.claude_desktop"
: status.host === "claude-code"
? "wizard.host.claude_code"
: status.host === "codex"
? "wizard.host.codex"
: null;
if (hostKey) {
node.textContent = MUREO.t(hostKey);
Expand Down Expand Up @@ -291,6 +293,10 @@
// rejects http config; Meta's hosted MCP has no OAuth DCR).
// Only the Connectors instruction applies.
appendNote("dashboard.provider_desktop_connectors_note");
} else if (status && status.host === "codex") {
// Codex has no account-level connector for Meta's hosted MCP,
// so the official path isn't available — keep mureo-native Meta.
appendNote("dashboard.provider_codex_hosted_na_note");
} else {
appendNote("dashboard.provider_hosted_oauth_note");
}
Expand Down
26 changes: 15 additions & 11 deletions mureo/_data/web/i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,17 @@
"wizard.next": "Next",
"wizard.skip": "Skip",
"wizard.step_progress": "Step {current} of {total}",
"wizard.host.title": "Choose your Claude app",
"wizard.host.claude_code": "Claude Code (CLI, Desktop app)",
"wizard.host.claude_desktop": "Claude Desktop app (Chat, Cowork)",
"wizard.host.sync_failed": "Couldn't save your Claude app choice to the local helper. Check it's still running, then re-select your app.",
"wizard.host.title": "Choose your AI agent",
"wizard.host.claude_code": "Claude Code — coding agent (CLI / IDE / app)",
"wizard.host.claude_desktop": "Claude Desktop — chat app (Chat / Cowork)",
"wizard.host.codex": "OpenAI Codex (CLI, IDE extension, desktop app)",
"wizard.host.sync_failed": "Couldn't save your AI agent choice to the local helper. Check it's still running, then re-select your agent.",
"wizard.basic.title": "mureo basic setup",
"wizard.basic.desc": "We will run the three mureo setup steps. Safe to re-run.",
"wizard.basic.install_button": "Install all three",
"wizard.basic.mureo_mcp": "mureo MCP server",
"wizard.basic.auth_hook": "Credential guard hook",
"wizard.basic.auth_hook_desktop_na": "(not available on the Desktop app)",
"wizard.basic.auth_hook_desktop_na": "(not available on Claude Desktop — the chat app)",
"wizard.basic.skills": "Workflow skills",
"wizard.basic.advanced_skip": "Advanced users who only call official MCPs directly may skip this step.",
"wizard.basic.advanced_skip_note": "Skipped basic setup. Click Next to continue.",
Expand Down Expand Up @@ -126,7 +127,7 @@
"dashboard.nav_demo": "Demo",
"dashboard.nav_byod": "BYOD",
"dashboard.nav_danger": "Danger Zone",
"dashboard.host_title": "Claude app",
"dashboard.host_title": "AI agent",
"dashboard.basic_title": "mureo basic setup",
"dashboard.native_title": "mureo integrations",
"dashboard.native_none": "No platforms connected via mureo yet.",
Expand Down Expand Up @@ -225,6 +226,7 @@
"dashboard.tooluse_err_generic": "Could not switch. Please try again.",
"dashboard.provider_hosted_oauth_note": "Registered as a hosted MCP. Finish in Claude Code: run /mcp → Authenticate (Meta login), then reconnect. Meta has no OAuth dynamic client registration — if /mcp reports that, re-register with `claude mcp add --transport http --client-id … --client-secret --callback-port …`, or use the account-level claude.ai Meta Ads connector.",
"dashboard.provider_desktop_connectors_note": "Can't be added from here. In Claude Desktop, open Settings → Connectors → Add custom connector, enter the URL https://mcp.facebook.com/ads, then sign in with Meta.",
"dashboard.provider_codex_hosted_na_note": "Meta's official hosted MCP isn't available on Codex (no account-level connector). Keep using mureo's built-in Meta tools instead.",
"connector.setup_title": "Add Meta Ads in Claude Desktop",
"connector.setup_lead": "Meta's hosted MCP can't be configured from here (it has no OAuth dynamic client registration). Add it once in Claude Desktop:",
"connector.step1": "Open Claude Desktop → Settings → Connectors",
Expand Down Expand Up @@ -335,16 +337,17 @@
"wizard.next": "次へ",
"wizard.skip": "スキップ",
"wizard.step_progress": "ステップ {current} / {total}",
"wizard.host.title": "設定先を選んでください",
"wizard.host.claude_code": "Claude Code(CLI、デスクトップアプリ)",
"wizard.host.claude_desktop": "Claudeデスクトップアプリ(Chat、Cowork)",
"wizard.host.sync_failed": "Claude アプリの選択をローカルヘルパーに保存できませんでした。起動中か確認し、アプリを選び直してください。",
"wizard.host.title": "AIエージェントを選んでください",
"wizard.host.claude_code": "Claude Code — コーディングエージェント(CLI / IDE / アプリ)",
"wizard.host.claude_desktop": "Claude Desktop — チャットアプリ(Chat / Cowork)",
"wizard.host.codex": "OpenAI Codex(CLI、IDE拡張、デスクトップアプリ)",
"wizard.host.sync_failed": "AIエージェントの選択をローカルヘルパーに保存できませんでした。起動中か確認し、エージェントを選び直してください。",
"wizard.basic.title": "基本セットアップ",
"wizard.basic.desc": "mureo の 3 つのセットアップを順次実行します。再実行しても安全です。",
"wizard.basic.install_button": "すべてセットアップする",
"wizard.basic.mureo_mcp": "mureo MCP サーバ",
"wizard.basic.auth_hook": "認証保護 hook",
"wizard.basic.auth_hook_desktop_na": "(デスクトップアプリでは利用できません)",
"wizard.basic.auth_hook_desktop_na": "(Claude Desktop=チャットアプリでは利用できません)",
"wizard.basic.skills": "ワークフロースキル",
"wizard.basic.advanced_skip": "公式 MCP のみで raw 呼出する上級者はこちらから skip 可。",
"wizard.basic.advanced_skip_note": "基本セットアップをスキップしました。「次へ」で続行できます。",
Expand Down Expand Up @@ -541,6 +544,7 @@
"dashboard.tooluse_err_generic": "切り替えできませんでした。再度お試しください。",
"dashboard.provider_hosted_oauth_note": "hosted MCP として登録済み。Claude Code で仕上げてください: /mcp → Authenticate(Meta ログイン)→ 再接続。Meta は OAuth 動的クライアント登録に非対応のため、/mcp がそのエラーを出す場合は `claude mcp add --transport http --client-id … --client-secret --callback-port …` で再登録するか、アカウント単位の claude.ai Meta Ads コネクタを利用してください。",
"dashboard.provider_desktop_connectors_note": "ここからは追加できません。Claude Desktop の 設定 → コネクタ → カスタムコネクタを追加 で URL https://mcp.facebook.com/ads を入力し、Meta でログインしてください。",
"dashboard.provider_codex_hosted_na_note": "Meta公式のホスト型MCPはCodexでは利用できません(アカウント単位のコネクタがありません)。mureo内蔵のMetaツールをそのままご利用ください。",
"connector.setup_title": "Claude Desktop で Meta Ads を追加",
"connector.setup_lead": "この hosted MCP はここからは設定できません(Meta は OAuth の動的クライアント登録に非対応)。Claude Desktop で一度だけ追加してください:",
"connector.step1": "Claude Desktop → 設定 → コネクタ を開く",
Expand Down
12 changes: 10 additions & 2 deletions mureo/_data/web/wizard.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
function readStoredHost() {
try {
const v = window.localStorage.getItem(HOST_KEY);
return v === "claude-code" || v === "claude-desktop" ? v : null;
return v === "claude-code" || v === "claude-desktop" || v === "codex"
? v
: null;
} catch (_e) {
return null;
}
Expand Down Expand Up @@ -239,7 +241,9 @@
'<label><input type="radio" name="host" value="claude-code">' +
MUREO.t("wizard.host.claude_code") + "</label><br>" +
'<label><input type="radio" name="host" value="claude-desktop">' +
MUREO.t("wizard.host.claude_desktop") + "</label>" +
MUREO.t("wizard.host.claude_desktop") + "</label><br>" +
'<label><input type="radio" name="host" value="codex">' +
MUREO.t("wizard.host.codex") + "</label>" +
"</div>";
// Assert the current selection to the server on step entry too —
// not only on a radio `change`. A resumed wizard / restarted
Expand Down Expand Up @@ -477,7 +481,11 @@
// the user adds it as a Claude.ai account connector. Don't list
// Meta as "saved"; surface an explicit, actionable reminder (the
// pending_meta copy covers both hosts).
// Codex has no claude.ai connector, so official Meta is never the
// active path there (mureo-native Meta stays) — don't show the
// Claude.ai-connector reminder; Meta surfaces as a normal saved row.
const metaPending =
STATE.host !== "codex" &&
STATE.platforms.meta_ads &&
STATE.providerChoice.meta_ads === "official";

Expand Down
54 changes: 52 additions & 2 deletions mureo/cli/setup_codex.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,18 @@ def install_codex_mcp_config() -> Path | None:
# ---------------------------------------------------------------------------


def install_codex_credential_guard() -> Path | None:
def install_codex_credential_guard(hooks_file: Path | None = None) -> Path | None:
"""Append PreToolUse hooks to ``~/.codex/hooks.json``.

Existing hook entries are preserved. Returns the path on first
install or ``None`` if the mureo tag is already present.

``hooks_file`` overrides the target (the home-aware configure-UI flow
passes ``<home>/.codex/hooks.json``); it defaults to the real
``~/.codex/hooks.json`` for the ``mureo setup codex`` CLI.
"""
hooks_file = Path.home() / ".codex" / "hooks.json"
if hooks_file is None:
hooks_file = Path.home() / ".codex" / "hooks.json"
hooks_file.parent.mkdir(parents=True, exist_ok=True)

existing: dict[str, Any] = {}
Expand Down Expand Up @@ -206,6 +211,51 @@ def install_codex_credential_guard() -> Path | None:
return hooks_file


def remove_codex_credential_guard(hooks_file: Path | None = None) -> Path | None:
"""Drop the mureo-tagged PreToolUse hooks from ``~/.codex/hooks.json``.

The inverse of :func:`install_codex_credential_guard`: removes only the
entries whose command carries the ``[mureo-credential-guard]`` tag, and
preserves every other hook. Returns the path when something was removed,
or ``None`` when the file is absent/unparseable or no tagged entry was
present (idempotent). ``hooks_file`` mirrors the install override.
"""
if hooks_file is None:
hooks_file = Path.home() / ".codex" / "hooks.json"
if not hooks_file.exists():
return None
try:
parsed = json.loads(hooks_file.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
logger.warning("Could not parse %s — refusing to overwrite", hooks_file)
return None
if not isinstance(parsed, dict):
return None
pre_tool_use = parsed.get("PreToolUse")
if not isinstance(pre_tool_use, list):
return None

def _is_mureo_entry(entry: Any) -> bool:
if not isinstance(entry, dict):
return False
return any(
_GUARD_TAG in hook.get("command", "")
for hook in entry.get("hooks", [])
if isinstance(hook, dict)
)

kept = [entry for entry in pre_tool_use if not _is_mureo_entry(entry)]
if len(kept) == len(pre_tool_use):
return None # nothing tagged — idempotent no-op
parsed["PreToolUse"] = kept
_atomic_write_text(
hooks_file,
json.dumps(parsed, indent=2, ensure_ascii=False) + "\n",
)
logger.info("Codex credential guard removed: %s", hooks_file)
return hooks_file


# ---------------------------------------------------------------------------
# 3. Skills (operational + foundation)
# ---------------------------------------------------------------------------
Expand Down
Loading