UNPKG

aura-ai

Version:

AI-powered marketing strategist CLI tool for developers

730 lines (690 loc) • 23.4 kB
#!/usr/bin/env -S node --no-warnings import { Command } from "commander"; import { existsSync, readFileSync } from "fs"; import { fileURLToPath } from "url"; import path, { dirname, join } from "path"; import dotenv from "dotenv"; import fs from "fs/promises"; import { g as gitService, s as syncAIService, c as chatService } from "./chatService-7b3Ru5ZZ.js"; import { exec } from "child_process"; import { promisify } from "util"; import os from "os"; async function initCommand(options) { const { answers, file, force, json, quiet, interactive } = options; try { if (existsSync("aura.md") && !force) { const error = "Product already analyzed. Use --force to overwrite."; if (json) { console.log(JSON.stringify({ status: "error", error })); process.exit(1); } else { console.error(`āŒ ${error}`); process.exit(1); } } let productAnswers = []; if (answers) { productAnswers = answers.split("|").map((a) => a.trim()); if (productAnswers.length !== 3) { throw new Error("Must provide exactly 3 answers separated by |"); } } else if (file) { const content = await fs.readFile(file, "utf-8"); productAnswers = content.split("\n").filter((line) => line.trim()).slice(0, 3); if (productAnswers.length !== 3) { throw new Error("File must contain exactly 3 lines of answers"); } } else if (!interactive) { throw new Error("Answers required in non-interactive mode. Use --answers or --file"); } else { if (!quiet) { console.log("Launching interactive product analysis..."); } const { runInitInteractive } = await import("./interactive-DdmguDCB.js"); await runInitInteractive(); if (json) { console.log(JSON.stringify({ status: "success", message: "Product analysis completed", file: "aura.md" })); } return; } const [description, audience, value] = productAnswers; if (!quiet && !json) { console.log("šŸ” Analyzing your product..."); } const analysis = await generateAnalysis(description, audience, value); await fs.writeFile("aura.md", analysis); if (json) { console.log("\n" + "=".repeat(60)); console.log("šŸŽÆ PRODUCT MARKETING ANALYSIS"); console.log("=".repeat(60)); console.log("\nšŸ“ Product:", description); console.log("šŸ‘„ Audience:", audience); console.log("šŸ’” Value:", value); console.log("\n" + "-".repeat(60)); console.log("\nšŸ“Š Generated Marketing Strategy:\n"); console.log(analysis); console.log("\n" + "=".repeat(60)); console.log("āœ… Analysis saved to aura.md"); console.log("=".repeat(60)); } else if (!quiet) { console.log("āœ… Product analysis completed!"); console.log("šŸ“„ Marketing strategy saved to: aura.md"); console.log("\nYour product profile:"); console.log(` • Product: ${description}`); console.log(` • Audience: ${audience}`); console.log(` • Value: ${value}`); } } catch (error) { if (json) { console.log(JSON.stringify({ status: "error", error: error.message })); process.exit(1); } else { console.error(`āŒ Error: ${error.message}`); process.exit(1); } } } async function generateAnalysis(description, audience, value) { const { openai } = await import("@ai-sdk/openai"); const { generateText } = await import("ai"); const prompt = `Create a comprehensive marketing strategy for: Product: ${description} Target Audience: ${audience} Value Proposition: ${value} Generate a detailed marketing analysis including: 1. Product positioning 2. Target market analysis 3. Marketing channels 4. Growth strategies 5. Key messaging 6. Competitive advantages Format as markdown with clear sections.`; try { const result = await generateText({ model: openai("gpt-4o-mini"), prompt, temperature: 0.7, maxTokens: 1e3 }); return `# Product Marketing Analysis ## Product Overview **Product:** ${description} **Target Audience:** ${audience} **Value Proposition:** ${value} --- ${result.text}`; } catch (error) { return `# Product Marketing Analysis ## Product Overview **Product:** ${description} **Target Audience:** ${audience} **Value Proposition:** ${value} ## Marketing Strategy This analysis will be enhanced with AI insights when API is configured. ### Target Market Your target audience is: ${audience} ### Value Proposition Key benefit: ${value} ### Next Steps 1. Define your unique selling points 2. Research competitor positioning 3. Identify primary marketing channels 4. Create messaging framework 5. Develop content strategy`; } } const execAsync = promisify(exec); async function syncCommand(options) { const { raw, since, copy, json, quiet } = options; try { if (!quiet && !json && !raw) { console.log("šŸ”„ Generating AI progress report..."); } let commits; if (since === "today") { commits = await gitService.getTodayCommits(); } else { commits = await getCommitsSince(since); } if (commits.length === 0) { const message = "No commits found for the specified period."; if (json) { console.log(JSON.stringify({ status: "success", data: { summary: message, highlights: [], markdown: `# Daily Progress Report ${message}`, commit_count: 0 } })); } else if (!raw) { console.log(`ā„¹ļø ${message}`); } return; } const formattedCommits = gitService.formatCommitsForAI(commits); const report = await syncAIService.generateDailyReport(formattedCommits); const today = /* @__PURE__ */ new Date(); const year = today.getFullYear(); const month = today.getMonth() + 1; const day = today.getDate(); const filename = `${year}-${month}-${day}-update.md`; try { await fs.writeFile(filename, report.markdown); if (!quiet && !raw) { console.log(`šŸ“„ Report saved to: ${filename}`); } } catch (err) { if (!quiet) { console.log(`āš ļø Could not save report to file: ${err.message}`); } } if (raw) { console.log(report.markdown); } else if (json) { console.log("\n" + "=".repeat(60)); console.log("šŸ“Š AI DAILY SYNC REPORT"); console.log("=".repeat(60) + "\n"); console.log(report.markdown); console.log("\n" + "=".repeat(60)); try { await copyToClipboard(report.markdown); console.log("āœ… Report automatically copied to clipboard!"); } catch (err) { console.log("āš ļø Could not copy to clipboard:", err.message); } console.log("\nšŸ“ˆ Summary:", report.summary); console.log(`šŸ“ Based on ${commits.length} commits`); console.log("šŸ• Generated at:", (/* @__PURE__ */ new Date()).toLocaleString()); console.log(`šŸ“„ Saved to: ${filename}`); console.log("=".repeat(60)); } else { console.log("\nšŸ“Š Daily Progress Report"); console.log("=".repeat(50)); console.log(` šŸ’« ${report.summary} `); if (report.highlights && report.highlights.length > 0) { console.log("✨ Key Achievements:"); report.highlights.forEach((h) => console.log(` • ${h}`)); } console.log(` šŸ“ˆ Based on ${commits.length} commits today`); } if (copy && report.markdown && !json) { await copyToClipboard(report.markdown); if (!quiet) { console.log("\nāœ… Report copied to clipboard!"); } } } catch (error) { if (json) { console.log(JSON.stringify({ status: "error", error: error.message })); process.exit(1); } else { console.error(`āŒ Error: ${error.message}`); process.exit(1); } } } async function getCommitsSince(since) { try { const { stdout } = await execAsync( `git log --since="${since}" --format="%H|%s|%an|%ai"` ); if (!stdout.trim()) { return []; } return stdout.trim().split("\n").map((line) => { const [hash, message, author, date] = line.split("|"); return { hash: hash.substring(0, 7), message, author, date: new Date(date).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" }) }; }); } catch (error) { console.error("Error fetching commits:", error); return []; } } async function copyToClipboard(text) { const platform = process.platform; try { if (platform === "darwin") { await execAsync(`echo "${text.replace(/"/g, '\\"').replace(/\$/g, "\\$")}" | pbcopy`); } else if (platform === "linux") { await execAsync(`echo "${text.replace(/"/g, '\\"').replace(/\$/g, "\\$")}" | xclip -selection clipboard`); } else if (platform === "win32") { await execAsync(`echo "${text.replace(/"/g, '\\"').replace(/\$/g, "\\$")}" | clip`); } } catch (error) { throw new Error(`Failed to copy to clipboard: ${error.message}`); } } async function chatCommand(question, options) { const { context, stdin, json, quiet } = options; try { let userQuestion = question; if (stdin) { userQuestion = await readStdin(); if (!userQuestion) { throw new Error("No input received from stdin"); } } else if (!userQuestion) { throw new Error("Question required. Use --stdin to read from input."); } let additionalContext = ""; if (context) { additionalContext = readFileSync(context, "utf-8"); } const messages = []; if (additionalContext) { messages.push({ role: "system", content: `Additional context: ${additionalContext}` }); } messages.push({ role: "user", content: userQuestion }); if (!quiet && !json) { console.log("šŸ¤” Thinking..."); } const stream = await chatService.sendMessage(messages); let fullResponse = ""; if (json) { console.log("\n" + "=".repeat(60)); console.log("šŸ’¬ AURA MARKETING CONSULTATION"); console.log("=".repeat(60)); console.log("\nšŸ“ Question:", userQuestion); console.log("\nšŸŒ€ Answer:\n"); for await (const chunk of stream.textStream) { process.stdout.write(chunk); fullResponse += chunk; } console.log("\n\n" + "=".repeat(60)); console.log("āœ… Consultation complete"); console.log("=".repeat(60)); } else { if (!quiet) { console.log("\nšŸŒ€ Aura:"); } for await (const chunk of stream.textStream) { process.stdout.write(chunk); fullResponse += chunk; } console.log("\n"); } } catch (error) { if (json) { console.log(JSON.stringify({ status: "error", error: error.message })); process.exit(1); } else { console.error(`āŒ Error: ${error.message}`); process.exit(1); } } } function readStdin() { return new Promise((resolve, reject) => { let data = ""; process.stdin.setEncoding("utf8"); process.stdin.on("data", (chunk) => { data += chunk; }); process.stdin.on("end", () => { resolve(data.trim()); }); process.stdin.on("error", reject); setTimeout(() => { if (!data) { process.stdin.destroy(); resolve(""); } }, 100); }); } const SETTINGS_FILE = ".aura.json"; const DEFAULT_SETTINGS = { avatars: { aura: "šŸŒ€", user: ">" } }; async function settingsCommand(options) { const { list, setAura, setUser, reset, json, quiet } = options; try { let settings = await loadSettings(); if (reset) { settings = { ...DEFAULT_SETTINGS }; await saveSettings(settings); if (json) { console.log(JSON.stringify({ status: "success", message: "Settings reset to defaults", data: settings })); } else if (!quiet) { console.log("āœ… Settings reset to defaults"); } return; } let changed = false; if (setAura) { settings.avatars.aura = setAura; changed = true; } if (setUser) { settings.avatars.user = setUser; changed = true; } if (changed) { await saveSettings(settings); if (json) { console.log(JSON.stringify({ status: "success", message: "Settings updated", data: settings })); } else if (!quiet) { console.log("āœ… Settings updated successfully"); console.log(` Aura avatar: ${settings.avatars.aura}`); console.log(` User avatar: ${settings.avatars.user}`); } } else if (list || !setAura && !setUser && !reset) { if (json) { console.log("\n" + "=".repeat(60)); console.log("āš™ļø AURA SETTINGS"); console.log("=".repeat(60)); console.log("\nšŸ“‹ Current Configuration:"); console.log(` Aura avatar: ${settings.avatars.aura}`); console.log(` User avatar: ${settings.avatars.user}`); console.log("\n" + "=".repeat(60)); } else { console.log("šŸ“‹ Current Settings:"); console.log(` Aura avatar: ${settings.avatars.aura}`); console.log(` User avatar: ${settings.avatars.user}`); } } } catch (error) { if (json) { console.log(JSON.stringify({ status: "error", error: error.message })); process.exit(1); } else { console.error(`āŒ Error: ${error.message}`); process.exit(1); } } } async function loadSettings() { try { if (existsSync(SETTINGS_FILE)) { const data = await fs.readFile(SETTINGS_FILE, "utf-8"); return JSON.parse(data); } } catch (error) { } return { ...DEFAULT_SETTINGS }; } async function saveSettings(settings) { await fs.writeFile(SETTINGS_FILE, JSON.stringify(settings, null, 2)); } const __filename$1 = fileURLToPath(import.meta.url); dirname(__filename$1); async function addAuraAgentCommand(options) { const { json, quiet } = options; try { const homeDir = os.homedir(); const claudeAgentsDir = path.join(homeDir, ".claude", "agents"); if (!quiet && !json) { console.log("šŸ” Checking Claude agents directory..."); } try { await fs.access(claudeAgentsDir); } catch { if (!quiet && !json) { console.log("šŸ“ Creating Claude agents directory..."); } await fs.mkdir(claudeAgentsDir, { recursive: true }); } const auraAgentSource = `--- name: aura description: Marketing AI agent that uses Aura CLI for product analysis, daily reports, and marketing insights tools: Bash model: sonnet color: cyan --- You are Aura, a marketing AI assistant that uses the Aura CLI tool via npx. ## Quick Command Reference \`\`\`bash # Daily sync report (saves file, prints, copies to clipboard) npx aura-ai sync --json # Marketing consultation npx aura-ai chat "your question" --json # Product analysis (one-time setup) npx aura-ai init --answers "product|audience|value" --json # View settings npx aura-ai settings --list --json \`\`\` ## How to Respond 1. **User says "sync"** → Run: \`npx aura-ai sync --json\` - Creates \`YYYY-M-D-update.md\` file automatically - Prints full report to terminal - Auto-copies to clipboard - Tell user: "Report generated, saved to [filename], and copied to clipboard" 2. **User asks marketing question** → Run: \`npx aura-ai chat "question" --json\` 3. **User wants product analysis** → Check if aura.md exists, if not run: \`npx aura-ai init --answers "..." --json\` ## Important - No installation needed! Uses npx to run directly - Sync command creates dated markdown files (e.g., \`2025-8-8-update.md\`) - All content is visible in terminal output - Reports are automatically saved and copied - Just run commands, don't read files`; const targetPath = path.join(claudeAgentsDir, "aura.md"); await fs.writeFile(targetPath, auraAgentSource); if (json) { console.log(JSON.stringify({ status: "success", data: { installed_to: targetPath, agent_name: "aura", message: "Aura agent successfully installed to Claude" } })); } else if (!quiet) { console.log("\nāœ… Aura agent successfully installed!"); console.log(`šŸ“„ Location: ${targetPath}`); console.log("\nāš ļø IMPORTANT: Please restart Claude to load the new agent"); console.log("\nšŸŽÆ After restarting Claude, you can use:"); console.log(" 1. Type: @aura sync"); console.log(" 2. Type: @aura help me with marketing"); console.log(" 3. Type: @aura analyze my product"); console.log("\nšŸ’” The agent will automatically use Aura CLI commands"); } } catch (error) { if (json) { console.log(JSON.stringify({ status: "error", error: error.message })); process.exit(1); } else { console.error(`āŒ Error: ${error.message}`); process.exit(1); } } } dotenv.config({ path: ".env.local" }); dotenv.config(); const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8")); const program = new Command(); program.name("aura").description("Aura AI - Marketing Intelligence CLI for Developers").version(packageJson.version).option("-j, --json", "Output in JSON format for agents").option("-q, --quiet", "Minimal output, no decorations").addHelpText("after", ` Examples: $ aura init --answers "SaaS tool|developers|saves time" $ aura sync --json $ aura chat "How to price my product?" $ aura settings --set-aura "šŸ¤–" $ aura add-aura-agent For agents: Use --json flag for structured output For interactive mode: Run 'aura' without commands For Claude integration: Run 'aura add-aura-agent'`); program.command("init").description("Analyze your product and generate marketing strategy").option("-i, --interactive", "Interactive mode (default)", true).option("-a, --answers <answers>", "Provide all 3 answers separated by |").option("-f, --file <path>", "Read answers from file").option("--force", "Overwrite existing analysis").addHelpText("after", ` Examples: Interactive: $ aura init Non-interactive (for agents): $ aura init --answers "Task management app|Remote teams|Increases productivity by 40%" JSON output: $ aura init --answers "answers here" --json Note: Answers required: 1. Product description (What is your product?) 2. Target audience (Who is it for?) 3. Value proposition (What problem does it solve?)`).action(async (options) => { const globalOpts = program.opts(); await initCommand({ ...options, ...globalOpts }); }); program.command("sync").description("Generate AI-powered daily progress report from git commits").option("--raw", "Output raw markdown only").option("--since <date>", "Analyze commits since date", "today").option("--copy", "Auto-copy to clipboard").addHelpText("after", ` Examples: Default report: $ aura sync Agent-friendly JSON: $ aura sync --json Raw markdown output: $ aura sync --raw > report.md Output format (--json): { "status": "success", "data": { "summary": "Executive summary", "highlights": ["achievement1", "achievement2"], "markdown": "Full report in markdown" } }`).action(async (options) => { const globalOpts = program.opts(); await syncCommand({ ...options, ...globalOpts }); }); program.command("chat").description("Ask marketing questions to AI strategist").argument("[question]", "Your marketing question").option("--context <file>", "Include additional context from file").option("--system <prompt>", "Override system prompt").option("--max-tokens <number>", "Maximum response tokens", "500").option("--stdin", "Read question from stdin").addHelpText("after", ` Examples: Single question: $ aura chat "How should I price my SaaS?" With JSON output: $ aura chat "Marketing strategy?" --json From stdin (for piping): $ echo "How to reach developers?" | aura chat --stdin Multiple questions from file: $ cat questions.txt | aura chat --stdin Output format (--json): { "status": "success", "data": { "question": "Your question", "answer": "AI response", "tokens_used": 245 } }`).action(async (question, options) => { const globalOpts = program.opts(); await chatCommand(question, { ...options, ...globalOpts }); }); program.command("settings").description("Manage Aura configuration and preferences").option("--list", "Show current settings").option("--set-aura <avatar>", "Set Aura avatar").option("--set-user <avatar>", "Set User avatar").option("--reset", "Reset to defaults").addHelpText("after", ` Examples: View settings: $ aura settings --list Change avatars: $ aura settings --set-aura "šŸ¤–" --set-user "šŸ‘¤" Reset all: $ aura settings --reset Agent usage: $ aura settings --list --json Output format (--json): { "avatars": { "aura": "šŸŒ€", "user": ">" } }`).action(async (options) => { const globalOpts = program.opts(); await settingsCommand({ ...options, ...globalOpts }); }); program.command("add-aura-agent").description("Install Aura agent to Claude for @aura commands").addHelpText("after", ` This command will: 1. Create ~/.claude/agents/ directory if needed 2. Install aura.md agent file 3. Enable @aura commands in Claude After installation, you can use in Claude: @aura sync @aura help me with marketing @aura analyze my product Examples: $ aura add-aura-agent $ aura add-aura-agent --json`).action(async (options) => { const globalOpts = program.opts(); await addAuraAgentCommand({ ...options, ...globalOpts }); }); program.command("help [command]").description("Display help for command").action((command) => { if (command) { const cmd = program.commands.find((c) => c.name() === command); if (cmd) { cmd.help(); } else { console.error(`Unknown command: ${command}`); program.help(); } } else { program.help(); } }); program.on("command:*", () => { console.error("Invalid command: %s\nSee --help for available commands.", program.args.join(" ")); process.exit(1); }); try { const hasCommand = process.argv.slice(2).some( (arg) => !arg.startsWith("-") && ["init", "sync", "chat", "settings", "help", "add-aura-agent"].includes(arg) ); if (process.argv.length === 2 || !hasCommand && !process.argv.slice(2).includes("--help") && !process.argv.slice(2).includes("-h") && !process.argv.slice(2).includes("--version")) { console.log("Starting interactive mode..."); console.log('Use "aura --help" for CLI commands\n'); const { default: runInteractive } = await import("./rina.js"); runInteractive(); } else { await program.parseAsync(process.argv); } } catch (error) { if (program.opts().json) { console.log(JSON.stringify({ status: "error", error: error.message })); } else { console.error("Error:", error.message); } process.exit(1); } //# sourceMappingURL=cli.js.map