[object Object]

← back to Tiktok Architect

tiktok-architect scout skill — initial build

eb4b7f37e8f16941d8a6f11ed523162f6d1c7714 · 2026-07-18 21:15:42 -0700 · Steve Abrams

Files touched

Diff

commit eb4b7f37e8f16941d8a6f11ed523162f6d1c7714
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Jul 18 21:15:42 2026 -0700

    tiktok-architect scout skill — initial build
---
 .gitignore                       |   9 +++
 README.md                        |  64 +++++++++++++++++++
 SKILL.md                         | 133 +++++++++++++++++++++++++++++++++++++++
 references/adoption-rubric.md    | 112 +++++++++++++++++++++++++++++++++
 references/retrieval-playbook.md |  75 ++++++++++++++++++++++
 references/sources.md            |  61 ++++++++++++++++++
 scripts/gh-repo-stats.js         | 123 ++++++++++++++++++++++++++++++++++++
 scripts/tt-list.js               |  80 +++++++++++++++++++++++
 scripts/tt-oembed.js             |  87 +++++++++++++++++++++++++
 scripts/tt-transcript.sh         |  77 +++++++++++++++++++++++
 10 files changed, 821 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4479347
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
+*.zip
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a9e93c6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,64 @@
+# tiktok-architect
+
+A Claude Code **skill** (its own git repo, symlinked into `~/.claude/skills/`)
+that scouts the **Claude Code / vibecoding tips and anti-patterns** posted by
+the TikTok creator **@tiktok_architect** (e.g. *"Never ask Claude code to do
+this!"*), **verifies each claim** against how Claude Code actually works, dedupes
+against Steve's existing rules, scores it, and drafts **gated** memos to codify
+the good ones into MEMORY.md / CLAUDE.md. Retrieval and scoring run freely;
+changing Steve's standing instructions is his call.
+
+## Why it exists
+
+@tiktok_architect's Claude content is a stream of short "never do X / always do
+Y / here's a trick" tips. That's a real signal — but short-form tips are
+engagement-optimized and often **outdated, model-version-stale, or wrong**. This
+skill is the **rule-scout** counterpart to `gurisingh-scout` (the tool-scout):
+where that one adopts open-source repos, this one adopts *behavioral rules*, so
+its first gate is **truth**, not stars. It turns the feed into adopt / watch /
+skip decisions instead of hype.
+
+## The two hard parts
+
+1. **Retrieval** — TikTok video pages are JS-rendered (HTML = the "Make Your
+   Day" shell) and there's no transcript API. The skill uses the no-JS
+   primitives: the oEmbed endpoint for a single link, yt-dlp's flat playlist for
+   a "what's new" sweep, and local-whisper on the audio only when the caption
+   isn't enough.
+2. **The mixed feed** — the creator also posts motorcycles, hobbies, and privacy
+   takes. Every scan filters to `claude_relevant` videos by hashtag/keyword.
+
+## Layout
+
+- `SKILL.md` — the procedure Claude runs (retrieve → filter → extract → VERIFY →
+  dedupe → score → deliver gated codify-shortlist).
+- `scripts/tt-oembed.js` — one TikTok URL (incl. `/t/` short link) → caption +
+  hashtags + author + `claude_relevant`. No JS, no auth, `$0`.
+- `scripts/tt-list.js` — a creator's newest N videos, each `claude_relevant`-
+  flagged, via yt-dlp. `$0`.
+- `scripts/tt-transcript.sh` — a tip's spoken text: yt-dlp subs → local whisper
+  → honest fallback. `$0`, optional.
+- `scripts/gh-repo-stats.js` — GitHub repo → stars/license/freshness, for tips
+  that name a tool. Honors `GITHUB_TOKEN`.
+- `references/sources.md` — the anchor handle, the mixed-feed filter, the
+  verify-the-claim principle, similar creators.
+- `references/retrieval-playbook.md` — the JS-wall-resistant retrieval order.
+- `references/adoption-rubric.md` — the correctness gate, six-dimension scoring,
+  dedupe, gating, output shape.
+
+## Quick checks
+
+```bash
+node scripts/tt-oembed.js "https://www.tiktok.com/@tiktok_architect/video/<id>"
+node scripts/tt-list.js tiktok_architect 12
+bash scripts/tt-transcript.sh "https://www.tiktok.com/@tiktok_architect/video/<id>"
+```
+
+## Invoke
+
+`/tiktok-architect` — or "tiktok architect", "what's tiktok_architect posting",
+"any new Claude Code tips worth adopting", or drop a `@tiktok_architect` link.
+
+> Note: the skill is named `tiktok-architect` (hyphen) to satisfy the
+> hyphen-case skill-naming rule; the creator's handle keeps its underscore
+> (`@tiktok_architect`).
diff --git a/SKILL.md b/SKILL.md
new file mode 100644
index 0000000..83436ef
--- /dev/null
+++ b/SKILL.md
@@ -0,0 +1,133 @@
+---
+name: tiktok-architect
+description: Scout the Claude Code / vibecoding tips and anti-patterns that TikTok creator @tiktok_architect posts (e.g. "Never ask Claude Code to do this!"), VERIFY each claim against how Claude Code actually works, dedupe against Steve's existing rules, score it, and draft gated memos to codify the good ones into MEMORY.md / CLAUDE.md. Use when Steve says "tiktok architect", "/tiktok-architect", "what is tiktok_architect posting", "scout the claude tiktok tips", "any new Claude Code tips worth adopting", "check @tiktok_architect", or drops a tiktok.com/@tiktok_architect link (or a /t/ short link) and asks what the tip is and whether to adopt it. The feed is MIXED (motorcycles, hobbies, AND Claude tips) so it filters to the on-topic videos; tips are treated as CLAIMS to verify, never as facts to relay.
+---
+
+# TikTok Architect Scout
+
+## Overview
+
+@tiktok_architect is a TikTok creator whose Claude-Code content is a stream of
+short tips and anti-patterns — *"Never ask Claude Code to do this!"*, *"Never
+ever ask Claude for this"*, *"How to get Claude Code for free"*. This skill
+harvests that stream, **verifies each claim against how Claude Code actually
+works**, and turns the true, useful ones into **codify / watch / skip** decisions
+for Steve's own standing rules — never a hype relay, and never an auto-edit of
+his instructions. Retrieval and scoring run freely; changing MEMORY.md or
+CLAUDE.md is gated.
+
+This is the **rule-scout** counterpart to `gurisingh-scout` (the tool-scout).
+Where that one adopts open-source repos, this one adopts *behavioral rules* — so
+its first gate is **truth**, not stars.
+
+## When to use
+
+- A periodic sweep: "what's @tiktok_architect posted lately that's worth
+  adopting as a rule?"
+- A one-off: Steve drops a `tiktok.com/@tiktok_architect/...` or `/t/<short>`
+  link and asks "what's the tip and should we do it?"
+- A wider sweep of TikTok/Shorts Claude-Code-tip creators, using
+  @tiktok_architect as the anchor plus any similar handles in
+  `references/sources.md`.
+
+## Workflow
+
+### 1. Retrieve (fan out — TikTok is JS-rendered and hostile)
+The video page HTML is useless (JS-rendered), so use the no-JS primitives.
+Follow `references/retrieval-playbook.md`:
+
+- **One dropped link** → clean caption + hashtags + author, `$0`:
+  ```bash
+  node scripts/tt-oembed.js "<tiktok-url-or-/t/short-link>"
+  ```
+- **"What's new" sweep** → newest videos, each `claude_relevant`-flagged, `$0`:
+  ```bash
+  node scripts/tt-list.js tiktok_architect 12
+  ```
+- **Caption too vague** → optional local transcript (`$0`, degrades honestly):
+  ```bash
+  bash scripts/tt-transcript.sh "<tiktok-video-url>"
+  ```
+
+### 2. Filter to the Claude/AI videos
+The feed is **mixed** — motorcycles, hobbies, privacy, AND Claude tips. Scout
+**only** the videos where `claude_relevant` is true. Do not treat every post as
+a tip. (See the mixed-feed note in `references/sources.md`.)
+
+### 3. Extract the candidate tip
+For each relevant video capture: **claim** (the assertion, restated as an
+imperative rule), **kind** (anti-pattern / best-practice / workflow / tool-repo /
+prompt-pack / setting), and the **specifics** (the *why* + the *instead-do-this*)
+from caption/on-screen/transcript. If the tip names a repo, resolve + enrich it:
+```bash
+node scripts/gh-repo-stats.js owner/repo
+```
+
+### 4. VERIFY the claim (the correctness gate — do this before anything else)
+A short-form tip is a CLAIM, and short-form claims are often outdated or wrong.
+Decide **is this true for CURRENT Claude Code?** using, in authority order: the
+`claude-code-guide` subagent, the `claude-docs` / `claude-api` skills (both `$0`),
+official docs (docs.claude.com / docs.anthropic.com), then a second web source
+via Exa/WebSearch if still unsure (metered — show cost). A claim that is false,
+version-stale, or unverifiable is **SKIP**, with a one-line "why it's wrong".
+Only verified-true claims proceed. (Full gate in `references/adoption-rubric.md`.)
+
+### 5. Dedupe against what Steve already codified
+Before crediting novelty, check for an existing equivalent rule:
+```bash
+grep -in "<keyword>" ~/.claude/CLAUDE.md 2>/dev/null
+grep -rin "<keyword>" ~/.claude/projects/*/memory/ 2>/dev/null | head
+grep -ril "<capability>" ~/.claude/skills/*/SKILL.md 2>/dev/null | head
+```
+If he already has it, the useful output is a **diff**, not a redundant re-add.
+
+### 6. Score & decide
+Apply the six-dimension rubric in `references/adoption-rubric.md` (correctness
+confidence, stack fit, novelty, leverage, adoption cost, safety-of-acting).
+Verdict bands: **≥14 CODIFY · 9–13 WATCH · ≤8 SKIP.**
+
+### 7. Deliver — shortlist + gated codify-memos
+Output a ranked table:
+`Tip (claim) | Video (id, date) | Kind | Verified? (source) | Dedupe | Score | Verdict | reason`.
+For each **CODIFY**, draft a memo to `~/.claude/yolo-queue/pending-approval/`
+with: the claim restated as a rule in Steve's voice, **why it's true + the
+source**, the exact proposed diff (a MEMORY.md feedback entry with frontmatter +
+**Why:** / **How to apply:** lines, OR a specific CLAUDE.md line), adoption cost
+(`$0` to codify), risks/caveats (loudly if the tip is dangerous), and an
+APPROVE/REVISE/BLOCK line.
+
+## Hard rules
+
+- **Verify before you codify.** A tip is a claim, not a fact. An unverified or
+  false "never do X" never becomes a rule — that's the whole point of this skill.
+- **Never auto-edit `CLAUDE.md` or write a rule-memory unprompted.** Adopting an
+  outside creator's claim as a standing rule is gated → pending-approval. The
+  deliverable is a decision-ready shortlist; adoption is Steve's call.
+- **Filter the mixed feed.** Only scout `claude_relevant` videos; ignore the
+  motorcycles/hobbies posts.
+- **A TikTok/throttle failure is not "nothing new."** Fall back to mirrors
+  (`references/retrieval-playbook.md`); never conclude "no new tips" from one
+  failed call.
+- **Flag dangerous tips loudly.** Some tips ("disable permission prompts",
+  "let it auto-push") are harmful — surface them as anti-recommendations, never
+  quietly codify them.
+- **Show costs** on every run (the scripts are `$0` local; Exa discovery is
+  metered). **Log real wins only** — a tip Steve actually adopts, not the scan.
+
+## Resources
+
+- `scripts/tt-oembed.js` — one TikTok URL (incl. `/t/` short link) → caption +
+  hashtags + author + `claude_relevant` flag. No JS, no auth, `$0`.
+- `scripts/tt-list.js` — a creator's newest N videos, each `claude_relevant`-
+  flagged, via yt-dlp flat playlist. `$0`. Exit 3 = throttled → use mirrors.
+- `scripts/tt-transcript.sh` — a tip's spoken text: yt-dlp subs → local whisper
+  on the audio → honest "no transcript" fallback. `$0`, optional.
+- `scripts/gh-repo-stats.js` — GitHub repo → stars/license/freshness +
+  Claude-skill detection, for tips that name an open-source tool. Honors
+  `GITHUB_TOKEN`.
+- `references/sources.md` — the anchor handle, the mixed-feed filter, the
+  verify-the-claim principle, retrieval primitives, similar creators.
+- `references/retrieval-playbook.md` — the ordered, JS-wall-resistant retrieval
+  procedure and failure-mode reads.
+- `references/adoption-rubric.md` — the correctness gate, the six-dimension
+  scoring model, the dedupe procedure, gating rules, and output shape.
diff --git a/references/adoption-rubric.md b/references/adoption-rubric.md
new file mode 100644
index 0000000..0204d39
--- /dev/null
+++ b/references/adoption-rubric.md
@@ -0,0 +1,112 @@
+# Adoption rubric — should this tip become one of Steve's rules?
+
+@tiktok_architect's feed is a firehose of *"never ask Claude Code to do X"* /
+*"always do Y"*. The job is NOT to relay hype — it's to answer, per tip:
+**codify, watch, or skip**, with a reason. Unlike a tool-scout, the thing being
+adopted is a **behavioral rule** (a MEMORY.md feedback entry or a CLAUDE.md
+line), so the first gate is truth.
+
+## The CORRECTNESS GATE (runs first — a hard filter, not a score)
+
+Before scoring anything, decide: **is this claim actually true for CURRENT
+Claude Code / the current Claude models?** Verify with, in order of authority:
+
+- the `claude-code-guide` subagent (features/hooks/CLI/MCP reality),
+- the `claude-docs` skill (skills/frontmatter) and `claude-api` skill
+  (models/pricing/tool-use), both `$0`,
+- official docs — docs.claude.com, docs.anthropic.com,
+- a second source via Exa/WebSearch if still unsure (metered — show cost).
+
+Verdict of the gate:
+- **FALSE / version-stale / can't be verified true** → **SKIP** immediately, with
+  a one-line "why it's wrong" (and the correct behavior, if worth noting). Never
+  score or codify an unverified behavioral claim.
+- **TRUE (or true-with-a-caveat)** → proceed to the six-dimension score below.
+
+This gate exists because short-form tips are engagement-optimized and routinely
+outdated. A catchy wrong rule baked into MEMORY.md is worse than no rule.
+
+## Dimensions (score each 0–3, 18 max — only for tips that PASS the gate)
+
+1. **Correctness confidence (0–3)** — how solidly verified? 3 = confirmed by an
+   authoritative source; 1 = plausible, thinly sourced; 0 shouldn't reach here
+   (the gate caught it). A caveat-laden "true only sometimes" caps this at 2.
+2. **Stack fit (0–3)** — does it apply to how Steve actually uses Claude Code?
+   High: skills/subagents/CLAUDE.md/MEMORY.md/hooks discipline, permissions,
+   cost/context management, catalog/scraper/DW pipelines, git hygiene. Low:
+   generic influencer platitudes, things irrelevant to his workflow.
+3. **Novelty vs. what Steve already codified (0–3)** — 3 = fills a real gap; 0 =
+   already a rule in CLAUDE.md / MEMORY.md / an existing skill. **Always run the
+   dedupe check below before scoring this.**
+4. **Leverage (0–3)** — does following it save real hours / avoid a real failure
+   / cut cost, or is it cargo-cult ritual? Favor "measurable" over "feels
+   productive" (Steve's theater-vs-real test). A tip with a concrete *why* beats
+   a bare assertion.
+5. **Adoption cost / reversibility (0–3, higher = cheaper)** — 3 = a one-line
+   MEMORY.md feedback entry, trivially reversible; 1 = needs a workflow change; 0
+   = demands new paid tooling or a big rewrite to follow.
+6. **Safety of ACTING on it (0–3, higher = safer)** — 3 = pure behavioral/local,
+   no risk; 0 = the tip itself is dangerous ("disable all permission prompts",
+   "let it auto-push", "run with --dangerously-skip"). A dangerous tip is not
+   auto-DQ, but it's flagged loudly and the memo must warn against it.
+
+**Verdict bands (after the gate passes):** ≥14 → **CODIFY** (draft the
+MEMORY.md/CLAUDE.md memo). 9–13 → **WATCH** (log it; revisit if it recurs or a
+second creator repeats it). ≤8 → **SKIP** (one-line why).
+
+## Dedupe against what Steve already codified (before scoring novelty)
+
+Steve's standing behavioral rules already live in `~/.claude/CLAUDE.md` and the
+auto-memory. Check for an existing equivalent BEFORE crediting novelty:
+
+```bash
+# behavioral rules already in the global instructions
+grep -in "<keyword>" ~/.claude/CLAUDE.md 2>/dev/null
+# already-saved memories (index + bodies)
+grep -rin "<keyword>" ~/.claude/projects/*/memory/ 2>/dev/null | head
+# an existing skill that already encodes the practice
+grep -ril "<capability>" ~/.claude/skills/*/SKILL.md 2>/dev/null | head
+```
+
+Examples of "he already has it": context/compaction discipline → CLAUDE.md
+compaction rules + `token-use`; verify-before-claiming-done → `validation-loop`;
+gated/irreversible actions → the whole yolo/officer gating model; cost visibility
+→ the always-show-costs rule + `cost-tracker`; prompt-cache hygiene →
+`prompt-cache-optimization`. If an equivalent exists, the useful output is a
+**diff** ("the tip adds a specific threshold his rule doesn't state"), not a
+redundant re-add.
+
+## Gating — what the scout may and may NOT do
+
+**Retrieval, transcript, verification, dedupe, scoring, and writing the
+shortlist are reversible and run freely.** Everything that changes Steve's
+standing configuration or spends is gated — draft it to
+`~/.claude/yolo-queue/pending-approval/` with an APPROVE/REVISE/BLOCK block;
+never auto-apply:
+
+- **Editing `~/.claude/CLAUDE.md`** (his global standing instructions) — always
+  gated. This is behavior-changing and irreversible-ish; Steve approves it.
+- **Writing a new MEMORY.md feedback/user rule** derived from an external tip —
+  gated. (The memory system lets Claude save memories, but adopting an outside
+  creator's claim as a standing rule is a judgment change Steve signs off on.)
+- Anything the tip tells you to *install/run* (a repo, an MCP, a CLI, a paid
+  key) — gated; route the tool half through `gurisingh-scout`'s adoption path.
+- Anything customer-facing / destructive / outbound the tip would trigger.
+
+The deliverable is a **decision-ready shortlist of rules**, not edited
+instructions. Adoption is Steve's call.
+
+## Output shape (per run)
+
+Produce a ranked table:
+`Tip (claim) | Video (id, date) | Kind | Verified? (source) | Dedupe (have/gap) | Score | Verdict | 1-line reason`.
+
+Then, for each **CODIFY**, a short pending-approval memo containing:
+- the claim, restated as an imperative rule in Steve's voice,
+- **why it's true** + the authoritative source that confirmed it,
+- the exact proposed diff — either a `MEMORY.md` feedback entry (with frontmatter
+  + **Why:** / **How to apply:** lines) or a specific CLAUDE.md line/section,
+- adoption cost (`$0` to codify; note any tooling the tip implies), risks/caveats
+  (loudly if the tip is dangerous), and an APPROVE/REVISE/BLOCK line.
+
+Log genuine wins only — a tip Steve actually adopts as a rule, not the scan.
diff --git a/references/retrieval-playbook.md b/references/retrieval-playbook.md
new file mode 100644
index 0000000..464189e
--- /dev/null
+++ b/references/retrieval-playbook.md
@@ -0,0 +1,75 @@
+# Retrieval playbook — getting a tip's real content off TikTok
+
+TikTok is hostile to reading: video pages are JS-rendered (HTML = the "Make Your
+Day" shell), there is no public transcript API, and short `/t/<code>` links are
+redirects. The answer is the same discipline as `gurisingh-scout`: **fan out
+across the no-JS primitives, take the first that yields the caption + claim, and
+never conclude "no new tips" from one failure.**
+
+## Step order (stop as soon as you have the tip's CLAIM)
+
+1. **A single dropped link → `tt-oembed.js`.**
+   ```bash
+   node scripts/tt-oembed.js "https://www.tiktok.com/t/ZP8GY4Rgo/"
+   ```
+   Follows the redirect to the canonical `@handle/video/<id>`, returns caption +
+   hashtags + author + `claude_relevant`. `$0`, no auth. For most tips the
+   caption already states the claim ("Never ask Claude code to do this!").
+
+2. **A "what's new" sweep → `tt-list.js`.**
+   ```bash
+   node scripts/tt-list.js tiktok_architect 12
+   ```
+   Newest N videos, each flagged `claude_relevant`. Scout ONLY the flagged ones
+   — the feed is mixed (motorcycles, hobbies). `$0`. The "attempting
+   impersonation" WARNING on stderr is normal, not a failure.
+
+3. **Caption too vague → `tt-transcript.sh`** (optional, `$0` local).
+   ```bash
+   bash scripts/tt-transcript.sh "https://www.tiktok.com/@tiktok_architect/video/<id>"
+   ```
+   Tries yt-dlp subtitles first (usually empty on TikTok), then LOCAL whisper on
+   the downloaded audio. If no local whisper exists it prints the audio path and
+   says so — it never fabricates a transcript. Reach for this only when the
+   claim can't be pinned from the caption + on-screen premise.
+
+4. **Verify / second-source the claim → Exa or WebSearch.** Search the tip's
+   phrasing to find reposts, write-ups, or discussion (Reddit/X/blogs) — and,
+   critically, to check whether the claim is TRUE for current Claude Code. Exa
+   is metered; show/log the cost. Also consult the `claude-code-guide` subagent
+   and the `claude-docs` / `claude-api` skills — those are `$0` and
+   authoritative for "is this actually how Claude Code works."
+
+5. **Tip names an open-source tool/repo → `gh-repo-stats.js`.**
+   ```bash
+   node scripts/gh-repo-stats.js owner/repo
+   ```
+   Some tips ("get Claude Code for free", "install this MCP") point at a repo.
+   Enrich it (stars / license / freshness / is-claude-skill) exactly like the
+   tool-scout, and hand the tool half to `gurisingh-scout`'s adoption path.
+
+## Turning a video into a candidate tip
+
+For each `claude_relevant` video, capture:
+
+- **claim** — the one-sentence assertion ("never ask Claude Code to write your
+  whole app in one prompt"), normalized to an imperative rule.
+- **kind** — anti-pattern ("never do X") | best-practice ("always do X") |
+  workflow | tool/repo | prompt-pack | setting/config.
+- **specifics** — the *why* and the *instead-do-this*, from caption/on-screen/
+  transcript. A tip with no reasoning is weaker (see rubric Leverage).
+- **references a repo?** — if yes, resolve + enrich it with `gh-repo-stats.js`.
+
+## Failure modes to expect (and how to read them)
+
+- **`curl`/WebFetch on the video page returns "Make Your Day"** → expected; the
+  page is JS-rendered. Use oEmbed/yt-dlp, never the raw HTML.
+- **`tt-list.js` exits 3 (0 videos)** → TikTok throttled or blocked, NOT "no new
+  tips". Retry once, then fall back to Exa/WebSearch for recent reposts.
+- **yt-dlp "attempting impersonation" WARNING** → informational. If a *download*
+  (audio) actually fails, install a curl_cffi impersonate target
+  (`yt-dlp#impersonation`) or accept caption-only for that video.
+- **No subtitles on a video** → normal for TikTok. Use audio+local-whisper, or
+  just work from the caption + on-screen premise.
+- **The tip can't be verified either way** → do NOT codify it. Mark it WATCH and
+  say so; an unverifiable behavioral claim is not a rule.
diff --git a/references/sources.md b/references/sources.md
new file mode 100644
index 0000000..1a2e491
--- /dev/null
+++ b/references/sources.md
@@ -0,0 +1,61 @@
+# Sources — where the tips come from
+
+## Primary subject
+
+- **@tiktok_architect** — TikTok creator (`tiktok.com/@tiktok_architect`),
+  "AI Educator" genre. Posts short-form **Claude Code / vibecoding** tips and
+  anti-patterns. The seed video for this skill: *"Never ask Claude code to do
+  this!"* (`#claude #claudecode #techtok #chatgptprompts #vibecoding`).
+- **What he posts (the signal this skill harvests):** recurring formats like
+  *"Never ask Claude Code to do X"*, *"Never ever ask Claude for this"*, *"How
+  to get Claude Code for free"*, *"3 prompts that…"* — short spoken tips with the
+  claim in the caption + on-screen text.
+
+## ⚠️ The feed is MIXED — filter before you spend effort
+
+@tiktok_architect is **not** a pure Claude-Code channel. Recent uploads
+interleave motorcycles (`#motorcyclesoftiktok #hondarebel`), hobbies
+(`#hobbiesinyour20s`), and privacy takes with the Claude tips. **Only scout the
+Claude/AI-coding videos.** `tt-list.js` sets `claude_relevant` per video by
+hashtag/keyword — trust that flag and skip the rest. Never treat every post as a
+tip.
+
+## ⚠️ Tips are CLAIMS, not facts — verify before codifying
+
+This is the core difference from a tool-scout. A behavioral tip ("never ask
+Claude Code to X") is engagement-optimized and frequently **outdated, model/
+version-stale, or just wrong**. The rubric's first gate is **correctness** — a
+tip that isn't true for *current* Claude Code is SKIP no matter how catchy.
+Verify against the `claude-code-guide` subagent, the `claude-docs` / `claude-api`
+skills, and official docs (docs.claude.com / docs.anthropic.com) BEFORE it is
+ever proposed for MEMORY.md.
+
+## Retrieval sources, in priority order
+
+1. **oEmbed** (`scripts/tt-oembed.js`) — for a single dropped link (incl. a
+   `/t/<short>` redirect). Returns caption + hashtags + author + a
+   `claude_relevant` flag. No JS, no auth, `$0`. The reliable primitive.
+2. **yt-dlp creator listing** (`scripts/tt-list.js`) — "what's new": the newest
+   N videos with captions + relevance flags. `$0`. Prints an "attempting
+   impersonation" WARNING even on success — that's not an error.
+3. **Transcript** (`scripts/tt-transcript.sh`) — ONLY when the caption doesn't
+   pin the tip's specifics. Tries yt-dlp subs (usually empty on TikTok), then
+   LOCAL whisper on the audio (`$0`). Optional — most tips are identifiable from
+   the caption alone.
+4. **Exa / WebSearch mirrors** — when TikTok throttles or a claim needs a
+   second source: search the tip's phrasing (e.g. `tiktok_architect "never ask
+   claude code"`) to find reposts, article write-ups, or the same tip discussed
+   on Reddit/X. Exa is metered — show/log the cost.
+5. **The direct video HTML is JS-rendered** — `curl`/WebFetch on it returns only
+   the "Make Your Day" shell. Never rely on it; use oEmbed/yt-dlp.
+
+## Similar creators worth folding into a wider sweep
+
+The skill's value is "surface Claude Code / AI-coding tips worth adopting."
+@tiktok_architect is the anchor; the same signal shows up from other TikTok/
+Shorts AI-coding educators. Add handles here as they prove useful (keep the
+mixed-feed relevance filter on all of them). Cross-reference the tool-side
+scout `gurisingh-scout` when a tip actually names an open-source repo.
+
+Keep this file current: when a new mirror/source proves reliable, or a creator
+stops being worth watching, edit it here so the next run inherits the lesson.
diff --git a/scripts/gh-repo-stats.js b/scripts/gh-repo-stats.js
new file mode 100755
index 0000000..1a81c4f
--- /dev/null
+++ b/scripts/gh-repo-stats.js
@@ -0,0 +1,123 @@
+#!/usr/bin/env node
+/**
+ * gh-repo-stats.js — turn a GitHub repo link into hard adoption signals.
+ *
+ * Guri Singh's posts almost always point at an open-source repo. Before scoring
+ * whether it's worth adopting, resolve the objective facts: popularity, license,
+ * how recently it was touched, primary language, archived/stale status, and —
+ * critically for us — whether it is literally a Claude Code skill/plugin
+ * (SKILL.md, .claude/, plugin manifest) versus a general CLI/tool.
+ *
+ * Usage:
+ *   node gh-repo-stats.js <github-url-or-owner/repo>
+ *
+ * Auth: unauthenticated works (60 req/hr). Set GITHUB_TOKEN in env to raise the
+ * limit to 5000/hr — the script reads it automatically if present.
+ *
+ * Exit codes: 0 ok, 1 error (bad input / not found / network).
+ * Output: JSON summary on stdout.
+ */
+
+'use strict';
+
+function parseRepo(arg) {
+  if (!arg) return null;
+  const m = String(arg).match(/github\.com\/([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+)/);
+  if (m) return { owner: m[1], repo: m[2].replace(/\.git$/, '') };
+  const slug = String(arg).match(/^([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+)$/);
+  if (slug) return { owner: slug[1], repo: slug[2] };
+  return null;
+}
+
+function ghHeaders() {
+  const h = {
+    'User-Agent': 'tiktok-architect-scout',
+    Accept: 'application/vnd.github+json',
+  };
+  const tok = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
+  if (tok) h.Authorization = `Bearer ${tok}`;
+  return h;
+}
+
+async function gh(url) {
+  const res = await fetch(url, { headers: ghHeaders() });
+  if (res.status === 404) return { __status: 404 };
+  if (res.status === 403) return { __status: 403, __rate: res.headers.get('x-ratelimit-remaining') };
+  if (!res.ok) throw new Error(`http ${res.status} ${url}`);
+  return res.json();
+}
+
+function daysSince(iso) {
+  if (!iso) return null;
+  return Math.round((Date.now() - new Date(iso).getTime()) / 86400000);
+}
+
+async function main() {
+  const parsed = parseRepo(process.argv[2]);
+  if (!parsed) {
+    console.error('usage: node gh-repo-stats.js <github-url-or-owner/repo>');
+    process.exit(1);
+  }
+  const { owner, repo } = parsed;
+
+  let meta;
+  try {
+    meta = await gh(`https://api.github.com/repos/${owner}/${repo}`);
+  } catch (e) {
+    console.error(`error: ${e.message}`);
+    process.exit(1);
+  }
+  if (meta.__status === 404) {
+    console.error(`not found: ${owner}/${repo}`);
+    process.exit(1);
+  }
+  if (meta.__status === 403) {
+    console.error(`rate-limited (remaining=${meta.__rate}). Set GITHUB_TOKEN to raise the limit.`);
+    process.exit(1);
+  }
+
+  // Root listing → detect "is this a Claude skill/plugin?" (one extra call).
+  let isClaudeSkill = false;
+  let claudeMarkers = [];
+  try {
+    const contents = await gh(`https://api.github.com/repos/${owner}/${repo}/contents`);
+    if (Array.isArray(contents)) {
+      const names = contents.map((c) => c.name.toLowerCase());
+      const markers = ['skill.md', '.claude', 'skills', 'plugin.json', '.claude-plugin', 'commands', 'agents'];
+      claudeMarkers = markers.filter((m) => names.includes(m));
+      isClaudeSkill = names.includes('skill.md') || names.includes('.claude') ||
+        names.includes('.claude-plugin') ||
+        (names.includes('skills') && (names.includes('plugin.json') || names.includes('.claude-plugin')));
+    }
+  } catch {
+    /* non-fatal — leave detection empty */
+  }
+
+  const daysStale = daysSince(meta.pushed_at);
+  const out = {
+    repo: `${owner}/${repo}`,
+    url: meta.html_url,
+    description: meta.description,
+    stars: meta.stargazers_count,
+    forks: meta.forks_count,
+    open_issues: meta.open_issues_count,
+    language: meta.language,
+    license: meta.license ? meta.license.spdx_id : 'NONE',
+    pushed_at: meta.pushed_at,
+    days_since_push: daysStale,
+    archived: meta.archived,
+    is_claude_skill: isClaudeSkill,
+    claude_markers: claudeMarkers,
+    // quick, opinionated flags to feed the adoption rubric
+    flags: {
+      unlicensed: !meta.license,
+      stale: daysStale != null && daysStale > 120,
+      archived: !!meta.archived,
+      low_traction: meta.stargazers_count < 50,
+    },
+  };
+  console.log(JSON.stringify(out, null, 2));
+  process.exit(0);
+}
+
+main();
diff --git a/scripts/tt-list.js b/scripts/tt-list.js
new file mode 100755
index 0000000..377336c
--- /dev/null
+++ b/scripts/tt-list.js
@@ -0,0 +1,80 @@
+#!/usr/bin/env node
+/**
+ * tt-list.js — list a TikTok creator's most-recent videos and flag the ones
+ * that are actually about Claude Code / AI-coding (the "what's new" scan).
+ *
+ * @tiktok_architect posts a MIXED feed — motorcycles, hobbies, privacy, AND
+ * Claude Code / vibecoding tips. This enumerates recent uploads via yt-dlp's
+ * flat playlist (id + caption, no download, $0) and marks each `claude_relevant`
+ * by hashtag/keyword so the scout only spends effort on the on-topic ones.
+ *
+ * Usage:
+ *   node tt-list.js [handle] [count]
+ *     handle  TikTok handle without @  (default: tiktok_architect)
+ *     count   how many newest videos to list (default: 12)
+ *
+ * Requires yt-dlp on PATH. TikTok may throttle; on failure this exits non-zero
+ * with a hint to fall back to the mirrors in references/retrieval-playbook.md.
+ * yt-dlp prints an "attempting impersonation" WARNING even on success — that is
+ * NOT an error. If downloads later fail, install a curl_cffi impersonate target.
+ *
+ * Output: JSON array on stdout, newest first:
+ *   [{ id, url, caption, hashtags[], claude_relevant }]
+ */
+
+'use strict';
+
+const { spawnSync } = require('child_process');
+
+const RELEVANCE_RE =
+  /\b(claude|claudecode|claude-code|anthropic|vibecod|cursor|codex|copilot|chatgpt|openai|\bllm\b|\bai\b|aitools|aiagent|agentic|mcp|prompt|coding|developer|softwareengineer|programming)\b/i;
+
+function hashtagsFrom(caption) {
+  const tags = new Set();
+  const re = /#([\p{L}\p{N}_]+)/gu;
+  let m;
+  while ((m = re.exec(caption || ''))) tags.add(m[1].toLowerCase());
+  return [...tags];
+}
+
+function main() {
+  const handle = (process.argv[2] || 'tiktok_architect').replace(/^@/, '');
+  const count = Math.max(1, parseInt(process.argv[3] || '12', 10) || 12);
+  const url = `https://www.tiktok.com/@${handle}`;
+
+  const r = spawnSync(
+    'yt-dlp',
+    ['--flat-playlist', '--playlist-end', String(count), '--dump-json', url],
+    { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, timeout: 90_000 }
+  );
+
+  if (r.error && r.error.code === 'ENOENT') {
+    console.error('yt-dlp not found on PATH. brew install yt-dlp — or use the mirror path in references/retrieval-playbook.md.');
+    process.exit(2);
+  }
+
+  const rows = [];
+  for (const line of (r.stdout || '').split('\n')) {
+    if (!line.trim()) continue;
+    let d;
+    try { d = JSON.parse(line); } catch { continue; }
+    const caption = d.title || d.description || '';
+    rows.push({
+      id: d.id || null,
+      url: d.url || (d.id ? `https://www.tiktok.com/@${handle}/video/${d.id}` : null),
+      caption,
+      hashtags: hashtagsFrom(caption),
+      claude_relevant: RELEVANCE_RE.test(caption),
+    });
+  }
+
+  if (!rows.length) {
+    console.error('yt-dlp returned 0 videos (throttle/block or empty feed). Fall back to mirrors — do NOT conclude "no new tips".');
+    if (r.stderr) console.error(r.stderr.split('\n').slice(-3).join('\n'));
+    process.exit(3);
+  }
+
+  console.log(JSON.stringify(rows, null, 2));
+}
+
+main();
diff --git a/scripts/tt-oembed.js b/scripts/tt-oembed.js
new file mode 100755
index 0000000..0d637e7
--- /dev/null
+++ b/scripts/tt-oembed.js
@@ -0,0 +1,87 @@
+#!/usr/bin/env node
+/**
+ * tt-oembed.js — resolve ONE TikTok URL to clean metadata WITHOUT auth or JS.
+ *
+ * TikTok video pages are JS-rendered, so WebFetch/curl on the HTML returns only
+ * the generic "Make Your Day" shell. The public oEmbed endpoint
+ * (tiktok.com/oembed?url=...) returns the caption (as `title`), author, and
+ * thumbnail as plain JSON — no login, no browser, $0. This is the reliable way
+ * to turn a dropped link (including a /t/<short> redirect) into a candidate.
+ *
+ * Usage:
+ *   node tt-oembed.js <tiktok-url>            # canonical or /t/<short> link
+ *
+ * Examples:
+ *   node tt-oembed.js https://www.tiktok.com/t/ZP8GY4Rgo/
+ *   node tt-oembed.js https://www.tiktok.com/@tiktok_architect/video/7643451753400241421
+ *
+ * Exit codes: 0 ok, 1 error (bad input / not found / network).
+ * Output: a JSON object on stdout:
+ *   { id, url, author, caption, hashtags[], claude_relevant, thumbnail }
+ * `claude_relevant` is a cheap keyword flag so the scout can skip the creator's
+ * off-topic posts (motorcycles, hobbies) without a second call.
+ */
+
+'use strict';
+
+const RELEVANCE_RE =
+  /\b(claude|claudecode|claude-code|anthropic|vibecod|cursor|codex|copilot|chatgpt|openai|\bllm\b|\bai\b|aitools|aiagent|agentic|mcp|prompt|coding|developer|softwareengineer|programming)\b/i;
+
+function hashtagsFrom(caption) {
+  const tags = new Set();
+  const re = /#([\p{L}\p{N}_]+)/gu;
+  let m;
+  while ((m = re.exec(caption || ''))) tags.add(m[1].toLowerCase());
+  return [...tags];
+}
+
+// Follow redirects (the /t/<short> links are 301s) to the canonical URL, then
+// pull the numeric video id out of .../video/<id>.
+async function resolve(url) {
+  let res;
+  try {
+    res = await fetch(url, { redirect: 'follow', headers: { 'User-Agent': 'Mozilla/5.0' } });
+  } catch (e) {
+    // network hiccup on the redirect hop — fall back to the input URL
+    return { canonical: url, id: (url.match(/video\/(\d{5,25})/) || [])[1] || null };
+  }
+  const canonical = res.url || url;
+  const id = (canonical.match(/video\/(\d{5,25})/) || [])[1] || null;
+  return { canonical, id };
+}
+
+async function main() {
+  const input = process.argv[2];
+  if (!input || !/tiktok\.com/i.test(input)) {
+    console.error('usage: node tt-oembed.js <tiktok-url>');
+    process.exit(1);
+  }
+
+  const { canonical, id } = await resolve(input);
+
+  const oe = `https://www.tiktok.com/oembed?url=${encodeURIComponent(canonical)}`;
+  const res = await fetch(oe, { headers: { 'User-Agent': 'Mozilla/5.0' } });
+  if (!res.ok) {
+    console.error(`oembed http ${res.status} for ${canonical}`);
+    process.exit(1);
+  }
+  const j = await res.json();
+
+  const caption = j.title || '';
+  const hashtags = hashtagsFrom(caption);
+  const out = {
+    id: id || j.embed_product_id || null,
+    url: canonical,
+    author: j.author_name || j.author_unique_id || null,
+    caption,
+    hashtags,
+    claude_relevant: RELEVANCE_RE.test(caption),
+    thumbnail: j.thumbnail_url || null,
+  };
+  console.log(JSON.stringify(out, null, 2));
+}
+
+main().catch((e) => {
+  console.error(String((e && e.message) || e));
+  process.exit(1);
+});
diff --git a/scripts/tt-transcript.sh b/scripts/tt-transcript.sh
new file mode 100755
index 0000000..a98b2a5
--- /dev/null
+++ b/scripts/tt-transcript.sh
@@ -0,0 +1,77 @@
+#!/usr/bin/env bash
+# tt-transcript.sh — get the spoken text of ONE TikTok tip video, $0 and local.
+#
+# Most @tiktok_architect videos expose NO subtitles (verified — yt-dlp lists an
+# empty subtitle set), and the actual "do this / never do that" detail is spoken
+# aloud + shown on-screen, not in the caption. This tries, in order:
+#   1) yt-dlp subtitles / auto-captions (instant, $0) — usually empty on TikTok
+#   2) download audio + LOCAL whisper transcription ($0, no API) if a local
+#      whisper is installed (whisper / whisper-cli / mlx_whisper)
+#   3) if no local whisper: leave the audio file and print its path + the next
+#      step, honestly — do NOT claim a transcript you don't have.
+#
+# Usage:  bash tt-transcript.sh <tiktok-video-url> [outdir]
+# Output: prints the transcript to stdout when it can produce one; otherwise
+#         prints WHERE the audio landed and what to run. Never fabricates text.
+#
+# NOTE: transcription is OPTIONAL. For most tips the oEmbed caption + the video
+# premise is enough to identify the CLAIM to verify. Reach for this only when a
+# tip's specifics can't be pinned down from the caption.
+
+set -euo pipefail
+URL="${1:-}"
+OUT="${2:-$(mktemp -d -t ttarch)}"
+[ -z "$URL" ] && { echo "usage: bash tt-transcript.sh <tiktok-video-url> [outdir]" >&2; exit 1; }
+command -v yt-dlp >/dev/null 2>&1 || { echo "yt-dlp not found. brew install yt-dlp" >&2; exit 2; }
+mkdir -p "$OUT"
+
+# --- 1) try real / auto subtitles (instant, $0) ---
+yt-dlp --skip-download --write-subs --write-auto-subs --sub-format vtt \
+       --sub-langs "en.*,en" -o "$OUT/%(id)s.%(ext)s" "$URL" >/dev/null 2>&1 || true
+VTT="$(ls "$OUT"/*.vtt 2>/dev/null | head -1 || true)"
+if [ -n "${VTT:-}" ] && [ -s "$VTT" ]; then
+  echo "# transcript source: yt-dlp subtitles ($VTT)  cost: \$0" >&2
+  # strip WEBVTT header, cue timestamps, and de-dupe consecutive lines
+  grep -vE '^(WEBVTT|Kind:|Language:|[0-9]{2}:[0-9]{2}|$)' "$VTT" \
+    | sed -E 's/<[^>]+>//g' | awk '!seen[$0]++'
+  exit 0
+fi
+
+# --- 2) download audio, then LOCAL whisper if present ($0) ---
+echo "# no subtitles on this video — downloading audio for local transcription…" >&2
+yt-dlp -x --audio-format mp3 --audio-quality 5 -o "$OUT/%(id)s.%(ext)s" "$URL" >/dev/null 2>&1 || {
+  echo "audio download failed (TikTok throttle or missing curl_cffi impersonate target)." >&2
+  echo "see references/retrieval-playbook.md → impersonation note." >&2
+  exit 3
+; }
+MP3="$(ls "$OUT"/*.mp3 2>/dev/null | head -1 || true)"
+[ -z "${MP3:-}" ] && { echo "no audio file produced." >&2; exit 3; }
+
+run_whisper() {
+  # try the common local whisper CLIs, cheapest/fastest first — all $0 local
+  if command -v mlx_whisper >/dev/null 2>&1; then
+    echo "# transcript source: mlx_whisper (local, \$0)" >&2
+    mlx_whisper "$MP3" --output-dir "$OUT" --output-format txt >/dev/null 2>&1 \
+      && cat "${MP3%.mp3}.txt" 2>/dev/null && return 0
+  fi
+  if command -v whisper-cli >/dev/null 2>&1; then
+    echo "# transcript source: whisper-cli (local, \$0)" >&2
+    whisper-cli -f "$MP3" -otxt -of "${MP3%.mp3}" >/dev/null 2>&1 \
+      && cat "${MP3%.mp3}.txt" 2>/dev/null && return 0
+  fi
+  if command -v whisper >/dev/null 2>&1; then
+    echo "# transcript source: openai-whisper (local, \$0)" >&2
+    whisper "$MP3" --model base --output_dir "$OUT" --output_format txt >/dev/null 2>&1 \
+      && cat "$OUT/$(basename "${MP3%.mp3}").txt" 2>/dev/null && return 0
+  fi
+  return 1
+}
+
+if run_whisper; then exit 0; fi
+
+# --- 3) honest fallback: no local transcriber available ---
+echo "NO_TRANSCRIPT: no local whisper found (tried mlx_whisper / whisper-cli / whisper)." >&2
+echo "Audio saved to: $MP3" >&2
+echo "Next step (still \$0 local): install a local whisper, OR read the tip's CLAIM" >&2
+echo "from the oEmbed caption + on-screen text instead of a full transcript." >&2
+exit 4

(oldest)  ·  back to Tiktok Architect  ·  tiktok-architect: decode the ironic 'never ask Claude' forma 434fa7c →