yt-briefing 1.2.1 → 1.2.3

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
@@ -137,9 +137,10 @@ The **Comment** field takes anything else. Type what you think in your own words
137
137
  shows, skip those") and press Enter: Claude turns it into a standing rule for that channel and
138
138
  infers the rating. `? your question` starts research with that question, `stop` closes the pane.
139
139
  Claude Code on a phone has no text fields, so there the pane shows the four keys only.
140
- Where no pane can be seen (`/yt` sent from claude.ai/code over Remote Control, or a surface that
141
- places no panes), the same loop runs in Claude Code's question dialog: the summary as plain text,
142
- OK / Weak / Research / Stop as choices, and a typed **Other** answer works as the Comment field.
140
+ Where no pane can be seen (`/yt` sent from claude.ai/code or the phone app over Remote Control, or
141
+ a surface that places no panes), `/yt` hands the same loop to Claude in the chat: each summary is
142
+ Claude's message, a short dialog asks for the rating (OK / Weak / Research / Stop), and a typed
143
+ **Other** answer works as the Comment field. This path uses the model, so it counts toward plan usage.
143
144
 
144
145
  Each step is the engine, not a chat turn: rating a video takes no turn and no context of your
145
146
  session, and the next summary is usually ready before you have finished reading the current one.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yt-briefing",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "A self-learning YouTube briefing for Claude Code: it sweeps the channels you follow, filters noise in two stages (title, then transcript), gives you a short briefing of the rest in your language in a pane, and adapts to your ratings — one video at a time.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yt-briefing",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "The /yt briefing loop as a Claude Code pane: summary, one-key rating, research hand-off.",
