yt-briefing 0.9.1 → 0.10.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/README.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # yt-briefing
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/yt-briefing)](https://www.npmjs.com/package/yt-briefing)
4
+ [![license](https://img.shields.io/npm/l/yt-briefing)](./LICENSE)
5
+ [![node](https://img.shields.io/node/v/yt-briefing)](https://www.npmjs.com/package/yt-briefing)
6
+ [![mac](https://github.com/michal90r/yt-briefing/actions/workflows/ci-mac.yml/badge.svg)](https://github.com/michal90r/yt-briefing/actions/workflows/ci-mac.yml)
7
+ [![ubuntu](https://github.com/michal90r/yt-briefing/actions/workflows/ci-ubuntu.yml/badge.svg)](https://github.com/michal90r/yt-briefing/actions/workflows/ci-ubuntu.yml)
8
+ [![windows](https://github.com/michal90r/yt-briefing/actions/workflows/ci-windows.yml/badge.svg)](https://github.com/michal90r/yt-briefing/actions/workflows/ci-windows.yml)
9
+
3
10
  Save hours on YouTube. yt-briefing watches the channels you follow so you don't have to. For
4
11
  each new video it gives you a short briefing in your own language — every point that matters, with
5
12
  only the filler cut, so nothing important is lost. Reading it takes a fraction of the time the
@@ -55,9 +62,9 @@ YT_BRIEFING_LLM_MODEL=gemini-2.5-flash
55
62
  YT_BRIEFING_YOUTUBE_API_KEY=<key> # console.cloud.google.com → enable "YouTube Data API v3"
56
63
  ```
57
64
 
58
- Any OpenAI-compatible endpoint works — see [Providers](#providers) to use OpenRouter, OpenAI, or a
59
- local Ollama instead of Gemini. Miss a key and the engine tells you exactly which one. Keep `.env`
60
- gitignored; `YT_BRIEFING_PROXY` (datacenter/VPS IPs) is the only optional extra.
65
+ Any OpenAI-compatible endpoint works — see [Providers](#providers) to use OpenRouter, OpenAI,
66
+ Anthropic, or a local Ollama instead of Gemini. `YT_BRIEFING_PROXY` (datacenter/VPS IPs) is the
67
+ only optional extra.
61
68
 
62
69
  4. Onboard:
63
70
 
@@ -84,23 +91,32 @@ another language? Just say so when you run it (e.g. `/yt-transcribe <url> in Ger
84
91
  won't change your setup. `--lang pl|en` is separate — it picks which caption track to fetch,
85
92
  not the summary language.
86
93
 
87
- The skill is installed alongside `/yt` by `init` / `install-skill`. From the plain CLI:
94
+ For example:
88
95
 
89
- ```bash
90
- npx yt-briefing transcribe <url-or-id> --lang auto # prints the transcript to stdout
96
+ ```
97
+ /yt-transcribe https://www.youtube.com/watch?v=dQw4w9WgXcQ
91
98
  ```
92
99
 
93
100
  ## Search within a channel
94
101
 
95
102
  Mine one channel's videos for a topic and get a comparison. Run `/yt-search` with a channel and
96
- an intent — e.g. `/yt-search @betterstack which terminal for AI coding`.
103
+ an intent — for example:
104
+
105
+ ```
106
+ /yt-search @betterstack which terminal for AI coding
107
+ ```
97
108
 
98
109
  It covers the channel's **whole history** (not just recent uploads), re-ranks every upload against
99
110
  your intent, then lazily yields one matching video at a time to keep or skip — and synthesizes a
100
111
  comparison from everything you kept.
101
112
 
102
113
  The one flag is `--top N` — how many of the top re-ranked matches to triage (**default 10**). Raise
103
- it to go deeper, lower it for a quicker pass: `/yt-search @betterstack which terminal --top 20`.
114
+ it to go deeper, lower it for a quicker pass:
115
+
116
+ ```
117
+ /yt-search @betterstack which terminal
118
+ /yt-search @betterstack which terminal --top 5
119
+ ```
104
120
 
105
121
  ## Run it
106
122
 
@@ -123,7 +139,8 @@ YT_BRIEFING_LLM_MODEL=gemini-2.5-flash
123
139
  > or switch to a paid key (enable billing, same model) to avoid it.
124
140
 
125
141
  Want something else? Change those three lines for OpenRouter (`https://openrouter.ai/api/v1`),
126
- OpenAI (`https://api.openai.com/v1`), or a local Ollama (`http://localhost:11434/v1`). Set
142
+ OpenAI (`https://api.openai.com/v1`), Anthropic (`https://api.anthropic.com/v1/`,
143
+ e.g. `claude-sonnet-4-6`), or a local Ollama (`http://localhost:11434/v1`). Set
127
144
  `YT_BRIEFING_LLM_BASE_URL`, `_API_KEY`, and `_MODEL` in your root `.env` (see [Setup](#setup)).
128
145
 
129
146
  ## Why an API, not the agent's native model
@@ -13,18 +13,31 @@
13
13
  * agent's cwd IS the package folder AND the runtime is Bun (which runs TypeScript directly).
14
14
  * That's true for the publisher's own day-to-day use, so it stays the default.
15
15
  *
16
- * For everyone else — a Node user, or any install whose cwd won't be the package — we bake an
17
- * absolute, runtime-correct command instead: `"<this runtime>" "<abs>/dist/X.js"`. The runtime
18
- * is `process.execPath` of whoever runs the installer (so `init` under Node bakes node, under
19
- * Bun bakes bun), and it points at the compiled build, so it runs from any cwd. The engine
20
- * resolves data/.env from its own location regardless. (Requires `dist/` — build once with
21
- * `bun run build` / `npm run build`; that's exactly how a Node user already got here.)
16
+ * For everyone else — a Node user, or any install whose cwd won't be the package — we rewrite to
17
+ * a PORTABLE command: `<runtime> "<project-relative>/dist/X.js"`. The runtime is the bare name
18
+ * (`node`/`bun`) resolved from PATH, never an absolute binary; the script and `data/` paths are
19
+ * relative to the PROJECT ROOT, never machine-absolute. The invariant this rests on is the same
20
+ * one the whole package already relies on (paths.ts derives BASE_DIR/DATA_DIR from
21
+ * `process.cwd()` when consumed): the agent runs from the project root. So the rewritten skill is
22
+ * machine-independent — it survives being committed to git and shared across machines (e.g. a
23
+ * Mac dev box and a Linux VPS), which an absolute `process.execPath`/`<abs>/dist` baking did not.
24
+ * (Requires `dist/` — build once with `bun run build` / `npm run build`.)
22
25
  */
23
26
  import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
24
- import { join, resolve } from 'node:path';
25
- import { PKG_ROOT, DATA_DIR } from "./paths.js";
26
- /** Compiled output dir — what a baked (dist) skill command points the runtime at. */
27
+ import { join, resolve, relative, dirname, sep } from 'node:path';
28
+ import { PKG_ROOT, BASE_DIR, DATA_DIR } from "./paths.js";
29
+ /** Compiled output dir — what a rewritten (dist) skill command points the runtime at. */
27
30
  const DIST_DIR = join(PKG_ROOT, 'dist');
31
+ /** Consumed as a dependency? Then PKG_ROOT lives under node_modules (mirrors paths.ts). */
32
+ const CONSUMED = PKG_ROOT.split(sep).includes('node_modules');
33
+ /**
34
+ * The project root the agent runs from — the cwd against which the rewritten skill's relative
35
+ * paths resolve. When consumed, that's the user's project (parent of `<project>/.yt-briefing`);
36
+ * in a clone it's the package itself. Matches how paths.ts picks BASE_DIR.
37
+ */
38
+ const PROJECT_ROOT = CONSUMED ? dirname(BASE_DIR) : PKG_ROOT;
39
+ /** An absolute path expressed relative to PROJECT_ROOT, with POSIX `/` (portable on Windows too). */
40
+ const toProjectRel = (abs) => relative(PROJECT_ROOT, abs).split(sep).join('/');
28
41
  /** The skills this package ships — each lives at `.claude/skills/<name>/SKILL.md`. */
29
42
  export const SKILLS = ['yt', 'yt-transcribe', 'yt-search'];
30
43
  /** True when the installer itself is running under Bun (vs plain Node). */
@@ -56,25 +69,26 @@ export const AGENTS = {
56
69
  /**
57
70
  * One shipped skill's SKILL.md. `dist=false` (default) returns it verbatim — the
58
71
  * `bun run src/X.ts` dev form, correct only when cwd is the package AND the runtime is Bun.
59
- * `dist=true` rewrites for the consumed case: engine commands become
60
- * `"<process.execPath>" "<abs>/dist/X.js"` (this machine's runtime, Node or Bun, against the
61
- * compiled build, so they run from any cwd), and the bare `data/…` paths the agent reads (e.g.
62
- * `data/config.json`) become the absolute `DATA_DIR`. In dev the agent's cwd IS the package so
63
- * `data/` resolves; when consumed, DATA_DIR moves to `<project>/.yt-briefing/data`, so the
64
- * dev-relative paths would miss — hence the rewrite.
72
+ * `dist=true` rewrites for the consumed case into PORTABLE, project-relative form: engine
73
+ * commands become `<node|bun> "<rel>/dist/X.js"` (bare runtime from PATH + a path relative to the
74
+ * project root, so they run on any machine from the project cwd), and the bare `data/…` paths the
75
+ * agent reads (e.g. `data/config.json`) become the project-relative DATA_DIR (`.yt-briefing/data/`
76
+ * when consumed). Nothing machine-absolute is baked, so the rewritten skill can be committed and
77
+ * shared across machines. The runtime name follows whoever runs the installer (Node→`node`,
78
+ * Bun→`bun`); the compiled `dist/` build runs under either.
65
79
  */
66
80
  export function skillBody(name, dist = false) {
67
81
  const raw = readFileSync(skillSource(name), 'utf8');
68
82
  if (!dist)
69
83
  return raw;
70
- const exe = process.execPath;
71
- const cmd = (base) => `"${exe}" "${join(DIST_DIR, base + '.js')}"`;
84
+ const runtime = isBun ? 'bun' : 'node';
85
+ const cmd = (base) => `${runtime} "${toProjectRel(join(DIST_DIR, base + '.js'))}"`;
72
86
  return raw
73
87
  .replace(/bun run src\/yt-sweep\.ts/g, cmd('yt-sweep'))
74
88
  .replace(/bun run src\/yt-rating\.ts/g, cmd('yt-rating'))
75
89
  .replace(/bun run src\/yt-transcript\.ts/g, cmd('yt-transcript'))
76
90
  .replace(/bun run src\/yt-search\.ts/g, cmd('yt-search'))
77
- .replace(/data\//g, DATA_DIR + '/');
91
+ .replace(/data\//g, toProjectRel(DATA_DIR) + '/');
78
92
  }
79
93
  /**
80
94
  * Write every shipped skill into `root`, each under its own `<name>/SKILL.md` subdir
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yt-briefing",
3
- "version": "0.9.1",
3
+ "version": "0.10.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": {
@@ -22,7 +22,8 @@
22
22
  "sweep": "bun run src/yt-sweep.ts",
23
23
  "rate": "bun run src/yt-rating.ts",
24
24
  "transcribe": "bun run src/yt-transcript.ts",
25
- "typecheck": "tsc --noEmit"
25
+ "typecheck": "tsc --noEmit",
26
+ "test": "vitest run"
26
27
  },
27
28
  "engines": {
28
29
  "node": ">=18",
@@ -51,6 +52,7 @@
51
52
  "devDependencies": {
52
53
  "@types/node": "^22",
53
54
  "typescript": "^5.7",
54
- "@types/bun": "latest"
55
+ "@types/bun": "latest",
56
+ "vitest": "^3"
55
57
  }
56
58
  }