Files
ajarbot/google_tools/people_client.py
Jordan Ramos 7697220c74 Refactor: Remove zombie code, fix bugs, and clean documentation
This comprehensive refactoring removes dead code, fixes bugs, and deletes
outdated documentation to make the codebase production-ready.

## Files Deleted (16 files)

### Temporary/zombie files (9 files):
- nul (Windows artifact)
- quick_start.bat (superseded by run.bat)
- scripts/proxmox_ssh.py (hardcoded credentials - security risk)
- scripts/proxmox_ssh.sh (hardcoded credentials - security risk)
- scripts/collection_output.txt (one-time audit output)
- scripts/collect-homelab-config.sh (one-off infrastructure script)
- scripts/collect-remote.sh (one-off infrastructure script)
- memory_workspace/MEMORY.md.old (backup file)
- promtail-config-optimized.yaml (misplaced homelab config)

### Outdated documentation (7 files):
- MCP_MIGRATION.md (migration complete - 2026-02-15)
- QUICK_REFERENCE_AGENT_SDK.md (orphaned from cleanup)
- SETUP.md (duplicate of README.md quick start)
- WINDOWS_QUICK_REFERENCE.md (duplicate of docs/WINDOWS_DEPLOYMENT.md)
- SUB_AGENTS.md (design doc for unimplemented feature)
- JARVIS_VOICE_INTEGRATION_PLAN.md (1300-line spec, code not implemented)
- OBSIDIAN_MCP_SETUP_INSTRUCTIONS.md (temporary troubleshooting doc)
- LOGGING.md (redundant with well-commented logging_config.py)
- docs/SECURITY_AUDIT_SUMMARY.md (completed audit from 2026-02-12)

## Critical Bug Fixes (2 bugs)

1. bot_runner.py line 122: Fixed wrong dict key reference
   - Changed send_to_platform → send_to
   - Bug caused scheduled task platform info to never print

2. usage_tracker.py: Added missing pricing for claude-sonnet-4-6
   - Model was default but had no pricing entry
   - Caused cost under-reporting in Direct API mode

## Code Removed (14 files modified, ~1200 lines deleted)

### Dead imports removed (9 imports):
- bot_runner.py: sys
- agent.py: time
- adapters/runtime.py: re
- adapters/skill_integration.py: subprocess
- tools.py: redundant Path import
- mcp_servers/loki/loki_server.py: json
- google_tools/oauth_manager.py: Thread, Dict
- google_tools/gmail_client.py: os
- google_tools/utils.py: email

### Unused functions/methods removed (9 functions):
- agent.py: MEMORY_RESPONSE_PREVIEW_LENGTH constant
- scheduled_tasks.py: integrate_scheduler_with_runtime()
- adapters/runtime.py: command_preprocessor(), markdown_postprocessor()
- adapters/skill_integration.py: invoke_skill_via_cli(), __main__ block
- tools.py: _extract_mcp_result()
- google_tools/oauth_manager.py: needs_refresh_soon(), revoke_authorization()
- google_tools/people_client.py: update_contact(), delete_contact()

### Code quality improvements:
- memory_system.py: Removed empty else: pass branch
- calendar_client.py: Fixed bare except: → except Exception:
- mcp_ssh.py: Updated asyncio.get_event_loop() → get_running_loop()
- calendar_client.py: Fixed deprecated datetime.utcnow() → now(timezone.utc)

## Impact

- ~1200 lines of dead code removed
- 16 obsolete files deleted
- 2 critical bugs fixed
- 3 deprecated APIs updated
- Zero functionality broken (all changes verified)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-24 12:46:56 -07:00

233 lines
7.2 KiB
Python

