osc52 0.1.0__py3-none-any.whl

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.
osc52/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """osc52 — copy to the system clipboard via the OSC 52 terminal escape."""
2
+
3
+ __version__ = "0.1.0"
osc52/cli.py ADDED
@@ -0,0 +1,113 @@
1
+ """osc52 — copy stdin (or arguments) to the system clipboard via OSC 52.
2
+
3
+ Designed to work over SSH + tmux: the escape sequence is written to stderr so
4
+ the controlling terminal parses it and routes the payload to the *local*
5
+ clipboard, while the original data is echoed to stdout (kept clean for any
6
+ downstream pipe).
7
+
8
+ Behavior:
9
+ * Default: echo input to stdout AND copy via OSC 52 to stderr.
10
+ * -q/--quiet: copy only, do not echo.
11
+
12
+ This is a Python port of the Go tool github.com/yeyi0003/osc52.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import base64
19
+ import sys
20
+
21
+ from . import __version__
22
+
23
+ # Largest base64 chunk emitted in a single OSC 52 sequence. Some terminals cap a
24
+ # single sequence; 32KB is a safe midpoint that keeps big copies working by
25
+ # sending multiple same-selection sequences, which terminals accumulate.
26
+ MAX_PAYLOAD = 32 * 1024
27
+
28
+ OSC_START = b"\x1b]52;c;"
29
+ OSC_END = b"\x07"
30
+
31
+ PROG = "osc52"
32
+
33
+
34
+ def _eprint(msg: str) -> None:
35
+ """Print a diagnostic line to stderr."""
36
+ print(f"{PROG}: {msg}", file=sys.stderr)
37
+
38
+
39
+ def write_osc52(buf, data: bytes) -> None:
40
+ """Emit one or more OSC 52 sequences to a binary writer.
41
+
42
+ Large payloads are split into MAX_PAYLOAD-sized base64 chunks; terminals
43
+ accumulate same-selection sequences into the clipboard.
44
+ """
45
+ b64 = base64.standard_b64encode(data).decode("ascii") if data else ""
46
+ chunks = 0
47
+ for i in range(0, len(b64), MAX_PAYLOAD):
48
+ chunk = b64[i : i + MAX_PAYLOAD].encode("ascii")
49
+ buf.write(OSC_START + chunk + OSC_END)
50
+ chunks += 1
51
+ buf.flush()
52
+ if chunks > 1:
53
+ _eprint(f"sent {chunks} chunks")
54
+
55
+
56
+ def _build_parser() -> argparse.ArgumentParser:
57
+ p = argparse.ArgumentParser(
58
+ prog=PROG,
59
+ description="copy stdin/args to the clipboard via OSC 52",
60
+ formatter_class=argparse.RawDescriptionHelpFormatter,
61
+ epilog=(
62
+ "examples:\n"
63
+ " nvidia-smi | osc52 # echo + copy\n"
64
+ " nvidia-smi | osc52 -q # copy only, no echo\n"
65
+ " osc52 < file # copy file contents\n"
66
+ ' osc52 "some text" # copy a literal string\n'
67
+ ),
68
+ )
69
+ p.add_argument("-q", "--quiet", action="store_true",
70
+ help="do not echo input to stdout")
71
+ p.add_argument("--version", action="version",
72
+ version=f"{PROG} {__version__}")
73
+ p.add_argument("args", nargs="*", help=argparse.SUPPRESS)
74
+ return p
75
+
76
+
77
+ def main(argv: list[str] | None = None) -> int:
78
+ parser = _build_parser()
79
+ ns = parser.parse_args(argv)
80
+
81
+ # Gather the data to copy.
82
+ if ns.args:
83
+ # Arguments: join with spaces. Warn if stdin is also piped.
84
+ if not sys.stdin.isatty():
85
+ _eprint("warning: arguments given, ignoring piped stdin")
86
+ data = " ".join(ns.args).encode("utf-8", "surrogateescape") + b"\n"
87
+ else:
88
+ # Read all of stdin as raw bytes.
89
+ data = sys.stdin.buffer.read()
90
+
91
+ # Echo to stdout unless quiet. Done before copying so the user sees output
92
+ # even if the copy path fails downstream.
93
+ if not ns.quiet:
94
+ try:
95
+ sys.stdout.buffer.write(data)
96
+ sys.stdout.buffer.flush()
97
+ except BrokenPipeError:
98
+ pass
99
+ except OSError as exc:
100
+ _eprint(f"write stdout: {exc}")
101
+ return 1
102
+
103
+ # Copy. OSC 52 goes to stderr so it does not pollute echoed stdout.
104
+ try:
105
+ write_osc52(sys.stderr.buffer, data)
106
+ except Exception as exc: # noqa: BLE001 - single top-level error path
107
+ _eprint(str(exc))
108
+ return 1
109
+ return 0
110
+
111
+
112
+ if __name__ == "__main__":
113
+ raise SystemExit(main())
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.4
2
+ Name: osc52
3
+ Version: 0.1.0
4
+ Summary: Copy stdin or arguments to the system clipboard via the OSC 52 terminal escape sequence — works over SSH + tmux.
5
+ Project-URL: Homepage, https://github.com/yeyi0003/osc52
6
+ Project-URL: Repository, https://github.com/yeyi0003/osc52
7
+ Author: yeyi0003
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: cli,clipboard,copy,osc52,ssh,terminal,tmux
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Topic :: Terminals
19
+ Classifier: Topic :: Utilities
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+
23
+ # osc52
24
+
25
+ Copy stdin (or arguments) to the system clipboard via the **OSC 52** terminal
26
+ escape sequence. Designed to work over **SSH + tmux**: the escape is written to
27
+ stderr, streamed back to your *local* terminal, and routed to your *local*
28
+ clipboard — so `ssh server "nvidia-smi | osc52"` copies the remote output to
29
+ your local machine, no remote clipboard tool required.
30
+
31
+ This is the Python port of the Go tool of the same name, distributed on PyPI so
32
+ `pip install osc52` gives you the `osc52` command directly.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install osc52
38
+ ```
39
+
40
+ This installs an `osc52` executable onto your `PATH` (an `osc52.exe` shim on
41
+ Windows).
42
+
43
+ ## Usage
44
+
45
+ ```bash
46
+ # Default: echo input to stdout AND copy to clipboard.
47
+ nvidia-smi | osc52
48
+
49
+ # Copy only, no echo.
50
+ nvidia-smi | osc52 -q
51
+
52
+ # Copy a file.
53
+ osc52 < config.yaml
54
+
55
+ # Copy a literal string.
56
+ osc52 "https://example.com/token"
57
+ ```
58
+
59
+ ## How it works
60
+
61
+ ```
62
+ stdin/argv ──▶ echo to stdout (visible) ─────────────────▶ terminal / pipe
63
+ └─▶ base64 ─▶ ESC ] 52 ; c ; <b64> BEL ─▶ stderr ─▶ terminal
64
+ (parses OSC 52
65
+ → local clipboard)
66
+ ```
67
+
68
+ - **Echo** goes to **stdout** so downstream pipes stay clean of escape sequences.
69
+ - **OSC 52** goes to **stderr** so the terminal still parses it, but it never
70
+ pollutes piped output.
71
+ - Large payloads are split into ≤32KB base64 chunks; terminals accumulate
72
+ same-selection sequences into the clipboard.
73
+
74
+ ## Options
75
+
76
+ | Flag | Meaning |
77
+ |------|---------|
78
+ | `-q, --quiet` | Do not echo input to stdout |
79
+ | `--version` | Print version |
80
+ | `-h, --help` | Help |
81
+
82
+ ## Terminal support
83
+
84
+ OSC 52 is supported by Windows Terminal, iTerm2, kitty, Alacritty, WezTerm,
85
+ foot, and recent GNOME Terminal. In tmux, ensure `set -g set-clipboard on`.
86
+
87
+ ## License
88
+
89
+ MIT
@@ -0,0 +1,7 @@
1
+ osc52/__init__.py,sha256=oHgUUtLoKM3nRtsFa8o5la-BqMQcgaQCpgKlqu0S9mU,100
2
+ osc52/cli.py,sha256=H4hGMiv3kPKJKYYLyGug5NitCsueEjlu7xvREqpG0d8,3690
3
+ osc52-0.1.0.dist-info/METADATA,sha256=GyK-0BHzl4dFMSE9tdNAgcCCbuH4hbNX5UcPrQsrqdA,2778
4
+ osc52-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
5
+ osc52-0.1.0.dist-info/entry_points.txt,sha256=OoznO1Ii7HACLSxz-vmHs2OJfAsD20hrEz-PbnnpRQg,41
6
+ osc52-0.1.0.dist-info/licenses/LICENSE,sha256=fUWYbfDFLvMDBBP4-MNYCqUTCkZTdXdzXgj9LvRoF_I,1065
7
+ osc52-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ osc52 = osc52.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 yeyi0003
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.