Skill: fuzzyb33s/ghostty (version 1.0.0)

--- name: ghostty description: "Your always-on digital self — monitors all your communication channels in parallel, learns your writing style, drafts replies in your voice, and routes them to the right channel with one-click approval. Use when: (1) setting up an AI proxy that acts as you when offline, (2) monitoring email, calendar, Slack, WhatsApp, Signal, Telegram in parallel, (3) drafting replies that sound exactly like you not generic AI, (4) building a persistent voice/style profile from your sent messages, (5) routing urgent messages to WhatsApp and formal replies to email, (6) escalating only what matters based on sender importance. Triggers on: be my digital self, always-on proxy, ghost me, respond as me, my AI twin, digital alter ego." --- # Ghostty — Your Always-On Digital Self Ghostty is an always-on AI proxy that acts as you when you're unavailable. It watches your channels, learns your voice, drafts replies, and escalates intelligently. ## Core Loop ``` 1. MONITOR → Spawn sub-agents watching email, calendar, Slack, WhatsApp 2. LEARN → Build voice profile from your sent messages (ongoing) 3. DRAFT → When meaningful event detected, draft reply in YOUR voice 4. ROUTE → Send via the right channel (urgent → WhatsApp, formal → email) 5. ESCALATE → Only interrupt you when it truly matters ``` ## Step 1 — First Time Setup: Build Your Voice Profile Before Ghostty can draft as you, it needs to learn your style. Run the profile builder: ``` scripts/profile_builder.py --source <email-folder or messages-export> --output ghostty/voice-profile.md ``` The builder analyzes: - Average sentence length and structure - Common phrases and fillers - Greeting and sign-off patterns - Formality level (1-10) - Preferred punctuation style - Tone indicators (confident, warm, terse, etc.) Store the profile at `ghostty/voice-profile.md` in your workspace. Update it monthly as your style evolves. ## Step 2 — Set Up Channel Monitors Spawn a persistent sub-agent for each channel you want Ghostty to watch. Use `mode="session"` so they run continuously. **Email (Gmail/Outlook):** ``` sessions_spawn( task="You monitor the email inbox for important messages. Rules: (1) Only act on emails from people in the PRIORITY_SENDERS list in ghostty/config.md, (2) For each important email, draft a reply using the voice profile at ghostty/voice-profile.md, (3) Send the draft to the escalation channel (WhatsApp/Signal) with [APPROVE] prefix for one-click send, (4) Mark as TODO in ghostty/pending-drafts.md Run continuously. Check every 15 minutes.", runtime="subagent", mode="session", label="ghostty-email" ) ``` **WhatsApp/Signal:** ``` sessions_spawn( task="You monitor WhatsApp messages. Rules: (1) Only respond to DND-mode messages or explicit @ghostty mentions, (2) Draft replies using voice profile at ghostty/voice-profile.md, (3) If sender is in PRIORITY_SENDERS, send directly. Otherwise queue for approval. Run continuously.", runtime="subagent", mode="session", label="ghostty-whatsapp" ) ``` **Calendar:** ``` sessions_spawn( task="You monitor the Google Calendar / Outlook calendar. Rules: (1) Alert via WhatsApp 1 hour before any calendar event, (2) If a meeting is about to start and you haven't joined, alert again, (3) If someone invites you to a meeting and you're not available (based on ghostty/availability.md), draft a polite decline using your voice profile. Run continuously.", runtime="subagent", mode="session", label="ghostty-calendar" ) ``` ## Step 3 — Escalation Rules Edit `ghostty/config.md` to define: - `PRIORITY_SENDERS` — list of people who always get a response - `URGENT_KEYWORDS` — words that trigger immediate WhatsApp alert - `IGNORE_SENDERS` — newsletters, bots, noise to skip - `RESPONSE_WINDOW_MINUTES` — how long before drafting (default: 60) - `APPROVAL_CHANNEL` — where to send drafts for approval (default: WhatsApp) ## Step 4 — Drafting in Your Voice When a monitor detects something worth responding to: ``` 1. Read ghostty/voice-profile.md — get style params 2. Read ghostty/config.md — get context (relationship, ongoing projects, preferences) 3. Fetch the incoming message — understand what it's about 4. Draft reply — apply voice profile (tone, length, phrases, formality) 5. If sender is PRIORITY — send directly 6. If not — queue to ghostty/pending-drafts.md and send preview to WhatsApp ``` **Voice drafting rules:** - Short replies: aim for same length as you typically write - Matching tone: if you use "Hey mate" don't use "Dear Sir" - Reference context: mention anything ongoing (projects, prior emails) - Signature: include your sign-off style from the voice profile ## Step 5 — One-Click Approval When a draft is sent to WhatsApp: ``` [Ghostty] Reply to John re: Q4 proposal Hey John, yeah happy to jump on Tuesday — 3pm works from my end. I'll send over the deck beforehand so we can dive straight in. [APPROVE to send] [EDIT then send] [SKIP] ``` Respond with "APPROVE" on WhatsApp → Ghostty sends the email/WhatsApp reply. ## File Structure ``` ghostty/ ├── voice-profile.md # Your style fingerprint — generated by profile_builder.py ├── config.md # Priority senders, keywords, escalation rules ├── availability.md # When you're reachable, timezone, DND hours ├── pending-drafts.md # Queue of drafts awaiting approval └── sent-log.md # History of what Ghostty sent on your behalf ``` ## Reference Files - `references/voice-profile.md` — detailed voice profiling methodology - `references/channel-monitors.md` — channel-specific setup (Gmail, WhatsApp, Calendar, Slack) - `references/draft-engine.md` — how to draft contextually in your voice - `references/delivery-router.md` — escalation logic, urgency routing, approval flows - `scripts/profile_builder.py` — analyze sample messages, output voice profile markdown ## Safety - Ghostty never sends without approval UNLESS the sender is in PRIORITY_SENDERS - All sent drafts are logged in `ghostty/sent-log.md` - DND hours from `ghostty/availability.md` are respected — no pings during sleep hours - Review `ghostty/pending-drafts.md` at least once daily

