vision - fswolf/luna-local-agent GitHub Wiki

Vision

Optional. Lets her look at your screen.

> "What does this error say?"
> "Read my editor for me."
> "Look at the wiki page."
> "What's on my screen?"

Needs grim, and a model that can see: a vision model in LM Studio, or on llama-server the model's mmproj file (see Or llama.cpp directly). When the server says the loaded model can't take images, look_at_screen isn't offered at all, and the tools pane says what's missing.

"vision": {
    "enabled": true,
    "scale": 0.5,
    "max_kb": 4096
}
Key Default Purpose
enabled false Off unless you ask for it
scale 0.5 grim's scale factor. Half a 4K screen still reads fine and is a quarter of the bytes
max_kb 4096 Refuses rather than sending something that takes ten seconds to move

How it works

A tool result is a string: there's nowhere in the tool-calling format to hand back an image. So look_at_screen captures one, stashes it, and returns a sentence saying it did; the image is then attached to the next message as an image_url block. From the model's point of view it asked to look at something and the next thing it saw was a picture.

The image lives for exactly one turn. It isn't written to history, so she can't look back at an earlier screenshot: ask "what about now?" and she takes a new one.

Which window

This is the fiddly part, and the answer depends on how you asked.

Typed at her, the focused window is her own terminal, and the one behind it is whatever you last touched: on a tiling compositor that's close to arbitrary. So typed turns capture the whole screen, which on Hyprland is honest anyway: everything is visible at once.

By voice through a compositor bind, you deliberately focused something before you spoke, so the focused window is exactly right.

Either way she never photographs herself: her own terminal is found by walking /proc up from her process, and skipped.

You can also just name it. The model passes your words through and the matching happens in Python, including the generic words people actually use:

"look at my browser"       -> firefox
"what's in my editor"      -> Code
"read the music player"    -> kitty running ncmpcpp

/look tests all of it without involving the model:

/look              list the windows she can choose from
/look full         the whole screen
/look select       drag a box, like a screenshot bind
/look firefox      one window by name

That separates "is grim working" from "can this model see", which are the two ways this fails and they look identical from the outside.

The camera

She can also look through your webcam, one picture at a time, when you ask.

> "What am I holding?"
> "Read this label for me."
> "Luna, look at this." (on stream, holding something up)

It's off until you switch it on. Open the tools pane (Tab twice), find the Camera group and press Space on look_at_camera, or type /camera on. On a fresh install, and in any config written before the camera existed, it stays off.

Once it's on:

  • One picture per request. No live feed, nothing in the background.
  • Nothing is saved. The picture lives for one turn, like a screenshot, and goes only to your local model.
  • Only for you at the desk. It's never offered to stream chat, Discord turns or scheduled jobs, so nobody can ask her to look but you.
  • You see it happen. Each picture shows Camera: took one picture in the conversation, and your camera's own light comes on.

It needs OpenCV, which works on Linux, macOS and Windows:

pip install opencv-python-headless      # or: python3 installer/install.py --extras camera

and, like the screen, a model that can see.

/camera            on or off, which camera, whether it's ready
/camera list       every camera that opens, with its size
/camera test       take a picture without sending it anywhere
"camera": { "device": 0, "width": 1280, "warmup": 5 }
Key Default Purpose
device 0 Which camera: a number (/camera list shows them; a virtual camera from OBS can take number 0), a device file like /dev/video2 on Linux, or a camera server's URL (below)
width 1280 Pictures are scaled down to this, plenty for reading a label
warmup 5 Frames thrown away first, because a webcam's first frame is nearly black

A camera server instead of a webcam. If your camera is already served over the network (mjpg-streamer, ustreamer, go2rtc, motion, an IP camera, a phone app like IP Webcam), point her at its URL:

/set camera.device http://192.168.1.20:8080/?action=snapshot
/set camera.device http://user:[email protected]:8080/video
/set camera.device rtsp://192.168.1.20:8554/cam
  • A snapshot URL (answers with one JPEG) is the fastest and best.
  • An MJPEG stream works too: she reads until the first whole picture and hangs up.
  • Both of those go straight over HTTP and don't need OpenCV at all. user:password@ in the URL is sent as a normal login, and never shown back in her messages.
  • RTSP goes through OpenCV.

That's also the easy way round Linux webcam trouble: a server like ustreamer owns the device, and everything else (OBS, a browser, Luna) reads from it instead of fighting over /dev/video0.

First time on each system:

  • macOS asks whether the terminal Luna runs in (Terminal, iTerm, kitty) may use the camera. If you said no, pictures fail or come back black: allow it in System Settings > Privacy & Security > Camera, then restart Luna.
  • Windows needs Settings > Privacy > Camera > Let desktop apps access your camera.
  • Linux usually just works. She asks for the camera through V4L2 in MJPEG, which most USB webcams need to give a full-size picture. If it can't open the camera, check that you're in the video group and that nothing else holds it. One webcam often shows up as two /dev/video files and only one gives pictures; /camera list shows which.

On every system, a camera that OBS or Discord is already using may refuse to open. She'll say so rather than guess.