devlinker 1.4.2__tar.gz → 1.4.3__tar.gz

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.
Files changed (33) hide show
  1. {devlinker-1.4.2 → devlinker-1.4.3}/MANIFEST.in +0 -1
  2. {devlinker-1.4.2/devlinker.egg-info → devlinker-1.4.3}/PKG-INFO +20 -3
  3. {devlinker-1.4.2 → devlinker-1.4.3}/README.md +19 -2
  4. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/main.py +7 -3
  5. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/proxy.py +8 -0
  6. devlinker-1.4.3/devlinker/share.py +64 -0
  7. {devlinker-1.4.2 → devlinker-1.4.3/devlinker.egg-info}/PKG-INFO +20 -3
  8. {devlinker-1.4.2 → devlinker-1.4.3}/pyproject.toml +1 -1
  9. devlinker-1.4.2/devlinker/share.py +0 -32
  10. {devlinker-1.4.2 → devlinker-1.4.3}/LICENSE +0 -0
  11. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/__init__.py +0 -0
  12. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/config.py +0 -0
  13. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/detection_state.py +0 -0
  14. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/detector.py +0 -0
  15. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/detector_ai.py +0 -0
  16. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/devlinker_loader_instant.html +0 -0
  17. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/devlinker_loader_snippet.html +0 -0
  18. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/doctor.py +0 -0
  19. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/fix.py +0 -0
  20. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/fixer.py +0 -0
  21. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/global_state.py +0 -0
  22. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/inspect.py +0 -0
  23. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/logger.py +0 -0
  24. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/monitor.py +0 -0
  25. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/runner.py +0 -0
  26. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/tunnel.py +0 -0
  27. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker.egg-info/SOURCES.txt +0 -0
  28. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker.egg-info/dependency_links.txt +0 -0
  29. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker.egg-info/entry_points.txt +0 -0
  30. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker.egg-info/requires.txt +0 -0
  31. {devlinker-1.4.2 → devlinker-1.4.3}/devlinker.egg-info/top_level.txt +0 -0
  32. {devlinker-1.4.2 → devlinker-1.4.3}/setup.cfg +0 -0
  33. {devlinker-1.4.2 → devlinker-1.4.3}/setup.py +0 -0
@@ -1,3 +1,2 @@
1
1
  include devlinker/devlinker_loader_instant.html
2
- include devlinker/devlinker_loader_minimal.html
3
2
  include devlinker/devlinker_loader_snippet.html
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devlinker
3
- Version: 1.4.2
3
+ Version: 1.4.3
4
4
  Summary: A lightweight proxy that combines your frontend and backend into one link for easy development and sharing.
5
5
  Author-email: Mani <mani1028@users.noreply.github.com>
6
6
  Requires-Python: >=3.7
@@ -130,6 +130,7 @@ If DevLinker helps you ship faster, consider supporting the project:
130
130
  - `devlinker support` — Show UPI support QR code in terminal
131
131
  - `devlinker --url` — Start with public tunnel (Cloudflare/ngrok)
132
132
  - `devlinker share` — Enable public tunnel at runtime (no restart)
133
+ - `devlinker share --proxy-port 18000` — Enable public tunnel for a custom proxy port
133
134
  - `devlinker unshare` — Disable public tunnel at runtime
134
135
  - `devlinker doctor` — Diagnose issues, see categorized problems and fixes
135
136
  - `devlinker fix` — Auto-fix common issues (env, API paths, config)
@@ -278,14 +279,16 @@ devlinker --docker
278
279
 
279
280
  ## Tunnel and Sharing Modes
280
281
 
281
- By default, DevLinker starts **fast local proxy only** (no tunnel). To enable a public tunnel, use the `--url` flag:
282
+ By default, DevLinker starts **fast local proxy only** (no tunnel). It prints a LAN URL when it can detect a local network interface, and you can share that link with devices on the same Wi-Fi/LAN.
283
+
284
+ For access from another network, start with a public tunnel using the `--url` flag:
282
285
 
283
286
 
284
287
  ```bash
285
288
  devlinker --url
286
289
  ```
287
290
 
288
- This will start the proxy and open a public tunnel (Cloudflare or ngrok). The output will show:
291
+ This starts the proxy and opens a public tunnel (Cloudflare or ngrok). The output will show:
289
292
 
290
293
  ```text
291
294
  🌍 Enabling public tunnel...
@@ -296,6 +299,20 @@ This will start the proxy and open a public tunnel (Cloudflare or ngrok). The ou
296
299
  ℹ Share this link with collaborators.
297
300
  ```
298
301
 
