Docs/Skills and code/src/

src/topic-research/cli.js

Code58 lines2.8 KBsrc/

What the file says about itself

Usage: node src/topic-research/cli.js "the fall of moser baer" [--entity="Moser Baer"] [--max=8] [--also="query"] [--refresh] Stages (each cached under research/<slug>/, so a re-run reuses everything unless --refresh): search ->…

Mentioned on: page 01, page 05, page 06, page 10, page 12

#!/usr/bin/env node
// Usage: node src/topic-research/cli.js "the fall of moser baer" [--entity="Moser Baer"] [--max=8] [--also="query"] [--refresh]
// Stages (each cached under research/<slug>/, so a re-run reuses everything unless --refresh):
//   search -> transcripts -> extract -> synthesize -> report.md
import path from "node:path";
import fs from "node:fs";
import { slugify } from "./util.js";
import { searchSources, collectDocuments, deriveEntity } from "./sources/youtube.js";
import { extractDocument } from "./extract.js";
import { synthesize } from "./synthesize.js";
import { renderReport } from "./report.js";

const args = process.argv.slice(2);
const flag = (name, fallback) => {
  const hit = args.find(a => a === `--${name}` || a.startsWith(`--${name}=`));
  return hit ? (hit.includes("=") ? hit.slice(name.length + 3).replace(/^"|"$/g, "") : true) : fallback;
};
const topic = args.find(a => !a.startsWith("--"));
if (!topic) { console.error('Give a topic, e.g. node src/topic-research/cli.js "the fall of moser baer"'); process.exit(1); }

process.env.GEMINI_TEMPERATURE ??= "0.1";
const entity = flag("entity") || deriveEntity(topic);
const dir = path.join(process.cwd(), "research", slugify(topic));
const llmCache = path.join(dir, ".llm-cache");
const refresh = !!flag("refresh");
// --refresh redoes this stage only. It must never delete the whole folder: research/<slug>/script/ (scripts, chains, lint
// reports) and teardown/ live beside these files.
if (refresh) {
  for (const name of ["search.json", "synthesis.json", "report.md", "transcripts", "extractions", ".llm-cache"]) {
    fs.rmSync(path.join(dir, name), { recursive: true, force: true });
  }
}

console.log(`Topic: ${topic}\nEntity: ${entity}\nOutput: ${dir}`);
const search = await searchSources({
  topic, entity, dir, refresh,
  maxVideos: Number(flag("max", 8)),
  extraQueries: args.filter(a => a.startsWith("--also=")).map(a => a.slice(7).replace(/^"|"$/g, ""))
});
console.log(`Selected ${search.selected.length} of ${search.candidatesKept} relevant videos:`);
search.selected.forEach(v => console.log(`  ${v.video_id}  ${v.views?.toLocaleString()} views  ${v.title}`));

const docs = await collectDocuments(search.selected, dir);
const withText = docs.filter(d => d.segments.length);
console.log(`Transcripts: ${withText.length}/${docs.length}`);

const extractions = [];
for (const doc of withText) {
  console.log(`Extracting ${doc.key} ${doc.meta.title.slice(0, 60)}`);
  extractions.push(await extractDocument({ topic, doc, dir, llmCache }));
}

const synthesis = await synthesize({ topic, docs: withText, extractions, dir, llmCache });
const report = renderReport({ topic, search, docs, extractions, synthesis });
const out = path.join(dir, "report.md");
fs.writeFileSync(out, report);
console.log(`\nReport: ${out}`);

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.