itwillsync 0.1.0 → 1.0.1

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 ADDED
@@ -0,0 +1,127 @@
1
+ # itwillsync
2
+
3
+ Sync any terminal-based coding agent to your phone over local network. Open source, agent-agnostic, zero cloud.
4
+
5
+ ```
6
+ npx itwillsync -- claude
7
+ npx itwillsync -- aider
8
+ npx itwillsync -- bash
9
+ ```
10
+
11
+ ## How it works
12
+
13
+ 1. Run `itwillsync` with your agent command
14
+ 2. A QR code appears in your terminal
15
+ 3. Scan it on your phone — opens a terminal in your browser
16
+ 4. Control your agent from your phone (or both phone and laptop simultaneously)
17
+
18
+ All data stays on your local network. No cloud, no relay, no account needed.
19
+
20
+ ## Requirements
21
+
22
+ - Node.js 20+
23
+ - Any terminal-based coding agent (Claude Code, Aider, Goose, Codex, or just `bash`)
24
+
25
+ ## Install & Use
26
+
27
+ ```bash
28
+ # Run directly (no install needed)
29
+ npx itwillsync -- claude
30
+
31
+ # Or install globally
32
+ npm install -g itwillsync
33
+ itwillsync -- aider --model gpt-4
34
+ ```
35
+
36
+ ## Options
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
74
+
75
+ - **No timeout**: Sessions live as long as the agent process runs. No TTL, no idle disconnect.
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.
80
+
81
+ ## Development
82
+
83
+ ```bash
84
+ # 1. Clone and enter the project
85
+ git clone https://github.com/your-username/itwillsync
86
+ cd itwillsync
87
+
88
+ # 2. Use Node 22 (required for node-pty native bindings)
89
+ nvm use # reads .nvmrc
90
+
91
+ # 3. Install dependencies
92
+ pnpm install
93
+
94
+ # 4. Build everything (web client first, then CLI)
95
+ pnpm build
96
+
97
+ # 5. Test it
98
+ node packages/cli/dist/index.js -- bash
99
+ ```
100
+
101
+ ### Project Structure
102
+
103
+ ```
104
+ packages/
105
+ ├── cli/ # Main npm package — PTY, server, auth, CLI
106
+ └── web-client/ # Browser terminal — xterm.js, mobile-friendly CSS
107
+ ```
108
+
109
+ ### Contributing
110
+
111
+ 1. Fork the repo
112
+ 2. Create a feature branch
113
+ 3. Make your changes
114
+ 4. Run `pnpm build` to verify
115
+ 5. Open a PR
116
+
117
+ ## Roadmap
118
+
119
+ - [ ] Chat-style input bar + quick action buttons on mobile
120
+ - [ ] Agent detection + structured view for Claude Code
121
+ - [ ] React Native mobile app
122
+ - [ ] VS Code extension adapter
123
+ - [ ] Agent Sync Protocol specification
124
+
125
+ ## License
126
+
127
+ MIT
package/dist/index.js CHANGED
@@ -3776,6 +3776,7 @@ function findAvailablePort(startPort) {
3776
3776
  import { createServer as createServer2 } from "http";
3777
3777
  import { readFile } from "fs/promises";
3778
3778
  import { join, extname } from "path";
3779
+ import { gzipSync } from "zlib";
3779
3780
 
3780
3781
  // ../../node_modules/.pnpm/ws@8.19.0/node_modules/ws/wrapper.mjs
3781
3782
  var import_stream = __toESM(require_stream(), 1);
@@ -3794,14 +3795,34 @@ var MIME_TYPES = {
3794
3795
  ".png": "image/png",
3795
3796
  ".ico": "image/x-icon"
3796
3797
  };
3797
- async function serveStaticFile(webClientPath, filePath, res) {
3798
+ var COMPRESSIBLE = /* @__PURE__ */ new Set([".html", ".js", ".css", ".json", ".svg"]);
3799
+ var gzipCache = /* @__PURE__ */ new Map();
3800
+ async function serveStaticFile(webClientPath, filePath, req, res) {
3798
3801
  const fullPath = join(webClientPath, filePath);
3799
3802
  const ext = extname(fullPath);
3800
3803
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
3801
3804
  try {
3802
- const content = await readFile(fullPath);
3803
- res.writeHead(200, { "Content-Type": contentType });
3804
- res.end(content);
3805
+ const raw = await readFile(fullPath);
3806
+ const acceptsGzip = (req.headers["accept-encoding"] || "").includes("gzip");
3807
+ if (acceptsGzip && COMPRESSIBLE.has(ext)) {
3808
+ let compressed = gzipCache.get(fullPath);
3809
+ if (!compressed) {
3810
+ compressed = gzipSync(raw);
3811
+ gzipCache.set(fullPath, compressed);
3812
+ }
3813
+ res.writeHead(200, {
3814
+ "Content-Type": contentType,
3815
+ "Content-Encoding": "gzip",
3816
+ "Content-Length": compressed.length
3817
+ });
3818
+ res.end(compressed);
3819
+ } else {
3820
+ res.writeHead(200, {
3821
+ "Content-Type": contentType,
3822
+ "Content-Length": raw.length
3823
+ });
3824
+ res.end(raw);
3825
+ }
3805
3826
  } catch {
3806
3827
  res.writeHead(404, { "Content-Type": "text/plain" });
3807
3828
  res.end("Not Found");
@@ -3820,7 +3841,7 @@ function createSyncServer(options) {
3820
3841
  if (pathname === "/") {
3821
3842
  pathname = "/index.html";
3822
3843
  }
3823
- await serveStaticFile(webClientPath, pathname, res);
3844
+ await serveStaticFile(webClientPath, pathname, req, res);
3824
3845
  });
3825
3846
  const wssServer = new import_websocket_server.default({ noServer: true });
3826
3847
  httpServer.on("upgrade", (req, socket, head) => {
@@ -3921,6 +3942,29 @@ function displayQR(url) {
3921
3942
  // src/index.ts
3922
3943
  import { fileURLToPath } from "url";
3923
3944
  import { join as join2, dirname } from "path";
3945
+ import { spawn as spawn2 } from "child_process";
3946
+ function preventSleep() {
3947
+ try {
3948
+ if (process.platform === "darwin") {
3949
+ const child = spawn2("caffeinate", ["-i", "-w", String(process.pid)], {
3950
+ stdio: "ignore",
3951
+ detached: true
3952
+ });
3953
+ child.unref();
3954
+ return child;
3955
+ } else if (process.platform === "linux") {
3956
+ return spawn2("systemd-inhibit", [
3957
+ "--what=idle",
3958
+ "--who=itwillsync",
3959
+ "--why=Terminal sync session active",
3960
+ "sleep",
3961
+ "infinity"
3962
+ ], { stdio: "ignore" });
3963
+ }
3964
+ } catch {
3965
+ }
3966
+ return null;
3967
+ }
3924
3968
  var DEFAULT_PORT = 3456;
3925
3969
  function parseArgs(argv) {
3926
3970
  const options = {
@@ -4010,9 +4054,11 @@ async function main() {
4010
4054
  Connect at: ${url}
4011
4055
  `);
4012
4056
  }
4057
+ const sleepGuard = preventSleep();
4013
4058
  console.log(` Server listening on ${host}:${port}`);
4014
4059
  console.log(` Running: ${options.command.join(" ")}`);
4015
4060
  console.log(` PID: ${ptyManager.pid}`);
4061
+ console.log(` Sleep prevention: ${sleepGuard ? "active" : "unavailable"}`);
4016
4062
  console.log("");
4017
4063
  if (process.stdin.isTTY) {
4018
4064
  process.stdin.setRawMode(true);
@@ -4036,6 +4082,7 @@ async function main() {
4036
4082
  if (process.stdin.isTTY) {
4037
4083
  process.stdin.setRawMode(false);
4038
4084
  }
4085
+ sleepGuard?.kill();
4039
4086
  server.close();
4040
4087
  ptyManager.kill();
4041
4088
  }