consortium
Version:
Remote control and session sharing CLI for AI coding agents
219 lines (213 loc) • 8.03 kB
JavaScript
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 };