302
+ If you already started DevLinker and want to turn on sharing without restarting, use:
303
+
304
+ ```bash
305
+ devlinker share
306
+ ```
307
+
308
+ If you use a custom proxy port, pass it explicitly:
309
+
310
+ ```bash
311
+ devlinker share --proxy-port 18000
312
+ ```
313
+
314
+ If your friend is on the same Wi-Fi/LAN, use the printed LAN URL like `http://192.168.x.x:<proxy-port>`. If they are outside your network, use the public tunnel URL instead.
315
+
299
316
  To force tunnel off (even if --url is passed):
300
317
 
301
318
  ```bash
@@ -110,6 +110,7 @@ If DevLinker helps you ship faster, consider supporting the project:
110
110
  - `devlinker support` — Show UPI support QR code in terminal
111
111
  - `devlinker --url` — Start with public tunnel (Cloudflare/ngrok)
112
112
  - `devlinker share` — Enable public tunnel at runtime (no restart)
113
+ - `devlinker share --proxy-port 18000` — Enable public tunnel for a custom proxy port
113
114
  - `devlinker unshare` — Disable public tunnel at runtime
114
115
  - `devlinker doctor` — Diagnose issues, see categorized problems and fixes
115
116
  - `devlinker fix` — Auto-fix common issues (env, API paths, config)
@@ -258,14 +259,16 @@ devlinker --docker
258
259
 
259
260
  ## Tunnel and Sharing Modes
260
261
 
261
- By default, DevLinker starts **fast local proxy only** (no tunnel). To enable a public tunnel, use the `--url` flag:
262
+ By default, DevLinker starts **fast local proxy only** (no tunnel). It prints a LAN URL when it can detect a local network interface, and you can share that link with devices on the same Wi-Fi/LAN.
263
+
264
+ For access from another network, start with a public tunnel using the `--url` flag:
262
265
 
263
266
 
264
267
  ```bash
265
268
  devlinker --url
266
269
  ```
267
270
 
268
- This will start the proxy and open a public tunnel (Cloudflare or ngrok). The output will show:
271
+ This starts the proxy and opens a public tunnel (Cloudflare or ngrok). The output will show:
269
272
 
270
273
  ```text
271
274
  🌍 Enabling public tunnel...
@@ -276,6 +279,20 @@ This will start the proxy and open a public tunnel (Cloudflare or ngrok). The ou
276
279
  ℹ Share this link with collaborators.
277
280
  ```
278
281
 
282
+ If you already started DevLinker and want to turn on sharing without restarting, use:
283
+
284
+ ```bash
285
+ devlinker share
286
+ ```
287
+
288
+ If you use a custom proxy port, pass it explicitly:
289
+
290
+ ```bash
291
+ devlinker share --proxy-port 18000
292
+ ```
293
+
294
+ If your friend is on the same Wi-Fi/LAN, use the printed LAN URL like `http://192.168.x.x:<proxy-port>`. If they are outside your network, use the public tunnel URL instead.
295
+
279
296
  To force tunnel off (even if --url is passed):
280
297
 
