diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml
new file mode 100644
index 0000000..e3cfb30
--- /dev/null
+++ b/.github/workflows/package.yml
@@ -0,0 +1,18 @@
+name: Package
+
+on: [push]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Package
+ run: make release
+ - name: Upload workflow artifact
+ uses: actions/upload-artifact@v1
+ with:
+ name: gopass.alfredworkflow
+ path: gopass.alfredworkflow
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..32a9147
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.idea
+*.iml
+gopass.alfredworkflow
diff --git a/Makefile b/Makefile
index 47d205d..ecca816 100644
--- a/Makefile
+++ b/Makefile
@@ -1,2 +1,3 @@
release:
- zip -r ../gopass.alfredworkflow *
+ rm -f ../gopass.alfredworkflow
+ zip -r ./gopass.alfredworkflow ./* -x@alfred_package.ignore
diff --git a/README.md b/README.md
index 708216b..de90201 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@
Workflow to interact with gopass from [Alfred 3](https://www.alfredapp.com/workflows/) on MacOS.
You will need an Alfred 3 license in order to use workflows.
Currently querying entries and copying username or password to clipboard is supported.
+
## Screenshot
![gopass alfred screenshot](./screenshot.png)
@@ -11,6 +12,58 @@ Currently querying entries and copying username or password to clipboard is supp
Download the [latest release package](https://github.com/gopasspw/gopass-alfred/releases/latest) from github or build your own via `make release` in checked out repository.
+## Autotyping
+
+The plugin can also type a username / password combination automatically. You can start the action by issuing `gpa` in Alfred.
+
+By default, a field called `autotype` will be used. The value can contain various statements.
+
+Example: `user :tab :pass`
+
+### Trigger Options
+
+However, there are more options:
+
+| Option | Effect |
+|---------------|-------------------------------------------------------------------------------|
+| user / pass | A field name (not prefixed with `:`. Can reference any other available field. |
+| `:tab` | Issues a tab key |
+| `:enter` | Enter key |
+| `:space` | Space key |
+| `:delete` | Delete key |
+| `:delay` | Waits one second before continuing |
+| `:clearField` | Clears the current input field (Issues Ctrl+A + backspace) |
+
+A rather typical example looks like the following:
+
+```
+myPassword
+---
+autotype: :clearField user :tab pass :tab :tab :enter :delay :someYk :enter
+user: myUsername
+usernamePassword: user :tab pass
+```
+
+### Custom autotype options
+
+You might have noted `:someYk` as option which was not listed in the table above. Not all commands can be hardcoded in a table like the one above. For example, you might have a form that requires a one-time-password such as from a YubiKey. You might be able to retrieve the information from a `bash` command - however.
+
+Those commands can be put to a `.gopass_autotype_handlers.json` json file. It can be place in `$XDG_CONFIG`, which defaults to your `HOME` directory. Example: `/users/someUser/.gopass_autotype_handlers.json`
+
+An example file content looks like the following:
+
+```json
+{
+ ":someYk": "/usr/local/bin/ykman oath code | grep \"YubiKey:name\" | sed 's/.*\\([0-9]\\{6\\}\\)$/\\1/g'"
+}
+```
+
+This example will retrieve a current TOTP token from the YubiKey by asking `ykman` for the current value and converts the output to the token.
+
+### Autotyping other fields than `autotype`
+
+The `gpa` command will always run the `autotype` field. However, you can also use `gpf` to autotype any available field in the gopass file. This also includes the trigger options from the table above.
+
## Development
Contributions to this project are welcome!
diff --git a/alfred_package.ignore b/alfred_package.ignore
new file mode 100644
index 0000000..e84249e
--- /dev/null
+++ b/alfred_package.ignore
@@ -0,0 +1,5 @@
+.idea
+.env
+.git
+*.iml
+.github
diff --git a/get_password.py b/get_password.py
index 96bf9de..859a712 100755
--- a/get_password.py
+++ b/get_password.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import subprocess
import sys
diff --git a/gopass_autotype.py b/gopass_autotype.py
new file mode 100755
index 0000000..2d8be2e
--- /dev/null
+++ b/gopass_autotype.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python3
+
+import json
+import os
+import subprocess
+import sys
+import time
+from os.path import expanduser
+
+home = expanduser("~")
+my_env = os.environ.copy()
+my_env['PATH'] = '/usr/local/bin:{}'.format(my_env['PATH'])
+
+special_autotype_handlers = {
+ ":tab": lambda: do_stroke(48),
+ ":space": lambda: do_stroke(49),
+ ":enter": lambda: do_stroke(36),
+ ":delete": lambda: do_stroke(51),
+ ":delay": lambda: time.sleep(1),
+ ":clearField": lambda: clear_field_content()
+}
+
+
+def get_additional_autotype_handlers_filename():
+ base_path = os.getenv("XDG_CONFIG", home)
+ return '{base_path}/{filename}'.format(base_path=base_path, filename=".gopass_autotype_handlers.json")
+
+
+def load_additional_autotype_handlers_config():
+ handlers_path = get_additional_autotype_handlers_filename()
+ if os.path.exists(handlers_path):
+ with open(handlers_path) as f:
+ return json.load(f)
+ else:
+ return {}
+
+
+def execute_additional_autotype_handler(command):
+ command_result = os.popen(command).read()
+ do_type(command_result)
+
+
+def load_additional_autotype_handlers():
+ config = load_additional_autotype_handlers_config()
+ return dict(map(lambda item: (item[0], lambda: execute_additional_autotype_handler(item[1])), config.items()))
+
+
+def all_autotype_handlers():
+ additional = load_additional_autotype_handlers()
+ return {**additional, **special_autotype_handlers}
+
+
+def do_type(to_type):
+ os.system(f"echo 'tell application \"System Events\" to keystroke \"{to_type}\"' | osascript")
+
+
+def do_stroke(stroke):
+ os.system(f"echo 'tell application \"System Events\" to key code \"{stroke}\"' | osascript")
+
+
+def clear_field_content():
+ os.system("""echo 'tell application "System Events" to keystroke "a" using command down' | osascript""")
+ do_stroke(51)
+
+
+def get_gopass_data_for(query):
+ return subprocess.run(["gopass", "show", "-f", query], stdout=subprocess.PIPE, env=my_env).stdout.decode("utf-8")
+
+
+def parse_gopass_data(data):
+ lines = data.split("\n")
+ password = lines[0]
+ non_empty_lines = list(filter(lambda line: len(line) > 0, lines))
+ lines_splitted = map(lambda line: line.split(": "), non_empty_lines[1:])
+ lines_with_two_items = filter(lambda elements: len(elements) == 2, lines_splitted)
+ items_dict = {item[0]: item[1] for item in lines_with_two_items}
+ items_dict["pass"] = password
+ return items_dict
+
+
+def autotype(items, field):
+ autotype = items.get(field)
+ handlers = all_autotype_handlers()
+ if autotype is None:
+ return
+ to_type = autotype.split(" ")
+ for word in to_type:
+ handler = handlers.get(word)
+ if handler is None:
+ mapped = items.get(word)
+ do_type(word if mapped is None else mapped)
+ else:
+ handler()
+
+
+def autotype_list(path, parsed):
+ return [
+ {
+ "uid": result,
+ "title": result,
+ "subtitle": path,
+ "arg": f"{path} {result}",
+ "autocomplete": result
+ } for result in parsed.keys()
+ ]
+
+
+action = sys.argv[1]
+path = sys.argv[2].split(" ")
+
+data = get_gopass_data_for(path[0])
+parsed = parse_gopass_data(data)
+autotype_action = "autotype" if len(path) == 1 else path[1]
+
+if action == 'list':
+ print(json.dumps({'items': autotype_list(path[0], parsed)}))
+elif action == 'autotype':
+ autotype(parsed, autotype_action)
diff --git a/gopass_filter.py b/gopass_filter.py
index b9f0b6b..5b91267 100755
--- a/gopass_filter.py
+++ b/gopass_filter.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import os
import sys
@@ -22,7 +22,7 @@
"title": result.split('/')[-1],
"subtitle": '/'.join(result.split('/')[:-1]),
"arg": result,
- "match": " ".join(set(re.split('[. /\-]', result))) + ' ' + result,
+ "match": " ".join(set(re.split('[. /\-_]', result))) + ' ' + " ".join(set(re.split('[/]', result))) + ' ' + result,
"autocomplete": result
} for result in stdout.decode('ascii').splitlines()
]
diff --git a/info.plist b/info.plist
index 16403ca..c62d69d 100644
--- a/info.plist
+++ b/info.plist
@@ -4,8 +4,6 @@
bundleid
pw.gopass.alfred
- category
- Productivity
connections
0D5132AE-3620-4D5F-BA72-EFD481455463
@@ -31,6 +29,45 @@
+ 1191CB7E-4E88-4BFB-BED0-579A0E6124C8
+
+
+ destinationuid
+ 7B342C01-E6A7-4B26-A83E-D593DA90F4E1
+ modifiers
+ 0
+ modifiersubtext
+
+ vitoclose
+
+
+
+ 2E65F512-65E5-47AD-83AD-EFBB0A988B69
+
+
+ destinationuid
+ 1191CB7E-4E88-4BFB-BED0-579A0E6124C8
+ modifiers
+ 0
+ modifiersubtext
+
+ vitoclose
+
+
+
+ 437FA632-22A0-4FA7-9B33-E0CFAFE9E61A
+
+
+ destinationuid
+ FB6C8961-5CC1-464F-AEF7-A9BCF53F228F
+ modifiers
+ 0
+ modifiersubtext
+
+ vitoclose
+
+
+
B1FF35DC-433E-4CCC-9CB3-7C8E1BC532F7
@@ -91,6 +128,8 @@ exit $RESULT
alfredfiltersresultsmatchmode
2
+ argumenttreatemptyqueryasnil
+
argumenttrimmode
0
argumenttype
@@ -130,7 +169,7 @@ exit $RESULT
uid
0D5132AE-3620-4D5F-BA72-EFD481455463
version
- 2
+ 3
config
@@ -157,6 +196,201 @@ echo $USER | pbcopy
version
2
+
+ config
+
+ alfredfiltersresults
+
+ alfredfiltersresultsmatchmode
+ 2
+ argumenttreatemptyqueryasnil
+
+ argumenttrimmode
+ 0
+ argumenttype
+ 0
+ escaping
+ 70
+ keyword
+ gpa
+ queuedelaycustom
+ 3
+ queuedelayimmediatelyinitially
+
+ queuedelaymode
+ 0
+ queuemode
+ 1
+ runningsubtext
+ loading gopass secrets
+ script
+ /usr/local/bin/gopass list -f > /tmp/all
+
+ scriptargtype
+ 1
+ scriptfile
+ gopass_filter.py
+ subtext
+ secret
+ title
+ Gopass autotype
+ type
+ 8
+ withspace
+
+
+ type
+ alfred.workflow.input.scriptfilter
+ uid
+ 437FA632-22A0-4FA7-9B33-E0CFAFE9E61A
+ version
+ 3
+
+
+ config
+
+ concurrently
+
+ escaping
+ 68
+ script
+ ./gopass_autotype.py autotype "{query}"
+ scriptargtype
+ 0
+ scriptfile
+
+ type
+ 0
+
+ type
+ alfred.workflow.action.script
+ uid
+ FB6C8961-5CC1-464F-AEF7-A9BCF53F228F
+ version
+ 2
+
+
+ config
+
+ alfredfiltersresults
+
+ alfredfiltersresultsmatchmode
+ 2
+ argumenttreatemptyqueryasnil
+
+ argumenttrimmode
+ 0
+ argumenttype
+ 0
+ escaping
+ 70
+ keyword
+ gpf
+ queuedelaycustom
+ 3
+ queuedelayimmediatelyinitially
+
+ queuedelaymode
+ 0
+ queuemode
+ 1
+ runningsubtext
+ loading gopass secrets
+ script
+ /usr/local/bin/gopass list -f > /tmp/all
+
+ scriptargtype
+ 1
+ scriptfile
+ gopass_filter.py
+ subtext
+ secret
+ title
+ gopass type field
+ type
+ 8
+ withspace
+
+
+ type
+ alfred.workflow.input.scriptfilter
+ uid
+ 2E65F512-65E5-47AD-83AD-EFBB0A988B69
+ version
+ 3
+
+
+ config
+
+ concurrently
+
+ escaping
+ 68
+ script
+ ./gopass_autotype.py autotype "{query}"
+ scriptargtype
+ 0
+ scriptfile
+
+ type
+ 0
+
+ type
+ alfred.workflow.action.script
+ uid
+ 7B342C01-E6A7-4B26-A83E-D593DA90F4E1
+ version
+ 2
+
+
+ config
+
+ alfredfiltersresults
+
+ alfredfiltersresultsmatchmode
+ 0
+ argumenttreatemptyqueryasnil
+
+ argumenttrimmode
+ 0
+ argumenttype
+ 0
+ escaping
+ 102
+ keyword
+ list for key
+ queuedelaycustom
+ 3
+ queuedelayimmediatelyinitially
+
+ queuedelaymode
+ 0
+ queuemode
+ 1
+ runningsubtext
+
+ script
+ ./gopass_autotype.py list "{query}"
+ scriptargtype
+ 0
+ scriptfile
+
+ subtext
+
+ title
+
+ type
+ 0
+ withspace
+
+
+ type
+ alfred.workflow.input.scriptfilter
+ uid
+ 1191CB7E-4E88-4BFB-BED0-579A0E6124C8
+ version
+ 3
+
readme
@@ -169,6 +403,34 @@ echo $USER | pbcopy
ypos
20
+ 1191CB7E-4E88-4BFB-BED0-579A0E6124C8
+
+ xpos
+ 255
+ ypos
+ 465
+
+ 2E65F512-65E5-47AD-83AD-EFBB0A988B69
+
+ xpos
+ 85
+ ypos
+ 465
+
+ 437FA632-22A0-4FA7-9B33-E0CFAFE9E61A
+
+ xpos
+ 90
+ ypos
+ 330
+
+ 7B342C01-E6A7-4B26-A83E-D593DA90F4E1
+
+ xpos
+ 430
+ ypos
+ 465
+
B1FF35DC-433E-4CCC-9CB3-7C8E1BC532F7
xpos
@@ -183,7 +445,18 @@ echo $USER | pbcopy
ypos
20
+ FB6C8961-5CC1-464F-AEF7-A9BCF53F228F
+
+ xpos
+ 250
+ ypos
+ 330
+
+ variablesdontexport
+
+ version
+
webaddress
https://www.gopass.pw