Skip to content

feat(cli): iris peers — watch peer accounts, list what worked that you haven't covered (FL-CO-112) - #348

Open
mayoalexander wants to merge 4 commits into
mainfrom
wt/191297-peer-watch
Open

mayoalexander wants to merge 4 commits into
mainfrom
wt/191297-peer-watch

Conversation

@mayoalexander

Copy link
Copy Markdown

Atlas ticket: #191297 (FL-CO-112 "Watch peer artists"), epic #191300

What

A new top-level command, iris peers. It watches peer and competitor accounts and lists the topics that did well for them that you haven't covered yet.

  • iris peers add <handle-or-url...> [--platform instagram|tiktok|youtube] [--label scene] [--mine], remove <handle>, list [--json]. The watch list is stored in ~/.iris/peers/peers.json and honours IRIS_HOME (or IRIS_PEERS_HOME). Only one account can be --mine.
  • iris peers pull [handle...] [--latest 12] [--transcribe] [--cookies <file>] [--urls <post-url...>] [--flat] [--delay 3] [--json]
    • Lists each account's latest N posts with yt-dlp in metadata mode (--skip-download --dump-json --playlist-end N --ignore-errors). No video is downloaded.
    • Saves id, url, date, title, caption, views, likes, comments and duration to ~/.iris/peers/<id>/posts.json. Posts are merged by id with first_seen_at and seen_at. A later pull that has fewer fields never erases fields an earlier pull found.
    • Runs one account at a time, with a delay between accounts.
    • --transcribe downloads audio only for posts not transcribed yet, then runs on-device whisper.cpp (transcribeLocal). It never falls back to a server upload, and an empty transcript is not redone.
    • When Instagram won't list a profile, the command says so in plain words for that account (suggests --cookies <cookies.txt> and the --urls fallback) and carries on with the other accounts.
    • Nothing is scheduled. --help shows a crontab line the user can add themselves.
  • iris peers gaps [--top 10] [--days 30] [--threshold 0.5] [--sort relative|views] [--all] [--json]
    • Ranks peer posts in the window by their metric relative to that account's own median, so a small account's breakout counts. Raw views are the tie-break, or the main sort with --sort views.
    • Pulls topic keywords from title, caption and transcript: lowercased, stopwords, URLs and @mentions removed, reach-bait hashtags (#fyp) dropped, and two-word phrases only from words that sat next to each other. Hashtags an account puts on 60% or more of its posts are left out of topic labels.
    • Marks each post covered or not covered by word overlap with the --mine account's posts. No LLM; the same input always gives the same output.

download.ts now exports downloadFile, metaAuthor and metaDate, which peers reuses. metadataToMarkdown uses the same two helpers. yt-dlp is called through the shared fetchWithYtDlp in media-fetch.ts.

iris hive peers is a subcommand of hive, so the top-level peers name is free. command-collisions.test.ts doesn't report a new collision.

Why

A creator pulls his competitors' latest reels every two days and lists the topics that worked for them that he hasn't covered. iris download only takes one post at a time.

How verified

  • bun test src/cli/cmd/platform-peers.test.ts: 36 pass. This covers handle/URL normalisation, yt-dlp JSON → post record (fixtures trimmed from real yt-dlp output for YouTube full and flat, TikTok and Instagram), merge/dedupe, error classification and messages, keyword extraction, signature tags, coverage, median-relative ranking and determinism. yt-dlp is mocked through fetchWithYtDlp's run hook, so no test touches the network.
  • bun run typecheck is clean. The pre-push hook passed with bun 1.3.11 (typecheck, capabilities, routes, playbooks). capabilities.json was regenerated to include the new subcommands.
  • media-fetch.test.ts passes. In command-collisions.test.ts, two tests fail ("alias detection is alive" and "known list has no stale entries"). They fail the same way on clean origin/main, so this PR doesn't cause them.
  • Ran it from source against real accounts, using a scratch IRIS_PEERS_HOME:
    • Added youtube.com/@mkbhd/shorts, tiktok.com/@khaby.lame, natgeo (Instagram), and @mrwhosetheboss --platform youtube --mine.
    • peers pull --latest 5: YouTube and TikTok each listed 5 posts with dates, views, likes and comments, in about 40s for all four accounts. Instagram printed the login/cookies message and the other accounts carried on.
    • peers pull natgeo --urls https://www.instagram.com/reel/DbcFYsCF8b9/ saved that post. Instagram gives likes but no views without a login.
    • Re-pulling gave 0 new posts and no duplicates.
    • peers pull mkbhd --latest 2 --transcribe transcribed one short on-device (264 words). The silent ASMR short gave an empty transcript, and the next run didn't try it again.
    • peers gaps --days 400: mkbhd's iPhone Duo posts came out covered by mine (mrwhosetheboss posted about iPhone Duo too). "camera robot", "esim holafly" and "charlie rocket / real fans" came out not covered.

Known limits

  • Instagram profiles: yt-dlp currently marks its instagram:user extractor as broken ("Unable to extract data"), so listing a profile fails even with a login, and --cookies may not help. Adding single posts with --urls works. Without a login, Instagram reports likes but no views, so gaps ranks Instagram posts by likes relative to that account's median.
  • --flat is faster but YouTube returns no dates or likes in flat mode. Undated posts count as in the window by their first_seen_at.
  • Topic matching is word overlap, not meaning: "vinyl" doesn't match "records". --threshold adjusts how strict it is.
  • No playbook change: there is no creator playbook in scaffold/. creator-os-content-plan lives in the registry and will be updated separately.

🤖 Generated with Claude Code

mayoalexander and others added 4 commits October 27, 2026 00:42
…en't covered (FL-CO-112)

Adds a top-level `iris peers` command (add/remove/list/pull/gaps). The watch
list and each account's posts live in ~/.iris/peers. pull lists the latest N
posts per account with yt-dlp in metadata mode (no video unless --transcribe),
merges into posts.json, and says plainly when Instagram needs a session
(--cookies). gaps ranks posts by views relative to each account's median and
marks them covered/not by keyword overlap with the --mine account. No LLM, no
schedule.

download.ts now exports downloadFile, metaAuthor and metaDate for reuse.

Atlas #191297, epic #191300.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pty transcripts not redone

Found running it against real accounts: #learnfromkhaby glued into every
phrase and topped every label; an ASMR short with no speech was
re-transcribed each pull. Caption words are never treated as signatures, so
an account that keeps posting one topic stays on topic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve conflicts with #349 (iris reel) and #350 (keyword-dm): keep both
command registrations in index.ts; regenerate capabilities.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mayoalexander

Copy link
Copy Markdown
Author

need to rename this to something other than peers - there is a naming conflict with IRIS HIVE Peers

This needs to be competitive-gap-analysis or something like that

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant