tacklebox-cli 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tacklebox_cli-0.1.0/.envrc +3 -0
- tacklebox_cli-0.1.0/.forgejo/workflows/actions.yml +93 -0
- tacklebox_cli-0.1.0/.gitignore +7 -0
- tacklebox_cli-0.1.0/LICENSE.md +25 -0
- tacklebox_cli-0.1.0/PKG-INFO +46 -0
- tacklebox_cli-0.1.0/README.md +28 -0
- tacklebox_cli-0.1.0/flake.lock +95 -0
- tacklebox_cli-0.1.0/flake.nix +83 -0
- tacklebox_cli-0.1.0/pyproject.toml +48 -0
- tacklebox_cli-0.1.0/renovate.json +6 -0
- tacklebox_cli-0.1.0/tacklebox/__init__.py +5 -0
- tacklebox_cli-0.1.0/tacklebox/commands/__init__.py +14 -0
- tacklebox_cli-0.1.0/tacklebox/commands/clipboard.py +171 -0
- tacklebox_cli-0.1.0/tacklebox/commands/spectacle.py +162 -0
- tacklebox_cli-0.1.0/tacklebox/commands/version.py +39 -0
- tacklebox_cli-0.1.0/tacklebox/entrypoint.py +29 -0
- tacklebox_cli-0.1.0/tacklebox/sync.py +23 -0
- tacklebox_cli-0.1.0/tacklebox/utils.py +7 -0
- tacklebox_cli-0.1.0/tacklebox/version.py +21 -0
- tacklebox_cli-0.1.0/uv.lock +711 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
name: Actions
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches:
|
|
5
|
+
- "main"
|
|
6
|
+
tags:
|
|
7
|
+
- "*"
|
|
8
|
+
pull_request:
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
name: Build
|
|
14
|
+
runs-on: docker
|
|
15
|
+
container: catthehacker/ubuntu:act-latest@sha256:932a29dd9f17cceda5a85597b9c6d4705fd8da13e488e800be81feb98e505594
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout
|
|
18
|
+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
|
19
|
+
with:
|
|
20
|
+
fetch-depth: 0
|
|
21
|
+
ref: ${{ github.ref }}
|
|
22
|
+
|
|
23
|
+
- name: Setup uv
|
|
24
|
+
uses: actions/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6
|
|
25
|
+
with:
|
|
26
|
+
version: "latest"
|
|
27
|
+
python-version: "3.12"
|
|
28
|
+
activate-environment: true
|
|
29
|
+
enable-cache: true
|
|
30
|
+
prune-cache: false
|
|
31
|
+
github-token: ${{ secrets.GITHUBTOKEN }}
|
|
32
|
+
|
|
33
|
+
- name: Install dependencies
|
|
34
|
+
run: uv sync --no-dev
|
|
35
|
+
|
|
36
|
+
- name: Build the package
|
|
37
|
+
run: uv build
|
|
38
|
+
|
|
39
|
+
- name: Upload the package
|
|
40
|
+
uses: actions/upload-artifact@16871d9e8cfcf27ff31822cac382bbb5450f1e1e # v4
|
|
41
|
+
with:
|
|
42
|
+
name: tacklebox-cli
|
|
43
|
+
path: ./dist/*
|
|
44
|
+
|
|
45
|
+
- name: Publish to CoastalCommits
|
|
46
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
47
|
+
run: uv publish
|
|
48
|
+
env:
|
|
49
|
+
UV_PUBLISH_USERNAME: ${{ github.repository_owner }}
|
|
50
|
+
UV_PUBLISH_PASSWORD: ${{ secrets.COASTALCOMMITSTOKEN }}
|
|
51
|
+
UV_PUBLISH_URL: https://c.csw.im/api/packages/${{ github.repository_owner }}/pypi
|
|
52
|
+
|
|
53
|
+
# - name: Publish to CoastalCommits
|
|
54
|
+
# if: startsWith(github.ref, 'refs/tags/')
|
|
55
|
+
# uses: actions/pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
|
|
56
|
+
# with:
|
|
57
|
+
# username: ${{ github.repository_owner }}
|
|
58
|
+
# password: ${{ secrets.COASTALCOMMITSTOKEN }}
|
|
59
|
+
# repository_url: https://c.csw.im/api/packages/${{ github.repository_owner }}/pypi
|
|
60
|
+
|
|
61
|
+
# - name: Publish to PyPi
|
|
62
|
+
# if: startsWith(github.ref, 'refs/tags/')
|
|
63
|
+
# uses: actions/pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
|
|
64
|
+
# with:
|
|
65
|
+
# password: ${{ secrets.PYPI_API_TOKEN }}
|
|
66
|
+
|
|
67
|
+
lint:
|
|
68
|
+
name: Lint
|
|
69
|
+
runs-on: docker
|
|
70
|
+
container: catthehacker/ubuntu:act-latest@sha256:932a29dd9f17cceda5a85597b9c6d4705fd8da13e488e800be81feb98e505594
|
|
71
|
+
steps:
|
|
72
|
+
- name: Checkout
|
|
73
|
+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
|
74
|
+
with:
|
|
75
|
+
fetch-depth: 0
|
|
76
|
+
|
|
77
|
+
- name: Setup uv
|
|
78
|
+
uses: actions/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6
|
|
79
|
+
with:
|
|
80
|
+
version: "latest"
|
|
81
|
+
enable-cache: true
|
|
82
|
+
prune-cache: false
|
|
83
|
+
github-token: ${{ secrets.GITHUBTOKEN }}
|
|
84
|
+
|
|
85
|
+
- name: Install dependencies
|
|
86
|
+
run: uv sync
|
|
87
|
+
|
|
88
|
+
- name: Analysing code with Ruff
|
|
89
|
+
run: uv run ruff check $(git ls-files '*.py')
|
|
90
|
+
|
|
91
|
+
- name: Analysing code with BasedPyright
|
|
92
|
+
if: ${{ success() }} || ${{ failure() }}
|
|
93
|
+
run: CI=false uv run basedpyright $(git ls-files '*.py')
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
=====================
|
|
3
|
+
|
|
4
|
+
Copyright © 2025 cswimr
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person
|
|
7
|
+
obtaining a copy of this software and associated documentation
|
|
8
|
+
files (the “Software”), to deal in the Software without
|
|
9
|
+
restriction, including without limitation the rights to use,
|
|
10
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the
|
|
12
|
+
Software is furnished to do so, subject to the following
|
|
13
|
+
conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be
|
|
16
|
+
included in all copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
20
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
21
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
22
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
23
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
24
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
25
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tacklebox-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A small colection of CLI utilities.
|
|
5
|
+
Project-URL: Homepage, https://c.csw.im/cswimr/Tacklebox
|
|
6
|
+
Project-URL: Issues, https://c.csw.im/cswimr/Tacklebox/issues
|
|
7
|
+
Project-URL: source_archive, https://c.csw.im/cswimr/Tacklebox/archive/0149870cfcd91418709412936f4866ca195b88f2.tar.gz
|
|
8
|
+
Author-email: cswimr <seaswimmerthefsh@gmail.com>
|
|
9
|
+
License-File: LICENSE.md
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Requires-Python: >=3.12
|
|
14
|
+
Requires-Dist: desktop-notifier==6.1.1
|
|
15
|
+
Requires-Dist: platformdirs==4.3.8
|
|
16
|
+
Requires-Dist: zipline-py[cli]==0.27.0
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# tacklebox-cli
|
|
20
|
+
|
|
21
|
+
tacklebox-cli offers a suite of useful CLI tools.
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### tacklebox clip
|
|
26
|
+
|
|
27
|
+
Cross-platform clipboard copying tool. Uses `wl-copy`, `xclip`, or `xsel` on Linux, `pbcopy` on MacOS, `clip` on Windows, and [OSC 52](https://www.reddit.com/r/vim/comments/k1ydpn/a_guide_on_how_to_copy_text_from_anywhere/) escape codes when operating over SSH or when no other tools are available.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
echo "a" | tr -d '\n' | tacklebox clip
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### tacklebox spectacle (Linux only)
|
|
34
|
+
|
|
35
|
+
Uses the [zipline.py](https://pypi.org/project/zipline-py/) CLI alongside KDE's [Spectacle](https://invent.kde.org/plasma/spectacle) application to take a screenshot or screen recording and automatically upload it to a [Zipline](https://github.com/diced/zipline) instance. This automatically reads Spectacle's configuration files to determine file formats.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
tacklebox spectacle | tr -d '\n' | tacklebox clip
|
|
39
|
+
|
|
40
|
+
# or to record a video
|
|
41
|
+
tacklebox spectacle --record | tr -d '\n' | tacklebox clip
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### tacklebox zipline
|
|
45
|
+
|
|
46
|
+
Wraps the [zipline.py](https://pypi.org/project/zipline-py/) CLI. See that project for documentation.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# tacklebox-cli
|
|
2
|
+
|
|
3
|
+
tacklebox-cli offers a suite of useful CLI tools.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
### tacklebox clip
|
|
8
|
+
|
|
9
|
+
Cross-platform clipboard copying tool. Uses `wl-copy`, `xclip`, or `xsel` on Linux, `pbcopy` on MacOS, `clip` on Windows, and [OSC 52](https://www.reddit.com/r/vim/comments/k1ydpn/a_guide_on_how_to_copy_text_from_anywhere/) escape codes when operating over SSH or when no other tools are available.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
echo "a" | tr -d '\n' | tacklebox clip
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### tacklebox spectacle (Linux only)
|
|
16
|
+
|
|
17
|
+
Uses the [zipline.py](https://pypi.org/project/zipline-py/) CLI alongside KDE's [Spectacle](https://invent.kde.org/plasma/spectacle) application to take a screenshot or screen recording and automatically upload it to a [Zipline](https://github.com/diced/zipline) instance. This automatically reads Spectacle's configuration files to determine file formats.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
tacklebox spectacle | tr -d '\n' | tacklebox clip
|
|
21
|
+
|
|
22
|
+
# or to record a video
|
|
23
|
+
tacklebox spectacle --record | tr -d '\n' | tacklebox clip
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### tacklebox zipline
|
|
27
|
+
|
|
28
|
+
Wraps the [zipline.py](https://pypi.org/project/zipline-py/) CLI. See that project for documentation.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"nodes": {
|
|
3
|
+
"devshell": {
|
|
4
|
+
"inputs": {
|
|
5
|
+
"nixpkgs": "nixpkgs"
|
|
6
|
+
},
|
|
7
|
+
"locked": {
|
|
8
|
+
"lastModified": 1741473158,
|
|
9
|
+
"narHash": "sha256-kWNaq6wQUbUMlPgw8Y+9/9wP0F8SHkjy24/mN3UAppg=",
|
|
10
|
+
"owner": "numtide",
|
|
11
|
+
"repo": "devshell",
|
|
12
|
+
"rev": "7c9e793ebe66bcba8292989a68c0419b737a22a0",
|
|
13
|
+
"type": "github"
|
|
14
|
+
},
|
|
15
|
+
"original": {
|
|
16
|
+
"owner": "numtide",
|
|
17
|
+
"repo": "devshell",
|
|
18
|
+
"type": "github"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"flake-utils": {
|
|
22
|
+
"inputs": {
|
|
23
|
+
"systems": "systems"
|
|
24
|
+
},
|
|
25
|
+
"locked": {
|
|
26
|
+
"lastModified": 1731533236,
|
|
27
|
+
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
28
|
+
"owner": "numtide",
|
|
29
|
+
"repo": "flake-utils",
|
|
30
|
+
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
31
|
+
"type": "github"
|
|
32
|
+
},
|
|
33
|
+
"original": {
|
|
34
|
+
"id": "flake-utils",
|
|
35
|
+
"type": "indirect"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"nixpkgs": {
|
|
39
|
+
"locked": {
|
|
40
|
+
"lastModified": 1722073938,
|
|
41
|
+
"narHash": "sha256-OpX0StkL8vpXyWOGUD6G+MA26wAXK6SpT94kLJXo6B4=",
|
|
42
|
+
"owner": "NixOS",
|
|
43
|
+
"repo": "nixpkgs",
|
|
44
|
+
"rev": "e36e9f57337d0ff0cf77aceb58af4c805472bfae",
|
|
45
|
+
"type": "github"
|
|
46
|
+
},
|
|
47
|
+
"original": {
|
|
48
|
+
"owner": "NixOS",
|
|
49
|
+
"ref": "nixpkgs-unstable",
|
|
50
|
+
"repo": "nixpkgs",
|
|
51
|
+
"type": "github"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"nixpkgs_2": {
|
|
55
|
+
"locked": {
|
|
56
|
+
"lastModified": 1748460289,
|
|
57
|
+
"narHash": "sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg=",
|
|
58
|
+
"owner": "nixos",
|
|
59
|
+
"repo": "nixpkgs",
|
|
60
|
+
"rev": "96ec055edbe5ee227f28cdbc3f1ddf1df5965102",
|
|
61
|
+
"type": "github"
|
|
62
|
+
},
|
|
63
|
+
"original": {
|
|
64
|
+
"owner": "nixos",
|
|
65
|
+
"ref": "nixos-unstable",
|
|
66
|
+
"repo": "nixpkgs",
|
|
67
|
+
"type": "github"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"root": {
|
|
71
|
+
"inputs": {
|
|
72
|
+
"devshell": "devshell",
|
|
73
|
+
"flake-utils": "flake-utils",
|
|
74
|
+
"nixpkgs": "nixpkgs_2"
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"systems": {
|
|
78
|
+
"locked": {
|
|
79
|
+
"lastModified": 1681028828,
|
|
80
|
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
81
|
+
"owner": "nix-systems",
|
|
82
|
+
"repo": "default",
|
|
83
|
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
84
|
+
"type": "github"
|
|
85
|
+
},
|
|
86
|
+
"original": {
|
|
87
|
+
"owner": "nix-systems",
|
|
88
|
+
"repo": "default",
|
|
89
|
+
"type": "github"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"root": "root",
|
|
94
|
+
"version": 7
|
|
95
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
inputs = {
|
|
3
|
+
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
|
4
|
+
devshell.url = "github:numtide/devshell";
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
outputs =
|
|
8
|
+
{
|
|
9
|
+
self,
|
|
10
|
+
nixpkgs,
|
|
11
|
+
flake-utils,
|
|
12
|
+
...
|
|
13
|
+
}@inputs:
|
|
14
|
+
flake-utils.lib.eachDefaultSystem (
|
|
15
|
+
system:
|
|
16
|
+
let
|
|
17
|
+
pkgs = import nixpkgs {
|
|
18
|
+
inherit system;
|
|
19
|
+
overlays = with inputs; [
|
|
20
|
+
devshell.overlays.default
|
|
21
|
+
];
|
|
22
|
+
};
|
|
23
|
+
in
|
|
24
|
+
{
|
|
25
|
+
devShells.default = pkgs.devshell.mkShell {
|
|
26
|
+
devshell = {
|
|
27
|
+
name = "Tacklebox";
|
|
28
|
+
startup = {
|
|
29
|
+
install-uv-dependencies.text = "uv sync --all-groups --locked";
|
|
30
|
+
source-venv = {
|
|
31
|
+
text = ''
|
|
32
|
+
source .venv/bin/activate
|
|
33
|
+
export PATH="${pkgs.ruff}/bin:${pkgs.basedpyright}/bin:$PATH"
|
|
34
|
+
'';
|
|
35
|
+
deps = [ "install-uv-dependencies" ];
|
|
36
|
+
};
|
|
37
|
+
ensure-data-dir-exists.text = ''mkdir -p "$PRJ_DATA_DIR"'';
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
commands = with pkgs; [
|
|
42
|
+
{ package = uv; }
|
|
43
|
+
{ package = ruff; } # the ruff pip package installs a dynamically linked binary that cannot run on NixOS
|
|
44
|
+
{ package = basedpyright; } # same as ruff
|
|
45
|
+
{ package = typos; }
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
packages = with pkgs; [
|
|
49
|
+
stdenv.cc.cc
|
|
50
|
+
python3
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
env = [
|
|
54
|
+
{
|
|
55
|
+
name = "CPPFLAGS";
|
|
56
|
+
eval = "-I$DEVSHELL_DIR/include";
|
|
57
|
+
}
|
|
58
|
+
{
|
|
59
|
+
name = "LDFLAGS";
|
|
60
|
+
eval = "-L$DEVSHELL_DIR/lib";
|
|
61
|
+
}
|
|
62
|
+
{
|
|
63
|
+
name = "LD_LIBRARY_PATH";
|
|
64
|
+
eval = "$DEVSHELL_DIR/lib:$LD_LIBRARY_PATH";
|
|
65
|
+
}
|
|
66
|
+
{
|
|
67
|
+
name = "UV_PYTHON_PREFERENCE";
|
|
68
|
+
value = "only-system";
|
|
69
|
+
}
|
|
70
|
+
{
|
|
71
|
+
name = "UV_PYTHON_DOWNLOADS";
|
|
72
|
+
value = "never";
|
|
73
|
+
}
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
motd = ''
|
|
77
|
+
{33}🔨 Welcome to the {208}Tacklebox{33} Devshell!{reset}
|
|
78
|
+
$(type -p menu &>/dev/null && menu)
|
|
79
|
+
'';
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tacklebox-cli"
|
|
3
|
+
description = "A small colection of CLI utilities."
|
|
4
|
+
authors = [{ name = "cswimr", email = "seaswimmerthefsh@gmail.com" }]
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
classifiers = [
|
|
8
|
+
"License :: OSI Approved :: MIT License",
|
|
9
|
+
"Programming Language :: Python",
|
|
10
|
+
"Programming Language :: Python :: 3",
|
|
11
|
+
]
|
|
12
|
+
dependencies = [
|
|
13
|
+
"desktop-notifier==6.1.1",
|
|
14
|
+
"platformdirs==4.3.8",
|
|
15
|
+
"zipline.py[cli]==0.27.0",
|
|
16
|
+
]
|
|
17
|
+
dynamic = ["version", "urls"]
|
|
18
|
+
|
|
19
|
+
[dependency-groups]
|
|
20
|
+
dev = ["basedpyright==1.29.2", "ruff==0.11.12"]
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
tacklebox = "tacklebox.entrypoint:app"
|
|
24
|
+
|
|
25
|
+
[tool.ruff]
|
|
26
|
+
line-length = 160
|
|
27
|
+
|
|
28
|
+
[tool.pyright]
|
|
29
|
+
typeCheckingMode = "strict"
|
|
30
|
+
reportMissingTypeStubs = false
|
|
31
|
+
|
|
32
|
+
[tool.typos]
|
|
33
|
+
files.extend-exclude = [".direnv/**", ".venv/**"]
|
|
34
|
+
|
|
35
|
+
[tool.hatch.version]
|
|
36
|
+
source = "vcs"
|
|
37
|
+
|
|
38
|
+
[tool.hatch.build.hooks.vcs]
|
|
39
|
+
version-file = "tacklebox/version.py"
|
|
40
|
+
|
|
41
|
+
[tool.hatch.metadata.hooks.vcs.urls]
|
|
42
|
+
Homepage = "https://c.csw.im/cswimr/Tacklebox"
|
|
43
|
+
Issues = "https://c.csw.im/cswimr/Tacklebox/issues"
|
|
44
|
+
source_archive = "https://c.csw.im/cswimr/Tacklebox/archive/{commit_hash}.tar.gz"
|
|
45
|
+
|
|
46
|
+
[build-system]
|
|
47
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
48
|
+
build-backend = "hatchling.build"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
|
|
3
|
+
from typer import Typer
|
|
4
|
+
from zipline.cli.entrypoint import app as zipline
|
|
5
|
+
|
|
6
|
+
from .clipboard import app as clipboard
|
|
7
|
+
from .version import app as version
|
|
8
|
+
|
|
9
|
+
commands: list[Typer] = [clipboard, version, zipline]
|
|
10
|
+
|
|
11
|
+
if platform.system() == "Linux":
|
|
12
|
+
from .spectacle import app as spectacle
|
|
13
|
+
|
|
14
|
+
commands.append(spectacle)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from base64 import b64encode
|
|
7
|
+
from typing import Annotated, Literal
|
|
8
|
+
|
|
9
|
+
from typer import Argument, Exit, Option, Typer, echo
|
|
10
|
+
|
|
11
|
+
from tacklebox.utils import get_environment
|
|
12
|
+
|
|
13
|
+
app = Typer()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _try_command(data: str, cmd: list[str], verbose: bool = False) -> bool:
|
|
17
|
+
if shutil.which(cmd[0]) is None:
|
|
18
|
+
if verbose:
|
|
19
|
+
echo(f"{cmd[0]} not found in PATH.", err=True)
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
process = subprocess.Popen(
|
|
24
|
+
cmd,
|
|
25
|
+
stdin=subprocess.PIPE,
|
|
26
|
+
stdout=subprocess.DEVNULL if not verbose else None,
|
|
27
|
+
stderr=subprocess.DEVNULL if not verbose else None,
|
|
28
|
+
env=get_environment(),
|
|
29
|
+
)
|
|
30
|
+
process.communicate(input=data.encode("utf-8"))
|
|
31
|
+
|
|
32
|
+
if process.returncode == 0:
|
|
33
|
+
if verbose:
|
|
34
|
+
echo(f"Copied using {' '.join(cmd)}")
|
|
35
|
+
return True
|
|
36
|
+
except Exception as e:
|
|
37
|
+
if verbose:
|
|
38
|
+
echo(f"{cmd[0]} failed: {e}", err=True)
|
|
39
|
+
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _copy_with_tooling(data: str, verbose: bool = False) -> bool:
|
|
44
|
+
system = platform.system()
|
|
45
|
+
|
|
46
|
+
if system == "Linux":
|
|
47
|
+
protocol: Literal["wayland", "x11"] | None = (
|
|
48
|
+
"wayland"
|
|
49
|
+
if "WAYLAND_DISPLAY" in get_environment()
|
|
50
|
+
else ("x11" if "DISPLAY" in get_environment() else None)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if verbose:
|
|
54
|
+
echo(f"Detected display protocol: {protocol}")
|
|
55
|
+
|
|
56
|
+
if protocol == "wayland":
|
|
57
|
+
if _try_command(data, ["wl-copy"], verbose):
|
|
58
|
+
return True
|
|
59
|
+
elif protocol == "x11":
|
|
60
|
+
for tool in [
|
|
61
|
+
["xclip", "-selection", "clipboard"],
|
|
62
|
+
["xsel", "--clipboard", "--input"],
|
|
63
|
+
]:
|
|
64
|
+
if _try_command(data, tool, verbose):
|
|
65
|
+
return True
|
|
66
|
+
else:
|
|
67
|
+
if verbose:
|
|
68
|
+
echo(
|
|
69
|
+
"Unknown display protocol: neither WAYLAND_DISPLAY nor DISPLAY set.",
|
|
70
|
+
err=True,
|
|
71
|
+
)
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
elif system == "Darwin":
|
|
75
|
+
return _try_command(data, ["pbcopy"], verbose)
|
|
76
|
+
|
|
77
|
+
elif system == "Windows":
|
|
78
|
+
return _try_command(data, ["clip"], verbose)
|
|
79
|
+
|
|
80
|
+
if verbose:
|
|
81
|
+
echo(f"No suitable clipboard tool found for platform {system}", err=True)
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _encode_osc52(data: str, verbose: bool = False) -> str:
|
|
86
|
+
"""Encode a string into an [OCS 52]() string, supporting tmux and screen as well.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
data (str): The data to encode.
|
|
90
|
+
verbose (bool, optional): Print additional information during execution. Defaults to False.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
str: The OSC 52 (& base64) encoded data.
|
|
94
|
+
"""
|
|
95
|
+
b64_data = b64encode(data.encode("utf-8")).decode("ascii")
|
|
96
|
+
osc_seq = f"\x1b]52;c;{b64_data}\x07"
|
|
97
|
+
|
|
98
|
+
if "TMUX" in os.environ:
|
|
99
|
+
if verbose:
|
|
100
|
+
echo("Wrapping OSC 52 for tmux.")
|
|
101
|
+
return f"\x1bPtmux;\x1b{osc_seq}\x1b\\"
|
|
102
|
+
elif os.environ.get("TERM", "").startswith("screen"):
|
|
103
|
+
if verbose:
|
|
104
|
+
echo("Wrapping OSC 52 for screen.")
|
|
105
|
+
return f"\x1bP{osc_seq}\x1b\\"
|
|
106
|
+
else:
|
|
107
|
+
if verbose:
|
|
108
|
+
echo("Using plain OSC 52.")
|
|
109
|
+
return osc_seq
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@app.command("clip")
|
|
113
|
+
def clipboard(
|
|
114
|
+
data: Annotated[
|
|
115
|
+
str | None,
|
|
116
|
+
Argument(
|
|
117
|
+
help="The data to copy to the clipboard. Reads from stdin if this is not provided.",
|
|
118
|
+
),
|
|
119
|
+
] = None,
|
|
120
|
+
verbose: Annotated[
|
|
121
|
+
bool,
|
|
122
|
+
Option(
|
|
123
|
+
"--verbose",
|
|
124
|
+
"-v",
|
|
125
|
+
help="Print some additional information during execution.",
|
|
126
|
+
),
|
|
127
|
+
] = False,
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Read from stdin and copy to the system clipboard using wl-copy or OSC 52."""
|
|
130
|
+
if not data:
|
|
131
|
+
data = sys.stdin.read()
|
|
132
|
+
|
|
133
|
+
if not data:
|
|
134
|
+
echo("No input received from stdin.", err=True)
|
|
135
|
+
raise Exit(code=1)
|
|
136
|
+
|
|
137
|
+
if verbose:
|
|
138
|
+
echo(
|
|
139
|
+
message="\n".join(
|
|
140
|
+
(
|
|
141
|
+
f"Platform: {platform.system()}",
|
|
142
|
+
f"TERM: {os.environ.get('TERM')}",
|
|
143
|
+
f"TMUX: {'present' if 'TMUX' in os.environ else 'absent'}",
|
|
144
|
+
f"SCREEN: {'present' if os.environ.get('TERM', '').startswith('screen') else 'absent'}",
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
success = _copy_with_tooling(data, verbose)
|
|
150
|
+
if success:
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
ssh = "SSH_CONNECTION" in os.environ
|
|
154
|
+
|
|
155
|
+
if ssh or not success:
|
|
156
|
+
if verbose:
|
|
157
|
+
echo("Clipboard tools failed; trying OSC 52...", err=True)
|
|
158
|
+
|
|
159
|
+
osc = _encode_osc52(data, verbose)
|
|
160
|
+
try:
|
|
161
|
+
with open("/dev/tty", "w") as tty:
|
|
162
|
+
tty.write(osc)
|
|
163
|
+
if verbose:
|
|
164
|
+
echo("Copied using OSC 52.")
|
|
165
|
+
except Exception as e:
|
|
166
|
+
echo(f"OSC 52 failed: {e}", err=True)
|
|
167
|
+
raise Exit(code=1)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
if __name__ == "__main__":
|
|
171
|
+
app()
|