Moises watch folder, the plan and the measured geometry

Automator watches the folder. Something smarter does the clicking. Every number below was measured on Jacks-iMac, not assumed.

Probed 2026-09-05, built and verified 2026-09-07 · Dr Wu iMac · macOS 13.7.8 · Moises.app 2.0.3 · Chrome, Moises Pro account

The thesis proved itself in 48 hours measured twice

Every geometry number changed between 2026-09-05 and 2026-09-07, with no deliberate action, just normal browsing.

 Sep 5Sep 7
Transform origin215, 2020, 130
Chrome window1778 × 10752048 × 1031
Vertical chrome1779
Side panelclosedopen, 386 pt
ButtonSep 5Sep 7Drift
Play New Track609, 276384, 204-225, -72
Studio New project863, 276619, 204-244, -72
Mastering New Track1117, 276853, 204-264, -72
Voice Studio New Track1370, 2761088, 204-282, -72

Read the middle two rows together. The Sep 5 point for Studio New project was x 863. On Sep 7, Mastering New Track sits at x 853, ten points away on a button 234 points wide. A stored coordinate would have started a mastering job, called it a stem split, and reported success.

Evidence. Guard check against the Sep 5 fingerprint returned exit 1: recorded [2048,1152]|2.0|1|[1778,1075]|177|0|2, live [2048,1152]|2.0|1|[2048,1031]|9|386|2.
The short version. You want to drop a song in a folder and have the computer put it into Moises for you and get the pieces back. The Mac has a built in tool called Automator that is good at noticing when a file lands in a folder. It is bad at clicking buttons inside a website. So Automator does the noticing, and a small script does the clicking. The tricky part is that a button on screen moves whenever a window moves, so instead of remembering where the button was, the script asks the page where the button is right now, every single time.
The one decision that carries the rest: stop storing button positions. Ask the page for them at run time. The measurements below show four separate things that move a Moises button on this exact Mac, and three of them moved during the hour this page was written.

1. What was measured

Display

PropertyValueWhy it matters
Panel5120 × 288027" iMac Retina 5K, Radeon Pro 570, internal, single display
Logical size (points)2048 × 1152The space pyautogui clicks in. This is the number that matters.
Screenshot size (pixels)4096 × 2304What screencapture and Pillow return
Backing scale2.0Screenshot coords are exactly double the click coords
Menu bar24 ptTop inset
Usable heighty 25 to 1101screen.availHeight 1076, so the Dock takes 51 pt at the bottom
Displays attached1No second monitor, so no origin offset problem today

The retina trap, confirmed. pyautogui.size() returns 2048 × 1152. A screenshot of that same screen comes back 4096 × 2304. Anything found by image matching reports a position in the second space and clicks in the first, landing at double the intended coordinate, which on a 2048 wide screen means clicking off the edge of the display. The old moises_batch_splitter.py dodged this by refusing to use image matching, which was the right instinct.

The scaling mode is not the default. A 5K iMac defaults to a 2560 × 1440 logical space. This machine is set to 2048 × 1152, a Larger Text mode. Every coordinate on this page is valid only in that mode. Changing the Displays setting invalidates all of them silently, with no error, and the clicks land on whatever moved into that spot.

Chrome window

PropertyValueNote
Window bounds215, 25, 1993, 1100left, top, right, bottom
Window size1778 × 1075Not maximised. 215 pt of empty desktop on the left.
Viewport, Studio home1778 × 898No side panel
Viewport, player2 tab1392 × 890Side panel open, 386 pt narrower
Vertical chrome177 or 185Varies by page in the same window
Horizontal chrome0 or 386Side panel is per tab, not per window
devicePixelRatio2CSS pixels map 1:1 to points here

Four things move a Moises button on this Mac all measured

  1. Display scaling mode. Currently 2048 × 1152. A change rescales everything.
  2. Chrome window origin. Currently x 215, y 25. Dragging the window moves every button by the same delta.
  3. The side panel. 386 pt wide, and it is per tab. The player2 tab has it open, the Studio home tab does not. Same window, same session, different button positions.
  4. Vertical browser chrome. 177 pt on one page, 185 pt on another, in the same window during the same probe run.
