Skip to content

Add "external script" paste method#638

Merged
cjpais merged 2 commits intocjpais:mainfrom
piec:main
Feb 19, 2026
Merged

Add "external script" paste method#638
cjpais merged 2 commits intocjpais:mainfrom
piec:main

Conversation

@piec
Copy link
Contributor

@piec piec commented Jan 21, 2026

Thanks for making Handy, I've only discovered a few days ago and it's awesome.
A few months ago I searched for something like this but couldn't find anything.

Before Submitting This PR

Please confirm you have done the following:

Human Written Description

Adds a new paste method that uses an external script.
That gives me the flexibility to post process the text as I want without adding complexity to Handy.
Examples:

  • when the focused window/tab is a chat I don't want capitals, or a final dot
  • depending on the language typography rules are not the same (space before "?")

Related Issues/Discussions

I'm noticing these after making the PR oops :)
But they complement each other:

Testing

I have tested it for a few days on an Arch Linux / xorg / i3 setup.
I think it should work on windows / macos too but cannot test on these platforms.

Simple external script example
#!/usr/bin/env python
import sys
import os
import subprocess

if len(sys.argv) <= 1:
        os.exit(1)

text = sys.argv[1]

# subprocess.run(['notify-send', '--', text])

# Make first character lowercase
if text:
    text = text[0].lower() + text[1:]

# Remove trailing period (but keep other punctuation)
if text.endswith('.'):
    text = text[:-1]

# French convention: add space before "?" and "!"
import re
text = re.sub(r'(?<! )([?!])', r' \1', text)

subprocess.run(['xdotool', 'type', '--', text], check=True)
I also have a more complex script where I change the behavior depending on the focused windows. With `xdotool getactivewindow getwindowname`.

Screenshots/Videos (if applicable)

image

AI Assistance

  • No AI was used in this PR
  • AI was used (please describe below)

If AI was used:

  • Tools used: Claude code
  • How extensively: Claude did the initial boilerplate for settings layout, which I simplified aferwards.

@cjpais
Copy link
Owner

cjpais commented Jan 21, 2026

Can we make this a Linux only thing?

@piec
Copy link
Contributor Author

piec commented Jan 21, 2026

Yes, but wouldn't it be useful to macos too?
Windows users probably aren't used to do stuff like this.

@cjpais
Copy link
Owner

cjpais commented Jan 21, 2026

I would rather keep options fairly slim where possible. For me everything works more or less perfect on MacOS. It's my primary machine and I have very few if any issues

@piec
Copy link
Contributor Author

piec commented Jan 21, 2026

@cjpais done
I only hid it in the UI, if in any case someone wants force it using the conf file

@gabenasci
Copy link

gabenasci commented Jan 22, 2026

Imo it would be nice to still be able to select one of the pre-existing paste methods but also run the script as well (for other use-cases other than just custom paste, like a custom post-transcription script that will run x y z commands). Lots of scripting possibilities here regardless of platform.

If the user were to use this script to handle only pasting, they could set the paste method to none + add their script that will run after transcription with their own custom pasting method.

Maybe this could be hidden inside "experimental features" instead? Just a thought!

@piec
Copy link
Contributor Author

piec commented Jan 22, 2026

Hi @gabenasci, maybe we need 2 things:

  • This PR: an "external script" paste method which can let users insert text with a custom tool. The intended usage is to insert text not to process text.
  • Another PR: an "external script" for post-processing, handy runs it, captures the command output and carries on with other post processing and pasting.

@cjpais
Copy link
Owner

cjpais commented Feb 16, 2026

@piec if you want this, could you rebase, I'll merge it as an option for Linux users

@piec
Copy link
Contributor Author

piec commented Feb 16, 2026

OK, will do :)

@piec
Copy link
Contributor Author

piec commented Feb 18, 2026

Hi @cjpais, rebased I also added the translations I knew but there are missing ones.

@piec
Copy link
Contributor Author

piec commented Feb 18, 2026

Related to this previous comment #638 (comment)

Are you interested by a PR for processing recognized text in an external command?

@cjpais
Copy link
Owner

cjpais commented Feb 19, 2026

@piec not at the moment, I'm mostly interested in PR's which are contributing to fixing the stability issues of the app. I will be putting a pause on most new features coming in.

@cjpais cjpais merged commit b90077b into cjpais:main Feb 19, 2026
3 of 4 checks passed
@suuuehgi
Copy link
Contributor

suuuehgi commented Feb 22, 2026

@cjpais Either this is not working on my computer or or I don't get how it's supposed to be used.

I created a small test script /tmp/test.sh

#!/bin/bash
while IFS= read -r line; do
  echo "$line" >> /tmp/test.log
done

and set the paste method to the external script but no /tmp/test.log appears.

@cjpais
Copy link
Owner

cjpais commented Feb 22, 2026

@piec please take a look, I am unable to test this

@piec
Copy link
Contributor Author

piec commented Feb 22, 2026

Hi @suuuehgi, is the script executable? chmod +x ...

Edit: Hmm also actually the text is passed as $1, not to stdin
It might have been better to pass it as process stdin to avoid cmdline max size, it's really huge these days though

@suuuehgi
Copy link
Contributor

Edit: Hmm also actually the text is passed as $1, not to stdin

That was it. I just assumed it to read from stdin as there's no documentation about this feature whatsoever.

Regarding stdin / $1 off the top of my head, I don't have any preferences on this.

Arbitrarily long strings should use stdin but at the same time we're speaking about life transcriptions. That's probably a couple of sentences at max.
I think there's a hard limit of 128 KiB for arguments by the kernel but thats quite a bit of plaintext.

@piec
Copy link
Contributor Author

piec commented Feb 22, 2026

