pbsync 0.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: baac0be30f2e90c3fea252ccc0cf94d71f9e75b30574ebb41736d45d5c8f210d
4
+ data.tar.gz: 9e3cb20d0f47f90e15cebdead17a63b610ea9c83ec69f22314f827084b9371dd
5
+ SHA512:
6
+ metadata.gz: 86d81c8534135c40f8231f2237057f35e952be688789ad45b59b6243a6ac7fbaf18173d01af2bae5949bad012425bc5c1a73e8dfded6e20e83f9258883cb62dd
7
+ data.tar.gz: '048c8c96c13c03ba4c81c1298579c82c18d4a0ff1f950b023d2af284a4eb8f9014506a2303fb7e109ffbb01b5b13cfbd9a91b6d674b983a52a101e8ed2d36e05'
data/CLAUDE.md ADDED
@@ -0,0 +1,174 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Overview
6
+
7
+ pbsync is a clipboard synchronization utility implemented as a Ruby gem. It allows bidirectional clipboard syncing between two machines over SSH using Unix domain sockets.
8
+
9
+ ## Architecture
10
+
11
+ The application operates in two modes:
12
+ - **Server mode** (`pbsync serve`): Listens on a Unix socket at `/tmp/pbsync.sock` and accepts one client connection
13
+ - **Client mode** (`pbsync connect <hostname>`): Establishes SSH tunnel to remote host and connects to the server's socket
14
+
15
+ Key components:
16
+ - `PBSync::ClipboardSync`: Core synchronization logic that monitors clipboard changes and handles network communication
17
+ - `PBSync::Server`: Server implementation
18
+ - `PBSync::Client`: Client implementation with SSH tunneling
19
+ - `PBSync::CLI`: Command-line interface using Clamp gem for argument parsing
20
+ - Uses clipboard gem for cross-platform clipboard access
21
+ - Binary protocol with 4-byte length prefix (network byte order) followed by UTF-8 message data
22
+ - Maximum clipboard size: 1MB
23
+ - Polling interval: 0.5 seconds
24
+
25
+ ## Ruby Gem Structure
26
+
27
+ ```
28
+ pbsync/
29
+ ├── bin/
30
+ │ └── pbsync # Executable script
31
+ ├── lib/
32
+ │ ├── pbsync.rb # Main module
33
+ │ └── pbsync/
34
+ │ ├── version.rb
35
+ │ ├── cli.rb
36
+ │ ├── clipboard_sync.rb
37
+ │ ├── server.rb
38
+ │ └── client.rb
39
+ ├── pbsync.gemspec # Gem specification
40
+ ├── Gemfile
41
+ └── LICENSE
42
+ ```
43
+
44
+ ## Installation & Build Commands
45
+
46
+ ```bash
47
+ # Install dependencies
48
+ bundle install
49
+
50
+ # Build the gem
51
+ gem build pbsync.gemspec
52
+
53
+ # Install the gem locally
54
+ gem install ./pbsync-0.1.0.gem
55
+
56
+ # Or install directly from source
57
+ bundle exec rake install
58
+ ```
59
+
60
+ ## Usage
61
+
62
+ ```bash
63
+ # Run as server (on remote machine)
64
+ pbsync serve
65
+ pbsync serve --verbose
66
+
67
+ # Connect as client (on local machine)
68
+ pbsync connect <hostname>
69
+ pbsync connect <hostname> --verbose
70
+
71
+ # Show help
72
+ pbsync --help
73
+ pbsync serve --help
74
+ pbsync connect --help
75
+ ```
76
+
77
+ ## Testing
78
+
79
+ The gem includes comprehensive unit and integration tests using RSpec.
80
+
81
+ ```bash
82
+ # Run all tests
83
+ bundle exec rake spec
84
+
85
+ # Run unit tests only
86
+ bundle exec rake unit
87
+
88
+ # Run integration tests only
89
+ bundle exec rake integration
90
+
91
+ # Run tests with coverage report
92
+ bundle exec rake coverage
93
+
94
+ # Run tests in verbose mode
95
+ bundle exec rake verbose
96
+ ```
97
+
98
+ ## Code Quality
99
+
100
+ The project uses RuboCop for Ruby code linting and style enforcement.
101
+
102
+ ```bash
103
+ # Run RuboCop linter
104
+ bundle exec rake lint
105
+
106
+ # Run RuboCop with auto-correct
107
+ bundle exec rake lint_fix
108
+
109
+ # Run RuboCop directly with specific options
110
+ bundle exec rubocop
111
+ bundle exec rubocop --autocorrect-all
112
+ ```
113
+
114
+ ### RuboCop Configuration
115
+
116
+ - Target Ruby version: 2.7+
117
+ - Style guide: Standard Ruby with some customizations
118
+ - Enforced string literals: single quotes
119
+ - Max method length: 20 lines
120
+ - Max class length: 150 lines
121
+ - Line length: 120 characters (except in specs)
122
+
123
+ ### Test Architecture
124
+
125
+ - **Unit Tests** (`spec/pbsync/`): Test individual classes with mocked dependencies
126
+ - `clipboard_sync_spec.rb`: Protocol handling, deduplication, thread safety
127
+ - `server_spec.rb`: Server socket creation and client handling
128
+ - `client_spec.rb`: SSH tunnel creation and connection management
129
+
130
+ - **Integration Tests** (`spec/integration/`): Test end-to-end socket communication
131
+ - Real Unix sockets in temp directories
132
+ - Bidirectional sync without SSH
133
+ - Protocol edge cases and error recovery
134
+
135
+ - **Test Helpers** (`spec/support/`):
136
+ - `MockClipboard`: Thread-safe clipboard mock for testing
137
+ - `MockSocket`: Simulated socket for unit testing
138
+ - `MockSocketPair`: Bidirectional socket pair for integration tests
139
+
140
+ ## Logging
141
+
142
+ The gem uses Ruby's built-in Logger for output with sensible defaults:
143
+
144
+ - **Default log level**: INFO (shows important operational messages)
145
+ - **Verbose mode**: DEBUG (shows detailed operational information)
146
+ - **Output**: STDOUT with clean formatting (no timestamps for CLI usage)
147
+ - **Log levels**:
148
+ - INFO: Normal operational messages (no prefix)
149
+ - DEBUG: Detailed debugging info ([DEBUG] prefix)
150
+ - WARN: Warning messages ([WARN] prefix)
151
+ - ERROR: Error messages ([ERROR] prefix)
152
+
153
+ ### Controlling Log Level
154
+
155
+ ```bash
156
+ # Normal mode (INFO level)
157
+ pbsync connect hostname
158
+
159
+ # Verbose mode (DEBUG level)
160
+ pbsync connect hostname --verbose
161
+ pbsync serve -v
162
+
163
+ # In tests (suppressed by default)
164
+ DEBUG_TESTS=1 bundle exec rake spec # Show debug logs in tests
165
+ ```
166
+
167
+ ## Development Notes
168
+
169
+ - The client assumes `pbsync` is available in the remote machine's $PATH
170
+ - Socket path is hardcoded as `/tmp/pbsync.sock`
171
+ - Dependencies: clipboard gem for clipboard access, clamp gem for CLI parsing
172
+ - Works on macOS, Linux, and Windows (clipboard gem provides cross-platform support)
173
+ - Tests use dependency injection to mock clipboard and socket operations
174
+ - Logger outputs to STDOUT with clean formatting optimized for CLI usage
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Remo
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.
data/README.md ADDED
@@ -0,0 +1,229 @@
1
+ # PBSync
2
+
3
+ A Ruby gem for bidirectional clipboard synchronization between machines over SSH using Unix domain sockets.
4
+
5
+ ## Features
6
+
7
+ - **Bidirectional sync**: Changes on either machine are automatically synchronized
8
+ - **SSH tunneling**: Secure connection between machines
9
+ - **Cross-platform**: Works on macOS, Linux, and Windows (via clipboard gem)
10
+ - **Simple setup**: No configuration files needed
11
+ - **Automatic deduplication**: Prevents sync loops and duplicate transfers
12
+ - **UTF-8 support**: Handles international text and emoji correctly
13
+ - **Size limits**: Protects against oversized clipboard content (1MB max)
14
+
15
+ ## Installation
16
+
17
+ ### From RubyGems (when published)
18
+
19
+ ```bash
20
+ gem install pbsync
21
+ ```
22
+
23
+ ### From Source
24
+
25
+ ```bash
26
+ git clone https://github.com/yourusername/pbsync.git
27
+ cd pbsync
28
+ bundle install
29
+ gem build pbsync.gemspec
30
+ gem install ./pbsync-0.1.0.gem
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ### Basic Usage
36
+
37
+ Simply connect to a remote machine:
38
+ ```bash
39
+ pbsync connect hostname
40
+ ```
41
+
42
+ That's it! Your clipboards are now synchronized. Copy text on either machine and it will appear on the other.
43
+
44
+ The `connect` command automatically:
45
+ 1. Establishes an SSH connection to the remote host
46
+ 2. Starts the pbsync server on the remote machine
47
+ 3. Creates a secure tunnel for clipboard synchronization
48
+ 4. Begins monitoring both clipboards for changes
49
+
50
+ ### Verbose Mode
51
+
52
+ For debugging or to see detailed operation logs:
53
+ ```bash
54
+ pbsync connect hostname --verbose
55
+ # or
56
+ pbsync connect hostname -v
57
+ ```
58
+
59
+ ### Command Reference
60
+
61
+ ```bash
62
+ # Show help
63
+ pbsync --help
64
+
65
+ # Connect to remote server (most common usage)
66
+ pbsync connect <hostname> [--verbose]
67
+
68
+ # Manually start server mode (rarely needed)
69
+ pbsync serve [--verbose]
70
+
71
+ # Show subcommand help
72
+ pbsync connect --help
73
+ pbsync serve --help
74
+ ```
75
+
76
+ ### Manual Server Mode
77
+
78
+ The `serve` command is available if you need to manually start a server, but this is rarely necessary since `connect` handles it automatically. You might use it for:
79
+ - Testing the server locally
80
+ - Running pbsync in a non-SSH environment
81
+ - Custom networking setups
82
+
83
+ ## How It Works
84
+
85
+ 1. **Server Mode**: Creates a Unix domain socket at `/tmp/pbsync.sock` and waits for connections
86
+ 2. **Client Mode**: Establishes an SSH tunnel to the remote host, forwarding the Unix socket
87
+ 3. **Monitoring**: Both sides poll their clipboard every 0.5 seconds for changes
88
+ 4. **Synchronization**: When a change is detected, it's sent to the other side via the socket
89
+ 5. **Deduplication**: Tracks sent and received content to prevent echo loops
90
+
91
+ ### Protocol
92
+
93
+ PBSync uses a simple binary protocol:
94
+ - 4-byte length prefix (network byte order)
95
+ - UTF-8 encoded clipboard content
96
+ - Maximum message size: 1MB
97
+
98
+ ## Requirements
99
+
100
+ - Ruby 2.7 or higher
101
+ - SSH access to the remote machine
102
+ - `pbsync` installed on both machines
103
+ - The remote machine must have `pbsync` in its `$PATH`
104
+
105
+ ## Development
106
+
107
+ ### Setup
108
+
109
+ ```bash
110
+ # Clone the repository
111
+ git clone https://github.com/yourusername/pbsync.git
112
+ cd pbsync
113
+
114
+ # Install dependencies
115
+ bundle install
116
+ ```
117
+
118
+ ### Testing
119
+
120
+ ```bash
121
+ # Run all tests
122
+ bundle exec rake spec
123
+
124
+ # Run with coverage report
125
+ bundle exec rake coverage
126
+
127
+ # Run specific test suites
128
+ bundle exec rake unit # Unit tests only
129
+ bundle exec rake integration # Integration tests only
130
+ ```
131
+
132
+ ### Code Quality
133
+
134
+ ```bash
135
+ # Run RuboCop linter
136
+ bundle exec rake lint
137
+
138
+ # Auto-fix linting issues
139
+ bundle exec rake lint_fix
140
+ ```
141
+
142
+ ### Building
143
+
144
+ ```bash
145
+ # Build the gem
146
+ gem build pbsync.gemspec
147
+
148
+ # Install locally for testing
149
+ gem install ./pbsync-0.1.0.gem
150
+ ```
151
+
152
+ ## Architecture
153
+
154
+ ```
155
+ ┌─────────────┐ ┌─────────────┐
156
+ │ Local │ │ Remote │
157
+ │ Machine │ │ Machine │
158
+ ├─────────────┤ ├─────────────┤
159
+ │ Clipboard │◄──┐ ┌──►│ Clipboard │
160
+ └─────────────┘ │ │ └─────────────┘
161
+ ▲ │ │ ▲
162
+ │ │ │ │
163
+ ▼ │ │ ▼
164
+ ┌─────────────┐ │ │ ┌─────────────┐
165
+ │ PBSync │ │ │ │ PBSync │
166
+ │ Client │ │ │ │ Server │
167
+ ├─────────────┤ │ │ ├─────────────┤
168
+ │ Monitor & │ │ │ │ Monitor & │
169
+ │ Send/Recv │ │ │ │ Send/Recv │
170
+ └─────────────┘ │ │ └─────────────┘
171
+ ▲ │ │ ▲
172
+ │ ▼ ▼ │
173
+ └──────────────────────────────────┘
174
+ SSH Tunnel + Unix Socket
175
+
176
+ ```
177
+
178
+ ## Troubleshooting
179
+
180
+ ### Connection Issues
181
+
182
+ If you can't connect:
183
+ 1. Ensure `pbsync` is installed on both machines
184
+ 2. Verify SSH access: `ssh hostname`
185
+ 3. Check that `pbsync` is in the remote machine's `$PATH`
186
+ 4. Use verbose mode (`-v`) to see detailed logs
187
+
188
+ ### Clipboard Not Syncing
189
+
190
+ 1. Check that the clipboard contains text (not images or files)
191
+ 2. Verify the content is under 1MB
192
+ 3. Try verbose mode to see what's being sent/received
193
+ 4. Ensure no other clipboard managers are interfering
194
+
195
+ ### Performance
196
+
197
+ - The default polling interval is 0.5 seconds
198
+ - Large clipboard content (near 1MB) may take longer to sync
199
+ - Network latency affects synchronization speed
200
+
201
+ ## Contributing
202
+
203
+ 1. Fork the repository
204
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
205
+ 3. Write tests for your changes
206
+ 4. Ensure all tests pass (`bundle exec rake spec`)
207
+ 5. Check code quality (`bundle exec rake lint`)
208
+ 6. Commit your changes
209
+ 7. Push to the branch (`git push origin feature/amazing-feature`)
210
+ 8. Open a Pull Request
211
+
212
+ ## License
213
+
214
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
215
+
216
+ ## Acknowledgments
217
+
218
+ - Built with the [clipboard](https://github.com/janlelis/clipboard) gem for cross-platform clipboard access
219
+ - Uses [Clamp](https://github.com/mdub/clamp) for command-line argument parsing
220
+ - Inspired by the need for seamless clipboard sharing in remote development
221
+
222
+ ## Changelog
223
+
224
+ ### 0.1.0 (Initial Release)
225
+
226
+ - Basic bidirectional clipboard synchronization
227
+ - SSH tunneling support
228
+ - UTF-8 and size limit handling
229
+ - Logging system with verbose mode
data/bin/pbsync ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/pbsync'
5
+
6
+ PBSync::CLI.run
data/lib/pbsync/cli.rb ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'clamp'
4
+
5
+ module PBSync
6
+ class CLI < Clamp::Command
7
+ option ['-v', '--verbose'], :flag, 'Enable verbose logging'
8
+
9
+ subcommand 'serve', 'Run in server mode' do
10
+ def execute
11
+ PBSync.verbose = verbose?
12
+ Server.new.run
13
+ end
14
+ end
15
+
16
+ subcommand 'connect', 'Connect and sync clipboard with host' do
17
+ parameter 'HOST', 'Hostname to connect to'
18
+
19
+ def execute
20
+ PBSync.verbose = verbose?
21
+ Client.new(host).run
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'open3'
5
+ require 'fileutils'
6
+
7
+ module PBSync
8
+ class Client
9
+ def initialize(host)
10
+ @host = host
11
+ end
12
+
13
+ def run
14
+ PBSync.logger.info("Entered client mode - host: #{@host}")
15
+
16
+ start_ssh_tunnel
17
+ wait_for_socket
18
+ connect_to_server
19
+ rescue Interrupt
20
+ PBSync.logger.info('Client interrupted')
21
+ rescue StandardError => e
22
+ PBSync.logger.error("Client error: #{e.message}")
23
+ PBSync.logger.debug(e.backtrace.join("\n")) if PBSync.verbose
24
+ ensure
25
+ cleanup
26
+ end
27
+
28
+ private
29
+
30
+ def start_ssh_tunnel
31
+ remote_command = 'pbsync serve'
32
+
33
+ ssh_args = [
34
+ 'ssh',
35
+ @host,
36
+ '-L', "#{SOCKET_PATH}:#{SOCKET_PATH}",
37
+ 'sh', '-c', "'#{remote_command}'",
38
+ ]
39
+
40
+ PBSync.log("Starting SSH tunnel with: #{ssh_args.join(" ")}")
41
+
42
+ @stdin, @stdout, @stderr, @ssh_thread = Open3.popen3(*ssh_args)
43
+
44
+ Thread.new do
45
+ while (line = @stderr.gets)
46
+ PBSync.log("SSH stderr: #{line.strip}")
47
+ end
48
+ end
49
+
50
+ PBSync.logger.info('SSH tunnel launched')
51
+ end
52
+
53
+ def wait_for_socket
54
+ PBSync.logger.info('Waiting for socket to become available...')
55
+
56
+ 30.times do
57
+ if File.exist?(SOCKET_PATH)
58
+ sleep 1
59
+ return
60
+ end
61
+ sleep 0.2
62
+ end
63
+
64
+ raise 'Failed to establish SSH tunnel - socket not created'
65
+ end
66
+
67
+ def connect_to_server
68
+ raise "Socket not found at #{SOCKET_PATH}" unless File.exist?(SOCKET_PATH)
69
+
70
+ PBSync.logger.info('Socket available, connecting...')
71
+
72
+ socket = UNIXSocket.new(SOCKET_PATH)
73
+ PBSync.logger.info("Connected to remote server via #{@host}")
74
+
75
+ sync = ClipboardSync.new(socket)
76
+ sync.start
77
+
78
+ PBSync.logger.info('Entering client loop')
79
+
80
+ Signal.trap('INT') do
81
+ PBSync.logger.info('Shutting down client...')
82
+ sync.stop
83
+ cleanup
84
+ exit(0)
85
+ end
86
+
87
+ sleep
88
+ end
89
+
90
+ def cleanup
91
+ @ssh_thread&.kill
92
+ [@stdin, @stdout, @stderr].each { |io| io&.close }
93
+ FileUtils.rm_f(SOCKET_PATH)
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PBSync
4
+ class ClipboardSync
5
+ attr_reader :running
6
+
7
+ def initialize(socket, clipboard_adapter: Clipboard)
8
+ @socket = socket
9
+ @clipboard = clipboard_adapter
10
+ @last_clipboard_sent = nil
11
+ @last_clipboard_received = nil
12
+ @running = true
13
+ end
14
+
15
+ def start
16
+ start_clipboard_monitor
17
+ start_receive_loop
18
+ end
19
+
20
+ def stop
21
+ @running = false
22
+ begin
23
+ @socket.close
24
+ rescue StandardError
25
+ nil
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def get_clipboard_text
32
+ text = @clipboard.paste
33
+ return nil if text.nil? || text.empty?
34
+ return nil if text.bytesize > MAX_SIZE
35
+
36
+ text
37
+ rescue StandardError => e
38
+ PBSync.log("Error reading clipboard: #{e.message}")
39
+ nil
40
+ end
41
+
42
+ def set_clipboard_text(text)
43
+ PBSync.log("Setting clipboard: #{text[0..99]}...")
44
+ @clipboard.copy(text)
45
+ rescue StandardError => e
46
+ PBSync.log("Error setting clipboard: #{e.message}")
47
+ end
48
+
49
+ def send_clipboard(text)
50
+ return unless text
51
+
52
+ data = text.encode('UTF-8')
53
+ length = [data.bytesize].pack('N')
54
+ packet = length + data
55
+
56
+ @socket.write(packet)
57
+ @socket.flush
58
+ PBSync.logger.info("📤 Sent clipboard (#{data.bytesize} bytes): \"#{text[0..99]}\"")
59
+ rescue StandardError => e
60
+ PBSync.log("Error sending clipboard: #{e.message}")
61
+ end
62
+
63
+ def read_exact(size)
64
+ data = ''
65
+ while data.bytesize < size
66
+ chunk = @socket.read(size - data.bytesize)
67
+ return nil if chunk.nil? || chunk.empty?
68
+
69
+ data += chunk
70
+ end
71
+ data
72
+ rescue StandardError => e
73
+ PBSync.log("Error reading from socket: #{e.message}")
74
+ nil
75
+ end
76
+
77
+ def start_clipboard_monitor
78
+ PBSync.logger.info('📋 Starting clipboard monitor')
79
+ Thread.new do
80
+ while @running
81
+ begin
82
+ PBSync.log('⏱️ Clipboard poll fired')
83
+ text = get_clipboard_text
84
+
85
+ if text.nil?
86
+ PBSync.log('📋 Clipboard is empty or invalid')
87
+ elsif text == @last_clipboard_sent
88
+ PBSync.log('📋 Clipboard unchanged (already sent)')
89
+ elsif text == @last_clipboard_received
90
+ PBSync.log('📋 Clipboard came from remote (already received)')
91
+ else
92
+ PBSync.log('📋 Clipboard changed → sending')
93
+ @last_clipboard_sent = text
94
+ send_clipboard(text)
95
+ end
96
+
97
+ sleep POLL_INTERVAL
98
+ rescue StandardError => e
99
+ PBSync.log("Error in clipboard monitor: #{e.message}")
100
+ sleep POLL_INTERVAL
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ def start_receive_loop
107
+ PBSync.logger.info('📥 Starting receive loop')
108
+ Thread.new do
109
+ while @running
110
+ begin
111
+ length_data = read_exact(4)
112
+ unless length_data && length_data.bytesize == 4
113
+ PBSync.logger.info('🔌 Disconnected (no length)')
114
+ break
115
+ end
116
+
117
+ length = length_data.unpack1('N')
118
+ message_data = read_exact(length)
119
+ unless message_data
120
+ PBSync.logger.info('Disconnected or bad message')
121
+ break
122
+ end
123
+
124
+ received = message_data.force_encoding('UTF-8')
125
+
126
+ if received != @last_clipboard_sent && received != @last_clipboard_received
127
+ @last_clipboard_received = received
128
+ set_clipboard_text(received)
129
+ PBSync.logger.info("📥 Received clipboard (#{message_data.bytesize} bytes): \"#{received[0..99]}\"")
130
+ else
131
+ PBSync.log('📥 Ignored duplicate clipboard content')
132
+ end
133
+ rescue StandardError => e
134
+ PBSync.log("Error in receive loop: #{e.message}")
135
+ break
136
+ end
137
+ end
138
+
139
+ stop
140
+ exit(0)
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'fileutils'
5
+
6
+ module PBSync
7
+ class Server
8
+ def run
9
+ PBSync.logger.info('Entered server mode')
10
+
11
+ FileUtils.rm_f(SOCKET_PATH)
12
+
13
+ server = UNIXServer.new(SOCKET_PATH)
14
+ PBSync.logger.info("Server listening on #{SOCKET_PATH}")
15
+
16
+ client_socket = server.accept
17
+ PBSync.logger.info('Client connected')
18
+
19
+ sync = ClipboardSync.new(client_socket)
20
+ sync.start
21
+
22
+ PBSync.logger.info('Entering server loop')
23
+
24
+ Signal.trap('INT') do
25
+ PBSync.logger.info('Shutting down server...')
26
+ sync.stop
27
+ server.close
28
+ FileUtils.rm_f(SOCKET_PATH)
29
+ exit(0)
30
+ end
31
+
32
+ sleep
33
+ rescue Interrupt
34
+ PBSync.logger.info('Server interrupted')
35
+ rescue StandardError => e
36
+ PBSync.logger.error("Server error: #{e.message}")
37
+ PBSync.logger.debug(e.backtrace.join("\n")) if PBSync.verbose
38
+ ensure
39
+ FileUtils.rm_f(SOCKET_PATH)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PBSync
4
+ VERSION = '0.1.0'
5
+ end
data/lib/pbsync.rb ADDED
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'clipboard'
5
+ require 'timeout'
6
+ require 'logger'
7
+ require_relative 'pbsync/version'
8
+ require_relative 'pbsync/clipboard_sync'
9
+ require_relative 'pbsync/server'
10
+ require_relative 'pbsync/client'
11
+ require_relative 'pbsync/cli'
12
+
13
+ module PBSync
14
+ SOCKET_PATH = '/tmp/pbsync.sock'
15
+ MAX_SIZE = 1_000_000
16
+ POLL_INTERVAL = 0.5
17
+
18
+ class << self
19
+ def logger
20
+ @logger ||= create_logger
21
+ end
22
+
23
+ attr_writer :logger
24
+
25
+ def verbose=(value)
26
+ logger.level = value ? Logger::DEBUG : Logger::INFO
27
+ end
28
+
29
+ def verbose
30
+ logger.level == Logger::DEBUG
31
+ end
32
+
33
+ # Compatibility method for existing code
34
+ def log(message)
35
+ logger.debug(message)
36
+ end
37
+
38
+ private
39
+
40
+ def create_logger
41
+ log = Logger.new($stdout)
42
+ log.level = Logger::INFO
43
+ log.formatter = proc do |severity, _datetime, _progname, msg|
44
+ # Simple format without timestamp for cleaner CLI output
45
+ case severity
46
+ when 'DEBUG'
47
+ "[DEBUG] #{msg}\n"
48
+ when 'WARN'
49
+ "[WARN] #{msg}\n"
50
+ when 'ERROR'
51
+ "[ERROR] #{msg}\n"
52
+ else
53
+ "#{msg}\n"
54
+ end
55
+ end
56
+ log
57
+ end
58
+ end
59
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pbsync
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Remo
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: clipboard
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.3'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.3'
26
+ - !ruby/object:Gem::Dependency
27
+ name: clamp
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.3'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.3'
40
+ - !ruby/object:Gem::Dependency
41
+ name: logger
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.5'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.5'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '13.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '13.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rspec
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.0'
82
+ description: Bidirectional clipboard syncing between machines over SSH using Unix
83
+ sockets
84
+ executables:
85
+ - pbsync
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - CLAUDE.md
90
+ - LICENSE
91
+ - README.md
92
+ - bin/pbsync
93
+ - lib/pbsync.rb
94
+ - lib/pbsync/cli.rb
95
+ - lib/pbsync/client.rb
96
+ - lib/pbsync/clipboard_sync.rb
97
+ - lib/pbsync/server.rb
98
+ - lib/pbsync/version.rb
99
+ homepage: https://github.com/sudoremo/pbsync
100
+ licenses:
101
+ - MIT
102
+ metadata:
103
+ homepage_uri: https://github.com/sudoremo/pbsync
104
+ source_code_uri: https://github.com/sudoremo/pbsync
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 2.7.0
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubygems_version: 3.6.8
120
+ specification_version: 4
121
+ summary: Clipboard synchronization utility for macOS
122
+ test_files: []