yt-briefing 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: yt-transcribe
3
+ description: Fetch a single YouTube video's transcript and summarize it. One-shot — paste a URL or video ID, get a journalist-grade summary. Same transcript engine (and WARP proxy support) as the /yt briefing loop; no channels, no state, no rating.
4
+ argument-hint: URL or VIDEO_ID. Optional --lang pl|en|auto (defaults to auto).
5
+ ---
6
+
7
+ ## Step 1 — Fetch the transcript
8
+
9
+ ```bash
10
+ bun run src/yt-transcript.ts <VIDEO_ID_OR_URL> --lang <lang>
11
+ ```
12
+
13
+ - First argument: URL or VIDEO_ID straight from the user — the script parses both formats (`youtube.com/watch?v=…`, `youtu.be/…`, or a bare 11-char ID).
14
+ - `--lang` selects which **caption track to fetch** (the input), not the summary language. `auto`
15
+ by default (best available track); pass `pl` / `en` only if the user wants a specific track.
16
+ - The transcript goes to **stdout**; stderr carries diagnostics. Run it bare — no redirects.
17
+
18
+ Exit codes — the message you show the user must match the real cause. Never report a
19
+ tooling failure as "no subtitles". When the code is non-zero, read the script's stderr
20
+ and surface that actual reason; do not paraphrase it away.
21
+ - `0` — transcript on stdout → continue to Step 2.
22
+ - `1` → the video genuinely has no subtitles. Tell the user exactly that and stop.
23
+ - `2` → YouTube rate-limit / IP block (common on datacenter/VPS IPs). Tell the user and
24
+ stop; it's transient. Recovery: route fetches through a proxy — see
25
+ `docs/warp-proxy.md` (set `YT_BRIEFING_PROXY`).
26
+ - `3` → tooling/integration failure (yt-dlp missing, fetch error, unavailable or private
27
+ video, empty/unparseable track, bad input). This is NOT a missing-captions case.
28
+ Show the stderr line verbatim so the user sees the real problem, and stop.
29
+
30
+ ---
31
+
32
+ ## Step 2 — Summary
33
+
34
+ Read the transcript from the tool result and respond as a narrative.
35
+
36
+ - **Header:** title, author, date
37
+ - One introductory sentence
38
+ - One paragraph per significant thread — a bold label + continuous prose, concrete facts. Preserve the logic and rhetoric of the original.
39
+ - Verdict: what's strong, what's weak, whether it was worth it
40
+
41
+ Style:
42
+ - an intelligent journalist — not like an AI assistant summarizing an article
43
+ - no generalities, always specifics: what was the thesis, what was the argument
44
+ - if the material has a clear host and guests, show that in the text
45
+
46
+ **Language:** Read `data/config.json` → `output_lang` once and write the summary in that
47
+ language — the same language chosen at onboarding that `/yt` uses. Order of precedence:
48
+ (1) a language the user explicitly asks for in this request wins; (2) otherwise `output_lang`
49
+ from config; (3) only if config is missing, match the language of the video. This is the
50
+ **output** language and is independent of `--lang` (which only picks the caption track to
51
+ fetch). Don't mix languages: write fully in the target language and insert foreign words only
52
+ when they are (a) a proper name of a technology/product, or (b) an established technical term
53
+ with no natural equivalent (API, REST, JSON, webhook, endpoint). Translate everything else.
54
+
55
+ ---
56
+
57
+ ## Rules
58
+
59
+ - **Transcripts:** never paste the raw transcript into chat — the summary is the artifact.
60
+ - **One-shot, stateless:** this skill reads no channels, writes no state, asks for no rating.
61
+ It is independent of the `/yt` briefing loop, though it shares the same transcript engine
62
+ and proxy. For the recurring channel briefing, use `/yt` instead.
package/README.md CHANGED
@@ -64,8 +64,24 @@ npx yt-briefing list # show the current l
64
64
  ## Run it
65
65
 
66
66
  Open your project in Claude Code or Cursor and run `/yt`. If it's not listed, start a fresh