Item 3 and item 4 both changed inside a single probe session. That is the argument against calibration in one sentence.

2. The decisions

Drive Chrome, not Moises.app decided

They are the same product. The desktop app is a ToDesktop Electron shell whose bundle loads https://studio.moises.ai from the network. Choosing the browser costs no features at all.

What the browser adds: Chrome answers AppleScript, so a script can set the window to a known rectangle before every run and read the live DOM for button positions. The Electron shell answers neither.

Evidence. Bundle id com.todesktop.220410vabqjud82, version 2.0.3. Strings in app.asar resolve to studio.moises.ai, studio-stage.moises.ai, plugins.moises.ai, and zero local player code. NSAppleScriptEnabled is absent from its Info.plist. System Events reports 0 windows for the Moises process. Chrome answered execute javascript "1+1" with 2, and bounds of window 1 with 215, 25, 1993, 1100.

Target the new Studio, and assert the URL decided

The account is already on the new stack. The navigation reads Home, Play, Studio (badged New), Mastering, Voice Studio, Lyric Writer, and the account shows Pro. The old app.moises.ai player is out of scope.

The split flow lives behind Studio New project on studio.moises.ai/home. The runner should assert it landed on that host and that the button exists, then stop if either check fails, rather than click into a redesigned page.

Evidence. Live tab studio.moises.ai/player2/e6a75b25… titled "01 - Inca Roads | Moises Play". Account label 7onething1 Pro. Home page exposes Play New Track, Studio New project, Mastering New Track, Voice Studio New Track.

Do not deep link the upload page tested, fails

studio.moises.ai/upload/split/3 looks like a shortcut into the split flow. Loading it cold renders the sidebar and nothing else. Three dialog nodes exist in the DOM with no text and no file input, so the upload modal is state pushed by the app rather than a routable page.

Consequence: the run has to pass through a real click on Studio New project, which is why solving that one button's position correctly is the load bearing step.

Evidence. Cold load of that URL returned fileInputs: 0, dialogs: 3, and a main region containing only sidebar text.

Automator watches the folder and nothing else decided

A Folder Action is genuinely good at one job: noticing a file landed and handing the path to a script. It is native, survives reboots, needs no daemon, and costs nothing. That is the job it gets.

Automator's Watch Me Do is rejected for the clicking. It records against the Accessibility tree, and a Chromium render surface exposes almost none of one, so replay degrades to raw screen coordinates. Those coordinates break on all four variables measured above, and Watch Me Do has no way to notice it missed. It clicks the wrong place and reports success.

Evidence. UI elements enabled returns true, and window enumeration still returns 0 windows for Moises, Chrome, and Firefox from this context. There is nothing stable for a recorder to anchor to.

Solve every coordinate at run time decided

Ask the page where the button is, convert to screen points, click, discard. Nothing is stored between runs except a guard string.

screen_x = window.screenX + rect.centerX - scrollX
screen_y = window.screenY + (outerHeight - innerHeight) + rect.centerY - scrollY
screenshot_px = screen_pt × backing_scale        # ×2 on this Mac

Worked from today's live probe of studio.moises.ai/home, with origin (215, 202):

ButtonCSS centreScreen pointScreenshot pixel
Play New Track394, 74609, 2761218, 552
Studio New project648, 74863, 2761726, 552
Mastering New Track902, 741117, 2762234, 552
Voice Studio New Track1155, 741370, 2762740, 552
These four rows are examples, not settings. They are correct for a Chrome window at 215, 25 with no side panel, at 2048 × 1152 scaling. Copying them into a script is the exact mistake this page exists to prevent.

The API is the real endgame, and it is not free your call

~/Projects/moises_api.py is a working Music.AI REST client, debugged as recently as today, including the Cloudflare 403 that reads like an auth failure and is not one. It uploads, creates jobs, polls, and downloads, with no browser involved.

Two honest blockers. There is no key on this machine, neither MUSIC_AI_KEY nor ~/.music_ai_key. And Music.AI API credits bill separately from the Moises Pro subscription, so a Pro plan does not unlock it. The browser route uses the subscription you already pay for.

The plan below keeps the queue and the filing layer identical for both, so swapping the driver later is a one file change rather than a rewrite.

Evidence. test -f ~/.music_ai_key returned not found, MUSIC_AI_KEY unset. Client targets https://api.music.ai/v1.

3. The three phases

Phase 1 · done today

Measure the machine and build the guard

Phase 2 · built and verified 2026-09-07

The watcher and the driver

Installed, attached, and exercised. The split in blast radius is the design point: queue maintenance runs on a timer forever, the mouse-driving pass stays a command you start on purpose.

Phase 3 · not started

Prove it on one real song

Everything after the click is unverified. The native file panel, the separation options, completion detection, and stem download have not been exercised, so the runner dispatches the upload and then stops rather than marking anything done.

Two things found while building worth your call

Clicking Studio New project creates an empty multitrack project. It does not open an upload dialog. The split-a-song entry point is Play New Track, and the runner targets that. Mapping this created one stray project in your account named Iron Station at studio.moises.ai/studio/2ifceRwlu9Kro. It is empty. I left it alone rather than deleting anything in your account.

Moises Studio has a hidden Studio MCP panel. If that is a real MCP server it would replace this whole clicking layer with tool calls. It does not render until the panel is opened, so it needs a look before more effort goes into the browser path.

4. What breaks each approach

ApproachBreaks whenDoes it notice?Verdict
Automator Watch Me DoWindow moves, side panel toggles, scaling changes, Moises ships a redesignNo. Clicks the wrong spot, reports successRejected
Stored coordinates, the May scriptSame four variables, plus the stale ~/Desktop pathsOnly if a screenshot check is addedRetire
Image matching on screenImmediately, from the 2.0 backing scaleReturns a wrong match, or nothingRejected
DOM solved coordinatesMoises renames or removes the buttonYes. Button missing means stopBuild this
Music.AI APINever, for geometry reasonsYes, real job statusEndgame, needs a key

5. Files

PathStateWhat it is
enqueue.shliveBody of the Folder Action. Appends a path and exits in milliseconds.
queue.pytestedDurable queue: flock, debounce, retries, filing, attempt return
install_folder_action.pyinstalledBuilds the .workflow bundle and attaches it. Reproducible.
chrome_ctl.pytestedAppleScript to Chrome: session check, live geometry, DOM rects, clicks
moises_runner.pydry run okClaims one item, asserts everything, drives the upload
drain.sh + com.drwu.moises-watchloaded60 s maintenance pass. Safe by default, --drive to upload.
probe_geometry.pybuilt, runStandalone measurement and drift guard
geometry_2026-09-05.json, _home.json, 2026-09-07.jsonwrittenThree fingerprints. The set is what proved the drift and the guard.
Projects/moises_automation/moises_batch_splitter.pystaleMay 2026 calibrated clicker. Desktop paths, dead since the June offload.
Projects/moises_api.pyno keyWorking Music.AI client, waiting on credentials

Daily use

cd ~/Projects/_outputs/moises-watch-folder

# drop songs into inbox/ from anywhere, then
python3 queue.py status              # what is waiting
python3 moises_runner.py --dry-run   # show every click it would make, touch nothing
python3 moises_runner.py --run       # upload one song, hands off the keyboard

python3 moises_runner.py --map       # when Moises changes its layout

Abort a real run by slamming the mouse into the top left corner. The pyautogui failsafe raises immediately. Ctrl+C also works.

Uninstall

python3 install_folder_action.py --detach
launchctl unload ~/Library/LaunchAgents/com.drwu.moises-watch.plist

The probe needs a Moises tab open in Chrome and Chrome's View → Developer → Allow JavaScript from Apple Events enabled. It is already on, verified by the probe returning live rects.