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.
- package/.claude/skills/yt-transcribe/SKILL.md +62 -0
- package/README.md +18 -2
- package/dist/bootstrap.js +13 -12
- package/dist/install-skill.js +24 -21
- package/dist/lib/skill-install.js +43 -27
- package/package.json +1 -1
|
@@ -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
|
|
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,
|
|
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('
|
|
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
|
|
152
|
-
// i.e. wherever you ran the command (the package clone in dev, or your own
|
|
153
|
-
// package is a dependency). The command baked in is the shipped `bun run src`
|
|
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
|
|
158
|
-
?
|
|
159
|
-
:
|
|
160
|
-
|
|
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
|
|
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 {
|
package/dist/install-skill.js
CHANGED
|
@@ -1,51 +1,54 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* install-skill — copy
|
|
4
|
-
* agent detects
|
|
5
|
-
* `"<this runtime>" "<abs>/dist/X.js"` (the compiled build), so it works
|
|
6
|
-
* working directory or runtime (the engine resolves data/.env from its own
|
|
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
|
|
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
|
|
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
|
|
16
|
-
* ships often
|
|
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,
|
|
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(
|
|
22
|
-
console.log(
|
|
23
|
-
|
|
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
|
|
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
|
|
33
|
-
// Arbitrary location → bake the absolute dist
|
|
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(
|
|
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
|
|
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
|
|
45
|
-
done(
|
|
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
|
|
50
|
-
done(
|
|
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
|
|
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
|
|
5
|
+
* `install-skill.ts` command, so both write the skills identically.
|
|
6
6
|
*
|
|
7
|
-
* The
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
|
|
34
|
-
|
|
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'
|
|
37
|
-
'2': { name: 'Cursor', sub: join('.cursor', 'skills'
|
|
44
|
+
'1': { name: 'Claude Code', sub: join('.claude', 'skills') },
|
|
45
|
+
'2': { name: 'Cursor', sub: join('.cursor', 'skills') },
|
|
38
46
|
};
|
|
39
47
|
/**
|
|
40
|
-
*
|
|
41
|
-
* dev form, correct only when cwd is the package AND the runtime is Bun.
|
|
42
|
-
* for the consumed case: engine commands become
|
|
43
|
-
* machine's runtime, Node or Bun, against the
|
|
44
|
-
* bare `data/…` paths the agent reads (e.g.
|
|
45
|
-
* In dev the agent's cwd IS the package so
|
|
46
|
-
* `<project>/.yt-briefing/data`, so the
|
|
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(
|
|
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
|
-
/**
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return
|
|
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
|
|
67
|
-
export const
|
|
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
|
|
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
|
+
"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": {
|