agent-relay 2.0.16 → 2.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deploy/workspace/entrypoint.sh +35 -19
- package/deploy/workspace/git-credential-relay +82 -7
- package/dist/dashboard/out/404.html +1 -1
- package/dist/dashboard/out/_next/static/chunks/320-402ffc8646b31da1.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/83-26d2bde54616ee90.js +1 -0
- package/{packages/dashboard/ui-dist/_next/static/chunks/app/app/page-9d6bc8729b429956.js → dist/dashboard/out/_next/static/chunks/app/app/page-366fb7c078d4e9e0.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/complete-profile/page-dd64bbdf66b639cd.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/login/page-435eceb0073be027.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/{page-487fa38f041815c1.js → page-8119d4246743574e.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/signup/page-c7a0a28341365ae0.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/{main-5a40a5ae29646e1b.js → main-311c3db74dcfadb7.js} +1 -1
- package/dist/dashboard/out/_next/static/css/{605dd4e30c91986f.css → 45361ce86b2847c4.css} +1 -1
- package/dist/dashboard/out/app/onboarding.html +1 -1
- package/dist/dashboard/out/app/onboarding.txt +1 -1
- package/dist/dashboard/out/app.html +1 -1
- package/dist/dashboard/out/app.txt +2 -2
- package/dist/dashboard/out/cloud/link.html +1 -1
- package/dist/dashboard/out/cloud/link.txt +1 -1
- package/dist/dashboard/out/complete-profile.html +5 -0
- package/dist/dashboard/out/complete-profile.txt +7 -0
- package/dist/dashboard/out/connect-repos.html +1 -1
- package/dist/dashboard/out/connect-repos.txt +1 -1
- package/dist/dashboard/out/history.html +1 -1
- package/dist/dashboard/out/history.txt +1 -1
- package/dist/dashboard/out/index.html +1 -1
- package/dist/dashboard/out/index.txt +2 -2
- package/dist/dashboard/out/login.html +2 -2
- package/dist/dashboard/out/login.txt +2 -2
- package/dist/dashboard/out/metrics.html +1 -1
- package/dist/dashboard/out/metrics.txt +1 -1
- package/dist/dashboard/out/pricing.html +2 -2
- package/dist/dashboard/out/pricing.txt +1 -1
- package/dist/dashboard/out/providers/setup/claude.html +1 -1
- package/dist/dashboard/out/providers/setup/claude.txt +1 -1
- package/dist/dashboard/out/providers/setup/codex.html +1 -1
- package/dist/dashboard/out/providers/setup/codex.txt +1 -1
- package/dist/dashboard/out/providers/setup/cursor.html +1 -1
- package/dist/dashboard/out/providers/setup/cursor.txt +1 -1
- package/dist/dashboard/out/providers.html +1 -1
- package/dist/dashboard/out/providers.txt +2 -2
- package/dist/dashboard/out/signup.html +2 -2
- package/dist/dashboard/out/signup.txt +2 -2
- package/dist/src/cli/index.js +3 -1
- package/package.json +22 -21
- package/packages/api-types/package.json +1 -1
- package/packages/bridge/package.json +8 -8
- package/packages/cloud/dist/api/auth.js +2 -0
- package/packages/cloud/dist/api/billing.js +4 -4
- package/packages/cloud/dist/api/email-auth.d.ts +11 -0
- package/packages/cloud/dist/api/email-auth.js +347 -0
- package/packages/cloud/dist/api/nango-auth.js +72 -5
- package/packages/cloud/dist/db/drizzle.d.ts +35 -1
- package/packages/cloud/dist/db/drizzle.js +136 -0
- package/packages/cloud/dist/db/index.d.ts +5 -4
- package/packages/cloud/dist/db/index.js +5 -3
- package/packages/cloud/dist/db/schema.d.ts +246 -2
- package/packages/cloud/dist/db/schema.js +39 -3
- package/packages/cloud/dist/provisioner/index.js +5 -1
- package/packages/cloud/dist/server.js +134 -24
- package/packages/cloud/dist/services/nango.d.ts +18 -0
- package/packages/cloud/dist/services/nango.js +32 -0
- package/packages/cloud/package.json +6 -6
- package/packages/config/package.json +2 -2
- package/packages/continuity/package.json +1 -1
- package/packages/daemon/package.json +12 -12
- package/packages/dashboard/dist/server.js +36 -7
- package/packages/dashboard/package.json +13 -13
- package/packages/dashboard/ui/app/complete-profile/page.tsx +204 -0
- package/packages/dashboard/ui/app/login/page.tsx +182 -38
- package/packages/dashboard/ui/app/signup/page.tsx +244 -54
- package/packages/dashboard/ui/lib/cloudApi.ts +1 -0
- package/packages/dashboard/ui/react-components/App.tsx +1 -1
- package/packages/dashboard/ui/react-components/ProviderAuthFlow.tsx +10 -0
- package/packages/dashboard/ui/react-components/RepoAccessPanel.tsx +160 -3
- package/packages/dashboard/ui-dist/404.html +1 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/320-402ffc8646b31da1.js +1 -0
- package/packages/dashboard/ui-dist/_next/static/chunks/83-26d2bde54616ee90.js +1 -0
- package/{dist/dashboard/out/_next/static/chunks/app/app/page-9d6bc8729b429956.js → packages/dashboard/ui-dist/_next/static/chunks/app/app/page-366fb7c078d4e9e0.js} +1 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/complete-profile/page-dd64bbdf66b639cd.js +1 -0
- package/packages/dashboard/ui-dist/_next/static/chunks/app/login/page-435eceb0073be027.js +1 -0
- package/packages/dashboard/ui-dist/_next/static/chunks/app/{page-487fa38f041815c1.js → page-8119d4246743574e.js} +1 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/signup/page-c7a0a28341365ae0.js +1 -0
- package/packages/dashboard/ui-dist/_next/static/chunks/{main-5a40a5ae29646e1b.js → main-311c3db74dcfadb7.js} +1 -1
- package/packages/dashboard/ui-dist/_next/static/css/{605dd4e30c91986f.css → 45361ce86b2847c4.css} +1 -1
- package/packages/dashboard/ui-dist/app/onboarding.html +1 -1
- package/packages/dashboard/ui-dist/app/onboarding.txt +1 -1
- package/packages/dashboard/ui-dist/app.html +1 -1
- package/packages/dashboard/ui-dist/app.txt +2 -2
- package/packages/dashboard/ui-dist/cloud/link.html +1 -1
- package/packages/dashboard/ui-dist/cloud/link.txt +1 -1
- package/packages/dashboard/ui-dist/complete-profile.html +5 -0
- package/packages/dashboard/ui-dist/complete-profile.txt +7 -0
- package/packages/dashboard/ui-dist/connect-repos.html +1 -1
- package/packages/dashboard/ui-dist/connect-repos.txt +1 -1
- package/packages/dashboard/ui-dist/history.html +1 -1
- package/packages/dashboard/ui-dist/history.txt +1 -1
- package/packages/dashboard/ui-dist/index.html +1 -1
- package/packages/dashboard/ui-dist/index.txt +2 -2
- package/packages/dashboard/ui-dist/login.html +2 -2
- package/packages/dashboard/ui-dist/login.txt +2 -2
- package/packages/dashboard/ui-dist/metrics.html +1 -1
- package/packages/dashboard/ui-dist/metrics.txt +1 -1
- package/packages/dashboard/ui-dist/pricing.html +2 -2
- package/packages/dashboard/ui-dist/pricing.txt +1 -1
- package/packages/dashboard/ui-dist/providers/setup/claude.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/claude.txt +1 -1
- package/packages/dashboard/ui-dist/providers/setup/codex.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/codex.txt +1 -1
- package/packages/dashboard/ui-dist/providers/setup/cursor.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/cursor.txt +1 -1
- package/packages/dashboard/ui-dist/providers.html +1 -1
- package/packages/dashboard/ui-dist/providers.txt +2 -2
- package/packages/dashboard/ui-dist/signup.html +2 -2
- package/packages/dashboard/ui-dist/signup.txt +2 -2
- package/packages/dashboard-server/dist/server.js +36 -7
- package/packages/dashboard-server/package.json +12 -12
- package/packages/hooks/package.json +4 -4
- package/packages/mcp/package.json +2 -2
- package/packages/memory/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/protocol/package.json +1 -1
- package/packages/resiliency/package.json +1 -1
- package/packages/sdk/package.json +2 -2
- package/packages/spawner/package.json +1 -1
- package/packages/state/package.json +1 -1
- package/packages/storage/package.json +2 -2
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +1 -1
- package/packages/wrapper/dist/relay-pty-orchestrator.js +17 -3
- package/packages/wrapper/package.json +6 -6
- package/relay-snippets/agent-policy-snippet.md +40 -0
- package/relay-snippets/agent-relay-protocol.md +101 -0
- package/relay-snippets/agent-relay-snippet.md +177 -0
- package/SESSION_HANDOFF.md +0 -67
- package/dist/dashboard/out/_next/static/chunks/320-23e5ffe6aa7eb934.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/83-4f08122d4e7e79a6.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/login/page-a0ca6f7ca6a100b8.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/signup/page-1ede2205b58649ca.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/320-23e5ffe6aa7eb934.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/83-4f08122d4e7e79a6.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/login/page-a0ca6f7ca6a100b8.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/signup/page-1ede2205b58649ca.js +0 -1
- package/test-push.txt +0 -1
- /package/dist/dashboard/out/_next/static/{itBGQ1M8yMA_hC42DKCqv → JIjqkuDKNeoSg7KaMMuhx}/_buildManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/{itBGQ1M8yMA_hC42DKCqv → JIjqkuDKNeoSg7KaMMuhx}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{ML6Zby1B5OtZvl0Pa1zSZ → JIjqkuDKNeoSg7KaMMuhx}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{ML6Zby1B5OtZvl0Pa1zSZ → JIjqkuDKNeoSg7KaMMuhx}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{Ni5Di0TB0PDcrvEYBFRKd → nmkOi7bqeDmLMoWBih8lz}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{Ni5Di0TB0PDcrvEYBFRKd → nmkOi7bqeDmLMoWBih8lz}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{itBGQ1M8yMA_hC42DKCqv → wk_gKRNSPpWE-ZhGL6UMl}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{itBGQ1M8yMA_hC42DKCqv → wk_gKRNSPpWE-ZhGL6UMl}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/
|
|
1
|
+
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/45361ce86b2847c4.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-1cdd8ed57114d5e1.js"/><script src="/_next/static/chunks/fd9d1056-609918ca7b6280bb.js" async=""></script><script src="/_next/static/chunks/117-c8afed19e821a35d.js" async=""></script><script src="/_next/static/chunks/main-app-fdbeb09028f57c9f.js" async=""></script><script src="/_next/static/chunks/282-980c2eb8fff20123.js" async=""></script><script src="/_next/static/chunks/app/signup/page-c7a0a28341365ae0.js" async=""></script><title>Agent Relay Dashboard</title><meta name="description" content="Fleet control dashboard for Agent Relay"/><link rel="apple-touch-icon" href="/apple-icon.png?9e7a840704165ca6" type="image/png" sizes="256x256"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div class="min-h-screen bg-gradient-to-br from-[#0a0a0f] via-[#0d1117] to-[#0a0a0f] flex flex-col items-center justify-center p-4"><div class="fixed inset-0 opacity-10"><div class="absolute inset-0" style="background-image:linear-gradient(rgba(0, 217, 255, 0.1) 1px, transparent 1px),
|
|
2
2
|
linear-gradient(90deg, rgba(0, 217, 255, 0.1) 1px, transparent 1px);background-size:50px 50px"></div></div><div class="fixed inset-0 overflow-hidden pointer-events-none"><div class="absolute -top-40 -left-40 w-80 h-80 bg-accent-cyan/20 rounded-full blur-[100px]"></div><div class="absolute -bottom-40 -right-40 w-80 h-80 bg-[#00ffc8]/15 rounded-full blur-[100px]"></div></div><div class="relative z-10 w-full max-w-md"><div class="flex flex-col items-center mb-8"><svg width="56" height="56" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg" class="
|
|
3
3
|
transition-all duration-300
|
|
4
4
|
drop-shadow-[0_0_8px_rgba(0,217,255,0.3)]
|
|
5
5
|
|
|
6
|
-
" aria-label="Agent Relay Logo" role="img"><path d="M30 80 L 50 20 L 70 80" stroke="#00d9ff" stroke-width="5" stroke-linejoin="round" stroke-linecap="round" fill="none"></path><line x1="40" y1="50" x2="60" y2="50" stroke="#00d9ff" stroke-width="5" stroke-linecap="round"></line><path d="M50 20 L 50 80" stroke="#00ffc8" stroke-width="2.5" stroke-linecap="round" opacity="0.7"></path><path d="M50 20 C 80 20 80 50 50 50 L 80 80" stroke="#00ffc8" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" fill="none" opacity="0.7"></path></svg><h1 class="mt-4 text-3xl font-bold text-white">Get Started</h1><p class="mt-2 text-text-muted text-center">Create your account and start orchestrating AI agents</p></div><div class="bg-bg-primary/80 backdrop-blur-sm border border-border-subtle rounded-2xl p-8 shadow-xl"><div><div class="mb-6 space-y-3"><div class="flex items-center gap-3 text-sm text-text-secondary"><div class="w-8 h-8 rounded-lg bg-accent-cyan/10 flex items-center justify-center flex-shrink-0"><svg class="w-4 h-4 text-accent-cyan" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg></div><span>Deploy AI agents in seconds</span></div><div class="flex items-center gap-3 text-sm text-text-secondary"><div class="w-8 h-8 rounded-lg bg-[#00ffc8]/10 flex items-center justify-center flex-shrink-0"><svg class="w-4 h-4 text-[#00ffc8]" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path></svg></div><span>Real-time agent collaboration</span></div><div class="flex items-center gap-3 text-sm text-text-secondary"><div class="w-8 h-8 rounded-lg bg-[#0891b2]/10 flex items-center justify-center flex-shrink-0"><svg class="w-4 h-4 text-[#0891b2]" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path></svg></div><span>Secure credential management</span></div></div><button type="button" disabled="" class="w-full py-4 px-6 bg-[#24292e] hover:bg-[#2f363d] border border-[#444d56] rounded-xl text-white font-medium flex items-center justify-center gap-3 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"><svg class="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg><span>Loading...</span></button><p class="mt-6 text-center text-text-muted text-sm">By signing up, you agree to our<!-- --> <a href="/terms" class="text-accent-cyan hover:underline">Terms of Service</a> <!-- -->and<!-- --> <a href="/privacy" class="text-accent-cyan hover:underline">Privacy Policy</a></p></div></div><div class="mt-6 text-center"><p class="text-text-muted">Already have an account?<!-- --> <a href="/login" class="text-accent-cyan hover:underline font-medium">Sign in</a></p></div><div class="mt-4 text-center"><a href="/" class="text-text-muted hover:text-white transition-colors text-sm">Back to home</a></div></div></div><script src="/_next/static/chunks/webpack-1cdd8ed57114d5e1.js" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/_next/static/css/
|
|
6
|
+
" aria-label="Agent Relay Logo" role="img"><path d="M30 80 L 50 20 L 70 80" stroke="#00d9ff" stroke-width="5" stroke-linejoin="round" stroke-linecap="round" fill="none"></path><line x1="40" y1="50" x2="60" y2="50" stroke="#00d9ff" stroke-width="5" stroke-linecap="round"></line><path d="M50 20 L 50 80" stroke="#00ffc8" stroke-width="2.5" stroke-linecap="round" opacity="0.7"></path><path d="M50 20 C 80 20 80 50 50 50 L 80 80" stroke="#00ffc8" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" fill="none" opacity="0.7"></path></svg><h1 class="mt-4 text-3xl font-bold text-white">Get Started</h1><p class="mt-2 text-text-muted text-center">Create your account and start orchestrating AI agents</p></div><div class="bg-bg-primary/80 backdrop-blur-sm border border-border-subtle rounded-2xl p-8 shadow-xl"><div><div class="flex mb-6 bg-bg-secondary/50 rounded-lg p-1"><button type="button" class="flex-1 py-2 px-4 rounded-md text-sm font-medium transition-colors bg-bg-primary text-white shadow-sm">GitHub</button><button type="button" class="flex-1 py-2 px-4 rounded-md text-sm font-medium transition-colors text-text-muted hover:text-white">Email</button></div><div class="mb-6 space-y-3"><div class="flex items-center gap-3 text-sm text-text-secondary"><div class="w-8 h-8 rounded-lg bg-accent-cyan/10 flex items-center justify-center flex-shrink-0"><svg class="w-4 h-4 text-accent-cyan" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg></div><span>Deploy AI agents in seconds</span></div><div class="flex items-center gap-3 text-sm text-text-secondary"><div class="w-8 h-8 rounded-lg bg-[#00ffc8]/10 flex items-center justify-center flex-shrink-0"><svg class="w-4 h-4 text-[#00ffc8]" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path></svg></div><span>Real-time agent collaboration</span></div><div class="flex items-center gap-3 text-sm text-text-secondary"><div class="w-8 h-8 rounded-lg bg-[#0891b2]/10 flex items-center justify-center flex-shrink-0"><svg class="w-4 h-4 text-[#0891b2]" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path></svg></div><span>Secure credential management</span></div></div><button type="button" disabled="" class="w-full py-4 px-6 bg-[#24292e] hover:bg-[#2f363d] border border-[#444d56] rounded-xl text-white font-medium flex items-center justify-center gap-3 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"><svg class="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg><span>Loading...</span></button><p class="mt-6 text-center text-text-muted text-sm">By signing up, you agree to our<!-- --> <a href="/terms" class="text-accent-cyan hover:underline">Terms of Service</a> <!-- -->and<!-- --> <a href="/privacy" class="text-accent-cyan hover:underline">Privacy Policy</a></p></div></div><div class="mt-6 text-center"><p class="text-text-muted">Already have an account?<!-- --> <a href="/login" class="text-accent-cyan hover:underline font-medium">Sign in</a></p></div><div class="mt-4 text-center"><a href="/" class="text-text-muted hover:text-white transition-colors text-sm">Back to home</a></div></div></div><script src="/_next/static/chunks/webpack-1cdd8ed57114d5e1.js" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/_next/static/css/45361ce86b2847c4.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"2:I[2846,[],\"\"]\n4:I[9107,[],\"ClientPageRoot\"]\n5:I[4665,[\"282\",\"static/chunks/282-980c2eb8fff20123.js\",\"966\",\"static/chunks/app/signup/page-c7a0a28341365ae0.js\"],\"default\",1]\n6:I[4707,[],\"\"]\n7:I[6423,[],\"\"]\n9:I[1060,[],\"\"]\na:[]\n"])</script><script>self.__next_f.push([1,"0:[\"$\",\"$L2\",null,{\"buildId\":\"JIjqkuDKNeoSg7KaMMuhx\",\"assetPrefix\":\"\",\"urlParts\":[\"\",\"signup\"],\"initialTree\":[\"\",{\"children\":[\"signup\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"signup\",{\"children\":[\"__PAGE__\",{},[[\"$L3\",[\"$\",\"$L4\",null,{\"props\":{\"params\":{},\"searchParams\":{}},\"Component\":\"$5\"}],null],null],null]},[null,[\"$\",\"$L6\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"signup\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L7\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\"}]],null]},[[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/45361ce86b2847c4.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$\",\"$L6\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L7\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[]}]}]}]],null],null],\"couldBeIntercepted\":false,\"initialHead\":[null,\"$L8\"],\"globalErrorComponent\":\"$9\",\"missingSlots\":\"$Wa\"}]\n"])</script><script>self.__next_f.push([1,"8:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"Agent Relay Dashboard\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"Fleet control dashboard for Agent Relay\"}],[\"$\",\"link\",\"4\",{\"rel\":\"apple-touch-icon\",\"href\":\"/apple-icon.png?9e7a840704165ca6\",\"type\":\"image/png\",\"sizes\":\"256x256\"}]]\n3:null\n"])</script></body></html>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
2:I[9107,[],"ClientPageRoot"]
|
|
2
|
-
3:I[4665,["282","static/chunks/282-980c2eb8fff20123.js","966","static/chunks/app/signup/page-
|
|
2
|
+
3:I[4665,["282","static/chunks/282-980c2eb8fff20123.js","966","static/chunks/app/signup/page-c7a0a28341365ae0.js"],"default",1]
|
|
3
3
|
4:I[4707,[],""]
|
|
4
4
|
5:I[6423,[],""]
|
|
5
|
-
0:["
|
|
5
|
+
0:["JIjqkuDKNeoSg7KaMMuhx",[[["",{"children":["signup",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["signup",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","signup","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/45361ce86b2847c4.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
|
6
6
|
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"Agent Relay Dashboard"}],["$","meta","3",{"name":"description","content":"Fleet control dashboard for Agent Relay"}],["$","link","4",{"rel":"apple-touch-icon","href":"/apple-icon.png?9e7a840704165ca6","type":"image/png","sizes":"256x256"}]]
|
|
7
7
|
1:null
|
package/dist/src/cli/index.js
CHANGED
|
@@ -156,9 +156,11 @@ program
|
|
|
156
156
|
console.error(`Project: ${paths.projectId}`);
|
|
157
157
|
// Auto-install MCP config if not present (project-local)
|
|
158
158
|
// Uses .mcp.json in the project root - doesn't modify global settings
|
|
159
|
+
// Feature gated: set RELAY_MCP_AUTO_INSTALL=1 to enable
|
|
160
|
+
const mcpAutoInstallEnabled = process.env.RELAY_MCP_AUTO_INSTALL === '1';
|
|
159
161
|
const projectMcpConfigPath = path.join(paths.projectRoot, '.mcp.json');
|
|
160
162
|
const socketPath = path.join(paths.projectRoot, '.agent-relay', 'relay.sock');
|
|
161
|
-
if (!fs.existsSync(projectMcpConfigPath)) {
|
|
163
|
+
if (!fs.existsSync(projectMcpConfigPath) && mcpAutoInstallEnabled) {
|
|
162
164
|
try {
|
|
163
165
|
const result = installMcpConfig(projectMcpConfigPath, {
|
|
164
166
|
configKey: 'mcpServers',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-relay",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.18",
|
|
4
4
|
"description": "Real-time agent-to-agent communication system",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"scripts": {
|
|
18
18
|
"postinstall": "npm rebuild better-sqlite3 && node scripts/postinstall.js",
|
|
19
19
|
"build": "npm run clean && npm run build:rust && turbo run build --filter='./packages/*' && tsc && npm run build:dashboard",
|
|
20
|
-
"build:sequential": "npm run clean && npm run build:rust && npm run build:protocol && npm run build:config && npm run build:storage && npm run build:state && npm run build:policy && npm run build:trajectory && npm run build:hooks && npm run build:memory && npm run build:utils && npm run build:continuity && npm run build:resiliency && npm run build:user-directory && npm run build:wrapper && npm run build:bridge && npm run build:telemetry && npm run build:cloud && npm run build:daemon && npm run build:sdk && npm run build:dashboard-server && npm run build:dashboard-pkg && npm run build:api-types && npm run build:spawner &&
|
|
21
|
-
"build:workspace": "npm run clean && npm run build:rust && npm run build:protocol && npm run build:config && npm run build:storage && npm run build:state && npm run build:policy && npm run build:trajectory && npm run build:hooks && npm run build:memory && npm run build:utils && npm run build:continuity && npm run build:resiliency && npm run build:user-directory && npm run build:wrapper && npm run build:bridge && npm run build:telemetry && npm run build:cloud && npm run build:daemon && npm run build:sdk && npm run build:dashboard-server && npm run build:dashboard-pkg && npm run build:api-types && npm run build:spawner &&
|
|
20
|
+
"build:sequential": "npm run clean && npm run build:rust && npm run build:protocol && npm run build:config && npm run build:storage && npm run build:state && npm run build:policy && npm run build:trajectory && npm run build:hooks && npm run build:memory && npm run build:utils && npm run build:continuity && npm run build:resiliency && npm run build:user-directory && npm run build:wrapper && npm run build:mcp && npm run build:bridge && npm run build:telemetry && npm run build:cloud && npm run build:daemon && npm run build:sdk && npm run build:dashboard-server && npm run build:dashboard-pkg && npm run build:api-types && npm run build:spawner && tsc && npm run build:dashboard",
|
|
21
|
+
"build:workspace": "npm run clean && npm run build:rust && npm run build:protocol && npm run build:config && npm run build:storage && npm run build:state && npm run build:policy && npm run build:trajectory && npm run build:hooks && npm run build:memory && npm run build:utils && npm run build:continuity && npm run build:resiliency && npm run build:user-directory && npm run build:wrapper && npm run build:mcp && npm run build:bridge && npm run build:telemetry && npm run build:cloud && npm run build:daemon && npm run build:sdk && npm run build:dashboard-server && npm run build:dashboard-pkg && npm run build:api-types && npm run build:spawner && tsc",
|
|
22
22
|
"build:packages": "turbo run build --filter='./packages/*'",
|
|
23
23
|
"build:packages:watch": "turbo run build --filter='./packages/*' --watch",
|
|
24
24
|
"build:mcp": "cd packages/mcp && npm run build",
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"build:api-types": "cd packages/api-types && npm run build",
|
|
45
45
|
"build:spawner": "cd packages/spawner && npm run build",
|
|
46
46
|
"build:telemetry": "cd packages/telemetry && npm run build",
|
|
47
|
-
"build:dashboard": "cd packages/dashboard/ui && npm run build",
|
|
47
|
+
"build:dashboard": "cd packages/dashboard/ui && npm ci && npm run build",
|
|
48
48
|
"postbuild": "chmod +x dist/src/cli/index.js && mkdir -p dist/dashboard && cp -r packages/dashboard/ui/out dist/dashboard/ && mkdir -p packages/dashboard/ui-dist && cp -r packages/dashboard/ui/out/* packages/dashboard/ui-dist/",
|
|
49
49
|
"dev:watch": "tsc -w",
|
|
50
|
-
"predev": "npm run clean && tsc && chmod +x dist/src/cli/index.js",
|
|
50
|
+
"predev": "npm run clean && npm run build:packages && tsc && chmod +x dist/src/cli/index.js",
|
|
51
51
|
"dev": "concurrently -n daemon,next -c blue,magenta \"npm run dev:daemon\" \"npm run dev:next\"",
|
|
52
52
|
"dev:daemon": "node dist/src/cli/index.js up --dashboard --port 3889",
|
|
53
53
|
"dev:next": "cd packages/dashboard/ui && npm run dev",
|
|
@@ -84,7 +84,8 @@
|
|
|
84
84
|
"cloud:setup": "./scripts/cloud-setup.sh",
|
|
85
85
|
"cloud:api": "WORKSPACE_IMAGE=relay-workspace:local WORKSPACE_DEV_MOUNT=true node -r dotenv/config packages/cloud/dist/index.js",
|
|
86
86
|
"precloud": "./scripts/cloud-setup.sh --skip-data",
|
|
87
|
-
"cloud": "concurrently -n api,daemon,dashboard -c cyan,blue,magenta \"npm run cloud:api\" \"npm run dev:daemon\" \"npm run dev:next\""
|
|
87
|
+
"cloud": "concurrently -n api,daemon,dashboard -c cyan,blue,magenta \"npm run cloud:api\" \"npm run dev:daemon\" \"npm run dev:next\"",
|
|
88
|
+
"docs:dev": "cd docs && npm run dev"
|
|
88
89
|
},
|
|
89
90
|
"keywords": [
|
|
90
91
|
"agent",
|
|
@@ -107,21 +108,21 @@
|
|
|
107
108
|
},
|
|
108
109
|
"homepage": "https://github.com/AgentWorkforce/relay#readme",
|
|
109
110
|
"dependencies": {
|
|
110
|
-
"@agent-relay/bridge": "2.0.
|
|
111
|
-
"@agent-relay/config": "2.0.
|
|
112
|
-
"@agent-relay/continuity": "2.0.
|
|
113
|
-
"@agent-relay/daemon": "2.0.
|
|
114
|
-
"@agent-relay/hooks": "2.0.
|
|
115
|
-
"@agent-relay/mcp": "2.0.
|
|
116
|
-
"@agent-relay/protocol": "2.0.
|
|
117
|
-
"@agent-relay/resiliency": "2.0.
|
|
118
|
-
"@agent-relay/sdk": "2.0.
|
|
119
|
-
"@agent-relay/storage": "2.0.
|
|
120
|
-
"@agent-relay/telemetry": "2.0.
|
|
121
|
-
"@agent-relay/trajectory": "2.0.
|
|
122
|
-
"@agent-relay/user-directory": "2.0.
|
|
123
|
-
"@agent-relay/utils": "2.0.
|
|
124
|
-
"@agent-relay/wrapper": "2.0.
|
|
111
|
+
"@agent-relay/bridge": "2.0.18",
|
|
112
|
+
"@agent-relay/config": "2.0.18",
|
|
113
|
+
"@agent-relay/continuity": "2.0.18",
|
|
114
|
+
"@agent-relay/daemon": "2.0.18",
|
|
115
|
+
"@agent-relay/hooks": "2.0.18",
|
|
116
|
+
"@agent-relay/mcp": "2.0.18",
|
|
117
|
+
"@agent-relay/protocol": "2.0.18",
|
|
118
|
+
"@agent-relay/resiliency": "2.0.18",
|
|
119
|
+
"@agent-relay/sdk": "2.0.18",
|
|
120
|
+
"@agent-relay/storage": "2.0.18",
|
|
121
|
+
"@agent-relay/telemetry": "2.0.18",
|
|
122
|
+
"@agent-relay/trajectory": "2.0.18",
|
|
123
|
+
"@agent-relay/user-directory": "2.0.18",
|
|
124
|
+
"@agent-relay/utils": "2.0.18",
|
|
125
|
+
"@agent-relay/wrapper": "2.0.18",
|
|
125
126
|
"@nangohq/node": "^0.69.20",
|
|
126
127
|
"@types/jsonwebtoken": "^9.0.10",
|
|
127
128
|
"agent-trajectories": "^0.2.3",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/bridge",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.18",
|
|
4
4
|
"description": "Multi-project bridge client utilities for Relay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"test:watch": "vitest"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@agent-relay/protocol": "2.0.
|
|
26
|
-
"@agent-relay/config": "2.0.
|
|
27
|
-
"@agent-relay/utils": "2.0.
|
|
28
|
-
"@agent-relay/policy": "2.0.
|
|
29
|
-
"@agent-relay/user-directory": "2.0.
|
|
30
|
-
"@agent-relay/wrapper": "2.0.
|
|
31
|
-
"@agent-relay/mcp": "2.0.
|
|
25
|
+
"@agent-relay/protocol": "2.0.18",
|
|
26
|
+
"@agent-relay/config": "2.0.18",
|
|
27
|
+
"@agent-relay/utils": "2.0.18",
|
|
28
|
+
"@agent-relay/policy": "2.0.18",
|
|
29
|
+
"@agent-relay/user-directory": "2.0.18",
|
|
30
|
+
"@agent-relay/wrapper": "2.0.18",
|
|
31
|
+
"@agent-relay/mcp": "2.0.18"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^22.19.3",
|
|
@@ -55,6 +55,7 @@ authRouter.get('/me', async (req, res) => {
|
|
|
55
55
|
res.json({
|
|
56
56
|
id: user.id,
|
|
57
57
|
githubUsername: user.githubUsername,
|
|
58
|
+
displayName: user.displayName || user.githubUsername || user.email?.split('@')[0] || null,
|
|
58
59
|
email: user.email,
|
|
59
60
|
avatarUrl: user.avatarUrl,
|
|
60
61
|
plan: user.plan,
|
|
@@ -117,6 +118,7 @@ authRouter.get('/session', async (req, res) => {
|
|
|
117
118
|
user: {
|
|
118
119
|
id: user.id,
|
|
119
120
|
githubUsername: user.githubUsername,
|
|
121
|
+
displayName: user.displayName || user.githubUsername || user.email?.split('@')[0] || null,
|
|
120
122
|
email: user.email,
|
|
121
123
|
avatarUrl: user.avatarUrl,
|
|
122
124
|
plan: user.plan,
|
|
@@ -172,7 +172,7 @@ billingRouter.get('/subscription', requireAuth, async (req, res) => {
|
|
|
172
172
|
return res.status(404).json({ error: 'User not found' });
|
|
173
173
|
}
|
|
174
174
|
// Admin users have special status - show their current plan without Stripe
|
|
175
|
-
if (isAdminUser(user.githubUsername)) {
|
|
175
|
+
if (user.githubUsername && isAdminUser(user.githubUsername)) {
|
|
176
176
|
return res.json({
|
|
177
177
|
tier: user.plan || 'enterprise',
|
|
178
178
|
subscription: null,
|
|
@@ -192,7 +192,7 @@ billingRouter.get('/subscription', requireAuth, async (req, res) => {
|
|
|
192
192
|
const billing = getBillingService();
|
|
193
193
|
// Get or create Stripe customer
|
|
194
194
|
const customerId = user.stripeCustomerId ||
|
|
195
|
-
await billing.getOrCreateCustomer(user.id, user.email || '', user.githubUsername);
|
|
195
|
+
await billing.getOrCreateCustomer(user.id, user.email || '', user.githubUsername ?? undefined);
|
|
196
196
|
// Save customer ID to database if newly created
|
|
197
197
|
if (!user.stripeCustomerId) {
|
|
198
198
|
await db.users.update(userId, { stripeCustomerId: customerId });
|
|
@@ -250,7 +250,7 @@ billingRouter.post('/checkout', requireAuth, async (req, res) => {
|
|
|
250
250
|
return res.status(404).json({ error: 'User not found' });
|
|
251
251
|
}
|
|
252
252
|
// Admin users get free upgrades - skip Stripe entirely
|
|
253
|
-
if (isAdminUser(user.githubUsername)) {
|
|
253
|
+
if (user.githubUsername && isAdminUser(user.githubUsername)) {
|
|
254
254
|
// Update user plan directly
|
|
255
255
|
await db.users.update(userId, { plan: tier });
|
|
256
256
|
console.log(`[billing] Admin user ${user.githubUsername} upgraded to ${tier} (free)`);
|
|
@@ -273,7 +273,7 @@ billingRouter.post('/checkout', requireAuth, async (req, res) => {
|
|
|
273
273
|
const billing = getBillingService();
|
|
274
274
|
// Get or create customer
|
|
275
275
|
const customerId = user.stripeCustomerId ||
|
|
276
|
-
await billing.getOrCreateCustomer(user.id, user.email || '', user.githubUsername);
|
|
276
|
+
await billing.getOrCreateCustomer(user.id, user.email || '', user.githubUsername ?? undefined);
|
|
277
277
|
// Save customer ID to database
|
|
278
278
|
if (!user.stripeCustomerId) {
|
|
279
279
|
await db.users.update(userId, { stripeCustomerId: customerId });
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Auth API Routes
|
|
3
|
+
*
|
|
4
|
+
* Handles email/password authentication:
|
|
5
|
+
* - Signup with email/password
|
|
6
|
+
* - Login with email/password
|
|
7
|
+
* - Email verification
|
|
8
|
+
* - Password reset (future)
|
|
9
|
+
*/
|
|
10
|
+
export declare const emailAuthRouter: import("express-serve-static-core").Router;
|
|
11
|
+
//# sourceMappingURL=email-auth.d.ts.map
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Auth API Routes
|
|
3
|
+
*
|
|
4
|
+
* Handles email/password authentication:
|
|
5
|
+
* - Signup with email/password
|
|
6
|
+
* - Login with email/password
|
|
7
|
+
* - Email verification
|
|
8
|
+
* - Password reset (future)
|
|
9
|
+
*/
|
|
10
|
+
import { Router } from 'express';
|
|
11
|
+
import { randomBytes, scrypt, timingSafeEqual } from 'node:crypto';
|
|
12
|
+
import { promisify } from 'node:util';
|
|
13
|
+
import { db } from '../db/index.js';
|
|
14
|
+
import { requireAuth } from './auth.js';
|
|
15
|
+
const scryptAsync = promisify(scrypt);
|
|
16
|
+
export const emailAuthRouter = Router();
|
|
17
|
+
// Password hashing configuration
|
|
18
|
+
const SALT_LENGTH = 32;
|
|
19
|
+
const KEY_LENGTH = 64;
|
|
20
|
+
/**
|
|
21
|
+
* Hash a password using scrypt
|
|
22
|
+
*/
|
|
23
|
+
async function hashPassword(password) {
|
|
24
|
+
const salt = randomBytes(SALT_LENGTH);
|
|
25
|
+
const derivedKey = await scryptAsync(password, salt, KEY_LENGTH);
|
|
26
|
+
return `${salt.toString('hex')}:${derivedKey.toString('hex')}`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Verify a password against a hash
|
|
30
|
+
*/
|
|
31
|
+
async function verifyPassword(password, storedHash) {
|
|
32
|
+
try {
|
|
33
|
+
const [saltHex, keyHex] = storedHash.split(':');
|
|
34
|
+
if (!saltHex || !keyHex)
|
|
35
|
+
return false;
|
|
36
|
+
const salt = Buffer.from(saltHex, 'hex');
|
|
37
|
+
const storedKey = Buffer.from(keyHex, 'hex');
|
|
38
|
+
const derivedKey = await scryptAsync(password, salt, KEY_LENGTH);
|
|
39
|
+
return timingSafeEqual(storedKey, derivedKey);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Generate a random verification token
|
|
47
|
+
*/
|
|
48
|
+
function generateVerificationToken() {
|
|
49
|
+
return randomBytes(32).toString('hex');
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Validate email format
|
|
53
|
+
*/
|
|
54
|
+
function isValidEmail(email) {
|
|
55
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
56
|
+
return emailRegex.test(email);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Validate password strength
|
|
60
|
+
*/
|
|
61
|
+
function isValidPassword(password) {
|
|
62
|
+
if (password.length < 8) {
|
|
63
|
+
return { valid: false, message: 'Password must be at least 8 characters long' };
|
|
64
|
+
}
|
|
65
|
+
if (password.length > 128) {
|
|
66
|
+
return { valid: false, message: 'Password must be less than 128 characters' };
|
|
67
|
+
}
|
|
68
|
+
return { valid: true };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* POST /api/auth/email/signup
|
|
72
|
+
* Create a new account with email/password
|
|
73
|
+
*/
|
|
74
|
+
emailAuthRouter.post('/signup', async (req, res) => {
|
|
75
|
+
try {
|
|
76
|
+
const { email, password, displayName } = req.body;
|
|
77
|
+
// Validate input
|
|
78
|
+
if (!email || typeof email !== 'string') {
|
|
79
|
+
return res.status(400).json({ error: 'Email is required' });
|
|
80
|
+
}
|
|
81
|
+
if (!password || typeof password !== 'string') {
|
|
82
|
+
return res.status(400).json({ error: 'Password is required' });
|
|
83
|
+
}
|
|
84
|
+
const normalizedEmail = email.toLowerCase().trim();
|
|
85
|
+
if (!isValidEmail(normalizedEmail)) {
|
|
86
|
+
return res.status(400).json({ error: 'Invalid email format' });
|
|
87
|
+
}
|
|
88
|
+
const passwordValidation = isValidPassword(password);
|
|
89
|
+
if (!passwordValidation.valid) {
|
|
90
|
+
return res.status(400).json({ error: passwordValidation.message });
|
|
91
|
+
}
|
|
92
|
+
// Check if email already exists (in users.email or user_emails table)
|
|
93
|
+
// This enables account reconciliation - if someone signed up via GitHub,
|
|
94
|
+
// their linked emails will be found and they should log in instead
|
|
95
|
+
const existingUser = await db.userEmails.findUserByEmail(normalizedEmail);
|
|
96
|
+
if (existingUser) {
|
|
97
|
+
// If the existing user signed up via GitHub, offer to use that account
|
|
98
|
+
if (existingUser.githubId && !existingUser.passwordHash) {
|
|
99
|
+
return res.status(409).json({
|
|
100
|
+
error: 'This email is associated with a GitHub account. Please log in with GitHub, or set a password on that account.',
|
|
101
|
+
code: 'GITHUB_ACCOUNT_EXISTS',
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return res.status(409).json({ error: 'An account with this email already exists' });
|
|
105
|
+
}
|
|
106
|
+
// Hash password and create user
|
|
107
|
+
const passwordHash = await hashPassword(password);
|
|
108
|
+
const user = await db.users.createEmailUser({
|
|
109
|
+
email: normalizedEmail,
|
|
110
|
+
passwordHash,
|
|
111
|
+
displayName: displayName?.trim() || undefined,
|
|
112
|
+
});
|
|
113
|
+
// Generate verification token
|
|
114
|
+
const verificationToken = generateVerificationToken();
|
|
115
|
+
const verificationExpires = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
|
|
116
|
+
await db.users.setEmailVerificationToken(user.id, verificationToken, verificationExpires);
|
|
117
|
+
// TODO: Send verification email
|
|
118
|
+
// For now, we'll auto-verify in development or skip email verification
|
|
119
|
+
// In production, you would send an email with a link containing the token
|
|
120
|
+
// Set session
|
|
121
|
+
req.session.userId = user.id;
|
|
122
|
+
res.status(201).json({
|
|
123
|
+
success: true,
|
|
124
|
+
user: {
|
|
125
|
+
id: user.id,
|
|
126
|
+
email: user.email,
|
|
127
|
+
displayName: user.displayName,
|
|
128
|
+
emailVerified: user.emailVerified,
|
|
129
|
+
},
|
|
130
|
+
// Include verification token in development for testing
|
|
131
|
+
...(process.env.NODE_ENV !== 'production' && { verificationToken }),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
console.error('Email signup error:', error);
|
|
136
|
+
res.status(500).json({ error: 'Failed to create account' });
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
/**
|
|
140
|
+
* POST /api/auth/email/login
|
|
141
|
+
* Login with email/password
|
|
142
|
+
*
|
|
143
|
+
* Supports account reconciliation: users can log in with any email address
|
|
144
|
+
* linked to their account via GitHub, not just their primary email.
|
|
145
|
+
*/
|
|
146
|
+
emailAuthRouter.post('/login', async (req, res) => {
|
|
147
|
+
try {
|
|
148
|
+
const { email, password } = req.body;
|
|
149
|
+
// Validate input
|
|
150
|
+
if (!email || typeof email !== 'string') {
|
|
151
|
+
return res.status(400).json({ error: 'Email is required' });
|
|
152
|
+
}
|
|
153
|
+
if (!password || typeof password !== 'string') {
|
|
154
|
+
return res.status(400).json({ error: 'Password is required' });
|
|
155
|
+
}
|
|
156
|
+
const normalizedEmail = email.toLowerCase().trim();
|
|
157
|
+
// Find user by email - checks both user_emails table (GitHub-linked emails)
|
|
158
|
+
// and users.email (primary email), enabling account reconciliation
|
|
159
|
+
const user = await db.userEmails.findUserByEmail(normalizedEmail);
|
|
160
|
+
if (!user) {
|
|
161
|
+
// Use same message for security (don't reveal if email exists)
|
|
162
|
+
return res.status(401).json({ error: 'Invalid email or password' });
|
|
163
|
+
}
|
|
164
|
+
// Check if user has a password (might be GitHub-only user)
|
|
165
|
+
if (!user.passwordHash) {
|
|
166
|
+
return res.status(401).json({
|
|
167
|
+
error: 'This account uses GitHub login. Please sign in with GitHub, or set a password to enable email login.',
|
|
168
|
+
code: 'GITHUB_ACCOUNT',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
// Verify password
|
|
172
|
+
const isValid = await verifyPassword(password, user.passwordHash);
|
|
173
|
+
if (!isValid) {
|
|
174
|
+
return res.status(401).json({ error: 'Invalid email or password' });
|
|
175
|
+
}
|
|
176
|
+
// Set session
|
|
177
|
+
req.session.userId = user.id;
|
|
178
|
+
res.json({
|
|
179
|
+
success: true,
|
|
180
|
+
user: {
|
|
181
|
+
id: user.id,
|
|
182
|
+
email: user.email,
|
|
183
|
+
displayName: user.displayName,
|
|
184
|
+
githubUsername: user.githubUsername,
|
|
185
|
+
avatarUrl: user.avatarUrl,
|
|
186
|
+
emailVerified: user.emailVerified,
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
console.error('Email login error:', error);
|
|
192
|
+
res.status(500).json({ error: 'Failed to login' });
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
/**
|
|
196
|
+
* POST /api/auth/email/verify
|
|
197
|
+
* Verify email with token
|
|
198
|
+
*/
|
|
199
|
+
emailAuthRouter.post('/verify', async (req, res) => {
|
|
200
|
+
try {
|
|
201
|
+
const { token } = req.body;
|
|
202
|
+
if (!token || typeof token !== 'string') {
|
|
203
|
+
return res.status(400).json({ error: 'Verification token is required' });
|
|
204
|
+
}
|
|
205
|
+
// Find user by token
|
|
206
|
+
const user = await db.users.findByEmailVerificationToken(token);
|
|
207
|
+
if (!user) {
|
|
208
|
+
return res.status(400).json({ error: 'Invalid or expired verification token' });
|
|
209
|
+
}
|
|
210
|
+
// Check if token expired
|
|
211
|
+
if (user.emailVerificationExpires && user.emailVerificationExpires < new Date()) {
|
|
212
|
+
return res.status(400).json({ error: 'Verification token has expired' });
|
|
213
|
+
}
|
|
214
|
+
// Verify email
|
|
215
|
+
await db.users.verifyEmail(user.id);
|
|
216
|
+
res.json({
|
|
217
|
+
success: true,
|
|
218
|
+
message: 'Email verified successfully',
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
console.error('Email verification error:', error);
|
|
223
|
+
res.status(500).json({ error: 'Failed to verify email' });
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
/**
|
|
227
|
+
* POST /api/auth/email/resend-verification
|
|
228
|
+
* Resend verification email (requires auth)
|
|
229
|
+
*/
|
|
230
|
+
emailAuthRouter.post('/resend-verification', requireAuth, async (req, res) => {
|
|
231
|
+
try {
|
|
232
|
+
const userId = req.session.userId;
|
|
233
|
+
const user = await db.users.findById(userId);
|
|
234
|
+
if (!user) {
|
|
235
|
+
return res.status(404).json({ error: 'User not found' });
|
|
236
|
+
}
|
|
237
|
+
if (user.emailVerified) {
|
|
238
|
+
return res.status(400).json({ error: 'Email is already verified' });
|
|
239
|
+
}
|
|
240
|
+
if (!user.email) {
|
|
241
|
+
return res.status(400).json({ error: 'No email address on this account' });
|
|
242
|
+
}
|
|
243
|
+
// Generate new verification token
|
|
244
|
+
const verificationToken = generateVerificationToken();
|
|
245
|
+
const verificationExpires = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
|
|
246
|
+
await db.users.setEmailVerificationToken(user.id, verificationToken, verificationExpires);
|
|
247
|
+
// TODO: Send verification email
|
|
248
|
+
// For now, return success and include token in development
|
|
249
|
+
res.json({
|
|
250
|
+
success: true,
|
|
251
|
+
message: 'Verification email sent',
|
|
252
|
+
...(process.env.NODE_ENV !== 'production' && { verificationToken }),
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
console.error('Resend verification error:', error);
|
|
257
|
+
res.status(500).json({ error: 'Failed to resend verification email' });
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
/**
|
|
261
|
+
* POST /api/auth/set-email
|
|
262
|
+
* Set email for GitHub users who don't have one (requires auth)
|
|
263
|
+
*/
|
|
264
|
+
emailAuthRouter.post('/set-email', requireAuth, async (req, res) => {
|
|
265
|
+
try {
|
|
266
|
+
const userId = req.session.userId;
|
|
267
|
+
const { email } = req.body;
|
|
268
|
+
if (!email || typeof email !== 'string') {
|
|
269
|
+
return res.status(400).json({ error: 'Email is required' });
|
|
270
|
+
}
|
|
271
|
+
const normalizedEmail = email.toLowerCase().trim();
|
|
272
|
+
if (!isValidEmail(normalizedEmail)) {
|
|
273
|
+
return res.status(400).json({ error: 'Invalid email format' });
|
|
274
|
+
}
|
|
275
|
+
const user = await db.users.findById(userId);
|
|
276
|
+
if (!user) {
|
|
277
|
+
return res.status(404).json({ error: 'User not found' });
|
|
278
|
+
}
|
|
279
|
+
// Check if user already has an email
|
|
280
|
+
if (user.email) {
|
|
281
|
+
return res.status(400).json({ error: 'Email is already set for this account' });
|
|
282
|
+
}
|
|
283
|
+
// Check if email is already used by another user (including in user_emails table)
|
|
284
|
+
const isLinkedToOther = await db.userEmails.isEmailLinkedToOtherUser(normalizedEmail, userId);
|
|
285
|
+
if (isLinkedToOther) {
|
|
286
|
+
return res.status(409).json({ error: 'This email is already associated with another account' });
|
|
287
|
+
}
|
|
288
|
+
// Also check users.email directly
|
|
289
|
+
const existingUser = await db.users.findByEmail(normalizedEmail);
|
|
290
|
+
if (existingUser && existingUser.id !== userId) {
|
|
291
|
+
return res.status(409).json({ error: 'This email is already associated with another account' });
|
|
292
|
+
}
|
|
293
|
+
// Update user with email
|
|
294
|
+
await db.users.update(userId, { email: normalizedEmail });
|
|
295
|
+
// Generate verification token
|
|
296
|
+
const verificationToken = generateVerificationToken();
|
|
297
|
+
const verificationExpires = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
|
|
298
|
+
await db.users.setEmailVerificationToken(userId, verificationToken, verificationExpires);
|
|
299
|
+
// TODO: Send verification email
|
|
300
|
+
res.json({
|
|
301
|
+
success: true,
|
|
302
|
+
message: 'Email set successfully',
|
|
303
|
+
email: normalizedEmail,
|
|
304
|
+
...(process.env.NODE_ENV !== 'production' && { verificationToken }),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
console.error('Set email error:', error);
|
|
309
|
+
res.status(500).json({ error: 'Failed to set email' });
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
/**
|
|
313
|
+
* POST /api/auth/email/set-password
|
|
314
|
+
* Set password for GitHub users who want to add email login (requires auth)
|
|
315
|
+
*/
|
|
316
|
+
emailAuthRouter.post('/set-password', requireAuth, async (req, res) => {
|
|
317
|
+
try {
|
|
318
|
+
const userId = req.session.userId;
|
|
319
|
+
const { password } = req.body;
|
|
320
|
+
if (!password || typeof password !== 'string') {
|
|
321
|
+
return res.status(400).json({ error: 'Password is required' });
|
|
322
|
+
}
|
|
323
|
+
const passwordValidation = isValidPassword(password);
|
|
324
|
+
if (!passwordValidation.valid) {
|
|
325
|
+
return res.status(400).json({ error: passwordValidation.message });
|
|
326
|
+
}
|
|
327
|
+
const user = await db.users.findById(userId);
|
|
328
|
+
if (!user) {
|
|
329
|
+
return res.status(404).json({ error: 'User not found' });
|
|
330
|
+
}
|
|
331
|
+
if (!user.email) {
|
|
332
|
+
return res.status(400).json({ error: 'Please set an email address first' });
|
|
333
|
+
}
|
|
334
|
+
// Hash password and update user
|
|
335
|
+
const passwordHash = await hashPassword(password);
|
|
336
|
+
await db.users.updatePassword(userId, passwordHash);
|
|
337
|
+
res.json({
|
|
338
|
+
success: true,
|
|
339
|
+
message: 'Password set successfully',
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
console.error('Set password error:', error);
|
|
344
|
+
res.status(500).json({ error: 'Failed to set password' });
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
//# sourceMappingURL=email-auth.js.map
|