{"id":35707031,"url":"https://github.com/jhzhu89/m2r","last_synced_at":"2026-03-03T07:05:23.485Z","repository":{"id":331632322,"uuid":"1128643854","full_name":"jhzhu89/m2r","owner":"jhzhu89","description":null,"archived":false,"fork":false,"pushed_at":"2026-01-18T09:00:46.000Z","size":82,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-18T18:19:48.290Z","etag":null,"topics":["azure-openai","claude-code","openai"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jhzhu89.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-06T00:20:58.000Z","updated_at":"2026-01-18T09:00:47.000Z","dependencies_parsed_at":"2026-01-11T06:01:39.352Z","dependency_job_id":null,"html_url":"https://github.com/jhzhu89/m2r","commit_stats":null,"previous_names":["jhzhu89/m2r"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/jhzhu89/m2r","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhzhu89%2Fm2r","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhzhu89%2Fm2r/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhzhu89%2Fm2r/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhzhu89%2Fm2r/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jhzhu89","download_url":"https://codeload.github.com/jhzhu89/m2r/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhzhu89%2Fm2r/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30033063,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-03T05:09:26.876Z","status":"ssl_error","status_checked_at":"2026-03-03T05:09:23.944Z","response_time":61,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["azure-openai","claude-code","openai"],"created_at":"2026-01-06T03:17:38.408Z","updated_at":"2026-03-03T07:05:23.481Z","avatar_url":"https://github.com/jhzhu89.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# m2r\n\nAnthropic Messages API → Azure OpenAI Responses API proxy.\nEnables Claude Code CLI and other Anthropic-compatible clients to use Azure OpenAI as the backend.\n\n## Installation\n\n```bash\nnpm install -g @jhzhu89/m2r\n```\n\n## Configuration\n\nCreate `~/.m2rrc` with your Azure OpenAI settings (Entra ID only; API keys are not used):\n\n```bash\n# Required\nAZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com\n\n# Optional\nAZURE_OPENAI_API_VERSION=2025-04-01-preview\nPROXY_PORT=8000\nLOG_LEVEL=info\n\n# Model routing (optional)\nMODEL_MAP={\"claude-3-5-sonnet\":\"gpt-5.2\"}\nTIER_HAIKU=gpt-5-mini\nTIER_SONNET=gpt-5.2\nTIER_OPUS=gpt-5.1-codex-max\n```\n\nAuth uses `DefaultAzureCredential`, so ensure your environment is logged in (e.g., `az login`) or set the usual `AZURE_CLIENT_ID` / `AZURE_TENANT_ID` / `AZURE_CLIENT_SECRET`.\n\nRouting: `MODEL_MAP` overrides exact model aliases; otherwise `haiku`/`sonnet`/`opus` substrings map to the configured tier models.\n\n## Usage\n\nStart the proxy server:\n\n```bash\nm2r\n```\n\nThen point your Anthropic client to `http://localhost:8000`.\n\n## Linux (systemd user service)\n\nInstall and start:\n\n```bash\n./scripts/m2r-service.sh install\n```\n\nStatus and logs:\n\n```bash\nsystemctl --user status m2r.service\njournalctl --user -u m2r.service -f\n```\n\nEnable start on boot without login:\n\n```bash\nloginctl enable-linger $USER\n```\n\nUninstall:\n\n```bash\n./scripts/m2r-service.sh uninstall\n```\n\n### Zsh / Bash (optional helpers)\n\nAdd to `~/.zshrc` or `~/.bashrc`:\n\n```bash\nclaude() {\n    local proxy_port=8000\n    local m2rrc=\"$HOME/.m2rrc\"\n\n    if [[ -f \"$m2rrc\" ]]; then\n        local port_line=$(grep '^PROXY_PORT=' \"$m2rrc\")\n        if [[ -n \"$port_line\" ]]; then\n            proxy_port=\"${port_line#PROXY_PORT=}\"\n        fi\n    fi\n\n    if ! nc -z localhost \"$proxy_port\" 2\u003e/dev/null; then\n        echo \"Starting m2r on port $proxy_port...\"\n        mkdir -p \"$HOME/.local/log\"\n        nohup m2r \u003e\u003e \"$HOME/.local/log/m2r.log\" 2\u003e\u00261 \u0026\n        sleep 1\n    fi\n\n    ANTHROPIC_BASE_URL=\"http://localhost:$proxy_port\" \\\n    ANTHROPIC_API_KEY=\"x\" \\\n    CLAUDE_CODE_MAX_OUTPUT_TOKENS=\"64000\" \\\n    command claude \"$@\"\n}\n\nm2r-config() {\n    local m2rrc=\"$HOME/.m2rrc\"\n    local action=\"$1\"\n    local key=\"$2\"\n    local value=\"$3\"\n\n    mkdir -p \"$(dirname \"$m2rrc\")\"\n\n    case \"$action\" in\n        get)\n            [[ -z \"$key\" ]] \u0026\u0026 echo \"Usage: m2r-config get KEY\" \u0026\u0026 return 1\n            [[ -f \"$m2rrc\" ]] \u0026\u0026 grep -E \"^${key}=\" \"$m2rrc\" | tail -n 1 | cut -d= -f2-\n            ;;\n        set)\n            [[ -z \"$key\" || -z \"$value\" ]] \u0026\u0026 echo \"Usage: m2r-config set KEY VALUE\" \u0026\u0026 return 1\n            if [[ -f \"$m2rrc\" ]] \u0026\u0026 grep -q \"^${key}=\" \"$m2rrc\"; then\n                if sed --version \u003e/dev/null 2\u003e\u00261; then\n                    sed -i \"s|^${key}=.*|${key}=${value}|\" \"$m2rrc\"\n                else\n                    sed -i '' \"s|^${key}=.*|${key}=${value}|\" \"$m2rrc\"\n                fi\n            else\n                echo \"${key}=${value}\" \u003e\u003e \"$m2rrc\"\n            fi\n            ;;\n        list|\"\")\n            [[ -f \"$m2rrc\" ]] \u0026\u0026 cat \"$m2rrc\" || true\n            ;;\n        *)\n            echo \"Usage: m2r-config [list|get KEY|set KEY VALUE]\"\n            return 1\n            ;;\n    esac\n}\n```\n\n### PowerShell\n\nAdd to your `$PROFILE`:\n\n```powershell\nfunction Get-M2rPort {\n    $m2rrc = \"$HOME\\.m2rrc\"\n    if (Test-Path $m2rrc) {\n        switch -Regex -File $m2rrc { '^PROXY_PORT=(\\d+)' { return [int]$Matches[1] } }\n    }\n    return 8000\n}\n\nfunction Test-M2rRunning($port) {\n    try { $tcp = [System.Net.Sockets.TcpClient]::new(\"localhost\", $port); $tcp.Dispose(); return $true } catch { return $false }\n}\n\nfunction Start-M2r($port) {\n    $logDir = \"$HOME\\.local\\log\"\n    New-Item -ItemType Directory -Path $logDir -Force -ErrorAction SilentlyContinue | Out-Null\n    Start-Process powershell -ArgumentList \"-WindowStyle Hidden -Command `\"m2r *\u003e\u003e '$logDir\\m2r.log'`\"\" -WindowStyle Hidden\n    for ($i = 0; $i -lt 20; $i++) {\n        Start-Sleep -Milliseconds 250\n        if (Test-M2rRunning $port) { return $true }\n    }\n    return $false\n}\n\nfunction claude {\n    $port = Get-M2rPort\n    if (-not (Test-M2rRunning $port)) {\n        Write-Host \"Starting m2r on port $port...\" -ForegroundColor Cyan\n        if (-not (Start-M2r $port)) { Write-Host \"Failed to start m2r\" -ForegroundColor Red; return }\n    }\n    $env:ANTHROPIC_BASE_URL = \"http://localhost:$port\"\n    $env:ANTHROPIC_API_KEY = \"x\"\n    $env:CLAUDE_CODE_MAX_OUTPUT_TOKENS = \"64000\"\n    \u0026 (Get-Command claude -CommandType Application)[0].Source @args\n}\n\nfunction m2r-restart {\n    $port = Get-M2rPort\n    $stopped = $false\n    Get-CimInstance Win32_Process -Filter \"Name='bun.exe'\" | Where-Object { $_.CommandLine -match 'm2r' } | ForEach-Object {\n        Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue\n        $stopped = $true\n    }\n    Write-Host $(if ($stopped) { \"Stopped m2r\" } else { \"m2r not running\" })\n    if (Start-M2r $port) { Write-Host \"m2r started on port $port\" } else { Write-Host \"Failed to start m2r\" -ForegroundColor Red }\n}\n\nfunction m2r-log {\n    param([switch]$Follow, [int]$Tail = 50)\n    $log = \"$HOME\\.local\\log\\m2r.log\"\n    if (-not (Test-Path $log)) {\n        Write-Host \"Log file not found: $log\" -ForegroundColor Yellow\n        return\n    }\n    if ($Follow) {\n        Get-Content $log -Wait -Tail $Tail\n    } else {\n        Get-Content $log -Tail $Tail\n    }\n}\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjhzhu89%2Fm2r","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjhzhu89%2Fm2r","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjhzhu89%2Fm2r/lists"}