simonw / simonw/llm

Enable mac clipboard via pngpaste (Support unseekable attachments)

Open
#971 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
12.5k
Forks
998
Avg merge
6d 12h
Merged PRs (30d)
15

Description

It would be so lovely if this would just work (on macos):

llm -m gemma3:27b "Transcribe to markdown" -a <(pngpaste -)

but unfortunately it doesn't (see below for the error output).

Background: pngpaste outputs what is currently on the clipboard, so pressing shift-cmd-4 and outlining an area (shortcut for "copy part of screen to clipboard as an image") and running the above command would be a pretty neat hack.

The reason for the exception is that the process substitution input file "<()" is not seekable. My proposal: if the input file for -a is not seekable, read it into a buffer for up to 1GB, then use that buffer with bytesio.

I can make a PR if this sounds ok.

Another alternative: read and save the first bytes, use those to do the magic check, and then stream all the rest of the bytes in. If that works for all filetypes, that could even just be the default.

Traceback (most recent call last):
  File "/Users/tjl/.local/pipx/venvs/llm/lib/python3.13/site-packages/puremagic/main.py", line 214, in _file_details
    fin.seek(-max_foot, os.SEEK_END)
    ~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
io.UnsupportedOperation: File or stream is not seekable.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/tjl/.local/bin/llm", line 8, in <module>
    sys.exit(cli())
             ~~~^^
  File "/Users/tjl/.local/pipx/venvs/llm/lib/python3.13/site-packages/click/core.py", line 1161, in __call__
    return self.main(*args, **kwargs)
           ~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/Users/tjl/.local/pipx/venvs/llm/lib/python3.13/site-packages/click/core.py", line 1082, in main
    rv = self.invoke(ctx)
  File "/Users/tjl/.local/pipx/venvs/llm/lib/python3.13/site-packages/click/core.py", line 1695, in invoke
    sub_ctx = cmd.make_context(cmd_name, args, parent=ctx)
  File "/Users/tjl/.local/pipx/venvs/llm/lib/python3.13/site-packages/click/core.py", line 949, in make_context
    self.parse_args(ctx, args)
    ~~~~~~~~~~~~~~~^^^^^^^^^^^
  File "/Users/tjl/.local/pipx/venvs/llm/lib/python3.13/site-packages/click/core.py", line 1417, in parse_args
    value, args = param.handle_parse_result(ctx, opts, args)
                  ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/Users/tjl/.local/pipx/venvs/llm/lib/python3.13/site-packages/click/core.py", line 2403, in handle_parse_result
    value = self.process_value(ctx, value)
  File "/Users/tjl/.local/pipx/venvs/llm/lib/python3.13/site-packages/click/core.py", line 2359, in process_value
    value = self.type_cast_value(ctx, value)
  File "/Users/tjl/.local/pipx/venvs/llm/lib/python3.13/site-packages/click/core.py", line 2345, in type_cast_value
    return tuple(convert(x) for x in check_iter(value))
  File "/Users/tjl/.local/pipx/venvs/llm/lib/python3.13/site-packages/click/core.py", line 2345, in <genexpr>
    return tuple(convert(x) for x in check_iter(value))
                 ~~~~~~~^^^
  File "/Users/tjl/.local/pipx/venvs/llm/lib/python3.13/site-packages/click/core.py", line 2319, in convert
    return self.type(value, param=self, ctx=ctx)
           ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/tjl/.local/pipx/venvs/llm/lib/python3.13/site-packages/click/types.py", line 84, in __call__
    return self.convert(value, param, ctx)
           ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
  File "/Users/tjl/.local/pipx/venvs/llm/lib/python3.13/site-packages/llm/cli.py", line 207, in convert
    return resolve_attachment(value)
  File "/Users/tjl/.local/pipx/venvs/llm/lib/python3.13/site-packages/llm/cli.py", line 195, in resolve_attachment
    mimetype = mimetype_from_path(str(path))
  File "/Users/tjl/.local/pipx/venvs/llm/lib/python3.13/site-packages/llm/utils.py", line 40, in mimetype_from_path
    type_ = puremagic.from_file(path, mime=True)
  File "/Users/tjl/.local/pipx/venvs/llm/lib/python3.13/site-packages/puremagic/main.py", line 270, in from_file
    head, foot = _file_details(filename)
                 ~~~~~~~~~~~~~^^^^^^^^^^
  File "/Users/tjl/.local/pipx/venvs/llm/lib/python3.13/site-packages/puremagic/main.py", line 216, in _file_details
    fin.seek(0)
    ~~~~~~~~^^^
io.UnsupportedOperation: File or stream is not seekable.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with resolve_attachment() in llm/cli.py and mimetype_from_path() in llm/utils.py, then reproduce the issue using the pngpaste process-substitution command shown. Check how attachment input is handled when it is not seekable, and consider the issue's proposed buffering behavior. Done means the command accepts the clipboard image without the reported puremagic seek error.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
cli
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.