chittycan
Version:
Your completely autonomous network that grows with you - DNA ownership platform with encrypted vaults, PDX portability, and ChittyFoundation governance
332 lines (279 loc) • 8.7 kB
text/typescript
/**
* Smart Command Predictions - AI-Powered Next Command Suggestions
*
* Predicts your next command based on:
* - Time of day patterns
* - Command sequences
* - Current working directory
* - Recent git activity
* - Day of week patterns
*/
import { loadUsageStats, type CommandUsage } from "./usage-tracker.js";
import { loadConfig } from "./config.js";
export interface Prediction {
command: string;
confidence: number;
reason: string;
suggestedNL: string; // Natural language version
}
export interface PredictionContext {
timeOfDay: "morning" | "afternoon" | "evening" | "night";
dayOfWeek: number;
currentDir: string;
recentCommands: string[];
gitBranch?: string;
gitStatus?: string;
}
/**
* Get current context for predictions
*/
export function getCurrentContext(): PredictionContext {
const now = new Date();
const hour = now.getHours();
const timeOfDay =
hour < 12 ? "morning" :
hour < 17 ? "afternoon" :
hour < 21 ? "evening" : "night";
return {
timeOfDay,
dayOfWeek: now.getDay(),
currentDir: process.cwd(),
recentCommands: []
};
}
/**
* Predict next commands based on AI + patterns
*/
export async function predictNextCommands(
context: PredictionContext,
limit: number = 5
): Promise<Prediction[]> {
const stats = loadUsageStats();
const predictions: Prediction[] = [];
// Pattern 1: Time-based predictions
const timeBasedPreds = getTimeBasedPredictions(stats, context);
predictions.push(...timeBasedPreds);
// Pattern 2: Sequence-based predictions
const sequencePreds = getSequenceBasedPredictions(stats, context);
predictions.push(...sequencePreds);
// Pattern 3: Directory-based predictions
const dirPreds = getDirectoryBasedPredictions(stats, context);
predictions.push(...dirPreds);
// Pattern 4: Day-of-week predictions
const dayPreds = getDayOfWeekPredictions(stats, context);
predictions.push(...dayPreds);
// Deduplicate and sort by confidence
const uniquePreds = deduplicatePredictions(predictions);
return uniquePreds.slice(0, limit);
}
/**
* Time-based predictions (morning routines, afternoon patterns, etc.)
*/
function getTimeBasedPredictions(
stats: any,
context: PredictionContext
): Prediction[] {
const predictions: Prediction[] = [];
const timeCommands = stats.commands.filter((cmd: CommandUsage) => {
const cmdTime = new Date(cmd.timestamp);
const cmdHour = cmdTime.getHours();
const contextHour = new Date().getHours();
// Match within 2-hour window
return Math.abs(cmdHour - contextHour) <= 2;
});
// Count frequency
const commandCounts: Record<string, number> = {};
for (const cmd of timeCommands) {
commandCounts[cmd.cli] = (commandCounts[cmd.cli] || 0) + 1;
}
// Top 3 time-based commands
const sorted = Object.entries(commandCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, 3);
for (const [cli, count] of sorted) {
predictions.push({
command: `can ${cli}`,
confidence: Math.min(count / timeCommands.length, 0.9),
reason: `You often use ${cli} during ${context.timeOfDay}`,
suggestedNL: `can ${cli} [your command]`
});
}
return predictions;
}
/**
* Sequence-based predictions (what usually comes after what you just did)
*/
function getSequenceBasedPredictions(
stats: any,
context: PredictionContext
): Prediction[] {
const predictions: Prediction[] = [];
if (stats.commands.length < 2) return predictions;
// Get last command
const lastCmd = stats.commands[stats.commands.length - 1];
// Find what usually follows this command
const sequences: Record<string, number> = {};
for (let i = 0; i < stats.commands.length - 1; i++) {
if (stats.commands[i].cli === lastCmd.cli) {
const nextCli = stats.commands[i + 1].cli;
sequences[nextCli] = (sequences[nextCli] || 0) + 1;
}
}
// Top 2 sequence predictions
const sorted = Object.entries(sequences)
.sort((a, b) => b[1] - a[1])
.slice(0, 2);
for (const [cli, count] of sorted) {
predictions.push({
command: `can ${cli}`,
confidence: Math.min(count / 5, 0.85),
reason: `You often use ${cli} after ${lastCmd.cli}`,
suggestedNL: `can ${cli} [your command]`
});
}
return predictions;
}
/**
* Directory-based predictions (different commands in different dirs)
*/
function getDirectoryBasedPredictions(
stats: any,
context: PredictionContext
): Prediction[] {
const predictions: Prediction[] = [];
// This would need directory context stored with commands
// For now, just return common project-related commands
if (context.currentDir.includes("projects") || context.currentDir.includes("development")) {
predictions.push({
command: "can git",
confidence: 0.7,
reason: "You're in a development directory",
suggestedNL: "can git status"
});
predictions.push({
command: "can gh",
confidence: 0.65,
reason: "Common GitHub operations in project dirs",
suggestedNL: "can gh pr status"
});
}
return predictions;
}
/**
* Day-of-week predictions (Monday morning patterns vs Friday afternoon)
*/
function getDayOfWeekPredictions(
stats: any,
context: PredictionContext
): Prediction[] {
const predictions: Prediction[] = [];
const dayCommands = stats.commands.filter((cmd: CommandUsage) => {
const cmdDay = new Date(cmd.timestamp).getDay();
return cmdDay === context.dayOfWeek;
});
if (dayCommands.length > 0) {
const commandCounts: Record<string, number> = {};
for (const cmd of dayCommands) {
commandCounts[cmd.cli] = (commandCounts[cmd.cli] || 0) + 1;
}
const topCmd = Object.entries(commandCounts)
.sort((a, b) => b[1] - a[1])[0];
if (topCmd) {
const dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
predictions.push({
command: `can ${topCmd[0]}`,
confidence: 0.6,
reason: `Common ${dayNames[context.dayOfWeek]} task`,
suggestedNL: `can ${topCmd[0]} [your command]`
});
}
}
return predictions;
}
/**
* Deduplicate predictions and sort by confidence
*/
function deduplicatePredictions(predictions: Prediction[]): Prediction[] {
const seen = new Map<string, Prediction>();
for (const pred of predictions) {
const existing = seen.get(pred.command);
if (!existing || pred.confidence > existing.confidence) {
seen.set(pred.command, pred);
}
}
return Array.from(seen.values())
.sort((a, b) => b.confidence - a.confidence);
}
/**
* Get AI-enhanced predictions using configured AI remote
*/
export async function getAIPredictions(
context: PredictionContext,
stats: any
): Promise<Prediction[]> {
const config = loadConfig();
const aiRemote = findAIRemote(config);
if (!aiRemote) {
return []; // Fall back to pattern-based only
}
try {
const prompt = buildPredictionPrompt(context, stats);
const response = await callAI(aiRemote, prompt);
return parsePredictions(response);
} catch (error) {
return []; // Silently fall back
}
}
/**
* Find AI remote in config
*/
function findAIRemote(config: any): any {
if (!config.remotes || !Array.isArray(config.remotes)) return null;
const aiTypes = ["openai", "anthropic", "ollama", "groq"];
return config.remotes.find((r: any) => aiTypes.includes(r.type));
}
/**
* Build prompt for AI prediction
*/
function buildPredictionPrompt(context: PredictionContext, stats: any): string {
const recentCmds = stats.commands.slice(-10).map((c: CommandUsage) =>
`${c.cli}: ${c.naturalLanguage}`
).join("\n");
return `Based on these recent commands and context, predict the next 3 most likely commands:
Recent commands:
${recentCmds}
Context:
- Time: ${context.timeOfDay}
- Day: ${context.dayOfWeek}
- Directory: ${context.currentDir}
Output format (one per line):
cli|confidence|reason|suggested_natural_language
Example:
gh|0.85|User often checks PR status after push|can gh check pr status`;
}
/**
* Call AI service
*/
async function callAI(remote: any, prompt: string): Promise<string> {
// Simplified - would use actual AI service
return "";
}
/**
* Parse AI predictions response
*/
function parsePredictions(response: string): Prediction[] {
const lines = response.trim().split("\n");
const predictions: Prediction[] = [];
for (const line of lines) {
const parts = line.split("|");
if (parts.length === 4) {
predictions.push({
command: `can ${parts[0]}`,
confidence: parseFloat(parts[1]),
reason: parts[2],
suggestedNL: parts[3]
});
}
}
return predictions;
}