281
298
  ```bash
@@ -33,7 +33,7 @@ except ImportError: # pragma: no cover - fallback when rich is unavailable
33
33
 
34
34
  from . import __version__
35
35
  from .detector import check_port, detect_ports, is_vite_port
36
- from .proxy import start_proxy
36
+ from .proxy import start_proxy, wait_for_proxy_startup
37
37
  from .runner import detect_backend_port, start_servers
38
38
  from .tunnel import start_tunnel
39
39
  from .doctor import doctor
@@ -43,6 +43,7 @@ from .share import share, unshare
43
43
  from .config import load_config
44
44
  from .inspect import inspect
45
45
  from .monitor import monitor
46
+ from .global_state import STATE
46
47
 
47
48
  SUPPORT_UPI_ID = "devlinker@upi"
48
49
  SUPPORT_UPI_LINK = "upi://pay?pa=devlinker@upi&pn=DevLinker&cu=INR&tn=Support%20DevLinker%20Project%20🚀"
@@ -602,6 +603,7 @@ def _run_proxy(
602
603
  )
603
604
 
604
605
  proxy_port = _select_proxy_port(proxy_port)
606
+ STATE["proxy_port"] = proxy_port
605
607
  _write_frontend_api_env(proxy_port)
606
608
 
607
609
  if not live_status:
@@ -619,8 +621,10 @@ def _run_proxy(
619
621
  enable_debug_logs=debug,
620
622
  )
621
623
 
622
- # Allow proxy thread to bind before opening tunnel.
623
- time.sleep(1)
624
+ if not wait_for_proxy_startup(timeout=5.0):
625
+ raise click.ClickException(
626
+ f"Proxy failed to start on port {proxy_port}. Check whether the port is already in use."
627
+ )
624
628
 
625
629
  if live_status:
626
630
  live_status.update("Proxy", f"✔ Active ({proxy_port})", style="green")
@@ -26,6 +26,7 @@ _printed_fixes = set()
26
26
  _printed_live_header = False
27
27
  LIVE_REQUEST_LOGGING_ENABLED = False
28
28
  MAX_RECENT_REQUESTS = 200
29
+ PROXY_READY_EVENT = threading.Event()
29
30
 
30
31
 
31
32
  def _format_request_context(path: str, method: str | None, status: int, target: str) -> str:
@@ -175,6 +176,7 @@ def _apply_cors_headers(headers: Dict[str, str], request: Request) -> Dict[str,
175
176
  async def _on_startup() -> None:
176
177
  global HTTP_CLIENT
177
178
  HTTP_CLIENT = httpx.AsyncClient(timeout=15.0, follow_redirects=False)
179
+ PROXY_READY_EVENT.set()
178
180
 
179
181
 
180
182
  @app.on_event("shutdown")
@@ -183,6 +185,7 @@ async def _on_shutdown() -> None:
183
185
  if HTTP_CLIENT is not None:
184
186
  await HTTP_CLIENT.aclose()
185
187
  HTTP_CLIENT = None
188
+ PROXY_READY_EVENT.clear()
186
189
 
187
190
 
188
191
  def _connection_header_tokens(headers: Dict[str, str]) -> set[str]:
@@ -720,9 +723,14 @@ def start_proxy(
720
723
  BACKEND = backend_port
721
724
  LIVE_REQUEST_LOGGING_ENABLED = enable_debug_logs
722
725
  _printed_live_header = False
726
+ PROXY_READY_EVENT.clear()
723
727
 
724
728
  thread = threading.Thread(
725
729
  target=lambda: uvicorn.run(app, host="0.0.0.0", port=proxy_port, log_level="warning"),
726
730
  daemon=True,
727
731
  )
728
732
  thread.start()
733
+
734
+
735
+ def wait_for_proxy_startup(timeout: float = 5.0) -> bool:
736
+ return PROXY_READY_EVENT.wait(timeout)
@@ -0,0 +1,64 @@
1
+ import click
2
+ import requests
3
+
4
+ from devlinker.global_state import STATE
5
+ from devlinker.tunnel import start_tunnel
6
+
7
+
8
+ _COMMON_PROXY_PORTS = (8000, 8001, 8002, 8003, 8004, 8005, 8006, 8007, 8008, 8009, 8010, 18000)
9
+
10
+
11
+ def _is_devlinker_proxy(port: int) -> bool:
12
+ try:
13
+ response = requests.get(f"http://127.0.0.1:{port}/__devlinker/dashboard", timeout=0.5)
14
+ except requests.RequestException:
15
+ return False
16
+ return response.status_code == 200 and "API Logs Dashboard" in response.text
17
+
18
+
19
+ def _resolve_proxy_port(requested_port: int | None) -> int:
20
+ if requested_port is not None:
21
+ if _is_devlinker_proxy(requested_port):
22
+ return requested_port
23
+ raise click.ClickException(
24
+ f"No DevLinker proxy is running on port {requested_port}. Start devlinker first, or pass the correct --proxy-port."
25
+ )
26
+
27
+ for candidate in _COMMON_PROXY_PORTS:
28
+ if _is_devlinker_proxy(candidate):
29
+ return candidate
30
+
31
+ raise click.ClickException(
32
+ "No running DevLinker proxy was found. Start devlinker first, or pass --proxy-port <port>."
33
+ )
34
+
35
+ @click.command()
36
+ @click.option("--proxy-port", type=int, default=None, help="Proxy port to tunnel. Auto-detect when omitted.")
37
+ def share(proxy_port: int | None):
38
+ """Enable public tunnel at runtime (no restart)."""
39
+ if STATE["tunnel"]:
40
+ click.secho("⚠️ Already shared", fg="yellow")
41
+ return
42
+ try:
43
+ resolved_proxy_port = _resolve_proxy_port(proxy_port)
44
+ STATE["proxy_port"] = resolved_proxy_port
45
+ provider, url = start_tunnel(resolved_proxy_port)
46
+ STATE["tunnel"] = url
47
+ click.secho("\n🌍 Public Sharing Enabled\n" + ("─" * 24), fg="green", bold=True)
48
+ click.secho("✔ Tunnel connected", fg="green")
49
+ click.secho(f"\nPublic URL:\n{url}\n", fg="cyan", bold=True)
50
+ click.secho("📤 Share this link with your team", fg="magenta")
51
+ except Exception as exc:
52
+ click.secho(f"[WARN] Tunnel failed: {exc}", fg="red")
53
+ click.secho("[INFO] Next step: install cloudflared or configure ngrok auth.", fg="yellow")
54
+
55
+ @click.command()
56
+ def unshare():
57
+ """Disable public tunnel at runtime (no restart)."""
58
+ if not STATE["tunnel"]:
59
+ click.secho("⚠️ No active tunnel", fg="yellow")
60
+ return
61
+ from devlinker.tunnel import stop_tunnel
62
+ stop_tunnel()
63
+ STATE["tunnel"] = None
64
+ click.secho("🛑 Sharing stopped", fg="red", bold=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devlinker
3
- Version: 1.4.2
3
+ Version: 1.4.3
4
4
  Summary: A lightweight proxy that combines your frontend and backend into one link for easy development and sharing.
5
5
  Author-email: Mani <mani1028@users.noreply.github.com>
6
6
  Requires-Python: >=3.7
@@ -130,6 +130,7 @@ If DevLinker helps you ship faster, consider supporting the project:
130
130
  - `devlinker support` — Show UPI support QR code in terminal
131
131
  - `devlinker --url` — Start with public tunnel (Cloudflare/ngrok)
132
132
  - `devlinker share` — Enable public tunnel at runtime (no restart)
133
+ - `devlinker share --proxy-port 18000` — Enable public tunnel for a custom proxy port
133
134
  - `devlinker unshare` — Disable public tunnel at runtime
134
135
  - `devlinker doctor` — Diagnose issues, see categorized problems and fixes
135
136
  - `devlinker fix` — Auto-fix common issues (env, API paths, config)
@@ -278,14 +279,16 @@ devlinker --docker
278
279
 
279
280
  ## Tunnel and Sharing Modes
280
281
 
281
- By default, DevLinker starts **fast local proxy only** (no tunnel). To enable a public tunnel, use the `--url` flag:
282
+ By default, DevLinker starts **fast local proxy only** (no tunnel). It prints a LAN URL when it can detect a local network interface, and you can share that link with devices on the same Wi-Fi/LAN.
283
+
284
+ For access from another network, start with a public tunnel using the `--url` flag:
282
285
 
283
286
 
284
287
  ```bash
