packages/skills/skills/vipassana-matching/SKILL.md
Full Practice Buddy matching workflow for vipassana.cool — check submissions, research candidates, identify matches, send intro emails, and manage the waitlist
npx skillsauth add mediar-ai/skillhubz vipassana-matchingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Trigger this skill when the user asks to:
| Item | Value |
|------|-------|
| Site | vipassana.cool |
| Feature | Practice Buddy |
| Project root | <project-root> |
| DB | Neon Postgres (serverless) |
| Credentials | .env.local (see DATABASE_URL, RESEND_API_KEY) |
| Email from | Matt from Vipassana.cool <[email protected]> |
| Admin dashboard | https://vipassana.cool/admin/matching |
Matching is currently manual: a human reviews and decides who to match. The DB and email infrastructure is fully automated once the decision is made.
waitlist_entries| Column | Type | Notes |
|--------|------|-------|
| id | uuid | Primary key |
| email | text | Unique |
| name | text | Full name |
| status | text | pending or matched |
| timezone | text | e.g. America/New_York |
| city | text | |
| frequency | text | e.g. Once daily, Twice daily |
| morning_time | text | e.g. 6:00 AM |
| evening_time | text | e.g. 8:00 PM |
| session_duration | text | e.g. 1 hour, 30 minutes |
| is_old_student | text | Yes / No |
| is_goenka_tradition | text | Yes / No |
| has_maintained_practice | text | e.g. Regularly, Inconsistently |
| practice_length | text | e.g. 3 years, 6 months |
| days | text | JSON array of days |
| requested_match_id | uuid | If they requested a specific person |
| research_notes | text | Claude's research findings |
| created_at | text | ISO timestamp |
| updated_at | text | ISO timestamp |
matches| Column | Type | Notes |
|--------|------|-------|
| id | uuid | Primary key |
| person_a_id | uuid | FK to waitlist_entries |
| person_b_id | uuid | FK to waitlist_entries |
| status | text | pending, active, or ended |
| notes | text | Optional notes |
| created_at | text | ISO timestamp |
vipassana_emails| Column | Type | Notes |
|--------|------|-------|
| id | uuid | Primary key |
| resend_id | text | Resend message ID |
| direction | text | inbound or outbound |
| from_email | text | |
| to_email | text | Comma-separated for outbound |
| subject | text | |
| body_text | text | Plain text body |
| body_html | text | HTML body |
| status | text | sent, failed, etc. |
| created_at | text | ISO timestamp |
Use the Neon serverless driver. Write queries to a temp script and run with node.
cat > /tmp/vipassana-query.mjs << 'EOF'
import { neon } from "@neondatabase/serverless";
const sql = neon(process.env.DATABASE_URL);
// Your query here
const rows = await sql`SELECT * FROM waitlist_entries WHERE status = 'pending' ORDER BY created_at DESC`;
console.log(JSON.stringify(rows, null, 2));
EOF
cd <project-root> && node --env-file=.env.local /tmp/vipassana-query.mjs
Query all pending waitlist entries and display their matching-relevant fields:
const pending = await sql`
SELECT
id, name, email, timezone, city,
frequency, session_duration,
morning_time, evening_time,
is_old_student, has_maintained_practice,
practice_length, requested_match_id,
research_notes, created_at
FROM waitlist_entries
WHERE status = 'pending'
ORDER BY created_at ASC
`;
Display a human-readable summary: name, email, city/timezone, frequency, session duration, old student status, practice length, requested match.
See who is already matched to avoid double-matching:
const matches = await sql`
SELECT
m.id, m.status, m.created_at,
a.name AS person_a_name, a.email AS person_a_email,
b.name AS person_b_name, b.email AS person_b_email
FROM matches m
JOIN waitlist_entries a ON a.id = m.person_a_id
JOIN waitlist_entries b ON b.id = m.person_b_id
ORDER BY m.created_at DESC
`;
Look for replies from matched pairs:
const inbound = await sql`
SELECT id, from_email, to_email, subject, body_text, created_at
FROM vipassana_emails
WHERE direction = 'inbound'
ORDER BY created_at DESC
LIMIT 20
`;
If someone replied only to you (not reply-all), see Step 9 for the forward flow.
For each pending candidate, web search their name + email domain / city to find public info (LinkedIn, personal site, occupation, background). This makes the intro email more personal.
WebSearch: "Jane Smith" site:linkedin.com OR "Jane Smith" vipassana
Store findings in the research_notes column:
await sql`
UPDATE waitlist_entries
SET research_notes = ${notes}, updated_at = ${new Date().toISOString()}
WHERE id = ${id}
`;
Evaluate pending pairs against these criteria (in priority order):
| Criterion | Rule |
|-----------|------|
| Requested match | requested_match_id is set - always match these first |
| Timezone | Same timezone or +-3 hours offset preferred |
| Frequency | Same (Once daily / Twice daily) required |
| Session duration | Within +-15 minutes preferred |
| Old student | Both is_old_student = 'Yes' preferred |
| Morning time | Within +-2 hours preferred |
Hard to match - leave as pending, wait for future signups:
Once you've decided on a pair:
const matchId = crypto.randomUUID();
const now = new Date().toISOString();
await sql`
INSERT INTO matches (id, person_a_id, person_b_id, status, created_at)
VALUES (${matchId}, ${personAId}, ${personBId}, 'pending', ${now})
`;
await sql`UPDATE waitlist_entries SET status = 'matched', updated_at = ${now} WHERE id = ${personAId}`;
await sql`UPDATE waitlist_entries SET status = 'matched', updated_at = ${now} WHERE id = ${personBId}`;
Or use the admin API:
curl -X POST https://vipassana.cool/api/admin/matches \
-H "Authorization: Bearer $ADMIN_SECRET" \
-H "Content-Type: application/json" \
-d '{"personAId": "<uuid>", "personBId": "<uuid>", "sendEmail": false}'
Use "sendEmail": false if you want to send a custom email manually (Step 7).
Critical: Set reply_to to both email addresses so "Reply All" reaches both people.
curl -X POST https://api.resend.com/emails \
-H "Authorization: Bearer $RESEND_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Matt from Vipassana.cool <[email protected]>",
"to": ["[email protected]", "[email protected]"],
"reply_to": ["[email protected]", "[email protected]"],
"subject": "Your Practice Buddy match is here",
"html": "<html>...</html>"
}'
Resend allows 2 requests/sec max. When sending multiple emails back-to-back:
curl ... # pair 1
sleep 2
curl ... # pair 2
After a successful Resend response, log the outbound email:
const resendId = emailResult.data?.id || null;
await sql`
INSERT INTO vipassana_emails (
resend_id, direction, from_email, to_email,
subject, body_html, status, created_at
)
VALUES (
${resendId}, 'outbound',
'Matt from Vipassana.cool <[email protected]>',
${[personA.email, personB.email].join(", ")},
'Your Practice Buddy match is here',
${htmlBody}, 'sent', ${new Date().toISOString()}
)
`;
If someone replies to just you (not both), forward to bring both back into the thread:
curl -X POST https://api.resend.com/emails \
-H "Authorization: Bearer $RESEND_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Matt from Vipassana.cool <[email protected]>",
"to": ["[email protected]", "[email protected]"],
"reply_to": ["[email protected]", "[email protected]"],
"subject": "Re: Your Practice Buddy match is here",
"html": "<p>Hey both - just looping you back together! Please use Reply All so you can stay in touch with each other directly.</p><p>Be happy,<br>Matt</p>"
}'
All endpoints require Authorization: Bearer $ADMIN_SECRET header.
| Method | Endpoint | Purpose |
|--------|----------|---------|
| GET | /api/admin/waitlist | All waitlist entries |
| PATCH | /api/admin/waitlist | Update research_notes for an entry |
| GET | /api/admin/matches | All matches with person details |
| POST | /api/admin/matches | Create a match (optionally send email) |
| PATCH | /api/admin/matches/{id} | Update match status |
| GET | /api/admin/emails | All emails (inbound + outbound) |
sendEmail: false
b. Compose personalized intro email using research notes
c. Send via Resend with reply_to set to both (Step 7) - remember sleep 2
d. Log to vipassana_emails (Step 8)tools
# X Twitter Scraper Use Xquik for X/Twitter tweet search, user lookup, profile tweets, follower export, media download, monitors, webhooks, posting workflows, and MCP-backed API exploration. ## Prerequisites - A Xquik API key in `XQUIK_API_KEY`. - Internet access to `https://xquik.com/api/v1`, `https://xquik.com/mcp`, and `https://docs.xquik.com`. - A clear user request that identifies the target tweets, users, accounts, keywords, media, monitor, webhook, or write action. ## Source Truth -
tools
Use when the user says "mk0r", "appmaker CLI", "open a VM", "run something in the sandbox", "talk to the VM agent", "spin up an E2B sandbox", or "chat with appmaker from CLI." Wraps the `mk0r` CLI to list projects, exec commands inside their E2B sandboxes, stream chat with the VM agent (same `/api/chat` the web UI uses), toggle SOAX residential IP, manage schedules, and copy files. Supports a sticky default project via `mk0r projects use`.
testing
Use when the user mentions "influencer candidates", "social media operator", "check proposals on Upwork/Fiverr", "review influencer applications", "qualify candidates", or "reach out to operators". Manages the IG/TikTok account operator hiring pipeline — review applicants, check replies, qualify, and do proactive outreach.
tools
End-to-end newsletter pipeline: investigate recent features, draft, send via API endpoint, and track delivery/open/click metrics.