path: references/channel-monitors.md
content: # Channel Monitors — Setting Up Each Channel

Each communication channel gets its own persistent sub-agent session. This document covers setup for each channel.

## Gmail

### Setup
Requires: Gmail API access (OAuth) or IMAP credentials stored in `ghostty/secrets/gmail.env`

```
GMAIL_USER=[REDACTED_SECRET]
GMAIL_CLIENT_ID=xxx
GMAIL_CLIENT_SECRET=xxx
GMAIL_REFRESH_TOKEN=xxx
```

### Monitor Agent Prompt
```
You are Ghostty's Gmail monitor. You check Gmail every 15 minutes.

CONFIG: Read ghostty/config.md for PRIORITY_SENDERS, IGNORE_SENDERS, URGENT_KEYWORDS.
VOICE: Read ghostty/voice-profile.md before drafting any reply.

RULES:
1. Only act on emails from addresses in PRIORITY_SENDERS OR containing URGENT_KEYWORDS
2. Skip newsletters, automated emails, mass emails
3. For each actionable email:
   a. Read the full email chain (latest + 1 prior for context)
   b. Draft a reply in MY voice using the voice profile
   c. If sender is in PRIORITY_SENDERS: send directly, log to ghostty/sent-log.md
   d. Otherwise: add to ghostty/pending-drafts.md AND send preview to WhatsApp with [APPROVE] buttons
4. Mark important follow-up emails in a笔记 to yourself

TOOLS: Use Gmail API (gmail_skill) or IMAP to read and send emails.

If Gmail API fails, fall back to IMAP on imap.gmail.com:993 with SSL.
```

## WhatsApp

### Setup
Requires: OpenClaw WhatsApp gateway connected (already configured in your setup)

### Monitor Agent Prompt
```
You are Ghostty's WhatsApp monitor. You run continuously.

CONFIG: Read ghostty/config.md for PRIORITY_SENDERS and APPROVAL_CHANNEL.
VOICE: Read ghostty/voice-profile.md before drafting.

RULES:
1. Only respond when: (a) message contains @ghostty mention, OR (b) sender is in PRIORITY_SENDERS
2. Check ghostty/availability.md — if I'm in DND hours (usually 23:00-07:00), silent-draft only
3. For actionable messages:
   a. Draft reply in MY voice
   b. If sender is PRIORITY: send directly
   c. Otherwise: add to ghostty/pending-drafts.md, reply "Got it — I'll flag this for [your name] to review"
4. If someone asks a question I can answer factually (not needing my opinion): answer directly
5. Never reveal you are an AI unless directly asked

CONTEXT: Maintain awareness of the last 5 messages in each active conversation.
```

## Google Calendar

### Setup
Requires: Google Calendar API credentials in `ghostty/secrets/calendar.env`

```
CALENDAR_ID=primary
GOOGLE_CLIENT_ID=xxx
GOOGLE_CLIENT_SECRET=xxx
GOOGLE_REFRESH_TOKEN=xxx
```

### Monitor Agent Prompt
```
You are Ghostty's Calendar guardian. You run continuously, checking every 5 minutes.

CONFIG: Read ghostty/availability.md for my schedule preferences and ghostty/config.md for alert rules.

RULES:
1. ONE HOUR BEFORE any calendar event: send WhatsApp reminder with:
   - Event title and time
   - Location/link if present
   - "Are you joining?" with [YES / NO / SNOOZE 15min] responses

2. WHEN EVENT STARTS (and no join detected):
   - If I haven't responded to the reminder: follow-up ping on WhatsApp
   
3. MEETING INVITES:
   - If I'm marked as FREE during invite time: auto-accept if sender is PRIORITY
   - If I'm BUSY or unsure: draft a polite decline in my voice, queue for approval
   - Decline template: acknowledge invite, apologize for conflict, offer alternative times

4. ALL-DAY EVENTS: notify me the night before (20:00) as a daily summary

5. If a PRIORITY_SENDER schedules something urgently (within 2 hours): escalate immediately

Keep state in ghostty/calendar-state.md (last notified events, accepted invites, etc.)
```

## Slack

### Setup
Requires: Slack bot token in `ghostty/secrets/slack.env`

```
SLACK_BOT_TOKEN=xoxb-xxx
SLACK_TEAM_ID=xxx
SLACK_CHANNEL_IDS= # comma-separated channel IDs to monitor
```

### Monitor Agent Prompt
```
You are Ghostty's Slack watcher. You monitor specified Slack channels.

CONFIG: Read ghostty/config.md for PRIORITY_SENDERS (by Slack handle).
VOICE: Read ghostty/voice-profile.md.

RULES:
1. Only act on: DMs to the Ghostty bot OR @ghostty mentions in monitored channels
2. Skip messages from IGNORE_KEYWORDS or bots
3. For @ghostty mentions in channels:
   a. Read the thread context (parent message + replies)
   b. Draft a reply in MY voice
   c. Post as a thread reply (not top-level) unless it's a new thread
4. For DMs: treat as PRIORITY-level, can send direct replies
5. If someone @mentions me in a large channel (>50 members), be more conservative

For short acknowledgements ("thanks", "nice", "lol"): can react with emoji instead of replying.
```

## Signal

### Setup
Requires: Signal gateway (signal-cli or similar) configured in OpenClaw

### Monitor Agent Prompt
```
You are Ghostty's Signal monitor. Same rules as WhatsApp monitor but for Signal.
Read ghostty/config.md and ghostty/voice-profile.md before acting.
Only respond to PRIORITY_SENDERS or @ghostty mentions.
```

## Multi-Channel Coordination

When the same event hits multiple channels:

```
Email from John → Ghostty drafts reply
  → John also Slacks me → combine into one cohesive response
  → It's on my calendar → calendar monitor already handled it
  
Slack thread with 5 messages → treat as conversational context
  → Draft single reply addressing the whole thread
  → Don't reply to each message separately
```

Use `ghostty/pending-drafts.md` as the coordination hub — any monitor can read pending drafts to avoid duplicate work.


path: references/delivery-router.md
content: # Delivery Router — When to Send, When to Ask

Ghostty routes every draft through an escalation matrix. This document defines the logic.

## The Escalation Matrix

```
INCOMING MESSAGE
       ↓
┌─────────────────────────────────────────┐
│  Is sender in PRIORITY_SENDERS?         │
└─────────────────────────────────────────┘
       ↓ YES              ↓ NO
┌─────────────┐    ┌──────────────────┐
│  Send       │    │ Is sender in     │
│  directly   │    │ IGNORE_SENDERS?  │
│  (auto-send)│    └──────────────────┘
└─────────────┘         ↓ YES            ↓ NO
              ┌──────────┐    ┌─────────────────────┐
              │  Skip    │    │ Does it contain    │
              │  (log    │    │ URGENT_KEYWORDS?   │
              │   only)  │    └─────────────────────┘
              └──────────┘         ↓ YES            ↓ NO
                          ┌──────────────┐  ┌───────────────────┐
                          │  WhatsApp    │  │ Queue to pending- │
                          │  alert +     │  │ drafts.md + send  │
                          │  APPROVE     │  │ preview to        │
                          │  request     │  │ WhatsApp          │
                          └──────────────┘  └───────────────────┘
```

## Channel Routing Rules

| Situation | Channel | Why |
|-----------|---------|-----|
| PRIORITY sender, short reply | WhatsApp/Signal | Fast, direct |
| PRIORITY sender, formal/long | Email | More appropriate |
| Non-priority, urgent keyword | WhatsApp alert | Make sure I see it |
| Non-priority, standard | Pending draft + preview | I review and approve |
| Meeting invite (free) | Auto-accept + calendar | No action needed |
| Meeting invite (busy) | Decline + queue | Polite response, my voice |
| All-day event reminder | WhatsApp evening summary | Night before, single digest |

## Urgency Scoring

Each message gets an urgency score (0-10):

```
Base score = 0

+3  Sender is in PRIORITY_SENDERS
+2  Contains URGENT_KEYWORD (ASAP, urgent, critical, please respond, tomorrow...)
+2  First contact (new sender)
+2  Reply expected and overdue (past RESPONSE_WINDOW)
+1  Sent during my typical response hours
+1  Contains a question requiring my input
+1  Affects a project marked ACTIVE in config
-2  Sent during DND hours (score capped at 5)
-2  Newsletter/automated sender detected
-3  Unsubscribeable bulk email
```

