Initial upload with CI/CD workflow
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-01-31 13:13:09 +00:00
parent a14aba3854
commit 3d71513820

View File

@@ -0,0 +1,263 @@
"""Time-based analysis command."""
import os
from collections import defaultdict
from datetime import datetime, timedelta
from typing import Optional
import click
from rich.console import Console
from rich.table import Table
from shellhist.core import HistoryLoader
from shellhist.utils import format_timestamp
@click.command("analyze-time")
@click.option(
"--history",
"-H",
type=str,
help="Path to history file",
)
@click.option(
"--daily/--no-daily",
default=False,
help="Show commands run at similar times daily",
)
@click.option(
"--weekly/--no-weekly",
default=False,
help="Show commands run at similar times weekly",
)
@click.option(
"--time-range",
"-t",
type=str,
default="7d",
help="Time range filter (e.g., '7d', '24h', '30d')",
)
@click.option(
"--shell",
"-s",
type=click.Choice(["bash", "zsh"]),
help="Shell type for parsing",
)
@click.pass_context
def analyze_time_command(
ctx: click.Context,
history: Optional[str],
daily: bool,
weekly: bool,
time_range: str,
shell: Optional[str],
) -> None:
"""Analyze time-based patterns in your shell history.
Examples:
\b
shellhist analyze-time --daily
shellhist analyze-time --weekly --time-range 30d
shellhist analyze-time --daily --time-range 7d
"""
console = Console()
try:
loader = HistoryLoader(history_path=history)
store = loader.load()
if not store.entries:
console.print("[yellow]No entries found in history.[/yellow]")
return
entries_with_time = [
e for e in store.entries
if e.timestamp is not None
]
if not entries_with_time:
console.print(
"[yellow]No timestamps found in history. "
"Ensure your history includes timestamps.[/yellow]"
)
return
cutoff = _parse_time_range(time_range)
recent_entries = [
e for e in entries_with_time
if e.timestamp >= cutoff
]
if daily:
_analyze_daily_patterns(console, recent_entries)
elif weekly:
_analyze_weekly_patterns(console, recent_entries)
else:
_analyze_hourly_distribution(console, recent_entries)
except FileNotFoundError as e:
console.print(f"[red]Error: {e}[/red]")
ctx.exit(1)
except Exception as e:
console.print(f"[red]Error analyzing time patterns: {e}[/red]")
ctx.exit(1)
def _parse_time_range(time_range: str) -> datetime:
"""Parse time range string like '7d', '24h', '30d'."""
value = int(time_range[:-1])
unit = time_range[-1].lower()
now = datetime.now()
if unit == 'h':
return now - timedelta(hours=value)
elif unit == 'd':
return now - timedelta(days=value)
elif unit == 'w':
return now - timedelta(weeks=value)
else:
return now - timedelta(days=7)
def _analyze_hourly_distribution(console: Console, entries: list) -> None:
"""Analyze command distribution by hour of day."""
hourly = defaultdict(list)
for entry in entries:
hour = entry.timestamp.hour
hourly[hour].append(entry.command)
console.print("\n[bold cyan]Hourly Command Distribution[/bold cyan]")
table = Table(show_header=True, header_style="bold magenta")
table.add_column("Hour", width=10)
table.add_column("Commands", width=10)
table.add_column("Top Commands", width=60)
for hour in range(24):
cmds = hourly.get(hour, [])
if cmds:
top = defaultdict(int)
for cmd in cmds:
top[cmd] += 1
top_sorted = sorted(top.items(), key=lambda x: x[1], reverse=True)[:2]
top_str = ", ".join(f"{c}({n})" for c, n in top_sorted)
else:
top_str = "-"
hour_label = f"{hour:02d}:00"
table.add_row(hour_label, str(len(cmds)), top_str[:58])
console.print(table)
def _analyze_daily_patterns(console: Console, entries: list) -> None:
"""Analyze commands run at similar times daily."""
daily_patterns = defaultdict(list)
for entry in entries:
hour = entry.timestamp.hour
key = (entry.timestamp.weekday(), hour)
daily_patterns[key].append(entry.command)
console.print("\n[bold cyan]Daily Time Patterns[/bold cyan]")
day_names = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
patterns_found = []
for (weekday, hour), cmds in daily_patterns.items():
if len(cmds) >= 2:
top = defaultdict(int)
for cmd in cmds:
top[cmd] += 1
top_sorted = sorted(top.items(), key=lambda x: x[1], reverse=True)[:3]
patterns_found.append({
"day": day_names[weekday],
"hour": hour,
"count": len(cmds),
"top": top_sorted,
})
if not patterns_found:
console.print("[yellow]No significant daily patterns found.[/yellow]")
return
patterns_found.sort(key=lambda x: x["count"], reverse=True)
table = Table(show_header=True, header_style="bold magenta")
table.add_column("Day", width=8)
table.add_column("Hour", width=8)
table.add_column("Times", width=8)
table.add_column("Common Commands", width=60)
for p in patterns_found[:20]:
top_str = ", ".join(f"{c}({n})" for c, n in p["top"])
table.add_row(
p["day"],
f"{p['hour']:02d}:00",
str(p["count"]),
top_str[:58],
)
console.print(table)
def _analyze_weekly_patterns(console: Console, entries: list) -> None:
"""Analyze commands run at similar times weekly."""
weekly_patterns = defaultdict(list)
for entry in entries:
key = (
entry.timestamp.isocalendar().year,
entry.timestamp.isocalendar().week,
entry.timestamp.weekday(),
entry.timestamp.hour,
)
weekly_patterns[key].append(entry.command)
console.print("\n[bold cyan]Weekly Time Patterns[/bold cyan]")
day_names = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
patterns_found = []
for key, cmds in weekly_patterns.items():
year, week, weekday, hour = key
if len(cmds) >= 2:
top = defaultdict(int)
for cmd in cmds:
top[cmd] += 1
top_sorted = sorted(top.items(), key=lambda x: x[1], reverse=True)[:3]
patterns_found.append({
"week": week,
"day": day_names[weekday],
"hour": hour,
"count": len(cmds),
"top": top_sorted,
})
if not patterns_found:
console.print("[yellow]No significant weekly patterns found.[/yellow]")
return
patterns_found.sort(key=lambda x: x["count"], reverse=True)
table = Table(show_header=True, header_style="bold magenta")
table.add_column("Week", width=8)
table.add_column("Day", width=8)
table.add_column("Hour", width=8)
table.add_column("Times", width=8)
table.add_column("Common Commands", width=50)
for p in patterns_found[:20]:
top_str = ", ".join(f"{c}({n})" for c, n in p["top"])
table.add_row(
str(p["week"]),
p["day"],
f"{p['hour']:02d}:00",
str(p["count"]),
top_str[:48],
)
console.print(table)