rawi
Version:
Rawi (راوي) is the developer-friendly AI CLI that brings the power of 12 major AI providers directly to your terminal. With seamless shell integration, persistent conversations, and 200+ specialized prompt templates, Rawi transforms your command line into
140 lines • 16.8 kB
JavaScript
/* Rawi (راوي) is the developer-friendly AI CLI that brings the power of 12 major AI providers directly to your terminal. With seamless shell integration, persistent conversations, and 200+ specialized prompt templates, Rawi transforms your command line into an intelligent development workspace. */
import{d as y}from"./chunk-S33HQEPX.js";import{b as a,c as D}from"./chunk-Y7XFV3CR.js";import{j as f}from"./chunk-3JCRW7ET.js";import{accessSync as L,constants as F,existsSync as h,mkdirSync as O,unlinkSync as U,writeFileSync as P}from"fs";import{dirname as M}from"path";import{createClient as m}from"@libsql/client";import{v4 as C}from"uuid";var I=class{client;dbPath;constructor(){try{this.dbPath=y(),this.ensureConfigDir(),this.ensureDatabaseFileExists();let e=`file:${this.dbPath}`;this.client=m({url:e}),a("Database adapter initialized with URL:",e)}catch(e){throw console.error("Error initializing database adapter:",e),new Error(`Failed to initialize database adapter: ${e instanceof Error?e.message:"Unknown error"}`)}}ensureConfigDir(){try{let e=D();a("Ensuring config directory exists:",e),h(e)?a("Config directory already exists"):(a("Config directory not found, creating it"),O(e,{recursive:!0}),a("Config directory created successfully"))}catch(e){throw console.error("Error ensuring config directory exists:",e),new Error(`Failed to create config directory: ${e instanceof Error?e.message:"Unknown error"}`)}}ensureDatabaseFileExists(){try{let e=M(this.dbPath);if(h(e)||(a(`Database directory not found, creating it: ${e}`),O(e,{recursive:!0})),h(this.dbPath)){a("Database file already exists");try{L(this.dbPath,F.W_OK),a("Database file is writable")}catch(r){throw console.error("Database file exists but is not writable!",r),new Error(`Database file at ${this.dbPath} is not writable`)}}else{if(a("Database file not found, creating it:",this.dbPath),P(this.dbPath,""),!h(this.dbPath))throw new Error(`Failed to create database file at ${this.dbPath}`);a("Empty database file created successfully")}}catch(e){throw console.error("Error ensuring database file exists:",e),new Error(`Failed to create or access database file: ${e instanceof Error?e.message:"Unknown error"}`)}}async ensureDatabaseInitialized(){let e=1,r=3;for(;e<=r;){try{a(`Checking if database schema exists (attempt ${e})...`),this.ensureConfigDir(),this.ensureDatabaseFileExists();let t=await this.client.execute(`
SELECT count(*) as table_count FROM sqlite_master
WHERE type='table' AND name IN ('chat_sessions', 'chat_messages');
`),n=Number(t.rows[0].table_count);if(n===2){a("Database schema verified, both tables exist");try{await this.client.execute("SELECT COUNT(*) FROM chat_sessions"),await this.client.execute("SELECT COUNT(*) FROM chat_messages"),a("Database tables are accessible");return}catch(s){console.error("Tables exist but querying them failed:",s instanceof Error?s.message:"Unknown error")}}a(`Found only ${n} of 2 required tables, initializing schema...`),await this.initialize();return}catch(t){if(a(`Error checking database schema (attempt ${e})`),t instanceof Error)if(a("Error details:",t.message),t.message.includes("no such table")||t.message.includes("unable to open database file"))if(a("Database tables missing or database file issues. Initializing schema..."),e===r)try{if(a("Last attempt - recreating database file from scratch"),this.client)try{await this.client.close()}catch(s){a("Error closing client:",s instanceof Error?s.message:"Unknown error")}try{h(this.dbPath)&&(U(this.dbPath),a("Deleted existing database file"))}catch(s){console.error("Error deleting database file:",s instanceof Error?s.message:"Unknown error")}this.ensureDatabaseFileExists(),this.client=m({url:`file:${this.dbPath}`}),a("Reconnected to fresh database. Attempting initialization..."),await this.initialize();return}catch(s){throw console.error("Failed to recreate and initialize database:",s instanceof Error?s.message:"Unknown error"),new Error(`Database recreation failed after ${r} attempts`)}else try{if(this.client)try{await this.client.close()}catch(s){a("Error closing client:",s instanceof Error?s.message:"Unknown error")}this.ensureDatabaseFileExists(),this.client=m({url:`file:${this.dbPath}`}),a("Reconnected to database. Attempting initialization..."),await this.initialize();return}catch(s){console.error("Failed to reconnect and initialize database:",s instanceof Error?s.message:"Unknown error")}else console.error("Unexpected database error, not related to missing tables:",t);else{a("Unknown error type detected during initialization:",typeof t);try{await this.initialize();return}catch(n){console.error("Failed to initialize after unknown error:",n instanceof Error?n.message:"Unknown error")}}}if(e++,e<=r){let t=500*e;a(`Waiting ${t}ms before attempt ${e}...`),await new Promise(n=>setTimeout(n,t))}}throw new Error(`Failed to initialize database after ${r} attempts`)}async initialize(){let e=0,r=3;for(;e<r;)try{this.ensureConfigDir(),this.ensureDatabaseFileExists();try{await this.client.execute("PRAGMA quick_check;"),a("Database connection verified")}catch(s){console.error("Database connection test failed:",s instanceof Error?s.message:"Unknown error");try{if(this.client)try{await this.client.close()}catch{}this.client=m({url:`file:${this.dbPath}`}),a("Database client recreated")}catch(i){throw console.error("Failed to recreate database client:",i instanceof Error?i.message:"Unknown error"),new Error("Cannot connect to database")}}a(`Creating database schema step by step (attempt ${e+1})...`);try{await this.client.execute("PRAGMA journal_mode = WAL;")}catch(s){let i=s instanceof Error?s.message:"Unknown error";a("Warning: Failed to set journal_mode pragma:",i)}try{await this.client.execute("PRAGMA synchronous = NORMAL;")}catch(s){let i=s instanceof Error?s.message:"Unknown error";a("Warning: Failed to set synchronous pragma:",i)}try{await this.client.execute("PRAGMA foreign_keys = ON;")}catch(s){let i=s instanceof Error?s.message:"Unknown error";a("Warning: Failed to set foreign_keys pragma:",i)}await this.client.execute(`
CREATE TABLE IF NOT EXISTS chat_sessions (
id TEXT PRIMARY KEY,
profile TEXT NOT NULL,
title TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
message_count INTEGER NOT NULL DEFAULT 0
);
`),a("Created chat_sessions table"),await this.client.execute(`
CREATE TABLE IF NOT EXISTS chat_messages (
id TEXT PRIMARY KEY,
session_id TEXT NOT NULL,
role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),
content TEXT NOT NULL,
timestamp TEXT NOT NULL DEFAULT (datetime('now')),
provider TEXT NOT NULL,
model TEXT NOT NULL,
temperature REAL,
max_tokens INTEGER,
metadata TEXT,
FOREIGN KEY (session_id) REFERENCES chat_sessions (id) ON DELETE CASCADE
);
`),a("Created chat_messages table");try{await this.client.execute(`
CREATE TRIGGER IF NOT EXISTS update_session_message_count_insert
AFTER INSERT ON chat_messages
BEGIN
UPDATE chat_sessions
SET message_count = (
SELECT COUNT(*) FROM chat_messages WHERE session_id = NEW.session_id
),
updated_at = datetime('now')
WHERE id = NEW.session_id;
END;
`),await this.client.execute(`
CREATE TRIGGER IF NOT EXISTS update_session_message_count_delete
AFTER DELETE ON chat_messages
BEGIN
UPDATE chat_sessions
SET message_count = (
SELECT COUNT(*) FROM chat_messages WHERE session_id = OLD.session_id
),
updated_at = datetime('now')
WHERE id = OLD.session_id;
END;
`),await this.client.execute(`
CREATE TRIGGER IF NOT EXISTS generate_session_title
AFTER INSERT ON chat_messages
WHEN NEW.role = 'user' AND (
SELECT title FROM chat_sessions WHERE id = NEW.session_id
) IS NULL
BEGIN
UPDATE chat_sessions
SET title = CASE
WHEN length(NEW.content) > ${50}
THEN substr(NEW.content, 1, ${50}) || '...'
ELSE NEW.content
END
WHERE id = NEW.session_id;
END;
`),a("Created triggers")}catch(s){let i=s instanceof Error?s.message:"Unknown error";a("Warning: Failed to create triggers:",i),a("Continuing with basic functionality")}try{await this.client.execute(`
CREATE INDEX IF NOT EXISTS idx_chat_sessions_profile
ON chat_sessions (profile);
`),await this.client.execute(`
CREATE INDEX IF NOT EXISTS idx_chat_sessions_created_at
ON chat_sessions (created_at DESC);
`),await this.client.execute(`
CREATE INDEX IF NOT EXISTS idx_chat_messages_session_id
ON chat_messages (session_id);
`),await this.client.execute(`
CREATE INDEX IF NOT EXISTS idx_chat_messages_timestamp
ON chat_messages (timestamp DESC);
`),await this.client.execute(`
CREATE INDEX IF NOT EXISTS idx_chat_messages_provider
ON chat_messages (provider);
`),await this.client.execute(`
CREATE INDEX IF NOT EXISTS idx_chat_messages_content_fts
ON chat_messages (content);
`),a("Created indexes")}catch(s){let i=s instanceof Error?s.message:"Unknown error";a("Warning: Failed to create indexes:",i),a("Continuing with basic functionality")}let t=await this.client.execute(`
SELECT count(*) as table_count FROM sqlite_master
WHERE type='table' AND name IN ('chat_sessions', 'chat_messages');
`),n=Number(t.rows[0].table_count);if(n!==2)throw new Error(`Expected 2 tables but found ${n}`);a("Database schema initialized successfully");return}catch(t){if(console.error(`Error initializing database schema (attempt ${e+1}):`,t),e++,e<r){let n=500*e;a(`Waiting ${n}ms before retry...`),await new Promise(s=>setTimeout(s,n))}}throw new Error(`Failed to initialize database after ${r} attempts`)}async createSession(e,r){await this.ensureDatabaseInitialized();let t=C();return await this.client.execute({sql:"INSERT INTO chat_sessions (id, profile, title) VALUES (?, ?, ?)",args:[t,e,r||null]}),t}async getSession(e){await this.ensureDatabaseInitialized();let r=await this.client.execute({sql:"SELECT * FROM chat_sessions WHERE id = ?",args:[e]});if(r.rows.length===0)return null;let t=r.rows[0];return{id:t.id,profile:t.profile,title:t.title??void 0,createdAt:t.created_at,updatedAt:t.updated_at,messageCount:Number(t.message_count)}}async getSessions(e={}){await this.ensureDatabaseInitialized();let{profile:r,limit:t=10,fromDate:n,toDate:s}=e,i=`
SELECT * FROM chat_sessions
WHERE 1=1
`,c=[];return r&&(i+=" AND profile = ?",c.push(r)),n&&(i+=" AND created_at >= ?",c.push(n)),s&&(i+=" AND created_at <= ?",c.push(s)),i+=" ORDER BY updated_at DESC LIMIT ?",c.push(t),(await this.client.execute({sql:i,args:c})).rows.map(o=>({id:o.id,profile:o.profile,title:o.title??void 0,createdAt:o.created_at,updatedAt:o.updated_at,messageCount:Number(o.message_count)}))}async deleteSession(e){return await this.ensureDatabaseInitialized(),(await this.client.execute({sql:"DELETE FROM chat_sessions WHERE id = ?",args:[e]})).rowsAffected>0}async updateSessionTitle(e,r){return(await this.client.execute({sql:"UPDATE chat_sessions SET title = ?, updated_at = datetime('now') WHERE id = ?",args:[r,e]})).rowsAffected>0}async addMessage(e,r,t,n,s,i,c,E){await this.ensureDatabaseInitialized();let o=C();return await this.client.execute({sql:`INSERT INTO chat_messages (
id, session_id, role, content, provider, model,
temperature, max_tokens, metadata
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,args:[o,e,r,t,n,s,i||null,c||null,E?JSON.stringify(E):null]}),o}async getMessages(e,r){await this.ensureDatabaseInitialized();let t=`
SELECT * FROM chat_messages
WHERE session_id = ?
ORDER BY timestamp ASC
`,n=[e];return r&&(t+=" LIMIT ?",n.push(r)),(await this.client.execute({sql:t,args:n})).rows.map(i=>({id:i.id,sessionId:i.session_id,role:i.role,content:i.content,timestamp:i.timestamp,provider:i.provider,model:i.model,temperature:i.temperature,maxTokens:i.max_tokens,metadata:i.metadata?JSON.parse(i.metadata):null}))}async searchMessages(e={}){await this.ensureDatabaseInitialized();let{profile:r,search:t,limit:n=10,fromDate:s,toDate:i,provider:c,model:E}=e,o=`
SELECT m.* FROM chat_messages m
JOIN chat_sessions s ON m.session_id = s.id
WHERE 1=1
`,d=[];return r&&(o+=" AND s.profile = ?",d.push(r)),t&&(o+=" AND m.content LIKE ?",d.push(`%${t}%`)),c&&(o+=" AND m.provider = ?",d.push(c)),E&&(o+=" AND m.model = ?",d.push(E)),s&&(o+=" AND m.timestamp >= ?",d.push(s)),i&&(o+=" AND m.timestamp <= ?",d.push(i)),o+=" ORDER BY m.timestamp DESC LIMIT ?",d.push(n),(await this.client.execute({sql:o,args:d})).rows.map(l=>({id:l.id,sessionId:l.session_id,role:l.role,content:l.content,timestamp:l.timestamp,provider:l.provider,model:l.model,temperature:l.temperature,maxTokens:l.max_tokens,metadata:l.metadata?JSON.parse(l.metadata):null}))}async getStats(e){await this.ensureDatabaseInitialized();let r="SELECT COUNT(*) as count FROM chat_sessions",t=[];e&&(r+=" WHERE profile = ?",t.push(e));let n=await this.client.execute({sql:r,args:t}),s=Number(n.rows[0].count),i=`
SELECT
COUNT(*) as total_count,
SUM(CASE WHEN role = 'user' THEN 1 ELSE 0 END) as user_count,
SUM(CASE WHEN role = 'assistant' THEN 1 ELSE 0 END) as assistant_count
FROM chat_messages
`,c=[];e&&(i+=`
JOIN chat_sessions ON chat_messages.session_id = chat_sessions.id
WHERE chat_sessions.profile = ?
`,c.push(e));let E=await this.client.execute({sql:i,args:c}),o=Number(E.rows[0].total_count),d=`
SELECT provider, COUNT(*) as count
FROM chat_messages
`,g=[];e&&(d+=`
JOIN chat_sessions ON chat_messages.session_id = chat_sessions.id
WHERE chat_sessions.profile = ?
`,g.push(e)),d+=" GROUP BY provider ORDER BY count DESC";let l=await this.client.execute({sql:d,args:g}),p={};l.rows.forEach(u=>{p[u.provider]=Number(u.count)});let _=`
SELECT model, COUNT(*) as count
FROM chat_messages
`,T=[];e&&(_+=`
JOIN chat_sessions ON chat_messages.session_id = chat_sessions.id
WHERE chat_sessions.profile = ?
`,T.push(e)),_+=" GROUP BY model ORDER BY count DESC";let A=await this.client.execute({sql:_,args:T}),b={};A.rows.forEach(u=>{b[u.model]=Number(u.count)});let x=await this.client.execute({sql:`
SELECT chat_sessions.profile, COUNT(*) as count
FROM chat_messages
JOIN chat_sessions ON chat_messages.session_id = chat_sessions.id
GROUP BY chat_sessions.profile
ORDER BY count DESC
`}),N={};x.rows.forEach(u=>{N[u.profile]=Number(u.count)});let w=`
SELECT
MIN(timestamp) as oldest,
MAX(timestamp) as newest
FROM chat_messages
`,R=[];e&&(w+=`
JOIN chat_sessions ON chat_messages.session_id = chat_sessions.id
WHERE chat_sessions.profile = ?
`,R.push(e));let S=await this.client.execute({sql:w,args:R});return{totalSessions:s,totalMessages:o,messagesByProvider:p,messagesByModel:b,messagesByProfile:N,oldestMessage:S.rows[0].oldest,newestMessage:S.rows[0].newest}}async deleteOldSessions(e,r){await this.ensureDatabaseInitialized();let t=new Date;t.setDate(t.getDate()-r);let n=t.toISOString();return(await this.client.execute({sql:`
DELETE FROM chat_sessions
WHERE profile = ? AND created_at < ?
`,args:[e,n]})).rowsAffected}async vacuum(){await this.ensureDatabaseInitialized(),await this.client.execute({sql:"VACUUM"})}async exportChatHistory(e={}){await this.ensureDatabaseInitialized();let{profile:r}=e,t=await this.getSessions({profile:r,limit:1e3}),n={};for(let i of t)n[i.id]=await this.getMessages(i.id);let s=await this.getStats(r);return{sessions:t,messages:n,stats:s}}async close(){await this.client.close()}};export{I as a};
/* Rawi (راوي) is the developer-friendly AI CLI that brings the power of 12 major AI providers directly to your terminal. With seamless shell integration, persistent conversations, and 200+ specialized prompt templates, Rawi transforms your command line into an intelligent development workspace. */
//# sourceMappingURL=chunk-GGXDDXXS.js.map