mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-27 20:29:44 +02:00
Move skills into apps/skills/, drop override + sync layers
Skills now ship with the app under /apps/skills/ (sibling of /apps/x).
Forge bundles the directory into Resources/skills/; main resolves it via
process.resourcesPath in production and a workspace-relative path in dev,
then registers it in the DI container. The runtime reads SKILL.md files
directly from the bundle — no copy to ~/.rowboat/skills/, no GitHub
tarball sync.
Drop the override layer (FSSkillsRepo, SkillOverride, edit/diff UI,
skill-update notification) since skills are now read-only and only ship
with app updates. Resolver simplifies to a single source.
Add a placeholder substitution layer so skills that need live data
(currently `tracks`, with {{TRACK_BLOCK_SCHEMA}}) keep dynamic content
without depending on TS-module evaluation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
66c0bc5fa7
commit
e23f4ad6d2
32 changed files with 5999 additions and 1446 deletions
261
apps/skills/draft-emails/SKILL.md
Normal file
261
apps/skills/draft-emails/SKILL.md
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
---
|
||||
name: draft-emails
|
||||
description: >-
|
||||
Process incoming emails and create draft responses using calendar and knowledge base for context. Use when the user wants to reply to emails or draft email responses.
|
||||
license: MIT
|
||||
compatibility: Designed for Rowboat desktop app
|
||||
metadata:
|
||||
version: "1.1.0"
|
||||
title: "Draft Emails"
|
||||
author: rowboatlabs
|
||||
tags: "email, productivity"
|
||||
---
|
||||
|
||||
# Email Draft Skill
|
||||
|
||||
You are helping the user draft email responses. Use their calendar and knowledge base for context.
|
||||
|
||||
## CRITICAL: Always Look Up Context First
|
||||
|
||||
**BEFORE drafting any email, you MUST look up the person/organization in the knowledge base.**
|
||||
|
||||
**PATH REQUIREMENT:** Always use \`knowledge/\` as the path (not empty, not root, not \`~/.rowboat\`).
|
||||
- **WRONG:** \`path: ""\` or \`path: "."\`
|
||||
- **CORRECT:** \`path: "knowledge/"\`
|
||||
|
||||
When the user says "draft an email to Monica" or mentions ANY person, organization, project, or topic:
|
||||
|
||||
1. **STOP** - Do not draft anything yet
|
||||
2. **SEARCH** - Look them up in the knowledge base (path MUST be \`knowledge/\`):
|
||||
\`\`\`
|
||||
workspace-grep({ pattern: "Monica", path: "knowledge/" })
|
||||
\`\`\`
|
||||
3. **READ** - Read their note to understand who they are:
|
||||
\`\`\`
|
||||
workspace-readFile("knowledge/People/Monica Smith.md")
|
||||
\`\`\`
|
||||
4. **UNDERSTAND** - Extract their role, organization, relationship history, past interactions, open items
|
||||
5. **THEN DRAFT** - Only now draft the email, using this context
|
||||
|
||||
**DO NOT** skip this step. **DO NOT** provide generic templates. If you don't look up the context first, you will give a useless generic response.
|
||||
|
||||
## Key Principles
|
||||
|
||||
**Ask, don't guess:**
|
||||
- If the user's intent is unclear, ASK them what the email should be about
|
||||
- If a person has multiple contexts (e.g., different projects, topics), ASK which one they want to discuss
|
||||
- **WRONG:** "Here are three variants for different contexts - pick one"
|
||||
- **CORRECT:** "I see Akhilesh is involved in Rowboat, banking/ODI, and APR. Which topic would you like to discuss in this email?"
|
||||
|
||||
**Be decisive, not generic:**
|
||||
- Once you know the context, draft ONE email - no multiple versions or options
|
||||
- Do NOT provide generic templates - every draft should be personalized based on knowledge base context
|
||||
- Infer the right tone, content, and approach from the context you gather
|
||||
- Do NOT hedge with "here are a few options" or "you could say X or Y" - either ask for clarification OR make a decision and draft ONE email
|
||||
|
||||
## State Management
|
||||
|
||||
All state is stored in \`pre-built/email-draft/\`:
|
||||
|
||||
- \`state.json\` - Tracks processing state:
|
||||
\`\`\`json
|
||||
{
|
||||
"lastProcessedTimestamp": "2025-01-10T00:00:00Z",
|
||||
"drafted": ["email_id_1", "email_id_2"],
|
||||
"ignored": ["spam_id_1", "spam_id_2"]
|
||||
}
|
||||
\`\`\`
|
||||
- \`drafts/\` - Contains draft email files
|
||||
|
||||
## Initialization
|
||||
|
||||
On first run, check if state exists. If not, create it:
|
||||
|
||||
1. Check if \`pre-built/email-draft/state.json\` exists
|
||||
2. If not, create \`pre-built/email-draft/\` and \`pre-built/email-draft/drafts/\`
|
||||
3. Initialize \`state.json\` with empty arrays and a timestamp of "1970-01-01T00:00:00Z"
|
||||
|
||||
## Processing Flow
|
||||
|
||||
### Step 1: Load State
|
||||
|
||||
Read \`pre-built/email-draft/state.json\` to get:
|
||||
- \`lastProcessedTimestamp\` - Only process emails newer than this
|
||||
- \`drafted\` - List of email IDs already drafted (skip these)
|
||||
- \`ignored\` - List of email IDs marked as ignored (skip these)
|
||||
|
||||
### Step 2: Scan for New Emails
|
||||
|
||||
List emails in \`gmail_sync/\` folder.
|
||||
|
||||
For each email file:
|
||||
1. Extract the email ID from filename (e.g., \`19048cf9c0317981.md\` -> \`19048cf9c0317981\`)
|
||||
2. Skip if ID is in \`drafted\` or \`ignored\` lists
|
||||
3. Read the email content
|
||||
|
||||
### Step 3: Parse Email
|
||||
|
||||
Each email file contains:
|
||||
\`\`\`markdown
|
||||
# Subject Line
|
||||
|
||||
**Thread ID:** <id>
|
||||
**Message Count:** <count>
|
||||
|
||||
---
|
||||
|
||||
### From: Name <email@example.com>
|
||||
**Date:** <date string>
|
||||
|
||||
<email body>
|
||||
\`\`\`
|
||||
|
||||
Extract:
|
||||
- Thread ID (this is the email ID)
|
||||
- From (sender name and email)
|
||||
- Date
|
||||
- Subject (from the # heading)
|
||||
- Body content
|
||||
- Message count (to understand if it's a thread)
|
||||
|
||||
### Step 4: Classify Email
|
||||
|
||||
Determine the email type and action:
|
||||
|
||||
**IGNORE these (add to \`ignored\` list):**
|
||||
- Newsletters (unsubscribe links, "View in browser", bulk sender indicators)
|
||||
- Marketing emails (promotional language, no-reply senders)
|
||||
- Automated notifications (GitHub, Jira, Slack, shipping updates)
|
||||
- Spam or cold outreach that's clearly irrelevant
|
||||
- Emails where you (the user) are the sender and it's outbound with no reply
|
||||
|
||||
**DRAFT response for:**
|
||||
- Meeting requests or scheduling emails
|
||||
- Personal emails from known contacts
|
||||
- Business inquiries that seem legitimate
|
||||
- Follow-ups on existing conversations
|
||||
- Emails requesting information or action
|
||||
|
||||
### Step 5: Gather Context
|
||||
|
||||
Before drafting, gather relevant context. **Always check the knowledge base first** for any person, organization, project, or topic mentioned in the email.
|
||||
|
||||
**Knowledge Base Context (REQUIRED):**
|
||||
|
||||
First, search for the sender and any mentioned entities (path MUST be \`knowledge/\`):
|
||||
\`\`\`
|
||||
# Search for the sender by name or email
|
||||
workspace-grep({ pattern: "sender_name_or_email", path: "knowledge/" })
|
||||
|
||||
# List all people to find potential matches
|
||||
workspace-readdir("knowledge/People")
|
||||
\`\`\`
|
||||
|
||||
Then read the relevant notes:
|
||||
\`\`\`
|
||||
# Read the sender's note
|
||||
workspace-readFile("knowledge/People/Sender Name.md")
|
||||
|
||||
# Read their organization's note
|
||||
workspace-readFile("knowledge/Organizations/Company Name.md")
|
||||
\`\`\`
|
||||
|
||||
Extract from these notes:
|
||||
- Their role, title, and organization
|
||||
- History of past interactions and meetings
|
||||
- Commitments made (by them or to them)
|
||||
- Open items and pending actions
|
||||
- Relationship context and rapport
|
||||
|
||||
Use this context to provide informed, personalized responses that demonstrate you remember past interactions.
|
||||
|
||||
**Calendar Context** (for scheduling emails):
|
||||
- Read calendar events from \`calendar_sync/\` folder
|
||||
- Look for events in the relevant time period
|
||||
- Check for conflicts, availability
|
||||
|
||||
### Step 6: Create Draft
|
||||
|
||||
For emails that need a response, create a draft file in \`pre-built/email-draft/drafts/\`:
|
||||
|
||||
**Filename:** \`{email_id}_draft.md\`
|
||||
|
||||
**Content format:**
|
||||
\`\`\`markdown
|
||||
# Draft Response
|
||||
|
||||
**Original Email ID:** {email_id}
|
||||
**Original Subject:** {subject}
|
||||
**From:** {sender}
|
||||
**Date Processed:** {current_date}
|
||||
|
||||
---
|
||||
|
||||
## Context Used
|
||||
|
||||
- Calendar: {relevant calendar info or "N/A"}
|
||||
- Memory: {relevant notes or "N/A"}
|
||||
|
||||
---
|
||||
|
||||
## Draft Response
|
||||
|
||||
Subject: Re: {original_subject}
|
||||
|
||||
{draft email body}
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
{any notes about why this response was crafted this way}
|
||||
\`\`\`
|
||||
|
||||
**Drafting Guidelines:**
|
||||
- Draft ONE email - do not offer multiple versions or options unless explicitly asked
|
||||
- Be concise and professional
|
||||
- For scheduling: propose specific times based on calendar availability
|
||||
- For inquiries: answer directly or indicate what info is needed
|
||||
- Reference any relevant context from memory naturally - show you remember past interactions
|
||||
- Match the tone of the incoming email
|
||||
- If it's a thread with multiple messages, read the full context
|
||||
- Do NOT use generic templates or placeholder language - personalize based on knowledge base
|
||||
- If you're unsure about the user's intent, ask a clarifying question first
|
||||
|
||||
### Step 7: Update State
|
||||
|
||||
After processing each email:
|
||||
1. Add the email ID to either \`drafted\` or \`ignored\` list
|
||||
2. Update \`lastProcessedTimestamp\` to the current time
|
||||
3. Write updated state to \`pre-built/email-draft/state.json\`
|
||||
|
||||
## Output
|
||||
|
||||
After processing all new emails, provide a summary:
|
||||
|
||||
\`\`\`
|
||||
## Processing Summary
|
||||
|
||||
**Emails Scanned:** X
|
||||
**Drafts Created:** Y
|
||||
**Ignored:** Z
|
||||
|
||||
### Drafts Created:
|
||||
- {email_id}: {subject} - {brief reason}
|
||||
|
||||
### Ignored:
|
||||
- {email_id}: {subject} - {reason for ignoring}
|
||||
\`\`\`
|
||||
|
||||
## Error Handling
|
||||
|
||||
- If an email file is malformed, log it and continue
|
||||
- If calendar/notes folders don't exist, proceed without that context
|
||||
- Always save state after each email to avoid reprocessing on failure
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Never actually send emails - only create drafts
|
||||
- The user will review and send drafts manually
|
||||
- Be conservative with ignore - when in doubt, create a draft
|
||||
- For ambiguous emails, create a draft with a note explaining the ambiguity
|
||||
Loading…
Add table
Add a link
Reference in a new issue