"""Google People API client for managing contacts."""
from typing import Any, Dict, List, Optional
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from .oauth_manager import GoogleOAuthManager
PERSON_FIELDS = "names,emailAddresses,phoneNumbers,biographies"
class PeopleClient:
"""Client for Google People API operations."""
def __init__(self, oauth_manager: GoogleOAuthManager):
"""Initialize People client.
Args:
oauth_manager: Initialized OAuth manager with valid credentials
"""
self.oauth_manager = oauth_manager
self.service = None
self._initialize_service()
def _initialize_service(self) -> bool:
"""Initialize People API service.
Returns:
True if initialized successfully, False otherwise
"""
credentials = self.oauth_manager.get_credentials()
if not credentials:
return False
try:
self.service = build("people", "v1", credentials=credentials)
return True
except Exception as e:
print(f"[People] Failed to initialize service: {e}")
return False
def _ensure_service(self) -> bool:
"""Ensure the service is initialized."""
if not self.service:
return self._initialize_service()
return True
def create_contact(
self,
given_name: str,
family_name: str = "",
email: str = "",
phone: Optional[str] = None,
notes: Optional[str] = None,
) -> Dict:
"""Create a new contact.
Args:
given_name: First name
family_name: Last name
email: Email address
phone: Optional phone number
notes: Optional notes/biography
Returns:
Dict with success status and contact details or error
"""
if not self._ensure_service():
return {
"success": False,
"error": "Not authorized. Run: python bot_runner.py --setup-google",
}
try:
body: Dict[str, Any] = {
"names": [{"givenName": given_name, "familyName": family_name}],
}
if email:
body["emailAddresses"] = [{"value": email}]
if phone:
body["phoneNumbers"] = [{"value": phone}]
if notes:
body["biographies"] = [{"value": notes, "contentType": "TEXT_PLAIN"}]
result = (
self.service.people()
.createContact(body=body)
.execute()
)
return {
"success": True,
"resource_name": result.get("resourceName", ""),
"name": f"{given_name} {family_name}".strip(),
}
except HttpError as e:
return {"success": False, "error": str(e)}
def list_contacts(
self,
max_results: int = 100,
query: Optional[str] = None,
) -> Dict:
"""List or search contacts.
Args:
max_results: Maximum number of contacts to return (max: 1000)
query: Optional search query string
Returns:
Dict with success status and contacts or error
"""
if not self._ensure_service():
return {
"success": False,
"error": "Not authorized. Run: python bot_runner.py --setup-google",
}
try:
max_results = min(max_results, 1000)
if query:
# Use searchContacts for query-based search
result = (
self.service.people()
.searchContacts(
query=query,
readMask="names,emailAddresses,phoneNumbers,biographies",
pageSize=min(max_results, 30), # searchContacts max is 30
)
.execute()
)
people = [r["person"] for r in result.get("results", [])]
else:
# Use connections.list for full listing
result = (
self.service.people()
.connections()
.list(
resourceName="people/me",
pageSize=max_results,
personFields=PERSON_FIELDS,
sortOrder="LAST_NAME_ASCENDING",
)
.execute()
)
people = result.get("connections", [])
contacts = [self._format_contact(p) for p in people]
return {
"success": True,
"contacts": contacts,
"count": len(contacts),
"summary": self._format_contacts_summary(contacts),
}
except HttpError as e:
return {"success": False, "error": str(e)}
def get_contact(self, resource_name: str) -> Dict:
"""Get a single contact by resource name.
Args:
resource_name: Contact resource name (e.g., "people/c1234567890")
Returns:
Dict with success status and contact details or error
"""
if not self._ensure_service():
return {
"success": False,
"error": "Not authorized. Run: python bot_runner.py --setup-google",
}
try:
result = (
self.service.people()
.get(resourceName=resource_name, personFields=PERSON_FIELDS)
.execute()
)
return {
"success": True,
"contact": self._format_contact(result),
}
except HttpError as e:
return {"success": False, "error": str(e)}
def _format_contact(self, person: Dict) -> Dict:
"""Format a person resource into a simple contact dict."""
names = person.get("names", [])
emails = person.get("emailAddresses", [])
phones = person.get("phoneNumbers", [])
bios = person.get("biographies", [])
name = names[0] if names else {}
return {
"resource_name": person.get("resourceName", ""),
"given_name": name.get("givenName", ""),
"family_name": name.get("familyName", ""),
"display_name": name.get("displayName", ""),
"email": emails[0].get("value", "") if emails else "",
"phone": phones[0].get("value", "") if phones else "",
"notes": bios[0].get("value", "") if bios else "",
}
def _format_contacts_summary(self, contacts: List[Dict]) -> str:
"""Format contacts into a readable summary."""
if not contacts:
return "No contacts found."
lines = []
for i, c in enumerate(contacts, 1):
name = c.get("display_name") or f"{c.get('given_name', '')} {c.get('family_name', '')}".strip()
parts = [f"{i}. {name or '(no name)'}"]
if c.get("email"):
parts.append(f" Email: {c['email']}")
if c.get("phone"):
parts.append(f" Phone: {c['phone']}")
lines.append("\n".join(parts))
return "\n".join(lines)