peer-term 1.4.6 → 1.4.8
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/package.json +1 -1
- package/src/cli.js +11 -11
- package/src/index.js +41 -6
- package/src/ui.js +18 -0
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 && this.fingerprintAuthorized) {
|
|
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,21 @@ 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, {
|
|
267
|
+
directPayload: JSON.stringify({ _meta: 'session-config', payload }),
|
|
268
|
+
meta: 'session-config',
|
|
269
|
+
});
|
|
248
270
|
} catch (err) {
|
|
249
271
|
this.log(`Failed to send session config: ${err.message}`);
|
|
250
272
|
}
|
|
@@ -941,8 +963,8 @@ class Session {
|
|
|
941
963
|
} else {
|
|
942
964
|
status = `waiting for client (expires in ${formatDuration(remaining)})`;
|
|
943
965
|
}
|
|
944
|
-
|
|
945
|
-
return status
|
|
966
|
+
const mode = this.readonly ? '[READ-ONLY]' : '[INTERACTIVE]';
|
|
967
|
+
return `${status} ${mode}`;
|
|
946
968
|
}
|
|
947
969
|
|
|
948
970
|
// ─── Session Duration Logging ────────────────────────────────────────
|
|
@@ -1034,6 +1056,19 @@ export class SessionManager {
|
|
|
1034
1056
|
await session.authorizeFingerprint();
|
|
1035
1057
|
}
|
|
1036
1058
|
|
|
1059
|
+
async toggleReadonlySession(code) {
|
|
1060
|
+
const session = this.sessions.get(code);
|
|
1061
|
+
if (!session) {
|
|
1062
|
+
logger.warn(`Session ${code} not found.`);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
await session.toggleReadonly();
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
async toggleReadonly(code) {
|
|
1069
|
+
return this.toggleReadonlySession(code);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1037
1072
|
killAll() {
|
|
1038
1073
|
for (const [, session] of this.sessions) {
|
|
1039
1074
|
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
|
+
|