Opens Instagram Reels in a small centered Safari window while Claude works, autoscrolls, and closes it when Claude finishes.

A Claude Code mod for macOS. When you submit a prompt, it opens Instagram Reels in a small window centered on your screen (in Safari, so your Chrome windows stay put), scrolls to the next reel each time one finishes, and closes the window and hands focus back to you when Claude is done.
In a Claude Code terminal session:
/plugin install reels-while-you-wait --marketplace jemsley06/reels-while-you-wait
Answer y to add the marketplace, then press Enter for the user scope.
Type /reels to toggle it. Your choice is remembered across sessions.
/ (slash commands) never open Reels.hooks/register.ts 112 lines1import type { Register } from 'claude-code'
2import { AUTOSCROLL_JS } from './autoscroll'
3
4const OPEN = `use framework "AppKit"
5use scripting additions
6set prev to path to frontmost application as text
7set wasRunning to application "Safari" is running
8-- \`launch\` starts Safari without the Start Page window a plain start opens
9if not wasRunning then tell application "Safari" to launch
10-- the screen holding the focused window; center a phone-sized 420x760 window on it
11set {{sx, sy}, {sw, sh}} to current application's NSScreen's mainScreen's frame() as list
12set x to sx + (sw - 420) div 2
13set y to (sh - 760) div 2
14tell application "Safari"
15 set existing to id of every window
16 make new document with properties {URL:"https://www.instagram.com/reels/"}
17 -- find our window as the new one on Instagram: right after creation \`front window\` can still be one of yours
18 set w to missing value
19 repeat 50 times
20 repeat with o in (every window)
21 if (id of o) is not in existing and (URL of current tab of o as text) contains "instagram.com" then set w to contents of o
22 end repeat
23 if w is not missing value then exit repeat
24 delay 0.1
25 end repeat
26 set bounds of w to {x, y, x + 420, y + 760}
27 set wid to id of w
28 -- Safari can still open its Start Page a moment after starting; close it so only Reels shows
29 if not wasRunning then
30 delay 1
31 repeat with o in (every window whose id is not wid)
32 if (URL of current tab of o as text) is in {"favorites://", "about:blank", "missing value"} then close o
33 end repeat
34 end if
35end tell
36-- not \`activate\`: that raises every Safari window over Claude; this raises only the new (key) one
37tell application "System Events" to set frontmost of process "Safari" to true
38return (wid as text) & linefeed & prev & linefeed & wasRunning`
39
40// every step checks Safari is running first: a \`tell\` to a quit Safari relaunches it, restoring old windows
41const INJECT = `on run argv
42 if application "Safari" is running then tell application "Safari" to do JavaScript (item 2 of argv) in current tab of (first window whose id is (item 1 of argv as integer))
43end run`
44
45const CLOSE = `on run argv
46 if not (application "Safari" is running) then return
47 -- read before closing: are you still watching, or have you moved on to something else?
48 tell application "System Events" to set stillWatching to frontmost of process "Safari"
49 tell application "Safari"
50 -- we started Safari: quit it unless you have a real page open (decided before closing: closes settle late)
51 set yours to 0
52 if item 3 of argv is "false" then
53 repeat with o in (every window whose id is not (item 1 of argv as integer))
54 if (URL of current tab of o as text) is not in {"favorites://", "about:blank", "missing value"} then set yours to yours + 1
55 end repeat
56 end if
57 if item 3 of argv is "false" and yours is 0 then
58 quit
59 else
60 close (every window whose id is (item 1 of argv as integer))
61 end if
62 end tell
63 if stillWatching then tell application (item 2 of argv) to activate
64end run`
65
66export const register: Register = on => {
67 // the open in flight, resolving to [windowId, prevApp, wasRunning]; awaited at turn end so a short turn can't miss it
68 // shortcut: it lives in a module variable, so a mod reload mid-turn leaves that window open
69 let opening: Promise<string[] | undefined> | undefined
70
71 on('session.start', async ($, e, next) => {
72 await $.command.register({ name: 'reels', description: 'Turn Reels-while-you-wait on or off' })
73 return next(e)
74 })
75
76 on('command.run', { command: 'reels' }, async $ => {
77 const isOff = !(await $.store.get('off'))
78 await $.store.set('off', isOff)
79 return { text: `Reels while you wait: ${isOff ? 'off' : 'on'}` }
80 })
81
82 on('prompt.submit', async ($, e, next) => {
83 // shortcut: skips every slash command (they may never end a turn, which would strand the window); skills invoked by slash won't get reels
84 if (!opening && !e.text.startsWith('/') && !(await $.store.get('off'))) {
85 opening = $.process.run(['osascript', '-e', OPEN]).then(opened => {
86 if (opened.exitCode === 0) return opened.stdout.trim().split('\n')
87 $.ui.toast(`reels: ${opened.stderr.trim()}`)
88 }).catch(err => void $.ui.toast(`reels: ${err}`))
89 const mine = opening
90 void opening.then(async win => {
91 if (!win || opening !== mine) return // turn already ended
92 await $.clock.sleep(5000) // let Instagram's page mount its <video>s
93 if (opening !== mine) return
94 const injected = await $.process.run(['osascript', '-e', INJECT, win[0], AUTOSCROLL_JS])
95 if (injected.exitCode !== 0)
96 $.ui.toast('reels: autoscroll needs Safari → Settings → Developer → Allow JavaScript from Apple Events')
97 }).catch(() => {}) // the window may already be closed when a turn is short
98 }
99 return next(e)
100 }).catch(($, e, next) => next(e)) // never block your prompt over a reels hiccup
101
102 on('turn.complete', async ($, e, next) => {
103 const result = await next(e)
104 if (!e.agentId && opening) {
105 const win = await opening
106 opening = undefined
107 if (win) await $.process.run(['osascript', '-e', CLOSE, ...win]).catch(() => {})
108 }
109 return result
110 })
111}
112hooks/autoscroll.ts 13 lines1// Injected into the Reels tab. Reels loop instead of firing 'ended', so "finished" = playhead near the end.
2export const AUTOSCROLL_JS = `(() => {
3 if (window.__reelScroll) return;
4 const done = new WeakSet();
5 window.__reelScroll = setInterval(() => {
6 const vids = [...document.querySelectorAll('video')];
7 const v = vids.find(v => !v.paused && v.duration && !done.has(v));
8 if (!v || v.currentTime < v.duration - 0.35) return;
9 done.add(v);
10 vids[vids.indexOf(v) + 1]?.scrollIntoView({ behavior: 'smooth', block: 'center' });
11 }, 250);
12})()`
13