sshcp 0.2.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.
- package/README.md +306 -0
- package/bin/check-deps.js +46 -0
- package/bin/sshcp.js +83 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# sshcp
|
|
2
|
+
|
|
3
|
+
Easy SSH file copy CLI tool with persistent server selection, bookmarks, rsync sync, and 2-way watch mode.
|
|
4
|
+
|
|
5
|
+
No more typing long `scp user@host:/path/to/file` commands. Just select your server once and copy files with ease.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Interactive server selection** - Arrow-key navigation from `~/.ssh/config`
|
|
10
|
+
- **Persistent server selection** - No need to re-select every time
|
|
11
|
+
- **Bookmarks** - Save frequently used remote paths
|
|
12
|
+
- **Rsync sync** - Efficient incremental directory syncing
|
|
13
|
+
- **Watch mode** - 2-way sync with conflict resolution
|
|
14
|
+
- **Beautiful terminal UI** - Rich formatting and progress display
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
### Using npx (easiest)
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx sshcp --help
|
|
22
|
+
npx sshcp set
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
> Requires Python and [uv](https://docs.astral.sh/uv/) or pipx installed.
|
|
26
|
+
|
|
27
|
+
### Using uv (recommended for Python users)
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Run without installing
|
|
31
|
+
uvx sshcp --help
|
|
32
|
+
|
|
33
|
+
# Or install globally
|
|
34
|
+
uv tool install sshcp
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Using pip
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install sshcp
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### From source
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
git clone https://github.com/shubham8550/sshcp.git
|
|
47
|
+
cd sshcp
|
|
48
|
+
uv sync
|
|
49
|
+
uv run sshcp --help
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# 1. Select your server (arrow keys to navigate)
|
|
56
|
+
sshcp set
|
|
57
|
+
|
|
58
|
+
# 2. Copy files
|
|
59
|
+
sshcp push ./local_file.txt /remote/path/
|
|
60
|
+
sshcp pull /remote/file.txt ./local/
|
|
61
|
+
|
|
62
|
+
# 3. Use bookmarks for frequent paths
|
|
63
|
+
sshcp bookmark add logs /var/log/myapp
|
|
64
|
+
sshcp pull @logs/error.log ./
|
|
65
|
+
|
|
66
|
+
# 4. Sync directories efficiently
|
|
67
|
+
sshcp sync ./src /var/www/app
|
|
68
|
+
|
|
69
|
+
# 5. Watch and auto-sync changes
|
|
70
|
+
sshcp watch ./project /deploy/app
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Commands
|
|
74
|
+
|
|
75
|
+
### Server Selection
|
|
76
|
+
|
|
77
|
+
#### `sshcp set`
|
|
78
|
+
|
|
79
|
+
Interactive server selector with arrow-key navigation:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
╭─ Select SSH Server ─────────────────────────────────╮
|
|
83
|
+
│ Name Host User Port │
|
|
84
|
+
│ prod 192.168.1.100 deploy 22 │
|
|
85
|
+
│ ▸ staging staging.example admin 22 │
|
|
86
|
+
│ dev 10.0.0.50 dev 2222 │
|
|
87
|
+
╰─────────────────────────────────────────────────────╯
|
|
88
|
+
↑/↓ navigate • Enter select • q quit
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### `sshcp status`
|
|
92
|
+
|
|
93
|
+
Show currently selected server with details.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### File Transfer
|
|
98
|
+
|
|
99
|
+
#### `sshcp push <local> <remote>`
|
|
100
|
+
|
|
101
|
+
Upload a file or directory to the selected server.
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
sshcp push ./myfile.txt /home/user/myfile.txt
|
|
105
|
+
sshcp push ./folder /remote/destination/
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### `sshcp pull <remote> <local>`
|
|
109
|
+
|
|
110
|
+
Download a file or directory from the selected server.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
sshcp pull /var/log/app.log ./app.log
|
|
114
|
+
sshcp pull /etc/nginx ./nginx_config/
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
### Bookmarks
|
|
120
|
+
|
|
121
|
+
Save frequently used remote paths for quick access.
|
|
122
|
+
|
|
123
|
+
#### `sshcp bookmark add <name> <path>`
|
|
124
|
+
|
|
125
|
+
Create a new bookmark:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
sshcp bookmark add logs /var/log/myapp
|
|
129
|
+
sshcp bookmark add config /etc/nginx
|
|
130
|
+
sshcp bookmark add deploy /var/www/production
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### `sshcp bookmark list`
|
|
134
|
+
|
|
135
|
+
Show all saved bookmarks:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
╭─────────────── Saved Bookmarks ───────────────╮
|
|
139
|
+
│ Name Path Usage │
|
|
140
|
+
│ @logs /var/log/myapp @logs/... │
|
|
141
|
+
│ @config /etc/nginx @config/... │
|
|
142
|
+
│ @deploy /var/www/production @deploy/... │
|
|
143
|
+
╰───────────────────────────────────────────────╯
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
#### `sshcp bookmark rm <name>`
|
|
147
|
+
|
|
148
|
+
Remove a bookmark.
|
|
149
|
+
|
|
150
|
+
#### Using Bookmarks
|
|
151
|
+
|
|
152
|
+
Use `@bookmark` syntax in any path:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
sshcp pull @logs/error.log ./ # → /var/log/myapp/error.log
|
|
156
|
+
sshcp push nginx.conf @config/ # → /etc/nginx/
|
|
157
|
+
sshcp sync ./src @deploy # → /var/www/production
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
### Sync (Rsync)
|
|
163
|
+
|
|
164
|
+
Efficient incremental directory syncing using rsync.
|
|
165
|
+
|
|
166
|
+
#### `sshcp sync <local> <remote> [options]`
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# Push local to remote (default)
|
|
170
|
+
sshcp sync ./local_folder /remote/folder
|
|
171
|
+
|
|
172
|
+
# Pull remote to local
|
|
173
|
+
sshcp sync ./local_folder /remote/folder --pull
|
|
174
|
+
|
|
175
|
+
# Delete files not in source
|
|
176
|
+
sshcp sync ./src @deploy --delete
|
|
177
|
+
|
|
178
|
+
# Preview changes without executing
|
|
179
|
+
sshcp sync ./src /remote --dry-run
|
|
180
|
+
|
|
181
|
+
# Exclude patterns
|
|
182
|
+
sshcp sync ./project /deploy --exclude "*.log" --exclude "node_modules"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Options:**
|
|
186
|
+
|
|
187
|
+
| Option | Short | Description |
|
|
188
|
+
|--------|-------|-------------|
|
|
189
|
+
| `--pull` | `-p` | Pull from remote to local (default is push) |
|
|
190
|
+
| `--delete` | `-d` | Delete files not present in source |
|
|
191
|
+
| `--dry-run` | `-n` | Preview changes without executing |
|
|
192
|
+
| `--exclude` | `-e` | Exclude patterns (can be used multiple times) |
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
### Watch Mode (2-Way Sync)
|
|
197
|
+
|
|
198
|
+
Monitor directories and sync changes bidirectionally in real-time.
|
|
199
|
+
|
|
200
|
+
#### `sshcp watch <local> <remote> [options]`
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Start watching (prompts on conflict - default)
|
|
204
|
+
sshcp watch ./src /var/www/app
|
|
205
|
+
|
|
206
|
+
# With bookmark
|
|
207
|
+
sshcp watch ./project @deploy
|
|
208
|
+
|
|
209
|
+
# Custom poll interval
|
|
210
|
+
sshcp watch ./src /app --interval 10
|
|
211
|
+
|
|
212
|
+
# Auto-resolve conflicts
|
|
213
|
+
sshcp watch ./src /app --on-conflict local # Always use local
|
|
214
|
+
sshcp watch ./src /app --on-conflict remote # Always use remote
|
|
215
|
+
sshcp watch ./src /app --on-conflict newer # Keep newer version
|
|
216
|
+
sshcp watch ./src /app -c skip # Skip conflicts
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Output:**
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
╭─────────── Watch Mode Active ───────────────────────╮
|
|
223
|
+
│ Local: /Users/me/project/src │
|
|
224
|
+
│ Remote: myserver:/var/www/app │
|
|
225
|
+
│ Mode: 2-way sync │
|
|
226
|
+
╰─────────────────────────────────────────────────────╯
|
|
227
|
+
|
|
228
|
+
Press Ctrl+C to stop
|
|
229
|
+
|
|
230
|
+
[12:34:56] → Updated: src/app.py
|
|
231
|
+
[12:35:10] ← Downloaded: config/settings.json
|
|
232
|
+
[12:35:20] ⚠ CONFLICT: data/cache.db
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Conflict Resolution:**
|
|
236
|
+
|
|
237
|
+
When both local and remote versions of a file change, you'll see:
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
╭─────────────── ⚠ Conflict Detected ─────────────────╮
|
|
241
|
+
│ File: data/cache.db │
|
|
242
|
+
│ │
|
|
243
|
+
│ Local Remote │
|
|
244
|
+
│ Modified 2024-01-13 12:35 2024-01-13 12:34 │
|
|
245
|
+
│ Size 1.2 KB 1.3 KB │
|
|
246
|
+
│ │
|
|
247
|
+
│ Local is newer │
|
|
248
|
+
│ │
|
|
249
|
+
│ [L] Keep local [R] Keep remote [S] Skip [Q] Quit │
|
|
250
|
+
╰─────────────────────────────────────────────────────╯
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Options:**
|
|
254
|
+
|
|
255
|
+
| Option | Short | Description |
|
|
256
|
+
|--------|-------|-------------|
|
|
257
|
+
| `--interval` | `-i` | Seconds between remote polling (default: 5) |
|
|
258
|
+
| `--on-conflict` | `-c` | Conflict resolution mode (default: ask) |
|
|
259
|
+
|
|
260
|
+
**Conflict Resolution Modes:**
|
|
261
|
+
|
|
262
|
+
| Mode | Description |
|
|
263
|
+
|------|-------------|
|
|
264
|
+
| `ask` | Prompt user for each conflict (default) |
|
|
265
|
+
| `local` | Always keep local version |
|
|
266
|
+
| `remote` | Always keep remote version |
|
|
267
|
+
| `newer` | Keep the newer version by timestamp |
|
|
268
|
+
| `skip` | Skip conflicting files |
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Configuration
|
|
273
|
+
|
|
274
|
+
### SSH Config
|
|
275
|
+
|
|
276
|
+
sshcp reads hosts from `~/.ssh/config`:
|
|
277
|
+
|
|
278
|
+
```
|
|
279
|
+
Host prod
|
|
280
|
+
HostName 192.168.1.100
|
|
281
|
+
User deploy
|
|
282
|
+
IdentityFile ~/.ssh/id_rsa
|
|
283
|
+
|
|
284
|
+
Host staging
|
|
285
|
+
HostName staging.example.com
|
|
286
|
+
User admin
|
|
287
|
+
Port 22
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### sshcp Config
|
|
291
|
+
|
|
292
|
+
Configuration is stored in `~/.config/sshcp/`:
|
|
293
|
+
|
|
294
|
+
- `config.json` - Selected server
|
|
295
|
+
- `bookmarks.json` - Saved bookmarks
|
|
296
|
+
|
|
297
|
+
## Requirements
|
|
298
|
+
|
|
299
|
+
- Python 3.10+
|
|
300
|
+
- OpenSSH (for `scp` and `ssh` commands)
|
|
301
|
+
- rsync (for sync command)
|
|
302
|
+
- SSH config file with configured hosts
|
|
303
|
+
|
|
304
|
+
## License
|
|
305
|
+
|
|
306
|
+
MIT
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require('child_process');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check if a command exists
|
|
7
|
+
*/
|
|
8
|
+
function commandExists(cmd) {
|
|
9
|
+
try {
|
|
10
|
+
const result = spawnSync(process.platform === 'win32' ? 'where' : 'which', [cmd], {
|
|
11
|
+
stdio: 'pipe',
|
|
12
|
+
});
|
|
13
|
+
return result.status === 0;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Check for available Python runners
|
|
20
|
+
const hasUvx = commandExists('uvx');
|
|
21
|
+
const hasPipx = commandExists('pipx');
|
|
22
|
+
const hasSshcp = commandExists('sshcp');
|
|
23
|
+
|
|
24
|
+
if (hasUvx || hasPipx || hasSshcp) {
|
|
25
|
+
// At least one method available
|
|
26
|
+
if (hasUvx) {
|
|
27
|
+
console.log('✓ sshcp will use uvx (fastest)');
|
|
28
|
+
} else if (hasPipx) {
|
|
29
|
+
console.log('✓ sshcp will use pipx');
|
|
30
|
+
} else {
|
|
31
|
+
console.log('✓ sshcp found in PATH');
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
console.warn(`
|
|
35
|
+
⚠ sshcp requires Python and a package runner
|
|
36
|
+
|
|
37
|
+
For best experience, install uv:
|
|
38
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
39
|
+
|
|
40
|
+
Or install pipx:
|
|
41
|
+
pip install pipx
|
|
42
|
+
|
|
43
|
+
Then run: npx sshcp --help
|
|
44
|
+
`);
|
|
45
|
+
}
|
|
46
|
+
|
package/bin/sshcp.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn, spawnSync } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const PACKAGE_NAME = 'sshcp';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Check if a command exists
|
|
10
|
+
*/
|
|
11
|
+
function commandExists(cmd) {
|
|
12
|
+
try {
|
|
13
|
+
const result = spawnSync(process.platform === 'win32' ? 'where' : 'which', [cmd], {
|
|
14
|
+
stdio: 'pipe',
|
|
15
|
+
});
|
|
16
|
+
return result.status === 0;
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Run sshcp using the best available method
|
|
24
|
+
*/
|
|
25
|
+
function runSshcp(args) {
|
|
26
|
+
// Try uvx first (fastest, from uv package manager)
|
|
27
|
+
if (commandExists('uvx')) {
|
|
28
|
+
const proc = spawn('uvx', [PACKAGE_NAME, ...args], {
|
|
29
|
+
stdio: 'inherit',
|
|
30
|
+
});
|
|
31
|
+
proc.on('close', (code) => process.exit(code || 0));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Try pipx (common Python tool runner)
|
|
36
|
+
if (commandExists('pipx')) {
|
|
37
|
+
const proc = spawn('pipx', ['run', PACKAGE_NAME, ...args], {
|
|
38
|
+
stdio: 'inherit',
|
|
39
|
+
});
|
|
40
|
+
proc.on('close', (code) => process.exit(code || 0));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Try direct sshcp command (if installed via pip)
|
|
45
|
+
if (commandExists('sshcp')) {
|
|
46
|
+
const proc = spawn('sshcp', args, {
|
|
47
|
+
stdio: 'inherit',
|
|
48
|
+
});
|
|
49
|
+
proc.on('close', (code) => process.exit(code || 0));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// No method available - show installation instructions
|
|
54
|
+
console.error(`
|
|
55
|
+
╭─────────────────────────────────────────────────────────────────╮
|
|
56
|
+
│ sshcp requires Python and one of: uv, pipx, or pip │
|
|
57
|
+
╰─────────────────────────────────────────────────────────────────╯
|
|
58
|
+
|
|
59
|
+
Install using one of these methods:
|
|
60
|
+
|
|
61
|
+
1. Using uv (recommended - fastest):
|
|
62
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
63
|
+
uvx sshcp --help
|
|
64
|
+
|
|
65
|
+
2. Using pipx:
|
|
66
|
+
pip install pipx
|
|
67
|
+
pipx run sshcp --help
|
|
68
|
+
|
|
69
|
+
3. Using pip:
|
|
70
|
+
pip install sshcp
|
|
71
|
+
sshcp --help
|
|
72
|
+
|
|
73
|
+
Learn more: https://github.com/shubham8550/sshcp
|
|
74
|
+
`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Get command line arguments (skip node and script path)
|
|
79
|
+
const args = process.argv.slice(2);
|
|
80
|
+
|
|
81
|
+
// Run sshcp with arguments
|
|
82
|
+
runSshcp(args);
|
|
83
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sshcp",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Easy SSH file copy CLI tool with persistent server selection, bookmarks, rsync sync, and 2-way watch mode",
|
|
5
|
+
"bin": {
|
|
6
|
+
"sshcp": "./bin/sshcp.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"postinstall": "node ./bin/check-deps.js"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"ssh",
|
|
13
|
+
"scp",
|
|
14
|
+
"file-transfer",
|
|
15
|
+
"sync",
|
|
16
|
+
"rsync",
|
|
17
|
+
"watch",
|
|
18
|
+
"cli"
|
|
19
|
+
],
|
|
20
|
+
"author": "shubham8550",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/shubham8550/sshcp.git"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/shubham8550/sshcp#readme",
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/shubham8550/sshcp/issues"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=14"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"bin/"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
|