📄 setup.py 8,075 bytes Apr 19, 2026 📋 Raw

"""Setup wizard for Family Assistant.

Creates family.yaml and .env template in ~/.config/family-assistant/.
"""

import os
import sys
from pathlib import Path

import yaml

CONFIG_DIR = Path.home() / ".config" / "family-assistant"
FAMILY_YAML_PATH = CONFIG_DIR / "family.yaml"
ENV_TEMPLATE_PATH = CONFIG_DIR / ".env"

ENV_TEMPLATE = """# Family Assistant configuration

Copy and fill in your values, then source this file or export the vars.

Gmail account for reading appointment emails

GMAIL_USER=
GMAIL_APP_PASSWORD=

Google Calendar (must match the calendar the service account has access to)

GCAL_CALENDAR_ID=
GCAL_SERVICE_ACCOUNT=gcal-service-account.json

LLM endpoint (default: local Ollama)

LLM_URL=http://localhost:11434/v1/chat/completions

LLM_MODEL=qwen2.5-coder:7b

Family config path (optional — defaults to CWD/family.yaml or ~/.config/family-assistant/family.yaml)

FAMILY_CONFIG_PATH=

"""

def _input(prompt, default=""):
"""Read input with a default value."""
if default:
result = input(f"{prompt} [{default}]: ").strip()
else:
result = input(f"{prompt}: ").strip()
return result or default

def _yes_no(prompt, default=False):
"""Read a yes/no answer."""
suffix = "[Y/n]" if default else "[y/N]"
result = input(f"{prompt} {suffix}: ").strip().lower()
if not result:
return default
return result in ("y", "yes")

def run_setup(non_interactive=False):
"""Run the setup wizard.

If non_interactive=True, creates template files without prompting.
"""
CONFIG_DIR.mkdir(parents=True, exist_ok=True)

if non_interactive:
    _create_templates()
    return

# Interactive setup
print("=" * 50)
print("Family Assistant Setup Wizard")
print("=" * 50)
print()

# Family members
members = []
print("Let's add your family members.")
print("Press Enter with no name to finish.\n")

while True:
    name = _input(f"  Member #{len(members)+1} name")
    if not name:
        break

    role = _input("  Role (e.g. dad, mom, son, daughter, dog)", "member")
    pronouns = _input("  Pronouns (e.g. he/him, she/her)", "")
    needs_adult = _yes_no("  Needs adult to transport/accompany?", False)

    nicknames = []
    nick_str = _input("  Nicknames (comma-separated, or Enter for none)", "")
    if nick_str:
        nicknames = [n.strip() for n in nick_str.split(",") if n.strip()]

    member = {"name": name, "role": role}
    if pronouns:
        member["pronouns"] = pronouns
    if nicknames:
        member["nicknames"] = nicknames
    if needs_adult:
        member["needs_adult"] = True

    members.append(member)
    print()

if not members:
    print("No members added. Creating a template family.yaml instead.")
    _create_templates()
    return

# Write family.yaml
family_data = {"family": {"members": members}}
with open(FAMILY_YAML_PATH, "w") as f:
    yaml.dump(family_data, f, default_flow_style=False, sort_keys=False)

print(f"✅ Created {FAMILY_YAML_PATH}")

# Write .env template
with open(ENV_TEMPLATE_PATH, "w") as f:
    f.write(ENV_TEMPLATE)

print(f"✅ Created {ENV_TEMPLATE_PATH}")

# Maintenance items
maint_items = []
print()
print("=" * 50)
print("Maintenance Tracking")
print("=" * 50)
print()
print("Track recurring household tasks (HVAC filters, pet meds, etc.).")
print("Add items now, or edit maintenance.yaml later.")
print()

