Docs/Skills and code/skill ecosystem-rater
.claude/skills/ecosystem-rater/scripts/probe-search.mjs
What the file says about itself
Runs the frozen probe query set for one ecosystem through YouTube search and saves the top results as JSON. Results are NOT written to the ecosystem database, so discovery data stays untouched.
#!/usr/bin/env node
// Runs the frozen probe query set for one ecosystem through YouTube search and saves the top results as JSON.
// Results are NOT written to the ecosystem database, so discovery data stays untouched.
// node probe-search.mjs --db <data/x.db | ecosystem name> [--limit 20] [--delay-ms 1500]
// Reads ecosystem-ratings/<eco>/probe-queries.json ({ version, language, queries: [{ bucket, query }] })
// Writes ecosystem-ratings/<eco>/probe-results-<YYYY-MM-DD>.json (one file per probe day; the rater averages all of them).
import fs from "node:fs";
import path from "node:path";
import { searchVideos } from "../../../../src/youtube.js";
import { parseArgs, resolveDb, ecosystemName, outDir, readJson } from "./lib.mjs";
const args = parseArgs(process.argv.slice(2));
const eco = ecosystemName(resolveDb(args.db));
const dir = outDir(eco);
const spec = readJson(path.join(dir, "probe-queries.json"));
if (!spec?.queries?.length) {
console.error(`No probe queries: write ${path.join(dir, "probe-queries.json")} first (see SKILL.md, step 1).`);
process.exit(2);
}
const limit = Number(args.limit) || 20;
const delayMs = Number(args["delay-ms"] ?? 1500);
const startedAt = new Date().toISOString();
const file = path.join(dir, `probe-results-${startedAt.slice(0, 10)}.json`);
if (fs.existsSync(file) && !args.force) {
console.error(`${file} already exists. Probe once per day, or pass --force to overwrite.`);
process.exit(3);
}
const results = [];
for (const { bucket, query } of spec.queries) {
const observedAt = new Date().toISOString();
try {
const videos = await searchVideos(query, limit);
results.push({
bucket, query, observed_at: observedAt,
items: videos.map((v, i) => ({
position: i + 1, video_id: v.video_id, title: v.title, channel_url: v.channel_url ?? null,
channel_name: v.channel_name ?? null, view_count: v.view_count ?? null,
published_at: v.published_at ?? null, published_at_text: v.published_at_text ?? null,
duration_seconds: v.duration_seconds ?? null
}))
});
console.log(`[probe] ${bucket.padEnd(9)} ${String(videos.length).padStart(2)} results ${query}`);
} catch (error) {
results.push({ bucket, query, observed_at: observedAt, error: String(error?.message ?? error), items: [] });
console.error(`[probe] FAILED ${query}: ${error?.message ?? error}`);
}
if (delayMs) await new Promise(r => setTimeout(r, delayMs));
}
fs.writeFileSync(file, JSON.stringify({ ecosystem: eco, queries_version: spec.version, started_at: startedAt, limit, results }, null, 2));
const failed = results.filter(r => r.error || r.items.length < 5).length;
console.log(`[probe] wrote ${file} (${results.length} queries, ${failed} failed or under 5 results)`);
Shown as it is in the repository at build time (2026-11-03); the home directory appears as <home>. If this page and the file differ, the file is right. Back to the catalogue.