itwillsync 1.0.6 → 1.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.
- package/README.md +35 -75
- package/dist/index.js +253 -29
- package/dist/index.js.map +1 -1
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# itwillsync
|
|
2
2
|
|
|
3
|
-
Sync any terminal-based coding agent to your phone
|
|
3
|
+
Sync any terminal-based coding agent to your phone. Local network or Tailscale. Open source, agent-agnostic, zero cloud.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
6
|
npx itwillsync -- claude
|
|
@@ -15,8 +15,6 @@ npx itwillsync -- bash
|
|
|
15
15
|
3. Scan it on your phone — opens a terminal in your browser
|
|
16
16
|
4. Control your agent from your phone (or both phone and laptop simultaneously)
|
|
17
17
|
|
|
18
|
-
All data stays on your local network. No cloud, no relay, no account needed.
|
|
19
|
-
|
|
20
18
|
## Requirements
|
|
21
19
|
|
|
22
20
|
- Node.js 20+
|
|
@@ -33,94 +31,56 @@ npm install -g itwillsync
|
|
|
33
31
|
itwillsync -- aider --model gpt-4
|
|
34
32
|
```
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
```
|
|
39
|
-
--port <number> Port to listen on (default: 3456)
|
|
40
|
-
--localhost Bind to 127.0.0.1 only (no LAN access)
|
|
41
|
-
--no-qr Don't display QR code
|
|
42
|
-
-h, --help Show help
|
|
43
|
-
-v, --version Show version
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## Remote Access
|
|
47
|
-
|
|
48
|
-
By default, itwillsync is accessible on your local network (same WiFi). For remote access from anywhere:
|
|
49
|
-
|
|
50
|
-
- **Tailscale** (recommended): Install on both devices, access via Tailscale IP
|
|
51
|
-
- **WireGuard / VPN**: Any VPN that puts devices on the same network
|
|
52
|
-
- **SSH tunnel**: `ssh -L 3456:localhost:3456 your-machine`
|
|
53
|
-
|
|
54
|
-
## Security
|
|
55
|
-
|
|
56
|
-
- Each session generates a random 64-character token
|
|
57
|
-
- Token is embedded in the QR code URL
|
|
58
|
-
- All WebSocket connections require the token
|
|
59
|
-
- No data leaves your network
|
|
60
|
-
|
|
61
|
-
## Architecture
|
|
62
|
-
|
|
63
|
-
```
|
|
64
|
-
Your Machine Your Phone
|
|
65
|
-
┌─────────────────────┐ ┌──────────────┐
|
|
66
|
-
│ itwillsync │ WiFi/LAN │ Browser │
|
|
67
|
-
│ ├─ PTY (your agent) │◄────────────►│ xterm.js │
|
|
68
|
-
│ ├─ HTTP server │ WebSocket │ terminal │
|
|
69
|
-
│ └─ WS server │ └──────────────┘
|
|
70
|
-
└─────────────────────┘
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Session Behavior
|
|
34
|
+
On first run, a setup wizard asks how you want to connect — local WiFi or Tailscale. Your choice is saved for future sessions.
|
|
74
35
|
|
|
75
|
-
|
|
76
|
-
- **Multiple devices**: Connect from phone, tablet, and laptop simultaneously — all see the same terminal.
|
|
77
|
-
- **Reconnect**: If your phone disconnects (WiFi switch, screen lock), it auto-reconnects and catches up with recent output.
|
|
78
|
-
- **Keepalive**: WebSocket pings every 30s prevent routers from closing idle connections.
|
|
79
|
-
- **One session per instance**: Run multiple `itwillsync` instances on different ports for multiple agents.
|
|
36
|
+
## Connect from Anywhere with Tailscale
|
|
80
37
|
|
|
81
|
-
|
|
38
|
+
By default, your phone needs to be on the same WiFi. With [Tailscale](https://tailscale.com), you can connect from anywhere — coffee shop, cellular, different network.
|
|
82
39
|
|
|
83
40
|
```bash
|
|
84
|
-
#
|
|
85
|
-
|
|
86
|
-
cd itwillsync
|
|
87
|
-
|
|
88
|
-
# 2. Use Node 22 (required for node-pty native bindings)
|
|
89
|
-
nvm use # reads .nvmrc
|
|
41
|
+
# First time: the setup wizard will detect Tailscale automatically
|
|
42
|
+
itwillsync -- claude
|
|
90
43
|
|
|
91
|
-
#
|
|
92
|
-
|
|
44
|
+
# Or use Tailscale for a single session
|
|
45
|
+
itwillsync --tailscale -- claude
|
|
93
46
|
|
|
94
|
-
#
|
|
95
|
-
|
|
47
|
+
# Switch back to local WiFi for a session
|
|
48
|
+
itwillsync --local -- claude
|
|
96
49
|
|
|
97
|
-
#
|
|
98
|
-
|
|
50
|
+
# Re-run setup anytime
|
|
51
|
+
itwillsync setup
|
|
99
52
|
```
|
|
100
53
|
|
|
101
|
-
|
|
54
|
+
**Setup:** Install Tailscale on both your computer and phone. That's it — itwillsync detects it automatically.
|
|
55
|
+
|
|
56
|
+
## Options
|
|
102
57
|
|
|
103
58
|
```
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
59
|
+
Commands:
|
|
60
|
+
setup Run the setup wizard (change networking mode)
|
|
61
|
+
|
|
62
|
+
Options:
|
|
63
|
+
--port <number> Port to listen on (default: 3456)
|
|
64
|
+
--localhost Bind to 127.0.0.1 only (no LAN access)
|
|
65
|
+
--tailscale Use Tailscale for this session
|
|
66
|
+
--local Use local WiFi for this session
|
|
67
|
+
--no-qr Don't display QR code
|
|
68
|
+
-h, --help Show help
|
|
69
|
+
-v, --version Show version
|
|
107
70
|
```
|
|
108
71
|
|
|
109
|
-
|
|
72
|
+
## Security
|
|
110
73
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
5. Open a PR
|
|
74
|
+
- Each session generates a random 64-character token
|
|
75
|
+
- Token is embedded in the QR code URL
|
|
76
|
+
- All WebSocket connections require the token
|
|
77
|
+
- No data leaves your network (local mode) or your Tailscale tailnet
|
|
116
78
|
|
|
117
|
-
##
|
|
79
|
+
## Links
|
|
118
80
|
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
- [ ] VS Code extension adapter
|
|
123
|
-
- [ ] Agent Sync Protocol specification
|
|
81
|
+
- **Website**: https://shrijayan.github.io/itwillsync/
|
|
82
|
+
- **GitHub**: https://github.com/shrijayan/itwillsync
|
|
83
|
+
- **Demo**: https://youtu.be/Zc0Tb98CXh0
|
|
124
84
|
|
|
125
85
|
## License
|
|
126
86
|
|
package/dist/index.js
CHANGED
|
@@ -3759,6 +3759,70 @@ function validateToken(provided, expected) {
|
|
|
3759
3759
|
// src/network.ts
|
|
3760
3760
|
import { networkInterfaces } from "os";
|
|
3761
3761
|
import { createServer } from "net";
|
|
3762
|
+
|
|
3763
|
+
// src/tailscale.ts
|
|
3764
|
+
import { execFile } from "child_process";
|
|
3765
|
+
function execCommand(cmd, args) {
|
|
3766
|
+
return new Promise((resolve, reject) => {
|
|
3767
|
+
execFile(cmd, args, { timeout: 5e3 }, (error, stdout, stderr) => {
|
|
3768
|
+
if (error) {
|
|
3769
|
+
reject(error);
|
|
3770
|
+
} else {
|
|
3771
|
+
resolve({ stdout: stdout.trim(), stderr: stderr.trim() });
|
|
3772
|
+
}
|
|
3773
|
+
});
|
|
3774
|
+
});
|
|
3775
|
+
}
|
|
3776
|
+
function getTailscalePaths() {
|
|
3777
|
+
const paths = ["tailscale"];
|
|
3778
|
+
if (process.platform === "darwin") {
|
|
3779
|
+
paths.push("/Applications/Tailscale.app/Contents/MacOS/Tailscale");
|
|
3780
|
+
}
|
|
3781
|
+
return paths;
|
|
3782
|
+
}
|
|
3783
|
+
async function tryExec(args) {
|
|
3784
|
+
let lastError = null;
|
|
3785
|
+
for (const bin of getTailscalePaths()) {
|
|
3786
|
+
try {
|
|
3787
|
+
const result = await execCommand(bin, args);
|
|
3788
|
+
return { status: "success", ...result };
|
|
3789
|
+
} catch (err) {
|
|
3790
|
+
if (err.code === "ENOENT") {
|
|
3791
|
+
continue;
|
|
3792
|
+
}
|
|
3793
|
+
lastError = err;
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
if (lastError) {
|
|
3797
|
+
return { status: "error", error: lastError };
|
|
3798
|
+
}
|
|
3799
|
+
return { status: "not_found" };
|
|
3800
|
+
}
|
|
3801
|
+
async function getTailscaleStatus() {
|
|
3802
|
+
const result = await tryExec(["ip", "-4"]);
|
|
3803
|
+
if (result.status === "not_found") {
|
|
3804
|
+
return { installed: false, running: false, ip: null, hostname: null };
|
|
3805
|
+
}
|
|
3806
|
+
if (result.status === "error") {
|
|
3807
|
+
return { installed: true, running: false, ip: null, hostname: null };
|
|
3808
|
+
}
|
|
3809
|
+
const ip = result.stdout.split("\n")[0]?.trim();
|
|
3810
|
+
if (!ip || !/^100\./.test(ip)) {
|
|
3811
|
+
return { installed: true, running: false, ip: null, hostname: null };
|
|
3812
|
+
}
|
|
3813
|
+
let hostname = null;
|
|
3814
|
+
try {
|
|
3815
|
+
const statusResult = await tryExec(["status", "--json"]);
|
|
3816
|
+
if (statusResult.status === "success") {
|
|
3817
|
+
const json = JSON.parse(statusResult.stdout);
|
|
3818
|
+
hostname = json?.Self?.HostName ?? null;
|
|
3819
|
+
}
|
|
3820
|
+
} catch {
|
|
3821
|
+
}
|
|
3822
|
+
return { installed: true, running: true, ip, hostname };
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
// src/network.ts
|
|
3762
3826
|
function getLocalIP() {
|
|
3763
3827
|
const interfaces = networkInterfaces();
|
|
3764
3828
|
for (const addresses of Object.values(interfaces)) {
|
|
@@ -3771,6 +3835,20 @@ function getLocalIP() {
|
|
|
3771
3835
|
}
|
|
3772
3836
|
return "127.0.0.1";
|
|
3773
3837
|
}
|
|
3838
|
+
async function resolveSessionIP(mode, isLocalhost) {
|
|
3839
|
+
if (isLocalhost) return "127.0.0.1";
|
|
3840
|
+
if (mode === "tailscale") {
|
|
3841
|
+
const status = await getTailscaleStatus();
|
|
3842
|
+
if (!status.running || !status.ip) {
|
|
3843
|
+
console.warn(
|
|
3844
|
+
"\n \u26A0 Tailscale is not running. Falling back to local WiFi.\n"
|
|
3845
|
+
);
|
|
3846
|
+
return getLocalIP();
|
|
3847
|
+
}
|
|
3848
|
+
return status.ip;
|
|
3849
|
+
}
|
|
3850
|
+
return getLocalIP();
|
|
3851
|
+
}
|
|
3774
3852
|
function findAvailablePort(startPort) {
|
|
3775
3853
|
return new Promise((resolve, reject) => {
|
|
3776
3854
|
const server = createServer();
|
|
@@ -3959,41 +4037,126 @@ function displayQR(url) {
|
|
|
3959
4037
|
});
|
|
3960
4038
|
}
|
|
3961
4039
|
|
|
3962
|
-
// src/
|
|
3963
|
-
import {
|
|
3964
|
-
import {
|
|
3965
|
-
import {
|
|
3966
|
-
|
|
4040
|
+
// src/config.ts
|
|
4041
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
4042
|
+
import { homedir } from "os";
|
|
4043
|
+
import { join as join3 } from "path";
|
|
4044
|
+
var DEFAULT_CONFIG = {
|
|
4045
|
+
networkingMode: "local"
|
|
4046
|
+
};
|
|
4047
|
+
function getConfigDir() {
|
|
4048
|
+
return process.env.ITWILLSYNC_CONFIG_DIR || join3(homedir(), ".itwillsync");
|
|
4049
|
+
}
|
|
4050
|
+
function getConfigPath() {
|
|
4051
|
+
return join3(getConfigDir(), "config.json");
|
|
4052
|
+
}
|
|
4053
|
+
function configExists() {
|
|
4054
|
+
return existsSync(getConfigPath());
|
|
4055
|
+
}
|
|
4056
|
+
function loadConfig() {
|
|
3967
4057
|
try {
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
4058
|
+
const raw = readFileSync(getConfigPath(), "utf-8");
|
|
4059
|
+
const parsed = JSON.parse(raw);
|
|
4060
|
+
return { ...DEFAULT_CONFIG, ...parsed };
|
|
4061
|
+
} catch {
|
|
4062
|
+
return { ...DEFAULT_CONFIG };
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
function saveConfig(config) {
|
|
4066
|
+
const dir = getConfigDir();
|
|
4067
|
+
mkdirSync(dir, { recursive: true });
|
|
4068
|
+
writeFileSync(
|
|
4069
|
+
join3(dir, "config.json"),
|
|
4070
|
+
JSON.stringify(config, null, 2) + "\n",
|
|
4071
|
+
"utf-8"
|
|
4072
|
+
);
|
|
4073
|
+
}
|
|
4074
|
+
|
|
4075
|
+
// src/wizard.ts
|
|
4076
|
+
import * as p from "@clack/prompts";
|
|
4077
|
+
async function runSetupWizard() {
|
|
4078
|
+
p.intro("itwillsync setup");
|
|
4079
|
+
const networkingMode = await p.select({
|
|
4080
|
+
message: "How do you want to connect your phone?",
|
|
4081
|
+
options: [
|
|
4082
|
+
{
|
|
4083
|
+
value: "local",
|
|
4084
|
+
label: "Local Network",
|
|
4085
|
+
hint: "Phone and computer on the same WiFi"
|
|
4086
|
+
},
|
|
4087
|
+
{
|
|
4088
|
+
value: "tailscale",
|
|
4089
|
+
label: "Tailscale",
|
|
4090
|
+
hint: "Connect from anywhere via Tailscale VPN"
|
|
4091
|
+
}
|
|
4092
|
+
]
|
|
4093
|
+
});
|
|
4094
|
+
if (p.isCancel(networkingMode)) {
|
|
4095
|
+
p.cancel("Setup cancelled.");
|
|
4096
|
+
process.exit(0);
|
|
4097
|
+
}
|
|
4098
|
+
if (networkingMode === "tailscale") {
|
|
4099
|
+
const s = p.spinner();
|
|
4100
|
+
s.start("Checking Tailscale status...");
|
|
4101
|
+
const status = await getTailscaleStatus();
|
|
4102
|
+
s.stop("Tailscale check complete");
|
|
4103
|
+
if (!status.installed) {
|
|
4104
|
+
p.log.error("Tailscale is not installed.");
|
|
4105
|
+
const installHint = process.platform === "darwin" ? "brew install tailscale or https://tailscale.com/download" : process.platform === "win32" ? "https://tailscale.com/download/windows" : "curl -fsSL https://tailscale.com/install.sh | sh";
|
|
4106
|
+
p.log.info(`Install: ${installHint}`);
|
|
4107
|
+
const saveAnyway = await p.confirm({
|
|
4108
|
+
message: "Save Tailscale as your default anyway? (You can install it later)",
|
|
4109
|
+
initialValue: false
|
|
3972
4110
|
});
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
4111
|
+
if (p.isCancel(saveAnyway) || !saveAnyway) {
|
|
4112
|
+
const config2 = { networkingMode: "local" };
|
|
4113
|
+
saveConfig(config2);
|
|
4114
|
+
p.outro("Saved as Local Network. Run 'itwillsync setup' to change later.");
|
|
4115
|
+
return config2;
|
|
4116
|
+
}
|
|
4117
|
+
} else if (!status.running) {
|
|
4118
|
+
p.log.warn("Tailscale is installed but not connected.");
|
|
4119
|
+
p.log.info("Run 'tailscale up' or start the Tailscale app to connect.");
|
|
4120
|
+
const saveAnyway = await p.confirm({
|
|
4121
|
+
message: "Save Tailscale as your default anyway?",
|
|
4122
|
+
initialValue: true
|
|
4123
|
+
});
|
|
4124
|
+
if (p.isCancel(saveAnyway) || !saveAnyway) {
|
|
4125
|
+
const config2 = { networkingMode: "local" };
|
|
4126
|
+
saveConfig(config2);
|
|
4127
|
+
p.outro("Saved as Local Network. Run 'itwillsync setup' to change later.");
|
|
4128
|
+
return config2;
|
|
4129
|
+
}
|
|
4130
|
+
} else {
|
|
4131
|
+
p.log.success(
|
|
4132
|
+
`Tailscale detected! IP: ${status.ip}${status.hostname ? ` (${status.hostname})` : ""}`
|
|
4133
|
+
);
|
|
3983
4134
|
}
|
|
3984
|
-
} catch {
|
|
3985
4135
|
}
|
|
3986
|
-
|
|
4136
|
+
const config = { networkingMode };
|
|
4137
|
+
saveConfig(config);
|
|
4138
|
+
const modeLabel = networkingMode === "tailscale" ? "Tailscale" : "Local Network";
|
|
4139
|
+
p.outro(`Saved! Your phone will connect via ${modeLabel}.`);
|
|
4140
|
+
return config;
|
|
3987
4141
|
}
|
|
4142
|
+
|
|
4143
|
+
// src/cli-options.ts
|
|
3988
4144
|
var DEFAULT_PORT = 3456;
|
|
3989
4145
|
function parseArgs(argv) {
|
|
3990
4146
|
const options = {
|
|
3991
4147
|
port: DEFAULT_PORT,
|
|
3992
4148
|
localhost: false,
|
|
3993
4149
|
noQr: false,
|
|
3994
|
-
command: []
|
|
4150
|
+
command: [],
|
|
4151
|
+
subcommand: null,
|
|
4152
|
+
tailscale: false,
|
|
4153
|
+
local: false
|
|
3995
4154
|
};
|
|
3996
4155
|
const args = argv.slice(2);
|
|
4156
|
+
if (args.length > 0 && args[0] === "setup") {
|
|
4157
|
+
options.subcommand = "setup";
|
|
4158
|
+
return options;
|
|
4159
|
+
}
|
|
3997
4160
|
let i = 0;
|
|
3998
4161
|
while (i < args.length) {
|
|
3999
4162
|
const arg = args[i];
|
|
@@ -4006,6 +4169,12 @@ function parseArgs(argv) {
|
|
|
4006
4169
|
} else if (arg === "--localhost") {
|
|
4007
4170
|
options.localhost = true;
|
|
4008
4171
|
i++;
|
|
4172
|
+
} else if (arg === "--tailscale") {
|
|
4173
|
+
options.tailscale = true;
|
|
4174
|
+
i++;
|
|
4175
|
+
} else if (arg === "--local") {
|
|
4176
|
+
options.local = true;
|
|
4177
|
+
i++;
|
|
4009
4178
|
} else if (arg === "--no-qr") {
|
|
4010
4179
|
options.noQr = true;
|
|
4011
4180
|
i++;
|
|
@@ -4029,36 +4198,91 @@ itwillsync \u2014 Sync any terminal agent to your phone
|
|
|
4029
4198
|
Usage:
|
|
4030
4199
|
itwillsync [options] -- <command> [args...]
|
|
4031
4200
|
itwillsync [options] <command> [args...]
|
|
4201
|
+
itwillsync setup
|
|
4032
4202
|
|
|
4033
4203
|
Examples:
|
|
4034
4204
|
itwillsync -- claude
|
|
4035
4205
|
itwillsync -- aider --model gpt-4
|
|
4036
4206
|
itwillsync bash
|
|
4037
4207
|
itwillsync --port 8080 -- claude
|
|
4208
|
+
itwillsync --tailscale -- claude
|
|
4209
|
+
itwillsync setup
|
|
4210
|
+
|
|
4211
|
+
Commands:
|
|
4212
|
+
setup Run the setup wizard (configure networking mode)
|
|
4038
4213
|
|
|
4039
4214
|
Options:
|
|
4040
|
-
--port <number>
|
|
4041
|
-
--localhost
|
|
4042
|
-
--
|
|
4043
|
-
|
|
4044
|
-
-
|
|
4215
|
+
--port <number> Port to listen on (default: ${DEFAULT_PORT})
|
|
4216
|
+
--localhost Bind to 127.0.0.1 only (no LAN access)
|
|
4217
|
+
--tailscale Use Tailscale IP for this session
|
|
4218
|
+
--local Use local network IP for this session
|
|
4219
|
+
--no-qr Don't display QR code
|
|
4220
|
+
-h, --help Show this help
|
|
4221
|
+
-v, --version Show version
|
|
4045
4222
|
`);
|
|
4046
4223
|
}
|
|
4224
|
+
|
|
4225
|
+
// src/index.ts
|
|
4226
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4227
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
4228
|
+
import { spawn as spawn2 } from "child_process";
|
|
4229
|
+
function preventSleep() {
|
|
4230
|
+
try {
|
|
4231
|
+
if (process.platform === "darwin") {
|
|
4232
|
+
const child = spawn2("caffeinate", ["-i", "-w", String(process.pid)], {
|
|
4233
|
+
stdio: "ignore",
|
|
4234
|
+
detached: true
|
|
4235
|
+
});
|
|
4236
|
+
child.unref();
|
|
4237
|
+
return child;
|
|
4238
|
+
} else if (process.platform === "linux") {
|
|
4239
|
+
return spawn2("systemd-inhibit", [
|
|
4240
|
+
"--what=idle",
|
|
4241
|
+
"--who=itwillsync",
|
|
4242
|
+
"--why=Terminal sync session active",
|
|
4243
|
+
"sleep",
|
|
4244
|
+
"infinity"
|
|
4245
|
+
], { stdio: "ignore" });
|
|
4246
|
+
}
|
|
4247
|
+
} catch {
|
|
4248
|
+
}
|
|
4249
|
+
return null;
|
|
4250
|
+
}
|
|
4047
4251
|
async function main() {
|
|
4048
4252
|
const options = parseArgs(process.argv);
|
|
4253
|
+
if (options.subcommand === "setup") {
|
|
4254
|
+
await runSetupWizard();
|
|
4255
|
+
return;
|
|
4256
|
+
}
|
|
4257
|
+
if (options.tailscale && options.local) {
|
|
4258
|
+
console.error("Error: Cannot use both --tailscale and --local.\n");
|
|
4259
|
+
process.exit(1);
|
|
4260
|
+
}
|
|
4261
|
+
if (!configExists() && !options.tailscale && !options.local && process.stdin.isTTY) {
|
|
4262
|
+
await runSetupWizard();
|
|
4263
|
+
}
|
|
4049
4264
|
if (options.command.length === 0) {
|
|
4050
4265
|
console.error("Error: No command specified.\n");
|
|
4051
4266
|
printHelp();
|
|
4052
4267
|
process.exit(1);
|
|
4053
4268
|
}
|
|
4269
|
+
let networkingMode = "local";
|
|
4270
|
+
if (options.tailscale) {
|
|
4271
|
+
networkingMode = "tailscale";
|
|
4272
|
+
} else if (options.local) {
|
|
4273
|
+
networkingMode = "local";
|
|
4274
|
+
} else {
|
|
4275
|
+
const config = loadConfig();
|
|
4276
|
+
networkingMode = config.networkingMode;
|
|
4277
|
+
}
|
|
4054
4278
|
const [cmd, ...cmdArgs] = options.command;
|
|
4055
4279
|
const token = generateToken();
|
|
4056
4280
|
const port = await findAvailablePort(options.port);
|
|
4057
4281
|
const host = options.localhost ? "127.0.0.1" : "0.0.0.0";
|
|
4058
|
-
const ip = options.localhost
|
|
4282
|
+
const ip = await resolveSessionIP(networkingMode, options.localhost);
|
|
4059
4283
|
const url = `http://${ip}:${port}?token=${token}`;
|
|
4060
4284
|
const __dirname = dirname2(fileURLToPath2(import.meta.url));
|
|
4061
|
-
const webClientPath =
|
|
4285
|
+
const webClientPath = join4(__dirname, "web-client");
|
|
4062
4286
|
const ptyManager = new PtyManager(cmd, cmdArgs);
|
|
4063
4287
|
const server = createSyncServer({
|
|
4064
4288
|
ptyManager,
|