67
- session. To install the skill again for another tool or project, run
68
- `npx yt-briefing install-skill`.
67
+ session. To install the skills again for another tool or project, run
68
+ `npx yt-briefing install-skill` (it installs both `/yt` and `/yt-transcribe`).
69
+
70
+ ## One-off: transcribe a single video
71
+
72
+ Just want one video summarized — no channels, no queue, no rating? Run `/yt-transcribe` and
73
+ paste a URL or video ID. It pulls that video's transcript and writes a journalist-grade
74
+ summary in the language you chose at setup (the same `output_lang` as `/yt`). Want a one-off in
75
+ another language? Just say so when you run it (e.g. `/yt-transcribe <url> in German`) — it
76
+ won't change your setup. `--lang pl|en` is separate — it picks which caption track to fetch,
77
+ not the summary language. Same transcript engine and proxy as the briefing loop, so on a
78
+ server it benefits from the same [WARP proxy](#running-on-a-vps).
79
+
80
+ The skill is installed alongside `/yt` by `init` / `install-skill`. From the plain CLI:
81
+
82
+ ```bash
83
+ npx yt-briefing transcribe <url-or-id> --lang auto # prints the transcript to stdout
84
+ ```
69
85
 
70
86
  ## Providers
71
87
 
package/dist/bootstrap.js CHANGED
@@ -16,7 +16,7 @@
16
16
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
17
17
  import { join } from 'node:path';
18
18
  import { DATA_DIR, BASE_DIR, PKG_ROOT, CHANNELS_DIR, CHANNELS_MD, STATE_MD, CONFIG_JSON, ENV_PATH, profilePath, } from "./lib/paths.js";
19
- import { AGENTS, installSkill, projectSkillDir, customSkillDirDefault, isPackageDevCwd } from "./lib/skill-install.js";
19
+ import { AGENTS, installSkills, projectSkillsRoot, customSkillsRootDefault, isPackageDevCwd } from "./lib/skill-install.js";
20
20
  import { question } from "./lib/prompt.js";
21
21
  import { normalizeHandle, slugify, serializeChannels, serializeState, profileBody, baselineStateRow } from "./lib/channels.js";
22
22
  const ask = (q, def = '') => {
@@ -114,7 +114,7 @@ function main() {
114
114
  console.log(' 1) Claude Code 2) Cursor 3) Custom folder (any other agent)\n');
115
115
  const agentKey = ask(' Your agent', '1');
116
116
  // For a custom target, ask the folder now (keeps all prompts in the interactive block).
117
- const customDir = AGENTS[agentKey] ? '' : ask(' Folder to install the skill into', customSkillDirDefault());
117
+ const customDir = AGENTS[agentKey] ? '' : ask(' Skills folder to install into', customSkillsRootDefault());
118
118
  // 7. Write everything --------------------------------------------------------
119
119
  mkdirSync(CHANNELS_DIR, { recursive: true });
120
120
  // .env
@@ -148,23 +148,24 @@ function main() {
148
148
  console.log(` ${CHANNELS_MD}`);
149
149
  console.log(` ${STATE_MD}`);
150
150
  console.log(` ${channels.length} profile(s) in ${CHANNELS_DIR}/`);
151
- // Install the /yt skill for the chosen agent (step 6), into THIS project — process.cwd(),
152
- // i.e. wherever you ran the command (the package clone in dev, or your own project when the
153
- // package is a dependency). The command baked in is the shipped `bun run src` only for the
154
- // dev-in-clone case; otherwise the compiled `dist/` command (so a consumed package works).
151
+ // Install the /yt + /yt-transcribe skills for the chosen agent (step 6), into THIS project —
152
+ // process.cwd(), i.e. wherever you ran the command (the package clone in dev, or your own
153
+ // project when the package is a dependency). The command baked in is the shipped `bun run src`
154
+ // only for the dev-in-clone case; otherwise the compiled `dist/` command (so a consumed package works).
155
155
  const agent = AGENTS[agentKey];
156
156
  try {
157
- const target = agent
158
- ? installSkill(projectSkillDir(agentKey, process.cwd()), /* dist */ !isPackageDevCwd())
159
- : installSkill(customDir, /* dist */ true);
160
- console.log(` /yt skill → ${target}`);
157
+ const targets = agent
158
+ ? installSkills(projectSkillsRoot(agentKey, process.cwd()), /* dist */ !isPackageDevCwd())
159
+ : installSkills(customDir, /* dist */ true);
160
+ for (const t of targets)
161
+ console.log(` skill → ${t}`);
161
162
  }
162
163
  catch (e) {
163
- console.log(` ! Couldn't install the skill (${e.message}) — run yt-briefing install-skill later.`);
164
+ console.log(` ! Couldn't install the skills (${e.message}) — run yt-briefing install-skill later.`);
164
165
  }
165
166
  console.log('\n Next:');
166
167
  console.log(` 1. Open this folder in ${agent ? agent.name : 'your agent'}.`);
167
- console.log(' 2. Start a new chat and type /yt');
168
+ console.log(' 2. Start a new chat and type /yt (or /yt-transcribe <url>)');
168
169
  console.log('\n No agent? Run it in the terminal instead — see the README.\n');
169
170
  }
170
171
  try {
@@ -1,51 +1,54 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * install-skill — copy the `/yt` skill into a coding agent's skills directory so the
4
- * agent detects it. For any target that isn't this package under Bun, the command is baked to
5
- * `"<this runtime>" "<abs>/dist/X.js"` (the compiled build), so it works no matter the agent's
6
- * working directory or runtime (the engine resolves data/.env from its own location).
3
+ * install-skill — copy this package's skills (`/yt` + `/yt-transcribe`) into a coding agent's
4
+ * skills directory so the agent detects them. For any target that isn't this package under Bun,
5
+ * each command is baked to `"<this runtime>" "<abs>/dist/X.js"` (the compiled build), so it works
6
+ * no matter the agent's working directory or runtime (the engine resolves data/.env from its own
7
+ * location).
7
8
  *
8
9
  * yt-briefing install-skill # interactive: pick agent + scope
9
10
  *
10
- * `bun run init` already installs this skill into the project as its final step; this
11
+ * `bun run init` already installs these skills into the project as its final step; this
11
12
  * standalone command is for re-installing, a different project, or a second agent. There is
12
- * deliberately no home-global install — the skill lives with the project that uses it.
13
+ * deliberately no home-global install — the skills live with the project that uses them.
13
14
  *
14
15
  * Both Claude Code and Cursor load `SKILL.md` skills and invoke them as `/<name>`.
15
- * Cursor also reads `.claude/skills/` for compatibility, so the copy this package already
16
- * ships often works in both — this command just (re)places it where you want.
16
+ * Cursor also reads `.claude/skills/` for compatibility, so the copies this package already
17
+ * ships often work in both — this command just (re)places them where you want.
17
18
  */
18
- import { AGENTS, installSkill, projectSkillDir, customSkillDirDefault, isPackageDevCwd } from "./lib/skill-install.js";
19
+ import { AGENTS, installSkills, projectSkillsRoot, customSkillsRootDefault, isPackageDevCwd } from "./lib/skill-install.js";
19
20
  import { question } from "./lib/prompt.js";
20
21
  const ask = (q, def = '') => question(def ? `${q} [${def}]:` : `${q}:`).trim() || def;
21
- function done(target) {
22
- console.log(`\n ✓ Installed → ${target}`);
23
- console.log(' Start a fresh agent session, then run /yt\n');
22
+ function done(targets) {
23
+ console.log('\n ✓ Installed:');
24
+ for (const t of targets)
25
+ console.log(` ${t}`);
26
+ console.log(' Start a fresh agent session, then run /yt or /yt-transcribe\n');
24
27
  }
25
28
  // 1) which agent → which skills subdir
26
- console.log('\n Install the /yt skill — which agent?\n');
29
+ console.log('\n Install the /yt + /yt-transcribe skills — which agent?\n');
27
30
  console.log(' 1) Claude Code');
28
31
  console.log(' 2) Cursor (also reads Claude\'s .claude/skills)');
29
32
  console.log(' 3) Custom folder (any other agent)\n');
30
33
  const agentKey = ask(' Agent', '1');
31
34
  const agent = AGENTS[agentKey];
32
- // 3) Custom — write SKILL.md straight into a folder the user names (their agent's skills dir).
33
- // Arbitrary location → bake the absolute dist command so it works whatever the agent's cwd is.
35
+ // 3) Custom — write the skills straight into a skills root the user names (their agent's dir).
36
+ // Arbitrary location → bake the absolute dist commands so they work whatever the agent's cwd is.
34
37
  if (!agent) {
35
- done(installSkill(ask(' Folder to install the skill into', customSkillDirDefault()), true));
38
+ done(installSkills(ask(' Skills folder to install into', customSkillsRootDefault()), true));
36
39
  process.exit(0);
37
40
  }
38
41
  // 2) Known agent → which project (default: the current folder). No home-global option by
39
- // design — the skill is always scoped to a project that uses it.
42
+ // design — the skills are always scoped to a project that uses them.
40
43
  console.log(`\n ${agent.name} — which project?\n`);
41
44
  console.log(' 1) This project (current folder) — recommended');
42
45
  console.log(' 2) Another project folder\n');
43
46
  if (ask(' Where', '1') === '2') {
44
- // A different project → the agent's cwd won't be the package, so bake the absolute dist command.
45
- done(installSkill(projectSkillDir(agentKey, ask(' Project folder', process.cwd())), true));
47
+ // A different project → the agent's cwd won't be the package, so bake the absolute dist commands.
48
+ done(installSkills(projectSkillsRoot(agentKey, ask(' Project folder', process.cwd())), true));
46
49
  }
47
50
  else {
48
51
  // Current folder: shipped `bun run src` only when developing in the package clone under Bun;
49
- // otherwise (incl. consuming the package as a dependency) bake the compiled dist command.
50
- done(installSkill(projectSkillDir(agentKey, process.cwd()), !isPackageDevCwd()));
52
+ // otherwise (incl. consuming the package as a dependency) bake the compiled dist commands.
53
+ done(installSkills(projectSkillsRoot(agentKey, process.cwd()), !isPackageDevCwd()));
51
54
  }
@@ -1,12 +1,17 @@
1
1
  /**
2
- * skill-install — place the `/yt` SKILL.md into a coding agent's skills directory.
2
+ * skill-install — place this package's SKILL.md files into a coding agent's skills directory.
3
3
  *
4
4
  * Shared by the onboarding wizard (`bootstrap.ts`, final step) and the standalone
5
- * `install-skill.ts` command, so both write the skill identically.
5
+ * `install-skill.ts` command, so both write the skills identically.
6
6
  *
7
- * The shipped SKILL.md uses `bun run src/X.ts` — the dev shortcut: it works when the agent's
8
- * cwd IS the package folder AND the runtime is Bun (which runs TypeScript directly). That's
9
- * true for the publisher's own day-to-day use, so it stays the default.
7
+ * The package ships TWO skills, each at `.claude/skills/<name>/SKILL.md`:
8
+ * yt — the recurring channel briefing loop (sweep + rate).
9
+ * yt-transcribe — one-shot: a single video's transcript → summary.
10
+ * Both are installed together so an agent gets the whole toolset in one step.
11
+ *
12
+ * The shipped SKILL.md files use `bun run src/X.ts` — the dev shortcut: it works when the
13
+ * agent's cwd IS the package folder AND the runtime is Bun (which runs TypeScript directly).
14
+ * That's true for the publisher's own day-to-day use, so it stays the default.
10
15
  *
11
16
  * For everyone else — a Node user, or any install whose cwd won't be the package — we bake an
12
17
  * absolute, runtime-correct command instead: `"<this runtime>" "<abs>/dist/X.js"`. The runtime
@@ -20,6 +25,8 @@ import { join, resolve } from 'node:path';
20
25
  import { PKG_ROOT, DATA_DIR } from "./paths.js";
21
26
  /** Compiled output dir — what a baked (dist) skill command points the runtime at. */
22
27
  const DIST_DIR = join(PKG_ROOT, 'dist');
28
+ /** The skills this package ships — each lives at `.claude/skills/<name>/SKILL.md`. */
29
+ export const SKILLS = ['yt', 'yt-transcribe'];
23
30
  /** True when the installer itself is running under Bun (vs plain Node). */
24
31
  export const isBun = process.versions.bun != null;
25
32
  /**
@@ -30,23 +37,25 @@ export const isBun = process.versions.bun != null;
30
37
  * compiled `dist/` command instead. This detects that one dev-in-clone case.
31
38
  */
32
39
  export const isPackageDevCwd = () => isBun && resolve(process.cwd()) === PKG_ROOT;
33
- export const SOURCE = join(PKG_ROOT, '.claude/skills/yt/SKILL.md');
34
- /** Agent key → display name + the skills subdirectory it scans. */
40
+ /** Source path of a shipped skill's SKILL.md, by skill name. */
41
+ export const skillSource = (name) => join(PKG_ROOT, '.claude', 'skills', name, 'SKILL.md');
42
+ /** Agent key → display name + the skills ROOT directory it scans (skills install under it). */
35
43
  export const AGENTS = {
36
- '1': { name: 'Claude Code', sub: join('.claude', 'skills', 'yt') },
37
- '2': { name: 'Cursor', sub: join('.cursor', 'skills', 'yt') },
44
+ '1': { name: 'Claude Code', sub: join('.claude', 'skills') },
45
+ '2': { name: 'Cursor', sub: join('.cursor', 'skills') },
38
46
  };
39
47
  /**
40
- * The shipped SKILL.md. `dist=false` (default) returns it verbatim — the `bun run src/X.ts`
41
- * dev form, correct only when cwd is the package AND the runtime is Bun. `dist=true` rewrites
42
- * for the consumed case: engine commands become `"<process.execPath>" "<abs>/dist/X.js"` (this
43
- * machine's runtime, Node or Bun, against the compiled build, so they run from any cwd), and the
44
- * bare `data/…` paths the agent reads (e.g. `data/config.json`) become the absolute `DATA_DIR`.
45
- * In dev the agent's cwd IS the package so `data/` resolves; when consumed, DATA_DIR moves to
46
- * `<project>/.yt-briefing/data`, so the dev-relative paths would miss — hence the rewrite.
48
+ * One shipped skill's SKILL.md. `dist=false` (default) returns it verbatim — the
49
+ * `bun run src/X.ts` dev form, correct only when cwd is the package AND the runtime is Bun.
50
+ * `dist=true` rewrites for the consumed case: engine commands become
51
+ * `"<process.execPath>" "<abs>/dist/X.js"` (this machine's runtime, Node or Bun, against the
52
+ * compiled build, so they run from any cwd), and the bare `data/…` paths the agent reads (e.g.
53
+ * `data/config.json`) become the absolute `DATA_DIR`. In dev the agent's cwd IS the package so
54
+ * `data/` resolves; when consumed, DATA_DIR moves to `<project>/.yt-briefing/data`, so the
55
+ * dev-relative paths would miss — hence the rewrite.
47
56
  */
48
- export function skillBody(dist = false) {
49
- const raw = readFileSync(SOURCE, 'utf8');
57
+ export function skillBody(name, dist = false) {
58
+ const raw = readFileSync(skillSource(name), 'utf8');
50
59
  if (!dist)
51
60
  return raw;
52
61
  const exe = process.execPath;
@@ -54,17 +63,24 @@ export function skillBody(dist = false) {
54
63
  return raw
55
64
  .replace(/bun run src\/yt-sweep\.ts/g, cmd('yt-sweep'))
56
65
  .replace(/bun run src\/yt-rating\.ts/g, cmd('yt-rating'))
66
+ .replace(/bun run src\/yt-transcript\.ts/g, cmd('yt-transcript'))
57
67
  .replace(/data\//g, DATA_DIR + '/');
58
68
  }
59
- /** Write the skill into `dir` (created if needed). Returns the SKILL.md path written. */
60
- export function installSkill(dir, dist = false) {
61
- mkdirSync(dir, { recursive: true });
62
- const target = join(dir, 'SKILL.md');
63
- writeFileSync(target, skillBody(dist), 'utf8');
64
- return target;
69
+ /**
70
+ * Write every shipped skill into `root`, each under its own `<name>/SKILL.md` subdir
71
+ * (created if needed). Returns the SKILL.md paths written, in `SKILLS` order.
72
+ */
73
+ export function installSkills(root, dist = false) {
74
+ return SKILLS.map((name) => {
75
+ const dir = join(root, name);
76
+ mkdirSync(dir, { recursive: true });
77
+ const target = join(dir, 'SKILL.md');
78
+ writeFileSync(target, skillBody(name, dist), 'utf8');
79
+ return target;
80
+ });
65
81
  }
66
- /** The agent's skills directory inside a project folder (the project you open in the agent). */
67
- export const projectSkillDir = (agentKey, projectDir) => join(projectDir, AGENTS[agentKey].sub);
82
+ /** The agent's skills ROOT inside a project folder (the project you open in the agent). */
83
+ export const projectSkillsRoot = (agentKey, projectDir) => join(projectDir, AGENTS[agentKey].sub);
68
84
  /** Suggested target for a "custom" (any other agent) install — the open `.agents` convention,
69
85
  * rooted at the user's current project (not the package, which may be in node_modules). */
70
- export const customSkillDirDefault = () => join(process.cwd(), '.agents', 'skills', 'yt');
86
+ export const customSkillsRootDefault = () => join(process.cwd(), '.agents', 'skills');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yt-briefing",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "A self-learning YouTube briefing engine: it sweeps the channels you follow, filters noise in two stages (title, then transcript), summarizes the rest in your language, and adapts to your ratings — one video at a time.",
5
5
  "type": "module",
6
6
  "bin": {