"""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("""
-
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 -
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 -
Configure environment variables:
- Edit {env_path} with your actual values
- Source it: source {env_path}
- Or export vars directly -
Review maintenance items:
- Edit ~/.config/family-assistant/maintenance.yaml
- Update last_done dates with actual completion dates
- Add/remove items to match your household -
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)