{"_id":"@ahwanulm/amrouter","_rev":"4-10d67eb9398ff2552e120a0877d3bd3b","name":"@ahwanulm/amrouter","dist-tags":{"latest":"1.2.3"},"versions":{"1.2.0":{"name":"@ahwanulm/amrouter","version":"1.2.0","keywords":["ai","router","proxy","openai","claude","anthropic","gemini","fallback","cursor","cline","codex","llm","auto-fallback"],"author":{"name":"ahwanulm"},"license":"MIT","_id":"@ahwanulm/amrouter@1.2.0","maintainers":[{"name":"ahwanulm","email":"ahwancules@gmail.com"}],"homepage":"https://amrouter.online","bugs":{"url":"https://github.com/ahwanulm/AmRouter/issues"},"bin":{"amrouter":"bin/amrouter.mjs","amrouter-reset-password":"bin/reset-password.mjs"},"dist":{"shasum":"79aaf8fa300cc690e606d138b1136229b619cc5e","tarball":"https://registry.npmjs.org/@ahwanulm/amrouter/-/amrouter-1.2.0.tgz","fileCount":4697,"integrity":"sha512-5SK5Ty0Jh5hq7zInfA8SxVCxsAKWTH4vAjHU4yUydijm6Z9kp9q6m2Ths3++7+p8fhciuVOQOKxaXH5huR3HZw==","signatures":[{"sig":"MEUCIGqc3XAoSPVq9Tgbi1mLvX/LNTZkwbQNmiRFa/BSspR7AiEA++e8eJ4OimFMJ6njh+re+OKETk3Ix4gH4x6hCUSr18M=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":183644925},"pnpm":{"onlyBuiltDependencies":["@parcel/watcher","@swc/core","better-sqlite3","esbuild","@ahwanulm/amrouter","sharp"]},"type":"module","engines":{"node":">=20.20.2 <21 || >=22.22.2 <23 || >=24.0.0 <27"},"gitHead":"f75dc7ed0c832bd573a1074d9d823e5e44ddf40e","scripts":{"dev":"node scripts/run-next.mjs dev","lint":"eslint .","test":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test --test-concurrency=10 tests/unit/*.test.ts","build":"node scripts/build-next-isolated.mjs","check":"npm run lint && npm run test","start":"node scripts/run-next.mjs start","prepare":"husky","env:sync":"node scripts/sync-env.mjs","test:all":"npm run test:unit && npm run test:vitest && npm run test:ecosystem && npm run test:e2e","test:e2e":"node scripts/run-playwright-tests.mjs test tests/e2e/*.spec.ts","build:cli":"node --import tsx/esm scripts/prepublish.ts","test:unit":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test --test-concurrency=10 tests/unit/*.test.ts","uninstall":"node scripts/uninstall.mjs","audit:deps":"npm audit --audit-level=moderate && npm run audit:electron","test:fixes":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/fixes-p1.test.ts","test:plan3":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/plan3-p0.test.ts","postinstall":"node scripts/postinstall.mjs","system-info":"node scripts/system-info.mjs","test:vitest":"vitest run --config vitest.mcp.config.ts","check:cycles":"node scripts/check-cycles.mjs","electron:dev":"concurrently \"npm run dev\" \"wait-on http://localhost:20128 && cd electron && npm run dev\"","test:coverage":"c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 60 --lines 60 --functions 60 --branches 60 node --import tsx/esm --test --test-concurrency=10 tests/unit/*.test.ts","test:security":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/security-fase01.test.ts","audit:electron":"npm --prefix electron audit --audit-level=moderate","electron:build":"npm run build && cd electron && npm run build","prepublishOnly":"npm run build:cli && npm run check:pack-artifact","test:ecosystem":"node scripts/run-ecosystem-tests.mjs","typecheck:core":"tsc --pretty false -p tsconfig.typecheck-core.json","uninstall:full":"node scripts/uninstall.mjs --full","coverage:report":"c8 report --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov","coverage:summary":"node scripts/test-report-summary.mjs --input coverage/coverage-summary.json --output coverage/coverage-report.md --threshold 60","test:integration":"node --import tsx/esm --test tests/integration/*.test.ts","check:node-runtime":"node --import tsx/esm scripts/check-supported-node-runtime.ts","electron:build:mac":"npm run build && cd electron && npm run build:mac","electron:build:win":"npm run build && cd electron && npm run build:win","test:protocols:e2e":"node scripts/run-protocol-clients-tests.mjs","check:pack-artifact":"node --import tsx/esm scripts/validate-pack-artifact.ts","backfill-aggregation":"node --import tsx/esm src/scripts/backfillAggregation.ts","check:any-budget:t11":"node scripts/check-t11-any-budget.mjs","check:pr-test-policy":"node scripts/check-pr-test-policy.mjs","electron:build:linux":"npm run build && cd electron && npm run build:linux","test:coverage:legacy":"c8 --output-dir coverage --exclude=open-sse --check-coverage --lines 50 --functions 50 --branches 50 node --import tsx/esm --test tests/unit/*.test.ts","coverage:report:legacy":"c8 report --output-dir coverage --exclude=open-sse --reporter=text --reporter=text-summary","electron:smoke:packaged":"node scripts/smoke-electron-packaged.mjs","typecheck:noimplicit:core":"tsc --pretty false -p tsconfig.typecheck-noimplicit-core.json","check:route-validation:t06":"node scripts/check-route-validation.mjs"},"_npmUser":{"name":"ahwanulm","email":"ahwancules@gmail.com"},"overrides":{"hono":"^4.12.18","react":"$react","postcss":"^8.5.10","dompurify":"^3.4.2","lodash-es":"^4.18.1","react-dom":"$react-dom","ip-address":"10.1.1","path-to-regexp":"^8.4.0","@hono/node-server":"^1.19.13"},"repository":{"url":"git+https://github.com/ahwanulm/AmRouter.git","type":"git"},"workspaces":["open-sse"],"_npmVersion":"10.9.7","description":"Unified AI router with 160+ providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.","directories":{},"lint-staged":{"*.{js,jsx,ts,tsx,mjs}":["prettier --write","eslint --fix --no-error-on-unmatched-pattern"],"*.{json,md,yml,yaml,css}":["prettier --write"]},"_nodeVersion":"22.22.2","dependencies":{"ora":"^9.4.0","tsx":"^4.21.0","zod":"^4.4.3","jose":"^6.2.3","next":"^16.2.6","open":"^11.0.0","pino":"^10.3.1","uuid":"^14.0.0","yazl":"^3.3.1","axios":"^1.16.0","lowdb":"^7.0.1","react":"19.2.6","marked":"^18.0.3","undici":"^8.2.0","express":"^5.2.1","fuse.js":"^7.3.0","ioredis":"^5.10.1","js-yaml":"^4.1.1","mermaid":"^11.14.0","wreq-js":"^2.3.0","zustand":"^5.0.13","bcryptjs":"^3.0.3","react-is":"^19.2.6","recharts":"^3.8.1","next-intl":"^4.11.1","react-dom":"19.2.6","bottleneck":"^2.19.5","selfsigned":"^5.5.0","fetch-socks":"^1.3.3","pino-pretty":"^13.1.3","xxhash-wasm":"^1.1.0","@ngrok/ngrok":"^1.7.0","@swc/helpers":"0.5.21","jsonc-parser":"^3.3.1","lucide-react":"^0.460.0","monaco-editor":"^0.55.1","@lobehub/icons":"^5.8.0","better-sqlite3":"^12.9.0","react-markdown":"^10.1.0","node-machine-id":"^1.1.12","tls-client-node":"^0.1.13","https-proxy-agent":"^9.0.0","@monaco-editor/react":"^4.7.0","isomorphic-dompurify":"^3.12.0","http-proxy-middleware":"^3.0.5","pino-abstract-transport":"^3.0.0","@modelcontextprotocol/sdk":"^1.29.0"},"_hasShrinkwrap":false,"devDependencies":{"c8":"^11.0.0","glob":"^13.0.6","husky":"^9.1.7","jsdom":"^29.1.1","eslint":"^9.39.4","vitest":"^4.1.5","wait-on":"^9.0.5","wtfnode":"^0.10.1","prettier":"^3.8.3","cross-env":"^10.1.0","typescript":"^6.0.3","@types/node":"^25.6.2","gray-matter":"^4.0.3","lint-staged":"^16.4.0","node-loader":"^2.1.0","tailwindcss":"^4.3.0","@types/react":"^19.2.14","concurrently":"^9.2.1","@types/keytar":"^4.4.2","@types/bcryptjs":"^3.0.0","@playwright/test":"^1.59.1","@types/react-dom":"^19.2.3","typescript-eslint":"^8.59.2","eslint-config-next":"16.2.6","@tailwindcss/postcss":"^4.3.0","@vitejs/plugin-react":"^6.0.1","@types/better-sqlite3":"^7.6.13","@testing-library/react":"^16.3.2","@testing-library/jest-dom":"^6.9.1"},"optionalDependencies":{"keytar":"^7.9.0"},"_npmOperationalInternal":{"tmp":"tmp/amrouter_1.2.0_1778689330459_0.4006050612196552","host":"s3://npm-registry-packages-npm-production"}},"1.2.1":{"name":"@ahwanulm/amrouter","version":"1.2.1","keywords":["ai","router","proxy","openai","claude","anthropic","gemini","fallback","cursor","cline","codex","llm","auto-fallback"],"author":{"name":"ahwanulm"},"license":"MIT","_id":"@ahwanulm/amrouter@1.2.1","maintainers":[{"name":"ahwanulm","email":"ahwancules@gmail.com"}],"homepage":"https://amrouter.online","bugs":{"url":"https://github.com/ahwanulm/AmRouter/issues"},"bin":{"amrouter":"bin/amrouter.mjs","amrouter-reset-password":"bin/reset-password.mjs"},"dist":{"shasum":"7b30edbe306c0f0198b09fb5f3d2a0d89258d6eb","tarball":"https://registry.npmjs.org/@ahwanulm/amrouter/-/amrouter-1.2.1.tgz","fileCount":4698,"integrity":"sha512-EJ4Ku0j/PHhikAhRQOLxbVNaGKtK577nLnkTtru1zzQTj3lutdj0bB3DEe+3eIyO5UKfgV8r2bfDLXvS/ecz5A==","signatures":[{"sig":"MEQCIHKrLbqxV3ubB0DLagOcIz4yV600SHChikcFQvuo/gQvAiBrJiTrGXG5KW4l+J6ykcW4a00aX/DvG9INsmzOouq7CQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":183721300},"pnpm":{"onlyBuiltDependencies":["@parcel/watcher","@swc/core","better-sqlite3","esbuild","@ahwanulm/amrouter","sharp"]},"type":"module","engines":{"node":">=20.20.2 <21 || >=22.22.2 <23 || >=24.0.0 <27"},"gitHead":"f2a4654c46213e022c4b67de97ee7c3d3dcf6869","scripts":{"dev":"node scripts/run-next.mjs dev","lint":"eslint .","test":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test --test-concurrency=10 tests/unit/*.test.ts","build":"node scripts/build-next-isolated.mjs","check":"npm run lint && npm run test","start":"node scripts/run-next.mjs start","prepare":"husky","env:sync":"node scripts/sync-env.mjs","test:all":"npm run test:unit && npm run test:vitest && npm run test:ecosystem && npm run test:e2e","test:e2e":"node scripts/run-playwright-tests.mjs test tests/e2e/*.spec.ts","build:cli":"node --import tsx/esm scripts/prepublish.ts","test:unit":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test --test-concurrency=10 tests/unit/*.test.ts","uninstall":"node scripts/uninstall.mjs","audit:deps":"npm audit --audit-level=moderate && npm run audit:electron","test:fixes":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/fixes-p1.test.ts","test:plan3":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/plan3-p0.test.ts","postinstall":"node scripts/postinstall.mjs","system-info":"node scripts/system-info.mjs","test:vitest":"vitest run --config vitest.mcp.config.ts","check:cycles":"node scripts/check-cycles.mjs","electron:dev":"concurrently \"npm run dev\" \"wait-on http://localhost:20128 && cd electron && npm run dev\"","test:coverage":"c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 60 --lines 60 --functions 60 --branches 60 node --import tsx/esm --test --test-concurrency=10 tests/unit/*.test.ts","test:security":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/security-fase01.test.ts","audit:electron":"npm --prefix electron audit --audit-level=moderate","electron:build":"npm run build && cd electron && npm run build","prepublishOnly":"npm run build:cli && npm run check:pack-artifact","test:ecosystem":"node scripts/run-ecosystem-tests.mjs","typecheck:core":"tsc --pretty false -p tsconfig.typecheck-core.json","uninstall:full":"node scripts/uninstall.mjs --full","coverage:report":"c8 report --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov","coverage:summary":"node scripts/test-report-summary.mjs --input coverage/coverage-summary.json --output coverage/coverage-report.md --threshold 60","test:integration":"node --import tsx/esm --test tests/integration/*.test.ts","check:node-runtime":"node --import tsx/esm scripts/check-supported-node-runtime.ts","electron:build:mac":"npm run build && cd electron && npm run build:mac","electron:build:win":"npm run build && cd electron && npm run build:win","test:protocols:e2e":"node scripts/run-protocol-clients-tests.mjs","check:pack-artifact":"node --import tsx/esm scripts/validate-pack-artifact.ts","backfill-aggregation":"node --import tsx/esm src/scripts/backfillAggregation.ts","check:any-budget:t11":"node scripts/check-t11-any-budget.mjs","check:pr-test-policy":"node scripts/check-pr-test-policy.mjs","electron:build:linux":"npm run build && cd electron && npm run build:linux","test:coverage:legacy":"c8 --output-dir coverage --exclude=open-sse --check-coverage --lines 50 --functions 50 --branches 50 node --import tsx/esm --test tests/unit/*.test.ts","coverage:report:legacy":"c8 report --output-dir coverage --exclude=open-sse --reporter=text --reporter=text-summary","electron:smoke:packaged":"node scripts/smoke-electron-packaged.mjs","typecheck:noimplicit:core":"tsc --pretty false -p tsconfig.typecheck-noimplicit-core.json","check:route-validation:t06":"node scripts/check-route-validation.mjs"},"_npmUser":{"name":"ahwanulm","email":"ahwancules@gmail.com"},"overrides":{"hono":"^4.12.18","react":"$react","postcss":"^8.5.10","dompurify":"^3.4.2","lodash-es":"^4.18.1","react-dom":"$react-dom","ip-address":"10.1.1","path-to-regexp":"^8.4.0","@hono/node-server":"^1.19.13"},"repository":{"url":"git+https://github.com/ahwanulm/AmRouter.git","type":"git"},"workspaces":["open-sse"],"_npmVersion":"10.9.7","description":"Unified AI router with 160+ providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.","directories":{},"lint-staged":{"*.{js,jsx,ts,tsx,mjs}":["prettier --write","eslint --fix --no-error-on-unmatched-pattern"],"*.{json,md,yml,yaml,css}":["prettier --write"]},"_nodeVersion":"22.22.2","dependencies":{"ora":"^9.4.0","tsx":"^4.21.0","zod":"^4.4.3","jose":"^6.2.3","next":"^16.2.6","open":"^11.0.0","pino":"^10.3.1","uuid":"^14.0.0","yazl":"^3.3.1","axios":"^1.16.0","lowdb":"^7.0.1","react":"19.2.6","marked":"^18.0.3","undici":"^8.2.0","express":"^5.2.1","fuse.js":"^7.3.0","ioredis":"^5.10.1","js-yaml":"^4.1.1","mermaid":"^11.14.0","wreq-js":"^2.3.0","zustand":"^5.0.13","bcryptjs":"^3.0.3","react-is":"^19.2.6","recharts":"^3.8.1","next-intl":"^4.11.1","react-dom":"19.2.6","bottleneck":"^2.19.5","selfsigned":"^5.5.0","fetch-socks":"^1.3.3","pino-pretty":"^13.1.3","xxhash-wasm":"^1.1.0","@ngrok/ngrok":"^1.7.0","@swc/helpers":"0.5.21","jsonc-parser":"^3.3.1","lucide-react":"^0.460.0","monaco-editor":"^0.55.1","@lobehub/icons":"^5.8.0","better-sqlite3":"^12.9.0","react-markdown":"^10.1.0","node-machine-id":"^1.1.12","tls-client-node":"^0.1.13","https-proxy-agent":"^9.0.0","@monaco-editor/react":"^4.7.0","isomorphic-dompurify":"^3.12.0","http-proxy-middleware":"^3.0.5","pino-abstract-transport":"^3.0.0","@modelcontextprotocol/sdk":"^1.29.0"},"_hasShrinkwrap":false,"devDependencies":{"c8":"^11.0.0","glob":"^13.0.6","husky":"^9.1.7","jsdom":"^29.1.1","eslint":"^9.39.4","vitest":"^4.1.5","wait-on":"^9.0.5","wtfnode":"^0.10.1","prettier":"^3.8.3","cross-env":"^10.1.0","typescript":"^6.0.3","@types/node":"^25.6.2","gray-matter":"^4.0.3","lint-staged":"^16.4.0","node-loader":"^2.1.0","tailwindcss":"^4.3.0","@types/react":"^19.2.14","concurrently":"^9.2.1","@types/keytar":"^4.4.2","@types/bcryptjs":"^3.0.0","@playwright/test":"^1.59.1","@types/react-dom":"^19.2.3","typescript-eslint":"^8.59.2","eslint-config-next":"16.2.6","@tailwindcss/postcss":"^4.3.0","@vitejs/plugin-react":"^6.0.1","@types/better-sqlite3":"^7.6.13","@testing-library/react":"^16.3.2","@testing-library/jest-dom":"^6.9.1"},"optionalDependencies":{"keytar":"^7.9.0"},"_npmOperationalInternal":{"tmp":"tmp/amrouter_1.2.1_1778691872244_0.9724996567667197","host":"s3://npm-registry-packages-npm-production"}},"1.2.2":{"name":"@ahwanulm/amrouter","version":"1.2.2","keywords":["ai","router","proxy","openai","claude","anthropic","gemini","fallback","cursor","cline","codex","llm","auto-fallback"],"author":{"name":"ahwanulm"},"license":"MIT","_id":"@ahwanulm/amrouter@1.2.2","maintainers":[{"name":"ahwanulm","email":"ahwancules@gmail.com"}],"homepage":"https://amrouter.com","bugs":{"url":"https://github.com/ahwanulm/AmRouter/issues"},"bin":{"amrouter":"bin/amrouter.mjs","amrouter-reset-password":"bin/reset-password.mjs"},"dist":{"shasum":"3a5425773a8384cb290819e249b3dd7579d4a421","tarball":"https://registry.npmjs.org/@ahwanulm/amrouter/-/amrouter-1.2.2.tgz","fileCount":4698,"integrity":"sha512-CNM2K4+jRg0nZabkTDouABjIdqzP2ov8Uapa+htHyfttAC7dDqZ9YneFzbIarEal15rGPwvVVE7oLFeLcxcFwA==","signatures":[{"sig":"MEUCIQCbyXPRqG6QTZcjHV2XFij0Y+2Rx8v1YznCptd1pecnGwIgBe7RIZbI6AnkIvB/x7Kiys4oEVbBx22ojkLPVLvqv4M=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":183720888},"pnpm":{"onlyBuiltDependencies":["@parcel/watcher","@swc/core","better-sqlite3","esbuild","@ahwanulm/amrouter","sharp"]},"type":"module","engines":{"node":">=20.20.2 <21 || >=22.22.2 <23 || >=24.0.0 <27"},"gitHead":"96c5152c49b1ca716de6bfe13dcbb4918b3bb761","scripts":{"dev":"node scripts/run-next.mjs dev","lint":"eslint .","test":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test --test-concurrency=10 tests/unit/*.test.ts","build":"node scripts/build-next-isolated.mjs","check":"npm run lint && npm run test","start":"node scripts/run-next.mjs start","prepare":"husky","env:sync":"node scripts/sync-env.mjs","test:all":"npm run test:unit && npm run test:vitest && npm run test:ecosystem && npm run test:e2e","test:e2e":"node scripts/run-playwright-tests.mjs test tests/e2e/*.spec.ts","build:cli":"node --import tsx/esm scripts/prepublish.ts","test:unit":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test --test-concurrency=10 tests/unit/*.test.ts","uninstall":"node scripts/uninstall.mjs","audit:deps":"npm audit --audit-level=moderate && npm run audit:electron","test:fixes":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/fixes-p1.test.ts","test:plan3":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/plan3-p0.test.ts","postinstall":"node scripts/postinstall.mjs","system-info":"node scripts/system-info.mjs","test:vitest":"vitest run --config vitest.mcp.config.ts","check:cycles":"node scripts/check-cycles.mjs","electron:dev":"concurrently \"npm run dev\" \"wait-on http://localhost:20128 && cd electron && npm run dev\"","test:coverage":"c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 60 --lines 60 --functions 60 --branches 60 node --import tsx/esm --test --test-concurrency=10 tests/unit/*.test.ts","test:security":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/security-fase01.test.ts","audit:electron":"npm --prefix electron audit --audit-level=moderate","electron:build":"npm run build && cd electron && npm run build","prepublishOnly":"npm run build:cli && npm run check:pack-artifact","test:ecosystem":"node scripts/run-ecosystem-tests.mjs","typecheck:core":"tsc --pretty false -p tsconfig.typecheck-core.json","uninstall:full":"node scripts/uninstall.mjs --full","coverage:report":"c8 report --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov","coverage:summary":"node scripts/test-report-summary.mjs --input coverage/coverage-summary.json --output coverage/coverage-report.md --threshold 60","test:integration":"node --import tsx/esm --test tests/integration/*.test.ts","check:node-runtime":"node --import tsx/esm scripts/check-supported-node-runtime.ts","electron:build:mac":"npm run build && cd electron && npm run build:mac","electron:build:win":"npm run build && cd electron && npm run build:win","test:protocols:e2e":"node scripts/run-protocol-clients-tests.mjs","check:pack-artifact":"node --import tsx/esm scripts/validate-pack-artifact.ts","backfill-aggregation":"node --import tsx/esm src/scripts/backfillAggregation.ts","check:any-budget:t11":"node scripts/check-t11-any-budget.mjs","check:pr-test-policy":"node scripts/check-pr-test-policy.mjs","electron:build:linux":"npm run build && cd electron && npm run build:linux","test:coverage:legacy":"c8 --output-dir coverage --exclude=open-sse --check-coverage --lines 50 --functions 50 --branches 50 node --import tsx/esm --test tests/unit/*.test.ts","coverage:report:legacy":"c8 report --output-dir coverage --exclude=open-sse --reporter=text --reporter=text-summary","electron:smoke:packaged":"node scripts/smoke-electron-packaged.mjs","typecheck:noimplicit:core":"tsc --pretty false -p tsconfig.typecheck-noimplicit-core.json","check:route-validation:t06":"node scripts/check-route-validation.mjs"},"_npmUser":{"name":"ahwanulm","email":"ahwancules@gmail.com"},"overrides":{"hono":"^4.12.18","react":"$react","postcss":"^8.5.10","dompurify":"^3.4.2","lodash-es":"^4.18.1","react-dom":"$react-dom","ip-address":"10.1.1","path-to-regexp":"^8.4.0","@hono/node-server":"^1.19.13"},"repository":{"url":"git+https://github.com/ahwanulm/AmRouter.git","type":"git"},"workspaces":["open-sse"],"_npmVersion":"10.9.7","description":"Unified AI router with 160+ providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.","directories":{},"lint-staged":{"*.{js,jsx,ts,tsx,mjs}":["prettier --write","eslint --fix --no-error-on-unmatched-pattern"],"*.{json,md,yml,yaml,css}":["prettier --write"]},"_nodeVersion":"22.22.2","dependencies":{"ora":"^9.4.0","tsx":"^4.21.0","zod":"^4.4.3","jose":"^6.2.3","next":"^16.2.6","open":"^11.0.0","pino":"^10.3.1","uuid":"^14.0.0","yazl":"^3.3.1","axios":"^1.16.0","lowdb":"^7.0.1","react":"19.2.6","marked":"^18.0.3","undici":"^8.2.0","express":"^5.2.1","fuse.js":"^7.3.0","ioredis":"^5.10.1","js-yaml":"^4.1.1","mermaid":"^11.14.0","wreq-js":"^2.3.0","zustand":"^5.0.13","bcryptjs":"^3.0.3","react-is":"^19.2.6","recharts":"^3.8.1","next-intl":"^4.11.1","react-dom":"19.2.6","bottleneck":"^2.19.5","selfsigned":"^5.5.0","fetch-socks":"^1.3.3","pino-pretty":"^13.1.3","xxhash-wasm":"^1.1.0","@ngrok/ngrok":"^1.7.0","@swc/helpers":"0.5.21","jsonc-parser":"^3.3.1","lucide-react":"^0.460.0","monaco-editor":"^0.55.1","@lobehub/icons":"^5.8.0","better-sqlite3":"^12.9.0","react-markdown":"^10.1.0","node-machine-id":"^1.1.12","tls-client-node":"^0.1.13","https-proxy-agent":"^9.0.0","@monaco-editor/react":"^4.7.0","isomorphic-dompurify":"^3.12.0","http-proxy-middleware":"^3.0.5","pino-abstract-transport":"^3.0.0","@modelcontextprotocol/sdk":"^1.29.0"},"_hasShrinkwrap":false,"devDependencies":{"c8":"^11.0.0","glob":"^13.0.6","husky":"^9.1.7","jsdom":"^29.1.1","eslint":"^9.39.4","vitest":"^4.1.5","wait-on":"^9.0.5","wtfnode":"^0.10.1","prettier":"^3.8.3","cross-env":"^10.1.0","typescript":"^6.0.3","@types/node":"^25.6.2","gray-matter":"^4.0.3","lint-staged":"^16.4.0","node-loader":"^2.1.0","tailwindcss":"^4.3.0","@types/react":"^19.2.14","concurrently":"^9.2.1","@types/keytar":"^4.4.2","@types/bcryptjs":"^3.0.0","@playwright/test":"^1.59.1","@types/react-dom":"^19.2.3","typescript-eslint":"^8.59.2","eslint-config-next":"16.2.6","@tailwindcss/postcss":"^4.3.0","@vitejs/plugin-react":"^6.0.1","@types/better-sqlite3":"^7.6.13","@testing-library/react":"^16.3.2","@testing-library/jest-dom":"^6.9.1"},"optionalDependencies":{"keytar":"^7.9.0"},"_npmOperationalInternal":{"tmp":"tmp/amrouter_1.2.2_1778693525151_0.7627522499777701","host":"s3://npm-registry-packages-npm-production"}},"1.2.3":{"name":"@ahwanulm/amrouter","version":"1.2.3","description":"Unified AI router with 160+ providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.","type":"module","bin":{"amrouter":"bin/amrouter.mjs","amrouter-reset-password":"bin/reset-password.mjs"},"workspaces":["open-sse"],"engines":{"node":">=20.20.2 <21 || >=22.22.2 <23 || >=24.0.0 <27"},"keywords":["ai","router","proxy","openai","claude","anthropic","gemini","fallback","cursor","cline","codex","llm","auto-fallback"],"license":"MIT","author":{"name":"ahwanulm"},"repository":{"type":"git","url":"git+https://github.com/ahwanulm/AmRouter.git"},"homepage":"https://amrouter.com","scripts":{"dev":"node scripts/run-next.mjs dev","build":"node scripts/build-next-isolated.mjs","build:cli":"node --import tsx/esm scripts/prepublish.ts","start":"node scripts/run-next.mjs start","lint":"eslint .","electron:dev":"concurrently \"npm run dev\" \"wait-on http://localhost:20128 && cd electron && npm run dev\"","electron:build":"npm run build && cd electron && npm run build","electron:build:win":"npm run build && cd electron && npm run build:win","electron:build:mac":"npm run build && cd electron && npm run build:mac","electron:build:linux":"npm run build && cd electron && npm run build:linux","electron:smoke:packaged":"node scripts/smoke-electron-packaged.mjs","test":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test --test-concurrency=10 tests/unit/*.test.ts","test:unit":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test --test-concurrency=10 tests/unit/*.test.ts","test:plan3":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/plan3-p0.test.ts","test:fixes":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/fixes-p1.test.ts","test:security":"cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --test tests/unit/security-fase01.test.ts","check:cycles":"node scripts/check-cycles.mjs","check:route-validation:t06":"node scripts/check-route-validation.mjs","check:any-budget:t11":"node scripts/check-t11-any-budget.mjs","check:node-runtime":"node --import tsx/esm scripts/check-supported-node-runtime.ts","check:pack-artifact":"node --import tsx/esm scripts/validate-pack-artifact.ts","audit:deps":"npm audit --audit-level=moderate && npm run audit:electron","audit:electron":"npm --prefix electron audit --audit-level=moderate","typecheck:core":"tsc --pretty false -p tsconfig.typecheck-core.json","typecheck:noimplicit:core":"tsc --pretty false -p tsconfig.typecheck-noimplicit-core.json","backfill-aggregation":"node --import tsx/esm src/scripts/backfillAggregation.ts","env:sync":"node scripts/sync-env.mjs","test:integration":"node --import tsx/esm --test tests/integration/*.test.ts","test:e2e":"node scripts/run-playwright-tests.mjs test tests/e2e/*.spec.ts","test:protocols:e2e":"node scripts/run-protocol-clients-tests.mjs","test:vitest":"vitest run --config vitest.mcp.config.ts","test:ecosystem":"node scripts/run-ecosystem-tests.mjs","test:coverage":"c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 60 --lines 60 --functions 60 --branches 60 node --import tsx/esm --test --test-concurrency=10 tests/unit/*.test.ts","test:coverage:legacy":"c8 --output-dir coverage --exclude=open-sse --check-coverage --lines 50 --functions 50 --branches 50 node --import tsx/esm --test tests/unit/*.test.ts","coverage:report":"c8 report --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov","coverage:summary":"node scripts/test-report-summary.mjs --input coverage/coverage-summary.json --output coverage/coverage-report.md --threshold 60","check:pr-test-policy":"node scripts/check-pr-test-policy.mjs","coverage:report:legacy":"c8 report --output-dir coverage --exclude=open-sse --reporter=text --reporter=text-summary","test:all":"npm run test:unit && npm run test:vitest && npm run test:ecosystem && npm run test:e2e","check":"npm run lint && npm run test","prepublishOnly":"npm run build:cli && npm run check:pack-artifact","postinstall":"node scripts/postinstall.mjs","uninstall":"node scripts/uninstall.mjs","uninstall:full":"node scripts/uninstall.mjs --full","prepare":"husky","system-info":"node scripts/system-info.mjs"},"dependencies":{"@lobehub/icons":"^5.8.0","@modelcontextprotocol/sdk":"^1.29.0","@monaco-editor/react":"^4.7.0","@ngrok/ngrok":"^1.7.0","@swc/helpers":"0.5.21","axios":"^1.16.0","bcryptjs":"^3.0.3","better-sqlite3":"^12.9.0","bottleneck":"^2.19.5","express":"^5.2.1","fetch-socks":"^1.3.3","fuse.js":"^7.3.0","http-proxy-middleware":"^3.0.5","https-proxy-agent":"^9.0.0","ioredis":"^5.10.1","isomorphic-dompurify":"^3.12.0","jose":"^6.2.3","js-yaml":"^4.1.1","jsonc-parser":"^3.3.1","lowdb":"^7.0.1","lucide-react":"^0.460.0","marked":"^18.0.3","mermaid":"^11.14.0","monaco-editor":"^0.55.1","next":"^16.2.6","next-intl":"^4.11.1","node-machine-id":"^1.1.12","open":"^11.0.0","ora":"^9.4.0","pino":"^10.3.1","pino-abstract-transport":"^3.0.0","pino-pretty":"^13.1.3","react":"19.2.6","react-dom":"19.2.6","react-is":"^19.2.6","react-markdown":"^10.1.0","recharts":"^3.8.1","selfsigned":"^5.5.0","tls-client-node":"^0.1.13","tsx":"^4.21.0","undici":"^8.2.0","uuid":"^14.0.0","wreq-js":"^2.3.0","xxhash-wasm":"^1.1.0","yazl":"^3.3.1","zod":"^4.4.3","zustand":"^5.0.13"},"optionalDependencies":{"keytar":"^7.9.0"},"devDependencies":{"@playwright/test":"^1.59.1","@tailwindcss/postcss":"^4.3.0","@testing-library/jest-dom":"^6.9.1","@testing-library/react":"^16.3.2","@types/bcryptjs":"^3.0.0","@types/better-sqlite3":"^7.6.13","@types/keytar":"^4.4.2","@types/node":"^25.6.2","@types/react":"^19.2.14","@types/react-dom":"^19.2.3","@vitejs/plugin-react":"^6.0.1","c8":"^11.0.0","concurrently":"^9.2.1","cross-env":"^10.1.0","eslint":"^9.39.4","eslint-config-next":"16.2.6","glob":"^13.0.6","gray-matter":"^4.0.3","husky":"^9.1.7","jsdom":"^29.1.1","lint-staged":"^16.4.0","node-loader":"^2.1.0","prettier":"^3.8.3","tailwindcss":"^4.3.0","typescript":"^6.0.3","typescript-eslint":"^8.59.2","vitest":"^4.1.5","wait-on":"^9.0.5","wtfnode":"^0.10.1"},"lint-staged":{"*.{js,jsx,ts,tsx,mjs}":["prettier --write","eslint --fix --no-error-on-unmatched-pattern"],"*.{json,md,yml,yaml,css}":["prettier --write"]},"pnpm":{"onlyBuiltDependencies":["@parcel/watcher","@swc/core","better-sqlite3","esbuild","@ahwanulm/amrouter","sharp"]},"overrides":{"lodash-es":"^4.18.1","dompurify":"^3.4.2","path-to-regexp":"^8.4.0","postcss":"^8.5.10","ip-address":"10.1.1","hono":"^4.12.18","@hono/node-server":"^1.19.13","react":"$react","react-dom":"$react-dom"},"_id":"@ahwanulm/amrouter@1.2.3","gitHead":"9eb7f041f30e9b81b209748f7a3dd5f2abc863fc","bugs":{"url":"https://github.com/ahwanulm/AmRouter/issues"},"_nodeVersion":"22.22.2","_npmVersion":"10.9.7","dist":{"integrity":"sha512-8vujBTEQMIsrJmQz7ZmhYeQrUcBc/aP5wPEaKEeC/ZxHlJRi0MPW3D6kKmTM82utgiS/5lCQJflJyyF9VND7uw==","shasum":"2d52cab899b83d640a1718712919219bb0962ff4","tarball":"https://registry.npmjs.org/@ahwanulm/amrouter/-/amrouter-1.2.3.tgz","fileCount":4698,"unpackedSize":183723540,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQC/LUOBYpFmj0K7Iudw/1TWyYtRw3Mphy80MVZemcMkzQIgb0ImReCYwFRLLPBoHPkupBP8N6yualtMRycYsVBJ0iY="}]},"_npmUser":{"name":"ahwanulm","email":"ahwancules@gmail.com"},"directories":{},"maintainers":[{"name":"ahwanulm","email":"ahwancules@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/amrouter_1.2.3_1778695292574_0.4259577466509201"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-13T16:22:10.373Z","modified":"2026-05-13T18:01:34.420Z","1.2.0":"2026-05-13T16:22:12.331Z","1.2.1":"2026-05-13T17:04:38.455Z","1.2.2":"2026-05-13T17:32:06.843Z","1.2.3":"2026-05-13T18:01:34.309Z"},"bugs":{"url":"https://github.com/ahwanulm/AmRouter/issues"},"author":{"name":"ahwanulm"},"license":"MIT","homepage":"https://amrouter.com","keywords":["ai","router","proxy","openai","claude","anthropic","gemini","fallback","cursor","cline","codex","llm","auto-fallback"],"repository":{"type":"git","url":"git+https://github.com/ahwanulm/AmRouter.git"},"description":"Unified AI router with 160+ providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.","maintainers":[{"name":"ahwanulm","email":"ahwancules@gmail.com"}],"readme":"<div align=\"center\">\n\n# AmRouter\n\n**Unified AI Router — One endpoint, 160+ providers, automatic fallback.**\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)\n[![Node](https://img.shields.io/badge/node-%E2%89%A520.20.2-brightgreen)](https://nodejs.org)\n[![Next.js](https://img.shields.io/badge/Next.js-16-black)](https://nextjs.org)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.9+-blue)](https://www.typescriptlang.org)\n[![Coverage](https://img.shields.io/badge/coverage-%E2%89%A560%25-green)](#testing)\n\n[Website](https://amrouter.com) · [Documentation](./docs) · [Contributing](./CONTRIBUTING.md) · [Changelog](./CHANGELOG.md) · [Security](./SECURITY.md)\n\n</div>\n\n---\n\n## Overview\n\n**AmRouter** is a self-hosted AI gateway that unifies access to 160+ LLM providers (OpenAI, Anthropic, Google, Groq, Mistral, local models, and more) behind a single OpenAI-compatible API. It ships with intelligent routing, automatic failover, request caching, rate limiting, and a full-featured management dashboard — all in a single Next.js application.\n\nPoint your existing SDKs at AmRouter and stop worrying about which provider is down, rate-limited, or most cost-effective for the current request.\n\n> **Acknowledgment.** AmRouter builds on the foundations of the earlier OmniRoute project. It is a refinement — consolidating the core routing engine, hardening the resilience model, and extending provider coverage, MCP/A2A tooling, and the management dashboard. Credit to the original OmniRoute authors for the groundwork that made this possible.\n\n## Key Features\n\n- **Drop-in OpenAI compatibility** — works with the OpenAI SDK, Cursor, Cline, Codex, and any OpenAI-compatible client\n- **Multi-protocol translation** — bidirectional conversion between OpenAI, Anthropic Claude, and Google Gemini request/response formats\n- **Responses API transformer** — seamlessly converts between the Responses API and Chat Completions formats\n- **13 routing strategies** — priority, weighted, round-robin, P2C, cost-optimized, context-optimized, LKGP, auto, and more\n- **Resilience runtime** — per-provider circuit breakers, per-connection cooldowns, and per-model lockouts with lazy recovery\n- **RTK + Caveman compression** — aggressive payload compression for faster responses and lower egress\n- **MCP server built in** — 29 tools across 10 scopes, served over 3 transports (stdio, HTTP, SSE)\n- **A2A agent protocol** — JSON-RPC 2.0 agent-to-agent server with a pluggable skill registry\n- **Persistent memory** — conversational memory layer you can reuse across sessions\n- **Management dashboard** — usage analytics, key management, provider health, request inspector, and live logs\n- **OAuth token refresh** — background scheduler keeps OAuth-based providers authenticated automatically\n- **Desktop app** — Electron build targets for Windows, macOS, and Linux\n- **Secure by default** — AES-256-GCM credential encryption, Zod-validated inputs, strict upstream header denylist\n\n## Quick Start\n\n### Prerequisites\n\n- Node.js `>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24 <27`\n- npm 10+\n\n### Install from npm\n\n```bash\nnpm install -g @ahwanulm/amrouter\n```\n\n### Run\n\n```bash\namrouter                         # start the server (opens the dashboard automatically)\namrouter --no-open               # start without opening the browser\namrouter --headless               # run quietly in the background (no interactive menu)\namrouter --port 20128             # use a custom port\namrouter --verbose                # show full server logs\n```\n\nThen open [http://localhost:20128](http://localhost:20128) and sign in with the initial password printed in your terminal on first boot.\n\n### Everyday commands\n\n```bash\namrouter doctor                   # quick health check\namrouter providers available      # list supported providers\namrouter providers list           # list configured providers\namrouter setup                    # interactive guided setup\namrouter update                   # check npm for a newer version and install it\namrouter uninstall --force        # remove the local data directory (npm uninstall step is printed next)\namrouter-reset-password           # reset the admin password\namrouter --help                   # full help\namrouter --version                # show installed version\n```\n\n### Configuration\n\nAmRouter reads environment variables from `~/.amrouter/.env` (auto-created from `.env.example` on first run). For production, generate strong secrets before first boot:\n\n```bash\nopenssl rand -base64 48           # JWT_SECRET\nopenssl rand -hex 32              # API_KEY_SECRET\n```\n\nAdd them to `~/.amrouter/.env`, then start `amrouter`.\n\n## Usage Example\n\nAmRouter exposes an OpenAI-compatible endpoint. Any OpenAI SDK works unchanged — just swap the base URL.\n\n```ts\nimport OpenAI from \"openai\";\n\nconst client = new OpenAI({\n  baseURL: \"http://localhost:20128/v1\",\n  apiKey: \"amr_your_key_here\",\n});\n\nconst response = await client.chat.completions.create({\n  model: \"gpt-4o-mini\", // or a combo id, e.g. \"combo:my-fallback\"\n  messages: [{ role: \"user\", content: \"Hello!\" }],\n  stream: true,\n});\n\nfor await (const chunk of response) {\n  process.stdout.write(chunk.choices[0]?.delta?.content ?? \"\");\n}\n```\n\n## Architecture\n\n```\nClient\n  │\n  ▼\n/v1/chat/completions  ─ CORS ─ Zod ─ Auth ─ Policy ─ Injection guard\n  │\n  ▼\nhandleChatCore()  ─ cache ─ rate-limit ─ combo routing?\n  │\n  ▼\nresolveComboTargets()  →  handleSingleModel() per target\n  │\n  ▼\ntranslateRequest()  →  getExecutor()  →  executor.execute()\n  │                                            │\n  │                                            ▼\n  │                                        fetch() upstream + retry/backoff\n  ▼\nresponse translation  →  SSE stream or JSON\n  │\n  ▼\nResponses API transformer (when applicable)\n```\n\n| Layer         | Location                | Purpose                                    |\n| ------------- | ----------------------- | ------------------------------------------ |\n| API routes    | `src/app/api/v1/`       | Next.js App Router entry points            |\n| Handlers      | `open-sse/handlers/`    | Request processing (chat, embeddings, etc) |\n| Executors     | `open-sse/executors/`   | Provider-specific HTTP dispatch            |\n| Translators   | `open-sse/translator/`  | OpenAI ↔ Claude ↔ Gemini format conversion |\n| Transformer   | `open-sse/transformer/` | Responses API ↔ Chat Completions           |\n| Services      | `open-sse/services/`    | Combo routing, rate limits, caching        |\n| Database      | `src/lib/db/`           | 22 SQLite domain modules (WAL)             |\n| Domain/Policy | `src/domain/`           | Policy engine, cost rules, fallback logic  |\n| MCP server    | `open-sse/mcp-server/`  | 29 tools, 3 transports, 10 scopes          |\n| A2A server    | `src/lib/a2a/`          | JSON-RPC 2.0 agent protocol                |\n| Skills        | `src/lib/skills/`       | Extensible skill framework                 |\n| Memory        | `src/lib/memory/`       | Persistent conversational memory           |\n\n**Monorepo layout**: `src/` (Next.js 16 app), `open-sse/` (streaming engine workspace), `electron/` (desktop), `tests/`, `bin/` (CLI).\n\nFor a deep architectural tour, see [`AGENTS.md`](./AGENTS.md) and [`CLAUDE.md`](./CLAUDE.md).\n\n## Resilience Model\n\nAmRouter has three independent failure-isolation layers so one bad key or one bad model never takes down a whole provider:\n\n| Mechanism               | Scope                          | Example trigger           |\n| ----------------------- | ------------------------------ | ------------------------- |\n| **Circuit breaker**     | Whole provider (e.g. `openai`) | Repeated 5xx / timeout    |\n| **Connection cooldown** | One account / API key          | Single key hits 429       |\n| **Model lockout**       | Provider + connection + model  | Per-model quota exhausted |\n\nAll three use lazy recovery — expired states self-refresh on read, so dashboards and combo candidate builders always reflect reality.\n\n## Scripts\n\n| Command                  | What it does                                   |\n| ------------------------ | ---------------------------------------------- |\n| `npm run dev`            | Dev server at `http://localhost:20128`         |\n| `npm run build`          | Production build (Next.js standalone)          |\n| `npm run start`          | Start the production server                    |\n| `npm run lint`           | ESLint across the repo                         |\n| `npm run typecheck:core` | TypeScript check                               |\n| `npm run test`           | Unit tests (Node.js native runner)             |\n| `npm run test:coverage`  | Unit tests with 60% coverage gate              |\n| `npm run test:vitest`    | MCP / autoCombo / cache suites                 |\n| `npm run test:e2e`       | Playwright end-to-end tests                    |\n| `npm run test:all`       | Full test matrix                               |\n| `npm run check`          | `lint` + `test`                                |\n| `npm run check:cycles`   | Detect circular dependencies                   |\n| `npm run electron:dev`   | Run dev server + Electron shell                |\n| `npm run electron:build` | Build the desktop app for the current platform |\n\n## Testing\n\n| Suite                    | Command                                                  |\n| ------------------------ | -------------------------------------------------------- |\n| Unit (all)               | `npm run test:unit`                                      |\n| Single file              | `node --import tsx/esm --test tests/unit/<file>.test.ts` |\n| Vitest (MCP, autoCombo)  | `npm run test:vitest`                                    |\n| Integration              | `npm run test:integration`                               |\n| E2E (Playwright)         | `npm run test:e2e`                                       |\n| Protocol E2E (MCP + A2A) | `npm run test:protocols:e2e`                             |\n| Ecosystem                | `npm run test:ecosystem`                                 |\n| Coverage gate (≥60%)     | `npm run test:coverage`                                  |\n\nAny PR changing production code in `src/`, `open-sse/`, `electron/`, or `bin/` must include or update tests.\n\n## Configuration\n\nCore environment variables (see `.env.example` for the full list):\n\n| Variable           | Description                                     |\n| ------------------ | ----------------------------------------------- |\n| `PORT`             | Server port (default `20128`)                   |\n| `DATA_DIR`         | Data + SQLite location (default `~/.amrouter/`) |\n| `JWT_SECRET`       | Dashboard session signing secret                |\n| `API_KEY_SECRET`   | AES-256-GCM key for credential encryption       |\n| `INITIAL_PASSWORD` | First-run admin password                        |\n| `REQUIRE_API_KEY`  | Require clients to pass an AmRouter key         |\n| `APP_LOG_LEVEL`    | `trace` · `debug` · `info` · `warn` · `error`   |\n\n## Deployment\n\n- **Docker** — `docker compose up -d` (see `docker-compose.yml` and `docker-compose.prod.yml`)\n- **Fly.io** — ready-made `fly.toml` included\n- **Standalone Node** — `npm run build && npm run start`\n- **Desktop (Electron)** — `npm run electron:build:{win,mac,linux}`\n\n## Protocols\n\n- **OpenAI-compatible REST** — Chat Completions, Responses, Embeddings, Models, Files\n- **Anthropic Messages** — native `/v1/messages` surface with translation\n- **Gemini `generateContent`** — accepted and translated\n- **MCP (Model Context Protocol)** — 29 tools across 10 scopes, over stdio / HTTP / SSE\n- **A2A (Agent-to-Agent)** — JSON-RPC 2.0 with a DB-backed skill registry\n\n## Security\n\n- No `eval()` / `new Function()` / implied eval — enforced by ESLint\n- All inputs validated with Zod schemas\n- Credentials encrypted at rest (AES-256-GCM)\n- Strict upstream header denylist (`src/shared/constants/upstreamHeaders.ts`)\n- OAuth tokens refreshed by a background scheduler\n\nFound a vulnerability? See [`SECURITY.md`](./SECURITY.md).\n\n## Contributing\n\nWe welcome pull requests. Please read [`CONTRIBUTING.md`](./CONTRIBUTING.md) and [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md) first.\n\nBranch naming: `feat/…`, `fix/…`, `refactor/…`, `docs/…`, `test/…`, `chore/…`\nCommit style: [Conventional Commits](https://www.conventionalcommits.org/) — e.g. `feat(db): add circuit breaker`\nHusky enforces lint-staged, doc-sync, and an `any` budget on pre-commit, plus unit tests on pre-push.\n\n## License\n\n[MIT](./LICENSE) © 2026 diegosouzapw, ahwanulm\n","readmeFilename":"README.md"}