yt-briefing 1.2.2 → 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.2",
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.2",
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,64 +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 briefing as transcript text: plain, with the filtered-out count at the end. */
176
- function briefing(out: SweepOut): string {
177
- return [plain(out.summary ?? ''), out.skipped ? `(${out.skipped} filtered out)` : undefined]
178
- .filter(line => line !== undefined).join('\n\n')
179
- }
180
-
181
- /** The rating loop in the engine's question dialog, from a sweep already run, for a session where no pane can be seen. */
182
- async function dialogLoop($: EngineInterface, first: SweepOut) {
183
- let out = first
184
- for (let shown = true; ; shown = false) {
185
- if (out.status !== 'rating_needed' || !out.summary || !out.pending) return void $.ui.log(endText(out))
186
-
187
- // The dialog draws its question in a large heading face, so the briefing goes to the
188
- // transcript as text and the dialog asks only for the rating. The first is the command's output.
189
- if (!shown) $.ui.log(briefing(out))
190
- const question = `${out.pending.channel}: rating?`
191
- let answer: string
192
- try {
193
- answer = (await $.ui.ask(question, { header: 'yt-briefing', options: CHOICES })).trim()
194
- } catch {
195
- return
196
- }
197
-
198
- if (answer === 'Stop' || answer.toLowerCase() === 'stop' || !answer) return
199
- if (answer === 'Research' || answer.startsWith('?')) {
200
- if (!(await record($, ['--rating', '1']))) return
201
- const q = answer.startsWith('?') ? answer.slice(1).trim() || undefined : undefined
202
- return handOff($, out.pending, out.summary, out.lang, q)
203
- }
204
- const args = answer === 'OK' ? ['--rating', '1'] : answer === 'Weak' ? ['--rating', '0'] : ['--raw-comment', answer]
205
- const saved = await record($, args)
206
- if (!saved) return
207
- $.ui.log(saved.rule ? `Rule saved: ${saved.rule}. Looking for the next video…` : 'Saved. Looking for the next video…')
208
- try {
209
- out = await runSweep($, false)
210
- } catch (err) {
211
- return void $.ui.log(`The engine did not answer: ${String(err)}`)
212
- }
213
- }
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')
214
184
  }
215
185
 
216
186
  export const register: Register = on => {
@@ -232,18 +202,10 @@ export const register: Register = on => {
232
202
  }
233
203
  await $.ui.close({ id: PANE })
234
204
  }
235
- // No pane to show progress in: the command itself runs while the first sweep does, so the
236
- // session shows it working, and only then hands over to the dialogs.
237
- let first: SweepOut
238
- try {
239
- first = await runSweep($, true)
240
- } catch (err) {
241
- return { text: `The engine did not answer: ${String(err)}` }
242
- }
243
- if (first.status !== 'rating_needed') return { text: endText(first) }
244
- 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)}`)))
245
207
 
246
- return { text: briefing(first) }
208
+ return { text: 'No pane here: the briefing runs in the chat.' }
247
209
  })
248
210
 
249
211
  on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {