UNPKG

aura-marketing-cli

Version:

AI-powered marketing strategist CLI tool for developers

1,477 lines (1,474 loc) 48.2 kB
#!/usr/bin/env node import { jsxs, jsx, Fragment } from "react/jsx-runtime"; import React, { createContext, useContext, useState, useEffect, useRef } from "react"; import { Box, Text, useInput, Spacer, useApp, Newline, Static, render } from "ink"; import process$1 from "node:process"; import "node:util"; import chalk from "chalk"; import fs from "fs/promises"; import TextInput from "ink-text-input"; import SelectInput from "ink-select-input"; import path, { dirname } from "path"; import { fileURLToPath } from "url"; import { openai } from "@ai-sdk/openai"; import { streamText } from "ai"; import dotenv from "dotenv"; function isUnicodeSupported() { const { env } = process$1; const { TERM, TERM_PROGRAM } = env; if (process$1.platform !== "win32") { return TERM !== "linux"; } return Boolean(env.WT_SESSION) || Boolean(env.TERMINUS_SUBLIME) || env.ConEmuTask === "{cmd::Cmder}" || TERM_PROGRAM === "Terminus-Sublime" || TERM_PROGRAM === "vscode" || TERM === "xterm-256color" || TERM === "alacritty" || TERM === "rxvt-unicode" || TERM === "rxvt-unicode-256color" || env.TERMINAL_EMULATOR === "JetBrains-JediTerm"; } const common = { circleQuestionMark: "(?)", questionMarkPrefix: "(?)", square: "█", squareDarkShade: "▓", squareMediumShade: "▒", squareLightShade: "░", squareTop: "▀", squareBottom: "▄", squareLeft: "▌", squareRight: "▐", squareCenter: "■", bullet: "●", dot: "․", ellipsis: "…", pointerSmall: "›", triangleUp: "▲", triangleUpSmall: "▴", triangleDown: "▼", triangleDownSmall: "▾", triangleLeftSmall: "◂", triangleRightSmall: "▸", home: "⌂", heart: "♥", musicNote: "♪", musicNoteBeamed: "♫", arrowUp: "↑", arrowDown: "↓", arrowLeft: "←", arrowRight: "→", arrowLeftRight: "↔", arrowUpDown: "↕", almostEqual: "≈", notEqual: "≠", lessOrEqual: "≤", greaterOrEqual: "≥", identical: "≡", infinity: "∞", subscriptZero: "₀", subscriptOne: "₁", subscriptTwo: "₂", subscriptThree: "₃", subscriptFour: "₄", subscriptFive: "₅", subscriptSix: "₆", subscriptSeven: "₇", subscriptEight: "₈", subscriptNine: "₉", oneHalf: "½", oneThird: "⅓", oneQuarter: "¼", oneFifth: "⅕", oneSixth: "⅙", oneEighth: "⅛", twoThirds: "⅔", twoFifths: "⅖", threeQuarters: "¾", threeFifths: "⅗", threeEighths: "⅜", fourFifths: "⅘", fiveSixths: "⅚", fiveEighths: "⅝", sevenEighths: "⅞", line: "─", lineBold: "━", lineDouble: "═", lineDashed0: "┄", lineDashed1: "┅", lineDashed2: "┈", lineDashed3: "┉", lineDashed4: "╌", lineDashed5: "╍", lineDashed6: "╴", lineDashed7: "╶", lineDashed8: "╸", lineDashed9: "╺", lineDashed10: "╼", lineDashed11: "╾", lineDashed12: "−", lineDashed13: "–", lineDashed14: "‐", lineDashed15: "⁃", lineVertical: "│", lineVerticalBold: "┃", lineVerticalDouble: "║", lineVerticalDashed0: "┆", lineVerticalDashed1: "┇", lineVerticalDashed2: "┊", lineVerticalDashed3: "┋", lineVerticalDashed4: "╎", lineVerticalDashed5: "╏", lineVerticalDashed6: "╵", lineVerticalDashed7: "╷", lineVerticalDashed8: "╹", lineVerticalDashed9: "╻", lineVerticalDashed10: "╽", lineVerticalDashed11: "╿", lineDownLeft: "┐", lineDownLeftArc: "╮", lineDownBoldLeftBold: "┓", lineDownBoldLeft: "┒", lineDownLeftBold: "┑", lineDownDoubleLeftDouble: "╗", lineDownDoubleLeft: "╖", lineDownLeftDouble: "╕", lineDownRight: "┌", lineDownRightArc: "╭", lineDownBoldRightBold: "┏", lineDownBoldRight: "┎", lineDownRightBold: "┍", lineDownDoubleRightDouble: "╔", lineDownDoubleRight: "╓", lineDownRightDouble: "╒", lineUpLeft: "┘", lineUpLeftArc: "╯", lineUpBoldLeftBold: "┛", lineUpBoldLeft: "┚", lineUpLeftBold: "┙", lineUpDoubleLeftDouble: "╝", lineUpDoubleLeft: "╜", lineUpLeftDouble: "╛", lineUpRight: "└", lineUpRightArc: "╰", lineUpBoldRightBold: "┗", lineUpBoldRight: "┖", lineUpRightBold: "┕", lineUpDoubleRightDouble: "╚", lineUpDoubleRight: "╙", lineUpRightDouble: "╘", lineUpDownLeft: "┤", lineUpBoldDownBoldLeftBold: "┫", lineUpBoldDownBoldLeft: "┨", lineUpDownLeftBold: "┥", lineUpBoldDownLeftBold: "┩", lineUpDownBoldLeftBold: "┪", lineUpDownBoldLeft: "┧", lineUpBoldDownLeft: "┦", lineUpDoubleDownDoubleLeftDouble: "╣", lineUpDoubleDownDoubleLeft: "╢", lineUpDownLeftDouble: "╡", lineUpDownRight: "├", lineUpBoldDownBoldRightBold: "┣", lineUpBoldDownBoldRight: "┠", lineUpDownRightBold: "┝", lineUpBoldDownRightBold: "┡", lineUpDownBoldRightBold: "┢", lineUpDownBoldRight: "┟", lineUpBoldDownRight: "┞", lineUpDoubleDownDoubleRightDouble: "╠", lineUpDoubleDownDoubleRight: "╟", lineUpDownRightDouble: "╞", lineDownLeftRight: "┬", lineDownBoldLeftBoldRightBold: "┳", lineDownLeftBoldRightBold: "┯", lineDownBoldLeftRight: "┰", lineDownBoldLeftBoldRight: "┱", lineDownBoldLeftRightBold: "┲", lineDownLeftRightBold: "┮", lineDownLeftBoldRight: "┭", lineDownDoubleLeftDoubleRightDouble: "╦", lineDownDoubleLeftRight: "╥", lineDownLeftDoubleRightDouble: "╤", lineUpLeftRight: "┴", lineUpBoldLeftBoldRightBold: "┻", lineUpLeftBoldRightBold: "┷", lineUpBoldLeftRight: "┸", lineUpBoldLeftBoldRight: "┹", lineUpBoldLeftRightBold: "┺", lineUpLeftRightBold: "┶", lineUpLeftBoldRight: "┵", lineUpDoubleLeftDoubleRightDouble: "╩", lineUpDoubleLeftRight: "╨", lineUpLeftDoubleRightDouble: "╧", lineUpDownLeftRight: "┼", lineUpBoldDownBoldLeftBoldRightBold: "╋", lineUpDownBoldLeftBoldRightBold: "╈", lineUpBoldDownLeftBoldRightBold: "╇", lineUpBoldDownBoldLeftRightBold: "╊", lineUpBoldDownBoldLeftBoldRight: "╉", lineUpBoldDownLeftRight: "╀", lineUpDownBoldLeftRight: "╁", lineUpDownLeftBoldRight: "┽", lineUpDownLeftRightBold: "┾", lineUpBoldDownBoldLeftRight: "╂", lineUpDownLeftBoldRightBold: "┿", lineUpBoldDownLeftBoldRight: "╃", lineUpBoldDownLeftRightBold: "╄", lineUpDownBoldLeftBoldRight: "╅", lineUpDownBoldLeftRightBold: "╆", lineUpDoubleDownDoubleLeftDoubleRightDouble: "╬", lineUpDoubleDownDoubleLeftRight: "╫", lineUpDownLeftDoubleRightDouble: "╪", lineCross: "╳", lineBackslash: "╲", lineSlash: "╱" }; const specialMainSymbols = { tick: "✔", info: "ℹ", warning: "⚠", cross: "✘", squareSmall: "◻", squareSmallFilled: "◼", circle: "◯", circleFilled: "◉", circleDotted: "◌", circleDouble: "◎", circleCircle: "ⓞ", circleCross: "ⓧ", circlePipe: "Ⓘ", radioOn: "◉", radioOff: "◯", checkboxOn: "☒", checkboxOff: "☐", checkboxCircleOn: "ⓧ", checkboxCircleOff: "Ⓘ", pointer: "❯", triangleUpOutline: "△", triangleLeft: "◀", triangleRight: "▶", lozenge: "◆", lozengeOutline: "◇", hamburger: "☰", smiley: "㋡", mustache: "෴", star: "★", play: "▶", nodejs: "⬢", oneSeventh: "⅐", oneNinth: "⅑", oneTenth: "⅒" }; const specialFallbackSymbols = { tick: "√", info: "i", warning: "‼", cross: "×", squareSmall: "□", squareSmallFilled: "■", circle: "( )", circleFilled: "(*)", circleDotted: "( )", circleDouble: "( )", circleCircle: "(○)", circleCross: "(×)", circlePipe: "(│)", radioOn: "(*)", radioOff: "( )", checkboxOn: "[×]", checkboxOff: "[ ]", checkboxCircleOn: "(×)", checkboxCircleOff: "( )", pointer: ">", triangleUpOutline: "∆", triangleLeft: "◄", triangleRight: "►", lozenge: "♦", lozengeOutline: "◊", hamburger: "≡", smiley: "☺", mustache: "┌─┐", star: "✶", play: "►", nodejs: "♦", oneSeventh: "1/7", oneNinth: "1/9", oneTenth: "1/10" }; const mainSymbols = { ...common, ...specialMainSymbols }; const fallbackSymbols = { ...common, ...specialFallbackSymbols }; const shouldUseMain = isUnicodeSupported(); const figures = shouldUseMain ? mainSymbols : fallbackSymbols; const colorByVariant$1 = { info: "blue", success: "green", error: "red", warning: "yellow" }; const theme$c = { styles: { container: ({ variant }) => ({ flexGrow: 1, borderStyle: "round", borderColor: colorByVariant$1[variant], gap: 1, paddingX: 1 }), iconContainer: () => ({ flexShrink: 0 }), icon: ({ variant }) => ({ color: colorByVariant$1[variant] }), content: () => ({ flexShrink: 1, flexGrow: 1, minWidth: 0, flexDirection: "column", gap: 1 }), title: () => ({ bold: true }), message: () => ({}) }, config({ variant }) { let icon; if (variant === "info") { icon = figures.info; } if (variant === "success") { icon = figures.tick; } if (variant === "error") { icon = figures.cross; } if (variant === "warning") { icon = figures.warning; } return { icon }; } }; const theme$b = { styles: { container: ({ color }) => ({ backgroundColor: color }), label: () => ({ color: "black" }) } }; const theme$a = { styles: { input: ({ isFocused }) => ({ dimColor: !isFocused }) } }; const theme$9 = { styles: { container: () => ({ flexDirection: "column" }), option: ({ isFocused }) => ({ gap: 1, paddingLeft: isFocused ? 0 : 2 }), selectedIndicator: () => ({ color: "green" }), focusIndicator: () => ({ color: "blue" }), label({ isFocused, isSelected }) { let color; if (isSelected) { color = "green"; } if (isFocused) { color = "blue"; } return { color }; }, highlightedText: () => ({ bold: true }) } }; const theme$8 = { styles: { list: () => ({ flexDirection: "column" }), listItem: () => ({ gap: 1 }), marker: () => ({ dimColor: true }), content: () => ({ flexDirection: "column" }) } }; const theme$7 = { styles: { container: () => ({ flexGrow: 1, minWidth: 0 }), completed: () => ({ color: "magenta" }), remaining: () => ({ dimColor: true }) }, config: () => ({ // Character for rendering a completed bar completedCharacter: figures.square, // Character for rendering a remaining bar remainingCharacter: figures.squareLightShade }) }; const theme$6 = { styles: { container: () => ({ flexDirection: "column" }), option: ({ isFocused }) => ({ gap: 1, paddingLeft: isFocused ? 0 : 2 }), selectedIndicator: () => ({ color: "green" }), focusIndicator: () => ({ color: "blue" }), label({ isFocused, isSelected }) { let color; if (isSelected) { color = "green"; } if (isFocused) { color = "blue"; } return { color }; }, highlightedText: () => ({ bold: true }) } }; const theme$5 = { styles: { container: () => ({ gap: 1 }), frame: () => ({ color: "blue" }), label: () => ({}) } }; const colorByVariant = { success: "green", error: "red", warning: "yellow", info: "blue" }; const iconByVariant = { success: figures.tick, error: figures.cross, warning: figures.warning, info: figures.info }; const theme$4 = { styles: { container: () => ({ gap: 1 }), iconContainer: () => ({ flexShrink: 0 }), icon: ({ variant }) => ({ color: colorByVariant[variant] }), message: () => ({}) }, config: ({ variant }) => ({ icon: iconByVariant[variant] }) }; const theme$3 = { styles: { list: () => ({ flexDirection: "column" }), listItem: () => ({ gap: 1 }), marker: () => ({ dimColor: true }), content: () => ({ flexDirection: "column" }) }, config: () => ({ marker: figures.line }) }; const theme$2 = { styles: { value: () => ({}) } }; const theme$1 = { styles: { value: () => ({}) } }; const theme = { styles: { value: () => ({}) } }; const defaultTheme = { components: { Alert: theme$c, Badge: theme$b, ConfirmInput: theme$a, MultiSelect: theme$9, OrderedList: theme$8, ProgressBar: theme$7, Select: theme$6, Spinner: theme$5, StatusMessage: theme$4, UnorderedList: theme$3, TextInput: theme$2, EmailInput: theme$1, PasswordInput: theme } }; const ThemeContext = createContext(defaultTheme); const useComponentTheme = (component) => { const theme2 = useContext(ThemeContext); return theme2.components[component]; }; const defaultMarker = figures.line; createContext({ marker: defaultMarker }); createContext({ depth: 0 }); chalk.inverse(" "); createContext({ marker: figures.line }); createContext({ marker: "" }); chalk.inverse(" "); function Alert({ children, variant, title }) { const { styles, config } = useComponentTheme("Alert"); return React.createElement( Box, { ...styles.container({ variant }) }, React.createElement( Box, { ...styles.iconContainer() }, React.createElement(Text, { ...styles.icon({ variant }) }, config({ variant }).icon) ), React.createElement( Box, { ...styles.content() }, title && React.createElement(Text, { ...styles.title() }, title), React.createElement(Text, { ...styles.message() }, children) ) ); } chalk.inverse(" "); const CommandInput = ({ value, onChange, onSubmit, placeholder = "Type a command or message...", commands = [], exitPrompt = false, onClearInput = null, onExit = null }) => { const [suggestions, setSuggestions] = useState([]); const [showSuggestions, setShowSuggestions] = useState(false); const [selectedIndex, setSelectedIndex] = useState(0); useInput((input, key) => { if (key.ctrl && input === "c") { if (value.length > 0 && onClearInput) { onClearInput(); return; } if (onExit) { onExit(); return; } } if (showSuggestions && suggestions.length > 0) { if (key.upArrow) { setSelectedIndex((prev) => Math.max(0, prev - 1)); return; } if (key.downArrow) { setSelectedIndex((prev) => Math.min(suggestions.length - 1, prev + 1)); return; } if (key.tab) { const selectedCmd = suggestions[selectedIndex]; if (selectedCmd) { onChange(`/${selectedCmd.name}`); setShowSuggestions(false); } return; } } }); useEffect(() => { if (value.startsWith("/")) { const query = value.substring(1).toLowerCase(); const filteredCommands = commands.filter( (cmd) => cmd.name.toLowerCase().includes(query) || cmd.description.toLowerCase().includes(query) ); setSuggestions(filteredCommands); setShowSuggestions(filteredCommands.length > 0); setSelectedIndex(0); } else { setSuggestions([]); setShowSuggestions(false); setSelectedIndex(0); } }, [value, commands]); const handleSubmit = (inputValue) => { if (showSuggestions && suggestions.length > 0) { const selectedCmd = suggestions[selectedIndex]; if (selectedCmd) { setShowSuggestions(false); onSubmit(`/${selectedCmd.name}`); return; } } setShowSuggestions(false); onSubmit(inputValue); }; return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: "100%", children: [ /* @__PURE__ */ jsxs(Box, { paddingX: 1, paddingY: 0, borderColor: "whiteBright", borderStyle: "round", width: "100%", children: [ /* @__PURE__ */ jsx(Text, { color: "blue", children: "> " }), /* @__PURE__ */ jsx( TextInput, { value, onChange, onSubmit: handleSubmit, placeholder, focus: true } ) ] }), showSuggestions && suggestions.length > 0 && /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, paddingLeft: 2, children: suggestions.map((cmd, index) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", flexWrap: "wrap", children: [ /* @__PURE__ */ jsx(Box, { width: 16, children: /* @__PURE__ */ jsx(Text, { color: index === selectedIndex ? "blueBright" : "gray", children: /* @__PURE__ */ jsxs(Text, { color: index === selectedIndex ? "blueBright" : "gray", children: [ "/", cmd.name ] }) }) }), /* @__PURE__ */ jsx(Box, { paddingLeft: 3, children: /* @__PURE__ */ jsx(Text, { color: index === selectedIndex ? "blueBright" : "gray", children: cmd.description }) }) ] }, index)) }), /* @__PURE__ */ jsxs(Box, { width: "100%", children: [ /* @__PURE__ */ jsx(Text, { dimColor: true, children: exitPrompt ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: "Press Ctrl+C again to exit" }) : value.startsWith("/") ? /* @__PURE__ */ jsx(Fragment, { children: "Press Enter to continue" }) : /* @__PURE__ */ jsx(Fragment, { children: "Type / for commands" }) }), /* @__PURE__ */ jsx(Spacer, {}), /* @__PURE__ */ jsx(Text, { dimColor: true, children: !exitPrompt && /* @__PURE__ */ jsxs(Fragment, { children: [ " Ctrl+C to ", value ? "clear input" : "exit" ] }) }) ] }) ] }); }; const CompositeInput = ({ options = [], onSubmit, placeholder = "Type your answer...", initialValue = "", onBack = null, isReview = false, onPrevious = null, showTypeYourAnswer = true, directSubmitValues = [] }) => { const [mode, setMode] = useState(options.length > 0 ? "select" : "text"); const [inputValue, setInputValue] = useState(""); const [exitPrompt, setExitPrompt] = useState(false); const lastCtrlC = useRef(0); useInput((input, key) => { if (key.ctrl && input === "c") { const now = Date.now(); if (now - lastCtrlC.current < 2e3) { if (onBack) onBack(); } else { setExitPrompt(true); lastCtrlC.current = now; setTimeout(() => { setExitPrompt(false); }, 2e3); } } }); useEffect(() => { setMode(options.length > 0 ? "select" : "text"); setInputValue(initialValue); setExitPrompt(false); }, [options, initialValue]); const selectItems = options.length > 0 ? [ ...options.map((opt) => ({ label: opt.label, value: opt.value })), ...showTypeYourAnswer ? [ { label: "Type your answer...", value: "__custom_input__" } ] : [] ] : []; const handleSelect = (item) => { if (item.value === "__custom_input__") { setMode("text"); setInputValue(""); } else if (directSubmitValues.includes(item.value)) { onSubmit(item.value); } else { setMode("text"); setInputValue(item.label); } }; const handleTextSubmit = (value) => { if (value.trim()) { if (value.toLowerCase() === "/back" && !isReview && onPrevious) { onPrevious(); return; } onSubmit(value); } }; return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [ /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: mode === "select" && options.length > 0 ? /* @__PURE__ */ jsx( Box, { paddingX: 1, paddingY: 0, borderColor: "whiteBright", borderStyle: "round", width: "100%", children: /* @__PURE__ */ jsx(SelectInput, { items: selectItems, onSelect: handleSelect }) } ) : /* @__PURE__ */ jsxs( Box, { paddingX: 1, paddingY: 0, borderColor: "whiteBright", borderStyle: "round", width: "100%", children: [ /* @__PURE__ */ jsx(Text, { color: "blue", children: "> " }), /* @__PURE__ */ jsx( TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleTextSubmit, placeholder, focus: true } ) ] } ) }), /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, fontSize: 12, children: mode === "select" ? /* @__PURE__ */ jsx(Fragment, { children: "Use ↑↓ to navigate, Enter to select" }) : isReview ? /* @__PURE__ */ jsx(Fragment, { children: "Use /1, /2, etc. to jump to specific questions" }) : /* @__PURE__ */ jsx(Fragment, { children: "Use /back to go to previous question" }) }) }), exitPrompt && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "Press Ctrl+C again to return to main menu" }) }) ] }); }; const EmojiSpinner = ({ label = "", emojis = ["💬", "💭"], interval = 500 }) => { const [currentEmojiIndex, setCurrentEmojiIndex] = useState(0); useEffect(() => { const timer = setInterval(() => { setCurrentEmojiIndex((prev) => (prev + 1) % emojis.length); }, interval); return () => clearInterval(timer); }, [emojis, interval]); return /* @__PURE__ */ jsxs(Box, { gap: 1, children: [ /* @__PURE__ */ jsx(Text, { children: emojis[currentEmojiIndex] }), label && /* @__PURE__ */ jsx(Text, { children: label }) ] }); }; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const InitPage = ({ onBack }) => { const [questions, setQuestions] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); const [answers, setAnswers] = useState([]); const [showConfirmSubmit, setShowConfirmSubmit] = useState(false); const [showModifyInput, setShowModifyInput] = useState(false); const [modifyInputValue, setModifyInputValue] = useState(""); const [editingQuestionIndex, setEditingQuestionIndex] = useState(-1); const [isProcessing, setIsProcessing] = useState(false); useEffect(() => { const loadQuestions = async () => { try { const possiblePaths = [ path.join(__dirname, "..", "src", "init", "generated", "questions.json"), // From dist to src/init path.join(path.dirname(__dirname), "src", "init", "generated", "questions.json"), // Alternative dist path path.resolve("./src/init/generated/questions.json"), // Absolute from project root "/Users/leo/Documents/GitHub/rina/src/init/generated/questions.json" // Fallback absolute path ]; let data = null; for (const jsonPath of possiblePaths) { try { data = await fs.readFile(jsonPath, "utf-8"); break; } catch (err) { } } if (!data) { throw new Error("Questions file not found in any expected location"); } const parsed = JSON.parse(data); setQuestions(parsed); setLoading(false); } catch (err) { setError(`Failed to load questions: ${err.message}`); setLoading(false); } }; loadQuestions(); }, []); const handlePrevious = () => { if (currentQuestionIndex > 0) { setCurrentQuestionIndex((prev) => prev - 1); setEditingQuestionIndex(-1); } }; const handleCompositeSubmit = (answer) => { if (answer.startsWith("/")) { const questionNum = parseInt(answer.substring(1)); if (questionNum >= 1 && questionNum <= questions.totalQuestions) { setCurrentQuestionIndex(questionNum - 1); setEditingQuestionIndex(questionNum - 1); return; } } setIsProcessing(true); const wasReAnswering = answers.some( (ans) => ans.question === questions.questions[currentQuestionIndex].question ); setTimeout(() => { setAnswers((prev) => { const newAnswers = [...prev]; const existingIndex = newAnswers.findIndex( (ans) => ans.question === questions.questions[currentQuestionIndex].question ); if (existingIndex >= 0) { newAnswers[existingIndex] = { question: questions.questions[currentQuestionIndex].question, answer }; } else { newAnswers.push({ question: questions.questions[currentQuestionIndex].question, answer }); } return newAnswers; }); setEditingQuestionIndex(-1); setIsProcessing(false); if (wasReAnswering) { const answeredQuestions = new Set(answers.map((ans) => ans.question)); answeredQuestions.add(questions.questions[currentQuestionIndex].question); for (let i = 0; i < questions.questions.length; i++) { if (!answeredQuestions.has(questions.questions[i].question)) { setCurrentQuestionIndex(i); return; } } setShowConfirmSubmit(true); } else { if (currentQuestionIndex < questions.questions.length - 1) { setCurrentQuestionIndex((prev) => prev + 1); } else { setShowConfirmSubmit(true); } } }, 2e3); }; const handleConfirmSubmit = (confirmed) => { { if (onBack) onBack(); } }; const handleModifyInput = (value) => { if (value.trim()) { if (value.startsWith("/")) { const questionNum2 = parseInt(value.substring(1)); if (questionNum2 >= 1 && questionNum2 <= questions.totalQuestions) { setCurrentQuestionIndex(questionNum2 - 1); setEditingQuestionIndex(questionNum2 - 1); setShowModifyInput(false); return; } } const questionNum = parseInt(value); if (questionNum >= 1 && questionNum <= questions.totalQuestions) { setCurrentQuestionIndex(questionNum - 1); setEditingQuestionIndex(questionNum - 1); setShowModifyInput(false); } } }; if (loading) { return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "Loading questions..." }) }); } if (error) { return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { color: "red", children: error }) }); } if (showConfirmSubmit) { return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [ /* @__PURE__ */ jsx(Box, { marginBottom: 2, children: /* @__PURE__ */ jsx(Text, { color: "green", bold: true, children: "🎉 All questions completed!" }) }), /* @__PURE__ */ jsxs( Box, { paddingX: 2, paddingY: 1, borderColor: "white", borderStyle: "round", marginBottom: 2, flexDirection: "column", children: [ /* @__PURE__ */ jsx(Text, { color: "yellow", children: "Your answers:" }), answers.map((ans, idx) => { const questionIndex = questions.questions.findIndex((q) => q.question === ans.question); const isEditing = editingQuestionIndex === questionIndex; return /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [ /* @__PURE__ */ jsxs(Text, { color: "white", children: [ "Q", idx + 1, ": ", ans.question ] }), /* @__PURE__ */ jsxs(Text, { color: "gray", children: [ "A", idx + 1, ": ", isEditing ? "[editing]" : ans.answer ] }) ] }, idx); }) ] } ), isProcessing && /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(EmojiSpinner, { label: "Aura is thinking..." }) }), /* @__PURE__ */ jsxs(Box, { columnGap: 1, marginBottom: 2, children: [ /* @__PURE__ */ jsx(Text, { children: "🌀" }), /* @__PURE__ */ jsx(Text, { bold: true, children: "Ready to submit?" }) ] }), /* @__PURE__ */ jsx( CompositeInput, { options: [ { label: "Yes,submit for deep analysis", value: "submit" }, { label: "No, modify answers...", value: "modify" } ], onSubmit: (value) => { setIsProcessing(true); setTimeout(() => { if (value === "submit") { handleConfirmSubmit(); } else if (value === "modify") { setShowConfirmSubmit(false); setShowModifyInput(true); setModifyInputValue(""); } setIsProcessing(false); }, 2e3); }, onBack, isReview: true, showTypeYourAnswer: false, directSubmitValues: ["submit", "modify"] } ) ] }); } if (showModifyInput) { return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [ answers.length > 0 && /* @__PURE__ */ jsxs(Box, { marginBottom: 2, flexDirection: "column", children: [ /* @__PURE__ */ jsx(Text, { color: "yellow", bold: true, children: "Your answers:" }), answers.map((ans, idx) => { const questionIndex = questions.questions.findIndex((q) => q.question === ans.question); const isEditing = editingQuestionIndex === questionIndex; return /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [ /* @__PURE__ */ jsxs(Text, { color: "white", children: [ "Q", idx + 1, ": ", ans.question ] }), /* @__PURE__ */ jsxs(Text, { color: "gray", children: [ "A", idx + 1, ": ", isEditing ? "[editing]" : ans.answer ] }) ] }, idx); }) ] }), /* @__PURE__ */ jsxs(Box, { columnGap: 1, marginBottom: 2, children: [ /* @__PURE__ */ jsx(Text, { children: "🌀" }), /* @__PURE__ */ jsx(Text, { bold: true, children: "Which question do you want to modify?" }) ] }), /* @__PURE__ */ jsx( CompositeInput, { options: [], onSubmit: handleModifyInput, placeholder: "/1, /2, etc. to jump to specific questions", initialValue: modifyInputValue, onBack, isReview: true, showTypeYourAnswer: false } ) ] }); } if (questions && questions.questions[currentQuestionIndex]) { const currentQuestion = questions.questions[currentQuestionIndex]; return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [ /* @__PURE__ */ jsxs(Box, { marginBottom: 1, flexDirection: "row", children: [ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { bold: true, children: [ "📝 Question ", currentQuestionIndex + 1, " of ", questions.totalQuestions ] }) }), /* @__PURE__ */ jsx(Spacer, {}), /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Box, { marginBottom: 1, marginRight: 2, children: (() => { { const completed = currentQuestionIndex + 1; const remaining = questions.totalQuestions - completed; return /* @__PURE__ */ jsxs(Text, { children: [ /* @__PURE__ */ jsx(Text, { color: "white", children: "🐤".repeat(completed) }), /* @__PURE__ */ jsx(Text, { color: "white", children: "🥚".repeat(remaining) }) ] }); } })() }) }) ] }), answers.length > 0 && answers.length === questions.totalQuestions && /* @__PURE__ */ jsx(Box, { marginBottom: 2, flexDirection: "column", width: "100%", children: answers.map((ans, idx) => { const questionIndex = questions.questions.findIndex((q) => q.question === ans.question); const isEditing = editingQuestionIndex === questionIndex; return /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [ /* @__PURE__ */ jsxs(Text, { color: "white", children: [ "Q", idx + 1, ": ", ans.question ] }), /* @__PURE__ */ jsxs(Text, { color: "gray", children: [ "A", idx + 1, ": ", isEditing ? "[editing]" : ans.answer ] }) ] }, idx); }) }), /* @__PURE__ */ jsxs(Box, { columnGap: 1, marginBottom: 2, children: [ /* @__PURE__ */ jsx(Text, { children: "🌀" }), /* @__PURE__ */ jsxs(Box, { flexDirection: "column", rowGap: 1, children: [ /* @__PURE__ */ jsx(Text, { bold: true, children: currentQuestion.question }), currentQuestion.explainer && /* @__PURE__ */ jsx(Text, { marginLeft: 2, dimColor: true, fontSize: 12, children: currentQuestion.explainer }) ] }) ] }), isProcessing && /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(EmojiSpinner, { label: "Processing your answer..." }) }), /* @__PURE__ */ jsx( CompositeInput, { options: currentQuestion.options || [], onSubmit: handleCompositeSubmit, placeholder: "Type your answer...", initialValue: editingQuestionIndex === currentQuestionIndex ? answers.find((ans) => ans.question === currentQuestion.question)?.answer || "" : "", onBack, onPrevious: handlePrevious, isReview: answers.length === questions.totalQuestions } ) ] }); } return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { color: "red", children: "No questions available" }) }); }; const SettingsPage = ({ onBack }) => { const [mode, setMode] = useState("menu"); const [inputValue, setInputValue] = useState(""); const [currentSetting, setCurrentSetting] = useState(""); const [settings, setSettings] = useState({ avatars: { aura: "🌀", user: ">" } }); useEffect(() => { loadSettings(); }, []); const loadSettings = async () => { try { const data = await fs.readFile("./.aura.json", "utf-8"); setSettings(JSON.parse(data)); } catch { } }; const saveSettings = async (newSettings) => { try { await fs.writeFile("./.aura.json", JSON.stringify(newSettings, null, 2)); } catch (error) { console.error("Failed to save settings:", error); } }; const menuItems = [ { label: `${settings.avatars.aura} Set Aura avatar`, value: "setAura" }, { label: `${settings.avatars.user} Set User avatar`, value: "setUser" }, { label: "Reset to defaults", value: "reset" }, { label: "Exit", value: "exit" } ]; const handleSelect = async (item) => { if (item.value === "exit") { onBack(); } else if (item.value === "reset") { const defaultSettings = { avatars: { aura: "🌀", user: ">" } }; await saveSettings(defaultSettings); setSettings(defaultSettings); onBack(); } else { setCurrentSetting(item.value); setMode("input"); setInputValue(""); } }; const handleAvatarSubmit = async (value) => { if (value && value.length > 0) { const char = value.trim(); const newSettings = { ...settings }; if (currentSetting === "setAura") { newSettings.avatars.aura = char; } else if (currentSetting === "setUser") { newSettings.avatars.user = char; } await saveSettings(newSettings); setSettings(newSettings); setMode("menu"); setInputValue(""); } }; if (mode === "input") { return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [ /* @__PURE__ */ jsx(Box, { paddingY: 1, paddingX: 2, borderColor: "blue", borderStyle: "round", width: "100%", children: /* @__PURE__ */ jsxs(Text, { children: [ /* @__PURE__ */ jsx(Text, { bold: true, children: "Settings" }), " - Avatar Configuration" ] }) }), /* @__PURE__ */ jsxs(Box, { marginTop: 2, flexDirection: "column", children: [ /* @__PURE__ */ jsx(Text, { children: currentSetting === "setAura" ? "Enter new Aura avatar (emoji or text):" : "Enter new User avatar (emoji or text):" }), /* @__PURE__ */ jsxs(Box, { marginTop: 1, paddingX: 1, borderColor: "gray", borderStyle: "single", children: [ /* @__PURE__ */ jsx(Text, { color: "blue", children: "> " }), /* @__PURE__ */ jsx( TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleAvatarSubmit, placeholder: "Enter avatar (e.g., 🐤, >, 👾)...", focus: true } ) ] }), /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press Enter to save, or Ctrl+C to cancel" }) }) ] }) ] }); } return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [ /* @__PURE__ */ jsx(Box, { paddingY: 1, paddingX: 2, borderColor: "blue", borderStyle: "round", width: "100%", children: /* @__PURE__ */ jsxs(Text, { children: [ /* @__PURE__ */ jsx(Text, { bold: true, children: "Settings" }), " - Avatar Configuration" ] }) }), /* @__PURE__ */ jsxs(Box, { marginTop: 2, flexDirection: "column", children: [ /* @__PURE__ */ jsx(Text, { color: "yellow", children: "Select an option:" }), /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(SelectInput, { items: menuItems, onSelect: handleSelect }) }) ] }), /* @__PURE__ */ jsx(Box, { marginTop: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [ "Current: Aura = ", settings.avatars.aura, " User = ", settings.avatars.user ] }) }) ] }); }; dotenv.config({ path: ".env.local" }); dotenv.config(); class ChatService { constructor() { this.apiKey = process.env.OPENAI_API_KEY; this.model = process.env.AI_MODEL || "gpt-4o-mini"; this.temperature = parseFloat(process.env.AI_TEMPERATURE || "0.7"); this.maxTokens = parseInt(process.env.AI_MAX_TOKENS || "200"); } async checkAuraConfig() { try { await fs.access("./aura.md"); return true; } catch { return false; } } async getSystemPrompt() { const hasConfig = await this.checkAuraConfig(); if (hasConfig) { try { const auraContent = await fs.readFile("./aura.md", "utf-8"); return `You are Aura, an AI marketing strategist. You have analyzed the user's product and created a personalized marketing strategy. Product Analysis: ${auraContent} Provide specific, actionable marketing advice based on this product analysis. Be concise and practical.`; } catch (error) { console.error("Error reading aura.md:", error); } } return `You are Aura, an AI marketing strategist. The user hasn't run product analysis yet. Provide general marketing advice and encourage them to run /init for personalized insights. Keep responses concise and actionable.`; } async sendMessage(messages) { if (!this.apiKey || this.apiKey === "your_openai_api_key_here") { throw new Error("OpenAI API key not configured. Please set OPENAI_API_KEY in .env file"); } try { const systemPrompt = await this.getSystemPrompt(); const result = await streamText({ model: openai(this.model), system: systemPrompt, messages, temperature: this.temperature, maxTokens: this.maxTokens }); return result; } catch (error) { console.error("Error calling OpenAI API:", error); throw error; } } } const chatService = new ChatService(); const App = () => { const [inputValue, setInputValue] = useState(""); const [exitPrompt, setExitPrompt] = useState(false); const [currentRoute, setCurrentRoute] = useState("home"); const [hasAuraConfig, setHasAuraConfig] = useState(false); const [settings, setSettings] = useState({ avatars: { aura: "🌀", user: ">" } }); const lastCtrlC = useRef(0); const { exit } = useApp(); const [messages, setMessages] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { const checkAuraConfig = async () => { try { await fs.access("./aura.md"); setHasAuraConfig(true); } catch { setHasAuraConfig(false); } }; const loadSettings = async () => { try { const data = await fs.readFile("./.aura.json", "utf-8"); setSettings(JSON.parse(data)); } catch { } }; checkAuraConfig(); loadSettings(); }, []); const availableCommands = [ { name: "init", description: "Analyze my product and generate a marketing strategy" }, { name: "today", description: "[DEMO] Check today's marketing report and recommended actions" }, { name: "post", description: "[DEMO] Tweet, blog post, email, short video, etc" }, { name: "settings", description: "Manage your Aura settings" }, { name: "claude-setup", description: "Make Aura a sub-agent of Claude" } ]; const handleClearInput = () => { setInputValue(""); setExitPrompt(false); }; const handleExit = () => { const now = Date.now(); if (now - lastCtrlC.current < 2e3) { exit(); } else { setExitPrompt(true); lastCtrlC.current = now; setTimeout(() => { setExitPrompt(false); }, 2e3); } }; const handleSubmit = (value) => { if (value.trim()) { const trimmedValue = value.trim(); if (trimmedValue.startsWith("/")) { const command = trimmedValue.substring(1).toLowerCase(); if (command === "init") { setCurrentRoute("init"); } else if (command === "settings") { setCurrentRoute("settings"); } setInputValue(""); } else { const userMessage = { id: Date.now().toString(), role: "user", content: trimmedValue }; setMessages((prev) => [...prev, userMessage]); setError(null); setIsLoading(true); chatService.sendMessage([...messages, userMessage]).then(async (stream) => { let fullContent = ""; const assistantMessageId = (Date.now() + 1).toString(); setMessages((prev) => [...prev, { id: assistantMessageId, role: "assistant", content: "" }]); for await (const chunk of stream.textStream) { fullContent += chunk; setMessages((prev) => prev.map( (msg) => msg.id === assistantMessageId ? { ...msg, content: fullContent } : msg )); } setIsLoading(false); }).catch((err) => { console.error("Chat error:", err); setError(err.message); if (err.message.includes("API key not configured")) { setMessages((prev) => [...prev, { id: (Date.now() + 1).toString(), role: "assistant", content: "⚠️ OpenAI API key not configured. Please set OPENAI_API_KEY in your .env file to enable AI chat." }]); } else { setMessages((prev) => [...prev, { id: (Date.now() + 1).toString(), role: "assistant", content: hasAuraConfig ? "Based on your product analysis, I recommend focusing on content marketing to build authority in your niche. Start with educational blog posts that solve your target audience's pain points." : "I'm using demo analysis results. Run /init to analyze your product for personalized marketing insights! Meanwhile, I can help with general marketing strategies." }]); } setIsLoading(false); }); setInputValue(""); } setExitPrompt(false); } }; const handleBack = () => { setCurrentRoute("home"); }; if (currentRoute === "init") { return /* @__PURE__ */ jsx(InitPage, { onBack: handleBack }); } if (currentRoute === "settings") { return /* @__PURE__ */ jsx(SettingsPage, { onBack: () => { const loadSettings = async () => { try { const data = await fs.readFile("./.aura.json", "utf-8"); setSettings(JSON.parse(data)); } catch { } }; loadSettings(); handleBack(); } }); } return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [ /* @__PURE__ */ jsx(Box, { paddingY: 1, paddingX: 2, borderColor: "blue", borderStyle: "round", width: "100%", children: /* @__PURE__ */ jsxs(Text, { children: [ "🌀 Welcome to ", /* @__PURE__ */ jsx(Text, { bold: true, children: "Aura!" }), /* @__PURE__ */ jsx(Newline, {}), /* @__PURE__ */ jsx(Newline, {}), /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: "Your AI marketing companion" }) ] }) }), /* @__PURE__ */ jsxs(Box, { marginTop: 1, paddingLeft: 1, flexDirection: "column", children: [ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Tips for getting started:" }), /* @__PURE__ */ jsx(Newline, {}), /* @__PURE__ */ jsx(Text, { color: "gray", children: " 1. Run /init to analyze your product and create a marketing strategy" }), /* @__PURE__ */ jsx(Text, { color: "gray", children: " 2. Chat directly with Aura for marketing insights and recommendations" }), /* @__PURE__ */ jsx(Text, { color: "gray", children: " 3. Be as specific as you would with a marketing expert for best results" }), /* @__PURE__ */ jsx(Text, { color: "gray", children: " 4. ✓ View available commands by typing /" }) ] }), !hasAuraConfig && messages.length > 0 && /* @__PURE__ */ jsx(Static, { items: [{ id: "demo-alert" }], children: () => /* @__PURE__ */ jsx(Box, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx(Alert, { variant: "warning", children: "You're chatting with demo analysis results. Run /init for personalized insights." }) }) }), error && /* @__PURE__ */ jsx(Static, { items: [{ id: "error-alert" }], children: () => /* @__PURE__ */ jsx(Box, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx(Alert, { variant: "error", children: error }) }) }), messages.length > 0 && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 2, marginBottom: 1, paddingLeft: 1, children: [ messages.filter((m) => m.role !== "system").map((msg) => /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsxs(Text, { children: [ msg.role === "user" ? `${settings.avatars.user} ` : `${settings.avatars.aura} `, msg.role === "user" ? /* @__PURE__ */ jsx(Text, { children: msg.content }) : /* @__PURE__ */ jsx(Text, { color: "white", children: msg.content }) ] }) }, msg.id)), isLoading && /* @__PURE__ */ jsx(EmojiSpinner, { label: "Aura is thinking..." }) ] }), /* @__PURE__ */ jsx(Box, { marginTop: messages.length === 0 ? 2 : 1, children: /* @__PURE__ */ jsx( CommandInput, { value: inputValue, onChange: setInputValue, onSubmit: handleSubmit, placeholder: "Type a command or message...", commands: availableCommands, exitPrompt, onClearInput: handleClearInput, onExit: handleExit } ) }) ] }); }; const { waitUntilExit } = render(/* @__PURE__ */ jsx(App, {}), { exitOnCtrlC: false // Disable automatic exit on Ctrl+C }); waitUntilExit(); //# sourceMappingURL=rina.js.map