← back to Gurisingh Scout
initial scaffold: gurisingh-scout skill
a8a04b89774676ed8f3ba093165ccddcf3015b7b · 2026-07-16 20:51:56 -0700 · Steve Abrams
Scouts newly open-sourced Claude Code skills / agent tools surfaced by Guri
Singh (@heygurisingh / gurisaroy) and evaluates each for adoption into the
stack. Retrieval fans out across mirrors to survive X's auth wall and the
account's intermittent suspension. Discovery/scoring free; install is gated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Files touched
A .gitignoreA README.mdA SKILL.mdA references/adoption-rubric.mdA references/retrieval-playbook.mdA references/sources.mdA scripts/gh-repo-stats.jsA scripts/hydrate-tweet.js
Diff
commit a8a04b89774676ed8f3ba093165ccddcf3015b7b
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 16 20:51:56 2026 -0700
initial scaffold: gurisingh-scout skill
Scouts newly open-sourced Claude Code skills / agent tools surfaced by Guri
Singh (@heygurisingh / gurisaroy) and evaluates each for adoption into the
stack. Retrieval fans out across mirrors to survive X's auth wall and the
account's intermittent suspension. Discovery/scoring free; install is gated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
.gitignore | 9 +++
README.md | 46 ++++++++++++++
SKILL.md | 95 ++++++++++++++++++++++++++++
references/adoption-rubric.md | 74 ++++++++++++++++++++++
references/retrieval-playbook.md | 60 ++++++++++++++++++
references/sources.md | 61 ++++++++++++++++++
scripts/gh-repo-stats.js | 123 ++++++++++++++++++++++++++++++++++++
scripts/hydrate-tweet.js | 132 +++++++++++++++++++++++++++++++++++++++
8 files changed, 600 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..266e471
--- /dev/null
+++ b/README.md
@@ -0,0 +1,46 @@
+# gurisingh-scout
+
+A Claude Code **skill** (its own git repo, symlinked into `~/.claude/skills/`)
+that scouts newly open-sourced Claude Code skills / subagents / MCP servers /
+AI-agent tools surfaced by **Guri Singh** (`@heygurisingh` on X, `gurisaroy` on
+LinkedIn) and similar educators — then evaluates each for adoption into Steve's
+stack: dedupe against existing skills, score fit/leverage/trust, draft **gated**
+install memos. Discovery and scoring run freely; installing anything is Steve's
+call.
+
+## Why it exists
+
+Guri Singh's whole genre is *"someone just open-sourced X for Claude Code — it's
+called Y, here's how it works ↓"* (past picks: `codeburn`, `career-ops`,
+`gstack`, `claude-code-best-practice`, "Understand Anything", `Graphify`,
+`Caveman`). That's a high-signal adoption feed — this skill harvests it into
+adopt/watch/skip decisions instead of hype.
+
+The hard part is **retrieval**: `x.com` 402s unauthenticated reads and the
+account is intermittently suspended, so the skill fans out across mirrors
+(Exa-indexed Thread Reader + LinkedIn `gurisaroy`, WebSearch) and only uses
+direct-X hydration when the account is live.
+
+## Layout
+
+- `SKILL.md` — the procedure Claude runs (retrieve → extract → enrich → dedupe →
+ score → deliver gated shortlist).
+- `scripts/hydrate-tweet.js` — one X post → verbatim text + GitHub links, no
+ auth, `$0` (computes the syndication token locally). Exit `3` on tombstone.
+- `scripts/gh-repo-stats.js` — GitHub repo → stars/license/freshness +
+ Claude-skill detection. Honors `GITHUB_TOKEN`.
+- `references/sources.md` — handles, mirrors, similar sources, suspension note.
+- `references/retrieval-playbook.md` — auth-wall-resistant retrieval order.
+- `references/adoption-rubric.md` — six-dimension scoring, dedupe, gating.
+
+## Quick checks
+
+```bash
+node scripts/hydrate-tweet.js https://x.com/heygurisingh/status/<id>
+node scripts/gh-repo-stats.js owner/repo
+```
+
+## Invoke
+
+`/gurisingh-scout` — or "gurisingh scout", "what's Guri Singh posting", "any new
+Claude Code skills worth adding".
diff --git a/SKILL.md b/SKILL.md
new file mode 100644
index 0000000..28b3266
--- /dev/null
+++ b/SKILL.md
@@ -0,0 +1,95 @@
+---
+name: gurisingh-scout
+description: Scout newly open-sourced Claude Code skills, subagents, MCP servers, and AI-agent tools that Guri Singh (@heygurisingh / LinkedIn gurisaroy) and similar educators surface, then evaluate each for adoption into Steve's stack — dedupe against existing skills, score fit/leverage/trust, and draft gated install memos. Use when Steve says 'gurisingh scout', '/gurisingh-scout', 'what is Guri Singh posting', 'any new Claude Code skills or tools worth adding', 'scout new agent tooling', 'find tools to adopt', or points at an @heygurisingh / gurisaroy post (even a tombstoned or suspended X link) and asks what it is and whether to adopt it. Retrieval survives X auth walls and intermittent account suspension by fanning out across mirrors.
+---
+
+# Gurisingh Scout
+
+## Overview
+
+Guri Singh's feed is a high-signal firehose of "someone just open-sourced X for
+Claude Code / AI agents". This skill harvests that signal, resolves the real
+GitHub repo behind each pick, and turns it into an **adopt / watch / skip**
+decision for Steve's specific stack — never a hype relay, and never an
+auto-install. Discovery and scoring run freely; installing anything is gated.
+
+## When to use
+
+- A periodic sweep: "what's worth adopting that Guri Singh posted lately?"
+- A one-off: Steve drops an `@heygurisingh` or `gurisaroy` link (often a
+ suspended/tombstoned X URL) and asks "what is this and should I use it?"
+- A wider sweep of newly open-sourced Claude Code / agent tooling, using Guri
+ Singh as the anchor plus the similar sources in `references/sources.md`.
+
+## Workflow
+
+### 1. Retrieve (fan out — never trust one source)
+X blocks unauthenticated reads and the account is **intermittently suspended**,
+so an X failure ≠ "no new picks". Follow `references/retrieval-playbook.md`:
+discover with Exa (`mcp__exa__web_search_exa`) + `WebSearch`, pull full bodies
+from the **LinkedIn `gurisaroy` mirror** or Thread Reader, and only use direct-X
+hydration when the account is live. Read `references/sources.md` for the exact
+handles, mirrors, and query strings.
+
+For a specific tweet id/URL, get the verbatim text + repo links:
+```bash
+node scripts/hydrate-tweet.js <tweet-id-or-url> # exit 3 = tombstone → use mirrors
+```
+
+### 2. Extract candidates
+For each post naming an open-source tool, capture: **tool name**, **GitHub
+repo**, **one-line what-it-does**, **category** (claude-skill | claude-plugin |
+CLI/TUI | MCP server | agent framework | prompt-pack). Resolve `lnkd.in`/`t.co`
+shortlinks to the real repo.
+
+### 3. Enrich each repo (hard signals)
+```bash
+node scripts/gh-repo-stats.js <github-url-or-owner/repo>
+```
+Returns stars, license, `days_since_push`, archived, `is_claude_skill`
+(detects `SKILL.md` / `.claude/` / plugin manifest), and quick `flags`. Set
+`GITHUB_TOKEN` in env to avoid the 60/hr unauthenticated rate limit.
+
+### 4. Dedupe against what Steve already has
+Before crediting novelty, check for an existing equivalent (procedure in
+`references/adoption-rubric.md`):
+```bash
+ls ~/.claude/skills/ | grep -i "<keyword>"
+grep -ril "<capability>" ~/.claude/skills/*/SKILL.md 2>/dev/null
+```
+If he already has it, the useful output is a **diff**, not a redundant recommend.
+
+### 5. Score & decide
+Apply the six-dimension rubric in `references/adoption-rubric.md` (stack fit,
+novelty, leverage, install cost, trust/license, safety). Verdict bands:
+**≥14 ADOPT · 9–13 WATCH · ≤8 SKIP.**
+
+### 6. Deliver — shortlist + gated memos
+Output a ranked table:
+`Tool | Repo (stars, license, last-push) | What it does | Category | Dedupe | Score | Verdict | 1-line reason`.
+For each **ADOPT**, draft a short memo to
+`~/.claude/yolo-queue/pending-approval/` with what it is, why it fits, exact
+install steps, **cost** (always show it — most scans are `$0 local`; Exa is
+metered), risks, and an APPROVE/REVISE/BLOCK line.
+
+## Hard rules
+
+- **Never auto-install.** `git clone`, `/plugin install`, `npx` on an unfamiliar
+ tool, pasting into `~/.claude`, or adding a paid key are all gated →
+ pending-approval. The deliverable is a decision-ready shortlist; adoption is
+ Steve's call.
+- **An X/tombstone failure is not "nothing new"** — fall back to mirrors.
+- **Show costs** on every run (discovery via Exa is metered; scripts are `$0`).
+- **Log real wins only** — a pick that actually gets adopted, not the scan.
+
+## Resources
+
+- `scripts/hydrate-tweet.js` — resolve one X post → verbatim text + GitHub links,
+ no auth, `$0` (computes the syndication token locally). Exit 3 on tombstone.
+- `scripts/gh-repo-stats.js` — GitHub repo → stars/license/freshness + Claude-skill
+ detection, feeds the rubric. Honors `GITHUB_TOKEN`.
+- `references/sources.md` — handles, mirrors, similar sources, suspension note.
+- `references/retrieval-playbook.md` — the ordered, auth-wall-resistant retrieval
+ procedure and failure-mode reads.
+- `references/adoption-rubric.md` — the six-dimension scoring model, 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..139033c
--- /dev/null
+++ b/references/adoption-rubric.md
@@ -0,0 +1,74 @@
+# Adoption rubric — should Steve actually add this?
+
+Guri Singh's feed is a firehose of "someone open-sourced X". Most picks are
+noise for *this* stack. The job isn't to relay hype — it's to answer, per
+candidate: **adopt, watch, or skip**, with a reason. Score each candidate on the
+six dimensions below (0–3 each, 18 max).
+
+## Dimensions
+
+1. **Stack fit (0–3)** — does it serve something Steve actually runs? High:
+ Claude Code skills/subagents/hooks, MCP servers, catalog/scraper/pricing
+ tooling, cost/observability for Claude, marketing/social automation, DW
+ commerce, directory builders. Low: generic web-dev boilerplate, crypto,
+ things with no path into his workflow.
+2. **Novelty vs. what he already has (0–3)** — 3 = fills a real gap; 0 = he
+ already has an equal-or-better skill/agent. **Always run the dedupe check
+ below before scoring this.**
+3. **Leverage (0–3)** — does it save real hours or unlock revenue, or is it a
+ toy? Favor "measurable time back" over "feels productive" (Steve's own
+ theater-vs-real test).
+4. **Install/run cost (0–3, higher = cheaper)** — 3 = one paste / `npx`, local,
+ no new paid API; 0 = heavy infra, new metered API, or a rewrite to fit.
+ Note any per-call cost explicitly (always-show-costs rule).
+5. **Trust & license (0–3)** — stars/traction, **license present** (MIT/Apache
+ good; `NONE`/`NOASSERTION` is a flag), recently maintained (`days_since_push`
+ low, not archived), no obvious security smell. Unlicensed + unmaintained caps
+ this at 1.
+6. **Safety/gating exposure (0–3, higher = safer)** — 3 = read-only/local, no
+ secrets, no outbound writes; 0 = wants prod creds, sends email, touches DNS,
+ writes to customer-facing systems, or auto-executes on a schedule. Low scores
+ here don't kill a pick — they route it to pending-approval (see Gating).
+
+**Verdict bands:** ≥14 → **ADOPT** (draft the install memo). 9–13 → **WATCH**
+(log it, revisit if it gains traction). ≤8 → **SKIP** (one-line why).
+
+## Dedupe against Steve's existing kit (do this before scoring novelty)
+
+Steve already has 200+ skills and dozens of agents. Before recommending
+anything, check for an existing equivalent:
+
+```bash
+ls ~/.claude/skills/ | grep -i "<keyword>"
+grep -ril "<capability keyword>" ~/.claude/skills/*/SKILL.md 2>/dev/null
+```
+
+Also sanity-check the cabinet/agents and CNCP. Examples of "he already has it":
+cost tracking → `cost-tracker`; token-spend analysis → overlaps `token-use`;
+multi-agent orchestration → the cabinet + `Workflow`; research → `last30`,
+`deep-research`, `web-extract`; code review → `claude-codex`, `code-review`.
+If an equivalent exists, the useful output is a **diff** ("codeburn breaks spend
+down by tool+MCP, which `cost-tracker` doesn't"), not a redundant recommend.
+
+## Gating — what the scout may and may NOT do
+
+**Discovery, retrieval, enrichment, scoring, and writing the shortlist are
+reversible and run freely.** Everything that changes Steve's machine or spends is
+gated — draft it to `~/.claude/yolo-queue/pending-approval/` with an
+APPROVE/REVISE/BLOCK block; never auto-run:
+
+- Installing a skill/plugin (`git clone`, `/plugin install`, paste-into-`.claude`).
+- `npx`/running an unfamiliar tool that executes code or reads `~/.claude`.
+- Anything needing a new paid API key or that spends.
+- Anything the tool itself does that is customer-facing / destructive / outbound.
+
+The scout's deliverable is a **decision-ready shortlist**, not an installed tool.
+Adoption is Steve's call.
+
+## Output shape (per run)
+
+Produce a ranked table: `Tool | Repo (stars, license, last-push) | What it does |
+Category | Dedupe (have/gap) | Score | Verdict | 1-line reason`. Then, for each
+ADOPT, a short pending-approval memo: what it is, why it fits, exact install
+steps, cost, risks, and the APPROVE/REVISE/BLOCK line. Log genuine wins (a pick
+that gets adopted) — not the scan itself.
diff --git a/references/retrieval-playbook.md b/references/retrieval-playbook.md
new file mode 100644
index 0000000..0c4f0c6
--- /dev/null
+++ b/references/retrieval-playbook.md
@@ -0,0 +1,60 @@
+# Retrieval playbook — getting the picks past X's auth wall
+
+The whole difficulty of this skill is retrieval: `x.com` returns HTTP 402 to
+unauthenticated readers, the target account is intermittently suspended, and
+Thread Reader gates some threads behind login. The answer is the same as
+`feed-first-scraper`: **never depend on one source; fan out and take the first
+that yields the post text + repo link.**
+
+## Step order (stop as soon as you have text + repo)
+
+1. **Discover with Exa.** Run `mcp__exa__web_search_exa` with 2–3 query
+ variants, e.g.:
+ - `heygurisingh open sourced Claude Code skill "it's called"`
+ - `Guri Singh gurisaroy Claude Code tool GitHub`
+ - `heygurisingh someone built agent skill terminal`
+ Exa returns Thread Reader unrolls **and** LinkedIn (`gurisaroy`) mirrors that
+ already contain the full caption + repo link. This alone usually answers the
+ whole scan. (Metered — show/log the cost per your always-show-costs rule.)
+
+2. **Confirm/expand with `WebSearch`** (free). Same intent, different index —
+ good for catching tweet URLs/IDs and the tool name when Exa misses.
+
+3. **Pull the full body from a mirror** if the search highlight was truncated:
+ `mcp__exa__web_fetch_exa` on the LinkedIn `gurisaroy_...` URL or the
+ `threadreaderapp.com/thread/<id>.html` URL. Prefer the LinkedIn mirror — it
+ rarely login-walls and usually has the `lnkd.in` repo shortlink.
+
+4. **Hydrate a specific tweet** only if you have an id/URL and want the verbatim
+ X text: `node scripts/hydrate-tweet.js <id-or-url>`. Exit `3` = tombstone
+ (suspended/deleted) → the account is likely suspended; go back to mirrors,
+ don't report "nothing new".
+
+5. **Resolve `lnkd.in` / `t.co` shortlinks** to the real GitHub URL by fetching
+ the shortlink (WebFetch/Exa follows the redirect) or by reading the repo name
+ out of the post body and searching GitHub for it.
+
+## Turning a post into a candidate
+
+For each post that names an open-source tool:
+
+- Extract: **tool name**, **GitHub repo URL**, **one-line what-it-does**,
+ **category** (claude-skill | claude-plugin | CLI/TUI | MCP server | agent
+ framework | prompt-pack).
+- Enrich the repo: `node scripts/gh-repo-stats.js <repo-url-or-owner/repo>`.
+ This returns stars, license, `days_since_push`, archived flag, and
+ `is_claude_skill` (detects `SKILL.md` / `.claude/` / plugin manifest) plus
+ quick `flags` (unlicensed / stale / archived / low_traction). Set
+ `GITHUB_TOKEN` in env to avoid the 60/hr unauthenticated rate limit.
+
+## Failure modes to expect (and how to read them)
+
+- **All tweets tombstone** → account suspended, not "no content". Use mirrors.
+- **Exa `SOURCE_NOT_AVAILABLE` on an x.com URL** → expected; never fetch x.com
+ directly, go through a mirror.
+- **Thread Reader login wall on one thread** → try the LinkedIn mirror or the
+ Thread Reader *user* page instead of that specific thread.
+- **`lnkd.in` won't resolve** → grep the repo name from the post and hit
+ `api.github.com/search/repositories?q=<name>` (or just `gh-repo-stats.js
+ owner/repo` once you know the slug).
+- **GitHub 403** → unauthenticated rate limit; set `GITHUB_TOKEN` and retry.
diff --git a/references/sources.md b/references/sources.md
new file mode 100644
index 0000000..fe737d1
--- /dev/null
+++ b/references/sources.md
@@ -0,0 +1,61 @@
+# Sources — where to find Guri Singh's picks
+
+## Primary subject
+
+- **Guri Singh** — X handle `@heygurisingh`; real name **Gurinderjeet Singh**,
+ Calgary AB. Self-described "AI Educator & Writer". Cross-posts the same
+ content to LinkedIn as **`gurisaroy`**.
+- **What he posts (the signal this skill harvests):** a recurring format —
+ *"Holy shit… someone just open-sourced a [Claude Code skill / AI-agent tool]
+ that does X. It's called Y. Here's how it works ↓"* — followed by a GitHub
+ repo link. He also posts prompt packs and tool roundups; those are lower
+ priority for adoption but still worth logging.
+- **Confirmed past picks (for calibration):** `codeburn` (Claude Code
+ token-spend TUI), `career-ops` (AI job-search pipeline), Garry Tan's
+ `gstack`, `claude-code-best-practice`, "Understand Anything" (codebase
+ knowledge-graph plugin), `Graphify`, `Caveman` (output-filler-stripping
+ skill), `GEO-SEO`, `The Agency` (147-agent org), `AnySearch`.
+
+## ⚠️ The account gets suspended intermittently
+
+As of the last scout run, `@heygurisingh` was **suspended** — every live tweet
+returns a "suspended account" tombstone through X's syndication endpoint, and
+`x.com` itself 402s unauthenticated. **Do not conclude "no new picks" from an X
+failure.** The mirrors below persist through suspensions and are the *primary*
+retrieval path; treat direct X as a bonus for when the account is live.
+
+## Retrieval sources, in priority order
+
+1. **Exa search** (`mcp__exa__web_search_exa`) with queries like
+ `heygurisingh open sourced Claude Code skill` — returns Thread Reader unrolls
+ AND LinkedIn mirrors that contain the **full post text + repo link**. This is
+ the most reliable discovery path and survives X suspensions. It's metered —
+ show/log the cost.
+2. **LinkedIn mirror** — the `gurisaroy` posts (e.g.
+ `linkedin.com/posts/gurisaroy_...`). Exa surfaces these with the complete
+ caption and often the `lnkd.in/...` repo shortlink. Fetch via Exa, not direct
+ (LinkedIn walls direct reads).
+3. **Thread Reader App** — `threadreaderapp.com/user/heygurisingh` lists his
+ recent threads; individual unrolled threads at
+ `threadreaderapp.com/thread/<id>.html`. Public unrolls are Exa-fetchable;
+ note that *some* specific threads hit a login wall, so fall back to source 1.
+4. **`WebSearch`** (built-in) — `heygurisingh "Claude Code" open sourced` — good
+ for surfacing tweet URLs/IDs and the tool name even when the body is walled.
+5. **Syndication hydration** (`scripts/hydrate-tweet.js`) — ONLY works when the
+ account is live and the specific tweet exists. Given a tweet id/URL it prints
+ the verbatim text + any GitHub links, $0, no auth. Exits `3` on a tombstone.
+
+## Similar accounts worth folding into a wider sweep
+
+The skill's value is "surface newly open-sourced Claude Code / agent tooling
+worth adopting." Guri Singh is the anchor, but the same signal shows up from:
+
+- **GitHub trending** — `github.com/trending?since=weekly` filtered to repos
+ whose description mentions `claude code`, `skill`, `subagent`, `MCP`, `agent`.
+- **Curated lists** — `GetBindu/awesome-claude-code-and-skills`,
+ `ykdojo/claude-code-tips` / `Opfour/claude-code-tips` (the `dx` plugin).
+- Other X educators posting the same "someone open-sourced X" format (e.g.
+ Garry Tan's `gstack`). Add handles here as they prove useful.
+
+Keep this file current: when a new mirror/source proves reliable (or a handle
+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..3ca5006
--- /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': 'gurisingh-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/hydrate-tweet.js b/scripts/hydrate-tweet.js
new file mode 100755
index 0000000..f119c95
--- /dev/null
+++ b/scripts/hydrate-tweet.js
@@ -0,0 +1,132 @@
+#!/usr/bin/env node
+/**
+ * hydrate-tweet.js — resolve a single X/Twitter post to plain text WITHOUT auth.
+ *
+ * X blocks unauthenticated page reads (WebFetch → HTTP 402), so this uses the
+ * public syndication endpoint that powers embedded tweets. The endpoint needs a
+ * `token` that is a deterministic function of the tweet id — we compute it
+ * locally ($0), so no API key and no login are ever required.
+ *
+ * Usage:
+ * node hydrate-tweet.js <tweet-id-or-url>
+ *
+ * Examples:
+ * node hydrate-tweet.js 2055557527153332556
+ * node hydrate-tweet.js https://x.com/heygurisingh/status/2055557527153332556
+ *
+ * Exit codes:
+ * 0 ok — printed tweet JSON summary
+ * 3 tombstone — tweet/account is suspended, deleted, or age-gated (unreadable)
+ * 1 error — bad input or network/parse failure
+ *
+ * Output: a JSON object on stdout:
+ * { id, url, author, created_at, text, urls[], github[] }
+ * On tombstone: { id, tombstone: "<reason text>" } and exit 3.
+ */
+
+'use strict';
+
+function extractId(arg) {
+ if (!arg) return null;
+ // trailing run of digits handles both bare ids and .../status/<id>[?query]
+ const m = String(arg).match(/(\d{5,25})/g);
+ return m ? m[m.length - 1] : null;
+}
+
+// react-tweet's documented token derivation: (id / 1e15 * PI) in base-36,
+// with zeros and the dot stripped. Verified against live tweets 2026-07.
+function syndicationToken(id) {
+ return ((Number(id) / 1e15) * Math.PI)
+ .toString(36)
+ .replace(/(0+|\.)/g, '');
+}
+
+function findGithub(text, urls) {
+ const found = new Set();
+ const scan = (s) => {
+ if (!s) return;
+ const re = /https?:\/\/(?:www\.)?github\.com\/[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+/g;
+ let m;
+ while ((m = re.exec(s))) found.add(m[0].replace(/[.,)]+$/, ''));
+ };
+ scan(text);
+ (urls || []).forEach(scan);
+ return [...found];
+}
+
+async function main() {
+ const id = extractId(process.argv[2]);
+ if (!id) {
+ console.error('usage: node hydrate-tweet.js <tweet-id-or-url>');
+ process.exit(1);
+ }
+
+ const token = syndicationToken(id);
+ const endpoint =
+ `https://cdn.syndication.twimg.com/tweet-result?id=${id}&lang=en&token=${token}`;
+
+ let res;
+ try {
+ res = await fetch(endpoint, {
+ headers: {
+ // a browser-ish UA avoids the occasional bot 403 on this endpoint
+ 'User-Agent':
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ' +
+ '(KHTML, like Gecko) Chrome/125.0 Safari/537.36',
+ Accept: 'application/json',
+ },
+ });
+ } catch (e) {
+ console.error(`network error: ${e.message}`);
+ process.exit(1);
+ }
+
+ if (res.status === 404) {
+ console.log(JSON.stringify({ id, tombstone: 'not found (404)' }, null, 2));
+ process.exit(3);
+ }
+ if (!res.ok) {
+ console.error(`http ${res.status} from syndication endpoint`);
+ process.exit(1);
+ }
+
+ let data;
+ try {
+ data = await res.json();
+ } catch (e) {
+ console.error(`parse error: ${e.message}`);
+ process.exit(1);
+ }
+
+ // Tombstone shapes: suspended/deleted/age-gated tweets come back as a
+ // __typename of TweetTombstone (or an object with a `tombstone` field).
+ const tomb =
+ data.__typename === 'TweetTombstone' ||
+ data.tombstone ||
+ (data.text && /suspended account|Post is from a suspended|isn.?t available/i.test(data.text) && !data.user);
+ if (tomb) {
+ const reason =
+ (data.tombstone && (data.tombstone.text?.text || data.tombstone.text)) ||
+ data.text ||
+ 'unavailable (suspended / deleted / age-gated)';
+ console.log(JSON.stringify({ id, tombstone: reason }, null, 2));
+ process.exit(3);
+ }
+
+ const urls = (data.entities?.urls || []).map((u) => u.expanded_url || u.url);
+ const out = {
+ id,
+ url: `https://x.com/${data.user?.screen_name || 'i'}/status/${id}`,
+ author: data.user
+ ? `@${data.user.screen_name} (${data.user.name})`
+ : null,
+ created_at: data.created_at || null,
+ text: data.text || '',
+ urls,
+ github: findGithub(data.text || '', urls),
+ };
+ console.log(JSON.stringify(out, null, 2));
+ process.exit(0);
+}
+
+main();
(oldest)
·
back to Gurisingh Scout
·
(newest)