devlinker 1.3.0__tar.gz → 1.3.1__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.
- {devlinker-1.3.0 → devlinker-1.3.1}/PKG-INFO +84 -14
- {devlinker-1.3.0 → devlinker-1.3.1}/README.md +83 -13
- devlinker-1.3.1/devlinker/detection_state.py +58 -0
- devlinker-1.3.1/devlinker/detector_ai.py +18 -0
- devlinker-1.3.1/devlinker/doctor.py +16 -0
- devlinker-1.3.1/devlinker/fix.py +16 -0
- devlinker-1.3.1/devlinker/fixer.py +27 -0
- devlinker-1.3.1/devlinker/logger.py +5 -0
- {devlinker-1.3.0 → devlinker-1.3.1}/devlinker/main.py +67 -36
- {devlinker-1.3.0 → devlinker-1.3.1}/devlinker/proxy.py +55 -3
- devlinker-1.3.1/devlinker/share.py +54 -0
- {devlinker-1.3.0 → devlinker-1.3.1}/devlinker.egg-info/PKG-INFO +84 -14
- {devlinker-1.3.0 → devlinker-1.3.1}/devlinker.egg-info/SOURCES.txt +7 -0
- {devlinker-1.3.0 → devlinker-1.3.1}/pyproject.toml +1 -1
- {devlinker-1.3.0 → devlinker-1.3.1}/devlinker/__init__.py +0 -0
- {devlinker-1.3.0 → devlinker-1.3.1}/devlinker/detector.py +0 -0
- {devlinker-1.3.0 → devlinker-1.3.1}/devlinker/runner.py +0 -0
- {devlinker-1.3.0 → devlinker-1.3.1}/devlinker/tunnel.py +0 -0
- {devlinker-1.3.0 → devlinker-1.3.1}/devlinker.egg-info/dependency_links.txt +0 -0
- {devlinker-1.3.0 → devlinker-1.3.1}/devlinker.egg-info/entry_points.txt +0 -0
- {devlinker-1.3.0 → devlinker-1.3.1}/devlinker.egg-info/requires.txt +0 -0
- {devlinker-1.3.0 → devlinker-1.3.1}/devlinker.egg-info/top_level.txt +0 -0
- {devlinker-1.3.0 → devlinker-1.3.1}/setup.cfg +0 -0
- {devlinker-1.3.0 → devlinker-1.3.1}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devlinker
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: AI-powered linking and automation tool
|
|
5
5
|
Author-email: Mani <mani1028@users.noreply.github.com>
|
|
6
6
|
Requires-Python: >=3.7
|
|
@@ -18,20 +18,36 @@ Requires-Dist: websockets
|
|
|
18
18
|
|
|
19
19
|
Dev Linker runs frontend and backend dev servers, proxies both through a single local port (8000), and creates a single public URL via Cloudflare or ngrok.
|
|
20
20
|
|
|
21
|
+
|
|
21
22
|
## Features
|
|
22
23
|
|
|
23
|
-
-
|
|
24
|
-
- Auto
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
|
|
24
|
+
- 🚀 **Unified Dev Proxy:** Combines frontend (Vite/React) and backend (FastAPI/Flask/Node/Docker) into a single local and public URL.
|
|
25
|
+
- 🔍 **Auto Detection:** Detects frontend/backend ports, runtime, Docker containers, and Vite servers automatically.
|
|
26
|
+
- 🧠 **Smart Detection & Doctor:** Real-time request analysis, backend intelligence, log analyzer, and `devlinker doctor` for instant diagnostics.
|
|
27
|
+
- 🛡️ **Auto-Fix Engine:** `devlinker fix` applies safe fixes (like VITE_API_URL) and suggests code changes.
|
|
28
|
+
- 🌍 **Public Sharing:** Share your local dev environment instantly with `--url` (startup) or `devlinker share` (runtime, no restart).
|
|
29
|
+
- 🔄 **Dynamic Tunnel Control:** `devlinker unshare` disables public tunnel at runtime.
|
|
30
|
+
- 📡 **WLAN Sharing:** Prints LAN URL for same-network device access.
|
|
31
|
+
- 🧑💻 **Interactive CLI:** Modern, colorized, emoji-rich terminal UX for all commands.
|
|
32
|
+
- 🧩 **Zero Config:** Works out-of-the-box for most FastAPI, Flask, Vite, and Docker projects.
|
|
33
|
+
- 🧪 **Runtime Smoke Test:** Built-in test for end-to-end proxy validation.
|
|
34
|
+
- 🛠️ **Extensible:** Modular architecture for future SaaS, dashboard, and team features.
|
|
35
|
+
|
|
36
|
+
## CLI Commands & Options
|
|
37
|
+
|
|
38
|
+
- `devlinker` — Start proxy (local only, fast)
|
|
39
|
+
- `devlinker --url` — Start with public tunnel (Cloudflare/ngrok)
|
|
40
|
+
- `devlinker share` — Enable public tunnel at runtime (no restart)
|
|
41
|
+
- `devlinker unshare` — Disable public tunnel at runtime
|
|
42
|
+
- `devlinker doctor` — Diagnose issues, see categorized problems and fixes
|
|
43
|
+
- `devlinker fix` — Auto-fix common issues (env, API paths, config)
|
|
44
|
+
- `devlinker --frontend 5173 --backend 5000` — Override detected ports
|
|
45
|
+
- `devlinker --docker` — Auto-start Docker backend
|
|
46
|
+
- `devlinker --no-tunnel` — Force local-only mode
|
|
47
|
+
- `devlinker --no-lan` — Hide WLAN sharing URL
|
|
48
|
+
- `devlinker --interactive-backend` — Prompt to choose backend if multiple found
|
|
49
|
+
- `devlinker --proxy-port 18000` — Use custom proxy port
|
|
50
|
+
- `devlinker --version` — Show version
|
|
35
51
|
|
|
36
52
|
## Project Structure
|
|
37
53
|
|
|
@@ -123,17 +139,71 @@ Enable Docker auto-start explicitly:
|
|
|
123
139
|
devlinker --docker
|
|
124
140
|
```
|
|
125
141
|
|
|
126
|
-
|
|
142
|
+
|
|
143
|
+
## Tunnel and Sharing Modes
|
|
144
|
+
|
|
145
|
+
By default, DevLinker starts **fast local proxy only** (no tunnel). To enable a public tunnel, use the `--url` flag:
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
devlinker --url
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
In your terminal output, you'll see:
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
URL, run:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
devlinker --url
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
This will start the proxy and open a public tunnel (Cloudflare or ngrok). The output will show:
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
🌍 Enabling public tunnel...
|
|
165
|
+
[OK] Tunnel provider: Cloudflare
|
|
166
|
+
[OK] Public URL:
|
|
167
|
+
https://xxxx.trycloudflare.com
|
|
168
|
+
Tip: Press Ctrl+Click to open link
|
|
169
|
+
[INFO] Share this link with collaborators.
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
To force tunnel off (even if --url is passed):
|
|
127
173
|
|
|
128
174
|
```bash
|
|
129
175
|
devlinker --no-tunnel
|
|
130
176
|
```
|
|
131
177
|
|
|
178
|
+
When running without `--url`, you’ll see:
|
|
179
|
+
|
|
180
|
+
```text
|
|
181
|
+
⚡ Skipping public tunnel (use --url to enable)
|
|
182
|
+
|
|
183
|
+
💡 Need to share outside network?
|
|
184
|
+
👉 Run: devlinker --url
|
|
185
|
+
```
|
|
186
|
+
|
|
132
187
|
Disable WLAN URL output:
|
|
133
188
|
|
|
134
189
|
```bash
|
|
135
190
|
devlinker --no-lan
|
|
136
191
|
```
|
|
192
|
+
## Smart Detection & Auto-Fix System
|
|
193
|
+
|
|
194
|
+
DevLinker now includes an AI-powered detection and auto-fix engine:
|
|
195
|
+
|
|
196
|
+
- **Request Inspector:** Real-time analysis of proxy traffic for common mistakes (missing `/api` prefix, 404s, CORS risks, upstream failures)
|
|
197
|
+
- **Backend Intelligence:** Probes backend endpoints and type at startup for smarter routing and hints
|
|
198
|
+
- **Log Analyzer:** Converts error messages (CORS, 404, connection refused) into human-readable explanations and actionable fixes
|
|
199
|
+
- **Smart Warning Engine:** Prints clean CLI warnings and suggestions, e.g.:
|
|
200
|
+
|
|
201
|
+
```text
|
|
202
|
+
⚠️ Detected direct backend call (localhost:5000)
|
|
203
|
+
👉 Use /api/* instead of direct URL
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
All detection and fixes are modular, async-compatible, and production-ready. See `devlinker/proxy.py`, `devlinker/detector_ai.py`, and `devlinker/logger.py` for implementation.
|
|
137
207
|
|
|
138
208
|
Interactive backend selection (when local and Docker are both detected):
|
|
139
209
|
|
|
@@ -2,20 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
Dev Linker runs frontend and backend dev servers, proxies both through a single local port (8000), and creates a single public URL via Cloudflare or ngrok.
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
## Features
|
|
6
7
|
|
|
7
|
-
-
|
|
8
|
-
- Auto
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
|
|
8
|
+
- 🚀 **Unified Dev Proxy:** Combines frontend (Vite/React) and backend (FastAPI/Flask/Node/Docker) into a single local and public URL.
|
|
9
|
+
- 🔍 **Auto Detection:** Detects frontend/backend ports, runtime, Docker containers, and Vite servers automatically.
|
|
10
|
+
- 🧠 **Smart Detection & Doctor:** Real-time request analysis, backend intelligence, log analyzer, and `devlinker doctor` for instant diagnostics.
|
|
11
|
+
- 🛡️ **Auto-Fix Engine:** `devlinker fix` applies safe fixes (like VITE_API_URL) and suggests code changes.
|
|
12
|
+
- 🌍 **Public Sharing:** Share your local dev environment instantly with `--url` (startup) or `devlinker share` (runtime, no restart).
|
|
13
|
+
- 🔄 **Dynamic Tunnel Control:** `devlinker unshare` disables public tunnel at runtime.
|
|
14
|
+
- 📡 **WLAN Sharing:** Prints LAN URL for same-network device access.
|
|
15
|
+
- 🧑💻 **Interactive CLI:** Modern, colorized, emoji-rich terminal UX for all commands.
|
|
16
|
+
- 🧩 **Zero Config:** Works out-of-the-box for most FastAPI, Flask, Vite, and Docker projects.
|
|
17
|
+
- 🧪 **Runtime Smoke Test:** Built-in test for end-to-end proxy validation.
|
|
18
|
+
- 🛠️ **Extensible:** Modular architecture for future SaaS, dashboard, and team features.
|
|
19
|
+
|
|
20
|
+
## CLI Commands & Options
|
|
21
|
+
|
|
22
|
+
- `devlinker` — Start proxy (local only, fast)
|
|
23
|
+
- `devlinker --url` — Start with public tunnel (Cloudflare/ngrok)
|
|
24
|
+
- `devlinker share` — Enable public tunnel at runtime (no restart)
|
|
25
|
+
- `devlinker unshare` — Disable public tunnel at runtime
|
|
26
|
+
- `devlinker doctor` — Diagnose issues, see categorized problems and fixes
|
|
27
|
+
- `devlinker fix` — Auto-fix common issues (env, API paths, config)
|
|
28
|
+
- `devlinker --frontend 5173 --backend 5000` — Override detected ports
|
|
29
|
+
- `devlinker --docker` — Auto-start Docker backend
|
|
30
|
+
- `devlinker --no-tunnel` — Force local-only mode
|
|
31
|
+
- `devlinker --no-lan` — Hide WLAN sharing URL
|
|
32
|
+
- `devlinker --interactive-backend` — Prompt to choose backend if multiple found
|
|
33
|
+
- `devlinker --proxy-port 18000` — Use custom proxy port
|
|
34
|
+
- `devlinker --version` — Show version
|
|
19
35
|
|
|
20
36
|
## Project Structure
|
|
21
37
|
|
|
@@ -107,17 +123,71 @@ Enable Docker auto-start explicitly:
|
|
|
107
123
|
devlinker --docker
|
|
108
124
|
```
|
|
109
125
|
|
|
110
|
-
|
|
126
|
+
|
|
127
|
+
## Tunnel and Sharing Modes
|
|
128
|
+
|
|
129
|
+
By default, DevLinker starts **fast local proxy only** (no tunnel). To enable a public tunnel, use the `--url` flag:
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
devlinker --url
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
In your terminal output, you'll see:
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
URL, run:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
devlinker --url
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
This will start the proxy and open a public tunnel (Cloudflare or ngrok). The output will show:
|
|
146
|
+
|
|
147
|
+
```text
|
|
148
|
+
🌍 Enabling public tunnel...
|
|
149
|
+
[OK] Tunnel provider: Cloudflare
|
|
150
|
+
[OK] Public URL:
|
|
151
|
+
https://xxxx.trycloudflare.com
|
|
152
|
+
Tip: Press Ctrl+Click to open link
|
|
153
|
+
[INFO] Share this link with collaborators.
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
To force tunnel off (even if --url is passed):
|
|
111
157
|
|
|
112
158
|
```bash
|
|
113
159
|
devlinker --no-tunnel
|
|
114
160
|
```
|
|
115
161
|
|
|
162
|
+
When running without `--url`, you’ll see:
|
|
163
|
+
|
|
164
|
+
```text
|
|
165
|
+
⚡ Skipping public tunnel (use --url to enable)
|
|
166
|
+
|
|
167
|
+
💡 Need to share outside network?
|
|
168
|
+
👉 Run: devlinker --url
|
|
169
|
+
```
|
|
170
|
+
|
|
116
171
|
Disable WLAN URL output:
|
|
117
172
|
|
|
118
173
|
```bash
|
|
119
174
|
devlinker --no-lan
|
|
120
175
|
```
|
|
176
|
+
## Smart Detection & Auto-Fix System
|
|
177
|
+
|
|
178
|
+
DevLinker now includes an AI-powered detection and auto-fix engine:
|
|
179
|
+
|
|
180
|
+
- **Request Inspector:** Real-time analysis of proxy traffic for common mistakes (missing `/api` prefix, 404s, CORS risks, upstream failures)
|
|
181
|
+
- **Backend Intelligence:** Probes backend endpoints and type at startup for smarter routing and hints
|
|
182
|
+
- **Log Analyzer:** Converts error messages (CORS, 404, connection refused) into human-readable explanations and actionable fixes
|
|
183
|
+
- **Smart Warning Engine:** Prints clean CLI warnings and suggestions, e.g.:
|
|
184
|
+
|
|
185
|
+
```text
|
|
186
|
+
⚠️ Detected direct backend call (localhost:5000)
|
|
187
|
+
👉 Use /api/* instead of direct URL
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
All detection and fixes are modular, async-compatible, and production-ready. See `devlinker/proxy.py`, `devlinker/detector_ai.py`, and `devlinker/logger.py` for implementation.
|
|
121
191
|
|
|
122
192
|
Interactive backend selection (when local and Docker are both detected):
|
|
123
193
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
|
|
2
|
+
class DetectionState:
|
|
3
|
+
def __init__(self):
|
|
4
|
+
self.issues = []
|
|
5
|
+
self.counts = {}
|
|
6
|
+
self.levels = {}
|
|
7
|
+
self.categories = {}
|
|
8
|
+
|
|
9
|
+
def add(self, issue, level="MEDIUM", category="general"):
|
|
10
|
+
self.issues.append(issue)
|
|
11
|
+
self.counts[issue] = self.counts.get(issue, 0) + 1
|
|
12
|
+
self.levels[issue] = level
|
|
13
|
+
self.categories.setdefault(category, []).append(issue)
|
|
14
|
+
|
|
15
|
+
def should_print(self, issue):
|
|
16
|
+
return self.counts.get(issue, 0) == 1
|
|
17
|
+
|
|
18
|
+
def get_issues(self):
|
|
19
|
+
return [(issue, self.levels.get(issue, "MEDIUM"), self.counts[issue], self._get_category(issue)) for issue in self.issues]
|
|
20
|
+
|
|
21
|
+
def summary(self):
|
|
22
|
+
summary = {}
|
|
23
|
+
for issue in self.issues:
|
|
24
|
+
level = self.levels.get(issue, "MEDIUM")
|
|
25
|
+
summary.setdefault(level, set()).add(issue)
|
|
26
|
+
return summary
|
|
27
|
+
|
|
28
|
+
def _get_category(self, issue):
|
|
29
|
+
for cat, issues in self.categories.items():
|
|
30
|
+
if issue in issues:
|
|
31
|
+
return cat
|
|
32
|
+
return "general"
|
|
33
|
+
|
|
34
|
+
def report(self):
|
|
35
|
+
print("\n🩺 DevLinker Doctor Report\n────────────────────────")
|
|
36
|
+
# Group by category
|
|
37
|
+
for category, issues in self.categories.items():
|
|
38
|
+
if not issues:
|
|
39
|
+
continue
|
|
40
|
+
if category == "network":
|
|
41
|
+
print("\n🌐 Network Issues")
|
|
42
|
+
elif category == "routing":
|
|
43
|
+
print("\n🔀 Routing Issues")
|
|
44
|
+
elif category == "cors":
|
|
45
|
+
print("\n🔐 CORS Issues")
|
|
46
|
+
else:
|
|
47
|
+
print(f"\n{category.title()} Issues")
|
|
48
|
+
for issue in set(issues):
|
|
49
|
+
level = self.levels.get(issue, "MEDIUM")
|
|
50
|
+
if level == "HIGH":
|
|
51
|
+
print(f"❌ {issue}")
|
|
52
|
+
elif level == "MEDIUM":
|
|
53
|
+
print(f"⚠️ {issue}")
|
|
54
|
+
else:
|
|
55
|
+
print(f"💡 {issue}")
|
|
56
|
+
|
|
57
|
+
# Singleton instance
|
|
58
|
+
state = DetectionState()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class DevLinkerAI:
|
|
2
|
+
def analyze_failure(self, error_text):
|
|
3
|
+
if "CORS" in error_text:
|
|
4
|
+
return [
|
|
5
|
+
"Frontend is calling backend directly",
|
|
6
|
+
"Use /api/* instead of localhost:PORT"
|
|
7
|
+
]
|
|
8
|
+
if "404" in error_text:
|
|
9
|
+
return [
|
|
10
|
+
"Route not found",
|
|
11
|
+
"Check if '/api' prefix is required"
|
|
12
|
+
]
|
|
13
|
+
if "connection refused" in error_text.lower():
|
|
14
|
+
return [
|
|
15
|
+
"Backend not reachable",
|
|
16
|
+
"Ensure backend is running"
|
|
17
|
+
]
|
|
18
|
+
return []
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from devlinker.detection_state import state
|
|
3
|
+
from devlinker.detector_ai import DevLinkerAI
|
|
4
|
+
from devlinker.logger import print_fix
|
|
5
|
+
|
|
6
|
+
@click.command()
|
|
7
|
+
def doctor():
|
|
8
|
+
"""Run DevLinker diagnostics and print a doctor report."""
|
|
9
|
+
ai = DevLinkerAI()
|
|
10
|
+
# Doctor now uses real-time, categorized issues from the global state
|
|
11
|
+
state.report()
|
|
12
|
+
print("\nFix Suggestions:")
|
|
13
|
+
for issue, level, count, category in state.get_issues():
|
|
14
|
+
suggestions = ai.analyze_failure(issue)
|
|
15
|
+
for s in suggestions:
|
|
16
|
+
print_fix(s)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from devlinker.detection_state import state
|
|
3
|
+
from devlinker.fixer import DevLinkerFixer
|
|
4
|
+
|
|
5
|
+
@click.command()
|
|
6
|
+
def fix():
|
|
7
|
+
"""Apply auto-fixes for detected issues."""
|
|
8
|
+
issues = state.get_issues()
|
|
9
|
+
fixer = DevLinkerFixer()
|
|
10
|
+
print("\n🔧 Applying fixes...")
|
|
11
|
+
results = fixer.apply_fixes(issues)
|
|
12
|
+
print("\n🔧 Fix Results")
|
|
13
|
+
for r in results:
|
|
14
|
+
print(f"✔ {r}")
|
|
15
|
+
if not results:
|
|
16
|
+
print("No auto-fixes applied. All clear or manual review needed.")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
class DevLinkerFixer:
|
|
4
|
+
def apply_fixes(self, issues):
|
|
5
|
+
fixes = []
|
|
6
|
+
for issue in issues:
|
|
7
|
+
desc = issue[0] if isinstance(issue, (list, tuple)) else issue.get("issue", "")
|
|
8
|
+
if "CORS" in desc:
|
|
9
|
+
fixes.append(self.fix_env())
|
|
10
|
+
if "Missing /api" in desc or "missing '/api'" in desc:
|
|
11
|
+
fixes.append(self.suggest_api_fix())
|
|
12
|
+
return fixes
|
|
13
|
+
|
|
14
|
+
def fix_env(self):
|
|
15
|
+
env_path = os.path.join("frontend", ".env")
|
|
16
|
+
line = "VITE_API_URL=http://localhost:8001"
|
|
17
|
+
# Only add if not already present
|
|
18
|
+
if os.path.exists(env_path):
|
|
19
|
+
with open(env_path, "r") as f:
|
|
20
|
+
if line in f.read():
|
|
21
|
+
return "VITE_API_URL already set in frontend/.env"
|
|
22
|
+
with open(env_path, "a") as f:
|
|
23
|
+
f.write(f"\n{line}\n")
|
|
24
|
+
return "Added VITE_API_URL to frontend/.env"
|
|
25
|
+
|
|
26
|
+
def suggest_api_fix(self):
|
|
27
|
+
return "Suggest: Replace hardcoded http://localhost:8000 with /api in frontend code (manual review)"
|
|
@@ -11,6 +11,9 @@ from .detector import check_port, detect_ports, is_vite_port
|
|
|
11
11
|
from .proxy import start_proxy
|
|
12
12
|
from .runner import detect_backend_port, start_servers
|
|
13
13
|
from .tunnel import start_tunnel
|
|
14
|
+
from .doctor import doctor
|
|
15
|
+
from .fix import fix
|
|
16
|
+
from .share import share, unshare
|
|
14
17
|
|
|
15
18
|
|
|
16
19
|
def _is_port_in_use(port: int) -> bool:
|
|
@@ -50,6 +53,7 @@ def _with_ngrok_skip_warning(url: str) -> str:
|
|
|
50
53
|
return urlunsplit((parts.scheme, parts.netloc, parts.path, new_query, parts.fragment))
|
|
51
54
|
|
|
52
55
|
|
|
56
|
+
|
|
53
57
|
def _print_summary(
|
|
54
58
|
frontend_port: int,
|
|
55
59
|
backend_port: int,
|
|
@@ -58,20 +62,23 @@ def _print_summary(
|
|
|
58
62
|
wlan_url: str | None,
|
|
59
63
|
startup_seconds: float,
|
|
60
64
|
) -> None:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
import click
|
|
66
|
+
banner = "\n" + ("═" * 36) + "\n🚀 DevLinker Ready\n" + ("═" * 36)
|
|
67
|
+
click.secho(f"{banner}", fg="green", bold=True)
|
|
68
|
+
click.secho(f"⏱️ Startup time: {startup_seconds:.1f}s\n", fg="yellow")
|
|
69
|
+
click.secho(f"Frontend: ", nl=False, fg="blue"); click.secho(f"http://localhost:{frontend_port}", fg="cyan", bold=True)
|
|
70
|
+
click.secho(f"Backend: ", nl=False, fg="blue"); click.secho(f"http://localhost:{backend_port}", fg="cyan", bold=True)
|
|
71
|
+
click.secho("\nAccess Links:", fg="magenta", bold=True)
|
|
72
|
+
click.secho(f" Local → http://localhost:{proxy_port}", fg="white")
|
|
66
73
|
if wlan_url:
|
|
67
|
-
|
|
74
|
+
click.secho(f" WLAN → {wlan_url}", fg="white")
|
|
68
75
|
else:
|
|
69
|
-
|
|
76
|
+
click.secho(" WLAN → unavailable", fg="white")
|
|
70
77
|
if public_url:
|
|
71
|
-
|
|
72
|
-
|
|
78
|
+
click.secho(f" Public → {public_url}", fg="cyan", bold=True)
|
|
79
|
+
click.secho("Tip: Press Ctrl+Click to open link", fg="magenta")
|
|
73
80
|
else:
|
|
74
|
-
|
|
81
|
+
click.secho(" Public → Disabled (use --url)", fg="yellow")
|
|
75
82
|
|
|
76
83
|
|
|
77
84
|
def _get_local_ip() -> str | None:
|
|
@@ -124,6 +131,7 @@ def _wait_for_readiness(
|
|
|
124
131
|
is_flag=True,
|
|
125
132
|
help="Auto-start Docker backends (manual Docker is the default).",
|
|
126
133
|
)
|
|
134
|
+
@click.option("--url", is_flag=True, help="Enable public tunnel URL.")
|
|
127
135
|
@click.option("--no-tunnel", is_flag=True, help="Skip public tunnel and run local proxy only.")
|
|
128
136
|
@click.option(
|
|
129
137
|
"--interactive-backend/--no-interactive-backend",
|
|
@@ -144,15 +152,18 @@ def cli(
|
|
|
144
152
|
backend_port_override: int | None,
|
|
145
153
|
proxy_port: int,
|
|
146
154
|
auto_start_docker: bool,
|
|
155
|
+
url: bool,
|
|
147
156
|
no_tunnel: bool,
|
|
148
157
|
interactive_backend: bool,
|
|
149
158
|
lan_enabled: bool,
|
|
150
159
|
debug: bool,
|
|
151
160
|
) -> None:
|
|
161
|
+
|
|
152
162
|
started = time.perf_counter()
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
163
|
+
banner = "\n" + ("═" * 36) + f"\n⚡ Dev Linker v{__version__} ⚡\n" + ("═" * 36)
|
|
164
|
+
click.secho(banner, fg="green", bold=True)
|
|
165
|
+
click.secho("[INFO] Mode: Auto (FastAPI async proxy + Docker detection)", fg="blue")
|
|
166
|
+
click.secho("[INFO] Booting local services...", fg="blue")
|
|
156
167
|
|
|
157
168
|
start_servers(auto_start_docker=auto_start_docker)
|
|
158
169
|
|
|
@@ -165,7 +176,7 @@ def cli(
|
|
|
165
176
|
if backend_port is None:
|
|
166
177
|
raise SystemExit(1)
|
|
167
178
|
|
|
168
|
-
|
|
179
|
+
click.secho("[INFO] Detecting frontend/backend ports...", fg="blue")
|
|
169
180
|
frontend_port, backend_port = detect_ports(frontend=frontend, backend=backend_port)
|
|
170
181
|
|
|
171
182
|
if frontend_port is None:
|
|
@@ -190,10 +201,10 @@ def cli(
|
|
|
190
201
|
|
|
191
202
|
proxy_port = _select_proxy_port(proxy_port)
|
|
192
203
|
|
|
193
|
-
|
|
194
|
-
|
|
204
|
+
click.secho(f"[OK] Frontend → {frontend_port}", fg="green")
|
|
205
|
+
click.secho(f"[OK] Backend → {backend_port}\n", fg="green")
|
|
195
206
|
|
|
196
|
-
|
|
207
|
+
click.secho(f"[INFO] Starting proxy on :{proxy_port}...", fg="blue")
|
|
197
208
|
start_proxy(frontend_port, backend_port, proxy_port=proxy_port)
|
|
198
209
|
|
|
199
210
|
# Allow proxy thread to bind before opening tunnel.
|
|
@@ -204,32 +215,41 @@ def cli(
|
|
|
204
215
|
local_ip = _get_local_ip()
|
|
205
216
|
if local_ip:
|
|
206
217
|
wlan_url = f"http://{local_ip}:{proxy_port}"
|
|
207
|
-
|
|
208
|
-
|
|
218
|
+
click.secho(f"[OK] WLAN URL: {wlan_url}", fg="green")
|
|
219
|
+
click.secho("[INFO] Share WLAN link with teammates on same WiFi/LAN.", fg="blue")
|
|
209
220
|
else:
|
|
210
|
-
|
|
211
|
-
|
|
221
|
+
click.secho("[WARN] WLAN URL unavailable (no active LAN interface detected).", fg="yellow")
|
|
222
|
+
click.secho("[INFO] If LAN sharing fails, allow proxy port in firewall and use same network.", fg="yellow")
|
|
223
|
+
|
|
224
|
+
click.secho(f"\n[OK] Proxy ready at http://localhost:{proxy_port}\n", fg="green", bold=True)
|
|
212
225
|
|
|
213
|
-
print(f"\n[OK] Proxy ready at http://localhost:{proxy_port}\n")
|
|
214
226
|
warning_free_url: str | None = None
|
|
227
|
+
enable_tunnel = False
|
|
228
|
+
if url:
|
|
229
|
+
enable_tunnel = True
|
|
215
230
|
if no_tunnel:
|
|
216
|
-
|
|
217
|
-
|
|
231
|
+
enable_tunnel = False
|
|
232
|
+
|
|
233
|
+
if enable_tunnel:
|
|
218
234
|
try:
|
|
219
|
-
|
|
235
|
+
click.secho("\n🌍 Enabling public tunnel...", fg="green", bold=True)
|
|
220
236
|
provider, public_url = start_tunnel(proxy_port)
|
|
221
237
|
warning_free_url = _with_ngrok_skip_warning(public_url)
|
|
222
238
|
provider_label = "Cloudflare" if provider == "cloudflare" else "ngrok"
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
239
|
+
click.secho(f"[OK] Tunnel provider: {provider_label}", fg="blue")
|
|
240
|
+
click.secho(f"[OK] Public URL:", fg="blue")
|
|
241
|
+
click.secho(f" {warning_free_url}\n", fg="cyan", bold=True)
|
|
242
|
+
click.secho("Tip: Press Ctrl+Click to open link", fg="magenta")
|
|
243
|
+
click.secho("[INFO] Share this link with collaborators.", fg="magenta")
|
|
228
244
|
except RuntimeError as exc:
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
245
|
+
click.secho(f"[WARN] Tunnel failed: {exc}", fg="red")
|
|
246
|
+
click.secho("[INFO] Next step: install cloudflared or configure ngrok auth.", fg="yellow")
|
|
247
|
+
click.secho("[INFO] Tip: run 'ngrok config add-authtoken <token>' for ngrok fallback.", fg="yellow")
|
|
248
|
+
click.secho(f"[OK] Continuing with local proxy at http://localhost:{proxy_port}", fg="green")
|
|
249
|
+
else:
|
|
250
|
+
click.secho("\n⚡ Skipping public tunnel (use --url to enable)", fg="yellow", bold=True)
|
|
251
|
+
click.secho("\n💡 Need to share outside network?", fg="magenta")
|
|
252
|
+
click.secho("👉 Run: devlinker --url", fg="magenta", bold=True)
|
|
233
253
|
|
|
234
254
|
_print_summary(
|
|
235
255
|
frontend_port,
|
|
@@ -244,8 +264,19 @@ def cli(
|
|
|
244
264
|
while True:
|
|
245
265
|
time.sleep(1)
|
|
246
266
|
except KeyboardInterrupt:
|
|
247
|
-
|
|
267
|
+
click.secho("\n[INFO] Dev Linker stopped.", fg="yellow")
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@click.group()
|
|
271
|
+
def main():
|
|
272
|
+
pass
|
|
273
|
+
|
|
248
274
|
|
|
275
|
+
main.add_command(cli)
|
|
276
|
+
main.add_command(doctor)
|
|
277
|
+
main.add_command(fix)
|
|
278
|
+
main.add_command(share)
|
|
279
|
+
main.add_command(unshare)
|
|
249
280
|
|
|
250
281
|
if __name__ == "__main__":
|
|
251
|
-
|
|
282
|
+
main()
|
|
@@ -15,6 +15,28 @@ from websockets.exceptions import ConnectionClosed
|
|
|
15
15
|
|
|
16
16
|
app = FastAPI()
|
|
17
17
|
|
|
18
|
+
# --- RequestInspector: Real-time request analyzer ---
|
|
19
|
+
from devlinker.detection_state import state
|
|
20
|
+
class RequestInspector:
|
|
21
|
+
def analyze(self, path, status, target):
|
|
22
|
+
warnings = []
|
|
23
|
+
# 1. Missing /api prefix
|
|
24
|
+
if not path.startswith("/api") and target == "backend":
|
|
25
|
+
issue = "Possible missing '/api' prefix"
|
|
26
|
+
state.add(issue, level="MEDIUM", category="routing")
|
|
27
|
+
warnings.append(issue)
|
|
28
|
+
# 2. 404 detection
|
|
29
|
+
if status == 404:
|
|
30
|
+
issue = "Route not found → check backend route"
|
|
31
|
+
state.add(issue, level="HIGH", category="routing")
|
|
32
|
+
warnings.append(issue)
|
|
33
|
+
# 3. Upstream failure
|
|
34
|
+
if status == 502:
|
|
35
|
+
issue = "Backend unreachable"
|
|
36
|
+
state.add(issue, level="HIGH", category="network")
|
|
37
|
+
warnings.append(issue)
|
|
38
|
+
return warnings
|
|
39
|
+
|
|
18
40
|
FRONTEND: Optional[int] = None
|
|
19
41
|
BACKEND: Optional[int] = None
|
|
20
42
|
HTTP_CLIENT: Optional[httpx.AsyncClient] = None
|
|
@@ -100,13 +122,28 @@ def _build_target_ws_url(port: int, path: str, query: str) -> str:
|
|
|
100
122
|
|
|
101
123
|
|
|
102
124
|
async def _forward_http(request: Request) -> Response:
|
|
125
|
+
from devlinker.logger import print_warning, print_fix
|
|
126
|
+
from devlinker.detector_ai import DevLinkerAI
|
|
127
|
+
|
|
128
|
+
inspector = RequestInspector()
|
|
129
|
+
ai = DevLinkerAI()
|
|
130
|
+
|
|
103
131
|
target_port = _target_port(request.url.path)
|
|
104
132
|
if target_port is None:
|
|
105
133
|
if request.url.path.startswith("/api"):
|
|
106
|
-
|
|
107
|
-
|
|
134
|
+
status = 503
|
|
135
|
+
warnings = inspector.analyze(request.url.path, status, "backend")
|
|
136
|
+
for w in warnings:
|
|
137
|
+
print_warning(w)
|
|
138
|
+
return PlainTextResponse("Backend is not configured.", status_code=status)
|
|
139
|
+
status = 503
|
|
140
|
+
warnings = inspector.analyze(request.url.path, status, "frontend")
|
|
141
|
+
for w in warnings:
|
|
142
|
+
print_warning(w)
|
|
143
|
+
return PlainTextResponse("Frontend is not configured.", status_code=status)
|
|
108
144
|
|
|
109
145
|
if HTTP_CLIENT is None:
|
|
146
|
+
print_warning("Proxy HTTP client is not ready.")
|
|
110
147
|
return PlainTextResponse("Proxy HTTP client is not ready.", status_code=503)
|
|
111
148
|
|
|
112
149
|
payload = await request.body()
|
|
@@ -121,7 +158,22 @@ async def _forward_http(request: Request) -> Response:
|
|
|
121
158
|
headers=_filter_request_headers(dict(request.headers)),
|
|
122
159
|
)
|
|
123
160
|
except httpx.RequestError as exc:
|
|
124
|
-
|
|
161
|
+
status = 502
|
|
162
|
+
warnings = inspector.analyze(request.url.path, status, "backend")
|
|
163
|
+
for w in warnings:
|
|
164
|
+
print_warning(w)
|
|
165
|
+
ai_suggestions = ai.analyze_failure(str(exc))
|
|
166
|
+
for s in ai_suggestions:
|
|
167
|
+
print_fix(s)
|
|
168
|
+
return PlainTextResponse(f"Upstream unavailable: {exc}", status_code=status)
|
|
169
|
+
|
|
170
|
+
# Analyze response for warnings and fixes
|
|
171
|
+
warnings = inspector.analyze(request.url.path, upstream.status_code, "backend")
|
|
172
|
+
for w in warnings:
|
|
173
|
+
print_warning(w)
|
|
174
|
+
ai_suggestions = ai.analyze_failure(str(upstream.text))
|
|
175
|
+
for s in ai_suggestions:
|
|
176
|
+
print_fix(s)
|
|
125
177
|
|
|
126
178
|
return Response(
|
|
127
179
|
content=upstream.content,
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from devlinker.tunnel import start_tunnel
|
|
3
|
+
|
|
4
|
+
# Global tunnel state
|
|
5
|
+
_tunnel_info = {
|
|
6
|
+
"provider": None,
|
|
7
|
+
"public_url": None,
|
|
8
|
+
"active": False,
|
|
9
|
+
"proxy_port": None,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@click.command()
|
|
13
|
+
def share():
|
|
14
|
+
"""Enable public tunnel at runtime (no restart)."""
|
|
15
|
+
from devlinker.main import _select_proxy_port
|
|
16
|
+
import sys
|
|
17
|
+
proxy_port = 8000
|
|
18
|
+
banner = "\n" + ("═" * 36) + "\n🌍 DevLinker Share Mode\n" + ("═" * 36)
|
|
19
|
+
if _tunnel_info["active"]:
|
|
20
|
+
click.secho(f"{banner}\n\n🔗 Tunnel already active:", fg="yellow", bold=True)
|
|
21
|
+
click.secho(f" {_tunnel_info['public_url']}\n", fg="cyan", bold=True)
|
|
22
|
+
return
|
|
23
|
+
try:
|
|
24
|
+
click.secho(f"{banner}\n\n🌍 Enabling public tunnel...", fg="green", bold=True)
|
|
25
|
+
provider, public_url = start_tunnel(proxy_port)
|
|
26
|
+
_tunnel_info["provider"] = provider
|
|
27
|
+
_tunnel_info["public_url"] = public_url
|
|
28
|
+
_tunnel_info["active"] = True
|
|
29
|
+
_tunnel_info["proxy_port"] = proxy_port
|
|
30
|
+
click.secho(f"\n[OK] Tunnel provider: {provider}", fg="blue")
|
|
31
|
+
click.secho(f"[OK] Public URL:", fg="blue")
|
|
32
|
+
click.secho(f" {public_url}\n", fg="cyan", bold=True)
|
|
33
|
+
click.secho("Tip: Press Ctrl+Click to open link", fg="magenta")
|
|
34
|
+
click.secho("[INFO] Share this link with collaborators.\n", fg="magenta")
|
|
35
|
+
except Exception as exc:
|
|
36
|
+
click.secho(f"[WARN] Tunnel failed: {exc}", fg="red")
|
|
37
|
+
click.secho("[INFO] Next step: install cloudflared or configure ngrok auth.", fg="yellow")
|
|
38
|
+
sys.exit(1)
|
|
39
|
+
|
|
40
|
+
@click.command()
|
|
41
|
+
def unshare():
|
|
42
|
+
"""Disable public tunnel at runtime (no restart)."""
|
|
43
|
+
banner = "\n" + ("═" * 36) + "\n🛑 DevLinker Unshare Mode\n" + ("═" * 36)
|
|
44
|
+
if not _tunnel_info["active"]:
|
|
45
|
+
click.secho(f"{banner}\n\nNo tunnel is currently active.\n", fg="yellow", bold=True)
|
|
46
|
+
return
|
|
47
|
+
# In a real implementation, stop the tunnel process here
|
|
48
|
+
click.secho(f"{banner}\n\n🛑 Disabling tunnel:", fg="red", bold=True)
|
|
49
|
+
click.secho(f" {_tunnel_info['public_url']}\n", fg="cyan", bold=True)
|
|
50
|
+
_tunnel_info["provider"] = None
|
|
51
|
+
_tunnel_info["public_url"] = None
|
|
52
|
+
_tunnel_info["active"] = False
|
|
53
|
+
_tunnel_info["proxy_port"] = None
|
|
54
|
+
click.secho("[OK] Tunnel disabled.\n", fg="green", bold=True)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devlinker
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: AI-powered linking and automation tool
|
|
5
5
|
Author-email: Mani <mani1028@users.noreply.github.com>
|
|
6
6
|
Requires-Python: >=3.7
|
|
@@ -18,20 +18,36 @@ Requires-Dist: websockets
|
|
|
18
18
|
|
|
19
19
|
Dev Linker runs frontend and backend dev servers, proxies both through a single local port (8000), and creates a single public URL via Cloudflare or ngrok.
|
|
20
20
|
|
|
21
|
+
|
|
21
22
|
## Features
|
|
22
23
|
|
|
23
|
-
-
|
|
24
|
-
- Auto
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
|
|
24
|
+
- 🚀 **Unified Dev Proxy:** Combines frontend (Vite/React) and backend (FastAPI/Flask/Node/Docker) into a single local and public URL.
|
|
25
|
+
- 🔍 **Auto Detection:** Detects frontend/backend ports, runtime, Docker containers, and Vite servers automatically.
|
|
26
|
+
- 🧠 **Smart Detection & Doctor:** Real-time request analysis, backend intelligence, log analyzer, and `devlinker doctor` for instant diagnostics.
|
|
27
|
+
- 🛡️ **Auto-Fix Engine:** `devlinker fix` applies safe fixes (like VITE_API_URL) and suggests code changes.
|
|
28
|
+
- 🌍 **Public Sharing:** Share your local dev environment instantly with `--url` (startup) or `devlinker share` (runtime, no restart).
|
|
29
|
+
- 🔄 **Dynamic Tunnel Control:** `devlinker unshare` disables public tunnel at runtime.
|
|
30
|
+
- 📡 **WLAN Sharing:** Prints LAN URL for same-network device access.
|
|
31
|
+
- 🧑💻 **Interactive CLI:** Modern, colorized, emoji-rich terminal UX for all commands.
|
|
32
|
+
- 🧩 **Zero Config:** Works out-of-the-box for most FastAPI, Flask, Vite, and Docker projects.
|
|
33
|
+
- 🧪 **Runtime Smoke Test:** Built-in test for end-to-end proxy validation.
|
|
34
|
+
- 🛠️ **Extensible:** Modular architecture for future SaaS, dashboard, and team features.
|
|
35
|
+
|
|
36
|
+
## CLI Commands & Options
|
|
37
|
+
|
|
38
|
+
- `devlinker` — Start proxy (local only, fast)
|
|
39
|
+
- `devlinker --url` — Start with public tunnel (Cloudflare/ngrok)
|
|
40
|
+
- `devlinker share` — Enable public tunnel at runtime (no restart)
|
|
41
|
+
- `devlinker unshare` — Disable public tunnel at runtime
|
|
42
|
+
- `devlinker doctor` — Diagnose issues, see categorized problems and fixes
|
|
43
|
+
- `devlinker fix` — Auto-fix common issues (env, API paths, config)
|
|
44
|
+
- `devlinker --frontend 5173 --backend 5000` — Override detected ports
|
|
45
|
+
- `devlinker --docker` — Auto-start Docker backend
|
|
46
|
+
- `devlinker --no-tunnel` — Force local-only mode
|
|
47
|
+
- `devlinker --no-lan` — Hide WLAN sharing URL
|
|
48
|
+
- `devlinker --interactive-backend` — Prompt to choose backend if multiple found
|
|
49
|
+
- `devlinker --proxy-port 18000` — Use custom proxy port
|
|
50
|
+
- `devlinker --version` — Show version
|
|
35
51
|
|
|
36
52
|
## Project Structure
|
|
37
53
|
|
|
@@ -123,17 +139,71 @@ Enable Docker auto-start explicitly:
|
|
|
123
139
|
devlinker --docker
|
|
124
140
|
```
|
|
125
141
|
|
|
126
|
-
|
|
142
|
+
|
|
143
|
+
## Tunnel and Sharing Modes
|
|
144
|
+
|
|
145
|
+
By default, DevLinker starts **fast local proxy only** (no tunnel). To enable a public tunnel, use the `--url` flag:
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
devlinker --url
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
In your terminal output, you'll see:
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
URL, run:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
devlinker --url
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
This will start the proxy and open a public tunnel (Cloudflare or ngrok). The output will show:
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
🌍 Enabling public tunnel...
|
|
165
|
+
[OK] Tunnel provider: Cloudflare
|
|
166
|
+
[OK] Public URL:
|
|
167
|
+
https://xxxx.trycloudflare.com
|
|
168
|
+
Tip: Press Ctrl+Click to open link
|
|
169
|
+
[INFO] Share this link with collaborators.
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
To force tunnel off (even if --url is passed):
|
|
127
173
|
|
|
128
174
|
```bash
|
|
129
175
|
devlinker --no-tunnel
|
|
130
176
|
```
|
|
131
177
|
|
|
178
|
+
When running without `--url`, you’ll see:
|
|
179
|
+
|
|
180
|
+
```text
|
|
181
|
+
⚡ Skipping public tunnel (use --url to enable)
|
|
182
|
+
|
|
183
|
+
💡 Need to share outside network?
|
|
184
|
+
👉 Run: devlinker --url
|
|
185
|
+
```
|
|
186
|
+
|
|
132
187
|
Disable WLAN URL output:
|
|
133
188
|
|
|
134
189
|
```bash
|
|
135
190
|
devlinker --no-lan
|
|
136
191
|
```
|
|
192
|
+
## Smart Detection & Auto-Fix System
|
|
193
|
+
|
|
194
|
+
DevLinker now includes an AI-powered detection and auto-fix engine:
|
|
195
|
+
|
|
196
|
+
- **Request Inspector:** Real-time analysis of proxy traffic for common mistakes (missing `/api` prefix, 404s, CORS risks, upstream failures)
|
|
197
|
+
- **Backend Intelligence:** Probes backend endpoints and type at startup for smarter routing and hints
|
|
198
|
+
- **Log Analyzer:** Converts error messages (CORS, 404, connection refused) into human-readable explanations and actionable fixes
|
|
199
|
+
- **Smart Warning Engine:** Prints clean CLI warnings and suggestions, e.g.:
|
|
200
|
+
|
|
201
|
+
```text
|
|
202
|
+
⚠️ Detected direct backend call (localhost:5000)
|
|
203
|
+
👉 Use /api/* instead of direct URL
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
All detection and fixes are modular, async-compatible, and production-ready. See `devlinker/proxy.py`, `devlinker/detector_ai.py`, and `devlinker/logger.py` for implementation.
|
|
137
207
|
|
|
138
208
|
Interactive backend selection (when local and Docker are both detected):
|
|
139
209
|
|
|
@@ -2,10 +2,17 @@ README.md
|
|
|
2
2
|
pyproject.toml
|
|
3
3
|
setup.py
|
|
4
4
|
devlinker/__init__.py
|
|
5
|
+
devlinker/detection_state.py
|
|
5
6
|
devlinker/detector.py
|
|
7
|
+
devlinker/detector_ai.py
|
|
8
|
+
devlinker/doctor.py
|
|
9
|
+
devlinker/fix.py
|
|
10
|
+
devlinker/fixer.py
|
|
11
|
+
devlinker/logger.py
|
|
6
12
|
devlinker/main.py
|
|
7
13
|
devlinker/proxy.py
|
|
8
14
|
devlinker/runner.py
|
|
15
|
+
devlinker/share.py
|
|
9
16
|
devlinker/tunnel.py
|
|
10
17
|
devlinker.egg-info/PKG-INFO
|
|
11
18
|
devlinker.egg-info/SOURCES.txt
|
|
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
|