add_maint = _yes_no("Add maintenance items now?", True)
if add_maint:
    categories = ["pet", "home", "seasonal", "vehicle", "health", "other"]
    print(f"Categories: {', '.join(categories)}")
    print("Press Enter with no name to finish.\n")

    while True:
        name = _input(f"  Item #{len(maint_items)+1} name")
        if not name:
            break

        cat = _input("  Category", "home")
        who = _input("  Who/what is this for?", "House")
        interval = _input("  Interval (e.g. '30 days', '3 months', '12 months')", "3 months")
        last_done = _input("  Last done date (YYYY-MM-DD)", "")
        notify = _input("  Remind how many days before due?", "7")
        due_month = _input("  Due month only? (1-12, or Enter for anytime)", "")

        item = {
            "name": name,
            "category": cat,
            "who": who,
            "interval": interval,
            "last_done": last_done or "",
            "notify_days_before": int(notify) if notify.isdigit() else 7,
        }
        if due_month.isdigit():
            item["due_month"] = int(due_month)

        maint_items.append(item)
        print()

if maint_items:
    maint_data = {"items": maint_items}
    with open(CONFIG_DIR / "maintenance.yaml", "w") as f:
        yaml.dump(maint_data, f, default_flow_style=False, sort_keys=False)
    print(f"✅ Created {CONFIG_DIR / 'maintenance.yaml'} with {len(maint_items)} items")
elif add_maint:
    # User said yes but didn't add any — create from example
    _copy_maintenance_template()
else:
    _copy_maintenance_template()

# Print next steps
print()
print("=" * 50)
print("Next Steps")
print("=" * 50)
print("""
  1. Set up Gmail App Password:
    - Go to https://myaccount.google.com/apppasswords
    - Create an app password for "Mail" on your device
    - Set GMAIL_APP_PASSWORD in your .env file

  2. Set up Google Calendar Service Account:
    - Go to https://console.cloud.google.com
    - Create a project and enable the Google Calendar API
    - Create a service account and download the JSON key
    - Place the key file as gcal-service-account.json (or set GCAL_SERVICE_ACCOUNT)
    - Share your Google Calendar with the service account email

  3. Configure environment variables:
    - Edit {env_path} with your actual values
    - Source it: source {env_path}
    - Or export vars directly

  4. Review maintenance items:
    - Edit ~/.config/family-assistant/maintenance.yaml
    - Update last_done dates with actual completion dates
    - Add/remove items to match your household

  5. Test the setup:
    family-assistant upcoming
    family-assistant process --dry-run
    family-assistant maintenance
    """.format(env_path=ENV_TEMPLATE_PATH))

def _copy_maintenance_template():
"""Create maintenance.yaml from the example template."""
import shutil
pkg_dir = Path(file).parent
example = pkg_dir / "maintenance.yaml.example"
dest = CONFIG_DIR / "maintenance.yaml"
if example.exists():
shutil.copy2(example, dest)
print(f"✅ Created {dest} from template — edit with your actual items")
else:
# Fallback: create minimal template
maint_data = {"items": []}
with open(dest, "w") as f:
yaml.dump(maint_data, f, default_flow_style=False, sort_keys=False)
print(f"✅ Created empty {dest} — add items later")

def _create_templates():
"""Create template family.yaml and .env without prompting."""
# Template family.yaml with example data
template_members = [
{"name": "Example", "role": "parent", "pronouns": "they/them", "nicknames": ["Ex"], "needs_adult": False},
]
family_data = {"family": {"members": template_members}}
with open(FAMILY_YAML_PATH, "w") as f:
yaml.dump(family_data, f, default_flow_style=False, sort_keys=False)

print(f"✅ Created template {FAMILY_YAML_PATH}")

# Template maintenance.yaml
_copy_maintenance_template()

with open(ENV_TEMPLATE_PATH, "w") as f:
    f.write(ENV_TEMPLATE)

print(f"✅ Created template {ENV_TEMPLATE_PATH}")
print()
print("Edit both files with your actual values, then run:")
print("  family-assistant upcoming")

if name == "main":
non_interactive = "--non-interactive" in sys.argv
run_setup(non_interactive=non_interactive)