UNPKG

@profullstack/therapy

Version:

CLI tool for interacting with an AI therapist

174 lines (144 loc) 5.34 kB
#!/usr/bin/env node import { program } from 'commander'; import chalk from 'chalk'; import ora from 'ora'; import fs from 'fs'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import readline from 'readline'; import { getPromptForMode } from './lib/prompts.js'; import { callAI } from './lib/ai-providers.js'; // Get package version without importing JSON directly const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const packageJson = JSON.parse(fs.readFileSync(join(__dirname, 'package.json'), 'utf8')); const version = packageJson.version; // Configure the CLI program program .name('therapy') .description('Interactive AI therapy session in your terminal') .version(version) .option('-m, --mode <mode>', 'therapy mode (cbt, person, trauma)', 'cbt') .option('-p, --provider <provider>', 'AI provider (openai, ollama)', process.env.PROVIDER || 'openai') .option('--model <model>', 'specific model to use (defaults based on provider)') .option('-v, --verbose', 'show detailed logs') .action((options) => { console.log(chalk.cyan('🧠 ') + chalk.bold('AI Therapy Session')); console.log(chalk.dim(`Mode: ${options.mode.toUpperCase()} | Provider: ${options.provider.toUpperCase()}\n`)); try { startTherapySession(options); } catch (error) { console.error(chalk.red('Error:'), error.message); process.exit(1); } }); // Add help examples program.addHelpText('after', ` Examples: $ therapy Start a CBT session with default provider $ therapy --mode trauma Start a trauma-informed session $ therapy -m person -p openai Start a person-centered session with OpenAI `); // Parse command line arguments program.parse(); /** * Start a therapy session */ function startTherapySession(options) { const { mode = 'cbt', provider = 'openai', model, verbose = false } = options; // Initialize the chat history with the system prompt const systemPrompt = getPromptForMode(mode); const chatHistory = [{ role: 'system', content: systemPrompt }]; // Extract initial greeting from the prompt const initialPrompt = systemPrompt; const startMessage = initialPrompt.split('Begin').pop().split('"')[1] || "I'm here to support you. What's been on your mind lately?"; console.log(chalk.cyan(`\nAI: ${startMessage}\n`)); // Create config object const config = { provider, model, verbose }; // Check if input is being piped in const isPiped = !process.stdin.isTTY; if (isPiped) { // Handle piped input (e.g., from echo) let pipeInput = ''; process.stdin.on('data', (chunk) => { pipeInput += chunk; }); process.stdin.on('end', async () => { if (pipeInput.trim()) { console.log(chalk.green(`You: ${pipeInput.trim()}`)); // Add user message to history chatHistory.push({ role: 'user', content: pipeInput.trim() }); // Start spinner startSpinner('🧠 AI is thinking'); try { // Get AI response const reply = await callAI(chatHistory, config); // Stop spinner and display response stopSpinner(); console.log(chalk.cyan(`\n🧠 AI: ${reply}\n`)); } catch (err) { stopSpinner(); console.error(chalk.red('❌ Error:'), err.message); } } else { console.log(chalk.yellow('No input provided.')); } }); } else { // Interactive mode const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); // Start the conversation chatWithAI(rl, chatHistory, config); } } /** * Spinner utility */ let spinnerInterval; const spinnerFrames = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏']; let spinnerIndex = 0; function startSpinner(text = 'Thinking') { process.stdout.write(`${text} `); spinnerInterval = setInterval(() => { process.stdout.write(`\r${text} ${spinnerFrames[spinnerIndex++ % spinnerFrames.length]} `); }, 80); } function stopSpinner() { clearInterval(spinnerInterval); process.stdout.write('\r\x1b[K'); // clear line } /** * Main conversation function - uses callback pattern like the original */ function chatWithAI(rl, chatHistory, config) { rl.question(chalk.green('You: '), async (input) => { // Check for exit commands if (['exit', 'quit', 'bye'].includes(input.toLowerCase())) { console.log(chalk.cyan('\nThank you for the conversation. Take care! 👋')); rl.close(); return; } // Add user message to history chatHistory.push({ role: 'user', content: input }); // Start spinner startSpinner('🧠 AI is thinking'); try { // Get AI response const reply = await callAI(chatHistory, config); // Stop spinner and display response stopSpinner(); console.log(chalk.cyan(`\n🧠 AI: ${reply}\n`)); // Add AI response to history chatHistory.push({ role: 'assistant', content: reply }); } catch (err) { stopSpinner(); console.error(chalk.red('❌ Error:'), err.message); } // Continue the conversation chatWithAI(rl, chatHistory, config); }); }