285
288
  devlinker --url
286
289
  ```
287
290
 
288
- This will start the proxy and open a public tunnel (Cloudflare or ngrok). The output will show:
291
+ This starts the proxy and opens a public tunnel (Cloudflare or ngrok). The output will show:
289
292
 
290
293
  ```text
291
294
  🌍 Enabling public tunnel...
@@ -296,6 +299,20 @@ This will start the proxy and open a public tunnel (Cloudflare or ngrok). The ou
296
299
  ℹ Share this link with collaborators.
297
300
  ```
298
301
 
302
+ If you already started DevLinker and want to turn on sharing without restarting, use:
303
+
304
+ ```bash
305
+ devlinker share
306
+ ```
307
+
308
+ If you use a custom proxy port, pass it explicitly:
309
+
310
+ ```bash
311
+ devlinker share --proxy-port 18000
312
+ ```
313
+
314
+ If your friend is on the same Wi-Fi/LAN, use the printed LAN URL like `http://192.168.x.x:<proxy-port>`. If they are outside your network, use the public tunnel URL instead.
315
+
299
316
  To force tunnel off (even if --url is passed):
300
317
 
301
318
  ```bash
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devlinker"
7
- version = "1.4.2"
7
+ version = "1.4.3"
8
8
  description = "A lightweight proxy that combines your frontend and backend into one link for easy development and sharing."
9
9
  authors = [
10
10
  { name = "Mani", email = "mani1028@users.noreply.github.com" }
@@ -1,32 +0,0 @@
1
-
2
- import click
3
- from devlinker.global_state import STATE
4
- from devlinker.tunnel import start_tunnel
5
-
6
- @click.command()
7
- def share():
8
- """Enable public tunnel at runtime (no restart)."""
9
- if STATE["tunnel"]:
10
- click.secho("⚠️ Already shared", fg="yellow")
11
- return
12
- try:
13
- provider, url = start_tunnel(STATE["proxy_port"])
14
- STATE["tunnel"] = url
15
- click.secho("\n🌍 Public Sharing Enabled\n" + ("─" * 24), fg="green", bold=True)
16
- click.secho("✔ Tunnel connected", fg="green")
17
- click.secho(f"\nPublic URL:\n{url}\n", fg="cyan", bold=True)
18
- click.secho("📤 Share this link with your team", fg="magenta")
19
- except Exception as exc:
20
- click.secho(f"[WARN] Tunnel failed: {exc}", fg="red")
21
- click.secho("[INFO] Next step: install cloudflared or configure ngrok auth.", fg="yellow")
22
-
23
- @click.command()
24
- def unshare():
25
- """Disable public tunnel at runtime (no restart)."""
26
- if not STATE["tunnel"]:
27
- click.secho("⚠️ No active tunnel", fg="yellow")
28
- return
29
- from devlinker.tunnel import stop_tunnel
30
- stop_tunnel()
31
- STATE["tunnel"] = None
32
- click.secho("🛑 Sharing stopped", fg="red", bold=True)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes