peer-term 1.4.5 → 1.4.7
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 +28 -0
- package/package.json +1 -1
- package/src/cli.js +11 -11
- package/src/index.js +38 -6
- package/src/ui.js +18 -0
package/README.md
CHANGED
|
@@ -37,10 +37,38 @@ peer-term --path . # starts in current directory
|
|
|
37
37
|
peer-term --readonly # view-only session
|
|
38
38
|
peer-term --secure # require fingerprint verification
|
|
39
39
|
peer-term --expiry 10m # custom expiry time
|
|
40
|
+
peer-term --rejoin 5m # custom reconnection window
|
|
40
41
|
peer-term --relay wss://custom # use a custom relay server
|
|
41
42
|
peer-term --verbose # enable debug logging
|
|
42
43
|
```
|
|
43
44
|
|
|
45
|
+
### CLI Highlights
|
|
46
|
+
|
|
47
|
+
| Flag | Description | Default |
|
|
48
|
+
| :--- | :--- | :--- |
|
|
49
|
+
| `--expiry <time>` | Session expiry duration (e.g. `5m`, `30s`, `1h`) | `5m` |
|
|
50
|
+
| `--rejoin <time>` | Reconnection window if connection drops (e.g. `2m`, `10m`) | `2m` |
|
|
51
|
+
| `--readonly` | Disable client keystrokes (view-only terminal mode) | Off |
|
|
52
|
+
| `--secure` | Enable fingerprint verification for MITM protection | Off |
|
|
53
|
+
| `--path <dir>` | Starting directory for the terminal session | `~` (Home) |
|
|
54
|
+
| `--relay <url>` | Custom relay server WebSocket URL(s) | `wss://relay.dhananjaybalekar.in` |
|
|
55
|
+
| `-V, --verbose` | Enable verbose debug logging | Off |
|
|
56
|
+
| `-h, --help` | Display usage information and exit | — |
|
|
57
|
+
| `-v, --version` | Output version number and exit | — |
|
|
58
|
+
|
|
59
|
+
### Interactive Controls
|
|
60
|
+
|
|
61
|
+
While running, you can manage sessions directly from the host CLI:
|
|
62
|
+
|
|
63
|
+
| Command | Description |
|
|
64
|
+
| :--- | :--- |
|
|
65
|
+
| `n` | Create a new session |
|
|
66
|
+
| `l` | List all active sessions |
|
|
67
|
+
| `a <code>` | Authorize verified fingerprint (in `--secure` mode) |
|
|
68
|
+
| `k <code>` | Kill a specific session |
|
|
69
|
+
| `q` | Quit all sessions and exit |
|
|
70
|
+
|
|
71
|
+
|
|
44
72
|
## Features
|
|
45
73
|
|
|
46
74
|
- **No Config**: Works instantly. No port forwarding or firewall configuration needed.
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
* Top-level orchestration:
|
|
5
5
|
* 1. Detects shell, resolves start path, runs interactive prompts
|
|
6
6
|
* 2. Creates a SessionManager and starts the first session
|
|
7
|
-
* 3. Provides a readline-based menu (n/l/k/a/q) for managing sessions
|
|
7
|
+
* 3. Provides a readline-based menu (n/l/r/k/a/q) for managing sessions
|
|
8
8
|
* 4. Handles SIGINT and global error handlers
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import readline from 'readline';
|
|
12
12
|
import logger from './logger.js';
|
|
13
|
-
import { printBanner } from './ui.js';
|
|
13
|
+
import { printBanner, printCommandsMenu } from './ui.js';
|
|
14
14
|
import { argv, RELAY_URLS } from './config.js';
|
|
15
15
|
import { formatDuration, detectShell, resolveStartPath } from './utils.js';
|
|
16
16
|
import { getExpiryInteractive, getRejoinInteractive } from './prompts.js';
|
|
@@ -50,14 +50,7 @@ async function main() {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// ─── Interactive CLI menu ──────────────────────────────────────────
|
|
53
|
-
|
|
54
|
-
console.log(' Commands:');
|
|
55
|
-
console.log(' [n] New session');
|
|
56
|
-
console.log(' [l] List sessions');
|
|
57
|
-
console.log(' [a <code>] Authorize verified fingerprint');
|
|
58
|
-
console.log(' [k <code>] Kill session');
|
|
59
|
-
console.log(' [q] Quit all');
|
|
60
|
-
console.log(' ─────────────────────────────────────────');
|
|
53
|
+
printCommandsMenu();
|
|
61
54
|
console.log('');
|
|
62
55
|
|
|
63
56
|
const rl = readline.createInterface({
|
|
@@ -76,6 +69,13 @@ async function main() {
|
|
|
76
69
|
await manager.createSession();
|
|
77
70
|
} else if (input === 'l') {
|
|
78
71
|
manager.listSessions();
|
|
72
|
+
} else if (input.startsWith('r ') || input.startsWith('rt ') || input === 'r' || input === 'rt') {
|
|
73
|
+
const code = input.startsWith('rt ')
|
|
74
|
+
? input.slice(3).trim()
|
|
75
|
+
: input.startsWith('r ')
|
|
76
|
+
? input.slice(2).trim()
|
|
77
|
+
: '';
|
|
78
|
+
await manager.toggleReadonlySession(code);
|
|
79
79
|
} else if (input.startsWith('a ')) {
|
|
80
80
|
const code = input.slice(2).trim();
|
|
81
81
|
await manager.authorizeSession(code);
|
|
@@ -88,7 +88,7 @@ async function main() {
|
|
|
88
88
|
rl.close();
|
|
89
89
|
process.exit(0);
|
|
90
90
|
} else if (input) {
|
|
91
|
-
console.log(' Unknown command. Use n, l, a <code>, k <code>, or q.');
|
|
91
|
+
console.log(' Unknown command. Use n, l, r <code>, a <code>, k <code>, or q.');
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
rl.prompt();
|
package/src/index.js
CHANGED
|
@@ -46,7 +46,7 @@ import { ViewerServer } from './viewer.js';
|
|
|
46
46
|
|
|
47
47
|
// ─── Session Class ───────────────────────────────────────────────────────────
|
|
48
48
|
|
|
49
|
-
class Session {
|
|
49
|
+
export class Session {
|
|
50
50
|
constructor(shell, expiryMs, rejoinMs, readOnly, startPath, onDestroy) {
|
|
51
51
|
this.shell = shell;
|
|
52
52
|
this.expiryMs = expiryMs;
|
|
@@ -92,6 +92,25 @@ class Session {
|
|
|
92
92
|
this.sessionDurationInterval = null; // Interval: periodic CLI status log
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
get readonly() {
|
|
96
|
+
return this.readOnly;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
set readonly(val) {
|
|
100
|
+
this.readOnly = Boolean(val);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async toggleReadonly() {
|
|
104
|
+
this.readonly = !this.readonly;
|
|
105
|
+
const modeStr = this.readonly ? 'READ-ONLY' : 'INTERACTIVE (write enabled)';
|
|
106
|
+
const codeLabel = this.code ? `Session ${this.code}` : 'Session';
|
|
107
|
+
this.log(`${codeLabel} is now ${modeStr}`);
|
|
108
|
+
if (this.sharedKey) {
|
|
109
|
+
await this._sendSessionConfig();
|
|
110
|
+
}
|
|
111
|
+
return this.readonly;
|
|
112
|
+
}
|
|
113
|
+
|
|
95
114
|
/**
|
|
96
115
|
* Build the context object for FileTransferHandler.
|
|
97
116
|
* Called on construction and whenever shared key changes.
|
|
@@ -233,18 +252,18 @@ class Session {
|
|
|
233
252
|
}
|
|
234
253
|
|
|
235
254
|
async _sendSessionConfig() {
|
|
236
|
-
if (!this.sharedKey
|
|
255
|
+
if (!this.sharedKey) return;
|
|
237
256
|
|
|
238
257
|
const config = JSON.stringify({
|
|
239
258
|
type: 'session-config',
|
|
240
|
-
readonly: this.
|
|
259
|
+
readonly: this.readonly,
|
|
241
260
|
version: version,
|
|
242
261
|
shell: this.shell,
|
|
243
262
|
startPath: this.startPath,
|
|
244
263
|
});
|
|
245
264
|
try {
|
|
246
265
|
const payload = await encrypt(this.sharedKey, config);
|
|
247
|
-
this.
|
|
266
|
+
this._sendEncryptedToClient(payload);
|
|
248
267
|
} catch (err) {
|
|
249
268
|
this.log(`Failed to send session config: ${err.message}`);
|
|
250
269
|
}
|
|
@@ -941,8 +960,8 @@ class Session {
|
|
|
941
960
|
} else {
|
|
942
961
|
status = `waiting for client (expires in ${formatDuration(remaining)})`;
|
|
943
962
|
}
|
|
944
|
-
|
|
945
|
-
return status
|
|
963
|
+
const mode = this.readonly ? '[READ-ONLY]' : '[INTERACTIVE]';
|
|
964
|
+
return `${status} ${mode}`;
|
|
946
965
|
}
|
|
947
966
|
|
|
948
967
|
// ─── Session Duration Logging ────────────────────────────────────────
|
|
@@ -1034,6 +1053,19 @@ export class SessionManager {
|
|
|
1034
1053
|
await session.authorizeFingerprint();
|
|
1035
1054
|
}
|
|
1036
1055
|
|
|
1056
|
+
async toggleReadonlySession(code) {
|
|
1057
|
+
const session = this.sessions.get(code);
|
|
1058
|
+
if (!session) {
|
|
1059
|
+
logger.warn(`Session ${code} not found.`);
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
await session.toggleReadonly();
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
async toggleReadonly(code) {
|
|
1066
|
+
return this.toggleReadonlySession(code);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1037
1069
|
killAll() {
|
|
1038
1070
|
for (const [, session] of this.sessions) {
|
|
1039
1071
|
session.destroy();
|
package/src/ui.js
CHANGED
|
@@ -164,3 +164,21 @@ export function printVersion() {
|
|
|
164
164
|
console.log('peer-term (version unknown)');
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
|
+
|
|
168
|
+
// ─── Commands Menu ───────────────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Print the interactive CLI commands menu.
|
|
172
|
+
*/
|
|
173
|
+
export function printCommandsMenu() {
|
|
174
|
+
console.log(' ─────────────────────────────────────────');
|
|
175
|
+
console.log(' Commands:');
|
|
176
|
+
console.log(' [n] New session');
|
|
177
|
+
console.log(' [l] List sessions');
|
|
178
|
+
console.log(' [r <code>] Toggle read-only mode');
|
|
179
|
+
console.log(' [a <code>] Authorize verified fingerprint');
|
|
180
|
+
console.log(' [k <code>] Kill session');
|
|
181
|
+
console.log(' [q] Quit all');
|
|
182
|
+
console.log(' ─────────────────────────────────────────');
|
|
183
|
+
}
|
|
184
|
+
|