feat(system): seedgo v2 operational, full system audit, 14/15 branches at 99%
Three days of intensive work bringing seedgo to full operational status and driving all branches through comprehensive standards compliance. Seedgo v2.0.0: - 22 checkers active (up from 20), standards pack fully operational - New introspection standard researched from Dev-Pass, FPLAN-0017 open - Bypass system for false positives (.seedgo config) - Standards query and audit commands fully functional System-wide audit (FPLAN-0016): - All 14 auditable branches at 99%+ compliance - CLI imports standardized across all branches (console from cli.apps.modules) - handle_command(command, args) → bool contract added to all modules - print_help() function naming fixed for checker pattern matching - Handler extraction: large modules split, file I/O moved to handler layer - New handlers created across ai_mail, backup, daemon, flow, skills, spawn, seedgo Branch-specific highlights: - ai_mail: email.py split 840→420 lines, 4 new handlers - flow: dplan_flow.py 688→591 lines, 4 new handlers - seedgo: massive restructure — standards moved to handlers/aipass_standards/, old standards/ tree removed, bypass system added, diagnostics module - commons: database module added, CLI imports fixed - skills: 5 handle_commands added, help function renamed - trigger: error reporter handler, handle_command routing - All branches: consistent architecture, clean drone routing Culture doc (CLAUDE.md) added — documents AIPass philosophy, identity, memory system, and collaboration principles. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
09e759a8a4
commit
babedd9c64
@@ -1,41 +1,23 @@
|
||||
# ai_mail
|
||||
# AI_MAIL
|
||||
|
||||
Inter-agent messaging for AIPass. File-based email system that lets agents send, receive, and process messages using `@branch` addresses. No SMTP, no external services — just JSON files and symbolic routing.
|
||||
**Purpose:** Inter-agent messaging for AIPass. File-based email system that lets agents send, receive, and process messages using `@branch` addresses. No SMTP, no external services — just JSON files and symbolic routing.
|
||||
**Module:** `aipass.ai_mail`
|
||||
**Created:** 2025-11-08
|
||||
**Last Updated:** 2026-03-08
|
||||
|
||||
---
|
||||
|
||||
**Status:** Building. Core email workflow (send/inbox/reply/close) is functional. Dispatch system is working.
|
||||
|
||||
## Usage
|
||||
|
||||
### CLI (via drone)
|
||||
## Commands / Usage
|
||||
|
||||
```bash
|
||||
# Send a message
|
||||
drone @ai_mail send @flow "Bug Report" "Found an issue in plan closing"
|
||||
|
||||
# Check inbox
|
||||
drone @ai_mail inbox
|
||||
|
||||
# View a message (marks as opened)
|
||||
drone @ai_mail view <message_id>
|
||||
|
||||
# Reply (auto-closes original)
|
||||
drone @ai_mail reply <message_id> "Fixed in v2.1"
|
||||
|
||||
# Close without reply
|
||||
drone @ai_mail close <message_id>
|
||||
|
||||
# Send with dispatch flag (recipient auto-executes the task)
|
||||
drone @ai_mail send @flow "Task" "Details here" --dispatch
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
from aipass.ai_mail.apps.modules.email import handle_command
|
||||
|
||||
# Module interface — all commands go through handle_command
|
||||
handle_command(["send", "@flow", "Subject", "Message body"])
|
||||
handle_command(["inbox"])
|
||||
drone @ai_mail send @target "Subject" "Body" # Send inter-branch email
|
||||
drone @ai_mail send @target "Subject" "Body" --dispatch # Send task dispatch email
|
||||
drone @ai_mail dispatch wake @target # Wake a branch
|
||||
drone @ai_mail dispatch wake --fresh @target # Fresh wake (no context)
|
||||
drone @ai_mail inbox # Check inbox
|
||||
drone @ai_mail --help # Full help
|
||||
```
|
||||
|
||||
## Email Lifecycle
|
||||
@@ -78,8 +60,19 @@ ai_mail/
|
||||
│ └── users/ # Branch detection, config generation
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
## Integration Points
|
||||
|
||||
- `prax` — Logging
|
||||
- `cli` — Display formatting
|
||||
- `drone` — Command routing and `@branch` resolution
|
||||
### Depends On
|
||||
- `aipass.prax` — Logging via `system_logger`
|
||||
- `aipass.cli` — Console output and display formatting
|
||||
- `aipass.drone` — Command routing and `@branch` resolution
|
||||
- Python stdlib (`pathlib`, `json`, `argparse`, `importlib`)
|
||||
|
||||
### Provides To
|
||||
- All modules — inter-branch messaging (send/receive/reply/close)
|
||||
- Dispatch system — autonomous task execution via `--dispatch` flag
|
||||
- Branch contacts — address book for `@branch` routing
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2026-03-08*
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: ai_mail.py - AI_MAIL Branch Orchestrator
|
||||
# Date: 2025-11-08
|
||||
# =================== AIPass ====================
|
||||
# Name: ai_mail.py
|
||||
# Description: Entry point CLI for drone @ai_mail — inter-branch email system
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/orchestrator
|
||||
# Created: 2026-03-08
|
||||
# Modified: 2026-03-08
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -26,7 +25,7 @@ from typing import Dict, Any, Optional, List
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
|
||||
# Dashboard integration (optional, requires dev_central package)
|
||||
_update_section = None # type: ignore
|
||||
_UPDATE_SECTION = None # type: ignore
|
||||
|
||||
# AIPass infrastructure imports
|
||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: central_writer.py - AI_MAIL Central File Writer
|
||||
# Date: 2025-11-27
|
||||
# =================== AIPass ====================
|
||||
# Name: central_writer.py
|
||||
# Description: AI_MAIL Central File Writer
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-27): Initial implementation - central file writer
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler tier 3: pure functions, raises exceptions
|
||||
# - No CLI imports (Prax, Rich, etc.)
|
||||
# - Follows Seed handler patterns
|
||||
# Created: 2025-11-27
|
||||
# Modified: 2025-11-27
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,24 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: daemon.py - Dispatch Daemon Handler
|
||||
# Date: 2026-02-17
|
||||
# =================== AIPass ====================
|
||||
# Name: daemon.py
|
||||
# Description: Dispatch Daemon Handler
|
||||
# Version: 1.8.0
|
||||
# Category: ai_mail/handlers/dispatch
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.8.0 (2026-03-02): Wire spawn_agent through dispatch_monitor (bounce emails + guaranteed lock cleanup)
|
||||
# - v1.7.0 (2026-03-02): Redirect stderr to log file + reap zombie children (debug silent deaths)
|
||||
# - v1.6.0 (2026-03-01): Add AIPASS_SESSION_TYPE env var + session rename for /resume picker
|
||||
# - v1.5.0 (2026-02-22): Stale lock cleanup every cycle + orphaned opened email retry (>30 min)
|
||||
# - v1.4.0 (2026-02-21): Skip spawn if branch has active Claude session (prevent toe-stepping)
|
||||
# - v1.3.0 (2026-02-20): DPLAN-024 Phase 3 - strip heartbeat logic (migrated to assistant plugins)
|
||||
# - v1.2.0 (2026-02-20): DPLAN-024 - fix hardcoded paths, inline lock_utils
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-handler or module imports
|
||||
# - Pure business logic only
|
||||
# - Uses Prax system_logger (FPLAN-0382)
|
||||
# Created: 2026-02-17
|
||||
# Modified: 2026-02-17
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -238,7 +223,7 @@ def load_config() -> Dict[str, Any]:
|
||||
"kill_switch_path": str(_REPO_ROOT / ".aipass" / "autonomous_pause"),
|
||||
"poll_interval_seconds": 300,
|
||||
"max_depth": 3,
|
||||
"max_turns_per_wake": 50,
|
||||
"max_turns_per_wake": 100,
|
||||
"max_dispatches_per_branch_per_day": 10,
|
||||
"session_rotation_cycles": 12,
|
||||
"cold_start_prompt": "Hi. Check inbox, process new emails, update memories when done.",
|
||||
@@ -407,7 +392,7 @@ def spawn_agent(
|
||||
sender = message.get("from", "unknown")
|
||||
msg_id = message.get("id", "unknown")
|
||||
subject = message.get("subject", "")
|
||||
max_turns = config.get("max_turns_per_wake", 15)
|
||||
max_turns = config.get("max_turns_per_wake", 100)
|
||||
|
||||
lock_file_path = str(branch_path / ".ai_mail.local" / ".dispatch.lock")
|
||||
|
||||
@@ -480,11 +465,9 @@ def spawn_agent(
|
||||
notif_title = f"Daemon → {branch_email}"
|
||||
notif_body = f"Task from {sender}: \"{subject[:80]}\"" if subject else f"Dispatch from {sender}"
|
||||
try:
|
||||
subprocess.run(
|
||||
["notify-send", "-i", "dialog-information", notif_title, notif_body],
|
||||
capture_output=True, timeout=5
|
||||
)
|
||||
except (subprocess.SubprocessError, FileNotFoundError, OSError):
|
||||
from aipass.ai_mail.apps.handlers.notify import send_notification
|
||||
send_notification(notif_title, notif_body, source=branch_email.lstrip("@"))
|
||||
except Exception:
|
||||
logger.info(f"Desktop notification unavailable for {branch_email}")
|
||||
|
||||
logger.info(f"SPAWN {branch_email} PID={monitor_pid} (monitor) sender={sender} subject=\"{subject[:60]}\"")
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: dispatch_monitor.py - Agent Lifecycle Monitor
|
||||
# Date: 2026-03-02
|
||||
# =================== AIPass ====================
|
||||
# Name: dispatch_monitor.py
|
||||
# Description: Agent Lifecycle Monitor
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/dispatch
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2026-03-02): Initial — wraps claude spawn, handles cleanup + bounce on failure
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-handler or module imports
|
||||
# - Runs as a detached background process
|
||||
# - Uses subprocess to send bounce emails (avoids imports)
|
||||
# Created: 2026-03-02
|
||||
# Modified: 2026-03-02
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -123,6 +114,12 @@ def main():
|
||||
for key in list(spawn_env.keys()):
|
||||
if key.startswith("CLAUDE") or key == "AIPASS_BOT_ID":
|
||||
spawn_env.pop(key)
|
||||
# Strip caller identity vars to prevent dispatch context leakage.
|
||||
spawn_env.pop("AIPASS_CALLER_BRANCH", None)
|
||||
spawn_env.pop("AIPASS_CALLER_CWD", None)
|
||||
# Set CWD-independent branch identity so agent knows who it is
|
||||
# even after cd'ing away. Drone reads this as fallback for caller detection.
|
||||
spawn_env["AIPASS_BRANCH_NAME"] = branch_email.lstrip("@")
|
||||
|
||||
# Extract CWD from lock file path (branch_path/.ai_mail.local/.dispatch.lock)
|
||||
lock_path = Path(lock_file)
|
||||
@@ -132,10 +129,15 @@ def main():
|
||||
start_time = time.time()
|
||||
|
||||
# Run claude — BLOCKING. Monitor stays alive as long as agent is working.
|
||||
stdout_log = str(branch_path / ".ai_mail.local" / "agent_stdout.log")
|
||||
try:
|
||||
stdout_fh = open(stdout_log, 'w', encoding='utf-8')
|
||||
except OSError:
|
||||
stdout_fh = subprocess.DEVNULL
|
||||
try:
|
||||
result = subprocess.run(
|
||||
claude_cmd,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stdout=stdout_fh if isinstance(stdout_fh, int) else stdout_fh,
|
||||
stderr=stderr_fh,
|
||||
cwd=cwd,
|
||||
env=spawn_env,
|
||||
@@ -153,10 +155,29 @@ def main():
|
||||
|
||||
duration = int(time.time() - start_time)
|
||||
|
||||
# Close stdout log
|
||||
if not isinstance(stdout_fh, int):
|
||||
try:
|
||||
stdout_fh.close()
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# Check for max-turns hit (Claude exits 0 but output contains stop_reason)
|
||||
max_turns_hit = False
|
||||
try:
|
||||
with open(stdout_log, 'r', encoding='utf-8') as f:
|
||||
stdout_content = f.read()
|
||||
if '"stop_reason":"max_turns"' in stdout_content or '"stop_reason": "max_turns"' in stdout_content:
|
||||
max_turns_hit = True
|
||||
logger.warning("[monitor] %s HIT MAX TURNS after %ds — work may be incomplete", branch_email, duration)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# Log completion
|
||||
if not isinstance(stderr_fh, int):
|
||||
try:
|
||||
stderr_fh.write(f"\n--- Agent exited: code={exit_code}, duration={duration}s ---\n")
|
||||
suffix = " [MAX TURNS HIT]" if max_turns_hit else ""
|
||||
stderr_fh.write(f"\n--- Agent exited: code={exit_code}, duration={duration}s{suffix} ---\n")
|
||||
stderr_fh.flush()
|
||||
stderr_fh.close()
|
||||
except OSError:
|
||||
@@ -188,17 +209,21 @@ def main():
|
||||
except OSError:
|
||||
logger.info("[monitor] Failed to clean up lock file %s", lock_file)
|
||||
|
||||
# Desktop notification on completion
|
||||
# Log completion to Prax
|
||||
status = "completed" if exit_code == 0 else f"FAILED (code {exit_code})"
|
||||
if max_turns_hit:
|
||||
status = f"MAX TURNS HIT ({duration}s)"
|
||||
logger.info("[monitor] %s agent %s — %ds", branch_email, status, duration)
|
||||
|
||||
# Desktop notification on completion
|
||||
try:
|
||||
subprocess.run(
|
||||
["notify-send", "-i",
|
||||
"dialog-information" if exit_code == 0 else "dialog-warning",
|
||||
f"Agent {branch_email} {status}",
|
||||
f"Duration: {duration}s"],
|
||||
capture_output=True, timeout=5
|
||||
from aipass.ai_mail.apps.handlers.notify import send_notification
|
||||
icon = "dialog-information" if exit_code == 0 else "dialog-warning"
|
||||
send_notification(
|
||||
f"Agent {branch_email} {status}", f"Duration: {duration}s",
|
||||
source=branch_email.lstrip("@"), icon=icon
|
||||
)
|
||||
except (subprocess.SubprocessError, FileNotFoundError, OSError):
|
||||
except Exception:
|
||||
logger.info("[monitor] Desktop notification unavailable")
|
||||
|
||||
sys.exit(0 if exit_code == 0 else 1)
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: pending_work.py - Pending Work Handler
|
||||
# Date: 2026-02-17
|
||||
# =================== AIPass ====================
|
||||
# Name: pending_work.py
|
||||
# Description: Pending Work Handler
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/dispatch
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2026-02-17): Initial version - per-branch pending work tracking
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-domain imports
|
||||
# - Pure business logic only
|
||||
# Created: 2026-02-17
|
||||
# Modified: 2026-02-17
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: status.py - Dispatch Status Handler
|
||||
# Date: 2026-02-02
|
||||
# =================== AIPass ====================
|
||||
# Name: status.py
|
||||
# Description: Dispatch Status Handler
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/dispatch
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2026-02-02): Initial version - dispatch log operations
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-domain imports
|
||||
# - Pure business logic only
|
||||
# Created: 2026-02-02
|
||||
# Modified: 2026-02-02
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,23 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: wake.py - Manual Branch Wake Handler
|
||||
# Date: 2026-03-02
|
||||
# =================== AIPass ====================
|
||||
# Name: wake.py
|
||||
# Description: Manual Branch Wake Handler
|
||||
# Version: 2.0.0
|
||||
# Category: ai_mail/handlers/dispatch
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v2.0.0 (2026-03-02): Step-by-step status, dispatch_monitor wrapper,
|
||||
# zombie cleanup, return-to-sender bounce, liveness check
|
||||
# - v1.7.0 (2026-03-02): Redirect stderr to log file instead of DEVNULL (debug silent deaths)
|
||||
# - v1.6.0 (2026-03-02): Manual wake returns success on active lock (agent will process inbox)
|
||||
# - v1.5.0 (2026-03-02): Manual wake bypasses autonomous_pause; add auto param for daemon use
|
||||
# - v1.4.0 (2026-03-01): Add AIPASS_SESSION_TYPE env var + session rename for /resume picker
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-handler or module imports
|
||||
# - Pure business logic only
|
||||
# - Uses Prax system_logger (FPLAN-0382)
|
||||
# Created: 2026-03-02
|
||||
# Modified: 2026-03-02
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -178,7 +164,7 @@ def _acquire_lock(branch_path: Path, pid: int) -> Tuple[bool, str]:
|
||||
|
||||
def _load_config() -> dict:
|
||||
"""Load safety config for max_turns."""
|
||||
defaults = {"max_turns_per_wake": 50}
|
||||
defaults = {"max_turns_per_wake": 100}
|
||||
config = _read_json(CONFIG_FILE)
|
||||
if config is None:
|
||||
return defaults
|
||||
@@ -381,7 +367,7 @@ def wake_branch(branch_email: str, custom_message: Optional[str] = None,
|
||||
|
||||
# Step 6: Build spawn command
|
||||
config = _load_config()
|
||||
max_turns = config.get("max_turns_per_wake", 50)
|
||||
max_turns = config.get("max_turns_per_wake", 100)
|
||||
|
||||
lock_file_path = str(branch_path / ".ai_mail.local" / ".dispatch.lock")
|
||||
if custom_message:
|
||||
@@ -473,11 +459,9 @@ def wake_branch(branch_email: str, custom_message: Optional[str] = None,
|
||||
# Desktop notification
|
||||
notif_body = custom_message[:80] if custom_message else "Manual wake: check inbox"
|
||||
try:
|
||||
subprocess.run(
|
||||
["notify-send", "-i", "dialog-information", f"Wake → {email}", notif_body],
|
||||
capture_output=True, timeout=5
|
||||
)
|
||||
except (subprocess.SubprocessError, FileNotFoundError, OSError):
|
||||
from aipass.ai_mail.apps.handlers.notify import send_notification
|
||||
send_notification(f"Wake → {email}", notif_body, source=email.lstrip("@"))
|
||||
except Exception:
|
||||
logger.info("[wake] Desktop notification unavailable")
|
||||
|
||||
return status, True
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: close_ops.py
|
||||
# Description: Email Close Operations Handler
|
||||
# Version: 1.0.0
|
||||
# Created: 2026-03-08
|
||||
# Modified: 2026-03-08
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
Email Close Operations Handler
|
||||
|
||||
Handles batch close operations and post-close cleanup.
|
||||
Independent handler - no module or display dependencies.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple, Callable, Optional
|
||||
|
||||
from aipass.prax import logger
|
||||
|
||||
|
||||
def batch_close(
|
||||
branch_path: Path,
|
||||
message_ids: List[str],
|
||||
mark_closed_fn: Callable,
|
||||
) -> Tuple[List[Tuple[str, bool, str]], int, int]:
|
||||
"""
|
||||
Close multiple emails by ID.
|
||||
|
||||
Args:
|
||||
branch_path: Path to branch directory
|
||||
message_ids: List of message IDs to close
|
||||
mark_closed_fn: Callable(branch_path, msg_id, skip_post_ops=bool) -> (bool, str)
|
||||
|
||||
Returns:
|
||||
Tuple of (results_list, closed_count, failed_count)
|
||||
results_list contains (message_id, success, message) tuples
|
||||
"""
|
||||
batch_mode = len(message_ids) > 1
|
||||
results = []
|
||||
closed_count = 0
|
||||
failed_count = 0
|
||||
|
||||
for message_id in message_ids:
|
||||
success, message = mark_closed_fn(branch_path, message_id, skip_post_ops=batch_mode)
|
||||
results.append((message_id, success, message))
|
||||
if success:
|
||||
closed_count += 1
|
||||
else:
|
||||
failed_count += 1
|
||||
|
||||
return results, closed_count, failed_count
|
||||
|
||||
|
||||
def batch_close_post_ops(
|
||||
branch_path: Path,
|
||||
push_dashboard_fn: Optional[Callable] = None,
|
||||
update_central_fn: Optional[Callable] = None,
|
||||
purge_deleted_fn: Optional[Callable] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Run post-operations after a batch close (dashboard update + purge).
|
||||
|
||||
Args:
|
||||
branch_path: Path to branch directory
|
||||
push_dashboard_fn: Optional push_dashboard_update callable
|
||||
update_central_fn: Optional update_central callable
|
||||
purge_deleted_fn: Optional purge_deleted_folder callable
|
||||
"""
|
||||
if push_dashboard_fn:
|
||||
try:
|
||||
push_dashboard_fn(branch_path)
|
||||
except Exception:
|
||||
pass
|
||||
if update_central_fn:
|
||||
try:
|
||||
update_central_fn()
|
||||
except Exception:
|
||||
pass
|
||||
if purge_deleted_fn:
|
||||
try:
|
||||
mailbox_path = branch_path / ".ai_mail.local"
|
||||
purge_deleted_fn(mailbox_path)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -1,21 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: create.py - Email File Creation Handler
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: create.py
|
||||
# Description: Email File Creation Handler
|
||||
# Version: 1.2.0
|
||||
# Category: ai_mail/handlers/email
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.4.0 (2026-02-04): Add auto-purge trigger after email creation
|
||||
# - v1.3.0 (2026-01-31): Add dispatched_to field for reply chain validation
|
||||
# - v1.2.0 (2026-01-29): Add auto-footer to all outgoing emails
|
||||
# - v1.1.0 (2026-01-29): Add reply_to parameter for redirecting replies
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-domain imports
|
||||
# - Uses Prax system_logger (FPLAN-0382)
|
||||
# - Pure business logic only
|
||||
# Created: 2025-11-15
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -33,19 +21,9 @@ from typing import Dict
|
||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||
|
||||
# Lazy imports
|
||||
_console = None
|
||||
_append_footer = None
|
||||
|
||||
|
||||
def _get_console():
|
||||
"""Lazy import console."""
|
||||
global _console
|
||||
if _console is None:
|
||||
from aipass.cli.apps.modules import console
|
||||
_console = console
|
||||
return _console
|
||||
|
||||
|
||||
def _get_append_footer():
|
||||
"""Lazy import append_footer."""
|
||||
global _append_footer
|
||||
@@ -169,7 +147,8 @@ def sanitize_subject(subject: str, max_length: int = 50) -> str:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
c = _get_console()
|
||||
from rich.console import Console
|
||||
c = Console()
|
||||
c.print("\n" + "="*70)
|
||||
c.print("EMAIL FILE CREATION HANDLER")
|
||||
c.print("="*70)
|
||||
|
||||
@@ -1,22 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: dashboard_sync.py - Dashboard Write-Through Helper
|
||||
# Date: 2026-02-25
|
||||
# =================== AIPass ====================
|
||||
# Name: dashboard_sync.py
|
||||
# Description: Dashboard Write-Through Helper
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/email
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2026-02-25): FPLAN-0373 Phase 2 - dashboard write-through for ai_mail
|
||||
# Reads inbox.json, calculates section data, calls write_section()
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-handler or module imports
|
||||
# - Can import AIPASS central services (devpulse write_section)
|
||||
# - No logger calls (module logs for handler)
|
||||
# - Pure business logic only
|
||||
# - BYPASS: Direct json.load required for reading inbox.json data files
|
||||
# - Dashboard failures never raised to caller
|
||||
# Created: 2026-02-25
|
||||
# Modified: 2026-02-25
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,23 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: delivery.py - Email Delivery Handler
|
||||
# Date: 2025-12-02
|
||||
# =================== AIPass ====================
|
||||
# Name: delivery.py
|
||||
# Description: Email Delivery Handler
|
||||
# Version: 3.0.0
|
||||
# Category: ai_mail/handlers/email
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v3.1.0 (2026-02-25): Private branch inbound email blocking (DPLAN-035 email isolation)
|
||||
# - v3.0.0 (2026-02-17): Remove spawn logic — delivery is write-only, daemon handles all spawning
|
||||
# - v2.4.0 (2026-02-10): Seed compliance - remove logger calls, cross-handler imports, fix naming, add json_handler
|
||||
# - v2.3.0 (2026-02-10): Phase 3 polish - concise bounce messages, dispatch chain logging, hardened loop detection
|
||||
# - v2.2.0 (2026-02-10): DEV_CENTRAL dispatch protection, notification throttling, self-reply loop detection
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-handler or module imports
|
||||
# - No logger calls (module logs for handler)
|
||||
# - Pure business logic only
|
||||
# - Uses json_handler for JSON operations
|
||||
# Created: 2025-12-02
|
||||
# Modified: 2025-12-02
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -62,11 +48,11 @@ def _get_inbox_lock():
|
||||
|
||||
|
||||
def _get_console():
|
||||
"""Lazy import console."""
|
||||
"""Lazy import console - only for __main__ block."""
|
||||
global _CONSOLE
|
||||
if _CONSOLE is None:
|
||||
from aipass.cli.apps.modules import console
|
||||
_CONSOLE = console
|
||||
from rich.console import Console
|
||||
_CONSOLE = Console()
|
||||
return _CONSOLE
|
||||
|
||||
|
||||
@@ -495,13 +481,10 @@ def _send_desktop_notification(sender: str, recipient: str, subject: str, messag
|
||||
body = f"{subject}\n{preview}"
|
||||
|
||||
try:
|
||||
subprocess.run(
|
||||
['notify-send', title, body],
|
||||
capture_output=True,
|
||||
timeout=5
|
||||
)
|
||||
from aipass.ai_mail.apps.handlers.notify import send_notification
|
||||
send_notification(title, body, source=sender_name)
|
||||
_NOTIFICATION_TIMESTAMPS[recipient].append(now)
|
||||
except (subprocess.SubprocessError, FileNotFoundError, OSError):
|
||||
except Exception:
|
||||
return
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: error_dispatch.py
|
||||
# Description: Email Error Dispatch Handler
|
||||
# Version: 1.0.0
|
||||
# Created: 2026-03-08
|
||||
# Modified: 2026-03-08
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
Email Error Dispatch Handler
|
||||
|
||||
Handles auto-dispatch of error reports when email delivery fails.
|
||||
Independent handler - no module or display dependencies.
|
||||
"""
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Callable, Optional
|
||||
|
||||
from aipass.prax import logger
|
||||
|
||||
|
||||
def build_error_report(to_branch: str, subject: str, error_msg: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Build an error report email data dict for dispatch to @drone.
|
||||
|
||||
Args:
|
||||
to_branch: Intended recipient that failed
|
||||
subject: Original email subject
|
||||
error_msg: Error message from delivery failure
|
||||
|
||||
Returns:
|
||||
Email data dict ready for delivery, or empty dict on failure.
|
||||
"""
|
||||
sender = os.environ.get("AIPASS_CALLER_BRANCH", "ai_mail")
|
||||
sender = f"@{sender.lstrip('@')}"
|
||||
|
||||
error_body = (
|
||||
f"Email delivery failed.\n\n"
|
||||
f"From: {sender}\n"
|
||||
f"To: {to_branch}\n"
|
||||
f"Subject: {subject}\n"
|
||||
f"Error: {error_msg}\n\n"
|
||||
f"This error was auto-dispatched for investigation."
|
||||
)
|
||||
|
||||
return {
|
||||
"from": "@ai_mail",
|
||||
"from_name": "AI_MAIL",
|
||||
"to": "@drone",
|
||||
"subject": f"[ERROR] Send failed to {to_branch}: {error_msg[:50]}",
|
||||
"message": error_body,
|
||||
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"auto_execute": False,
|
||||
"priority": "normal",
|
||||
"reply_to": "@dev_central",
|
||||
}
|
||||
|
||||
|
||||
def dispatch_send_error(
|
||||
to_branch: str,
|
||||
subject: str,
|
||||
error_msg: str,
|
||||
deliver_fn: Callable,
|
||||
) -> bool:
|
||||
"""
|
||||
Auto-dispatch error report to @drone when email delivery fails.
|
||||
|
||||
Args:
|
||||
to_branch: Intended recipient that failed
|
||||
subject: Original email subject
|
||||
error_msg: Error message from delivery failure
|
||||
deliver_fn: Callable to deliver email (deliver_email_to_branch)
|
||||
|
||||
Returns:
|
||||
True if error dispatched successfully, False otherwise.
|
||||
"""
|
||||
try:
|
||||
email_data = build_error_report(to_branch, subject, error_msg)
|
||||
deliver_fn("@drone", email_data)
|
||||
logger.info(f"[email] Error auto-dispatched to @drone for failed send to {to_branch}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"[email] Failed to dispatch send error to @drone: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def on_email_delivered(
|
||||
branch_path,
|
||||
new_count: int,
|
||||
opened_count: int,
|
||||
total: int,
|
||||
push_dashboard_fn: Optional[Callable] = None,
|
||||
update_central_fn: Optional[Callable] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Post-delivery callback: update dashboard and central.
|
||||
|
||||
Args:
|
||||
branch_path: Path to the branch that received email
|
||||
new_count: Number of new (unread) messages
|
||||
opened_count: Number of opened messages
|
||||
total: Total message count
|
||||
push_dashboard_fn: Callable for push_dashboard_update
|
||||
update_central_fn: Callable for update_central
|
||||
"""
|
||||
if push_dashboard_fn:
|
||||
try:
|
||||
push_dashboard_fn(branch_path)
|
||||
except Exception:
|
||||
pass # Dashboard update is best-effort
|
||||
if update_central_fn:
|
||||
try:
|
||||
update_central_fn()
|
||||
except Exception:
|
||||
pass # Central update is best-effort
|
||||
@@ -1,18 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: footer.py - Email Footer Handler
|
||||
# Date: 2026-01-29
|
||||
# =================== AIPass ====================
|
||||
# Name: footer.py
|
||||
# Description: Email Footer Handler
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/email
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2026-01-29): Created - auto-footer for all outgoing emails
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-domain imports
|
||||
# - Can import Prax modules (service providers)
|
||||
# - Pure business logic only
|
||||
# Created: 2026-01-29
|
||||
# Modified: 2026-01-29
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -27,10 +18,10 @@ Independent handler - no module dependencies.
|
||||
STANDARD_FOOTER = """
|
||||
---
|
||||
⚠️ TASK CHECKLIST (before marking complete):
|
||||
□ SEED CHECK → drone @seed audit @branch (80%+)
|
||||
□ UPDATE MEMORIES → Your .local.json records this work
|
||||
□ SEEDGO CHECK → drone @seedgo audit @branch (80%+)
|
||||
□ UPDATE MEMORIES → Your .trinity/local.json records this work
|
||||
□ CLOSE FPLAN → drone @flow close <plan_id>
|
||||
□ CONFIRM → Reply with completion summary
|
||||
□ EMAIL SENDER → drone @ai_mail send @<sender> "Subject" "Summary"
|
||||
|
||||
Memories = Presence. No update = No learning.
|
||||
---"""
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: format.py - Email Formatting Handler
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: format.py
|
||||
# Description: Email Formatting Handler
|
||||
# Version: 1.1.0
|
||||
# Category: ai_mail/handlers/email
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.1.0 (2026-02-21): DPLAN-027 - Show branch alias in sender display
|
||||
# - v1.0.0 (2025-11-15): Created - email formatting and display utilities
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-domain imports
|
||||
# - Uses Prax system_logger (FPLAN-0382)
|
||||
# - Pure business logic only
|
||||
# Created: 2025-11-15
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -28,7 +18,6 @@ from pathlib import Path
|
||||
from typing import Dict, Optional
|
||||
|
||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||
from aipass.cli.apps.modules import console
|
||||
|
||||
def _find_repo_root() -> Path:
|
||||
"""Walk up from this file to find AIPASS_REGISTRY.json (repo root)."""
|
||||
@@ -226,6 +215,7 @@ def truncate_text(text: str, max_length: int, suffix: str = "...") -> str:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from aipass.cli.apps.modules import console
|
||||
console.print("\n" + "="*70)
|
||||
console.print("EMAIL FORMATTING HANDLER")
|
||||
console.print("="*70)
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: header.py
|
||||
# Description: Email Header Handler
|
||||
# Version: 1.0.0
|
||||
# Created: 2026-02-04
|
||||
# Modified: 2026-02-04
|
||||
# =============================================
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: header.py - Email Header Handler
|
||||
# Date: 2026-02-04
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/email
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.1.0 (2026-02-25): Add --no-memory-save conditional header for private branch dispatch
|
||||
# - v1.0.0 (2026-02-04): Created - dispatch header for critical reminders
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-domain imports
|
||||
# - Can import Prax modules (service providers)
|
||||
# - Pure business logic only
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
Email Header Handler
|
||||
|
||||
|
||||
@@ -1,23 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: inbox_cleanup.py - Inbox Cleanup Handler
|
||||
# Date: 2025-11-27
|
||||
# =================== AIPass ====================
|
||||
# Name: inbox_cleanup.py
|
||||
# Description: Inbox Cleanup Handler
|
||||
# Version: 3.3.0
|
||||
# Category: ai_mail/handlers/email
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v3.3.0 (2026-02-25): FPLAN-0373 Phase 2 - use enriched push_dashboard_update from dashboard_sync
|
||||
# - v3.2.0 (2026-02-14): Add skip_post_ops param to mark_as_closed_and_archive for batch close perf
|
||||
# - v3.1.0 (2026-02-09): Add fcntl.flock inbox.json locking to prevent concurrent write corruption
|
||||
# - v3.0.0 (2026-02-04): Migrate to deleted/ directory (individual files like sent/)
|
||||
# - v2.1.0 (2026-02-04): Add auto-purge trigger after archiving to deleted
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-domain imports
|
||||
# - Can import Prax modules (service providers)
|
||||
# - Can import AIPASS central services (dashboard)
|
||||
# - Pure business logic only
|
||||
# Created: 2025-11-27
|
||||
# Modified: 2025-11-27
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -49,9 +35,9 @@ def _get_inbox_lock():
|
||||
|
||||
|
||||
def _get_console() -> Any:
|
||||
"""Lazy import console."""
|
||||
from aipass.cli.apps.modules import console
|
||||
return console
|
||||
"""Lazy import console - only for __main__ block."""
|
||||
from rich.console import Console
|
||||
return Console()
|
||||
|
||||
|
||||
def _get_update_section() -> Any:
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: inbox_lock.py - Inbox File Lock Handler
|
||||
# Date: 2026-02-09
|
||||
# =================== AIPass ====================
|
||||
# Name: inbox_lock.py
|
||||
# Description: Inbox File Lock Handler
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/email
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2026-02-09): Initial implementation - fcntl.flock based inbox.json locking
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-domain imports
|
||||
# - Uses logging module for diagnostics
|
||||
# - Pure business logic only
|
||||
# Created: 2026-02-09
|
||||
# Modified: 2026-02-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: inbox_ops.py - Inbox Operations Handler
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: inbox_ops.py
|
||||
# Description: Inbox Operations Handler
|
||||
# Version: 1.1.0
|
||||
# Category: ai_mail/handlers/email
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.1.0 (2026-02-08): Add auto-migration for old inbox format {"inbox": []} → v2 schema
|
||||
# - v1.0.0 (2025-11-15): Extracted from email.py - inbox file I/O operations
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-domain imports
|
||||
# - Can import Prax modules (service providers)
|
||||
# - Pure business logic only
|
||||
# Created: 2025-11-15
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -27,8 +17,6 @@ import json
|
||||
from pathlib import Path
|
||||
from typing import Dict
|
||||
|
||||
from aipass.cli.apps.modules import console
|
||||
|
||||
|
||||
|
||||
def load_inbox(inbox_file: Path) -> Dict:
|
||||
@@ -98,6 +86,7 @@ def load_inbox(inbox_file: Path) -> Dict:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from aipass.cli.apps.modules import console
|
||||
console.print("\n" + "="*70)
|
||||
console.print("INBOX OPERATIONS HANDLER")
|
||||
console.print("="*70)
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: inbox_resolve.py
|
||||
# Description: Inbox Resolution Handler
|
||||
# Version: 1.0.0
|
||||
# Created: 2026-03-08
|
||||
# Modified: 2026-03-08
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
Inbox Resolution Handler
|
||||
|
||||
Resolves inbox paths and branch info for inbox viewing.
|
||||
Independent handler - no module or display dependencies.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional, Any, Callable, Tuple
|
||||
|
||||
|
||||
def resolve_inbox_target(
|
||||
args_first: Optional[str],
|
||||
repo_root: Path,
|
||||
get_branch_by_email_fn: Callable,
|
||||
get_current_user_fn: Callable,
|
||||
) -> Tuple[bool, Dict[str, Any]]:
|
||||
"""
|
||||
Resolve which inbox to display based on args.
|
||||
|
||||
Args:
|
||||
args_first: First argument (e.g., '@branch') or None
|
||||
repo_root: Repository root path
|
||||
get_branch_by_email_fn: Callable to look up branch by email
|
||||
get_current_user_fn: Callable to detect current user
|
||||
|
||||
Returns:
|
||||
Tuple of (success, result_dict).
|
||||
result_dict contains:
|
||||
inbox_file: Path to inbox.json
|
||||
display_name: str for display
|
||||
target_branch: str | None (the explicit target, or None for current)
|
||||
error: str | None (set when success is False)
|
||||
"""
|
||||
target_branch = None
|
||||
if args_first and args_first.startswith("@"):
|
||||
target_branch = args_first
|
||||
|
||||
if target_branch:
|
||||
branch_info = get_branch_by_email_fn(target_branch)
|
||||
if not branch_info:
|
||||
return False, {"error": f"Unknown branch: {target_branch}"}
|
||||
|
||||
branch_path = Path(branch_info["path"])
|
||||
if not branch_path.is_absolute():
|
||||
branch_path = (repo_root / branch_path).resolve()
|
||||
mailbox_path = branch_path / ".ai_mail.local"
|
||||
display_name = branch_info.get("name", target_branch)
|
||||
else:
|
||||
user_info = get_current_user_fn()
|
||||
mailbox_path = Path(user_info["mailbox_path"])
|
||||
display_name = user_info.get("display_name", "")
|
||||
|
||||
inbox_file = mailbox_path / "inbox.json"
|
||||
|
||||
return True, {
|
||||
"inbox_file": inbox_file,
|
||||
"display_name": display_name,
|
||||
"target_branch": target_branch,
|
||||
"error": None,
|
||||
}
|
||||
@@ -1,19 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: lock_utils.py - Dispatch Lock Handler
|
||||
# Date: 2026-02-09
|
||||
# =================== AIPass ====================
|
||||
# Name: lock_utils.py
|
||||
# Description: Dispatch Lock Handler
|
||||
# Version: 1.1.0
|
||||
# Category: ai_mail/handlers/email
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.1.0 (2026-02-10): Reduce stale lock timeout from 1800s to 600s (10 min)
|
||||
# - v1.0.0 (2026-02-09): Initial implementation - PID-based single instance lock per branch
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-domain imports
|
||||
# - Uses logging module for diagnostics
|
||||
# - Pure business logic only
|
||||
# Created: 2026-02-09
|
||||
# Modified: 2026-02-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: purge.py - Sent/Deleted Auto-Purge Handler
|
||||
# Date: 2026-02-04
|
||||
# =================== AIPass ====================
|
||||
# Name: purge.py
|
||||
# Description: Sent/Deleted Auto-Purge Handler
|
||||
# Version: 2.0.0
|
||||
# Category: ai_mail/handlers/email
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v2.0.0 (2026-02-04): Update deleted purge for deleted/ directory structure
|
||||
# - v1.0.0 (2026-02-04): Initial version - auto-purge sent/deleted folders
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-domain imports
|
||||
# - Vectorization via subprocess (no direct Memory Bank imports)
|
||||
# - Uses Prax system_logger (FPLAN-0382)
|
||||
# Created: 2026-02-04
|
||||
# Modified: 2026-02-04
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: reply.py - Email Reply Handler
|
||||
# Date: 2025-11-30
|
||||
# =================== AIPass ====================
|
||||
# Name: reply.py
|
||||
# Description: Email Reply Handler
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/email
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.2.0 (2026-01-31): Add reply chain validation - fail loud on identity mismatch
|
||||
# - v1.1.0 (2026-01-29): Add reply_to field support - replies go to reply_to address if set
|
||||
# - v1.0.0 (2025-11-30): Initial creation - reply to email + auto-close
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-domain imports
|
||||
# - Can import Prax modules (service providers)
|
||||
# - Can import AIPASS central services (dashboard)
|
||||
# - Pure business logic only
|
||||
# Created: 2025-11-30
|
||||
# Modified: 2025-11-30
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: send.py
|
||||
# Description: Email Send Handler
|
||||
# Version: 1.0.0
|
||||
# Created: 2026-03-08
|
||||
# Modified: 2026-03-08
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
Email Send Handler
|
||||
|
||||
Core send logic for email delivery workflows.
|
||||
Handles sender resolution, email creation, and delivery orchestration.
|
||||
Independent handler - no module or display dependencies.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Optional, Tuple, List, Dict, Any
|
||||
|
||||
from aipass.prax import logger
|
||||
# logger imported from aipass.prax
|
||||
|
||||
|
||||
def resolve_sender_info(
|
||||
from_branch: Optional[str],
|
||||
repo_root: Path,
|
||||
ai_mail_dir: Path,
|
||||
get_branch_by_email_fn,
|
||||
get_current_user_fn,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Resolve sender user_info from explicit branch or PWD detection.
|
||||
|
||||
Args:
|
||||
from_branch: Optional explicit sender branch (e.g., '@trigger').
|
||||
repo_root: Repository root path.
|
||||
ai_mail_dir: AI_Mail module directory.
|
||||
get_branch_by_email_fn: Callable to look up branch by email.
|
||||
get_current_user_fn: Callable to detect current user from PWD.
|
||||
|
||||
Returns:
|
||||
Dict with email_address, display_name, mailbox_path, timestamp_format.
|
||||
"""
|
||||
if from_branch:
|
||||
email_addr = f"@{from_branch.lstrip('@').lower()}"
|
||||
branch_info = get_branch_by_email_fn(email_addr)
|
||||
if branch_info:
|
||||
branch_path = Path(branch_info["path"])
|
||||
if not branch_path.is_absolute():
|
||||
branch_path = (repo_root / branch_path).resolve()
|
||||
return {
|
||||
"email_address": email_addr,
|
||||
"display_name": branch_info["name"],
|
||||
"mailbox_path": str(branch_path / ".ai_mail.local"),
|
||||
"timestamp_format": "%Y-%m-%d %H:%M:%S"
|
||||
}
|
||||
else:
|
||||
branch_name = from_branch.lstrip('@').upper()
|
||||
return {
|
||||
"email_address": email_addr,
|
||||
"display_name": branch_name,
|
||||
"mailbox_path": str(ai_mail_dir.parent / from_branch.lstrip('@').lower() / ".ai_mail.local"),
|
||||
"timestamp_format": "%Y-%m-%d %H:%M:%S"
|
||||
}
|
||||
else:
|
||||
return get_current_user_fn()
|
||||
|
||||
|
||||
def send_to_broadcast(
|
||||
subject: str,
|
||||
message: str,
|
||||
user_info: Dict[str, Any],
|
||||
auto_execute: bool,
|
||||
no_memory_save: bool,
|
||||
reply_to: Optional[str],
|
||||
dispatched_to: Optional[str],
|
||||
branches: List[Dict[str, Any]],
|
||||
create_email_file_fn,
|
||||
load_email_file_fn,
|
||||
deliver_email_to_branch_fn,
|
||||
on_delivered_callback,
|
||||
log_operation_fn,
|
||||
update_central_fn,
|
||||
) -> Tuple[bool, int, int, Optional[str]]:
|
||||
"""
|
||||
Execute broadcast send to all branches.
|
||||
|
||||
Returns:
|
||||
Tuple of (success, success_count, total_count, error_msg).
|
||||
error_msg is set if the email file could not be loaded.
|
||||
"""
|
||||
email_file = create_email_file_fn("all", subject, message, user_info, reply_to=reply_to, dispatched_to=dispatched_to)
|
||||
email_data = load_email_file_fn(email_file)
|
||||
|
||||
if email_data is None:
|
||||
log_operation_fn("broadcast_failed", {"error": "Email file could not be loaded"})
|
||||
return False, 0, len(branches), "Email file could not be loaded"
|
||||
|
||||
results = [] # List of (branch_name, success, error_msg)
|
||||
for branch in branches:
|
||||
delivery_data = email_data.copy()
|
||||
delivery_data['to'] = branch['email']
|
||||
delivery_data['auto_execute'] = auto_execute
|
||||
if no_memory_save:
|
||||
delivery_data['no_memory_save'] = True
|
||||
|
||||
success, error_msg = deliver_email_to_branch_fn(branch['email'], delivery_data, on_delivered=on_delivered_callback)
|
||||
results.append((branch.get('name', branch['email']), success, error_msg))
|
||||
|
||||
success_count = sum(1 for _, s, _ in results if s)
|
||||
log_operation_fn("broadcast_sent", {"recipients": len(branches), "successful": success_count})
|
||||
|
||||
# Fire trigger event (best-effort)
|
||||
try:
|
||||
from aipass.trigger.apps.modules.core import trigger
|
||||
trigger.fire('email_broadcast_sent', recipients=len(branches), successful=success_count, subject=subject)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# Update central (best-effort)
|
||||
try:
|
||||
if update_central_fn:
|
||||
update_central_fn()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return success_count > 0, success_count, len(branches), results
|
||||
|
||||
|
||||
def send_to_single(
|
||||
to_branch: str,
|
||||
subject: str,
|
||||
message: str,
|
||||
user_info: Dict[str, Any],
|
||||
auto_execute: bool,
|
||||
no_memory_save: bool,
|
||||
reply_to: Optional[str],
|
||||
dispatched_to: Optional[str],
|
||||
create_email_file_fn,
|
||||
load_email_file_fn,
|
||||
deliver_email_to_branch_fn,
|
||||
on_delivered_callback,
|
||||
log_operation_fn,
|
||||
update_central_fn,
|
||||
) -> Tuple[bool, Optional[str]]:
|
||||
"""
|
||||
Execute single-recipient email send.
|
||||
|
||||
Returns:
|
||||
Tuple of (success, error_msg). error_msg is None on success.
|
||||
"""
|
||||
email_file = create_email_file_fn(to_branch, subject, message, user_info, reply_to=reply_to, dispatched_to=dispatched_to)
|
||||
email_data = load_email_file_fn(email_file)
|
||||
|
||||
if email_data is None:
|
||||
log_operation_fn("email_failed", {"to": to_branch, "error": "Email file could not be loaded"})
|
||||
return False, "Email file could not be loaded"
|
||||
|
||||
email_data['auto_execute'] = auto_execute
|
||||
if dispatched_to:
|
||||
email_data['dispatched_to'] = dispatched_to
|
||||
if no_memory_save:
|
||||
email_data['no_memory_save'] = True
|
||||
|
||||
success, error_msg = deliver_email_to_branch_fn(to_branch, email_data, on_delivered=on_delivered_callback)
|
||||
|
||||
if success:
|
||||
log_operation_fn("email_sent", {"to": to_branch, "subject": subject, "auto_execute": auto_execute})
|
||||
|
||||
# Fire trigger event (best-effort)
|
||||
try:
|
||||
from aipass.trigger.apps.modules.core import trigger
|
||||
trigger.fire('email_sent', to=to_branch, subject=subject, auto_execute=auto_execute)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# Update central (best-effort)
|
||||
try:
|
||||
if update_central_fn:
|
||||
update_central_fn()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return True, None
|
||||
else:
|
||||
log_operation_fn("email_failed", {"to": to_branch, "error": error_msg})
|
||||
return False, error_msg
|
||||
|
||||
|
||||
def collect_interactive_input(branches: List[Dict[str, Any]]) -> Optional[Dict[str, str]]:
|
||||
"""
|
||||
Collect send parameters from interactive user input.
|
||||
|
||||
Args:
|
||||
branches: List of available branch dicts with 'name' and 'email' keys.
|
||||
|
||||
Returns:
|
||||
Dict with 'to', 'subject', 'message' keys, or None if cancelled.
|
||||
"""
|
||||
try:
|
||||
selection = input(f"\nPick (1-{len(branches) + 1}): ").strip()
|
||||
idx = int(selection) - 1
|
||||
|
||||
if idx == len(branches):
|
||||
selected_email = "all"
|
||||
elif idx < 0 or idx >= len(branches):
|
||||
return None
|
||||
else:
|
||||
selected_email = branches[idx]["email"]
|
||||
except (ValueError, KeyboardInterrupt, EOFError):
|
||||
return None
|
||||
|
||||
try:
|
||||
subject = input("Subject: ").strip()
|
||||
if not subject:
|
||||
return None
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
return None
|
||||
|
||||
try:
|
||||
message_lines = []
|
||||
while True:
|
||||
try:
|
||||
line = input()
|
||||
message_lines.append(line)
|
||||
except EOFError:
|
||||
break
|
||||
message = "\n".join(message_lines).strip()
|
||||
if not message:
|
||||
return None
|
||||
except KeyboardInterrupt:
|
||||
return None
|
||||
|
||||
try:
|
||||
confirm = input("\nSend? (y/n): ").strip().lower()
|
||||
if confirm != 'y':
|
||||
return None
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
return None
|
||||
|
||||
return {
|
||||
"to": selected_email,
|
||||
"subject": subject,
|
||||
"message": message,
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: send_args.py
|
||||
# Description: Email Send Argument Parsing Handler
|
||||
# Version: 1.0.0
|
||||
# Created: 2026-03-08
|
||||
# Modified: 2026-03-08
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
Email Send Argument Parsing Handler
|
||||
|
||||
Parses send command arguments into structured data.
|
||||
Independent handler - no module or display dependencies.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Optional, Any
|
||||
|
||||
|
||||
def parse_send_args(args: List[str]) -> Dict[str, Any]:
|
||||
"""
|
||||
Parse send command arguments into structured result.
|
||||
|
||||
Args:
|
||||
args: Raw argument list from CLI
|
||||
|
||||
Returns:
|
||||
Dict with keys:
|
||||
auto_execute: bool
|
||||
no_memory_save: bool
|
||||
reply_to: str | None
|
||||
recipients: List[str]
|
||||
subject: str | None
|
||||
message: str | None
|
||||
mode: 'direct' | 'interactive' | 'error'
|
||||
error: str | None (set when mode=='error')
|
||||
"""
|
||||
working_args = list(args)
|
||||
|
||||
# Extract --dispatch / --auto-execute
|
||||
auto_execute = '--dispatch' in working_args or '--auto-execute' in working_args
|
||||
working_args = [a for a in working_args if a not in ('--dispatch', '--auto-execute')]
|
||||
|
||||
# Extract --no-memory-save
|
||||
no_memory_save = '--no-memory-save' in working_args
|
||||
working_args = [a for a in working_args if a != '--no-memory-save']
|
||||
|
||||
# Extract --reply-to
|
||||
reply_to = None
|
||||
if '--reply-to' in working_args:
|
||||
idx = working_args.index('--reply-to')
|
||||
if idx + 1 < len(working_args):
|
||||
reply_to = working_args[idx + 1]
|
||||
working_args = working_args[:idx] + working_args[idx + 2:]
|
||||
else:
|
||||
return {
|
||||
"auto_execute": auto_execute,
|
||||
"no_memory_save": no_memory_save,
|
||||
"reply_to": None,
|
||||
"recipients": [],
|
||||
"subject": None,
|
||||
"message": None,
|
||||
"mode": "error",
|
||||
"error": "--reply-to requires a branch address (e.g., --reply-to @dev_central)",
|
||||
}
|
||||
|
||||
# Separate recipients from subject/message
|
||||
recipients = []
|
||||
rest = []
|
||||
for a in working_args:
|
||||
if a.startswith('@') and not rest:
|
||||
recipients.append(a)
|
||||
elif a.startswith('/') and not rest:
|
||||
recipients.append(a)
|
||||
else:
|
||||
rest.append(a)
|
||||
|
||||
# Determine mode
|
||||
if recipients and len(rest) >= 2:
|
||||
mode = "direct"
|
||||
subject = rest[0]
|
||||
message = rest[1]
|
||||
elif not recipients and not rest:
|
||||
mode = "interactive"
|
||||
subject = None
|
||||
message = None
|
||||
else:
|
||||
mode = "error"
|
||||
subject = rest[0] if rest else None
|
||||
message = rest[1] if len(rest) >= 2 else None
|
||||
|
||||
return {
|
||||
"auto_execute": auto_execute,
|
||||
"no_memory_save": no_memory_save,
|
||||
"reply_to": reply_to,
|
||||
"recipients": recipients,
|
||||
"subject": subject,
|
||||
"message": message,
|
||||
"mode": mode,
|
||||
"error": None if mode != "error" else "Usage: send @recipient [subject] [message]",
|
||||
}
|
||||
|
||||
|
||||
def resolve_dispatch_target(
|
||||
branch: str,
|
||||
auto_execute: bool,
|
||||
get_branch_info_fn=None,
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Resolve dispatch target address for a recipient.
|
||||
|
||||
Args:
|
||||
branch: Recipient address (e.g., '@flow' or '/path/to/branch')
|
||||
auto_execute: Whether dispatch was requested
|
||||
get_branch_info_fn: Callable to look up branch info from registry by path
|
||||
|
||||
Returns:
|
||||
Dispatch target address string, or None if no dispatch.
|
||||
"""
|
||||
if not auto_execute:
|
||||
return None
|
||||
|
||||
if branch.startswith('/') or branch.startswith('~'):
|
||||
if get_branch_info_fn:
|
||||
branch_info = get_branch_info_fn(Path(branch))
|
||||
if branch_info:
|
||||
return branch_info.get("email", f"@{Path(branch).name.lower()}")
|
||||
return f"@{Path(branch).name.lower()}"
|
||||
|
||||
return branch
|
||||
@@ -1,17 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: json_handler.py - JSON Handler (Canonical Path)
|
||||
# Date: 2026-02-28
|
||||
# =================== AIPass ====================
|
||||
# Name: json_handler.py
|
||||
# Description: JSON Handler (Canonical Path)
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/json
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2026-02-28): Re-export from json_utils for Seed architecture compliance
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Re-exports json_handler from json_utils/ (canonical implementation)
|
||||
# - Satisfies Seed architecture standard for apps/handlers/json/ path
|
||||
# Created: 2026-02-28
|
||||
# Modified: 2026-02-28
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: json_handler.py - JSON Handler
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: json_handler.py
|
||||
# Description: JSON Handler
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/json_utils
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-15): Initial creation - auto-creating self-healing JSON
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-domain imports
|
||||
# - BYPASS: Direct json.load/json.dump required - this IS the json handler
|
||||
# - Pure business logic only
|
||||
# Created: 2025-11-15
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: data_ops.py - Error Monitor Data Operations Handler
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: data_ops.py
|
||||
# Description: Error Monitor Data Operations Handler
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/monitoring
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-15): Extracted from error_monitor.py
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-domain imports
|
||||
# - Pure business logic only
|
||||
# Created: 2025-11-15
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: errors.py - Error Detection Handler
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: errors.py
|
||||
# Description: Error Detection Handler
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/monitoring
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-15): Extracted from ai_mail_error_monitor.py
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-domain imports
|
||||
# - Pure business logic only
|
||||
# Created: 2025-11-15
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -34,8 +26,6 @@ import re
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Tuple
|
||||
|
||||
from aipass.cli.apps.modules import console
|
||||
|
||||
|
||||
def _find_repo_root() -> Path:
|
||||
"""Walk up from this file to find AIPASS_REGISTRY.json (repo root)."""
|
||||
@@ -251,6 +241,7 @@ def validate_error_data_entry(error_info: Dict) -> bool:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from aipass.cli.apps.modules import console
|
||||
console.print("\n" + "="*70)
|
||||
console.print("ERROR DETECTION HANDLER")
|
||||
console.print("="*70)
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: memory.py - Memory Health Handler
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: memory.py
|
||||
# Description: Memory Health Handler
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/monitoring
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-15): Extracted from ai_mail_local_memory_monitor.py
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-domain imports
|
||||
# - Pure business logic only
|
||||
# Created: 2025-11-15
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -31,7 +23,6 @@ Architecture:
|
||||
# =============================================
|
||||
from pathlib import Path
|
||||
|
||||
from aipass.cli.apps.modules import console
|
||||
|
||||
# =============================================
|
||||
# CONSTANTS
|
||||
@@ -189,6 +180,7 @@ def validate_thresholds(green_max: int, yellow_min: int, yellow_max: int, red_mi
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from aipass.cli.apps.modules import console
|
||||
console.print("\n" + "="*70)
|
||||
console.print("MEMORY HEALTH HANDLER")
|
||||
console.print("="*70)
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: notify.py
|
||||
# Description: Desktop Notification Handler
|
||||
# Version: 1.0.0
|
||||
# Created: 2026-03-08
|
||||
# Modified: 2026-03-08
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
Desktop Notification Handler
|
||||
|
||||
Sends persistent, stacking desktop notifications via D-Bus.
|
||||
GNOME's Portal mode strips persistence hints from notify-send,
|
||||
so we use the dbus module directly with unique app names per
|
||||
notification source to ensure they stack in the notification center.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
|
||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||
|
||||
|
||||
def send_notification(title: str, body: str, source: str = "ai_mail",
|
||||
icon: str = "dialog-information") -> bool:
|
||||
"""Send a persistent desktop notification.
|
||||
|
||||
Args:
|
||||
title: Notification title (e.g. "Email from @spawn")
|
||||
body: Notification body text
|
||||
source: Branch/context name used as app identity for stacking.
|
||||
Each unique source gets its own slot in the notification center.
|
||||
icon: Icon name (dialog-information, dialog-warning, etc.)
|
||||
|
||||
Returns:
|
||||
True if sent, False on failure
|
||||
"""
|
||||
# Primary: dbus direct (bypasses Portal, supports stacking)
|
||||
if _send_via_dbus(title, body, source, icon):
|
||||
return True
|
||||
|
||||
# Fallback: notify-send (works on non-GNOME, macOS via homebrew, etc.)
|
||||
return _send_via_notify_send(title, body, icon)
|
||||
|
||||
|
||||
def _send_via_dbus(title: str, body: str, source: str,
|
||||
icon: str) -> bool:
|
||||
"""Send notification via D-Bus using system python."""
|
||||
try:
|
||||
# Use system python which has dbus module (venv python may not)
|
||||
result = subprocess.run(
|
||||
["/usr/bin/python3", "-c", _DBUS_SCRIPT,
|
||||
source, icon, title, body],
|
||||
capture_output=True, text=True, timeout=5
|
||||
)
|
||||
return result.returncode == 0
|
||||
except (subprocess.SubprocessError, FileNotFoundError, OSError):
|
||||
return False
|
||||
|
||||
|
||||
def _send_via_notify_send(title: str, body: str, icon: str) -> bool:
|
||||
"""Fallback: send via notify-send."""
|
||||
try:
|
||||
subprocess.run(
|
||||
["notify-send", "-i", icon, title, body],
|
||||
capture_output=True, timeout=5
|
||||
)
|
||||
return True
|
||||
except (subprocess.SubprocessError, FileNotFoundError, OSError):
|
||||
return False
|
||||
|
||||
|
||||
# Inline script executed by system python (which has dbus module).
|
||||
# Kept as a string to avoid importing dbus in the venv.
|
||||
_DBUS_SCRIPT = """\
|
||||
import sys, dbus
|
||||
source, icon, title, body = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
|
||||
bus = dbus.SessionBus()
|
||||
proxy = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications')
|
||||
iface = dbus.Interface(proxy, 'org.freedesktop.Notifications')
|
||||
iface.Notify(source, 0, icon, title, body, [], {'urgency': dbus.Byte(1)}, -1)
|
||||
"""
|
||||
@@ -1,18 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: load.py - Registry Loading Handler
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: load.py
|
||||
# Description: Registry Loading Handler
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/registry
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-15): Initial handler - extracted from local_memory_monitor module
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Business logic implementation
|
||||
# - Pure functions where possible
|
||||
# - Clear error handling
|
||||
# Created: 2025-11-15
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: read.py - Registry Read Handler
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: read.py
|
||||
# Description: Registry Read Handler
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/registry
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-15): Extracted from ai_mail_cli.py
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handlers are INDEPENDENT (no cross-domain imports)
|
||||
# - Use: from prax.apps.modules.logger import system_logger as logger
|
||||
# - Keep handlers <300 lines each
|
||||
# Created: 2025-11-15
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -33,7 +24,6 @@ import json
|
||||
from pathlib import Path
|
||||
from typing import List, Dict
|
||||
|
||||
from aipass.cli.apps.modules import console
|
||||
|
||||
# Constants
|
||||
MODULE_NAME = "registry.read"
|
||||
@@ -172,6 +162,7 @@ def get_branch_path_map() -> Dict[str, str]:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from aipass.cli.apps.modules import console
|
||||
console.print("\n" + "="*70)
|
||||
console.print("AI_MAIL HANDLER: registry/read.py")
|
||||
console.print("="*70)
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: update.py - Registry Update Handler
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: update.py
|
||||
# Description: Registry Update Handler
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/registry
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-15): Extracted from ai_mail_branch_ping.py
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handlers are INDEPENDENT (no cross-domain imports)
|
||||
# - Use: from prax.apps.modules.logger import system_logger as logger
|
||||
# - Keep handlers <300 lines each
|
||||
# Created: 2025-11-15
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -34,7 +25,6 @@ from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Dict, Tuple
|
||||
|
||||
from aipass.cli.apps.modules import console
|
||||
|
||||
# Constants
|
||||
MODULE_NAME = "registry.update"
|
||||
@@ -247,6 +237,7 @@ def get_branch_context() -> Tuple[str, Path]:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from aipass.cli.apps.modules import console
|
||||
console.print("\n" + "="*70)
|
||||
console.print("AI_MAIL HANDLER: registry/update.py")
|
||||
console.print("="*70)
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: validate.py - Registry Validation Handler
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: validate.py
|
||||
# Description: Registry Validation Handler
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/registry
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-15): Extracted from ai_mail_cli.py
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handlers are INDEPENDENT (no cross-domain imports)
|
||||
# - Use: from prax.apps.modules.logger import system_logger as logger
|
||||
# - Keep handlers <300 lines each
|
||||
# Created: 2025-11-15
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -32,7 +23,6 @@ Handler Independence:
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Tuple
|
||||
|
||||
from aipass.cli.apps.modules import console
|
||||
|
||||
# Constants
|
||||
MODULE_NAME = "registry.validate"
|
||||
@@ -216,6 +206,7 @@ def get_duplicate_paths(branches: List[Dict]) -> List[str]:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from aipass.cli.apps.modules import console
|
||||
console.print("\n" + "="*70)
|
||||
console.print("AI_MAIL HANDLER: registry/validate.py")
|
||||
console.print("="*70)
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: error_handler.py - Error Detected Event Consumer
|
||||
# Date: 2026-02-02
|
||||
# =================== AIPass ====================
|
||||
# Name: error_handler.py
|
||||
# Description: Error Detected Event Consumer
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/trigger
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2026-02-02): Created - FPLAN-0284 Phase 2
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Follows AIPass Seed standards
|
||||
# - NO console.print() - handlers return data to modules
|
||||
# - NO logger calls in handler (causes recursion with trigger)
|
||||
# - Silent failure pattern - catch all exceptions
|
||||
# - Responds to error_detected events from Trigger's log_watcher
|
||||
# Created: 2026-02-02
|
||||
# Modified: 2026-02-02
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
|
||||
# =============================================
|
||||
# META DATA HEADER
|
||||
# Name: branch_detection.py - Branch Auto-Detection Handler
|
||||
# Date: 2025-11-18
|
||||
# =================== AIPass ====================
|
||||
# Name: branch_detection.py
|
||||
# Description: Branch Auto-Detection Handler
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/users
|
||||
#
|
||||
# CHANGELOG:
|
||||
# - v1.0.0 (2025-11-18): Initial creation - PWD/CWD branch detection
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-domain imports
|
||||
# - Can import Prax modules (service providers)
|
||||
# - Pure business logic only
|
||||
# Created: 2025-11-18
|
||||
# Modified: 2025-11-18
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
|
||||
# =============================================
|
||||
# META DATA HEADER
|
||||
# Name: config_generator.py - Local Config Auto-Generation Handler
|
||||
# Date: 2025-11-18
|
||||
# =================== AIPass ====================
|
||||
# Name: config_generator.py
|
||||
# Description: Local Config Auto-Generation Handler
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/handlers/users
|
||||
#
|
||||
# CHANGELOG:
|
||||
# - v1.0.0 (2025-11-18): Initial creation - auto-generate branch configs
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: NO cross-domain imports
|
||||
# - Can import Prax modules (service providers)
|
||||
# - Pure business logic only
|
||||
# Created: 2025-11-18
|
||||
# Modified: 2025-11-18
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
|
||||
# =============================================
|
||||
# META DATA HEADER
|
||||
# Name: load.py - User Config Loading Handler
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: load.py
|
||||
# Description: User Config Loading Handler
|
||||
# Version: 1.2.0
|
||||
# Category: ai_mail/handlers/users
|
||||
#
|
||||
# CHANGELOG:
|
||||
# - v1.2.0 (2025-11-18): Added per-branch config support with PWD detection
|
||||
# - v1.1.0 (2025-11-15): Renamed domain config -> users for business purpose naming
|
||||
# - v1.0.0 (2025-11-15): Extracted from ai_mail_cli.py and ai_mail_local_memory_monitor.py
|
||||
# Created: 2025-11-15
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
|
||||
# =============================================
|
||||
# META DATA HEADER
|
||||
# Name: user.py - User Info Handler
|
||||
# Date: 2025-11-30
|
||||
# =================== AIPass ====================
|
||||
# Name: user.py
|
||||
# Description: User Info Handler
|
||||
# Version: 2.0.0
|
||||
# Category: ai_mail/handlers/users
|
||||
#
|
||||
# CHANGELOG:
|
||||
# - v2.0.0 (2025-11-30): Removed all fallbacks - fail hard if branch detection fails
|
||||
# - v1.1.0 (2025-11-15): Renamed domain config -> users for business purpose naming
|
||||
# - v1.0.0 (2025-11-15): Extracted from ai_mail_cli.py
|
||||
# Created: 2025-11-30
|
||||
# Modified: 2025-11-30
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: branch_ping.py - Branch Ping Orchestration Module
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: branch_ping.py
|
||||
# Description: Branch Ping Orchestration Module
|
||||
# Version: 1.0.0
|
||||
# Category: ai_mail/modules
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-15): Initial orchestration module for branch ping
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Modules ORCHESTRATE (no business logic)
|
||||
# - Delegate to handlers for all operations
|
||||
# - Use json_handler.log_operation()
|
||||
# - Keep modules 110-155 lines
|
||||
# Created: 2025-11-15
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -168,7 +158,7 @@ EXAMPLES:
|
||||
drone ai_mail branch_ping thresholds
|
||||
"""
|
||||
)
|
||||
parser.print_help()
|
||||
console.print(parser.format_help())
|
||||
|
||||
|
||||
def handle_command(command: str, args: List[str]) -> bool:
|
||||
@@ -193,6 +183,26 @@ def handle_command(command: str, args: List[str]) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def print_introspection():
|
||||
"""Display module introspection info."""
|
||||
console.print()
|
||||
console.print("branch_ping Module")
|
||||
console.print("Orchestrates branch memory health monitoring: ping, status, registry, and thresholds.")
|
||||
console.print()
|
||||
console.print("Connected Handlers:")
|
||||
console.print(" handlers/monitoring/")
|
||||
console.print(" - memory.py (count_file_lines — count lines in a memory file)")
|
||||
console.print(" - memory.py (get_status_from_count — derive health status from line count)")
|
||||
console.print(" handlers/registry/")
|
||||
console.print(" - update.py (ping_registry — update memory health registry for a branch)")
|
||||
console.print(" - update.py (get_branch_context — resolve current branch name and directory)")
|
||||
console.print(" - update.py (update_json_memory_health — write health metadata into memory file)")
|
||||
console.print(" - load.py (load_registry — load the memory health registry file)")
|
||||
console.print(" handlers/json_utils/")
|
||||
console.print(" - json_handler.py (log_operation — log structured operation to JSON)")
|
||||
console.print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Handle --help flag
|
||||
if len(sys.argv) > 1 and sys.argv[1] in ['--help', '-h', 'help']:
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: dispatch.py - Dispatch Module
|
||||
# Date: 2026-02-02
|
||||
# =================== AIPass ====================
|
||||
# Name: dispatch.py
|
||||
# Description: Dispatch Module
|
||||
# Version: 3.0.0
|
||||
# Category: ai_mail/modules
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v3.0.0 (2026-02-20): Add wake subcommand - manual branch spawn without daemon
|
||||
# - v2.0.0 (2026-02-17): Add daemon subcommand, move status logic to handler
|
||||
# - v1.0.0 (2026-02-02): Initial version - dispatch status tracking
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Orchestration only - delegates to handlers
|
||||
# - Uses json_handler.log_operation()
|
||||
# Created: 2026-02-02
|
||||
# Modified: 2026-02-02
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -215,6 +205,22 @@ def _orchestrate_daemon() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def print_introspection():
|
||||
"""Display module introspection info."""
|
||||
console.print()
|
||||
console.print("dispatch Module")
|
||||
console.print("Orchestrates dispatch commands: status tracking, daemon management, and manual branch wake.")
|
||||
console.print()
|
||||
console.print("Connected Handlers:")
|
||||
console.print(" handlers/dispatch/")
|
||||
console.print(" - status.py (load_dispatch_log — load dispatch log entries)")
|
||||
console.print(" - status.py (check_pid_status — check if a spawned process is still running)")
|
||||
console.print(" - status.py (calculate_age — calculate age string from timestamp)")
|
||||
console.print(" - wake.py (wake_branch — manually wake a branch by spawning an agent)")
|
||||
console.print(" - daemon.py (run_daemon — start the continuous dispatch daemon)")
|
||||
console.print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) == 1:
|
||||
print_help()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
||||
|
||||
**Purpose:** LLM API access layer with provider abstraction, key management, model routing, and usage tracking.
|
||||
**Module:** `aipass.api`
|
||||
**Last Updated:** 2026-03-08
|
||||
|
||||
---
|
||||
|
||||
@@ -20,7 +21,7 @@
|
||||
|
||||
---
|
||||
|
||||
## CLI
|
||||
## Commands / Usage
|
||||
|
||||
```bash
|
||||
drone @api get-key # Retrieve API key for provider
|
||||
@@ -30,8 +31,11 @@ drone @api models # List available models from provider
|
||||
drone @api track # Track API usage metrics
|
||||
drone @api stats # Display API usage statistics
|
||||
drone @api --help # Full help output
|
||||
drone @api --version # Show version
|
||||
```
|
||||
|
||||
Running `drone @api` with no arguments displays module introspection (discovered modules and status).
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
@@ -76,3 +80,7 @@ api/
|
||||
### Provides To
|
||||
- All modules -- LLM API access for any branch that needs model inference
|
||||
- System-wide API key management and credential validation
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2026-03-08*
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: main.py - api Branch Orchestrator
|
||||
# Date: 2025-11-08
|
||||
# =================== AIPass ====================
|
||||
# Name: api.py
|
||||
# Description: Entry point CLI for drone @api — LLM client via OpenRouter
|
||||
# Version: 1.0.0
|
||||
# Category: api/entry_point
|
||||
# CODE STANDARDS: Seed v1.0.0
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-08): Initial version - modular architecture
|
||||
# Created: 2026-03-08
|
||||
# Modified: 2026-03-08
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: env.py - .env file operations
|
||||
# Date: 2025-11-16
|
||||
# =================== AIPass ====================
|
||||
# Name: env.py
|
||||
# Description: .env file operations
|
||||
# Version: 0.2.0
|
||||
# Category: api/handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v0.2.0 (2025-11-16): Extracted from api_connect.py - complete implementation
|
||||
# - v0.1.0 (2025-11-15): Initial handler stub
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler layer (standalone functions)
|
||||
# - Uses CLI service for output
|
||||
# - Under 300 lines
|
||||
# ==============================================
|
||||
# Created: 2025-11-16
|
||||
# Modified: 2025-11-16
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
.env File Handler
|
||||
@@ -36,8 +26,8 @@ import sys
|
||||
# Standard library
|
||||
from typing import Optional, Dict, List
|
||||
|
||||
# CLI services
|
||||
from aipass.cli.apps.modules import console
|
||||
# Logging
|
||||
from aipass.prax import logger
|
||||
|
||||
|
||||
# ==============================================
|
||||
@@ -172,7 +162,7 @@ def create_env_template(provider: str = "openrouter", target_path: Optional[Path
|
||||
# Don't overwrite existing file
|
||||
if env_path.exists():
|
||||
# .env file already exists
|
||||
console.print(f"[yellow]ℹ[/yellow] .env file already exists at {env_path}")
|
||||
logger.info(f".env file already exists at {env_path}")
|
||||
return True
|
||||
|
||||
# Template content based on provider
|
||||
@@ -208,12 +198,12 @@ OPENAI_API_KEY=sk-your-openai-key-here
|
||||
f.write(env_template)
|
||||
|
||||
# Created .env template
|
||||
console.print(f"[green]✓[/green] Created .env template at {env_path}")
|
||||
logger.info(f"Created .env template at {env_path}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
# Failed to create .env template
|
||||
console.print(f"[red]✗[/red] Failed to create .env template: {e}")
|
||||
logger.error(f"Failed to create .env template: {e}")
|
||||
return False
|
||||
|
||||
|
||||
@@ -267,12 +257,12 @@ def create_custom_env_template(variables: Dict[str, str], target_path: Path,
|
||||
f.write(content)
|
||||
|
||||
# Created custom .env template
|
||||
console.print(f"[green]✓[/green] Created custom .env template at {target_path}")
|
||||
logger.info(f"Created custom .env template at {target_path}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
# Failed to create custom .env template
|
||||
console.print(f"[red]✗[/red] Failed to create custom .env template: {e}")
|
||||
logger.error(f"Failed to create custom .env template: {e}")
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: keys.py - API Key Management Handler
|
||||
# Date: 2025-11-16
|
||||
# =================== AIPass ====================
|
||||
# Name: keys.py
|
||||
# Description: API Key Management Handler
|
||||
# Version: 2.0.0
|
||||
# Category: api/handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v2.0.0 (2025-11-16): Extracted from api_connect.py - complete implementation
|
||||
# - v1.0.0 (2025-11-15): Initial handler stub
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler layer (standalone functions)
|
||||
# - Uses CLI service for output
|
||||
# - Uses prax logger for system logs
|
||||
# - Under 300 lines
|
||||
# ==============================================
|
||||
# Created: 2025-11-16
|
||||
# Modified: 2025-11-16
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
API Key Management Handler
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: provider.py - Provider Configuration Handler
|
||||
# Date: 2025-11-16
|
||||
# =================== AIPass ====================
|
||||
# Name: provider.py
|
||||
# Description: Provider Configuration Handler
|
||||
# Version: 2.0.0
|
||||
# Category: api/handlers/config
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v2.0.0 (2025-11-16): Complete extraction from api_connect.py
|
||||
# - v1.0.0 (2025-11-15): Initial handler stub
|
||||
# Created: 2025-11-16
|
||||
# Modified: 2025-11-16
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -36,16 +31,8 @@ from typing import Dict, Any, Optional
|
||||
# Internal handlers
|
||||
from aipass.api.apps.handlers.json.json_handler import load_json, save_json
|
||||
|
||||
# Console for user feedback
|
||||
try:
|
||||
from rich.console import Console
|
||||
console = Console()
|
||||
except ImportError:
|
||||
# Fallback console if rich not available
|
||||
class SimpleConsole:
|
||||
def print(self, *args, **kwargs):
|
||||
print(*args)
|
||||
console = SimpleConsole()
|
||||
# Logging
|
||||
from aipass.prax import logger
|
||||
|
||||
# =============================================
|
||||
# CONSTANTS
|
||||
@@ -223,12 +210,12 @@ def update_provider_config(provider: str, updates: Dict[str, Any]) -> bool:
|
||||
json.dump(config, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# Updated config for provider
|
||||
console.print(f"[green]✓[/green] Provider config updated: {provider}")
|
||||
logger.info(f"Provider config updated: {provider}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
# Failed to update provider config
|
||||
console.print(f"[red]✗[/red] Failed to update provider config: {e}")
|
||||
logger.error(f"Failed to update provider config: {e}")
|
||||
return False
|
||||
|
||||
|
||||
@@ -345,7 +332,7 @@ def _create_default_config() -> bool:
|
||||
json.dump(default_config, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# Created default config
|
||||
console.print(f"[green]✓[/green] Created default config: {config_path}")
|
||||
logger.info(f"Created default config: {config_path}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: json_handler.py - JSON Auto-Creating Handler
|
||||
# Date: 2025-11-21
|
||||
# =================== AIPass ====================
|
||||
# Name: json_handler.py
|
||||
# Description: JSON Auto-Creating Handler
|
||||
# Version: 1.1.0
|
||||
# Category: api/handlers/json
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.1.0 (2025-11-21): Refactored to comply with error handling
|
||||
# - v1.0.0 (2025-11-13): Initial JSON auto-creation system
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Pure functions with proper error raising
|
||||
# - No Prax imports (handler tier 3)
|
||||
# Created: 2025-11-21
|
||||
# Modified: 2025-11-21
|
||||
# =============================================
|
||||
|
||||
import json
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
|
||||
# =============================================
|
||||
# META DATA HEADER
|
||||
# =================== AIPass ====================
|
||||
# Name: caller.py
|
||||
# Date: 2025-11-16
|
||||
# Description: OpenRouter Caller Detection Handler
|
||||
# Version: 1.0.0
|
||||
# Category: api/handlers
|
||||
#
|
||||
# CHANGELOG:
|
||||
# - v1.0.0 (2025-11-16): Initial caller detection handler extracted from archive
|
||||
# Created: 2025-11-16
|
||||
# Modified: 2025-11-16
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: client.py - OpenRouter Client Handler
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: client.py
|
||||
# Description: OpenRouter Client Handler
|
||||
# Version: 3.0.0
|
||||
# Category: api/handlers/openrouter
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v3.0.0 (2026-02-20): Fallback model chain in get_response() + retry logic in make_api_request()
|
||||
# - v2.0.0 (2025-11-16): Complete extraction from archive - client creation, API requests, response handling
|
||||
# - v1.0.0 (2025-11-15): Initial handler stub
|
||||
# Created: 2025-11-15
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -31,7 +25,7 @@ Configuration:
|
||||
- Connection pooling via client caching
|
||||
|
||||
Standards:
|
||||
- Uses console.print() for user output (NO print())
|
||||
- Uses prax logger for output (NO print() or console.print())
|
||||
- Uses logger.info() for system logging
|
||||
- Integrates with auth/keys, caller detection, usage tracking handlers
|
||||
- Standalone functions (no classes)
|
||||
@@ -46,8 +40,8 @@ from pathlib import Path
|
||||
import time
|
||||
from typing import Optional, Dict, List, Any
|
||||
|
||||
# AIPASS imports
|
||||
from aipass.cli.apps.modules import console
|
||||
# Logging
|
||||
from aipass.prax import logger
|
||||
|
||||
# OpenAI SDK for OpenRouter compatibility
|
||||
try:
|
||||
@@ -104,12 +98,12 @@ def create_client(api_key: str, base_url: str = OPENROUTER_BASE_URL, timeout: in
|
||||
"""
|
||||
if not OPENAI_AVAILABLE:
|
||||
# logger.error("OpenAI SDK not installed - cannot create client")
|
||||
console.print("[red]Error: OpenAI SDK not installed. Run: pip install openai[/red]")
|
||||
logger.error("OpenAI SDK not installed. Run: pip install openai")
|
||||
return None
|
||||
|
||||
if not api_key:
|
||||
# logger.error("Cannot create client - no API key provided")
|
||||
console.print("[red]Error: API key required for client creation[/red]")
|
||||
logger.error("API key required for client creation")
|
||||
return None
|
||||
|
||||
try:
|
||||
@@ -126,7 +120,7 @@ def create_client(api_key: str, base_url: str = OPENROUTER_BASE_URL, timeout: in
|
||||
|
||||
except Exception as e:
|
||||
# logger.error(f"Failed to create OpenRouter client: {e}")
|
||||
console.print(f"[red]Error creating OpenRouter client: {e}[/red]")
|
||||
logger.error(f"Error creating OpenRouter client: {e}")
|
||||
return None
|
||||
|
||||
|
||||
@@ -208,16 +202,16 @@ def make_api_request(client: OpenAI, messages: List[Dict], model: str, retries:
|
||||
try:
|
||||
response = client.chat.completions.create(**api_params)
|
||||
if attempt > 0:
|
||||
print(f"[INFO] API request succeeded on retry {attempt} for model {model}")
|
||||
logger.info(f"API request succeeded on retry {attempt} for model {model}")
|
||||
return response
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
if attempt < retries:
|
||||
delay = 1.0 * (attempt + 1) # 1s, 2s, ...
|
||||
print(f"[INFO] API request failed for {model} (attempt {attempt + 1}/{1 + retries}): {e} — retrying in {delay:.0f}s")
|
||||
logger.info(f"API request failed for {model} (attempt {attempt + 1}/{1 + retries}): {e} — retrying in {delay:.0f}s")
|
||||
time.sleep(delay)
|
||||
|
||||
print(f"[INFO] API request failed for {model} after {1 + retries} attempts: {last_error}")
|
||||
logger.error(f"API request failed for {model} after {1 + retries} attempts: {last_error}")
|
||||
return None
|
||||
|
||||
|
||||
@@ -318,15 +312,15 @@ def get_response(prompt: str, caller: Optional[str] = None, model: Optional[str]
|
||||
# Step 2: Require model from caller - no defaults
|
||||
if not model:
|
||||
# logger.error("No model specified - caller must provide model from their branch config")
|
||||
console.print("[red]Error: No model specified.[/red]")
|
||||
console.print("[yellow]Callers must provide their own model via branch config (e.g., flow_json/openrouter_config.json)[/yellow]")
|
||||
logger.error("No model specified.")
|
||||
logger.warning("Callers must provide their own model via branch config (e.g., flow_json/openrouter_config.json)")
|
||||
return None
|
||||
|
||||
# Step 3: Get API key
|
||||
api_key = get_api_key("openrouter")
|
||||
if not api_key:
|
||||
# logger.error("Cannot get response - no API key available")
|
||||
console.print("[red]Error: No OpenRouter API key available[/red]")
|
||||
logger.error("No OpenRouter API key available")
|
||||
return None
|
||||
|
||||
# Step 4: Get or create client
|
||||
@@ -376,7 +370,7 @@ def clear_client_cache() -> None:
|
||||
count = len(_client_cache)
|
||||
_client_cache.clear()
|
||||
# logger.info(f"Cleared {count} cached clients")
|
||||
console.print(f"[green]Cleared {count} cached OpenRouter clients[/green]")
|
||||
logger.info(f"Cleared {count} cached OpenRouter clients")
|
||||
|
||||
|
||||
def get_cache_stats() -> Dict[str, Any]:
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: models.py - OpenRouter Model Management
|
||||
# Date: 2025-11-16
|
||||
# =================== AIPass ====================
|
||||
# Name: models.py
|
||||
# Description: OpenRouter Model Management
|
||||
# Version: 1.0.0
|
||||
# Category: api/handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-16): Complete handler - model fetching, filtering, pricing
|
||||
# Created: 2025-11-16
|
||||
# Modified: 2025-11-16
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -29,8 +25,8 @@ from pathlib import Path
|
||||
# Standard library imports
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
# Console import (NO print())
|
||||
from aipass.cli.apps.modules import console
|
||||
# Logging
|
||||
from aipass.prax import logger
|
||||
|
||||
# Third-party imports
|
||||
import requests
|
||||
@@ -67,7 +63,7 @@ def get_available_models(api_key: Optional[str] = None) -> List[Dict]:
|
||||
|
||||
Example:
|
||||
>>> models = get_available_models()
|
||||
>>> console.print(f"Found {len(models)} models")
|
||||
>>> logger.info(f"Found {len(models)} models")
|
||||
"""
|
||||
try:
|
||||
# Get API key if not provided
|
||||
@@ -76,7 +72,7 @@ def get_available_models(api_key: Optional[str] = None) -> List[Dict]:
|
||||
|
||||
if not api_key:
|
||||
# logger.info(f"[{MODULE_NAME}] No API key available for OpenRouter")
|
||||
console.print("[yellow]No OpenRouter API key found[/yellow]")
|
||||
logger.warning("No OpenRouter API key found")
|
||||
return []
|
||||
|
||||
# Fetch models from API
|
||||
@@ -91,7 +87,7 @@ def get_available_models(api_key: Optional[str] = None) -> List[Dict]:
|
||||
|
||||
except Exception as e:
|
||||
# logger.info(f"[{MODULE_NAME}] Failed to get available models: {e}")
|
||||
console.print(f"[red]Error fetching models: {e}[/red]")
|
||||
logger.error(f"Error fetching models: {e}")
|
||||
return []
|
||||
|
||||
|
||||
@@ -111,7 +107,7 @@ def get_free_models(api_key: Optional[str] = None) -> List[Dict]:
|
||||
Example:
|
||||
>>> free_models = get_free_models()
|
||||
>>> for model in free_models:
|
||||
... console.print(f"Free: {model['id']}")
|
||||
... logger.info(f"Free: {model['id']}")
|
||||
"""
|
||||
try:
|
||||
# Get all models first
|
||||
@@ -128,7 +124,7 @@ def get_free_models(api_key: Optional[str] = None) -> List[Dict]:
|
||||
|
||||
except Exception as e:
|
||||
# logger.info(f"[{MODULE_NAME}] Failed to get free models: {e}")
|
||||
console.print(f"[red]Error fetching free models: {e}[/red]")
|
||||
logger.error(f"Error fetching free models: {e}")
|
||||
return []
|
||||
|
||||
|
||||
@@ -166,7 +162,7 @@ def fetch_models_from_api(api_key: str) -> List[Dict]:
|
||||
# Check response status
|
||||
if response.status_code != 200:
|
||||
# logger.info(f"[{MODULE_NAME}] API request failed with status {response.status_code}")
|
||||
console.print(f"[red]OpenRouter API error: {response.status_code}[/red]")
|
||||
logger.error(f"OpenRouter API error: {response.status_code}")
|
||||
return []
|
||||
|
||||
# Parse JSON response
|
||||
@@ -183,22 +179,22 @@ def fetch_models_from_api(api_key: str) -> List[Dict]:
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
# logger.info(f"[{MODULE_NAME}] API request timeout after {DEFAULT_TIMEOUT}s")
|
||||
console.print(f"[red]Request timeout - OpenRouter API not responding[/red]")
|
||||
logger.error("Request timeout - OpenRouter API not responding")
|
||||
return []
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
# logger.info(f"[{MODULE_NAME}] Network error: {e}")
|
||||
console.print(f"[red]Network error: {e}[/red]")
|
||||
logger.error(f"Network error: {e}")
|
||||
return []
|
||||
|
||||
except ValueError as e:
|
||||
# logger.info(f"[{MODULE_NAME}] JSON parse error: {e}")
|
||||
console.print(f"[red]Invalid JSON response from API[/red]")
|
||||
logger.error("Invalid JSON response from API")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
# logger.info(f"[{MODULE_NAME}] Unexpected error fetching models: {e}")
|
||||
console.print(f"[red]Error: {e}[/red]")
|
||||
logger.error(f"Error: {e}")
|
||||
return []
|
||||
|
||||
|
||||
@@ -291,7 +287,7 @@ def extract_model_metadata(model: Dict) -> Dict:
|
||||
Example:
|
||||
>>> model = get_model_by_id("meta-llama/llama-3.3-70b-instruct:free")
|
||||
>>> meta = extract_model_metadata(model)
|
||||
>>> console.print(f"Context: {meta['context_length']}")
|
||||
>>> logger.info(f"Context: {meta['context_length']}")
|
||||
"""
|
||||
try:
|
||||
pricing = model.get("pricing", {})
|
||||
@@ -341,27 +337,27 @@ def list_model_ids(models: List[Dict]) -> List[str]:
|
||||
|
||||
def display_models(models: List[Dict], show_pricing: bool = True) -> None:
|
||||
"""
|
||||
Display models in formatted output using console
|
||||
Display models in formatted output using logger
|
||||
|
||||
Args:
|
||||
models: List of model dictionaries to display
|
||||
show_pricing: Whether to show pricing information
|
||||
"""
|
||||
if not models:
|
||||
console.print("[yellow]No models to display[/yellow]")
|
||||
logger.info("No models to display")
|
||||
return
|
||||
|
||||
console.print(f"\n[bold]Found {len(models)} models:[/bold]\n")
|
||||
logger.info(f"Found {len(models)} models:")
|
||||
|
||||
for i, model in enumerate(models, 1):
|
||||
model_id = model.get("id", "unknown")
|
||||
name = model.get("name", "")
|
||||
context = model.get("context_length", 0)
|
||||
|
||||
console.print(f"[cyan]{i}. {model_id}[/cyan]")
|
||||
logger.info(f"{i}. {model_id}")
|
||||
if name:
|
||||
console.print(f" Name: {name}")
|
||||
console.print(f" Context: {context:,} tokens")
|
||||
logger.info(f" Name: {name}")
|
||||
logger.info(f" Context: {context:,} tokens")
|
||||
|
||||
if show_pricing:
|
||||
pricing = model.get("pricing", {})
|
||||
@@ -369,11 +365,9 @@ def display_models(models: List[Dict], show_pricing: bool = True) -> None:
|
||||
completion_cost = pricing.get("completion", "0")
|
||||
|
||||
if prompt_cost == "0" and completion_cost == "0":
|
||||
console.print(" [green]FREE[/green]")
|
||||
logger.info(" FREE")
|
||||
else:
|
||||
console.print(f" Prompt: ${prompt_cost} / Completion: ${completion_cost}")
|
||||
|
||||
console.print()
|
||||
logger.info(f" Prompt: ${prompt_cost} / Completion: ${completion_cost}")
|
||||
|
||||
|
||||
def display_free_models_summary(api_key: Optional[str] = None) -> None:
|
||||
@@ -386,20 +380,20 @@ def display_free_models_summary(api_key: Optional[str] = None) -> None:
|
||||
Args:
|
||||
api_key: Optional OpenRouter API key
|
||||
"""
|
||||
console.print("[bold]Searching for FREE models on OpenRouter...[/bold]")
|
||||
console.print("=" * 60)
|
||||
logger.info("Searching for FREE models on OpenRouter...")
|
||||
logger.info("=" * 60)
|
||||
|
||||
free_models = get_free_models(api_key)
|
||||
|
||||
if not free_models:
|
||||
console.print("[yellow]No free models found[/yellow]")
|
||||
console.print("OpenRouter may have changed pricing.")
|
||||
console.print("Alternative: Use very cheap models like openai/gpt-4o-mini")
|
||||
logger.info("No free models found")
|
||||
logger.info("OpenRouter may have changed pricing.")
|
||||
logger.info("Alternative: Use very cheap models like openai/gpt-4o-mini")
|
||||
return
|
||||
|
||||
display_models(free_models, show_pricing=False)
|
||||
|
||||
console.print("\n[bold]RECOMMENDED FREE MODELS TO TRY:[/bold]")
|
||||
console.print("-" * 60)
|
||||
logger.info("RECOMMENDED FREE MODELS TO TRY:")
|
||||
logger.info("-" * 60)
|
||||
for model in free_models[:5]: # Top 5
|
||||
console.print(f" - {model.get('id', '')}")
|
||||
logger.info(f" - {model.get('id', '')}")
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: provision.py - Caller Auto-Provisioning Handler
|
||||
# Date: 2025-11-16
|
||||
# =================== AIPass ====================
|
||||
# Name: provision.py
|
||||
# Description: Caller Auto-Provisioning Handler
|
||||
# Version: 1.0.0
|
||||
# Category: api/handlers/openrouter
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-16): Initial handler - auto-provision caller configs
|
||||
# Created: 2025-11-16
|
||||
# Modified: 2025-11-16
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -21,7 +17,7 @@ Business logic for provisioning OpenRouter API configs:
|
||||
- Ensure caller has complete 3-file JSON structure
|
||||
|
||||
COMPLIANT STANDARDS:
|
||||
- Uses console.print() for output (NO print())
|
||||
- Uses prax logger for output (NO print() or console.print())
|
||||
- Uses prax logger for operations
|
||||
- Standalone functions (no class dependencies)
|
||||
- Imports from caller handler for detection logic
|
||||
@@ -36,7 +32,7 @@ import json
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Optional, Tuple
|
||||
|
||||
from aipass.cli.apps.modules import console
|
||||
from aipass.prax import logger
|
||||
|
||||
from aipass.api.apps.handlers.openrouter.caller import detect_caller_from_stack
|
||||
|
||||
@@ -168,12 +164,12 @@ def provision_json_folder(json_folder: Path) -> bool:
|
||||
|
||||
json_folder.mkdir(parents=True, exist_ok=True)
|
||||
# logger.info(f"Created JSON folder: {json_folder}")
|
||||
console.print(f"[green]Created JSON folder:[/green] {json_folder}")
|
||||
logger.info(f"Created JSON folder: {json_folder}")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
# logger.error(f"Failed to create JSON folder {json_folder}: {e}")
|
||||
console.print(f"[red]Error:[/red] Failed to create JSON folder: {e}")
|
||||
logger.error(f"Failed to create JSON folder: {e}")
|
||||
return False
|
||||
|
||||
|
||||
@@ -207,7 +203,7 @@ def create_caller_config(caller: str, json_folder: Path) -> Dict[str, Any]:
|
||||
return {}
|
||||
|
||||
# logger.info(f"Created API config for {caller}: {config_file}")
|
||||
console.print(f"[green]Created config:[/green] {config_file.name}")
|
||||
logger.info(f"Created config: {config_file.name}")
|
||||
|
||||
# Create data file
|
||||
data_file = json_folder / "openrouter_skill_data.json"
|
||||
@@ -215,7 +211,7 @@ def create_caller_config(caller: str, json_folder: Path) -> Dict[str, Any]:
|
||||
|
||||
if write_json(data_file, data):
|
||||
# logger.info(f"Created data file for {caller}: {data_file}")
|
||||
console.print(f"[green]Created data:[/green] {data_file.name}")
|
||||
logger.info(f"Created data: {data_file.name}")
|
||||
|
||||
# Create log file
|
||||
log_file = json_folder / "openrouter_skill_log.json"
|
||||
@@ -223,16 +219,16 @@ def create_caller_config(caller: str, json_folder: Path) -> Dict[str, Any]:
|
||||
|
||||
if write_json(log_file, log_data):
|
||||
# logger.info(f"Created log file for {caller}: {log_file}")
|
||||
console.print(f"[green]Created log:[/green] {log_file.name}")
|
||||
logger.info(f"Created log: {log_file.name}")
|
||||
|
||||
console.print(f"[cyan]Info:[/cyan] Auto-provisioned OpenRouter config for '{caller}'")
|
||||
console.print(f"[yellow]Note:[/yellow] Reload config and retry request")
|
||||
logger.info(f"Auto-provisioned OpenRouter config for '{caller}'")
|
||||
logger.warning("Reload config and retry request")
|
||||
|
||||
return config
|
||||
|
||||
except Exception as e:
|
||||
# logger.error(f"Failed to create config for {caller}: {e}")
|
||||
console.print(f"[red]Error:[/red] Config creation failed: {e}")
|
||||
logger.error(f"Config creation failed: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
@@ -259,7 +255,7 @@ def ensure_caller_config(caller: str | None = None) -> Dict[str, Any]:
|
||||
# logger.info(f"Auto-detected caller: {caller}")
|
||||
else:
|
||||
# logger.warning("Could not detect caller from stack")
|
||||
console.print("[yellow]Warning:[/yellow] Could not detect caller module")
|
||||
logger.warning("Could not detect caller module")
|
||||
return {}
|
||||
|
||||
# Get JSON folder path if not already detected
|
||||
@@ -267,7 +263,7 @@ def ensure_caller_config(caller: str | None = None) -> Dict[str, Any]:
|
||||
_, json_folder = detect_caller_from_stack()
|
||||
if not json_folder:
|
||||
# logger.error(f"Could not determine JSON folder for {caller}")
|
||||
console.print(f"[red]Error:[/red] Could not find JSON folder for '{caller}'")
|
||||
logger.error(f"Could not find JSON folder for '{caller}'")
|
||||
return {}
|
||||
|
||||
# Check if config already exists
|
||||
@@ -280,14 +276,14 @@ def ensure_caller_config(caller: str | None = None) -> Dict[str, Any]:
|
||||
return config
|
||||
else:
|
||||
# logger.warning(f"Config file corrupted for {caller}, regenerating")
|
||||
console.print(f"[yellow]Warning:[/yellow] Corrupted config, regenerating...")
|
||||
logger.warning("Corrupted config, regenerating...")
|
||||
|
||||
# Create new config
|
||||
return create_caller_config(caller, json_folder)
|
||||
|
||||
except Exception as e:
|
||||
# logger.error(f"Failed to ensure config for {caller}: {e}")
|
||||
console.print(f"[red]Error:[/red] Config provisioning failed: {e}")
|
||||
logger.error(f"Config provisioning failed: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: aggregation.py - Usage Aggregation Handler
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: aggregation.py
|
||||
# Description: Usage Aggregation Handler
|
||||
# Version: 1.0.0
|
||||
# Category: api/handlers/usage
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-15): Initial handler - usage stats aggregation
|
||||
# - v1.1.0 (2025-11-16): Extracted aggregation logic from archive
|
||||
# Created: 2025-11-15
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: cleanup.py - Usage data retention and cleanup
|
||||
# Date: 2025-11-16
|
||||
# =================== AIPass ====================
|
||||
# Name: cleanup.py
|
||||
# Description: Usage data retention and cleanup
|
||||
# Version: 0.1.0
|
||||
# Category: api/handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v0.1.0 (2025-11-16): Extracted from api_usage.py
|
||||
# - v1.0.0 (2025-11-15): Initial handler - old data cleanup
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler layer (standalone functions)
|
||||
# - Uses CLI service for output
|
||||
# - Under 300 lines
|
||||
# ==============================================
|
||||
# Created: 2025-11-16
|
||||
# Modified: 2025-11-16
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
Usage Data Cleanup Handler
|
||||
@@ -32,8 +22,8 @@ import json
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, Dict, List
|
||||
|
||||
# CLI services
|
||||
from aipass.cli.apps.modules import console, success, warning
|
||||
# Logging
|
||||
from aipass.prax import logger
|
||||
|
||||
|
||||
def _read_json(file_path: Path) -> Optional[Dict]:
|
||||
@@ -99,13 +89,13 @@ def cleanup_old_data(data_file_path: Path, retention_days: int = 30) -> int:
|
||||
|
||||
_write_json(data_file_path, data)
|
||||
# logger.info(f"Cleaned up {len(old_generations)} generation entries")
|
||||
success(f"Cleaned up {len(old_generations)} generation entries older than {retention_days} days")
|
||||
logger.info(f"Cleaned up {len(old_generations)} generation entries older than {retention_days} days")
|
||||
|
||||
return len(old_generations)
|
||||
|
||||
except Exception as e:
|
||||
# logger.error(f"Cleanup failed: {e}")
|
||||
console.print(f"[red]Cleanup failed: {e}[/red]")
|
||||
logger.error(f"Cleanup failed: {e}")
|
||||
raise
|
||||
|
||||
|
||||
@@ -162,13 +152,13 @@ def cleanup_daily_totals(data_file_path: Path, retention_days: int = 90) -> int:
|
||||
|
||||
_write_json(data_file_path, data)
|
||||
# logger.info(f"Cleaned up {len(old_dates)} daily total entries")
|
||||
success(f"Cleaned up {len(old_dates)} daily total entries older than {retention_days} days")
|
||||
logger.info(f"Cleaned up {len(old_dates)} daily total entries older than {retention_days} days")
|
||||
|
||||
return len(old_dates)
|
||||
|
||||
except Exception as e:
|
||||
# logger.error(f"Daily totals cleanup failed: {e}")
|
||||
console.print(f"[red]Daily totals cleanup failed: {e}[/red]")
|
||||
logger.error(f"Daily totals cleanup failed: {e}")
|
||||
raise
|
||||
|
||||
|
||||
@@ -190,7 +180,7 @@ def auto_cleanup(data_file_path: Path, config: Optional[Dict] = None) -> Dict[st
|
||||
|
||||
except Exception as e:
|
||||
# logger.error(f"Auto cleanup failed: {e}")
|
||||
console.print(f"[red]Auto cleanup failed: {e}[/red]")
|
||||
logger.error(f"Auto cleanup failed: {e}")
|
||||
raise
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: tracking.py - Usage Tracking Handler
|
||||
# Date: 2025-11-16
|
||||
# =================== AIPass ====================
|
||||
# Name: tracking.py
|
||||
# Description: Usage Tracking Handler
|
||||
# Version: 1.0.0
|
||||
# Category: api/handlers/usage
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-16): Extracted tracking logic from archive
|
||||
# Created: 2025-11-16
|
||||
# Modified: 2025-11-16
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: api_key.py - API Key Management Module
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: api_key.py
|
||||
# Description: API Key Management Module
|
||||
# Version: 1.0.0
|
||||
# Category: api/modules
|
||||
# CODE STANDARDS: Seed v1.0.0
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-15): Initial module - orchestrates key management
|
||||
# Created: 2025-11-15
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: openrouter_client.py - OpenRouter Client Module
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: openrouter_client.py
|
||||
# Description: OpenRouter Client Module
|
||||
# Version: 1.0.0
|
||||
# Category: api/modules
|
||||
# CODE STANDARDS: Seed v1.0.0
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-15): Initial module - orchestrates OpenRouter operations
|
||||
# Created: 2025-11-15
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -237,7 +232,7 @@ def get_response(prompt: str, caller: str | None = None, model: str | None = Non
|
||||
>>> from aipass.api.apps.modules.openrouter_client import get_response
|
||||
>>> response = get_response("Hello", caller="flow", model="anthropic/claude-3.5-sonnet")
|
||||
>>> if response:
|
||||
... print(response['content'])
|
||||
... console.print(response['content'])
|
||||
"""
|
||||
return client.get_response(prompt, caller, model, **kwargs)
|
||||
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: usage_tracker.py - Usage Tracking Module
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: usage_tracker.py
|
||||
# Description: Usage Tracking Module
|
||||
# Version: 1.0.0
|
||||
# Category: api/modules
|
||||
# CODE STANDARDS: Seed v1.0.0
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-15): Initial module - orchestrates usage tracking
|
||||
# Created: 2025-11-15
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
+116
-2
@@ -3,13 +3,127 @@
|
||||
**Purpose:** Multi-mode backup with Google Drive integration
|
||||
**Module:** `aipass.backup`
|
||||
**Created:** 2026-03-07
|
||||
**Citizen Class:** birthright
|
||||
**Citizen Class:** builder
|
||||
**Last Updated:** 2026-03-08
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Birthright citizen — minimal presence with identity and memory.
|
||||
Builder citizen — full 3-layer architecture with identity and memory.
|
||||
|
||||
Provides automated file protection through snapshot backups, versioned backups,
|
||||
and Google Drive synchronization. The entry point routes commands to four
|
||||
specialized modules: `backup_core`, `google_drive_sync`, `integrations`, and
|
||||
`reauth_drive`.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
backup/
|
||||
├── __init__.py
|
||||
├── README.md
|
||||
├── apps/
|
||||
│ ├── backup.py # Entry point (CLI) — drone @backup
|
||||
│ ├── modules/
|
||||
│ │ ├── backup_core.py # Core backup operations (snapshot, versioned)
|
||||
│ │ ├── google_drive_sync.py # Google Drive sync orchestration
|
||||
│ │ ├── integrations.py # Cross-module integration commands
|
||||
│ │ └── reauth_drive.py # Google Drive re-authentication
|
||||
│ ├── handlers/
|
||||
│ │ ├── config/
|
||||
│ │ │ └── config_handler.py # Configuration management
|
||||
│ │ ├── diff/
|
||||
│ │ │ ├── diff_generator.py # Diff generation between backups
|
||||
│ │ │ ├── version_manager.py # Version tracking
|
||||
│ │ │ └── vscode_integration.py # VS Code diff viewer integration
|
||||
│ │ ├── json/
|
||||
│ │ │ ├── backup_info_handler.py # Backup info JSON read/write
|
||||
│ │ │ ├── backup_metadata_builder.py # Metadata construction
|
||||
│ │ │ ├── changelog_handler.py # Changelog JSON management
|
||||
│ │ │ ├── drive_sync_json.py # Drive sync state tracking
|
||||
│ │ │ ├── json_handler.py # Generic JSON utilities
|
||||
│ │ │ └── statistics_handler.py # Backup statistics
|
||||
│ │ ├── models/
|
||||
│ │ │ └── backup_models.py # Data models for backup objects
|
||||
│ │ ├── operations/
|
||||
│ │ │ ├── drive_sync_client.py # Google Drive API client
|
||||
│ │ │ ├── drive_sync_ops.py # Drive sync implementation
|
||||
│ │ │ ├── file_cleanup.py # Old backup cleanup
|
||||
│ │ │ ├── file_operations.py # File copy/move operations
|
||||
│ │ │ ├── file_scanner.py # File discovery and filtering
|
||||
│ │ │ ├── integration_ops.py # Integration operation logic
|
||||
│ │ │ └── path_builder.py # Backup path construction
|
||||
│ │ ├── reporting/
|
||||
│ │ │ └── report_formatter.py # Backup report formatting
|
||||
│ │ └── utils/
|
||||
│ │ ├── backup_timestamps.py # Timestamp utilities
|
||||
│ │ ├── reauth_handler.py # Re-auth implementation
|
||||
│ │ └── system_utils.py # System-level utilities
|
||||
│ ├── extensions/ # Extension point (placeholder)
|
||||
│ ├── json_templates/ # JSON template files
|
||||
│ └── plugins/ # Plugin point (placeholder)
|
||||
├── backup_json/ # JSON tracking data
|
||||
├── artifacts/ # Backup artifacts
|
||||
├── docs/ # Documentation
|
||||
├── tests/ # Test suite
|
||||
└── tools/ # Branch verification utilities
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Commands / Usage
|
||||
|
||||
```bash
|
||||
drone @backup # Introspection — list discovered modules
|
||||
drone @backup --help # Show full help
|
||||
drone @backup --version # Show version
|
||||
drone @backup --all # Full backup cycle: snapshot -> versioned -> drive-sync
|
||||
drone @backup snapshot # Create a system snapshot backup
|
||||
drone @backup versioned # Create a versioned backup
|
||||
drone @backup drive-test # Test Google Drive connectivity
|
||||
drone @backup drive-sync # Sync backups to Google Drive
|
||||
drone @backup drive-sync --test # Run a small test sync to verify integration
|
||||
drone @backup drive-stats # Show Drive file tracker statistics
|
||||
drone @backup drive-clear-tracker # Clear Drive file tracker cache
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--verbose`, `-v` | Extra diagnostic output |
|
||||
| `--dry-run` | Preview what would happen, execute nothing |
|
||||
| `--note NOTE` | Add a backup note/description |
|
||||
| `--project NAME` | Project name for Drive sync (default: AIPass) |
|
||||
| `--force` | Force sync all files (ignore change tracker) |
|
||||
| `--limit N` | Limit drive-sync to first N files |
|
||||
|
||||
---
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Depends On
|
||||
- `rich` — Console output and formatting
|
||||
- Python stdlib (`sys`, `argparse`, `logging`, `pathlib`)
|
||||
|
||||
### Provides To
|
||||
- All modules — automated file protection, snapshot and versioned backups
|
||||
- Google Drive — cloud backup synchronization
|
||||
- Other branches — backup artifacts via `backup_json/` and `artifacts/`
|
||||
|
||||
---
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Purpose |
|
||||
|--------|---------|
|
||||
| `backup_core` | Core backup operations — snapshot and versioned backup creation |
|
||||
| `google_drive_sync` | Google Drive synchronization — upload, track, and manage cloud backups |
|
||||
| `integrations` | Cross-module integration commands and coordination |
|
||||
| `reauth_drive` | Google Drive re-authentication when credentials expire |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: backup.py - Backup System Entry Point
|
||||
# Date: 2025-11-22
|
||||
# Version: 1.3.0
|
||||
# Category: core/backup
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.3.0 (2026-03-06): Adapted for AIPass public repo
|
||||
# * Removed shebang, sys.path manipulation, prax/cli imports
|
||||
# * Replaced get_modules() with explicit relative imports
|
||||
# * Uses standard logging and rich console
|
||||
# - v1.2.0 (2026-02-22): Seed CLI flags standard: --version, backup all command
|
||||
# - v1.1.0 (2025-11-22): Fixed META header - correct filename, category, and standards
|
||||
# - v1.0.0 (2025-11-08): Initial version - modular architecture
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handlers implement logic, modules orchestrate
|
||||
# =================== AIPass ====================
|
||||
# Name: backup.py
|
||||
# Description: Entry point CLI for drone @backup
|
||||
# Version: 1.0.0
|
||||
# Created: 2026-03-08
|
||||
# Modified: 2026-03-08
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -28,14 +16,11 @@ Main handles routing, modules implement functionality.
|
||||
# Standard library imports
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any, List
|
||||
|
||||
from rich.console import Console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
console = Console()
|
||||
from aipass.cli.apps.modules import console
|
||||
from aipass.prax import logger
|
||||
|
||||
# Explicit module imports (replaces dynamic discover_modules)
|
||||
from aipass.backup.apps.modules import backup_core, google_drive_sync, integrations, reauth_drive
|
||||
|
||||
@@ -1,29 +1,9 @@
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: config_handler.py - Backup system configuration and patterns
|
||||
# Date: 2025-11-23
|
||||
# =================== AIPass ====================
|
||||
# Name: config_handler.py
|
||||
# Description: Backup system configuration and patterns
|
||||
# Version: 2.0.2
|
||||
# Category: handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v2.0.2 (2026-03-06): Adapted for AIPass public repo
|
||||
# * Removed shebang and sys.path manipulation
|
||||
# * Cleared CLI_TRACKING_PATTERNS (hardcoded internal paths removed)
|
||||
# - v2.0.1 (2025-11-23): Added ignore patterns for log/data JSON files
|
||||
# * Enabled *_data.json pattern to ignore backup_core_data.json
|
||||
# * Enabled *_log.json pattern to ignore backup_core_log.json, drone_log.json
|
||||
# * Enabled snapshot_backup.json pattern
|
||||
# * Prevents frequent-change files from being backed up every run
|
||||
# - v2.0.0 (2025-11-16): Seed standard extraction from archive
|
||||
# * All configuration constants and patterns
|
||||
# * Helper functions for pattern matching and filtering
|
||||
# * Pure configuration - no complex logic
|
||||
# - v1.0.0 (2025-10-14): Initial extraction from backup.py
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Follow seed 3-layer architecture
|
||||
# - Handlers must be independent and transportable
|
||||
# - No cross-handler imports except within same domain
|
||||
# Created: 2025-11-23
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,22 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: diff_generator.py - Unified diff generation with binary detection
|
||||
# Date: 2025-11-16
|
||||
# =================== AIPass ====================
|
||||
# Name: diff_generator.py
|
||||
# Description: Unified diff generation with binary detection
|
||||
# Version: 2.0.0
|
||||
# Category: handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v2.0.0 (2025-11-16): Extracted from backup_diff.py
|
||||
# * Diff generation functionality
|
||||
# * Binary file detection
|
||||
# * Unified diff content generation
|
||||
# * Updated imports to new handler locations
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Follow seed 3-layer architecture
|
||||
# - Handlers must be independent and transportable
|
||||
# - No cross-handler imports except within same domain
|
||||
# Created: 2025-11-16
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,22 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: version_manager.py - Version discovery and management
|
||||
# Date: 2025-11-16
|
||||
# =================== AIPass ====================
|
||||
# Name: version_manager.py
|
||||
# Description: Version discovery and management
|
||||
# Version: 2.0.0
|
||||
# Category: handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v2.0.0 (2025-11-16): Extracted from backup_diff.py
|
||||
# * Version file discovery functionality
|
||||
# * Versioned file listing
|
||||
# * Support for file-organized structure
|
||||
# * Updated imports to new handler locations
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Follow seed 3-layer architecture
|
||||
# - Handlers must be independent and transportable
|
||||
# - No cross-handler imports except within same domain
|
||||
# Created: 2025-11-16
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -30,11 +17,11 @@ Supports version listing and filtering.
|
||||
# IMPORTS
|
||||
# =============================================
|
||||
|
||||
import logging
|
||||
from aipass.prax import logger
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
# logger imported from aipass.prax
|
||||
|
||||
# Import from handlers
|
||||
from aipass.backup.apps.handlers.utils.system_utils import safe_print
|
||||
|
||||
@@ -1,22 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: vscode_integration.py - VS Code diff viewer integration
|
||||
# Date: 2025-11-16
|
||||
# =================== AIPass ====================
|
||||
# Name: vscode_integration.py
|
||||
# Description: VS Code diff viewer integration
|
||||
# Version: 2.0.0
|
||||
# Category: handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v2.0.0 (2025-11-16): Extracted from backup_diff.py
|
||||
# * VS Code diff viewer integration
|
||||
# * File comparison logic
|
||||
# * Baseline vs current comparison
|
||||
# * Updated imports to new handler locations
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Follow seed 3-layer architecture
|
||||
# - Handlers must be independent and transportable
|
||||
# - No cross-handler imports except within same domain
|
||||
# Created: 2025-11-16
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -33,10 +20,10 @@ Supports baseline vs current and version-to-version comparisons.
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import logging
|
||||
from aipass.prax import logger
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
# logger imported from aipass.prax
|
||||
|
||||
# Import from handlers
|
||||
from aipass.backup.apps.handlers.utils.system_utils import safe_print
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: backup_info_handler.py - Backup state and statistics
|
||||
# Date: 2025-11-16
|
||||
# =================== AIPass ====================
|
||||
# Name: backup_info_handler.py
|
||||
# Description: Backup state and statistics
|
||||
# Version: 1.0.0
|
||||
# Category: handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-16): Initial extraction from backup_core.py
|
||||
# * load_backup_info() - Load mode-specific state
|
||||
# * save_backup_info() - Persist backup state
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Follow seed 3-layer architecture
|
||||
# - Handlers must be independent and transportable
|
||||
# - No cross-handler imports except within same domain
|
||||
# Created: 2025-11-16
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: backup_metadata_builder.py - Backup metadata construction
|
||||
# Date: 2025-11-18
|
||||
# =================== AIPass ====================
|
||||
# Name: backup_metadata_builder.py
|
||||
# Description: Backup metadata construction
|
||||
# Version: 1.0.0
|
||||
# Category: handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-18): Extracted from backup_core.py
|
||||
# * Extracted backup_info creation logic
|
||||
# * Handles mode-specific metadata formatting
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Follow seed 3-layer architecture
|
||||
# - Handlers must be independent and transportable
|
||||
# - No cross-handler imports except within same domain
|
||||
# Created: 2025-11-18
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: changelog_handler.py - Backup changelog management
|
||||
# Date: 2025-11-16
|
||||
# =================== AIPass ====================
|
||||
# Name: changelog_handler.py
|
||||
# Description: Backup changelog management
|
||||
# Version: 1.0.0
|
||||
# Category: handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-16): Initial extraction from backup_core.py
|
||||
# * load_changelog() - Load persistent changelog
|
||||
# * save_changelog_entry() - Add new entry
|
||||
# * display_previous_comments() - Show history
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Follow seed 3-layer architecture
|
||||
# - Handlers must be independent and transportable
|
||||
# - No cross-handler imports except within same domain
|
||||
# Created: 2025-11-16
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -35,17 +23,12 @@ Functions:
|
||||
# =============================================
|
||||
|
||||
import json
|
||||
import logging
|
||||
from aipass.prax import logger
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from rich.console import Console
|
||||
|
||||
# Initialize console for output
|
||||
console = Console()
|
||||
# logger imported from aipass.prax
|
||||
|
||||
# =============================================
|
||||
# CHANGELOG OPERATIONS
|
||||
@@ -65,8 +48,7 @@ def load_changelog(changelog_file: Path) -> Dict:
|
||||
with open(changelog_file, 'r', encoding='utf-8', errors='replace') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error loading changelog: {e}[/red]")
|
||||
logger.warning(f"Error loading changelog: {e}")
|
||||
logger.error(f"Error loading changelog: {e}")
|
||||
return {"entries": []}
|
||||
|
||||
|
||||
@@ -98,7 +80,6 @@ def save_changelog_entry(changelog_file: Path, note: str, mode: str,
|
||||
logger.info(f"[changelog_handler] Saved changelog entry: {note[:50]}")
|
||||
return True
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error saving changelog entry: {e}[/red]")
|
||||
logger.error(f"[changelog_handler] Error saving changelog: {e}")
|
||||
return False
|
||||
|
||||
@@ -115,14 +96,10 @@ def display_previous_comments(changelog_file: Path, mode_name: str):
|
||||
entries = changelog.get("entries", [])
|
||||
|
||||
if not entries:
|
||||
console.print(f"[yellow]No previous {mode_name} backup comments found.[/yellow]")
|
||||
logger.warning(f"No previous {mode_name} backup comments found.")
|
||||
return
|
||||
|
||||
from rich.panel import Panel
|
||||
console.print()
|
||||
console.print(Panel(f"PREVIOUS {mode_name.upper()} BACKUP COMMENTS",
|
||||
style="bold cyan",
|
||||
border_style="cyan"))
|
||||
logger.info(f"PREVIOUS {mode_name.upper()} BACKUP COMMENTS")
|
||||
|
||||
# Show last 10 entries (most recent first)
|
||||
recent_entries = entries[-10:]
|
||||
@@ -133,21 +110,20 @@ def display_previous_comments(changelog_file: Path, mode_name: str):
|
||||
mode_info = entry.get('mode', 'unknown')
|
||||
# Handle encoding issues in notes
|
||||
note = str(entry['note']).encode('ascii', errors='replace').decode('ascii')
|
||||
console.print(f"[bright_blue]{i:2d}.[/bright_blue] [{formatted_time}] [green][{mode_info}][/green] {note}")
|
||||
logger.info(f"{i:2d}. [{formatted_time}] [{mode_info}] {note}")
|
||||
except Exception as e:
|
||||
console.print(f"[bright_blue]{i:2d}.[/bright_blue] [red][ERROR] Failed to display entry: {e}[/red]")
|
||||
logger.error(f"{i:2d}. [ERROR] Failed to display entry: {e}")
|
||||
|
||||
if len(entries) > 10:
|
||||
console.print(f"\n[dim]... and {len(entries) - 10} older entries[/dim]")
|
||||
console.print()
|
||||
logger.info(f"... and {len(entries) - 10} older entries")
|
||||
except FileNotFoundError:
|
||||
console.print(f"[yellow]No previous {mode_name} backup comments found.[/yellow]")
|
||||
logger.warning(f"No previous {mode_name} backup comments found.")
|
||||
except PermissionError as e:
|
||||
console.print(f"[yellow]Warning: Cannot read backup history - permission denied: {e}[/yellow]")
|
||||
console.print("[yellow]Continuing with backup...[/yellow]")
|
||||
logger.warning(f"Cannot read backup history - permission denied: {e}")
|
||||
logger.warning("Continuing with backup...")
|
||||
except Exception as e:
|
||||
console.print(f"[yellow]Warning: Error displaying comments: {e}[/yellow]")
|
||||
console.print("[yellow]Continuing with backup...[/yellow]")
|
||||
logger.warning(f"Error displaying comments: {e}")
|
||||
logger.warning("Continuing with backup...")
|
||||
|
||||
|
||||
# =============================================
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: drive_sync_json.py - Google Drive Sync JSON Operations Handler
|
||||
# Date: 2026-02-20
|
||||
# =================== AIPass ====================
|
||||
# Name: drive_sync_json.py
|
||||
# Description: Google Drive Sync JSON Operations Handler
|
||||
# Version: 1.0.0
|
||||
# Category: backup_system/handlers/json
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2026-02-20): Extracted from google_drive_sync module
|
||||
# * Moved JSON I/O functions to handler layer
|
||||
# * Follows seed 3-layer architecture standards
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handlers must be independent and transportable
|
||||
# - No prax imports (handler tier)
|
||||
# - Pure file I/O operations
|
||||
# Created: 2026-02-20
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: json_handler.py - JSON Auto-Creating Handler
|
||||
# Date: 2025-11-22
|
||||
# =================== AIPass ====================
|
||||
# Name: json_handler.py
|
||||
# Description: JSON Auto-Creating Handler
|
||||
# Version: 3.1.0
|
||||
# Category: backup_system/handlers/json
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v3.1.0 (2026-01-31): Refactored to comply with error handling standards
|
||||
# - v3.0.0 (2025-11-22): Updated to match Cortex template with all features
|
||||
# - v2.0.0 (2025-11-16): Migrated to seed 3-layer architecture
|
||||
# - v1.0.0 (2025-10-14): Initial implementation with auto-detection
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Pure functions with proper error raising
|
||||
# - No Prax imports (handler tier 3)
|
||||
# Created: 2025-11-22
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
import json
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: statistics_handler.py - Backup statistics tracking
|
||||
# Date: 2025-11-18
|
||||
# =================== AIPass ====================
|
||||
# Name: statistics_handler.py
|
||||
# Description: Backup statistics tracking
|
||||
# Version: 1.0.0
|
||||
# Category: handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-18): Extracted from backup_core.py
|
||||
# * Extracted update_data_file() statistics tracking
|
||||
# * Handles runtime state and statistics updates
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Follow seed 3-layer architecture
|
||||
# - Handlers must be independent and transportable
|
||||
# - No cross-handler imports except within same domain
|
||||
# Created: 2025-11-18
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -27,11 +16,11 @@ Manages backup operation statistics and runtime state persistence.
|
||||
# IMPORTS
|
||||
# =============================================
|
||||
|
||||
import logging
|
||||
from aipass.prax import logger
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
# logger imported from aipass.prax
|
||||
|
||||
# Import handlers
|
||||
from aipass.backup.apps.handlers.json.json_handler import load_json, save_json
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: backup_models.py - Backup system data models and structures
|
||||
# Date: 2025-11-16
|
||||
# =================== AIPass ====================
|
||||
# Name: backup_models.py
|
||||
# Description: Backup system data models and structures
|
||||
# Version: 2.0.1
|
||||
# Category: handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v2.0.1 (2026-03-06): Adapted for AIPass public repo
|
||||
# * Removed shebang and sys.path manipulation
|
||||
# - v2.0.0 (2025-11-16): Migrated to seed 3-layer architecture
|
||||
# - v1.0.0 (2025-10-14): Initial extraction from backup.py
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Follow seed 3-layer architecture
|
||||
# - Handlers must be independent and transportable
|
||||
# - No cross-handler imports except within same domain
|
||||
# Created: 2025-11-16
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: drive_sync_client.py - Google Drive Sync Client Handler
|
||||
# Date: 2026-02-20
|
||||
# =================== AIPass ====================
|
||||
# Name: drive_sync_client.py
|
||||
# Description: Google Drive Sync Client Handler
|
||||
# Version: 1.0.0
|
||||
# Category: backup_system/handlers/operations
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2026-02-20): Extracted GoogleDriveSync class from module
|
||||
# * Moved implementation to handler layer per seed 3-layer architecture
|
||||
# * Module now delegates to this handler for all Drive operations
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handlers must be independent and transportable
|
||||
# - No prax imports in class methods (handler tier)
|
||||
# - Google API interactions isolated here
|
||||
# Created: 2026-02-20
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: drive_sync_ops.py - Google Drive Sync Business Operations Handler
|
||||
# Date: 2026-02-20
|
||||
# =================== AIPass ====================
|
||||
# Name: drive_sync_ops.py
|
||||
# Description: Google Drive Sync Business Operations Handler
|
||||
# Version: 1.0.0
|
||||
# Category: backup_system/handlers/operations
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2026-02-20): Extracted from google_drive_sync module
|
||||
# * Moved get_status, clear_file_tracker, show_file_tracker_stats,
|
||||
# test_drive_sync to handler layer
|
||||
# * Follows seed 3-layer architecture standards
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handlers must be independent and transportable
|
||||
# - No prax imports (handler tier)
|
||||
# - Delegates to json handler for file operations
|
||||
# Created: 2026-02-20
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,29 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: file_cleanup.py - File deletion operations
|
||||
# Date: 2025-11-23
|
||||
# =================== AIPass ====================
|
||||
# Name: file_cleanup.py
|
||||
# Description: File deletion operations
|
||||
# Version: 1.0.2
|
||||
# Category: handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.2 (2025-11-23): Added dry-run support for cleanup operations
|
||||
# * Added dry_run parameter to cleanup_deleted_files()
|
||||
# * Shows "Would delete" messages instead of actually deleting in dry-run
|
||||
# * All three passes (directories, files, empty dirs) respect dry-run mode
|
||||
# - v1.0.1 (2025-11-23): CRITICAL BUG FIX - respect exceptions in empty dir cleanup
|
||||
# * Third pass now checks should_ignore() before removing empty directories
|
||||
# * Prevents deletion of empty template directories
|
||||
# * Added source_dir mapping to check exceptions properly
|
||||
# * Only removes empty dirs if source doesn't exist OR should be ignored
|
||||
# - v1.0.0 (2025-11-18): Extracted from backup_core.py
|
||||
# * Extracted file deletion logic for dynamic mode
|
||||
# * Handles cleanup of deleted source files
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Follow seed 3-layer architecture
|
||||
# - Handlers must be independent and transportable
|
||||
# - No cross-handler imports except within same domain
|
||||
# Created: 2025-11-23
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -36,11 +16,11 @@ Handles cleanup of backup files when source files are deleted.
|
||||
# IMPORTS
|
||||
# =============================================
|
||||
|
||||
import logging
|
||||
from aipass.prax import logger
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
# logger imported from aipass.prax
|
||||
|
||||
from aipass.backup.apps.handlers.utils.system_utils import temporarily_writable, safe_print
|
||||
from aipass.backup.apps.handlers.models.backup_models import BackupResult
|
||||
|
||||
@@ -1,40 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: file_operations.py - Core backup file operations
|
||||
# Date: 2025-11-23
|
||||
# =================== AIPass ====================
|
||||
# Name: file_operations.py
|
||||
# Description: Core backup file operations
|
||||
# Version: 2.0.4
|
||||
# Category: handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v2.0.4 (2025-11-23): Fix baseline snapshot messages for VS Code clickability
|
||||
# * Changed baseline snapshot message from {baseline_name} to {baseline_path}
|
||||
# * Users can now Ctrl+click on baseline paths to jump directly to files
|
||||
# * Shows full path (e.g., /home/aipass/backups/project/README-baseline-2025-11-23.md)
|
||||
# - v2.0.3 (2025-11-23): CRITICAL performance fix for versioned backup
|
||||
# * Skip copying unchanged files (only copy if mtime differs)
|
||||
# * Fixes 5s -> <1s regression (was copying all 5000+ files every time)
|
||||
# * Added file_changed flag to track copy necessity
|
||||
# * Only copy if is_new_file or file_changed
|
||||
# - v2.0.2 (2025-11-23): Added per-file output to snapshot mode
|
||||
# * Snapshot backup now shows each file as it's copied
|
||||
# * Displays "Copied (new)" or "Copied (updated)" for each file
|
||||
# * Matches the verbosity of versioned backup mode
|
||||
# - v2.0.1 (2025-11-23): Fixed automatic VS Code diff opening
|
||||
# * Removed automatic VS Code integration from copy_versioned_file()
|
||||
# * Diffs are now created silently without opening in editor
|
||||
# * VS Code integration still available via separate command
|
||||
# - v2.0.0 (2025-11-16): Extraction from backup_operations.py
|
||||
# * Extracted core file operations (lines 49-271)
|
||||
# * copy_file_with_structure for snapshot mode
|
||||
# * copy_versioned_file with baseline snapshots and diff generation
|
||||
# * Extensive Linux permission handling with temporarily_writable()
|
||||
# * Retry logic for filesystem errors
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Follow seed 3-layer architecture
|
||||
# - Handlers must be independent and transportable
|
||||
# - No cross-handler imports except within same domain
|
||||
# Created: 2025-11-23
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -52,12 +21,12 @@ Functions:
|
||||
# IMPORTS
|
||||
# =============================================
|
||||
|
||||
import logging
|
||||
from aipass.prax import logger
|
||||
import shutil
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
# logger imported from aipass.prax
|
||||
|
||||
# Import from handlers modules
|
||||
from aipass.backup.apps.handlers.utils.system_utils import temporarily_writable, safe_print
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: file_scanner.py - File system scanning operations
|
||||
# Date: 2025-11-18
|
||||
# =================== AIPass ====================
|
||||
# Name: file_scanner.py
|
||||
# Description: File system scanning operations
|
||||
# Version: 1.0.0
|
||||
# Category: handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-18): Extracted from backup_core.py
|
||||
# * Extracted file scanning logic (os.walk loops)
|
||||
# * Handles file discovery with ignore patterns
|
||||
# * Returns file list with metadata
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Follow seed 3-layer architecture
|
||||
# - Handlers must be independent and transportable
|
||||
# - No cross-handler imports except within same domain
|
||||
# Created: 2025-11-18
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: integration_ops.py - Backup Integration Operations Handler
|
||||
# Date: 2026-02-20
|
||||
# =================== AIPass ====================
|
||||
# Name: integration_ops.py
|
||||
# Description: Backup Integration Operations Handler
|
||||
# Version: 1.0.0
|
||||
# Category: backup_system/handlers/operations
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2026-02-20): Extracted from integrations module
|
||||
# * Moved sync_to_drive() and set_backup_readonly() to handler layer
|
||||
# * Follows seed 3-layer architecture standards
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handlers must be independent and transportable
|
||||
# - No prax imports (handler tier)
|
||||
# - No cross-handler imports except within same domain
|
||||
# Created: 2026-02-20
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: path_builder.py - Backup path construction
|
||||
# Date: 2025-11-18
|
||||
# =================== AIPass ====================
|
||||
# Name: path_builder.py
|
||||
# Description: Backup path construction
|
||||
# Version: 1.0.0
|
||||
# Category: handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2025-11-18): Extracted from backup_core.py
|
||||
# * Extracted path construction logic for versioned mode
|
||||
# * Handles long filename hashing
|
||||
# * Mode-specific path building
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Follow seed 3-layer architecture
|
||||
# - Handlers must be independent and transportable
|
||||
# - No cross-handler imports except within same domain
|
||||
# Created: 2025-11-18
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: sync_test_ops.py
|
||||
# Description: File operations for Drive sync test
|
||||
# Version: 1.0.0
|
||||
# Created: 2026-03-08
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
Sync Test File Operations Handler
|
||||
|
||||
Handles file system operations for the Drive sync test workflow:
|
||||
- Creating test directory and test files
|
||||
- Cleaning up test directory after test completes
|
||||
"""
|
||||
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
from aipass.prax import logger
|
||||
|
||||
|
||||
def create_sync_test_files(backup_root: Path) -> dict:
|
||||
"""Create test directory and test files for Drive sync verification.
|
||||
|
||||
Args:
|
||||
backup_root: Root backup directory (e.g., src/aipass/backup/)
|
||||
|
||||
Returns:
|
||||
dict with keys:
|
||||
- success (bool): Whether files were created
|
||||
- test_dir (Path): Path to test directory
|
||||
- file_count (int): Number of test files created
|
||||
- error (str|None): Error message if failed
|
||||
"""
|
||||
try:
|
||||
test_dir = backup_root / "backups" / "_sync_test"
|
||||
test_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
test_files = {
|
||||
"test_file_1.txt": "Hello from AIPass backup test",
|
||||
"test_file_2.json": '{"test": true, "timestamp": "' + datetime.now().isoformat() + '"}',
|
||||
"subdir/nested_file.txt": "Nested directory test",
|
||||
"subdir/deep/deeper_file.md": "# Deep nested test\nVerifying folder structure",
|
||||
}
|
||||
|
||||
for name, content in test_files.items():
|
||||
path = test_dir / name
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content)
|
||||
|
||||
logger.info(f"[sync_test_ops] Created {len(test_files)} test files in {test_dir}")
|
||||
return {
|
||||
"success": True,
|
||||
"test_dir": test_dir,
|
||||
"file_count": len(test_files),
|
||||
"error": None,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"[sync_test_ops] Failed to create test files: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"test_dir": None,
|
||||
"file_count": 0,
|
||||
"error": str(e),
|
||||
}
|
||||
|
||||
|
||||
def cleanup_sync_test_dir(test_dir: Path) -> dict:
|
||||
"""Remove the sync test directory and all contents.
|
||||
|
||||
Args:
|
||||
test_dir: Path to test directory to remove
|
||||
|
||||
Returns:
|
||||
dict with keys:
|
||||
- success (bool): Whether cleanup succeeded
|
||||
- error (str|None): Error message if failed
|
||||
"""
|
||||
try:
|
||||
shutil.rmtree(test_dir, ignore_errors=True)
|
||||
logger.info(f"[sync_test_ops] Cleaned up test directory: {test_dir}")
|
||||
return {"success": True, "error": None}
|
||||
except Exception as e:
|
||||
logger.error(f"[sync_test_ops] Failed to cleanup test directory: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
@@ -1,25 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: report_formatter.py - Backup result reporting
|
||||
# Date: 2025-11-23
|
||||
# =================== AIPass ====================
|
||||
# Name: report_formatter.py
|
||||
# Description: Backup result reporting
|
||||
# Version: 1.1.0
|
||||
# Category: handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.1.0 (2025-11-23): Added dry-run statistics display
|
||||
# * Added dry_run parameter to display_backup_results()
|
||||
# * Shows "Would copy", "Would skip", "Would delete" in dry-run mode
|
||||
# * Provides clear differentiation between dry-run and actual execution
|
||||
# - v1.0.0 (2025-11-18): Extracted from backup_core.py
|
||||
# * Extracted statistics display formatting
|
||||
# * Handles error/warning display
|
||||
# * Formats skipped items reports
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Follow seed 3-layer architecture
|
||||
# - Handlers must be independent and transportable
|
||||
# - No cross-handler imports except within same domain
|
||||
# Created: 2025-11-23
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -35,16 +19,15 @@ Formats and displays backup operation results, statistics, errors, and warnings.
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from rich.console import Console
|
||||
from aipass.prax import logger
|
||||
from aipass.backup.apps.handlers.models.backup_models import BackupResult
|
||||
|
||||
console = Console()
|
||||
# logger imported from aipass.prax
|
||||
|
||||
def _header(text):
|
||||
console.print(f"\n[bold cyan]{'='*70}[/bold cyan]")
|
||||
console.print(f"[bold cyan] {text}[/bold cyan]")
|
||||
console.print(f"[bold cyan]{'='*70}[/bold cyan]")
|
||||
|
||||
from aipass.backup.apps.handlers.models.backup_models import BackupResult
|
||||
logger.info(f"{'='*70}")
|
||||
logger.info(f" {text}")
|
||||
logger.info(f"{'='*70}")
|
||||
|
||||
# =============================================
|
||||
# REPORT FORMATTING OPERATIONS
|
||||
@@ -66,88 +49,77 @@ def display_backup_results(result: BackupResult, mode_config: dict, backup_path:
|
||||
|
||||
# Determine overall result status
|
||||
if result.critical_errors:
|
||||
status_style = "[bold red]"
|
||||
status_icon = "!"
|
||||
status_text = "FAILED"
|
||||
elif result.errors > 0:
|
||||
status_style = "[bold red]"
|
||||
status_icon = "X"
|
||||
status_text = "COMPLETED WITH ERRORS"
|
||||
elif result.warnings:
|
||||
status_style = "[bold yellow]"
|
||||
status_icon = "!"
|
||||
status_text = "COMPLETED WITH WARNINGS"
|
||||
else:
|
||||
status_style = "[bold green]"
|
||||
status_icon = "OK"
|
||||
status_text = "COMPLETED SUCCESSFULLY"
|
||||
|
||||
# Use _header for main status
|
||||
console.print()
|
||||
_header(f"{status_icon} {mode_config['name'].upper()} {status_text}")
|
||||
console.print()
|
||||
|
||||
# Statistics Section
|
||||
console.print("[bold cyan]STATISTICS:[/bold cyan]")
|
||||
console.print(f" Files checked: [dim]{result.files_checked}[/dim]")
|
||||
logger.info("STATISTICS:")
|
||||
logger.info(f" Files checked: {result.files_checked}")
|
||||
|
||||
if dry_run:
|
||||
# Dry-run mode: show "would be" language
|
||||
console.print(f" Would copy: [bold]{result.files_copied}[/bold] files")
|
||||
console.print(f" Would skip: [dim]{result.files_skipped}[/dim] files (unchanged)")
|
||||
logger.info(f" Would copy: {result.files_copied} files")
|
||||
logger.info(f" Would skip: {result.files_skipped} files (unchanged)")
|
||||
if result.files_deleted > 0:
|
||||
console.print(f" Would delete: [yellow]{result.files_deleted}[/yellow] files")
|
||||
logger.warning(f" Would delete: {result.files_deleted} files")
|
||||
else:
|
||||
# Normal mode: show actual actions
|
||||
console.print(f" Files copied: [bold]{result.files_copied}[/bold]")
|
||||
logger.info(f" Files copied: {result.files_copied}")
|
||||
if result.mode == 'versioned' and result.files_added > 0:
|
||||
console.print(f" Files added: [green]{result.files_added}[/green] (new)")
|
||||
console.print(f" Files skipped: [dim]{result.files_skipped}[/dim]")
|
||||
logger.info(f" Files added: {result.files_added} (new)")
|
||||
logger.info(f" Files skipped: {result.files_skipped}")
|
||||
if result.mode == 'versioned' and result.files_deleted == 0:
|
||||
console.print(f" Files deleted: [dim]{result.files_deleted}[/dim] [dim](cleanup disabled - keeps history)[/dim]")
|
||||
logger.info(f" Files deleted: {result.files_deleted} (cleanup disabled - keeps history)")
|
||||
else:
|
||||
console.print(f" Files deleted: [red]{result.files_deleted}[/red]")
|
||||
logger.info(f" Files deleted: {result.files_deleted}")
|
||||
|
||||
console.print(f" Errors: {status_style}{result.errors}[/]")
|
||||
console.print(f" Warnings: [yellow]{len(result.warnings)}[/yellow]")
|
||||
console.print(f" Duration: [blue]{duration.total_seconds():.2f}s[/blue]")
|
||||
console.print(f" Location: [dim]{backup_path}[/dim]")
|
||||
logger.info(f" Errors: {result.errors}")
|
||||
logger.info(f" Warnings: {len(result.warnings)}")
|
||||
logger.info(f" Duration: {duration.total_seconds():.2f}s")
|
||||
logger.info(f" Location: {backup_path}")
|
||||
|
||||
# Display detailed error information
|
||||
if result.critical_errors:
|
||||
console.print()
|
||||
console.print(f"[bold red]CRITICAL ERRORS ({len(result.critical_errors)}):[/bold red]")
|
||||
console.print("-" * 40)
|
||||
logger.error(f"CRITICAL ERRORS ({len(result.critical_errors)}):")
|
||||
logger.info("-" * 40)
|
||||
for i, error in enumerate(result.critical_errors, 1):
|
||||
console.print(f" {i}. [red]{error}[/red]")
|
||||
console.print()
|
||||
console.print("[bold]RECOVERY SUGGESTIONS:[/bold]")
|
||||
console.print(" - Check disk space and permissions")
|
||||
console.print(" - Ensure backup destination is accessible")
|
||||
console.print(" - Try running as administrator if permission issues")
|
||||
console.print(" - Check if antivirus is blocking file operations")
|
||||
logger.error(f" {i}. {error}")
|
||||
logger.info("RECOVERY SUGGESTIONS:")
|
||||
logger.info(" - Check disk space and permissions")
|
||||
logger.info(" - Ensure backup destination is accessible")
|
||||
logger.info(" - Try running as administrator if permission issues")
|
||||
logger.info(" - Check if antivirus is blocking file operations")
|
||||
|
||||
elif result.error_details:
|
||||
console.print()
|
||||
console.print(f"[bold red]ERRORS ({len(result.error_details)}):[/bold red]")
|
||||
console.print("-" * 40)
|
||||
logger.error(f"ERRORS ({len(result.error_details)}):")
|
||||
logger.info("-" * 40)
|
||||
for i, error in enumerate(result.error_details[:10], 1):
|
||||
console.print(f" {i}. [red]{error}[/red]")
|
||||
logger.error(f" {i}. {error}")
|
||||
if len(result.error_details) > 10:
|
||||
console.print(f" [dim]... and {len(result.error_details) - 10} more errors[/dim]")
|
||||
console.print()
|
||||
console.print("[bold]SUGGESTIONS:[/bold]")
|
||||
console.print(" - Some files may be in use - try closing applications")
|
||||
console.print(" - Check file permissions on failed files")
|
||||
logger.info(f" ... and {len(result.error_details) - 10} more errors")
|
||||
logger.info("SUGGESTIONS:")
|
||||
logger.info(" - Some files may be in use - try closing applications")
|
||||
logger.info(" - Check file permissions on failed files")
|
||||
|
||||
if result.warnings:
|
||||
console.print()
|
||||
console.print(f"[bold yellow]WARNINGS ({len(result.warnings)}):[/bold yellow]")
|
||||
console.print("-" * 40)
|
||||
logger.warning(f"WARNINGS ({len(result.warnings)}):")
|
||||
logger.info("-" * 40)
|
||||
for i, warning in enumerate(result.warnings[:5], 1):
|
||||
console.print(f" {i}. [yellow]{warning}[/yellow]")
|
||||
logger.warning(f" {i}. {warning}")
|
||||
if len(result.warnings) > 5:
|
||||
console.print(f" [dim]... and {len(result.warnings) - 5} more warnings[/dim]")
|
||||
logger.info(f" ... and {len(result.warnings) - 5} more warnings")
|
||||
|
||||
# Display project-specific skipped items
|
||||
tracked_items = filter_tracked_items_func(skipped_items)
|
||||
@@ -155,26 +127,24 @@ def display_backup_results(result: BackupResult, mode_config: dict, backup_path:
|
||||
total_all_skipped = len(skipped_items["directories"]) + len(skipped_items["files"])
|
||||
|
||||
if total_tracked > 0:
|
||||
console.print()
|
||||
console.print(f"[bold cyan]NOTABLE SKIPPED ITEMS ({total_tracked}):[/bold cyan]")
|
||||
console.print(f"[dim]Total ignored: {total_all_skipped}[/dim]")
|
||||
console.print("-" * 50)
|
||||
logger.info(f"NOTABLE SKIPPED ITEMS ({total_tracked}):")
|
||||
logger.info(f"Total ignored: {total_all_skipped}")
|
||||
logger.info("-" * 50)
|
||||
|
||||
if tracked_items["directories"]:
|
||||
console.print(f"[bold]Directories ({len(tracked_items['directories'])}):[/bold]")
|
||||
logger.info(f"Directories ({len(tracked_items['directories'])}):")
|
||||
for i, dir_path in enumerate(sorted(tracked_items["directories"]), 1):
|
||||
console.print(f" {i}. [dim]{dir_path}/[/dim]")
|
||||
logger.info(f" {i}. {dir_path}/")
|
||||
|
||||
if tracked_items["files"]:
|
||||
console.print(f"[bold]Files ({len(tracked_items['files'])}):[/bold]")
|
||||
logger.info(f"Files ({len(tracked_items['files'])}):")
|
||||
for i, file_path in enumerate(sorted(tracked_items["files"]), 1):
|
||||
console.print(f" {i}. [dim]{file_path}[/dim]")
|
||||
logger.info(f" {i}. {file_path}")
|
||||
else:
|
||||
console.print()
|
||||
if total_all_skipped > 0:
|
||||
console.print(f"[dim]No project-specific items skipped ({total_all_skipped} common items filtered)[/dim]")
|
||||
logger.info(f"No project-specific items skipped ({total_all_skipped} common items filtered)")
|
||||
else:
|
||||
console.print("[dim]No items were skipped.[/dim]")
|
||||
logger.info("No items were skipped.")
|
||||
|
||||
|
||||
# =============================================
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: backup_timestamps.py - Tracks last-run timestamps for all backup modes
|
||||
# Date: 2026-02-22
|
||||
# =================== AIPass ====================
|
||||
# Name: backup_timestamps.py
|
||||
# Description: Tracks last-run timestamps for all backup modes
|
||||
# Version: 1.0.0
|
||||
# Category: handlers/utils
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2026-02-22): Initial version - show/update backup timestamps
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler utility: no console output, returns data for module layer
|
||||
# - JSON file at backups/backup_timestamps.json
|
||||
# Created: 2026-02-22
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: reauth_handler.py - Google Drive Re-Authentication Handler
|
||||
# Date: 2026-02-20
|
||||
# =================== AIPass ====================
|
||||
# Name: reauth_handler.py
|
||||
# Description: Google Drive Re-Authentication Handler
|
||||
# Version: 1.0.0
|
||||
# Category: backup_system/handlers/utils
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2026-02-20): Extracted from reauth_drive.py module
|
||||
# * Moved reauth() implementation to handler layer
|
||||
# * Follows seed 3-layer architecture standards
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handlers must be independent and transportable
|
||||
# - No prax imports (handler tier)
|
||||
# - No cross-handler imports except within same domain
|
||||
# Created: 2026-02-20
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,31 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: system_utils.py - Platform-aware file and console utilities
|
||||
# Date: 2025-11-23
|
||||
# =================== AIPass ====================
|
||||
# Name: system_utils.py
|
||||
# Description: Platform-aware file and console utilities
|
||||
# Version: 2.1.1
|
||||
# Category: handlers
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v2.1.1 (2025-11-23): CRITICAL FIX - Switch to plain print() for clickability
|
||||
# * Replaced console.print() with plain print() in safe_print()
|
||||
# * Rich console truncates at 80 chars breaking long paths
|
||||
# * Plain print() preserves full paths for terminal Ctrl+click
|
||||
# - v2.1.0 (2025-11-23): Attempted fix with no_wrap parameter (insufficient)
|
||||
# * Added no_wrap=True to console.print() but Rich still truncates
|
||||
# * Identified Rich console 80-char limit as root cause
|
||||
# - v2.0.0 (2025-11-16): Extracted to seed standards
|
||||
# * Applied seed standards formatting and meta header
|
||||
# * Preserved all logic from backup_utils v1.0.0
|
||||
# - v1.1.0 (2025-11-22): Updated to seed Rich formatting standards
|
||||
# - v1.0.0 (2025-11-18): Extracted from backup_core.py
|
||||
# * Extracted system utilities
|
||||
# * Cross-platform file operations
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Follow seed 3-layer architecture
|
||||
# - Handlers must be independent and transportable
|
||||
# - No cross-handler imports except within same domain
|
||||
# Created: 2025-11-23
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -48,11 +26,11 @@ Key Functions:
|
||||
import sys
|
||||
import os
|
||||
import stat
|
||||
import logging
|
||||
from aipass.prax import logger
|
||||
from pathlib import Path
|
||||
from contextlib import contextmanager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
# logger imported from aipass.prax
|
||||
|
||||
# =============================================
|
||||
# CONSTANTS
|
||||
|
||||
@@ -1,25 +1,9 @@
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: backup_core.py - Main backup system orchestration module
|
||||
# Date: 2025-11-23
|
||||
# =================== AIPass ====================
|
||||
# Name: backup_core.py
|
||||
# Description: Main backup system orchestration module
|
||||
# Version: 2.1.0
|
||||
# Category: backup_system
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v2.1.0 (2026-03-06): Adapted for AIPass public repo
|
||||
# * Removed shebang, sys.path manipulation, prax/cli imports
|
||||
# * Uses standard logging and rich console
|
||||
# * Relative handler imports
|
||||
# * JSON_DIR uses path relative to __file__
|
||||
# - v2.0.4 (2025-11-23): CRITICAL FIX - Versioned dry-run accuracy
|
||||
# - v2.0.3 (2025-11-23): Enhanced dry-run with accurate statistics
|
||||
# - v2.0.2 (2025-11-23): CRITICAL FIX - Dry-run now shows proposed changes
|
||||
# - v2.0.1 (2025-11-23): CRITICAL BUG FIX - Dry-run statistics counter
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Follow seed 3-layer architecture
|
||||
# - Orchestrate workflows, delegate to handlers
|
||||
# - Import handlers, never implement business logic
|
||||
# Created: 2025-11-23
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -48,14 +32,11 @@ Architecture Pattern:
|
||||
# Infrastructure
|
||||
import sys
|
||||
import datetime
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Dict
|
||||
|
||||
from rich.console import Console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
console = Console()
|
||||
from aipass.cli.apps.modules import console
|
||||
from aipass.prax import logger
|
||||
|
||||
|
||||
def _header(text):
|
||||
|
||||
@@ -1,24 +1,9 @@
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: google_drive_sync.py - Google Drive Integration for AIPass Backup System
|
||||
# Date: 2025-10-30
|
||||
# =================== AIPass ====================
|
||||
# Name: google_drive_sync.py
|
||||
# Description: Google Drive Integration for AIPass Backup System
|
||||
# Version: 2.6.0
|
||||
# Category: backup_system/modules
|
||||
#
|
||||
# CHANGELOG (Max 5 entries - remove oldest when adding new):
|
||||
# - v2.6.0 (2026-03-06): Adapted for AIPass public repo
|
||||
# * Removed shebang, sys.path manipulation, prax/cli imports
|
||||
# * Uses standard logging and rich console
|
||||
# * Relative handler imports, Google API deps wrapped in try/except
|
||||
# - v2.5.0 (2026-02-22): Rich progress bar for drive-sync uploads, per-file progress callback
|
||||
# - v2.4.0 (2026-02-21): Two-phase sync (prepare+upload), CLI visibility, dedup fix, --test mode
|
||||
# - v2.3.2 (2026-02-10): Fixed deepcopy RuntimeError in _save_data during concurrent uploads
|
||||
# - v2.2.0 (2026-02-10): Fixed SSL concurrency bug - per-thread credentials, retry with backoff, folder cache lock
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Module orchestrates, handlers implement
|
||||
# - CLI display (console.print) belongs here, not in handlers
|
||||
# - Google API calls delegated to drive_sync_client handler
|
||||
# Created: 2025-10-30
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -33,15 +18,10 @@ drive_sync_client handler for API operations.
|
||||
# =============================================
|
||||
|
||||
import sys
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
from rich.console import Console
|
||||
from rich.progress import Progress, BarColumn, TextColumn, TimeElapsedColumn, TimeRemainingColumn
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
console = Console()
|
||||
from aipass.cli.apps.modules import console
|
||||
from aipass.prax import logger
|
||||
|
||||
_BACKUP_ROOT = Path(__file__).resolve().parents[2] # src/aipass/backup/
|
||||
|
||||
@@ -103,6 +83,11 @@ except ImportError:
|
||||
get_file_tracker_stats = None # type: ignore
|
||||
_test_drive_connection = None # type: ignore
|
||||
|
||||
from aipass.backup.apps.handlers.operations.sync_test_ops import (
|
||||
create_sync_test_files,
|
||||
cleanup_sync_test_dir,
|
||||
)
|
||||
|
||||
|
||||
def _show_file_tracker_stats() -> bool:
|
||||
"""Display file tracker statistics."""
|
||||
@@ -163,34 +148,23 @@ def _test_drive_sync() -> bool:
|
||||
|
||||
def _run_sync_test() -> bool:
|
||||
"""Run a small test sync to verify Drive integration."""
|
||||
import shutil
|
||||
|
||||
console.print("[bold cyan]Drive Sync Test[/bold cyan]")
|
||||
console.print()
|
||||
|
||||
# Create test files
|
||||
test_dir = _BACKUP_ROOT / "backups" / "_sync_test"
|
||||
test_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Create test files via handler
|
||||
setup = create_sync_test_files(_BACKUP_ROOT)
|
||||
if not setup["success"]:
|
||||
console.print(f"[red]Failed to create test files: {setup['error']}[/red]")
|
||||
return False
|
||||
|
||||
test_files = {
|
||||
"test_file_1.txt": "Hello from AIPass backup test",
|
||||
"test_file_2.json": '{"test": true, "timestamp": "' + datetime.now().isoformat() + '"}',
|
||||
"subdir/nested_file.txt": "Nested directory test",
|
||||
"subdir/deep/deeper_file.md": "# Deep nested test\nVerifying folder structure",
|
||||
}
|
||||
|
||||
for name, content in test_files.items():
|
||||
path = test_dir / name
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content)
|
||||
|
||||
console.print(f" Created {len(test_files)} test files in {test_dir}")
|
||||
test_dir = setup["test_dir"]
|
||||
console.print(f" Created {setup['file_count']} test files in {test_dir}")
|
||||
|
||||
# Run sync
|
||||
sync = GoogleDriveSync()
|
||||
if not sync.authenticate():
|
||||
console.print("[red]Auth failed[/red]")
|
||||
shutil.rmtree(test_dir, ignore_errors=True)
|
||||
cleanup_sync_test_dir(test_dir)
|
||||
return False
|
||||
|
||||
console.print(f"\nScanning...")
|
||||
@@ -205,7 +179,7 @@ def _run_sync_test() -> bool:
|
||||
if not folder_id:
|
||||
error_msg = sync.last_error or "Unknown error"
|
||||
console.print(f"[red]FAILED: {error_msg}[/red]")
|
||||
shutil.rmtree(test_dir, ignore_errors=True)
|
||||
cleanup_sync_test_dir(test_dir)
|
||||
return False
|
||||
console.print(f" Drive folder ready: AIPass_Test")
|
||||
|
||||
@@ -225,7 +199,7 @@ def _run_sync_test() -> bool:
|
||||
|
||||
if result.get("error"):
|
||||
console.print(f"\n[red]FAILED: {result['error']}[/red]")
|
||||
shutil.rmtree(test_dir, ignore_errors=True)
|
||||
cleanup_sync_test_dir(test_dir)
|
||||
return False
|
||||
|
||||
console.print()
|
||||
@@ -243,8 +217,8 @@ def _run_sync_test() -> bool:
|
||||
else:
|
||||
console.print(f"[red]Dedup check failed: {len(files_to_upload2)} files flagged for re-upload[/red]")
|
||||
|
||||
# Cleanup local test dir
|
||||
shutil.rmtree(test_dir, ignore_errors=True)
|
||||
# Cleanup local test dir via handler
|
||||
cleanup_sync_test_dir(test_dir)
|
||||
console.print(f"\nCleaned up local test files")
|
||||
console.print(f"[dim]Test Drive folder 'AIPass_Test' left on Drive for inspection[/dim]")
|
||||
|
||||
@@ -423,6 +397,28 @@ def handle_command(args) -> bool:
|
||||
# CLI/EXECUTION
|
||||
# =============================================
|
||||
|
||||
def print_introspection():
|
||||
"""Display module introspection info."""
|
||||
console.print()
|
||||
console.print("google_drive_sync Module")
|
||||
console.print("Orchestrates Google Drive backup sync with two-phase upload pipeline")
|
||||
console.print()
|
||||
console.print("Connected Handlers:")
|
||||
console.print(" handlers/operations/")
|
||||
console.print(" - drive_sync_client.py (GoogleDriveSync — Drive API client and upload engine)")
|
||||
console.print(" - drive_sync_ops.py (clear_file_tracker — reset sync tracker cache)")
|
||||
console.print(" - drive_sync_ops.py (get_file_tracker_stats — tracker statistics)")
|
||||
console.print(" - drive_sync_ops.py (test_drive_connection — connectivity verification)")
|
||||
console.print(" - sync_test_ops.py (create_sync_test_files — generate test fixtures)")
|
||||
console.print(" - sync_test_ops.py (cleanup_sync_test_dir — remove test fixtures)")
|
||||
console.print(" handlers/json/")
|
||||
console.print(" - drive_sync_json.py (load_config — read module config JSON)")
|
||||
console.print(" - drive_sync_json.py (load_data — read module data JSON)")
|
||||
console.print(" handlers/utils/")
|
||||
console.print(" - backup_timestamps.py (get_timestamps, update_timestamp, format_age — backup timing)")
|
||||
console.print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
@@ -550,4 +546,4 @@ EXAMPLES:
|
||||
sys.exit(0)
|
||||
else:
|
||||
console.print("Google Drive sync test failed")
|
||||
sys.exit(1)
|
||||
sys.exit(1)
|
||||
|
||||
@@ -1,24 +1,9 @@
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: integrations.py - External integrations and backup protection
|
||||
# Date: 2025-11-29
|
||||
# =================== AIPass ====================
|
||||
# Name: integrations.py
|
||||
# Description: External integrations and backup protection
|
||||
# Version: 2.1.0
|
||||
# Category: backup_system
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v2.1.0 (2026-03-06): Adapted for AIPass public repo
|
||||
# * Removed shebang, sys.path manipulation, prax/cli imports
|
||||
# * Uses standard logging and rich console
|
||||
# * Relative handler imports, GoogleDriveSync via sibling module
|
||||
# - v2.0.1 (2025-11-29): UX FIX - Import error and missing help output
|
||||
# - v2.0.0 (2025-11-16): Created seed-compliant layout module
|
||||
# - v1.1.0 (2025-10-30): Original backup_integrations features
|
||||
# - v1.0.0 (2025-10-14): Initial extraction
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Follow seed 3-layer architecture
|
||||
# - Orchestrate workflows, delegate to handlers
|
||||
# - Import handlers, never implement business logic
|
||||
# Created: 2025-11-29
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -45,13 +30,10 @@ Architecture Pattern:
|
||||
import sys
|
||||
import os
|
||||
import stat
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from rich.console import Console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
console = Console()
|
||||
from aipass.cli.apps.modules import console
|
||||
from aipass.prax import logger
|
||||
|
||||
|
||||
def _header(text):
|
||||
@@ -278,6 +260,21 @@ def print_help():
|
||||
safe_print("="*70)
|
||||
safe_print("")
|
||||
|
||||
def print_introspection():
|
||||
"""Display module introspection info."""
|
||||
console.print()
|
||||
console.print("integrations Module")
|
||||
console.print("External integrations and backup protection (Drive sync, read-only)")
|
||||
console.print()
|
||||
console.print("Connected Handlers:")
|
||||
console.print(" handlers/utils/")
|
||||
console.print(" - system_utils.py (safe_print — terminal-safe output wrapper)")
|
||||
console.print(" handlers/operations/")
|
||||
console.print(" - integration_ops.py (sync_to_drive — Drive upload orchestration)")
|
||||
console.print(" - integration_ops.py (set_backup_readonly — read-only permission setter)")
|
||||
console.print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
"""Display help when module is run directly."""
|
||||
import sys as _sys
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: reauth_drive.py - Google Drive Re-Authentication Module
|
||||
# Date: 2026-02-21
|
||||
# =================== AIPass ====================
|
||||
# Name: reauth_drive.py
|
||||
# Description: Google Drive Re-Authentication Module
|
||||
# Version: 1.2.0
|
||||
# Category: backup_system
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.2.0 (2026-03-06): Adapted for AIPass public repo
|
||||
# * Removed shebang, sys.path manipulation, prax/cli imports
|
||||
# * Uses standard logging and rich console
|
||||
# * Relative handler imports, Google API deps wrapped in try/except
|
||||
# - v1.1.0 (2026-02-21): Fixed handle_command() signature (command, args) → (args)
|
||||
# - v1.0.0 (2026-02-20): Initial version - standalone reauth utility
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handlers implement logic, modules orchestrate
|
||||
# Created: 2026-02-21
|
||||
# Modified: 2026-03-09
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -26,13 +15,10 @@ Delegates implementation to reauth_handler.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from rich.console import Console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
console = Console()
|
||||
from aipass.cli.apps.modules import console
|
||||
from aipass.prax import logger
|
||||
|
||||
# Handler imports
|
||||
from aipass.backup.apps.handlers.utils.reauth_handler import reauth as _run_reauth
|
||||
@@ -139,6 +125,18 @@ def _execute_reauth() -> bool:
|
||||
return success
|
||||
|
||||
|
||||
def print_introspection():
|
||||
"""Display module introspection info."""
|
||||
console.print()
|
||||
console.print("reauth_drive Module")
|
||||
console.print("Google Drive re-authentication via console OAuth flow")
|
||||
console.print()
|
||||
console.print("Connected Handlers:")
|
||||
console.print(" handlers/utils/")
|
||||
console.print(" - reauth_handler.py (reauth — OAuth credential refresh and re-auth)")
|
||||
console.print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1 and sys.argv[1] in ['--help', '-h', 'help']:
|
||||
print_help()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# CLI
|
||||
|
||||
Display and output formatting service for AIPass modules. Provides consistent terminal output — headers, success/error/warning messages, section breaks, and operation templates — so every module looks the same without duplicating Rich formatting code.
|
||||
**Purpose:** Display and output formatting service for AIPass modules. Provides consistent terminal output — headers, success/error/warning messages, section breaks, and operation templates — so every module looks the same without duplicating Rich formatting code.
|
||||
**Module:** `aipass.cli`
|
||||
**Last Updated:** 2026-03-08
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -48,3 +50,29 @@ cli/
|
||||
|
||||
- `apps/modules/` — Public API. Import from here.
|
||||
- `apps/handlers/` — Internal implementation. Don't import directly.
|
||||
|
||||
## Commands / Usage
|
||||
|
||||
```bash
|
||||
drone @cli --help # Show services and Rich formatting showcase
|
||||
drone @cli --version # Show version
|
||||
drone @cli help # Same as --help
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Depends On
|
||||
- `aipass.prax` — Logging via `system_logger`
|
||||
- `rich` — Rich library for terminal formatting (Table, Panel, Columns, Text)
|
||||
- Python stdlib (`sys`, `importlib`, `pathlib`)
|
||||
|
||||
### Provides To
|
||||
- All modules — display formatting (headers, success/error/warning, section breaks)
|
||||
- All modules — operation templates (`operation_start`, `operation_complete`)
|
||||
- All modules — Rich console access
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2026-03-08*
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: cli.py - CLI Branch Entry Point [SHOWROOM]
|
||||
# Date: 2025-11-12
|
||||
# Version: 0.2.0
|
||||
# Category: cli
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v0.2.0 (2025-11-15): Added print_introspection() - the CLI showroom
|
||||
# - v0.1.0 (2025-11-12): Initial structure - service provider for display/output
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - SHOWROOM - demonstrates CLI capabilities and architecture
|
||||
# =================== AIPass ====================
|
||||
# Name: cli.py
|
||||
# Description: Entry point CLI for drone @cli — display service and Rich formatting
|
||||
# Version: 1.0.0
|
||||
# Created: 2026-03-08
|
||||
# Modified: 2026-03-08
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: json_handler.py - JSON Auto-Creating Handler
|
||||
# Date: 2025-11-21
|
||||
# =================== AIPass ====================
|
||||
# Name: json_handler.py
|
||||
# Description: JSON auto-creating handler — manages CLI JSON files with templates and rotation
|
||||
# Version: 1.1.0
|
||||
# Category: cli/handlers/json
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.1.0 (2025-11-21): Refactored to comply with error handling
|
||||
# - v1.0.0 (2025-11-13): Initial JSON auto-creation system
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Pure functions with proper error raising
|
||||
# - No Prax imports (handler tier 3)
|
||||
# Created: 2025-11-13
|
||||
# Modified: 2025-11-21
|
||||
# =============================================
|
||||
|
||||
"""JSON Auto-Creating Handler - manages CLI JSON files with templates and auto-rotation."""
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: display.py - CLI Display Module
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: display.py
|
||||
# Description: CLI Display Module — public API for Rich terminal output formatting
|
||||
# Version: 0.4.0
|
||||
# Category: cli/modules
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v0.4.0 (2025-11-15): Replaced argparse help with Rich formatted help (SEED pattern)
|
||||
# - v0.3.0 (2025-11-15): Restructured to follow SEED module pattern
|
||||
# - v0.2.0 (2025-11-12): Implemented Rich library formatting
|
||||
# - v0.1.0 (2025-11-12): Public API for display functions
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - PUBLIC API - thin wrapper over handler implementation
|
||||
# - Follows SEED module pattern (introspection/help/command handling)
|
||||
# Created: 2025-11-12
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -40,6 +29,9 @@ from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
from rich.columns import Columns
|
||||
|
||||
# NOTE: Cannot import prax here — circular import (prax depends on cli)
|
||||
# from aipass.prax import logger
|
||||
|
||||
# Initialize Rich console (lowercase follows service instance pattern)
|
||||
CONSOLE = Console() # Internal constant
|
||||
console = CONSOLE # Primary export (lowercase service instance pattern)
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: templates.py - CLI Templates Module
|
||||
# Date: 2025-11-15
|
||||
# =================== AIPass ====================
|
||||
# Name: templates.py
|
||||
# Description: CLI Templates Module — reusable operation output patterns
|
||||
# Version: 0.3.0
|
||||
# Category: cli/modules
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v0.3.0 (2025-11-15): Restructured to follow SEED module pattern
|
||||
# - v0.2.0 (2025-11-12): Implemented Rich library formatting
|
||||
# - v0.1.0 (2025-11-12): Public API for output templates
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - PUBLIC API - reusable output patterns
|
||||
# - Follows SEED module pattern (introspection/help/command handling)
|
||||
# Created: 2025-11-12
|
||||
# Modified: 2025-11-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -32,6 +22,8 @@ from typing import Dict, Any, Optional, List
|
||||
|
||||
# Import console from CLI display module (using our own service!)
|
||||
from aipass.cli.apps.modules.display import console as CONSOLE
|
||||
# NOTE: Cannot import prax here — circular import (prax depends on cli)
|
||||
# from aipass.prax import logger
|
||||
|
||||
|
||||
# ============================================================================
|
||||
|
||||
+107
-3
@@ -1,15 +1,115 @@
|
||||
# DAEMON
|
||||
|
||||
**Purpose:** Cron-triggered task scheduler with plugin system
|
||||
**Purpose:** Cron-triggered task scheduler with plugin system. Routes commands to modules for scheduled tasks, activity reports, action management, and status digests.
|
||||
**Module:** `aipass.daemon`
|
||||
**Created:** 2026-03-07
|
||||
**Citizen Class:** birthright
|
||||
**Citizen Class:** builder
|
||||
**Last Updated:** 2026-03-08
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Birthright citizen — minimal presence with identity and memory.
|
||||
Builder citizen -- full 3-layer architecture with identity and memory. DAEMON serves as the background orchestration branch: it discovers modules at startup, routes CLI commands to them, and provides introspection and help output via Rich console.
|
||||
|
||||
### What I Do
|
||||
- Route CLI commands to discovered modules (update, schedule, activity_report, actions)
|
||||
- Provide scheduled task management and follow-ups
|
||||
- Generate activity reports across branches
|
||||
- Manage an action registry with toggle, info, reminders, and schedules
|
||||
- Produce status digest updates
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
daemon/
|
||||
├── __init__.py
|
||||
├── README.md
|
||||
├── DASHBOARD.local.json
|
||||
├── apps/
|
||||
│ ├── daemon.py # Entry point (CLI) — module discovery + command routing
|
||||
│ ├── daemon_wakeup.py # Wakeup / cron trigger
|
||||
│ ├── scheduler_cron.py # Cron scheduler
|
||||
│ ├── modules/
|
||||
│ │ ├── update.py # Status digest module — summarizes DAEMON activity
|
||||
│ │ ├── schedule.py # Scheduled follow-ups — fire-and-forget task management
|
||||
│ │ ├── activity_report.py # Branch activity report generator
|
||||
│ │ └── actions.py # Action registry CLI — list, toggle, info, reminders
|
||||
│ ├── handlers/
|
||||
│ │ ├── actions/
|
||||
│ │ │ └── actions_registry.py # Action registry implementation
|
||||
│ │ ├── json/
|
||||
│ │ │ └── json_handler.py # JSON data operations
|
||||
│ │ ├── monitoring/
|
||||
│ │ │ ├── activity_collector.py # Collects branch activity data
|
||||
│ │ │ ├── memory_health.py # Memory health checks
|
||||
│ │ │ └── red_flag_detector.py # Detects anomalies / red flags
|
||||
│ │ ├── schedule/
|
||||
│ │ │ ├── task_registry.py # Task registry for scheduled items
|
||||
│ │ │ ├── assistant_notifier.py # Assistant notification dispatch
|
||||
│ │ │ └── telegram_notifier.py # Telegram notification dispatch
|
||||
│ │ ├── telegram/
|
||||
│ │ │ └── assistant_chat.py # Telegram assistant chat handler
|
||||
│ │ └── update/
|
||||
│ │ └── data_loader.py # Data loading for status digests
|
||||
│ ├── extensions/ # Extension point for additional capabilities
|
||||
│ ├── json_templates/ # JSON template definitions
|
||||
│ └── plugins/
|
||||
│ ├── botfather_reminder.py # BotFather reminder plugin
|
||||
│ ├── community_rotation.py # Community rotation plugin
|
||||
│ ├── daily_audit.py # Daily audit plugin
|
||||
│ ├── dev_central_monitor.py # Dev-Central monitor plugin
|
||||
│ └── heartbeat.py # Heartbeat / liveness plugin
|
||||
├── daemon_json/ # JSON tracking data
|
||||
├── docs/ # Documentation
|
||||
├── tools/ # Branch verification utilities
|
||||
└── tests/ # Test suite
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Commands / Usage
|
||||
|
||||
```bash
|
||||
drone @daemon # Show discovered modules (introspection)
|
||||
drone @daemon --help # Rich-formatted help with all commands
|
||||
drone @daemon --version # Print version
|
||||
|
||||
drone @daemon update [args...] # Status digest — summarize DAEMON activity
|
||||
drone @daemon schedule [args...] # Manage scheduled follow-ups and tasks
|
||||
drone @daemon activity_report [args...] # Generate branch activity reports
|
||||
drone @daemon actions [args...] # Action registry — list, toggle, info, set reminder/schedule
|
||||
```
|
||||
|
||||
Each module accepts `--help` for module-specific usage:
|
||||
```bash
|
||||
drone @daemon <command> --help
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Description |
|
||||
|--------|-------------|
|
||||
| `update` | Status digest of DAEMON activity |
|
||||
| `schedule` | Fire-and-forget scheduled follow-ups and task management |
|
||||
| `activity_report` | Branch activity report generator (plain text output) |
|
||||
| `actions` | Action registry CLI -- list, toggle, info, set reminder, set schedule, plugin migration |
|
||||
|
||||
---
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Depends On
|
||||
- `rich` -- Console output and formatted display
|
||||
- Python stdlib (`sys`, `typing`, `logging`)
|
||||
|
||||
### Provides To
|
||||
- All modules -- background task scheduling, activity monitoring, action tracking
|
||||
- Plugins -- extensible plugin system for recurring tasks (heartbeat, daily audit, community rotation, etc.)
|
||||
|
||||
---
|
||||
|
||||
@@ -19,3 +119,7 @@ Birthright citizen — minimal presence with identity and memory.
|
||||
- **Session History:** `.trinity/local.json`
|
||||
- **Observations:** `.trinity/observations.json`
|
||||
- **Branch Prompt:** `.aipass/branch_system_prompt.md`
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2026-03-08*
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: daemon.py - DAEMON Branch Entry Point
|
||||
# Date: 2026-01-21
|
||||
# =================== AIPass ====================
|
||||
# Name: daemon.py
|
||||
# Description: Entry point CLI for drone @daemon
|
||||
# Version: 1.0.0
|
||||
# Category: daemon
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2026-01-21): Initial branch creation
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handlers implement logic, modules orchestrate
|
||||
# Created: 2026-03-08
|
||||
# Modified: 2026-03-08
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -26,12 +19,10 @@ import sys
|
||||
from typing import List, Any
|
||||
|
||||
# Logger
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||
|
||||
# Console
|
||||
from rich.console import Console
|
||||
console = Console()
|
||||
from aipass.cli.apps.modules import console
|
||||
|
||||
def _header(text):
|
||||
console.print(f"\n[bold cyan]{'='*70}[/bold cyan]")
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: daemon_wakeup.py - DAEMON Wake-Up Cron Trigger
|
||||
# Date: 2026-02-15
|
||||
# =================== AIPass ====================
|
||||
# Name: daemon_wakeup.py
|
||||
# Description: DAEMON Wake-Up Cron Trigger
|
||||
# Version: 1.0.0
|
||||
# Category: daemon/apps
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2026-02-15): Initial implementation - cron-triggered wake-up checker
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handlers implement logic, modules orchestrate
|
||||
# - No Rich console (headless cron execution)
|
||||
# - Stdout logging (cron redirects to logs/daemon_wakeup.log)
|
||||
# - stdlib only + optional imports
|
||||
# Created: 2026-02-15
|
||||
# Modified: 2026-02-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -40,21 +30,23 @@ import time
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
from aipass.prax import logger
|
||||
# logger imported from aipass.prax
|
||||
|
||||
from aipass.cli.apps.modules import console
|
||||
|
||||
# =============================================
|
||||
# OPTIONAL IMPORTS
|
||||
# OPTIONAL IMPORTS (via module layer)
|
||||
# =============================================
|
||||
|
||||
# Telegram notifications (optional) — use absolute imports for standalone script
|
||||
# Telegram notifications (optional) — route through modules, not handlers directly
|
||||
try:
|
||||
from aipass.daemon.apps.handlers.schedule.assistant_notifier import (
|
||||
from aipass.daemon.apps.modules.wakeup_ops import (
|
||||
notify_wakeup,
|
||||
notify_report,
|
||||
notify_error,
|
||||
TELEGRAM_AVAILABLE,
|
||||
)
|
||||
TELEGRAM_AVAILABLE = True
|
||||
except ImportError:
|
||||
TELEGRAM_AVAILABLE = False
|
||||
notify_wakeup = None
|
||||
@@ -76,10 +68,34 @@ INBOX_PATH = _DAEMON_ROOT / "ai_mail.local" / "inbox.json"
|
||||
# LOGGING
|
||||
# =============================================
|
||||
|
||||
def print_introspection():
|
||||
"""Display module introspection info."""
|
||||
console.print()
|
||||
console.print("daemon_wakeup Module")
|
||||
console.print("Cron trigger for daemon wake-up inbox checking and reporting")
|
||||
console.print()
|
||||
console.print("Connected Handlers:")
|
||||
console.print(" modules/")
|
||||
console.print(" - wakeup_ops.py (notify_wakeup, notify_report, notify_error — daemon bot Telegram notifications)")
|
||||
console.print()
|
||||
|
||||
|
||||
def print_help() -> None:
|
||||
"""Display usage information for daemon_wakeup."""
|
||||
console.print("\n[bold cyan]daemon_wakeup.py - DAEMON Wake-Up Cron Trigger[/bold cyan]")
|
||||
console.print("\n[yellow]USAGE:[/yellow]")
|
||||
console.print(" python daemon_wakeup.py Run the wake-up checker")
|
||||
console.print(" python daemon_wakeup.py --help Show this help message")
|
||||
console.print("\n[yellow]DESCRIPTION:[/yellow]")
|
||||
console.print(" Checks daemon's email inbox and sends summary reports.")
|
||||
console.print(" Intended to be called periodically by cron.")
|
||||
console.print()
|
||||
|
||||
|
||||
def log(message: str) -> None:
|
||||
"""Print timestamped log line to stdout (captured by cron redirect)."""
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
print(f"[{timestamp}] {message}", flush=True)
|
||||
console.print(f"[{timestamp}] {message}")
|
||||
|
||||
|
||||
# =============================================
|
||||
@@ -211,6 +227,16 @@ def main() -> int:
|
||||
Returns:
|
||||
0 on success, 1 on error
|
||||
"""
|
||||
args = sys.argv[1:]
|
||||
|
||||
if not args:
|
||||
print_introspection()
|
||||
return 0
|
||||
|
||||
if args[0] in ['--help', '-h']:
|
||||
print_help()
|
||||
sys.exit(0)
|
||||
|
||||
log("=" * 60)
|
||||
log("Daemon wake-up triggered")
|
||||
|
||||
@@ -287,7 +313,7 @@ if __name__ == "__main__":
|
||||
except Exception as e:
|
||||
# Last-resort catch -- never crash silently
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
print(f"[{timestamp}] FATAL: Unhandled exception: {e}", flush=True)
|
||||
console.print(f"[{timestamp}] FATAL: Unhandled exception: {e}")
|
||||
if TELEGRAM_AVAILABLE:
|
||||
try:
|
||||
notify_error(f"FATAL: {e}")
|
||||
|
||||
@@ -1,22 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: actions_registry.py - Numbered Action Registry
|
||||
# Date: 2026-03-02
|
||||
# =================== AIPass ====================
|
||||
# Name: actions_registry.py
|
||||
# Description: Numbered Action Registry
|
||||
# Version: 1.0.0
|
||||
# Category: daemon/handlers/actions
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.0.0 (2026-03-02): Initial creation - DPLAN-043
|
||||
# * Sequential 4-digit IDs (0001-9999)
|
||||
# * JSON storage with CRUD operations
|
||||
# * Plugin auto-migration from PLUGIN_CONFIG
|
||||
# * Due-checking logic for all schedule types
|
||||
# * Supports: plugin, schedule, reminder action types
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Handler independence: pure business logic
|
||||
# - No Rich console (headless compatible)
|
||||
# Created: 2026-03-02
|
||||
# Modified: 2026-03-02
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -35,12 +22,12 @@ Action types:
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from aipass.prax import logger
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
# logger imported from aipass.prax
|
||||
|
||||
# Paths
|
||||
_DAEMON_ROOT = Path(__file__).resolve().parents[3] # src/aipass/daemon/
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
|
||||
# ===================AIPASS====================
|
||||
# META DATA HEADER
|
||||
# Name: json_handler.py - JSON Auto-Creating Handler
|
||||
# Date: 2025-11-21
|
||||
# =================== AIPass ====================
|
||||
# Name: json_handler.py
|
||||
# Description: JSON Auto-Creating Handler
|
||||
# Version: 1.2.0
|
||||
# Category: daemon/handlers/json
|
||||
#
|
||||
# CHANGELOG (Max 5 entries):
|
||||
# - v1.2.0 (2026-01-29): Fixed paths to point to DAEMON branch
|
||||
# - v1.1.0 (2025-11-21): Refactored to comply with error handling
|
||||
# - v1.0.0 (2025-11-13): Initial JSON auto-creation system
|
||||
#
|
||||
# CODE STANDARDS:
|
||||
# - Pure functions with proper error raising
|
||||
# - No Prax imports (handler tier 3)
|
||||
# Created: 2025-11-21
|
||||
# Modified: 2026-01-29
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user