yt-briefing 1.2.3 → 1.2.4
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yt-briefing",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
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": {
|
|
@@ -159,30 +159,82 @@ async function paneUnseen($: EngineInterface, origin: { kind: string } | undefin
|
|
|
159
159
|
return !(await $.session.surfaces()).some(s => s !== 'terminal')
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
*/
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
162
|
+
// Where no pane can be seen the loop runs in the chat. A phone or web client draws a plugin's
|
|
163
|
+
// command output and its dialogs, but not its log lines, and it folds text written mid-turn into a
|
|
164
|
+
// one-line digest; only a turn's last message shows whole. So each briefing is the model's whole
|
|
165
|
+
// answer to a prompt this plugin submits, and the rating dialog opens once that turn has ended.
|
|
166
|
+
|
|
167
|
+
/** True while the chat loop runs; a prompt the person types ends it. */
|
|
168
|
+
let looping = false
|
|
169
|
+
|
|
170
|
+
/** An answer this soon after its dialog opened is a tap meant for the dialog before it. */
|
|
171
|
+
const STRAY_TAP_MS = 1500
|
|
172
|
+
|
|
173
|
+
const CHOICES = ['OK', 'Weak', 'Research', 'Stop'] as const
|
|
174
|
+
|
|
175
|
+
/** The prompt for one briefing: the model sweeps and answers with the summary alone. */
|
|
176
|
+
function fetchPrompt(reset: boolean): string {
|
|
177
|
+
const sweep = [...engine('yt-sweep'), ...(reset ? ['--reset'] : [])].join(' ')
|
|
172
178
|
return [
|
|
173
|
-
|
|
174
|
-
'',
|
|
175
|
-
|
|
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.',
|
|
179
|
+
`Next video of my YouTube briefing: run \`${sweep}\`. It prints one JSON line.`,
|
|
180
|
+
'If `status` is `rating_needed`, answer with `summary` verbatim as your whole message, Markdown kept: nothing before or after it, no question, no tool call after it (the rating dialog follows on its own).',
|
|
181
|
+
'Otherwise answer with one short line saying why there is nothing to rate. Write in the language of the summaries.',
|
|
183
182
|
].join('\n')
|
|
184
183
|
}
|
|
185
184
|
|
|
185
|
+
/** Submit a prompt once the hook that wants it has answered: one submitted from inside would wait on it. */
|
|
186
|
+
function submitLater($: EngineInterface, text: string) {
|
|
187
|
+
$.clock.after(0, () => void $.prompt.submit({ text }).catch(err => {
|
|
188
|
+
looping = false
|
|
189
|
+
$.ui.toast(`Could not continue the briefing: ${String(err)}`)
|
|
190
|
+
}))
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** After a briefing turn: ask for the rating, record it and fetch the next one. */
|
|
194
|
+
async function rateAfterTurn($: EngineInterface) {
|
|
195
|
+
let out: SweepOut
|
|
196
|
+
try {
|
|
197
|
+
out = await runSweep($, false)
|
|
198
|
+
} catch {
|
|
199
|
+
looping = false
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
if (out.status !== 'rating_needed' || !out.pending || !out.summary) {
|
|
203
|
+
looping = false
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
const title = out.pending.title.length > 80 ? `${out.pending.title.slice(0, 79)}…` : out.pending.title
|
|
207
|
+
let answer: string
|
|
208
|
+
for (;;) {
|
|
209
|
+
const opened = Date.now()
|
|
210
|
+
try {
|
|
211
|
+
answer = (await $.ui.ask(`«${title}» — rating?`, { header: 'yt-briefing', options: CHOICES })).trim()
|
|
212
|
+
} catch {
|
|
213
|
+
looping = false
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
if (!looping) return
|
|
217
|
+
if (Date.now() - opened >= STRAY_TAP_MS) break
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (answer === 'Stop' || answer.toLowerCase() === 'stop' || !answer) {
|
|
221
|
+
looping = false
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
if (answer === 'Research' || answer.startsWith('?')) {
|
|
225
|
+
looping = false
|
|
226
|
+
if (!(await record($, ['--rating', '1']))) return
|
|
227
|
+
const q = answer.startsWith('?') ? answer.slice(1).trim() || undefined : undefined
|
|
228
|
+
return handOff($, out.pending, out.summary, out.lang, q)
|
|
229
|
+
}
|
|
230
|
+
const args = answer === 'OK' ? ['--rating', '1'] : answer === 'Weak' ? ['--rating', '0'] : ['--raw-comment', answer]
|
|
231
|
+
if (!(await record($, args))) {
|
|
232
|
+
looping = false
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
submitLater($, fetchPrompt(false))
|
|
236
|
+
}
|
|
237
|
+
|
|
186
238
|
export const register: Register = on => {
|
|
187
239
|
on('session.start', async ($, e, next) => {
|
|
188
240
|
await $.command.register({
|
|
@@ -202,12 +254,22 @@ export const register: Register = on => {
|
|
|
202
254
|
}
|
|
203
255
|
await $.ui.close({ id: PANE })
|
|
204
256
|
}
|
|
205
|
-
|
|
206
|
-
|
|
257
|
+
looping = true
|
|
258
|
+
submitLater($, fetchPrompt(true))
|
|
207
259
|
|
|
208
260
|
return { text: 'No pane here: the briefing runs in the chat.' }
|
|
209
261
|
})
|
|
210
262
|
|
|
263
|
+
on('prompt.submit', async ($, e, next) => {
|
|
264
|
+
if (looping && e.origin?.kind !== 'plugin') looping = false
|
|
265
|
+
return next(e)
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
on('turn.complete', async ($, e, next) => {
|
|
269
|
+
if (looping && !e.agentId && e.reason === 'answer') void rateAfterTurn($)
|
|
270
|
+
return next(e)
|
|
271
|
+
})
|
|
272
|
+
|
|
211
273
|
on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
|
|
212
274
|
const { Box, Text, Button, Markdown } = $.ui.resolve(e)
|
|
213
275
|
const v = await read($, view)
|