**Routing thresholds:**
- Score 7+: WhatsApp immediate alert
- Score 4-6: Queue + WhatsApp preview (APPROVE request)
- Score 0-3: Queue only, no notification (review when convenient)

## One-Click Approval Format

When Ghostty sends a draft to WhatsApp for approval:

```
[Ghostty] ✉️ Draft Reply → {sender}

---
{message_body}
---

Length: {N} sentences | Tone: {tone} | Channel: {email|WhatsApp|Slack}

[✅ APPROVE] [✏️ EDIT] [⏭️ SKIP]
```

**On "APPROVE":**
→ Send immediately, log to `ghostty/sent-log.md`

**On "EDIT":**
→ Ghostty opens a reply thread — user types corrections, Ghostty applies them, sends when done

**On "SKIP":**
→ Log as skipped, remove from pending-drafts.md

## DND Hours

Respect `ghostty/availability.md`:

```
DND_HOURS: 23:00 - 07:00 [timezone]
```

During DND:
- Score is capped at 5 (no immediate WhatsApp alerts)
- Drafts queue silently
- Calendar reminders: still send 1hr before events, but softer tone ("Morning — heads up...")
- If PRIORITY sender sends DURING DND with URGENT keywords: still alert (it's urgent)

## Auto-Responses

For messages that don't need a response at all:

| Situation | Ghostty's Action |
|-----------|-----------------|
| "Thanks" / "Nice" / thumbs up | React with emoji, no draft |
| Out-of-office reply received | Note in pending-drafts.md, no reply needed |
| Meeting invite (accepted) | Mark in calendar state, no reply needed |
| Newsletter confirmation | Skip, no action |
| Request I can't fulfill | Draft in voice explaining limitation, queue |

## Sent Log Format

Every sent draft gets logged to `ghostty/sent-log.md`:

```markdown
## YYYY-MM-DD HH:MM [Channel]

**To:** {sender}
**In response to:** {subject/excerpt of what they said}
**Intent:** {question/request/update/etc}
**Urgency score:** {0-10}
**Routed:** {direct/approved}

---
{message_body}
---
```


path: references/draft-engine.md
content: # Draft Engine — How Ghostty Drafts in Your Voice

Ghostty's draft engine takes an incoming message and produces a reply that sounds like you. This document explains the methodology.

## The Drafting Algorithm

### Step 1 — Load Context

Before drafting, gather:
1. **Voice profile** — `ghostty/voice-profile.md` (non-negotiable)
2. **Per-person override** — `ghostty/per-person/{sender-name}.md` if exists
3. **Config** — `ghostty/config.md` (relationship to sender, ongoing projects)
4. **Conversation history** — last 3-5 messages in this thread
5. **Recency** — when was the last sent/received message?

### Step 2 — Classify the Message

Determine:
- **Intent**: question / request / update / acknowledgment / social / pitch / complaint / other
- **Tone needed**: warm / neutral / apologetic / assertive / celebratory
- **Length**: brief ack (1-2 sentences) / standard reply (3-5 sentences) / detailed response (full email)
- **Formality shift**: should this be more formal than usual? (e.g., first time contacting)

### Step 3 — Apply Voice Profile

From the voice profile, apply these constraints to the draft:

**Length matching:**
```
if my_avg_length == "short (1-2 sentences)":
    target_length = 1-3 sentences
elif my_avg_length == "medium (3-5 sentences)":
    target_length = 3-6 sentences
else:
    target_length = 5-8 sentences
```

**Sign-off selection:**
```
if formal_context: use my_professional_signoff
elif ongoing_thread: use my_casual_signoff or nothing
else: use my_default_signoff
```

**Tone enforcement:**
```
if confidence_score < 5: add hedging phrases I use (probably, I think, might)
if confidence_score >= 7: be direct, no filler
if warmth_score >= 7: add personal element (hope you're well, great to hear...)
if warmth_score < 4: drop pleasantries, get to the point
```

**Phrase injection:**
- Inject 1-2 of my pet phrases naturally into the draft
- If I never use "please" — don't add it just because it's polite
- If I always say "no worries" when apologizing — use that instead of "I apologize"

### Step 4 — Contextual Awareness

Good drafts reference ongoing context:

```
# Bad (generic AI):
"Thank you for your email. I will review the proposal and get back to you."

# Good (Ghostty, contextual):
"Thanks for sending over the Q4 proposal — I'll take a look through it this afternoon 
and ping you if I have any questions before our Tuesday call."
```

Add context by:
- Mentioning a shared project or prior discussion
- Referencing a specific time/date mentioned in prior messages
- Acknowledging continuity in an ongoing thread
- Being specific, not generic ("the deck" not "the document you sent")

### Step 5 — Self-Edit Check

Before finalizing, verify:
- [ ] Does this sound like ME, not AI? (read it aloud if unsure)
- [ ] Is the length appropriate for this context?
- [ ] Did I include my typical sign-off style?
- [ ] Are there 1-2 pet phrases or natural fillers I use?
- [ ] Is it specific, not generic?
- [ ] Would I actually send this?

If the draft fails the "would I actually send this" test, regenerate with different constraints.

## Response Templates by Intent

### Question → Direct Answer
```
[Open with acknowledgment if I typically do, e.g. "Hey, good question —"]
[Give the answer directly, matching my verbosity]
[Add follow-up if I typically do, e.g. "Let me know if that doesn't answer it"]
[Sign off in my style]
```

### Request → Acknowledgment + Action
```
[Confirm receipt in my style]
[State what I'll do / am doing]
[If delay: give realistic timeframe in my language]
[Sign off]
```

### Update → Acknowledgment + Reaction
```
[React in my style (enthusiastic / measured / brief)]
[If relevant: ask a follow-up question I would ask]
[End naturally]
```

### Apology Needed → Sincere + Fix
```
[Apologize in MY style (direct "sorry" vs. "my apologies" vs. "my mistake")]
[Explain briefly if appropriate (I usually do / don't over-explain)]
[State the fix/next step]
[Close without over-doing it]
```

### Social / Nudge → Light Touch
```
[Respond at my typical social energy level]
[If action needed: add light nudge without pressure]
[Keep it short if I typically keep social messages short]
```

## Draft Quality Anti-Patterns

AVOID these — they indicate generic AI voice, not you:
- "I hope this email finds you well"
- "Please do not hesitate to reach out"
- "I would be happy to assist"
- "As per our conversation"
- "Kindly note"
- "I trust you are well"
- Excessive exclamation marks (!!!)
- Starting with "Great question!" or "Thanks for reaching out!"
- Emoticons like 🙂 or 🙏 unless you actually use them


path: references/voice-profile.md
content: # Voice Profile — How Ghostty Learns to Write Like You

Your voice profile lives at `ghostty/voice-profile.md` and is the core of Ghostty's ability to sound like you, not generic AI.

## What Gets Measured

### 1. Surface Metrics (easy to detect)
- **Average sentence length** — short=terse/urgent, long=thoughtful/deliberate
- **Paragraph length** — do you write in blocks or one-liners?
- **Punctuation style** — ellipses..., em dashes—, exclamation frequency!
- **Caps usage** — ALL CAPS for emphasis? Never?
- **Emoji frequency** — 😐 or none?

### 2. Structural Patterns
- **Greeting style** — "Hey", "Hi", "Hello", "Yo", "Dear"
- **Sign-off style** — "Cheers", "Thanks", "Best", "Kind regards", nothing
- **Opener patterns** — do you jump straight in or acknowledge first?
- **Question frequency** — do you ask a lot of questions?
- **Directness** — do you say "I want X" or "Would it be possible to X"?

### 3. Tone Indicators (harder to detect)
- **Confidence level** — assertive ("do this") vs. hedged ("maybe", "perhaps", "I think")
- **Warmth** — emotional language, exclamation, personal references
- **Formality register** — startup casual vs. corporate formal
- **Humor** — dry, sarcastic, none detectable?
- **Tone consistency** — same tone with everyone or different with different people?

### 4. Content Patterns
- **Topic avoidance** — what you never talk about
- **Pet phrases** — "no worries", "all good", "gotcha", "perfect"
- **Signature phrases** — things you always say at the end
- **Attention to detail** — do you proof, follow up, add caveats?

## Manual Voice Profile Template

Copy this to `ghostty/voice-profile.md`:

```markdown
# My Voice Profile

## Quick Summary
[TWO SENTENCES: how you sound to a stranger reading your emails]

## Greeting Style
[How you start emails/messages to: (a) friends, (b) colleagues, (c) clients, (d) authority figures]

## Sign-off Style
[Your sign-offs by context: casual, professional, close contacts]

## Typical Length
- Quick reply to friend: [X-Y sentences]
- Reply to colleague: [X-Y sentences]  
- Client/professional: [X-Y sentences]
- Difficult situation: [X-Y sentences]

## Tone Settings
- Confidence: [1-10, 10=always assertive]
- Warmth: [1-10, 10=very warm/expressive]
- Formality: [1-10, 10=very formal]
- Humor: [none / dry / light / frequent]

## Phrases I Use
- Filler/phatic: [e.g., "no worries", "all good", "hope you're well"]
- Agreement: [e.g., "perfect", "sounds good", "deal"]
- Disagreement: [e.g., "I see it differently", "not sure I agree", "hmm"]
- Closing: [e.g., "talk soon", "let me know", "solid"]

## Phrases I Never Use
[e.g., "Kind regards", "As per our conversation", "Please find attached"]

## Emoji Usage
[Frequency: never / occasional / frequent. Types you use: 🙏 💯 😂 etc.]

## What Makes My Replies Distinctive
[2-3 things that make your writing yours]
```

## Auto-Building from Samples

The `scripts/profile_builder.py` can analyze a folder of your sent emails or message exports and produce the profile above automatically.

**To use the profile builder:**
```bash
python3 scripts/profile_builder.py \
  --source ./my-sent-emails/ \
  --format eml \
  --output ghostty/voice-profile.md
```

**Supported formats:**
- `eml` — exported Gmail/Outlook .eml files
- `json` — WhatsApp/Telegram chat export
- `csv` — Slack message export
- `mbox` — Gmailmbox export

## Per-Person Overrides

Sometimes you write differently to different people. Ghostty supports per-person profiles:

```markdown
## Per-Person Overrides

### [boss-name]
More formal. Always use "Kind regards" sign-off. Max 3 sentences.

### [close-friend-name]  
All caps for emphasis OK. Emojis welcome. Can be rambling.

### [new-client-name]
Formal but warm. Confirm everything in writing.
```

Add a `ghostty/per-person/` directory with markdown files named `{person-name}.md` for overrides.


path: scripts/profile_builder.py
content: #!/usr/bin/env python3
"""
Ghostty Voice Profile Builder

Analyzes sent messages (emails, WhatsApp, Slack exports) and generates
a voice profile markdown file that Ghostty uses to draft in your voice.

Usage:
    python3 profile_builder.py --source ./my-emails/ --format eml --output ghostty/voice-profile.md
    python3 profile_builder.py --source ./whatsapp-chat.json --format json --output ghostty/voice-profile.md
    python3 profile_builder.py --source ./slack-export.csv --format csv --output ghostty/voice-profile.md
"""

import argparse
import os
import re
import sys
from collections import Counter
from pathlib import Path


def extract_text_from_eml(folder_path):
    """Extract message bodies from .eml files."""
    messages = []
    for root, _, files in os.walk(folder_path):
        for file in files:
            if file.endswith('.eml'):
                path = os.path.join(root, file)
                try:
                    with open(path, 'r', encoding='utf-8', errors='ignore') as f:
                        content = f.read()
                    # Extract body after first blank line (skip headers)
                    body = re.split(r'\n\s*\n', content, maxsplit=1)
                    if len(body) > 1:
                        messages.append(body[1].strip())
                except Exception:
                    pass
    return messages


def extract_text_from_json(file_path):
    """Extract message bodies from WhatsApp/Telegram JSON export."""
    import json
    messages = []
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
        if isinstance(data, list):
            for item in data:
                if isinstance(item, dict) and 'text' in item:
                    messages.append(str(item['text']))
                elif isinstance(item, dict) and 'message' in item:
                    messages.append(str(item['message']))
        elif isinstance(data, dict):
            # Try common WhatsApp export structures
            for key in ['messages', 'chat', 'conversation']:
                if key in data and isinstance(data[key], list):
                    for item in data[key]:
                        if isinstance(item, dict):
                            for body_key in ['text', 'message', 'content']:
                                if body_key in item:
                                    messages.append(str(item[body_key]))
                                    break
    except Exception as e:
        print(f"Error reading JSON: {e}", file=sys.stderr)
    return messages


def extract_text_from_csv(file_path):
    """Extract message bodies from Slack/CSV export."""
    import csv
    messages = []
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            reader = csv.DictReader(f)
            for row in reader:
                # Try common column names for message body
                for col in ['text', 'message', 'content', 'body']:
                    if col in row and row[col]:
                        messages.append(str(row[col]))
                        break
    except Exception as e:
        print(f"Error reading CSV: {e}", file=sys.stderr)
    return messages


def analyze_messages(messages):
    """Analyze a collection of messages and extract voice patterns."""
    if not messages:
        return None
    
    # Filter: keep only messages that look like actual written communication
    # (remove auto-replies, system messages, very short grunts)
    filtered = [m for m in messages if len(m.split()) >= 3 and not m.startswith('[')]
    if not filtered:
        filtered = messages
    
    # Basic metrics
    all_words = ' '.join(filtered).split()
    sentence_counts = [len(re.split(r'[.!?]+', m)) for m in filtered]
    
    avg_sentence_len = sum(len(m.split()) for m in filtered) / len(filtered)
    avg_sentences = sum(sentence_counts) / len(sentence_counts)
    
    # Greeting detection
    greeting_patterns = [
        r'^hey[a-z]*\b', r'^hi[a-z]*\b', r'^hello\b', r'^yo\b',
        r'^dear\b', r'^greetings\b', r'^good (morning|afternoon|evening)',
        r'^[a-z]+,\s*$'  # "Hey," as opener
    ]
    greetings = []
    for msg in filtered[:50]:  # Check first 50 messages
        first_line = msg.strip().split('\n')[0]
        for pat in greeting_patterns:
            if re.search(pat, first_line.lower()):
                greetings.append(first_line.strip())
                break
    
    # Sign-off detection
    signoff_patterns = [
        r'(cheers|thanks|best|regards|kind regards|warmly|sincerely|yours|talk soon|let me know|looking forward|kindly)',
        r'(👍|🙏|😊|🔥|💯|😂)',  # emoji sign-offs
    ]
    signoffs = []
    for msg in filtered[:50]:
        lines = msg.strip().split('\n')
        last_line = lines[-1].strip() if lines else ''
        for pat in signoff_patterns:
            if re.search(pat, last_line.lower()):
                signoffs.append(last_line)
                break
    
    # Pet phrases (most common bigrams and trigrams excluding stop words)
    stop_words = {'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
                  'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
                  'should', 'may', 'might', 'must', 'shall', 'can', 'to', 'of', 'in',
                  'for', 'on', 'with', 'at', 'by', 'from', 'as', 'into', 'through',
                  'during', 'before', 'after', 'above', 'below', 'between', 'under',
                  'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where',
                  'why', 'how', 'all', 'each', 'few', 'more', 'most', 'other', 'some',
                  'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than',
                  'too', 'very', 'just', 'and', 'but', 'or', 'if', 'because', 'until',
                  'while', 'that', 'which', 'who', 'whom', 'this', 'these', 'those',
                  'am', 'it', 'its', 'i', 'me', 'my', 'we', 'us', 'our', 'you', 'your',
                  'he', 'him', 'his', 'she', 'her', 'they', 'them', 'their', 'what',
                  'any', 'both', 'few', 'more', 'most', 'other', 'some', 'such'}
    
    bigrams = []
    for msg in filtered:
        words = [w.lower().strip('.,!?;:"') for w in msg.split()]
        for i in range(len(words) - 1):
            if words[i] not in stop_words and words[i+1] not in stop_words:
                bigrams.append(f"{words[i]} {words[i+1]}")
    
    top_bigrams = Counter(bigrams).most_common(20)
    
    # Emoji detection
    emojis = re.findall(r'[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\U0001F680-\U0001F6FF\U0001F1E0-\U0001F1FF]', ' '.join(filtered))
    emoji_freq = len(emojis) / len(filtered)
    top_emojis = Counter(emojis).most_common(10)
    
    # Confidence markers (hedging)
    hedge_words = ['probably', 'perhaps', 'maybe', 'i think', 'i guess', 'might', 'could',
                   'seems', 'appears', 'likely', 'possibly', 'supposedly', 'presumably']
    hedges = sum(1 for msg in filtered for hw in hedge_words if hw in msg.lower())
    hedge_ratio = hedges / len(filtered)
    
    # Exclamation usage
    exclamations = sum(1 for msg in filtered if '!' in msg)
    exclamation_ratio = exclamations / len(filtered)
    
    # Question usage
    questions = sum(1 for msg in filtered if '?' in msg)
    question_ratio = questions / len(filtered)
    
    # ALL CAPS usage
    caps_words = re.findall(r'\b[A-Z]{3,}\b', ' '.join(filtered))
    caps_ratio = len(caps_words) / len(filtered)
    
    return {
        'message_count': len(filtered),
        'avg_sentence_length': round(avg_sentence_len, 1),
        'avg_sentences_per_message': round(avg_sentences, 1),
        'top_greetings': Counter(greetings).most_common(5),
        'top_signoffs': Counter(signoffs).most_common(5),
        'top_bigrams': top_bigrams,
        'emoji_usage': emoji_freq,
        'top_emojis': top_emojis,
        'hedge_ratio': round(hedge_ratio, 2),
        'exclamation_ratio': round(exclamation_ratio, 2),
        'question_ratio': round(question_ratio, 2),
        'caps_ratio': round(caps_ratio, 3),
    }


def generate_profile(analysis, output_path):
    """Write the voice profile markdown file."""
    a = analysis
    
    # Determine style scores
    confidence = max(1, min(10, int(10 - (a['hedge_ratio'] * 20))))
    warmth = max(1, min(10, int(5 + (a['emoji_usage'] * 50) + (a['exclamation_ratio'] * 10))))
    formality = max(1, min(10, int(5 + (a['caps_ratio'] * 50) - (a['emoji_usage'] * 20))))
    
    top_greetings = ', '.join([f'"{g}"' for g, _ in a['top_greetings'][:3]]) or 'varies'
    top_signoffs = ', '.join([f'"{s}"' for s, _ in a['top_signoffs'][:3]]) or 'varies'
    top_phrases = ', '.join([f'"{b}"' for b, _ in a['top_bigrams'][:5]])
    top_emojis_str = ' '.join([e for e, _ in a['top_emojis'][:5]]) if a['top_emojis'] else 'none'
    
    # Determine length category
    if a['avg_sentence_length'] < 10:
        length_cat = "short and punchy (1-3 sentences)"
    elif a['avg_sentence_length'] < 20:
        length_cat = "medium (3-6 sentences)"
    else:
        length_cat = "detailed (5-10 sentences)"
    
    profile = f"""# My Voice Profile

> Auto-generated by Ghostty profile_builder.py
> Analyzed {a['message_count']} messages

## Quick Summary
A {length_cat} writer. {"Terse and direct" if confidence >= 7 else "Considered and careful"}. 
{"Warm and expressive" if warmth >= 7 else "Measured and understated"} in tone. 
{"Formally correct" if formality >= 7 else "Casual and relaxed"} in register.

## Greeting Style
Typical greetings: {top_greetings}

## Sign-off Style
Typical sign-offs: {top_signoffs}

## Typical Length
Average: {a['avg_sentence_length']} words per sentence, {a['avg_sentences_per_message']} sentences per message.
Writing style: {length_cat}.

## Tone Settings
- **Confidence:** {confidence}/10 {"(direct, assertive — says what needs saying)" if confidence >= 7 else "(hedged, careful — leaves room)"}
- **Warmth:** {warmth}/10 {"(expressive, personal — uses emoji and emotional language)" if warmth >= 7 else "(reserved, neutral)"}
- **Formality:** {formality}/10 {"(professional, proper)" if formality >= 7 else "(casual, relaxed)"}
- **Humor:** {"dry and subtle" if a['hedge_ratio'] > 0.1 else "direct, minimal filler"}
- **Question frequency:** {"asks often" if a['question_ratio'] > 0.3 else "asks selectively"}

## Phrases I Use Naturally
Most common patterns: {top_phrases}

## Emoji Usage
{"Frequently uses emoji" if a['emoji_usage'] > 0.3 else "Occasional emoji" if a['emoji_usage'] > 0.1 else "Rarely or never uses emoji"}
Common: {top_emojis_str}

## Structural Habits
- Exclamation usage: {"high (enthusiastic)" if a['exclamation_ratio'] > 0.2 else "moderate" if a['exclamation_ratio'] > 0.05 else "sparingly (reserved)"}
- ALL CAPS: {"used for emphasis occasionally" if a['caps_ratio'] > 0.01 else "rarely or never"}
- Hedging ("probably", "maybe", "I think"): {"frequent" if a['hedge_ratio'] > 0.2 else "moderate" if a['hedge_ratio'] > 0.05 else "minimal"}

## What Makes My Writing Distinctive
1. {"Gets straight to the point — minimal pleasantries" if a['avg_sentences_per_message'] < 3 else "Takes time to build context before the main point"}
2. {"Uses exclamation sparingly — each one lands" if a['exclamation_ratio'] < 0.1 else "Uses exclamation to convey genuine enthusiasm"}
3. {"Sign-offs are brief or absent" if not a['top_signoffs'] else "Always closes with a characteristic sign-off"}

## Phrases I Almost Never Use
- "I hope this email finds you well"
- "Please do not hesitate to reach out"
- "As per our conversation"
- "Kindly note"
- "I would be happy to assist"
- "Great question!" (as an opener)

## Per-Person Overrides
Add person-specific overrides in ghostty/per-person/{{name}}.md
"""
    
    os.makedirs(os.path.dirname(output_path), exist_ok=True)
    with open(output_path, 'w', encoding='utf-8') as f:
        f.write(profile)
    
    print(f"Voice profile written to: {output_path}")
    print(f"Analyzed {a['message_count']} messages")
    print(f"Summary: {confidence}/10 confidence, {warmth}/10 warmth, {formality}/10 formality")
    
    return profile


def main():
    parser = argparse.ArgumentParser(description='Ghostty Voice Profile Builder')
    parser.add_argument('--source', required=True, help='Path to email folder or export file')
    parser.add_argument('--format', required=True, choices=['eml', 'json', 'csv'],
                        help='Format of the source files')
    parser.add_argument('--output', required=True, help='Output path for voice profile markdown')
    args = parser.parse_args()
    
    print(f"Extracting messages from {args.source} (format: {args.format})...")
    
    if args.format == 'eml':
        messages = extract_text_from_eml(args.source)
    elif args.format == 'json':
        messages = extract_text_from_json(args.source)
    elif args.format == 'csv':
        messages = extract_text_from_csv(args.source)
    
    print(f"Found {len(messages)} messages")
    
    if len(messages) < 5:
        print("ERROR: Not enough messages to build a reliable profile. Need at least 5.",
              file=sys.stderr)
        sys.exit(1)
    
    print("Analyzing voice patterns...")
    analysis = analyze_messages(messages)
    
    print("Generating profile...")
    generate_profile(analysis, args.output)


if __name__ == '__main__':
    main()


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)