app-store-review-arbit…
Fetches low-star App Store and Google Play reviews, clusters them into broken-promise patterns, and generates a ranked copy brief…
Takes a competitor's public GitHub repo URL, fetches their open issues via the GitHub REST API, filters noise locally, clusters issues into 6 demand categories, computes a demand score per issue and per cluster, and outputs a ranked demand gap report with a GTM messaging brief.
Claude Code
$ npx -y skills add Varnan-Tech/opendirectory --skill gh-issue-to-demand-signal --agent claude-code
Codex
$ npx skills add Varnan-Tech/opendirectory --skill gh-issue-to-demand-signal -a codex -y
Cursor
$ npx skills add Varnan-Tech/opendirectory --skill gh-issue-to-demand-signal -a cursor -y
Gemini CLI
$ npx skills add Varnan-Tech/opendirectory --skill gh-issue-to-demand-signal -a gemini-cli -y
GitHub Copilot
$ npx skills add Varnan-Tech/opendirectory --skill gh-issue-to-demand-signal -a github-copilot -y
OpenCode
$ npx skills add Varnan-Tech/opendirectory --skill gh-issue-to-demand-signal -a opencode -y
Windsurf
$ npx skills add Varnan-Tech/opendirectory --skill gh-issue-to-demand-signal -a windsurf -y
name: gh-issue-to-demand-signal description: Takes a competitor's public GitHub repo URL, fetches their open issues via the GitHub REST API, filters noise locally, clusters issues into 6 demand categories, computes a demand score per issue and per cluster, and outputs a ranked demand gap report with a GTM messaging brief. Use when asked to scan a competitor's GitHub issues, find what their users are begging for, turn GitHub complaints into product positioning, identify competitor feature gaps, or generate messaging from real user demand. Trigger when a user says "scan competitor issues", "what are users asking for on X repo", "find demand gaps in Y", "turn GitHub issues into messaging", or "what should I build based on competitor complaints".
Take a competitor's public GitHub repo. Fetch their open issues. Filter noise locally. Cluster into 6 demand categories. Score by real engagement. Output a ranked demand gap report and GTM messaging brief.
---
**Critical rule:** Every issue title in the output must be verbatim from the GitHub API response. Every cluster theme name must be derived from actual issue titles in that cluster. If fewer than 10 issues remain after noise filtering, stop and tell the user -- the repo is too small for reliable clustering. No invented issue content anywhere.
---
| The agent will want to... | Why that's wrong | |---|---| | Send all 200 raw issues to the AI without filtering | Bot issues, PRs, and zero-engagement noise inflate cluster counts and waste context. Filter locally first. | | Use comment count as the primary demand signal | Comments include maintainer responses, off-topic discussion, and spam. reactions["+1"] is the cleanest buyer signal. | | Paraphrase issue titles when summarizing clusters | Paraphrasing loses the buyer's exact language, which is the entire point. Use verbatim issue titles. | | Continue past Step 4 if fewer than 10 issues remain after filtering | Under 10 issues means the repo is too small or the wrong URL was given. Clustering on sparse data produces meaningless categories. | | Include pull requests in the analysis | The GitHub Issues endpoint returns PRs too. Filter by checking that the pull_request key is absent on the issue object. | | Mark an issue as ignored demand without checking all 3 criteria | All three must be true: reactions >= 10, age >= 180 days, no planned/in-progress/roadmap label. Missing one criterion disqualifies the issue. |
---
echo "GITHUB_TOKEN: ${GITHUB_TOKEN:-not set, unauthenticated rate limit applies (60 req/hr)}"**If GITHUB_TOKEN is not set:** Continue. Tell the user: "GITHUB_TOKEN is not set. Unauthenticated rate limit is 60 requests/hour -- enough for 2 fetches before hitting the limit. For repeated use, add a token at github.com/settings/tokens (no scopes needed for public repos)."
---
You need:
Parse owner and repo from input:
python3 << 'PYEOF'
import re, sys, os
raw = "REPO_INPUT_HERE"
# Normalize to owner/repo
if raw.startswith("http"):
m = re.search(r"github\.com/([^/]+)/([^/?\s]+)", raw)
if not m:
print("ERROR: Could not parse GitHub URL. Expected format: https://github.com/owner/repo")
sys.exit(1)
owner, repo = m.group(1), m.group(2).rstrip("/")
elif "/" in raw:
parts = raw.strip().split("/")
owner, repo = parts[0], parts[1]
else:
print("ERROR: Input must be a GitHub URL or owner/repo slug (e.g. vercel/next.js)")
sys.exit(1)
print(f"Owner: {owner}")
print(f"Repo: {repo}")
with open("/tmp/ghd-target.txt", "w") as f:
f.write(f"{owner}/{repo}")
PYEOF**If parsing fails:** Stop. Ask: "Please provide the GitHub repo as a URL (https://github.com/owner/repo) or an owner/repo slug (e.g. vercel/next.js)."
---
Fetch up to 200 issues (2 pages of 100). Check rate limit after the first fetch.
python3 << 'PYEOF'
import json, urllib.request, os, sys
from datetime import datetime, timezone
target = open("/tmp/ghd-target.txt").read().strip()
owner_repo = target
token = os.environ.get("GITHUB_TOKEN", "")
headers = {"Accept": "application/vnd.github+json", "User-Agent": "gh-issue-demand-signal/1.0"}
if token:
headers["Authorization"] = f"Bearer {token}"
all_issues = []
rate_limit_hit = False
for page in [1, 2]:
url = f"https://api.github.com/repos/{owner_repo}/issues?state=open&per_page=100&page={page}"
req = urllib.request.Request(url, headers=headers)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
# Check rate limit after first page
if page == 1:
remaining = int(resp.headers.get("X-RateLimit-Remaining", 999))
reset_ts = resp.headers.get("X-RateLimit-Reset", "")
if remaining == 0:
reset_str = datetime.fromtimestamp(int(reset_ts), tz=timezone.utc).strftime("%H:%M UTC") if reset_ts else "unknown"
print(f"ERROR: GitHub rate limit exhausted. Resets at {reset_str}.")
print("Add GITHUB_TOKEN to your .env file to get 5000 req/hr. See github.com/settings/tokens (no scopes needed).")
sys.exit(1)
print(f"Rate limit remaining: {remaining}")
# Check for 404/403
status = resp.status
if status == 404:
print(f"ERROR: Repo '{owner_repo}' not found. Check the URL or slug.")
sys.exit(1)
page_data = json.loads(resp.read())
if not page_data:
print(f"Page {page}: empty, stopping.")
break
all_issues.extend(page_data)
print(f"Page {page}: {len(page_data)} issues fetched")
except urllib.error.HTTPError as e:
iFetches low-star App Store and Google Play reviews, clusters them into broken-promise patterns, and generates a ranked copy brief…
Use when the user asks to generate a blog cover image, thumbnail, or article header. Automatically uses modern typography, brand…
World-class brand strategist and naming expert. Uses an interrogation-led discovery phase to extract your brand's DNA, then…
Use when the user asks to generate or update a project's CLAUDE or AGENTS context file from a codebase scan. Writes a focused…
Use when the user wants to verify cold emails, enrich a lead list, or autonomously guess email addresses from a CSV using…
Competitive intelligence orchestrator tracking companies across 8+ platforms (GitHub, Twitter, Reddit, HN, PH, YC Jobs) with heat…
When the user wants to plan, design, or implement an A/B test or experiment, or build a growth experimentation program. Also use…
Comprehensive SEO analysis for any website or business type. Full site audits, single-page analysis, technical SEO (crawlability,…
Turn the current project website into a short, polished, shareable launch video using Hyperframes. Use when someone says "/brag",…
Full website GEO+SEO audit with parallel subagent delegation. Orchestrates a comprehensive Generative Engine Optimization audit…