| name | sync-desktop-skills |
| description | Import or update skills in this public repo from Shaw's canonical skills repo at ~/agents/skills. Use whenever Shaw says "sync skills", "update the non-coding skills", "pull <skill> from canonical", "add these skills to the repo", or otherwise references moving his private canonical skills into this public repo. Handles PII scrubbing via parallel sub-agents, README table maintenance, and produces a clean commit. Do NOT use for creating skills from scratch (→ skill-creator) or one-off edits to a single already-imported skill. |
Sync Desktop Skills
Workflow for bringing skills from Shaw's canonical skills repo into this public GitHub repo. The hard parts are (1) scrubbing PII without over-scrubbing brand references, and (2) keeping the README table sorted and accurate.
Naming note: This skill is named sync-desktop-skills for historical reasons — the canonical source was originally the Claude Desktop skills-plugin cache. It has since moved to a dedicated git repo at ~/agents/skills/. The skill name is preserved; the workflow below reflects the current source.
When to use
- Shaw names specific skills to import ("add conversion-copy and sales-letter-writer from canonical")
- Shaw wants a refresh of already-imported skills after canonical-side edits
- Shaw asks for a bulk sync / comparison between canonical and repo
When NOT to use
- Creating a skill from scratch → skill-creator
- Editing one file inside a single already-imported skill → just edit it directly
Note: Code-native skills (animation-builder, graphic-builder, keynote, skill-sync, skill-updater, training-proposal, video-editor) are NOT in the canonical ~/agents/skills repo — they source from ~/.claude/skills/ instead. They are still in scope for this skill; see "Second source" below. (July 2026: infographic-builder was renamed upstream to graphic-builder, and skill-sync moved out of canonical into Code-native.)
Source location
The canonical skills repo lives at:
/Users/shaw/agents/skills/
It's a regular git repo — one directory per skill, no session/instance hunting needed. List contents with:
ls /Users/shaw/agents/skills/
Target is always this repo's root: /Users/shaw/Documents/_code/_stv/_repos/non-coding-skills/.
Second source — Code-native skills (~/.claude/skills/)
Shaw maintains two skill sources, and a full sync covers both:
~/agents/skills/ — the canonical repo above. The "portable" skills (run on any surface / Chat / Cowork). The scrub.py script targets this source.
~/.claude/skills/ — his live Claude Code skills. This is the ONLY source for the Code-native skills that have no canonical copy: animation-builder, graphic-builder, keynote, skill-sync, skill-updater, training-proposal, video-editor. (It also contains the canonical skills, plus Code-only skills that are intentionally NOT published — e.g. fasthtml, playwright-mcp-guidance, claude-trace-helper, generate-video-ideas. Don't add those without Shaw's say-so.)
For the canonical set, compare against ~/agents/skills/. For the six Code-native skills, compare against ~/.claude/skills/. To tell which truly changed (vs. PII-scrub noise), scrub a temp copy of the source and diff that against the repo.
Syncing Code-native skills (scrub.py does NOT handle these — it's wired to ~/agents/skills + Notion patterns):
- Use
rsync -a --delete per skill so upstream deletions propagate (e.g. video-editor dropped analyze.py/propose_edits.py). Pass each --exclude as its own argument inline — do NOT collect them in a shell variable and expand it: zsh does not word-split unquoted variables, so $EXC arrives as one bogus pattern and every exclude silently no-ops (this leaked full .venv trees and a real .env in one run).
- Exclude junk/secrets/generated:
.DS_Store __pycache__ *.pyc .venv *.egg-info build dist wheels .env uv.lock output transcripts claude-edits .gitkeep. (uv.lock/.python-version: don't churn them — restore whatever the repo already committed with git checkout; the repo's footprint is deliberate and you can't run uv to regen a lock.)
- Protect repo adaptations from
--delete by also --exclude-ing them (so they're neither overwritten nor removed), then reconcile by hand:
keynote/ — repo renamed shaw-style-guide.md → style-guide.md and de-personalizes ("Shaw" → "your", "author": "Shaw Talebi" → "Your Name", etc.). Port new style-guide bullets into the renamed file; fix the SKILL.md reference to style-guide.md. (The shawhintalebi@gmail.com curl-UA contact is KEEP — allowlisted + matches the committed infographic-builder precedent.)
video-editor/ — keep the sanitized .env.example, README.md, .gitignore; never copy the real .env.
training-proposal/ — keep scripts/LOGO-README.md (placeholder for the gitignored logo-square.png); skip evals/ and the real logo binary. As of July 2026 upstream dropped the example proposals entirely in favor of the content-free references/format-reference.md (the repo's fictional Acme/TechStart/Globex examples were deleted to match). Upstream SKILL.md prose embeds real client names (filename examples, "freshest proposal" anecdotes) and points at Shaw's local proposals folder — scrub names to [Company]/fictional placeholders and genericize personal absolute paths (/Users/shaw/... → ~/... or "your proposals directory"); also strip personal-website logo links from md-to-pdf.py. Grep case-insensitively: one run missed CyberCube by grepping lowercase.
graphic-builder/ (renamed from infographic-builder in July 2026) — @shawhintalebi footer handle, shawhintalebi@gmail.com curl-UA, and aibuilder.academy are KEEP (public). Genericize personal folder paths (/Users/shaw/... → ~/...). The billing@acme-invoices.net in the prompt-injection example graphic is fictional payload — allowlisted in scrub.py.
Heads-up — prior-pass misses surface here. Earlier syncs left some PII in the committed repo (e.g. crm/workflows/workflow-4-link-calls.md and workflow-1-sync.md had real lead/company names; pre-call-research/SKILL.md had a private Google Sheets ID; crm/SKILL.md had a bare first-name example). Re-scan the whole file even when a name appears unchanged vs HEAD, and scrub for consistency. The report-pii mode + the private denylist exist to catch exactly these — run it after every sync. Note: the deterministic overwrite reintroduces real names where the committed repo had placeholders — check each name against the committed version and restore its placeholder.
Google Drive / Forms IDs: scrub.py only catches Notion 32-hex/UUID. Google Form/Sheet/Doc IDs (43-char mixed-case with -/_) and forms.gle / docs.google.com/.../d/<id> URLs must be scrubbed by hand → [form-id]/[sheet-id]/[page-id]. EXCEPTION: google-drive-helper's "Known Folders" table IDs are load-bearing and KEEP (committed precedent — folder IDs aren't access grants).
Workflow
1. Intake
- List source skills:
ls <source>/skills/
- List target repo skills:
ls <repo-root>
- For each skill Shaw requested, classify: NEW (not in repo), OVERWRITE (exists, refresh), or SKIP.
- Diff overlapping skills to confirm the source is actually newer before overwriting.
Present the plan to Shaw before writing anything. Include:
- List of NEW vs OVERWRITE skills
- Stale files in target that don't exist in source (candidates for deletion — ask before deleting)
- Confirmation of the scrub ruleset (see below)
2. PII scrub ruleset
KEEP AS-IS (public brand / load-bearing):
- "Shaw", "Shawhin", "@ShawhinTalebi"
- "AI Builder Academy", "ABA", "ABB", "Bootcamp"
shaw@aibuilder.academy (public business email — load-bearing in CRM workflow files)
- YouTube channel references, public positioning language
- Generic / illustrative dollar amounts (e.g., "$5K workshop" as example pricing)
- Generic Notion database names (e.g., "ABA Calls", "CRM", "Tasks", "SOPs") — useful for structure, not sensitive
notifications@calendly.com and similar system addresses
- Generic LinkedIn URLs, DOM selectors, public analytics paths (in scripts)
SCRUB → bracketed placeholder:
- Email addresses like
name@domain.com → [email]
- Real third-party person names →
[Person] / [Lead Name] / [Client Name] / [Consultant Name] (pick the most fitting)
- Real third-party company names →
[Company]
- Notion page and database IDs (32-char hex or UUID form, anywhere they appear — URLs,
<mention-page> tags, code blocks) → [page-id] / [database-id]
- Phone numbers →
[phone]
- Personal booking links with tokens →
[calendar-link]
- URLs to private Google Docs / Notion pages → strip the identifier portion
- LinkedIn profile URLs of non-public-figure leads →
[linkedin-url]
- Auth tokens, cookies, session IDs, hardcoded usernames in scripts →
[token]
- Dollar figures tied to a real named engagement (e.g., "$40K deal with AcmeCo") →
$[amount]
Rule of thumb: if uncertain whether something is real-person PII vs. illustrative, SCRUB. Err on the side of scrubbing.
Per-skill carve-outs:
linkedin-post-writer/references/examples-*.md — keep verbatim. These are Shaw's public LinkedIn posts; public-figure names (Karpathy, Hamel Husain, etc.) and real metrics stay.
email-helper/references/three-way-intros.md — the consultant roster must become a template + stubbed placeholder entries ([Consultant 1], [Consultant 2], …). Never commit real contact details.
linkedin-post-analytics/scripts/*.js — keep scraping logic, generic selectors, and public LinkedIn URL paths intact. Scrub only Shaw-specific identifiers (his LinkedIn user ID, cookies, auth tokens, hardcoded profile URLs).
crm/workflows/*.md — historically the densest source of Notion page/database IDs. Scrub aggressively.
3. Deterministic scrub pass (script)
Before spawning agents, run the regex scrubber at scripts/scrub.py. It copies source → target while replacing the deterministic categories: Notion IDs (32-char hex + UUID), emails (with allowlist for shaw@aibuilder.academy, notifications@calendly.com, placeholder/system addresses), phone numbers, Calendly + Google Doc/Sheet URLs, and forms.gle links. This handles the bulk of replacements (~70+ per full sync) with no agent tokens.
It also applies the private name/company denylist at pii-denylist.local.json (gitignored, never committed — see "Second source" above for why and how it's stored). Known real names/companies/IDs are auto-replaced (longest-match-first, possessive-safe); risky: true entries (bare first names) are report-only. An entry can carry allow_in: ["path/prefix", ...] — paths where that name is deliberately public and report-pii won't flag it (e.g. Toyota is allowed in the aba-brand-system "Ex-Toyota Data Scientist" bio line and proposal-deck-creator's Toyota/A3-methodology reference; Matt is allowed in sfc-helper/SKILL.md where it's public creator Matt Gray). Use allow_in instead of deleting an entry when a name is KEEP in some files but PII elsewhere. Keep-verbatim files (linkedin-post-writer/references/examples-*) are copied without scrubbing so public-figure names survive. When you scrub a NEW name by hand during a sync, add it to the denylist so it's caught automatically next time. Full-name entries don't catch bare-first-name uses of the same person (e.g. Niraiventhi Mareesan missed a bare Niraiventhi) — add distinctive bare names as separate entries.
python3 .claude/skills/sync-desktop-skills/scripts/scrub.py scrub \
--source ~/agents/skills \
--target . \
--skills crm,outreach,notion-helper,... \
--overwrite \
--log /tmp/sync-scrub.tsv
python3 .claude/skills/sync-desktop-skills/scripts/scrub.py verify --target .
python3 .claude/skills/sync-desktop-skills/scripts/scrub.py report-pii --target .
Notion-ID replacement uses a small left-context heuristic: [database-id] if the word "database"/"data_source" appears within 60 chars to the left, else [page-id]. Spot-check the log; agents can fix mis-classifications during their pass.
4. Parallel sub-agents (judgment-only)
After the script runs, spawn ~3 general-purpose sub-agents in parallel for judgment-only categories: real person names, company names, dollar amounts tied to real engagements, LinkedIn profile URLs of non-public-figures, and the carve-out files. The script has already handled deterministic patterns — agents must NOT re-do that work.
Typical batches (rough volume balance):
- Batch A — Notion-heavy: crm, outreach, notion-helper, executive-briefing, sop-helper, engagements, referrals, testimonials, stripe-helper (most names live here)
- Batch B — voice + people-heavy: email-helper (three-way-intros template carve-out), calendar-helper, pre-call-research, google-drive-helper, sfc-helper (sfc-helper is built from client-call research — read every line)
- Batch C — copy-heavy + Code-native: business-strategy, conversion-copy, linkedin-post-writer, linkedin-post-analytics, sales-letter-writer, workshop-use-case-researcher, youtube-strategy, aba-brand-system, skimmable-summary, proposal-deck-creator, plus the Code-native set (historically near-zero edits — but freshly synced upstream prose DOES reintroduce real client names, e.g. CyberCube/Evolution Equity in training-proposal, so still read everything)
Each sub-agent prompt must include:
- Repo root (target only — script already did the copy)
- Exact skill list for the batch
- The judgment-only scrub categories + KEEP-AS-IS list
- Per-skill carve-outs relevant to the batch (esp. linkedin-post-writer/examples-*.md keep-verbatim and email-helper/three-way-intros.md template-stub)
- Report format: per-skill replacement counts by category (zero is valid), examples, judgment calls flagged
- Hard boundary: do NOT commit, do NOT touch files outside assigned skill dirs, do NOT re-scrub deterministic patterns
4. Consolidation audit
After all agents return:
- Run the same PII greps across the whole repo (not just touched skills) to catch cross-skill inconsistencies
- Surface disagreements between agents — one agent may keep a pattern another scrubbed. Get Shaw's call on the inconsistent item and apply uniformly.
- Flag any stale files in target that don't exist in source (likely renamed upstream). Ask Shaw before deleting.
5. README maintenance
The Skills table is ordered simplest/most portable at top, heaviest deps at bottom. Tier order:
Any surface + None deps
Any + Web search
Any + single MCP (Gmail or Notion)
Any + multiple MCPs / mixed deps
Claude Chat or Cowork
Claude Code or Cowork + Chrome tool
Claude Code or Cowork + native/Python deps (skills-sync CLI, etc.)
Claude Code only — runtime-specific deps that break in Cowork (macOS-only apps like Keynote, ffmpeg-based pipelines, PDF tooling, etc.)
Alphabetize within each tier. Within tier 4, order by number of deps ascending, then alphabetical.
Surface labels (use exactly these strings):
Any
Claude Chat or Cowork — runs anywhere with the chat UI but not the CLI
Claude Code or Cowork — needs CLI tooling but works in either
Claude Code — CLI only; Cowork environment can't satisfy the deps
For each new skill, add a row. For deleted skills, remove the row. Update install-example URLs at the bottom if they point to a skill that no longer exists.
6. Pre-commit checklist
git diff --stat — sanity check file counts
- No
.DS_Store, no .env, no files outside intended skill dirs
scrub.py report-pii --target . → "Denylist leaks: none". This is the gate. Eyeball the candidate list for any real name/company not yet in the denylist; scrub it and add it to the denylist.
scrub.py verify --target . → clean (deterministic patterns)
- Repo-wide grep:
grep -rnE "[a-f0-9]{32}" --include="*.md" → should be empty
- Repo-wide grep:
grep -rnE "[a-z0-9._-]+@[a-z0-9.-]+\.(com|net|org|io|ai)" → should only show shaw@aibuilder.academy-adjacent hits and known system addresses
- Confirm
pii-denylist.local.json is gitignored and NOT staged (git check-ignore it)
- Confirm stale files were handled (deleted or preserved per Shaw's call)
7. Commit + push
Commit with a summary of what changed. Example:
Sync canonical skills: add X, update Y, remove Z
- Added: conversion-copy, sales-letter-writer, ...
- Updated: crm, email-helper, ...
- Removed: 3-way-intro (folded into email-helper)
- README table resorted by complexity
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Never push without explicit approval from Shaw.
Notes / precedent
- Past session established that most content-heavy skills (
conversion-copy, sales-letter-writer, most single-file skills) need zero scrubs — the PII concentrates in Notion-integrated skills and email examples.
- Typical scrub volumes to expect per full sync: ~50–60 Notion IDs, ~15–20 person names, ~5–10 emails, ~5–10 company names.
workflow-1-review.md was an example of upstream rename (→ workflow-1-sync.md) that left a stale file in target. Watch for similar renames.