clipto 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.
- clipto-0.1.0/.github/workflows/ci.yml +28 -0
- clipto-0.1.0/.github/workflows/publish.yml +45 -0
- clipto-0.1.0/.gitignore +27 -0
- clipto-0.1.0/LICENSE +21 -0
- clipto-0.1.0/PKG-INFO +151 -0
- clipto-0.1.0/README.md +120 -0
- clipto-0.1.0/pyproject.toml +56 -0
- clipto-0.1.0/src/clipto/__init__.py +4 -0
- clipto-0.1.0/src/clipto/__main__.py +4 -0
- clipto-0.1.0/src/clipto/cli.py +174 -0
- clipto-0.1.0/src/clipto/server.py +186 -0
- clipto-0.1.0/src/clipto/utils.py +99 -0
- clipto-0.1.0/src/clipto/web/app.js +386 -0
- clipto-0.1.0/src/clipto/web/index.html +124 -0
- clipto-0.1.0/src/clipto/web/style.css +616 -0
- clipto-0.1.0/tests/test_clipto.py +151 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: |
|
|
24
|
+
python -m pip install --upgrade pip
|
|
25
|
+
pip install .[dev]
|
|
26
|
+
- name: Run tests
|
|
27
|
+
run: |
|
|
28
|
+
pytest
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
name: Build distribution š¦
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- name: Set up Python
|
|
15
|
+
uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.12"
|
|
18
|
+
- name: Install pypa/build
|
|
19
|
+
run: python3 -m pip install build --user
|
|
20
|
+
- name: Build a binary wheel and a source tarball
|
|
21
|
+
run: python3 -m build
|
|
22
|
+
- name: Store the distribution packages
|
|
23
|
+
uses: actions/upload-artifact@v4
|
|
24
|
+
with:
|
|
25
|
+
name: python-package-distributions
|
|
26
|
+
path: dist/
|
|
27
|
+
|
|
28
|
+
publish-to-pypi:
|
|
29
|
+
name: Publish to PyPI š
|
|
30
|
+
needs:
|
|
31
|
+
- build
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
environment:
|
|
34
|
+
name: pypi
|
|
35
|
+
url: https://pypi.org/p/clipto
|
|
36
|
+
permissions:
|
|
37
|
+
id-token: write # Mandatory for PyPI Trusted Publishing
|
|
38
|
+
steps:
|
|
39
|
+
- name: Download distribution packages
|
|
40
|
+
uses: actions/download-artifact@v4
|
|
41
|
+
with:
|
|
42
|
+
name: python-package-distributions
|
|
43
|
+
path: dist/
|
|
44
|
+
- name: Publish distribution to PyPI
|
|
45
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
clipto-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
.eggs/
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
ENV/
|
|
16
|
+
env/
|
|
17
|
+
|
|
18
|
+
# IDE & OS
|
|
19
|
+
.DS_Store
|
|
20
|
+
.idea/
|
|
21
|
+
.vscode/
|
|
22
|
+
*.swp
|
|
23
|
+
|
|
24
|
+
# Testing & coverage
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.coverage
|
|
27
|
+
htmlcov/
|
clipto-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
clipto-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: clipto
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Instant clipboard, screenshot, and file bridge from your browser to your terminal/remote server.
|
|
5
|
+
Project-URL: Homepage, https://github.com/mattintech/clipto
|
|
6
|
+
Project-URL: Repository, https://github.com/mattintech/clipto
|
|
7
|
+
Project-URL: Issues, https://github.com/mattintech/clipto/issues
|
|
8
|
+
Author-email: Matt <matt@example.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai-tools,cli,clipboard,developer-tools,remote-transfer,screenshot,upload
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: build>=1.0.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: twine>=5.0.0; extra == 'dev'
|
|
28
|
+
Provides-Extra: qr
|
|
29
|
+
Requires-Dist: segno>=1.6.0; extra == 'qr'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# š Clipto
|
|
33
|
+
|
|
34
|
+
[](https://pypi.org/project/clipto/)
|
|
35
|
+
[](LICENSE)
|
|
36
|
+
[](https://github.com/mattintech/clipto/actions/workflows/ci.yml)
|
|
37
|
+
|
|
38
|
+
> **Instant clipboard, screenshot, and file bridge from your browser to your terminal or remote server.**
|
|
39
|
+
|
|
40
|
+
Ever run an AI agent, Docker container, or SSH session on a remote server, but need to get a screenshot or log file from your local machine to that server?
|
|
41
|
+
|
|
42
|
+
**Clipto** solves this. Run `clipto` in any directory on your machine or remote server, open the link in your browser, and hit **Cmd+V / Ctrl+V**. Your screenshot or clipboard contents are immediately saved right into that folder.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## ⨠Features
|
|
47
|
+
|
|
48
|
+
- **ā” Zero-Click Paste:** Open the page and press `Cmd+V` or `Ctrl+V` anywhere. No buttons or input selection required.
|
|
49
|
+
- **šÆ Drag & Drop:** Drop multiple files, images, or documents straight onto the window.
|
|
50
|
+
- **š Text & Note Box:** Paste stack traces, error logs, or notes to save directly as `.txt` files.
|
|
51
|
+
- **š¤ AI Agent Friendly (`--once`):** One-shot mode spins up the server, waits for an upload, writes the file, prints the saved path to `stdout`, and exits cleanly.
|
|
52
|
+
- **š Smart Port Hunting:** Automatically picks the next free port (`8765`, `8766`, etc.) so multiple instances never collide.
|
|
53
|
+
- **š·ļø Multi-Instance Context:** The UI prominently shows the host machine, target directory, and optional custom `--title`.
|
|
54
|
+
- **šŖ¶ Zero Dependencies:** Powered purely by Python's standard library. Installs in milliseconds with zero dependency conflicts.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## š Quick Start
|
|
59
|
+
|
|
60
|
+
### Installation
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install clipto
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Basic Usage
|
|
67
|
+
|
|
68
|
+
Run `clipto` in whichever directory you want files to land:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
clipto
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
You'll see a clean terminal banner with the local and LAN URLs:
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
78
|
+
ā š Clipto v0.1.0 ā
|
|
79
|
+
ā Saving to: /Users/matt/code/my-project ā
|
|
80
|
+
ā Local: http://localhost:8765 ā
|
|
81
|
+
ā Network: http://192.168.1.50:8765 ā
|
|
82
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
1. Open the URL in your browser (or pass `-o / --open` to auto-open).
|
|
86
|
+
2. Hit `Cmd+V` to paste a screenshot, or drag files onto the page.
|
|
87
|
+
3. The files are instantly written to your current directory!
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## š¤ Using with AI Agents & Scripts
|
|
92
|
+
|
|
93
|
+
Clipto is built from the ground up to integrate cleanly into automated agent workflows (Antigravity, Claude Code, Aider, OpenHands, etc.).
|
|
94
|
+
|
|
95
|
+
### One-Shot Mode (`--once`)
|
|
96
|
+
|
|
97
|
+
When run with `--once` (or `-1`), Clipto waits for a single upload batch, saves the file(s), prints the resolved absolute path to `stdout`, and shuts down:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Agent or script runs this:
|
|
101
|
+
FILE_PATH=$(clipto --once --title "Submit login bug screenshot" --timeout 120)
|
|
102
|
+
|
|
103
|
+
echo "Agent received file at: $FILE_PATH"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The agent gets the exact file path back into its visual/multimodal context!
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## āļø CLI Reference
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
usage: clipto [-h] [-v] [-p PORT] [--no-hunt] [-d DIR] [-1] [-t TITLE] [-o] [--host HOST] [--timeout TIMEOUT]
|
|
114
|
+
|
|
115
|
+
Instant clipboard, screenshot, and file bridge from browser to terminal.
|
|
116
|
+
|
|
117
|
+
options:
|
|
118
|
+
-h, --help show this help message and exit
|
|
119
|
+
-v, --version show program's version number and exit
|
|
120
|
+
-p PORT, --port PORT Port to listen on (default: 8765; use 0 for random).
|
|
121
|
+
--no-hunt Disable automatic port hunting if port is busy.
|
|
122
|
+
-d DIR, --dir DIR Target directory to save files (default: .).
|
|
123
|
+
-1, --once One-shot mode: exit after upload and print file path(s).
|
|
124
|
+
-t TITLE, --title TITLE
|
|
125
|
+
Custom session title or prompt shown in the UI header.
|
|
126
|
+
-o, --open Automatically open web UI in default browser on launch.
|
|
127
|
+
--host HOST Host to bind to (default: 0.0.0.0).
|
|
128
|
+
--timeout TIMEOUT Timeout in seconds (useful with --once).
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## š ļø Multi-Instance Support
|
|
134
|
+
|
|
135
|
+
You can run multiple `clipto` instances simultaneously across different projects or terminals:
|
|
136
|
+
* Each instance automatically detects occupied ports and claims the next available one.
|
|
137
|
+
* Each browser tab displays its target folder path and optional session title so you always know where your file is landing.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## š Security
|
|
142
|
+
|
|
143
|
+
* By default, Clipto binds to `0.0.0.0` for convenience across local networks (e.g. testing with phone/laptop).
|
|
144
|
+
* If you only want local machine access, pass `--host 127.0.0.1`.
|
|
145
|
+
* Filenames are strictly sanitized to prevent directory traversal attacks.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## š License
|
|
150
|
+
|
|
151
|
+
MIT Ā© Matt
|
clipto-0.1.0/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# š Clipto
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/clipto/)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://github.com/mattintech/clipto/actions/workflows/ci.yml)
|
|
6
|
+
|
|
7
|
+
> **Instant clipboard, screenshot, and file bridge from your browser to your terminal or remote server.**
|
|
8
|
+
|
|
9
|
+
Ever run an AI agent, Docker container, or SSH session on a remote server, but need to get a screenshot or log file from your local machine to that server?
|
|
10
|
+
|
|
11
|
+
**Clipto** solves this. Run `clipto` in any directory on your machine or remote server, open the link in your browser, and hit **Cmd+V / Ctrl+V**. Your screenshot or clipboard contents are immediately saved right into that folder.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ⨠Features
|
|
16
|
+
|
|
17
|
+
- **ā” Zero-Click Paste:** Open the page and press `Cmd+V` or `Ctrl+V` anywhere. No buttons or input selection required.
|
|
18
|
+
- **šÆ Drag & Drop:** Drop multiple files, images, or documents straight onto the window.
|
|
19
|
+
- **š Text & Note Box:** Paste stack traces, error logs, or notes to save directly as `.txt` files.
|
|
20
|
+
- **š¤ AI Agent Friendly (`--once`):** One-shot mode spins up the server, waits for an upload, writes the file, prints the saved path to `stdout`, and exits cleanly.
|
|
21
|
+
- **š Smart Port Hunting:** Automatically picks the next free port (`8765`, `8766`, etc.) so multiple instances never collide.
|
|
22
|
+
- **š·ļø Multi-Instance Context:** The UI prominently shows the host machine, target directory, and optional custom `--title`.
|
|
23
|
+
- **šŖ¶ Zero Dependencies:** Powered purely by Python's standard library. Installs in milliseconds with zero dependency conflicts.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## š Quick Start
|
|
28
|
+
|
|
29
|
+
### Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install clipto
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Basic Usage
|
|
36
|
+
|
|
37
|
+
Run `clipto` in whichever directory you want files to land:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
clipto
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
You'll see a clean terminal banner with the local and LAN URLs:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
47
|
+
ā š Clipto v0.1.0 ā
|
|
48
|
+
ā Saving to: /Users/matt/code/my-project ā
|
|
49
|
+
ā Local: http://localhost:8765 ā
|
|
50
|
+
ā Network: http://192.168.1.50:8765 ā
|
|
51
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
1. Open the URL in your browser (or pass `-o / --open` to auto-open).
|
|
55
|
+
2. Hit `Cmd+V` to paste a screenshot, or drag files onto the page.
|
|
56
|
+
3. The files are instantly written to your current directory!
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## š¤ Using with AI Agents & Scripts
|
|
61
|
+
|
|
62
|
+
Clipto is built from the ground up to integrate cleanly into automated agent workflows (Antigravity, Claude Code, Aider, OpenHands, etc.).
|
|
63
|
+
|
|
64
|
+
### One-Shot Mode (`--once`)
|
|
65
|
+
|
|
66
|
+
When run with `--once` (or `-1`), Clipto waits for a single upload batch, saves the file(s), prints the resolved absolute path to `stdout`, and shuts down:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Agent or script runs this:
|
|
70
|
+
FILE_PATH=$(clipto --once --title "Submit login bug screenshot" --timeout 120)
|
|
71
|
+
|
|
72
|
+
echo "Agent received file at: $FILE_PATH"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The agent gets the exact file path back into its visual/multimodal context!
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## āļø CLI Reference
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
usage: clipto [-h] [-v] [-p PORT] [--no-hunt] [-d DIR] [-1] [-t TITLE] [-o] [--host HOST] [--timeout TIMEOUT]
|
|
83
|
+
|
|
84
|
+
Instant clipboard, screenshot, and file bridge from browser to terminal.
|
|
85
|
+
|
|
86
|
+
options:
|
|
87
|
+
-h, --help show this help message and exit
|
|
88
|
+
-v, --version show program's version number and exit
|
|
89
|
+
-p PORT, --port PORT Port to listen on (default: 8765; use 0 for random).
|
|
90
|
+
--no-hunt Disable automatic port hunting if port is busy.
|
|
91
|
+
-d DIR, --dir DIR Target directory to save files (default: .).
|
|
92
|
+
-1, --once One-shot mode: exit after upload and print file path(s).
|
|
93
|
+
-t TITLE, --title TITLE
|
|
94
|
+
Custom session title or prompt shown in the UI header.
|
|
95
|
+
-o, --open Automatically open web UI in default browser on launch.
|
|
96
|
+
--host HOST Host to bind to (default: 0.0.0.0).
|
|
97
|
+
--timeout TIMEOUT Timeout in seconds (useful with --once).
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## š ļø Multi-Instance Support
|
|
103
|
+
|
|
104
|
+
You can run multiple `clipto` instances simultaneously across different projects or terminals:
|
|
105
|
+
* Each instance automatically detects occupied ports and claims the next available one.
|
|
106
|
+
* Each browser tab displays its target folder path and optional session title so you always know where your file is landing.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## š Security
|
|
111
|
+
|
|
112
|
+
* By default, Clipto binds to `0.0.0.0` for convenience across local networks (e.g. testing with phone/laptop).
|
|
113
|
+
* If you only want local machine access, pass `--host 127.0.0.1`.
|
|
114
|
+
* Filenames are strictly sanitized to prevent directory traversal attacks.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## š License
|
|
119
|
+
|
|
120
|
+
MIT Ā© Matt
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "clipto"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Instant clipboard, screenshot, and file bridge from your browser to your terminal/remote server."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Matt", email = "matt@example.com" }
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"clipboard",
|
|
17
|
+
"screenshot",
|
|
18
|
+
"upload",
|
|
19
|
+
"cli",
|
|
20
|
+
"developer-tools",
|
|
21
|
+
"ai-tools",
|
|
22
|
+
"remote-transfer"
|
|
23
|
+
]
|
|
24
|
+
classifiers = [
|
|
25
|
+
"Development Status :: 4 - Beta",
|
|
26
|
+
"Environment :: Console",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
"License :: OSI Approved :: MIT License",
|
|
29
|
+
"Operating System :: OS Independent",
|
|
30
|
+
"Programming Language :: Python :: 3",
|
|
31
|
+
"Programming Language :: Python :: 3.9",
|
|
32
|
+
"Programming Language :: Python :: 3.10",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Topic :: Utilities",
|
|
36
|
+
]
|
|
37
|
+
dependencies = []
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
qr = ["segno>=1.6.0"]
|
|
41
|
+
dev = [
|
|
42
|
+
"pytest>=8.0.0",
|
|
43
|
+
"build>=1.0.0",
|
|
44
|
+
"twine>=5.0.0",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.scripts]
|
|
48
|
+
clipto = "clipto.cli:main"
|
|
49
|
+
|
|
50
|
+
[project.urls]
|
|
51
|
+
Homepage = "https://github.com/mattintech/clipto"
|
|
52
|
+
Repository = "https://github.com/mattintech/clipto"
|
|
53
|
+
Issues = "https://github.com/mattintech/clipto/issues"
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.wheel]
|
|
56
|
+
packages = ["src/clipto"]
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
import webbrowser
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from clipto import __version__
|
|
11
|
+
from clipto.server import CliptoHTTPServer, CliptoRequestHandler
|
|
12
|
+
from clipto.utils import (
|
|
13
|
+
find_available_port,
|
|
14
|
+
get_local_ip,
|
|
15
|
+
get_tailscale_ip,
|
|
16
|
+
terminal_hyperlink,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
21
|
+
parser = argparse.ArgumentParser(
|
|
22
|
+
prog="clipto",
|
|
23
|
+
description="Instant clipboard, screenshot, and file bridge from browser to terminal.",
|
|
24
|
+
)
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"-v", "--version",
|
|
27
|
+
action="version",
|
|
28
|
+
version=f"%(prog)s {__version__}",
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"-p", "--port",
|
|
32
|
+
type=int,
|
|
33
|
+
default=8765,
|
|
34
|
+
help="Port to listen on (default: 8765, auto-increments if occupied; use 0 for random).",
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"--no-hunt",
|
|
38
|
+
action="store_true",
|
|
39
|
+
help="Disable automatic port hunting if specified port is in use.",
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"-d", "--dir",
|
|
43
|
+
type=Path,
|
|
44
|
+
default=Path("."),
|
|
45
|
+
help="Target directory where files will be saved (default: current directory).",
|
|
46
|
+
)
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"-1", "--once",
|
|
49
|
+
action="store_true",
|
|
50
|
+
help="One-shot mode: exit immediately after receiving an upload and print saved file path(s).",
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"-t", "--title",
|
|
54
|
+
type=str,
|
|
55
|
+
default=None,
|
|
56
|
+
help="Custom session title or prompt shown in the web UI header.",
|
|
57
|
+
)
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"-o", "--open",
|
|
60
|
+
action="store_true",
|
|
61
|
+
help="Automatically open the web UI in the default browser on launch.",
|
|
62
|
+
)
|
|
63
|
+
parser.add_argument(
|
|
64
|
+
"--host",
|
|
65
|
+
type=str,
|
|
66
|
+
default="0.0.0.0",
|
|
67
|
+
help="Host address to bind to (default: 0.0.0.0).",
|
|
68
|
+
)
|
|
69
|
+
parser.add_argument(
|
|
70
|
+
"--timeout",
|
|
71
|
+
type=int,
|
|
72
|
+
default=None,
|
|
73
|
+
help="Timeout in seconds (useful with --once to prevent hanging indefinitely).",
|
|
74
|
+
)
|
|
75
|
+
return parser
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def print_banner(
|
|
79
|
+
port: int,
|
|
80
|
+
target_dir: Path,
|
|
81
|
+
title: Optional[str] = None,
|
|
82
|
+
once: bool = False,
|
|
83
|
+
):
|
|
84
|
+
local_url = f"http://localhost:{port}"
|
|
85
|
+
lan_ip = get_local_ip()
|
|
86
|
+
tailscale_ip = get_tailscale_ip()
|
|
87
|
+
|
|
88
|
+
lines = [
|
|
89
|
+
f"š Clipto v{__version__}" + (" [ONE-SHOT MODE]" if once else ""),
|
|
90
|
+
f"Saving to: {target_dir.resolve()}",
|
|
91
|
+
]
|
|
92
|
+
if title:
|
|
93
|
+
lines.append(f"Session: {title}")
|
|
94
|
+
|
|
95
|
+
lines.append(f"Local: {terminal_hyperlink(local_url)}")
|
|
96
|
+
if lan_ip and lan_ip != "127.0.0.1":
|
|
97
|
+
lines.append(f"Network: {terminal_hyperlink(f'http://{lan_ip}:{port}')}")
|
|
98
|
+
if tailscale_ip:
|
|
99
|
+
lines.append(f"Tailscale: {terminal_hyperlink(f'http://{tailscale_ip}:{port}')}")
|
|
100
|
+
|
|
101
|
+
max_len = max(len(line) for line in lines)
|
|
102
|
+
border = "ā" * (max_len + 4)
|
|
103
|
+
|
|
104
|
+
print(f"\nā{border}ā", file=sys.stderr)
|
|
105
|
+
for line in lines:
|
|
106
|
+
padding = " " * (max_len - len(line))
|
|
107
|
+
print(f"ā {line}{padding} ā", file=sys.stderr)
|
|
108
|
+
print(f"ā{border}ā\n", file=sys.stderr)
|
|
109
|
+
print("Press Cmd+V or drag files into the browser tab. Press Ctrl+C to stop.\n", file=sys.stderr)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def main():
|
|
113
|
+
parser = build_parser()
|
|
114
|
+
args = parser.parse_args()
|
|
115
|
+
|
|
116
|
+
target_dir = args.dir.resolve()
|
|
117
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
118
|
+
|
|
119
|
+
# Determine port
|
|
120
|
+
if args.no_hunt:
|
|
121
|
+
port = args.port
|
|
122
|
+
else:
|
|
123
|
+
try:
|
|
124
|
+
port = find_available_port(args.port)
|
|
125
|
+
except RuntimeError as e:
|
|
126
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
127
|
+
sys.exit(1)
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
server = CliptoHTTPServer(
|
|
131
|
+
(args.host, port),
|
|
132
|
+
CliptoRequestHandler,
|
|
133
|
+
upload_dir=target_dir,
|
|
134
|
+
title=args.title,
|
|
135
|
+
once=args.once,
|
|
136
|
+
)
|
|
137
|
+
except OSError as e:
|
|
138
|
+
print(f"Error binding to port {port}: {e}", file=sys.stderr)
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
|
|
141
|
+
print_banner(port, target_dir, title=args.title, once=args.once)
|
|
142
|
+
|
|
143
|
+
if args.open:
|
|
144
|
+
webbrowser.open(f"http://localhost:{port}")
|
|
145
|
+
|
|
146
|
+
# Start server thread
|
|
147
|
+
server_thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
148
|
+
server_thread.start()
|
|
149
|
+
|
|
150
|
+
start_time = time.time()
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
while not server.shutdown_event.is_set():
|
|
154
|
+
if args.timeout and (time.time() - start_time) > args.timeout:
|
|
155
|
+
print("\nOperation timed out.", file=sys.stderr)
|
|
156
|
+
server.shutdown()
|
|
157
|
+
sys.exit(124)
|
|
158
|
+
time.sleep(0.1)
|
|
159
|
+
|
|
160
|
+
# Shutdown triggered (in once mode)
|
|
161
|
+
server.shutdown()
|
|
162
|
+
|
|
163
|
+
# In once mode, output the uploaded file path(s) to stdout
|
|
164
|
+
for file_path in server.uploaded_files:
|
|
165
|
+
print(str(file_path.resolve()))
|
|
166
|
+
|
|
167
|
+
except KeyboardInterrupt:
|
|
168
|
+
print("\nStopping Clipto server...", file=sys.stderr)
|
|
169
|
+
server.shutdown()
|
|
170
|
+
sys.exit(0)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
if __name__ == "__main__":
|
|
174
|
+
main()
|