5
5
  "author": {
6
6
  "name": "Michal Ryzio"
@@ -153,56 +153,34 @@ async function comment($: EngineInterface, raw: string) {
153
153
  if (saved?.rule) $.ui.toast(`Rule saved: ${saved.rule}`)
154
154
  }
155
155
 
156
- /** The dialog's choices; anything else is text typed under "Other". */
157
- const CHOICES = ['OK', 'Weak', 'Research', 'Stop'] as const
158
-
159
- /** The dialog draws plain text: drop the briefing's Markdown marks so no `**` or `###` shows. */
160
- function plain(md: string): string {
161
- return md
162
- .replace(/^#{1,6}\s+/gm, '')
163
- .replace(/\*\*(.+?)\*\*/g, '$1')
164
- .replace(/^_(.+)_$/gm, '$1')
165
- .replace(/^(\d+\.)\s+/gm, '$1 ')
166
- .replace(/(\d{4}-\d{2}-\d{2})T[\d:.]+Z/g, '$1')
167
- }
168
-
169
156
  /** True when the person typed /yt through Remote Control and nothing attached can show a pane. */
170
157
  async function paneUnseen($: EngineInterface, origin: { kind: string } | undefined): Promise<boolean> {
171
158
  if (origin?.kind !== 'bridge') return false
172
159
  return !(await $.session.surfaces()).some(s => s !== 'terminal')
173
160
  }
174
161
 
175
- /** The rating loop in the engine's question dialog, from a sweep already run, for a session where no pane can be seen. */
176
- async function dialogLoop($: EngineInterface, first: SweepOut) {
177
- let out = first
178
- for (;;) {
179
- if (out.status !== 'rating_needed' || !out.summary || !out.pending) return void $.ui.log(endText(out))
180
-
181
- const filtered = out.skipped ? `(${out.skipped} filtered out)` : undefined
182
- const question = [plain(out.summary), filtered, '', 'Rating?'].filter(line => line !== undefined).join('\n')
183
- let answer: string
184
- try {
185
- answer = (await $.ui.ask(question, { header: 'yt-briefing', options: CHOICES })).trim()
186
- } catch {
187
- return
188
- }
189
-
190
- if (answer === 'Stop' || answer.toLowerCase() === 'stop' || !answer) return
191
- if (answer === 'Research' || answer.startsWith('?')) {
192
- if (!(await record($, ['--rating', '1']))) return
193
- const q = answer.startsWith('?') ? answer.slice(1).trim() || undefined : undefined
194
- return handOff($, out.pending, out.summary, out.lang, q)
195
- }
196
- const args = answer === 'OK' ? ['--rating', '1'] : answer === 'Weak' ? ['--rating', '0'] : ['--raw-comment', answer]
197
- const saved = await record($, args)
198
- if (!saved) return
199
- $.ui.log(saved.rule ? `Rule saved: ${saved.rule}. Looking for the next video…` : 'Saved. Looking for the next video…')
200
- try {
201
- out = await runSweep($, false)
202
- } catch (err) {
203
- return void $.ui.log(`The engine did not answer: ${String(err)}`)
204
- }
205
- }
162
+ /**
163
+ * The loop for a session where no pane can be seen, handed to the session as a prompt. A plugin
164
+ * cannot show a second transcript row a remote client draws (it cannot run its own command, and
165
+ * its log lines reach only the terminal), so the briefing is the model's own message text and the
166
+ * dialog asks for the rating alone: its question is drawn in a heading face, too large for a briefing.
167
+ */
168
+ function remoteLoop(): string {
169
+ const sweep = engine('yt-sweep').join(' ')
170
+ const rating = engine('yt-rating').join(' ')
171
+ const transcript = [...engine('yt-transcript'), '<videoId>', '--lang', 'auto'].join(' ')
172
+ return [
173
+ 'Run my YouTube briefing here in the chat, one video at a time, until I stop or nothing is left.',
174
+ '',
175
+ `1. Run \`${sweep} --reset\` the first time, \`${sweep}\` after that. It prints one JSON line.`,
176
+ '2. When `status` is `rating_needed`: post `summary` as your whole message, verbatim, Markdown kept. Add nothing before or after it.',
177
+ '3. Then call AskUserQuestion with one question, exactly `«<pending.title>» — rating?` (nothing else in it), header `yt-briefing`, options OK, Weak, Research, Stop, no descriptions or previews.',
178
+ `4. OK: \`${rating} --rating 1\`. Weak: \`${rating} --rating 0\`. Text typed under Other: \`${rating} --raw-comment "<the text>"\`. Then back to step 1, without a word in between.`,
179
+ `5. Research: \`${rating} --rating 1\`, then research the video with me: ask what I want to dig into, pull the transcript with \`${transcript}\` when needed (never paste it), keep what the video claims apart from what you verify.`,
180
+ '6. Stop, or any other `status`: say so in one short line and end.',
181
+ '',
182
+ 'Write in the language of the summaries.',
183
+ ].join('\n')
206
184
  }
207
185
 
208
186
  export const register: Register = on => {
@@ -224,18 +202,10 @@ export const register: Register = on => {
224
202
  }
225
203
  await $.ui.close({ id: PANE })
226
204
  }
227
- // No pane to show progress in: the command itself runs while the first sweep does, so the
228
- // session shows it working, and only then hands over to the dialogs.
229
- let first: SweepOut
230
- try {
231
- first = await runSweep($, true)
232
- } catch (err) {
233
- return { text: `The engine did not answer: ${String(err)}` }
234
- }
235
- if (first.status !== 'rating_needed') return { text: endText(first) }
236
- void dialogLoop($, first)
205
+ // A prompt submitted while this hook holds the command would wait on it: submit once it has answered.
206
+ $.clock.after(0, () => void $.prompt.submit({ text: remoteLoop() }).catch(err => $.ui.toast(`Could not start the briefing: ${String(err)}`)))
237
207
 
238
- return { text: 'Briefing ready: rate it in the question dialog.' }
208
+ return { text: 'No pane here: the briefing runs in the chat.' }
239
209
  })
240
210
 
241
211
  on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {