Yes, without the viewer it's a bit different.
You can replace
def server = viewer.getServer()
with this
def imageData = getCurrentImageData()
def server = imageData.getServer()
However, that alone won't be enough because
viewer.getOverlayLayers().toList()
will of course be dependent on the image currently open, because the current overlay within the viewer is based on the current image data open in that viewer.
You
could start working with overlays directly (e.g.
HierarchyOverlay) but then it might be better to work with painting independently of overlays (since overlays are really intended for the viewer... and you'd now be trying to avoid the viewer here).
Basically, anything should be possible - but it requires defining exactly what you want. Hopefully the above script and these hints help give a starting point in the QuPath code.