This commit is contained in:
Bendt
2025-12-18 22:11:47 -05:00
parent 0ed7800575
commit a41d59e529
26 changed files with 4187 additions and 373 deletions

View File

@@ -1,8 +1,32 @@
"""Calendar CLI commands."""
import click
import subprocess
@click.command()
def calendar():
"""Open the calendar (khal interactive)."""
click.echo("Opening calendar...")
subprocess.run(["khal", "interactive"])
@click.option("--interactive", "-i", is_flag=True, help="Use khal interactive mode")
@click.option("--weekdays", "-w", is_flag=True, help="Show only weekdays (Mon-Fri)")
def calendar(interactive: bool, weekdays: bool):
"""Open the calendar TUI.
Displays a week view of calendar events from khal.
Navigation:
j/k - Move up/down (time)
h/l - Move left/right (day)
H/L - Previous/Next week
g - Go to today
w - Toggle weekends
Enter - View event details
q - Quit
"""
if interactive:
# Fallback to khal interactive mode
import subprocess
click.echo("Opening khal interactive...")
subprocess.run(["khal", "interactive"])
else:
from src.calendar import run_app
run_app()

View File

@@ -20,6 +20,7 @@ from src.services.microsoft_graph.calendar import (
)
from src.services.microsoft_graph.mail import (
fetch_mail_async,
fetch_archive_mail_async,
archive_mail_async,
delete_mail_async,
synchronize_maildir_async,
@@ -216,7 +217,9 @@ def create_maildir_structure(base_path):
ensure_directory_exists(os.path.join(base_path, "cur"))
ensure_directory_exists(os.path.join(base_path, "new"))
ensure_directory_exists(os.path.join(base_path, "tmp"))
ensure_directory_exists(os.path.join(base_path, ".Archives"))
ensure_directory_exists(os.path.join(base_path, ".Archive", "cur"))
ensure_directory_exists(os.path.join(base_path, ".Archive", "new"))
ensure_directory_exists(os.path.join(base_path, ".Archive", "tmp"))
ensure_directory_exists(os.path.join(base_path, ".Trash", "cur"))
# Create outbox structure for sending emails
ensure_directory_exists(os.path.join(base_path, "outbox", "new"))
@@ -436,6 +439,7 @@ async def _sync_outlook_data(
with progress:
task_fetch = progress.add_task("[green]Syncing Inbox...", total=0)
task_fetch_archive = progress.add_task("[green]Syncing Archive...", total=0)
task_calendar = progress.add_task("[cyan]Fetching calendar...", total=0)
task_local_calendar = progress.add_task(
"[magenta]Syncing local calendar...", total=0
@@ -515,6 +519,15 @@ async def _sync_outlook_data(
dry_run,
download_attachments,
),
fetch_archive_mail_async(
maildir_path,
attachments_dir,
headers,
progress,
task_fetch_archive,
dry_run,
download_attachments,
),
fetch_calendar_async(
headers,
progress,

View File

@@ -17,15 +17,45 @@ from textual.binding import Binding
from rich.text import Text
from datetime import datetime, timedelta
import asyncio
import json
import os
import sys
import time
from typing import Dict, Any, Optional, List, Callable
from pathlib import Path
from src.utils.shared_config import get_theme_name
# Default sync interval in seconds (5 minutes)
DEFAULT_SYNC_INTERVAL = 300
# Sync tasks config file path
SYNC_TASKS_CONFIG_PATH = os.path.expanduser("~/.config/luk/sync_tasks.json")
def load_sync_tasks_config() -> Dict[str, bool]:
"""Load sync tasks enabled/disabled state from config file.
Returns:
Dict mapping task_id to enabled state (True = enabled, False = disabled)
"""
if os.path.exists(SYNC_TASKS_CONFIG_PATH):
try:
with open(SYNC_TASKS_CONFIG_PATH, "r") as f:
return json.load(f)
except Exception:
pass
# Default: all tasks enabled
return {}
def save_sync_tasks_config(config: Dict[str, bool]) -> None:
"""Save sync tasks enabled/disabled state to config file."""
os.makedirs(os.path.dirname(SYNC_TASKS_CONFIG_PATH), exist_ok=True)
with open(SYNC_TASKS_CONFIG_PATH, "w") as f:
json.dump(config, f, indent=2)
# Futuristic spinner frames
# SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
# Alternative spinners you could use:
@@ -73,7 +103,9 @@ class TaskStatus:
class TaskListItem(ListItem):
"""A list item representing a sync task."""
def __init__(self, task_id: str, task_name: str, *args, **kwargs):
def __init__(
self, task_id: str, task_name: str, enabled: bool = True, *args, **kwargs
):
super().__init__(*args, **kwargs)
self.task_id = task_id
self.task_name = task_name
@@ -81,6 +113,7 @@ class TaskListItem(ListItem):
self.progress = 0
self.total = 100
self.spinner_frame = 0
self.enabled = enabled
def compose(self) -> ComposeResult:
"""Compose the task item layout."""
@@ -88,6 +121,8 @@ class TaskListItem(ListItem):
def _get_status_icon(self) -> str:
"""Get icon based on status."""
if not self.enabled:
return "" # Disabled icon
if self.status == TaskStatus.RUNNING:
return SPINNER_FRAMES[self.spinner_frame % len(SPINNER_FRAMES)]
icons = {
@@ -103,6 +138,8 @@ class TaskListItem(ListItem):
def _get_status_color(self) -> str:
"""Get color based on status."""
if not self.enabled:
return "dim italic"
colors = {
TaskStatus.PENDING: "dim",
TaskStatus.RUNNING: "cyan",
@@ -116,6 +153,13 @@ class TaskListItem(ListItem):
icon = self._get_status_icon()
color = self._get_status_color()
# Disabled tasks show differently
if not self.enabled:
text = Text()
text.append(f"{icon} ", style="dim")
text.append(f"{self.task_name} [Disabled]", style=color)
return text
# Use green checkmark for completed, but white text for readability
if self.status == TaskStatus.RUNNING:
progress_pct = (
@@ -156,6 +200,7 @@ class SyncDashboard(App):
Binding("s", "sync_now", "Sync Now"),
Binding("d", "daemonize", "Daemonize"),
Binding("r", "refresh", "Refresh"),
Binding("t", "toggle_task", "Toggle"),
Binding("+", "increase_interval", "+Interval"),
Binding("-", "decrease_interval", "-Interval"),
Binding("up", "cursor_up", "Up", show=False),
@@ -277,10 +322,16 @@ class SyncDashboard(App):
self._initial_sync_interval = sync_interval
self._notify = notify
self._demo_mode = demo_mode
# Load task enabled/disabled config
self._tasks_config = load_sync_tasks_config()
# Merge provided config with defaults
self._sync_config = {**DEFAULT_SYNC_CONFIG, **(sync_config or {})}
self._sync_config["notify"] = notify
def _is_task_enabled(self, task_id: str) -> bool:
"""Check if a task is enabled (default: True)."""
return self._tasks_config.get(task_id, True)
def compose(self) -> ComposeResult:
"""Compose the dashboard layout."""
yield Header()
@@ -291,15 +342,56 @@ class SyncDashboard(App):
yield Static("Tasks", classes="sidebar-title")
yield ListView(
# Stage 1: Sync local changes to server
TaskListItem("archive", "Archive Mail", id="task-archive"),
TaskListItem("outbox", "Outbox Send", id="task-outbox"),
TaskListItem(
"archive",
"Archive Mail",
enabled=self._is_task_enabled("archive"),
id="task-archive",
),
TaskListItem(
"outbox",
"Outbox Send",
enabled=self._is_task_enabled("outbox"),
id="task-outbox",
),
# Stage 2: Fetch from server
TaskListItem("inbox", "Inbox Sync", id="task-inbox"),
TaskListItem("calendar", "Calendar Sync", id="task-calendar"),
TaskListItem(
"inbox",
"Inbox Sync",
enabled=self._is_task_enabled("inbox"),
id="task-inbox",
),
TaskListItem(
"archive-fetch",
"Archive Sync",
enabled=self._is_task_enabled("archive-fetch"),
id="task-archive-fetch",
),
TaskListItem(
"calendar",
"Calendar Sync",
enabled=self._is_task_enabled("calendar"),
id="task-calendar",
),
# Stage 3: Task management
TaskListItem("godspeed", "Godspeed Sync", id="task-godspeed"),
TaskListItem("dstask", "dstask Sync", id="task-dstask"),
TaskListItem("sweep", "Task Sweep", id="task-sweep"),
TaskListItem(
"godspeed",
"Godspeed Sync",
enabled=self._is_task_enabled("godspeed"),
id="task-godspeed",
),
TaskListItem(
"dstask",
"dstask Sync",
enabled=self._is_task_enabled("dstask"),
id="task-dstask",
),
TaskListItem(
"sweep",
"Task Sweep",
enabled=self._is_task_enabled("sweep"),
id="task-sweep",
),
id="task-list",
)
# Countdown timer at bottom of sidebar
@@ -332,6 +424,9 @@ class SyncDashboard(App):
def on_mount(self) -> None:
"""Initialize the dashboard."""
# Set theme from shared config
self.theme = get_theme_name()
# Store references to task items
task_list = self.query_one("#task-list", ListView)
for item in task_list.children:
@@ -436,6 +531,12 @@ class SyncDashboard(App):
if task_id == self.selected_task:
self._update_main_panel()
def is_task_enabled(self, task_id: str) -> bool:
"""Check if a task is enabled."""
if task_id in self._task_items:
return self._task_items[task_id].enabled
return self._is_task_enabled(task_id)
def update_task(self, task_id: str, progress: int, message: str = "") -> None:
"""Update task progress."""
if task_id in self._task_items:
@@ -495,6 +596,25 @@ class SyncDashboard(App):
task_list = self.query_one("#task-list", ListView)
task_list.action_cursor_down()
def action_toggle_task(self) -> None:
"""Toggle the selected task enabled/disabled state."""
if self.selected_task not in self._task_items:
return
item = self._task_items[self.selected_task]
# Toggle enabled state
item.enabled = not item.enabled
item.update_display()
# Update config and save
self._tasks_config[self.selected_task] = item.enabled
save_sync_tasks_config(self._tasks_config)
# Log the change
state = "enabled" if item.enabled else "disabled"
self._log_to_task(self.selected_task, f"Task {state}")
self._update_main_panel()
def action_sync_now(self) -> None:
"""Trigger an immediate sync."""
if self._sync_callback:
@@ -770,6 +890,10 @@ class SyncProgressTracker:
"""Mark a task as skipped."""
self.dashboard.skip_task(task_id, reason)
def is_task_enabled(self, task_id: str) -> bool:
"""Check if a task is enabled."""
return self.dashboard.is_task_enabled(task_id)
# Global dashboard instance
_dashboard_instance: Optional[SyncDashboard] = None
@@ -823,68 +947,99 @@ async def run_dashboard_sync(
# Stage 1: Sync local changes to server
# Archive mail
tracker.start_task("archive", 100)
tracker.update_task("archive", 50, "Scanning for archived messages...")
await asyncio.sleep(0.3)
tracker.update_task("archive", 100, "Moving 3 messages to archive...")
await asyncio.sleep(0.2)
tracker.complete_task("archive", "3 messages archived")
if tracker.is_task_enabled("archive"):
tracker.start_task("archive", 100)
tracker.update_task("archive", 50, "Scanning for archived messages...")
await asyncio.sleep(0.3)
tracker.update_task("archive", 100, "Moving 3 messages to archive...")
await asyncio.sleep(0.2)
tracker.complete_task("archive", "3 messages archived")
else:
tracker.skip_task("archive", "Disabled")
# Outbox
tracker.start_task("outbox", 100)
tracker.update_task("outbox", 50, "Checking outbox...")
await asyncio.sleep(0.2)
tracker.complete_task("outbox", "No pending emails")
if tracker.is_task_enabled("outbox"):
tracker.start_task("outbox", 100)
tracker.update_task("outbox", 50, "Checking outbox...")
await asyncio.sleep(0.2)
tracker.complete_task("outbox", "No pending emails")
else:
tracker.skip_task("outbox", "Disabled")
# Stage 2: Fetch from server
# Inbox sync - simulate finding new messages
tracker.start_task("inbox", 100)
for i in range(0, 101, 20):
tracker.update_task("inbox", i, f"Fetching emails... {i}%")
await asyncio.sleep(0.3)
if tracker.is_task_enabled("inbox"):
tracker.start_task("inbox", 100)
for i in range(0, 101, 20):
tracker.update_task("inbox", i, f"Fetching emails... {i}%")
await asyncio.sleep(0.3)
new_message_count = random.randint(0, 5)
if new_message_count > 0:
tracker.complete_task("inbox", f"{new_message_count} new emails")
if dashboard._notify:
from src.utils.notifications import notify_new_emails
new_message_count = random.randint(0, 5)
if new_message_count > 0:
tracker.complete_task("inbox", f"{new_message_count} new emails")
if dashboard._notify:
from src.utils.notifications import notify_new_emails
notify_new_emails(new_message_count, "")
notify_new_emails(new_message_count, "")
else:
tracker.complete_task("inbox", "No new emails")
else:
tracker.complete_task("inbox", "No new emails")
tracker.skip_task("inbox", "Disabled")
# Archive fetch
if tracker.is_task_enabled("archive-fetch"):
tracker.start_task("archive-fetch", 100)
for i in range(0, 101, 25):
tracker.update_task("archive-fetch", i, f"Fetching archive... {i}%")
await asyncio.sleep(0.2)
tracker.complete_task("archive-fetch", "Archive synced")
else:
tracker.skip_task("archive-fetch", "Disabled")
# Calendar sync
tracker.start_task("calendar", 100)
for i in range(0, 101, 25):
tracker.update_task("calendar", i, f"Syncing events... {i}%")
await asyncio.sleep(0.3)
tracker.complete_task("calendar", "25 events synced")
if tracker.is_task_enabled("calendar"):
tracker.start_task("calendar", 100)
for i in range(0, 101, 25):
tracker.update_task("calendar", i, f"Syncing events... {i}%")
await asyncio.sleep(0.3)
tracker.complete_task("calendar", "25 events synced")
else:
tracker.skip_task("calendar", "Disabled")
# Stage 3: Task management
# Godspeed sync
tracker.start_task("godspeed", 100)
for i in range(0, 101, 33):
tracker.update_task(
"godspeed", min(i, 100), f"Syncing tasks... {min(i, 100)}%"
)
await asyncio.sleep(0.3)
tracker.complete_task("godspeed", "42 tasks synced")
if tracker.is_task_enabled("godspeed"):
tracker.start_task("godspeed", 100)
for i in range(0, 101, 33):
tracker.update_task(
"godspeed", min(i, 100), f"Syncing tasks... {min(i, 100)}%"
)
await asyncio.sleep(0.3)
tracker.complete_task("godspeed", "42 tasks synced")
else:
tracker.skip_task("godspeed", "Disabled")
# dstask sync
tracker.start_task("dstask", 100)
tracker.update_task("dstask", 30, "Running dstask sync...")
await asyncio.sleep(0.3)
tracker.update_task("dstask", 70, "Pushing changes...")
await asyncio.sleep(0.2)
tracker.complete_task("dstask", "Sync completed")
if tracker.is_task_enabled("dstask"):
tracker.start_task("dstask", 100)
tracker.update_task("dstask", 30, "Running dstask sync...")
await asyncio.sleep(0.3)
tracker.update_task("dstask", 70, "Pushing changes...")
await asyncio.sleep(0.2)
tracker.complete_task("dstask", "Sync completed")
else:
tracker.skip_task("dstask", "Disabled")
# Task sweep
tracker.start_task("sweep")
tracker.update_task("sweep", 50, "Scanning notes directory...")
await asyncio.sleep(0.2)
tracker.skip_task("sweep", "Before 6 PM, skipping daily sweep")
if tracker.is_task_enabled("sweep"):
tracker.start_task("sweep")
tracker.update_task("sweep", 50, "Scanning notes directory...")
await asyncio.sleep(0.2)
tracker.skip_task("sweep", "Before 6 PM, skipping daily sweep")
else:
tracker.skip_task("sweep", "Disabled")
# Schedule next sync
dashboard.schedule_next_sync()
@@ -902,6 +1057,7 @@ async def run_dashboard_sync(
synchronize_maildir_async,
process_outbox_async,
fetch_mail_async,
fetch_archive_mail_async,
)
from src.services.microsoft_graph.calendar import (
fetch_calendar_events,
@@ -955,32 +1111,46 @@ async def run_dashboard_sync(
# ===== STAGE 1: Sync local changes to server =====
# Archive mail
tracker.start_task("archive", 100)
tracker.update_task("archive", 10, "Checking for archived messages...")
try:
archive_progress = DashboardProgressAdapter(tracker, "archive")
await archive_mail_async(
maildir_path, headers, archive_progress, None, dry_run
)
tracker.complete_task("archive", "Archive sync complete")
except Exception as e:
tracker.error_task("archive", str(e))
if tracker.is_task_enabled("archive"):
tracker.start_task("archive", 100)
tracker.update_task("archive", 10, "Checking for archived messages...")
try:
archive_progress = DashboardProgressAdapter(tracker, "archive")
await archive_mail_async(
maildir_path,
headers,
archive_progress,
None,
dry_run,
is_cancelled=lambda: not tracker.is_task_enabled("archive"),
)
if tracker.is_task_enabled("archive"):
tracker.complete_task("archive", "Archive sync complete")
else:
tracker.skip_task("archive", "Cancelled")
except Exception as e:
tracker.error_task("archive", str(e))
else:
tracker.skip_task("archive", "Disabled")
# Process outbox (send pending emails)
tracker.start_task("outbox", 100)
tracker.update_task("outbox", 10, "Checking outbox...")
try:
outbox_progress = DashboardProgressAdapter(tracker, "outbox")
result = await process_outbox_async(
base_maildir_path, org, headers, outbox_progress, None, dry_run
)
sent_count, failed_count = result if result else (0, 0)
if sent_count > 0:
tracker.complete_task("outbox", f"{sent_count} emails sent")
else:
tracker.complete_task("outbox", "No pending emails")
except Exception as e:
tracker.error_task("outbox", str(e))
if tracker.is_task_enabled("outbox"):
tracker.start_task("outbox", 100)
tracker.update_task("outbox", 10, "Checking outbox...")
try:
outbox_progress = DashboardProgressAdapter(tracker, "outbox")
result = await process_outbox_async(
base_maildir_path, org, headers, outbox_progress, None, dry_run
)
sent_count, failed_count = result if result else (0, 0)
if sent_count > 0:
tracker.complete_task("outbox", f"{sent_count} emails sent")
else:
tracker.complete_task("outbox", "No pending emails")
except Exception as e:
tracker.error_task("outbox", str(e))
else:
tracker.skip_task("outbox", "Disabled")
# ===== STAGE 2: Fetch from server =====
@@ -994,160 +1164,216 @@ async def run_dashboard_sync(
messages_before += len([f for f in os.listdir(cur_dir) if ".eml" in f])
# Inbox sync
tracker.start_task("inbox", 100)
tracker.update_task("inbox", 10, "Fetching emails from server...")
try:
inbox_progress = DashboardProgressAdapter(tracker, "inbox")
await fetch_mail_async(
maildir_path,
attachments_dir,
headers,
inbox_progress,
None,
dry_run,
download_attachments,
)
tracker.update_task("inbox", 80, "Processing messages...")
# Count new messages
messages_after = 0
if os.path.exists(new_dir):
messages_after += len(
[f for f in os.listdir(new_dir) if ".eml" in f]
)
if os.path.exists(cur_dir):
messages_after += len(
[f for f in os.listdir(cur_dir) if ".eml" in f]
if tracker.is_task_enabled("inbox"):
tracker.start_task("inbox", 100)
tracker.update_task("inbox", 10, "Fetching emails from server...")
try:
inbox_progress = DashboardProgressAdapter(tracker, "inbox")
await fetch_mail_async(
maildir_path,
attachments_dir,
headers,
inbox_progress,
None,
dry_run,
download_attachments,
is_cancelled=lambda: not tracker.is_task_enabled("inbox"),
)
new_message_count = messages_after - messages_before
# Check if cancelled before completing
if not tracker.is_task_enabled("inbox"):
tracker.skip_task("inbox", "Cancelled")
else:
tracker.update_task("inbox", 80, "Processing messages...")
if new_message_count > 0:
tracker.complete_task("inbox", f"{new_message_count} new emails")
if dashboard._notify and not dry_run:
notify_new_emails(new_message_count, org)
else:
tracker.complete_task("inbox", "No new emails")
except Exception as e:
tracker.error_task("inbox", str(e))
# Count new messages
messages_after = 0
if os.path.exists(new_dir):
messages_after += len(
[f for f in os.listdir(new_dir) if ".eml" in f]
)
if os.path.exists(cur_dir):
messages_after += len(
[f for f in os.listdir(cur_dir) if ".eml" in f]
)
new_message_count = messages_after - messages_before
if new_message_count > 0:
tracker.complete_task(
"inbox", f"{new_message_count} new emails"
)
if dashboard._notify and not dry_run:
notify_new_emails(new_message_count, org)
else:
tracker.complete_task("inbox", "No new emails")
except Exception as e:
tracker.error_task("inbox", str(e))
else:
tracker.skip_task("inbox", "Disabled")
# Archive sync (fetch archived messages from server)
if tracker.is_task_enabled("archive-fetch"):
tracker.start_task("archive-fetch", 100)
tracker.update_task("archive-fetch", 10, "Fetching archived emails...")
try:
archive_progress = DashboardProgressAdapter(
tracker, "archive-fetch"
)
await fetch_archive_mail_async(
maildir_path,
attachments_dir,
headers,
archive_progress,
None,
dry_run,
download_attachments,
is_cancelled=lambda: not tracker.is_task_enabled(
"archive-fetch"
),
)
if tracker.is_task_enabled("archive-fetch"):
tracker.complete_task("archive-fetch", "Archive synced")
else:
tracker.skip_task("archive-fetch", "Cancelled")
except Exception as e:
tracker.error_task("archive-fetch", str(e))
else:
tracker.skip_task("archive-fetch", "Disabled")
# Calendar sync
tracker.start_task("calendar", 100)
tracker.update_task("calendar", 10, "Fetching calendar events...")
try:
events, total_events = await fetch_calendar_events(
headers=headers, days_back=days_back, days_forward=days_forward
)
tracker.update_task(
"calendar", 50, f"Processing {len(events)} events..."
)
if tracker.is_task_enabled("calendar"):
tracker.start_task("calendar", 100)
tracker.update_task("calendar", 10, "Fetching calendar events...")
try:
events, total_events = await fetch_calendar_events(
headers=headers, days_back=days_back, days_forward=days_forward
)
tracker.update_task(
"calendar", 50, f"Processing {len(events)} events..."
)
if not dry_run:
calendar_progress = DashboardProgressAdapter(tracker, "calendar")
org_vdir_path = os.path.join(vdir, org) if vdir else None
if vdir and org_vdir_path:
save_events_to_vdir(
events, org_vdir_path, calendar_progress, None, dry_run
)
elif icsfile:
save_events_to_file(
events,
f"{icsfile}/events_latest.ics",
calendar_progress,
None,
dry_run,
if not dry_run:
calendar_progress = DashboardProgressAdapter(
tracker, "calendar"
)
org_vdir_path = os.path.join(vdir, org) if vdir else None
if vdir and org_vdir_path:
save_events_to_vdir(
events, org_vdir_path, calendar_progress, None, dry_run
)
elif icsfile:
save_events_to_file(
events,
f"{icsfile}/events_latest.ics",
calendar_progress,
None,
dry_run,
)
tracker.complete_task("calendar", f"{len(events)} events synced")
except Exception as e:
tracker.error_task("calendar", str(e))
tracker.complete_task("calendar", f"{len(events)} events synced")
except Exception as e:
tracker.error_task("calendar", str(e))
else:
tracker.skip_task("calendar", "Disabled")
# ===== STAGE 3: Godspeed operations =====
# Godspeed sync (runs every 15 minutes)
tracker.start_task("godspeed", 100)
if should_run_godspeed_sync():
tracker.update_task("godspeed", 10, "Syncing with Godspeed...")
try:
email, password, token = get_godspeed_credentials()
if token or (email and password):
from src.services.godspeed.client import GodspeedClient
from src.services.godspeed.sync import GodspeedSync
if tracker.is_task_enabled("godspeed"):
tracker.start_task("godspeed", 100)
if should_run_godspeed_sync():
tracker.update_task("godspeed", 10, "Syncing with Godspeed...")
try:
email, password, token = get_godspeed_credentials()
if token or (email and password):
from src.services.godspeed.client import GodspeedClient
from src.services.godspeed.sync import GodspeedSync
sync_dir = get_godspeed_sync_directory()
client = GodspeedClient(
email=email, password=password, token=token
)
sync_engine = GodspeedSync(client, sync_dir)
sync_engine.sync_bidirectional()
sync_dir = get_godspeed_sync_directory()
client = GodspeedClient(
email=email, password=password, token=token
)
sync_engine = GodspeedSync(client, sync_dir)
sync_engine.sync_bidirectional()
state = load_sync_state()
state["last_godspeed_sync"] = time.time()
save_sync_state(state)
state = load_sync_state()
state["last_godspeed_sync"] = time.time()
save_sync_state(state)
tracker.complete_task("godspeed", "Sync completed")
else:
tracker.skip_task("godspeed", "No credentials configured")
except Exception as e:
tracker.error_task("godspeed", str(e))
tracker.complete_task("godspeed", "Sync completed")
else:
tracker.skip_task("godspeed", "No credentials configured")
except Exception as e:
tracker.error_task("godspeed", str(e))
else:
tracker.skip_task("godspeed", "Not due yet (every 15 min)")
else:
tracker.skip_task("godspeed", "Not due yet (every 15 min)")
tracker.skip_task("godspeed", "Disabled")
# dstask sync
tracker.start_task("dstask", 100)
try:
from src.services.dstask.client import DstaskClient
if tracker.is_task_enabled("dstask"):
tracker.start_task("dstask", 100)
try:
from src.services.dstask.client import DstaskClient
dstask_client = DstaskClient()
if dstask_client.is_available():
tracker.update_task("dstask", 30, "Running dstask sync...")
success = dstask_client.sync()
if success:
tracker.complete_task("dstask", "Sync completed")
dstask_client = DstaskClient()
if dstask_client.is_available():
tracker.update_task("dstask", 30, "Running dstask sync...")
success = dstask_client.sync()
if success:
tracker.complete_task("dstask", "Sync completed")
else:
tracker.error_task("dstask", "Sync failed")
else:
tracker.error_task("dstask", "Sync failed")
else:
tracker.skip_task("dstask", "dstask not installed")
except Exception as e:
tracker.error_task("dstask", str(e))
tracker.skip_task("dstask", "dstask not installed")
except Exception as e:
tracker.error_task("dstask", str(e))
else:
tracker.skip_task("dstask", "Disabled")
# Task sweep (runs once daily after 6 PM)
tracker.start_task("sweep", 100)
if should_run_sweep():
tracker.update_task("sweep", 10, "Sweeping tasks from notes...")
try:
from src.cli.godspeed import TaskSweeper
if tracker.is_task_enabled("sweep"):
tracker.start_task("sweep", 100)
if should_run_sweep():
tracker.update_task("sweep", 10, "Sweeping tasks from notes...")
try:
from src.cli.godspeed import TaskSweeper
from datetime import datetime
notes_dir_env = os.getenv("NOTES_DIR")
if notes_dir_env and Path(notes_dir_env).exists():
godspeed_dir = get_godspeed_sync_directory()
sweeper = TaskSweeper(
Path(notes_dir_env), godspeed_dir, dry_run=dry_run
)
result = sweeper.sweep_tasks()
state = load_sync_state()
state["last_sweep_date"] = datetime.now().strftime(
"%Y-%m-%d"
)
save_sync_state(state)
swept = result.get("swept_tasks", 0)
if swept > 0:
tracker.complete_task("sweep", f"{swept} tasks swept")
else:
tracker.complete_task("sweep", "No tasks to sweep")
else:
tracker.skip_task("sweep", "$NOTES_DIR not configured")
except Exception as e:
tracker.error_task("sweep", str(e))
else:
from datetime import datetime
notes_dir_env = os.getenv("NOTES_DIR")
if notes_dir_env and Path(notes_dir_env).exists():
godspeed_dir = get_godspeed_sync_directory()
sweeper = TaskSweeper(
Path(notes_dir_env), godspeed_dir, dry_run=dry_run
)
result = sweeper.sweep_tasks()
state = load_sync_state()
state["last_sweep_date"] = datetime.now().strftime("%Y-%m-%d")
save_sync_state(state)
swept = result.get("swept_tasks", 0)
if swept > 0:
tracker.complete_task("sweep", f"{swept} tasks swept")
else:
tracker.complete_task("sweep", "No tasks to sweep")
current_hour = datetime.now().hour
if current_hour < 18:
tracker.skip_task("sweep", "Before 6 PM")
else:
tracker.skip_task("sweep", "$NOTES_DIR not configured")
except Exception as e:
tracker.error_task("sweep", str(e))
tracker.skip_task("sweep", "Already completed today")
else:
from datetime import datetime
current_hour = datetime.now().hour
if current_hour < 18:
tracker.skip_task("sweep", "Before 6 PM")
else:
tracker.skip_task("sweep", "Already completed today")
tracker.skip_task("sweep", "Disabled")
# Schedule next sync
dashboard.schedule_next_sync()