UNPKG

consortium

Version:

Remote control and session sharing CLI for AI coding agents

219 lines (213 loc) 8.03 kB
import React, { useState, useEffect, useCallback } from 'react'; import { Text, useInput, Box, render } from 'ink'; import axios from 'axios'; import { c as configuration, j as writeCredentialsTokenOnly } from './types-DETLaopx.mjs'; import { E as EmailInput } from './EmailInput-DNuvoMjN.mjs'; import 'chalk'; import 'fs'; import 'node:fs'; import 'node:os'; import 'node:path'; import 'node:events'; import 'socket.io-client'; import 'zod'; import 'node:crypto'; import 'tweetnacl'; import 'child_process'; import 'util'; import 'fs/promises'; import 'crypto'; import 'path'; import 'url'; import 'os'; import 'node:child_process'; import 'node:fs/promises'; import 'node:module'; import 'node:util'; import 'expo-server-sdk'; const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"]; const Spinner = ({ text, color = "cyan" }) => { const [frameIndex, setFrameIndex] = useState(0); useEffect(() => { const timer = setInterval(() => { setFrameIndex((prev) => (prev + 1) % frames.length); }, 80); return () => clearInterval(timer); }, []); return /* @__PURE__ */ React.createElement(Text, { color }, frames[frameIndex], " ", text); }; async function sendOtp(email) { try { await axios.post(`${configuration.serverUrl}/v1/auth/email/send-otp`, { email }); return { success: true }; } catch (err) { const error = err; if (error.response?.status === 429) { const retryAfter = error.response.data?.retryAfter ?? 60; throw new Error(`Too many requests. Please try again in ${retryAfter} seconds.`); } if (error.response?.status === 400 && error.response.data?.error === "invalid_email") { throw new Error("Invalid email address. Please check and try again."); } throw new Error("Failed to send verification code. Please try again."); } } async function verifyOtp(email, code) { try { const response = await axios.post(`${configuration.serverUrl}/v1/auth/email/verify-otp`, { email, code }); return { token: response.data.token, isNewAccount: response.data.isNewAccount }; } catch (err) { const error = err; if (error.response?.status === 400) { const errorCode = error.response.data?.error; if (errorCode === "invalid_code") { const remaining = error.response.data?.attemptsRemaining ?? 0; throw new Error(`Invalid code. ${remaining} attempt${remaining === 1 ? "" : "s"} remaining.`); } if (errorCode === "otp_expired") { throw new Error("Verification code has expired. Please request a new one."); } if (errorCode === "too_many_attempts") { throw new Error("Too many failed attempts. Please request a new code."); } } throw new Error("Failed to verify code. Please try again."); } } const EmailAuthFlow = ({ onComplete, onCancel }) => { const [flowState, setFlowState] = useState("enterEmail"); const [email, setEmail] = useState(""); const [code, setCode] = useState(""); const [errorMessage, setErrorMessage] = useState(""); const [countdown, setCountdown] = useState(0); useEffect(() => { if (countdown <= 0) return; const timer = setInterval(() => { setCountdown((prev) => { if (prev <= 1) { clearInterval(timer); return 0; } return prev - 1; }); }, 1e3); return () => clearInterval(timer); }, [countdown]); const handleSendOtp = useCallback(async (targetEmail) => { setFlowState("sendingOtp"); try { await sendOtp(targetEmail); setFlowState("enterCode"); setCode(""); setCountdown(300); } catch (err) { setErrorMessage(err.message); setFlowState("error"); } }, []); const handleVerifyOtp = useCallback(async (targetEmail, targetCode) => { setFlowState("verifying"); try { const result = await verifyOtp(targetEmail, targetCode); setFlowState("success"); onComplete(result.token, result.isNewAccount); } catch (err) { const message = err.message; setErrorMessage(message); if (message.includes("attempt")) { setFlowState("enterCode"); setCode(""); } else { setFlowState("error"); } } }, [onComplete]); useInput((input, key) => { if (flowState === "enterEmail") { return; } else if (flowState === "enterCode") { if (key.escape) { onCancel(); } else if (key.return && code.length === 6) { handleVerifyOtp(email, code); } else if (key.backspace || key.delete) { setCode((prev) => prev.slice(0, -1)); } else if (input === "r" || input === "R") { handleSendOtp(email); } else if (/^[0-9]$/.test(input) && code.length < 6) { setCode((prev) => prev + input); } } else if (flowState === "error") { if (key.escape) { onCancel(); } else if (key.return) { setErrorMessage(""); setEmail(""); setCode(""); setFlowState("enterEmail"); } } }); const formatCountdown = (seconds) => { const m = Math.floor(seconds / 60); const s = seconds % 60; return `${m}:${s.toString().padStart(2, "0")}`; }; if (flowState === "enterEmail") { return /* @__PURE__ */ React.createElement( EmailInput, { onSubmit: (submittedEmail) => { setEmail(submittedEmail); handleSendOtp(submittedEmail); }, onCancel } ); } if (flowState === "sendingOtp") { return /* @__PURE__ */ React.createElement(Box, { paddingY: 1 }, /* @__PURE__ */ React.createElement(Spinner, { text: "Sending verification code..." })); } if (flowState === "enterCode") { const codeDisplay = Array.from( { length: 6 }, (_, i) => i < code.length ? "\u25CF" : "\u25CB" ).join(" "); return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Enter the 6-digit code sent to ", /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, email)), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, codeDisplay)), countdown > 0 && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Code expires in ", formatCountdown(countdown))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Press R to resend, Escape to cancel"))); } if (flowState === "verifying") { return /* @__PURE__ */ React.createElement(Box, { paddingY: 1 }, /* @__PURE__ */ React.createElement(Spinner, { text: "Verifying code..." })); } if (flowState === "success") { return /* @__PURE__ */ React.createElement(Box, { paddingY: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "green" }, "\u2713 Authentication successful")); } return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "red" }, "\u2717 ", errorMessage), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Press Enter to retry or Escape to cancel"))); }; async function doEmailAuth() { return new Promise((resolve) => { let hasResolved = false; const onComplete = async (token, _isNewAccount) => { if (!hasResolved) { hasResolved = true; await writeCredentialsTokenOnly(token); app.unmount(); resolve({ token, encryption: null }); } }; const onCancel = () => { if (!hasResolved) { hasResolved = true; app.unmount(); resolve(null); } }; const app = render(React.createElement(EmailAuthFlow, { onComplete, onCancel }), { exitOnCtrlC: false, patchConsole: false }); }); } export { doEmailAuth };