UNPKG

c9ai

Version:

Universal AI assistant with vibe-based workflows, hybrid cloud+local AI, and comprehensive tool integration

541 lines (480 loc) 15.2 kB
"use strict"; /** * Google Drive Todo Fetcher - Fetches todos from Google Drive documents and executes them locally */ const fs = require("node:fs"); const path = require("node:path"); const { exec } = require("node:child_process"); const { promisify } = require("node:util"); const execAsync = promisify(exec); class GDriveFetcher { constructor() { this.todoFile = path.join(process.cwd(), 'gdrive-todos.json'); this.cacheDir = path.join(process.cwd(), '.gdrive-cache'); this.ensureCacheDir(); } ensureCacheDir() { if (!fs.existsSync(this.cacheDir)) { fs.mkdirSync(this.cacheDir, { recursive: true }); } } /** * Fetch documents from Google Drive (requires rclone or gdrive CLI) */ async fetchDocuments(query = 'todo', format = 'txt') { try { // Try rclone first (more reliable for automation) const rcloneResult = await this.fetchWithRclone(query, format); if (rcloneResult.success) { return rcloneResult; } // Fallback to direct Google Drive API simulation return await this.fetchWithSimulation(query); } catch (error) { return { success: false, error: `Failed to fetch from Google Drive: ${error.message}`, suggestion: "Install rclone and configure Google Drive: 'rclone config'" }; } } /** * Fetch using rclone (recommended) */ async fetchWithRclone(query, format) { try { // Check if rclone is configured with a Google Drive remote const { stdout: remotes } = await execAsync('rclone listremotes', { timeout: 5000 }); const driveRemotes = remotes.split('\n').filter(line => line.includes('gdrive') || line.includes('drive') || line.includes('google') ); if (driveRemotes.length === 0) { throw new Error('No Google Drive remote found in rclone config'); } const remoteName = driveRemotes[0].replace(':', ''); // List files matching query const { stdout: files } = await execAsync( `rclone lsf "${remoteName}:" --include "*${query}*" --include "*.${format}" --include "*.md" --include "*.txt"`, { timeout: 30000 } ); if (!files.trim()) { return { success: true, source: 'rclone', files_found: 0, todos: [], message: `No files matching '${query}' found in Google Drive` }; } // Download matching files to cache const fileList = files.trim().split('\n').filter(f => f.trim()); const downloadedFiles = []; for (const filename of fileList.slice(0, 5)) { // Limit to first 5 files try { const localPath = path.join(this.cacheDir, filename); await execAsync(`rclone copy "${remoteName}:${filename}" "${this.cacheDir}"`, { timeout: 30000 }); if (fs.existsSync(localPath)) { const content = fs.readFileSync(localPath, 'utf8'); downloadedFiles.push({ filename, localPath, content, size: content.length }); } } catch (error) { console.warn(`Failed to download ${filename}: ${error.message}`); } } return this.processDocuments(downloadedFiles, 'rclone'); } catch (error) { return { success: false, error: `Rclone failed: ${error.message}` }; } } /** * Simulate Google Drive fetch with local examples */ async fetchWithSimulation(query) { // Create sample documents for demonstration const sampleDocs = [ { filename: 'Project_Todos.md', localPath: path.join(this.cacheDir, 'Project_Todos.md'), content: `# Project Todos ## High Priority Tasks - [ ] Update README documentation - [ ] Fix authentication bug in login system - [ ] @calc 125 * 0.15 (calculate tax) - [ ] Deploy to production server ## Code Tasks \`\`\`bash npm run build npm run test git commit -am "Update project" \`\`\` ## Analysis Tasks - [ ] @analyze sales_data.csv - [ ] @system (check system status) - [ ] Generate monthly report ## Shell Commands \`\`\`shell ls -la /var/log/ df -h ps aux | grep node \`\`\` `, size: 450 }, { filename: 'Weekly_Goals.txt', localPath: path.join(this.cacheDir, 'Weekly_Goals.txt'), content: `Weekly Goals - C9AI Project 1. Complete JIT system implementation ✓ 2. Add GitHub integration 3. Test sigil routing system 4. @github-fetch my-org/project-repo 5. @count important_files.txt 6. Review and deploy changes Meeting Notes: - Discussed automation workflows - Need to @search "rclone google drive setup" - Schedule follow-up @email team@company.com "Weekly Update" "Progress report" `, size: 380 } ]; // Save sample documents to cache for (const doc of sampleDocs) { fs.writeFileSync(doc.localPath, doc.content); } return this.processDocuments(sampleDocs, 'simulation'); } /** * Process downloaded documents into executable todos */ processDocuments(documents, source) { const allTodos = []; for (const doc of documents) { const todos = this.extractTodosFromContent(doc.content, doc.filename); allTodos.push(...todos); } const result = { success: true, source: source, files_processed: documents.length, files: documents.map(d => ({ filename: d.filename, size: d.size, todos_found: this.extractTodosFromContent(d.content, d.filename).length })), total_todos: allTodos.length, executable_count: allTodos.filter(t => t.executable.length > 0).length, todos: allTodos }; return result; } /** * Extract todos from document content */ extractTodosFromContent(content, filename) { const todos = []; let todoId = 1; // Extract markdown-style checkboxes const todoRegex = /- \[ \]\s+(.+)/gi; let match; while ((match = todoRegex.exec(content)) !== null) { const taskText = match[1].trim(); const todo = { id: `gdrive-${filename}-${todoId++}`, source: 'google_drive', filename: filename, title: taskText, type: 'checkbox', executable: this.extractExecutableCommands(taskText), created_at: new Date().toISOString() }; todos.push(todo); } // Extract code blocks const codeBlockRegex = /```(?:bash|shell|terminal|sh|cmd)?\n([\s\S]*?)```/gi; let codeBlockId = 1; while ((match = codeBlockRegex.exec(content)) !== null) { const code = match[1].trim(); if (code) { const todo = { id: `gdrive-${filename}-code-${codeBlockId++}`, source: 'google_drive', filename: filename, title: `Code block: ${code.split('\n')[0].substring(0, 50)}...`, type: 'code_block', executable: [{ type: 'shell', command: code, source: 'code_block' }], created_at: new Date().toISOString() }; todos.push(todo); } } // Extract sigil commands const sigilRegex = /@(\w+)\s+([^\n\r]*)/gi; let sigilId = 1; while ((match = sigilRegex.exec(content)) !== null) { const todo = { id: `gdrive-${filename}-sigil-${sigilId++}`, source: 'google_drive', filename: filename, title: `Sigil command: @${match[1]} ${match[2]}`, type: 'sigil', executable: [{ type: 'sigil', sigil: match[1], args: match[2].trim(), source: 'sigil_command' }], created_at: new Date().toISOString() }; todos.push(todo); } return todos; } /** * Extract executable commands from task text */ extractExecutableCommands(taskText) { const commands = []; // Look for sigil commands in task text const sigilMatch = taskText.match(/@(\w+)\s+(.+)/); if (sigilMatch) { commands.push({ type: 'sigil', sigil: sigilMatch[1], args: sigilMatch[2].trim(), source: 'task_text' }); } // Look for shell-like commands if (taskText.match(/^(ls|cd|mkdir|rm|cp|mv|git|npm|node|python|docker)\s/)) { commands.push({ type: 'shell', command: taskText, source: 'task_text' }); } return commands; } /** * Save todos to local file */ async saveTodos(todosData) { try { fs.writeFileSync(this.todoFile, JSON.stringify(todosData, null, 2)); return { success: true, saved_to: this.todoFile, count: todosData.todos ? todosData.todos.length : 0 }; } catch (error) { return { success: false, error: `Failed to save todos: ${error.message}` }; } } /** * List all fetched todos */ listTodos() { try { if (!fs.existsSync(this.todoFile)) { return { success: false, error: 'No todos file found. Run fetch first.' }; } const todosData = JSON.parse(fs.readFileSync(this.todoFile, 'utf8')); return { success: true, total: todosData.todos.length, executable: todosData.todos.filter(t => t.executable.length > 0).length, files_processed: todosData.files_processed, todos: todosData.todos.map(todo => ({ id: todo.id, filename: todo.filename, title: todo.title, type: todo.type, executable_commands: todo.executable.length, commands_preview: todo.executable.slice(0, 2).map(c => c.command || `@${c.sigil} ${c.args || ''}` ) })) }; } catch (error) { return { success: false, error: error.message }; } } /** * Execute a specific todo by ID */ async executeTodo(todoId, dryRun = true) { try { if (!fs.existsSync(this.todoFile)) { throw new Error('No todos file found. Run fetch first.'); } const todosData = JSON.parse(fs.readFileSync(this.todoFile, 'utf8')); const todo = todosData.todos.find(t => t.id === todoId); if (!todo) { throw new Error(`Todo with ID ${todoId} not found`); } const results = []; for (const cmd of todo.executable) { if (dryRun) { results.push({ type: cmd.type, command: cmd.command || `@${cmd.sigil} ${cmd.args}`, action: 'would_execute', dry_run: true }); } else { try { let result; if (cmd.type === 'shell') { const { stdout, stderr } = await execAsync(cmd.command, { timeout: 60000, cwd: process.cwd() }); result = { stdout, stderr, success: true }; } else if (cmd.type === 'sigil') { result = { message: `Sigil @${cmd.sigil} ${cmd.args} would be executed`, success: true }; } results.push({ type: cmd.type, command: cmd.command || `@${cmd.sigil} ${cmd.args}`, result: result, executed: true }); } catch (error) { results.push({ type: cmd.type, command: cmd.command || `@${cmd.sigil} ${cmd.args}`, error: error.message, executed: false }); } } } return { success: true, todo_id: todoId, title: todo.title, filename: todo.filename, total_commands: todo.executable.length, dry_run: dryRun, results: results }; } catch (error) { return { success: false, error: error.message }; } } /** * Cleanup cache files */ cleanupCache() { try { if (fs.existsSync(this.cacheDir)) { const files = fs.readdirSync(this.cacheDir); files.forEach(file => { fs.unlinkSync(path.join(this.cacheDir, file)); }); fs.rmdirSync(this.cacheDir); } return { success: true, message: 'Cache cleaned' }; } catch (error) { return { success: false, error: error.message }; } } } // Tool interface for C9AI async function executeGDriveFetcher(args) { const fetcher = new GDriveFetcher(); const { action, query, format, todoId, dryRun } = args; try { switch (action) { case 'fetch': const docsResult = await fetcher.fetchDocuments(query, format); if (docsResult.success) { const saveResult = await fetcher.saveTodos(docsResult); return { ...docsResult, saved: saveResult.success, saved_to: saveResult.saved_to }; } return docsResult; case 'list': return fetcher.listTodos(); case 'execute': if (!todoId) { return { success: false, error: 'todoId is required for execute action' }; } return await fetcher.executeTodo(todoId, dryRun !== false); case 'cleanup': return fetcher.cleanupCache(); default: return { success: false, error: `Unknown action: ${action}. Supported: fetch, list, execute, cleanup` }; } } catch (error) { return { success: false, error: `Google Drive fetcher failed: ${error.message}` }; } } module.exports = { name: "gdrive-fetcher", description: "Fetch and execute todos from Google Drive documents", parameters: { type: "object", properties: { action: { type: "string", description: "Action to perform: 'fetch', 'list', 'execute', 'cleanup'", enum: ["fetch", "list", "execute", "cleanup"] }, query: { type: "string", description: "Search query for documents (default: 'todo')", default: "todo" }, format: { type: "string", description: "Document format to fetch (txt, md, doc)", default: "txt" }, todoId: { type: "string", description: "Todo ID to execute (required for execute action)" }, dryRun: { type: "boolean", description: "If true, shows what would be executed without running commands", default: true } }, required: ["action"] }, execute: executeGDriveFetcher };