point-and-speak
Health Warn
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Fail
- rm -rf — Recursive force deletion command in helper/test_lookbundle.py
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Point with the mouse while you talk to Claude Code. A macOS mod that adds where your pointer was, and what was under it, to a spoken or typed prompt.
point-and-speak
Point with the mouse while you talk, and Claude sees what you mean.
Dictate with Claude Code's built-in /voice (or type). Say things like "look at this and change it",
"make that smaller" or "fix my circled region". Hover over, or circle, something on screen while you speak.
The mod adds hidden context to your prompt: where the pointer was when you said each word, what was under it,
and one small annotated screenshot. Then it deletes the files after the answer.
Privacy policy · Security · License
It works over any app, not only the terminal. Keep the terminal focused and only hover. A click moves focus.
Install
/plugin install point-and-speak --marketplace powerpratik/point-and-speak
Then follow Set up below. This is a young project. The author uses it on one Mac with macOS 26.
How it works
you speak (built-in /voice) helper (small, on your Mac) Claude Code session
─────────────────────────── ─────────────────────────── ───────────────────
hold Space, say "change this" ───▶ mouse trail, 60 samples a second
"is a microphone in use?" flag ──▶ start and stop time of your speech
while you speak: 2 frames a second, keep only frames where the
screen CHANGED (a static screen is 1 frame); read the element
under the pointer about 3 times a second, and at each pause
you submit the prompt ───────────────────────────────────────────────▶ prompt.submit hook asks the helper
words get times, spread evenly over the speech window
"this/that/here" → the pointer spot at that moment
"circled/highlighted" → a loop drawn with the mouse
writes: overview.png (trail + numbered points),
a crop only where there is no element text
──────────────────────▶ context added to your prompt
the answer finishes ◀─────────────────────────────────────────────────── the files are deleted
What reaches the model, for example:
Spoken text with pointer markers: "change this[@1] to blue and that[@2] to red"
@1 (dwell) at (812,430): Safari, window "Docs", AXButton "Save"
@2 (point) at (1204,310): no accessibility info; image: .../point2.png
Overview with the mouse trail (red) and numbered markers (blue): .../overview.png
The model gets text first. It reads an image only when the text is not enough. A static screen costs one screenshot.
A clear element label costs no image.
What it needs
- macOS. Tested on macOS 26 only.
- Claude Code with
/voicefor speech. The mod also works for typed prompts. uv. It runs the helper. On the first run it downloads five Python packages from PyPI:pillow,numpy,pyobjc-framework-Quartz,pyobjc-framework-ApplicationServicesandpyobjc-framework-Cocoa.
The versions are pinned, a lock file (helper/pointer_helper.py.lock) records them, and a date limit stops uv from
taking anything newer. It also fetches Python 3.12 or 3.13 if the machine has none.- Two macOS permissions for your terminal app (see below). It needs no microphone permission.
It records no audio. It runs no speech-to-text and no text-to-speech. The helper reads one value from CoreAudio:
whether any input device is in use. Claude Code's /voice does the listening and the transcription.
Set up
Permissions. Open System Settings, Privacy & Security. Turn on your terminal app (the app that runs Claude Code) in:
- Screen Recording, for the screenshot
- Accessibility, to read the element under the pointer
Quit and reopen the terminal app after you grant Screen Recording. Accessibility takes effect without a restart.
Check. Run
uv run --script helper/pointer_helper.py --selftestin this folder. Both permissions should saygranted.Turn on. In a session, run
/look on./look statusnames anything that is missing./look offstops the helper
from sampling, makes it drop what it holds, and deletes the folders of the current project.
The helper does nothing until /look on. It samples no mouse and takes no screenshot while the mod is off.
Without the two permissions the mod still works in a reduced way. It adds the spoken text with markers and the pointer
positions, but no screenshot and no element text.
Use
Turn on /voice (tap mode is easiest), hover over what you mean, and say "what is this". A prompt typed while you hover
works too: the mod uses the last 6 seconds of the mouse trail.
By default the context is added only to prompts that contain a pointing word: this, that, these, those, here, there,
circled, circle, highlighted, selected, outlined, boxed. Turn that filter off in /config (onlyWithPointingWords)
to add context to every prompt.
Several pointing words said over one resting spot become one marker, so a long sentence does not make many crops.
Settings (/config):
| Setting | Default | Meaning |
|---|---|---|
onlyWithPointingWords |
on | Add context only to prompts that contain a pointing word |
screenshots |
on | Add the screenshot and crops. Turn off to send only the text, the positions and the control names |
uvPath |
uv |
Path to uv if it is not on the PATH |
Privacy
What is sent to the model. The text of what you said, the pointer positions, and the names of the app, the window and the
control under the pointer. It also sends the label of the control, up to 160 characters of its text, and the page address
(scheme, host and path only, never a query or fragment). If Claude reads an image, the screenshot goes to the model too.
The screenshot is of the whole display. It is downscaled to 1280 pixels wide, with your mouse trail drawn on it. It can
show anything that is on that display: notifications, a password manager, chat windows. The mod does not remove secrets from
it. Hover away from anything you do not want in a prompt, or set screenshots to off.
Password and secret fields. The mod skips the text of a field when the field is a secure text field, or when its label
mentions a password, passcode, token, API key, card number, CVV or similar words. A web or Electron field that exposes
neither a secure role nor such a label can still be read. Treat this as a help, not a guarantee.
Text from the screen is untrusted. A web page or a document can hold text that tries to give the model orders. The mod
flattens the quoted screen text to one line, drops invisible and control characters, caps its length, and tells the model that
it is data and not an instruction. That helps, but it does not make a hostile page safe. Treat hovering over an untrusted page
as asking Claude to read it.
What is stored.
- The files live in
<project>/.claude/lookat/<id>/until the answer ends. They areoverview.png,point<N>.pngcrops,screen_change<N>.pngframes, andcontext.json(positions, marker kinds and loop boxes, but not your prompt text).
A.gitignoreinsidelookat/keeps them out of git. - Claude Code saves each image that Claude reads in the session transcript. Deleting the folder does not remove
those copies. If that matters to you, keep the filter on, so images are made only for pointing prompts, or turnscreenshotsoff. - Nothing else is written. The helper keeps frames in memory, at most 6 recordings, and drops them on
/look off. It deletes
the temporary file thatscreencaptureuses.
When the files are deleted.
- When a turn ends with an answer, the folders of the prompt that turn answered are deleted. A prompt you send while a turn
runs keeps its folder until its own turn ends. - After an interrupted turn, the folder stays for a retry, then goes with the next sweep.
- Folders older than 15 minutes are swept after each answer. Folders older than 1 hour are swept when
/look onruns and
when a session starts with the mod on./look offdeletes the folders of the current project while the helper runs.
Folders in a project that you never open again with the mod on stay until you delete them.
Security of the helper
The helper is a small web server on 127.0.0.1 with a random port. Any program on your Mac, and any web page in your
browser, can send a request to a local port. So the helper protects itself:
- It makes a random secret each time it starts, and gives it only to the mod. Every request must send that secret.
- It refuses a request whose
Hostheader is not127.0.0.1:<port>, which blocks DNS rebinding. - The mod names the one
.claude/lookatfolder that the helper may use. The helper refuses every other folder, even one
that looks right, and refuses any file action before the mod has named one. It accepts only plain folder names,
and it refuses symlinks. - It refuses a request body over 64 KB, a negative length, and a client that stalls for more than 10 seconds.
- It exits on its own if Claude Code goes away.
helper/check_server.py tests these rules against the real helper.
What the mod runs and contacts
Programs it runs. The mod and the helper run no shell, no model, no agent and no MCP tool. They run only these:
| Program | Run by | Why |
|---|---|---|
uv run --script helper/pointer_helper.py |
the mod, once per session | Starts the helper. uv installs the pinned packages. The mod looks for uv at the uvPath setting, then ~/.local/bin/uv, then /opt/homebrew/bin/uv |
screencapture -x -t png -R<x>,<y>,<w>,<h> <temporary file> |
the helper | Takes a screenshot only if the in-process screen grab fails. The temporary file is deleted at once |
ps -o ppid= -p <pid> |
the helper, every 5 seconds | Checks that Claude Code is still running, so the helper can exit when it is gone |
The mod reads one environment variable, HOME, to find uv. It writes none.
What it contacts. The mod calls only the helper, at http://127.0.0.1:<random port> on your Mac. It sends the text of a
prompt (up to 20 KB), the path of the folder for the files, the on/off and screenshot settings, and the secret token. The helper
answers with text and file paths. Neither the mod nor the helper opens a connection to any other host. The one outside
traffic is from uv, not from this plugin: on the first run it downloads the five pinned packages from PyPI
(pypi.org, files.pythonhosted.org), and it downloads Python itself if the machine has no Python 3.12 or 3.13.
The calls in hooks/register.ts, one by one. None of them is written as one fixed text, and each says why.
| Call | Where | What it is | Why it is not fixed text |
|---|---|---|---|
$.process.spawn |
runHelper |
argv is [<uv>, "run", "--script", "<plugin folder>/helper/pointer_helper.py"], run in <plugin folder>/helper. Nothing you say, type or see on screen ever goes into argv. |
Two parts vary: <uv> (the uvPath setting, then ~/.local/bin/uv, then /opt/homebrew/bin/uv) and <plugin folder> (where Claude Code installed the mod) |
$.http.fetch |
call |
One request to the helper on your own Mac: http://127.0.0.1:<port> plus one of /status, /enable, /bundle, /sweep or /cleanup. The method is GET or POST, with the headers content-type and x-look-token, and a JSON body. The address is always the loopback address, never another host, and the mod uses http there because nothing leaves the machine. |
The helper picks a random free port each time it starts and prints it, so the port cannot be written in advance |
$.env.get |
launch |
Reads HOME only, to find ~/.local/bin/uv |
The home folder differs per person |
$.command.register |
session.start |
Registers the /look command |
Fixed text. Listed for completeness |
What each hook does with the calls it sees.
| Hook | What it does |
|---|---|
session.start |
Registers the /look command. If the mod was on last time, it starts the helper, switches it on, and sweeps old folders |
command.run for /look |
Handles /look on, /look off and /look status. It sees no other command |
prompt.submit |
Sees every prompt. It passes a prompt on unchanged unless the mod is on, the person typed or dictated it, and it contains a pointing word (or the filter is off). Then it sends the text to the helper and adds the text that comes back as extra context. It never edits your words and never blocks a prompt. If anything fails, the prompt goes on as it was |
turn.complete |
After the main session answers, it deletes the folders of the prompt that was answered, and sweeps old ones. It sees answers from subagents and ignores them. It returns the result unchanged |
Limits
- The built-in dictation gives no word times, so each word gets an even share of the speech window. The mod snaps a
pointing word to the resting spot nearest to it. This is accurate for one or two pointed things, and less so for many quick ones. - A call app (Teams, Zoom) turns on the same microphone flag. The mod uses a window only when a prompt follows it closely.
- The mod handles the display under the pointer. Tested with one display and with dictated prompts. Typed prompts and
several displays are not yet tested live. - Hover only. A click moves focus away from the terminal.
- macOS only.
Files
hooks/register.ts: the mod (the/lookcommand, theprompt.submithook, the cleanup)helper/pointer_helper.py: mouse, microphone flag, screen, accessibility, and the local serverhelper/lookcore.py: pauses, loops, and matching words to pointer positions (pure, tested)helper/lookimg.py: frame-change check, trail drawing, crops (tested)helper/lookbundle.py: builds the folder and the text, path checks, cleanup (tested)helper/check_server.py: live check of the helper's security rules (20 checks)helper/pointer_helper.py.lock: the locked dependency versionsSECURITY.md: the threat model and how to report a problem
Checks
claude plugin validate .
claude plugin test .
cd helper && uv run --with numpy --with pillow python -m unittest
cd helper && python3 check_server.py # macOS, starts the real helper
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found