UNPKG

rawi

Version:

Rawi (راوي) is the developer-friendly AI CLI that brings the power of 11 major AI providers directly to your terminal. With seamless shell integration, persistent conversations, and 200+ specialized prompt templates, Rawi transforms your command line into

1 lines 20.4 kB
{"version":3,"sources":["/home/mkabumattar/work/withrawi/rawi/dist/chunk-PFLY5EL5.cjs","../src/cli/commands/chat/managers/session.manager.ts"],"names":["SessionManager","dbManager","options","profile","recentSessions","chalk","selectedSessionId","error","SessionNotFoundError","ProfileMismatchError"],"mappings":"AAAA;AACA,wDAAsD,wBCD1B,4CACE,4EACZ,IAcLA,CAAAA,CAAN,KAAqB,CAClB,WAER,CAAYC,CAAAA,CAA4B,CACtC,IAAA,CAAK,SAAA,CAAYA,CACnB,CAEA,MAAM,kBAAA,CAAmBC,CAAAA,CAAuC,CAC9D,GAAI,CACF,IAAMC,CAAAA,CAAUD,CAAAA,CAAQ,OAAA,EAAW,SAAA,CAEnC,EAAA,CAAIA,CAAAA,CAAQ,UAAA,CACV,OAAO,MAAM,IAAA,CAAK,gBAAA,CAAiBC,CAAO,CAAA,CAG5C,EAAA,CAAID,CAAAA,CAAQ,OAAA,CAEV,MAAA,CADgB,MAAM,IAAA,CAAK,eAAA,CAAgBA,CAAAA,CAAQ,OAAA,CAASC,CAAO,CAAA,CAAA,CACpD,SAAA,CAGjB,IAAMC,CAAAA,CAAiB,MAAM,IAAA,CAAK,iBAAA,CAAkBD,CAAAA,CAAS,CAAC,CAAA,CAE9D,EAAA,CAAIC,CAAAA,CAAe,MAAA,GAAW,CAAA,CAC5B,OAAA,OAAA,CAAQ,GAAA,CACNC,eAAAA,CAAM,GAAA,CAAI,qDAAqD,CACjE,CAAA,CACO,MAAM,IAAA,CAAK,gBAAA,CAAiBF,CAAO,CAAA,CAG5C,IAAMG,CAAAA,CACJ,MAAM,IAAA,CAAK,uBAAA,CAAwBF,CAAc,CAAA,CAEnD,OAAIE,CAAAA,GAAsB,IAAA,CACjB,MAAM,IAAA,CAAK,gBAAA,CAAiBH,CAAO,CAAA,CAAA,CAG5C,MAAM,IAAA,CAAK,eAAA,CAAgBG,CAAAA,CAAmBH,CAAO,CAAA,CAC9CG,CAAAA,CACT,CAAA,KAAA,CAASC,CAAAA,CAAO,CACd,EAAA,CACEA,EAAAA,WAAiBC,mBAAAA,EACjBD,EAAAA,WAAiBE,mBAAAA,CAEjB,OAAA,OAAA,CAAQ,KAAA,CAAMJ,eAAAA,CAAM,GAAA,CAAI,CAAA,OAAA,EAAKE,CAAAA,CAAM,OAAO,CAAA,CAAA;AA4JjB,0BAAA;AAwDd,qCAAA;AAuEW,oCAAA;AAgKE,+BAAA;AA4BA,mBAAA;AAEH,2BAAA;AAqBV,UAAA;AAAA;AACwC;AAAA;AACT;AAAA;AAGU;AAAA;AACnB;AACU;AAC6B;AACA;AACvB;AAAA;AAIrC;AAAA;AAIwB;AAAA;AAAyB;AAAA;AAAA;AAAA;AAInD;AAKlB;ADpiBsxB","file":"/home/mkabumattar/work/withrawi/rawi/dist/chunk-PFLY5EL5.cjs","sourcesContent":[null,"import {writeFileSync} from 'node:fs';\nimport {confirm, select} from '@inquirer/prompts';\nimport chalk from 'chalk';\nimport type {DatabaseManager} from '../../../../core/database/manager.js';\nimport {\n type ChatOptions,\n type ChatSession,\n DatabaseConnectionError,\n type DeleteSessionOptions,\n type EnhancedChatSession,\n type ExportSessionsOptions,\n type ListSessionsOptions,\n ProfileMismatchError,\n SessionNotFoundError,\n} from '../types.js';\n\nexport class SessionManager {\n private dbManager: DatabaseManager;\n\n constructor(dbManager: DatabaseManager) {\n this.dbManager = dbManager;\n }\n\n async handleSessionStart(options: ChatOptions): Promise<string> {\n try {\n const profile = options.profile || 'default';\n\n if (options.newSession) {\n return await this.createNewSession(profile);\n }\n\n if (options.session) {\n const session = await this.continueSession(options.session, profile);\n return session.sessionId;\n }\n\n const recentSessions = await this.getRecentSessions(profile, 5);\n\n if (recentSessions.length === 0) {\n console.log(\n chalk.dim('No recent sessions found. Creating a new session...'),\n );\n return await this.createNewSession(profile);\n }\n\n const selectedSessionId =\n await this.displaySessionSelection(recentSessions);\n\n if (selectedSessionId === null) {\n return await this.createNewSession(profile);\n }\n\n await this.continueSession(selectedSessionId, profile);\n return selectedSessionId;\n } catch (error) {\n if (\n error instanceof SessionNotFoundError ||\n error instanceof ProfileMismatchError\n ) {\n console.error(chalk.red(`❌ ${error.message}`));\n console.log(chalk.dim('Falling back to creating a new session...'));\n return await this.createNewSession(options.profile || 'default');\n }\n\n if (error instanceof DatabaseConnectionError) {\n console.error(chalk.red(`❌ Database error: ${error.message}`));\n console.log(chalk.dim('Creating temporary session...'));\n return await this.dbManager.createEmergencySession(\n options.profile || 'default',\n 'chat',\n );\n }\n\n throw error;\n }\n }\n\n async createNewSession(profile: string, title?: string): Promise<string> {\n try {\n const sessionId = await this.dbManager.createSession(\n profile,\n title,\n 'chat',\n );\n console.log(chalk.green(`✅ Created new session: ${sessionId}`));\n return sessionId;\n } catch (error) {\n console.error(chalk.red(`❌ Failed to create session: ${error}`));\n throw new DatabaseConnectionError(\n 'Failed to create new session',\n error instanceof Error ? error : new Error(String(error)),\n );\n }\n }\n\n async continueSession(\n sessionId: string,\n profile: string,\n ): Promise<EnhancedChatSession> {\n try {\n const session = await this.dbManager.getSession(sessionId);\n\n if (!session) {\n throw new SessionNotFoundError(sessionId, profile);\n }\n\n if (session.profile !== profile) {\n throw new ProfileMismatchError(sessionId, profile, session.profile);\n }\n\n const messages = await this.dbManager.getMessages(sessionId);\n\n console.log(chalk.green(`✅ Continuing session: ${sessionId}`));\n if (session.title) {\n console.log(chalk.dim(`📝 Title: ${session.title}`));\n }\n console.log(chalk.dim(`💬 Messages: ${messages.length}`));\n\n return {\n sessionId: session.id,\n profile: session.profile,\n title: session.title,\n messages,\n displaySessionInfo: () => this.displaySessionInfo(session),\n displayConversationHistory: (limit?: number) =>\n this.displayConversationHistory(messages, limit),\n addUserMessage: async (content: string) => {\n await this.dbManager.addMessage(\n sessionId,\n 'user',\n content,\n 'unknown',\n 'unknown',\n );\n },\n addAssistantMessage: async (content: string, metadata: any) => {\n await this.dbManager.addMessage(\n sessionId,\n 'assistant',\n content,\n metadata.provider || 'unknown',\n metadata.model || 'unknown',\n metadata.temperature,\n metadata.maxTokens,\n metadata,\n );\n },\n updateSessionTitle: async (newTitle: string) => {\n await this.dbManager.updateSessionTitle(sessionId, newTitle);\n },\n getSessionStats: () => ({\n messageCount: messages.length,\n createdAt: session.createdAt,\n updatedAt: session.updatedAt,\n duration: this.calculateDuration(\n session.createdAt,\n session.updatedAt,\n ),\n providers: Array.from(new Set(messages.map((m) => m.provider))),\n models: Array.from(new Set(messages.map((m) => m.model))),\n }),\n };\n } catch (error) {\n if (\n error instanceof SessionNotFoundError ||\n error instanceof ProfileMismatchError\n ) {\n throw error;\n }\n\n console.error(chalk.red(`❌ Failed to continue session: ${error}`));\n throw new DatabaseConnectionError(\n 'Failed to continue session',\n error instanceof Error ? error : new Error(String(error)),\n );\n }\n }\n\n async getRecentSessions(profile: string, limit = 10): Promise<ChatSession[]> {\n try {\n return await this.dbManager.getSessions({profile, limit});\n } catch (error) {\n console.error(chalk.red(`❌ Failed to get recent sessions: ${error}`));\n throw new DatabaseConnectionError(\n 'Failed to retrieve recent sessions',\n error instanceof Error ? error : new Error(String(error)),\n );\n }\n }\n\n async displaySessionSelection(\n sessions: ChatSession[],\n ): Promise<string | null> {\n console.log(chalk.bold.blue('\\n🔍 Recent Sessions:'));\n\n const choices = [\n {\n name: chalk.green('➕ Create new session'),\n value: 'new',\n },\n ...sessions.map((session) => ({\n name: this.formatSessionChoice(session),\n value: session.id,\n })),\n ];\n\n try {\n const answer = await select({\n message: 'Choose a session to continue or create a new one:',\n choices,\n });\n\n return answer === 'new' ? null : answer;\n } catch (error) {\n if (error instanceof Error && error.name === 'ExitPromptError') {\n console.log(chalk.yellow('\\n👋 Session selection cancelled'));\n process.exit(0);\n }\n throw error;\n }\n }\n\n async listSessions(options: ListSessionsOptions): Promise<void> {\n try {\n const sessions = await this.dbManager.getSessions({\n profile: options.profile,\n limit: options.limit || 20,\n fromDate: options.fromDate,\n toDate: options.toDate,\n });\n\n if (sessions.length === 0) {\n console.log(chalk.dim('No sessions found.'));\n return;\n }\n\n const format = options.format || 'table';\n\n switch (format) {\n case 'json':\n console.log(JSON.stringify(sessions, null, 2));\n break;\n case 'summary':\n this.displaySessionsSummary(sessions);\n break;\n default:\n this.displaySessionsTable(sessions);\n break;\n }\n } catch (error) {\n console.error(chalk.red(`❌ Failed to list sessions: ${error}`));\n throw new DatabaseConnectionError(\n 'Failed to list sessions',\n error instanceof Error ? error : new Error(String(error)),\n );\n }\n }\n\n async deleteSession(\n sessionId: string,\n options: DeleteSessionOptions,\n ): Promise<boolean> {\n try {\n const session = await this.dbManager.getSession(sessionId);\n if (!session) {\n console.error(chalk.red(`❌ Session '${sessionId}' not found`));\n return false;\n }\n\n if (!options.force && options.confirm !== false) {\n const shouldDelete = await confirm({\n message: `Are you sure you want to delete session '${sessionId}'${session.title ? ` (${session.title})` : ''}? This will delete all ${session.messageCount} messages.`,\n default: false,\n });\n\n if (!shouldDelete) {\n console.log(chalk.dim('Session deletion cancelled.'));\n return false;\n }\n }\n\n const deleted = await this.dbManager.deleteSession(sessionId);\n\n if (deleted) {\n console.log(\n chalk.green(`✅ Session '${sessionId}' deleted successfully`),\n );\n } else {\n console.error(chalk.red(`❌ Failed to delete session '${sessionId}'`));\n }\n\n return deleted;\n } catch (error) {\n if (error instanceof Error && error.name === 'ExitPromptError') {\n console.log(chalk.yellow('\\n👋 Session deletion cancelled'));\n return false;\n }\n\n console.error(chalk.red(`❌ Failed to delete session: ${error}`));\n throw new DatabaseConnectionError(\n 'Failed to delete session',\n error instanceof Error ? error : new Error(String(error)),\n );\n }\n }\n\n async renameSession(sessionId: string, newTitle: string): Promise<boolean> {\n try {\n const session = await this.dbManager.getSession(sessionId);\n if (!session) {\n console.error(chalk.red(`❌ Session '${sessionId}' not found`));\n return false;\n }\n\n if (!newTitle || newTitle.trim().length === 0) {\n console.error(chalk.red('❌ Session title cannot be empty'));\n return false;\n }\n\n const trimmedTitle = newTitle.trim();\n if (trimmedTitle.length > 200) {\n console.error(\n chalk.red('❌ Session title cannot exceed 200 characters'),\n );\n return false;\n }\n\n const updated = await this.dbManager.updateSessionTitle(\n sessionId,\n trimmedTitle,\n );\n\n if (updated) {\n console.log(\n chalk.green(`✅ Session '${sessionId}' renamed to: ${trimmedTitle}`),\n );\n } else {\n console.error(chalk.red(`❌ Failed to rename session '${sessionId}'`));\n }\n\n return updated;\n } catch (error) {\n console.error(chalk.red(`❌ Failed to rename session: ${error}`));\n throw new DatabaseConnectionError(\n 'Failed to rename session',\n error instanceof Error ? error : new Error(String(error)),\n );\n }\n }\n\n async exportSessions(options: ExportSessionsOptions): Promise<string> {\n try {\n const exportData = await this.dbManager.exportChatHistory({\n profile: options.profile,\n fromDate: options.fromDate,\n toDate: options.toDate,\n });\n\n let sessionsToExport = exportData.sessions;\n let messagesToExport = exportData.messages;\n\n if (options.sessions && options.sessions.length > 0) {\n sessionsToExport = exportData.sessions.filter((session) =>\n options.sessions!.includes(session.id),\n );\n messagesToExport = Object.fromEntries(\n Object.entries(exportData.messages).filter(([sessionId]) =>\n options.sessions!.includes(sessionId),\n ),\n );\n }\n\n if (sessionsToExport.length === 0) {\n console.log(chalk.dim('No sessions found to export.'));\n return '';\n }\n\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-');\n const filename =\n options.output || `rawi-sessions-${timestamp}.${options.format}`;\n\n let content: string;\n\n switch (options.format) {\n case 'json':\n content = JSON.stringify(\n {\n exportedAt: new Date().toISOString(),\n sessions: sessionsToExport,\n messages: messagesToExport,\n stats: exportData.stats,\n },\n null,\n 2,\n );\n break;\n case 'markdown':\n content = this.formatSessionsAsMarkdown(\n sessionsToExport,\n messagesToExport,\n );\n break;\n default:\n throw new Error(`Unsupported export format: ${options.format}`);\n }\n\n writeFileSync(filename, content, 'utf8');\n\n console.log(\n chalk.green(\n `✅ Exported ${sessionsToExport.length} sessions to: ${filename}`,\n ),\n );\n return filename;\n } catch (error) {\n console.error(chalk.red(`❌ Failed to export sessions: ${error}`));\n throw new DatabaseConnectionError(\n 'Failed to export sessions',\n error instanceof Error ? error : new Error(String(error)),\n );\n }\n }\n\n private formatSessionChoice(session: ChatSession): string {\n const age = this.formatAge(session.updatedAt);\n const title = session.title || 'Untitled';\n const messageCount = session.messageCount;\n\n return `${chalk.cyan(session.id.slice(0, 8))} - ${chalk.white(title)} ${chalk.dim(`(${messageCount} messages, ${age})`)}`;\n }\n\n private formatAge(dateString: string): string {\n const date = new Date(dateString);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffMins = Math.floor(diffMs / (1000 * 60));\n const diffHours = Math.floor(diffMs / (1000 * 60 * 60));\n const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));\n\n if (diffMins < 60) {\n return `${diffMins}m ago`;\n } else if (diffHours < 24) {\n return `${diffHours}h ago`;\n } else {\n return `${diffDays}d ago`;\n }\n }\n\n private calculateDuration(createdAt: string, updatedAt: string): string {\n const created = new Date(createdAt);\n const updated = new Date(updatedAt);\n const diffMs = updated.getTime() - created.getTime();\n const diffMins = Math.floor(diffMs / (1000 * 60));\n const diffHours = Math.floor(diffMs / (1000 * 60 * 60));\n const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));\n\n if (diffMins < 60) {\n return `${diffMins} minutes`;\n } else if (diffHours < 24) {\n return `${diffHours} hours`;\n } else {\n return `${diffDays} days`;\n }\n }\n\n private displaySessionInfo(session: ChatSession): void {\n console.log(chalk.dim(`ID: ${session.id}`));\n console.log(chalk.dim(`Profile: ${session.profile}`));\n if (session.title) {\n console.log(chalk.dim(`Title: ${session.title}`));\n }\n console.log(\n chalk.dim(`Created: ${new Date(session.createdAt).toLocaleString()}`),\n );\n console.log(\n chalk.dim(`Updated: ${new Date(session.updatedAt).toLocaleString()}`),\n );\n console.log(chalk.dim(`Messages: ${session.messageCount}`));\n }\n\n private displayConversationHistory(messages: any[], limit?: number): void {\n const messagesToShow = limit ? messages.slice(-limit) : messages;\n\n if (messagesToShow.length === 0) {\n console.log(chalk.dim('No previous messages in this session.'));\n return;\n }\n\n console.log(chalk.bold.blue('\\n💬 Conversation History:'));\n messagesToShow.forEach((message) => {\n const role =\n message.role === 'user' ? chalk.blue('You') : chalk.green('Assistant');\n const content =\n message.content.length > 100\n ? `${message.content.substring(0, 100)}...`\n : message.content;\n console.log(`${role}: ${chalk.dim(content)}`);\n });\n console.log('');\n }\n\n private displaySessionsTable(sessions: ChatSession[]): void {\n console.log(chalk.bold.blue('\\n📋 Sessions:'));\n console.log(\n chalk.dim(\n 'ID'.padEnd(10) +\n 'Title'.padEnd(30) +\n 'Messages'.padEnd(10) +\n 'Updated'.padEnd(20),\n ),\n );\n console.log(chalk.dim('-'.repeat(70)));\n\n sessions.forEach((session) => {\n const id = session.id.slice(0, 8);\n const title = (session.title || 'Untitled').slice(0, 28);\n const messages = session.messageCount.toString();\n const updated = this.formatAge(session.updatedAt);\n\n console.log(\n chalk.cyan(id.padEnd(10)) +\n chalk.white(title.padEnd(30)) +\n chalk.dim(messages.padEnd(10)) +\n chalk.dim(updated.padEnd(20)),\n );\n });\n console.log('');\n }\n\n private displaySessionsSummary(sessions: ChatSession[]): void {\n console.log(chalk.bold.blue('\\n📋 Sessions Summary:'));\n sessions.forEach((session) => {\n console.log(chalk.cyan(`\\n🔹 ${session.id.slice(0, 8)}`));\n console.log(chalk.white(` Title: ${session.title || 'Untitled'}`));\n console.log(chalk.dim(` Messages: ${session.messageCount}`));\n console.log(\n chalk.dim(\n ` Created: ${new Date(session.createdAt).toLocaleString()}`,\n ),\n );\n console.log(\n chalk.dim(\n ` Updated: ${new Date(session.updatedAt).toLocaleString()}`,\n ),\n );\n });\n console.log('');\n }\n\n private formatSessionsAsMarkdown(\n sessions: ChatSession[],\n messages: Record<string, any[]>,\n ): string {\n let markdown = '# Rawi Chat Sessions Export\\n\\n';\n markdown += `Exported on: ${new Date().toLocaleString()}\\n\\n`;\n markdown += `Total sessions: ${sessions.length}\\n\\n`;\n\n sessions.forEach((session) => {\n markdown += `## Session: ${session.title || 'Untitled'}\\n\\n`;\n markdown += `- **ID**: ${session.id}\\n`;\n markdown += `- **Profile**: ${session.profile}\\n`;\n markdown += `- **Created**: ${new Date(session.createdAt).toLocaleString()}\\n`;\n markdown += `- **Updated**: ${new Date(session.updatedAt).toLocaleString()}\\n`;\n markdown += `- **Messages**: ${session.messageCount}\\n\\n`;\n\n const sessionMessages = messages[session.id] || [];\n if (sessionMessages.length > 0) {\n markdown += '### Conversation\\n\\n';\n sessionMessages.forEach((message) => {\n const role = message.role === 'user' ? '**You**' : '**Assistant**';\n const timestamp = new Date(message.timestamp).toLocaleString();\n markdown += `${role} _(${timestamp})_:\\n\\n${message.content}\\n\\n---\\n\\n`;\n });\n }\n\n markdown += '\\n';\n });\n\n return markdown;\n }\n}\n"]}