UNPKG

@stackmemoryai/stackmemory

Version:

Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.

307 lines (306 loc) 10.2 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import { Command } from "commander"; import chalk from "chalk"; import inquirer from "inquirer"; import { existsSync, writeFileSync, mkdirSync, readFileSync } from "fs"; import { join } from "path"; import { homedir } from "os"; import { loadSMSConfig, saveSMSConfig, getMissingConfig } from "../../hooks/sms-notify.js"; function createSettingsCommand() { const cmd = new Command("settings").description("View and configure StackMemory settings").addHelpText( "after", ` Examples: stackmemory settings Show all settings and missing config stackmemory settings notifications Configure notifications interactively stackmemory settings env Show required environment variables ` ); cmd.command("show").description("Show current settings and what is missing").action(() => { showSettings(); }); cmd.command("notifications").alias("notify").description("Configure notifications interactively").action(async () => { await configureNotifications(); }); cmd.command("env").description("Show required environment variables").action(() => { showEnvVars(); }); cmd.action(() => { showSettings(); }); return cmd; } function showSettings() { console.log(chalk.blue.bold("\nStackMemory Settings\n")); const config = loadSMSConfig(); const { missing, configured, ready } = getMissingConfig(); console.log(chalk.cyan("Notifications:")); console.log( ` ${chalk.gray("Enabled:")} ${config.enabled ? chalk.green("yes") : chalk.yellow("no")}` ); console.log( ` ${chalk.gray("Channel:")} ${config.channel === "whatsapp" ? chalk.cyan("WhatsApp") : chalk.blue("SMS")}` ); console.log( ` ${chalk.gray("Ready:")} ${ready ? chalk.green("yes") : chalk.red("no")}` ); if (configured.length > 0) { console.log(` ${chalk.green("Configured:")}`); configured.forEach((item) => { console.log(` ${chalk.green("\u2713")} ${item}`); }); } if (missing.length > 0) { console.log(` ${chalk.red("Missing:")}`); missing.forEach((item) => { console.log(` ${chalk.red("\u2717")} ${item}`); }); console.log( chalk.yellow('\n Run "stackmemory settings notifications" to configure') ); } const ngrokUrlPath = join(homedir(), ".stackmemory", "ngrok-url.txt"); if (existsSync(ngrokUrlPath)) { const ngrokUrl = readFileSync(ngrokUrlPath, "utf8").trim(); console.log(` ${chalk.gray("Webhook URL:")} ${ngrokUrl}/sms/incoming`); } console.log(); } function showEnvVars() { console.log(chalk.blue.bold("\nRequired Environment Variables\n")); const { missing, configured } = getMissingConfig(); const config = loadSMSConfig(); console.log(chalk.cyan("Twilio Credentials (required):")); console.log( ` ${configured.includes("TWILIO_ACCOUNT_SID") ? chalk.green("\u2713") : chalk.red("\u2717")} TWILIO_ACCOUNT_SID` ); console.log( ` ${configured.includes("TWILIO_AUTH_TOKEN") ? chalk.green("\u2713") : chalk.red("\u2717")} TWILIO_AUTH_TOKEN` ); console.log( chalk.cyan( ` ${config.channel === "whatsapp" ? "WhatsApp" : "SMS"} Numbers:` ) ); if (config.channel === "whatsapp") { console.log( ` ${configured.includes("TWILIO_WHATSAPP_FROM") ? chalk.green("\u2713") : chalk.red("\u2717")} TWILIO_WHATSAPP_FROM` ); console.log( ` ${configured.includes("TWILIO_WHATSAPP_TO") ? chalk.green("\u2713") : chalk.red("\u2717")} TWILIO_WHATSAPP_TO` ); } else { console.log( ` ${configured.includes("TWILIO_SMS_FROM") ? chalk.green("\u2713") : chalk.red("\u2717")} TWILIO_SMS_FROM` ); console.log( ` ${configured.includes("TWILIO_SMS_TO") ? chalk.green("\u2713") : chalk.red("\u2717")} TWILIO_SMS_TO` ); } if (missing.length > 0) { console.log(chalk.yellow("\nAdd to your .env file or shell profile:")); console.log(chalk.gray("\u2500".repeat(50))); if (missing.includes("TWILIO_ACCOUNT_SID")) { console.log('export TWILIO_ACCOUNT_SID="your_account_sid"'); } if (missing.includes("TWILIO_AUTH_TOKEN")) { console.log('export TWILIO_AUTH_TOKEN="your_auth_token"'); } if (missing.includes("TWILIO_WHATSAPP_FROM")) { console.log( 'export TWILIO_WHATSAPP_FROM="+14155238886" # Twilio sandbox' ); } if (missing.includes("TWILIO_WHATSAPP_TO")) { console.log('export TWILIO_WHATSAPP_TO="+1234567890" # Your phone'); } if (missing.includes("TWILIO_SMS_FROM")) { console.log('export TWILIO_SMS_FROM="+1234567890" # Twilio number'); } if (missing.includes("TWILIO_SMS_TO")) { console.log('export TWILIO_SMS_TO="+1234567890" # Your phone'); } console.log(chalk.gray("\u2500".repeat(50))); } console.log(); } async function configureNotifications() { console.log(chalk.blue.bold("\nNotification Setup\n")); const config = loadSMSConfig(); const { missing } = getMissingConfig(); const { enable } = await inquirer.prompt([ { type: "confirm", name: "enable", message: "Enable SMS/WhatsApp notifications?", default: config.enabled } ]); if (!enable) { config.enabled = false; saveSMSConfig(config); console.log(chalk.yellow("Notifications disabled")); return; } const { channel } = await inquirer.prompt([ { type: "list", name: "channel", message: "Which channel do you want to use?", choices: [ { name: "WhatsApp (recommended - cheaper for conversations)", value: "whatsapp" }, { name: "SMS (requires A2P 10DLC registration for US)", value: "sms" } ], default: config.channel } ]); config.channel = channel; if (missing.includes("TWILIO_ACCOUNT_SID") || missing.includes("TWILIO_AUTH_TOKEN")) { console.log(chalk.yellow("\nTwilio credentials not found in environment.")); const { hasAccount } = await inquirer.prompt([ { type: "confirm", name: "hasAccount", message: "Do you have a Twilio account?", default: true } ]); if (!hasAccount) { console.log(chalk.cyan("\nCreate a free Twilio account:")); console.log(" https://www.twilio.com/try-twilio\n"); console.log("Then run this command again."); return; } const { saveToEnv } = await inquirer.prompt([ { type: "confirm", name: "saveToEnv", message: "Would you like to save credentials to ~/.stackmemory/.env?", default: true } ]); if (saveToEnv) { const { accountSid, authToken } = await inquirer.prompt([ { type: "input", name: "accountSid", message: "Twilio Account SID:", validate: (input) => input.startsWith("AC") ? true : "Account SID should start with AC" }, { type: "password", name: "authToken", message: "Twilio Auth Token:", mask: "*" } ]); saveToEnvFile({ TWILIO_ACCOUNT_SID: accountSid, TWILIO_AUTH_TOKEN: authToken }); console.log(chalk.green("Credentials saved to ~/.stackmemory/.env")); } } if (channel === "whatsapp") { console.log(chalk.cyan("\nWhatsApp Setup:")); console.log( " 1. Go to: https://console.twilio.com/us1/develop/sms/try-it-out/whatsapp-learn" ); console.log(" 2. Note the sandbox number (e.g., +14155238886)"); console.log(" 3. Send the join code from your phone\n"); const { whatsappFrom, whatsappTo } = await inquirer.prompt([ { type: "input", name: "whatsappFrom", message: "Twilio WhatsApp number (sandbox):", default: config.whatsappFromNumber || "+14155238886" }, { type: "input", name: "whatsappTo", message: "Your phone number:", default: config.whatsappToNumber, validate: (input) => input.startsWith("+") ? true : "Include country code (e.g., +1234567890)" } ]); saveToEnvFile({ TWILIO_WHATSAPP_FROM: whatsappFrom, TWILIO_WHATSAPP_TO: whatsappTo, TWILIO_CHANNEL: "whatsapp" }); } else { console.log(chalk.cyan("\nSMS Setup:")); console.log( chalk.yellow(" Note: US carriers require A2P 10DLC registration") ); console.log( " Register at: https://console.twilio.com/us1/develop/sms/settings/compliance\n" ); const { smsFrom, smsTo } = await inquirer.prompt([ { type: "input", name: "smsFrom", message: "Twilio SMS number:", default: config.smsFromNumber }, { type: "input", name: "smsTo", message: "Your phone number:", default: config.smsToNumber, validate: (input) => input.startsWith("+") ? true : "Include country code (e.g., +1234567890)" } ]); saveToEnvFile({ TWILIO_SMS_FROM: smsFrom, TWILIO_SMS_TO: smsTo, TWILIO_CHANNEL: "sms" }); } config.enabled = true; saveSMSConfig(config); console.log(chalk.green("\nNotifications configured!")); console.log(chalk.gray("Test with: stackmemory notify test")); } function saveToEnvFile(vars) { const envDir = join(homedir(), ".stackmemory"); const envPath = join(envDir, ".env"); if (!existsSync(envDir)) { mkdirSync(envDir, { recursive: true }); } let content = ""; if (existsSync(envPath)) { content = readFileSync(envPath, "utf8"); } for (const [key, value] of Object.entries(vars)) { const regex = new RegExp(`^${key}=.*$`, "m"); const line = `${key}="${value}"`; if (regex.test(content)) { content = content.replace(regex, line); } else { content += `${content.endsWith("\n") || content === "" ? "" : "\n"}${line} `; } } writeFileSync(envPath, content); } var settings_default = createSettingsCommand; export { createSettingsCommand, settings_default as default }; //# sourceMappingURL=settings.js.map