The only documentation so far is the python example in a collapsible section in the text of the PR at the top of the page 🙂

@suuuehgi
Copy link
Contributor

in a collapsible section

Well hidden. 😅 I always overlooked that when skimming through it.

@AlexanderYastrebov
Copy link

Hi, I think this is a useful feature (for macos users as well).

Adds a new paste method that uses an external script.
That gives me the flexibility to post process the text as I want without adding complexity to Handy.

The original need seems to be about processing the transcription rather that pasting it differently. Therefore I think this feature might be evolved/improved in the following way:

  1. pass transcription to the script using stdin - this is a standard unix practice
  2. in case exit code is 0, read script stdout and if its not empty - paste using selected method. This way script may preprocess transcription without bothering about paste method as well as use custom paste method by returning empty stdout. AI post processing or what not could be done by such a script as well.

@cjpais
Copy link
Owner

cjpais commented Feb 27, 2026

I agree, but if you are advanced enough you can use it on macOS by editing the settings file. This is not a setting for average users and it shouldn't be in the UI

@piec
Copy link
Contributor Author

piec commented Feb 27, 2026

Hi I also thought making an additional PR to modify the code in order use stdin instead of the arg

AlexanderYastrebov added a commit to AlexanderYastrebov/Handy that referenced this pull request Mar 1, 2026
Add support for transcription hook - an executable script in app's data directory.

If `transcription_hook` file exists, Handy runs it passing transcription text via stdin and
uses script stdout as a transcription result.

This approach is a flexible extension point for advanced users
(which nowadays means with access to coding LLM) akin to git hooks.

Here are some possible scenarios:
* simple transcription modifications
* a pipeline involving LLM processing, language detection and translation
* custom paste method (as Handy does nothing if transcription is empty)
* conditional processing based on the active application waiting for the input

See related:
* cjpais#168
* cjpais#739
* cjpais#638
* cjpais#455
AlexanderYastrebov added a commit to AlexanderYastrebov/Handy that referenced this pull request Mar 1, 2026
Add support for transcription hook - an executable script in app's data directory.

If `transcription_hook` file exists, Handy runs it passing transcription text via stdin and
uses script stdout as a transcription result.

This approach is a flexible extension point for advanced users
(which nowadays means with access to coding LLM) akin to git hooks.

Here are some possible scenarios:
* simple transcription modifications
* a pipeline involving LLM processing, language detection and translation
* custom paste method (as Handy does nothing if transcription is empty)
* conditional processing based on the active application waiting for the input

See related:
* cjpais#168
* cjpais#739
* cjpais#638
* cjpais#455
@AlexanderYastrebov AlexanderYastrebov mentioned this pull request Mar 1, 2026
6 tasks
AlexanderYastrebov added a commit to AlexanderYastrebov/Handy that referenced this pull request Mar 1, 2026
Add support for transcription hook - an executable script in app's data directory.

If `transcription_hook` file exists, Handy runs it passing transcription text via stdin and
uses script stdout as a transcription result.

This approach is a flexible extension point for advanced users
(which nowadays means with access to coding LLM) akin to git hooks.

Here are some possible scenarios:
* simple transcription modifications
* a pipeline involving LLM processing, language detection and translation
* custom paste method (as Handy does nothing if transcription is empty)
* conditional processing based on the active application waiting for the input

See related:
* cjpais#168
* cjpais#162
* cjpais#916
* cjpais#911
* cjpais#834
* cjpais#847
* cjpais#833
* cjpais#662
* cjpais#601
* cjpais#335
* cjpais#162
* cjpais#739
* cjpais#638
* cjpais#455
* cjpais#157
AlexanderYastrebov added a commit to AlexanderYastrebov/Handy that referenced this pull request Mar 1, 2026
Add support for transcription hook - an executable script in app's data directory.

If `transcription_hook` file exists, Handy runs it passing transcription text via stdin and
uses script stdout as a transcription result.

This approach is a flexible extension point for advanced users
(which nowadays means with access to coding LLM) akin to git hooks.

Here are some possible scenarios:
* simple transcription modifications
* a pipeline involving LLM processing, language detection and translation
* custom paste method (as Handy does nothing if transcription is empty)
* conditional processing based on the active application waiting for the input

See related:
* cjpais#168
* cjpais#162
* cjpais#916
* cjpais#911
* cjpais#834
* cjpais#847
* cjpais#833
* cjpais#662
* cjpais#601
* cjpais#335
* cjpais#739
* cjpais#638
* cjpais#455
* cjpais#157
@AlexanderYastrebov
Copy link

Therefore I think this feature might be evolved/improved

I've created #930

@piec
Copy link
Contributor Author

piec commented Mar 2, 2026

Hello @AlexanderYastrebov, I also did something like this but a bit differently, I will share it on my fork :)

AlexanderYastrebov added a commit to AlexanderYastrebov/Handy that referenced this pull request Mar 3, 2026
Add support for transcription hook - an executable script in app's data directory.

If `transcription_hook` file exists, Handy runs it passing transcription text via stdin and
uses script stdout as a transcription result.

This approach is a flexible extension point for advanced users
(which nowadays means with access to coding LLM) akin to git hooks.

Here are some possible scenarios:
* simple transcription modifications
* a pipeline involving LLM processing, language detection and translation
* custom paste method (as Handy does nothing if transcription is empty)
* conditional processing based on the active application waiting for the input

See related:
* cjpais#168
* cjpais#162
* cjpais#916
* cjpais#911
* cjpais#834
* cjpais#847
* cjpais#833
* cjpais#662
* cjpais#601
* cjpais#335
* cjpais#739
* cjpais#638
* cjpais#455
* cjpais#157
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants