contaigents
Version:
Modular AI Content Ecosystem with Audio Generation
353 lines (352 loc) ⢠13.5 kB
JavaScript
import { Command } from "commander";
import { Server } from "./server.js";
import open from 'open';
import { AudioGenerator } from "./services/audioGenerator.js";
import { ChatService } from "./services/chatService.js";
import { readFile } from 'fs/promises';
import path from 'path';
import dotenv from 'dotenv';
import readline from 'readline';
import { testToolArchitecture } from './tests/toolTest.js';
import { demonstrateChatWithTools } from './tests/chatWithToolsDemo.js';
import { testFileWriteTool } from './tests/fileWriteToolTest.js';
import { testCombinedTools } from './tests/combinedToolsTest.js';
import { testToolCallLimits } from './tests/toolCallLimitTest.js';
import { debugChatServiceWrite } from './tests/debugChatServiceWrite.js';
import { manualWriteTest } from './tests/manualWriteTest.js';
import { createTestCommandToolCommand } from './commands/test-command-tool.js';
import { createConfigureCommand } from './commands/configure.js';
// Load environment variables
dotenv.config();
const program = new Command();
program
.version("0.1.0-alpha.7")
.description("Contaigents - AI Content Ecosystem");
// Default action when no command is provided
program.action(async () => {
const server = new Server();
server.start();
const url = "http://localhost:2668";
console.log(`Opening editor at ${url}`);
await open(url);
});
// Keep the develop command as an alias
program
.command("develop")
.description("Start development server with file editing capabilities")
.action(async () => {
const server = new Server();
server.start();
const url = "http://localhost:2668/files";
console.log(`Opening editor at ${url}`);
await open(url);
});
// Audio generation command
program
.command("audio")
.description("Generate audio from text using AI providers")
.option("-t, --text <text>", "Text to convert to audio (direct input)")
.option("-f, --file <file>", "Path to file containing text to convert")
.option("-o, --output <output>", "Output file path (default: audio_output)")
.option("-p, --provider <provider>", "AI provider to use", "gemini")
.option("-v, --voice <voice>", "Voice to use for generation", "Zephyr")
.option("-m, --model <model>", "Model to use for generation")
.option("--api-key <apiKey>", "API key for the provider (or set GEMINI_API_KEY env var)")
.option("--output-dir <dir>", "Output directory (default: current directory)")
.action(async (options) => {
try {
// Validate input
if (!options.text && !options.file) {
console.error("ā Error: Either --text or --file must be provided");
process.exit(1);
}
if (options.text && options.file) {
console.error("ā Error: Cannot use both --text and --file options");
process.exit(1);
}
// Get API key from options or environment
const apiKey = options.apiKey || process.env.GEMINI_API_KEY;
if (!apiKey) {
console.error("ā Error: API key is required. Use --api-key option or set GEMINI_API_KEY environment variable");
process.exit(1);
}
// Get text content
let textContent;
if (options.file) {
try {
const filePath = path.resolve(options.file);
textContent = await readFile(filePath, 'utf-8');
console.log(`š Read text from file: ${filePath}`);
}
catch (error) {
console.error(`ā Error reading file: ${error.message}`);
process.exit(1);
}
}
else {
textContent = options.text;
}
if (!textContent.trim()) {
console.error("ā Error: Text content is empty");
process.exit(1);
}
console.log(`š Text length: ${textContent.length} characters`);
// Initialize audio generator
const audioGenerator = new AudioGenerator({
provider: options.provider,
apiKey: apiKey,
voice: options.voice,
model: options.model
});
console.log(`šļø Available voices: ${audioGenerator.getAvailableVoices().join(', ')}`);
// Generate audio
console.log("š Generating audio...");
const audioBuffer = await audioGenerator.generateAudio(textContent);
// Determine output path
const outputDir = options.outputDir || process.cwd();
const outputName = options.output || 'audio_output';
const outputPath = path.resolve(outputDir, outputName);
// Save audio file
const savedPath = await audioGenerator.saveAudioToFile(outputPath, audioBuffer);
console.log(`ā
Audio generation completed!`);
console.log(`š File saved to: ${savedPath}`);
console.log(`š File URL: file://${savedPath}`);
}
catch (error) {
console.error(`ā Error generating audio: ${error.message}`);
process.exit(1);
}
});
// Chat command
program
.command("chat")
.description("Start an interactive chat session with AI")
.option("-p, --provider <provider>", "AI provider to use (openai, anthropic, google, etc.)")
.option("-m, --model <model>", "Model to use for generation")
.option("-t, --temperature <temperature>", "Temperature for response generation (0.0-1.0)", parseFloat)
.option("--max-tokens <maxTokens>", "Maximum tokens for response", parseInt)
.option("--system-prompt <systemPrompt>", "Custom system prompt for the chat session")
.option("--non-interactive", "Run in non-interactive mode (for testing)")
.option("--debug", "Enable debug logging (shows prompts, responses, and internal operations)")
.option("--debug-file <file>", "Save debug logs to file (default: chat-debug.log)")
.action(async (options) => {
try {
console.log("š¤ Starting Contaigents Chat...");
// Set up debug logging
const debugFile = options.debug ? (options.debugFile || 'chat-debug.log') : undefined;
const chatService = new ChatService(process.cwd(), options.debug, debugFile);
if (options.debug) {
console.log(`š Debug logging enabled${debugFile ? ` (saving to ${debugFile})` : ''}`);
}
// Create a new chat session
const session = chatService.createSession({
provider: options.provider,
model: options.model,
temperature: options.temperature,
maxTokens: options.maxTokens,
systemPrompt: options.systemPrompt
});
console.log(`š¬ Chat session created: ${session.id}`);
console.log("Type 'exit', 'quit', or press Ctrl+C to end the chat.\n");
if (options.nonInteractive) {
console.log("Running in non-interactive mode for testing");
// In non-interactive mode, read from stdin if available
let stdinData = '';
// Check if there's input from stdin (piped input)
if (!process.stdin.isTTY) {
process.stdin.setEncoding('utf8');
for await (const chunk of process.stdin) {
stdinData += chunk;
}
if (stdinData.trim()) {
console.log(`š Processing input: ${stdinData.trim()}`);
try {
const response = await chatService.sendMessage(session.id, stdinData.trim(), {
provider: options.provider,
model: options.model,
temperature: options.temperature,
maxTokens: options.maxTokens
});
console.log(`š¤ Assistant: ${response.content}`);
}
catch (error) {
console.error(`ā Error: ${error.message}`);
}
}
else {
console.log("No input provided via stdin");
}
}
else {
console.log("No piped input detected");
}
return;
}
// Set up readline interface for interactive chat
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: 'š¤ You: '
});
// Handle graceful shutdown
const cleanup = async () => {
console.log("\nš Chat session ended.");
// Save debug logs if enabled
if (options.debug) {
const debugLogger = chatService.getDebugLogger();
const dumpFile = `chat-debug-dump-${Date.now()}.json`;
await debugLogger.saveDumpToFile(dumpFile);
console.log(`š Debug session saved to: ${dumpFile}`);
}
console.log("Goodbye!");
rl.close();
process.exit(0);
};
process.on('SIGINT', () => cleanup().catch(console.error));
process.on('SIGTERM', () => cleanup().catch(console.error));
// Start the interactive chat loop
rl.prompt();
rl.on('line', async (input) => {
const userInput = input.trim();
if (userInput.toLowerCase() === 'exit' || userInput.toLowerCase() === 'quit') {
cleanup().catch(console.error);
return;
}
if (!userInput) {
rl.prompt();
return;
}
try {
console.log("š¤ Thinking...");
const response = await chatService.sendMessage(session.id, userInput, {
provider: options.provider,
model: options.model,
temperature: options.temperature,
maxTokens: options.maxTokens
});
console.log(`š¤ Assistant: ${response.content}\n`);
}
catch (error) {
console.error(`ā Error: ${error.message}\n`);
}
rl.prompt();
});
rl.on('close', () => {
cleanup().catch(console.error);
});
}
catch (error) {
console.error(`ā Error starting chat: ${error.message}`);
process.exit(1);
}
});
// Test tools command
program
.command("test-tools")
.description("Test the tool architecture functionality")
.action(async () => {
try {
await testToolArchitecture();
}
catch (error) {
console.error(`ā Tool test failed: ${error.message}`);
process.exit(1);
}
});
// Demo chat with tools command
program
.command("demo-chat-tools")
.description("Demonstrate chat service with tool integration")
.action(async () => {
try {
await demonstrateChatWithTools();
}
catch (error) {
console.error(`ā Chat tools demo failed: ${error.message}`);
process.exit(1);
}
});
// Test conversation flow command
program
.command("test-conversation-flow")
.description("Test improved conversation flow with tool calls")
.action(async () => {
try {
const { testImprovedConversationFlow } = await import('./tests/conversationFlowTest.js');
await testImprovedConversationFlow();
}
catch (error) {
console.error(`ā Conversation flow test failed: ${error.message}`);
process.exit(1);
}
});
// Test file write tool command
program
.command("test-file-write")
.description("Test the file writing tool functionality")
.action(async () => {
try {
await testFileWriteTool();
}
catch (error) {
console.error(`ā File write tool test failed: ${error.message}`);
process.exit(1);
}
});
// Test combined tools command
program
.command("test-combined-tools")
.description("Test the combined read/write tool workflow")
.action(async () => {
try {
await testCombinedTools();
}
catch (error) {
console.error(`ā Combined tools test failed: ${error.message}`);
process.exit(1);
}
});
// Test tool call limits command
program
.command("test-tool-limits")
.description("Test the limits and performance of tool call processing")
.action(async () => {
try {
await testToolCallLimits();
}
catch (error) {
console.error(`ā Tool call limits test failed: ${error.message}`);
process.exit(1);
}
});
// Debug ChatService write issue command
program
.command("debug-chat-write")
.description("Debug ChatService file writing issues")
.action(async () => {
try {
await debugChatServiceWrite();
}
catch (error) {
console.error(`ā Debug chat write failed: ${error.message}`);
process.exit(1);
}
});
// Manual write test command
program
.command("manual-write-test")
.description("Manually test writing to README.md file")
.action(async () => {
try {
await manualWriteTest();
}
catch (error) {
console.error(`ā Manual write test failed: ${error.message}`);
process.exit(1);
}
});
// Test command execution tool
program.addCommand(createTestCommandToolCommand());
// Configure command
program.addCommand(createConfigureCommand());
program.parse(process.argv);