aura-ai
Version:
AI-powered marketing strategist CLI tool for developers
730 lines (690 loc) ⢠23.4 kB
JavaScript
#!/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: sync");
console.log(" 2. Type: help me with marketing");
console.log(" 3. Type: 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 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