@miraj181/ipingyou 2.1.19 → 2.1.22
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 +1 -2
- package/package.json +1 -1
- package/src/cli.js +22 -14
- package/src/lib/ai/safety.js +1 -1
- package/src/lib/{broker.js → client/broker.js} +24 -8
- package/src/lib/{path-browser.js → client/path-browser.js} +6 -2
- package/src/lib/{session-log.js → mod/session-log.js} +17 -8
- package/src/lib/{worker-runtime.js → mod/worker-runtime.js} +1 -1
- package/src/lib/{chat.js → services/chat.js} +2 -2
- package/src/lib/services/platform.js +364 -0
- package/src/lib/{tunnel.js → services/tunnel.js} +2 -2
- package/src/modes/ai.js +18 -9
- package/src/modes/client.js +90 -25
- package/src/modes/doctor.js +5 -7
- package/src/modes/host.js +303 -153
- package/src/server.js +50 -4
- package/src/lib/platform.js +0 -90
- /package/src/lib/{allowlist.js → mod/allowlist.js} +0 -0
- /package/src/lib/{animations.js → mod/animations.js} +0 -0
- /package/src/lib/{checksum.js → mod/checksum.js} +0 -0
- /package/src/lib/{cleanup.js → mod/cleanup.js} +0 -0
- /package/src/lib/{config.js → mod/config.js} +0 -0
- /package/src/lib/{crypto.js → mod/crypto.js} +0 -0
- /package/src/lib/{open-url.js → mod/open-url.js} +0 -0
- /package/src/lib/{secure-print.js → mod/secure-print.js} +0 -0
- /package/src/lib/{socket-firewall.js → mod/socket-firewall.js} +0 -0
- /package/src/lib/{tmux.js → mod/tmux.js} +0 -0
- /package/src/lib/{uid.js → mod/uid.js} +0 -0
- /package/src/lib/{ssh.js → services/ssh.js} +0 -0
package/src/modes/ai.js
CHANGED
|
@@ -8,16 +8,16 @@ import inquirer from 'inquirer';
|
|
|
8
8
|
import fs from 'node:fs';
|
|
9
9
|
import os from 'node:os';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
-
import { getAlias } from '../lib/config.js';
|
|
12
|
-
import { resolveUID } from '../lib/broker.js';
|
|
13
|
-
import { buildSshArgs, extractHostname, quoteRemoteShell } from '../lib/ssh.js';
|
|
14
|
-
import { addCleanupHook, cleanupAll } from '../lib/cleanup.js';
|
|
11
|
+
import { getAlias } from '../lib/mod/config.js';
|
|
12
|
+
import { resolveUID } from '../lib/client/broker.js';
|
|
13
|
+
import { buildSshArgs, extractHostname, quoteRemoteShell } from '../lib/services/ssh.js';
|
|
14
|
+
import { addCleanupHook, cleanupAll, trackPID, untrackPID } from '../lib/mod/cleanup.js';
|
|
15
15
|
import { startHostMode } from './host.js';
|
|
16
16
|
import { startClientMode } from './client.js';
|
|
17
17
|
import { performSCPNonInteractive } from './client.js';
|
|
18
18
|
import { DEFAULT_AI_MODEL, createGroqChatCompletion, getGroqApiKey, getRateLimitWarnings, listGroqModels, estimateTokensForMessages } from '../lib/ai/groq.js';
|
|
19
19
|
import { assertSafeReadablePath, classifyCommand, redactSensitive, sanitizeUserTask, truncateForModel } from '../lib/ai/safety.js';
|
|
20
|
-
import { recordEvent } from '../lib/session-log.js';
|
|
20
|
+
import { recordEvent } from '../lib/mod/session-log.js';
|
|
21
21
|
|
|
22
22
|
let BROKER_URL = process.env.BROKER_URL || 'https://ipingyou.onrender.com';
|
|
23
23
|
|
|
@@ -223,11 +223,14 @@ async function runLocalCommand(command) {
|
|
|
223
223
|
if (args.length === 0) {
|
|
224
224
|
return { exitCode: 1, stdout: '', stderr: 'Empty or unsafe command after parsing' };
|
|
225
225
|
}
|
|
226
|
-
const
|
|
226
|
+
const child = execa(args[0], args.slice(1), {
|
|
227
227
|
reject: false,
|
|
228
228
|
timeout: 30000,
|
|
229
229
|
maxBuffer: 1024 * 1024,
|
|
230
230
|
});
|
|
231
|
+
trackPID(child.pid);
|
|
232
|
+
const result = await child;
|
|
233
|
+
untrackPID(child.pid);
|
|
231
234
|
|
|
232
235
|
return {
|
|
233
236
|
exitCode: result.exitCode,
|
|
@@ -290,11 +293,14 @@ export function parseLocalCommand(command) {
|
|
|
290
293
|
async function runRemoteCommand(context, command) {
|
|
291
294
|
const sshArgs = buildSshArgs(context.hostname, context.privateKeyPath);
|
|
292
295
|
sshArgs.push(`${context.username}@${context.hostname}`, command);
|
|
293
|
-
const
|
|
296
|
+
const child = execa('ssh', sshArgs, {
|
|
294
297
|
reject: false,
|
|
295
298
|
timeout: 30000,
|
|
296
299
|
maxBuffer: 1024 * 1024,
|
|
297
300
|
});
|
|
301
|
+
trackPID(child.pid);
|
|
302
|
+
const result = await child;
|
|
303
|
+
untrackPID(child.pid);
|
|
298
304
|
|
|
299
305
|
return {
|
|
300
306
|
exitCode: result.exitCode,
|
|
@@ -650,7 +656,7 @@ async function tryAITransfer(task, context) {
|
|
|
650
656
|
if (context && context.scope === 'remote' && context.hostname && context.username) {
|
|
651
657
|
console.log(chalk.dim(` Using active remote session: ${context.username}@${context.hostname}`));
|
|
652
658
|
|
|
653
|
-
const { buildProxyCommandOption, getSshControlOptions, formatScpRemotePath } = await import('../lib/ssh.js');
|
|
659
|
+
const { buildProxyCommandOption, getSshControlOptions, formatScpRemotePath } = await import('../lib/services/ssh.js');
|
|
654
660
|
const scpArgs = [
|
|
655
661
|
'-r',
|
|
656
662
|
...buildProxyCommandOption(context.hostname),
|
|
@@ -670,7 +676,10 @@ async function tryAITransfer(task, context) {
|
|
|
670
676
|
}
|
|
671
677
|
|
|
672
678
|
try {
|
|
673
|
-
const
|
|
679
|
+
const child = execa('scp', scpArgs, { stdio: 'inherit', reject: false });
|
|
680
|
+
trackPID(child.pid);
|
|
681
|
+
const result = await child;
|
|
682
|
+
untrackPID(child.pid);
|
|
674
683
|
if (result.exitCode === 0) {
|
|
675
684
|
console.log(chalk.green(' ✅ Transfer completed via active remote session.'));
|
|
676
685
|
recordEvent('ai_transfer_success', { direction, localPath, remotePath, hostname: context.hostname, reusedContext: true });
|
package/src/modes/client.js
CHANGED
|
@@ -18,20 +18,64 @@ import inquirer from 'inquirer';
|
|
|
18
18
|
import fs from 'node:fs';
|
|
19
19
|
import path from 'node:path';
|
|
20
20
|
import os from 'node:os';
|
|
21
|
-
import { cleanupAll, trackPID, untrackPID, addCleanupHook } from '../lib/cleanup.js';
|
|
22
|
-
import { createSpinner, sshSpinner, networkSpinner, fileTransferSpinner, showConnectionTrace, simulateTransferProgress } from '../lib/animations.js';
|
|
23
|
-
import { getConfig, saveAlias } from '../lib/config.js';
|
|
24
|
-
import { pushTelemetry, requestHostApproval, resolveUID, revokeUID, waitForApproval } from '../lib/broker.js';
|
|
25
|
-
import { calculateChecksum } from '../lib/checksum.js';
|
|
26
|
-
import { promptLocalPath, promptRemotePath } from '../lib/path-browser.js';
|
|
27
|
-
import { buildProxyCommandOption, buildSshArgs, extractHostname, formatScpRemotePath, getKnownHostsOptions, getSshControlOptions, quoteRemoteShell } from '../lib/ssh.js';
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import { cleanupSessionLog, initSessionLog, logSessionEvent, recordEvent } from '../lib/session-log.js';
|
|
21
|
+
import { cleanupAll, trackPID, untrackPID, addCleanupHook } from '../lib/mod/cleanup.js';
|
|
22
|
+
import { createSpinner, sshSpinner, networkSpinner, fileTransferSpinner, showConnectionTrace, simulateTransferProgress } from '../lib/mod/animations.js';
|
|
23
|
+
import { getConfig, saveAlias } from '../lib/mod/config.js';
|
|
24
|
+
import { pushTelemetry, requestHostApproval, resolveUID, revokeUID, waitForApproval } from '../lib/client/broker.js';
|
|
25
|
+
import { calculateChecksum } from '../lib/mod/checksum.js';
|
|
26
|
+
import { promptLocalPath, promptRemotePath } from '../lib/client/path-browser.js';
|
|
27
|
+
import { buildProxyCommandOption, buildSshArgs, extractHostname, formatScpRemotePath, getKnownHostsOptions, getSshControlOptions, quoteRemoteShell } from '../lib/services/ssh.js';
|
|
28
|
+
import { openUrl } from '../lib/mod/open-url.js';
|
|
29
|
+
import { secureSensitiveUrl } from '../lib/mod/secure-print.js';
|
|
30
|
+
import { cleanupSessionLog, initSessionLog, logSessionEvent, recordEvent } from '../lib/mod/session-log.js';
|
|
32
31
|
|
|
33
32
|
let BROKER_URL = process.env.BROKER_URL || 'https://ipingyou.onrender.com';
|
|
34
33
|
|
|
34
|
+
function startLiveLogSync(username, hostname, privateKeyPath, remoteDropPath, localLogPath, persistKnownHosts = true) {
|
|
35
|
+
if (!remoteDropPath || !localLogPath) return;
|
|
36
|
+
|
|
37
|
+
let lastSize = -1;
|
|
38
|
+
let lastMtime = 0;
|
|
39
|
+
let isSyncing = false;
|
|
40
|
+
const interval = setInterval(async () => {
|
|
41
|
+
if (isSyncing) return;
|
|
42
|
+
isSyncing = true;
|
|
43
|
+
try {
|
|
44
|
+
if (!fs.existsSync(localLogPath)) return;
|
|
45
|
+
const stats = fs.statSync(localLogPath);
|
|
46
|
+
if (stats.size === lastSize && stats.mtimeMs === lastMtime) return;
|
|
47
|
+
|
|
48
|
+
const scpArgs = [
|
|
49
|
+
'-O',
|
|
50
|
+
...buildProxyCommandOption(hostname),
|
|
51
|
+
...getKnownHostsOptions(persistKnownHosts),
|
|
52
|
+
'-o', 'IdentitiesOnly=yes',
|
|
53
|
+
...getSshControlOptions(hostname)
|
|
54
|
+
];
|
|
55
|
+
if (privateKeyPath) {
|
|
56
|
+
scpArgs.push('-i', privateKeyPath, '-o', 'IdentityAgent=none');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const clientName = `${os.userInfo().username}-${os.hostname()}`;
|
|
60
|
+
const remoteFilePath = `${remoteDropPath}/client-${clientName}.log`;
|
|
61
|
+
|
|
62
|
+
scpArgs.push(localLogPath, `${username}@${hostname}:${formatScpRemotePath(remoteFilePath)}`);
|
|
63
|
+
|
|
64
|
+
const result = await execa('scp', scpArgs, { reject: false });
|
|
65
|
+
if (result.exitCode === 0) {
|
|
66
|
+
lastSize = stats.size;
|
|
67
|
+
lastMtime = stats.mtimeMs;
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// Ignore background sync failures silently
|
|
71
|
+
} finally {
|
|
72
|
+
isSyncing = false;
|
|
73
|
+
}
|
|
74
|
+
}, 3000);
|
|
75
|
+
|
|
76
|
+
addCleanupHook(() => clearInterval(interval));
|
|
77
|
+
}
|
|
78
|
+
|
|
35
79
|
async function promptUsername() {
|
|
36
80
|
const { username } = await inquirer.prompt([
|
|
37
81
|
{
|
|
@@ -94,18 +138,11 @@ async function connectSSH(username, hostname, privateKeyPath, persistKnownHosts
|
|
|
94
138
|
|
|
95
139
|
const sshArgs = buildSshArgs(hostname, privateKeyPath, [
|
|
96
140
|
'-o', 'ServerAliveInterval=30',
|
|
97
|
-
'-o', 'ServerAliveCountMax=3'
|
|
141
|
+
'-o', 'ServerAliveCountMax=3',
|
|
142
|
+
'-t'
|
|
98
143
|
], { persistKnownHosts });
|
|
99
144
|
|
|
100
145
|
sshArgs.push(`${username}@${hostname}`);
|
|
101
|
-
const tmuxSession = buildTmuxSessionName(username);
|
|
102
|
-
const quotedSocket = quoteRemoteShell(TMUX_SOCKET_PATH);
|
|
103
|
-
const quotedSession = quoteRemoteShell(tmuxSession);
|
|
104
|
-
const tmuxSocketCmdStr = `tmux -S ${quotedSocket}`;
|
|
105
|
-
const tmuxPrepare = `${tmuxSocketCmdStr} has-session -t ${quotedSession} 2>/dev/null || ${tmuxSocketCmdStr} new-session -d -s ${quotedSession}`;
|
|
106
|
-
const tmuxAttach = `${tmuxSocketCmdStr} attach -t ${quotedSession}`;
|
|
107
|
-
const tmuxCommand = `if command -v tmux >/dev/null 2>&1; then (${tmuxPrepare} && ${tmuxAttach}) || exec $SHELL -l; else exec $SHELL -l; fi`;
|
|
108
|
-
sshArgs.push('-t', tmuxCommand);
|
|
109
146
|
|
|
110
147
|
const child = execa('ssh', sshArgs, {
|
|
111
148
|
stdio: 'inherit',
|
|
@@ -199,6 +236,7 @@ async function performSCP(username, hostname, direction, privateKeyPath, sharedD
|
|
|
199
236
|
// Construct SCP args
|
|
200
237
|
const scpArgs = [
|
|
201
238
|
'-r', // recursive just in case
|
|
239
|
+
'-O', // Force legacy SCP protocol so that shell quoting in formatScpRemotePath works correctly
|
|
202
240
|
...buildProxyCommandOption(hostname),
|
|
203
241
|
...getKnownHostsOptions(persistKnownHosts),
|
|
204
242
|
'-o', 'IdentitiesOnly=yes',
|
|
@@ -293,6 +331,7 @@ async function downloadSpecificRemotePath(username, hostname, privateKeyPath, re
|
|
|
293
331
|
await showConnectionTrace('Local', 'Remote SCP');
|
|
294
332
|
const scpArgs = [
|
|
295
333
|
'-r',
|
|
334
|
+
'-O', // Force legacy SCP protocol
|
|
296
335
|
...buildProxyCommandOption(hostname),
|
|
297
336
|
...getKnownHostsOptions(persistKnownHosts),
|
|
298
337
|
'-o', 'IdentitiesOnly=yes',
|
|
@@ -300,7 +339,10 @@ async function downloadSpecificRemotePath(username, hostname, privateKeyPath, re
|
|
|
300
339
|
];
|
|
301
340
|
if (privateKeyPath) scpArgs.push('-i', privateKeyPath, '-o', 'IdentityAgent=none');
|
|
302
341
|
scpArgs.push(`${username}@${hostname}:${formatScpRemotePath(remotePath)}`, localPath);
|
|
303
|
-
const
|
|
342
|
+
const child = execa('scp', scpArgs, { stdio: 'inherit', reject: false });
|
|
343
|
+
trackPID(child.pid);
|
|
344
|
+
const result = await child;
|
|
345
|
+
untrackPID(child.pid);
|
|
304
346
|
return result.exitCode === 0;
|
|
305
347
|
}
|
|
306
348
|
|
|
@@ -435,12 +477,27 @@ export async function startClientMode(options = {}) {
|
|
|
435
477
|
console.log(chalk.dim(' The host has enabled approval gating. Submitting your access request...'));
|
|
436
478
|
|
|
437
479
|
try {
|
|
480
|
+
let localIp = '127.0.0.1';
|
|
481
|
+
try {
|
|
482
|
+
const interfaces = os.networkInterfaces();
|
|
483
|
+
for (const devName in interfaces) {
|
|
484
|
+
const iface = interfaces[devName];
|
|
485
|
+
for (const alias of iface) {
|
|
486
|
+
if (alias.family === 'IPv4' && !alias.internal) {
|
|
487
|
+
localIp = alias.address;
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
} catch {}
|
|
493
|
+
|
|
438
494
|
const approvalDetails = {
|
|
439
495
|
username: os.userInfo().username,
|
|
440
496
|
hostname: os.hostname(),
|
|
441
497
|
os: `${os.type()} ${os.release()} (${os.arch()})`,
|
|
442
498
|
intent: 'connect',
|
|
443
499
|
time: new Date().toISOString(),
|
|
500
|
+
localIp,
|
|
444
501
|
};
|
|
445
502
|
|
|
446
503
|
const { requestId, status: reqStatus, approvalRequired } = await requestHostApproval(
|
|
@@ -457,9 +514,9 @@ export async function startClientMode(options = {}) {
|
|
|
457
514
|
console.log(chalk.dim(' This may take a few minutes. Press Ctrl+C to cancel.'));
|
|
458
515
|
console.log('');
|
|
459
516
|
|
|
460
|
-
const
|
|
517
|
+
const approvalResult = await waitForApproval(BROKER_URL, targetUid, requestId, 300000);
|
|
461
518
|
|
|
462
|
-
if (approved) {
|
|
519
|
+
if (approvalResult && approvalResult.approved) {
|
|
463
520
|
console.log(chalk.green(' ✅ Host approved your access request!'));
|
|
464
521
|
logSessionEvent('client_approval_granted', { uid: targetUid, requestId });
|
|
465
522
|
payload = await resolveUID(BROKER_URL, targetUid, targetPassword, false, requestId);
|
|
@@ -560,6 +617,11 @@ export async function startClientMode(options = {}) {
|
|
|
560
617
|
}
|
|
561
618
|
}
|
|
562
619
|
|
|
620
|
+
// Start background E2E client log sync if sharedDropPath is configured
|
|
621
|
+
if (payload.sharedDropPath && sessionLogPath) {
|
|
622
|
+
startLiveLogSync(username, hostname, privateKeyPath, payload.sharedDropPath, sessionLogPath, persistKnownHosts);
|
|
623
|
+
}
|
|
624
|
+
|
|
563
625
|
// ─── One-Time File Share Auto-Download ────────────────────
|
|
564
626
|
if (payload.oneTime && payload.oneTimeSharePath) {
|
|
565
627
|
console.log('');
|
|
@@ -684,7 +746,7 @@ export async function performSCPNonInteractive(params = {}) {
|
|
|
684
746
|
const privateKeyPath = payload.privateKey ? await writeEphemeralPrivateKey(payload.privateKey) : null;
|
|
685
747
|
|
|
686
748
|
// Build scp args similar to performSCP
|
|
687
|
-
const scpArgs = ['-r', ...buildProxyCommandOption(hostname), ...getKnownHostsOptions(persistKnownHosts), '-o', 'IdentitiesOnly=yes', ...getSshControlOptions(hostname)];
|
|
749
|
+
const scpArgs = ['-r', '-O', ...buildProxyCommandOption(hostname), ...getKnownHostsOptions(persistKnownHosts), '-o', 'IdentitiesOnly=yes', ...getSshControlOptions(hostname)];
|
|
688
750
|
if (privateKeyPath) scpArgs.push('-i', privateKeyPath, '-o', 'IdentityAgent=none');
|
|
689
751
|
|
|
690
752
|
const remoteSpec = `${username}@${hostname}:${formatScpRemotePath(remotePath)}`;
|
|
@@ -695,7 +757,10 @@ export async function performSCPNonInteractive(params = {}) {
|
|
|
695
757
|
}
|
|
696
758
|
|
|
697
759
|
try {
|
|
698
|
-
const
|
|
760
|
+
const child = execa('scp', scpArgs, { stdio: 'inherit', reject: false });
|
|
761
|
+
trackPID(child.pid);
|
|
762
|
+
const result = await child;
|
|
763
|
+
untrackPID(child.pid);
|
|
699
764
|
if (result.exitCode === 0) {
|
|
700
765
|
recordEvent('scp_transfer_success', { direction, localPath, remotePath, hostname, automated: true });
|
|
701
766
|
return true;
|
package/src/modes/doctor.js
CHANGED
|
@@ -7,8 +7,8 @@ import chalk from 'chalk';
|
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import os from 'node:os';
|
|
9
9
|
import path from 'node:path';
|
|
10
|
-
import { commandExists, detectOS } from '../lib/platform.js';
|
|
11
|
-
import { pingBroker } from '../lib/broker.js';
|
|
10
|
+
import { commandExists, detectOS, isLinuxSSHActive } from '../lib/services/platform.js';
|
|
11
|
+
import { pingBroker } from '../lib/client/broker.js';
|
|
12
12
|
import { classifyCommand, redactSensitive } from '../lib/ai/safety.js';
|
|
13
13
|
|
|
14
14
|
let BROKER_URL = process.env.BROKER_URL || 'https://ipingyou.onrender.com';
|
|
@@ -96,13 +96,12 @@ async function checkSshService() {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
if (osInfo.isLinux) {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
if (sshd.exitCode === 0) return { status: 'pass', detail: 'SSH service is active' };
|
|
99
|
+
const active = await isLinuxSSHActive();
|
|
100
|
+
if (active) return { status: 'pass', detail: 'SSH service is active' };
|
|
102
101
|
return {
|
|
103
102
|
status: 'warn',
|
|
104
103
|
detail: 'SSH service is not reported active',
|
|
105
|
-
hint: 'Run `sudo systemctl start ssh
|
|
104
|
+
hint: 'Run `sudo systemctl start ssh`, `sudo service ssh start` or equivalent before hosting.',
|
|
106
105
|
};
|
|
107
106
|
}
|
|
108
107
|
|
|
@@ -232,7 +231,6 @@ export async function startDoctorMode(options = {}) {
|
|
|
232
231
|
await check('scp client', () => commandFound('scp'));
|
|
233
232
|
await check('ssh-keygen', () => commandFound('ssh-keygen'));
|
|
234
233
|
await check('cloudflared', () => commandVersion('cloudflared', ['--version']));
|
|
235
|
-
await check('tmux', () => commandVersion('tmux', ['-V']));
|
|
236
234
|
|
|
237
235
|
console.log(chalk.bold('\n Host readiness'));
|
|
238
236
|
await check('SSH service', checkSshService);
|